@tea-agent/loop-agent 0.1.0-fe-test.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 +114 -0
- package/CHANGELOG.md +2283 -0
- package/README.md +177 -0
- package/bin/agent-worker.js +22 -0
- package/bin/loop-agent.js +57 -0
- package/dist/adapters/aimax.js +91 -0
- package/dist/adapters/context.js +32 -0
- package/dist/adapters/index.js +29 -0
- package/dist/adapters/loop-agent.js +192 -0
- package/dist/adapters/types.js +1 -0
- package/dist/application/context-usage/skill-resolution-stats.js +263 -0
- package/dist/application/dag/args.js +486 -0
- package/dist/application/dag/generate-task-dag.js +408 -0
- package/dist/application/dag/report-dag.js +14 -0
- package/dist/application/dag/run-dag.js +109 -0
- package/dist/application/dag/validate-dag.js +166 -0
- package/dist/application/evaluation/alias.js +184 -0
- package/dist/application/evaluation/budget.js +192 -0
- package/dist/application/evaluation/campaign-hash.js +47 -0
- package/dist/application/evaluation/campaign-matrix.js +372 -0
- package/dist/application/evaluation/campaign-scorecard.js +135 -0
- package/dist/application/evaluation/campaign.js +370 -0
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +69 -0
- package/dist/application/evaluation/corpus-hash.js +38 -0
- package/dist/application/evaluation/corpus.js +56 -0
- package/dist/application/evaluation/experiment.js +294 -0
- package/dist/application/evaluation/ignition.js +198 -0
- package/dist/application/evaluation/integrity-audit.js +162 -0
- package/dist/application/evaluation/outer-loop.js +132 -0
- package/dist/application/evaluation/pi-cell-executor.js +39 -0
- package/dist/application/evaluation/private-verifier.js +46 -0
- package/dist/application/evaluation/promotion-policy.js +151 -0
- package/dist/application/evaluation/proposer.js +98 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +652 -0
- package/dist/application/loop/run-action.js +19 -0
- package/dist/application/task-lifecycle/advance.js +1204 -0
- package/dist/application/task-lifecycle/gates.js +130 -0
- package/dist/application/task-lifecycle/index.js +7 -0
- package/dist/application/task-lifecycle/observe.js +424 -0
- package/dist/application/task-lifecycle/plan-transitions.js +251 -0
- package/dist/application/task-lifecycle/recommendations.js +180 -0
- package/dist/application/task-lifecycle/record.js +73 -0
- package/dist/application/task-lifecycle/types.js +1 -0
- package/dist/build-stamp.json +6 -0
- package/dist/cli/catalog.js +24 -0
- package/dist/cli/command-definitions.js +659 -0
- package/dist/cli/help.js +56 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/program.js +647 -0
- package/dist/cli/router.js +13 -0
- package/dist/cli/update/init-surface-notifier.js +167 -0
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +92 -0
- package/dist/cli/update/runtime-activity.js +29 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli-governance/active-residue-check.js +38 -0
- package/dist/cli.js +57 -0
- package/dist/commands/client-recovery.js +1373 -0
- package/dist/commands/closeout.js +13 -0
- package/dist/commands/coverage-audit.js +14 -0
- package/dist/commands/coverage-report.js +50 -0
- package/dist/commands/cursor-prompt.js +182 -0
- package/dist/commands/dag-approve.js +142 -0
- package/dist/commands/dag-final-verification.js +76 -0
- package/dist/commands/dag-init-hybrid.js +57 -0
- package/dist/commands/dag-reconcile-run.js +5 -0
- package/dist/commands/dag-reconcile-tasks.js +51 -0
- package/dist/commands/dag-reject.js +91 -0
- package/dist/commands/dag-report.js +76 -0
- package/dist/commands/dag-request-interrupt.js +20 -0
- package/dist/commands/dag-rerun-task.js +19 -0
- package/dist/commands/dag-rerun.js +111 -0
- package/dist/commands/dag-resume.js +35 -0
- package/dist/commands/dag-run-task.js +12 -0
- package/dist/commands/dag-validate.js +20 -0
- package/dist/commands/dag-workflow-compile.js +91 -0
- package/dist/commands/dag-workflow-plan.js +130 -0
- package/dist/commands/dag-workflow-validate.js +66 -0
- package/dist/commands/delegate.js +130 -0
- package/dist/commands/docs-archive.js +5 -0
- package/dist/commands/docs-audit.js +6 -0
- package/dist/commands/doctor.js +283 -0
- package/dist/commands/eval.js +1398 -0
- package/dist/commands/examples.js +90 -0
- package/dist/commands/goal.js +92 -0
- package/dist/commands/handoff-check.js +5 -0
- package/dist/commands/harvest.js +44 -0
- package/dist/commands/import-prd.js +81 -0
- package/dist/commands/init-upgrade.js +3085 -0
- package/dist/commands/init.js +3630 -0
- package/dist/commands/inspect.js +11 -0
- package/dist/commands/instructions.js +223 -0
- package/dist/commands/knowledge.js +162 -0
- package/dist/commands/loop-benchmark.js +72 -0
- package/dist/commands/loop.js +251 -0
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/operator.js +44 -0
- package/dist/commands/pi-prompt.js +174 -0
- package/dist/commands/pi-reuse-benchmark.js +153 -0
- package/dist/commands/plan-list.js +5 -0
- package/dist/commands/plan.js +50 -0
- package/dist/commands/promote-run.js +29 -0
- package/dist/commands/reference-index.js +16 -0
- package/dist/commands/run-dag-progress.js +123 -0
- package/dist/commands/run-dag.js +22 -0
- package/dist/commands/spine.js +38 -0
- package/dist/commands/stats.js +113 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/study-init.js +192 -0
- package/dist/commands/task-advance.js +366 -0
- package/dist/commands/task-contract.js +269 -0
- package/dist/commands/task-source-prepare.js +479 -0
- package/dist/commands/task-status.js +133 -0
- package/dist/commands/workflow.js +259 -0
- package/dist/commands/worktree-create.js +31 -0
- package/dist/commands/worktree-list.js +5 -0
- package/dist/commands/worktree-remove.js +26 -0
- package/dist/executors/config-core.js +5 -0
- package/dist/executors/config.js +2 -0
- package/dist/executors/dag-pi-executor.js +1277 -0
- package/dist/executors/dag-static-executor.js +42 -0
- package/dist/executors/dag.js +3 -0
- package/dist/executors/index.js +6 -0
- package/dist/executors/model-routing.js +71 -0
- package/dist/executors/pi-defaults.js +9 -0
- package/dist/executors/pi-event-serializer.js +65 -0
- package/dist/executors/pi-executor.js +981 -0
- package/dist/executors/pi-playwright-cli-tool.js +1006 -0
- package/dist/executors/pi-prompt-transport.js +198 -0
- package/dist/executors/pi-reuse-benchmark.js +316 -0
- package/dist/executors/pi-runtime-reuse.js +29 -0
- package/dist/executors/pi-sdk-executor.js +722 -0
- package/dist/executors/pi-sdk.js +1 -0
- package/dist/executors/pi-writer-tool-policy.js +266 -0
- package/dist/executors/pi.js +3 -0
- package/dist/executors/playwright-cli-launcher.js +63 -0
- package/dist/executors/process-tree.js +33 -0
- package/dist/executors/shell-executor.js +3055 -0
- package/dist/executors/shell-presets.js +99 -0
- package/dist/executors/shell-verification.js +262 -0
- package/dist/executors/shell-write-guard.js +543 -0
- package/dist/executors/shell.js +3 -0
- package/dist/executors/static.js +1 -0
- package/dist/governance/checks.js +437 -0
- package/dist/governance/document-index-closure.js +164 -0
- package/dist/governance/exec-plans.js +549 -0
- package/dist/governance/harness.js +9 -0
- package/dist/governance/index.js +3 -0
- package/dist/governance/manifest-types.js +254 -0
- package/dist/governance/manifest.js +2 -0
- package/dist/governance/path-guard.js +69 -0
- package/dist/governance/path-guards.js +2 -0
- package/dist/governance/profiles.js +3 -0
- package/dist/governance/requirement-coverage.js +425 -0
- package/dist/governance/skill-safety.js +135 -0
- package/dist/governance/spine-audit.js +155 -0
- package/dist/infrastructure/evaluation/alias-store.js +199 -0
- package/dist/infrastructure/evaluation/campaign-store.js +154 -0
- package/dist/infrastructure/evaluation/candidate-store.js +439 -0
- package/dist/infrastructure/evaluation/corpus-store.js +181 -0
- package/dist/infrastructure/evaluation/experiment-store.js +124 -0
- package/dist/infrastructure/evaluation/ignition-store.js +82 -0
- package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
- package/dist/infrastructure/evaluation/proposer-store.js +78 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/infrastructure/harness/active-residue-policy.js +73 -0
- package/dist/infrastructure/harness/artifact-store.js +72 -0
- package/dist/infrastructure/harness/atomic-write.js +50 -0
- package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
- package/dist/infrastructure/harness/loop-action-store.js +20 -0
- package/dist/infrastructure/harness/loop-store.js +41 -0
- package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
- package/dist/infrastructure/harness/task-store.js +108 -0
- package/dist/records/closeout.js +2 -0
- package/dist/records/harvest.js +215 -0
- package/dist/records/index.js +3 -0
- package/dist/records/one-shot-runs.js +386 -0
- package/dist/records/promotion.js +200 -0
- package/dist/shared/artifacts-core.js +107 -0
- package/dist/shared/artifacts.js +2 -0
- package/dist/shared/context-files.js +32 -0
- package/dist/shared/context.js +2 -0
- package/dist/shared/copy-dir.js +17 -0
- package/dist/shared/git-progress.js +172 -0
- package/dist/shared/index.js +5 -0
- package/dist/shared/logger.js +17 -0
- package/dist/shared/one-shot-prompt-args.js +98 -0
- package/dist/shared/openspec-spec.js +125 -0
- package/dist/shared/operator/capabilities.js +2659 -0
- package/dist/shared/operator/command-lifecycle.js +94 -0
- package/dist/shared/operator/envelope.js +59 -0
- package/dist/shared/operator/index.js +5 -0
- package/dist/shared/operator/registry.js +38 -0
- package/dist/shared/operator/types.js +5 -0
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +530 -0
- package/dist/shared/path-refs.js +31 -0
- package/dist/shared/pi-retry-settings.js +23 -0
- package/dist/shared/playwright-cli-command-policy.js +41 -0
- package/dist/shared/preview.js +39 -0
- package/dist/shared/prompts.js +26 -0
- package/dist/shared/reference-context.js +264 -0
- package/dist/shared/resilient-git.js +133 -0
- package/dist/shared/runtime-activity.js +6 -0
- package/dist/shared/timeout-policy.js +24 -0
- package/dist/shared/timeout.js +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/sidecars/cursor-prompt/executor.js +428 -0
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +235 -0
- package/dist/task/config.js +2 -0
- package/dist/task/contract/adopt.js +166 -0
- package/dist/task/contract/apply.js +326 -0
- package/dist/task/contract/canonicalize.js +60 -0
- package/dist/task/contract/constants.js +30 -0
- package/dist/task/contract/diff.js +177 -0
- package/dist/task/contract/hash.js +42 -0
- package/dist/task/contract/import-revision.js +96 -0
- package/dist/task/contract/index.js +17 -0
- package/dist/task/contract/journal.js +155 -0
- package/dist/task/contract/lock.js +153 -0
- package/dist/task/contract/observe.js +296 -0
- package/dist/task/contract/paths.js +19 -0
- package/dist/task/contract/project.js +185 -0
- package/dist/task/contract/recover.js +312 -0
- package/dist/task/contract/request-ledger.js +37 -0
- package/dist/task/contract/schema.js +153 -0
- package/dist/task/contract/transaction.js +160 -0
- package/dist/task/contract/types.js +1 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/dag-source-paths.js +50 -0
- package/dist/task/delegate.js +208 -0
- package/dist/task/frontend-preflight.js +131 -0
- package/dist/task/frontend-project-capability.js +570 -0
- package/dist/task/goal-audit.js +51 -0
- package/dist/task/goal-policy.js +8 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/ids.js +1 -0
- package/dist/task/index.js +12 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/read-model.js +140 -0
- package/dist/task/runtime.js +708 -0
- package/dist/task/source-prepare/artifact-meta.js +137 -0
- package/dist/task/source-prepare/build-draft.js +232 -0
- package/dist/task/source-prepare/completeness.js +248 -0
- package/dist/task/source-prepare/index.js +10 -0
- package/dist/task/source-prepare/parse-intent.js +496 -0
- package/dist/task/source-prepare/path-policy.js +197 -0
- package/dist/task/source-prepare/placeholder-paths.js +77 -0
- package/dist/task/source-prepare/prepare.js +920 -0
- package/dist/task/source-prepare/reference-integrity.js +290 -0
- package/dist/task/source-prepare/semantic-intake.js +717 -0
- package/dist/task/source-prepare/types.js +7 -0
- package/dist/task/source-references.js +228 -0
- package/dist/task/source-state.js +1 -0
- package/dist/task/state.js +41 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/task/task-demand-routing.js +443 -0
- package/dist/task/workflow-state-types.js +92 -0
- package/dist/task/worktree-cleanup.js +140 -0
- package/dist/task/worktree.js +388 -0
- package/dist/verification/maven/cache.js +142 -0
- package/dist/verification/maven/index.js +120 -0
- package/dist/verification/maven/plan-commands.js +421 -0
- package/dist/verification/maven/pom-static.js +136 -0
- package/dist/verification/maven/scope-resolve.js +153 -0
- package/dist/verification/maven/stale.js +130 -0
- package/dist/verification/maven/types.js +23 -0
- package/dist/verification/maven/workspace-graph.js +322 -0
- package/dist/worker/cli.js +957 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/console/app-data.js +316 -0
- package/dist/worker/console/chat/artifact-card.js +30 -0
- package/dist/worker/console/chat/assistant-content.js +121 -0
- package/dist/worker/console/chat/chat-event-store.js +981 -0
- package/dist/worker/console/chat/chat-ui-policy.js +25 -0
- package/dist/worker/console/chat/compaction-errors.js +64 -0
- package/dist/worker/console/chat/composer-draft-store.js +45 -0
- package/dist/worker/console/chat/context-panel.js +54 -0
- package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
- package/dist/worker/console/chat/explore-tools.js +299 -0
- package/dist/worker/console/chat/human-gate-card.js +45 -0
- package/dist/worker/console/chat/interview-adapter.js +149 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/model-resolver.js +215 -0
- package/dist/worker/console/chat/operation-card.js +92 -0
- package/dist/worker/console/chat/pi-console-config.js +376 -0
- package/dist/worker/console/chat/pi-runtime.js +2446 -0
- package/dist/worker/console/chat/repo-browser.js +140 -0
- package/dist/worker/console/chat/repo-walk.js +125 -0
- package/dist/worker/console/chat/resource-loader.js +59 -0
- package/dist/worker/console/chat/resource-preferences-store.js +152 -0
- package/dist/worker/console/chat/routes.js +3137 -0
- package/dist/worker/console/chat/runtime-context.js +102 -0
- package/dist/worker/console/chat/runtime-selection.js +96 -0
- package/dist/worker/console/chat/semantic-activity.js +489 -0
- package/dist/worker/console/chat/session-store.js +484 -0
- package/dist/worker/console/chat/shortcuts.js +217 -0
- package/dist/worker/console/chat/tool-adapter.js +131 -0
- package/dist/worker/console/chat/tool-preview.js +247 -0
- package/dist/worker/console/chat/tools.js +212 -0
- package/dist/worker/console/chat/turn-execution-registry.js +82 -0
- package/dist/worker/console/chat/turn-process.js +346 -0
- package/dist/worker/console/chat/usage.js +165 -0
- package/dist/worker/console/chat/workspace-landing.js +103 -0
- package/dist/worker/console/dag-confirmation.js +347 -0
- package/dist/worker/console/dag-execution-receipt.js +393 -0
- package/dist/worker/console/doctor.js +230 -0
- package/dist/worker/console/draft-store.js +158 -0
- package/dist/worker/console/harness-pi-model-matrix.js +137 -0
- package/dist/worker/console/human-gate-token.js +130 -0
- package/dist/worker/console/index.js +17 -0
- package/dist/worker/console/inspect-split.js +82 -0
- package/dist/worker/console/interview/assessment.js +67 -0
- package/dist/worker/console/interview/grill-me.js +277 -0
- package/dist/worker/console/interview/session.js +100 -0
- package/dist/worker/console/interview/tools.js +109 -0
- package/dist/worker/console/loopback.js +16 -0
- package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
- package/dist/worker/console/night-aux-ticker.js +146 -0
- package/dist/worker/console/observe-health-match.js +179 -0
- package/dist/worker/console/observe-link.js +42 -0
- package/dist/worker/console/open-browser.js +134 -0
- package/dist/worker/console/operation-run-facts.js +190 -0
- package/dist/worker/console/operation-runner.js +353 -0
- package/dist/worker/console/operation-sse.js +184 -0
- package/dist/worker/console/operation-store.js +169 -0
- package/dist/worker/console/operation-wait.js +355 -0
- package/dist/worker/console/operator-actions.js +3768 -0
- package/dist/worker/console/operator-selection.js +105 -0
- package/dist/worker/console/operator-surface-health.js +24 -0
- package/dist/worker/console/operator-user-error.js +45 -0
- package/dist/worker/console/pi-readiness.js +288 -0
- package/dist/worker/console/prd-identity.js +102 -0
- package/dist/worker/console/prd-intake-bridge.js +441 -0
- package/dist/worker/console/prd-reference-discovery.js +124 -0
- package/dist/worker/console/recovery-cta.js +320 -0
- package/dist/worker/console/recovery-error-copy.js +198 -0
- package/dist/worker/console/recovery-selection.js +107 -0
- package/dist/worker/console/repo-fingerprint.js +35 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +115 -0
- package/dist/worker/console/resource-loader.js +95 -0
- package/dist/worker/console/routes.js +475 -0
- package/dist/worker/console/security.js +170 -0
- package/dist/worker/console/server.js +357 -0
- package/dist/worker/console/sibling-controller.js +33 -0
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-BmOEx0gA.js +1 -0
- package/dist/worker/console/static/assets/arc-BGiVVe8b.js +1 -0
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-nDdu4rgJ.js +36 -0
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-Bb5u0BWf.js +132 -0
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-BlhC0j94.js +10 -0
- package/dist/worker/console/static/assets/channel-Tl-FkUqr.js +1 -0
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-Ai8_qQXI.js +1 -0
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B9gyCjqX.js +1 -0
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-Bzbj3zGH.js +231 -0
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-BT415PvT.js +15 -0
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-D_A5_5QL.js +88 -0
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-srJkaP3f.js +206 -0
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-UYGvEP4l.js +1 -0
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-B8luBrrz.js +1 -0
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-DN7wXZSq.js +1 -0
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-DXKyMuKS.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BaehtRk4.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BaehtRk4.js +1 -0
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-dqvoynXj.js +1 -0
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2--PcyL34A.js +166 -0
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DvWs_hTY.js +62 -0
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-DxeIxbyO.js +4 -0
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-DPCG61Of.js +30 -0
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-CmuU50-U.js +3 -0
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-B61XLD_v.js +24 -0
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BB7HTRRY.js +24 -0
- package/dist/worker/console/static/assets/diagram-UB23O5K3-DvkboAIp.js +41 -0
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-BdJ_y1fL.js +1 -0
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-9bUBZTRi.js +85 -0
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-Dw4L4RBa.js +156 -0
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-Jr82N_QP.js +292 -0
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-DAz9wFLr.js +106 -0
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
- package/dist/worker/console/static/assets/index-B7Y-0g3J.css +1 -0
- package/dist/worker/console/static/assets/index-DkCrqaYg.js +325 -0
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-CBB5Np7y.js +2 -0
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-D8IOp94y.js +70 -0
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-Dgd3AHGG.js +139 -0
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BmYOxLE9.js +89 -0
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
- package/dist/worker/console/static/assets/linear-DfYTEF6J.js +1 -0
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
- package/dist/worker/console/static/assets/mermaid.core-BjuFPRqa.js +308 -0
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS-CLyUViGd.js +96 -0
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-BvX2Di4-.js +1 -0
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-BFj0_OLv.js +39 -0
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-BIUrD_hx.js +7 -0
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-CHBB48fj.js +1 -0
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-sJrYw2Sf.js +84 -0
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-BzHH6fPR.js +40 -0
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-7QCnelCt.js +162 -0
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-sDYSxn72.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-XpiUdwzm.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-eEhq2A8Y.js +1 -0
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-DvqgNPzD.js +2 -0
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-la8AC7fo.js +8 -0
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-2TyOYNx0.js +120 -0
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-B-nM6wM2.js +34 -0
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-B86wPdbr.js +78 -0
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-Dr1C4AQz.js +7 -0
- package/dist/worker/console/static/favicon.svg +37 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static/index.html +15 -0
- package/dist/worker/console/static-src/app/console-types.js +184 -0
- package/dist/worker/console/static-src/app/useConsoleShell.js +101 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +324 -0
- package/dist/worker/console/static-src/app/usePrdImport.js +172 -0
- package/dist/worker/console/static-src/app/useRecoveryActions.js +289 -0
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +425 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +296 -0
- package/dist/worker/console/static-src/chat-markdown-security.js +38 -0
- package/dist/worker/console/static-src/chat-view-types.js +2 -0
- package/dist/worker/console/static-src/night/night-types.js +24 -0
- package/dist/worker/console/static-src/night/useNightBoard.js +94 -0
- package/dist/worker/console/static-src/night/useNightWizard.js +171 -0
- package/dist/worker/console/static-src/night-prepare-result.js +118 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +125 -0
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +73 -0
- package/dist/worker/console/static-src/operator-chat/activity-references.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +420 -0
- package/dist/worker/console/static-src/operator-chat/format.js +71 -0
- package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +86 -0
- package/dist/worker/console/static-src/operator-chat/resource-auto-invocation.js +91 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
- package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
- package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +24 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +38 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +77 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +690 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +158 -0
- package/dist/worker/console/static-src/operator-chat/types.js +1 -0
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +59 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +495 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +780 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +280 -0
- package/dist/worker/console/static-src/operator-chat/useComposer.js +257 -0
- package/dist/worker/console/static-src/operator-chat/useInterview.js +108 -0
- package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +148 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +358 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +35 -0
- package/dist/worker/console/static-src/prd-file-import.js +218 -0
- package/dist/worker/console/vite.config.js +27 -0
- package/dist/worker/console/workflow-kinds.js +46 -0
- package/dist/worker/delivery/final-verification.js +302 -0
- package/dist/worker/delivery/git-transaction.js +497 -0
- package/dist/worker/delivery/package.js +537 -0
- package/dist/worker/delivery/verification-bundle.js +544 -0
- package/dist/worker/feature/acceptance-policy.js +227 -0
- package/dist/worker/feature/advance.js +301 -0
- package/dist/worker/feature/decision-loader.js +99 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/doctor.js +223 -0
- package/dist/worker/feature/fullstack-validate.js +337 -0
- package/dist/worker/feature/next-action.js +140 -0
- package/dist/worker/feature/profile-schema.js +47 -0
- package/dist/worker/feature/ready-plan-projection.js +82 -0
- package/dist/worker/feature/reducer.js +137 -0
- package/dist/worker/feature/review.js +678 -0
- package/dist/worker/feature/run.js +390 -0
- package/dist/worker/feature/scaffold.js +812 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +273 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/loop-agent/command-result.js +1 -0
- package/dist/worker/loop-agent/controller-protocol.js +143 -0
- package/dist/worker/loop-agent/loop-agent-client.js +489 -0
- package/dist/worker/loop-agent/parse-json.js +14 -0
- package/dist/worker/materialize/harness-task-lifecycle-probe.js +126 -0
- package/dist/worker/materialize/harness-task-lineage.js +220 -0
- package/dist/worker/materialize/harness-task-materializer.js +648 -0
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/dag-execution-trajectory.js +591 -0
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/event-store.js +83 -0
- package/dist/worker/observability/events.js +79 -0
- package/dist/worker/observability/interrupt-eligibility.js +264 -0
- package/dist/worker/observability/progress-composite.js +34 -0
- package/dist/worker/observability/read-model.js +2499 -0
- package/dist/worker/observability/snapshot-store.js +43 -0
- package/dist/worker/observability/types.js +1 -0
- package/dist/worker/observe/dag-node-execution-output.js +180 -0
- package/dist/worker/observe/dag-run-artifacts.js +90 -0
- package/dist/worker/observe/health.js +58 -0
- package/dist/worker/observe/night-jobs.js +104 -0
- package/dist/worker/observe/node-input.js +441 -0
- package/dist/worker/observe/paths.js +174 -0
- package/dist/worker/observe/routes.js +1077 -0
- package/dist/worker/observe/server.js +117 -0
- package/dist/worker/observe/spec-evidence.js +398 -0
- package/dist/worker/observe/static/api.js +117 -0
- package/dist/worker/observe/static/app.js +169 -0
- package/dist/worker/observe/static/constants.js +182 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/custom-select.js +567 -0
- package/dist/worker/observe/static/dag-edge-routing.js +368 -0
- package/dist/worker/observe/static/dag-helpers.js +161 -0
- package/dist/worker/observe/static/dag-history-labels.js +95 -0
- package/dist/worker/observe/static/dag-layout.d.ts +36 -0
- package/dist/worker/observe/static/dag-layout.js +163 -0
- package/dist/worker/observe/static/dag-model.js +145 -0
- package/dist/worker/observe/static/dom.js +220 -0
- package/dist/worker/observe/static/favicon.svg +37 -0
- package/dist/worker/observe/static/format-pool.d.ts +71 -0
- package/dist/worker/observe/static/format-pool.js +153 -0
- package/dist/worker/observe/static/format.js +347 -0
- package/dist/worker/observe/static/index.html +409 -0
- package/dist/worker/observe/static/kpi.js +78 -0
- package/dist/worker/observe/static/markdown-render.js +124 -0
- package/dist/worker/observe/static/operator-chrome.css +517 -0
- package/dist/worker/observe/static/operator-chrome.d.ts +83 -0
- package/dist/worker/observe/static/operator-chrome.js +623 -0
- package/dist/worker/observe/static/relations.js +133 -0
- package/dist/worker/observe/static/router.js +156 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +65 -0
- package/dist/worker/observe/static/state.js +496 -0
- package/dist/worker/observe/static/styles.css +3567 -0
- package/dist/worker/observe/static/views/batch.js +227 -0
- package/dist/worker/observe/static/views/dag-graph.js +508 -0
- package/dist/worker/observe/static/views/dag-inspector.js +1722 -0
- package/dist/worker/observe/static/views/dag-trajectory.js +313 -0
- package/dist/worker/observe/static/views/dag.js +483 -0
- package/dist/worker/observe/static/views/dags.js +877 -0
- package/dist/worker/observe/static/views/dashboard.js +747 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +492 -0
- package/dist/worker/observe/static/views/night.js +201 -0
- package/dist/worker/observe/static/views/pool.js +708 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +771 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +315 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/worker/outcomes/adapters.js +153 -0
- package/dist/worker/outcomes/declared-artifacts.js +103 -0
- package/dist/worker/outcomes/evidence-tokens.js +29 -0
- package/dist/worker/outcomes/gate.js +40 -0
- package/dist/worker/outcomes/projector.js +227 -0
- package/dist/worker/outcomes/registry.js +1 -0
- package/dist/worker/outcomes/store.js +131 -0
- package/dist/worker/outcomes/types.js +79 -0
- package/dist/worker/pool/attempt-identity.js +41 -0
- package/dist/worker/pool/attempt-lease.js +184 -0
- package/dist/worker/pool/attempt-transition.js +210 -0
- package/dist/worker/pool/begin-attempt-with-lease.js +26 -0
- package/dist/worker/pool/begin-attempt.js +35 -0
- package/dist/worker/pool/doctor.js +165 -0
- package/dist/worker/pool/failure-routing.js +182 -0
- package/dist/worker/pool/migrate-state.js +303 -0
- package/dist/worker/pool/reconcile.js +285 -0
- package/dist/worker/pool/recovery-decision.js +163 -0
- package/dist/worker/pool/run-owner-store.js +126 -0
- package/dist/worker/pool/run-store.js +360 -0
- package/dist/worker/pool/runtime-reconcile-inventory.js +127 -0
- package/dist/worker/pool/state-projection.js +57 -0
- package/dist/worker/pool/types.js +17 -0
- package/dist/worker/pool/validation.js +144 -0
- package/dist/worker/preflight.js +157 -0
- package/dist/worker/profile-mapping.js +76 -0
- package/dist/worker/progress-reporter.js +63 -0
- package/dist/worker/report/morning-report.js +155 -0
- package/dist/worker/repos/repo-resolver.js +23 -0
- package/dist/worker/run-task/execute-prepared-task.js +178 -0
- package/dist/worker/run-task/run-task.js +912 -0
- package/dist/worker/runner/run-ready.js +561 -0
- package/dist/worker/runner/single-task-attempt.js +176 -0
- package/dist/worker/scheduler/admission.js +547 -0
- package/dist/worker/scheduler/auto-followup.js +99 -0
- package/dist/worker/scheduler/cli.js +644 -0
- package/dist/worker/scheduler/clock-install/darwin-launchd.js +238 -0
- package/dist/worker/scheduler/clock-install/linux-systemd-user.js +224 -0
- package/dist/worker/scheduler/clock-install/types.js +1 -0
- package/dist/worker/scheduler/clock-install/win32-schtasks.js +227 -0
- package/dist/worker/scheduler/clock-install.js +284 -0
- package/dist/worker/scheduler/clock.js +420 -0
- package/dist/worker/scheduler/dispatcher.js +503 -0
- package/dist/worker/scheduler/doctor.js +431 -0
- package/dist/worker/scheduler/evidence.js +170 -0
- package/dist/worker/scheduler/git-base.js +90 -0
- package/dist/worker/scheduler/index.js +25 -0
- package/dist/worker/scheduler/lease.js +114 -0
- package/dist/worker/scheduler/lifecycle.js +348 -0
- package/dist/worker/scheduler/lock.js +80 -0
- package/dist/worker/scheduler/morning-window.js +191 -0
- package/dist/worker/scheduler/night-git-finalizer.js +114 -0
- package/dist/worker/scheduler/night-harvest.js +412 -0
- package/dist/worker/scheduler/paths.js +92 -0
- package/dist/worker/scheduler/prepared-attempt-recovery.js +471 -0
- package/dist/worker/scheduler/recovery.js +277 -0
- package/dist/worker/scheduler/reservation.js +146 -0
- package/dist/worker/scheduler/retry.js +199 -0
- package/dist/worker/scheduler/scheduler-loop.js +272 -0
- package/dist/worker/scheduler/store.js +275 -0
- package/dist/worker/scheduler/traceability.js +54 -0
- package/dist/worker/scheduler/trigger.js +258 -0
- package/dist/worker/scheduler/types.js +369 -0
- package/dist/worker/scheduler/workspace-adapter.js +128 -0
- package/dist/worker/task-graph/acceptance-schema.js +40 -0
- package/dist/worker/task-graph/ready-planner.js +267 -0
- package/dist/worker/task-graph/ready-queue.js +23 -0
- package/dist/worker/task-graph/task-graph-schema.js +59 -0
- package/dist/worker/task-graph/types.js +1 -0
- package/dist/worker/task-graph/validate.js +293 -0
- package/dist/worker/task-spec/complexity-mapping.js +8 -0
- package/dist/worker/task-spec/schema.js +125 -0
- package/dist/worker/task-spec/types.js +1 -0
- package/dist/worker/task-spec/validate.js +391 -0
- package/dist/worker/task-spec/workflow-routing.js +149 -0
- package/dist/workflows/dag/authoring.js +8 -0
- package/dist/workflows/dag/authority-surface.js +138 -0
- package/dist/workflows/dag/backend-test-analysis-contract.js +507 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +1985 -0
- package/dist/workflows/dag/backend-test-case-manifest.js +638 -0
- package/dist/workflows/dag/backend-test-classification-contract.js +38 -0
- package/dist/workflows/dag/backend-test-contract-envelope.js +205 -0
- package/dist/workflows/dag/backend-test-coverage-contract.js +202 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +715 -0
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +178 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +1984 -0
- package/dist/workflows/dag/backend-test-module-stem.js +26 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +859 -0
- package/dist/workflows/dag/backend-test-result-contract.js +1030 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +1670 -0
- package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
- package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
- package/dist/workflows/dag/backend-test-stability-contract.js +57 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +1058 -0
- package/dist/workflows/dag/budget-enforcement.js +67 -0
- package/dist/workflows/dag/canvas-observer.js +474 -0
- package/dist/workflows/dag/context-policy.js +137 -0
- package/dist/workflows/dag/contract-output-registry.js +14 -0
- package/dist/workflows/dag/contract-validator-registrations.js +10 -0
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/convergence/controller.js +596 -0
- package/dist/workflows/dag/decision-envelope.js +557 -0
- package/dist/workflows/dag/decision-evidence.js +153 -0
- package/dist/workflows/dag/decision-gates.js +1 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +161 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +435 -0
- package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +203 -0
- package/dist/workflows/dag/event-observer.js +132 -0
- package/dist/workflows/dag/executor-registry.js +23 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/failure-category.js +118 -0
- package/dist/workflows/dag/failure-routing.js +105 -0
- package/dist/workflows/dag/final-verification.js +180 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1911 -0
- package/dist/workflows/dag/frontend-lint-baseline.js +474 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +1011 -0
- package/dist/workflows/dag/frontend-project-capability.js +1 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
- package/dist/workflows/dag/frontend-recovery-run.js +539 -0
- package/dist/workflows/dag/frontend-repair.js +541 -0
- package/dist/workflows/dag/frontend-review-context.js +116 -0
- package/dist/workflows/dag/frontend-risk.js +161 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +343 -0
- package/dist/workflows/dag/frontend-test-case-manifest.js +104 -0
- package/dist/workflows/dag/frontend-test-case-quality.js +105 -0
- package/dist/workflows/dag/frontend-test-html-report.js +174 -0
- package/dist/workflows/dag/frontend-test-l5-report.js +177 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +487 -0
- package/dist/workflows/dag/frontend-verification-trace.js +275 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +201 -0
- package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
- package/dist/workflows/dag/governance-constants.js +5 -0
- package/dist/workflows/dag/governance-profile.js +413 -0
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +7345 -0
- package/dist/workflows/dag/interrupt-request.js +559 -0
- package/dist/workflows/dag/knowledge-curator.js +165 -0
- package/dist/workflows/dag/l5-report-metrics.js +33 -0
- package/dist/workflows/dag/lifecycle.js +855 -0
- package/dist/workflows/dag/liveness-policy.js +251 -0
- package/dist/workflows/dag/node-execution.js +1081 -0
- package/dist/workflows/dag/observer-compose.js +52 -0
- package/dist/workflows/dag/output-protocol.js +356 -0
- package/dist/workflows/dag/project-governance-context.js +514 -0
- package/dist/workflows/dag/prompt-source.js +88 -0
- package/dist/workflows/dag/prompt.js +348 -0
- package/dist/workflows/dag/reconcile-run.js +145 -0
- package/dist/workflows/dag/reconcile-tasks.js +404 -0
- package/dist/workflows/dag/recovery-recommendation.js +329 -0
- package/dist/workflows/dag/repair-artifact.js +363 -0
- package/dist/workflows/dag/report.js +1229 -0
- package/dist/workflows/dag/rerun-plan.js +614 -0
- package/dist/workflows/dag/rerun-run.js +556 -0
- package/dist/workflows/dag/rerun-task.js +370 -0
- package/dist/workflows/dag/retry-policy.js +263 -0
- package/dist/workflows/dag/run-store.js +41 -0
- package/dist/workflows/dag/runner.js +1069 -0
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/runtime.js +5 -0
- package/dist/workflows/dag/scheduler.js +497 -0
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +478 -0
- package/dist/workflows/dag/skill-snapshot.js +553 -0
- package/dist/workflows/dag/skills.js +41 -0
- package/dist/workflows/dag/spec.js +3 -0
- package/dist/workflows/dag/task-contract-binding.js +138 -0
- package/dist/workflows/dag/task-demand-routing.js +1 -0
- package/dist/workflows/dag/topo.js +30 -0
- package/dist/workflows/dag/types.js +1145 -0
- package/dist/workflows/dag/upstream-artifacts.js +98 -0
- package/dist/workflows/dag/validate.js +998 -0
- package/dist/workflows/dag/workspace-checkpoint.js +186 -0
- package/dist/workflows/dynamic/artifacts.js +65 -0
- package/dist/workflows/dynamic/compile.js +371 -0
- package/dist/workflows/dynamic/compileTypes.js +1 -0
- package/dist/workflows/dynamic/errors.js +5 -0
- package/dist/workflows/dynamic/index.js +7 -0
- package/dist/workflows/dynamic/profiles.js +156 -0
- package/dist/workflows/dynamic/spec.js +115 -0
- package/dist/workflows/dynamic/validate.js +275 -0
- package/dist/workflows/loop/actions/dag-action.js +129 -0
- package/dist/workflows/loop/actions/pi-review.js +267 -0
- package/dist/workflows/loop/actions/shared.js +157 -0
- package/dist/workflows/loop/actions/shell-verify.js +82 -0
- package/dist/workflows/loop/actions/types.js +1 -0
- package/dist/workflows/loop/actions/workflow-action.js +255 -0
- package/dist/workflows/loop/actions.js +160 -0
- package/dist/workflows/loop/benchmark.js +510 -0
- package/dist/workflows/loop/closeout.js +135 -0
- package/dist/workflows/loop/context.js +47 -0
- package/dist/workflows/loop/events.js +26 -0
- package/dist/workflows/loop/hash.js +32 -0
- package/dist/workflows/loop/index.js +8 -0
- package/dist/workflows/loop/paths.js +17 -0
- package/dist/workflows/loop/policy/auto-policy.js +112 -0
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/dist/workflows/loop/rounds.js +81 -0
- package/dist/workflows/loop/signals.js +52 -0
- package/dist/workflows/loop/state.js +116 -0
- package/dist/workflows/loop/templates.js +54 -0
- package/dist/workflows/loop/types.js +28 -0
- package/docs/README.md +82 -0
- package/docs/architecture/README.md +35 -0
- package/docs/architecture/dag-execution.md +165 -0
- package/docs/architecture/evolution.md +108 -0
- package/docs/architecture/facts-and-state.md +74 -0
- package/docs/architecture/runtime-boundaries.md +226 -0
- package/docs/architecture/system-overview.md +100 -0
- package/docs/architecture/worker-and-feature.md +122 -0
- package/docs/governance/README.md +18 -0
- package/docs/governance/harness-methodology-debugging.md +177 -0
- package/docs/governance/harness-methodology-tdd.md +130 -0
- package/docs/governance/harness-methodology-verification.md +27 -0
- package/docs/init-surface.manifest.json +358 -0
- package/docs/operations/README.md +15 -0
- package/docs/operations/local-development-environment.md +77 -0
- package/docs/skills/README.md +7 -0
- package/docs/skills/vetted-skill-registry.md +49 -0
- package/docs/templates/README.md +67 -0
- package/docs/templates/adr.md +60 -0
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
- package/docs/templates/agent-dag-report.schema.json +473 -0
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
- package/docs/templates/agent-dag.base.json +190 -0
- package/docs/templates/agent-dag.final-verification.json +185 -0
- package/docs/templates/agent-dag.schema.json +590 -0
- package/docs/templates/agent-dag.supervised-implementation.json +640 -0
- package/docs/templates/agent-worker-production-readiness-checklist.md +47 -0
- package/docs/templates/backend-test-analysis.schema.json +37 -0
- package/docs/templates/backend-test-case-manifest.schema.json +223 -0
- package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +41 -0
- package/docs/templates/backend-test-dag.json +671 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +165 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +36 -0
- package/docs/templates/backend-test-execution.schema.json +138 -0
- package/docs/templates/backend-test-result.schema.json +99 -0
- package/docs/templates/branch-merge-report.md +115 -0
- package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
- package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
- package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
- package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
- package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
- package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
- package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
- package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
- package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
- package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
- package/docs/templates/exec-plan.md +66 -0
- package/docs/templates/feature-spec.md +53 -0
- package/docs/templates/frontend-design-contract.md +68 -0
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
- package/docs/templates/frontend-eval/metrics.md +138 -0
- package/docs/templates/frontend-eval/smoke-targets.md +53 -0
- package/docs/templates/frontend-implementation-contract.schema.json +35 -0
- package/docs/templates/frontend-task-constraints.md +62 -0
- package/docs/templates/frontend-task-requirement.md +104 -0
- package/docs/templates/frontend-test-case-checklist.md +44 -0
- package/docs/templates/frontend-test-case.example.md +143 -0
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +63 -0
- package/docs/templates/frontend-test-dag.json +409 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +21 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/frontend-test-standard-scenarios.v1.json +159 -0
- package/docs/templates/harness.schema.json +351 -0
- package/docs/templates/hybrid-dag.json +188 -0
- package/docs/templates/init-evolution-review.md +35 -0
- package/docs/templates/init-managed-agents.md +157 -0
- package/docs/templates/interactive-ui-round2-experiment.md +66 -0
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
- package/docs/templates/knowledge-sync-dag.json +178 -0
- package/docs/templates/knowledge-sync-draft.schema.json +71 -0
- package/docs/templates/product-line/AGENTS.md +9 -0
- package/docs/templates/product-line/README.md +42 -0
- package/docs/templates/product-line/acceptance.yaml +23 -0
- package/docs/templates/product-line/closeout.yaml +9 -0
- package/docs/templates/product-line/design.md +13 -0
- package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
- package/docs/templates/product-line/feature.yaml +11 -0
- package/docs/templates/product-line/links.md +10 -0
- package/docs/templates/product-line/requirement.md +17 -0
- package/docs/templates/product-line/scaffold-samples/README.md +36 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +16 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +16 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/task-graph.yaml +23 -0
- package/docs/templates/product-line/task.yaml +68 -0
- package/docs/templates/product-line/test-plan.md +7 -0
- package/docs/templates/production-readiness-checklist.md +57 -0
- package/docs/templates/progress-log.md +27 -0
- package/docs/templates/project-start-checklist.md +9 -0
- package/docs/templates/qa-report.md +48 -0
- package/docs/templates/sprint-contract.md +29 -0
- package/docs/templates/worker-dogfood-evidence.md +80 -0
- package/docs/templates/worker-dogfood-setup.md +68 -0
- package/examples/decision-gate-agent-dag.json +177 -0
- package/examples/example-dag.json +46 -0
- package/examples/hybrid-loop-agent-dag.json +189 -0
- package/examples/l5-report-coms-process-definition.html +322 -0
- package/examples/l5-report-demo.html +79 -0
- package/harness.json +90 -0
- package/package.json +98 -0
- package/scripts/kb-bootstrap-init-skeleton.sh +241 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -0
- package/scripts/kb-graph-materialize.mjs +105 -0
- package/scripts/kb-graph-promote.mjs +164 -0
- package/scripts/kb-query.mjs +554 -0
- package/skills/agent-worker/SKILL.md +48 -0
- package/skills/agent-worker/references/agent-worker-operator.md +159 -0
- package/skills/ai-engineering-context/SKILL.md +48 -0
- package/skills/analyze-product-dependencies/SKILL.md +108 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +35 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +38 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +110 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +34 -0
- package/skills/analyze-product-dependencies/references/kb-integration.md +64 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +76 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +460 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +194 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +200 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +90 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +138 -0
- package/skills/analyze-product-requirements/SKILL.md +145 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +87 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +70 -0
- package/skills/analyze-product-requirements/references/example.md +104 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +144 -0
- package/skills/analyze-product-requirements/references/kb-integration.md +56 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +41 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +42 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +66 -0
- package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +501 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +100 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +109 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +109 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +175 -0
- package/skills/browser-tools/SKILL.md +196 -0
- package/skills/browser-tools/browser-content.js +103 -0
- package/skills/browser-tools/browser-cookies.js +35 -0
- package/skills/browser-tools/browser-eval.js +53 -0
- package/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/skills/browser-tools/browser-nav.js +44 -0
- package/skills/browser-tools/browser-pick.js +162 -0
- package/skills/browser-tools/browser-screenshot.js +34 -0
- package/skills/browser-tools/browser-start.js +86 -0
- package/skills/browser-tools/package-lock.json +2556 -0
- package/skills/browser-tools/package.json +19 -0
- package/skills/code-review-core/SKILL.md +20 -0
- package/skills/codebase-scout/SKILL.md +19 -0
- package/skills/frontend-bounded-implement/SKILL.md +53 -0
- package/skills/frontend-design-review/SKILL.md +79 -0
- package/skills/frontend-design-review/references/review-checklist.md +43 -0
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +55 -0
- package/skills/frontend-review/references/review-findings.md +50 -0
- package/skills/frontend-verification/SKILL.md +61 -0
- package/skills/frontend-verification/references/verification-checklist.md +48 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +80 -0
- package/skills/grill-with-docs/adr-format.md +58 -0
- package/skills/grill-with-docs/context-format.md +52 -0
- package/skills/init-capability-evolution/SKILL.md +70 -0
- package/skills/local-jacoco-coverage/SKILL.md +281 -0
- package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
- package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
- package/skills/loop-agent/SKILL.md +68 -0
- package/skills/loop-agent/references/README.md +67 -0
- package/skills/loop-agent/references/command-reference.md +645 -0
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +289 -0
- package/skills/loop-agent/references/hybrid-dag.md +245 -0
- package/skills/loop-agent/references/learned/README.md +21 -0
- package/skills/loop-agent/references/long-running-loop.md +57 -0
- package/skills/loop-agent/references/model-routing.md +45 -0
- package/skills/loop-agent/references/multi-worktree.md +54 -0
- package/skills/loop-agent/references/one-shot-runs.md +84 -0
- package/skills/loop-agent/references/orchestrator-and-interventions.md +175 -0
- package/skills/loop-agent/references/pi-prompt.md +23 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +54 -0
- package/skills/loop-agent/references/source-and-plan-practice.md +169 -0
- package/skills/loop-agent/references/task-workflow.md +104 -0
- package/skills/loop-agent/references/verification-and-failure-handling.md +148 -0
- package/skills/playwright-cli/SKILL.md +93 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +5 -0
- package/skills/playwright-cli/references/video-recording.md +5 -0
- package/skills/playwright-cli-case-generator/SKILL.md +108 -0
- package/skills/requesting-code-review/SKILL.md +101 -0
- package/skills/requesting-code-review/code-reviewer.md +168 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +312 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +27 -0
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/skills/verification-before-completion/SKILL.md +154 -0
- package/skills/webapp-testing/SKILL.md +19 -0
|
@@ -0,0 +1,3768 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { buildOperatorCapabilitiesDocument, OFFICIAL_TAIL_ACTION_IDS, } from "../../shared/operator/capabilities.js";
|
|
3
|
+
import { buildOperatorResult, operatorFailed, operatorSucceeded, } from "../../shared/operator/envelope.js";
|
|
4
|
+
import { CANONICAL_TASK_ID_PATTERN, formatLocalBusinessDate, getTaskPaths, loadTaskConfig, } from "../../task/runtime.js";
|
|
5
|
+
import { diffTaskContract, validateDraftForTask, } from "../../task/contract/index.js";
|
|
6
|
+
import { stageUtf8Text } from "./app-data.js";
|
|
7
|
+
import { ALL_CONFIRMATION_CHALLENGES, hashDagBytes, } from "./dag-confirmation.js";
|
|
8
|
+
import { capabilityForObserveTarget, DEFAULT_OBSERVE_BASE_URL, OBSERVE_START_COMMAND, probeAndClassifyObserve, } from "./observe-health-match.js";
|
|
9
|
+
import { buildInspectDeepLink, buildObserveDeepLink, } from "./observe-link.js";
|
|
10
|
+
import { resolveLatestDagRunIdForTask } from "./resolve-dag-run-for-task.js";
|
|
11
|
+
import { repoFingerprintV1 } from "./repo-fingerprint.js";
|
|
12
|
+
import { scheduleOperation, } from "./operation-runner.js";
|
|
13
|
+
import { readinessGateFailure, } from "./pi-readiness.js";
|
|
14
|
+
import { validatePiModelMatrixTiers, writePiModelMatrix, } from "./harness-pi-model-matrix.js";
|
|
15
|
+
import { isAssessmentFreshForDraft } from "./interview/assessment.js";
|
|
16
|
+
import { applyGrillMeAnswer, draftSummary, emptyDraft, isDraftStructurallyComplete, nextGrillMeQuestion, } from "./interview/grill-me.js";
|
|
17
|
+
import { toCanonicalDraftForCli, } from "./draft-store.js";
|
|
18
|
+
import { normalizeConsoleWorkflowKind } from "./workflow-kinds.js";
|
|
19
|
+
import { deriveTaskIdentityFromPrd, nextTaskIdRevision, } from "./prd-identity.js";
|
|
20
|
+
import { appendEngineeringCliArgs, buildDraftFromTaskIntake, parseEngineeringBoundaryFromParams, } from "./prd-intake-bridge.js";
|
|
21
|
+
import { discoverPrdReferences } from "./prd-reference-discovery.js";
|
|
22
|
+
import { clearSemanticIntakeAttempt, SEMANTIC_INTAKE_CLI_TIMEOUT_MS, } from "../../task/source-prepare/semantic-intake.js";
|
|
23
|
+
import nodePath from "node:path";
|
|
24
|
+
import { resolveSiblingAgentWorkerBin } from "./sibling-controller.js";
|
|
25
|
+
import { projectOperationEventSummary, projectOperationForChat, } from "./chat/chat-event-store.js";
|
|
26
|
+
import { OperationWaitError, waitForOperationChange, } from "./operation-wait.js";
|
|
27
|
+
import { MutationGateReceiptStore } from "./mutation-gate-receipt-store.js";
|
|
28
|
+
import { formatIneligibleRerunMessage } from "./recovery-error-copy.js";
|
|
29
|
+
import { evaluateConsoleDagInterruptEligibility } from "../observability/interrupt-eligibility.js";
|
|
30
|
+
import { INTERRUPT_ERROR_MESSAGES, isInterruptReasonCode, normalizeInterruptReasonDetail, } from "../observability/interrupt-eligibility.js";
|
|
31
|
+
import { assessAutonomousExecutionEligibility, deriveEligibilityFactsFromDagSpec, } from "./dag-execution-receipt.js";
|
|
32
|
+
import { issueHumanGateToken, mutationGatePayloadHash, verifyHumanGateToken, } from "./human-gate-token.js";
|
|
33
|
+
const MUTATION_GATE_ACTIONS = new Set([
|
|
34
|
+
// 2026-08-11: standaloneTaskRerun / workerTaskRetry are autonomous (server
|
|
35
|
+
// run-facts checks). Destructive DAG reconciliation, live cooperative
|
|
36
|
+
// interrupt, and Night Scheduler mutations still need the browser Human Gate.
|
|
37
|
+
"dagReconcileRun",
|
|
38
|
+
"dagRequestInterrupt",
|
|
39
|
+
"workerAdmissionPrepare",
|
|
40
|
+
"workerSchedulerAdd",
|
|
41
|
+
"workerSchedulerCancel",
|
|
42
|
+
"workerSchedulerHarvest",
|
|
43
|
+
"workerSchedulerDiscard",
|
|
44
|
+
]);
|
|
45
|
+
const MUTATION_OR_LONG = new Set([
|
|
46
|
+
"newTask",
|
|
47
|
+
"importPrd",
|
|
48
|
+
"contractApply",
|
|
49
|
+
"contractAdopt",
|
|
50
|
+
"contractRecover",
|
|
51
|
+
"taskAdvance",
|
|
52
|
+
"dagRunTask",
|
|
53
|
+
"runDag",
|
|
54
|
+
"dagRerun",
|
|
55
|
+
"dagReconcileRun",
|
|
56
|
+
"dagRequestInterrupt",
|
|
57
|
+
"standaloneTaskRerun",
|
|
58
|
+
"workerTaskRetry",
|
|
59
|
+
]);
|
|
60
|
+
function paramsOf(req) {
|
|
61
|
+
return (req.actionParams ?? {});
|
|
62
|
+
}
|
|
63
|
+
function str(v) {
|
|
64
|
+
return typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
65
|
+
}
|
|
66
|
+
const TASK_ID_PLACEHOLDER_RE = /[<>]|^(?:id|task-?id|your-task-id|todo|tbd)$/i;
|
|
67
|
+
/** Mirror of runtime GENERIC_TASK_SLUGS (not exported there); keep in sync. */
|
|
68
|
+
const GENERIC_SLUG_SET = new Set(["test", "fix", "tmp", "new-task"]);
|
|
69
|
+
const TASK_ADVANCE_ALIAS_ACTIONS = new Set(["promoteRun", "closeoutTask"]);
|
|
70
|
+
function taskIdInputError(taskId) {
|
|
71
|
+
if (!TASK_ID_PLACEHOLDER_RE.test(taskId))
|
|
72
|
+
return undefined;
|
|
73
|
+
return `taskId must be a concrete canonical id; placeholder values are not allowed: ${JSON.stringify(taskId)}`;
|
|
74
|
+
}
|
|
75
|
+
/** Extract write-set gate token from task advance operator JSON. */
|
|
76
|
+
function extractWriteSetGateTokenFromCliJson(json, taskId) {
|
|
77
|
+
if (!json || typeof json !== "object")
|
|
78
|
+
return undefined;
|
|
79
|
+
const envelope = json;
|
|
80
|
+
const payload = envelope.result && typeof envelope.result === "object"
|
|
81
|
+
? envelope.result
|
|
82
|
+
: envelope;
|
|
83
|
+
const gate = payload.gate && typeof payload.gate === "object"
|
|
84
|
+
? payload.gate
|
|
85
|
+
: undefined;
|
|
86
|
+
const id = typeof gate?.id === "string" ? gate.id : undefined;
|
|
87
|
+
const digest = typeof gate?.digest === "string" ? gate.digest : undefined;
|
|
88
|
+
if (!id || !digest)
|
|
89
|
+
return undefined;
|
|
90
|
+
// taskId kept for future binding checks / logging symmetry with worker.
|
|
91
|
+
void taskId;
|
|
92
|
+
return `${id}:${digest}`;
|
|
93
|
+
}
|
|
94
|
+
function readWriteSetGateToken(reviewPacket) {
|
|
95
|
+
if (!reviewPacket)
|
|
96
|
+
return undefined;
|
|
97
|
+
const token = reviewPacket.writeSetGateToken;
|
|
98
|
+
return typeof token === "string" && token.includes(":") ? token : undefined;
|
|
99
|
+
}
|
|
100
|
+
/** Extract the review packet from a task advance --dag-output operator JSON. */
|
|
101
|
+
function extractReviewPacketFromCliJson(json) {
|
|
102
|
+
if (!json || typeof json !== "object")
|
|
103
|
+
return undefined;
|
|
104
|
+
const envelope = json;
|
|
105
|
+
const payload = envelope.result && typeof envelope.result === "object"
|
|
106
|
+
? envelope.result
|
|
107
|
+
: envelope;
|
|
108
|
+
const packet = payload.reviewPacket;
|
|
109
|
+
return packet && typeof packet === "object"
|
|
110
|
+
? packet
|
|
111
|
+
: undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Read the live managed Task Contract binding for a task (server-derived).
|
|
115
|
+
* Returns undefined when the task is not managed or status facts are missing
|
|
116
|
+
* so callers can fail closed.
|
|
117
|
+
*/
|
|
118
|
+
async function readLiveTaskContractBinding(ctx, taskId) {
|
|
119
|
+
const showBody = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
120
|
+
const statusResult = (showBody.result ?? {});
|
|
121
|
+
const c = showBody.ok ? statusResult.contract : undefined;
|
|
122
|
+
if (!c?.canonicalHash || typeof c.revision !== "number")
|
|
123
|
+
return undefined;
|
|
124
|
+
return {
|
|
125
|
+
schemaVersion: 1,
|
|
126
|
+
taskId,
|
|
127
|
+
revision: c.revision,
|
|
128
|
+
canonicalHash: c.canonicalHash,
|
|
129
|
+
taskConfigSha256: c.taskConfigSha256 ?? undefined,
|
|
130
|
+
projectionVersion: c.projectionVersion ?? undefined,
|
|
131
|
+
canonicalizerVersion: c.canonicalizerVersion ?? undefined,
|
|
132
|
+
taskConfigSchemaVersion: c.taskConfigSchemaVersion ?? undefined,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function gateError(action, code, message, details) {
|
|
136
|
+
const status = code === "NOT_FOUND" ? 404 : code === "INVALID_INPUT" ? 400 : 403;
|
|
137
|
+
return {
|
|
138
|
+
kind: "error",
|
|
139
|
+
status,
|
|
140
|
+
body: operatorFailed({
|
|
141
|
+
command: action,
|
|
142
|
+
outcome: "blocked",
|
|
143
|
+
code,
|
|
144
|
+
message,
|
|
145
|
+
details,
|
|
146
|
+
}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* `dag report --json` is `{ schemaVersion, runs: [...] }` (no Operator `ok`).
|
|
151
|
+
* `runReadCli` used to wrap that as `{ stdout, raw }`, which made the
|
|
152
|
+
* autonomous rerun gate miss `primaryFailure` even after the UI showed the
|
|
153
|
+
* report (2026-08-16). Accept both the native report shape and the wrap.
|
|
154
|
+
*/
|
|
155
|
+
function extractDagReportRunFacts(reportResult, runId) {
|
|
156
|
+
const asRecord = (value) => value && typeof value === "object" && !Array.isArray(value)
|
|
157
|
+
? value
|
|
158
|
+
: undefined;
|
|
159
|
+
const result = asRecord(reportResult);
|
|
160
|
+
const envelope = Array.isArray(result?.runs)
|
|
161
|
+
? result
|
|
162
|
+
: asRecord(result?.raw);
|
|
163
|
+
if (!envelope)
|
|
164
|
+
return undefined;
|
|
165
|
+
const runs = Array.isArray(envelope.runs) ? envelope.runs : [];
|
|
166
|
+
const matched = runs.find((entry) => asRecord(entry)?.runId === runId) ?? runs[0];
|
|
167
|
+
const matchedRec = asRecord(matched);
|
|
168
|
+
if (matchedRec?.primaryFailure) {
|
|
169
|
+
return {
|
|
170
|
+
primaryFailure: asRecord(matchedRec.primaryFailure),
|
|
171
|
+
primaryRecovery: asRecord(matchedRec.primaryRecovery),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (envelope.primaryFailure) {
|
|
175
|
+
return {
|
|
176
|
+
primaryFailure: asRecord(envelope.primaryFailure),
|
|
177
|
+
primaryRecovery: asRecord(envelope.primaryRecovery),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 2026-08-11 R2 run-facts check for standaloneTaskRerun (read-only CLI
|
|
184
|
+
* evidence only — no browser Human Gate). Fails closed when facts are missing.
|
|
185
|
+
*/
|
|
186
|
+
async function assertAutonomousRerunEligible(ctx, runId, taskId) {
|
|
187
|
+
const report = await runReadCli(ctx, ["dag", "report", "--run-id", runId, "--json"], "dag report");
|
|
188
|
+
const reportResult = extractDagReportRunFacts(report.ok ? report.result : undefined, runId);
|
|
189
|
+
const recovery = reportResult?.primaryRecovery;
|
|
190
|
+
if (!report.ok || !reportResult?.primaryFailure || !recovery) {
|
|
191
|
+
return gateError("standaloneTaskRerun", "INVALID_INPUT", "cannot read dagReport run facts; standaloneTaskRerun requires server-side run evidence", { runId });
|
|
192
|
+
}
|
|
193
|
+
if (recovery.humanRequired === true) {
|
|
194
|
+
return gateError("standaloneTaskRerun", "HUMAN_CONFIRMATION_REQUIRED", `primaryRecovery requires a human decision (action=${recovery.action ?? "?"}); stop autonomous rerun`, { runId });
|
|
195
|
+
}
|
|
196
|
+
const nodeId = reportResult.primaryFailure.scope === "node"
|
|
197
|
+
? reportResult.primaryFailure.nodeId
|
|
198
|
+
: undefined;
|
|
199
|
+
let planEligible;
|
|
200
|
+
if (nodeId) {
|
|
201
|
+
const plan = await runReadCli(ctx, [
|
|
202
|
+
"dag",
|
|
203
|
+
"rerun",
|
|
204
|
+
"--run-id",
|
|
205
|
+
runId,
|
|
206
|
+
"--from-node",
|
|
207
|
+
nodeId,
|
|
208
|
+
"--plan",
|
|
209
|
+
"--json",
|
|
210
|
+
], "dag rerun plan");
|
|
211
|
+
const planResult = (plan.result ?? {});
|
|
212
|
+
if (plan.ok && planResult.eligible === true) {
|
|
213
|
+
return gateError("standaloneTaskRerun", "INVALID_INPUT", `node rerun plan is eligible (planHash=${planResult.planHash ?? "?"}); use dagRerun with the fresh planHash instead of standaloneTaskRerun`, { runId, nodeId });
|
|
214
|
+
}
|
|
215
|
+
if (!plan.ok && planResult.eligible === undefined) {
|
|
216
|
+
return gateError("standaloneTaskRerun", "INVALID_INPUT", "dagRerunPlan run facts unavailable; standaloneTaskRerun fails closed", { runId, nodeId });
|
|
217
|
+
}
|
|
218
|
+
planEligible = planResult.eligible;
|
|
219
|
+
}
|
|
220
|
+
if (planEligible !== false) {
|
|
221
|
+
// No ineligible-plan evidence: require an explicit rerun-task recommendation.
|
|
222
|
+
const recommendsRerunTask = recovery.action === "rerun-after-fix" ||
|
|
223
|
+
/rerun-task|standalone/i.test(recovery.commandHint ?? "");
|
|
224
|
+
if (!recommendsRerunTask) {
|
|
225
|
+
return gateError("standaloneTaskRerun", "HUMAN_CONFIRMATION_REQUIRED", `run facts do not support autonomous standaloneTaskRerun (recovery action=${recovery.action ?? "?"}); stop`, { runId });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (taskId) {
|
|
229
|
+
// Keep the original taskId: the run must still be bound to it.
|
|
230
|
+
const status = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
231
|
+
const statusResult = (status.ok ? status.result : undefined);
|
|
232
|
+
const boundRunId = statusResult?.activeRun?.runId ?? statusResult?.latestRun?.runId;
|
|
233
|
+
if (!status.ok || !boundRunId || boundRunId !== runId) {
|
|
234
|
+
return gateError("standaloneTaskRerun", "INVALID_INPUT", `taskId ${taskId} is not bound to run ${runId}; keep the original taskId for same-task rerun`, { runId, taskId, boundRunId });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { kind: "ready" };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 2026-08-11 read-only facts check for workerTaskRetry: the task must exist
|
|
241
|
+
* in the pool doctor inventory with status Failed (featureId consistent).
|
|
242
|
+
*/
|
|
243
|
+
async function assertWorkerTaskRetryFacts(ctx, taskId, featureId) {
|
|
244
|
+
const run = ctx.runCommand ??
|
|
245
|
+
((args, options) => ctx.client.runExternal
|
|
246
|
+
? ctx.client.runExternal(resolveSiblingAgentWorkerBin(), args, options)
|
|
247
|
+
: ctx.client.run(args, options));
|
|
248
|
+
let result;
|
|
249
|
+
try {
|
|
250
|
+
result = await run(["pool", "doctor", "--repo", ctx.repoRoot, "--json"], {
|
|
251
|
+
cwd: ctx.repoRoot,
|
|
252
|
+
artifactName: "worker-task-retry-facts",
|
|
253
|
+
expectJson: true,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
return gateError("workerTaskRetry", "INVALID_INPUT", `cannot read pool doctor facts: ${error instanceof Error ? error.message : String(error)}`);
|
|
258
|
+
}
|
|
259
|
+
if (!result.ok || result.exitCode !== 0 || !result.json) {
|
|
260
|
+
return gateError("workerTaskRetry", "INVALID_INPUT", "cannot read pool doctor run facts; workerTaskRetry fails closed");
|
|
261
|
+
}
|
|
262
|
+
const doctor = result.json;
|
|
263
|
+
const v2Entry = (doctor.v2 ?? []).find((entry) => entry.taskId === taskId);
|
|
264
|
+
const legacyEntry = (doctor.legacy ?? []).find((entry) => entry.taskId === taskId && Boolean(entry.featureId));
|
|
265
|
+
const entry = v2Entry ?? legacyEntry;
|
|
266
|
+
if (!entry) {
|
|
267
|
+
return gateError("workerTaskRetry", "INVALID_INPUT", `task ${taskId} not found in the pool doctor inventory`, { taskId });
|
|
268
|
+
}
|
|
269
|
+
if (entry.status !== "Failed") {
|
|
270
|
+
return gateError("workerTaskRetry", "HUMAN_CONFIRMATION_REQUIRED", `task ${taskId} is ${entry.status}; only Failed tasks are retry-eligible`, { taskId, status: entry.status });
|
|
271
|
+
}
|
|
272
|
+
if (featureId && entry.featureId && entry.featureId !== featureId) {
|
|
273
|
+
return gateError("workerTaskRetry", "INVALID_INPUT", `featureId ${featureId} does not match the pool task ${taskId} (${entry.featureId})`, { taskId, featureId, observedFeatureId: entry.featureId });
|
|
274
|
+
}
|
|
275
|
+
return { kind: "ready" };
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Prefer structured stdout JSON failure semantics (findings / message) over bare
|
|
279
|
+
* `exit N` when stderr is empty — spine audit and similar read CLIs write details
|
|
280
|
+
* to stdout JSON and exit non-zero with empty stderr.
|
|
281
|
+
*/
|
|
282
|
+
function summarizeReadCliFailure(result) {
|
|
283
|
+
const fromJson = summarizeJsonFailure(result.json);
|
|
284
|
+
if (fromJson)
|
|
285
|
+
return fromJson;
|
|
286
|
+
const stderr = (result.stderr ?? "").trim();
|
|
287
|
+
if (stderr)
|
|
288
|
+
return stderr;
|
|
289
|
+
return `exit ${result.exitCode}`;
|
|
290
|
+
}
|
|
291
|
+
function summarizeJsonFailure(json) {
|
|
292
|
+
if (!json || typeof json !== "object")
|
|
293
|
+
return undefined;
|
|
294
|
+
const raw = json;
|
|
295
|
+
if (Array.isArray(raw.findings)) {
|
|
296
|
+
const messages = raw.findings
|
|
297
|
+
.map((finding) => {
|
|
298
|
+
if (!finding || typeof finding !== "object")
|
|
299
|
+
return "";
|
|
300
|
+
const message = finding.message;
|
|
301
|
+
return typeof message === "string" ? message.trim() : "";
|
|
302
|
+
})
|
|
303
|
+
.filter(Boolean);
|
|
304
|
+
if (messages.length > 0) {
|
|
305
|
+
const head = messages.slice(0, 6).join("; ");
|
|
306
|
+
return messages.length > 6 ? `${head}…` : head;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const nestedError = raw.error;
|
|
310
|
+
if (nestedError && typeof nestedError === "object") {
|
|
311
|
+
const message = nestedError.message;
|
|
312
|
+
if (typeof message === "string" && message.trim())
|
|
313
|
+
return message.trim();
|
|
314
|
+
}
|
|
315
|
+
if (typeof raw.message === "string" && raw.message.trim()) {
|
|
316
|
+
return raw.message.trim();
|
|
317
|
+
}
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
async function runReadCli(ctx, args, command) {
|
|
321
|
+
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
322
|
+
try {
|
|
323
|
+
const result = await run(args, {
|
|
324
|
+
cwd: ctx.repoRoot,
|
|
325
|
+
artifactName: `read-${command.replace(/\s+/g, "-")}`,
|
|
326
|
+
expectJson: true,
|
|
327
|
+
});
|
|
328
|
+
if (result.json && typeof result.json === "object") {
|
|
329
|
+
const raw = result.json;
|
|
330
|
+
const env = raw;
|
|
331
|
+
if (env.schemaVersion === 1 && typeof env.ok === "boolean") {
|
|
332
|
+
return env;
|
|
333
|
+
}
|
|
334
|
+
if (raw.schemaVersion === 1 && Array.isArray(raw.runs)) {
|
|
335
|
+
return operatorSucceeded(command, raw);
|
|
336
|
+
}
|
|
337
|
+
if (command === "dag rerun plan" &&
|
|
338
|
+
raw.plan &&
|
|
339
|
+
typeof raw.plan === "object") {
|
|
340
|
+
const plan = raw.plan;
|
|
341
|
+
if (raw.ok === true) {
|
|
342
|
+
return operatorSucceeded(command, plan);
|
|
343
|
+
}
|
|
344
|
+
const blockedReasons = Array.isArray(plan.blockedReasons)
|
|
345
|
+
? plan.blockedReasons.map(String)
|
|
346
|
+
: [];
|
|
347
|
+
const blockingNodes = Array.isArray(plan.blockingNodes)
|
|
348
|
+
? plan.blockingNodes
|
|
349
|
+
: [];
|
|
350
|
+
return buildOperatorResult({
|
|
351
|
+
command,
|
|
352
|
+
ok: false,
|
|
353
|
+
outcome: "rejected",
|
|
354
|
+
result: plan,
|
|
355
|
+
error: {
|
|
356
|
+
code: "DAG_RERUN_INELIGIBLE",
|
|
357
|
+
message: formatIneligibleRerunMessage({
|
|
358
|
+
blockedReasons,
|
|
359
|
+
blockingNodes,
|
|
360
|
+
}),
|
|
361
|
+
details: {
|
|
362
|
+
eligible: false,
|
|
363
|
+
planHash: typeof plan.planHash === "string" ? plan.planHash : undefined,
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (result.ok && result.exitCode === 0) {
|
|
370
|
+
return operatorSucceeded(command, {
|
|
371
|
+
stdout: result.stdout,
|
|
372
|
+
raw: result.json,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const message = summarizeReadCliFailure(result);
|
|
376
|
+
if (result.json && typeof result.json === "object") {
|
|
377
|
+
return buildOperatorResult({
|
|
378
|
+
command,
|
|
379
|
+
ok: false,
|
|
380
|
+
outcome: "rejected",
|
|
381
|
+
result: result.json,
|
|
382
|
+
error: {
|
|
383
|
+
code: "INTERNAL_ERROR",
|
|
384
|
+
message,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return operatorFailed({
|
|
389
|
+
command,
|
|
390
|
+
outcome: "rejected",
|
|
391
|
+
code: "INTERNAL_ERROR",
|
|
392
|
+
message,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
return operatorFailed({
|
|
397
|
+
command,
|
|
398
|
+
outcome: "needs-reconcile",
|
|
399
|
+
code: "OPERATION_NEEDS_RECONCILE",
|
|
400
|
+
message: error instanceof Error ? error.message : String(error),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async function acceptOperation(ctx, input) {
|
|
405
|
+
const created = await ctx.operations.create({
|
|
406
|
+
clientRequestId: input.clientRequestId,
|
|
407
|
+
action: input.action,
|
|
408
|
+
actionParams: input.actionParams,
|
|
409
|
+
taskId: input.taskId,
|
|
410
|
+
cliArgs: input.cliArgs,
|
|
411
|
+
...(input.externalCommand
|
|
412
|
+
? { externalCommand: input.externalCommand }
|
|
413
|
+
: {}),
|
|
414
|
+
});
|
|
415
|
+
if (created.kind === "conflict") {
|
|
416
|
+
return {
|
|
417
|
+
kind: "error",
|
|
418
|
+
status: 409,
|
|
419
|
+
body: operatorFailed({
|
|
420
|
+
command: input.action,
|
|
421
|
+
outcome: "conflict",
|
|
422
|
+
code: "REQUEST_ID_REUSE_CONFLICT",
|
|
423
|
+
message: created.message,
|
|
424
|
+
details: { operationId: created.existing.operationId },
|
|
425
|
+
}),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (created.kind === "idempotent") {
|
|
429
|
+
return {
|
|
430
|
+
kind: "accepted",
|
|
431
|
+
status: 202,
|
|
432
|
+
body: {
|
|
433
|
+
operationId: created.operation.operationId,
|
|
434
|
+
state: created.operation.state,
|
|
435
|
+
clientRequestId: created.operation.clientRequestId,
|
|
436
|
+
action: created.operation.action,
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const runnerDeps = {
|
|
441
|
+
store: ctx.operations,
|
|
442
|
+
events: ctx.events,
|
|
443
|
+
client: ctx.client,
|
|
444
|
+
repoRoot: ctx.repoRoot,
|
|
445
|
+
runCommand: ctx.runCommand,
|
|
446
|
+
};
|
|
447
|
+
scheduleOperation(created.operation.operationId, runnerDeps);
|
|
448
|
+
return {
|
|
449
|
+
kind: "accepted",
|
|
450
|
+
status: 202,
|
|
451
|
+
body: {
|
|
452
|
+
operationId: created.operation.operationId,
|
|
453
|
+
state: created.operation.state,
|
|
454
|
+
clientRequestId: created.operation.clientRequestId,
|
|
455
|
+
action: created.operation.action,
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
export async function dispatchOperatorAction(ctx, req) {
|
|
460
|
+
const action = req.action?.trim();
|
|
461
|
+
if (!action) {
|
|
462
|
+
return {
|
|
463
|
+
kind: "error",
|
|
464
|
+
status: 400,
|
|
465
|
+
body: operatorFailed({
|
|
466
|
+
command: "operator",
|
|
467
|
+
outcome: "invalid",
|
|
468
|
+
code: "INVALID_INPUT",
|
|
469
|
+
message: "缺少 action",
|
|
470
|
+
}),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
const readiness = ctx.getReadiness?.() ?? ctx.readiness;
|
|
474
|
+
const blocked = readinessGateFailure(readiness, action);
|
|
475
|
+
if (blocked) {
|
|
476
|
+
return {
|
|
477
|
+
kind: "error",
|
|
478
|
+
status: 412,
|
|
479
|
+
body: operatorFailed({
|
|
480
|
+
command: action,
|
|
481
|
+
outcome: "blocked",
|
|
482
|
+
code: "PI_SETUP_REQUIRED",
|
|
483
|
+
message: blocked.message,
|
|
484
|
+
details: { state: blocked.state },
|
|
485
|
+
}),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
const p = paramsOf(req);
|
|
489
|
+
switch (action) {
|
|
490
|
+
case "operatorCapabilities": {
|
|
491
|
+
const doc = buildOperatorCapabilitiesDocument();
|
|
492
|
+
return {
|
|
493
|
+
kind: "sync",
|
|
494
|
+
status: 200,
|
|
495
|
+
body: operatorSucceeded("operator capabilities", doc),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
case "operationGet": {
|
|
499
|
+
const operationId = str(p.operationId);
|
|
500
|
+
if (!operationId)
|
|
501
|
+
return invalid(action, "operationId is required");
|
|
502
|
+
const operation = await ctx.operations.get(operationId);
|
|
503
|
+
if (!operation) {
|
|
504
|
+
return {
|
|
505
|
+
kind: "error",
|
|
506
|
+
status: 404,
|
|
507
|
+
body: operatorFailed({
|
|
508
|
+
command: action,
|
|
509
|
+
outcome: "not-found",
|
|
510
|
+
code: "NOT_FOUND",
|
|
511
|
+
message: `operation not found: ${operationId}`,
|
|
512
|
+
}),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
kind: "sync",
|
|
517
|
+
status: 200,
|
|
518
|
+
body: operatorSucceeded(action, projectOperationForChat(operation)),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
case "operationList": {
|
|
522
|
+
const rawLimit = typeof p.limit === "number" ? p.limit : Number(p.limit ?? 20);
|
|
523
|
+
if (!Number.isFinite(rawLimit))
|
|
524
|
+
return invalid(action, "limit must be a number");
|
|
525
|
+
const limit = Math.max(1, Math.min(50, Math.floor(rawLimit)));
|
|
526
|
+
const state = str(p.state);
|
|
527
|
+
const actionFilter = str(p.action);
|
|
528
|
+
const operations = (await ctx.operations.list())
|
|
529
|
+
.filter((operation) => !state || operation.state === state)
|
|
530
|
+
.filter((operation) => !actionFilter || operation.action === actionFilter)
|
|
531
|
+
.slice(-limit)
|
|
532
|
+
.reverse()
|
|
533
|
+
.map(projectOperationForChat);
|
|
534
|
+
return {
|
|
535
|
+
kind: "sync",
|
|
536
|
+
status: 200,
|
|
537
|
+
body: operatorSucceeded(action, { operations, limit }),
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
case "operationEventSummary": {
|
|
541
|
+
const operationId = str(p.operationId);
|
|
542
|
+
if (!operationId)
|
|
543
|
+
return invalid(action, "operationId is required");
|
|
544
|
+
const operation = await ctx.operations.get(operationId);
|
|
545
|
+
if (!operation) {
|
|
546
|
+
return {
|
|
547
|
+
kind: "error",
|
|
548
|
+
status: 404,
|
|
549
|
+
body: operatorFailed({
|
|
550
|
+
command: action,
|
|
551
|
+
outcome: "not-found",
|
|
552
|
+
code: "NOT_FOUND",
|
|
553
|
+
message: `operation not found: ${operationId}`,
|
|
554
|
+
}),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
const rawLimit = typeof p.limit === "number" ? p.limit : Number(p.limit ?? 20);
|
|
558
|
+
if (!Number.isFinite(rawLimit))
|
|
559
|
+
return invalid(action, "limit must be a number");
|
|
560
|
+
const limit = Math.max(1, Math.min(100, Math.floor(rawLimit)));
|
|
561
|
+
const events = ctx.events
|
|
562
|
+
.snapshot(operationId)
|
|
563
|
+
.slice(-limit)
|
|
564
|
+
.map(projectOperationEventSummary);
|
|
565
|
+
return {
|
|
566
|
+
kind: "sync",
|
|
567
|
+
status: 200,
|
|
568
|
+
body: operatorSucceeded(action, {
|
|
569
|
+
operation: projectOperationForChat(operation),
|
|
570
|
+
events,
|
|
571
|
+
limit,
|
|
572
|
+
}),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
case "operationWait": {
|
|
576
|
+
const operationId = str(p.operationId);
|
|
577
|
+
if (!operationId)
|
|
578
|
+
return invalid(action, "operationId is required");
|
|
579
|
+
const rawAfterSeq = p.afterSeq ?? 0;
|
|
580
|
+
const afterSeq = typeof rawAfterSeq === "number" ? rawAfterSeq : Number(rawAfterSeq);
|
|
581
|
+
const rawMaxWaitMs = p.maxWaitMs;
|
|
582
|
+
const maxWaitMs = typeof rawMaxWaitMs === "number"
|
|
583
|
+
? rawMaxWaitMs
|
|
584
|
+
: rawMaxWaitMs === undefined
|
|
585
|
+
? undefined
|
|
586
|
+
: Number(rawMaxWaitMs);
|
|
587
|
+
const rawWakeOn = str(p.wakeOn);
|
|
588
|
+
const wakeOn = rawWakeOn === "all" || rawWakeOn === "meaningful" ? rawWakeOn : undefined;
|
|
589
|
+
try {
|
|
590
|
+
const result = await waitForOperationChange({
|
|
591
|
+
operations: ctx.operations,
|
|
592
|
+
events: ctx.events,
|
|
593
|
+
operationId,
|
|
594
|
+
afterSeq,
|
|
595
|
+
maxWaitMs,
|
|
596
|
+
...(wakeOn ? { wakeOn } : {}),
|
|
597
|
+
poll: async () => {
|
|
598
|
+
const current = await ctx.operations.get(operationId);
|
|
599
|
+
const runId = current?.dagRunId ??
|
|
600
|
+
(typeof current?.actionParams?.runId === "string"
|
|
601
|
+
? current.actionParams.runId
|
|
602
|
+
: undefined);
|
|
603
|
+
if (!current || !runId)
|
|
604
|
+
return false;
|
|
605
|
+
const eligibility = await evaluateConsoleDagInterruptEligibility({
|
|
606
|
+
repoRoot: ctx.repoRoot,
|
|
607
|
+
operations: ctx.operations,
|
|
608
|
+
runId,
|
|
609
|
+
});
|
|
610
|
+
if (!eligibility.interrupt)
|
|
611
|
+
return false;
|
|
612
|
+
const targetId = eligibility.targetOperationId ?? current.operationId;
|
|
613
|
+
return syncTargetInterruptProjection({
|
|
614
|
+
operations: ctx.operations,
|
|
615
|
+
events: ctx.events,
|
|
616
|
+
operationId: targetId,
|
|
617
|
+
runId,
|
|
618
|
+
interrupt: eligibility.interrupt,
|
|
619
|
+
});
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
return {
|
|
623
|
+
kind: "sync",
|
|
624
|
+
status: 200,
|
|
625
|
+
body: operatorSucceeded(action, result),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
catch (error) {
|
|
629
|
+
if (error instanceof OperationWaitError) {
|
|
630
|
+
if (error.code === "NOT_FOUND") {
|
|
631
|
+
return {
|
|
632
|
+
kind: "error",
|
|
633
|
+
status: 404,
|
|
634
|
+
body: operatorFailed({
|
|
635
|
+
command: action,
|
|
636
|
+
outcome: "not-found",
|
|
637
|
+
code: "NOT_FOUND",
|
|
638
|
+
message: error.message,
|
|
639
|
+
}),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
if (error.code === "EVENT_CURSOR_EXPIRED") {
|
|
643
|
+
return {
|
|
644
|
+
kind: "error",
|
|
645
|
+
status: 410,
|
|
646
|
+
body: operatorFailed({
|
|
647
|
+
command: action,
|
|
648
|
+
outcome: "blocked",
|
|
649
|
+
code: "EVENT_CURSOR_EXPIRED",
|
|
650
|
+
message: error.message,
|
|
651
|
+
}),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
return invalid(action, error.message);
|
|
655
|
+
}
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
case "inspect": {
|
|
660
|
+
const body = await runReadCli(ctx, ["inspect", "--json"], "inspect");
|
|
661
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
662
|
+
}
|
|
663
|
+
case "doctor": {
|
|
664
|
+
const body = await runReadCli(ctx, ["doctor", "--json"], "doctor");
|
|
665
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
666
|
+
}
|
|
667
|
+
case "dagReport": {
|
|
668
|
+
const runId = str(p.runId) ?? str(p.dagRunId);
|
|
669
|
+
if (!runId) {
|
|
670
|
+
return invalid(action, "runId is required");
|
|
671
|
+
}
|
|
672
|
+
const body = await runReadCli(ctx, ["dag", "report", "--run-id", runId, "--json"], "dag report");
|
|
673
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
674
|
+
}
|
|
675
|
+
case "dagDoctor": {
|
|
676
|
+
const runId = str(p.runId) ?? str(p.dagRunId);
|
|
677
|
+
const args = ["dag", "doctor"];
|
|
678
|
+
if (runId)
|
|
679
|
+
args.push("--run-id", runId);
|
|
680
|
+
const body = await runReadCli(ctx, args, "dag doctor");
|
|
681
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
682
|
+
}
|
|
683
|
+
case "dagStatus": {
|
|
684
|
+
const runId = str(p.runId) ?? str(p.dagRunId);
|
|
685
|
+
if (!runId)
|
|
686
|
+
return invalid(action, "runId is required");
|
|
687
|
+
const body = await runReadCli(ctx, ["dag", "status", "--run-id", runId], "dag status");
|
|
688
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
689
|
+
}
|
|
690
|
+
case "workerPoolDoctor": {
|
|
691
|
+
const taskId = str(p.taskId);
|
|
692
|
+
const run = ctx.runCommand ??
|
|
693
|
+
((args, options) => ctx.client.runExternal
|
|
694
|
+
? ctx.client.runExternal(resolveSiblingAgentWorkerBin(), args, options)
|
|
695
|
+
: ctx.client.run(args, options));
|
|
696
|
+
let result;
|
|
697
|
+
try {
|
|
698
|
+
result = await run(["pool", "doctor", "--repo", ctx.repoRoot, "--json"], {
|
|
699
|
+
cwd: ctx.repoRoot,
|
|
700
|
+
artifactName: "read-pool-doctor",
|
|
701
|
+
expectJson: true,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
return {
|
|
706
|
+
kind: "error",
|
|
707
|
+
status: 400,
|
|
708
|
+
body: operatorFailed({
|
|
709
|
+
command: action,
|
|
710
|
+
outcome: "invalid",
|
|
711
|
+
code: "INVALID_INPUT",
|
|
712
|
+
message: `cannot read pool doctor facts: ${error instanceof Error ? error.message : String(error)}`,
|
|
713
|
+
}),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
if (!result.ok || result.exitCode !== 0 || !result.json) {
|
|
717
|
+
return {
|
|
718
|
+
kind: "error",
|
|
719
|
+
status: 400,
|
|
720
|
+
body: operatorFailed({
|
|
721
|
+
command: action,
|
|
722
|
+
outcome: "invalid",
|
|
723
|
+
code: "INVALID_INPUT",
|
|
724
|
+
message: summarizeReadCliFailure(result),
|
|
725
|
+
}),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
const inventory = result.json;
|
|
729
|
+
if (!taskId) {
|
|
730
|
+
return {
|
|
731
|
+
kind: "sync",
|
|
732
|
+
status: 200,
|
|
733
|
+
body: operatorSucceeded(action, inventory),
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
const v2 = (inventory.v2 ?? []).filter((entry) => entry.taskId === taskId);
|
|
737
|
+
const legacy = (inventory.legacy ?? []).filter((entry) => entry.taskId === taskId);
|
|
738
|
+
return {
|
|
739
|
+
kind: "sync",
|
|
740
|
+
status: 200,
|
|
741
|
+
body: operatorSucceeded(action, {
|
|
742
|
+
...inventory,
|
|
743
|
+
v2,
|
|
744
|
+
legacy,
|
|
745
|
+
focusedTaskId: taskId,
|
|
746
|
+
}),
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
case "status": {
|
|
750
|
+
const taskId = str(p.taskId);
|
|
751
|
+
if (!taskId) {
|
|
752
|
+
return invalid(action, "taskId is required");
|
|
753
|
+
}
|
|
754
|
+
const body = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
755
|
+
return {
|
|
756
|
+
kind: "sync",
|
|
757
|
+
status: body.ok ? 200 : body.outcome === "not-found" ? 404 : 400,
|
|
758
|
+
body,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
case "spineCheck": {
|
|
762
|
+
const taskId = str(p.taskId);
|
|
763
|
+
if (!taskId)
|
|
764
|
+
return invalid(action, "taskId is required");
|
|
765
|
+
const body = await runReadCli(ctx, ["spine", "audit", taskId, "--json"], "spine audit");
|
|
766
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
767
|
+
}
|
|
768
|
+
case "sourceInstructions": {
|
|
769
|
+
const taskId = str(p.taskId);
|
|
770
|
+
if (!taskId)
|
|
771
|
+
return invalid(action, "taskId is required");
|
|
772
|
+
const body = await runReadCli(ctx, ["instructions", "source", "--task", taskId, "--json"], "instructions source");
|
|
773
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
774
|
+
}
|
|
775
|
+
case "contractShow": {
|
|
776
|
+
const taskId = str(p.taskId);
|
|
777
|
+
if (!taskId)
|
|
778
|
+
return invalid(action, "taskId is required");
|
|
779
|
+
const body = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
780
|
+
const snap = (body.result ?? {});
|
|
781
|
+
const c = snap.contract;
|
|
782
|
+
const reshaped = {
|
|
783
|
+
...body,
|
|
784
|
+
command: "contractShow",
|
|
785
|
+
result: {
|
|
786
|
+
taskId,
|
|
787
|
+
state: {
|
|
788
|
+
effectiveStatus: c?.effectiveStatus ?? "legacy-unversioned",
|
|
789
|
+
observedCanonicalHash: c?.observedCanonicalHash ?? undefined,
|
|
790
|
+
ref: c?.canonicalHash && typeof c.revision === "number"
|
|
791
|
+
? {
|
|
792
|
+
schemaVersion: 1,
|
|
793
|
+
taskId,
|
|
794
|
+
revision: c.revision,
|
|
795
|
+
canonicalHash: c.canonicalHash,
|
|
796
|
+
taskConfigSha256: c.taskConfigSha256 ?? undefined,
|
|
797
|
+
projectionVersion: c.projectionVersion ?? undefined,
|
|
798
|
+
canonicalizerVersion: c.canonicalizerVersion ?? undefined,
|
|
799
|
+
taskConfigSchemaVersion: c.taskConfigSchemaVersion ?? undefined,
|
|
800
|
+
}
|
|
801
|
+
: undefined,
|
|
802
|
+
},
|
|
803
|
+
snapshot: snap,
|
|
804
|
+
},
|
|
805
|
+
};
|
|
806
|
+
const notFound = body.ok === true && snap.exists === false;
|
|
807
|
+
return {
|
|
808
|
+
kind: "sync",
|
|
809
|
+
status: notFound
|
|
810
|
+
? 404
|
|
811
|
+
: body.ok
|
|
812
|
+
? 200
|
|
813
|
+
: body.outcome === "not-found"
|
|
814
|
+
? 404
|
|
815
|
+
: 400,
|
|
816
|
+
body: reshaped,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
case "contractValidate":
|
|
820
|
+
case "contractDiff":
|
|
821
|
+
return dispatchContractValidateOrDiff(ctx, action, p);
|
|
822
|
+
case "contractDraftSave": {
|
|
823
|
+
const draft = p.draft;
|
|
824
|
+
const taskId = str(p.taskId) ?? str(draft?.taskId);
|
|
825
|
+
if (!taskId || !draft) {
|
|
826
|
+
return invalid(action, "taskId and draft are required");
|
|
827
|
+
}
|
|
828
|
+
const { schemaVersion: _sv, taskId: _tid, ...draftRest } = draft;
|
|
829
|
+
const saved = await ctx.drafts.save({
|
|
830
|
+
...draftRest,
|
|
831
|
+
schemaVersion: 1,
|
|
832
|
+
taskId,
|
|
833
|
+
title: str(draft.title) ?? taskId,
|
|
834
|
+
});
|
|
835
|
+
return {
|
|
836
|
+
kind: "sync",
|
|
837
|
+
status: 200,
|
|
838
|
+
body: operatorSucceeded("contractDraftSave", {
|
|
839
|
+
taskId: saved.draft.taskId,
|
|
840
|
+
draftSha256: saved.draftSha256,
|
|
841
|
+
path: saved.path,
|
|
842
|
+
// never claim canonical
|
|
843
|
+
canonical: false,
|
|
844
|
+
storage: "console-application-data",
|
|
845
|
+
}),
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
case "interviewStart": {
|
|
849
|
+
return dispatchInterviewStart(ctx, p);
|
|
850
|
+
}
|
|
851
|
+
case "interviewTurn": {
|
|
852
|
+
return dispatchInterviewTurn(ctx, p);
|
|
853
|
+
}
|
|
854
|
+
case "interviewSkip": {
|
|
855
|
+
return dispatchInterviewSkip(ctx, p);
|
|
856
|
+
}
|
|
857
|
+
case "interviewGet": {
|
|
858
|
+
const sessionId = str(p.sessionId);
|
|
859
|
+
if (!sessionId)
|
|
860
|
+
return invalid(action, "sessionId is required");
|
|
861
|
+
const session = await ctx.interviews.get(sessionId);
|
|
862
|
+
if (!session) {
|
|
863
|
+
return {
|
|
864
|
+
kind: "error",
|
|
865
|
+
status: 404,
|
|
866
|
+
body: operatorFailed({
|
|
867
|
+
command: action,
|
|
868
|
+
outcome: "not-found",
|
|
869
|
+
code: "NOT_FOUND",
|
|
870
|
+
message: `interview session not found: ${sessionId}`,
|
|
871
|
+
}),
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
const draftRec = await ctx.drafts.get(session.taskId);
|
|
875
|
+
return {
|
|
876
|
+
kind: "sync",
|
|
877
|
+
status: 200,
|
|
878
|
+
body: operatorSucceeded("interviewGet", {
|
|
879
|
+
session,
|
|
880
|
+
draft: draftRec?.draft ?? null,
|
|
881
|
+
draftSha256: draftRec?.draftSha256 ?? null,
|
|
882
|
+
summary: draftRec ? draftSummary(draftRec.draft) : null,
|
|
883
|
+
}),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
case "refreshReadiness": {
|
|
887
|
+
if (ctx.refreshReadiness) {
|
|
888
|
+
const report = await ctx.refreshReadiness();
|
|
889
|
+
return {
|
|
890
|
+
kind: "sync",
|
|
891
|
+
status: 200,
|
|
892
|
+
body: operatorSucceeded("refreshReadiness", report),
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
kind: "sync",
|
|
897
|
+
status: 200,
|
|
898
|
+
body: operatorSucceeded("refreshReadiness", ctx.getReadiness?.() ?? ctx.readiness),
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
case "observeLink": {
|
|
902
|
+
const consoleOrigin = ctx.getConsoleOrigin?.();
|
|
903
|
+
// Wave 5: prefer unified Console origin; do not fall back to retired 8787 Observe.
|
|
904
|
+
const baseUrl = consoleOrigin ?? ctx.observeBaseUrl ?? DEFAULT_OBSERVE_BASE_URL;
|
|
905
|
+
const dagRunId = str(p.dagRunId);
|
|
906
|
+
const taskId = str(p.taskId);
|
|
907
|
+
const featureId = str(p.featureId);
|
|
908
|
+
const workerRunId = str(p.workerRunId);
|
|
909
|
+
let target;
|
|
910
|
+
let resolvedFromTask;
|
|
911
|
+
let fallbackToTask = false;
|
|
912
|
+
let openHome = false;
|
|
913
|
+
if (dagRunId) {
|
|
914
|
+
target = { kind: "dag", dagRunId };
|
|
915
|
+
}
|
|
916
|
+
else if (taskId) {
|
|
917
|
+
// Prefer `#/dag/<runId>` when a run exists; otherwise open `#/task/<taskId>`.
|
|
918
|
+
resolvedFromTask = await resolveLatestDagRunIdForTask(ctx.repoRoot, taskId);
|
|
919
|
+
if (resolvedFromTask) {
|
|
920
|
+
target = { kind: "dag", dagRunId: resolvedFromTask.dagRunId };
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
target = { kind: "task", taskId };
|
|
924
|
+
fallbackToTask = true;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
else if (featureId) {
|
|
928
|
+
target = { kind: "feature", featureId };
|
|
929
|
+
}
|
|
930
|
+
else if (workerRunId) {
|
|
931
|
+
target = { kind: "worker", workerRunId };
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
openHome = true;
|
|
935
|
+
}
|
|
936
|
+
const requiredCapability = target
|
|
937
|
+
? capabilityForObserveTarget(target)
|
|
938
|
+
: undefined;
|
|
939
|
+
const usesUnifiedSurface = Boolean(consoleOrigin) &&
|
|
940
|
+
(!ctx.observeBaseUrl ||
|
|
941
|
+
ctx.observeBaseUrl.replace(/\/+$/, "") ===
|
|
942
|
+
consoleOrigin?.replace(/\/+$/, ""));
|
|
943
|
+
if (usesUnifiedSurface) {
|
|
944
|
+
const inspectPath = target
|
|
945
|
+
? buildInspectDeepLink(consoleOrigin, target).slice(consoleOrigin.length)
|
|
946
|
+
: "/inspect/#/";
|
|
947
|
+
return {
|
|
948
|
+
kind: "sync",
|
|
949
|
+
status: 200,
|
|
950
|
+
body: operatorSucceeded("observeLink", {
|
|
951
|
+
url: target
|
|
952
|
+
? buildInspectDeepLink(consoleOrigin, target)
|
|
953
|
+
: `${consoleOrigin.replace(/\/+$/, "")}/inspect/#/`,
|
|
954
|
+
inspectPath,
|
|
955
|
+
status: "same-origin",
|
|
956
|
+
requiredCapability: requiredCapability ?? null,
|
|
957
|
+
dagRunId: target?.kind === "dag"
|
|
958
|
+
? target.dagRunId
|
|
959
|
+
: resolvedFromTask?.dagRunId,
|
|
960
|
+
taskId: taskId ?? undefined,
|
|
961
|
+
home: openHome || undefined,
|
|
962
|
+
fallbackToTask: fallbackToTask || undefined,
|
|
963
|
+
}),
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
let localFingerprint;
|
|
967
|
+
try {
|
|
968
|
+
localFingerprint = repoFingerprintV1(ctx.repoRoot);
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
return {
|
|
972
|
+
kind: "sync",
|
|
973
|
+
status: 400,
|
|
974
|
+
body: operatorFailed({
|
|
975
|
+
command: "observeLink",
|
|
976
|
+
outcome: "rejected",
|
|
977
|
+
code: "OBSERVE_LINK_BLOCKED",
|
|
978
|
+
message: `cannot compute local repoFingerprint: ${error instanceof Error ? error.message : String(error)}`,
|
|
979
|
+
details: {
|
|
980
|
+
status: "mismatch",
|
|
981
|
+
startCommand: OBSERVE_START_COMMAND,
|
|
982
|
+
},
|
|
983
|
+
}),
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
const classified = await probeAndClassifyObserve({
|
|
987
|
+
baseUrl,
|
|
988
|
+
localFingerprint,
|
|
989
|
+
requiredCapability,
|
|
990
|
+
fetchImpl: ctx.fetchImpl ?? fetch,
|
|
991
|
+
});
|
|
992
|
+
if (classified.status !== "match") {
|
|
993
|
+
return {
|
|
994
|
+
kind: "sync",
|
|
995
|
+
status: classified.status === "unavailable" ? 503 : 409,
|
|
996
|
+
body: operatorFailed({
|
|
997
|
+
command: "observeLink",
|
|
998
|
+
outcome: classified.status === "unavailable" ? "blocked" : "conflict",
|
|
999
|
+
code: "OBSERVE_LINK_BLOCKED",
|
|
1000
|
+
message: classified.message,
|
|
1001
|
+
details: {
|
|
1002
|
+
status: classified.status,
|
|
1003
|
+
startCommand: OBSERVE_START_COMMAND,
|
|
1004
|
+
requiredCapability,
|
|
1005
|
+
healthUrl: classified.url,
|
|
1006
|
+
localFingerprint,
|
|
1007
|
+
remoteFingerprint: classified.remoteFingerprint,
|
|
1008
|
+
},
|
|
1009
|
+
}),
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
const link = openHome
|
|
1013
|
+
? `${baseUrl.replace(/\/+$/, "")}/`
|
|
1014
|
+
: buildObserveDeepLink(baseUrl, target);
|
|
1015
|
+
return {
|
|
1016
|
+
kind: "sync",
|
|
1017
|
+
status: 200,
|
|
1018
|
+
body: operatorSucceeded("observeLink", {
|
|
1019
|
+
url: link,
|
|
1020
|
+
status: "match",
|
|
1021
|
+
requiredCapability: requiredCapability ?? null,
|
|
1022
|
+
dagRunId: target?.kind === "dag"
|
|
1023
|
+
? target.dagRunId
|
|
1024
|
+
: resolvedFromTask?.dagRunId,
|
|
1025
|
+
taskId: taskId ?? undefined,
|
|
1026
|
+
home: openHome || undefined,
|
|
1027
|
+
fallbackToTask: fallbackToTask || undefined,
|
|
1028
|
+
note: fallbackToTask
|
|
1029
|
+
? `任务 ${taskId} 尚无 DAG run,已打开任务页;运行 DAG 后可直达 run 详情`
|
|
1030
|
+
: undefined,
|
|
1031
|
+
resolvedFromTask: resolvedFromTask
|
|
1032
|
+
? {
|
|
1033
|
+
dagRunId: resolvedFromTask.dagRunId,
|
|
1034
|
+
lifecycle: resolvedFromTask.lifecycle,
|
|
1035
|
+
}
|
|
1036
|
+
: undefined,
|
|
1037
|
+
}),
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
case "dagValidate": {
|
|
1041
|
+
const dagPath = str(p.dagPath) ?? str(p.dagHandle);
|
|
1042
|
+
const taskId = str(p.taskId);
|
|
1043
|
+
if (!dagPath)
|
|
1044
|
+
return invalid(action, "dagPath is required");
|
|
1045
|
+
const args = ["dag", "validate", "--dag", dagPath, "--json"];
|
|
1046
|
+
if (taskId)
|
|
1047
|
+
args.push("--spine-task", taskId);
|
|
1048
|
+
const body = await runReadCli(ctx, args, "dag validate");
|
|
1049
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
1050
|
+
}
|
|
1051
|
+
case "prepareDagConfirmation": {
|
|
1052
|
+
const dagHandle = str(p.dagHandle) ?? "staged-dag.json";
|
|
1053
|
+
const taskId = str(p.taskId);
|
|
1054
|
+
const operatorAction = str(p.operatorAction);
|
|
1055
|
+
const operatorActionArgs = p.operatorActionArgs;
|
|
1056
|
+
if (operatorActionArgs !== undefined &&
|
|
1057
|
+
(!Array.isArray(operatorActionArgs) ||
|
|
1058
|
+
operatorActionArgs.some((value) => typeof value !== "string"))) {
|
|
1059
|
+
return invalid(action, "operatorActionArgs must be an array of strings");
|
|
1060
|
+
}
|
|
1061
|
+
if (operatorAction) {
|
|
1062
|
+
const target = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === operatorAction &&
|
|
1063
|
+
OFFICIAL_TAIL_ACTION_IDS.has(entry.action) &&
|
|
1064
|
+
entry.humanConfirmation === "required");
|
|
1065
|
+
if (!target)
|
|
1066
|
+
return invalid(action, "operatorAction must name a required official action");
|
|
1067
|
+
}
|
|
1068
|
+
if (!taskId)
|
|
1069
|
+
return invalid(action, "taskId is required");
|
|
1070
|
+
let dagText = str(p.dagText) ?? str(p.dagJson);
|
|
1071
|
+
let staged;
|
|
1072
|
+
// When generated via lifecycle, store writeSet gate token for runDag approve path.
|
|
1073
|
+
let writeSetGateToken;
|
|
1074
|
+
if (!dagText) {
|
|
1075
|
+
// Happy Path: generate + strict-validate via task advance; never confirm empty stub.
|
|
1076
|
+
staged = await stageUtf8Text(ctx.appData, "", { extension: ".json" });
|
|
1077
|
+
const profile = str(p.profile) ?? "auto";
|
|
1078
|
+
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
1079
|
+
const gen = await run([
|
|
1080
|
+
"task",
|
|
1081
|
+
"advance",
|
|
1082
|
+
taskId,
|
|
1083
|
+
"--profile",
|
|
1084
|
+
profile,
|
|
1085
|
+
"--dag-output",
|
|
1086
|
+
staged.path,
|
|
1087
|
+
"--json",
|
|
1088
|
+
], {
|
|
1089
|
+
cwd: ctx.repoRoot,
|
|
1090
|
+
artifactName: "prepare-dag-task-advance",
|
|
1091
|
+
expectJson: true,
|
|
1092
|
+
timeoutMs: 180_000,
|
|
1093
|
+
});
|
|
1094
|
+
if (!gen.ok || gen.exitCode !== 0) {
|
|
1095
|
+
return {
|
|
1096
|
+
kind: "error",
|
|
1097
|
+
status: 400,
|
|
1098
|
+
body: operatorFailed({
|
|
1099
|
+
command: action,
|
|
1100
|
+
outcome: "invalid",
|
|
1101
|
+
code: "INVALID_INPUT",
|
|
1102
|
+
message: gen.stderr?.trim() ||
|
|
1103
|
+
`task advance (prepare DAG) failed with exit ${gen.exitCode}`,
|
|
1104
|
+
details: {
|
|
1105
|
+
exitCode: gen.exitCode,
|
|
1106
|
+
stdoutPreview: (gen.stdout ?? "").slice(0, 1200),
|
|
1107
|
+
},
|
|
1108
|
+
}),
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
dagText = await readFile(staged.path, "utf8");
|
|
1112
|
+
if (!dagText.trim()) {
|
|
1113
|
+
// Fallback: advance may skip generate-dag when a draft already
|
|
1114
|
+
// exists; still prefer the task DAG over an empty staged file.
|
|
1115
|
+
const taskDagPath = getTaskPaths(ctx.repoRoot, taskId).dagDraftPath;
|
|
1116
|
+
try {
|
|
1117
|
+
dagText = await readFile(taskDagPath, "utf8");
|
|
1118
|
+
}
|
|
1119
|
+
catch {
|
|
1120
|
+
dagText = "";
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
if (!dagText.trim()) {
|
|
1124
|
+
return {
|
|
1125
|
+
kind: "error",
|
|
1126
|
+
status: 400,
|
|
1127
|
+
body: operatorFailed({
|
|
1128
|
+
command: action,
|
|
1129
|
+
outcome: "invalid",
|
|
1130
|
+
code: "INVALID_INPUT",
|
|
1131
|
+
message: "task advance produced empty DAG output",
|
|
1132
|
+
}),
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
writeSetGateToken = extractWriteSetGateTokenFromCliJson(gen.json, taskId);
|
|
1136
|
+
staged = await stageUtf8Text(ctx.appData, dagText, {
|
|
1137
|
+
extension: ".json",
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
else {
|
|
1141
|
+
staged = await stageUtf8Text(ctx.appData, dagText, {
|
|
1142
|
+
extension: ".json",
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
const controller = ctx.client.getIdentity?.()?.packageFingerprint?.value ??
|
|
1146
|
+
"unknown-controller";
|
|
1147
|
+
// Server-derived bindings only: never trust browser-supplied hashes as success defaults.
|
|
1148
|
+
const draftRec = await ctx.drafts.get(taskId);
|
|
1149
|
+
const sourceBindingSha256 = draftRec
|
|
1150
|
+
? draftRec.draftSha256
|
|
1151
|
+
: hashDagBytes(`source:${taskId}:${staged.sha256}`);
|
|
1152
|
+
const validateBody = await runReadCli(ctx, ["dag", "validate", "--dag", staged.path, "--json"], "dag validate");
|
|
1153
|
+
if (!validateBody.ok) {
|
|
1154
|
+
return {
|
|
1155
|
+
kind: "error",
|
|
1156
|
+
status: 400,
|
|
1157
|
+
body: operatorFailed({
|
|
1158
|
+
command: action,
|
|
1159
|
+
outcome: "invalid",
|
|
1160
|
+
code: "INVALID_INPUT",
|
|
1161
|
+
message: validateBody.error?.message ??
|
|
1162
|
+
"dag validate failed; cannot prepare confirmation for an invalid DAG",
|
|
1163
|
+
details: {
|
|
1164
|
+
validateError: validateBody.error ?? null,
|
|
1165
|
+
validateResult: validateBody.result ?? null,
|
|
1166
|
+
dagSha256: staged.sha256,
|
|
1167
|
+
},
|
|
1168
|
+
}),
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
const validationResultSha256 = hashDagBytes(JSON.stringify({
|
|
1172
|
+
ok: true,
|
|
1173
|
+
command: "dag validate",
|
|
1174
|
+
result: validateBody.result ?? null,
|
|
1175
|
+
}));
|
|
1176
|
+
// Prefer live managed contract observation over client-supplied binding fields.
|
|
1177
|
+
const showBody = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
1178
|
+
const statusResult = (showBody.result ?? {});
|
|
1179
|
+
const c = showBody.ok ? statusResult.contract : undefined;
|
|
1180
|
+
const ref = c?.canonicalHash && typeof c.revision === "number"
|
|
1181
|
+
? {
|
|
1182
|
+
revision: c.revision,
|
|
1183
|
+
canonicalHash: c.canonicalHash,
|
|
1184
|
+
taskConfigSha256: c.taskConfigSha256 ?? undefined,
|
|
1185
|
+
projectionVersion: c.projectionVersion ?? undefined,
|
|
1186
|
+
canonicalizerVersion: c.canonicalizerVersion ?? undefined,
|
|
1187
|
+
taskConfigSchemaVersion: c.taskConfigSchemaVersion ?? undefined,
|
|
1188
|
+
}
|
|
1189
|
+
: undefined;
|
|
1190
|
+
const showResult = {
|
|
1191
|
+
state: {
|
|
1192
|
+
effectiveStatus: c?.effectiveStatus,
|
|
1193
|
+
ref,
|
|
1194
|
+
},
|
|
1195
|
+
};
|
|
1196
|
+
if (!ref?.canonicalHash || typeof ref.revision !== "number") {
|
|
1197
|
+
return {
|
|
1198
|
+
kind: "error",
|
|
1199
|
+
status: 409,
|
|
1200
|
+
body: operatorFailed({
|
|
1201
|
+
command: action,
|
|
1202
|
+
outcome: "blocked",
|
|
1203
|
+
code: "BINDING_DRIFT",
|
|
1204
|
+
message: "managed Task Contract binding unavailable; apply/adopt before prepareDagConfirmation",
|
|
1205
|
+
details: {
|
|
1206
|
+
effectiveStatus: showResult.state?.effectiveStatus,
|
|
1207
|
+
showOk: showBody.ok,
|
|
1208
|
+
},
|
|
1209
|
+
}),
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
const taskContractBinding = {
|
|
1213
|
+
taskId,
|
|
1214
|
+
revision: ref.revision,
|
|
1215
|
+
canonicalHash: ref.canonicalHash,
|
|
1216
|
+
taskConfigSha256: ref.taskConfigSha256,
|
|
1217
|
+
projectionVersion: ref.projectionVersion,
|
|
1218
|
+
canonicalizerVersion: ref.canonicalizerVersion,
|
|
1219
|
+
taskConfigSchemaVersion: ref.taskConfigSchemaVersion,
|
|
1220
|
+
};
|
|
1221
|
+
const confirmation = await ctx.confirmations.prepare({
|
|
1222
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
1223
|
+
dagHandle,
|
|
1224
|
+
dagBytes: dagText,
|
|
1225
|
+
dagBytesPath: staged.path,
|
|
1226
|
+
sourceBindingSha256,
|
|
1227
|
+
taskContractBinding,
|
|
1228
|
+
controllerFingerprint: controller,
|
|
1229
|
+
validationResultSha256,
|
|
1230
|
+
// Writer scopes must come from server-side review packet only; ignore client writeSet claims.
|
|
1231
|
+
confirmedWriterScopes: [],
|
|
1232
|
+
reviewPacket: {
|
|
1233
|
+
taskId,
|
|
1234
|
+
dagHandle,
|
|
1235
|
+
validationOk: true,
|
|
1236
|
+
...(writeSetGateToken ? { writeSetGateToken } : {}),
|
|
1237
|
+
...(operatorAction
|
|
1238
|
+
? {
|
|
1239
|
+
operatorAction,
|
|
1240
|
+
operatorActionArgs: operatorActionArgs ?? [],
|
|
1241
|
+
}
|
|
1242
|
+
: {}),
|
|
1243
|
+
},
|
|
1244
|
+
});
|
|
1245
|
+
return {
|
|
1246
|
+
kind: "sync",
|
|
1247
|
+
status: 200,
|
|
1248
|
+
body: operatorSucceeded("prepareDagConfirmation", {
|
|
1249
|
+
confirmation,
|
|
1250
|
+
requiredChallenges: ALL_CONFIRMATION_CHALLENGES,
|
|
1251
|
+
dagSha256: staged.sha256,
|
|
1252
|
+
}),
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
case "prepareDagExecution": {
|
|
1256
|
+
const taskId = str(p.taskId);
|
|
1257
|
+
if (!taskId)
|
|
1258
|
+
return invalid(action, "taskId is required");
|
|
1259
|
+
const profile = str(p.profile) ?? "auto";
|
|
1260
|
+
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
1261
|
+
// Server-side generate + strict validate only. The model never supplies
|
|
1262
|
+
// DAG bytes or raw paths (AC-02): everything is re-derived here.
|
|
1263
|
+
const staged = await stageUtf8Text(ctx.appData, "", {
|
|
1264
|
+
extension: ".json",
|
|
1265
|
+
});
|
|
1266
|
+
const gen = await run([
|
|
1267
|
+
"task",
|
|
1268
|
+
"advance",
|
|
1269
|
+
taskId,
|
|
1270
|
+
"--profile",
|
|
1271
|
+
profile,
|
|
1272
|
+
"--dag-output",
|
|
1273
|
+
staged.path,
|
|
1274
|
+
"--json",
|
|
1275
|
+
], {
|
|
1276
|
+
cwd: ctx.repoRoot,
|
|
1277
|
+
artifactName: "prepare-dag-execution-task-advance",
|
|
1278
|
+
expectJson: true,
|
|
1279
|
+
timeoutMs: 180_000,
|
|
1280
|
+
});
|
|
1281
|
+
if (!gen.ok || gen.exitCode !== 0) {
|
|
1282
|
+
return {
|
|
1283
|
+
kind: "error",
|
|
1284
|
+
status: 400,
|
|
1285
|
+
body: operatorFailed({
|
|
1286
|
+
command: action,
|
|
1287
|
+
outcome: "invalid",
|
|
1288
|
+
code: "INVALID_INPUT",
|
|
1289
|
+
message: gen.stderr?.trim() ||
|
|
1290
|
+
`task advance (prepare DAG execution) failed with exit ${gen.exitCode}`,
|
|
1291
|
+
details: {
|
|
1292
|
+
exitCode: gen.exitCode,
|
|
1293
|
+
stdoutPreview: (gen.stdout ?? "").slice(0, 1200),
|
|
1294
|
+
},
|
|
1295
|
+
}),
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
let dagText;
|
|
1299
|
+
try {
|
|
1300
|
+
dagText = await readFile(staged.path, "utf8");
|
|
1301
|
+
}
|
|
1302
|
+
catch {
|
|
1303
|
+
dagText = "";
|
|
1304
|
+
}
|
|
1305
|
+
if (!dagText.trim()) {
|
|
1306
|
+
// Fallback: advance may skip generate-dag when a draft already
|
|
1307
|
+
// exists; still prefer the task DAG over an empty staged file.
|
|
1308
|
+
const taskDagPath = getTaskPaths(ctx.repoRoot, taskId).dagDraftPath;
|
|
1309
|
+
try {
|
|
1310
|
+
dagText = await readFile(taskDagPath, "utf8");
|
|
1311
|
+
}
|
|
1312
|
+
catch {
|
|
1313
|
+
dagText = "";
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (!dagText.trim()) {
|
|
1317
|
+
return {
|
|
1318
|
+
kind: "error",
|
|
1319
|
+
status: 400,
|
|
1320
|
+
body: operatorFailed({
|
|
1321
|
+
command: action,
|
|
1322
|
+
outcome: "invalid",
|
|
1323
|
+
code: "INVALID_INPUT",
|
|
1324
|
+
message: "task advance produced empty DAG output",
|
|
1325
|
+
}),
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
const writeSetGateToken = extractWriteSetGateTokenFromCliJson(gen.json, taskId);
|
|
1329
|
+
const finalStaged = await stageUtf8Text(ctx.appData, dagText, {
|
|
1330
|
+
extension: ".json",
|
|
1331
|
+
});
|
|
1332
|
+
const controller = ctx.client.getIdentity?.()?.packageFingerprint?.value ??
|
|
1333
|
+
"unknown-controller";
|
|
1334
|
+
const draftRec = await ctx.drafts.get(taskId);
|
|
1335
|
+
const sourceBindingSha256 = draftRec
|
|
1336
|
+
? draftRec.draftSha256
|
|
1337
|
+
: hashDagBytes(`source:${taskId}:${finalStaged.sha256}`);
|
|
1338
|
+
const validateBody = await runReadCli(ctx, ["dag", "validate", "--dag", finalStaged.path, "--json"], "dag validate");
|
|
1339
|
+
if (!validateBody.ok) {
|
|
1340
|
+
return {
|
|
1341
|
+
kind: "error",
|
|
1342
|
+
status: 400,
|
|
1343
|
+
body: operatorFailed({
|
|
1344
|
+
command: action,
|
|
1345
|
+
outcome: "invalid",
|
|
1346
|
+
code: "INVALID_INPUT",
|
|
1347
|
+
message: validateBody.error?.message ??
|
|
1348
|
+
"dag validate failed; cannot prepare execution for an invalid DAG",
|
|
1349
|
+
details: {
|
|
1350
|
+
validateError: validateBody.error ?? null,
|
|
1351
|
+
dagSha256: finalStaged.sha256,
|
|
1352
|
+
},
|
|
1353
|
+
}),
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
const validationResultSha256 = hashDagBytes(JSON.stringify({
|
|
1357
|
+
ok: true,
|
|
1358
|
+
command: "dag validate",
|
|
1359
|
+
result: validateBody.result ?? null,
|
|
1360
|
+
}));
|
|
1361
|
+
const taskContractBinding = await readLiveTaskContractBinding(ctx, taskId);
|
|
1362
|
+
if (!taskContractBinding) {
|
|
1363
|
+
return {
|
|
1364
|
+
kind: "error",
|
|
1365
|
+
status: 409,
|
|
1366
|
+
body: operatorFailed({
|
|
1367
|
+
command: action,
|
|
1368
|
+
outcome: "blocked",
|
|
1369
|
+
code: "BINDING_DRIFT",
|
|
1370
|
+
message: "managed Task Contract binding unavailable; apply/adopt before prepareDagExecution",
|
|
1371
|
+
details: { taskId },
|
|
1372
|
+
}),
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
// G2 deterministic assessment from server-side facts only.
|
|
1376
|
+
let allowedPaths;
|
|
1377
|
+
let forbiddenPaths;
|
|
1378
|
+
try {
|
|
1379
|
+
const taskConfig = await loadTaskConfig(ctx.repoRoot, taskId);
|
|
1380
|
+
allowedPaths = Array.isArray(taskConfig.allowedPaths)
|
|
1381
|
+
? taskConfig.allowedPaths
|
|
1382
|
+
: [];
|
|
1383
|
+
forbiddenPaths = Array.isArray(taskConfig.forbiddenPaths)
|
|
1384
|
+
? taskConfig.forbiddenPaths
|
|
1385
|
+
: [];
|
|
1386
|
+
}
|
|
1387
|
+
catch {
|
|
1388
|
+
return {
|
|
1389
|
+
kind: "error",
|
|
1390
|
+
status: 403,
|
|
1391
|
+
body: operatorFailed({
|
|
1392
|
+
command: action,
|
|
1393
|
+
outcome: "blocked",
|
|
1394
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
1395
|
+
message: "task boundary (task.json allowedPaths/forbiddenPaths) unavailable; autonomous execution not permitted",
|
|
1396
|
+
}),
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
const reviewPacket = extractReviewPacketFromCliJson(gen.json) ?? {};
|
|
1400
|
+
let parsedDagSpec;
|
|
1401
|
+
try {
|
|
1402
|
+
parsedDagSpec = JSON.parse(dagText);
|
|
1403
|
+
}
|
|
1404
|
+
catch (error) {
|
|
1405
|
+
return {
|
|
1406
|
+
kind: "error",
|
|
1407
|
+
status: 409,
|
|
1408
|
+
body: operatorFailed({
|
|
1409
|
+
command: action,
|
|
1410
|
+
outcome: "blocked",
|
|
1411
|
+
code: "DAG_GENERATION_FAILED",
|
|
1412
|
+
message: `generated DAG is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
1413
|
+
}),
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
const derivedFacts = deriveEligibilityFactsFromDagSpec({
|
|
1417
|
+
dagSpec: parsedDagSpec,
|
|
1418
|
+
allowedPaths,
|
|
1419
|
+
forbiddenPaths,
|
|
1420
|
+
});
|
|
1421
|
+
const packetWriters = Array.isArray(reviewPacket.writers)
|
|
1422
|
+
? reviewPacket.writers.map((writer) => ({
|
|
1423
|
+
nodeId: writer.nodeId ?? "unknown",
|
|
1424
|
+
writeSet: Array.isArray(writer.writeSet)
|
|
1425
|
+
? writer.writeSet.filter((v) => typeof v === "string")
|
|
1426
|
+
: [],
|
|
1427
|
+
}))
|
|
1428
|
+
: derivedFacts.writersWriteSets;
|
|
1429
|
+
const packetShellVerification = Array.isArray(reviewPacket.shellVerification)
|
|
1430
|
+
? reviewPacket.shellVerification.length > 0
|
|
1431
|
+
: derivedFacts.hasStructuredVerification;
|
|
1432
|
+
const eligibility = {
|
|
1433
|
+
writersWriteSets: packetWriters,
|
|
1434
|
+
allowedPaths,
|
|
1435
|
+
forbiddenPaths,
|
|
1436
|
+
broadWriteSetRisk: typeof reviewPacket.broadWriteSetRisk === "boolean"
|
|
1437
|
+
? reviewPacket.broadWriteSetRisk
|
|
1438
|
+
: derivedFacts.broadWriteSetRisk,
|
|
1439
|
+
forbiddenOverlapRisk: typeof reviewPacket.forbiddenOverlapRisk === "boolean"
|
|
1440
|
+
? reviewPacket.forbiddenOverlapRisk
|
|
1441
|
+
: derivedFacts.forbiddenOverlapRisk,
|
|
1442
|
+
hasStructuredVerification: packetShellVerification,
|
|
1443
|
+
};
|
|
1444
|
+
const assessment = assessAutonomousExecutionEligibility(eligibility);
|
|
1445
|
+
if (!assessment.ok) {
|
|
1446
|
+
return {
|
|
1447
|
+
kind: "error",
|
|
1448
|
+
status: 403,
|
|
1449
|
+
body: operatorFailed({
|
|
1450
|
+
command: action,
|
|
1451
|
+
outcome: "blocked",
|
|
1452
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
1453
|
+
message: assessment.reason,
|
|
1454
|
+
details: {
|
|
1455
|
+
taskId,
|
|
1456
|
+
dagSha256: finalStaged.sha256,
|
|
1457
|
+
eligibility: "denied",
|
|
1458
|
+
},
|
|
1459
|
+
}),
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
const prepared = await ctx.dagExecutionReceipts.prepare({
|
|
1463
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
1464
|
+
taskId,
|
|
1465
|
+
dagHandle: "staged-dag.json",
|
|
1466
|
+
dagBytes: dagText,
|
|
1467
|
+
dagBytesPath: finalStaged.path,
|
|
1468
|
+
sourceBindingSha256,
|
|
1469
|
+
taskContractBinding,
|
|
1470
|
+
controllerFingerprint: controller,
|
|
1471
|
+
validationResultSha256,
|
|
1472
|
+
eligibility,
|
|
1473
|
+
reviewPacket: {
|
|
1474
|
+
taskId,
|
|
1475
|
+
dagHandle: "staged-dag.json",
|
|
1476
|
+
validationOk: true,
|
|
1477
|
+
...(writeSetGateToken ? { writeSetGateToken } : {}),
|
|
1478
|
+
},
|
|
1479
|
+
});
|
|
1480
|
+
if (!prepared.ok) {
|
|
1481
|
+
return {
|
|
1482
|
+
kind: "error",
|
|
1483
|
+
status: 403,
|
|
1484
|
+
body: operatorFailed({
|
|
1485
|
+
command: action,
|
|
1486
|
+
outcome: "blocked",
|
|
1487
|
+
code: prepared.code,
|
|
1488
|
+
message: prepared.reason,
|
|
1489
|
+
}),
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
return {
|
|
1493
|
+
kind: "sync",
|
|
1494
|
+
status: 200,
|
|
1495
|
+
body: operatorSucceeded("prepareDagExecution", {
|
|
1496
|
+
executionId: prepared.receipt.executionId,
|
|
1497
|
+
eligibility: "approved",
|
|
1498
|
+
taskId,
|
|
1499
|
+
dagSha256: finalStaged.sha256,
|
|
1500
|
+
expiresAt: prepared.receipt.expiresAt,
|
|
1501
|
+
}),
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
case "confirmDagConfirmation": {
|
|
1505
|
+
const confirmationId = str(p.confirmationId);
|
|
1506
|
+
const humanGateToken = p.humanGateToken;
|
|
1507
|
+
if (!confirmationId) {
|
|
1508
|
+
return invalid(action, "confirmationId is required");
|
|
1509
|
+
}
|
|
1510
|
+
// Ignore client-supplied binding fields; re-derive current from store + staged bytes + live controller.
|
|
1511
|
+
const conf = await ctx.confirmations.get(confirmationId);
|
|
1512
|
+
if (!conf) {
|
|
1513
|
+
return {
|
|
1514
|
+
kind: "error",
|
|
1515
|
+
status: 404,
|
|
1516
|
+
body: operatorFailed({
|
|
1517
|
+
command: action,
|
|
1518
|
+
outcome: "not-found",
|
|
1519
|
+
code: "NOT_FOUND",
|
|
1520
|
+
message: `confirmation not found: ${confirmationId}`,
|
|
1521
|
+
}),
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
let dagBytes;
|
|
1525
|
+
try {
|
|
1526
|
+
dagBytes = await readFile(conf.dagBytesPath, "utf8");
|
|
1527
|
+
}
|
|
1528
|
+
catch (error) {
|
|
1529
|
+
return {
|
|
1530
|
+
kind: "error",
|
|
1531
|
+
status: 409,
|
|
1532
|
+
body: operatorFailed({
|
|
1533
|
+
command: action,
|
|
1534
|
+
outcome: "conflict",
|
|
1535
|
+
code: "CONFIRMATION_STALE",
|
|
1536
|
+
message: `cannot re-read staged DAG bytes: ${error instanceof Error ? error.message : String(error)}`,
|
|
1537
|
+
}),
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
const controller = ctx.client.getIdentity?.()?.packageFingerprint?.value ??
|
|
1541
|
+
conf.controllerFingerprint;
|
|
1542
|
+
const result = await ctx.confirmations.confirm({
|
|
1543
|
+
confirmationId,
|
|
1544
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
1545
|
+
humanGateToken,
|
|
1546
|
+
current: {
|
|
1547
|
+
dagSha256: hashDagBytes(dagBytes),
|
|
1548
|
+
sourceBindingSha256: conf.sourceBindingSha256,
|
|
1549
|
+
controllerFingerprint: controller,
|
|
1550
|
+
validationResultSha256: conf.validationResultSha256,
|
|
1551
|
+
taskContractBinding: conf.taskContractBinding,
|
|
1552
|
+
},
|
|
1553
|
+
});
|
|
1554
|
+
if (!result.ok) {
|
|
1555
|
+
return {
|
|
1556
|
+
kind: "error",
|
|
1557
|
+
status: confirmationErrorStatus(result.code),
|
|
1558
|
+
body: operatorFailed({
|
|
1559
|
+
command: action,
|
|
1560
|
+
outcome: "rejected",
|
|
1561
|
+
code: result.code,
|
|
1562
|
+
message: result.message,
|
|
1563
|
+
}),
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
return {
|
|
1567
|
+
kind: "sync",
|
|
1568
|
+
status: 200,
|
|
1569
|
+
body: operatorSucceeded(action, { confirmation: result.confirmation }),
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
case "newTask": {
|
|
1573
|
+
const taskId = str(p.taskId);
|
|
1574
|
+
const title = str(p.title) ?? taskId;
|
|
1575
|
+
const clientRequestId = str(req.clientRequestId);
|
|
1576
|
+
if (!taskId || !clientRequestId) {
|
|
1577
|
+
return invalid(action, "taskId and clientRequestId are required");
|
|
1578
|
+
}
|
|
1579
|
+
const taskIdError = taskIdInputError(taskId);
|
|
1580
|
+
if (taskIdError)
|
|
1581
|
+
return invalid(action, taskIdError);
|
|
1582
|
+
// Canonical id gate at admission (2026-08-16): non-canonical ids were
|
|
1583
|
+
// accepted here (validateNewTaskId only warns) and every downstream
|
|
1584
|
+
// advance/import then flailed on the malformed task directory. Fail
|
|
1585
|
+
// fast with a concrete suggested id instead.
|
|
1586
|
+
if (!CANONICAL_TASK_ID_PATTERN.test(taskId)) {
|
|
1587
|
+
const slug = taskId
|
|
1588
|
+
.toLowerCase()
|
|
1589
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
1590
|
+
.replace(/^-+|-+$/g, "")
|
|
1591
|
+
.slice(0, 48);
|
|
1592
|
+
const suggested = slug && !GENERIC_SLUG_SET.has(slug)
|
|
1593
|
+
? `${formatLocalBusinessDate()}-${slug}`
|
|
1594
|
+
: `${formatLocalBusinessDate()}-${slug || "task"}-r2`;
|
|
1595
|
+
return invalid(action, `taskId must be canonical YYYY-MM-DD-<kebab-slug> (local business date); got "${taskId}" — suggested id: "${suggested}" (e.g. 2026-08-16-tool-preview-edge-test); retry newTask with that id`);
|
|
1596
|
+
}
|
|
1597
|
+
return acceptOperation(ctx, {
|
|
1598
|
+
action,
|
|
1599
|
+
actionParams: p,
|
|
1600
|
+
clientRequestId,
|
|
1601
|
+
taskId,
|
|
1602
|
+
cliArgs: ["task", "advance", taskId, title, "--json"],
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
case "taskAdvance": {
|
|
1606
|
+
const taskId = str(p.taskId);
|
|
1607
|
+
const clientRequestId = str(req.clientRequestId);
|
|
1608
|
+
if (!taskId || !clientRequestId) {
|
|
1609
|
+
return invalid(action, "taskId and clientRequestId are required");
|
|
1610
|
+
}
|
|
1611
|
+
const taskIdError = taskIdInputError(taskId);
|
|
1612
|
+
if (taskIdError)
|
|
1613
|
+
return invalid(action, taskIdError);
|
|
1614
|
+
const args = ["task", "advance", taskId];
|
|
1615
|
+
const title = str(p.title);
|
|
1616
|
+
if (title)
|
|
1617
|
+
args.push(title);
|
|
1618
|
+
const taskKind = str(p.taskKind);
|
|
1619
|
+
if (taskKind)
|
|
1620
|
+
args.push("--task-kind", taskKind);
|
|
1621
|
+
const featureId = str(p.featureId);
|
|
1622
|
+
if (featureId)
|
|
1623
|
+
args.push("--feature-id", featureId);
|
|
1624
|
+
const profile = str(p.profile);
|
|
1625
|
+
if (profile)
|
|
1626
|
+
args.push("--profile", profile);
|
|
1627
|
+
appendEngineeringCliArgs(args, parseEngineeringBoundaryFromParams(p));
|
|
1628
|
+
args.push("--json");
|
|
1629
|
+
return acceptOperation(ctx, {
|
|
1630
|
+
action,
|
|
1631
|
+
actionParams: p,
|
|
1632
|
+
clientRequestId,
|
|
1633
|
+
taskId,
|
|
1634
|
+
cliArgs: args,
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
case "bootstrapFromPrd":
|
|
1638
|
+
return dispatchBootstrapFromPrd(ctx, p);
|
|
1639
|
+
case "importPrd": {
|
|
1640
|
+
// Chat / operator path: same intake bridge as bootstrapFromPrd, but
|
|
1641
|
+
// for an existing taskId (create-if-missing via advance --prd).
|
|
1642
|
+
const taskId = str(p.taskId);
|
|
1643
|
+
const content = str(p.content) ?? str(p.fileText);
|
|
1644
|
+
if (!taskId || !content) {
|
|
1645
|
+
return invalid(action, "taskId and content are required");
|
|
1646
|
+
}
|
|
1647
|
+
const taskIdError = taskIdInputError(taskId);
|
|
1648
|
+
if (taskIdError)
|
|
1649
|
+
return invalid(action, taskIdError);
|
|
1650
|
+
const taskKindRaw = str(p.taskKind)?.trim();
|
|
1651
|
+
const taskKind = normalizeConsoleWorkflowKind(taskKindRaw, "standard");
|
|
1652
|
+
const taskKindExplicit = Boolean(taskKindRaw);
|
|
1653
|
+
const engineering = parseEngineeringBoundaryFromParams(p);
|
|
1654
|
+
const identity = deriveTaskIdentityFromPrd(content);
|
|
1655
|
+
const title = str(p.title) ?? identity.title;
|
|
1656
|
+
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
1657
|
+
const staged = await stageUtf8Text(ctx.appData, content, {
|
|
1658
|
+
extension: ".md",
|
|
1659
|
+
});
|
|
1660
|
+
const imported = await run([
|
|
1661
|
+
"task",
|
|
1662
|
+
"advance",
|
|
1663
|
+
taskId,
|
|
1664
|
+
title,
|
|
1665
|
+
"--prd",
|
|
1666
|
+
staged.path,
|
|
1667
|
+
"--role",
|
|
1668
|
+
"requirement",
|
|
1669
|
+
"--json",
|
|
1670
|
+
], {
|
|
1671
|
+
cwd: ctx.repoRoot,
|
|
1672
|
+
artifactName: "import-prd-task-advance",
|
|
1673
|
+
expectJson: true,
|
|
1674
|
+
timeoutMs: 60_000,
|
|
1675
|
+
});
|
|
1676
|
+
if (!imported.ok || imported.exitCode !== 0 || imported.timedOut) {
|
|
1677
|
+
return {
|
|
1678
|
+
kind: "error",
|
|
1679
|
+
status: 400,
|
|
1680
|
+
body: operatorFailed({
|
|
1681
|
+
command: action,
|
|
1682
|
+
outcome: "rejected",
|
|
1683
|
+
code: "INTERNAL_ERROR",
|
|
1684
|
+
message: imported.stderr?.trim() ||
|
|
1685
|
+
`task advance --prd failed with exit ${imported.exitCode}`,
|
|
1686
|
+
details: {
|
|
1687
|
+
exitCode: imported.exitCode,
|
|
1688
|
+
timedOut: imported.timedOut,
|
|
1689
|
+
taskId,
|
|
1690
|
+
},
|
|
1691
|
+
}),
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
const intakeArgs = ["task", "advance", taskId, "--json"];
|
|
1695
|
+
if (taskKindExplicit) {
|
|
1696
|
+
intakeArgs.push("--task-kind", taskKind);
|
|
1697
|
+
}
|
|
1698
|
+
appendEngineeringCliArgs(intakeArgs, engineering);
|
|
1699
|
+
const intake = await run(intakeArgs, {
|
|
1700
|
+
cwd: ctx.repoRoot,
|
|
1701
|
+
artifactName: "import-prd-task-advance-intake",
|
|
1702
|
+
expectJson: true,
|
|
1703
|
+
timeoutMs: SEMANTIC_INTAKE_CLI_TIMEOUT_MS,
|
|
1704
|
+
});
|
|
1705
|
+
const intakeJson = intake.json ?? null;
|
|
1706
|
+
const intakeOk = intake.ok && intake.exitCode === 0 && !intake.timedOut;
|
|
1707
|
+
const bridged = await buildDraftFromTaskIntake({
|
|
1708
|
+
repoRoot: ctx.repoRoot,
|
|
1709
|
+
taskId,
|
|
1710
|
+
title,
|
|
1711
|
+
taskKind,
|
|
1712
|
+
taskKindExplicit,
|
|
1713
|
+
advanceJson: intakeJson,
|
|
1714
|
+
engineering,
|
|
1715
|
+
});
|
|
1716
|
+
const draftSaved = await ctx.drafts.save(bridged.draft);
|
|
1717
|
+
const parseStatus = intakeOk || bridged.parseStatus !== "pending"
|
|
1718
|
+
? bridged.parseStatus
|
|
1719
|
+
: "failed";
|
|
1720
|
+
return {
|
|
1721
|
+
kind: "sync",
|
|
1722
|
+
status: 200,
|
|
1723
|
+
body: operatorSucceeded(action, {
|
|
1724
|
+
taskId,
|
|
1725
|
+
title: draftSaved.draft.title ?? title,
|
|
1726
|
+
taskKind,
|
|
1727
|
+
draftSha256: draftSaved.draftSha256,
|
|
1728
|
+
parseStatus,
|
|
1729
|
+
semanticIntake: bridged.semanticIntake,
|
|
1730
|
+
gaps: bridged.gaps,
|
|
1731
|
+
lifecycleState: bridged.lifecycleState ?? "intake-incomplete",
|
|
1732
|
+
intake: {
|
|
1733
|
+
ok: intakeOk,
|
|
1734
|
+
exitCode: intake.exitCode,
|
|
1735
|
+
timedOut: intake.timedOut,
|
|
1736
|
+
},
|
|
1737
|
+
import: imported.json ?? null,
|
|
1738
|
+
draftPreview: {
|
|
1739
|
+
objective: draftSaved.draft.requirement?.objective ?? null,
|
|
1740
|
+
scope: draftSaved.draft.requirement?.scope ?? [],
|
|
1741
|
+
acceptanceCriteria: draftSaved.draft.requirement?.acceptanceCriteria ?? [],
|
|
1742
|
+
allowedPaths: draftSaved.draft.constraints?.allowedPaths ?? [],
|
|
1743
|
+
},
|
|
1744
|
+
}),
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
case "contractApply": {
|
|
1748
|
+
const taskId = str(p.taskId);
|
|
1749
|
+
const clientRequestId = str(req.clientRequestId);
|
|
1750
|
+
if (!taskId || !clientRequestId) {
|
|
1751
|
+
return invalid(action, "taskId and clientRequestId are required");
|
|
1752
|
+
}
|
|
1753
|
+
// Require fresh complete assessment bound to current draft
|
|
1754
|
+
const draftRec = await ctx.drafts.get(taskId);
|
|
1755
|
+
if (!draftRec) {
|
|
1756
|
+
return {
|
|
1757
|
+
kind: "error",
|
|
1758
|
+
status: 400,
|
|
1759
|
+
body: operatorFailed({
|
|
1760
|
+
command: action,
|
|
1761
|
+
outcome: "invalid",
|
|
1762
|
+
code: "INVALID_INPUT",
|
|
1763
|
+
message: "该任务尚无 Console 草稿,请先保存草稿",
|
|
1764
|
+
}),
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1767
|
+
const assessmentId = str(p.assessmentId) ??
|
|
1768
|
+
draftRec.draft.interviewAssessmentRef?.assessmentId;
|
|
1769
|
+
if (!assessmentId) {
|
|
1770
|
+
return {
|
|
1771
|
+
kind: "error",
|
|
1772
|
+
status: 403,
|
|
1773
|
+
body: operatorFailed({
|
|
1774
|
+
command: action,
|
|
1775
|
+
outcome: "blocked",
|
|
1776
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
1777
|
+
message: "fresh complete Interview assessment required before contractApply",
|
|
1778
|
+
}),
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
const assessment = await ctx.assessments.get(assessmentId);
|
|
1782
|
+
if (!assessment) {
|
|
1783
|
+
return {
|
|
1784
|
+
kind: "error",
|
|
1785
|
+
status: 403,
|
|
1786
|
+
body: operatorFailed({
|
|
1787
|
+
command: action,
|
|
1788
|
+
outcome: "blocked",
|
|
1789
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
1790
|
+
message: "未找到评估结果",
|
|
1791
|
+
}),
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
const fresh = isAssessmentFreshForDraft(assessment, draftRec.draft);
|
|
1795
|
+
if (!fresh.fresh) {
|
|
1796
|
+
return {
|
|
1797
|
+
kind: "error",
|
|
1798
|
+
status: 403,
|
|
1799
|
+
body: operatorFailed({
|
|
1800
|
+
command: action,
|
|
1801
|
+
outcome: "blocked",
|
|
1802
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
1803
|
+
message: fresh.reason,
|
|
1804
|
+
}),
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
const staged = await stageUtf8Text(ctx.appData, JSON.stringify(toCanonicalDraftForCli(draftRec.draft), null, 2), { extension: ".json" });
|
|
1808
|
+
// Lifecycle apply via task advance --from-draft (managed contract projection).
|
|
1809
|
+
// Optimistic concurrency remains inside applyTaskContract via prepareTaskSource.
|
|
1810
|
+
const statusBody = await runReadCli(ctx, ["task", "status", taskId, "--json"], "task status");
|
|
1811
|
+
const statusContract = (statusBody.result ?? {}).contract;
|
|
1812
|
+
const payloadSha = staged.sha256;
|
|
1813
|
+
return acceptOperation(ctx, {
|
|
1814
|
+
action,
|
|
1815
|
+
actionParams: {
|
|
1816
|
+
taskId,
|
|
1817
|
+
assessmentId,
|
|
1818
|
+
draftSha256: draftRec.draftSha256,
|
|
1819
|
+
requestPayloadSha256: payloadSha,
|
|
1820
|
+
expectedRevision: Number(p.expectedRevision ?? statusContract?.revision ?? 0),
|
|
1821
|
+
expectedObservedHash: str(p.expectedObservedHash) ??
|
|
1822
|
+
statusContract?.observedCanonicalHash ??
|
|
1823
|
+
null,
|
|
1824
|
+
effectiveStatus: statusContract?.effectiveStatus,
|
|
1825
|
+
},
|
|
1826
|
+
clientRequestId,
|
|
1827
|
+
taskId,
|
|
1828
|
+
cliArgs: [
|
|
1829
|
+
"task",
|
|
1830
|
+
"advance",
|
|
1831
|
+
taskId,
|
|
1832
|
+
"--from-draft",
|
|
1833
|
+
staged.path,
|
|
1834
|
+
"--json",
|
|
1835
|
+
],
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
case "dagRunTask": {
|
|
1839
|
+
const taskId = str(p.taskId);
|
|
1840
|
+
const clientRequestId = str(req.clientRequestId);
|
|
1841
|
+
if (!taskId || !clientRequestId) {
|
|
1842
|
+
return invalid(action, "taskId and clientRequestId are required");
|
|
1843
|
+
}
|
|
1844
|
+
const taskIdError = taskIdInputError(taskId);
|
|
1845
|
+
if (taskIdError)
|
|
1846
|
+
return invalid(action, taskIdError);
|
|
1847
|
+
const out = await stageUtf8Text(ctx.appData, "", { extension: ".json" });
|
|
1848
|
+
return acceptOperation(ctx, {
|
|
1849
|
+
action,
|
|
1850
|
+
actionParams: p,
|
|
1851
|
+
clientRequestId,
|
|
1852
|
+
taskId,
|
|
1853
|
+
// Lifecycle generate + strict validate; stops at writeSet gate.
|
|
1854
|
+
cliArgs: [
|
|
1855
|
+
"task",
|
|
1856
|
+
"advance",
|
|
1857
|
+
taskId,
|
|
1858
|
+
"--profile",
|
|
1859
|
+
str(p.profile) ?? "auto",
|
|
1860
|
+
"--dag-output",
|
|
1861
|
+
out.path,
|
|
1862
|
+
"--json",
|
|
1863
|
+
],
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
case "runDag": {
|
|
1867
|
+
// Model path (AC-02): only executionId — never a raw dag path from
|
|
1868
|
+
// the model. The browser Human Gate route (chat human-gate dag-run)
|
|
1869
|
+
// still dispatches with confirmationId for the preserved global
|
|
1870
|
+
// Human Gate flow; that path is not model-reachable (the confirmation
|
|
1871
|
+
// must be in "confirmed" state, which requires a server-signed
|
|
1872
|
+
// human-gate token verified by confirmDagConfirmation).
|
|
1873
|
+
const clientRequestId = str(req.clientRequestId);
|
|
1874
|
+
const executionId = str(p.executionId);
|
|
1875
|
+
if (!clientRequestId) {
|
|
1876
|
+
return invalid(action, "runDag requires clientRequestId");
|
|
1877
|
+
}
|
|
1878
|
+
if (executionId && str(p.confirmationId)) {
|
|
1879
|
+
return invalid(action, "runDag accepts either executionId or confirmationId, not both");
|
|
1880
|
+
}
|
|
1881
|
+
if (!executionId) {
|
|
1882
|
+
const confirmationId = str(p.confirmationId);
|
|
1883
|
+
if (!confirmationId) {
|
|
1884
|
+
return invalid(action, "runDag requires executionId (autonomous) or confirmationId (browser Human Gate)");
|
|
1885
|
+
}
|
|
1886
|
+
// Preserved browser Human Gate flow (dag-confirmation.ts untouched).
|
|
1887
|
+
const conf = await ctx.confirmations.get(confirmationId);
|
|
1888
|
+
if (!conf) {
|
|
1889
|
+
return {
|
|
1890
|
+
kind: "error",
|
|
1891
|
+
status: 404,
|
|
1892
|
+
body: operatorFailed({
|
|
1893
|
+
command: action,
|
|
1894
|
+
outcome: "not-found",
|
|
1895
|
+
code: "NOT_FOUND",
|
|
1896
|
+
message: `confirmation not found: ${confirmationId}`,
|
|
1897
|
+
}),
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
// Re-read staged DAG bytes and hash.
|
|
1901
|
+
let dagBytes;
|
|
1902
|
+
try {
|
|
1903
|
+
dagBytes = await readFile(conf.dagBytesPath, "utf8");
|
|
1904
|
+
}
|
|
1905
|
+
catch (error) {
|
|
1906
|
+
return {
|
|
1907
|
+
kind: "error",
|
|
1908
|
+
status: 409,
|
|
1909
|
+
body: operatorFailed({
|
|
1910
|
+
command: action,
|
|
1911
|
+
outcome: "conflict",
|
|
1912
|
+
code: "CONFIRMATION_STALE",
|
|
1913
|
+
message: `cannot re-read staged DAG bytes: ${error instanceof Error ? error.message : String(error)}`,
|
|
1914
|
+
}),
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
const dagSha256 = hashDagBytes(dagBytes);
|
|
1918
|
+
const controller = ctx.client.getIdentity?.()?.packageFingerprint?.value ??
|
|
1919
|
+
conf.controllerFingerprint;
|
|
1920
|
+
const consumed = await ctx.confirmations.consume({
|
|
1921
|
+
confirmationId,
|
|
1922
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
1923
|
+
current: {
|
|
1924
|
+
dagSha256,
|
|
1925
|
+
sourceBindingSha256: conf.sourceBindingSha256,
|
|
1926
|
+
taskContractBinding: conf.taskContractBinding,
|
|
1927
|
+
controllerFingerprint: controller,
|
|
1928
|
+
validationResultSha256: conf.validationResultSha256,
|
|
1929
|
+
},
|
|
1930
|
+
});
|
|
1931
|
+
if (!consumed.ok) {
|
|
1932
|
+
return {
|
|
1933
|
+
kind: "error",
|
|
1934
|
+
status: confirmationErrorStatus(consumed.code),
|
|
1935
|
+
body: operatorFailed({
|
|
1936
|
+
command: action,
|
|
1937
|
+
outcome: "rejected",
|
|
1938
|
+
code: consumed.code,
|
|
1939
|
+
message: consumed.message,
|
|
1940
|
+
}),
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
const taskId = conf.taskContractBinding.taskId;
|
|
1944
|
+
const gateToken = readWriteSetGateToken(conf.reviewPacket);
|
|
1945
|
+
if (gateToken) {
|
|
1946
|
+
return acceptOperation(ctx, {
|
|
1947
|
+
action,
|
|
1948
|
+
actionParams: { confirmationId },
|
|
1949
|
+
clientRequestId,
|
|
1950
|
+
taskId,
|
|
1951
|
+
cliArgs: [
|
|
1952
|
+
"task",
|
|
1953
|
+
"advance",
|
|
1954
|
+
taskId,
|
|
1955
|
+
"--approve-gate",
|
|
1956
|
+
gateToken,
|
|
1957
|
+
"--json",
|
|
1958
|
+
],
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
return acceptOperation(ctx, {
|
|
1962
|
+
action,
|
|
1963
|
+
actionParams: { confirmationId },
|
|
1964
|
+
clientRequestId,
|
|
1965
|
+
taskId,
|
|
1966
|
+
cliArgs: [
|
|
1967
|
+
"dag",
|
|
1968
|
+
"execute",
|
|
1969
|
+
"--dag",
|
|
1970
|
+
conf.dagBytesPath,
|
|
1971
|
+
"--cwd",
|
|
1972
|
+
ctx.repoRoot,
|
|
1973
|
+
"--json",
|
|
1974
|
+
],
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
const receipt = await ctx.dagExecutionReceipts.get(executionId);
|
|
1978
|
+
if (!receipt) {
|
|
1979
|
+
return {
|
|
1980
|
+
kind: "error",
|
|
1981
|
+
status: 404,
|
|
1982
|
+
body: operatorFailed({
|
|
1983
|
+
command: action,
|
|
1984
|
+
outcome: "not-found",
|
|
1985
|
+
code: "NOT_FOUND",
|
|
1986
|
+
message: `execution receipt not found: ${executionId}`,
|
|
1987
|
+
}),
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
const taskId = receipt.taskId;
|
|
1991
|
+
// Live server-derived bindings only; any drift fails closed inside
|
|
1992
|
+
// consume (staged bytes/hash + controller + task binding + source).
|
|
1993
|
+
const controller = ctx.client.getIdentity?.()?.packageFingerprint?.value ??
|
|
1994
|
+
receipt.controllerFingerprint;
|
|
1995
|
+
let sourceBindingSha256 = receipt.sourceBindingSha256;
|
|
1996
|
+
try {
|
|
1997
|
+
const draftRec = await ctx.drafts.get(taskId);
|
|
1998
|
+
if (draftRec)
|
|
1999
|
+
sourceBindingSha256 = draftRec.draftSha256;
|
|
2000
|
+
}
|
|
2001
|
+
catch {
|
|
2002
|
+
// keep the receipt binding; consume will still compare it
|
|
2003
|
+
}
|
|
2004
|
+
const liveBinding = await readLiveTaskContractBinding(ctx, taskId);
|
|
2005
|
+
if (!liveBinding) {
|
|
2006
|
+
return {
|
|
2007
|
+
kind: "error",
|
|
2008
|
+
status: 409,
|
|
2009
|
+
body: operatorFailed({
|
|
2010
|
+
command: action,
|
|
2011
|
+
outcome: "blocked",
|
|
2012
|
+
code: "BINDING_DRIFT",
|
|
2013
|
+
message: "managed Task Contract binding unavailable at consume time",
|
|
2014
|
+
details: { taskId },
|
|
2015
|
+
}),
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
const consumed = await ctx.dagExecutionReceipts.consume({
|
|
2019
|
+
executionId,
|
|
2020
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
2021
|
+
controllerFingerprint: controller,
|
|
2022
|
+
taskContractBinding: liveBinding,
|
|
2023
|
+
sourceBindingSha256,
|
|
2024
|
+
validationResultSha256: receipt.validationResultSha256,
|
|
2025
|
+
});
|
|
2026
|
+
if (!consumed.ok) {
|
|
2027
|
+
return {
|
|
2028
|
+
kind: "error",
|
|
2029
|
+
status: confirmationErrorStatus(consumed.code),
|
|
2030
|
+
body: operatorFailed({
|
|
2031
|
+
command: action,
|
|
2032
|
+
outcome: "rejected",
|
|
2033
|
+
code: consumed.code,
|
|
2034
|
+
message: consumed.message,
|
|
2035
|
+
}),
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
const gateToken = readWriteSetGateToken(receipt.reviewPacket);
|
|
2039
|
+
const dispatchRun = async () => {
|
|
2040
|
+
if (gateToken) {
|
|
2041
|
+
// Standard lifecycle path: the execution receipt replaces the
|
|
2042
|
+
// browser confirm; the gate token is still consumed by the CLI.
|
|
2043
|
+
return acceptOperation(ctx, {
|
|
2044
|
+
action,
|
|
2045
|
+
actionParams: { executionId },
|
|
2046
|
+
clientRequestId,
|
|
2047
|
+
taskId,
|
|
2048
|
+
cliArgs: [
|
|
2049
|
+
"task",
|
|
2050
|
+
"advance",
|
|
2051
|
+
taskId,
|
|
2052
|
+
"--approve-gate",
|
|
2053
|
+
gateToken,
|
|
2054
|
+
"--json",
|
|
2055
|
+
],
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
// Advanced: execute the server-staged DAG bytes (never a model path).
|
|
2059
|
+
return acceptOperation(ctx, {
|
|
2060
|
+
action,
|
|
2061
|
+
actionParams: { executionId },
|
|
2062
|
+
clientRequestId,
|
|
2063
|
+
taskId,
|
|
2064
|
+
cliArgs: [
|
|
2065
|
+
"dag",
|
|
2066
|
+
"execute",
|
|
2067
|
+
"--dag",
|
|
2068
|
+
receipt.dagBytesPath,
|
|
2069
|
+
"--cwd",
|
|
2070
|
+
ctx.repoRoot,
|
|
2071
|
+
"--json",
|
|
2072
|
+
],
|
|
2073
|
+
});
|
|
2074
|
+
};
|
|
2075
|
+
const accepted = await dispatchRun();
|
|
2076
|
+
if (accepted.kind === "accepted") {
|
|
2077
|
+
await ctx.dagExecutionReceipts.markDispatched(executionId, accepted.body.operationId);
|
|
2078
|
+
}
|
|
2079
|
+
return accepted;
|
|
2080
|
+
}
|
|
2081
|
+
case "dagRerunPlan": {
|
|
2082
|
+
const runId = str(p.runId);
|
|
2083
|
+
const fromNode = str(p.fromNode);
|
|
2084
|
+
if (!runId || !fromNode) {
|
|
2085
|
+
return invalid(action, "runId and fromNode are required");
|
|
2086
|
+
}
|
|
2087
|
+
const body = await runReadCli(ctx, [
|
|
2088
|
+
"dag",
|
|
2089
|
+
"rerun",
|
|
2090
|
+
"--run-id",
|
|
2091
|
+
runId,
|
|
2092
|
+
"--from-node",
|
|
2093
|
+
fromNode,
|
|
2094
|
+
"--plan",
|
|
2095
|
+
"--json",
|
|
2096
|
+
], "dag rerun plan");
|
|
2097
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
2098
|
+
}
|
|
2099
|
+
case "dagRerun": {
|
|
2100
|
+
const runId = str(p.runId);
|
|
2101
|
+
const fromNode = str(p.fromNode);
|
|
2102
|
+
const planHash = str(p.planHash);
|
|
2103
|
+
const reason = str(p.reason);
|
|
2104
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2105
|
+
if (!runId || !fromNode || !planHash || !reason || !clientRequestId) {
|
|
2106
|
+
return invalid(action, "runId, fromNode, planHash, reason, and clientRequestId are required");
|
|
2107
|
+
}
|
|
2108
|
+
return acceptOperation(ctx, {
|
|
2109
|
+
action,
|
|
2110
|
+
actionParams: p,
|
|
2111
|
+
clientRequestId,
|
|
2112
|
+
cliArgs: [
|
|
2113
|
+
"dag",
|
|
2114
|
+
"rerun",
|
|
2115
|
+
"--run-id",
|
|
2116
|
+
runId,
|
|
2117
|
+
"--from-node",
|
|
2118
|
+
fromNode,
|
|
2119
|
+
"--plan-hash",
|
|
2120
|
+
planHash,
|
|
2121
|
+
"--request-id",
|
|
2122
|
+
clientRequestId,
|
|
2123
|
+
"--reason",
|
|
2124
|
+
reason,
|
|
2125
|
+
"--json",
|
|
2126
|
+
],
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
case "dagInterruptEligibility": {
|
|
2130
|
+
const runId = str(p.runId) ?? str(p.dagRunId);
|
|
2131
|
+
if (!runId)
|
|
2132
|
+
return invalid(action, "runId is required");
|
|
2133
|
+
const eligibility = await evaluateConsoleDagInterruptEligibility({
|
|
2134
|
+
repoRoot: ctx.repoRoot,
|
|
2135
|
+
operations: ctx.operations,
|
|
2136
|
+
runId,
|
|
2137
|
+
});
|
|
2138
|
+
if (eligibility.interrupt && eligibility.targetOperationId) {
|
|
2139
|
+
await syncTargetInterruptProjection({
|
|
2140
|
+
operations: ctx.operations,
|
|
2141
|
+
events: ctx.events,
|
|
2142
|
+
operationId: eligibility.targetOperationId,
|
|
2143
|
+
runId,
|
|
2144
|
+
interrupt: eligibility.interrupt,
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
return {
|
|
2148
|
+
kind: "sync",
|
|
2149
|
+
status: 200,
|
|
2150
|
+
body: operatorSucceeded(action, eligibility),
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
case "dagRequestInterrupt": {
|
|
2154
|
+
const runId = str(p.runId);
|
|
2155
|
+
const reasonCode = str(p.reasonCode);
|
|
2156
|
+
const reasonDetail = str(p.reasonDetail) ?? str(p.reason);
|
|
2157
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2158
|
+
if (!runId || !reasonCode || !reasonDetail || !clientRequestId) {
|
|
2159
|
+
return invalid(action, "runId, reasonCode, reasonDetail, and clientRequestId are required");
|
|
2160
|
+
}
|
|
2161
|
+
if (!isInterruptReasonCode(reasonCode)) {
|
|
2162
|
+
return invalid(action, `reasonCode must be one of requirements-changed | suspected-runaway | resource-protection | operator-request | other`);
|
|
2163
|
+
}
|
|
2164
|
+
try {
|
|
2165
|
+
normalizeInterruptReasonDetail(reasonDetail);
|
|
2166
|
+
}
|
|
2167
|
+
catch (error) {
|
|
2168
|
+
return invalid(action, error instanceof Error ? error.message : String(error));
|
|
2169
|
+
}
|
|
2170
|
+
const gated = await consumeMutationGateReceipt(ctx, action, p);
|
|
2171
|
+
if (gated.kind === "error")
|
|
2172
|
+
return gated;
|
|
2173
|
+
if (gated.kind === "idempotent")
|
|
2174
|
+
return gated.result;
|
|
2175
|
+
const eligibility = await evaluateConsoleDagInterruptEligibility({
|
|
2176
|
+
repoRoot: ctx.repoRoot,
|
|
2177
|
+
operations: ctx.operations,
|
|
2178
|
+
runId,
|
|
2179
|
+
});
|
|
2180
|
+
if (!eligibility.eligible || !eligibility.targetOperationId || !eligibility.expectedRunnerIdentity) {
|
|
2181
|
+
const failed = interruptActionFailed(action, eligibility.reasonCodes[0] ?? "RUN_NOT_ACTIVE", eligibility.message);
|
|
2182
|
+
return failed;
|
|
2183
|
+
}
|
|
2184
|
+
const expected = eligibility.expectedRunnerIdentity;
|
|
2185
|
+
const accepted = await acceptOperation(ctx, {
|
|
2186
|
+
action,
|
|
2187
|
+
actionParams: {
|
|
2188
|
+
runId,
|
|
2189
|
+
reasonCode,
|
|
2190
|
+
reasonDetail,
|
|
2191
|
+
targetOperationId: eligibility.targetOperationId,
|
|
2192
|
+
expectedRunnerIdentity: expected,
|
|
2193
|
+
},
|
|
2194
|
+
clientRequestId,
|
|
2195
|
+
cliArgs: [
|
|
2196
|
+
"dag",
|
|
2197
|
+
"request-interrupt",
|
|
2198
|
+
"--run-id",
|
|
2199
|
+
runId,
|
|
2200
|
+
"--request-id",
|
|
2201
|
+
clientRequestId,
|
|
2202
|
+
"--target-operation-id",
|
|
2203
|
+
eligibility.targetOperationId,
|
|
2204
|
+
"--reason-code",
|
|
2205
|
+
reasonCode,
|
|
2206
|
+
"--reason",
|
|
2207
|
+
reasonDetail,
|
|
2208
|
+
"--expected-runner-pid",
|
|
2209
|
+
String(expected.pid),
|
|
2210
|
+
"--expected-runner-hostname",
|
|
2211
|
+
expected.hostname,
|
|
2212
|
+
"--expected-runner-started-at",
|
|
2213
|
+
expected.startedAt,
|
|
2214
|
+
"--expected-controller-fingerprint",
|
|
2215
|
+
expected.controllerFingerprint,
|
|
2216
|
+
"--json",
|
|
2217
|
+
],
|
|
2218
|
+
});
|
|
2219
|
+
await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
|
|
2220
|
+
return accepted;
|
|
2221
|
+
}
|
|
2222
|
+
case "dagReconcileRun": {
|
|
2223
|
+
// S6 Recovery: orphan/interrupted runs — structured reason is the human
|
|
2224
|
+
// confirmation surface (CLI eligibility still fail-closed).
|
|
2225
|
+
const runId = str(p.runId);
|
|
2226
|
+
const reason = str(p.reason);
|
|
2227
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2228
|
+
const reconcileActionRaw = str(p.reconcileAction) ?? "supersede";
|
|
2229
|
+
if (reconcileActionRaw !== "supersede" &&
|
|
2230
|
+
reconcileActionRaw !== "abandon") {
|
|
2231
|
+
return invalid(action, "reconcileAction must be supersede or abandon");
|
|
2232
|
+
}
|
|
2233
|
+
const reconcileAction = reconcileActionRaw;
|
|
2234
|
+
if (!runId || !reason || !clientRequestId) {
|
|
2235
|
+
return invalid(action, "runId, reason, and clientRequestId are required");
|
|
2236
|
+
}
|
|
2237
|
+
const gated = await consumeMutationGateReceipt(ctx, action, p);
|
|
2238
|
+
if (gated.kind === "error")
|
|
2239
|
+
return gated;
|
|
2240
|
+
if (gated.kind === "idempotent")
|
|
2241
|
+
return gated.result;
|
|
2242
|
+
const accepted = await acceptOperation(ctx, {
|
|
2243
|
+
action,
|
|
2244
|
+
actionParams: {
|
|
2245
|
+
runId,
|
|
2246
|
+
reason,
|
|
2247
|
+
reconcileAction,
|
|
2248
|
+
},
|
|
2249
|
+
clientRequestId,
|
|
2250
|
+
cliArgs: [
|
|
2251
|
+
"dag",
|
|
2252
|
+
"reconcile-run",
|
|
2253
|
+
"--run-id",
|
|
2254
|
+
runId,
|
|
2255
|
+
"--action",
|
|
2256
|
+
reconcileAction,
|
|
2257
|
+
"--reason",
|
|
2258
|
+
reason,
|
|
2259
|
+
"--json",
|
|
2260
|
+
],
|
|
2261
|
+
});
|
|
2262
|
+
await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
|
|
2263
|
+
return accepted;
|
|
2264
|
+
}
|
|
2265
|
+
case "standaloneTaskRerun": {
|
|
2266
|
+
const runId = str(p.runId);
|
|
2267
|
+
const reason = str(p.reason);
|
|
2268
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2269
|
+
if (!runId || !reason || !clientRequestId) {
|
|
2270
|
+
return invalid(action, "runId, reason, and clientRequestId are required");
|
|
2271
|
+
}
|
|
2272
|
+
// 2026-08-11: read-only run-facts check replaces the browser Human
|
|
2273
|
+
// Gate (R2). Fails closed when evidence is missing.
|
|
2274
|
+
const gated = await assertAutonomousRerunEligible(ctx, runId, str(p.taskId));
|
|
2275
|
+
if (gated.kind === "error")
|
|
2276
|
+
return gated;
|
|
2277
|
+
const profile = str(p.profile);
|
|
2278
|
+
const taskId = str(p.taskId);
|
|
2279
|
+
const cliArgs = [
|
|
2280
|
+
"dag",
|
|
2281
|
+
"rerun-task",
|
|
2282
|
+
"--run-id",
|
|
2283
|
+
runId,
|
|
2284
|
+
"--reason",
|
|
2285
|
+
reason,
|
|
2286
|
+
"--request-id",
|
|
2287
|
+
clientRequestId,
|
|
2288
|
+
"--json",
|
|
2289
|
+
];
|
|
2290
|
+
if (profile)
|
|
2291
|
+
cliArgs.push("--profile", profile);
|
|
2292
|
+
if (taskId)
|
|
2293
|
+
cliArgs.push("--task-id", taskId);
|
|
2294
|
+
return acceptOperation(ctx, {
|
|
2295
|
+
action,
|
|
2296
|
+
actionParams: p,
|
|
2297
|
+
clientRequestId,
|
|
2298
|
+
taskId,
|
|
2299
|
+
cliArgs,
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
case "workerTaskRetry": {
|
|
2303
|
+
const taskId = str(p.taskId);
|
|
2304
|
+
const featureId = str(p.featureId);
|
|
2305
|
+
const reason = str(p.reason);
|
|
2306
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2307
|
+
if (!taskId || !reason || !clientRequestId) {
|
|
2308
|
+
return invalid(action, "taskId, reason, and clientRequestId are required");
|
|
2309
|
+
}
|
|
2310
|
+
// 2026-08-11: read-only pool facts check replaces the browser Human
|
|
2311
|
+
// Gate (task must exist in Failed state; featureId consistent).
|
|
2312
|
+
const gated = await assertWorkerTaskRetryFacts(ctx, taskId, featureId);
|
|
2313
|
+
if (gated.kind === "error")
|
|
2314
|
+
return gated;
|
|
2315
|
+
const args = [
|
|
2316
|
+
"task",
|
|
2317
|
+
"retry",
|
|
2318
|
+
taskId,
|
|
2319
|
+
"--repo",
|
|
2320
|
+
ctx.repoRoot,
|
|
2321
|
+
"--reason",
|
|
2322
|
+
reason,
|
|
2323
|
+
];
|
|
2324
|
+
if (featureId)
|
|
2325
|
+
args.push("--feature-id", featureId);
|
|
2326
|
+
return acceptOperation(ctx, {
|
|
2327
|
+
action,
|
|
2328
|
+
actionParams: p,
|
|
2329
|
+
clientRequestId,
|
|
2330
|
+
taskId,
|
|
2331
|
+
cliArgs: args,
|
|
2332
|
+
externalCommand: resolveSiblingAgentWorkerBin(),
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
case "workerAdmissionPrepare": {
|
|
2336
|
+
const featureDir = str(p.featureDir);
|
|
2337
|
+
const taskId = str(p.taskId);
|
|
2338
|
+
const at = str(p.at);
|
|
2339
|
+
const tz = str(p.tz);
|
|
2340
|
+
const taskCard = str(p.taskCard);
|
|
2341
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2342
|
+
if (!featureDir || !taskId || !at || !clientRequestId) {
|
|
2343
|
+
return invalid(action, "featureDir, taskId, at, and clientRequestId are required");
|
|
2344
|
+
}
|
|
2345
|
+
const gated = await consumeMutationGateReceipt(ctx, action, p);
|
|
2346
|
+
if (gated.kind === "error")
|
|
2347
|
+
return gated;
|
|
2348
|
+
if (gated.kind === "idempotent")
|
|
2349
|
+
return gated.result;
|
|
2350
|
+
const args = [
|
|
2351
|
+
"admission",
|
|
2352
|
+
"prepare",
|
|
2353
|
+
"--repo",
|
|
2354
|
+
ctx.repoRoot,
|
|
2355
|
+
"--feature-dir",
|
|
2356
|
+
featureDir,
|
|
2357
|
+
"--task-id",
|
|
2358
|
+
taskId,
|
|
2359
|
+
"--at",
|
|
2360
|
+
at,
|
|
2361
|
+
"--json",
|
|
2362
|
+
];
|
|
2363
|
+
if (tz)
|
|
2364
|
+
args.push("--tz", tz);
|
|
2365
|
+
if (taskCard)
|
|
2366
|
+
args.push("--task-card", taskCard);
|
|
2367
|
+
const accepted = await acceptOperation(ctx, {
|
|
2368
|
+
action,
|
|
2369
|
+
actionParams: p,
|
|
2370
|
+
clientRequestId,
|
|
2371
|
+
taskId,
|
|
2372
|
+
cliArgs: args,
|
|
2373
|
+
externalCommand: resolveSiblingAgentWorkerBin(),
|
|
2374
|
+
});
|
|
2375
|
+
await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
|
|
2376
|
+
return accepted;
|
|
2377
|
+
}
|
|
2378
|
+
case "workerSchedulerAdd": {
|
|
2379
|
+
const scheduleId = str(p.scheduleId);
|
|
2380
|
+
const approveGate = str(p.approveGate);
|
|
2381
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2382
|
+
if (!scheduleId || !approveGate || !clientRequestId) {
|
|
2383
|
+
return invalid(action, "scheduleId, approveGate, and clientRequestId are required");
|
|
2384
|
+
}
|
|
2385
|
+
const gated = await consumeMutationGateReceipt(ctx, action, p);
|
|
2386
|
+
if (gated.kind === "error")
|
|
2387
|
+
return gated;
|
|
2388
|
+
if (gated.kind === "idempotent")
|
|
2389
|
+
return gated.result;
|
|
2390
|
+
const accepted = await acceptOperation(ctx, {
|
|
2391
|
+
action,
|
|
2392
|
+
actionParams: p,
|
|
2393
|
+
clientRequestId,
|
|
2394
|
+
cliArgs: [
|
|
2395
|
+
"scheduler",
|
|
2396
|
+
"add",
|
|
2397
|
+
scheduleId,
|
|
2398
|
+
"--repo",
|
|
2399
|
+
ctx.repoRoot,
|
|
2400
|
+
"--approve-gate",
|
|
2401
|
+
approveGate,
|
|
2402
|
+
"--json",
|
|
2403
|
+
],
|
|
2404
|
+
externalCommand: resolveSiblingAgentWorkerBin(),
|
|
2405
|
+
});
|
|
2406
|
+
await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
|
|
2407
|
+
return accepted;
|
|
2408
|
+
}
|
|
2409
|
+
case "workerSchedulerCancel": {
|
|
2410
|
+
const scheduleId = str(p.scheduleId);
|
|
2411
|
+
const reason = str(p.reason);
|
|
2412
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2413
|
+
if (!scheduleId || !clientRequestId) {
|
|
2414
|
+
return invalid(action, "scheduleId and clientRequestId are required");
|
|
2415
|
+
}
|
|
2416
|
+
const gated = await consumeMutationGateReceipt(ctx, action, p);
|
|
2417
|
+
if (gated.kind === "error")
|
|
2418
|
+
return gated;
|
|
2419
|
+
if (gated.kind === "idempotent")
|
|
2420
|
+
return gated.result;
|
|
2421
|
+
const args = [
|
|
2422
|
+
"scheduler",
|
|
2423
|
+
"cancel",
|
|
2424
|
+
scheduleId,
|
|
2425
|
+
"--repo",
|
|
2426
|
+
ctx.repoRoot,
|
|
2427
|
+
"--json",
|
|
2428
|
+
];
|
|
2429
|
+
if (reason)
|
|
2430
|
+
args.push("--reason", reason);
|
|
2431
|
+
const accepted = await acceptOperation(ctx, {
|
|
2432
|
+
action,
|
|
2433
|
+
actionParams: p,
|
|
2434
|
+
clientRequestId,
|
|
2435
|
+
cliArgs: args,
|
|
2436
|
+
externalCommand: resolveSiblingAgentWorkerBin(),
|
|
2437
|
+
});
|
|
2438
|
+
await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
|
|
2439
|
+
return accepted;
|
|
2440
|
+
}
|
|
2441
|
+
case "workerSchedulerHarvest": {
|
|
2442
|
+
const scheduleId = str(p.scheduleId);
|
|
2443
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2444
|
+
if (!scheduleId || !clientRequestId) {
|
|
2445
|
+
return invalid(action, "scheduleId and clientRequestId are required");
|
|
2446
|
+
}
|
|
2447
|
+
const gated = await consumeMutationGateReceipt(ctx, action, p);
|
|
2448
|
+
if (gated.kind === "error")
|
|
2449
|
+
return gated;
|
|
2450
|
+
if (gated.kind === "idempotent")
|
|
2451
|
+
return gated.result;
|
|
2452
|
+
const accepted = await acceptOperation(ctx, {
|
|
2453
|
+
action,
|
|
2454
|
+
actionParams: p,
|
|
2455
|
+
clientRequestId,
|
|
2456
|
+
cliArgs: [
|
|
2457
|
+
"scheduler",
|
|
2458
|
+
"harvest",
|
|
2459
|
+
scheduleId,
|
|
2460
|
+
"--repo",
|
|
2461
|
+
ctx.repoRoot,
|
|
2462
|
+
"--json",
|
|
2463
|
+
],
|
|
2464
|
+
externalCommand: resolveSiblingAgentWorkerBin(),
|
|
2465
|
+
});
|
|
2466
|
+
await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
|
|
2467
|
+
return accepted;
|
|
2468
|
+
}
|
|
2469
|
+
case "workerSchedulerDiscard": {
|
|
2470
|
+
const scheduleId = str(p.scheduleId);
|
|
2471
|
+
const reason = str(p.reason);
|
|
2472
|
+
const force = p.force === true;
|
|
2473
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2474
|
+
if (!scheduleId || !reason || !clientRequestId) {
|
|
2475
|
+
return invalid(action, "scheduleId, reason, and clientRequestId are required");
|
|
2476
|
+
}
|
|
2477
|
+
const gated = await consumeMutationGateReceipt(ctx, action, p);
|
|
2478
|
+
if (gated.kind === "error")
|
|
2479
|
+
return gated;
|
|
2480
|
+
if (gated.kind === "idempotent")
|
|
2481
|
+
return gated.result;
|
|
2482
|
+
const args = [
|
|
2483
|
+
"scheduler",
|
|
2484
|
+
"discard",
|
|
2485
|
+
scheduleId,
|
|
2486
|
+
"--repo",
|
|
2487
|
+
ctx.repoRoot,
|
|
2488
|
+
"--reason",
|
|
2489
|
+
reason,
|
|
2490
|
+
"--json",
|
|
2491
|
+
];
|
|
2492
|
+
if (force)
|
|
2493
|
+
args.push("--force");
|
|
2494
|
+
const accepted = await acceptOperation(ctx, {
|
|
2495
|
+
action,
|
|
2496
|
+
actionParams: p,
|
|
2497
|
+
clientRequestId,
|
|
2498
|
+
cliArgs: args,
|
|
2499
|
+
externalCommand: resolveSiblingAgentWorkerBin(),
|
|
2500
|
+
});
|
|
2501
|
+
await finalizeMutationGateReceipt(ctx, gated.receiptId, accepted);
|
|
2502
|
+
return accepted;
|
|
2503
|
+
}
|
|
2504
|
+
case "prepareMutationGate": {
|
|
2505
|
+
const targetAction = str(p.action);
|
|
2506
|
+
const actionParams = p.actionParams &&
|
|
2507
|
+
typeof p.actionParams === "object" &&
|
|
2508
|
+
!Array.isArray(p.actionParams)
|
|
2509
|
+
? p.actionParams
|
|
2510
|
+
: undefined;
|
|
2511
|
+
if (!targetAction || !actionParams) {
|
|
2512
|
+
return invalid(action, "action and actionParams are required");
|
|
2513
|
+
}
|
|
2514
|
+
if (!MUTATION_GATE_ACTIONS.has(targetAction)) {
|
|
2515
|
+
return invalid(action, "action must be a mutation-gate target (dagReconcileRun | dagRequestInterrupt | workerAdmissionPrepare | workerSchedulerAdd | workerSchedulerCancel | workerSchedulerHarvest | workerSchedulerDiscard)");
|
|
2516
|
+
}
|
|
2517
|
+
const secret = ctx.humanGateSecret;
|
|
2518
|
+
if (!secret) {
|
|
2519
|
+
return {
|
|
2520
|
+
kind: "error",
|
|
2521
|
+
status: 503,
|
|
2522
|
+
body: operatorFailed({
|
|
2523
|
+
command: action,
|
|
2524
|
+
outcome: "blocked",
|
|
2525
|
+
code: "INTERNAL_ERROR",
|
|
2526
|
+
message: "humanGateSecret is not configured on action context",
|
|
2527
|
+
}),
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
const paramsHash = mutationGatePayloadHash({
|
|
2531
|
+
action: targetAction,
|
|
2532
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
2533
|
+
actionParams,
|
|
2534
|
+
});
|
|
2535
|
+
const expiresAt = new Date(Date.now() + 30 * 60_000).toISOString();
|
|
2536
|
+
const receiptId = `mut_${crypto.randomUUID()}`;
|
|
2537
|
+
const humanGateToken = issueHumanGateToken({ confirmationId: receiptId, payloadHash: paramsHash, expiresAt }, secret);
|
|
2538
|
+
const store = new MutationGateReceiptStore(ctx.appData);
|
|
2539
|
+
const receipt = await store.prepare({
|
|
2540
|
+
receiptId,
|
|
2541
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
2542
|
+
action: targetAction,
|
|
2543
|
+
paramsHash,
|
|
2544
|
+
actionParams,
|
|
2545
|
+
expiresAt,
|
|
2546
|
+
humanGateToken,
|
|
2547
|
+
});
|
|
2548
|
+
return {
|
|
2549
|
+
kind: "sync",
|
|
2550
|
+
status: 200,
|
|
2551
|
+
body: operatorSucceeded("prepareMutationGate", {
|
|
2552
|
+
receiptId: receipt.receiptId,
|
|
2553
|
+
action: receipt.action,
|
|
2554
|
+
expiresAt: receipt.expiresAt,
|
|
2555
|
+
paramsHash: receipt.paramsHash,
|
|
2556
|
+
humanGateToken: receipt.humanGateToken,
|
|
2557
|
+
state: receipt.state,
|
|
2558
|
+
}),
|
|
2559
|
+
};
|
|
2560
|
+
}
|
|
2561
|
+
case "savePiModelMatrix": {
|
|
2562
|
+
// 「模型与认证」页模型矩阵编辑器:三档字符串值经校验后原子写入
|
|
2563
|
+
// harness.json executors.pi,并自动重测 readiness。写路径本身由 HTTP
|
|
2564
|
+
// 层 evaluateMutationGate 拦截(cookie + confirmation 头 + origin)。
|
|
2565
|
+
const matrixInput = p.matrix ?? p;
|
|
2566
|
+
const validation = validatePiModelMatrixTiers(matrixInput);
|
|
2567
|
+
if (!validation.ok) {
|
|
2568
|
+
return {
|
|
2569
|
+
kind: "error",
|
|
2570
|
+
status: 400,
|
|
2571
|
+
body: operatorFailed({
|
|
2572
|
+
command: action,
|
|
2573
|
+
outcome: "invalid",
|
|
2574
|
+
code: validation.code,
|
|
2575
|
+
message: validation.message,
|
|
2576
|
+
...(validation.tier ? { details: { tier: validation.tier } } : {}),
|
|
2577
|
+
}),
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
try {
|
|
2581
|
+
await writePiModelMatrix(ctx.repoRoot, validation.tiers);
|
|
2582
|
+
}
|
|
2583
|
+
catch (error) {
|
|
2584
|
+
return {
|
|
2585
|
+
kind: "error",
|
|
2586
|
+
status: 500,
|
|
2587
|
+
body: operatorFailed({
|
|
2588
|
+
command: action,
|
|
2589
|
+
outcome: "blocked",
|
|
2590
|
+
code: "PI_MODEL_MATRIX_WRITE_FAILED",
|
|
2591
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2592
|
+
}),
|
|
2593
|
+
};
|
|
2594
|
+
}
|
|
2595
|
+
// 保存成功自动重测 readiness(复用 probePiReadiness),让页面立即
|
|
2596
|
+
// 展示新的 modelsAvailable / modelsMessage / piModelMatrix(AC-4)。
|
|
2597
|
+
const report = ctx.refreshReadiness
|
|
2598
|
+
? await ctx.refreshReadiness()
|
|
2599
|
+
: ctx.getReadiness?.() ?? ctx.readiness;
|
|
2600
|
+
return {
|
|
2601
|
+
kind: "sync",
|
|
2602
|
+
status: 200,
|
|
2603
|
+
body: operatorSucceeded("savePiModelMatrix", {
|
|
2604
|
+
tiers: validation.tiers,
|
|
2605
|
+
readiness: report,
|
|
2606
|
+
}),
|
|
2607
|
+
};
|
|
2608
|
+
}
|
|
2609
|
+
default: {
|
|
2610
|
+
// M5 official-command coverage uses one generic tail for newly registered
|
|
2611
|
+
// CLI actions. It remains inside dispatchOperatorAction (the sole dispatch
|
|
2612
|
+
// surface) and only accepts argv arrays declared by the capability schema.
|
|
2613
|
+
// 2026-08-18: high-frequency entries got real descriptions; membership is
|
|
2614
|
+
// now derived from OFFICIAL_TAIL_ACTION_IDS, not description prefixes.
|
|
2615
|
+
const capability = buildOperatorCapabilitiesDocument().actions.find((entry) => entry.action === action && OFFICIAL_TAIL_ACTION_IDS.has(entry.action));
|
|
2616
|
+
if (capability) {
|
|
2617
|
+
const rawArgs = p.args;
|
|
2618
|
+
if (rawArgs !== undefined &&
|
|
2619
|
+
(!Array.isArray(rawArgs) ||
|
|
2620
|
+
rawArgs.some((value) => typeof value !== "string"))) {
|
|
2621
|
+
return invalid(action, "args must be an array of strings");
|
|
2622
|
+
}
|
|
2623
|
+
if (TASK_ADVANCE_ALIAS_ACTIONS.has(action) && Array.isArray(rawArgs)) {
|
|
2624
|
+
const aliasTaskId = str(rawArgs[0]);
|
|
2625
|
+
const taskIdError = aliasTaskId
|
|
2626
|
+
? taskIdInputError(aliasTaskId)
|
|
2627
|
+
: undefined;
|
|
2628
|
+
if (taskIdError)
|
|
2629
|
+
return invalid(action, taskIdError);
|
|
2630
|
+
}
|
|
2631
|
+
if (capability.humanConfirmation === "required") {
|
|
2632
|
+
const confirmationId = str(p.confirmationId);
|
|
2633
|
+
if (!confirmationId)
|
|
2634
|
+
return invalid(action, "confirmationId is required");
|
|
2635
|
+
const conf = await ctx.confirmations.get(confirmationId);
|
|
2636
|
+
if (!conf) {
|
|
2637
|
+
return {
|
|
2638
|
+
kind: "error",
|
|
2639
|
+
status: 404,
|
|
2640
|
+
body: operatorFailed({
|
|
2641
|
+
command: action,
|
|
2642
|
+
outcome: "not-found",
|
|
2643
|
+
code: "NOT_FOUND",
|
|
2644
|
+
message: `confirmation not found: ${confirmationId}`,
|
|
2645
|
+
}),
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
const confirmedArgs = conf.reviewPacket?.operatorActionArgs;
|
|
2649
|
+
const requestedArgs = rawArgs ?? [];
|
|
2650
|
+
if (conf.operatorSessionId !== ctx.operatorSessionId ||
|
|
2651
|
+
conf.reviewPacket?.operatorAction !== action ||
|
|
2652
|
+
!Array.isArray(confirmedArgs) ||
|
|
2653
|
+
JSON.stringify(confirmedArgs) !== JSON.stringify(requestedArgs)) {
|
|
2654
|
+
return {
|
|
2655
|
+
kind: "error",
|
|
2656
|
+
status: 403,
|
|
2657
|
+
body: operatorFailed({
|
|
2658
|
+
command: action,
|
|
2659
|
+
outcome: "blocked",
|
|
2660
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
2661
|
+
message: "confirmation receipt is not bound to this action, arguments, and operator session",
|
|
2662
|
+
}),
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
let receiptBytes;
|
|
2666
|
+
try {
|
|
2667
|
+
receiptBytes = await readFile(conf.dagBytesPath, "utf8");
|
|
2668
|
+
}
|
|
2669
|
+
catch (error) {
|
|
2670
|
+
return {
|
|
2671
|
+
kind: "error",
|
|
2672
|
+
status: 409,
|
|
2673
|
+
body: operatorFailed({
|
|
2674
|
+
command: action,
|
|
2675
|
+
outcome: "conflict",
|
|
2676
|
+
code: "CONFIRMATION_STALE",
|
|
2677
|
+
message: `cannot re-read confirmation receipt bytes: ${error instanceof Error ? error.message : String(error)}`,
|
|
2678
|
+
}),
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
const consumed = await ctx.confirmations.consume({
|
|
2682
|
+
confirmationId,
|
|
2683
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
2684
|
+
current: {
|
|
2685
|
+
dagSha256: hashDagBytes(receiptBytes),
|
|
2686
|
+
sourceBindingSha256: conf.sourceBindingSha256,
|
|
2687
|
+
taskContractBinding: conf.taskContractBinding,
|
|
2688
|
+
controllerFingerprint: ctx.client.getIdentity?.()?.packageFingerprint?.value ??
|
|
2689
|
+
conf.controllerFingerprint,
|
|
2690
|
+
validationResultSha256: conf.validationResultSha256,
|
|
2691
|
+
},
|
|
2692
|
+
});
|
|
2693
|
+
if (!consumed.ok) {
|
|
2694
|
+
return {
|
|
2695
|
+
kind: "error",
|
|
2696
|
+
status: confirmationErrorStatus(consumed.code),
|
|
2697
|
+
body: operatorFailed({
|
|
2698
|
+
command: action,
|
|
2699
|
+
outcome: "rejected",
|
|
2700
|
+
code: consumed.code,
|
|
2701
|
+
message: consumed.message,
|
|
2702
|
+
}),
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
const prefix = capability.cli
|
|
2707
|
+
.replace(/^(loop-agent|agent-worker)\s+/, "")
|
|
2708
|
+
.split(/\s+/);
|
|
2709
|
+
const args = [...prefix, ...(rawArgs ?? [])];
|
|
2710
|
+
if (TASK_ADVANCE_ALIAS_ACTIONS.has(action) && !args.includes("--json")) {
|
|
2711
|
+
args.push("--json");
|
|
2712
|
+
}
|
|
2713
|
+
if (capability.kind === "read") {
|
|
2714
|
+
if (!args.includes("--json"))
|
|
2715
|
+
args.push("--json");
|
|
2716
|
+
const body = await runReadCli(ctx, args, prefix.join(" "));
|
|
2717
|
+
return { kind: "sync", status: body.ok ? 200 : 400, body };
|
|
2718
|
+
}
|
|
2719
|
+
const clientRequestId = str(req.clientRequestId);
|
|
2720
|
+
if (!clientRequestId)
|
|
2721
|
+
return invalid(action, "clientRequestId is required");
|
|
2722
|
+
return acceptOperation(ctx, {
|
|
2723
|
+
action,
|
|
2724
|
+
actionParams: p,
|
|
2725
|
+
clientRequestId,
|
|
2726
|
+
cliArgs: args,
|
|
2727
|
+
...(capability.cli.startsWith("agent-worker ")
|
|
2728
|
+
? { externalCommand: resolveSiblingAgentWorkerBin() }
|
|
2729
|
+
: {}),
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
return {
|
|
2733
|
+
kind: "error",
|
|
2734
|
+
status: 400,
|
|
2735
|
+
body: operatorFailed({
|
|
2736
|
+
command: action,
|
|
2737
|
+
outcome: "invalid",
|
|
2738
|
+
code: "INVALID_INPUT",
|
|
2739
|
+
message: `unknown or unsupported action: ${action}`,
|
|
2740
|
+
}),
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
async function consumeMutationGateReceipt(ctx, action, p) {
|
|
2746
|
+
const confirmationId = str(p.confirmationId);
|
|
2747
|
+
const humanGateToken = p.humanGateToken;
|
|
2748
|
+
if (!confirmationId) {
|
|
2749
|
+
return {
|
|
2750
|
+
kind: "error",
|
|
2751
|
+
status: 400,
|
|
2752
|
+
body: operatorFailed({
|
|
2753
|
+
command: action,
|
|
2754
|
+
outcome: "blocked",
|
|
2755
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
2756
|
+
message: "confirmationId is required — call prepareMutationGate then confirm via the browser Human Gate",
|
|
2757
|
+
}),
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
const secret = ctx.humanGateSecret;
|
|
2761
|
+
if (!secret) {
|
|
2762
|
+
return {
|
|
2763
|
+
kind: "error",
|
|
2764
|
+
status: 503,
|
|
2765
|
+
body: operatorFailed({
|
|
2766
|
+
command: action,
|
|
2767
|
+
outcome: "blocked",
|
|
2768
|
+
code: "INTERNAL_ERROR",
|
|
2769
|
+
message: "humanGateSecret is not configured on action context",
|
|
2770
|
+
}),
|
|
2771
|
+
};
|
|
2772
|
+
}
|
|
2773
|
+
const store = new MutationGateReceiptStore(ctx.appData);
|
|
2774
|
+
const receipt = await store.get(confirmationId);
|
|
2775
|
+
if (!receipt || receipt.operatorSessionId !== ctx.operatorSessionId) {
|
|
2776
|
+
return {
|
|
2777
|
+
kind: "error",
|
|
2778
|
+
status: 404,
|
|
2779
|
+
body: operatorFailed({
|
|
2780
|
+
command: action,
|
|
2781
|
+
outcome: "not-found",
|
|
2782
|
+
code: "NOT_FOUND",
|
|
2783
|
+
message: `mutation gate receipt not found: ${confirmationId}`,
|
|
2784
|
+
}),
|
|
2785
|
+
};
|
|
2786
|
+
}
|
|
2787
|
+
if (receipt.action !== action) {
|
|
2788
|
+
return {
|
|
2789
|
+
kind: "error",
|
|
2790
|
+
status: 403,
|
|
2791
|
+
body: operatorFailed({
|
|
2792
|
+
command: action,
|
|
2793
|
+
outcome: "blocked",
|
|
2794
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
2795
|
+
message: "mutation gate receipt is bound to a different action",
|
|
2796
|
+
}),
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
const { confirmationId: _cid, humanGateToken: _tok, ...boundParams } = p;
|
|
2800
|
+
const expectedHash = mutationGatePayloadHash({
|
|
2801
|
+
action,
|
|
2802
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
2803
|
+
actionParams: receipt.actionParams,
|
|
2804
|
+
});
|
|
2805
|
+
if (expectedHash !== receipt.paramsHash) {
|
|
2806
|
+
return {
|
|
2807
|
+
kind: "error",
|
|
2808
|
+
status: 403,
|
|
2809
|
+
body: operatorFailed({
|
|
2810
|
+
command: action,
|
|
2811
|
+
outcome: "blocked",
|
|
2812
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
2813
|
+
message: "mutation gate receipt params hash mismatch",
|
|
2814
|
+
}),
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
// Bound params from the receipt must match the dispatch params (minus gate fields).
|
|
2818
|
+
const presentedParams = {};
|
|
2819
|
+
for (const key of Object.keys(receipt.actionParams)) {
|
|
2820
|
+
presentedParams[key] = boundParams[key];
|
|
2821
|
+
}
|
|
2822
|
+
const presentedHash = mutationGatePayloadHash({
|
|
2823
|
+
action,
|
|
2824
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
2825
|
+
actionParams: presentedParams,
|
|
2826
|
+
});
|
|
2827
|
+
if (presentedHash !== receipt.paramsHash) {
|
|
2828
|
+
return {
|
|
2829
|
+
kind: "error",
|
|
2830
|
+
status: 403,
|
|
2831
|
+
body: operatorFailed({
|
|
2832
|
+
command: action,
|
|
2833
|
+
outcome: "blocked",
|
|
2834
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
2835
|
+
message: "dispatch params do not match the prepared mutation gate receipt",
|
|
2836
|
+
}),
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
const check = verifyHumanGateToken(humanGateToken, { confirmationId: receipt.receiptId, payloadHash: receipt.paramsHash }, secret);
|
|
2840
|
+
if (!check.ok) {
|
|
2841
|
+
return {
|
|
2842
|
+
kind: "error",
|
|
2843
|
+
status: 403,
|
|
2844
|
+
body: operatorFailed({
|
|
2845
|
+
command: action,
|
|
2846
|
+
outcome: "blocked",
|
|
2847
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
2848
|
+
message: check.message,
|
|
2849
|
+
}),
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
const reserved = await store.tryReserve(confirmationId);
|
|
2853
|
+
if (!reserved.ok) {
|
|
2854
|
+
return {
|
|
2855
|
+
kind: "error",
|
|
2856
|
+
status: reserved.code === "NOT_FOUND"
|
|
2857
|
+
? 404
|
|
2858
|
+
: reserved.code === "EXPIRED"
|
|
2859
|
+
? 410
|
|
2860
|
+
: 409,
|
|
2861
|
+
body: operatorFailed({
|
|
2862
|
+
command: action,
|
|
2863
|
+
outcome: "rejected",
|
|
2864
|
+
code: reserved.code === "EXPIRED"
|
|
2865
|
+
? "CONFIRMATION_EXPIRED"
|
|
2866
|
+
: "HUMAN_CONFIRMATION_REQUIRED",
|
|
2867
|
+
message: reserved.message,
|
|
2868
|
+
}),
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
if (!reserved.reserved) {
|
|
2872
|
+
const operationId = reserved.receipt.operationId;
|
|
2873
|
+
if (operationId) {
|
|
2874
|
+
const existing = await ctx.operations.get(operationId);
|
|
2875
|
+
if (existing) {
|
|
2876
|
+
return {
|
|
2877
|
+
kind: "idempotent",
|
|
2878
|
+
result: {
|
|
2879
|
+
kind: "accepted",
|
|
2880
|
+
status: 202,
|
|
2881
|
+
body: {
|
|
2882
|
+
operationId: existing.operationId,
|
|
2883
|
+
state: existing.state,
|
|
2884
|
+
clientRequestId: existing.clientRequestId,
|
|
2885
|
+
action: existing.action,
|
|
2886
|
+
},
|
|
2887
|
+
},
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
return {
|
|
2892
|
+
kind: "error",
|
|
2893
|
+
status: 409,
|
|
2894
|
+
body: operatorFailed({
|
|
2895
|
+
command: action,
|
|
2896
|
+
outcome: "conflict",
|
|
2897
|
+
code: "CONFIRMATION_CONSUMED",
|
|
2898
|
+
message: `mutation gate receipt already ${reserved.reason}`,
|
|
2899
|
+
details: { operationId },
|
|
2900
|
+
}),
|
|
2901
|
+
};
|
|
2902
|
+
}
|
|
2903
|
+
return { kind: "ready", receiptId: confirmationId };
|
|
2904
|
+
}
|
|
2905
|
+
async function finalizeMutationGateReceipt(ctx, receiptId, accepted) {
|
|
2906
|
+
const store = new MutationGateReceiptStore(ctx.appData);
|
|
2907
|
+
if (accepted.kind === "accepted") {
|
|
2908
|
+
await store.markDispatched(receiptId, accepted.body.operationId);
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
await store.releaseToPrepared(receiptId);
|
|
2912
|
+
}
|
|
2913
|
+
async function taskExistsOnDisk(repoRoot, taskId) {
|
|
2914
|
+
try {
|
|
2915
|
+
await access(getTaskPaths(repoRoot, taskId).taskConfigPath);
|
|
2916
|
+
return true;
|
|
2917
|
+
}
|
|
2918
|
+
catch {
|
|
2919
|
+
return false;
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
async function allocateUniqueTaskId(repoRoot, preferredId) {
|
|
2923
|
+
for (let revision = 1; revision <= 50; revision += 1) {
|
|
2924
|
+
const candidate = nextTaskIdRevision(preferredId, revision);
|
|
2925
|
+
if (!(await taskExistsOnDisk(repoRoot, candidate)))
|
|
2926
|
+
return candidate;
|
|
2927
|
+
}
|
|
2928
|
+
throw new Error(`unable to allocate unique taskId near ${preferredId}`);
|
|
2929
|
+
}
|
|
2930
|
+
async function dispatchBootstrapFromPrd(ctx, p) {
|
|
2931
|
+
const content = str(p.content) ?? str(p.fileText);
|
|
2932
|
+
if (!content?.trim()) {
|
|
2933
|
+
return invalid("bootstrapFromPrd", "content is required");
|
|
2934
|
+
}
|
|
2935
|
+
const parsedDocs = parseBootstrapDocuments(p.documents);
|
|
2936
|
+
if (!parsedDocs.ok) {
|
|
2937
|
+
return invalid("bootstrapFromPrd", parsedDocs.message);
|
|
2938
|
+
}
|
|
2939
|
+
const documents = parsedDocs.documents;
|
|
2940
|
+
// B1: free-form prose that references repo-local markdown paths (e.g.
|
|
2941
|
+
// "请参照这三个prd路径 docs/product-analysis/foo.md …") only materializes
|
|
2942
|
+
// the sentence itself. Discover the referenced files and import them as
|
|
2943
|
+
// additional documents so intake has real requirement facts to structure.
|
|
2944
|
+
let autoImportedReferences = [];
|
|
2945
|
+
let unresolvedReferences = [];
|
|
2946
|
+
const autoDocs = [];
|
|
2947
|
+
if (!documents) {
|
|
2948
|
+
const discovery = await discoverPrdReferences({
|
|
2949
|
+
prose: content,
|
|
2950
|
+
repoRoot: ctx.repoRoot,
|
|
2951
|
+
});
|
|
2952
|
+
autoImportedReferences = discovery.references.map((r) => ({
|
|
2953
|
+
repoRelative: r.repoRelative,
|
|
2954
|
+
role: r.role,
|
|
2955
|
+
}));
|
|
2956
|
+
unresolvedReferences = discovery.unresolved;
|
|
2957
|
+
for (const ref of discovery.references) {
|
|
2958
|
+
const absolute = nodePath.resolve(ctx.repoRoot, ref.repoRelative);
|
|
2959
|
+
const refContent = await readFile(absolute, "utf-8").catch(() => "");
|
|
2960
|
+
if (!refContent.trim())
|
|
2961
|
+
continue;
|
|
2962
|
+
autoDocs.push({
|
|
2963
|
+
name: ref.repoRelative.split("/").pop() ?? ref.repoRelative,
|
|
2964
|
+
content: refContent,
|
|
2965
|
+
fileName: ref.repoRelative,
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
const taskKindRaw = str(p.taskKind)?.trim();
|
|
2970
|
+
const taskKind = normalizeConsoleWorkflowKind(taskKindRaw, "standard");
|
|
2971
|
+
const taskKindExplicit = Boolean(taskKindRaw);
|
|
2972
|
+
const engineering = parseEngineeringBoundaryFromParams(p);
|
|
2973
|
+
const identity = deriveTaskIdentityFromPrd(content);
|
|
2974
|
+
let taskId;
|
|
2975
|
+
try {
|
|
2976
|
+
taskId = await allocateUniqueTaskId(ctx.repoRoot, identity.taskId);
|
|
2977
|
+
}
|
|
2978
|
+
catch (error) {
|
|
2979
|
+
return {
|
|
2980
|
+
kind: "error",
|
|
2981
|
+
status: 409,
|
|
2982
|
+
body: operatorFailed({
|
|
2983
|
+
command: "bootstrapFromPrd",
|
|
2984
|
+
outcome: "conflict",
|
|
2985
|
+
code: "REQUEST_ID_REUSE_CONFLICT",
|
|
2986
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2987
|
+
}),
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
const title = identity.title;
|
|
2991
|
+
const run = ctx.runCommand ?? ((a, o) => ctx.client.run(a, o));
|
|
2992
|
+
// Standard lifecycle entry: task advance create + PRD import(s).
|
|
2993
|
+
const advanceCreate = await run(["task", "advance", taskId, title, "--json"], {
|
|
2994
|
+
cwd: ctx.repoRoot,
|
|
2995
|
+
artifactName: "bootstrap-task-advance-create",
|
|
2996
|
+
expectJson: true,
|
|
2997
|
+
timeoutMs: 60_000,
|
|
2998
|
+
});
|
|
2999
|
+
if (!advanceCreate.ok ||
|
|
3000
|
+
advanceCreate.exitCode !== 0 ||
|
|
3001
|
+
advanceCreate.timedOut) {
|
|
3002
|
+
return {
|
|
3003
|
+
kind: "error",
|
|
3004
|
+
status: 400,
|
|
3005
|
+
body: operatorFailed({
|
|
3006
|
+
command: "bootstrapFromPrd",
|
|
3007
|
+
outcome: "rejected",
|
|
3008
|
+
code: "INTERNAL_ERROR",
|
|
3009
|
+
message: advanceCreate.stderr?.trim() ||
|
|
3010
|
+
`task advance (create) failed with exit ${advanceCreate.exitCode}`,
|
|
3011
|
+
details: {
|
|
3012
|
+
exitCode: advanceCreate.exitCode,
|
|
3013
|
+
timedOut: advanceCreate.timedOut,
|
|
3014
|
+
taskId,
|
|
3015
|
+
title,
|
|
3016
|
+
stdoutPreview: (advanceCreate.stdout ?? "").slice(0, 400),
|
|
3017
|
+
},
|
|
3018
|
+
}),
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3021
|
+
const succeededImports = [];
|
|
3022
|
+
const importResults = [];
|
|
3023
|
+
const runImportViaAdvance = async (label, text, name, role = "requirement") => {
|
|
3024
|
+
const staged = await stageUtf8Text(ctx.appData, text, {
|
|
3025
|
+
extension: ".md",
|
|
3026
|
+
});
|
|
3027
|
+
const imported = await run([
|
|
3028
|
+
"task",
|
|
3029
|
+
"advance",
|
|
3030
|
+
taskId,
|
|
3031
|
+
"--prd",
|
|
3032
|
+
staged.path,
|
|
3033
|
+
"--role",
|
|
3034
|
+
role,
|
|
3035
|
+
"--json",
|
|
3036
|
+
], {
|
|
3037
|
+
cwd: ctx.repoRoot,
|
|
3038
|
+
artifactName: `bootstrap-task-advance-prd-${label}`,
|
|
3039
|
+
expectJson: true,
|
|
3040
|
+
timeoutMs: 60_000,
|
|
3041
|
+
});
|
|
3042
|
+
if (!imported.ok || imported.exitCode !== 0) {
|
|
3043
|
+
return {
|
|
3044
|
+
kind: "error",
|
|
3045
|
+
status: 400,
|
|
3046
|
+
body: operatorFailed({
|
|
3047
|
+
command: "bootstrapFromPrd",
|
|
3048
|
+
outcome: "rejected",
|
|
3049
|
+
code: "INTERNAL_ERROR",
|
|
3050
|
+
message: imported.stderr?.trim() ||
|
|
3051
|
+
`task advance --prd failed for "${name}" with exit ${imported.exitCode}. 任务已创建但不完整,请用 CLI 补 PRD 或换 taskId 重试。`,
|
|
3052
|
+
details: {
|
|
3053
|
+
exitCode: imported.exitCode,
|
|
3054
|
+
taskId,
|
|
3055
|
+
failedName: name,
|
|
3056
|
+
succeededImports: [...succeededImports],
|
|
3057
|
+
},
|
|
3058
|
+
}),
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
succeededImports.push(name);
|
|
3062
|
+
importResults.push(imported.json ?? null);
|
|
3063
|
+
return null;
|
|
3064
|
+
};
|
|
3065
|
+
if (documents && documents.length > 0) {
|
|
3066
|
+
for (const doc of documents) {
|
|
3067
|
+
const failure = await runImportViaAdvance(doc.name, doc.content, doc.name);
|
|
3068
|
+
if (failure)
|
|
3069
|
+
return failure;
|
|
3070
|
+
}
|
|
3071
|
+
const composedFailure = await runImportViaAdvance("composed", content, "composed");
|
|
3072
|
+
if (composedFailure)
|
|
3073
|
+
return composedFailure;
|
|
3074
|
+
}
|
|
3075
|
+
else if (autoDocs.length > 0) {
|
|
3076
|
+
// B1: discovered repo-local references become explicit imports (role
|
|
3077
|
+
// inferred per document), then the prose itself imports as composed.
|
|
3078
|
+
const roleByBase = new Map(autoImportedReferences.map((r) => {
|
|
3079
|
+
const base = r.repoRelative.split("/").pop() ?? r.repoRelative;
|
|
3080
|
+
return [base, r.role];
|
|
3081
|
+
}));
|
|
3082
|
+
for (const doc of autoDocs) {
|
|
3083
|
+
const role = roleByBase.get(doc.name) ?? "requirement";
|
|
3084
|
+
const failure = await runImportViaAdvance(doc.name, doc.content, doc.name, role);
|
|
3085
|
+
if (failure)
|
|
3086
|
+
return failure;
|
|
3087
|
+
}
|
|
3088
|
+
const composedFailure = await runImportViaAdvance("composed", content, "composed");
|
|
3089
|
+
if (composedFailure)
|
|
3090
|
+
return composedFailure;
|
|
3091
|
+
}
|
|
3092
|
+
else {
|
|
3093
|
+
const staged = await stageUtf8Text(ctx.appData, content, {
|
|
3094
|
+
extension: ".md",
|
|
3095
|
+
});
|
|
3096
|
+
const imported = await run(["task", "advance", taskId, "--prd", staged.path, "--json"], {
|
|
3097
|
+
cwd: ctx.repoRoot,
|
|
3098
|
+
artifactName: "bootstrap-task-advance-prd",
|
|
3099
|
+
expectJson: true,
|
|
3100
|
+
timeoutMs: 60_000,
|
|
3101
|
+
});
|
|
3102
|
+
if (!imported.ok || imported.exitCode !== 0) {
|
|
3103
|
+
return {
|
|
3104
|
+
kind: "error",
|
|
3105
|
+
status: 400,
|
|
3106
|
+
body: operatorFailed({
|
|
3107
|
+
command: "bootstrapFromPrd",
|
|
3108
|
+
outcome: "rejected",
|
|
3109
|
+
code: "INTERNAL_ERROR",
|
|
3110
|
+
message: imported.stderr?.trim() ||
|
|
3111
|
+
`task advance --prd failed with exit ${imported.exitCode}`,
|
|
3112
|
+
details: { exitCode: imported.exitCode, taskId },
|
|
3113
|
+
}),
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
importResults.push(imported.json ?? null);
|
|
3117
|
+
}
|
|
3118
|
+
// C: a fresh import (manual or discovered) is new evidence; clear the
|
|
3119
|
+
// one-shot semantic-intake attempt marker so intake may run again.
|
|
3120
|
+
if (succeededImports.length > 0) {
|
|
3121
|
+
await clearSemanticIntakeAttempt(ctx.repoRoot, taskId);
|
|
3122
|
+
}
|
|
3123
|
+
// Phase 2 (design 2026-08-11): drive task advance intake after import so
|
|
3124
|
+
// deterministic parse + one-shot Semantic Intake run on archived references.
|
|
3125
|
+
// Timeout is longer than import: Pi MED may take minutes.
|
|
3126
|
+
const intakeArgs = ["task", "advance", taskId, "--json"];
|
|
3127
|
+
if (taskKindExplicit) {
|
|
3128
|
+
intakeArgs.push("--task-kind", taskKind);
|
|
3129
|
+
}
|
|
3130
|
+
appendEngineeringCliArgs(intakeArgs, engineering);
|
|
3131
|
+
const intake = await run(intakeArgs, {
|
|
3132
|
+
cwd: ctx.repoRoot,
|
|
3133
|
+
artifactName: "bootstrap-task-advance-intake",
|
|
3134
|
+
expectJson: true,
|
|
3135
|
+
timeoutMs: SEMANTIC_INTAKE_CLI_TIMEOUT_MS,
|
|
3136
|
+
});
|
|
3137
|
+
const intakeJson = intake.json ?? null;
|
|
3138
|
+
const intakeOk = intake.ok && intake.exitCode === 0 && !intake.timedOut;
|
|
3139
|
+
const bridged = await buildDraftFromTaskIntake({
|
|
3140
|
+
repoRoot: ctx.repoRoot,
|
|
3141
|
+
taskId,
|
|
3142
|
+
title,
|
|
3143
|
+
taskKind,
|
|
3144
|
+
taskKindExplicit,
|
|
3145
|
+
advanceJson: intakeJson,
|
|
3146
|
+
engineering,
|
|
3147
|
+
});
|
|
3148
|
+
// Never fall back to grill-me product templates when intake fails.
|
|
3149
|
+
const draftSaved = await ctx.drafts.save(bridged.draft);
|
|
3150
|
+
const parseStatus = intakeOk || bridged.parseStatus !== "pending"
|
|
3151
|
+
? bridged.parseStatus
|
|
3152
|
+
: "failed";
|
|
3153
|
+
return {
|
|
3154
|
+
kind: "sync",
|
|
3155
|
+
status: 200,
|
|
3156
|
+
body: operatorSucceeded("bootstrapFromPrd", {
|
|
3157
|
+
taskId,
|
|
3158
|
+
title: draftSaved.draft.title ?? title,
|
|
3159
|
+
taskKind,
|
|
3160
|
+
identitySource: identity.source,
|
|
3161
|
+
preferredTaskId: identity.taskId,
|
|
3162
|
+
draftSha256: draftSaved.draftSha256,
|
|
3163
|
+
parseStatus,
|
|
3164
|
+
semanticIntake: bridged.semanticIntake,
|
|
3165
|
+
gaps: bridged.gaps,
|
|
3166
|
+
lifecycleState: bridged.lifecycleState ??
|
|
3167
|
+
(intakeOk ? "intake-incomplete" : "intake-incomplete"),
|
|
3168
|
+
intake: {
|
|
3169
|
+
ok: intakeOk,
|
|
3170
|
+
exitCode: intake.exitCode,
|
|
3171
|
+
timedOut: intake.timedOut,
|
|
3172
|
+
json: intakeJson,
|
|
3173
|
+
},
|
|
3174
|
+
import: importResults.length === 1 ? importResults[0] : null,
|
|
3175
|
+
imports: importResults,
|
|
3176
|
+
documentNames: documents && documents.length > 0
|
|
3177
|
+
? [...documents.map((d) => d.name), "composed"]
|
|
3178
|
+
: autoDocs.length > 0
|
|
3179
|
+
? [...autoDocs.map((d) => d.name), "composed"]
|
|
3180
|
+
: undefined,
|
|
3181
|
+
autoImportedReferences: autoImportedReferences.length > 0
|
|
3182
|
+
? autoImportedReferences
|
|
3183
|
+
: undefined,
|
|
3184
|
+
unresolvedReferences: unresolvedReferences.length > 0 ? unresolvedReferences : undefined,
|
|
3185
|
+
draftPreview: {
|
|
3186
|
+
objective: draftSaved.draft.requirement?.objective ?? null,
|
|
3187
|
+
scope: draftSaved.draft.requirement?.scope ?? [],
|
|
3188
|
+
acceptanceCriteria: draftSaved.draft.requirement?.acceptanceCriteria ?? [],
|
|
3189
|
+
},
|
|
3190
|
+
}),
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
const MAX_BOOTSTRAP_DOCUMENTS = 8;
|
|
3194
|
+
function parseBootstrapDocuments(raw) {
|
|
3195
|
+
if (raw === undefined || raw === null) {
|
|
3196
|
+
return { ok: true, documents: null };
|
|
3197
|
+
}
|
|
3198
|
+
if (!Array.isArray(raw)) {
|
|
3199
|
+
return { ok: false, message: "documents must be an array" };
|
|
3200
|
+
}
|
|
3201
|
+
if (raw.length === 0) {
|
|
3202
|
+
return { ok: true, documents: null };
|
|
3203
|
+
}
|
|
3204
|
+
if (raw.length > MAX_BOOTSTRAP_DOCUMENTS) {
|
|
3205
|
+
return {
|
|
3206
|
+
ok: false,
|
|
3207
|
+
message: `documents supports at most ${MAX_BOOTSTRAP_DOCUMENTS} entries`,
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
const documents = [];
|
|
3211
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
3212
|
+
const entry = raw[i];
|
|
3213
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
3214
|
+
return { ok: false, message: `documents[${i}] must be an object` };
|
|
3215
|
+
}
|
|
3216
|
+
const rec = entry;
|
|
3217
|
+
const name = str(rec.name)?.trim();
|
|
3218
|
+
const content = str(rec.content);
|
|
3219
|
+
const fileName = str(rec.fileName)?.trim();
|
|
3220
|
+
if (!name) {
|
|
3221
|
+
return { ok: false, message: `documents[${i}].name is required` };
|
|
3222
|
+
}
|
|
3223
|
+
if (!content?.trim()) {
|
|
3224
|
+
return {
|
|
3225
|
+
ok: false,
|
|
3226
|
+
message: `documents[${i}].content is required`,
|
|
3227
|
+
};
|
|
3228
|
+
}
|
|
3229
|
+
documents.push({
|
|
3230
|
+
name,
|
|
3231
|
+
content,
|
|
3232
|
+
...(fileName ? { fileName } : {}),
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
return { ok: true, documents };
|
|
3236
|
+
}
|
|
3237
|
+
async function ensureDraft(ctx, taskId, title, options) {
|
|
3238
|
+
const existing = await ctx.drafts.get(taskId);
|
|
3239
|
+
if (existing) {
|
|
3240
|
+
const seededKind = str(options?.taskKind);
|
|
3241
|
+
if (seededKind && !existing.draft.taskKind?.trim()) {
|
|
3242
|
+
const saved = await ctx.drafts.save({
|
|
3243
|
+
...existing.draft,
|
|
3244
|
+
taskKind: normalizeConsoleWorkflowKind(seededKind),
|
|
3245
|
+
});
|
|
3246
|
+
return { draft: saved.draft, draftSha256: saved.draftSha256 };
|
|
3247
|
+
}
|
|
3248
|
+
return { draft: existing.draft, draftSha256: existing.draftSha256 };
|
|
3249
|
+
}
|
|
3250
|
+
const created = await ctx.drafts.save(emptyDraft(taskId, title, { taskKind: options?.taskKind }));
|
|
3251
|
+
return { draft: created.draft, draftSha256: created.draftSha256 };
|
|
3252
|
+
}
|
|
3253
|
+
async function finalizeInterviewIfComplete(ctx, sessionId, draft) {
|
|
3254
|
+
const question = nextGrillMeQuestion(draft);
|
|
3255
|
+
if (question) {
|
|
3256
|
+
const session = await ctx.interviews.setCurrentQuestion(sessionId, {
|
|
3257
|
+
id: question.id,
|
|
3258
|
+
text: `${question.text}\n\n推荐:${question.recommendation}`,
|
|
3259
|
+
affectsFields: question.affectsFields,
|
|
3260
|
+
risk: question.risk,
|
|
3261
|
+
}, "eliciting");
|
|
3262
|
+
const saved = await ctx.drafts.save(draft);
|
|
3263
|
+
return {
|
|
3264
|
+
session,
|
|
3265
|
+
draft: saved.draft,
|
|
3266
|
+
draftSha256: saved.draftSha256,
|
|
3267
|
+
question,
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
if (!isDraftStructurallyComplete(draft)) {
|
|
3271
|
+
const saved = await ctx.drafts.save({
|
|
3272
|
+
...draft,
|
|
3273
|
+
openQuestions: listResidualNotes(draft),
|
|
3274
|
+
});
|
|
3275
|
+
const current = await ctx.interviews.get(sessionId);
|
|
3276
|
+
if (!current) {
|
|
3277
|
+
return {
|
|
3278
|
+
session: undefined,
|
|
3279
|
+
draft: saved.draft,
|
|
3280
|
+
draftSha256: saved.draftSha256,
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
const blocked = await ctx.interviews.save({
|
|
3284
|
+
...current,
|
|
3285
|
+
state: "validation-blocked",
|
|
3286
|
+
currentQuestion: undefined,
|
|
3287
|
+
});
|
|
3288
|
+
return {
|
|
3289
|
+
session: blocked,
|
|
3290
|
+
draft: saved.draft,
|
|
3291
|
+
draftSha256: saved.draftSha256,
|
|
3292
|
+
};
|
|
3293
|
+
}
|
|
3294
|
+
const saved = await ctx.drafts.save({
|
|
3295
|
+
...draft,
|
|
3296
|
+
openQuestions: [],
|
|
3297
|
+
});
|
|
3298
|
+
const assessment = await ctx.assessments.create({
|
|
3299
|
+
draft: saved.draft,
|
|
3300
|
+
instructionVersion: "grill-me@1",
|
|
3301
|
+
provider: "console",
|
|
3302
|
+
model: "deterministic-integrity-v1",
|
|
3303
|
+
sessionId,
|
|
3304
|
+
turnId: `turn_${saved.draftSha256.slice(0, 12)}`,
|
|
3305
|
+
outcome: "complete",
|
|
3306
|
+
});
|
|
3307
|
+
const withRef = await ctx.drafts.save({
|
|
3308
|
+
...saved.draft,
|
|
3309
|
+
interviewAssessmentRef: {
|
|
3310
|
+
assessmentId: assessment.assessmentId,
|
|
3311
|
+
draftSha256: saved.draftSha256,
|
|
3312
|
+
},
|
|
3313
|
+
});
|
|
3314
|
+
const session = await ctx.interviews.markDraftReady(sessionId, withRef.draft, withRef.draftSha256);
|
|
3315
|
+
await ctx.interviews.save({
|
|
3316
|
+
...session,
|
|
3317
|
+
assessmentId: assessment.assessmentId,
|
|
3318
|
+
state: "draft-ready",
|
|
3319
|
+
currentQuestion: undefined,
|
|
3320
|
+
});
|
|
3321
|
+
const ready = await ctx.interviews.get(sessionId);
|
|
3322
|
+
return {
|
|
3323
|
+
session: ready,
|
|
3324
|
+
draft: withRef.draft,
|
|
3325
|
+
draftSha256: withRef.draftSha256,
|
|
3326
|
+
assessmentId: assessment.assessmentId,
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3329
|
+
function listResidualNotes(draft) {
|
|
3330
|
+
return [
|
|
3331
|
+
`validation-blocked: Draft still missing required Interview fields: ${JSON.stringify(draftSummary(draft).gaps)}`,
|
|
3332
|
+
];
|
|
3333
|
+
}
|
|
3334
|
+
async function dispatchInterviewStart(ctx, p) {
|
|
3335
|
+
const taskId = str(p.taskId);
|
|
3336
|
+
if (!taskId)
|
|
3337
|
+
return invalid("interviewStart", "taskId is required");
|
|
3338
|
+
const title = str(p.title) ?? taskId;
|
|
3339
|
+
const taskKind = str(p.taskKind);
|
|
3340
|
+
const { draft } = await ensureDraft(ctx, taskId, title, { taskKind });
|
|
3341
|
+
const session = await ctx.interviews.create({
|
|
3342
|
+
taskId,
|
|
3343
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
3344
|
+
instructionVersion: "grill-me@1",
|
|
3345
|
+
});
|
|
3346
|
+
const finalized = await finalizeInterviewIfComplete(ctx, session.sessionId, draft);
|
|
3347
|
+
return {
|
|
3348
|
+
kind: "sync",
|
|
3349
|
+
status: 200,
|
|
3350
|
+
body: operatorSucceeded("interviewStart", {
|
|
3351
|
+
session: finalized.session,
|
|
3352
|
+
draft: finalized.draft,
|
|
3353
|
+
draftSha256: finalized.draftSha256,
|
|
3354
|
+
assessmentId: finalized.assessmentId ?? null,
|
|
3355
|
+
question: finalized.question
|
|
3356
|
+
? {
|
|
3357
|
+
id: finalized.question.id,
|
|
3358
|
+
text: finalized.question.text,
|
|
3359
|
+
recommendation: finalized.question.recommendation,
|
|
3360
|
+
affectsFields: finalized.question.affectsFields,
|
|
3361
|
+
risk: finalized.question.risk,
|
|
3362
|
+
gapId: finalized.question.gapId,
|
|
3363
|
+
}
|
|
3364
|
+
: null,
|
|
3365
|
+
summary: draftSummary(finalized.draft),
|
|
3366
|
+
zeroExtraQuestions: !finalized.question && Boolean(finalized.assessmentId),
|
|
3367
|
+
}),
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
/**
|
|
3371
|
+
* Skip interactive grilling only when requirement structure is already present
|
|
3372
|
+
* (from PRD intake) and remaining gaps are engineering/taskKind that can take
|
|
3373
|
+
* non-template recommendations. Never invent console product templates.
|
|
3374
|
+
*/
|
|
3375
|
+
async function dispatchInterviewSkip(ctx, p) {
|
|
3376
|
+
const taskId = str(p.taskId);
|
|
3377
|
+
if (!taskId)
|
|
3378
|
+
return invalid("interviewSkip", "taskId is required");
|
|
3379
|
+
const title = str(p.title) ?? taskId;
|
|
3380
|
+
const taskKind = str(p.taskKind);
|
|
3381
|
+
let { draft } = await ensureDraft(ctx, taskId, title, { taskKind });
|
|
3382
|
+
const requirementGaps = new Set([
|
|
3383
|
+
"objective",
|
|
3384
|
+
"scope",
|
|
3385
|
+
"nonGoals",
|
|
3386
|
+
"acceptanceCriteria",
|
|
3387
|
+
]);
|
|
3388
|
+
const currentGaps = draftSummary(draft).gaps;
|
|
3389
|
+
const missingRequirement = currentGaps.filter((g) => requirementGaps.has(g));
|
|
3390
|
+
if (missingRequirement.length > 0) {
|
|
3391
|
+
return {
|
|
3392
|
+
kind: "error",
|
|
3393
|
+
status: 400,
|
|
3394
|
+
body: operatorFailed({
|
|
3395
|
+
command: "interviewSkip",
|
|
3396
|
+
outcome: "invalid",
|
|
3397
|
+
code: "REQUIREMENT_INCOMPLETE",
|
|
3398
|
+
message: "无法跳过:需求层尚未结构化(缺 objective/scope/AC 等)。请先导入 PRD 完成 intake,或手动访谈补齐,禁止用模板冒充解析结果。",
|
|
3399
|
+
details: {
|
|
3400
|
+
gaps: currentGaps,
|
|
3401
|
+
missingRequirement,
|
|
3402
|
+
},
|
|
3403
|
+
}),
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
const accepted = [];
|
|
3407
|
+
// Only auto-accept engineering / taskKind recommendations; never product templates.
|
|
3408
|
+
const autoAcceptGaps = new Set([
|
|
3409
|
+
"taskKind",
|
|
3410
|
+
"allowedPaths",
|
|
3411
|
+
"forbiddenPaths",
|
|
3412
|
+
"verifyCommands",
|
|
3413
|
+
]);
|
|
3414
|
+
for (let i = 0; i < 24; i += 1) {
|
|
3415
|
+
const question = nextGrillMeQuestion(draft);
|
|
3416
|
+
if (!question)
|
|
3417
|
+
break;
|
|
3418
|
+
if (!autoAcceptGaps.has(question.gapId))
|
|
3419
|
+
break;
|
|
3420
|
+
// Refuse placeholder recommendations that are not real path/command values.
|
|
3421
|
+
if (question.gapId === "allowedPaths" &&
|
|
3422
|
+
(/请|示例|模板|(/.test(question.recommendation) ||
|
|
3423
|
+
!question.recommendation.includes("*"))) {
|
|
3424
|
+
break;
|
|
3425
|
+
}
|
|
3426
|
+
draft = applyGrillMeAnswer(draft, question, {
|
|
3427
|
+
response: "accept-recommendation",
|
|
3428
|
+
});
|
|
3429
|
+
accepted.push(question.gapId);
|
|
3430
|
+
}
|
|
3431
|
+
if (!isDraftStructurallyComplete(draft)) {
|
|
3432
|
+
const saved = await ctx.drafts.save({
|
|
3433
|
+
...draft,
|
|
3434
|
+
openQuestions: listResidualNotes(draft),
|
|
3435
|
+
});
|
|
3436
|
+
return {
|
|
3437
|
+
kind: "error",
|
|
3438
|
+
status: 400,
|
|
3439
|
+
body: operatorFailed({
|
|
3440
|
+
command: "interviewSkip",
|
|
3441
|
+
outcome: "invalid",
|
|
3442
|
+
code: "ENGINEERING_BOUNDARY_REQUIRED",
|
|
3443
|
+
message: "无法跳过:工程边界(allowedPaths / forbiddenPaths / verify)尚未确认,请在访谈中填写或通过 CLI flags 提供",
|
|
3444
|
+
details: {
|
|
3445
|
+
gaps: draftSummary(saved.draft).gaps,
|
|
3446
|
+
acceptedRecommendations: accepted,
|
|
3447
|
+
},
|
|
3448
|
+
}),
|
|
3449
|
+
};
|
|
3450
|
+
}
|
|
3451
|
+
const session = await ctx.interviews.create({
|
|
3452
|
+
taskId,
|
|
3453
|
+
operatorSessionId: ctx.operatorSessionId,
|
|
3454
|
+
instructionVersion: "grill-me@1+skip",
|
|
3455
|
+
});
|
|
3456
|
+
const priorAssumptions = Array.isArray(draft.assumptions)
|
|
3457
|
+
? draft.assumptions
|
|
3458
|
+
: [];
|
|
3459
|
+
const finalized = await finalizeInterviewIfComplete(ctx, session.sessionId, {
|
|
3460
|
+
...draft,
|
|
3461
|
+
assumptions: [
|
|
3462
|
+
...priorAssumptions,
|
|
3463
|
+
"operator skipped interactive interview; grill-me recommendations applied",
|
|
3464
|
+
],
|
|
3465
|
+
});
|
|
3466
|
+
if (!finalized.assessmentId) {
|
|
3467
|
+
return {
|
|
3468
|
+
kind: "error",
|
|
3469
|
+
status: 400,
|
|
3470
|
+
body: operatorFailed({
|
|
3471
|
+
command: "interviewSkip",
|
|
3472
|
+
outcome: "invalid",
|
|
3473
|
+
code: "INVALID_INPUT",
|
|
3474
|
+
message: "跳过访谈后未能签发完整评估",
|
|
3475
|
+
details: { gaps: draftSummary(finalized.draft).gaps },
|
|
3476
|
+
}),
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
// Re-tag assessment provenance as explicit skip (finalize uses deterministic model).
|
|
3480
|
+
const assessment = await ctx.assessments.get(finalized.assessmentId);
|
|
3481
|
+
if (assessment) {
|
|
3482
|
+
await ctx.assessments.save({
|
|
3483
|
+
...assessment,
|
|
3484
|
+
provider: "console-skip",
|
|
3485
|
+
model: "accept-recommendations-v1",
|
|
3486
|
+
instructionVersion: "grill-me@1+skip",
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
return {
|
|
3490
|
+
kind: "sync",
|
|
3491
|
+
status: 200,
|
|
3492
|
+
body: operatorSucceeded("interviewSkip", {
|
|
3493
|
+
session: finalized.session,
|
|
3494
|
+
draft: finalized.draft,
|
|
3495
|
+
draftSha256: finalized.draftSha256,
|
|
3496
|
+
assessmentId: finalized.assessmentId,
|
|
3497
|
+
question: null,
|
|
3498
|
+
summary: draftSummary(finalized.draft),
|
|
3499
|
+
skipped: true,
|
|
3500
|
+
acceptedRecommendations: accepted,
|
|
3501
|
+
complete: true,
|
|
3502
|
+
}),
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
async function dispatchInterviewTurn(ctx, p) {
|
|
3506
|
+
const sessionId = str(p.sessionId);
|
|
3507
|
+
if (!sessionId)
|
|
3508
|
+
return invalid("interviewTurn", "sessionId is required");
|
|
3509
|
+
const session = await ctx.interviews.get(sessionId);
|
|
3510
|
+
if (!session) {
|
|
3511
|
+
return {
|
|
3512
|
+
kind: "error",
|
|
3513
|
+
status: 404,
|
|
3514
|
+
body: operatorFailed({
|
|
3515
|
+
command: "interviewTurn",
|
|
3516
|
+
outcome: "not-found",
|
|
3517
|
+
code: "NOT_FOUND",
|
|
3518
|
+
message: `interview session not found: ${sessionId}`,
|
|
3519
|
+
}),
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3522
|
+
const questionId = str(p.questionId) ?? session.currentQuestion?.id;
|
|
3523
|
+
if (!questionId || !session.currentQuestion) {
|
|
3524
|
+
return invalid("interviewTurn", "no open question; call interviewStart or interviewGet");
|
|
3525
|
+
}
|
|
3526
|
+
if (session.currentQuestion.id !== questionId) {
|
|
3527
|
+
return invalid("interviewTurn", "questionId does not match open question");
|
|
3528
|
+
}
|
|
3529
|
+
const response = str(p.response) ??
|
|
3530
|
+
(str(p.acceptRecommendation) === "true"
|
|
3531
|
+
? "accept-recommendation"
|
|
3532
|
+
: str(p.text)
|
|
3533
|
+
? "override"
|
|
3534
|
+
: "accept-recommendation");
|
|
3535
|
+
const text = str(p.text);
|
|
3536
|
+
const draftRec = await ctx.drafts.get(session.taskId);
|
|
3537
|
+
const draft = draftRec?.draft ?? emptyDraft(session.taskId);
|
|
3538
|
+
const gapId = questionId.replace(/^q-/, "");
|
|
3539
|
+
const question = nextGrillMeQuestion(draft)?.id === questionId
|
|
3540
|
+
? nextGrillMeQuestion(draft)
|
|
3541
|
+
: {
|
|
3542
|
+
id: questionId,
|
|
3543
|
+
gapId,
|
|
3544
|
+
text: session.currentQuestion.text,
|
|
3545
|
+
recommendation: extractRecommendation(session.currentQuestion.text),
|
|
3546
|
+
affectsFields: session.currentQuestion.affectsFields,
|
|
3547
|
+
risk: session.currentQuestion.risk ?? "medium",
|
|
3548
|
+
};
|
|
3549
|
+
if (response === "defer" && question.risk === "high") {
|
|
3550
|
+
return {
|
|
3551
|
+
kind: "error",
|
|
3552
|
+
status: 403,
|
|
3553
|
+
body: operatorFailed({
|
|
3554
|
+
command: "interviewTurn",
|
|
3555
|
+
outcome: "blocked",
|
|
3556
|
+
code: "HUMAN_CONFIRMATION_REQUIRED",
|
|
3557
|
+
message: "高风险访谈问题不可暂缓",
|
|
3558
|
+
details: { questionId, risk: question.risk },
|
|
3559
|
+
}),
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
await ctx.interviews.recordAnswer(sessionId, {
|
|
3563
|
+
questionId,
|
|
3564
|
+
response,
|
|
3565
|
+
text,
|
|
3566
|
+
at: new Date().toISOString(),
|
|
3567
|
+
});
|
|
3568
|
+
const updatedDraft = applyGrillMeAnswer(draft, question, { response, text });
|
|
3569
|
+
const finalized = await finalizeInterviewIfComplete(ctx, sessionId, updatedDraft);
|
|
3570
|
+
return {
|
|
3571
|
+
kind: "sync",
|
|
3572
|
+
status: 200,
|
|
3573
|
+
body: operatorSucceeded("interviewTurn", {
|
|
3574
|
+
session: finalized.session,
|
|
3575
|
+
draft: finalized.draft,
|
|
3576
|
+
draftSha256: finalized.draftSha256,
|
|
3577
|
+
assessmentId: finalized.assessmentId ?? null,
|
|
3578
|
+
question: finalized.question
|
|
3579
|
+
? {
|
|
3580
|
+
id: finalized.question.id,
|
|
3581
|
+
text: finalized.question.text,
|
|
3582
|
+
recommendation: finalized.question.recommendation,
|
|
3583
|
+
affectsFields: finalized.question.affectsFields,
|
|
3584
|
+
risk: finalized.question.risk,
|
|
3585
|
+
gapId: finalized.question.gapId,
|
|
3586
|
+
}
|
|
3587
|
+
: null,
|
|
3588
|
+
summary: draftSummary(finalized.draft),
|
|
3589
|
+
complete: Boolean(finalized.assessmentId),
|
|
3590
|
+
}),
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
function extractRecommendation(questionText) {
|
|
3594
|
+
const m = questionText.match(/推荐[::]\s*(.+)$/m);
|
|
3595
|
+
return m?.[1]?.trim() || "";
|
|
3596
|
+
}
|
|
3597
|
+
function invalid(action, message) {
|
|
3598
|
+
return {
|
|
3599
|
+
kind: "error",
|
|
3600
|
+
status: 400,
|
|
3601
|
+
body: operatorFailed({
|
|
3602
|
+
command: action,
|
|
3603
|
+
outcome: "invalid",
|
|
3604
|
+
code: "INVALID_INPUT",
|
|
3605
|
+
message,
|
|
3606
|
+
}),
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
function interruptActionFailed(action, code, message) {
|
|
3610
|
+
const blocked = code === "INTERRUPT_ALREADY_PENDING" ||
|
|
3611
|
+
code === "INTERRUPT_NEEDS_RECONCILE" ||
|
|
3612
|
+
code === "RUN_ALREADY_TERMINAL" ||
|
|
3613
|
+
code === "RUN_NOT_CONSOLE_OWNED" ||
|
|
3614
|
+
code === "RUNNER_IDENTITY_MISMATCH" ||
|
|
3615
|
+
code === "RUN_NOT_ACTIVE";
|
|
3616
|
+
return {
|
|
3617
|
+
kind: "error",
|
|
3618
|
+
status: code === "INVALID_INPUT" ? 400 : 409,
|
|
3619
|
+
body: operatorFailed({
|
|
3620
|
+
command: action,
|
|
3621
|
+
outcome: blocked ? "blocked" : "invalid",
|
|
3622
|
+
code,
|
|
3623
|
+
message: message || INTERRUPT_ERROR_MESSAGES[code] || message,
|
|
3624
|
+
}),
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
async function syncTargetInterruptProjection(input) {
|
|
3628
|
+
const current = await input.operations.get(input.operationId);
|
|
3629
|
+
if (!current)
|
|
3630
|
+
return false;
|
|
3631
|
+
if (current.interrupt?.status === input.interrupt.status &&
|
|
3632
|
+
current.interrupt?.requestId === input.interrupt.requestId) {
|
|
3633
|
+
return false;
|
|
3634
|
+
}
|
|
3635
|
+
const nextStatus = input.interrupt.status;
|
|
3636
|
+
if (nextStatus !== "requested" &&
|
|
3637
|
+
nextStatus !== "acknowledged" &&
|
|
3638
|
+
nextStatus !== "settled" &&
|
|
3639
|
+
nextStatus !== "needs-reconcile") {
|
|
3640
|
+
return false;
|
|
3641
|
+
}
|
|
3642
|
+
await input.operations.update(input.operationId, {
|
|
3643
|
+
dagRunId: current.dagRunId ?? input.runId,
|
|
3644
|
+
interrupt: {
|
|
3645
|
+
status: nextStatus,
|
|
3646
|
+
...(input.interrupt.requestId
|
|
3647
|
+
? { requestId: input.interrupt.requestId }
|
|
3648
|
+
: {}),
|
|
3649
|
+
...(input.interrupt.reasonCode
|
|
3650
|
+
? { reasonCode: input.interrupt.reasonCode }
|
|
3651
|
+
: {}),
|
|
3652
|
+
...(input.interrupt.requestedAt
|
|
3653
|
+
? { requestedAt: input.interrupt.requestedAt }
|
|
3654
|
+
: {}),
|
|
3655
|
+
...(input.interrupt.acknowledgedAt
|
|
3656
|
+
? { acknowledgedAt: input.interrupt.acknowledgedAt }
|
|
3657
|
+
: {}),
|
|
3658
|
+
...(input.interrupt.settledAt
|
|
3659
|
+
? { settledAt: input.interrupt.settledAt }
|
|
3660
|
+
: {}),
|
|
3661
|
+
...(input.interrupt.outcome ? { outcome: input.interrupt.outcome } : {}),
|
|
3662
|
+
},
|
|
3663
|
+
});
|
|
3664
|
+
input.events.append(input.operationId, {
|
|
3665
|
+
kind: "state",
|
|
3666
|
+
message: `interrupt ${nextStatus}`,
|
|
3667
|
+
data: {
|
|
3668
|
+
interruptStatus: nextStatus,
|
|
3669
|
+
runId: input.runId,
|
|
3670
|
+
},
|
|
3671
|
+
});
|
|
3672
|
+
return true;
|
|
3673
|
+
}
|
|
3674
|
+
function confirmationErrorStatus(code) {
|
|
3675
|
+
if (code === "NOT_FOUND")
|
|
3676
|
+
return 404;
|
|
3677
|
+
if (code === "HUMAN_CONFIRMATION_REQUIRED")
|
|
3678
|
+
return 403;
|
|
3679
|
+
return 409;
|
|
3680
|
+
}
|
|
3681
|
+
async function dispatchContractValidateOrDiff(ctx, action, p) {
|
|
3682
|
+
const taskId = str(p.taskId);
|
|
3683
|
+
const draftText = str(p.draftJson) ?? str(p.inputText);
|
|
3684
|
+
if (!taskId)
|
|
3685
|
+
return invalid(action, "taskId is required");
|
|
3686
|
+
let draft;
|
|
3687
|
+
try {
|
|
3688
|
+
if (!draftText) {
|
|
3689
|
+
const stored = await ctx.drafts.get(taskId);
|
|
3690
|
+
if (!stored) {
|
|
3691
|
+
return invalid(action, "draftJson or stored draft required");
|
|
3692
|
+
}
|
|
3693
|
+
draft = toCanonicalDraftForCli(stored.draft);
|
|
3694
|
+
}
|
|
3695
|
+
else {
|
|
3696
|
+
draft = JSON.parse(draftText);
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
catch (error) {
|
|
3700
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3701
|
+
return {
|
|
3702
|
+
kind: "error",
|
|
3703
|
+
status: 400,
|
|
3704
|
+
body: operatorFailed({
|
|
3705
|
+
command: action,
|
|
3706
|
+
outcome: "invalid",
|
|
3707
|
+
code: "INVALID_INPUT",
|
|
3708
|
+
message: `invalid draft JSON: ${message}`,
|
|
3709
|
+
}),
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
try {
|
|
3713
|
+
if (action === "contractValidate") {
|
|
3714
|
+
const validation = await validateDraftForTask({
|
|
3715
|
+
repoRoot: ctx.repoRoot,
|
|
3716
|
+
taskId,
|
|
3717
|
+
draft: draft,
|
|
3718
|
+
});
|
|
3719
|
+
if (!validation.ok) {
|
|
3720
|
+
return {
|
|
3721
|
+
kind: "sync",
|
|
3722
|
+
status: 400,
|
|
3723
|
+
body: operatorFailed({
|
|
3724
|
+
command: action,
|
|
3725
|
+
outcome: "invalid",
|
|
3726
|
+
code: "INVALID_INPUT",
|
|
3727
|
+
message: validation.errors.join("; "),
|
|
3728
|
+
details: { errors: validation.errors },
|
|
3729
|
+
}),
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
return {
|
|
3733
|
+
kind: "sync",
|
|
3734
|
+
status: 200,
|
|
3735
|
+
body: operatorSucceeded(action, {
|
|
3736
|
+
taskId,
|
|
3737
|
+
warnings: validation.warnings,
|
|
3738
|
+
}),
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
const diff = await diffTaskContract({
|
|
3742
|
+
repoRoot: ctx.repoRoot,
|
|
3743
|
+
taskId,
|
|
3744
|
+
draft: draft,
|
|
3745
|
+
});
|
|
3746
|
+
return {
|
|
3747
|
+
kind: "sync",
|
|
3748
|
+
status: 200,
|
|
3749
|
+
body: operatorSucceeded(action, { taskId, diff }),
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
catch (error) {
|
|
3753
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3754
|
+
return {
|
|
3755
|
+
kind: "error",
|
|
3756
|
+
status: 400,
|
|
3757
|
+
body: operatorFailed({
|
|
3758
|
+
command: action,
|
|
3759
|
+
outcome: "blocked",
|
|
3760
|
+
code: "INTERNAL_ERROR",
|
|
3761
|
+
message,
|
|
3762
|
+
}),
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
export function isMutationAction(action) {
|
|
3767
|
+
return MUTATION_OR_LONG.has(action);
|
|
3768
|
+
}
|