@tea-agent/loop-agent 0.39.0-next.9 → 0.39.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/AGENTS.md +4 -1
- package/CHANGELOG.md +269 -69
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +16 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +16 -5
- package/dist/cli/program.js +16 -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/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 +428 -27
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +124 -18
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +555 -34
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-failure-category.js +138 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/operator/capabilities.js +181 -27
- 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 +28 -0
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +3 -0
- package/dist/task/contract/diff.js +26 -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 +20 -1
- package/dist/task/contract/schema.js +8 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +474 -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 +262 -1
- package/dist/task/source-prepare/semantic-intake.js +19 -3
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -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 +165 -83
- package/dist/worker/console/chat/repo-walk.js +10 -1
- package/dist/worker/console/chat/routes.js +480 -55
- package/dist/worker/console/chat/semantic-activity.js +20 -8
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/chat/turn-process.js +10 -10
- 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/inspect-split.js +18 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +1 -1
- package/dist/worker/console/operator-actions.js +320 -4
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/routes.js +46 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +111 -7
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-BLXFe4qI.js → abnfDiagram-N423BO3Z-CTx1TrNa.js} +1 -1
- package/dist/worker/console/static/assets/{arc-7iOJeqho.js → arc-B4i9cVxX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CYoyj5K7.js → architectureDiagram-T3A2C74G-BAXE8_Rl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-fK01JNlR.js → blockDiagram-VBNYF7ZC-C5Xh59at.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-bzbw5D_Z.js → c4Diagram-5PPSVZJV-B_asOzAu.js} +1 -1
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-Cf6hJFFG.js → chunk-2GRJ4B5K-wRO_iRNc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DSFXR-et.js → chunk-2Q5K7J3B-ByOaSh_G.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BagJechl.js → chunk-5RXB4S5H-DjLzudan.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-E7QNMFaW.js → chunk-5VM5RSS4-JCNGkXYT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BG_eOBrC.js → chunk-6Q2QTUOP-BhrPXXMZ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Bo42dqol.js → chunk-GF5L2VYU-TJ5JuEBT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-dibM-87v.js → chunk-JWPE2WC7-VVthn_mD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Bytrp3Oe.js → chunk-KBJHAD2P-D60-Mny4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DBABILrr.js → chunk-RYQCIY6F-zXv7aVye.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DCUk5Pf9.js → chunk-XXDRQBXY-By9VQoi1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BlGE9v6E.js → cose-bilkent-JH36ORCC-C4zQLe-M.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DGl5ewph.js → cynefin-VYW2F7L2-wh5N2ReN.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DsJ_SVH_.js → cynefinDiagram-MW4NZA55-Dlu5wIqs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BIhjFdzv.js → dagre-VZM6K2ZE-CrDgthc6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dmy1ctTb.js → diagram-7IWD3JNH-zN1clg27.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DYCtWUF3.js → diagram-B4RE2ZJO-BN-o7J7-.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BMZFHVd2.js → diagram-LBJQPF4R-C8SuPt0d.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-b_U8gfaO.js → diagram-Q27KOJAE-BoizuHbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Dzy_bn-I.js → diagram-UB23O5K3-BzUzXixG.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DJCiMNEI.js → ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-28lD016B.js → erDiagram-JOGREHBK-GALUzRbs.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DGyHJmIw.js → flowDiagram-UKHOOZJN-CZJN65Sb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CzdwUjrO.js → ganttDiagram-PKOTCBZU-JLB9OESl.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-D9bGwuo8.js → gitGraphDiagram-DS77QQ5N-CcvCr4GD.js} +1 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +406 -0
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-LJgJUQFC.js → infoDiagram-6WML65LV-HG5z7aVg.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-98LaB8zb.js → ishikawaDiagram-WSZJBQD7-BHEu9chR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-D-ISzwlQ.js → journeyDiagram-NVQOT4AX-Cp_Up8-a.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-gfYOI-EL.js → kanban-definition-27J2QSJJ-DvVeY-em.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CyntZgKV.js → linear-aGmcjgTn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BBOR7dg0.js → mermaid.core-Biyo-tqA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CQAgwPMk.js → mindmap-definition-FAOFIHXS-DyxY2pIb.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DkMiZFuV.js → pegDiagram-VL7TDLO6-CkdGuc50.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DLNXa_0J.js → pieDiagram-7S7Q4E2Y-NNQI9xVR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Dc7bdzjm.js → quadrantDiagram-CIZ2JOQS-PAJkuz_G.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BxMB0QqW.js → railroadDiagram-AXF67PYL-ZwrQod0O.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-FjumoKn4.js → requirementDiagram-LRYGKXZP-D3ZfrzM2.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CkhKc_E3.js → sankeyDiagram-W5VNT64P-DDGFmWk5.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DoBWqTTH.js → sequenceDiagram-SI44F4Z6-DO6mmXTA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CQeh_YeE.js → sizeCapture-X5ZJPWSS-DMbCQiJt.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-LuHmFW-I.js → stateDiagram-OKZ733FA-Di_YUEfC.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CHwbDctY.js → swimlanes-SLNWSIFB-Bn2PphNJ.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CoMngk0X.js → timeline-definition-Z64GVDOM-DPh8pRsp.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CURYbtbO.js → vennDiagram-T6HMQDX7-GMfl26pW.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BdqSECjU.js → wardleyDiagram-T6FBY63Y-DTlmVrmF.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DbMUYuAg.js → xychartDiagram-ELKLHX3M-YGBjKAS5.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +40 -1
- package/dist/worker/console/static-src/app/useOperatorActions.js +6 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +38 -2
- package/dist/worker/console/static-src/app/useRunProgress.js +46 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +73 -1
- 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 +74 -26
- 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/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +54 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +237 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +50 -22
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +26 -1
- 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/loop-agent/loop-agent-client.js +7 -3
- 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/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +67 -1
- 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/format.js +60 -0
- package/dist/worker/observe/static/index.html +5 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.js +4 -0
- 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/state.js +13 -2
- package/dist/worker/observe/static/styles.css +85 -0
- package/dist/worker/observe/static/views/dag-inspector.js +912 -8
- package/dist/worker/observe/static/views/dag.js +8 -1
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +55 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +856 -54
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +343 -33
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +454 -36
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +110 -61
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +3 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +25 -9
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-implementation-contract.js +465 -6
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +293 -84
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-review-context.js +92 -10
- package/dist/workflows/dag/frontend-risk.js +15 -2
- 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-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +1019 -209
- package/dist/workflows/dag/node-execution.js +228 -15
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +224 -27
- package/dist/workflows/dag/rerun-run.js +61 -6
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +109 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +185 -12
- package/dist/workflows/dag/validate.js +64 -20
- package/docs/README.md +1 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +6 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +2 -2
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-implementation-contract.schema.json +681 -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/harness.json +6 -4
- package/package.json +12 -4
- 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/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -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-implementation/references/node-contracts.md +2 -2
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/loop-agent/references/hybrid-dag.md +7 -0
- 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-BBOTJHvD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Cm8Yf8tD.js +0 -1
- package/dist/worker/console/static/assets/index-B-qWGThd.js +0 -325
- package/dist/worker/console/static/assets/index-C-xRgY_0.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DZihfdKc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CKnXCjHX.js +0 -8
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseJsonReviewVerdict } from "./output-protocol.js";
|
|
5
|
+
export const MAX_RERUN_FEEDBACK_CHARS = 12_000;
|
|
6
|
+
const RERUN_FEEDBACK_TRUNCATION_MARKER = "\n\n...[上一轮反馈已按长度上限截断]...\n\n";
|
|
7
|
+
const MAX_EVIDENCE_REFS = 8;
|
|
8
|
+
function sha256(value) {
|
|
9
|
+
return createHash("sha256").update(value).digest("hex");
|
|
10
|
+
}
|
|
11
|
+
function withDigest(payload) {
|
|
12
|
+
return {
|
|
13
|
+
...payload,
|
|
14
|
+
digest: sha256(JSON.stringify(payload)),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function sourceBindingHash(spec) {
|
|
18
|
+
return spec.sourceBinding
|
|
19
|
+
? sha256(JSON.stringify(spec.sourceBinding))
|
|
20
|
+
: undefined;
|
|
21
|
+
}
|
|
22
|
+
function canonicalNodeOutput(node) {
|
|
23
|
+
const assistantText = node?.assistantText?.trim();
|
|
24
|
+
return assistantText || node?.stdout?.trim() || "";
|
|
25
|
+
}
|
|
26
|
+
function requestRevision(text) {
|
|
27
|
+
const jsonVerdict = parseJsonReviewVerdict(text);
|
|
28
|
+
if (jsonVerdict.ok)
|
|
29
|
+
return jsonVerdict.verdict === "request-revision";
|
|
30
|
+
return text
|
|
31
|
+
.split(/\r?\n/)
|
|
32
|
+
.some((line) => {
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
const emphasized = trimmed.match(/^\*{1,3}\s*(VERDICT:[^*]+?)\s*\*{1,3}$/);
|
|
35
|
+
return (emphasized?.[1] ?? trimmed).trim() === "VERDICT: request-revision";
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function pathInside(root, candidate) {
|
|
39
|
+
const relative = path.relative(root, candidate);
|
|
40
|
+
return (relative === "" ||
|
|
41
|
+
(!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative)));
|
|
42
|
+
}
|
|
43
|
+
function safeRepositoryRelativePath(repoRoot, runDir, rawPath, fallback) {
|
|
44
|
+
let evidencePath = path.resolve(runDir, fallback);
|
|
45
|
+
if (rawPath) {
|
|
46
|
+
const absolute = path.isAbsolute(rawPath)
|
|
47
|
+
? path.normalize(rawPath)
|
|
48
|
+
: path.resolve(runDir, rawPath);
|
|
49
|
+
if (pathInside(runDir, absolute)) {
|
|
50
|
+
evidencePath = absolute;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (pathInside(repoRoot, evidencePath)) {
|
|
54
|
+
return path.relative(repoRoot, evidencePath).replaceAll(path.sep, "/");
|
|
55
|
+
}
|
|
56
|
+
return path.relative(runDir, evidencePath).replaceAll(path.sep, "/");
|
|
57
|
+
}
|
|
58
|
+
function nodeRecordCandidates(runDir, pass, ref, nodeId) {
|
|
59
|
+
const values = [
|
|
60
|
+
ref?.preservedNodeRecordPath,
|
|
61
|
+
pass ? path.join("convergence", `pass-${pass.pass}`, `${nodeId}.json`) : undefined,
|
|
62
|
+
`${nodeId}.json`,
|
|
63
|
+
].filter((value) => Boolean(value));
|
|
64
|
+
const resolved = values
|
|
65
|
+
.map((value) => (path.isAbsolute(value) ? path.normalize(value) : path.resolve(runDir, value)))
|
|
66
|
+
.filter((value) => pathInside(runDir, value));
|
|
67
|
+
return [...new Set(resolved)];
|
|
68
|
+
}
|
|
69
|
+
async function readNodeRecord(runDir, pass, ref, nodeId, fallback) {
|
|
70
|
+
for (const candidate of nodeRecordCandidates(runDir, pass, ref, nodeId)) {
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(await readFile(candidate, "utf8"));
|
|
73
|
+
if (parsed && typeof parsed === "object")
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Try the next canonical/preserved candidate, then the state projection.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return fallback;
|
|
81
|
+
}
|
|
82
|
+
function feedbackSourceCandidates(pass, state) {
|
|
83
|
+
const preferred = [];
|
|
84
|
+
if (pass?.reason.includes("verify"))
|
|
85
|
+
preferred.push("verify-pi");
|
|
86
|
+
if (pass?.reviewVerdict === "request-revision") {
|
|
87
|
+
preferred.push("review-verdict-recovery-pi", "review-pi");
|
|
88
|
+
}
|
|
89
|
+
preferred.push("verify-pi", "review-verdict-recovery-pi", "review-pi", "process-supervisor-pi");
|
|
90
|
+
preferred.push(...(pass?.artifactRefs.map((ref) => ref.nodeId) ?? []));
|
|
91
|
+
preferred.push(...Object.keys(state.nodes));
|
|
92
|
+
return [...new Set(preferred)];
|
|
93
|
+
}
|
|
94
|
+
function scrubAndBoundFeedbackText(value, runDir, cwd) {
|
|
95
|
+
let text = value
|
|
96
|
+
.replaceAll(runDir, "<parent-run-dir>")
|
|
97
|
+
.replaceAll(runDir.replaceAll("\\", "/"), "<parent-run-dir>")
|
|
98
|
+
.replaceAll(cwd, "<repo-root>")
|
|
99
|
+
.replaceAll(cwd.replaceAll("\\", "/"), "<repo-root>")
|
|
100
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "")
|
|
101
|
+
.trim();
|
|
102
|
+
if (!text) {
|
|
103
|
+
return "上一轮验证节点返回 request-revision,但详细反馈正文不可用;请读取证据引用并重新核对当前代码。";
|
|
104
|
+
}
|
|
105
|
+
if (text.length <= MAX_RERUN_FEEDBACK_CHARS)
|
|
106
|
+
return text;
|
|
107
|
+
const remaining = MAX_RERUN_FEEDBACK_CHARS - RERUN_FEEDBACK_TRUNCATION_MARKER.length;
|
|
108
|
+
const prefixLength = Math.floor(remaining * 0.7);
|
|
109
|
+
const suffixLength = remaining - prefixLength;
|
|
110
|
+
text = `${text.slice(0, prefixLength)}${RERUN_FEEDBACK_TRUNCATION_MARKER}${text.slice(-suffixLength)}`;
|
|
111
|
+
return text;
|
|
112
|
+
}
|
|
113
|
+
function evidenceRefs(repoRoot, runDir, pass, sourceNodeId) {
|
|
114
|
+
const refs = pass?.artifactRefs ?? [];
|
|
115
|
+
const relevant = refs.filter((ref) => ref.nodeId === sourceNodeId ||
|
|
116
|
+
/(?:verify|review|supervisor|repair|gate)/i.test(ref.nodeId));
|
|
117
|
+
const selected = relevant.length > 0 ? relevant : refs;
|
|
118
|
+
const result = selected.slice(0, MAX_EVIDENCE_REFS).map((ref) => ({
|
|
119
|
+
nodeId: ref.nodeId,
|
|
120
|
+
preservedNodeRecordPath: safeRepositoryRelativePath(repoRoot, runDir, ref.preservedNodeRecordPath, path.join("convergence", `pass-${pass?.pass ?? 1}`, `${ref.nodeId}.json`)),
|
|
121
|
+
}));
|
|
122
|
+
if (result.length === 0) {
|
|
123
|
+
result.push({
|
|
124
|
+
nodeId: sourceNodeId,
|
|
125
|
+
preservedNodeRecordPath: safeRepositoryRelativePath(repoRoot, runDir, undefined, `${sourceNodeId}.json`),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Derive only the latest unresolved verify/review feedback from canonical
|
|
132
|
+
* parent-run facts. Provider-only failures intentionally return undefined.
|
|
133
|
+
*/
|
|
134
|
+
export async function deriveDagRerunFeedback(input) {
|
|
135
|
+
const pass = input.state.convergence?.passHistory.at(-1);
|
|
136
|
+
let sourceNodeId;
|
|
137
|
+
let sourceNode;
|
|
138
|
+
for (const nodeId of feedbackSourceCandidates(pass, input.state)) {
|
|
139
|
+
const ref = pass?.artifactRefs.find((candidate) => candidate.nodeId === nodeId);
|
|
140
|
+
const node = await readNodeRecord(input.runDir, pass, ref, nodeId, input.state.nodes[nodeId]);
|
|
141
|
+
if (requestRevision(canonicalNodeOutput(node))) {
|
|
142
|
+
sourceNodeId = nodeId;
|
|
143
|
+
sourceNode = node;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!sourceNodeId || !sourceNode)
|
|
148
|
+
return undefined;
|
|
149
|
+
const completedPasses = pass?.pass ?? Math.max(1, input.state.convergence?.currentPass ?? 1);
|
|
150
|
+
const maxPasses = Math.max(completedPasses, input.state.convergence?.maxPasses ?? 1);
|
|
151
|
+
const parentSourceBindingHash = sourceBindingHash(input.spec);
|
|
152
|
+
return withDigest({
|
|
153
|
+
schemaVersion: 1,
|
|
154
|
+
kind: "unresolved-terminal-feedback",
|
|
155
|
+
parentRunId: input.parentRunId,
|
|
156
|
+
taskId: input.taskId,
|
|
157
|
+
sourceNodeId,
|
|
158
|
+
verdict: "request-revision",
|
|
159
|
+
...(sourceNode.failureCategory
|
|
160
|
+
? { failureCategory: sourceNode.failureCategory }
|
|
161
|
+
: {}),
|
|
162
|
+
feedbackText: scrubAndBoundFeedbackText(canonicalNodeOutput(sourceNode), input.runDir, input.state.cwd),
|
|
163
|
+
evidenceRefs: evidenceRefs(input.state.cwd, input.runDir, pass, sourceNodeId),
|
|
164
|
+
attemptSummary: {
|
|
165
|
+
completedPasses,
|
|
166
|
+
maxPasses,
|
|
167
|
+
...(input.state.convergence?.terminalReason
|
|
168
|
+
? { terminalReason: input.state.convergence.terminalReason }
|
|
169
|
+
: input.state.terminalReason
|
|
170
|
+
? { terminalReason: input.state.terminalReason }
|
|
171
|
+
: {}),
|
|
172
|
+
},
|
|
173
|
+
binding: {
|
|
174
|
+
...(parentSourceBindingHash
|
|
175
|
+
? { sourceCanonicalHash: parentSourceBindingHash }
|
|
176
|
+
: {}),
|
|
177
|
+
...(input.spec.taskContractBinding?.canonicalHash
|
|
178
|
+
? {
|
|
179
|
+
taskContractCanonicalHash: input.spec.taskContractBinding.canonicalHash,
|
|
180
|
+
}
|
|
181
|
+
: {}),
|
|
182
|
+
status: "unknown",
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/** Bind parent feedback to the newly generated live task snapshot. */
|
|
187
|
+
export function bindDagRerunFeedback(feedback, currentSpec) {
|
|
188
|
+
const comparisons = [];
|
|
189
|
+
const currentSourceHash = sourceBindingHash(currentSpec);
|
|
190
|
+
if (feedback.binding.sourceCanonicalHash) {
|
|
191
|
+
comparisons.push(currentSourceHash !== undefined &&
|
|
192
|
+
feedback.binding.sourceCanonicalHash === currentSourceHash);
|
|
193
|
+
}
|
|
194
|
+
if (feedback.binding.taskContractCanonicalHash) {
|
|
195
|
+
comparisons.push(currentSpec.taskContractBinding?.canonicalHash !== undefined &&
|
|
196
|
+
feedback.binding.taskContractCanonicalHash ===
|
|
197
|
+
currentSpec.taskContractBinding.canonicalHash);
|
|
198
|
+
}
|
|
199
|
+
const status = comparisons.some((same) => !same)
|
|
200
|
+
? "changed"
|
|
201
|
+
: comparisons.length > 0
|
|
202
|
+
? "exact"
|
|
203
|
+
: "unknown";
|
|
204
|
+
const { digest: _digest, ...payload } = feedback;
|
|
205
|
+
return withDigest({
|
|
206
|
+
...payload,
|
|
207
|
+
binding: {
|
|
208
|
+
...feedback.binding,
|
|
209
|
+
status,
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
|
|
4
|
-
import { isSafeReadOnlyPiRetryCandidate } from "./retry-policy.js";
|
|
4
|
+
import { isSafeReadOnlyPiRetryCandidate, isTransportCrashedExclusiveWriter } from "./retry-policy.js";
|
|
5
5
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
6
|
+
import { composeDagPromptOverridePrompt, MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS, } from "../../shared/dag-prompt-override.js";
|
|
6
7
|
const REVIEW_VERDICT_RECOVERY_NODE_ID = "review-verdict-recovery-pi";
|
|
7
8
|
const SAFE_SHELL_GATE_KEYS = [
|
|
8
9
|
"verdictGate",
|
|
@@ -122,32 +123,56 @@ function hasDynamicRuntime(task) {
|
|
|
122
123
|
task.dynamicCondition ||
|
|
123
124
|
task.dynamicLoopUntil);
|
|
124
125
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Narrow admission for the standard DAG's generated final verification node.
|
|
128
|
+
* `verifyEvidence` alone is metadata, not proof that an arbitrary shell is
|
|
129
|
+
* read-only, so custom shell ids and non-canonical shapes stay blocked.
|
|
130
|
+
*/
|
|
131
|
+
function isCanonicalGeneratedVerifyShell(task) {
|
|
132
|
+
if (task.executor !== "shell")
|
|
133
|
+
return false;
|
|
134
|
+
const shell = task.shell;
|
|
135
|
+
if (!shell)
|
|
136
|
+
return false;
|
|
137
|
+
const evidence = shell.verifyEvidence;
|
|
138
|
+
if (task.id !== "verify-shell")
|
|
139
|
+
return false;
|
|
140
|
+
if (task.role !== "verifier")
|
|
141
|
+
return false;
|
|
142
|
+
if (task.writePolicy !== "read-only")
|
|
143
|
+
return false;
|
|
144
|
+
if (!evidence)
|
|
145
|
+
return false;
|
|
146
|
+
if (evidence.phase !== "final")
|
|
147
|
+
return false;
|
|
148
|
+
if (evidence.quota !== "full")
|
|
149
|
+
return false;
|
|
150
|
+
if (evidence.commandSource !== "adapter")
|
|
151
|
+
return false;
|
|
152
|
+
if (evidence.finalFullRequired !== true)
|
|
153
|
+
return false;
|
|
154
|
+
if ((shell.commands?.length ?? 0) === 0)
|
|
155
|
+
return false;
|
|
156
|
+
if (evidence.commandCount !== shell.commands.length)
|
|
157
|
+
return false;
|
|
158
|
+
// Any side-effect-capable config disqualifies the node.
|
|
128
159
|
if (shell.preset)
|
|
129
|
-
return
|
|
160
|
+
return false;
|
|
130
161
|
if (shell.projectGovernanceGate)
|
|
131
|
-
return
|
|
162
|
+
return false;
|
|
132
163
|
if (shell.frontendPrewriteGate)
|
|
133
|
-
return
|
|
164
|
+
return false;
|
|
134
165
|
if (shell.frontendVerificationBundle)
|
|
135
|
-
return
|
|
166
|
+
return false;
|
|
136
167
|
if (shell.frontendReviewContext)
|
|
137
|
-
return
|
|
168
|
+
return false;
|
|
138
169
|
if (shell.backendTestPipeline)
|
|
139
|
-
return
|
|
140
|
-
if (shell.
|
|
141
|
-
return
|
|
142
|
-
if (shell.nonZeroExitPolicy)
|
|
143
|
-
return true;
|
|
170
|
+
return false;
|
|
171
|
+
if (shell.nonZeroExitPolicy === "record")
|
|
172
|
+
return false;
|
|
144
173
|
if (shell.envAllowlist?.length)
|
|
145
|
-
return
|
|
146
|
-
|
|
147
|
-
return true;
|
|
148
|
-
if (shell.cwd)
|
|
149
|
-
return true;
|
|
150
|
-
return false;
|
|
174
|
+
return false;
|
|
175
|
+
return true;
|
|
151
176
|
}
|
|
152
177
|
function isSafeGateOnlyShell(task) {
|
|
153
178
|
if (task.executor !== "shell")
|
|
@@ -155,9 +180,32 @@ function isSafeGateOnlyShell(task) {
|
|
|
155
180
|
const shell = task.shell;
|
|
156
181
|
if (!shell)
|
|
157
182
|
return false;
|
|
158
|
-
if (
|
|
183
|
+
if (!SAFE_SHELL_GATE_KEYS.some((key) => shell[key] !== undefined)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
// No side-effect-capable config may ride along on a gate-only shell.
|
|
187
|
+
// Deterministic/read-only riders are admitted: projectGovernanceGate (repo
|
|
188
|
+
// governance context path), cwd, and timeoutMs never execute commands, so
|
|
189
|
+
// the governance-standard gate stays rerun-eligible.
|
|
190
|
+
if ((shell.commands?.length ?? 0) > 0)
|
|
159
191
|
return false;
|
|
160
|
-
|
|
192
|
+
if (shell.preset)
|
|
193
|
+
return false;
|
|
194
|
+
if (shell.frontendPrewriteGate)
|
|
195
|
+
return false;
|
|
196
|
+
if (shell.frontendVerificationBundle)
|
|
197
|
+
return false;
|
|
198
|
+
if (shell.frontendReviewContext)
|
|
199
|
+
return false;
|
|
200
|
+
if (shell.backendTestPipeline)
|
|
201
|
+
return false;
|
|
202
|
+
if (shell.verifyEvidence)
|
|
203
|
+
return false;
|
|
204
|
+
if (shell.nonZeroExitPolicy)
|
|
205
|
+
return false;
|
|
206
|
+
if (shell.envAllowlist?.length)
|
|
207
|
+
return false;
|
|
208
|
+
return true;
|
|
161
209
|
}
|
|
162
210
|
function isStaticTask(task) {
|
|
163
211
|
return task.executor === "static";
|
|
@@ -259,7 +307,10 @@ export function computeResetClosure(spec, effectiveFromNodeId) {
|
|
|
259
307
|
...collectReachableDescendants(spec, effectiveFromNodeId),
|
|
260
308
|
].sort();
|
|
261
309
|
}
|
|
262
|
-
|
|
310
|
+
function isNeverStartedNodeStatus(status) {
|
|
311
|
+
return status === undefined || status === "SKIPPED" || status === "PENDING";
|
|
312
|
+
}
|
|
313
|
+
export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
|
|
263
314
|
const tasks = taskById(spec);
|
|
264
315
|
const blockingNodes = [];
|
|
265
316
|
const reasonCodes = [];
|
|
@@ -267,7 +318,17 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
|
|
|
267
318
|
const task = tasks.get(nodeId);
|
|
268
319
|
if (!task)
|
|
269
320
|
continue;
|
|
321
|
+
if (options.parentNodes !== undefined &&
|
|
322
|
+
isNeverStartedNodeStatus(options.parentNodes[nodeId]?.status)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
270
325
|
if (isWriterTask(task, spec)) {
|
|
326
|
+
if (nodeId === options.allowedTransportWriterNodeId)
|
|
327
|
+
continue;
|
|
328
|
+
if (nodeId === options.allowedExclusiveShellFromNodeId &&
|
|
329
|
+
task.executor === "shell") {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
271
332
|
blockingNodes.push({
|
|
272
333
|
nodeId,
|
|
273
334
|
reasonCode: "restart-subgraph-contains-writer",
|
|
@@ -305,7 +366,8 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
|
|
|
305
366
|
continue;
|
|
306
367
|
}
|
|
307
368
|
if (task.executor === "shell") {
|
|
308
|
-
if (!isSafeGateOnlyShell(task)
|
|
369
|
+
if (!isSafeGateOnlyShell(task) &&
|
|
370
|
+
!isCanonicalGeneratedVerifyShell(task)) {
|
|
309
371
|
blockingNodes.push({
|
|
310
372
|
nodeId,
|
|
311
373
|
reasonCode: "restart-subgraph-contains-unsafe-shell",
|
|
@@ -320,6 +382,100 @@ export function assessResetClosureSafety(spec, resetNodeIds) {
|
|
|
320
382
|
reasonCodes: [...new Set(reasonCodes)],
|
|
321
383
|
};
|
|
322
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Experimental prompt-override admission (fail-closed):
|
|
387
|
+
* - requires the internal switch (see shared/dag-prompt-override.ts);
|
|
388
|
+
* - the target must be a failed (ERROR) node inside the reset closure, so an
|
|
389
|
+
* override can only adjust how an already-failed node retries — never a
|
|
390
|
+
* finished, skipped, or not-yet-started node;
|
|
391
|
+
* - only `subtask_prompt` semantics change: "append" keeps the frozen base
|
|
392
|
+
* and adds a marked addendum block, "replace" swaps the whole prompt;
|
|
393
|
+
* base/final sha256 are recorded for provenance and bound into planHash.
|
|
394
|
+
*/
|
|
395
|
+
function assessPromptOverrides(input) {
|
|
396
|
+
const blockingNodes = [];
|
|
397
|
+
const reasonCodes = [];
|
|
398
|
+
const tasks = taskById(input.spec);
|
|
399
|
+
const resetSet = new Set(input.resetNodeIds);
|
|
400
|
+
const seen = new Set();
|
|
401
|
+
const entries = [];
|
|
402
|
+
if (!input.enabled) {
|
|
403
|
+
reasonCodes.push("prompt-override-disabled");
|
|
404
|
+
}
|
|
405
|
+
for (const override of input.overrides) {
|
|
406
|
+
const nodeId = override.nodeId?.trim() ?? "";
|
|
407
|
+
if (seen.has(nodeId)) {
|
|
408
|
+
blockingNodes.push({
|
|
409
|
+
nodeId,
|
|
410
|
+
reasonCode: "prompt-override-duplicate-node",
|
|
411
|
+
});
|
|
412
|
+
reasonCodes.push("prompt-override-duplicate-node");
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
seen.add(nodeId);
|
|
416
|
+
const task = tasks.get(nodeId);
|
|
417
|
+
if (!task) {
|
|
418
|
+
blockingNodes.push({
|
|
419
|
+
nodeId,
|
|
420
|
+
reasonCode: "prompt-override-node-missing",
|
|
421
|
+
});
|
|
422
|
+
reasonCodes.push("prompt-override-node-missing");
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (!resetSet.has(nodeId)) {
|
|
426
|
+
blockingNodes.push({
|
|
427
|
+
nodeId,
|
|
428
|
+
reasonCode: "prompt-override-outside-reset-closure",
|
|
429
|
+
});
|
|
430
|
+
reasonCodes.push("prompt-override-outside-reset-closure");
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (input.state.nodes[nodeId]?.status !== "ERROR") {
|
|
434
|
+
blockingNodes.push({
|
|
435
|
+
nodeId,
|
|
436
|
+
reasonCode: "prompt-override-node-not-failed",
|
|
437
|
+
});
|
|
438
|
+
reasonCodes.push("prompt-override-node-not-failed");
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const mode = override.mode === "append" ? "append" : "replace";
|
|
442
|
+
const prompt = override.prompt ?? "";
|
|
443
|
+
if (!prompt.trim() ||
|
|
444
|
+
prompt.length > MAX_DAG_RERUN_PROMPT_OVERRIDE_CHARS) {
|
|
445
|
+
blockingNodes.push({
|
|
446
|
+
nodeId,
|
|
447
|
+
reasonCode: "prompt-override-invalid-prompt",
|
|
448
|
+
});
|
|
449
|
+
reasonCodes.push("prompt-override-invalid-prompt");
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
const basePromptSha256 = createHash("sha256")
|
|
453
|
+
.update(task.subtask_prompt, "utf8")
|
|
454
|
+
.digest("hex");
|
|
455
|
+
const finalPrompt = composeDagPromptOverridePrompt({
|
|
456
|
+
basePrompt: task.subtask_prompt,
|
|
457
|
+
text: prompt,
|
|
458
|
+
mode,
|
|
459
|
+
});
|
|
460
|
+
const promptSha256 = createHash("sha256")
|
|
461
|
+
.update(finalPrompt, "utf8")
|
|
462
|
+
.digest("hex");
|
|
463
|
+
if (promptSha256 === basePromptSha256) {
|
|
464
|
+
blockingNodes.push({
|
|
465
|
+
nodeId,
|
|
466
|
+
reasonCode: "prompt-override-unchanged",
|
|
467
|
+
});
|
|
468
|
+
reasonCodes.push("prompt-override-unchanged");
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
entries.push({ nodeId, mode, basePromptSha256, promptSha256 });
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
entries: entries.length > 0 ? entries : undefined,
|
|
475
|
+
blockingNodes,
|
|
476
|
+
reasonCodes: [...new Set(reasonCodes)],
|
|
477
|
+
};
|
|
478
|
+
}
|
|
323
479
|
export function computePlanHash(planWithoutHash) {
|
|
324
480
|
const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
|
|
325
481
|
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
@@ -448,6 +604,20 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
448
604
|
: [];
|
|
449
605
|
const resetSet = new Set(resetNodeIds);
|
|
450
606
|
const importedNodeIds = allNodeIds.filter((nodeId) => !resetSet.has(nodeId));
|
|
607
|
+
const tasksByIdForPlan = taskById(input.parentSpec);
|
|
608
|
+
const effectiveTask = effectiveFromNodeId
|
|
609
|
+
? tasksByIdForPlan.get(effectiveFromNodeId)
|
|
610
|
+
: undefined;
|
|
611
|
+
const effectiveRecord = effectiveFromNodeId
|
|
612
|
+
? input.parentState.nodes[effectiveFromNodeId]
|
|
613
|
+
: undefined;
|
|
614
|
+
const transportWriterRestart = Boolean(effectiveFromNodeId &&
|
|
615
|
+
effectiveRecord?.status === "ERROR" &&
|
|
616
|
+
isTransportCrashedExclusiveWriter(effectiveTask, effectiveRecord.failureCategory));
|
|
617
|
+
const convergenceWriterRestart = Boolean(transportWriterRestart &&
|
|
618
|
+
input.parentSpec.convergence?.enabled === true &&
|
|
619
|
+
input.parentState.convergence?.enabled === true &&
|
|
620
|
+
!input.parentState.convergence.terminalReason);
|
|
451
621
|
const controllerIdentityMatched = input.currentControllerFingerprint === undefined ||
|
|
452
622
|
input.parentControllerFingerprint === undefined ||
|
|
453
623
|
input.currentControllerFingerprint === input.parentControllerFingerprint;
|
|
@@ -482,11 +652,11 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
482
652
|
reasonCodes.push("parent-lifecycle-ineligible");
|
|
483
653
|
blockedReasons.push("parent-lifecycle-ineligible");
|
|
484
654
|
}
|
|
485
|
-
if (input.parentSpec.convergence?.enabled) {
|
|
655
|
+
if (input.parentSpec.convergence?.enabled && !convergenceWriterRestart) {
|
|
486
656
|
reasonCodes.push("convergence-rerun-unsupported");
|
|
487
657
|
blockedReasons.push("convergence-rerun-unsupported");
|
|
488
658
|
}
|
|
489
|
-
if (input.parentState.convergence?.enabled) {
|
|
659
|
+
if (input.parentState.convergence?.enabled && !convergenceWriterRestart) {
|
|
490
660
|
reasonCodes.push("convergence-rerun-unsupported");
|
|
491
661
|
blockedReasons.push("convergence-rerun-unsupported");
|
|
492
662
|
}
|
|
@@ -530,7 +700,17 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
530
700
|
blockedReasons.push("ambiguous-skipped-rewrite");
|
|
531
701
|
}
|
|
532
702
|
}
|
|
533
|
-
const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds
|
|
703
|
+
const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds, {
|
|
704
|
+
...(transportWriterRestart && effectiveFromNodeId
|
|
705
|
+
? { allowedTransportWriterNodeId: effectiveFromNodeId }
|
|
706
|
+
: {}),
|
|
707
|
+
parentNodes: input.parentState.nodes,
|
|
708
|
+
...(effectiveFromNodeId &&
|
|
709
|
+
effectiveTask?.executor === "shell" &&
|
|
710
|
+
effectiveRecord?.status === "ERROR"
|
|
711
|
+
? { allowedExclusiveShellFromNodeId: effectiveFromNodeId }
|
|
712
|
+
: {}),
|
|
713
|
+
});
|
|
534
714
|
blockingNodes.push(...closureSafety.blockingNodes);
|
|
535
715
|
for (const code of closureSafety.reasonCodes) {
|
|
536
716
|
reasonCodes.push(code);
|
|
@@ -563,6 +743,22 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
563
743
|
blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
|
|
564
744
|
}
|
|
565
745
|
}
|
|
746
|
+
let promptOverrideEntries;
|
|
747
|
+
if (input.promptOverrides !== undefined && input.promptOverrides.length > 0) {
|
|
748
|
+
const overrideAssessment = assessPromptOverrides({
|
|
749
|
+
spec: input.parentSpec,
|
|
750
|
+
state: input.parentState,
|
|
751
|
+
resetNodeIds,
|
|
752
|
+
overrides: input.promptOverrides,
|
|
753
|
+
enabled: input.promptOverrideEnabled === true,
|
|
754
|
+
});
|
|
755
|
+
blockingNodes.push(...overrideAssessment.blockingNodes);
|
|
756
|
+
for (const code of overrideAssessment.reasonCodes) {
|
|
757
|
+
reasonCodes.push(code);
|
|
758
|
+
blockedReasons.push(code);
|
|
759
|
+
}
|
|
760
|
+
promptOverrideEntries = overrideAssessment.entries;
|
|
761
|
+
}
|
|
566
762
|
const uniqueReasonCodes = [...new Set(reasonCodes)];
|
|
567
763
|
const uniqueBlockedReasons = [...new Set(blockedReasons)];
|
|
568
764
|
const eligible = uniqueBlockedReasons.length === 0 &&
|
|
@@ -606,6 +802,7 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
606
802
|
parentLifecycle: input.parentLifecycle,
|
|
607
803
|
workerManaged: input.workerManaged,
|
|
608
804
|
}),
|
|
805
|
+
...(promptOverrideEntries ? { promptOverrides: promptOverrideEntries } : {}),
|
|
609
806
|
};
|
|
610
807
|
return {
|
|
611
808
|
...planWithoutHash,
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { access, copyFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
5
|
+
import { composeDagPromptOverridePrompt, isDagRerunPromptOverrideEnabled, } from "../../shared/dag-prompt-override.js";
|
|
5
6
|
import { resolveRunningControllerIdentity } from "../../shared/package-metadata.js";
|
|
6
7
|
import { readControllerIdentityArtifact, captureControllerIdentity } from "./controller-identity.js";
|
|
7
8
|
import { DAG_RUNS_DIR, getDagRunDir, isDagNonExecutionLifecycle, locateDagRun, readDagRunSpec, readDagRunState, } from "./lifecycle.js";
|
|
@@ -190,6 +191,8 @@ export async function planDagRerun(input) {
|
|
|
190
191
|
parentSpec: nonCompletedSpec,
|
|
191
192
|
parentState: nonCompletedState,
|
|
192
193
|
parentLifecycle,
|
|
194
|
+
promptOverrides: input.promptOverrides,
|
|
195
|
+
promptOverrideEnabled: isDagRerunPromptOverrideEnabled(),
|
|
193
196
|
workerManaged: nonCompletedWorkerHints.workerManaged,
|
|
194
197
|
workerAssociation: nonCompletedWorkerHints.workerManaged
|
|
195
198
|
? {
|
|
@@ -229,6 +232,8 @@ export async function planDagRerun(input) {
|
|
|
229
232
|
parentTerminalWorkspace,
|
|
230
233
|
currentWorkspace,
|
|
231
234
|
skillSnapshotOk,
|
|
235
|
+
promptOverrides: input.promptOverrides,
|
|
236
|
+
promptOverrideEnabled: isDagRerunPromptOverrideEnabled(),
|
|
232
237
|
workerManaged: workerHints.workerManaged,
|
|
233
238
|
workerAssociation: workerHints.workerManaged
|
|
234
239
|
? {
|
|
@@ -384,15 +389,57 @@ function buildPendingNodeRecord(task) {
|
|
|
384
389
|
...(task.outputMode ? { outputMode: task.outputMode } : {}),
|
|
385
390
|
};
|
|
386
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Stage the continuation spec with operator prompt overrides applied. The
|
|
394
|
+
* parent spec stays frozen; every override must match the plan entry (sha256
|
|
395
|
+
* of the composed final prompt and of the parent base prompt) so the
|
|
396
|
+
* two-phase planHash remains the single source of truth for what changed.
|
|
397
|
+
* "append" entries keep the frozen base and append the operator addendum in a
|
|
398
|
+
* marked block; "replace" entries swap the whole prompt.
|
|
399
|
+
*/
|
|
400
|
+
function applyPromptOverridesToSpec(input) {
|
|
401
|
+
const planned = new Map((input.plan.promptOverrides ?? []).map((entry) => [entry.nodeId, entry]));
|
|
402
|
+
const spec = structuredClone(input.spec);
|
|
403
|
+
for (const override of input.overrides) {
|
|
404
|
+
const entry = planned.get(override.nodeId);
|
|
405
|
+
if (!entry) {
|
|
406
|
+
throw new Error(`prompt override is not present in the rerun plan: ${override.nodeId}`);
|
|
407
|
+
}
|
|
408
|
+
const task = spec.tasks.find((candidate) => candidate.id === override.nodeId);
|
|
409
|
+
if (!task) {
|
|
410
|
+
throw new Error(`prompt override node missing from spec: ${override.nodeId}`);
|
|
411
|
+
}
|
|
412
|
+
if (sha256Hex(task.subtask_prompt) !== entry.basePromptSha256) {
|
|
413
|
+
throw new Error(`prompt override base prompt drifted from plan: ${override.nodeId}`);
|
|
414
|
+
}
|
|
415
|
+
const finalPrompt = composeDagPromptOverridePrompt({
|
|
416
|
+
basePrompt: task.subtask_prompt,
|
|
417
|
+
text: override.prompt,
|
|
418
|
+
mode: override.mode === "append" ? "append" : "replace",
|
|
419
|
+
});
|
|
420
|
+
if (sha256Hex(finalPrompt) !== entry.promptSha256) {
|
|
421
|
+
throw new Error(`prompt override text does not match the planned sha256: ${override.nodeId}`);
|
|
422
|
+
}
|
|
423
|
+
task.subtask_prompt = finalPrompt;
|
|
424
|
+
}
|
|
425
|
+
return spec;
|
|
426
|
+
}
|
|
387
427
|
async function stageContinuationRun(input) {
|
|
388
|
-
const
|
|
389
|
-
|
|
428
|
+
const specForRun = input.promptOverrides !== undefined && input.promptOverrides.length > 0
|
|
429
|
+
? applyPromptOverridesToSpec({
|
|
430
|
+
spec: input.parentSpec,
|
|
431
|
+
plan: input.plan,
|
|
432
|
+
overrides: input.promptOverrides,
|
|
433
|
+
})
|
|
434
|
+
: input.parentSpec;
|
|
435
|
+
const { ranks } = topoSortToRanks(specForRun);
|
|
436
|
+
const runId = await buildDagRunId(specForRun, { cwd: input.cwd });
|
|
390
437
|
const runDir = getDagRunDir(input.cwd, "active", runId);
|
|
391
438
|
await prepareActiveRunDir(runDir);
|
|
392
|
-
await writeRunSpec(runDir,
|
|
439
|
+
await writeRunSpec(runDir, specForRun);
|
|
393
440
|
const resetSet = new Set(input.plan.resetNodeIds);
|
|
394
441
|
const importedSet = new Set(input.plan.importedNodeIds);
|
|
395
|
-
const tasksById = new Map(
|
|
442
|
+
const tasksById = new Map(specForRun.tasks.map((task) => [task.id, task]));
|
|
396
443
|
const importManifestNodes = [];
|
|
397
444
|
const nodes = {};
|
|
398
445
|
for (const nodeId of input.plan.importedNodeIds) {
|
|
@@ -433,6 +480,9 @@ async function stageContinuationRun(input) {
|
|
|
433
480
|
reason: input.reason,
|
|
434
481
|
requestId: input.requestId,
|
|
435
482
|
planHash: input.plan.planHash,
|
|
483
|
+
...(input.plan.promptOverrides
|
|
484
|
+
? { promptOverrides: input.plan.promptOverrides }
|
|
485
|
+
: {}),
|
|
436
486
|
createdAt,
|
|
437
487
|
};
|
|
438
488
|
const state = {
|
|
@@ -445,6 +495,9 @@ async function stageContinuationRun(input) {
|
|
|
445
495
|
ranks,
|
|
446
496
|
nodes,
|
|
447
497
|
continuation,
|
|
498
|
+
...(input.parentState.convergence
|
|
499
|
+
? { convergence: structuredClone(input.parentState.convergence) }
|
|
500
|
+
: {}),
|
|
448
501
|
...(input.parentState.evaluation
|
|
449
502
|
? { evaluation: structuredClone(input.parentState.evaluation) }
|
|
450
503
|
: {}),
|
|
@@ -479,7 +532,7 @@ async function stageContinuationRun(input) {
|
|
|
479
532
|
await writeJsonAtomic(path.join(runDir, IMPORT_MANIFEST_REL_PATH), importManifest);
|
|
480
533
|
await writeJsonAtomic(path.join(runDir, RERUN_PLAN_REL_PATH), input.plan);
|
|
481
534
|
await writeRunState(runDir, state);
|
|
482
|
-
return { runId, runDir, state };
|
|
535
|
+
return { runId, runDir, state, spec: specForRun };
|
|
483
536
|
}
|
|
484
537
|
export async function executeDagRerun(input) {
|
|
485
538
|
const existing = await readExistingOperatorRequest(input.cwd, input.requestId);
|
|
@@ -490,6 +543,7 @@ export async function executeDagRerun(input) {
|
|
|
490
543
|
cwd: input.cwd,
|
|
491
544
|
parentRunId: input.parentRunId,
|
|
492
545
|
selectedNodeId: input.selectedNodeId,
|
|
546
|
+
promptOverrides: input.promptOverrides,
|
|
493
547
|
});
|
|
494
548
|
if (plan.planHash !== input.planHash) {
|
|
495
549
|
return {
|
|
@@ -527,10 +581,11 @@ export async function executeDagRerun(input) {
|
|
|
527
581
|
plan,
|
|
528
582
|
reason: input.reason,
|
|
529
583
|
requestId: input.requestId,
|
|
584
|
+
promptOverrides: input.promptOverrides,
|
|
530
585
|
});
|
|
531
586
|
const continuationOptions = {
|
|
532
587
|
cwd: input.cwd,
|
|
533
|
-
spec:
|
|
588
|
+
spec: staged.spec,
|
|
534
589
|
state: staged.state,
|
|
535
590
|
runDir: staged.runDir,
|
|
536
591
|
...(input.maxConcurrent !== undefined
|