@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/CHANGELOG.md +248 -95
- package/README.md +9 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +23 -5
- package/dist/cli/program.js +21 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/cli.js +2 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +455 -28
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +2306 -61
- package/dist/executors/pi-executor.js +7 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +180 -33
- package/dist/executors/shell-executor.js +1249 -224
- package/dist/executors/shell-write-guard.js +14 -0
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-failure-category.js +150 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +39 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +70 -15
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +50 -5
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +18 -1
- package/dist/task/contract/schema.js +4 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +199 -18
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +298 -1
- package/dist/task/source-prepare/semantic-intake.js +25 -5
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/task/source-references.js +22 -1
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/chat-event-store.js +62 -4
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +264 -16
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +298 -32
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +14 -0
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +227 -7
- package/dist/worker/console/operator-actions.js +388 -0
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +355 -3
- package/dist/worker/console/security.js +67 -13
- package/dist/worker/console/server.js +609 -164
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-BAsjg7ew.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-CyIlrwZw.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-Bo4tobhH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dfpow9gu.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-DPmzG_B4.js} +1 -1
- package/dist/worker/console/static/assets/channel-DSrJAn0-.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-pKQpB1UW.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-5n9YYquZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DP3CtA2a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DYxMJPUy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-CS3D50Io.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-gSdDfo5_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-A48q0ZNt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-ow8Mm3DY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-CMKfLWYE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BiTBdQPX.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-Cm7exRz_.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-D9O5qGOu.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-CkvQnako.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-B1rzp7CB.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BP07Ipcc.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BlPzweQb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-CxPa7n0S.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CMTZs53A.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-CxG_X52r.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D8axtJM0.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DIsGzn7b.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CU9zkDG0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-DCjTmz5P.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BksdmFzg.js} +1 -1
- package/dist/worker/console/static/assets/index-B-O5ChYq.css +1 -0
- package/dist/worker/console/static/assets/index-CfnY8AQx.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-CGzzzJtL.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BdseB80N.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-DM9gsgha.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-3q-_9R2b.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-B8N-Mxsd.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-EPIHUUsu.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CxwO12aa.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-bPqbVX6Y.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-KzqIvzeX.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-CyofpWiV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-cTWJvLTP.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-BK--q27B.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-C0SDOHXK.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-Cong6YIo.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DUVkgZx8.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C7XgxuZg.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-D9Ayvwcy.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-Byb000cH.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DMLegPt4.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-ChMMeOsP.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-bEMEPisW.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
- package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +102 -21
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
- package/dist/worker/console/workspace-context.js +344 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +160 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +224 -3
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +90 -4
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +208 -0
- package/dist/worker/observe/static/format.js +60 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -96
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +184 -1
- package/dist/worker/observe/static/styles.css +395 -0
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +14 -5
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +7 -424
- package/dist/workflows/dag/frontend-review-context.js +288 -14
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +52 -17
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +272 -0
- package/dist/workflows/dag/frontend-writer-status.js +244 -0
- package/dist/workflows/dag/init-hybrid.js +984 -613
- package/dist/workflows/dag/interrupt-request.js +10 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +468 -58
- package/dist/workflows/dag/prompt.js +44 -19
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +124 -1
- package/dist/workflows/dag/rerun-plan.js +144 -5
- package/dist/workflows/dag/rerun-run.js +81 -6
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +318 -8
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +321 -31
- package/dist/workflows/dag/upstream-artifacts.js +0 -4
- package/dist/workflows/dag/validate.js +92 -32
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +9 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +45 -1
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +3 -3
- package/package.json +7 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +3 -4
- package/skills/frontend-implementation/references/design-spec.md +15 -10
- package/skills/frontend-implementation/references/node-contracts.md +23 -13
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/command-reference.md +12 -4
- package/skills/loop-agent/references/hybrid-dag.md +8 -1
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
6
|
+
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
6
7
|
import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
|
|
7
8
|
import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isFrontendWriterAuthorized, readFrontendPrewriteResult, } from "./scheduler.js";
|
|
8
9
|
import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
|
|
@@ -10,11 +11,15 @@ import { resolveContextPolicy } from "./context-policy.js";
|
|
|
10
11
|
import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
|
|
11
12
|
import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
|
|
12
13
|
import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
|
|
13
|
-
import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
|
|
14
|
+
import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
|
|
14
15
|
import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
|
|
15
16
|
import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
|
|
16
17
|
import { getStructuredContractValidator } from "./contract-output-registry.js";
|
|
17
18
|
import "./contract-validator-registrations.js";
|
|
19
|
+
import { computeNormalizedFailureFingerprint } from "./frontend-recovery-lineage.js";
|
|
20
|
+
import { readTypedEventStoreFromJsonl } from "./frontend-typed-event-store.js";
|
|
21
|
+
import { allowedRepairReadPaths, auditRepairAttemptToolUse, buildStructuredOutputRepairPrompt, freezeStructuredOutputRepairContext, hasNonEmptyStructuredCandidate, isFrontendStructuredRepairSchemaId, isStructuredRepairableFailureCategory, persistStructuredAttemptRaw, sessionEventsByteLength, GOVERNANCE_BLOCKED_CATEGORY, STRUCTURED_REPAIR_EXHAUSTED_CATEGORY, } from "./structured-output-repair.js";
|
|
22
|
+
import { readFrontendCanonicalCandidate } from "./frontend-implementation-contract.js";
|
|
18
23
|
import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
19
24
|
import { buildProjectGovernanceContext, readCompletedWriterChangeManifests, writeProjectGovernanceContext, } from "./project-governance-context.js";
|
|
20
25
|
import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
|
|
@@ -132,16 +137,26 @@ export function buildNodePrompt(spec, task, upstream, options) {
|
|
|
132
137
|
function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
133
138
|
if (schemaId === "frontend-implementation-contract-plan-patch-v1") {
|
|
134
139
|
return [
|
|
135
|
-
"Return exactly
|
|
140
|
+
"Return exactly this artifact and nothing else: one short lead-in line, then exactly one fenced json block containing only the editable RFC 7386 plan patch, immediately followed by exactly one fenced openspec-citations block.",
|
|
136
141
|
"The runtime applies your patch to its protected contract skeleton. Do not emit schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff; do not emit the full contract.",
|
|
137
142
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
143
|
+
"The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
|
|
144
|
+
"```openspec-citations",
|
|
145
|
+
'{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
|
|
146
|
+
'{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
|
|
147
|
+
"```",
|
|
138
148
|
];
|
|
139
149
|
}
|
|
140
150
|
if (schemaId === "frontend-implementation-contract-revision-patch-v1") {
|
|
141
151
|
return [
|
|
142
|
-
"Return exactly
|
|
152
|
+
"Return exactly this artifact and nothing else: one short lead-in line, then exactly one fenced json block containing the RFC 7386 merge-patch delta, immediately followed by exactly one fenced openspec-citations block.",
|
|
143
153
|
"The delta contains only the fields you change; null deletes a key, arrays and scalars replace, and plain objects merge recursively. Do not emit a full contract or a schemaVersion/targets section.",
|
|
144
154
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
155
|
+
"The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
|
|
156
|
+
"```openspec-citations",
|
|
157
|
+
'{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
|
|
158
|
+
'{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
|
|
159
|
+
"```",
|
|
145
160
|
];
|
|
146
161
|
}
|
|
147
162
|
return [
|
|
@@ -149,30 +164,131 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
|
149
164
|
"The contract JSON fields are the complete implementation plan. Do not emit a separate plan document, Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
150
165
|
];
|
|
151
166
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
const FRONTEND_PLAN_NODE_ID = "frontend-plan-pi";
|
|
168
|
+
const COMPACT_RETRY_SECTION_MAX_CHARS = 1_200;
|
|
169
|
+
const COMPACT_RETRY_TASK_MAX_CHARS = 4_800;
|
|
170
|
+
/** Keep alias/field-shape remediation out of the initial Plan prompt. */
|
|
171
|
+
function frontendPlanValidationRetryGuidance(reason) {
|
|
172
|
+
const guidance = [];
|
|
173
|
+
if (/notApplicableReason|uiState.*reason/i.test(reason)) {
|
|
174
|
+
guidance.push("For this retry, use uiStates[].notApplicableReason for a non-applicable state; omit expectedBehavior instead of supplying an empty value.");
|
|
175
|
+
}
|
|
176
|
+
if (/interaction.*(?:\bid\b|\bname\b)|requires non-empty interaction\.name/i.test(reason)) {
|
|
177
|
+
guidance.push("For this retry, use interactions[].name with non-empty trigger and expectedBehavior.");
|
|
178
|
+
}
|
|
179
|
+
if (/verificationTargets.*uiStates|uiStates.*verificationTargets/i.test(reason)) {
|
|
180
|
+
guidance.push("For this retry, provide verificationTargets[].uiStates as an array; use [] when the target has no named UI state.");
|
|
181
|
+
}
|
|
182
|
+
return guidance;
|
|
155
183
|
}
|
|
156
|
-
function
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
184
|
+
function compactRetryText(text, maxChars) {
|
|
185
|
+
if (text.length <= maxChars)
|
|
186
|
+
return text;
|
|
187
|
+
const headChars = Math.floor(maxChars * 0.78);
|
|
188
|
+
const tailChars = Math.max(0, maxChars - headChars - 52);
|
|
189
|
+
return `${text.slice(0, headChars)}\n… [omitted after context-overflow] …\n${text.slice(-tailChars)}`;
|
|
190
|
+
}
|
|
191
|
+
function compactRetrySection(prompt, name, maxChars) {
|
|
192
|
+
const match = prompt.match(new RegExp(`<${name}>([\\s\\S]*?)</${name}>`));
|
|
193
|
+
return compactRetryText(match?.[1]?.trim() ?? "(not available)", maxChars);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* A context-overflow retry must not resend the full resolved skills, governance
|
|
197
|
+
* payload, repeated source excerpts, and upstream previews that caused the
|
|
198
|
+
* previous session to overflow. Keep the immutable node contract plus bounded
|
|
199
|
+
* objective/criteria/task excerpts; canonical artifacts remain readable by
|
|
200
|
+
* pointer from the retained task instructions.
|
|
201
|
+
*/
|
|
202
|
+
export function buildContextOverflowRetryPrompt(task, basePrompt) {
|
|
162
203
|
return [
|
|
163
|
-
"<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
204
|
+
"<compact_retry_context>",
|
|
205
|
+
"This is a fresh, compact retry after provider context-overflow. The original full prompt remains audit evidence but is intentionally not resent.",
|
|
206
|
+
"Do not re-read task sources, upstream stdout, skills, or full diff artifacts that are already represented by canonical artifacts. Start from the smallest retained artifact and expand only the exact file or diff fragment needed.",
|
|
207
|
+
"</compact_retry_context>",
|
|
208
|
+
`<dag_objective>\n${compactRetrySection(basePrompt, "dag_objective", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</dag_objective>`,
|
|
209
|
+
`<success_criteria>\n${compactRetrySection(basePrompt, "success_criteria", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</success_criteria>`,
|
|
210
|
+
`<global_constraints>\n${compactRetrySection(basePrompt, "global_constraints", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</global_constraints>`,
|
|
211
|
+
`<node_contract>\n${compactRetrySection(basePrompt, "node_contract", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</node_contract>`,
|
|
212
|
+
`<upstream_context>\n${compactRetrySection(basePrompt, "upstream_context", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</upstream_context>`,
|
|
213
|
+
[
|
|
214
|
+
"<task>",
|
|
215
|
+
`Original task id: ${task.id}`,
|
|
216
|
+
compactRetrySection(basePrompt, "task", COMPACT_RETRY_TASK_MAX_CHARS),
|
|
217
|
+
"</task>",
|
|
218
|
+
].join("\n"),
|
|
219
|
+
].join("\n\n");
|
|
172
220
|
}
|
|
173
|
-
function
|
|
221
|
+
function isFrontendPlanLadderTask(task) {
|
|
222
|
+
return task.id === FRONTEND_PLAN_NODE_ID;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Read the committed plan ledger snapshot for the ladder's "no new committed
|
|
226
|
+
* fact" check. The digest is over the sorted committed payload hashes, and
|
|
227
|
+
* `hasTerminalFact` reflects a committed `finalize_plan` terminal fact.
|
|
228
|
+
*/
|
|
229
|
+
async function readFrontendPlanCommittedSnapshot(runDir, nodeId) {
|
|
230
|
+
const records = await readTypedEventStoreFromJsonl(path.join(runDir, nodeId, "plan-typed-facts.jsonl"));
|
|
231
|
+
const committed = records.filter((record) => record.phase === "committed");
|
|
232
|
+
const digest = createHash("sha256")
|
|
233
|
+
.update(committed
|
|
234
|
+
.map((record) => record.payloadSha256)
|
|
235
|
+
.sort()
|
|
236
|
+
.join("\n"))
|
|
237
|
+
.digest("hex");
|
|
238
|
+
const hasTerminalFact = committed.some((record) => record.fact.kind === "finalize_plan");
|
|
239
|
+
return { digest, hasTerminalFact };
|
|
240
|
+
}
|
|
241
|
+
export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep) {
|
|
174
242
|
if (attemptNumber <= 1)
|
|
175
243
|
return basePrompt;
|
|
244
|
+
// Context overflow is a transport/session failure, not a plan-protocol
|
|
245
|
+
// failure. It must take precedence over the plan retry ladder so every
|
|
246
|
+
// retry, including a repeated plan overflow, starts from the compact
|
|
247
|
+
// envelope rather than re-sending the original prompt.
|
|
248
|
+
if (previousFailureCategory === "context-overflow") {
|
|
249
|
+
return [
|
|
250
|
+
buildContextOverflowRetryPrompt(task, basePrompt),
|
|
251
|
+
"",
|
|
252
|
+
"<retry_instruction>",
|
|
253
|
+
"The previous attempt exceeded the provider context window. This retry starts a fresh session: keep the evidence surface narrow.",
|
|
254
|
+
"Do not re-read task sources, upstream stdout, skills, or full diff artifacts that are already represented by the canonical contract/review context. Read the smallest relevant artifact first, then only the specific source file or diff fragment needed to decide a finding.",
|
|
255
|
+
"Do not read artifacts/diff_patch.patch in full. For frontend review, read the bounded diff summary and then at most the specific per-file diff fragments it identifies, in part order.",
|
|
256
|
+
"Use concise tool calls and finish the required terminal/output protocol as soon as the evidence is sufficient.",
|
|
257
|
+
"</retry_instruction>",
|
|
258
|
+
].join("\n");
|
|
259
|
+
}
|
|
260
|
+
if (frontendPlanRetryStep === "compact-terminal-first") {
|
|
261
|
+
return [
|
|
262
|
+
basePrompt,
|
|
263
|
+
"",
|
|
264
|
+
"<retry_instruction>",
|
|
265
|
+
"Frontend plan retry ladder step: compact-terminal-first.",
|
|
266
|
+
"Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts, fill the missing required facts, then call finalize_plan exactly once.",
|
|
267
|
+
"Keep the existing committed ledger intact; do not re-derive already committed facts.",
|
|
268
|
+
"</retry_instruction>",
|
|
269
|
+
].join("\n");
|
|
270
|
+
}
|
|
271
|
+
if (frontendPlanRetryStep === "bounded-tool-only") {
|
|
272
|
+
return [
|
|
273
|
+
basePrompt,
|
|
274
|
+
"",
|
|
275
|
+
"<retry_instruction>",
|
|
276
|
+
"Frontend plan retry ladder step: bounded-tool-only.",
|
|
277
|
+
"Reduce reasoning and wall-clock time. Use only read / fact / terminal tools and only for the necessary missing facts, then call finalize_plan exactly once.",
|
|
278
|
+
"Do not expand scope or re-derive already committed facts.",
|
|
279
|
+
"</retry_instruction>",
|
|
280
|
+
].join("\n");
|
|
281
|
+
}
|
|
282
|
+
if (frontendPlanRetryStep === "backup-model") {
|
|
283
|
+
return [
|
|
284
|
+
basePrompt,
|
|
285
|
+
"",
|
|
286
|
+
"<retry_instruction>",
|
|
287
|
+
"Frontend plan retry ladder step: backup-model.",
|
|
288
|
+
"A backup provider/model route was selected after repeated non-converging failures. Re-derive only the missing committed facts, then call finalize_plan exactly once; do not repeat the failed strategy.",
|
|
289
|
+
"</retry_instruction>",
|
|
290
|
+
].join("\n");
|
|
291
|
+
}
|
|
176
292
|
if (previousFailureCategory === "protocol-invalid" &&
|
|
177
293
|
task.outputProtocol &&
|
|
178
294
|
previousProtocolReason) {
|
|
@@ -182,15 +298,64 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
182
298
|
buildProtocolRetryInstruction(task.outputProtocol, previousProtocolReason),
|
|
183
299
|
].join("\n");
|
|
184
300
|
}
|
|
301
|
+
if (previousFailureCategory === "review-terminal-missing") {
|
|
302
|
+
return [
|
|
303
|
+
basePrompt,
|
|
304
|
+
"",
|
|
305
|
+
"<retry_instruction>",
|
|
306
|
+
"The review emitted a verdict in response text but never committed the authoritative typed terminal tool call (approve_review / request_review_changes). The response text is NOT the authority: no branch or gate reads it.",
|
|
307
|
+
"Call exactly one typed terminal tool to finish: approve_review (implementation passes, no Critical/Important findings) or request_review_changes (with typed issueCategory, at least one evidenceRef, and non-empty findings). Do not repeat the review analysis; commit the terminal tool once and stop.",
|
|
308
|
+
"</retry_instruction>",
|
|
309
|
+
].join("\n");
|
|
310
|
+
}
|
|
311
|
+
if (previousFailureCategory === "read-burst") {
|
|
312
|
+
if (task.id !== FRONTEND_PLAN_NODE_ID) {
|
|
313
|
+
return [
|
|
314
|
+
basePrompt,
|
|
315
|
+
"",
|
|
316
|
+
"<retry_instruction>",
|
|
317
|
+
"The previous attempt exceeded its read budget. Start a fresh, evidence-minimal pass: do not re-read task sources, upstream stdout, skills, or full diff artifacts already represented by a canonical context artifact.",
|
|
318
|
+
"Read the smallest relevant summary first, then only the specific source file or diff fragment needed for the required decision. Finish the required terminal/output protocol as soon as evidence is sufficient.",
|
|
319
|
+
"</retry_instruction>",
|
|
320
|
+
].join("\n");
|
|
321
|
+
}
|
|
322
|
+
return [
|
|
323
|
+
basePrompt,
|
|
324
|
+
"",
|
|
325
|
+
"<retry_instruction>",
|
|
326
|
+
"Previous plan attempt issued too many read-only tool calls (read/grep/ls/find) and blew up the context window. Trust the upstream frontend-contract-pi typed requirement facts and frontend-scout-pi target surface already provided — do NOT re-read contract/scout stdout, PRD/source files, or component sources you already inspected.",
|
|
327
|
+
"Minimize discovery reads: only read what you genuinely need, once. Commit record_plan_requirement / record_plan_verification_target / record_* facts directly from the facts already in context (one tool call per message), then call finalize_plan exactly once.",
|
|
328
|
+
"</retry_instruction>",
|
|
329
|
+
].join("\n");
|
|
330
|
+
}
|
|
185
331
|
if (previousFailureCategory === "invalid-output" &&
|
|
186
332
|
task.structuredContractOutput &&
|
|
187
333
|
previousProtocolReason) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
334
|
+
// The frontend plan node's compile authority is the committed typed
|
|
335
|
+
// ledger, not a fenced JSON text artifact: its retry guidance must
|
|
336
|
+
// direct the model to re-commit corrected record_* facts and
|
|
337
|
+
// finalize_plan. The legacy full-contract JSON guidance below applies
|
|
338
|
+
// only to nodes whose authority is still a text contract artifact.
|
|
339
|
+
if (task.structuredContractOutput.schemaId ===
|
|
340
|
+
"frontend-implementation-contract-plan-patch-v1") {
|
|
341
|
+
const splitGuidance = /write-set-too-large|split the task/.test(previousProtocolReason ?? "")
|
|
342
|
+
? [
|
|
343
|
+
"",
|
|
344
|
+
"The writeSet is too large for one implement node. Do NOT delete implementation files to squeeze under the limit — that drops required work. Split the task via record_split_proposal (or narrow targets.files to a genuine subset) so each implement node stays bounded; the full file set must remain covered across the split.",
|
|
345
|
+
]
|
|
346
|
+
: [];
|
|
347
|
+
return [
|
|
348
|
+
basePrompt,
|
|
349
|
+
"",
|
|
350
|
+
"<retry_instruction>",
|
|
351
|
+
"Previous plan ledger facts failed canonical contract validation:",
|
|
352
|
+
previousProtocolReason,
|
|
353
|
+
"Fix the reported violations by re-committing corrected record_* facts and calling finalize_plan exactly once. The committed typed ledger is the only compile authority.",
|
|
354
|
+
"Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
|
|
355
|
+
...frontendPlanValidationRetryGuidance(previousProtocolReason),
|
|
356
|
+
...splitGuidance,
|
|
357
|
+
"</retry_instruction>",
|
|
358
|
+
].join("\n");
|
|
194
359
|
}
|
|
195
360
|
return [
|
|
196
361
|
basePrompt,
|
|
@@ -203,15 +368,30 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
203
368
|
"</retry_instruction>",
|
|
204
369
|
].join("\n");
|
|
205
370
|
}
|
|
371
|
+
if (previousFailureCategory === "invalid-output" &&
|
|
372
|
+
task.id === "frontend-scout-pi") {
|
|
373
|
+
return [
|
|
374
|
+
basePrompt,
|
|
375
|
+
"",
|
|
376
|
+
"<retry_instruction>",
|
|
377
|
+
"The previous Scout attempt did not commit a complete, runtime-evidenced target surface.",
|
|
378
|
+
"Search the repository only as needed to establish the real entrypoint, implementation ownership, and applicable test path. Commit record_target_surface with completeness=complete and unresolvedPaths=[] only after at least one named target has fresh runtime evidence. If ownership truly cannot be established, record completeness=blocked with each unresolved path; do not make Plan discover it.",
|
|
379
|
+
"</retry_instruction>",
|
|
380
|
+
].join("\n");
|
|
381
|
+
}
|
|
206
382
|
if (previousFailureCategory === "structured-output-truncated" &&
|
|
207
383
|
task.structuredContractOutput) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
384
|
+
if (task.structuredContractOutput.schemaId ===
|
|
385
|
+
"frontend-implementation-contract-plan-patch-v1") {
|
|
386
|
+
return [
|
|
387
|
+
basePrompt,
|
|
388
|
+
"",
|
|
389
|
+
"<retry_instruction>",
|
|
390
|
+
"Previous attempt was truncated by the provider (stopReason=length) before the plan facts were fully committed.",
|
|
391
|
+
"Re-commit the missing record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority. Do NOT re-read contract/scout outputs or source files — use the facts already in context. Commit record_* facts one tool call per message, then finalize_plan immediately.",
|
|
392
|
+
"Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
|
|
393
|
+
"</retry_instruction>",
|
|
394
|
+
].join("\n");
|
|
215
395
|
}
|
|
216
396
|
return [
|
|
217
397
|
basePrompt,
|
|
@@ -284,16 +464,32 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
284
464
|
"</retry_instruction>",
|
|
285
465
|
].join("\n");
|
|
286
466
|
}
|
|
467
|
+
if (previousFailureCategory === "writer-clean-timeout") {
|
|
468
|
+
return [
|
|
469
|
+
basePrompt,
|
|
470
|
+
"",
|
|
471
|
+
"<retry_instruction>",
|
|
472
|
+
"Previous attempt ended in a clean provider/transport failure with zero attributed workspace writes and zero write/edit tool calls.",
|
|
473
|
+
"Do not invent existing implementation progress. Continue from the original task only if the workspace is still unchanged.",
|
|
474
|
+
"</retry_instruction>",
|
|
475
|
+
].join("\n");
|
|
476
|
+
}
|
|
287
477
|
if (previousFailureCategory !== "output-too-large") {
|
|
288
478
|
return basePrompt;
|
|
289
479
|
}
|
|
290
480
|
if (task.outputMode === "structured-required") {
|
|
291
481
|
if (task.structuredContractOutput) {
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
482
|
+
if (task.structuredContractOutput.schemaId ===
|
|
483
|
+
"frontend-implementation-contract-plan-patch-v1") {
|
|
484
|
+
return [
|
|
485
|
+
basePrompt,
|
|
486
|
+
"",
|
|
487
|
+
"<retry_instruction>",
|
|
488
|
+
"Previous attempt exceeded the structured output size limit.",
|
|
489
|
+
"Re-commit corrected record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority.",
|
|
490
|
+
"Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
|
|
491
|
+
"</retry_instruction>",
|
|
492
|
+
].join("\n");
|
|
297
493
|
}
|
|
298
494
|
return [
|
|
299
495
|
basePrompt,
|
|
@@ -326,6 +522,35 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
326
522
|
"</retry_instruction>",
|
|
327
523
|
].join("\n");
|
|
328
524
|
}
|
|
525
|
+
const PROMPT_RESTART_CANDIDATE_MAX_BYTES = 64 * 1024;
|
|
526
|
+
function promptRestartCandidateForAttempt(input) {
|
|
527
|
+
if (input.attemptPrompt === input.baseRuntimePrompt) {
|
|
528
|
+
return input.taskPrompt;
|
|
529
|
+
}
|
|
530
|
+
const appendedPrefix = `${input.baseRuntimePrompt}\n\n`;
|
|
531
|
+
if (input.attemptPrompt.startsWith(appendedPrefix)) {
|
|
532
|
+
return [
|
|
533
|
+
input.taskPrompt,
|
|
534
|
+
input.attemptPrompt.slice(appendedPrefix.length),
|
|
535
|
+
]
|
|
536
|
+
.filter(Boolean)
|
|
537
|
+
.join("\n\n");
|
|
538
|
+
}
|
|
539
|
+
return input.attemptPrompt;
|
|
540
|
+
}
|
|
541
|
+
async function persistFailedPromptRestartCandidate(input) {
|
|
542
|
+
const redacted = redactSecrets(input.prompt);
|
|
543
|
+
const preview = truncateUtf8Preview(redacted, PROMPT_RESTART_CANDIDATE_MAX_BYTES);
|
|
544
|
+
await writeDagNodeJsonArtifact(input.runDir, input.nodeId, `prompt-candidate-attempt-${input.attempt}.json`, {
|
|
545
|
+
schemaVersion: 1,
|
|
546
|
+
attempt: input.attempt,
|
|
547
|
+
failureCategory: input.failureCategory ?? "unknown",
|
|
548
|
+
prompt: preview,
|
|
549
|
+
truncated: preview !== redacted,
|
|
550
|
+
maxBytes: PROMPT_RESTART_CANDIDATE_MAX_BYTES,
|
|
551
|
+
sha256: createHash("sha256").update(redacted).digest("hex"),
|
|
552
|
+
});
|
|
553
|
+
}
|
|
329
554
|
export async function buildNodePromptWithResolvedSkillInstructions(spec, task, upstream, cwd, options) {
|
|
330
555
|
const policy = resolveContextPolicy(spec);
|
|
331
556
|
const skillNames = policy.resolveSkills(spec, task);
|
|
@@ -715,7 +940,7 @@ export async function executeDagNode(input) {
|
|
|
715
940
|
}
|
|
716
941
|
node.resolvedSkills = resolvedSkills;
|
|
717
942
|
await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
|
|
718
|
-
|
|
943
|
+
let model = resolveModelForTask(task, spec.executorModels);
|
|
719
944
|
let thinking;
|
|
720
945
|
if (task.executor === "pi") {
|
|
721
946
|
try {
|
|
@@ -726,16 +951,25 @@ export async function executeDagNode(input) {
|
|
|
726
951
|
// Invalid or missing harness preserves the previous no-override behavior.
|
|
727
952
|
}
|
|
728
953
|
}
|
|
729
|
-
const
|
|
954
|
+
const specRetryPolicy = task.retryPolicy &&
|
|
730
955
|
(isSafeReadOnlyPiRetryCandidate(task) ||
|
|
731
956
|
isWriterEmptyDiffRetryCandidate(task) ||
|
|
732
957
|
isWriterTransportRetryCandidate(task))
|
|
733
958
|
? task.retryPolicy
|
|
734
959
|
: undefined;
|
|
960
|
+
const implicitVerifyRetryPolicy = specRetryPolicy === undefined &&
|
|
961
|
+
isCanonicalFinalVerifyShellRetryCandidate(task) &&
|
|
962
|
+
isTargetTemplateTransientRetryNode(task)
|
|
963
|
+
? VERIFY_SHELL_RETRY_POLICY
|
|
964
|
+
: undefined;
|
|
965
|
+
const retryPolicy = specRetryPolicy ?? implicitVerifyRetryPolicy;
|
|
735
966
|
const maxAttempts = retryPolicy?.maxAttempts ?? 1;
|
|
736
967
|
const attempts = [];
|
|
737
968
|
let totalAttemptWallDurationMs = 0;
|
|
738
969
|
let totalBackoffMs = 0;
|
|
970
|
+
const frontendPlanLadderEnabled = isFrontendPlanLadderTask(task);
|
|
971
|
+
let frontendPlanRetryStep = "normal";
|
|
972
|
+
const frontendPriorFingerprints = [];
|
|
739
973
|
const livenessPolicy = resolveLivenessPolicy(spec.defaults?.livenessPolicy, task.livenessPolicy);
|
|
740
974
|
/**
|
|
741
975
|
* Attempt-fenced, throttled activity sink. Late events from a previous
|
|
@@ -773,10 +1007,25 @@ export async function executeDagNode(input) {
|
|
|
773
1007
|
let terminalResult;
|
|
774
1008
|
let previousFailureCategory;
|
|
775
1009
|
let previousProtocolReason;
|
|
1010
|
+
let pendingStructuredArtifact;
|
|
1011
|
+
let lastRepairAllowlist = [];
|
|
776
1012
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
777
1013
|
const attemptStartedAt = new Date().toISOString();
|
|
778
1014
|
node.currentAttempt = attemptNumber;
|
|
779
1015
|
node.livenessStatus = "active";
|
|
1016
|
+
const beforeCommittedDigest = frontendPlanLadderEnabled
|
|
1017
|
+
? (await readFrontendPlanCommittedSnapshot(runDir, nodeId)).digest
|
|
1018
|
+
: undefined;
|
|
1019
|
+
if (frontendPlanLadderEnabled &&
|
|
1020
|
+
frontendPlanRetryStep === "backup-model") {
|
|
1021
|
+
model = resolveModelForTask(task, spec.executorModels);
|
|
1022
|
+
}
|
|
1023
|
+
// Capture the session-events.jsonl length BEFORE the executor appends this
|
|
1024
|
+
// attempt's events, so the post-attempt repair tool audit can be scoped to
|
|
1025
|
+
// exactly this attempt's segment (B1: earlier attempts legitimately use
|
|
1026
|
+
// ls/find and must not be attributed to the repair attempt).
|
|
1027
|
+
const attemptSessionEventsOffset = await sessionEventsByteLength(runDir, nodeId);
|
|
1028
|
+
let promptRestartCandidate = task.subtask_prompt;
|
|
780
1029
|
let result;
|
|
781
1030
|
try {
|
|
782
1031
|
validateRepairArtifactGateBeforeShell({
|
|
@@ -799,12 +1048,65 @@ export async function executeDagNode(input) {
|
|
|
799
1048
|
(acc[i.path] ??= []).push(i.detail);
|
|
800
1049
|
return acc;
|
|
801
1050
|
}, {});
|
|
1051
|
+
let attemptPrompt = buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep);
|
|
1052
|
+
if (attemptNumber > 1 &&
|
|
1053
|
+
// The plan node (frontend-plan-pi) is a typed-facts ladder task:
|
|
1054
|
+
// its retry is driven by the §5.1 ladder (compact-terminal-first
|
|
1055
|
+
// / backup-model) and its compile authority is the committed
|
|
1056
|
+
// ledger — never the legacy frozen fenced-JSON repair. The
|
|
1057
|
+
// frozen repair path below applies only to non-ladder
|
|
1058
|
+
// structured nodes whose output authority is a text artifact.
|
|
1059
|
+
!frontendPlanLadderEnabled &&
|
|
1060
|
+
isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
|
|
1061
|
+
isStructuredRepairableFailureCategory(previousFailureCategory) &&
|
|
1062
|
+
(await hasNonEmptyStructuredCandidate({
|
|
1063
|
+
runDir,
|
|
1064
|
+
nodeId,
|
|
1065
|
+
beforeAttempt: attemptNumber,
|
|
1066
|
+
}))) {
|
|
1067
|
+
const originalCanonical = task.structuredContractOutput?.schemaId ===
|
|
1068
|
+
"frontend-implementation-contract-revision-patch-v1"
|
|
1069
|
+
? (await readFrontendCanonicalCandidate(runDir, "frontend-plan-pi"))
|
|
1070
|
+
: undefined;
|
|
1071
|
+
const frozen = await freezeStructuredOutputRepairContext({
|
|
1072
|
+
runDir,
|
|
1073
|
+
runId: state.runId,
|
|
1074
|
+
taskId: spec.sourceBinding?.taskId ?? task.id,
|
|
1075
|
+
nodeId,
|
|
1076
|
+
nodeAttempt: attemptNumber,
|
|
1077
|
+
targetSchemaId: task.structuredContractOutput.schemaId,
|
|
1078
|
+
skeleton: task.structuredContractOutput?.skeleton,
|
|
1079
|
+
originalCanonical,
|
|
1080
|
+
latestStopReason: previousFailureCategory === "structured-output-truncated"
|
|
1081
|
+
? "length"
|
|
1082
|
+
: previousFailureCategory,
|
|
1083
|
+
});
|
|
1084
|
+
lastRepairAllowlist = allowedRepairReadPaths({
|
|
1085
|
+
runDir,
|
|
1086
|
+
context: frozen.context,
|
|
1087
|
+
contextPath: frozen.relativePath,
|
|
1088
|
+
});
|
|
1089
|
+
attemptPrompt = await buildStructuredOutputRepairPrompt({
|
|
1090
|
+
runDir,
|
|
1091
|
+
context: frozen.context,
|
|
1092
|
+
contextPath: frozen.relativePath,
|
|
1093
|
+
contextSha256: frozen.sha256,
|
|
1094
|
+
maxAttempts,
|
|
1095
|
+
schemaGuidance: frontendStructuredArtifactRetryGuidance(task.structuredContractOutput.schemaId),
|
|
1096
|
+
previousReason: previousProtocolReason,
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
promptRestartCandidate = promptRestartCandidateForAttempt({
|
|
1100
|
+
taskPrompt: task.subtask_prompt,
|
|
1101
|
+
baseRuntimePrompt: prompt,
|
|
1102
|
+
attemptPrompt,
|
|
1103
|
+
});
|
|
802
1104
|
result = await executeNode({
|
|
803
1105
|
task,
|
|
804
1106
|
cwd,
|
|
805
1107
|
model,
|
|
806
1108
|
...(thinking ? { thinking } : {}),
|
|
807
|
-
prompt:
|
|
1109
|
+
prompt: attemptPrompt,
|
|
808
1110
|
attempt: attemptNumber,
|
|
809
1111
|
reportActivity,
|
|
810
1112
|
timeoutMs: livenessPolicy.absoluteMaxWallClockMs,
|
|
@@ -820,6 +1122,35 @@ export async function executeDagNode(input) {
|
|
|
820
1122
|
durationMs: 0,
|
|
821
1123
|
};
|
|
822
1124
|
}
|
|
1125
|
+
if (isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId)) {
|
|
1126
|
+
const rawText = canonicalNodeOutput(result);
|
|
1127
|
+
if (rawText.trim().length > 0) {
|
|
1128
|
+
await persistStructuredAttemptRaw({
|
|
1129
|
+
runDir,
|
|
1130
|
+
nodeId,
|
|
1131
|
+
attempt: attemptNumber,
|
|
1132
|
+
text: rawText,
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
if (attemptNumber > 1 && lastRepairAllowlist.length > 0) {
|
|
1136
|
+
const audit = await auditRepairAttemptToolUse({
|
|
1137
|
+
runDir,
|
|
1138
|
+
nodeId,
|
|
1139
|
+
allowedPaths: lastRepairAllowlist,
|
|
1140
|
+
fromByteOffset: attemptSessionEventsOffset,
|
|
1141
|
+
});
|
|
1142
|
+
if (!audit.ok) {
|
|
1143
|
+
result = {
|
|
1144
|
+
...result,
|
|
1145
|
+
ok: false,
|
|
1146
|
+
failureCategory: GOVERNANCE_BLOCKED_CATEGORY,
|
|
1147
|
+
stderr: [result.stderr, audit.reason]
|
|
1148
|
+
.filter(Boolean)
|
|
1149
|
+
.join("\n"),
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
823
1154
|
// R0: executor ok=true still fails closed when outputProtocol is violated.
|
|
824
1155
|
// Valid semantic results (e.g. VERDICT: request-revision) pass validation.
|
|
825
1156
|
if (result.ok && task.outputProtocol) {
|
|
@@ -856,16 +1187,17 @@ export async function executeDagNode(input) {
|
|
|
856
1187
|
structuredContractOutput: task.structuredContractOutput,
|
|
857
1188
|
});
|
|
858
1189
|
if (!contractCheck.ok) {
|
|
859
|
-
// A provider stopReason=length means the response was cut before the
|
|
860
|
-
// JSON contract could complete; separate it from an ordinary bad
|
|
861
|
-
// contract so the retry switches to a JSON-only output strategy.
|
|
862
1190
|
const attemptStopReason = result.stopReason;
|
|
1191
|
+
const failureCategory = contractCheck.classification === "governance-blocked"
|
|
1192
|
+
? GOVERNANCE_BLOCKED_CATEGORY
|
|
1193
|
+
: attemptStopReason === "length" ||
|
|
1194
|
+
contractCheck.classification === "truncated"
|
|
1195
|
+
? "structured-output-truncated"
|
|
1196
|
+
: "invalid-output";
|
|
863
1197
|
result = {
|
|
864
1198
|
...result,
|
|
865
1199
|
ok: false,
|
|
866
|
-
failureCategory
|
|
867
|
-
? "structured-output-truncated"
|
|
868
|
-
: "invalid-output",
|
|
1200
|
+
failureCategory,
|
|
869
1201
|
stderr: [result.stderr, contractCheck.reason]
|
|
870
1202
|
.filter(Boolean)
|
|
871
1203
|
.join("\n"),
|
|
@@ -873,14 +1205,6 @@ export async function executeDagNode(input) {
|
|
|
873
1205
|
previousProtocolReason = contractCheck.reason;
|
|
874
1206
|
}
|
|
875
1207
|
else {
|
|
876
|
-
if (contractCheck.structuredArtifact) {
|
|
877
|
-
node.structuredArtifactPath = contractCheck.structuredArtifact.path;
|
|
878
|
-
node.structuredArtifactSha256 = contractCheck.structuredArtifact.sha256;
|
|
879
|
-
node.structuredArtifactSchemaId = contractCheck.structuredArtifact.schemaId;
|
|
880
|
-
}
|
|
881
|
-
if (contractCheck.frontendPlanOutput) {
|
|
882
|
-
node.frontendPlanOutput = contractCheck.frontendPlanOutput;
|
|
883
|
-
}
|
|
884
1208
|
if (contractCheck.normalizedText) {
|
|
885
1209
|
result = {
|
|
886
1210
|
...result,
|
|
@@ -888,6 +1212,9 @@ export async function executeDagNode(input) {
|
|
|
888
1212
|
stdout: contractCheck.normalizedText,
|
|
889
1213
|
};
|
|
890
1214
|
}
|
|
1215
|
+
if (contractCheck.artifact) {
|
|
1216
|
+
pendingStructuredArtifact = contractCheck.artifact;
|
|
1217
|
+
}
|
|
891
1218
|
previousProtocolReason = undefined;
|
|
892
1219
|
}
|
|
893
1220
|
}
|
|
@@ -900,6 +1227,16 @@ export async function executeDagNode(input) {
|
|
|
900
1227
|
const attemptFinishedAt = new Date().toISOString();
|
|
901
1228
|
const attemptWallDurationMs = durationBetween(attemptStartedAt, attemptFinishedAt);
|
|
902
1229
|
totalAttemptWallDurationMs += attemptWallDurationMs;
|
|
1230
|
+
const rawFailureCategory = result.rawFailureCategory ?? result.failureCategory;
|
|
1231
|
+
if (task.executor === "pi" && !result.ok) {
|
|
1232
|
+
await persistFailedPromptRestartCandidate({
|
|
1233
|
+
runDir,
|
|
1234
|
+
nodeId,
|
|
1235
|
+
attempt: attemptNumber,
|
|
1236
|
+
failureCategory: result.failureCategory,
|
|
1237
|
+
prompt: promptRestartCandidate,
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
903
1240
|
const attemptRecord = {
|
|
904
1241
|
attempt: attemptNumber,
|
|
905
1242
|
startedAt: attemptStartedAt,
|
|
@@ -911,6 +1248,7 @@ export async function executeDagNode(input) {
|
|
|
911
1248
|
stderr: result.stderr,
|
|
912
1249
|
assistantText: result.assistantText,
|
|
913
1250
|
failureCategory: result.failureCategory,
|
|
1251
|
+
...(rawFailureCategory ? { rawFailureCategory } : {}),
|
|
914
1252
|
backend: result.backend,
|
|
915
1253
|
sdkAttempted: result.sdkAttempted,
|
|
916
1254
|
tokensUsed: result.tokensUsed,
|
|
@@ -925,6 +1263,60 @@ export async function executeDagNode(input) {
|
|
|
925
1263
|
await writeDagNodeJsonArtifact(runDir, nodeId, `attempt-${attemptNumber}.json`, attemptRecord);
|
|
926
1264
|
node.attempts = attempts;
|
|
927
1265
|
}
|
|
1266
|
+
// Frontend plan retry ladder: project the 7-value protocol failure
|
|
1267
|
+
// reason, detect new committed facts, and escalate instead of re-running.
|
|
1268
|
+
if (frontendPlanLadderEnabled && !result.ok) {
|
|
1269
|
+
const afterSnapshot = await readFrontendPlanCommittedSnapshot(runDir, nodeId);
|
|
1270
|
+
const hasNewCommittedFact = beforeCommittedDigest !== undefined &&
|
|
1271
|
+
afterSnapshot.digest !== beforeCommittedDigest;
|
|
1272
|
+
const protocolReason = projectFrontendNodeProtocolFailureReason({
|
|
1273
|
+
failureCategory: result.failureCategory,
|
|
1274
|
+
stopReason: result.stopReason,
|
|
1275
|
+
assistantText: result.assistantText ?? result.stdout,
|
|
1276
|
+
hasTerminalFact: afterSnapshot.hasTerminalFact,
|
|
1277
|
+
});
|
|
1278
|
+
const fingerprint = computeNormalizedFailureFingerprint({
|
|
1279
|
+
failureOwner: "plan",
|
|
1280
|
+
protocolFailureReason: protocolReason ?? "",
|
|
1281
|
+
});
|
|
1282
|
+
const backupRoute = resolveFrontendPlanBackupRoute(protocolReason);
|
|
1283
|
+
const nextStep = resolveFrontendPlanRetryStep({
|
|
1284
|
+
currentStep: frontendPlanRetryStep,
|
|
1285
|
+
protocolFailureReason: protocolReason,
|
|
1286
|
+
hasNewCommittedFact,
|
|
1287
|
+
fingerprint,
|
|
1288
|
+
priorFingerprints: frontendPriorFingerprints,
|
|
1289
|
+
backupRouteAvailable: backupRoute.ok,
|
|
1290
|
+
});
|
|
1291
|
+
frontendPriorFingerprints.push(fingerprint);
|
|
1292
|
+
frontendPlanRetryStep = nextStep;
|
|
1293
|
+
if (nextStep === "unsupported-provider-capability") {
|
|
1294
|
+
result = {
|
|
1295
|
+
...result,
|
|
1296
|
+
ok: false,
|
|
1297
|
+
failureCategory: "unsupported-provider-capability",
|
|
1298
|
+
stderr: [
|
|
1299
|
+
result.stderr,
|
|
1300
|
+
"frontend plan retry ladder: no compatible provider capability route for the protocol failure reason",
|
|
1301
|
+
]
|
|
1302
|
+
.filter(Boolean)
|
|
1303
|
+
.join("\n"),
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
else if (nextStep === "non-converging") {
|
|
1307
|
+
result = {
|
|
1308
|
+
...result,
|
|
1309
|
+
ok: false,
|
|
1310
|
+
failureCategory: "non-converging",
|
|
1311
|
+
stderr: [
|
|
1312
|
+
result.stderr,
|
|
1313
|
+
"frontend plan retry ladder: repeated failure fingerprint with no new committed fact",
|
|
1314
|
+
]
|
|
1315
|
+
.filter(Boolean)
|
|
1316
|
+
.join("\n"),
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
928
1320
|
// Reflect the latest attempt on the node so progress is observable,
|
|
929
1321
|
// but keep node.status RUNNING while retry is still possible.
|
|
930
1322
|
node.durationMs = durationBetween(node.startedAt, attemptFinishedAt);
|
|
@@ -958,13 +1350,26 @@ export async function executeDagNode(input) {
|
|
|
958
1350
|
break;
|
|
959
1351
|
const canRetry = retryPolicy !== undefined && attemptNumber < maxAttempts;
|
|
960
1352
|
const retryable = retryPolicy !== undefined &&
|
|
1353
|
+
result.failureCategory !== GOVERNANCE_BLOCKED_CATEGORY &&
|
|
961
1354
|
(isRetryablePiFailureCategory(result.failureCategory, {
|
|
962
1355
|
retryCategories: retryPolicy.retryCategories,
|
|
963
1356
|
}) ||
|
|
964
1357
|
(result.failureCategory === "protocol-invalid" &&
|
|
965
1358
|
task.outputProtocol?.retryOnInvalid === true));
|
|
966
|
-
if (!canRetry || !retryable)
|
|
1359
|
+
if (!canRetry || !retryable) {
|
|
1360
|
+
if (!canRetry &&
|
|
1361
|
+
isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
|
|
1362
|
+
isStructuredRepairableFailureCategory(result.failureCategory)) {
|
|
1363
|
+
result = {
|
|
1364
|
+
...result,
|
|
1365
|
+
failureCategory: STRUCTURED_REPAIR_EXHAUSTED_CATEGORY,
|
|
1366
|
+
};
|
|
1367
|
+
terminalResult = result;
|
|
1368
|
+
previousFailureCategory = result.failureCategory;
|
|
1369
|
+
node.failureCategory = result.failureCategory;
|
|
1370
|
+
}
|
|
967
1371
|
break;
|
|
1372
|
+
}
|
|
968
1373
|
const delayMs = computeBackoffDelayMs(attemptNumber + 1, retryPolicy);
|
|
969
1374
|
if (delayMs > 0) {
|
|
970
1375
|
const backoffStartedAt = new Date().toISOString();
|
|
@@ -1113,6 +1518,11 @@ export async function executeDagNode(input) {
|
|
|
1113
1518
|
node.structuredArtifactSchemaId = "backend-test-result-v1";
|
|
1114
1519
|
}
|
|
1115
1520
|
}
|
|
1521
|
+
else if (pendingStructuredArtifact) {
|
|
1522
|
+
node.structuredArtifactPath = pendingStructuredArtifact.path;
|
|
1523
|
+
node.structuredArtifactSha256 = pendingStructuredArtifact.sha256;
|
|
1524
|
+
node.structuredArtifactSchemaId = pendingStructuredArtifact.schemaId;
|
|
1525
|
+
}
|
|
1116
1526
|
}
|
|
1117
1527
|
const finishedAt = new Date().toISOString();
|
|
1118
1528
|
node.finishedAt = finishedAt;
|