@tea-agent/loop-agent 0.41.1-beta.0 → 0.42.0-next.1
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 +61 -207
- 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 +9 -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-result-contract.js +1 -1
- 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 +640 -685
- package/dist/workflows/dag/interrupt-request.js +0 -7
- package/dist/workflows/dag/node-execution.js +116 -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 +104 -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/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
|
@@ -35,8 +35,25 @@ function toProgressNode(node) {
|
|
|
35
35
|
startedAt: typeof node.startedAt === "string" ? node.startedAt : undefined,
|
|
36
36
|
durationMs: typeof node.durationMs === "number" ? node.durationMs : undefined,
|
|
37
37
|
errorPreview: typeof node.errorPreview === "string" ? node.errorPreview : undefined,
|
|
38
|
+
lastMeaningfulProgressAt: typeof node.lastMeaningfulProgressAt === "string"
|
|
39
|
+
? node.lastMeaningfulProgressAt
|
|
40
|
+
: undefined,
|
|
41
|
+
livenessStatus: typeof node.livenessStatus === "string" ? node.livenessStatus : undefined,
|
|
38
42
|
};
|
|
39
43
|
}
|
|
44
|
+
function latestIso(values) {
|
|
45
|
+
let latest = null;
|
|
46
|
+
for (const iso of values) {
|
|
47
|
+
if (!iso)
|
|
48
|
+
continue;
|
|
49
|
+
const time = Date.parse(iso);
|
|
50
|
+
if (!Number.isFinite(time))
|
|
51
|
+
continue;
|
|
52
|
+
if (!latest || time > latest.time)
|
|
53
|
+
latest = { iso, time };
|
|
54
|
+
}
|
|
55
|
+
return latest?.iso ?? null;
|
|
56
|
+
}
|
|
40
57
|
function elapsedSince(iso, now) {
|
|
41
58
|
if (!iso)
|
|
42
59
|
return null;
|
|
@@ -58,6 +75,8 @@ export function summarizeRunProgress(dag, now = Date.now()) {
|
|
|
58
75
|
stalled: false,
|
|
59
76
|
currentNodeElapsedMs: null,
|
|
60
77
|
runElapsedMs: null,
|
|
78
|
+
lastMeaningfulProgressAt: null,
|
|
79
|
+
observedAtMs: now,
|
|
61
80
|
};
|
|
62
81
|
}
|
|
63
82
|
const rawNodes = Array.isArray(dag.nodes) ? dag.nodes : [];
|
|
@@ -86,6 +105,8 @@ export function summarizeRunProgress(dag, now = Date.now()) {
|
|
|
86
105
|
runElapsedMs: terminal
|
|
87
106
|
? (runDurationMs ?? elapsedSince(runStartedAt, now))
|
|
88
107
|
: elapsedSince(runStartedAt, now),
|
|
108
|
+
lastMeaningfulProgressAt: latestIso(nodes.map((node) => node.lastMeaningfulProgressAt)),
|
|
109
|
+
observedAtMs: now,
|
|
89
110
|
};
|
|
90
111
|
}
|
|
91
112
|
const TERMINAL_RUN_PROGRESS_STATUSES = new Set([
|
|
@@ -108,3 +129,88 @@ export function formatDurationMs(ms) {
|
|
|
108
129
|
return `${minutes} 分 ${seconds} 秒`;
|
|
109
130
|
return `${seconds} 秒`;
|
|
110
131
|
}
|
|
132
|
+
function friendlyRunPhase(node) {
|
|
133
|
+
if (!node)
|
|
134
|
+
return "正在等待下一阶段";
|
|
135
|
+
const searchable = `${node.nodeId} ${node.label}`.toLowerCase();
|
|
136
|
+
if (/contract|boundary|intake/.test(searchable))
|
|
137
|
+
return "正在确认执行边界";
|
|
138
|
+
if (/scout|explore|research/.test(searchable))
|
|
139
|
+
return "正在梳理影响范围";
|
|
140
|
+
if (/plan|design/.test(searchable))
|
|
141
|
+
return "正在规划实施步骤";
|
|
142
|
+
if (/implement|repair|fix|write/.test(searchable))
|
|
143
|
+
return "正在实现本次修改";
|
|
144
|
+
if (/verify|test|lint|review|check/.test(searchable))
|
|
145
|
+
return "正在验证本次修改";
|
|
146
|
+
if (/closeout|handoff|report/.test(searchable))
|
|
147
|
+
return "正在整理执行结果";
|
|
148
|
+
if (node.label && node.label !== node.nodeId)
|
|
149
|
+
return `正在进行${node.label}`;
|
|
150
|
+
return "正在推进当前阶段";
|
|
151
|
+
}
|
|
152
|
+
function formatMeaningfulAge(iso, now) {
|
|
153
|
+
if (!iso)
|
|
154
|
+
return "等待首个执行进展";
|
|
155
|
+
const time = Date.parse(iso);
|
|
156
|
+
if (!Number.isFinite(time))
|
|
157
|
+
return "等待首个执行进展";
|
|
158
|
+
const seconds = Math.max(0, Math.floor((now - time) / 1000));
|
|
159
|
+
if (seconds < 60)
|
|
160
|
+
return `最近进展 ${seconds} 秒前`;
|
|
161
|
+
const minutes = Math.floor(seconds / 60);
|
|
162
|
+
if (minutes < 60)
|
|
163
|
+
return `最近进展 ${minutes} 分钟前`;
|
|
164
|
+
return `最近进展 ${Math.floor(minutes / 60)} 小时前`;
|
|
165
|
+
}
|
|
166
|
+
/** Human-facing copy shared by the task panel and Operator Chat progress row. */
|
|
167
|
+
export function presentRunProgress(summary, now = Date.now()) {
|
|
168
|
+
if (!summary || !summary.effectiveStatus) {
|
|
169
|
+
return {
|
|
170
|
+
kind: "pending",
|
|
171
|
+
title: "DAG 正在启动",
|
|
172
|
+
detail: "正在等待首个执行进展",
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const status = summary.effectiveStatus;
|
|
176
|
+
const liveElapsed = summary.runElapsedMs == null
|
|
177
|
+
? null
|
|
178
|
+
: summary.runElapsedMs +
|
|
179
|
+
(summary.terminal ? 0 : Math.max(0, now - summary.observedAtMs));
|
|
180
|
+
const elapsed = formatDurationMs(liveElapsed);
|
|
181
|
+
const progressDetail = `${friendlyRunPhase(summary.running)} · 已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`;
|
|
182
|
+
if (status === "running") {
|
|
183
|
+
return { kind: "progress", title: `DAG 执行中 · 已运行 ${elapsed}`, detail: progressDetail };
|
|
184
|
+
}
|
|
185
|
+
if (status === "running-quiet") {
|
|
186
|
+
return { kind: "quiet", title: `DAG 仍在执行 · 已运行 ${elapsed}`, detail: progressDetail };
|
|
187
|
+
}
|
|
188
|
+
if (status === "running-suspected-stall") {
|
|
189
|
+
return { kind: "warning", title: `执行可能停滞 · 已运行 ${elapsed}`, detail: progressDetail };
|
|
190
|
+
}
|
|
191
|
+
if (["needs-attention", "paused", "interrupted"].includes(status)) {
|
|
192
|
+
return { kind: "warning", title: "DAG 需要处理", detail: progressDetail };
|
|
193
|
+
}
|
|
194
|
+
if (status === "finished") {
|
|
195
|
+
return {
|
|
196
|
+
kind: "success",
|
|
197
|
+
title: `DAG 已完成 · 总用时 ${elapsed}`,
|
|
198
|
+
detail: `已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (status === "partial_failed" || status === "failed") {
|
|
202
|
+
return {
|
|
203
|
+
kind: "error",
|
|
204
|
+
title: `DAG 未完成 · 总用时 ${elapsed}`,
|
|
205
|
+
detail: `已完成 ${summary.finished}/${summary.total} 步 · ${formatMeaningfulAge(summary.lastMeaningfulProgressAt, now)}`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (status === "pending") {
|
|
209
|
+
return { kind: "pending", title: "DAG 正在启动", detail: progressDetail };
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
kind: "warning",
|
|
213
|
+
title: `DAG 已结束 · 总用时 ${elapsed}`,
|
|
214
|
+
detail: `已完成 ${summary.finished}/${summary.total} 步`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll /api/health until a (possibly brand-new) Console instance answers,
|
|
3
|
+
* then reload. A handoff restart kills the old process before the new one
|
|
4
|
+
* starts listening, so reloading immediately after "restarting" hits a dead
|
|
5
|
+
* port and reads as "the page is gone".
|
|
6
|
+
*/
|
|
7
|
+
export async function reloadWhenConsoleReady(timeoutMs = 30_000) {
|
|
8
|
+
const started = Date.now();
|
|
9
|
+
while (Date.now() - started < timeoutMs) {
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch(`/api/health?ts=${Date.now()}`, {
|
|
12
|
+
cache: "no-store",
|
|
13
|
+
});
|
|
14
|
+
if (res.ok) {
|
|
15
|
+
window.location.reload();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// old process gone, new one not listening yet — keep polling
|
|
21
|
+
}
|
|
22
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
23
|
+
}
|
|
24
|
+
window.location.reload();
|
|
25
|
+
}
|
|
@@ -19,10 +19,9 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { randomBytes } from "node:crypto";
|
|
21
21
|
import { existsSync } from "node:fs";
|
|
22
|
-
import { rm } from "node:fs/promises";
|
|
23
22
|
import path from "node:path";
|
|
24
23
|
import { LoopAgentClient } from "../loop-agent/loop-agent-client.js";
|
|
25
|
-
import { openConsoleAppData,
|
|
24
|
+
import { openConsoleAppData, } from "../../infrastructure/console/app-data.js";
|
|
26
25
|
import { DagConfirmationStore } from "./dag-confirmation.js";
|
|
27
26
|
import { DagExecutionReceiptStore } from "./dag-execution-receipt.js";
|
|
28
27
|
import { DraftStore } from "./draft-store.js";
|
|
@@ -38,10 +37,10 @@ import { GoalRegistry } from "./chat/goals.js";
|
|
|
38
37
|
import { GoalRoundDriver } from "./chat/goal-round-driver.js";
|
|
39
38
|
import { driveGoalRound, recoverDeferredTurns } from "./chat/routes.js";
|
|
40
39
|
import { createOperationEventStore, } from "./operation-sse.js";
|
|
41
|
-
import { OperationStore } from "
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
40
|
+
import { OperationStore } from "../../infrastructure/console/operation-store.js";
|
|
41
|
+
import { reconcileContextExports } from "../../infrastructure/console/context-export-store.js";
|
|
42
|
+
import { reconcileArtifactRevisions } from "../../infrastructure/console/artifact-revision-store.js";
|
|
43
|
+
import { recordContextTransferEvent } from "./context-transfer-diagnostics.js";
|
|
45
44
|
import { probePiReadiness, } from "./pi-readiness.js";
|
|
46
45
|
import { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
|
|
47
46
|
import { NightAuxTicker } from "./night-aux-ticker.js";
|
|
@@ -77,106 +76,6 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
77
76
|
const events = createOperationEventStore({
|
|
78
77
|
persistenceDir: path.join(appData.operations, "events"),
|
|
79
78
|
});
|
|
80
|
-
// Frontend split runtime is repo-scoped: materialization, durable schedule
|
|
81
|
-
// intents, and aggregate closeout must use this workspace's app-data and
|
|
82
|
-
// OperationStore rather than the process startup workspace.
|
|
83
|
-
const splitScheduleDir = path.join(appData.root, "frontend-split-schedule");
|
|
84
|
-
const splitAuditDir = path.join(appData.root, "frontend-split-audit");
|
|
85
|
-
const splitAggregateDir = path.join(appData.root, "frontend-split-aggregate");
|
|
86
|
-
const splitMaterializationBackend = {
|
|
87
|
-
async ensureTask({ taskId }) {
|
|
88
|
-
return { taskId, complete: true };
|
|
89
|
-
},
|
|
90
|
-
async ensureOperation({ taskId, operationId, payloadDigest, request }) {
|
|
91
|
-
const action = request.kind === "child"
|
|
92
|
-
? "frontendSplitChild"
|
|
93
|
-
: "frontendSplitIntegration";
|
|
94
|
-
const { operation } = await operations.ensureSplitMaterializedOperation({
|
|
95
|
-
operationId,
|
|
96
|
-
taskId,
|
|
97
|
-
action,
|
|
98
|
-
actionParams: request,
|
|
99
|
-
payloadHash: payloadDigest,
|
|
100
|
-
});
|
|
101
|
-
return {
|
|
102
|
-
taskId: operation.taskId ?? taskId,
|
|
103
|
-
operationId: operation.operationId,
|
|
104
|
-
complete: true,
|
|
105
|
-
};
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
const materializer = new FrontendSplitTaskMaterializer(splitMaterializationBackend);
|
|
109
|
-
const schedule = async (input) => {
|
|
110
|
-
const fact = {
|
|
111
|
-
schemaVersion: 1,
|
|
112
|
-
kind: "frontend-split-schedule-intent",
|
|
113
|
-
operationId: input.operationId,
|
|
114
|
-
generation: input.generation,
|
|
115
|
-
bindingDigest: input.bindingDigest,
|
|
116
|
-
recordedAt: new Date().toISOString(),
|
|
117
|
-
};
|
|
118
|
-
const name = `${sha256Json({
|
|
119
|
-
operationId: input.operationId,
|
|
120
|
-
generation: input.generation,
|
|
121
|
-
bindingDigest: input.bindingDigest,
|
|
122
|
-
})}.json`;
|
|
123
|
-
await writeSecureJson(path.join(splitScheduleDir, name), fact);
|
|
124
|
-
};
|
|
125
|
-
const auditStale = async (input) => {
|
|
126
|
-
const fact = {
|
|
127
|
-
schemaVersion: 1,
|
|
128
|
-
kind: "frontend-split-stale-audit",
|
|
129
|
-
operationId: input.operationId,
|
|
130
|
-
reason: input.reason,
|
|
131
|
-
fact: input.fact,
|
|
132
|
-
recordedAt: new Date().toISOString(),
|
|
133
|
-
};
|
|
134
|
-
await writeSecureJson(path.join(splitAuditDir, `${sha256Json(fact)}.json`), fact);
|
|
135
|
-
};
|
|
136
|
-
const prepareAggregate = async ({ operationId, aggregate, }) => {
|
|
137
|
-
const before = await operations.get(operationId);
|
|
138
|
-
if (!before) {
|
|
139
|
-
throw new Error(`cannot prepare aggregate: parent operation not found: ${operationId}`);
|
|
140
|
-
}
|
|
141
|
-
const aggregateFile = path.join(splitAggregateDir, `${aggregate.digest}.json`);
|
|
142
|
-
let parentRecordPath = "";
|
|
143
|
-
let wroteAggregate = false;
|
|
144
|
-
let wroteParent = false;
|
|
145
|
-
return {
|
|
146
|
-
committedRef: {
|
|
147
|
-
ref: aggregateFile,
|
|
148
|
-
digest: aggregate.digest,
|
|
149
|
-
commitState: "committed",
|
|
150
|
-
},
|
|
151
|
-
async commit(input) {
|
|
152
|
-
parentRecordPath = input.parentRecordPath;
|
|
153
|
-
await writeSecureJson(aggregateFile, {
|
|
154
|
-
schemaVersion: 1,
|
|
155
|
-
kind: "frontend-split-aggregate-closeout",
|
|
156
|
-
operationId,
|
|
157
|
-
aggregate,
|
|
158
|
-
});
|
|
159
|
-
wroteAggregate = true;
|
|
160
|
-
await writeSecureJson(input.parentRecordPath, input.parentRecord);
|
|
161
|
-
wroteParent = true;
|
|
162
|
-
},
|
|
163
|
-
async rollback() {
|
|
164
|
-
if (wroteAggregate)
|
|
165
|
-
await rm(aggregateFile, { force: true });
|
|
166
|
-
if (wroteParent && parentRecordPath) {
|
|
167
|
-
await writeSecureJson(parentRecordPath, before);
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
const splitOrchestrator = createFrontendSplitOperationAdapter({
|
|
173
|
-
store: operations,
|
|
174
|
-
materializer,
|
|
175
|
-
schedule,
|
|
176
|
-
auditStale,
|
|
177
|
-
prepareAggregate,
|
|
178
|
-
});
|
|
179
|
-
const humanDecisionAdapter = createFrontendHumanDecisionAdapter(operations);
|
|
180
79
|
const drafts = new DraftStore(appData);
|
|
181
80
|
const confirmations = new DagConfirmationStore(appData, deps.bootToken.confirmationToken);
|
|
182
81
|
const dagExecutionReceipts = new DagExecutionReceiptStore(appData);
|
|
@@ -184,6 +83,29 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
184
83
|
const assessments = new AssessmentStore(appData);
|
|
185
84
|
if (!deps.skipReconcile) {
|
|
186
85
|
await operations.reconcileOnBoot();
|
|
86
|
+
const [expiredExports, expiredRevisions] = await Promise.all([
|
|
87
|
+
reconcileContextExports({ appData }),
|
|
88
|
+
reconcileArtifactRevisions({ appData }),
|
|
89
|
+
]);
|
|
90
|
+
// Boot-time lifecycle evidence for what reconcile reclaimed. Only
|
|
91
|
+
// 32-hex ids are real export/revision identities; uploads/ and lock/
|
|
92
|
+
// entries are blob housekeeping and get no event.
|
|
93
|
+
await Promise.all([
|
|
94
|
+
...expiredExports.removed
|
|
95
|
+
.filter((exportId) => /^[a-f0-9]{32}$/.test(exportId))
|
|
96
|
+
.map((exportId) => recordContextTransferEvent({
|
|
97
|
+
appData,
|
|
98
|
+
event: "context_export.expired",
|
|
99
|
+
fields: { exportId },
|
|
100
|
+
}).catch(() => undefined)),
|
|
101
|
+
...expiredRevisions
|
|
102
|
+
.filter((revisionId) => /^[a-f0-9]{32}$/.test(revisionId))
|
|
103
|
+
.map((revisionId) => recordContextTransferEvent({
|
|
104
|
+
appData,
|
|
105
|
+
event: "artifact_revision.expired",
|
|
106
|
+
fields: { revisionId },
|
|
107
|
+
}).catch(() => undefined)),
|
|
108
|
+
]);
|
|
187
109
|
}
|
|
188
110
|
const getReadiness = () => readinessCache;
|
|
189
111
|
const refreshReadiness = async () => {
|
|
@@ -201,9 +123,6 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
201
123
|
client,
|
|
202
124
|
operations,
|
|
203
125
|
events,
|
|
204
|
-
splitOrchestrator,
|
|
205
|
-
humanDecisionAdapter,
|
|
206
|
-
materializer,
|
|
207
126
|
drafts,
|
|
208
127
|
confirmations,
|
|
209
128
|
dagExecutionReceipts,
|
|
@@ -299,9 +218,6 @@ export async function createWorkspaceContext(record, deps) {
|
|
|
299
218
|
client,
|
|
300
219
|
operations,
|
|
301
220
|
events,
|
|
302
|
-
splitOrchestrator,
|
|
303
|
-
humanDecisionAdapter,
|
|
304
|
-
materializer,
|
|
305
221
|
drafts,
|
|
306
222
|
confirmations,
|
|
307
223
|
dagExecutionReceipts,
|
|
@@ -19,8 +19,8 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
19
19
|
import { realpathSync, statSync, unwatchFile, watchFile } from "node:fs";
|
|
20
20
|
import { mkdir, open, readFile, rm, stat } from "node:fs/promises";
|
|
21
21
|
import path from "node:path";
|
|
22
|
-
import { readJsonIfExists, writeSecureJson, } from "
|
|
23
|
-
import { normalizeWorktreeRealpath, repoFingerprintV1, } from "
|
|
22
|
+
import { readJsonIfExists, writeSecureJson, } from "../../infrastructure/console/app-data.js";
|
|
23
|
+
import { normalizeWorktreeRealpath, repoFingerprintV1, } from "../../infrastructure/console/repo-fingerprint.js";
|
|
24
24
|
export const WORKSPACE_REGISTRY_SCHEMA_VERSION = 3;
|
|
25
25
|
/** Stable workspaceId: sha256("loop-console-workspace-v1\0" + normalizedRealpath). */
|
|
26
26
|
export function workspaceIdForRealpath(normalizedRealpath) {
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { deriveWorkerContinuationRunId, workerContinuationAuthorityV1Schema, } from "../../shared/context-transfer.js";
|
|
3
|
+
import { controllerIdentityAnchor } from "../../shared/package-metadata.js";
|
|
4
|
+
import { locateDagRun, readDagRunState, } from "../../workflows/dag/lifecycle.js";
|
|
5
|
+
import { beginWorkerAttemptWithLease } from "../pool/begin-attempt-with-lease.js";
|
|
6
|
+
import { readAttemptLease, releaseAttemptLease, } from "../pool/attempt-lease.js";
|
|
7
|
+
import { readRunOwner } from "../pool/run-owner-store.js";
|
|
8
|
+
import { findRunByWorkerRunId, recordTaskPoolRun, readTaskPoolState, writeTaskPoolState, } from "../pool/run-store.js";
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a Worker parent into an immutable continuation authority. Standalone
|
|
11
|
+
* and missing fixture parents return undefined; a detected Worker parent with
|
|
12
|
+
* incomplete Task Pool ownership fails closed.
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveWorkerContinuationAuthority(input) {
|
|
15
|
+
const located = await locateDagRun(input.repoRoot, input.parentDagRunId);
|
|
16
|
+
if (!located)
|
|
17
|
+
return undefined;
|
|
18
|
+
const state = await readDagRunState(located.runDir);
|
|
19
|
+
const association = state.workerAssociation;
|
|
20
|
+
if (!association)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (located.lifecycle !== "completed" || state.status !== "finished") {
|
|
23
|
+
throw new Error(`WORKER_CONTINUATION_AUTHORITY_MISMATCH: parent DAG is not completed: ${input.parentDagRunId}`);
|
|
24
|
+
}
|
|
25
|
+
const expectedChildWorkerRunId = deriveWorkerContinuationRunId(association.workerRunId, input.revisionId);
|
|
26
|
+
const [owner, parentRun, childRun, poolState, activeLease] = await Promise.all([
|
|
27
|
+
readRunOwner(input.repoRoot, association.workerRunId),
|
|
28
|
+
findRunByWorkerRunId(input.repoRoot, association.workerRunId),
|
|
29
|
+
findRunByWorkerRunId(input.repoRoot, expectedChildWorkerRunId),
|
|
30
|
+
readTaskPoolState(input.repoRoot, {
|
|
31
|
+
featureId: association.featureId,
|
|
32
|
+
taskId: association.taskId,
|
|
33
|
+
}),
|
|
34
|
+
readAttemptLease(input.repoRoot, association.featureId, association.taskId),
|
|
35
|
+
]);
|
|
36
|
+
const parentOutcomeReady = poolState?.status === "Done" &&
|
|
37
|
+
poolState.workerRunId === association.workerRunId;
|
|
38
|
+
const matchingActiveContinuation = input.allowActiveRevision === true &&
|
|
39
|
+
poolState?.status === "Running" &&
|
|
40
|
+
poolState.workerRunId === expectedChildWorkerRunId &&
|
|
41
|
+
poolState.attemptKind === "artifact-continuation" &&
|
|
42
|
+
poolState.continuation?.parentWorkerRunId === association.workerRunId &&
|
|
43
|
+
poolState.continuation.parentDagRunId === input.parentDagRunId &&
|
|
44
|
+
poolState.continuation.revisionId === input.revisionId &&
|
|
45
|
+
activeLease?.workerRunId === expectedChildWorkerRunId &&
|
|
46
|
+
activeLease.revisionId === input.revisionId &&
|
|
47
|
+
activeLease.claimId === poolState.continuation.operationId;
|
|
48
|
+
const matchingTerminalContinuation = input.allowActiveRevision === true &&
|
|
49
|
+
poolState?.status === "Failed" &&
|
|
50
|
+
poolState.workerRunId === expectedChildWorkerRunId &&
|
|
51
|
+
poolState.attemptKind === "artifact-continuation" &&
|
|
52
|
+
poolState.continuation?.parentWorkerRunId === association.workerRunId &&
|
|
53
|
+
poolState.continuation.parentDagRunId === input.parentDagRunId &&
|
|
54
|
+
poolState.continuation.revisionId === input.revisionId &&
|
|
55
|
+
childRun?.status === "failed" &&
|
|
56
|
+
childRun.attemptKind === "artifact-continuation" &&
|
|
57
|
+
childRun.continuation?.operationId ===
|
|
58
|
+
poolState.continuation.operationId &&
|
|
59
|
+
activeLease === undefined;
|
|
60
|
+
if (!owner ||
|
|
61
|
+
!parentRun ||
|
|
62
|
+
owner.workerRunId !== association.workerRunId ||
|
|
63
|
+
owner.featureId !== association.featureId ||
|
|
64
|
+
owner.taskId !== association.taskId ||
|
|
65
|
+
parentRun.featureId !== association.featureId ||
|
|
66
|
+
parentRun.taskId !== association.taskId ||
|
|
67
|
+
parentRun.batchRunId !== owner.batchRunId ||
|
|
68
|
+
parentRun.status !== "succeeded" ||
|
|
69
|
+
(!parentOutcomeReady &&
|
|
70
|
+
!matchingActiveContinuation &&
|
|
71
|
+
!matchingTerminalContinuation)) {
|
|
72
|
+
throw new Error(`WORKER_CONTINUATION_AUTHORITY_MISMATCH: parent Worker ownership is incomplete for ${association.workerRunId}`);
|
|
73
|
+
}
|
|
74
|
+
const authority = workerContinuationAuthorityV1Schema.parse({
|
|
75
|
+
schemaVersion: 1,
|
|
76
|
+
parentWorkerRunId: association.workerRunId,
|
|
77
|
+
childWorkerRunId: expectedChildWorkerRunId,
|
|
78
|
+
featureId: association.featureId,
|
|
79
|
+
taskId: association.taskId,
|
|
80
|
+
batchRunId: owner.batchRunId,
|
|
81
|
+
revisionId: input.revisionId,
|
|
82
|
+
});
|
|
83
|
+
return { authority, parentDagRunId: input.parentDagRunId };
|
|
84
|
+
}
|
|
85
|
+
export async function beginWorkerContinuation(input) {
|
|
86
|
+
const { authority } = input.resolved;
|
|
87
|
+
const priorState = await readTaskPoolState(input.repoRoot, {
|
|
88
|
+
featureId: authority.featureId,
|
|
89
|
+
taskId: authority.taskId,
|
|
90
|
+
});
|
|
91
|
+
const existingLease = await readAttemptLease(input.repoRoot, authority.featureId, authority.taskId);
|
|
92
|
+
const freshParentOutcome = priorState?.status === "Done" &&
|
|
93
|
+
priorState.workerRunId === authority.parentWorkerRunId &&
|
|
94
|
+
priorState.lastContinuation?.revisionId !== authority.revisionId;
|
|
95
|
+
const sameCanonicalRunning = priorState?.status === "Running" &&
|
|
96
|
+
priorState.workerRunId === authority.childWorkerRunId &&
|
|
97
|
+
priorState.continuation?.operationId === input.operationId &&
|
|
98
|
+
existingLease?.workerRunId === authority.childWorkerRunId &&
|
|
99
|
+
existingLease.revisionId === authority.revisionId &&
|
|
100
|
+
existingLease.claimId === input.operationId;
|
|
101
|
+
if (!freshParentOutcome && !sameCanonicalRunning) {
|
|
102
|
+
throw new Error(`state-conflict for ${authority.featureId}/${authority.taskId}: continuation admission is not fresh`);
|
|
103
|
+
}
|
|
104
|
+
if (existingLease) {
|
|
105
|
+
const sameCanonicalOperation = existingLease.workerRunId === authority.childWorkerRunId &&
|
|
106
|
+
existingLease.revisionId === authority.revisionId &&
|
|
107
|
+
existingLease.claimId === input.operationId &&
|
|
108
|
+
priorState?.continuation?.operationId === input.operationId;
|
|
109
|
+
if (!sameCanonicalOperation) {
|
|
110
|
+
throw new Error(`attempt-lease-conflict for ${authority.featureId}/${authority.taskId}: held by ${existingLease.workerRunId}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const identity = input.client.getIdentity?.();
|
|
114
|
+
const lineage = buildLineage(input.resolved, input.operationId);
|
|
115
|
+
try {
|
|
116
|
+
await beginWorkerAttemptWithLease({
|
|
117
|
+
repoRoot: input.repoRoot,
|
|
118
|
+
featureId: authority.featureId,
|
|
119
|
+
taskId: authority.taskId,
|
|
120
|
+
workerRunId: authority.childWorkerRunId,
|
|
121
|
+
batchRunId: authority.batchRunId,
|
|
122
|
+
revisionId: authority.revisionId,
|
|
123
|
+
leaseClaimId: input.operationId,
|
|
124
|
+
...(identity
|
|
125
|
+
? {
|
|
126
|
+
controllerAnchor: controllerIdentityAnchor(identity),
|
|
127
|
+
skipProtocolPreflight: true,
|
|
128
|
+
}
|
|
129
|
+
: { skipProtocolPreflight: true }),
|
|
130
|
+
stateExtras: {
|
|
131
|
+
attemptKind: "artifact-continuation",
|
|
132
|
+
continuation: lineage,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (!existingLease) {
|
|
138
|
+
const lease = await readAttemptLease(input.repoRoot, authority.featureId, authority.taskId);
|
|
139
|
+
const ownsLease = lease?.workerRunId === authority.childWorkerRunId &&
|
|
140
|
+
lease.revisionId === authority.revisionId &&
|
|
141
|
+
lease.claimId === input.operationId;
|
|
142
|
+
if (ownsLease && lease) {
|
|
143
|
+
await releaseAttemptLease({
|
|
144
|
+
repoRoot: input.repoRoot,
|
|
145
|
+
featureId: authority.featureId,
|
|
146
|
+
taskId: authority.taskId,
|
|
147
|
+
token: lease.token,
|
|
148
|
+
workerRunId: authority.childWorkerRunId,
|
|
149
|
+
}).catch(() => undefined);
|
|
150
|
+
}
|
|
151
|
+
const current = await readTaskPoolState(input.repoRoot, {
|
|
152
|
+
featureId: authority.featureId,
|
|
153
|
+
taskId: authority.taskId,
|
|
154
|
+
});
|
|
155
|
+
if (ownsLease &&
|
|
156
|
+
priorState &&
|
|
157
|
+
current?.workerRunId === authority.childWorkerRunId &&
|
|
158
|
+
current.continuation?.operationId === input.operationId) {
|
|
159
|
+
await writeTaskPoolState(input.repoRoot, priorState);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Terminal projection is idempotent. needs-reconcile deliberately retains the
|
|
167
|
+
* Running state and Task lease so recovery cannot be raced by a new attempt.
|
|
168
|
+
*/
|
|
169
|
+
export async function settleWorkerContinuation(input) {
|
|
170
|
+
if (input.state === "needs-reconcile")
|
|
171
|
+
return;
|
|
172
|
+
if (input.state !== "succeeded" &&
|
|
173
|
+
input.state !== "failed" &&
|
|
174
|
+
input.state !== "timed-out")
|
|
175
|
+
return;
|
|
176
|
+
const { authority } = input.resolved;
|
|
177
|
+
const lineage = buildLineage(input.resolved, input.operation.operationId);
|
|
178
|
+
const existing = await findRunByWorkerRunId(input.repoRoot, authority.childWorkerRunId);
|
|
179
|
+
let terminalRun = existing;
|
|
180
|
+
if (existing) {
|
|
181
|
+
assertMatchingTerminalRecord(existing, lineage, authority);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const located = await locateDagRun(input.repoRoot, authority.childWorkerRunId);
|
|
185
|
+
const succeeded = input.state === "succeeded" && input.info.ok;
|
|
186
|
+
terminalRun = {
|
|
187
|
+
schemaVersion: 1,
|
|
188
|
+
batchRunId: authority.batchRunId,
|
|
189
|
+
workerRunId: authority.childWorkerRunId,
|
|
190
|
+
taskId: authority.taskId,
|
|
191
|
+
featureId: authority.featureId,
|
|
192
|
+
status: succeeded ? "succeeded" : "failed",
|
|
193
|
+
attemptKind: "artifact-continuation",
|
|
194
|
+
continuation: lineage,
|
|
195
|
+
harnessTaskId: authority.childWorkerRunId,
|
|
196
|
+
...(located
|
|
197
|
+
? { dagPath: path.relative(input.repoRoot, located.runDir).split(path.sep).join("/") }
|
|
198
|
+
: {}),
|
|
199
|
+
recordedAt: input.operation.finishedAt ?? new Date().toISOString(),
|
|
200
|
+
...(succeeded
|
|
201
|
+
? {}
|
|
202
|
+
: {
|
|
203
|
+
error: input.operation.errorMessage ?? input.info.errorCode ?? "artifact continuation failed",
|
|
204
|
+
failure: {
|
|
205
|
+
category: "Unknown",
|
|
206
|
+
recommendedFollowUpKind: "manual-review",
|
|
207
|
+
derivedFollowUpTaskId: `${authority.taskId}-continuation-review`,
|
|
208
|
+
source: "runner-error",
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
};
|
|
212
|
+
await recordTaskPoolRun({
|
|
213
|
+
repoRoot: input.repoRoot,
|
|
214
|
+
run: terminalRun,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (terminalRun?.status === "succeeded") {
|
|
218
|
+
const parentRun = await findRunByWorkerRunId(input.repoRoot, authority.parentWorkerRunId);
|
|
219
|
+
if (!parentRun || parentRun.status !== "succeeded") {
|
|
220
|
+
throw new Error(`parent Worker outcome is unavailable after continuation: ${authority.parentWorkerRunId}`);
|
|
221
|
+
}
|
|
222
|
+
await writeTaskPoolState(input.repoRoot, {
|
|
223
|
+
schemaVersion: 2,
|
|
224
|
+
featureId: authority.featureId,
|
|
225
|
+
taskId: authority.taskId,
|
|
226
|
+
status: "Done",
|
|
227
|
+
updatedAt: terminalRun.recordedAt,
|
|
228
|
+
workerRunId: authority.parentWorkerRunId,
|
|
229
|
+
...(parentRun.runRecordPath
|
|
230
|
+
? { lastRunRecordPath: parentRun.runRecordPath }
|
|
231
|
+
: {}),
|
|
232
|
+
lastContinuation: {
|
|
233
|
+
...lineage,
|
|
234
|
+
status: "succeeded",
|
|
235
|
+
recordedAt: terminalRun.recordedAt,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const lease = await readAttemptLease(input.repoRoot, authority.featureId, authority.taskId);
|
|
240
|
+
if (!lease)
|
|
241
|
+
return;
|
|
242
|
+
if (lease.workerRunId !== authority.childWorkerRunId ||
|
|
243
|
+
lease.revisionId !== authority.revisionId ||
|
|
244
|
+
lease.claimId !== input.operation.operationId) {
|
|
245
|
+
throw new Error(`attempt lease ownership drift for ${authority.featureId}/${authority.taskId}`);
|
|
246
|
+
}
|
|
247
|
+
await releaseAttemptLease({
|
|
248
|
+
repoRoot: input.repoRoot,
|
|
249
|
+
featureId: authority.featureId,
|
|
250
|
+
taskId: authority.taskId,
|
|
251
|
+
token: lease.token,
|
|
252
|
+
workerRunId: authority.childWorkerRunId,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function buildLineage(resolved, operationId) {
|
|
256
|
+
return {
|
|
257
|
+
schemaVersion: 1,
|
|
258
|
+
kind: "artifact-continuation",
|
|
259
|
+
parentWorkerRunId: resolved.authority.parentWorkerRunId,
|
|
260
|
+
parentDagRunId: resolved.parentDagRunId,
|
|
261
|
+
childWorkerRunId: resolved.authority.childWorkerRunId,
|
|
262
|
+
revisionId: resolved.authority.revisionId,
|
|
263
|
+
operationId,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function assertMatchingTerminalRecord(run, lineage, authority) {
|
|
267
|
+
if (run.attemptKind !== "artifact-continuation" ||
|
|
268
|
+
run.featureId !== authority.featureId ||
|
|
269
|
+
run.taskId !== authority.taskId ||
|
|
270
|
+
run.batchRunId !== authority.batchRunId ||
|
|
271
|
+
run.continuation?.parentWorkerRunId !== lineage.parentWorkerRunId ||
|
|
272
|
+
run.continuation?.parentDagRunId !== lineage.parentDagRunId ||
|
|
273
|
+
run.continuation?.childWorkerRunId !== lineage.childWorkerRunId ||
|
|
274
|
+
run.continuation?.revisionId !== lineage.revisionId ||
|
|
275
|
+
run.continuation?.operationId !== lineage.operationId) {
|
|
276
|
+
throw new Error(`Worker continuation terminal record conflict: ${authority.childWorkerRunId}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -2362,6 +2362,8 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
|
2362
2362
|
durationMs: readNumber(node, "durationMs"),
|
|
2363
2363
|
startedAt: readString(node, "startedAt"),
|
|
2364
2364
|
finishedAt: readString(node, "finishedAt"),
|
|
2365
|
+
lastMeaningfulProgressAt: readString(node, "lastMeaningfulProgressAt"),
|
|
2366
|
+
livenessStatus: readString(node, "livenessStatus"),
|
|
2365
2367
|
...(outputPreview ? { outputPreview } : {}),
|
|
2366
2368
|
...(errorPreview ? { errorPreview } : {}),
|
|
2367
2369
|
...(attempts && attempts.length > 0
|
|
@@ -2407,6 +2409,8 @@ function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
|
2407
2409
|
durationMs: readNumber(node, "durationMs"),
|
|
2408
2410
|
startedAt: readString(node, "startedAt"),
|
|
2409
2411
|
finishedAt: readString(node, "finishedAt"),
|
|
2412
|
+
lastMeaningfulProgressAt: readString(node, "lastMeaningfulProgressAt"),
|
|
2413
|
+
livenessStatus: readString(node, "livenessStatus"),
|
|
2410
2414
|
...(outputPreview ? { outputPreview } : {}),
|
|
2411
2415
|
...(errorPreview ? { errorPreview } : {}),
|
|
2412
2416
|
...(attempts && attempts.length > 0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { findPackageRoot, readPackageName, readPackageVersion, } from "../../shared/package-metadata.js";
|
|
4
|
-
import { repoFingerprintV1 } from "
|
|
4
|
+
import { repoFingerprintV1 } from "../../infrastructure/console/repo-fingerprint.js";
|
|
5
5
|
/** Observe `/api/health` schema version (independent of Console product health). */
|
|
6
6
|
export const OBSERVE_HEALTH_SCHEMA_VERSION = 1;
|
|
7
7
|
function routeExists(routes, method, pathname) {
|