@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.2
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/CHANGELOG.md +78 -206
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +19 -75
- package/dist/application/task-lifecycle/advance.js +5 -24
- package/dist/application/task-lifecycle/observe.js +17 -171
- package/dist/application/task-lifecycle/plan-transitions.js +7 -42
- package/dist/application/task-lifecycle/recommendations.js +2 -13
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -7
- package/dist/cli/program.js +23 -6
- package/dist/commands/client-recovery.js +0 -3
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init.js +1 -27
- package/dist/commands/task-advance.js +0 -19
- package/dist/executors/dag-pi-executor.js +266 -3792
- package/dist/executors/pi-executor.js +4 -15
- package/dist/executors/pi-sdk-executor.js +3 -0
- package/dist/executors/shell-executor.js +217 -945
- package/dist/executors/shell-write-guard.js +0 -7
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/console/operation-store.js +197 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/dag-failure-category.js +0 -12
- package/dist/shared/openspec-spec.js +4 -70
- package/dist/shared/operator/capabilities.js +14 -7
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +80 -35
- package/dist/task/contract/adopt.js +0 -4
- package/dist/task/contract/import-revision.js +0 -4
- package/dist/task/contract/project.js +0 -3
- package/dist/task/contract/schema.js +1 -2
- package/dist/task/frontend-project-capability.js +20 -203
- package/dist/task/runtime.js +2 -5
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +9 -15
- package/dist/task/source-prepare/prepare.js +1 -89
- package/dist/task/source-prepare/semantic-intake.js +2 -6
- package/dist/task/source-references.js +1 -22
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +2 -2
- package/dist/worker/console/chat/pi-runtime.js +3 -2
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +13 -4
- package/dist/worker/console/chat/semantic-activity.js +12 -11
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +1 -1
- package/dist/worker/console/chat/turn-process.js +28 -4
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -6
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +107 -168
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.js +60 -1
- package/dist/worker/console/routes.js +637 -8
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +6 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-D6PvaVd-.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
- package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-CfUiDDB3.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-BQe6fpqj.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
- package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/workspace-context.js +28 -112
- package/dist/worker/console/workspace-registry.js +2 -2
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observability/read-model.js +4 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/dag-node-purpose.js +0 -5
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1037 -126
- package/dist/worker/observe/static/views/dag-inspector.js +1217 -65
- package/dist/worker/observe/static/views/session-timeline.js +121 -25
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +99 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +45 -3
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
- package/dist/workflows/dag/frontend-plan-render.js +1 -2
- package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
- package/dist/workflows/dag/frontend-recovery-run.js +20 -33
- package/dist/workflows/dag/frontend-repair.js +432 -1
- package/dist/workflows/dag/frontend-review-context.js +15 -261
- package/dist/workflows/dag/frontend-verification-trace.js +24 -249
- package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +740 -718
- package/dist/workflows/dag/interrupt-request.js +0 -7
- package/dist/workflows/dag/node-execution.js +136 -759
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +30 -38
- package/dist/workflows/dag/report.js +1 -37
- package/dist/workflows/dag/rerun-feedback.js +1 -256
- package/dist/workflows/dag/rerun-plan.js +557 -11
- package/dist/workflows/dag/rerun-run.js +601 -38
- package/dist/workflows/dag/rerun-task.js +0 -29
- package/dist/workflows/dag/retry-policy.js +109 -214
- package/dist/workflows/dag/runner.js +142 -430
- package/dist/workflows/dag/scheduler.js +16 -114
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +195 -246
- package/dist/workflows/dag/validate.js +12 -28
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/init-surface.manifest.json +12 -30
- package/docs/skills/vetted-skill-registry.md +2 -4
- package/docs/templates/README.md +0 -2
- package/docs/templates/agent-dag-report.schema.json +2 -8
- package/docs/templates/agent-dag.schema.json +33 -1
- package/docs/templates/backend-test-dag.json +18 -18
- package/docs/templates/frontend-implementation-contract.schema.json +1 -4
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +3 -1
- package/skills/frontend-bounded-implement/SKILL.md +14 -15
- package/skills/frontend-design-review/SKILL.md +41 -22
- 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 +15 -20
- package/skills/frontend-review/references/review-findings.md +7 -6
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +6 -1
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/commands/dag-follow-up.js +0 -138
- package/dist/executors/pi-read-budget-policy.js +0 -239
- package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
- package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
- package/dist/worker/console/operation-store.js +0 -389
- package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
- package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
- package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
- package/dist/worker/console/static-src/active-run-badge.js +0 -17
- package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
- package/dist/workflows/dag/dag-retry-schema.js +0 -138
- package/dist/workflows/dag/frontend-closeout.js +0 -221
- package/dist/workflows/dag/frontend-design-policy.js +0 -400
- package/dist/workflows/dag/frontend-human-decision.js +0 -182
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
- package/dist/workflows/dag/frontend-read-budgets.js +0 -12
- package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
- package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
- package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
- package/dist/workflows/dag/frontend-review-findings.js +0 -270
- package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
- package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
- package/dist/workflows/dag/frontend-shape-facts.js +0 -409
- package/dist/workflows/dag/frontend-shape.js +0 -427
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
- package/dist/workflows/dag/frontend-split-application-service.js +0 -203
- package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
- package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
- package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
- package/dist/workflows/dag/frontend-writer-admission.js +0 -285
- package/dist/workflows/dag/frontend-writer-status.js +0 -256
- package/dist/workflows/dag/recovery-lease.js +0 -80
- package/docs/templates/frontend-implementation-dag.json +0 -89
- package/docs/templates/spec-registry.schema.json +0 -45
- package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
- package/skills/frontend-contract/SKILL.md +0 -23
- package/skills/frontend-contract/references/contract-protocol.md +0 -34
- package/skills/frontend-plan/SKILL.md +0 -26
- package/skills/frontend-plan/references/decision-contract.md +0 -37
- package/skills/frontend-plan/references/design-decisions.md +0 -17
- package/skills/frontend-scout/SKILL.md +0 -25
- package/skills/frontend-scout/references/design-evidence.md +0 -16
- package/skills/frontend-scout/references/scout-evidence.md +0 -23
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
4
4
|
import { hostname } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
@@ -7,7 +7,7 @@ import { isHardBudgetBreached, resolveEffectiveMaxConcurrent, } from "../../appl
|
|
|
7
7
|
import { readCandidateRecord } from "../../infrastructure/evaluation/candidate-store.js";
|
|
8
8
|
import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/runtime.js";
|
|
9
9
|
import { assertFrozenBudget, initRunBudgetLedger, preflightBudgetOrBreach, recordFinishedNodeBudget, writeBudgetLedgerArtifacts, } from "./budget-enforcement.js";
|
|
10
|
-
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, requireActiveDagRun, } from "./lifecycle.js";
|
|
10
|
+
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readDagRunSpec, readDagRunState, readHumanApprovalArtifact, requireActiveDagRun, } from "./lifecycle.js";
|
|
11
11
|
import { markInterruptNeedsReconcile, mergeAbortSignals, readInterruptRequest, runnerIdentityFromState, settleDagInterrupt, startInterruptWatcher, } from "./interrupt-request.js";
|
|
12
12
|
import { moveToCompletedRunDir, moveToPausedRunDir, prepareRunDir, writeRunSpec, writeRunState, } from "./run-store.js";
|
|
13
13
|
import { evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
|
|
@@ -26,12 +26,7 @@ import { runConvergencePassController, } from "./convergence/controller.js";
|
|
|
26
26
|
import { executeDagRanksOnce, FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isConditionSkippedReason, readFrontendPrewriteResult, } from "./scheduler.js";
|
|
27
27
|
import { topoSortToRanks } from "./topo.js";
|
|
28
28
|
import { isFrontendWriterTransientPartialWrite } from "./frontend-writer-recovery.js";
|
|
29
|
-
import { classifyReviewIssueCategoryFailureOwner, readCommittedDesignRequestFact, readCommittedReviewRequestFact, writeFrontendReviewFindingsPrd, } from "./frontend-review-findings.js";
|
|
30
|
-
import { bindFrontendShapeStateToRunTopology, commitFrontendShapeTransitionCas, commitRuntimeFrontendShapeFact, isCanonicalFrontendShapeDag, } from "./frontend-shape-facts.js";
|
|
31
|
-
import { assertFrontendTopologyBinding, buildFrontendTopologyBinding, computeFrontendShapeSourceDigest, } from "./frontend-shape.js";
|
|
32
|
-
import { ensureFrontendShapeCapsuleRecord } from "./frontend-shape-capsule-store.js";
|
|
33
29
|
import { parseDagSpec, resolveModelForTask, } from "./types.js";
|
|
34
|
-
import { acquireDagRecoveryLease, bindDagRecoveryLeaseExecution, } from "./recovery-lease.js";
|
|
35
30
|
import { executeDynamicCondition } from "./dynamic-runtime/condition.js";
|
|
36
31
|
import { executeDynamicLoopUntil } from "./dynamic-runtime/loop-until.js";
|
|
37
32
|
import { executeDynamicMapExpansion } from "./dynamic-runtime/map.js";
|
|
@@ -51,51 +46,6 @@ export function toKebabSlug(text, maxLen = 40) {
|
|
|
51
46
|
.replace(/^-|-$/g, "")
|
|
52
47
|
.slice(0, maxLen) || "dag");
|
|
53
48
|
}
|
|
54
|
-
export async function loadCanonicalFrontendTaskSourceBinding(cwd, spec) {
|
|
55
|
-
const binding = spec.sourceBinding;
|
|
56
|
-
if (!binding?.taskId)
|
|
57
|
-
throw new Error("canonical frontend DAG requires sourceBinding.taskId");
|
|
58
|
-
const requirement = binding.sources.find((source) => source.kind === "requirement");
|
|
59
|
-
if (!requirement)
|
|
60
|
-
throw new Error("canonical frontend DAG requires a bound requirement source");
|
|
61
|
-
const constraint = binding.sources.find((source) => source.kind === "constraint");
|
|
62
|
-
const readFreshSource = async (source) => {
|
|
63
|
-
let bytes;
|
|
64
|
-
try {
|
|
65
|
-
bytes = await readFile(path.resolve(cwd, source.path));
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
throw new Error(`canonical frontend ${source.kind} source freshness read failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
69
|
-
}
|
|
70
|
-
const actualSha256 = createHash("sha256").update(bytes).digest("hex");
|
|
71
|
-
if (actualSha256 !== source.sha256) {
|
|
72
|
-
throw new Error(`canonical frontend ${source.kind} source sha256 drifted: expected ${source.sha256}, received ${actualSha256}`);
|
|
73
|
-
}
|
|
74
|
-
return bytes.toString("utf8");
|
|
75
|
-
};
|
|
76
|
-
const requirementMarkdown = await readFreshSource(requirement);
|
|
77
|
-
const constraintMarkdown = constraint ? await readFreshSource(constraint) : "";
|
|
78
|
-
return {
|
|
79
|
-
taskId: binding.taskId,
|
|
80
|
-
requirementMarkdown,
|
|
81
|
-
constraintMarkdown,
|
|
82
|
-
sourceDigest: computeFrontendShapeSourceDigest({ requirementMarkdown, constraintMarkdown }),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
export async function ensureFreshFrontendShapeCapsuleRecord(input) {
|
|
86
|
-
const freshSource = await loadCanonicalFrontendTaskSourceBinding(input.cwd, input.spec);
|
|
87
|
-
if (freshSource.taskId !== input.canonicalSource.taskId ||
|
|
88
|
-
freshSource.sourceDigest !== input.canonicalSource.sourceDigest) {
|
|
89
|
-
throw new Error("canonical frontend task source freshness binding drifted before transition commit");
|
|
90
|
-
}
|
|
91
|
-
await (input.ensureRecord ?? ensureFrontendShapeCapsuleRecord)({
|
|
92
|
-
repoRoot: input.cwd,
|
|
93
|
-
taskId: input.canonicalSource.taskId,
|
|
94
|
-
parentRunId: input.parentRunId,
|
|
95
|
-
terminalOutcome: input.terminalOutcome,
|
|
96
|
-
capsule: input.capsule,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
49
|
export function extractTaskIdFromDagSpec(spec) {
|
|
100
50
|
if (spec.objective) {
|
|
101
51
|
const match = spec.objective.match(TASK_ID_IN_TEXT_PATTERN);
|
|
@@ -230,15 +180,6 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
|
|
|
230
180
|
...(opts.workerAssociation
|
|
231
181
|
? { workerAssociation: structuredClone(opts.workerAssociation) }
|
|
232
182
|
: {}),
|
|
233
|
-
...(isCanonicalFrontendShapeDag(new Map(spec.tasks.map((task) => [task.id, task])))
|
|
234
|
-
? {
|
|
235
|
-
frontendShapeState: {
|
|
236
|
-
frozenTopology: buildFrontendTopologyBinding(spec.tasks.map((task) => task.id)),
|
|
237
|
-
revision: 0,
|
|
238
|
-
committedFacts: {},
|
|
239
|
-
},
|
|
240
|
-
}
|
|
241
|
-
: {}),
|
|
242
183
|
};
|
|
243
184
|
initRunBudgetLedger(state, spec.budget);
|
|
244
185
|
return state;
|
|
@@ -360,25 +301,27 @@ export async function runDag(spec, opts) {
|
|
|
360
301
|
activeRunDir,
|
|
361
302
|
completedRunDir,
|
|
362
303
|
pausedRunDir,
|
|
363
|
-
recovery: opts.recovery,
|
|
364
304
|
});
|
|
365
305
|
}
|
|
366
306
|
finally {
|
|
367
307
|
}
|
|
368
308
|
}
|
|
369
309
|
export async function runDagContinuation(opts) {
|
|
310
|
+
assertValidDagSpec(opts.spec);
|
|
311
|
+
assertFrozenBudget(opts.spec.budget, opts.state.budget, opts.state.runId);
|
|
312
|
+
if (opts.spec.budget) {
|
|
313
|
+
const ledger = opts.state.budgetLedger;
|
|
314
|
+
if (!ledger ||
|
|
315
|
+
ledger.mode !== opts.spec.budget.mode ||
|
|
316
|
+
JSON.stringify(ledger.limits) !== JSON.stringify(opts.spec.budget.limits)) {
|
|
317
|
+
throw new Error(`dag continuation ${opts.state.runId} is missing a matching cumulative budget ledger`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const { assertDagSpecAllowsNewWriterExecution, assertTaskContractBindingConsistent, } = await import("./task-contract-binding.js");
|
|
321
|
+
assertDagSpecAllowsNewWriterExecution(opts.spec);
|
|
322
|
+
await assertTaskContractBindingConsistent({ repoRoot: opts.cwd, spec: opts.spec });
|
|
370
323
|
const { maxConcurrent } = resolveEffectiveMaxConcurrent(Math.max(1, opts.maxConcurrent ?? 4), opts.spec.budget);
|
|
371
324
|
const state = opts.state;
|
|
372
|
-
const canonicalFrontend = isCanonicalFrontendShapeDag(new Map(opts.spec.tasks.map((task) => [task.id, task])));
|
|
373
|
-
if (canonicalFrontend) {
|
|
374
|
-
assertFrontendTopologyBinding(state.frontendShapeState?.frozenTopology, opts.spec.tasks.map((task) => task.id));
|
|
375
|
-
}
|
|
376
|
-
else if (state.frontendShapeState) {
|
|
377
|
-
throw new Error("non-frontend DAG unexpectedly contains frontend topology state");
|
|
378
|
-
}
|
|
379
|
-
if (state.frontendShapeState?.terminalOutcome) {
|
|
380
|
-
throw new Error(`dag run ${state.runId} is terminal due to frontend shape ${state.frontendShapeState.terminalOutcome}`);
|
|
381
|
-
}
|
|
382
325
|
state.status = "running";
|
|
383
326
|
const resumedAt = new Date().toISOString();
|
|
384
327
|
state.runner = {
|
|
@@ -398,12 +341,12 @@ export async function runDagContinuation(opts) {
|
|
|
398
341
|
cwd: opts.cwd,
|
|
399
342
|
maxConcurrent,
|
|
400
343
|
executeNode: opts.executeNode,
|
|
344
|
+
beforeTerminalArchive: opts.beforeTerminalArchive,
|
|
401
345
|
observer: opts.observer,
|
|
402
346
|
abortSignal: opts.abortSignal,
|
|
403
347
|
activeRunDir,
|
|
404
348
|
completedRunDir,
|
|
405
349
|
pausedRunDir,
|
|
406
|
-
recovery: opts.recovery,
|
|
407
350
|
});
|
|
408
351
|
}
|
|
409
352
|
export async function resumeDagRun(opts) {
|
|
@@ -506,13 +449,9 @@ export async function resumeDagRun(opts) {
|
|
|
506
449
|
}
|
|
507
450
|
async function executeDagCheckpoint(input) {
|
|
508
451
|
const { spec, state, ranks, runDir: initialRunDir, cwd, maxConcurrent, completedRunDir, pausedRunDir, } = input;
|
|
509
|
-
bindFrontendShapeStateToRunTopology(state);
|
|
510
452
|
let runDir = initialRunDir;
|
|
511
453
|
let stateWriteQueue = Promise.resolve();
|
|
512
|
-
let shapeTransitionBarrier;
|
|
513
454
|
const persistState = async (options) => {
|
|
514
|
-
if (shapeTransitionBarrier)
|
|
515
|
-
await shapeTransitionBarrier;
|
|
516
455
|
stateWriteQueue = stateWriteQueue.then(() => writeRunState(runDir, state, options));
|
|
517
456
|
await stateWriteQueue;
|
|
518
457
|
};
|
|
@@ -637,50 +576,12 @@ async function executeDagCheckpoint(input) {
|
|
|
637
576
|
.map((rank) => rank.filter((nodeId) => nodeId === "closeout-pi"))
|
|
638
577
|
.filter((rank) => rank.length > 0)
|
|
639
578
|
: [];
|
|
640
|
-
const canonicalFrontendSource = isCanonicalFrontendShapeDag(tasksById)
|
|
641
|
-
? await loadCanonicalFrontendTaskSourceBinding(cwd, spec)
|
|
642
|
-
: undefined;
|
|
643
579
|
const executeRanks = (selectedRanks) => executeDagRanksOnce({
|
|
644
580
|
state,
|
|
645
581
|
ranks: selectedRanks,
|
|
646
582
|
tasksById,
|
|
647
583
|
maxConcurrent,
|
|
648
584
|
persistState,
|
|
649
|
-
commitFrontendShapeTransition: async (request) => {
|
|
650
|
-
let releaseBarrier;
|
|
651
|
-
shapeTransitionBarrier = new Promise((resolve) => {
|
|
652
|
-
releaseBarrier = resolve;
|
|
653
|
-
});
|
|
654
|
-
try {
|
|
655
|
-
if (!canonicalFrontendSource) {
|
|
656
|
-
throw new Error("frontend shape transition requires canonical task source binding");
|
|
657
|
-
}
|
|
658
|
-
return await commitFrontendShapeTransitionCas({
|
|
659
|
-
state,
|
|
660
|
-
tasksById,
|
|
661
|
-
request,
|
|
662
|
-
taskSourceDigest: canonicalFrontendSource.sourceDigest,
|
|
663
|
-
ensureRecord: async (capsule, terminalOutcome) => {
|
|
664
|
-
await ensureFreshFrontendShapeCapsuleRecord({
|
|
665
|
-
cwd,
|
|
666
|
-
spec,
|
|
667
|
-
canonicalSource: canonicalFrontendSource,
|
|
668
|
-
parentRunId: state.runId,
|
|
669
|
-
terminalOutcome,
|
|
670
|
-
capsule,
|
|
671
|
-
});
|
|
672
|
-
},
|
|
673
|
-
persist: async (nextState) => {
|
|
674
|
-
stateWriteQueue = stateWriteQueue.then(() => writeRunState(runDir, nextState));
|
|
675
|
-
await stateWriteQueue;
|
|
676
|
-
},
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
finally {
|
|
680
|
-
releaseBarrier();
|
|
681
|
-
shapeTransitionBarrier = undefined;
|
|
682
|
-
}
|
|
683
|
-
},
|
|
684
585
|
runDir,
|
|
685
586
|
abortSignal: input.abortSignal,
|
|
686
587
|
createExecuteNodeForRank: (rankWriterNodeIds) => buildRankAwareExecuteNode({
|
|
@@ -716,18 +617,6 @@ async function executeDagCheckpoint(input) {
|
|
|
716
617
|
});
|
|
717
618
|
const node = state.nodes[nodeId];
|
|
718
619
|
if (node && (node.status === "FINISHED" || node.status === "ERROR")) {
|
|
719
|
-
if (node.status === "FINISHED") {
|
|
720
|
-
if (canonicalFrontendSource) {
|
|
721
|
-
await commitRuntimeFrontendShapeFact({
|
|
722
|
-
state,
|
|
723
|
-
tasksById,
|
|
724
|
-
sourceNodeId: nodeId,
|
|
725
|
-
taskId: canonicalFrontendSource.taskId,
|
|
726
|
-
requirementMarkdown: canonicalFrontendSource.requirementMarkdown,
|
|
727
|
-
constraintMarkdown: canonicalFrontendSource.constraintMarkdown,
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
620
|
recordFinishedNodeBudget(state, node);
|
|
732
621
|
await writeBudgetLedgerArtifacts(runDir, state.budgetLedger);
|
|
733
622
|
await persistState();
|
|
@@ -737,9 +626,6 @@ async function executeDagCheckpoint(input) {
|
|
|
737
626
|
let pausedByNodeId;
|
|
738
627
|
while (true) {
|
|
739
628
|
pausedByNodeId = await executeRanks(convergenceRanks);
|
|
740
|
-
if (state.frontendShapeState?.terminalOutcome) {
|
|
741
|
-
break;
|
|
742
|
-
}
|
|
743
629
|
if (pausedByNodeId) {
|
|
744
630
|
break;
|
|
745
631
|
}
|
|
@@ -795,7 +681,7 @@ async function executeDagCheckpoint(input) {
|
|
|
795
681
|
runDir = await moveToPausedRunDir(runDir, pausedRunDir);
|
|
796
682
|
}
|
|
797
683
|
else {
|
|
798
|
-
const { recoveryPending
|
|
684
|
+
const { recoveryPending } = await finalizeTerminalRunStatus(state, spec.tasks.length, runDir, cwd);
|
|
799
685
|
await finalizeRunOwnedInterrupt({
|
|
800
686
|
runDir,
|
|
801
687
|
state,
|
|
@@ -803,65 +689,56 @@ async function executeDagCheckpoint(input) {
|
|
|
803
689
|
interruptAborted: interruptController.signal.aborted,
|
|
804
690
|
});
|
|
805
691
|
await persistState();
|
|
806
|
-
if (recoveryPending
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
// Same clientRequestId with a different payload → fail closed; never
|
|
820
|
-
// create a second recovery operation (AC-004).
|
|
821
|
-
state.status = "failed";
|
|
822
|
-
state.terminalReason =
|
|
823
|
-
"frontend recovery request-id reuse conflict";
|
|
824
|
-
state.frontendRecoveryResult = buildRecoveryResultForTrigger(state, recoveryTrigger, "auto-recovery-blocked");
|
|
825
|
-
}
|
|
826
|
-
else if (created.kind === "lease-held") {
|
|
827
|
-
// A different recovery entrance has already durably claimed this
|
|
828
|
-
// parent. Do not "help" by running its operation: that would reopen
|
|
829
|
-
// the exact two-child-DAG race the lease prevents.
|
|
830
|
-
state.status = "failed";
|
|
831
|
-
state.terminalReason =
|
|
832
|
-
"frontend recovery parent lease already held";
|
|
833
|
-
state.frontendRecoveryResult = buildRecoveryResultForTrigger(state, recoveryTrigger, "auto-recovery-blocked");
|
|
834
|
-
}
|
|
835
|
-
else {
|
|
836
|
-
const { runRecoveryOperationController } = await import("./frontend-recovery-controller.js");
|
|
837
|
-
const result = await runRecoveryOperationController({
|
|
838
|
-
operationId: created.operation.operationId,
|
|
692
|
+
if (recoveryPending) {
|
|
693
|
+
// Recovery coordinator (phase 3c AC-2/AC-3): materialize the reserved
|
|
694
|
+
// child and run it synchronously. Dynamic import avoids a static
|
|
695
|
+
// runner ↔ frontend-recovery-run module cycle. stageRecoveryChild
|
|
696
|
+
// locates the parent via the canonical active/ layout, so recovery
|
|
697
|
+
// only fires when this run is still the canonical active run dir
|
|
698
|
+
// (direct runDagContinuation callers using an arbitrary runDir keep
|
|
699
|
+
// the previous scheduler-layer skip semantics).
|
|
700
|
+
const canonicalActiveDir = getDagRunDir(cwd, "active", state.runId);
|
|
701
|
+
if (path.resolve(runDir) === path.resolve(canonicalActiveDir)) {
|
|
702
|
+
const { stageRecoveryChild } = await import("./frontend-recovery-run.js");
|
|
703
|
+
const staged = await stageRecoveryChild({
|
|
704
|
+
cwd,
|
|
839
705
|
parentRunId: state.runId,
|
|
840
|
-
deps: {
|
|
841
|
-
...input.recovery,
|
|
842
|
-
failureOwner: input.recovery.failureOwner ??
|
|
843
|
-
recoveryTrigger.failureOwner,
|
|
844
|
-
protocolFailureReason: input.recovery.protocolFailureReason ??
|
|
845
|
-
recoveryTrigger.protocolFailureReason,
|
|
846
|
-
recoveryMode: input.recovery.recoveryMode ?? recoveryTrigger.recoveryMode,
|
|
847
|
-
},
|
|
848
706
|
});
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
707
|
+
// stageRecoveryChild CAS-advanced the parent's on-disk lineage
|
|
708
|
+
// (child-staging → child-running + childRunId). Refresh the in-memory
|
|
709
|
+
// copy so the terminal persistState below does not regress the parent
|
|
710
|
+
// back to stale child-staging.
|
|
711
|
+
const parentOnDisk = await readDagRunState(runDir);
|
|
712
|
+
state.frontendRecoveryState = parentOnDisk.frontendRecoveryState;
|
|
713
|
+
const childState = await readDagRunState(staged.childRunDir);
|
|
714
|
+
const childSpec = await readDagRunSpec(staged.childRunDir);
|
|
715
|
+
await runDagContinuation({
|
|
716
|
+
cwd,
|
|
717
|
+
spec: childSpec,
|
|
718
|
+
state: childState,
|
|
719
|
+
runDir: staged.childRunDir,
|
|
720
|
+
maxConcurrent,
|
|
721
|
+
executeNode: input.executeNode,
|
|
722
|
+
observer: input.observer,
|
|
723
|
+
abortSignal,
|
|
724
|
+
});
|
|
852
725
|
}
|
|
853
726
|
}
|
|
854
727
|
await notifyRunObserver(input.observer, "onRunFinish", state);
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
728
|
+
if (!recoveryPending) {
|
|
729
|
+
if (input.beforeTerminalArchive) {
|
|
730
|
+
await input.beforeTerminalArchive({ runDir, state });
|
|
731
|
+
await persistState();
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
const terminalCheckpoint = await captureWorkspaceCheckpoint(cwd);
|
|
735
|
+
await writeWorkspaceCheckpoint(runDir, WORKSPACE_CHECKPOINT_TERMINAL_REL, terminalCheckpoint);
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
console.warn(`[run-dag] warning: failed to write workspace terminal checkpoint: ${error instanceof Error ? error.message : String(error)}`);
|
|
739
|
+
}
|
|
740
|
+
runDir = await moveToCompletedRunDir(runDir, completedRunDir);
|
|
863
741
|
}
|
|
864
|
-
runDir = await moveToCompletedRunDir(runDir, completedRunDir);
|
|
865
742
|
}
|
|
866
743
|
await relocateRunArtifactPaths({
|
|
867
744
|
runDir,
|
|
@@ -994,21 +871,39 @@ function isSuccessfulConvergenceTerminal(state) {
|
|
|
994
871
|
return true;
|
|
995
872
|
return SUCCESSFUL_CONVERGENCE_TERMINAL_REASONS.has(reason);
|
|
996
873
|
}
|
|
874
|
+
function buildFrontendRecoveryIntent(state, failureSource) {
|
|
875
|
+
const existing = state.frontendRecoveryState;
|
|
876
|
+
const requestId = existing?.requestId ?? randomUUID();
|
|
877
|
+
return {
|
|
878
|
+
schemaVersion: 1,
|
|
879
|
+
phase: "child-staging",
|
|
880
|
+
requestId,
|
|
881
|
+
recoveryRootRunId: existing?.recoveryRootRunId ?? state.runId,
|
|
882
|
+
parentRunId: state.runId,
|
|
883
|
+
...(existing?.childRunId ? { childRunId: existing.childRunId } : {}),
|
|
884
|
+
attemptId: existing?.attemptId ?? `recovery-${requestId}`,
|
|
885
|
+
attemptIndex: existing?.attemptIndex ?? 0,
|
|
886
|
+
continuationCount: existing?.continuationCount ?? 0,
|
|
887
|
+
...(failureSource ? { failureSource } : {}),
|
|
888
|
+
revision: (existing?.revision ?? 0) + 1,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
997
891
|
function buildCandidateContractInvalidResult(state, result, artifactHash) {
|
|
892
|
+
const recovery = state.frontendRecoveryState;
|
|
998
893
|
return {
|
|
999
894
|
schemaVersion: 1,
|
|
1000
895
|
outcome: "candidate-contract-invalid",
|
|
1001
896
|
origin: {
|
|
1002
897
|
kind: "frontend-prewrite-gate",
|
|
1003
|
-
parentRunId: state.runId,
|
|
1004
|
-
|
|
1005
|
-
|
|
898
|
+
parentRunId: recovery?.parentRunId ?? state.runId,
|
|
899
|
+
...(recovery?.childRunId ? { childRunId: recovery.childRunId } : {}),
|
|
900
|
+
requestId: recovery?.requestId ?? "",
|
|
901
|
+
failedNodeId: result.selectedPlanNodeId,
|
|
1006
902
|
},
|
|
1007
903
|
failureClass: {
|
|
1008
904
|
code: "candidate-contract-invalid",
|
|
1009
905
|
classification: result.classification,
|
|
1010
|
-
reason: result.
|
|
1011
|
-
"candidate contract invalid",
|
|
906
|
+
reason: result.failureReason ?? "candidate contract invalid",
|
|
1012
907
|
},
|
|
1013
908
|
evidenceRefs: [
|
|
1014
909
|
{
|
|
@@ -1020,13 +915,15 @@ function buildCandidateContractInvalidResult(state, result, artifactHash) {
|
|
|
1020
915
|
};
|
|
1021
916
|
}
|
|
1022
917
|
function buildWriterPartialWriteBlockedResult(state, failedNodeId, reason) {
|
|
918
|
+
const recovery = state.frontendRecoveryState;
|
|
1023
919
|
return {
|
|
1024
920
|
schemaVersion: 1,
|
|
1025
921
|
outcome: "auto-recovery-blocked",
|
|
1026
922
|
origin: {
|
|
1027
923
|
kind: "frontend-writer",
|
|
1028
|
-
parentRunId: state.runId,
|
|
1029
|
-
|
|
924
|
+
parentRunId: recovery?.parentRunId ?? state.runId,
|
|
925
|
+
...(recovery?.childRunId ? { childRunId: recovery.childRunId } : {}),
|
|
926
|
+
requestId: recovery?.requestId ?? "",
|
|
1030
927
|
failedNodeId,
|
|
1031
928
|
},
|
|
1032
929
|
failureClass: {
|
|
@@ -1037,115 +934,64 @@ function buildWriterPartialWriteBlockedResult(state, failedNodeId, reason) {
|
|
|
1037
934
|
evidenceRefs: [],
|
|
1038
935
|
};
|
|
1039
936
|
}
|
|
1040
|
-
function
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
937
|
+
function settleFrontendRecovery(state, outcome) {
|
|
938
|
+
const recovery = state.frontendRecoveryState;
|
|
939
|
+
if (!recovery)
|
|
940
|
+
return;
|
|
941
|
+
recovery.phase = "settled";
|
|
942
|
+
if (!state.frontendRecoveryResult) {
|
|
943
|
+
state.frontendRecoveryResult = {
|
|
944
|
+
schemaVersion: 1,
|
|
945
|
+
outcome,
|
|
946
|
+
origin: {
|
|
947
|
+
kind: "frontend-prewrite-gate",
|
|
948
|
+
parentRunId: recovery.parentRunId,
|
|
949
|
+
...(recovery.childRunId ? { childRunId: recovery.childRunId } : {}),
|
|
950
|
+
requestId: recovery.requestId,
|
|
951
|
+
failedNodeId: "",
|
|
952
|
+
},
|
|
953
|
+
evidenceRefs: [],
|
|
954
|
+
};
|
|
955
|
+
}
|
|
1054
956
|
}
|
|
1055
|
-
export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd
|
|
957
|
+
export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd) {
|
|
1056
958
|
const repoRoot = cwd ?? path.resolve(runDir, "..", "..", "..", "..");
|
|
1057
|
-
if (state.frontendShapeState?.terminalOutcome &&
|
|
1058
|
-
state.frontendShapeState.transitionCapsule) {
|
|
1059
|
-
state.status = "failed";
|
|
1060
|
-
return { recoveryPending: false };
|
|
1061
|
-
}
|
|
1062
959
|
// Terminal override: a denied frontend writer must never surface as a
|
|
1063
960
|
// mechanical partial_failed just because prewrite FINISHED while the writer
|
|
1064
|
-
// was SKIPPED. Fail closed on blocked before aggregation.
|
|
1065
|
-
let
|
|
1066
|
-
const skipFrontendRecovery = state.failureCategory === "controller-interrupted"
|
|
961
|
+
// was SKIPPED. Fail closed on retryable-invalid/blocked before aggregation.
|
|
962
|
+
let recoveryPending = false;
|
|
963
|
+
const skipFrontendRecovery = state.failureCategory === "controller-interrupted" ||
|
|
964
|
+
state.continuation?.writerTransaction !== undefined;
|
|
1067
965
|
const hasFrontendWriter = Object.keys(state.nodes).some((id) => FRONTEND_WRITER_NODE_IDS.includes(id));
|
|
1068
966
|
if (hasFrontendWriter && !skipFrontendRecovery) {
|
|
1069
967
|
const admission = await readFrontendPrewriteResult(runDir);
|
|
1070
968
|
if (admission.ok) {
|
|
1071
|
-
|
|
1072
|
-
// failureSource is frontend-plan-pi is the contract-invalid analog of
|
|
1073
|
-
// the old retryable-invalid path (candidate continuation may re-plan);
|
|
1074
|
-
// every other blocked admission is terminal.
|
|
1075
|
-
const planContractInvalid = admission.result.classification === "blocked" &&
|
|
1076
|
-
admission.result.failureSource === "frontend-plan-pi";
|
|
1077
|
-
// A blocked admission caused by the design review committing
|
|
1078
|
-
// `request_design_changes` is a definite finding (not a protocol or
|
|
1079
|
-
// environment error): the design was reviewed and rejected. It is the
|
|
1080
|
-
// design-review analog of review-request-changes — not terminal, but
|
|
1081
|
-
// re-enters the next plan round with the committed design findings.
|
|
1082
|
-
// The failureSource may be frontend-writer-admission-shell or
|
|
1083
|
-
// frontend-design-policy-shell; the committed design fact is the
|
|
1084
|
-
// authoritative signal.
|
|
1085
|
-
const designRequestFact = await readCommittedDesignRequestFact({
|
|
1086
|
-
runDir,
|
|
1087
|
-
});
|
|
1088
|
-
const designRejected = admission.result.classification === "blocked" &&
|
|
1089
|
-
designRequestFact !== undefined;
|
|
1090
|
-
const terminalAdmissionDenial = (admission.result.classification === "blocked" ||
|
|
1091
|
-
admission.result.classification === "stale") &&
|
|
1092
|
-
!planContractInvalid &&
|
|
1093
|
-
!designRejected;
|
|
1094
|
-
if (terminalAdmissionDenial) {
|
|
969
|
+
if (admission.result.classification === "blocked") {
|
|
1095
970
|
state.status = "failed";
|
|
1096
971
|
return { recoveryPending: false };
|
|
1097
972
|
}
|
|
1098
|
-
if (
|
|
1099
|
-
state.terminalReason = "design-request-changes";
|
|
1100
|
-
try {
|
|
1101
|
-
await writeFrontendReviewFindingsPrd({
|
|
1102
|
-
runDir,
|
|
1103
|
-
parentRunId: state.runId,
|
|
1104
|
-
fact: designRequestFact,
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
catch (error) {
|
|
1108
|
-
console.warn(`[run-dag] warning: failed to write frontend design findings PRD: ${error instanceof Error ? error.message : String(error)}`);
|
|
1109
|
-
}
|
|
1110
|
-
const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
|
|
1111
|
-
if (continuationCount < frontendRecoveryQuota) {
|
|
1112
|
-
recoveryTrigger = {
|
|
1113
|
-
requestId: state.runId,
|
|
1114
|
-
failureSource: "frontend-design-review-pi",
|
|
1115
|
-
failureOwner: classifyReviewIssueCategoryFailureOwner(designRequestFact.issueCategory),
|
|
1116
|
-
protocolFailureReason: `frontend design review request_design_changes (issueCategory=${designRequestFact.issueCategory})`,
|
|
1117
|
-
recoveryMode: "new-dag",
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
if (planContractInvalid) {
|
|
973
|
+
if (admission.result.classification === "retryable-invalid") {
|
|
1122
974
|
const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
|
|
1123
|
-
if (continuationCount >=
|
|
1124
|
-
// Continuation quota exhausted: a second
|
|
975
|
+
if (continuationCount >= 1) {
|
|
976
|
+
// Continuation quota exhausted: a second retryable-invalid candidate
|
|
1125
977
|
// is authoritative candidate-contract-invalid → forced failed.
|
|
1126
978
|
state.frontendRecoveryResult = buildCandidateContractInvalidResult(state, admission.result, admission.artifactHash);
|
|
1127
979
|
state.status = "failed";
|
|
1128
980
|
return { recoveryPending: false };
|
|
1129
981
|
}
|
|
1130
|
-
// Root continuation still available:
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
failureSource: admission.result.failureSource ?? "frontend-plan-pi",
|
|
1136
|
-
failureOwner: "plan",
|
|
1137
|
-
protocolFailureReason: "frontend candidate contract invalid",
|
|
1138
|
-
recoveryMode: "new-dag",
|
|
1139
|
-
};
|
|
982
|
+
// Root continuation still available: record the recovery intent and let
|
|
983
|
+
// mechanical aggregation settle partial_failed/failed. The parent keeps
|
|
984
|
+
// phase != settled (recovery in progress) and no final root manifest.
|
|
985
|
+
state.frontendRecoveryState = buildFrontendRecoveryIntent(state);
|
|
986
|
+
recoveryPending = true;
|
|
1140
987
|
}
|
|
1141
988
|
}
|
|
1142
989
|
}
|
|
1143
|
-
// Phase 5: writer transient partial write → rollback + recovery
|
|
1144
|
-
//
|
|
1145
|
-
//
|
|
1146
|
-
//
|
|
1147
|
-
|
|
1148
|
-
if (hasFrontendWriter && !recoveryTrigger && !skipFrontendRecovery) {
|
|
990
|
+
// Phase 5: writer transient partial write → rollback + recovery intent. Only
|
|
991
|
+
// for frontend-implementation writers with a remaining root continuation, and
|
|
992
|
+
// only when the rollback journal (captured before the provider call) restores
|
|
993
|
+
// cleanly; otherwise the run stays failed with auto-recovery-blocked.
|
|
994
|
+
if (hasFrontendWriter && !recoveryPending && !skipFrontendRecovery) {
|
|
1149
995
|
const writerNodeId = FRONTEND_WRITER_NODE_IDS.find((id) => {
|
|
1150
996
|
const node = state.nodes[id];
|
|
1151
997
|
return (node &&
|
|
@@ -1156,20 +1002,15 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, f
|
|
|
1156
1002
|
});
|
|
1157
1003
|
if (writerNodeId) {
|
|
1158
1004
|
const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
|
|
1159
|
-
if (continuationCount <
|
|
1005
|
+
if (continuationCount < 1) {
|
|
1160
1006
|
const { rollbackFrontendWriter } = await import("./frontend-writer-recovery.js");
|
|
1161
1007
|
const rollback = await rollbackFrontendWriter({
|
|
1162
1008
|
cwd: repoRoot,
|
|
1163
1009
|
parentRunId: state.runId,
|
|
1164
1010
|
});
|
|
1165
1011
|
if (rollback.ok) {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
failureSource: "frontend-implement-pi",
|
|
1169
|
-
failureOwner: "implementation",
|
|
1170
|
-
protocolFailureReason: "frontend writer transient partial write",
|
|
1171
|
-
recoveryMode: "continuation",
|
|
1172
|
-
};
|
|
1012
|
+
state.frontendRecoveryState = buildFrontendRecoveryIntent(state, "frontend-implement-pi");
|
|
1013
|
+
recoveryPending = true;
|
|
1173
1014
|
}
|
|
1174
1015
|
else {
|
|
1175
1016
|
state.frontendRecoveryResult = buildWriterPartialWriteBlockedResult(state, writerNodeId, rollback.reason);
|
|
@@ -1179,64 +1020,6 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, f
|
|
|
1179
1020
|
}
|
|
1180
1021
|
}
|
|
1181
1022
|
}
|
|
1182
|
-
// Phase 6: verification failure → automatic recovery (re-implement +
|
|
1183
|
-
// re-verify). The verify shell is terminal by design so the failed run's
|
|
1184
|
-
// evidence stays clean; the M6 recovery operation re-runs implement and
|
|
1185
|
-
// verify in a fresh continuation, guarded by the same continuation quota,
|
|
1186
|
-
// budget fold and failure fingerprint as the other recovery paths.
|
|
1187
|
-
if (hasFrontendWriter && !recoveryTrigger && !skipFrontendRecovery) {
|
|
1188
|
-
const verifyFailed = state.nodes["frontend-verify-shell"]?.status === "ERROR";
|
|
1189
|
-
if (verifyFailed) {
|
|
1190
|
-
const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
|
|
1191
|
-
if (continuationCount < frontendRecoveryQuota) {
|
|
1192
|
-
recoveryTrigger = {
|
|
1193
|
-
requestId: state.runId,
|
|
1194
|
-
failureSource: "frontend-verify-shell",
|
|
1195
|
-
failureOwner: "implementation",
|
|
1196
|
-
protocolFailureReason: "frontend verification failure",
|
|
1197
|
-
recoveryMode: "continuation",
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
// Phase 7: frontend review "request changes" → not passed. When the review
|
|
1203
|
-
// node FINISHED but committed `request_review_changes` (typed terminal
|
|
1204
|
-
// verdict), the implementation was reviewed and rejected. The run must not
|
|
1205
|
-
// surface as finished: terminal aggregation below stays partial_failed, a
|
|
1206
|
-
// terminal reason is recorded, the committed findings are rendered into a
|
|
1207
|
-
// downstream-task PRD draft (run-owned), and a recovery trigger carries the
|
|
1208
|
-
// issueCategory-derived owner so a later DAG round consumes the findings
|
|
1209
|
-
// instead of repeating the previous flow.
|
|
1210
|
-
const reviewRequestFact = await readCommittedReviewRequestFact({
|
|
1211
|
-
runDir,
|
|
1212
|
-
});
|
|
1213
|
-
if (reviewRequestFact) {
|
|
1214
|
-
state.terminalReason = "review-request-changes";
|
|
1215
|
-
try {
|
|
1216
|
-
await writeFrontendReviewFindingsPrd({
|
|
1217
|
-
runDir,
|
|
1218
|
-
parentRunId: state.runId,
|
|
1219
|
-
fact: reviewRequestFact,
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
catch (error) {
|
|
1223
|
-
// The PRD draft is additive; a write failure must not mask the
|
|
1224
|
-
// not-passed verdict. The terminal aggregation below still applies.
|
|
1225
|
-
console.warn(`[run-dag] warning: failed to write frontend review findings PRD: ${error instanceof Error ? error.message : String(error)}`);
|
|
1226
|
-
}
|
|
1227
|
-
if (!recoveryTrigger && !skipFrontendRecovery) {
|
|
1228
|
-
const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
|
|
1229
|
-
if (continuationCount < frontendRecoveryQuota) {
|
|
1230
|
-
recoveryTrigger = {
|
|
1231
|
-
requestId: state.runId,
|
|
1232
|
-
failureSource: "frontend-review-pi",
|
|
1233
|
-
failureOwner: classifyReviewIssueCategoryFailureOwner(reviewRequestFact.issueCategory),
|
|
1234
|
-
protocolFailureReason: `frontend review request_review_changes (issueCategory=${reviewRequestFact.issueCategory})`,
|
|
1235
|
-
recoveryMode: "new-dag",
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
1023
|
const finishedCount = Object.values(state.nodes).filter((n) => n.status === "FINISHED").length;
|
|
1241
1024
|
const { supersededIds, supersededFailures } = collectSupersededIntermediateFailures(state);
|
|
1242
1025
|
if (state.convergence && supersededFailures.length > 0) {
|
|
@@ -1252,13 +1035,7 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, f
|
|
|
1252
1035
|
if (effectiveErrorCount === 0 &&
|
|
1253
1036
|
successTerminal &&
|
|
1254
1037
|
finishedCount + conditionSkippedCount + supersededErrorCount === taskCount) {
|
|
1255
|
-
|
|
1256
|
-
// rejected the implementation; the run must never surface as finished
|
|
1257
|
-
// even though every node technically FINISHED.
|
|
1258
|
-
state.status =
|
|
1259
|
-
state.terminalReason === "review-request-changes"
|
|
1260
|
-
? "partial_failed"
|
|
1261
|
-
: "finished";
|
|
1038
|
+
state.status = "finished";
|
|
1262
1039
|
}
|
|
1263
1040
|
else if (finishedCount > 0) {
|
|
1264
1041
|
state.status = "partial_failed";
|
|
@@ -1266,84 +1043,19 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, f
|
|
|
1266
1043
|
else {
|
|
1267
1044
|
state.status = "failed";
|
|
1268
1045
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
* partition is frozen via `computeFrontendRecoveryPlanForSource` and
|
|
1281
|
-
* serialized under the run dir's bounded `.runtime/` namespace. `planHash` is
|
|
1282
|
-
* the second idempotency dimension of `actionParams`.
|
|
1283
|
-
*/
|
|
1284
|
-
async function createRecoveryOperation(input) {
|
|
1285
|
-
const { cwd, runDir, spec, state, trigger } = input;
|
|
1286
|
-
const recoveryLease = await acquireDagRecoveryLease({
|
|
1287
|
-
cwd,
|
|
1288
|
-
parentRunId: state.runId,
|
|
1289
|
-
requestId: trigger.requestId,
|
|
1290
|
-
kind: "auto-frontend-recovery",
|
|
1291
|
-
});
|
|
1292
|
-
if (recoveryLease.kind === "held") {
|
|
1293
|
-
return { kind: "lease-held", lease: recoveryLease.lease };
|
|
1294
|
-
}
|
|
1295
|
-
const { computeFrontendRecoveryPlanForSource } = await import("./frontend-recovery-plan.js");
|
|
1296
|
-
const plan = computeFrontendRecoveryPlanForSource({
|
|
1297
|
-
spec,
|
|
1298
|
-
parentRunId: state.runId,
|
|
1299
|
-
requestId: trigger.requestId,
|
|
1300
|
-
failureSource: trigger.failureSource,
|
|
1301
|
-
});
|
|
1302
|
-
const planHash = createHash("sha256")
|
|
1303
|
-
.update(JSON.stringify(plan))
|
|
1304
|
-
.digest("hex");
|
|
1305
|
-
const continuationPath = path.join(runDir, ".runtime", "frontend-recovery", `${trigger.requestId}.json`);
|
|
1306
|
-
await writeJsonAtomic(continuationPath, {
|
|
1307
|
-
schemaVersion: 1,
|
|
1308
|
-
parentRunId: state.runId,
|
|
1309
|
-
requestId: trigger.requestId,
|
|
1310
|
-
failureSource: trigger.failureSource,
|
|
1311
|
-
recoveryMode: trigger.recoveryMode,
|
|
1312
|
-
plan,
|
|
1313
|
-
});
|
|
1314
|
-
const created = await input.deps.store.create({
|
|
1315
|
-
clientRequestId: `dag-recovery-${state.runId}`,
|
|
1316
|
-
action: "dagRunTask",
|
|
1317
|
-
actionParams: {
|
|
1318
|
-
parentRunId: state.runId,
|
|
1319
|
-
requestId: trigger.requestId,
|
|
1320
|
-
failureSource: trigger.failureSource,
|
|
1321
|
-
failureOwner: trigger.failureOwner,
|
|
1322
|
-
protocolFailureReason: trigger.protocolFailureReason,
|
|
1323
|
-
recoveryMode: trigger.recoveryMode,
|
|
1324
|
-
planHash,
|
|
1325
|
-
},
|
|
1326
|
-
cliArgs: [
|
|
1327
|
-
"dag",
|
|
1328
|
-
"execute",
|
|
1329
|
-
"--dag",
|
|
1330
|
-
continuationPath,
|
|
1331
|
-
"--cwd",
|
|
1332
|
-
cwd,
|
|
1333
|
-
"--json",
|
|
1334
|
-
],
|
|
1335
|
-
});
|
|
1336
|
-
if (created.kind !== "conflict") {
|
|
1337
|
-
// The durable lease must name the operation before controller execution.
|
|
1338
|
-
// A crash in this narrow window leaves a safely blocked reservation rather
|
|
1339
|
-
// than a second process that can launch another recovery DAG.
|
|
1340
|
-
await bindDagRecoveryLeaseExecution({
|
|
1341
|
-
leasePath: recoveryLease.leasePath,
|
|
1342
|
-
lease: recoveryLease.lease,
|
|
1343
|
-
executionRef: created.operation.operationId,
|
|
1344
|
-
});
|
|
1046
|
+
// Terminal recovery bookkeeping for a child attempt: a child that finished
|
|
1047
|
+
// recovered settles its own lineage; any other child terminal settles as
|
|
1048
|
+
// auto-recovery-blocked (phase 3 performs no further recovery).
|
|
1049
|
+
const recovery = state.frontendRecoveryState;
|
|
1050
|
+
if (recovery && recovery.attemptIndex >= 1) {
|
|
1051
|
+
if (state.status === "finished") {
|
|
1052
|
+
settleFrontendRecovery(state, "recovered");
|
|
1053
|
+
}
|
|
1054
|
+
else if (!state.frontendRecoveryResult) {
|
|
1055
|
+
settleFrontendRecovery(state, "auto-recovery-blocked");
|
|
1056
|
+
}
|
|
1345
1057
|
}
|
|
1346
|
-
return
|
|
1058
|
+
return { recoveryPending };
|
|
1347
1059
|
}
|
|
1348
1060
|
function concurrentSiblingWriteSetsForNode(rankWriterNodeIds, nodeId, tasksById) {
|
|
1349
1061
|
if (rankWriterNodeIds.length <= 1)
|