@tea-agent/loop-agent 0.42.0-next.9 → 0.42.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 +111 -45
- package/dist/application/dag/run-dag.js +8 -2
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +26 -8
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/cli/program.js +1 -1
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/task-source-prepare.js +3 -1
- package/dist/executors/dag-pi-executor.js +2584 -600
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-extension-resolver.js +14 -2
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +135 -55
- package/dist/shared/dag-failure-category.js +6 -0
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/contract/apply.js +36 -2
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/parse-intent.js +7 -0
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +143 -0
- package/dist/worker/console/chat/browser-routes.js +148 -0
- package/dist/worker/console/chat/chat-event-store.js +4 -2
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime.js +173 -94
- package/dist/worker/console/chat/resource-loader.js +4 -1
- package/dist/worker/console/chat/routes.js +208 -66
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +78 -11
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +27 -7
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
- package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
- package/dist/worker/console/chat/subagents/index.js +5 -0
- package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
- package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
- package/dist/worker/console/chat/subagents/types.js +13 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +284 -0
- package/dist/worker/console/chat/terminal-tools.js +199 -0
- package/dist/worker/console/chat/tool-preview.js +21 -0
- package/dist/worker/console/chat/tools.js +13 -1
- package/dist/worker/console/chat/turn-process.js +1 -0
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
- package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
- package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
- package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/chat-view-types.js +1 -1
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/constants.js +22 -22
- package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
- package/dist/worker/observe/static/dag-history-labels.js +1 -0
- package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
- package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
- package/dist/worker/observe/static/dag-node-purpose.js +10 -10
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/index.html +4 -4
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
- package/dist/worker/observe/static/relations.js +7 -5
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +431 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +51 -4
- package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +64 -22
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +194 -243
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/dag-retry-schema.js +3 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-contract-facts.js +130 -0
- package/dist/workflows/dag/frontend-design-policy.js +103 -17
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +21 -3
- package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
- package/dist/workflows/dag/frontend-recovery-run.js +146 -16
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-risk.js +2 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +46 -7
- package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
- package/dist/workflows/dag/frontend-verification-trace.js +38 -4
- package/dist/workflows/dag/frontend-writer-admission.js +2 -2
- package/dist/workflows/dag/init-hybrid.js +70 -35
- package/dist/workflows/dag/node-execution.js +225 -226
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/recovery-lease.js +106 -16
- package/dist/workflows/dag/rerun-feedback.js +1 -0
- package/dist/workflows/dag/rerun-plan.js +96 -4
- package/dist/workflows/dag/rerun-run.js +28 -6
- package/dist/workflows/dag/rerun-task.js +221 -18
- package/dist/workflows/dag/retry-policy.js +27 -10
- package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
- package/dist/workflows/dag/runner.js +246 -19
- package/dist/workflows/dag/structured-output-repair.js +4 -1
- package/dist/workflows/dag/types.js +10 -3
- package/dist/workflows/dag/validate.js +10 -8
- package/dist/workflows/dag/workspace-checkpoint.js +66 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +6 -4
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +68 -4
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/harness.json +1 -1
- package/package.json +8 -6
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +22 -2
- package/skills/frontend-plan/references/decision-contract.md +114 -5
- package/skills/frontend-plan/references/design-decisions.md +32 -0
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
- package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
|
@@ -6,8 +6,8 @@ import path from "node:path";
|
|
|
6
6
|
import { isHardBudgetBreached, resolveEffectiveMaxConcurrent, } from "../../application/evaluation/budget.js";
|
|
7
7
|
import { readCandidateRecord } from "../../infrastructure/evaluation/candidate-store.js";
|
|
8
8
|
import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/runtime.js";
|
|
9
|
-
import { assertFrozenBudget, initRunBudgetLedger, preflightBudgetOrBreach, recordFinishedNodeBudget, writeBudgetLedgerArtifacts, } from "./budget-enforcement.js";
|
|
10
|
-
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, requireActiveDagRun, } from "./lifecycle.js";
|
|
9
|
+
import { assertFrozenBudget, initRunBudgetLedger, preflightBudgetOrBreach, recordFinishedNodeBudget, validateCumulativeBudgetSuccessor, writeBudgetLedgerArtifacts, } from "./budget-enforcement.js";
|
|
10
|
+
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, readDagRunState, 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";
|
|
@@ -31,11 +31,14 @@ import { bindFrontendShapeStateToRunTopology, commitFrontendShapeTransitionCas,
|
|
|
31
31
|
import { assertFrontendTopologyBinding, buildFrontendTopologyBinding, computeFrontendShapeSourceDigest, } from "./frontend-shape.js";
|
|
32
32
|
import { ensureFrontendShapeCapsuleRecord } from "./frontend-shape-capsule-store.js";
|
|
33
33
|
import { parseDagSpec, resolveModelForTask, } from "./types.js";
|
|
34
|
-
import {
|
|
34
|
+
import { computeActionPayloadHash, } from "../../infrastructure/console/operation-store.js";
|
|
35
|
+
import { classifyFailureOwner, failureOwnerSchema } from "./frontend-recovery-capsule.js";
|
|
36
|
+
import { acquireDagRecoveryLease, bindDagRecoveryLeaseExecution, claimDagRecoveryLease, } from "./recovery-lease.js";
|
|
35
37
|
import { executeDynamicCondition } from "./dynamic-runtime/condition.js";
|
|
36
38
|
import { executeDynamicLoopUntil } from "./dynamic-runtime/loop-until.js";
|
|
37
39
|
import { executeDynamicMapExpansion } from "./dynamic-runtime/map.js";
|
|
38
40
|
import { executeDynamicReduction } from "./dynamic-runtime/reduction.js";
|
|
41
|
+
import { installRunnerExitDiagnostics } from "./runner-exit-diagnostics.js";
|
|
39
42
|
export { buildNodePrompt, buildNodePromptWithResolvedSkillInstructions };
|
|
40
43
|
export async function loadDagSpecFromFile(dagPath) {
|
|
41
44
|
const raw = JSON.parse(await readFile(dagPath, "utf-8"));
|
|
@@ -601,6 +604,36 @@ async function executeDagCheckpoint(input) {
|
|
|
601
604
|
const onSigInt = () => shutdownOnSignal("SIGINT");
|
|
602
605
|
process.once("SIGTERM", onSigTerm);
|
|
603
606
|
process.once("SIGINT", onSigInt);
|
|
607
|
+
// Exit-time diagnostics (silent-death forensics): write an `armed` line at
|
|
608
|
+
// checkpoint start and append process-exit / uncaught-exception /
|
|
609
|
+
// unhandled-rejection lines with a live state snapshot so an abnormal death
|
|
610
|
+
// (supervisor hard kill, wedged event loop, crash with lost stderr) leaves
|
|
611
|
+
// a durable trace next to state.json. Removed on clean terminal return.
|
|
612
|
+
const exitDiagnostics = installRunnerExitDiagnostics({
|
|
613
|
+
runDir,
|
|
614
|
+
runId: state.runId,
|
|
615
|
+
title: state.title,
|
|
616
|
+
snapshot: () => {
|
|
617
|
+
const heartbeatAt = state.runner?.heartbeatAt;
|
|
618
|
+
return {
|
|
619
|
+
status: state.status,
|
|
620
|
+
terminalReason: state.terminalReason,
|
|
621
|
+
failureCategory: state.failureCategory,
|
|
622
|
+
heartbeatAt,
|
|
623
|
+
heartbeatAgeMs: heartbeatAt === undefined
|
|
624
|
+
? undefined
|
|
625
|
+
: Math.max(0, Date.now() - Date.parse(heartbeatAt)),
|
|
626
|
+
running: Object.values(state.nodes)
|
|
627
|
+
.filter((node) => node.status === "RUNNING")
|
|
628
|
+
.map((node) => ({
|
|
629
|
+
id: node.id,
|
|
630
|
+
attempt: node.currentAttempt,
|
|
631
|
+
startedAt: node.startedAt,
|
|
632
|
+
lastMeaningfulProgressAt: node.lastMeaningfulProgressAt,
|
|
633
|
+
})),
|
|
634
|
+
};
|
|
635
|
+
},
|
|
636
|
+
});
|
|
604
637
|
try {
|
|
605
638
|
const tasksById = new Map(spec.tasks.map((task) => [task.id, task]));
|
|
606
639
|
const baseExecuteNode = input.executeNode ??
|
|
@@ -851,8 +884,34 @@ async function executeDagCheckpoint(input) {
|
|
|
851
884
|
const result = await runRecoveryOperationController({
|
|
852
885
|
operationId: created.operation.operationId,
|
|
853
886
|
parentRunId: state.runId,
|
|
887
|
+
observeOnly: created.kind === "observed",
|
|
854
888
|
deps: {
|
|
855
889
|
...input.recovery,
|
|
890
|
+
budget: input.recovery.budget ?? state.budget,
|
|
891
|
+
readLedgers: input.recovery.readLedgers ?? (async () => {
|
|
892
|
+
if (!state.budget)
|
|
893
|
+
return [];
|
|
894
|
+
const location = await locateDagRun(cwd, state.runId);
|
|
895
|
+
let current = await readDagRunState(location?.runDir ?? runDir);
|
|
896
|
+
const visited = new Set([current.runId]);
|
|
897
|
+
while (current.frontendRecoveryState?.parentRunId === current.runId && current.frontendRecoveryState.childRunId && current.frontendRecoveryState.childRunId !== current.runId) {
|
|
898
|
+
if (["child-staging", "child-activating"].includes(current.frontendRecoveryState.phase))
|
|
899
|
+
break;
|
|
900
|
+
const childId = current.frontendRecoveryState.childRunId;
|
|
901
|
+
if (visited.has(childId))
|
|
902
|
+
throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: cyclic recovery");
|
|
903
|
+
visited.add(childId);
|
|
904
|
+
const childLocation = await locateDagRun(cwd, childId);
|
|
905
|
+
if (!childLocation)
|
|
906
|
+
throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: missing recovery child");
|
|
907
|
+
const child = await readDagRunState(childLocation.runDir);
|
|
908
|
+
validateCumulativeBudgetSuccessor(current, child);
|
|
909
|
+
current = child;
|
|
910
|
+
}
|
|
911
|
+
if (!current.budgetLedger)
|
|
912
|
+
throw Error("FRONTEND_PROVIDER_BUDGET_INVALID: recovery ledger missing");
|
|
913
|
+
return [current.budgetLedger];
|
|
914
|
+
}),
|
|
856
915
|
failureOwner: input.recovery.failureOwner ??
|
|
857
916
|
recoveryTrigger.failureOwner,
|
|
858
917
|
protocolFailureReason: input.recovery.protocolFailureReason ??
|
|
@@ -860,7 +919,40 @@ async function executeDagCheckpoint(input) {
|
|
|
860
919
|
recoveryMode: input.recovery.recoveryMode ?? recoveryTrigger.recoveryMode,
|
|
861
920
|
},
|
|
862
921
|
});
|
|
863
|
-
|
|
922
|
+
// The recovery CLI stages its child by writing a fresh
|
|
923
|
+
// `frontendRecoveryState` into the parent run on disk. A successful
|
|
924
|
+
// operation without that bound lineage is not a successful recovery:
|
|
925
|
+
// fail closed instead of archiving a false-positive `recovered` result.
|
|
926
|
+
let recoveryLineageError;
|
|
927
|
+
if (result.terminalReason === "succeeded") {
|
|
928
|
+
try {
|
|
929
|
+
const persistedParentState = await readDagRunState(runDir);
|
|
930
|
+
const persistedRecovery = persistedParentState.frontendRecoveryState;
|
|
931
|
+
if (!persistedRecovery ||
|
|
932
|
+
persistedRecovery.phase !== "child-running" ||
|
|
933
|
+
persistedRecovery.parentRunId !== state.runId ||
|
|
934
|
+
persistedRecovery.requestId !== recoveryTrigger.requestId ||
|
|
935
|
+
!persistedRecovery.childRunId) {
|
|
936
|
+
throw new Error("persisted recovery lineage is missing or unbound");
|
|
937
|
+
}
|
|
938
|
+
await assertRecoveredFrontendLineage({
|
|
939
|
+
cwd,
|
|
940
|
+
rootRunId: state.runId,
|
|
941
|
+
rootRequestId: recoveryTrigger.requestId,
|
|
942
|
+
recovery: persistedRecovery,
|
|
943
|
+
maxContinuations: spec.frontendRecovery?.maxContinuations ?? 1,
|
|
944
|
+
});
|
|
945
|
+
state.frontendRecoveryState = structuredClone(persistedRecovery);
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
recoveryLineageError =
|
|
949
|
+
error instanceof Error ? error.message : String(error);
|
|
950
|
+
state.status = "failed";
|
|
951
|
+
state.terminalReason =
|
|
952
|
+
`frontend recovery lineage invalid: ${recoveryLineageError}`;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
state.frontendRecoveryResult = buildRecoveryResultForTrigger(state, recoveryTrigger, result.terminalReason === "succeeded" && !recoveryLineageError
|
|
864
956
|
? "recovered"
|
|
865
957
|
: "auto-recovery-blocked");
|
|
866
958
|
}
|
|
@@ -903,10 +995,61 @@ async function executeDagCheckpoint(input) {
|
|
|
903
995
|
finally {
|
|
904
996
|
clearInterval(heartbeatTimer);
|
|
905
997
|
interruptWatcher?.stop();
|
|
998
|
+
exitDiagnostics.stop();
|
|
906
999
|
process.removeListener("SIGTERM", onSigTerm);
|
|
907
1000
|
process.removeListener("SIGINT", onSigInt);
|
|
908
1001
|
}
|
|
909
1002
|
}
|
|
1003
|
+
async function assertRecoveredFrontendLineage(input) {
|
|
1004
|
+
const maxContinuations = Math.max(1, Math.min(5, input.maxContinuations));
|
|
1005
|
+
let parentRunId = input.rootRunId;
|
|
1006
|
+
let recovery = input.recovery;
|
|
1007
|
+
const recoveryRootRunId = recovery.recoveryRootRunId;
|
|
1008
|
+
const visited = new Set();
|
|
1009
|
+
if (recoveryRootRunId !== input.rootRunId) {
|
|
1010
|
+
throw new Error("recovery root lineage binding mismatch");
|
|
1011
|
+
}
|
|
1012
|
+
for (let depth = 0; depth < maxContinuations; depth += 1) {
|
|
1013
|
+
// The persisted parent intent represents the edge about to be traversed,
|
|
1014
|
+
// so its counters equal the number of already-completed continuation edges.
|
|
1015
|
+
// Each completed child increments both counters exactly once; no edge may
|
|
1016
|
+
// exceed the frozen DAG quota.
|
|
1017
|
+
if (recovery.continuationCount !== depth ||
|
|
1018
|
+
recovery.attemptIndex !== depth) {
|
|
1019
|
+
throw new Error("recovery child lineage counters are not monotonic");
|
|
1020
|
+
}
|
|
1021
|
+
if (recovery.phase !== "child-running" ||
|
|
1022
|
+
recovery.parentRunId !== parentRunId ||
|
|
1023
|
+
recovery.recoveryRootRunId !== recoveryRootRunId ||
|
|
1024
|
+
!recovery.childRunId ||
|
|
1025
|
+
(depth === 0
|
|
1026
|
+
? recovery.requestId !== input.rootRequestId
|
|
1027
|
+
: recovery.requestId !== parentRunId)) {
|
|
1028
|
+
throw new Error("persisted recovery lineage is missing or unbound");
|
|
1029
|
+
}
|
|
1030
|
+
if (visited.has(recovery.childRunId)) {
|
|
1031
|
+
throw new Error("recovery child lineage contains a cycle");
|
|
1032
|
+
}
|
|
1033
|
+
visited.add(recovery.childRunId);
|
|
1034
|
+
const child = await locateDagRun(input.cwd, recovery.childRunId);
|
|
1035
|
+
if (!child || child.lifecycle !== "completed") {
|
|
1036
|
+
throw new Error("recovery child is not completed");
|
|
1037
|
+
}
|
|
1038
|
+
const childState = await readDagRunState(child.runDir);
|
|
1039
|
+
if (childState.runId !== recovery.childRunId) {
|
|
1040
|
+
throw new Error("recovery child state binding mismatch");
|
|
1041
|
+
}
|
|
1042
|
+
if (childState.status === "finished")
|
|
1043
|
+
return;
|
|
1044
|
+
if (childState.frontendRecoveryResult?.outcome !== "recovered" ||
|
|
1045
|
+
!childState.frontendRecoveryState) {
|
|
1046
|
+
throw new Error("recovery child terminal state is not successful");
|
|
1047
|
+
}
|
|
1048
|
+
parentRunId = childState.runId;
|
|
1049
|
+
recovery = childState.frontendRecoveryState;
|
|
1050
|
+
}
|
|
1051
|
+
throw new Error("recovery child lineage exceeds the continuation quota");
|
|
1052
|
+
}
|
|
910
1053
|
async function finalizeRunOwnedInterrupt(input) {
|
|
911
1054
|
const request = await readInterruptRequest(input.runDir);
|
|
912
1055
|
if (!request)
|
|
@@ -1211,13 +1354,40 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, f
|
|
|
1211
1354
|
if (verifyFailed) {
|
|
1212
1355
|
const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
|
|
1213
1356
|
if (continuationCount < frontendRecoveryQuota) {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1357
|
+
let recoveryOwner = classifyFailureOwner({
|
|
1358
|
+
nodeId: "frontend-verify-shell",
|
|
1359
|
+
rawFailureCategory: state.nodes["frontend-verify-shell"]?.failureCategory,
|
|
1360
|
+
});
|
|
1361
|
+
try {
|
|
1362
|
+
const verifyFailure = JSON.parse(await readFile(path.join(runDir, "contracts/frontend-verify-failure.json"), "utf8"));
|
|
1363
|
+
const owner = failureOwnerSchema.safeParse(verifyFailure.failureOwner);
|
|
1364
|
+
if (verifyFailure.schemaVersion === 1 &&
|
|
1365
|
+
verifyFailure.schemaId === "frontend-verify-failure-v1" &&
|
|
1366
|
+
verifyFailure.nodeId === "frontend-verify-shell" &&
|
|
1367
|
+
verifyFailure.failureCategory === state.nodes["frontend-verify-shell"]?.failureCategory &&
|
|
1368
|
+
owner.success) {
|
|
1369
|
+
recoveryOwner = owner.data;
|
|
1370
|
+
}
|
|
1371
|
+
else {
|
|
1372
|
+
recoveryOwner = "unknown";
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
catch {
|
|
1376
|
+
// The node's classified category remains the fallback; never turn
|
|
1377
|
+
// unknown/environment/configuration failures into writer work.
|
|
1378
|
+
}
|
|
1379
|
+
if (recoveryOwner === "implementation")
|
|
1380
|
+
recoveryTrigger = {
|
|
1381
|
+
requestId: state.runId,
|
|
1382
|
+
failureSource: "frontend-verify-shell",
|
|
1383
|
+
failureOwner: recoveryOwner,
|
|
1384
|
+
protocolFailureReason: "frontend verification failure",
|
|
1385
|
+
recoveryMode: "continuation",
|
|
1386
|
+
};
|
|
1387
|
+
else
|
|
1388
|
+
state.terminalReason = recoveryOwner === "environment"
|
|
1389
|
+
? "frontend-verification-wait-external"
|
|
1390
|
+
: `frontend-verification-${recoveryOwner}-requires-contract-review`;
|
|
1221
1391
|
}
|
|
1222
1392
|
}
|
|
1223
1393
|
}
|
|
@@ -1313,8 +1483,24 @@ async function createRecoveryOperation(input) {
|
|
|
1313
1483
|
requestId: trigger.requestId,
|
|
1314
1484
|
kind: "auto-frontend-recovery",
|
|
1315
1485
|
});
|
|
1486
|
+
let effectiveLease = recoveryLease;
|
|
1487
|
+
let observeOnly = false;
|
|
1316
1488
|
if (recoveryLease.kind === "held") {
|
|
1317
|
-
|
|
1489
|
+
if (recoveryLease.lease.kind !== "auto-frontend-recovery" ||
|
|
1490
|
+
recoveryLease.lease.requestId !== trigger.requestId) {
|
|
1491
|
+
return { kind: "lease-held", lease: recoveryLease.lease };
|
|
1492
|
+
}
|
|
1493
|
+
const claimed = await claimDagRecoveryLease({
|
|
1494
|
+
leasePath: recoveryLease.leasePath,
|
|
1495
|
+
lease: recoveryLease.lease,
|
|
1496
|
+
});
|
|
1497
|
+
effectiveLease = claimed;
|
|
1498
|
+
if (claimed.kind === "held") {
|
|
1499
|
+
if (!claimed.lease.executionRef) {
|
|
1500
|
+
return { kind: "lease-held", lease: claimed.lease };
|
|
1501
|
+
}
|
|
1502
|
+
observeOnly = true;
|
|
1503
|
+
}
|
|
1318
1504
|
}
|
|
1319
1505
|
const { computeFrontendRecoveryPlanForSource } = await import("./frontend-recovery-plan.js");
|
|
1320
1506
|
const plan = computeFrontendRecoveryPlanForSource({
|
|
@@ -1333,9 +1519,10 @@ async function createRecoveryOperation(input) {
|
|
|
1333
1519
|
requestId: trigger.requestId,
|
|
1334
1520
|
failureSource: trigger.failureSource,
|
|
1335
1521
|
recoveryMode: trigger.recoveryMode,
|
|
1522
|
+
planHash,
|
|
1336
1523
|
plan,
|
|
1337
1524
|
});
|
|
1338
|
-
const
|
|
1525
|
+
const operationInput = {
|
|
1339
1526
|
clientRequestId: `dag-recovery-${state.runId}`,
|
|
1340
1527
|
action: "dagRunTask",
|
|
1341
1528
|
actionParams: {
|
|
@@ -1356,18 +1543,58 @@ async function createRecoveryOperation(input) {
|
|
|
1356
1543
|
trigger.protocolFailureReason ?? "frontend automatic recovery",
|
|
1357
1544
|
"--request-id",
|
|
1358
1545
|
trigger.requestId,
|
|
1546
|
+
"--frontend-recovery",
|
|
1359
1547
|
"--json",
|
|
1360
1548
|
],
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1549
|
+
};
|
|
1550
|
+
let created;
|
|
1551
|
+
if (effectiveLease.lease.executionRef) {
|
|
1552
|
+
// Replay the exact operation bound by the durable lease. A missing record
|
|
1553
|
+
// cannot be replaced with a new operation id without violating the fence.
|
|
1554
|
+
const existing = await input.deps.store.get(effectiveLease.lease.executionRef);
|
|
1555
|
+
if (!existing) {
|
|
1556
|
+
throw new Error(`frontend recovery lease operation not found: ${effectiveLease.lease.executionRef}`);
|
|
1557
|
+
}
|
|
1558
|
+
if (existing.clientRequestId !== operationInput.clientRequestId ||
|
|
1559
|
+
existing.action !== operationInput.action ||
|
|
1560
|
+
existing.payloadHash !==
|
|
1561
|
+
computeActionPayloadHash(operationInput.action, operationInput.actionParams)) {
|
|
1562
|
+
throw new Error("frontend recovery lease execution identity drift: referenced operation payload does not match the recovery request");
|
|
1563
|
+
}
|
|
1564
|
+
created = { kind: "idempotent", operation: existing };
|
|
1565
|
+
}
|
|
1566
|
+
else {
|
|
1567
|
+
created = await input.deps.store.create(operationInput);
|
|
1568
|
+
}
|
|
1569
|
+
if (created.kind !== "conflict" && !observeOnly) {
|
|
1363
1570
|
// The durable lease must name the operation before controller execution.
|
|
1364
|
-
//
|
|
1365
|
-
// than a second process that can launch another recovery DAG.
|
|
1571
|
+
// This also repairs a lease left behind by a crash before the original bind.
|
|
1366
1572
|
await bindDagRecoveryLeaseExecution({
|
|
1367
|
-
leasePath:
|
|
1368
|
-
lease:
|
|
1573
|
+
leasePath: effectiveLease.leasePath,
|
|
1574
|
+
lease: effectiveLease.lease,
|
|
1369
1575
|
executionRef: created.operation.operationId,
|
|
1370
1576
|
});
|
|
1577
|
+
// Repair CLI args on both fresh and replayed operations. This closes the
|
|
1578
|
+
// crash window after operation creation but before args were persisted.
|
|
1579
|
+
await input.deps.store.update(created.operation.operationId, {
|
|
1580
|
+
cliArgs: [
|
|
1581
|
+
"dag",
|
|
1582
|
+
"rerun-task",
|
|
1583
|
+
"--run-id",
|
|
1584
|
+
state.runId,
|
|
1585
|
+
"--reason",
|
|
1586
|
+
trigger.protocolFailureReason ?? "frontend automatic recovery",
|
|
1587
|
+
"--request-id",
|
|
1588
|
+
trigger.requestId,
|
|
1589
|
+
"--frontend-recovery",
|
|
1590
|
+
"--recovery-operation-id",
|
|
1591
|
+
created.operation.operationId,
|
|
1592
|
+
"--json",
|
|
1593
|
+
],
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
if (created.kind !== "conflict" && observeOnly) {
|
|
1597
|
+
return { kind: "observed", operation: created.operation };
|
|
1371
1598
|
}
|
|
1372
1599
|
return created;
|
|
1373
1600
|
}
|
|
@@ -112,7 +112,10 @@ export function isStructuredRepairableFailureCategory(category) {
|
|
|
112
112
|
return (category === "invalid-output" ||
|
|
113
113
|
category === "structured-output-truncated" ||
|
|
114
114
|
category === "output-too-large" ||
|
|
115
|
-
category === "empty-output"
|
|
115
|
+
category === "empty-output" ||
|
|
116
|
+
// A length-truncated structured turn with a non-empty candidate artifact
|
|
117
|
+
// still repairs best through the frozen artifact repair prompt.
|
|
118
|
+
category === "output-limit");
|
|
116
119
|
}
|
|
117
120
|
export function formatStructuredArtifactPointer(input) {
|
|
118
121
|
return [
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { frontendExecutionPolicySchema } from "../../shared/frontend-execution-policy.js";
|
|
2
3
|
import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, OPENSPEC_SPEC_EXT_RE, } from "../../shared/openspec-spec.js";
|
|
3
4
|
import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
|
|
4
5
|
import { assertDagPromptSourceRule } from "./prompt-source.js";
|
|
@@ -1385,6 +1386,7 @@ export const dagSpecSchema = z
|
|
|
1385
1386
|
evaluation: dagEvaluationBindingSchema.optional(),
|
|
1386
1387
|
/** Optional hard/record-only budget; requires version 3 or 4. */
|
|
1387
1388
|
budget: dagBudgetSchema.optional(),
|
|
1389
|
+
frontendExecutionPolicy: frontendExecutionPolicySchema.optional(),
|
|
1388
1390
|
sourceBinding: dagSourceBindingSchema.optional(),
|
|
1389
1391
|
/** v4 managed Task Contract binding; only valid on version 4. */
|
|
1390
1392
|
taskContractBinding: dagTaskContractBindingSchema.optional(),
|
|
@@ -1548,7 +1550,8 @@ export const frontendRecoveryPhaseSchema = z.enum([
|
|
|
1548
1550
|
* the child carries a frozen lineage snapshot so the activation gate can prove
|
|
1549
1551
|
* it is the reserved child of a child-running parent.
|
|
1550
1552
|
*
|
|
1551
|
-
* Invariants: `attemptIndex
|
|
1553
|
+
* Invariants: `attemptIndex` and `continuationCount` are bounded by the frozen
|
|
1554
|
+
* frontend recovery quota (currently 1-5); a child's
|
|
1552
1555
|
* `recoveryRootRunId` always equals the root runId; `revision` is the CAS
|
|
1553
1556
|
* pre-comparison counter. `revision` monotonicity is enforced at runtime by the
|
|
1554
1557
|
* runner's `(existing?.revision ?? 0) + 1` CAS write (the schema only bounds it
|
|
@@ -1566,9 +1569,13 @@ export const frontendRecoveryStateSchema = z
|
|
|
1566
1569
|
recoveryRootRunId: z.string().min(1),
|
|
1567
1570
|
parentRunId: z.string().min(1),
|
|
1568
1571
|
childRunId: z.string().min(1).optional(),
|
|
1572
|
+
budgetLineage: z.object({
|
|
1573
|
+
parentLedgerSha256: z.string().regex(/^[a-f0-9]{64}$/), inheritedAt: z.string().datetime(),
|
|
1574
|
+
inheritedConsumed: z.object({ tokens: z.number().nonnegative().nullable(), wallTimeMs: z.number().nonnegative(), executorCalls: z.number().int().nonnegative(), providerRequests: z.number().int().nonnegative().optional(), repairPasses: z.number().int().nonnegative(), peakContextChars: z.number().int().nonnegative(), missingTokenNodeIds: z.array(z.string()) }).strict(),
|
|
1575
|
+
}).strict().optional(),
|
|
1569
1576
|
attemptId: z.string().min(1),
|
|
1570
|
-
attemptIndex: z.number().int().min(0).max(
|
|
1571
|
-
continuationCount: z.number().int().min(0).max(
|
|
1577
|
+
attemptIndex: z.number().int().min(0).max(5),
|
|
1578
|
+
continuationCount: z.number().int().min(0).max(5),
|
|
1572
1579
|
/** Node id of the reset-closure root: frontend-plan-pi /
|
|
1573
1580
|
* frontend-plan-revision-pi / frontend-prewrite-gate-shell /
|
|
1574
1581
|
* frontend-implement-pi (writer partial write). */
|
|
@@ -3,7 +3,7 @@ import { resolveShellCommands } from "../../executors/shell-executor.js";
|
|
|
3
3
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
4
4
|
import { resolveRepairTaskForGate } from "./repair-artifact.js";
|
|
5
5
|
import { topoSortToRanks } from "./topo.js";
|
|
6
|
-
import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, CONTEXT_OVERFLOW_RETRY_CATEGORY, } from "./retry-policy.js";
|
|
6
|
+
import { isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, INCOMPLETE_WRITE_SET_RETRY_CATEGORY, OUTPUT_LIMIT_RETRY_CATEGORY, WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY, WRITER_EMPTY_DIFF_RETRY_CATEGORY, CONTEXT_OVERFLOW_RETRY_CATEGORY, } from "./retry-policy.js";
|
|
7
7
|
import { isBackendTestCompletenessRetryCandidate } from "./backend-test-writer-completeness.js";
|
|
8
8
|
import { defaultFrontendTestLayout, frontendTestLayoutFromSpec, } from "./frontend-test-layout.js";
|
|
9
9
|
const GOVERNANCE_WARNING_TYPES = new Set([
|
|
@@ -630,27 +630,28 @@ function validateRetryPolicyTaskConfig(task, issues) {
|
|
|
630
630
|
const allowed = new Set([
|
|
631
631
|
WRITER_EMPTY_DIFF_RETRY_CATEGORY,
|
|
632
632
|
INCOMPLETE_WRITE_SET_RETRY_CATEGORY,
|
|
633
|
+
OUTPUT_LIMIT_RETRY_CATEGORY,
|
|
633
634
|
]);
|
|
634
635
|
const unsupported = task.retryPolicy.retryCategories.filter((category) => !allowed.has(category));
|
|
635
636
|
if (task.retryPolicy.maxAttempts < 2 ||
|
|
636
|
-
task.retryPolicy.maxAttempts >
|
|
637
|
+
task.retryPolicy.maxAttempts > 5 ||
|
|
637
638
|
unsupported.length > 0 ||
|
|
638
639
|
!categories.has(WRITER_EMPTY_DIFF_RETRY_CATEGORY)) {
|
|
639
640
|
issues.push({
|
|
640
641
|
type: "invalid-retry-policy",
|
|
641
|
-
message: `task ${task.id} backend-test writer retryPolicy must use 2..
|
|
642
|
+
message: `task ${task.id} backend-test writer retryPolicy must use 2..5 total attempts and only ${WRITER_EMPTY_DIFF_RETRY_CATEGORY}, ${INCOMPLETE_WRITE_SET_RETRY_CATEGORY}, and/or ${OUTPUT_LIMIT_RETRY_CATEGORY}`,
|
|
642
643
|
});
|
|
643
644
|
}
|
|
644
645
|
return;
|
|
645
646
|
}
|
|
646
647
|
if (isWriterEmptyDiffCandidate &&
|
|
647
648
|
(task.retryPolicy.maxAttempts !== 2 ||
|
|
648
|
-
task.retryPolicy.retryCategories.
|
|
649
|
-
task.retryPolicy.retryCategories
|
|
650
|
-
|
|
649
|
+
!task.retryPolicy.retryCategories.includes(WRITER_EMPTY_DIFF_RETRY_CATEGORY) ||
|
|
650
|
+
task.retryPolicy.retryCategories.some((category) => category !== WRITER_EMPTY_DIFF_RETRY_CATEGORY &&
|
|
651
|
+
category !== OUTPUT_LIMIT_RETRY_CATEGORY))) {
|
|
651
652
|
issues.push({
|
|
652
653
|
type: "invalid-retry-policy",
|
|
653
|
-
message: `task ${task.id} writer retryPolicy must use exactly two total attempts and
|
|
654
|
+
message: `task ${task.id} writer retryPolicy must use exactly two total attempts and ${WRITER_EMPTY_DIFF_RETRY_CATEGORY}, with optional ${OUTPUT_LIMIT_RETRY_CATEGORY}`,
|
|
654
655
|
});
|
|
655
656
|
return;
|
|
656
657
|
}
|
|
@@ -659,6 +660,7 @@ function validateRetryPolicyTaskConfig(task, issues) {
|
|
|
659
660
|
const allowedWriterTransportCategories = new Set([
|
|
660
661
|
WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY,
|
|
661
662
|
CONTEXT_OVERFLOW_RETRY_CATEGORY,
|
|
663
|
+
OUTPUT_LIMIT_RETRY_CATEGORY,
|
|
662
664
|
]);
|
|
663
665
|
const allAllowed = [...categories].every((category) => allowedWriterTransportCategories.has(category));
|
|
664
666
|
if (task.retryPolicy.maxAttempts !== 2 ||
|
|
@@ -667,7 +669,7 @@ function validateRetryPolicyTaskConfig(task, issues) {
|
|
|
667
669
|
!categories.has(WRITER_CLEAN_TIMEOUT_RETRY_CATEGORY)) {
|
|
668
670
|
issues.push({
|
|
669
671
|
type: "invalid-retry-policy",
|
|
670
|
-
message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only writer-clean-timeout (plus optional context-overflow)`,
|
|
672
|
+
message: `task ${task.id} writer transport retryPolicy must use exactly two total attempts and only writer-clean-timeout (plus optional context-overflow/output-limit)`,
|
|
671
673
|
});
|
|
672
674
|
}
|
|
673
675
|
}
|
|
@@ -184,3 +184,69 @@ export function workspaceFingerprintsMatch(left, right) {
|
|
|
184
184
|
return false;
|
|
185
185
|
return left.fingerprint === right.fingerprint;
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Path-level drift between two checkpoints: paths whose content hash,
|
|
189
|
+
* porcelain state, or presence differs (changed paths plus stable Task
|
|
190
|
+
* Contract governance inputs). The whole-tree fingerprint also moves on HEAD
|
|
191
|
+
* commits or porcelain churn that leaves every path's content untouched;
|
|
192
|
+
* only content-level path diffs are actionable for scoped rerun eligibility.
|
|
193
|
+
* Returns undefined when either checkpoint lacks a path inventory so callers
|
|
194
|
+
* fail closed to the strict whole-fingerprint comparison.
|
|
195
|
+
*/
|
|
196
|
+
export function workspaceDriftedPaths(parent, current) {
|
|
197
|
+
// changedPaths is only the porcelain inventory; it cannot explain files
|
|
198
|
+
// already committed between two different HEADs. Never localize that drift.
|
|
199
|
+
if (!parent.gitHeadSha ||
|
|
200
|
+
!current.gitHeadSha ||
|
|
201
|
+
parent.gitHeadSha !== current.gitHeadSha) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
if (!Array.isArray(parent.changedPaths) || !Array.isArray(current.changedPaths)) {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
// Porcelain v1 records only the rename destination in this checkpoint
|
|
208
|
+
// schema, so the removed source path cannot be proven safe for imported
|
|
209
|
+
// readers. Keep scoped reuse fail-closed until both sides expose complete
|
|
210
|
+
// rename lineage.
|
|
211
|
+
if (parent.changedPaths.some((entry) => entry.state === "renamed") ||
|
|
212
|
+
current.changedPaths.some((entry) => entry.state === "renamed")) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
if ((parent.stableGovernanceInputs === undefined) !==
|
|
216
|
+
(current.stableGovernanceInputs === undefined)) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const drifted = new Set();
|
|
220
|
+
const parentByPath = new Map(parent.changedPaths.map((entry) => [entry.path, entry]));
|
|
221
|
+
const currentByPath = new Map(current.changedPaths.map((entry) => [entry.path, entry]));
|
|
222
|
+
for (const [pathKey, parentEntry] of parentByPath) {
|
|
223
|
+
const currentEntry = currentByPath.get(pathKey);
|
|
224
|
+
if (!currentEntry) {
|
|
225
|
+
drifted.add(pathKey);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (parentEntry.contentSha256 !== currentEntry.contentSha256 ||
|
|
229
|
+
parentEntry.state !== currentEntry.state ||
|
|
230
|
+
parentEntry.mode !== currentEntry.mode) {
|
|
231
|
+
drifted.add(pathKey);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
for (const pathKey of currentByPath.keys()) {
|
|
235
|
+
if (!parentByPath.has(pathKey))
|
|
236
|
+
drifted.add(pathKey);
|
|
237
|
+
}
|
|
238
|
+
if (parent.stableGovernanceInputs && current.stableGovernanceInputs) {
|
|
239
|
+
const parentGov = new Map(parent.stableGovernanceInputs.map((entry) => [entry.path, entry.contentSha256]));
|
|
240
|
+
const currentGov = new Map(current.stableGovernanceInputs.map((entry) => [entry.path, entry.contentSha256]));
|
|
241
|
+
for (const [pathKey, hash] of parentGov) {
|
|
242
|
+
const other = currentGov.get(pathKey);
|
|
243
|
+
if (other === undefined || other !== hash)
|
|
244
|
+
drifted.add(pathKey);
|
|
245
|
+
}
|
|
246
|
+
for (const [pathKey, hash] of currentGov) {
|
|
247
|
+
if (parentGov.get(pathKey) !== hash)
|
|
248
|
+
drifted.add(pathKey);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return [...drifted];
|
|
252
|
+
}
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
- [`github-collaboration.md`](github-collaboration.md):仓库内 GitHub 协作约定及发布通道。
|
|
10
10
|
- [`version-and-release-management.md`](version-and-release-management.md):版本发布管理——next / release-x.y / latest 渠道、移动式 `npm-next` ref、`inspect next` 查询入口、workflow 与 promote 手册。
|
|
11
11
|
- [`production-readiness.md`](production-readiness.md):交付前的生产就绪判断。
|
|
12
|
+
- [`frontend-reliability-canary-runbook.md`](frontend-reliability-canary-runbook.md):frontend 可靠性修复的 live canary 内网执行手册(协议冻结自 exec-plan §17;配对矩阵、证据注册与人工晋升门)。
|
|
12
13
|
- [`agent-worker-production-readiness.md`](agent-worker-production-readiness.md):agent-worker Feature/Task Pool 外层编排的 Production Readiness v1 操作合同。
|
|
13
14
|
- [`backend-test-jacoco-coverage.md`](backend-test-jacoco-coverage.md):Java 被测服务挂载 JaCoCo tcpserver agent 的 Maven、`java -jar` 与 Docker 操作手册。
|
|
15
|
+
- [`console-chat-dogfood-playbook.md`](console-chat-dogfood-playbook.md):Operator Chat(含 Explore 子 Agent)真实模型 dogfood 的 CLI 驱动步骤、mutation 双 token / SSE 超时 / question 应答等已知陷阱与验证方式。
|
|
14
16
|
|
|
15
17
|
验证命令仍以 [`../governance/verification-matrix.md`](../governance/verification-matrix.md) 为准。
|
|
@@ -466,9 +466,9 @@
|
|
|
466
466
|
"retryCategories": {
|
|
467
467
|
"type": "array",
|
|
468
468
|
"items": {
|
|
469
|
-
"enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout", "read-burst", "context-overflow"]
|
|
469
|
+
"enum": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-limit", "output-too-large", "protocol-invalid", "invalid-output", "writer-empty-diff", "incomplete-write-set", "writer-clean-timeout", "read-burst", "context-overflow"]
|
|
470
470
|
},
|
|
471
|
-
"default": ["timeout", "network", "rate-limit", "unavailable", "empty-output"],
|
|
471
|
+
"default": ["timeout", "network", "rate-limit", "unavailable", "empty-output", "output-limit"],
|
|
472
472
|
"description": "Failure categories eligible for retry. quota/auth/write-guard are never eligible. invalid-output is allowed only on explicit structured/protocol policies; writer-empty-diff and incomplete-write-set are reserved for requireChangedFiles generation/completeness writers; writer-clean-timeout is reserved for standard bounded writers after explicit zero-write-tool-call and empty-diff evidence."
|
|
473
473
|
}
|
|
474
474
|
}
|