@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +349 -97
- package/README.md +9 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +23 -5
- package/dist/cli/program.js +21 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/cli.js +2 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +455 -28
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +2370 -64
- package/dist/executors/pi-executor.js +11 -2
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +225 -33
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +1258 -224
- package/dist/executors/shell-write-guard.js +14 -0
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-failure-category.js +150 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +39 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +70 -15
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +50 -5
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +18 -1
- package/dist/task/contract/schema.js +4 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +199 -18
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +298 -1
- package/dist/task/source-prepare/semantic-intake.js +25 -5
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/task/source-references.js +22 -1
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/chat-event-store.js +75 -5
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +395 -59
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +227 -7
- package/dist/worker/console/operator-actions.js +388 -0
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +355 -3
- package/dist/worker/console/security.js +67 -13
- package/dist/worker/console/server.js +609 -164
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-CXj_GnSb.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BZp6JAp7.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
- package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
- package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
- package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-BsjbDoXi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-0B7NnWKk.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-B32Ark0D.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
- package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +183 -48
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +344 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +160 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +224 -3
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +90 -4
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +208 -0
- package/dist/worker/observe/static/format.js +60 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -96
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +184 -1
- package/dist/worker/observe/static/styles.css +395 -0
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +14 -5
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +2 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +7 -424
- package/dist/workflows/dag/frontend-review-context.js +288 -14
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +52 -17
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +272 -0
- package/dist/workflows/dag/frontend-writer-status.js +244 -0
- package/dist/workflows/dag/init-hybrid.js +1072 -660
- package/dist/workflows/dag/interrupt-request.js +10 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +552 -58
- package/dist/workflows/dag/prompt.js +44 -19
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +124 -1
- package/dist/workflows/dag/rerun-plan.js +144 -5
- package/dist/workflows/dag/rerun-run.js +81 -6
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +318 -8
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +338 -31
- package/dist/workflows/dag/upstream-artifacts.js +0 -4
- package/dist/workflows/dag/validate.js +92 -32
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +9 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +63 -1
- package/docs/templates/backend-test-dag.json +265 -237
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +7 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +3 -4
- package/skills/frontend-implementation/references/design-spec.md +15 -10
- package/skills/frontend-implementation/references/node-contracts.md +23 -13
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/command-reference.md +12 -4
- package/skills/loop-agent/references/hybrid-dag.md +8 -1
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
* Mutation routes go through the Console mutation gate (boot token + Origin).
|
|
13
13
|
* Read routes (GET capabilities / sessions) are open to loopback.
|
|
14
14
|
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
15
16
|
import { parseAssistantContent } from "./assistant-content.js";
|
|
16
17
|
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, } from "./tools.js";
|
|
17
18
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
18
19
|
import { cleanChatTitle, acceptedOperationIdFromResult } from "./session-store.js";
|
|
19
20
|
import { hashChatTurnPayload, projectChatToolEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
|
|
20
|
-
import { projectDeferredTurnSummary, } from "./deferred-turn.js";
|
|
21
|
+
import { projectDeferredTurnSummary, projectDeferredTurnSummaries, } from "./deferred-turn.js";
|
|
21
22
|
import { THINKING_LEVELS, RuntimeSnapshotUnavailableError, } from "./pi-runtime.js";
|
|
22
23
|
import { ResourcePreferencesError, setSkillAutoInvocationPreference, } from "./resource-preferences-store.js";
|
|
23
24
|
import { ComposerDraftStore } from "./composer-draft-store.js";
|
|
@@ -33,9 +34,11 @@ import { dispatchOperatorAction } from "../operator-actions.js";
|
|
|
33
34
|
import { makeHumanGateCard, } from "./human-gate-card.js";
|
|
34
35
|
import { ContractApplyReceiptStore } from "./contract-apply-receipt-store.js";
|
|
35
36
|
import { projectTaskContext } from "./context-panel.js";
|
|
37
|
+
import { deriveChatSessionTelemetry } from "./session-stats.js";
|
|
36
38
|
import { contractApplyPayloadHash, issueHumanGateToken, verifyHumanGateToken, } from "../human-gate-token.js";
|
|
37
39
|
import { sendJson } from "../routes.js";
|
|
38
40
|
import { openSseResponse, writeSseEvent } from "../operation-sse.js";
|
|
41
|
+
import { buildTodoCompactionInstructions, projectCurrentTodos } from "./todos.js";
|
|
39
42
|
function turnSummary(turn) {
|
|
40
43
|
return {
|
|
41
44
|
turnId: turn.turnId,
|
|
@@ -52,7 +55,6 @@ function turnSummary(turn) {
|
|
|
52
55
|
function isTerminalTurnState(state) {
|
|
53
56
|
return state === "settled" || state === "aborted" || state === "failed";
|
|
54
57
|
}
|
|
55
|
-
const DEFAULT_ABORT_WAIT_MS = 2_500;
|
|
56
58
|
/** Parse Chat's `<epoch>:<seq>` SSE cursor. Legacy `<sessionId>:<seq>` has
|
|
57
59
|
* the same wire shape; its prefix intentionally fails the current process
|
|
58
60
|
* epoch comparison and triggers safe snapshot recovery (M5.1). */
|
|
@@ -87,11 +89,38 @@ function turnEventToStorePartial(event) {
|
|
|
87
89
|
case "message_update":
|
|
88
90
|
case "message_end": {
|
|
89
91
|
const content = parseAssistantContent(event.content);
|
|
92
|
+
const timingSource = event.type === "message_end" &&
|
|
93
|
+
event.timing &&
|
|
94
|
+
typeof event.timing === "object"
|
|
95
|
+
? event.timing
|
|
96
|
+
: undefined;
|
|
97
|
+
const timing = timingSource &&
|
|
98
|
+
typeof timingSource.llmMs === "number" &&
|
|
99
|
+
Number.isFinite(timingSource.llmMs)
|
|
100
|
+
? {
|
|
101
|
+
llmMs: Math.max(0, timingSource.llmMs),
|
|
102
|
+
...(typeof timingSource.ttftMs === "number" &&
|
|
103
|
+
Number.isFinite(timingSource.ttftMs)
|
|
104
|
+
? { ttftMs: Math.max(0, timingSource.ttftMs) }
|
|
105
|
+
: {}),
|
|
106
|
+
...(typeof timingSource.decodeMs === "number" &&
|
|
107
|
+
Number.isFinite(timingSource.decodeMs)
|
|
108
|
+
? { decodeMs: Math.max(0, timingSource.decodeMs) }
|
|
109
|
+
: {}),
|
|
110
|
+
...(typeof timingSource.outputTokens === "number" &&
|
|
111
|
+
Number.isFinite(timingSource.outputTokens)
|
|
112
|
+
? {
|
|
113
|
+
outputTokens: Math.max(0, timingSource.outputTokens),
|
|
114
|
+
}
|
|
115
|
+
: {}),
|
|
116
|
+
}
|
|
117
|
+
: undefined;
|
|
90
118
|
return {
|
|
91
119
|
kind: event.type,
|
|
92
120
|
data: {
|
|
93
121
|
text: String(event.text ?? ""),
|
|
94
122
|
...(content ? { content } : {}),
|
|
123
|
+
...(timing ? { timing } : {}),
|
|
95
124
|
},
|
|
96
125
|
};
|
|
97
126
|
}
|
|
@@ -123,6 +152,9 @@ function turnEventToStorePartial(event) {
|
|
|
123
152
|
}),
|
|
124
153
|
};
|
|
125
154
|
}
|
|
155
|
+
case "todo_updated":
|
|
156
|
+
case "todo_cleared":
|
|
157
|
+
return { kind: event.type, data: { todos: Array.isArray(event.todos) ? event.todos : [], ...(event.toolCallId ? { toolCallId: String(event.toolCallId) } : {}) } };
|
|
126
158
|
case "agent_end":
|
|
127
159
|
return {
|
|
128
160
|
kind: "agent_end",
|
|
@@ -130,8 +162,53 @@ function turnEventToStorePartial(event) {
|
|
|
130
162
|
};
|
|
131
163
|
case "agent_settled":
|
|
132
164
|
return { kind: "agent_settled", data: {} };
|
|
133
|
-
case "
|
|
134
|
-
|
|
165
|
+
case "compaction_start":
|
|
166
|
+
case "compaction_end": {
|
|
167
|
+
const reason = event.reason === "threshold" || event.reason === "overflow"
|
|
168
|
+
? event.reason
|
|
169
|
+
: "manual";
|
|
170
|
+
const status = event.type === "compaction_start"
|
|
171
|
+
? "running"
|
|
172
|
+
: event.status === "aborted" || event.status === "failed"
|
|
173
|
+
? event.status
|
|
174
|
+
: "completed";
|
|
175
|
+
const data = {
|
|
176
|
+
schemaVersion: 1,
|
|
177
|
+
compactionId: String(event.compactionId ?? ""),
|
|
178
|
+
reason,
|
|
179
|
+
status,
|
|
180
|
+
...(typeof event.summary === "string"
|
|
181
|
+
? { summary: event.summary }
|
|
182
|
+
: {}),
|
|
183
|
+
...(typeof event.firstKeptEntryId === "string"
|
|
184
|
+
? { firstKeptEntryId: event.firstKeptEntryId }
|
|
185
|
+
: {}),
|
|
186
|
+
...(typeof event.tokensBefore === "number"
|
|
187
|
+
? { tokensBefore: event.tokensBefore }
|
|
188
|
+
: {}),
|
|
189
|
+
...(typeof event.estimatedTokensAfter === "number"
|
|
190
|
+
? { estimatedTokensAfter: event.estimatedTokensAfter }
|
|
191
|
+
: {}),
|
|
192
|
+
willRetry: Boolean(event.willRetry),
|
|
193
|
+
...(typeof event.errorMessage === "string"
|
|
194
|
+
? { errorMessage: event.errorMessage }
|
|
195
|
+
: {}),
|
|
196
|
+
};
|
|
197
|
+
return { kind: event.type, data };
|
|
198
|
+
}
|
|
199
|
+
case "error": {
|
|
200
|
+
const content = parseAssistantContent(event.content);
|
|
201
|
+
return {
|
|
202
|
+
kind: "error",
|
|
203
|
+
data: {
|
|
204
|
+
message: String(event.message ?? ""),
|
|
205
|
+
...(content ? { content } : {}),
|
|
206
|
+
...(event.providerError && typeof event.providerError === "object"
|
|
207
|
+
? { providerError: event.providerError }
|
|
208
|
+
: {}),
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
135
212
|
default:
|
|
136
213
|
return undefined;
|
|
137
214
|
}
|
|
@@ -197,6 +274,13 @@ function ensureAgentEndEvent(deps, sessionId, turnId) {
|
|
|
197
274
|
}
|
|
198
275
|
}
|
|
199
276
|
async function readJsonBody(req, maxBytes = 2 * 1024 * 1024) {
|
|
277
|
+
// AC-R5-003: the multi-workspace dispatcher (server.ts readBodyOnce)
|
|
278
|
+
// pre-reads and JSON-parses the body for workspaceId resolution, stashing
|
|
279
|
+
// it on req.__preReadBody. Consume it first so the body is parsed exactly
|
|
280
|
+
// once per request; re-reading the consumed stream would yield {}.
|
|
281
|
+
const preRead = req.__preReadBody;
|
|
282
|
+
if (preRead !== undefined)
|
|
283
|
+
return preRead;
|
|
200
284
|
const chunks = [];
|
|
201
285
|
let total = 0;
|
|
202
286
|
for await (const chunk of req) {
|
|
@@ -270,6 +354,7 @@ export function parseChatImages(value) {
|
|
|
270
354
|
}
|
|
271
355
|
function gateMutation(req, deps) {
|
|
272
356
|
const failure = evaluateMutationGate(req, {
|
|
357
|
+
cookieName: deps.bootToken.cookieName,
|
|
273
358
|
bootToken: deps.bootToken.value,
|
|
274
359
|
confirmationToken: deps.bootToken.confirmationToken,
|
|
275
360
|
consoleOrigin: deps.getConsoleOrigin(),
|
|
@@ -343,7 +428,10 @@ export async function handleChatCapabilities(_req, res, deps) {
|
|
|
343
428
|
hasBash: true,
|
|
344
429
|
});
|
|
345
430
|
}
|
|
346
|
-
export async function handleCreateChatSession(req, res, deps
|
|
431
|
+
export async function handleCreateChatSession(req, res, deps,
|
|
432
|
+
/** Explicit owning workspaceId from the multi-workspace dispatcher
|
|
433
|
+
* (AC-R7-002). The body still parses exactly once via req.__preReadBody. */
|
|
434
|
+
workspaceId) {
|
|
347
435
|
const gate = gateMutation(req, deps);
|
|
348
436
|
if (!gate.ok) {
|
|
349
437
|
sendJson(res, gate.status, {
|
|
@@ -421,6 +509,10 @@ export async function handleCreateChatSession(req, res, deps) {
|
|
|
421
509
|
sessionFile: handle.sessionFile,
|
|
422
510
|
modelProvider: handle.model?.provider,
|
|
423
511
|
modelId: handle.model?.modelId,
|
|
512
|
+
// AC-R7-002: explicit parameter from the dispatcher — the runtime
|
|
513
|
+
// store.create wrapper is gone; body workspaceId (if any) only acts as
|
|
514
|
+
// a fallback for direct (non-dispatcher) handler tests.
|
|
515
|
+
workspaceId: workspaceId ?? (typeof body.workspaceId === "string" && body.workspaceId ? body.workspaceId : undefined),
|
|
424
516
|
});
|
|
425
517
|
if (snapshot)
|
|
426
518
|
deps.events.append(handle.sessionId, `${handle.sessionId}:snapshot`, {
|
|
@@ -645,8 +737,10 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
645
737
|
await deps.operationLinker.recoverSession(sessionId);
|
|
646
738
|
const composerDraft = await new ComposerDraftStore(deps.appData).get(sessionId);
|
|
647
739
|
const snapshot = deps.events.snapshot(sessionId);
|
|
740
|
+
const telemetry = deriveChatSessionTelemetry(snapshot);
|
|
648
741
|
const lastEvent = snapshot.at(-1);
|
|
649
742
|
const activeTurn = deps.events.getActiveTurn(sessionId);
|
|
743
|
+
const pendingQuestion = await deps.questionRegistry?.get(sessionId);
|
|
650
744
|
sendJson(res, 200, {
|
|
651
745
|
ok: true,
|
|
652
746
|
eventEpoch: deps.events.epoch(sessionId),
|
|
@@ -659,8 +753,22 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
659
753
|
? "stopping"
|
|
660
754
|
: "running"
|
|
661
755
|
: "idle",
|
|
756
|
+
pendingQuestion: pendingQuestion ?? null,
|
|
757
|
+
todos: projectCurrentTodos(snapshot) ?? [],
|
|
662
758
|
},
|
|
663
759
|
hasBash: true,
|
|
760
|
+
sessionStats: telemetry.stats,
|
|
761
|
+
usage: telemetry.usage,
|
|
762
|
+
compactions: snapshot
|
|
763
|
+
.filter((event) => event.kind === "compact" ||
|
|
764
|
+
event.kind === "compaction_start" ||
|
|
765
|
+
event.kind === "compaction_end")
|
|
766
|
+
.map((event) => ({
|
|
767
|
+
eventId: event.eventId,
|
|
768
|
+
kind: event.kind,
|
|
769
|
+
at: event.at,
|
|
770
|
+
data: event.data,
|
|
771
|
+
})),
|
|
664
772
|
lastEventId: lastEvent?.eventId ?? null,
|
|
665
773
|
lastEventSeq: lastEvent?.seq ?? 0,
|
|
666
774
|
});
|
|
@@ -727,13 +835,15 @@ export async function handlePutDeferredTurn(req, res, deps, sessionId) {
|
|
|
727
835
|
return;
|
|
728
836
|
}
|
|
729
837
|
const active = deps.events.getActiveTurn(sessionId);
|
|
730
|
-
|
|
838
|
+
// Queueing binds to the active Turn (running OR stopping); the durable
|
|
839
|
+
// follow-up auto-dispatches when that Turn reaches a terminal state.
|
|
840
|
+
if (!active || active.turnId !== blockedByTurnId) {
|
|
731
841
|
sendJson(res, 409, {
|
|
732
842
|
ok: false,
|
|
733
843
|
accepted: false,
|
|
734
844
|
error: {
|
|
735
|
-
code: "
|
|
736
|
-
message: "
|
|
845
|
+
code: "TURN_NOT_ACTIVE",
|
|
846
|
+
message: "没有匹配的运行中 Turn,无法保存待发送消息",
|
|
737
847
|
},
|
|
738
848
|
activeTurn: active ? turnSummary(active) : null,
|
|
739
849
|
});
|
|
@@ -766,11 +876,12 @@ export async function handlePutDeferredTurn(req, res, deps, sessionId) {
|
|
|
766
876
|
: "待发送消息无效或会话不存在",
|
|
767
877
|
},
|
|
768
878
|
deferredTurn: result.deferredTurn ?? null,
|
|
879
|
+
deferredTurns: projectDeferredTurnSummaries(await deps.store.listDeferredTurns(sessionId)),
|
|
769
880
|
summary: projectDeferredTurnSummary(result.deferredTurn),
|
|
770
881
|
});
|
|
771
882
|
return;
|
|
772
883
|
}
|
|
773
|
-
emitDeferredTurnUpdate(deps, sessionId, blockedByTurnId, result.deferredTurn, result.deferredTurn.revision);
|
|
884
|
+
await emitDeferredTurnUpdate(deps, sessionId, blockedByTurnId, result.deferredTurn, result.deferredTurn.revision);
|
|
774
885
|
sendJson(res, 200, {
|
|
775
886
|
ok: true,
|
|
776
887
|
accepted: true,
|
|
@@ -778,6 +889,7 @@ export async function handlePutDeferredTurn(req, res, deps, sessionId) {
|
|
|
778
889
|
state: result.deferredTurn.state,
|
|
779
890
|
revision: result.deferredTurn.revision,
|
|
780
891
|
deferredTurn: result.deferredTurn,
|
|
892
|
+
deferredTurns: projectDeferredTurnSummaries(await deps.store.listDeferredTurns(sessionId)),
|
|
781
893
|
summary: projectDeferredTurnSummary(result.deferredTurn),
|
|
782
894
|
});
|
|
783
895
|
void dispatchDeferredTurn(deps, sessionId);
|
|
@@ -823,18 +935,90 @@ export async function handleDeleteDeferredTurn(req, res, deps, sessionId) {
|
|
|
823
935
|
: "待发送消息已变化,请刷新后重试",
|
|
824
936
|
},
|
|
825
937
|
deferredTurn: result.deferredTurn ?? null,
|
|
938
|
+
deferredTurns: projectDeferredTurnSummaries(await deps.store.listDeferredTurns(sessionId)),
|
|
826
939
|
});
|
|
827
940
|
return;
|
|
828
941
|
}
|
|
829
942
|
const clearedRevision = result.deferredTurn.revision + 1;
|
|
830
|
-
emitDeferredTurnUpdate(deps, sessionId, result.deferredTurn.blockedByTurnId, undefined, clearedRevision);
|
|
943
|
+
await emitDeferredTurnUpdate(deps, sessionId, result.deferredTurn.blockedByTurnId, undefined, clearedRevision);
|
|
831
944
|
sendJson(res, 200, {
|
|
832
945
|
ok: true,
|
|
833
946
|
cancelled: true,
|
|
834
947
|
deferredTurn: result.deferredTurn,
|
|
948
|
+
deferredTurns: projectDeferredTurnSummaries(await deps.store.listDeferredTurns(sessionId)),
|
|
835
949
|
revision: clearedRevision,
|
|
836
950
|
});
|
|
837
951
|
}
|
|
952
|
+
/** Move one addressed FIFO item into Pi's live steering queue. */
|
|
953
|
+
export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientRequestId) {
|
|
954
|
+
const gate = gateMutation(req, deps);
|
|
955
|
+
if (!gate.ok) {
|
|
956
|
+
sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
let body = {};
|
|
960
|
+
try {
|
|
961
|
+
body = await readJsonBody(req);
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
sendJson(res, 400, { ok: false, error: { code: "INVALID_INPUT", message: error instanceof Error ? error.message : String(error) } });
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const expectedRevision = typeof body.expectedRevision === "number" && Number.isSafeInteger(body.expectedRevision)
|
|
968
|
+
? body.expectedRevision
|
|
969
|
+
: -1;
|
|
970
|
+
const active = deps.events.getActiveTurn(sessionId);
|
|
971
|
+
if (!active) {
|
|
972
|
+
sendJson(res, 409, { ok: false, error: { code: "TURN_NOT_ACTIVE", message: "当前没有可插话的运行中回合" } });
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const claim = await deps.store.claimDeferredTurn(sessionId, expectedRevision, clientRequestId);
|
|
976
|
+
if (!claim.ok) {
|
|
977
|
+
sendJson(res, claim.code === "NOT_FOUND" ? 404 : 409, {
|
|
978
|
+
ok: false,
|
|
979
|
+
error: { code: claim.code, message: "排队消息已变化,请刷新后重试" },
|
|
980
|
+
});
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const item = claim.deferredTurn;
|
|
984
|
+
try {
|
|
985
|
+
await deps.runtime.steer(sessionId, item.text, item.images);
|
|
986
|
+
const userMessage = await deps.store.appendMessage(sessionId, {
|
|
987
|
+
role: "user",
|
|
988
|
+
text: item.text,
|
|
989
|
+
});
|
|
990
|
+
deps.events.append(sessionId, active.turnId, {
|
|
991
|
+
kind: "user_message",
|
|
992
|
+
data: {
|
|
993
|
+
clientRequestId: item.clientRequestId,
|
|
994
|
+
message: { id: userMessage.id, text: userMessage.text, at: userMessage.at },
|
|
995
|
+
},
|
|
996
|
+
});
|
|
997
|
+
await deps.store.clearDeferredTurnForRequest(sessionId, item.clientRequestId);
|
|
998
|
+
await emitDeferredTurnUpdate(deps, sessionId, active.turnId, undefined, item.revision + 2);
|
|
999
|
+
sendJson(res, 200, {
|
|
1000
|
+
ok: true,
|
|
1001
|
+
accepted: true,
|
|
1002
|
+
deferredTurns: projectDeferredTurnSummaries(await deps.store.listDeferredTurns(sessionId)),
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
const restored = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1007
|
+
expectedRevision: item.revision,
|
|
1008
|
+
clientRequestId: item.clientRequestId,
|
|
1009
|
+
state: "queued",
|
|
1010
|
+
});
|
|
1011
|
+
if (restored)
|
|
1012
|
+
await emitDeferredTurnUpdate(deps, sessionId, active.turnId, restored, restored.revision);
|
|
1013
|
+
sendJson(res, 409, {
|
|
1014
|
+
ok: false,
|
|
1015
|
+
error: {
|
|
1016
|
+
code: "STEER_FAILED",
|
|
1017
|
+
message: error instanceof Error ? error.message : "插话发送失败",
|
|
1018
|
+
},
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
838
1022
|
/** Lightweight busy-truth endpoint for the client reconcile loop (pi-web
|
|
839
1023
|
* get_state equivalent): exposes the active turn + the latest turn's durable
|
|
840
1024
|
* outcome WITHOUT the full message payload, so the UI can poll it on an
|
|
@@ -862,6 +1046,7 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
862
1046
|
isBashRunning: false,
|
|
863
1047
|
};
|
|
864
1048
|
const runtimeBusy = Object.values(runtimeFacts).some((value) => value === true);
|
|
1049
|
+
const pendingQuestion = await deps.questionRegistry?.get(sessionId);
|
|
865
1050
|
const turnPhase = active
|
|
866
1051
|
? active.abortRequestedAt
|
|
867
1052
|
? "stopping"
|
|
@@ -881,10 +1066,60 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
881
1066
|
latestTurn: latest ? turnSummary(latest) : null,
|
|
882
1067
|
runtime: runtimeFacts,
|
|
883
1068
|
turnPhase,
|
|
884
|
-
deferredTurn: projectDeferredTurnSummary(record.
|
|
1069
|
+
deferredTurn: projectDeferredTurnSummary(record.deferredTurns?.[0]),
|
|
1070
|
+
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
1071
|
+
pendingQuestion: pendingQuestion ?? null,
|
|
1072
|
+
todos: projectCurrentTodos(deps.events.snapshot(sessionId)) ?? [],
|
|
885
1073
|
},
|
|
886
1074
|
});
|
|
887
1075
|
}
|
|
1076
|
+
export async function handleAnswerUserQuestion(req, res, deps, sessionId, requestId) {
|
|
1077
|
+
const gate = gateMutation(req, deps);
|
|
1078
|
+
if (!gate.ok) {
|
|
1079
|
+
sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
if (!(await requireChatSession(res, deps, sessionId)))
|
|
1083
|
+
return;
|
|
1084
|
+
if (!deps.questionRegistry) {
|
|
1085
|
+
sendJson(res, 503, { ok: false, error: { code: "USER_QUESTIONS_UNAVAILABLE", message: "user question capability is unavailable" } });
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
try {
|
|
1089
|
+
const body = await readJsonBody(req);
|
|
1090
|
+
const question = await deps.questionRegistry.answer(sessionId, requestId, body);
|
|
1091
|
+
sendJson(res, 200, { ok: true, resolved: true, question });
|
|
1092
|
+
}
|
|
1093
|
+
catch (error) {
|
|
1094
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1095
|
+
const status = message === "USER_QUESTION_NOT_FOUND" ? 404 : message === "USER_QUESTION_STALE" ? 409 : 400;
|
|
1096
|
+
sendJson(res, status, { ok: false, error: { code: message, message } });
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
export async function handleCancelUserQuestion(req, res, deps, sessionId, requestId) {
|
|
1100
|
+
const gate = gateMutation(req, deps);
|
|
1101
|
+
if (!gate.ok) {
|
|
1102
|
+
sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (!(await requireChatSession(res, deps, sessionId)))
|
|
1106
|
+
return;
|
|
1107
|
+
if (!deps.questionRegistry) {
|
|
1108
|
+
sendJson(res, 503, { ok: false, error: { code: "USER_QUESTIONS_UNAVAILABLE", message: "user question capability is unavailable" } });
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
try {
|
|
1112
|
+
const body = await readJsonBody(req).catch(() => ({}));
|
|
1113
|
+
const reason = typeof body.reason === "string" && body.reason.trim() ? body.reason.trim().slice(0, 120) : "USER_CANCELLED";
|
|
1114
|
+
const question = await deps.questionRegistry.cancel(sessionId, requestId, reason);
|
|
1115
|
+
sendJson(res, 200, { ok: true, cancelled: true, question });
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1119
|
+
const status = message === "USER_QUESTION_NOT_FOUND" ? 404 : 409;
|
|
1120
|
+
sendJson(res, status, { ok: false, error: { code: message, message } });
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
888
1123
|
export async function handleListChatSessions(_req, res, deps) {
|
|
889
1124
|
const sessions = await deps.store.list({ includeArchived: true });
|
|
890
1125
|
sendJson(res, 200, {
|
|
@@ -900,7 +1135,8 @@ export async function handleListChatSessions(_req, res, deps) {
|
|
|
900
1135
|
: active
|
|
901
1136
|
? "running"
|
|
902
1137
|
: "idle",
|
|
903
|
-
deferredTurn: projectDeferredTurnSummary(session.
|
|
1138
|
+
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1139
|
+
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
904
1140
|
};
|
|
905
1141
|
}),
|
|
906
1142
|
hasBash: true,
|
|
@@ -1147,6 +1383,7 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1147
1383
|
store: deps.store,
|
|
1148
1384
|
actionContext,
|
|
1149
1385
|
sessionId,
|
|
1386
|
+
turnId,
|
|
1150
1387
|
text,
|
|
1151
1388
|
images,
|
|
1152
1389
|
signal: ac.signal,
|
|
@@ -1442,6 +1679,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1442
1679
|
store: deps.store,
|
|
1443
1680
|
actionContext,
|
|
1444
1681
|
sessionId,
|
|
1682
|
+
turnId: turn.turnId,
|
|
1445
1683
|
text,
|
|
1446
1684
|
images,
|
|
1447
1685
|
signal: controller.signal,
|
|
@@ -1527,12 +1765,16 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1527
1765
|
}
|
|
1528
1766
|
})();
|
|
1529
1767
|
}
|
|
1530
|
-
function emitDeferredTurnUpdate(deps, sessionId, turnId,
|
|
1768
|
+
async function emitDeferredTurnUpdate(deps, sessionId, turnId, _deferredTurn, _revision) {
|
|
1769
|
+
const record = await deps.store.get(sessionId);
|
|
1770
|
+
if (!record)
|
|
1771
|
+
return;
|
|
1531
1772
|
deps.events.append(sessionId, turnId, {
|
|
1532
1773
|
kind: "queue_update",
|
|
1533
1774
|
data: {
|
|
1534
|
-
deferredTurn: projectDeferredTurnSummary(
|
|
1535
|
-
|
|
1775
|
+
deferredTurn: projectDeferredTurnSummary(record.deferredTurns?.[0]),
|
|
1776
|
+
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
1777
|
+
revision: record.deferredQueueRevision ?? 0,
|
|
1536
1778
|
},
|
|
1537
1779
|
});
|
|
1538
1780
|
}
|
|
@@ -1568,7 +1810,7 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1568
1810
|
deferredDispatchingSessions.add(sessionId);
|
|
1569
1811
|
try {
|
|
1570
1812
|
let record = await deps.store.get(sessionId);
|
|
1571
|
-
let deferred = record?.
|
|
1813
|
+
let deferred = record?.deferredTurns?.find((item) => item.state === "dispatching" || item.state === "queued");
|
|
1572
1814
|
if (!deferred)
|
|
1573
1815
|
return;
|
|
1574
1816
|
if (deferred.state === "accepted")
|
|
@@ -1580,17 +1822,19 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1580
1822
|
if (existing) {
|
|
1581
1823
|
const accepted = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1582
1824
|
expectedRevision: deferred.revision,
|
|
1825
|
+
clientRequestId: deferred.clientRequestId,
|
|
1583
1826
|
state: "accepted",
|
|
1584
1827
|
acceptedTurnId: existing.turnId,
|
|
1585
1828
|
});
|
|
1586
1829
|
if (accepted)
|
|
1587
|
-
emitDeferredTurnUpdate(deps, sessionId, existing.turnId, accepted, accepted.revision);
|
|
1830
|
+
await emitDeferredTurnUpdate(deps, sessionId, existing.turnId, accepted, accepted.revision);
|
|
1588
1831
|
return;
|
|
1589
1832
|
}
|
|
1590
1833
|
if (deps.events.getActiveTurn(sessionId))
|
|
1591
1834
|
return;
|
|
1592
1835
|
const requeued = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1593
1836
|
expectedRevision: deferred.revision,
|
|
1837
|
+
clientRequestId: deferred.clientRequestId,
|
|
1594
1838
|
state: "queued",
|
|
1595
1839
|
});
|
|
1596
1840
|
if (!requeued)
|
|
@@ -1601,11 +1845,11 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1601
1845
|
const runtimeBusy = Boolean(runtimeFacts && Object.values(runtimeFacts).some((value) => value === true));
|
|
1602
1846
|
if (deps.events.getActiveTurn(sessionId) || runtimeBusy)
|
|
1603
1847
|
return;
|
|
1604
|
-
const claim = await deps.store.claimDeferredTurn(sessionId, deferred.revision);
|
|
1848
|
+
const claim = await deps.store.claimDeferredTurn(sessionId, deferred.revision, deferred.clientRequestId);
|
|
1605
1849
|
if (!claim.ok)
|
|
1606
1850
|
return;
|
|
1607
1851
|
deferred = claim.deferredTurn;
|
|
1608
|
-
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, deferred, deferred.revision);
|
|
1852
|
+
await emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, deferred, deferred.revision);
|
|
1609
1853
|
const started = await startTrustedChatTurn(deps, sessionId, {
|
|
1610
1854
|
clientRequestId: deferred.clientRequestId,
|
|
1611
1855
|
text: deferred.text,
|
|
@@ -1624,7 +1868,7 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1624
1868
|
});
|
|
1625
1869
|
const cleared = await deps.store.clearDeferredTurnForRequest(sessionId, deferred.clientRequestId);
|
|
1626
1870
|
if (cleared)
|
|
1627
|
-
emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, cleared.clearedRevision);
|
|
1871
|
+
await emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, cleared.clearedRevision);
|
|
1628
1872
|
},
|
|
1629
1873
|
});
|
|
1630
1874
|
const accepted = started.body.accepted === true;
|
|
@@ -1634,11 +1878,12 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1634
1878
|
if (accepted && turn?.turnId) {
|
|
1635
1879
|
const next = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1636
1880
|
expectedRevision: deferred.revision,
|
|
1881
|
+
clientRequestId: deferred.clientRequestId,
|
|
1637
1882
|
state: "accepted",
|
|
1638
1883
|
acceptedTurnId: turn.turnId,
|
|
1639
1884
|
});
|
|
1640
1885
|
if (next)
|
|
1641
|
-
emitDeferredTurnUpdate(deps, sessionId, turn.turnId, next, next.revision);
|
|
1886
|
+
await emitDeferredTurnUpdate(deps, sessionId, turn.turnId, next, next.revision);
|
|
1642
1887
|
return;
|
|
1643
1888
|
}
|
|
1644
1889
|
const error = started.body.error && typeof started.body.error === "object"
|
|
@@ -1647,14 +1892,16 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1647
1892
|
if (error.code === "TURN_ACTIVE") {
|
|
1648
1893
|
const next = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1649
1894
|
expectedRevision: deferred.revision,
|
|
1895
|
+
clientRequestId: deferred.clientRequestId,
|
|
1650
1896
|
state: "queued",
|
|
1651
1897
|
});
|
|
1652
1898
|
if (next)
|
|
1653
|
-
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, next, next.revision);
|
|
1899
|
+
await emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, next, next.revision);
|
|
1654
1900
|
return;
|
|
1655
1901
|
}
|
|
1656
1902
|
const failed = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1657
1903
|
expectedRevision: deferred.revision,
|
|
1904
|
+
clientRequestId: deferred.clientRequestId,
|
|
1658
1905
|
state: "failed",
|
|
1659
1906
|
lastError: {
|
|
1660
1907
|
code: error.code ?? `HTTP_${started.status}`,
|
|
@@ -1662,17 +1909,18 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1662
1909
|
},
|
|
1663
1910
|
});
|
|
1664
1911
|
if (failed)
|
|
1665
|
-
emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, failed, failed.revision);
|
|
1912
|
+
await emitDeferredTurnUpdate(deps, sessionId, deferred.blockedByTurnId, failed, failed.revision);
|
|
1666
1913
|
}
|
|
1667
1914
|
catch (error) {
|
|
1668
1915
|
// Keep fire-and-forget callers safe and make a dispatch failure visible and
|
|
1669
1916
|
// recoverable from the composer instead of leaving the slot indefinitely
|
|
1670
1917
|
// in an ambiguous queued/dispatching state.
|
|
1671
1918
|
try {
|
|
1672
|
-
const current = (await deps.store.get(sessionId))?.
|
|
1919
|
+
const current = (await deps.store.get(sessionId))?.deferredTurns?.find((item) => item.state === "queued" || item.state === "dispatching");
|
|
1673
1920
|
if (current && (current.state === "queued" || current.state === "dispatching")) {
|
|
1674
1921
|
const failed = await deps.store.transitionDeferredTurn(sessionId, {
|
|
1675
1922
|
expectedRevision: current.revision,
|
|
1923
|
+
clientRequestId: current.clientRequestId,
|
|
1676
1924
|
state: "failed",
|
|
1677
1925
|
lastError: {
|
|
1678
1926
|
code: "DEFERRED_TURN_DISPATCH_FAILED",
|
|
@@ -1680,7 +1928,7 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1680
1928
|
},
|
|
1681
1929
|
});
|
|
1682
1930
|
if (failed)
|
|
1683
|
-
emitDeferredTurnUpdate(deps, sessionId, failed.blockedByTurnId, failed, failed.revision);
|
|
1931
|
+
await emitDeferredTurnUpdate(deps, sessionId, failed.blockedByTurnId, failed, failed.revision);
|
|
1684
1932
|
}
|
|
1685
1933
|
}
|
|
1686
1934
|
catch {
|
|
@@ -1695,12 +1943,9 @@ export async function dispatchDeferredTurn(deps, sessionId) {
|
|
|
1695
1943
|
export async function recoverDeferredTurns(deps) {
|
|
1696
1944
|
const sessions = await deps.store.list({ includeArchived: false });
|
|
1697
1945
|
await Promise.all(sessions
|
|
1698
|
-
.filter((session) => session.
|
|
1699
|
-
? session.deferredTurn.state === "queued" ||
|
|
1700
|
-
session.deferredTurn.state === "dispatching"
|
|
1701
|
-
: false)
|
|
1946
|
+
.filter((session) => (session.deferredTurns ?? []).some((item) => item.state === "queued" || item.state === "dispatching"))
|
|
1702
1947
|
.map(async (session) => {
|
|
1703
|
-
const deferred = session.
|
|
1948
|
+
const deferred = session.deferredTurns.find((item) => item.state === "dispatching" || item.state === "queued");
|
|
1704
1949
|
const active = deps.events.getActiveTurn(session.sessionId);
|
|
1705
1950
|
// A process restart cannot retain AbortController ownership. When the
|
|
1706
1951
|
// durable blocker was already stopping and no execution owns it now,
|
|
@@ -1767,34 +2012,24 @@ export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
|
|
|
1767
2012
|
});
|
|
1768
2013
|
return;
|
|
1769
2014
|
}
|
|
1770
|
-
deps.events.markAbortRequested?.(sessionId, turnId);
|
|
2015
|
+
const stopping = deps.events.markAbortRequested?.(sessionId, turnId) ?? turn;
|
|
2016
|
+
// DSH-style acknowledgement: deliver the AbortSignal synchronously, then let
|
|
2017
|
+
// the detached Turn closeout own SDK/process cleanup and terminal persistence.
|
|
2018
|
+
// The HTTP request reports that Stop was accepted; it does not wait for Pi to
|
|
2019
|
+
// become idle. This keeps cancellation latency independent of slow providers,
|
|
2020
|
+
// tools and process-tree cleanup while preserving the active-Turn lease.
|
|
1771
2021
|
if (!execution.controller.signal.aborted)
|
|
1772
2022
|
execution.controller.abort();
|
|
1773
|
-
const
|
|
1774
|
-
if (
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
...turn,
|
|
1778
|
-
state: waited.outcome.state,
|
|
1779
|
-
};
|
|
1780
|
-
sendJson(res, 200, {
|
|
1781
|
-
ok: true,
|
|
1782
|
-
accepted: true,
|
|
1783
|
-
idempotent: false,
|
|
1784
|
-
turn: turnSummary(finalTurn),
|
|
1785
|
-
});
|
|
1786
|
-
return;
|
|
1787
|
-
}
|
|
1788
|
-
// Timeout or missing completion: keep non-terminal; client reconciles via /state.
|
|
1789
|
-
const pending = deps.events.getTurn(sessionId, turnId) ?? turn;
|
|
2023
|
+
const questionCancellation = deps.questionRegistry?.cancelForSession(sessionId, "TURN_ABORTED");
|
|
2024
|
+
if (questionCancellation)
|
|
2025
|
+
void questionCancellation.catch(() => undefined);
|
|
2026
|
+
const acceptedTurn = deps.events.getTurn(sessionId, turnId) ?? stopping;
|
|
1790
2027
|
sendJson(res, 202, {
|
|
1791
|
-
ok:
|
|
1792
|
-
accepted:
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
},
|
|
1797
|
-
turn: turnSummary(pending),
|
|
2028
|
+
ok: true,
|
|
2029
|
+
accepted: true,
|
|
2030
|
+
idempotent: false,
|
|
2031
|
+
cleanupPending: !isTerminalTurnState(acceptedTurn.state),
|
|
2032
|
+
turn: turnSummary(acceptedTurn),
|
|
1798
2033
|
});
|
|
1799
2034
|
}
|
|
1800
2035
|
export async function handleGetChatTurn(_req, res, deps, sessionId, turnId) {
|
|
@@ -1918,6 +2153,8 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
1918
2153
|
});
|
|
1919
2154
|
return;
|
|
1920
2155
|
}
|
|
2156
|
+
await deps.questionRegistry?.cancelForSession(sessionId, "SESSION_DELETED").catch(() => undefined);
|
|
2157
|
+
deps.todoRegistry?.clear(sessionId);
|
|
1921
2158
|
deps.runtime.dispose(sessionId);
|
|
1922
2159
|
deps.operationLinker.clearSession(sessionId);
|
|
1923
2160
|
deps.events.clear(sessionId);
|
|
@@ -2592,6 +2829,36 @@ async function handleLinkWorkspaceOperation(req, res, deps, sessionId) {
|
|
|
2592
2829
|
});
|
|
2593
2830
|
}
|
|
2594
2831
|
}
|
|
2832
|
+
async function handleUnlinkWorkspaceOperation(req, res, deps, sessionId, operationId) {
|
|
2833
|
+
const gate = gateMutation(req, deps);
|
|
2834
|
+
if (!gate.ok) {
|
|
2835
|
+
sendJson(res, gate.status, {
|
|
2836
|
+
ok: false,
|
|
2837
|
+
error: { code: gate.code, message: gate.message },
|
|
2838
|
+
});
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
if (!(await requireChatSession(res, deps, sessionId)))
|
|
2842
|
+
return;
|
|
2843
|
+
// AC-R7-001 (2026-08-23): removing a workspace-linked DAG association is
|
|
2844
|
+
// idempotent — a missing link is already-removed (200), never an error.
|
|
2845
|
+
try {
|
|
2846
|
+
const removed = await deps.operationLinker.unlink({
|
|
2847
|
+
sessionId,
|
|
2848
|
+
operationId,
|
|
2849
|
+
});
|
|
2850
|
+
sendJson(res, 200, { ok: true, removed });
|
|
2851
|
+
}
|
|
2852
|
+
catch (error) {
|
|
2853
|
+
sendJson(res, 400, {
|
|
2854
|
+
ok: false,
|
|
2855
|
+
error: {
|
|
2856
|
+
code: "INVALID_INPUT",
|
|
2857
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2858
|
+
},
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2595
2862
|
async function handleStartInterview(req, res, deps, sessionId) {
|
|
2596
2863
|
const gate = gateMutation(req, deps);
|
|
2597
2864
|
if (!gate.ok) {
|
|
@@ -3244,14 +3511,59 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
3244
3511
|
return;
|
|
3245
3512
|
}
|
|
3246
3513
|
const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
3247
|
-
const
|
|
3514
|
+
const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
|
|
3515
|
+
const customInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
|
|
3516
|
+
const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
|
|
3517
|
+
const compactionId = `${sessionId}:compaction:${randomUUID()}`;
|
|
3518
|
+
deps.events.append(sessionId, turnId, {
|
|
3519
|
+
kind: "compaction_start",
|
|
3520
|
+
data: {
|
|
3521
|
+
schemaVersion: 1,
|
|
3522
|
+
compactionId,
|
|
3523
|
+
reason: "manual",
|
|
3524
|
+
status: "running",
|
|
3525
|
+
willRetry: false,
|
|
3526
|
+
},
|
|
3527
|
+
});
|
|
3248
3528
|
try {
|
|
3249
3529
|
const snapshot = await deps.runtime.compact(sessionId, customInstructions);
|
|
3250
|
-
const event = deps.events.append(sessionId,
|
|
3530
|
+
const event = deps.events.append(sessionId, turnId, {
|
|
3531
|
+
kind: "compaction_end",
|
|
3532
|
+
data: {
|
|
3533
|
+
schemaVersion: 1,
|
|
3534
|
+
compactionId,
|
|
3535
|
+
reason: "manual",
|
|
3536
|
+
status: "completed",
|
|
3537
|
+
summary: snapshot.summary,
|
|
3538
|
+
...(snapshot.firstKeptEntryId
|
|
3539
|
+
? { firstKeptEntryId: snapshot.firstKeptEntryId }
|
|
3540
|
+
: {}),
|
|
3541
|
+
...(snapshot.tokensBefore === undefined
|
|
3542
|
+
? {}
|
|
3543
|
+
: { tokensBefore: snapshot.tokensBefore }),
|
|
3544
|
+
...(snapshot.estimatedTokensAfter === undefined
|
|
3545
|
+
? {}
|
|
3546
|
+
: { estimatedTokensAfter: snapshot.estimatedTokensAfter }),
|
|
3547
|
+
willRetry: false,
|
|
3548
|
+
},
|
|
3549
|
+
});
|
|
3251
3550
|
sendJson(res, 200, { ok: true, snapshot, eventId: event.eventId });
|
|
3252
3551
|
}
|
|
3253
3552
|
catch (error) {
|
|
3254
3553
|
const classified = classifyChatCompactionFailure(error);
|
|
3554
|
+
const aborted = error instanceof Error &&
|
|
3555
|
+
(error.name === "AbortError" || /cancel(?:led|ed)|aborted/i.test(error.message));
|
|
3556
|
+
deps.events.append(sessionId, turnId, {
|
|
3557
|
+
kind: "compaction_end",
|
|
3558
|
+
data: {
|
|
3559
|
+
schemaVersion: 1,
|
|
3560
|
+
compactionId,
|
|
3561
|
+
reason: "manual",
|
|
3562
|
+
status: aborted ? "aborted" : "failed",
|
|
3563
|
+
willRetry: false,
|
|
3564
|
+
...(aborted ? {} : { errorMessage: classified.message }),
|
|
3565
|
+
},
|
|
3566
|
+
});
|
|
3255
3567
|
sendJson(res, classified.status, {
|
|
3256
3568
|
ok: false,
|
|
3257
3569
|
error: { code: classified.code, message: classified.message },
|
|
@@ -3280,8 +3592,10 @@ export async function handleChatMessages(res, deps, sessionId, url) {
|
|
|
3280
3592
|
/**
|
|
3281
3593
|
* Dispatch a chat HTTP request. Returns true if handled.
|
|
3282
3594
|
* Mounted under /api/operator/v1/chat/*.
|
|
3595
|
+
* workspaceScope (AC-R7-002): the explicit owning workspaceId for session
|
|
3596
|
+
* create, forwarded to handleCreateChatSession → store.create.
|
|
3283
3597
|
*/
|
|
3284
|
-
export async function handleChatRequest(req, res, deps, pathname) {
|
|
3598
|
+
export async function handleChatRequest(req, res, deps, pathname, workspaceScope) {
|
|
3285
3599
|
if (!deps)
|
|
3286
3600
|
return false;
|
|
3287
3601
|
if (!pathname.startsWith("/api/operator/v1/chat/") &&
|
|
@@ -3359,6 +3673,16 @@ export async function handleChatRequest(req, res, deps, pathname) {
|
|
|
3359
3673
|
await handleStartInterview(req, res, deps, decodeURIComponent(interviewStart[1]));
|
|
3360
3674
|
return true;
|
|
3361
3675
|
}
|
|
3676
|
+
const userQuestionAnswer = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/questions\/([^/]+)\/answer$/);
|
|
3677
|
+
if (method === "POST" && userQuestionAnswer) {
|
|
3678
|
+
await handleAnswerUserQuestion(req, res, deps, decodeURIComponent(userQuestionAnswer[1]), decodeURIComponent(userQuestionAnswer[2]));
|
|
3679
|
+
return true;
|
|
3680
|
+
}
|
|
3681
|
+
const userQuestionCancel = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/questions\/([^/]+)\/cancel$/);
|
|
3682
|
+
if (method === "POST" && userQuestionCancel) {
|
|
3683
|
+
await handleCancelUserQuestion(req, res, deps, decodeURIComponent(userQuestionCancel[1]), decodeURIComponent(userQuestionCancel[2]));
|
|
3684
|
+
return true;
|
|
3685
|
+
}
|
|
3362
3686
|
const interviewAnswer = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/interviews\/([^/]+)\/answers$/);
|
|
3363
3687
|
if (method === "POST" && interviewAnswer) {
|
|
3364
3688
|
await handleAnswerInterview(req, res, deps, decodeURIComponent(interviewAnswer[1]), decodeURIComponent(interviewAnswer[2]));
|
|
@@ -3465,6 +3789,11 @@ export async function handleChatRequest(req, res, deps, pathname) {
|
|
|
3465
3789
|
await handleDeleteDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|
|
3466
3790
|
return true;
|
|
3467
3791
|
}
|
|
3792
|
+
const deferredTurnSteer = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn\/([^/]+)\/steer$/);
|
|
3793
|
+
if (method === "POST" && deferredTurnSteer) {
|
|
3794
|
+
await handleSteerDeferredTurn(req, res, deps, decodeURIComponent(deferredTurnSteer[1]), decodeURIComponent(deferredTurnSteer[2]));
|
|
3795
|
+
return true;
|
|
3796
|
+
}
|
|
3468
3797
|
const sessionTurnAbort = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/turns\/([^/]+)\/abort$/);
|
|
3469
3798
|
if (method === "POST" && sessionTurnAbort) {
|
|
3470
3799
|
await handleAbortChatTurn(req, res, deps, decodeURIComponent(sessionTurnAbort[1]), decodeURIComponent(sessionTurnAbort[2]));
|
|
@@ -3480,6 +3809,13 @@ export async function handleChatRequest(req, res, deps, pathname) {
|
|
|
3480
3809
|
await handleLinkWorkspaceOperation(req, res, deps, decodeURIComponent(operationLink[1]));
|
|
3481
3810
|
return true;
|
|
3482
3811
|
}
|
|
3812
|
+
const operationLinkItem = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/operation-links\/([^/]+)$/);
|
|
3813
|
+
if (method === "DELETE" && operationLinkItem) {
|
|
3814
|
+
// AC-R7-001: Details rail DAG remove — real DELETE of the workspace
|
|
3815
|
+
// operation-link association (idempotent).
|
|
3816
|
+
await handleUnlinkWorkspaceOperation(req, res, deps, decodeURIComponent(operationLinkItem[1]), decodeURIComponent(operationLinkItem[2]));
|
|
3817
|
+
return true;
|
|
3818
|
+
}
|
|
3483
3819
|
const sessionCompact = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/compact$/);
|
|
3484
3820
|
if (method === "POST" && sessionCompact) {
|
|
3485
3821
|
await handleChatCompact(req, res, deps, decodeURIComponent(sessionCompact[1]));
|
|
@@ -3527,7 +3863,7 @@ export async function handleChatRequest(req, res, deps, pathname) {
|
|
|
3527
3863
|
}
|
|
3528
3864
|
}
|
|
3529
3865
|
if (method === "POST" && pathname === "/api/operator/v1/chat/sessions") {
|
|
3530
|
-
await handleCreateChatSession(req, res, deps);
|
|
3866
|
+
await handleCreateChatSession(req, res, deps, workspaceScope);
|
|
3531
3867
|
return true;
|
|
3532
3868
|
}
|
|
3533
3869
|
if (isMutationMethod(method)) {
|