@tea-agent/loop-agent 0.43.0-next.9 → 0.43.0
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 +1 -1
- package/CHANGELOG.md +31 -2309
- package/README.md +7 -0
- package/dist/application/dag/args.js +8 -0
- package/dist/application/task-lifecycle/advance.js +38 -7
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +2 -0
- package/dist/cli/program.js +3 -1
- package/dist/cli/update/init-surface-notifier.js +2 -0
- package/dist/cli/update/policy.js +2 -0
- package/dist/commands/client-recovery.js +20 -55
- package/dist/commands/cursor-prompt.js +2 -0
- package/dist/commands/dag-approve.js +2 -0
- package/dist/commands/dag-final-verification.js +2 -0
- package/dist/commands/dag-init-hybrid.js +2 -0
- package/dist/commands/dag-reconcile-tasks.js +2 -0
- package/dist/commands/dag-reject.js +2 -0
- package/dist/commands/dag-report.js +2 -0
- package/dist/commands/dag-rerun.js +2 -0
- package/dist/commands/dag-resume.js +2 -0
- package/dist/commands/dag-workflow-compile.js +2 -0
- package/dist/commands/dag-workflow-plan.js +2 -0
- package/dist/commands/dag-workflow-validate.js +2 -0
- package/dist/commands/delegate.js +2 -0
- package/dist/commands/examples.js +2 -0
- package/dist/commands/import-prd.js +2 -0
- package/dist/commands/init-model-catalog.js +2 -0
- package/dist/commands/init.js +19 -13
- package/dist/commands/instructions.js +2 -0
- package/dist/commands/knowledge.js +2 -0
- package/dist/commands/loop-benchmark.js +2 -0
- package/dist/commands/pi-prompt.js +2 -0
- package/dist/commands/pi-reuse-benchmark.js +2 -0
- package/dist/commands/promote-run.js +2 -0
- package/dist/commands/stats.js +1 -1
- package/dist/commands/study-init.js +2 -0
- package/dist/commands/task-advance.js +34 -0
- package/dist/commands/task-source-prepare.js +2 -0
- package/dist/commands/worktree-create.js +2 -0
- package/dist/commands/worktree-remove.js +2 -0
- package/dist/executors/dag-pi-executor.js +1650 -198
- package/dist/executors/pi-executor.js +85 -3
- package/dist/executors/pi-sdk-executor.js +18 -0
- package/dist/executors/shell-executor.js +93 -7
- package/dist/executors/shell-verification.js +3 -0
- package/dist/executors/shell-write-guard.js +27 -32
- package/dist/governance/exec-plans.js +6 -3
- package/dist/infrastructure/console/app-data.js +6 -0
- package/dist/shared/artifacts-core.js +2 -0
- package/dist/shared/backend-dogfood-preflight.js +47 -0
- package/dist/shared/dag-failure-category.js +3 -0
- package/dist/shared/git-progress.js +2 -0
- package/dist/shared/one-shot-prompt-args.js +2 -0
- package/dist/shared/operator/capabilities.js +180 -0
- package/dist/shared/package-metadata.js +6 -4
- package/dist/shared/pi-context-pressure/extension.js +26 -16
- package/dist/shared/pi-context-pressure/sift-bridge.js +1 -1
- package/dist/shared/pi-provider-recovery.js +182 -0
- package/dist/shared/pi-retry-settings.js +40 -0
- package/dist/shared/reference-context.js +2 -0
- package/dist/task/config-types.js +29 -0
- package/dist/task/contract/project.js +9 -0
- package/dist/task/contract/recover.js +6 -0
- package/dist/task/contract/schema.js +17 -0
- package/dist/task/source-prepare/artifact-meta.js +9 -2
- package/dist/task/source-prepare/build-draft.js +60 -0
- package/dist/task/source-prepare/fragment-inventory.js +4 -1
- package/dist/task/source-prepare/parse-intent.js +36 -11
- package/dist/task/task-demand-routing.js +5 -2
- package/dist/worker/console/chat/browser-automation.js +523 -0
- package/dist/worker/console/chat/browser-policy.js +6 -36
- package/dist/worker/console/chat/browser-routes.js +188 -56
- package/dist/worker/console/chat/explore-tools.js +5 -2
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +155 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +184 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +79 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +94 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/scheduled-goal-tools.js +22 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
- package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
- package/dist/worker/console/chat/pi-runtime/progressive-tools.js +195 -0
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
- package/dist/worker/console/chat/pi-runtime.js +208 -1138
- package/dist/worker/console/chat/routes.js +27 -7
- package/dist/worker/console/chat/scheduled-goal-booking.js +59 -0
- package/dist/worker/console/chat/scheduled-goal-delivery.js +27 -0
- package/dist/worker/console/chat/scheduled-goal-request.js +190 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +1 -1
- package/dist/worker/console/chat/session-mode-view.js +5 -4
- package/dist/worker/console/chat/session-mode.js +18 -12
- package/dist/worker/console/chat/session-store.js +42 -9
- package/dist/worker/console/chat/tool-preview.js +115 -0
- package/dist/worker/console/chat/tools.js +10 -0
- package/dist/worker/console/chat/turn-order.js +13 -0
- package/dist/worker/console/chat/turn-process.js +32 -30
- package/dist/worker/console/console-update-and-init.js +1 -19
- package/dist/worker/console/console-update-runtime.js +5 -20
- package/dist/worker/console/operator-actions.js +63 -1
- package/dist/worker/console/prd-intake-bridge.js +54 -1
- package/dist/worker/console/routes.js +8 -0
- package/dist/worker/console/scheduled-goal-host.js +98 -0
- package/dist/worker/console/scheduled-goal-operation-adapter.js +127 -0
- package/dist/worker/console/server.js +24 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-D_fNbWc_.js → abnfDiagram-N423BO3Z-8-j6y-sd.js} +1 -1
- package/dist/worker/console/static/assets/{arc-_HuqLW6m.js → arc-eoQiMvuk.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Djy2qY1m.js → architectureDiagram-T3A2C74G-C4A3uMcI.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-bB-1TSyY.js → blockDiagram-VBNYF7ZC-D4zD2F-Q.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-C5Hmghrz.js → c4Diagram-5PPSVZJV-j1RkJziL.js} +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Duyl39oy.js → chunk-2GRJ4B5K-YBHmhik1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-x6mdnSyf.js → chunk-2Q5K7J3B-COfWyo9P.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-WlBuFj7W.js → chunk-5RXB4S5H-I99OUkHY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CTF429HX.js → chunk-5VM5RSS4-XKxoJNJ4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dwd6r-SF.js → chunk-6Q2QTUOP-DLT_cYx1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C-Br82D1.js → chunk-GF5L2VYU-CxcKZ9mV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CuGp0DeE.js → chunk-JWPE2WC7-V1EqXdjY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CJhFX6Lh.js → chunk-KBJHAD2P-DebTtdFp.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DpGOa_jT.js → chunk-RYQCIY6F-B-ivNRDf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DXJaTynd.js → chunk-XXDRQBXY-DC_Ds11b.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CRBZWjE4.js → cose-bilkent-JH36ORCC-sWEqKwIb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BdXcxtlb.js → cynefin-VYW2F7L2-BXa_dcu4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-07UKsOts.js → cynefinDiagram-MW4NZA55-C-Mle84F.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-D38p9__S.js → dagre-VZM6K2ZE-CXPDBITe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-nSiBNNjH.js → diagram-7IWD3JNH-CC-WJQfa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BFOTot5D.js → diagram-B4RE2ZJO-CDAV5Vs4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DypKaZ6k.js → diagram-LBJQPF4R-CmFiAcNz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-B6yoOQeW.js → diagram-Q27KOJAE-DPBZHuyn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BHYS6viX.js → diagram-UB23O5K3-jUlm_Ds3.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-D2VD5Snj.js → ebnfDiagram-BXEA7PRR-DWhQ3mfY.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-E98mWxN1.js → erDiagram-JOGREHBK-Tr2gMqet.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CT1zxhqP.js → flowDiagram-UKHOOZJN-DJjVQHPA.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-6GcngNcy.js → ganttDiagram-PKOTCBZU-D74dQ4u0.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DCL6NvKe.js → gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js} +1 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +1 -0
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +469 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C0627e-9.js → infoDiagram-6WML65LV-ILCbxJyb.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js → ishikawaDiagram-WSZJBQD7-DeuWBQ89.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DPDPv9HL.js → journeyDiagram-NVQOT4AX-CF5ih8Fk.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D3OHdWiO.js → kanban-definition-27J2QSJJ-C7yOSuRO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI88zD7n.js → linear-BDZ9riWi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-p2l8IrWY.js → mermaid.core-7pKqYtpZ.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-B1bAX-oS.js → mindmap-definition-FAOFIHXS-LeJDybSU.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DwOnWxdD.js → pegDiagram-VL7TDLO6-BJvT3pMD.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-xRkTXnJC.js → pieDiagram-7S7Q4E2Y-_rGqpMan.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-_9UC5Zio.js → quadrantDiagram-CIZ2JOQS-DPcSv5aA.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CgEUirWJ.js → railroadDiagram-AXF67PYL-Drxx4hkJ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Czbpwiyz.js → requirementDiagram-LRYGKXZP-BCTUdU4z.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-_j7_KiqY.js → sankeyDiagram-W5VNT64P-B6wzbZmB.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BNJa6Hyi.js → sequenceDiagram-SI44F4Z6-BGt8d3QQ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DedpKtOc.js → sizeCapture-X5ZJPWSS-7nlhJKo7.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BBvthr7c.js → stateDiagram-OKZ733FA-Cv2stqMA.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Ds7umgEM.js → swimlanes-SLNWSIFB-qDAo4Yc1.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKa-p1nF.js → timeline-definition-Z64GVDOM-CKbDNKTr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-LoUNR56o.js → vennDiagram-T6HMQDX7-DI-9EHic.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Bqbl6ym7.js → wardleyDiagram-T6FBY63Y-BrzzRDpX.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B8dUg4lf.js → xychartDiagram-ELKLHX3M-BclH5hGh.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +24 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +41 -11
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +3 -17
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +192 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +9 -1
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +15 -5
- package/dist/worker/console/static-src/operator-chat/timeline-merge.js +29 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +92 -38
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +145 -70
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +38 -55
- package/dist/worker/console/static-src/operator-chat/useComposer.js +182 -69
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +2 -0
- package/dist/worker/console/static-src/prd-file-import.js +3 -1
- package/dist/worker/console/workspace-context.js +22 -0
- package/dist/worker/console/workspace-initialization.js +209 -0
- package/dist/worker/delivery/git-transaction.js +2 -0
- package/dist/worker/feature/fullstack-validate.js +2 -0
- package/dist/worker/loop-agent/loop-agent-client.js +8 -2
- package/dist/worker/materialize/harness-task-materializer.js +4 -1
- package/dist/worker/metrics/projector.js +1 -1
- package/dist/worker/observability/event-store.js +4 -1
- package/dist/worker/observability/read-model.js +4 -1
- package/dist/worker/observe/node-transparency.js +23 -1
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/operator-chrome.js +3 -1
- package/dist/worker/observe/static/styles.css +7 -4
- package/dist/worker/observe/static/views/dag-inspector.js +5 -19
- package/dist/worker/outcomes/declared-artifacts.js +2 -0
- package/dist/worker/preflight.js +3 -0
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +117 -0
- package/dist/worker/scheduler/scheduled-goal-evidence.js +167 -0
- package/dist/worker/scheduler/scheduled-goal-recovery.js +62 -0
- package/dist/worker/scheduler/scheduled-goal-store.js +699 -0
- package/dist/worker/scheduler/scheduled-goal-supervisor.js +132 -0
- package/dist/worker/scheduler/scheduled-goal-time.js +102 -0
- package/dist/worker/scheduler/scheduled-goal-types.js +95 -0
- package/dist/worker/task-spec/validate.js +4 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +6 -24
- package/dist/workflows/dag/backend-test-plan-protocol.js +82 -5
- package/dist/workflows/dag/dag-retry-schema.js +11 -0
- package/dist/workflows/dag/decision-envelope.js +2 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +2 -2
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-committed-facts.js +461 -0
- package/dist/workflows/dag/frontend-durable-tools.js +15 -3
- package/dist/workflows/dag/frontend-implementation-contract.js +369 -12
- package/dist/workflows/dag/frontend-plan-canary.js +53 -0
- package/dist/workflows/dag/frontend-plan-decision-contract.js +803 -0
- package/dist/workflows/dag/frontend-plan-render.js +0 -2
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +8 -61
- package/dist/workflows/dag/frontend-recovery-run.js +57 -0
- package/dist/workflows/dag/frontend-repair.js +55 -76
- package/dist/workflows/dag/frontend-review-context.js +42 -68
- package/dist/workflows/dag/frontend-review-scopes.js +2 -2
- package/dist/workflows/dag/frontend-risk.js +92 -10
- package/dist/workflows/dag/frontend-session-budget.js +117 -3
- package/dist/workflows/dag/frontend-shape.js +11 -55
- package/dist/workflows/dag/frontend-test-execution-evidence.js +35 -10
- package/dist/workflows/dag/frontend-typed-event-store.js +32 -25
- package/dist/workflows/dag/frontend-verification-trace.js +33 -54
- package/dist/workflows/dag/frontend-writer-admission.js +3 -47
- package/dist/workflows/dag/frontend-writer-status.js +0 -23
- package/dist/workflows/dag/init-hybrid.js +47 -19
- package/dist/workflows/dag/interrupt-request.js +2 -0
- package/dist/workflows/dag/lifecycle.js +11 -2
- package/dist/workflows/dag/node-execution.js +50 -13
- package/dist/workflows/dag/reconcile-run.js +2 -0
- package/dist/workflows/dag/repair-artifact.js +3 -1
- package/dist/workflows/dag/report.js +2 -0
- package/dist/workflows/dag/rerun-plan.js +10 -0
- package/dist/workflows/dag/rerun-task.js +79 -1
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/scheduler.js +2 -4
- package/dist/workflows/dag/types.js +44 -6
- package/dist/workflows/dag/validate.js +4 -0
- package/docs/README.md +1 -0
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/governance/README.md +1 -0
- package/docs/init-surface.manifest.json +1 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +14 -10
- package/docs/templates/frontend-implementation-contract.schema.json +0 -7
- package/package.json +9 -3
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +3 -1
- package/skills/frontend-plan/SKILL.md +8 -6
- package/skills/frontend-plan/references/decision-contract.md +3 -3
- package/skills/frontend-review/SKILL.md +18 -28
- package/skills/frontend-review/references/review-findings.md +8 -3
- package/dist/commands/new-task.js +0 -5
- package/dist/commands/status.js +0 -57
- package/dist/commands/task-contract.js +0 -269
- package/dist/shared/prompts.js +0 -26
- package/dist/task/config.js +0 -2
- package/dist/task/contract/validate-draft.js +0 -106
- package/dist/task/goal.js +0 -3
- package/dist/task/index.js +0 -12
- package/dist/task/lifecycle.js +0 -1
- package/dist/task/operator/capabilities.js +0 -6
- package/dist/task/operator/envelope.js +0 -2
- package/dist/task/operator/index.js +0 -5
- package/dist/task/operator/registry.js +0 -2
- package/dist/task/operator/types.js +0 -1
- package/dist/task/paths.js +0 -1
- package/dist/task/source-state.js +0 -1
- package/dist/task/subagent-guidance.js +0 -1
- package/dist/worker/console/chat/sift-bridge.js +0 -1
- package/dist/worker/console/index.js +0 -20
- package/dist/worker/console/static/assets/channel-C0A6UEIa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +0 -1
- package/dist/worker/console/static/assets/index-24jp3aBh.js +0 -468
- package/dist/worker/console/static/assets/index-CY6y6I8M.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +0 -8
- package/dist/worker/scheduler/index.js +0 -25
- package/dist/workflows/dag/facts.js +0 -4
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -975
- package/dist/workflows/dag/index.js +0 -6
- package/dist/workflows/dynamic/index.js +0 -7
|
@@ -134,6 +134,16 @@ export function projectStoredMessagesWithEventOrder(messages, events) {
|
|
|
134
134
|
if (event.kind !== expectedKind)
|
|
135
135
|
continue;
|
|
136
136
|
const data = event.data;
|
|
137
|
+
// Exact alignment wins: since 2026-09-13 message_end frames carry the
|
|
138
|
+
// durable assistant message id, so restore matches by id, never by
|
|
139
|
+
// text (duplicate assistant texts would mis-align within a turn).
|
|
140
|
+
if (typeof data.messageId === "string" && data.messageId) {
|
|
141
|
+
if (data.messageId === message.id) {
|
|
142
|
+
assistantCursor = index + 1;
|
|
143
|
+
return event;
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
137
147
|
const eventText = typeof data.text === "string"
|
|
138
148
|
? data.text
|
|
139
149
|
: typeof data.message === "string"
|
|
@@ -271,6 +281,15 @@ function turnEventToStorePartial(event) {
|
|
|
271
281
|
text: String(event.text ?? ""),
|
|
272
282
|
...(content ? { content } : {}),
|
|
273
283
|
...(timing ? { timing } : {}),
|
|
284
|
+
// Durable assistant message id (2026-09-13): the session
|
|
285
|
+
// store persists message_end BEFORE framing, so a reconnecting
|
|
286
|
+
// client and the restore projection can align on this id
|
|
287
|
+
// instead of matching text within a time window.
|
|
288
|
+
...(typeof event.messageId ===
|
|
289
|
+
"string" &&
|
|
290
|
+
event.messageId
|
|
291
|
+
? { messageId: event.messageId }
|
|
292
|
+
: {}),
|
|
274
293
|
},
|
|
275
294
|
};
|
|
276
295
|
}
|
|
@@ -1327,7 +1346,7 @@ export async function handleChatGoal(req, res, deps, sessionId) {
|
|
|
1327
1346
|
});
|
|
1328
1347
|
return;
|
|
1329
1348
|
}
|
|
1330
|
-
if (!(await
|
|
1349
|
+
if (!(await requireChatSession(res, deps, sessionId)))
|
|
1331
1350
|
return;
|
|
1332
1351
|
if (req.method === "GET") {
|
|
1333
1352
|
sendJson(res, 200, { ok: true, goal: registry.get(sessionId) });
|
|
@@ -2256,10 +2275,6 @@ export async function driveGoalRound(deps, sessionId) {
|
|
|
2256
2275
|
registry.disarm(sessionId);
|
|
2257
2276
|
return "idle";
|
|
2258
2277
|
}
|
|
2259
|
-
if (!isWorkflowSessionMode(readStoredSessionMode(record.sessionMode))) {
|
|
2260
|
-
registry.disarm(sessionId);
|
|
2261
|
-
return "idle";
|
|
2262
|
-
}
|
|
2263
2278
|
const runtimeFacts = deps.runtime.getSessionBusyFacts(sessionId);
|
|
2264
2279
|
const operationStore = deps.runtime.actionContext?.operations;
|
|
2265
2280
|
const linkedOperationIds = linkedOperationIdsForGoal(deps.events.snapshot(sessionId));
|
|
@@ -4283,8 +4298,13 @@ async function dispatchChatRequest(req, res, deps, pathname, workspaceScope) {
|
|
|
4283
4298
|
return true;
|
|
4284
4299
|
}
|
|
4285
4300
|
}
|
|
4286
|
-
//
|
|
4287
|
-
if (await handleBrowserRequest(req, res, {
|
|
4301
|
+
// Shared real-browser API for the Browser panel and chat browser tools.
|
|
4302
|
+
if (await handleBrowserRequest(req, res, {
|
|
4303
|
+
automation: deps.browserAutomation,
|
|
4304
|
+
requireWorkspace: () => workspaceScope
|
|
4305
|
+
? { ok: true, workspaceId: workspaceScope }
|
|
4306
|
+
: { ok: false },
|
|
4307
|
+
}, pathname)) {
|
|
4288
4308
|
return true;
|
|
4289
4309
|
}
|
|
4290
4310
|
if (!pathname.startsWith("/api/operator/v1/chat/") &&
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { prepareScheduledGoalDraft } from "../../scheduler/scheduled-goal-dispatch.js";
|
|
3
|
+
import { scheduledGoalDraftSchema } from "../../scheduler/scheduled-goal-types.js";
|
|
4
|
+
import { scheduledGoalHash } from "../../scheduler/scheduled-goal-store.js";
|
|
5
|
+
import { readScheduledGoalResult } from "../../scheduler/scheduled-goal-recovery.js";
|
|
6
|
+
export const scheduledGoalBookingSchema = scheduledGoalDraftSchema.omit({ prepared: true, controllerRef: true }).extend({ chatSessionRef: z.string().regex(/^[a-zA-Z0-9_-]+$/) });
|
|
7
|
+
export const scheduledGoalConfirmationSchema = z.object({
|
|
8
|
+
goalId: z.string().min(1), stateVersion: z.number().int().positive(),
|
|
9
|
+
envelopeHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
10
|
+
}).strict();
|
|
11
|
+
/** Only non-capability fields are returned to Chat/browser. */
|
|
12
|
+
export function projectScheduledGoalBooking(goal) {
|
|
13
|
+
const { prepared: _prepared, controllerRef, ...scope } = goal.frozen;
|
|
14
|
+
return {
|
|
15
|
+
goalId: goal.goalId, stateVersion: goal.stateVersion, goalRevision: goal.goalRevision,
|
|
16
|
+
envelopeHash: goal.envelopeHash, state: goal.state, scope, controllerRef,
|
|
17
|
+
confirmationRequired: goal.state === "planned",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Preparation is not authorization, even when the clock is already due. */
|
|
21
|
+
export async function prepareScheduledGoalBooking(input, deps, resolveFinalTime) {
|
|
22
|
+
const parsed = scheduledGoalBookingSchema.parse(input);
|
|
23
|
+
const session = await deps.sessions.get(parsed.chatSessionRef);
|
|
24
|
+
if (!session || session.state !== "active")
|
|
25
|
+
throw new Error("booking requires an active Chat session");
|
|
26
|
+
const admitted = await prepareScheduledGoalDraft(parsed, deps.client);
|
|
27
|
+
// High-level relative time starts after slow strict admission, not before it.
|
|
28
|
+
const frozen = resolveFinalTime ? scheduledGoalDraftSchema.parse({ ...admitted, time: resolveFinalTime() }) : admitted;
|
|
29
|
+
const existing = (await deps.goals.list()).find((goal) => goal.frozen.managedTaskId === frozen.managedTaskId && goal.state === "planned" &&
|
|
30
|
+
scheduledGoalHash(goal.frozen) === scheduledGoalHash(frozen));
|
|
31
|
+
const goal = existing ?? await deps.goals.createDraft(frozen);
|
|
32
|
+
return projectScheduledGoalBooking(goal);
|
|
33
|
+
}
|
|
34
|
+
export async function confirmScheduledGoalBooking(input, deps, human) {
|
|
35
|
+
// Provenance is supplied by the authenticated route, never parsed from model input.
|
|
36
|
+
if (human.source !== "human-ui" || !human.requestId?.trim())
|
|
37
|
+
throw new Error("human confirmation required");
|
|
38
|
+
const parsed = scheduledGoalConfirmationSchema.parse(input);
|
|
39
|
+
const goal = await deps.goals.get(parsed.goalId);
|
|
40
|
+
if (goal.approval?.approvalRef === human.requestId && goal.approval.envelopeHash === parsed.envelopeHash &&
|
|
41
|
+
goal.approval.approvedVersion === parsed.stateVersion)
|
|
42
|
+
return projectScheduledGoalBooking(goal);
|
|
43
|
+
const session = await deps.sessions.get(goal.frozen.chatSessionRef);
|
|
44
|
+
if (!session || session.state !== "active")
|
|
45
|
+
throw new Error("booking requires an active Chat session");
|
|
46
|
+
if (goal.envelopeHash !== parsed.envelopeHash || goal.stateVersion !== parsed.stateVersion || goal.state !== "planned")
|
|
47
|
+
throw new Error("booking confirmation binding mismatch");
|
|
48
|
+
// Revalidate controller, strict gate, Task and scope immediately before approval.
|
|
49
|
+
const { prepared: _prepared, controllerRef: _controller, ...scope } = goal.frozen;
|
|
50
|
+
const fresh = await prepareScheduledGoalDraft(scope, deps.client);
|
|
51
|
+
if (scheduledGoalHash(fresh) !== scheduledGoalHash(goal.frozen))
|
|
52
|
+
throw new Error("booking preparation drift");
|
|
53
|
+
const approved = await deps.goals.approve(goal.goalId, parsed.stateVersion, parsed.envelopeHash, human.requestId);
|
|
54
|
+
return projectScheduledGoalBooking(approved);
|
|
55
|
+
}
|
|
56
|
+
export async function getScheduledGoalBooking(goalId, goals) {
|
|
57
|
+
const goal = await goals.get(goalId);
|
|
58
|
+
return { ...projectScheduledGoalBooking(goal), result: await readScheduledGoalResult(goalId, goals) };
|
|
59
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { scheduledGoalDeliveryKey } from "../../scheduler/scheduled-goal-store.js";
|
|
2
|
+
/** The durable Chat record deduplicates a retry after ledger acknowledgement is lost.
|
|
3
|
+
* A missing Chat never gets recreated. The goal remains available through its ledger.
|
|
4
|
+
*/
|
|
5
|
+
export async function deliverScheduledGoalResult(goal, goals, sessions) {
|
|
6
|
+
const key = scheduledGoalDeliveryKey(goal);
|
|
7
|
+
if (goal.deliveryKey === key && goal.deliveryState === "delivered")
|
|
8
|
+
return;
|
|
9
|
+
const evidence = goal.evidence.at(-1);
|
|
10
|
+
const states = {
|
|
11
|
+
accepted: "已验收", cancelled: "已取消", blocked: "已停止", budget_exhausted: "预算用尽", unknown: "需要核对",
|
|
12
|
+
failed: "未通过", pending: "待处理", stale: "证据已失效", succeeded: "通过",
|
|
13
|
+
};
|
|
14
|
+
const label = (value) => states[value] ?? value;
|
|
15
|
+
const text = [
|
|
16
|
+
`预约目标:${goal.frozen.originalGoal}`,
|
|
17
|
+
`目标状态:${label(goal.state)};验收:${label(goal.acceptanceState)}。已执行 ${goal.intents.length} 轮。`,
|
|
18
|
+
...(goal.reasonCode ? ["停止原因与需要核对的运行记录,可在本会话的预约面板查看。"] : []),
|
|
19
|
+
...(evidence ? evidence.assessment.criteria.map((ac) => {
|
|
20
|
+
const criterion = goal.frozen.acceptance.criteria.find((item) => item.acId === ac.acId);
|
|
21
|
+
return `${ac.acId} · ${criterion?.description ?? ac.acId}:${label(ac.status)}${ac.exitCode !== 0 ? `(退出码 ${ac.exitCode ?? "未知"})` : ""}。`;
|
|
22
|
+
}) : []),
|
|
23
|
+
`预约编号:${goal.goalId}。证据和历史保留在预约面板;删除会话不会删除运行事实。`,
|
|
24
|
+
].join("\n\n");
|
|
25
|
+
const delivered = await sessions.appendScheduledGoalResult(goal.frozen.chatSessionRef, key, text);
|
|
26
|
+
await goals.recordDelivery(goal.goalId, key, delivered ? "delivered" : "unavailable");
|
|
27
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { mkdir, open, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { getTaskPaths } from "../../../task/runtime.js";
|
|
8
|
+
import { scheduledGoalDraftSchema, scheduledGoalTimeSchema } from "../../scheduler/scheduled-goal-types.js";
|
|
9
|
+
import { resolveScheduledGoalTime } from "../../scheduler/scheduled-goal-time.js";
|
|
10
|
+
import { ScheduledGoalStore, scheduledGoalHash } from "../../scheduler/scheduled-goal-store.js";
|
|
11
|
+
import { captureScheduledGoalTree } from "../../scheduler/scheduled-goal-evidence.js";
|
|
12
|
+
import { getScheduledGoalBooking, prepareScheduledGoalBooking } from "./scheduled-goal-booking.js";
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
const protectedPaths = [".harness/**", "node_modules/**", ".git/**"];
|
|
15
|
+
const relativePath = z.string().trim().min(1).max(500).refine((value) => !path.isAbsolute(value) && !/^[A-Za-z]:/.test(value) && !value.includes("\\") &&
|
|
16
|
+
!value.split("/").some((part) => part === ".." || part === "." || part === "") && !value.startsWith("-"), "use a relative repository path without traversal");
|
|
17
|
+
const scheduleSchema = z.object({
|
|
18
|
+
delayMinutes: z.number().positive().max(525600).optional().describe("Delay AFTER preparation finishes; use for '3 minutes later'. The confirmation card shows the resulting exact time."),
|
|
19
|
+
localAt: z.string().regex(/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}$/).optional().describe("Explicit local date/time YYYY-MM-DD HH:mm; never silently move a past date."),
|
|
20
|
+
timezone: z.string().min(1).optional().describe("IANA timezone, e.g. Asia/Shanghai for Beijing. Defaults to the Console host timezone."),
|
|
21
|
+
fold: z.enum(["earlier", "later"]).optional(),
|
|
22
|
+
allowedHours: z.array(z.string()).optional().describe("Local allowed windows; default [] means all day."),
|
|
23
|
+
startGraceMinutes: z.number().positive().max(1440).optional(),
|
|
24
|
+
maxDurationMinutes: z.number().positive().max(1440).optional(),
|
|
25
|
+
}).strict().refine((value) => (value.delayMinutes !== undefined) !== (value.localAt !== undefined), "choose exactly one of delayMinutes or localAt");
|
|
26
|
+
/** The model supplies business meaning, never runtime identity or authorization. */
|
|
27
|
+
export const scheduledTaskRequestSchema = z.object({
|
|
28
|
+
originalGoal: z.string().trim().min(1).max(24000).describe("Faithfully preserve the complete user goal and constraints. Do not add unrequested failure cases, mocks, filenames or acceptance obligations; preserve real-service testing requirements."),
|
|
29
|
+
workflow: scheduledGoalDraftSchema.shape.workflow,
|
|
30
|
+
allowedPaths: z.array(relativePath).min(1).max(100).describe("Business outputs in the CURRENT project directory. For a read-only inspection, allow only a report such as scheduled-report.md, never .harness/."),
|
|
31
|
+
forbiddenPaths: z.array(relativePath).max(100).optional(),
|
|
32
|
+
acceptance: scheduledGoalDraftSchema.shape.acceptance.describe("Use minimal independent executable checks with AC-1, AC-2 IDs. Each command must exit nonzero on failure. For backend-test, acceptance must only inspect the finished run and must never invoke pytest. Prefer an existing read-only verification command or test -s report.md. Do not invent extra requirements, language-specific grep checks, shell pipelines, command substitution, redirects or compound shell statements. The host already enforces write scope."),
|
|
33
|
+
budget: scheduledGoalDraftSchema.shape.budget.optional().describe("Omit for ordinary tasks: defaults to one DAG run, one goal attempt and one supervisor call. maxTurns counts goal attempts, NOT model tool calls; maxSupervisorCalls does NOT count polling. Always displayed before confirmation."),
|
|
34
|
+
schedule: scheduleSchema,
|
|
35
|
+
}).strict();
|
|
36
|
+
export function resolveScheduledTaskTime(input, now) {
|
|
37
|
+
const schedule = scheduleSchema.parse(input);
|
|
38
|
+
const timezone = schedule.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
39
|
+
const executeAtUtc = schedule.localAt
|
|
40
|
+
? resolveScheduledGoalTime({ localAt: schedule.localAt, timezone, fold: schedule.fold })
|
|
41
|
+
: new Date(now.getTime() + schedule.delayMinutes * 60000).toISOString();
|
|
42
|
+
if (Date.parse(executeAtUtc) <= now.getTime())
|
|
43
|
+
throw new Error("requested time is in the past; provide a new future time (不会自动顺延)");
|
|
44
|
+
const latestStart = Date.parse(executeAtUtc) + (schedule.startGraceMinutes ?? 15) * 60000;
|
|
45
|
+
const stop = latestStart + (schedule.maxDurationMinutes ?? 60) * 60000;
|
|
46
|
+
return scheduledGoalTimeSchema.parse({ executeAtUtc, latestStartUtc: new Date(latestStart).toISOString(),
|
|
47
|
+
stopAtUtc: new Date(stop).toISOString(), expiresAt: new Date(stop).toISOString(), timezone,
|
|
48
|
+
allowedHours: schedule.allowedHours ?? [], misfirePolicy: "run-immediately" });
|
|
49
|
+
}
|
|
50
|
+
const git = async (cwd, ...args) => (await execFileAsync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 30000, maxBuffer: 4 * 1024 * 1024 })).stdout.trim();
|
|
51
|
+
const sameSet = (a, b) => JSON.stringify([...new Set(a)].sort()) === JSON.stringify([...new Set(b)].sort());
|
|
52
|
+
function assertScheduledAcceptanceDoesNotRepeatBackendTests(request) {
|
|
53
|
+
if (request.workflow !== "backend-test")
|
|
54
|
+
return;
|
|
55
|
+
const repeated = request.acceptance.criteria.find((criterion) => /(?:^|\s)(?:python(?:3)?\s+-m\s+)?pytest\b/i.test(criterion.command));
|
|
56
|
+
if (repeated)
|
|
57
|
+
throw new Error(`backend-test acceptance must not invoke pytest; ${repeated.acId} must inspect the completed run instead`);
|
|
58
|
+
}
|
|
59
|
+
/** Synchronous PREPARATION only. CLI calls are bounded and NEVER approve a DAG.
|
|
60
|
+
* A durable exclusive request marker prevents retries from creating duplicate Tasks
|
|
61
|
+
* or overwriting imported source. Unknown interrupted preparation is not reclaimed.
|
|
62
|
+
*/
|
|
63
|
+
export async function prepareScheduledTask(input, deps) {
|
|
64
|
+
const request = scheduledTaskRequestSchema.parse(input);
|
|
65
|
+
assertScheduledAcceptanceDoesNotRepeatBackendTests(request);
|
|
66
|
+
for (const ref of request.allowedPaths) {
|
|
67
|
+
if (/^(?:\.harness|\.git|node_modules)(?:\/|$)/.test(ref) || ref.includes("*") && !/^[\w.-]+\//.test(ref))
|
|
68
|
+
throw new Error(`protected or unbounded output path ${ref}; use an ordinary bounded report/output path, never runtime directories`);
|
|
69
|
+
}
|
|
70
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(deps.sessionId) || (await deps.sessions.get(deps.sessionId))?.state !== "active")
|
|
71
|
+
throw new Error("preparation requires an active bound Chat session");
|
|
72
|
+
const identity = deps.client.getIdentity();
|
|
73
|
+
if (!identity)
|
|
74
|
+
throw new Error("preparation requires a pinned controller");
|
|
75
|
+
const repoRoot = await realpath(deps.repoRoot);
|
|
76
|
+
const now = deps.now ?? (() => new Date());
|
|
77
|
+
const key = scheduledGoalHash({ repoRoot, sessionId: deps.sessionId, request });
|
|
78
|
+
// Keep generated paths compact: runtimes/extensions may encode the whole cwd
|
|
79
|
+
// into one filename. The full hash below still verifies receipt identity.
|
|
80
|
+
const directory = path.join(deps.preparationRoot, key.slice(0, 24));
|
|
81
|
+
const recordPath = path.join(directory, "preparation.json");
|
|
82
|
+
const goals = new ScheduledGoalStore(repoRoot);
|
|
83
|
+
const existing = await readFile(recordPath, "utf8").then((text) => JSON.parse(text)).catch((error) => {
|
|
84
|
+
if (error.code === "ENOENT")
|
|
85
|
+
return undefined;
|
|
86
|
+
throw error;
|
|
87
|
+
});
|
|
88
|
+
if (existing) {
|
|
89
|
+
if (existing.version !== 1 || existing.requestHash !== key)
|
|
90
|
+
throw new Error("preparation record binding mismatch");
|
|
91
|
+
if (existing.goalId)
|
|
92
|
+
return getScheduledGoalBooking(existing.goalId, goals);
|
|
93
|
+
// Covers a crash after the goal ledger commit but before the local receipt.
|
|
94
|
+
const committed = (await goals.list()).find((goal) => goal.frozen.managedTaskId === existing.taskId && goal.frozen.chatSessionRef === deps.sessionId);
|
|
95
|
+
if (committed)
|
|
96
|
+
return getScheduledGoalBooking(committed.goalId, goals);
|
|
97
|
+
throw new Error(`previous preparation ${existing.stage}: ${existing.error ?? "still running or interrupted; do not duplicate or edit its artifacts"}; evidence: ${recordPath}`);
|
|
98
|
+
}
|
|
99
|
+
// Replays return their durable result even after the requested time has passed.
|
|
100
|
+
// New requests validate time before creating artifacts; relative time is also
|
|
101
|
+
// resolved after preparation so preparation cannot consume the delay.
|
|
102
|
+
resolveScheduledTaskTime(request.schedule, now());
|
|
103
|
+
// Check before creating a Task or receipt; never clean or reset the user's workspace.
|
|
104
|
+
const base = await git(repoRoot, "rev-parse", "HEAD");
|
|
105
|
+
const initial = await captureScheduledGoalTree(repoRoot, base, request.allowedPaths, protectedPaths);
|
|
106
|
+
if (initial.changedPaths.length)
|
|
107
|
+
throw new Error(`current workspace has uncommitted changes: ${initial.changedPaths.slice(0, 10).join(", ")}. Resolve them before booking; do not automatically commit, stash or reset user changes.`);
|
|
108
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
109
|
+
const lockPath = path.join(directory, "preparing.lock");
|
|
110
|
+
const lock = await open(lockPath, "wx", 0o600).catch((error) => {
|
|
111
|
+
if (error.code === "EEXIST")
|
|
112
|
+
throw new Error("preparation already running; do not duplicate or take over");
|
|
113
|
+
throw error;
|
|
114
|
+
});
|
|
115
|
+
const record = { version: 1, requestHash: key, stage: "workspace", taskId: `scheduled-${key.slice(0, 24)}`, worktree: repoRoot };
|
|
116
|
+
const persist = async () => {
|
|
117
|
+
const temporary = `${recordPath}.${randomUUID()}.tmp`;
|
|
118
|
+
await writeFile(temporary, JSON.stringify(record, null, 2), { mode: 0o600 });
|
|
119
|
+
await rename(temporary, recordPath);
|
|
120
|
+
};
|
|
121
|
+
let started = false;
|
|
122
|
+
try {
|
|
123
|
+
// Recheck after acquiring the lock: another request may have finished between
|
|
124
|
+
// our initial read and lock acquisition.
|
|
125
|
+
const raced = await readFile(recordPath, "utf8").catch((e) => { if (e.code === "ENOENT")
|
|
126
|
+
return undefined; throw e; });
|
|
127
|
+
if (raced)
|
|
128
|
+
throw new Error("preparation changed concurrently; retry reading the existing request");
|
|
129
|
+
started = true;
|
|
130
|
+
await persist();
|
|
131
|
+
const forbiddenPaths = [...new Set([...protectedPaths, ...(request.forbiddenPaths ?? [])])];
|
|
132
|
+
const prdPath = path.join(directory, "request.md");
|
|
133
|
+
const prd = ["# Scheduled task", "## Goal", request.originalGoal,
|
|
134
|
+
"## Execution context", `Execution and all outputs: current project directory ${record.worktree}.`,
|
|
135
|
+
"Inspect this project directly. Write only the authorized outputs here; for read-only inspection, write only the requested report. Implementation changes remain in this directory for the user to review. Do not create another worktree, copy outputs elsewhere, or reset/stash existing changes. The confirmed Git base must remain unchanged until execution finishes.",
|
|
136
|
+
"## Acceptance criteria", ...request.acceptance.criteria.map((ac) => `- ${ac.acId}: ${ac.description}\n Verification: ${ac.command}`),
|
|
137
|
+
"## Boundaries", `Workflow: ${request.workflow}; policy: ${request.acceptance.policy}.`,
|
|
138
|
+
`Allowed outputs: ${JSON.stringify(request.allowedPaths)}. Forbidden outputs: ${JSON.stringify(forbiddenPaths)}.`,
|
|
139
|
+
"Never modify imported requirements, .harness runtime, dependencies, Git history or configuration. No push, merge or publication. The host delivers the final result to the original Chat; do not implement a second delivery/scheduling mechanism.",
|
|
140
|
+
"## Verification", ...request.acceptance.criteria.map((ac) => ac.command), ""].join("\n\n");
|
|
141
|
+
await writeFile(prdPath, prd, { flag: "wx", mode: 0o600 });
|
|
142
|
+
const boundary = request.allowedPaths.flatMap((ref) => ["--allowed-path", ref])
|
|
143
|
+
.concat(forbiddenPaths.flatMap((ref) => ["--forbidden-path", ref]))
|
|
144
|
+
.concat(request.acceptance.criteria.flatMap((ac) => ["--verify", `${ac.acId}:${ac.command}`]));
|
|
145
|
+
for (let step = 0; step < 2; step++) {
|
|
146
|
+
record.stage = step === 0 ? "task-import" : "task-prepare";
|
|
147
|
+
await persist();
|
|
148
|
+
const args = ["task", "advance", record.taskId, ...(step === 0 ? ["Scheduled task", "--prd", prdPath] : []),
|
|
149
|
+
"--task-kind", request.workflow, ...boundary, "--json"];
|
|
150
|
+
const result = await deps.client.run(args, { cwd: record.worktree, artifactName: `${record.taskId}-${step}`, expectJson: true, timeoutMs: 300000 });
|
|
151
|
+
if (!result.ok || result.exitCode !== 0 || result.timedOut) {
|
|
152
|
+
const detail = JSON.stringify(result.json ?? result.stderr ?? "no CLI evidence").slice(0, 6000);
|
|
153
|
+
throw new Error(`${record.stage} failed: ${detail}. Correct the request and re-import through this tool; never edit source/references or runtime files.`);
|
|
154
|
+
}
|
|
155
|
+
const envelope = result.json;
|
|
156
|
+
if (envelope?.ok !== true)
|
|
157
|
+
throw new Error(`${record.stage}: missing successful CLI envelope`);
|
|
158
|
+
if (envelope.result?.lifecycleState === "awaiting-write-set-approval")
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
record.stage = "booking";
|
|
162
|
+
await persist();
|
|
163
|
+
// Freeze actual strict-prepared path ordering/default restrictions. Never adopt
|
|
164
|
+
// a broader allowed set or drop a caller restriction to make admission pass.
|
|
165
|
+
const config = z.object({ taskKind: z.string(), allowedPaths: z.array(z.string()), forbiddenPaths: z.array(z.string()) }).parse(JSON.parse(await readFile(getTaskPaths(record.worktree, record.taskId).taskConfigPath, "utf8")));
|
|
166
|
+
if (config.taskKind !== request.workflow || !sameSet(config.allowedPaths, request.allowedPaths) || forbiddenPaths.some((ref) => !config.forbiddenPaths.includes(ref)))
|
|
167
|
+
throw new Error("prepared task scope differs from request; refusing to widen scope");
|
|
168
|
+
if (deps.client.getIdentity()?.packageFingerprint.value !== identity.packageFingerprint.value)
|
|
169
|
+
throw new Error("controller changed during preparation");
|
|
170
|
+
const booking = await prepareScheduledGoalBooking({ managedTaskId: record.taskId, originalGoal: request.originalGoal,
|
|
171
|
+
chatSessionRef: deps.sessionId, workflow: request.workflow, worktreeRef: record.worktree, frozenBase: base,
|
|
172
|
+
allowedPaths: config.allowedPaths, forbiddenPaths: config.forbiddenPaths, acceptance: request.acceptance,
|
|
173
|
+
budget: request.budget ?? { maxTurns: 1, maxDagRuns: 1, maxSupervisorCalls: 1 }, time: resolveScheduledTaskTime(request.schedule, now()),
|
|
174
|
+
}, { goals, client: deps.client, sessions: deps.sessions }, () => resolveScheduledTaskTime(request.schedule, now()));
|
|
175
|
+
record.goalId = booking.goalId;
|
|
176
|
+
record.stage = "planned";
|
|
177
|
+
await persist();
|
|
178
|
+
return booking;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
182
|
+
if (started)
|
|
183
|
+
await persist();
|
|
184
|
+
throw new Error(`Scheduled task preparation stopped at ${record.stage}: ${record.error}`);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
await lock.close();
|
|
188
|
+
await unlink(lockPath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -144,7 +144,7 @@ export class SddDataAlignmentError extends Error {
|
|
|
144
144
|
}
|
|
145
145
|
export function selectRandomSddSkillName(random = Math.random) {
|
|
146
146
|
const index = Math.min(SDD_SKILL_NAMES.length - 1, Math.max(0, Math.floor(random() * SDD_SKILL_NAMES.length)));
|
|
147
|
-
return SDD_SKILL_NAMES[index];
|
|
147
|
+
return SDD_SKILL_NAMES[index] ?? "";
|
|
148
148
|
}
|
|
149
149
|
export function normalizeSddSkillName(value, random = Math.random) {
|
|
150
150
|
if (value !== undefined && typeof value !== "string") {
|
|
@@ -27,21 +27,22 @@ export const SESSION_MODE_COPY = {
|
|
|
27
27
|
description: "保留压缩与 Explore,直接协作编码",
|
|
28
28
|
createLabel: "新建 Pi 会话",
|
|
29
29
|
bannerTitle: "Pi",
|
|
30
|
-
bannerBody: "保留 Console
|
|
30
|
+
bannerBody: "保留 Console 压缩、Explore 与持续目标,直接协作编码。不注入工作流业务指令与业务工具。",
|
|
31
31
|
landingTitle: "开始一段 Pi 协作",
|
|
32
|
-
landingBody: "Pi 模式保留压缩、Explore
|
|
32
|
+
landingBody: "Pi 模式保留压缩、Explore、持续目标、认证与 SDD 接入,取消工作流业务注入。适合有人监督的直接编码与调试。",
|
|
33
33
|
emptyTitle: "直接协作编码",
|
|
34
34
|
emptyBody: "会话已就绪。输入框上方可切换工作区或模式。使用 Pi 工具读写仓库;Explore 负责只读深挖。本会话不加载任务 / DAG 工作流工具。",
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
37
|
/** Slash actions that only exist in workflow sessions. Browser-safe: pure
|
|
38
|
-
* string constants, no runtime/tool policy imports.
|
|
38
|
+
* string constants, no runtime/tool policy imports. `/goal` was moved to
|
|
39
|
+
* shared on 2026-09-10 (exec-plan 2026-09-10-console-chat-goal-for-pi-mode);
|
|
40
|
+
* interview/panel navigation remain workflow-only. */
|
|
39
41
|
export const WORKFLOW_EXCLUSIVE_SLASH_ACTIONS = Object.freeze([
|
|
40
42
|
"focus-panel:task",
|
|
41
43
|
"focus-panel:contract",
|
|
42
44
|
"focus-panel:dag",
|
|
43
45
|
"focus:interview",
|
|
44
|
-
"goal",
|
|
45
46
|
]);
|
|
46
47
|
export function isWorkflowExclusiveSlashAction(action) {
|
|
47
48
|
return WORKFLOW_EXCLUSIVE_SLASH_ACTIONS.includes(action);
|
|
@@ -74,7 +74,9 @@ export const PI_CHAT_SYSTEM_PROMPT_BASE = [
|
|
|
74
74
|
"Use read/write/edit/bash/grep/find/ls to inspect and change the workspace. apply_patch, full-tools, shell and coding-chat are denied.",
|
|
75
75
|
"Agent (Explore) is a built-in read-only subagent for path-uncertain codebase search. Do not use it for a known single-file read. Prefer safe-read/safe-grep for secret-safe probes.",
|
|
76
76
|
"ask_user_question pauses for a real user answer. todo_write tracks the current work and is restored after compression; it is not a workflow auto-continue signal. The persistent terminal tool is available.",
|
|
77
|
-
"
|
|
77
|
+
"Browser tools (navigate/search/snapshot/act/tabs/scroll/press/dialog/wait/screenshot) drive the workspace-controlled real browser shown in the Browser panel. Use refs from the latest page inspection for element actions; they never edit workspace files or execute page scripts. browser_snapshot means browser page inspection, not workspace snapshots.",
|
|
78
|
+
"Goal tools (get_goal / create_goal / update_goal) track the session's durable cross-turn objective. Create a Goal only when the user explicitly asks to start one; iterate in bounded rounds until complete or blocked.",
|
|
79
|
+
"This session keeps Console compression (L0/L1/L2, sift, strict checkpoint) and existing Aimax / SDD access. Do not create tasks, run DAGs, or start requirement interviews from this session.",
|
|
78
80
|
"Obey repository AGENTS.md, skills and extensions when they apply. Choosing Pi mode is not extra write authorization and does not bypass Console access control or output scrubbing.",
|
|
79
81
|
].join("\n");
|
|
80
82
|
export const CHAT_SAFE_EXPLORE_TOOL_IDS = Object.freeze([
|
|
@@ -87,7 +89,11 @@ export const PI_CHAT_GENERIC_HELPER_TOOLS = Object.freeze([
|
|
|
87
89
|
"ask_user_question",
|
|
88
90
|
"todo_write",
|
|
89
91
|
]);
|
|
90
|
-
|
|
92
|
+
/** Durable cross-turn Goal tools. Shared by both session modes since
|
|
93
|
+
* 2026-09-10 (exec-plan 2026-09-10-console-chat-goal-for-pi-mode); they are
|
|
94
|
+
* NOT workflow business tools and are registered whenever a GoalRegistry
|
|
95
|
+
* is present. */
|
|
96
|
+
export const GOAL_TOOL_IDS = Object.freeze([
|
|
91
97
|
"get_goal",
|
|
92
98
|
"create_goal",
|
|
93
99
|
"update_goal",
|
|
@@ -102,7 +108,6 @@ const CHAT_EXTENSION_DENIED_TOOL_IDS = new Set([
|
|
|
102
108
|
"shell",
|
|
103
109
|
].map((name) => name.toLowerCase()));
|
|
104
110
|
const WORKFLOW_OPERATOR_TOOL_IDS = new Set(OPERATOR_CHAT_ALLOWED_TOOLS.map((name) => name.toLowerCase()));
|
|
105
|
-
const PI_GOAL_TOOL_IDS = new Set(PI_CHAT_WORKFLOW_TOOL_IDS.map((name) => name.toLowerCase()));
|
|
106
111
|
export function workflowActiveToolBaseline() {
|
|
107
112
|
return [
|
|
108
113
|
...OPERATOR_CHAT_ALLOWED_TOOLS,
|
|
@@ -117,6 +122,7 @@ export function piActiveToolBaseline() {
|
|
|
117
122
|
...CHAT_SAFE_EXPLORE_TOOL_IDS,
|
|
118
123
|
...OPERATOR_CHAT_BUILTIN_CUSTOM_TOOLS,
|
|
119
124
|
...PI_CHAT_GENERIC_HELPER_TOOLS,
|
|
125
|
+
...GOAL_TOOL_IDS,
|
|
120
126
|
];
|
|
121
127
|
}
|
|
122
128
|
export function chatActiveToolBaseline(sessionMode) {
|
|
@@ -126,10 +132,13 @@ export function chatActiveToolBaseline(sessionMode) {
|
|
|
126
132
|
}
|
|
127
133
|
/**
|
|
128
134
|
* Gate 2 pin: mode baseline plus user/project extension tools. Pi mode must
|
|
129
|
-
* not re-add operator actions
|
|
135
|
+
* not re-add operator actions via the extras pass (Goal tools are shared and
|
|
136
|
+
* part of the Pi baseline since 2026-09-10).
|
|
130
137
|
*/
|
|
131
|
-
export function computeChatActiveToolNames(session, sessionMode) {
|
|
132
|
-
const
|
|
138
|
+
export function computeChatActiveToolNames(session, sessionMode, exposedWorkflowTools) {
|
|
139
|
+
const exposure = sessionMode === "workflow" && exposedWorkflowTools
|
|
140
|
+
? new Set(exposedWorkflowTools.map(name => name.toLowerCase())) : undefined;
|
|
141
|
+
const baseline = chatActiveToolBaseline(sessionMode).filter(name => !exposure || !WORKFLOW_OPERATOR_TOOL_IDS.has(name.toLowerCase()) || exposure.has(name.toLowerCase()));
|
|
133
142
|
if (!session?.getAllTools)
|
|
134
143
|
return baseline;
|
|
135
144
|
const baselineLower = new Set(baseline.map((name) => name.toLowerCase()));
|
|
@@ -141,11 +150,11 @@ export function computeChatActiveToolNames(session, sessionMode) {
|
|
|
141
150
|
const lower = name.toLowerCase();
|
|
142
151
|
if (baselineLower.has(lower))
|
|
143
152
|
continue;
|
|
144
|
-
if (CHAT_EXTENSION_DENIED_TOOL_IDS.has(lower))
|
|
153
|
+
if (CHAT_EXTENSION_DENIED_TOOL_IDS.has(lower) || isOperatorChatToolDenied(name))
|
|
145
154
|
continue;
|
|
146
155
|
if (sessionMode === "pi" && WORKFLOW_OPERATOR_TOOL_IDS.has(lower))
|
|
147
156
|
continue;
|
|
148
|
-
if (
|
|
157
|
+
if (exposure && WORKFLOW_OPERATOR_TOOL_IDS.has(lower) && !exposure.has(lower))
|
|
149
158
|
continue;
|
|
150
159
|
extras.push(name);
|
|
151
160
|
}
|
|
@@ -172,7 +181,7 @@ export function authorizeChatSessionTool(toolId, sessionMode) {
|
|
|
172
181
|
};
|
|
173
182
|
}
|
|
174
183
|
const lower = id.toLowerCase();
|
|
175
|
-
if (WORKFLOW_OPERATOR_TOOL_IDS.has(lower)
|
|
184
|
+
if (WORKFLOW_OPERATOR_TOOL_IDS.has(lower)) {
|
|
176
185
|
return {
|
|
177
186
|
ok: false,
|
|
178
187
|
code: "tool-denied",
|
|
@@ -207,9 +216,6 @@ export { WORKFLOW_EXCLUSIVE_SLASH_ACTIONS, isWorkflowExclusiveSlashAction, } fro
|
|
|
207
216
|
export function shouldLoadWorkflowBusinessTools(sessionMode) {
|
|
208
217
|
return sessionMode === "workflow";
|
|
209
218
|
}
|
|
210
|
-
export function shouldInjectGoalContinuation(sessionMode) {
|
|
211
|
-
return sessionMode === "workflow";
|
|
212
|
-
}
|
|
213
219
|
export function shouldAcceptOperatorSnapshot(sessionMode) {
|
|
214
220
|
return sessionMode === "workflow";
|
|
215
221
|
}
|
|
@@ -477,6 +477,24 @@ export class ChatSessionStore {
|
|
|
477
477
|
};
|
|
478
478
|
});
|
|
479
479
|
}
|
|
480
|
+
/** Idempotent persistent delivery, serialized with normal Chat updates. */
|
|
481
|
+
async appendScheduledGoalResult(sessionId, resultKey, text) {
|
|
482
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(sessionId) || !/^[a-f0-9]{64}$/.test(resultKey))
|
|
483
|
+
throw new Error("invalid scheduled goal delivery identity");
|
|
484
|
+
return this.serialize(sessionId, async () => {
|
|
485
|
+
const record = await this.get(sessionId);
|
|
486
|
+
if (!record)
|
|
487
|
+
return false;
|
|
488
|
+
const id = `scheduled-goal-${resultKey}`;
|
|
489
|
+
if (record.messages.some((message) => message.id === id))
|
|
490
|
+
return true;
|
|
491
|
+
const at = new Date().toISOString();
|
|
492
|
+
record.messages.push({ id, role: "assistant", text, at });
|
|
493
|
+
record.updatedAt = at;
|
|
494
|
+
await this.persist(record);
|
|
495
|
+
return true;
|
|
496
|
+
});
|
|
497
|
+
}
|
|
480
498
|
async appendMessage(sessionId, message) {
|
|
481
499
|
return this.serialize(sessionId, async () => {
|
|
482
500
|
const record = await this.get(sessionId);
|
|
@@ -637,21 +655,36 @@ export async function runChatTurn(options) {
|
|
|
637
655
|
// never observes agent_settled before its accepted operation ref.
|
|
638
656
|
settledEvent = event;
|
|
639
657
|
}
|
|
658
|
+
else if (event.type === "message_end") {
|
|
659
|
+
// Persist the assistant bubble FIRST so the SSE frame and the restore
|
|
660
|
+
// projection share the same durable message id (2026-09-13). The text-
|
|
661
|
+
// match + 30s window in projectStoredMessagesWithEventOrder was the only
|
|
662
|
+
// bridge left between the two, and it mis-aligned on duplicate text.
|
|
663
|
+
let messageId;
|
|
664
|
+
try {
|
|
665
|
+
const stored = await store.appendMessage(sessionId, {
|
|
666
|
+
role: "assistant",
|
|
667
|
+
text: event.text,
|
|
668
|
+
...(event.content && event.content.length > 0
|
|
669
|
+
? { content: event.content }
|
|
670
|
+
: {}),
|
|
671
|
+
});
|
|
672
|
+
messageId = stored.id;
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
// Persistence failure must not swallow the SSE frame — the client
|
|
676
|
+
// still needs the terminal text even if the durable row is absent.
|
|
677
|
+
}
|
|
678
|
+
options.onEvent?.(messageId ? { ...event, messageId } : event);
|
|
679
|
+
}
|
|
640
680
|
else {
|
|
641
681
|
options.onEvent?.(event);
|
|
642
682
|
}
|
|
643
683
|
switch (event.type) {
|
|
644
684
|
case "message_update":
|
|
645
685
|
case "message_end":
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
role: "assistant",
|
|
649
|
-
text: event.text,
|
|
650
|
-
...(event.content && event.content.length > 0
|
|
651
|
-
? { content: event.content }
|
|
652
|
-
: {}),
|
|
653
|
-
}));
|
|
654
|
-
}
|
|
686
|
+
// message_update carries no durable row; message_end was already
|
|
687
|
+
// persisted + framed above. Nothing further to persist here.
|
|
655
688
|
break;
|
|
656
689
|
case "tool_call":
|
|
657
690
|
// Forwarded to the caller (SSE) for UI feedback. The actual tool
|