@tea-agent/loop-agent 0.38.2 → 0.38.4
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 +171 -96
- package/README.md +11 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +4 -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.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 +76 -10
- 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 +51 -12
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +265 -15
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/operator/capabilities.js +32 -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 +41 -8
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/operator-actions.js +116 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +36 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
- package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-pm3c2Rsl.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
- package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_Bq2ZqK.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BC6Bgosf.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
- package/dist/worker/console/static/assets/index-C8rOIAJr.css +1 -0
- package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DEbyrEfW.js} +147 -68
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
- package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-CAyXjQjN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-CcWKn6tS.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DOSnT7hb.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DVJQA7AN.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- 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/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/observe/node-input.js +74 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +36 -3
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +66 -0
- 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 +23 -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/state.js +13 -2
- package/dist/worker/observe/static/styles.css +73 -0
- package/dist/worker/observe/static/views/dag-inspector.js +612 -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/workflows/dag/backend-test-case-coverage-analysis.js +76 -6
- package/dist/workflows/dag/backend-test-scenario-partitions.js +73 -1
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/failure-category.js +12 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
- package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
- package/dist/workflows/dag/frontend-repair.js +14 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
- package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
- package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
- package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/frontend-verification-trace.js +29 -4
- package/dist/workflows/dag/init-hybrid.js +196 -117
- package/dist/workflows/dag/node-execution.js +139 -10
- package/dist/workflows/dag/prompt.js +15 -1
- package/dist/workflows/dag/rerun-plan.js +134 -3
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +91 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +32 -1
- package/dist/workflows/dag/validate.js +41 -1
- package/docs/init-surface.manifest.json +5 -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 +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +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 +2 -2
- 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/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/playwright-cli/SKILL.md +22 -0
- package/skills/playwright-cli-case-generator/SKILL.md +20 -1
- package/dist/worker/console/static/assets/channel-R6oQIDkw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +0 -1
- package/dist/worker/console/static/assets/index-CuH5CNFu.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +0 -8
|
@@ -10,11 +10,13 @@ import { resolveContextPolicy } from "./context-policy.js";
|
|
|
10
10
|
import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
|
|
11
11
|
import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
|
|
12
12
|
import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
|
|
13
|
-
import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
|
|
13
|
+
import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
|
|
14
14
|
import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
|
|
15
15
|
import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
|
|
16
16
|
import { getStructuredContractValidator } from "./contract-output-registry.js";
|
|
17
17
|
import "./contract-validator-registrations.js";
|
|
18
|
+
import { allowedRepairReadPaths, auditRepairAttemptToolUse, buildStructuredOutputRepairPrompt, freezeStructuredOutputRepairContext, hasNonEmptyStructuredCandidate, isFrontendStructuredRepairSchemaId, isStructuredRepairableFailureCategory, persistStructuredAttemptRaw, sessionEventsByteLength, GOVERNANCE_BLOCKED_CATEGORY, STRUCTURED_REPAIR_EXHAUSTED_CATEGORY, } from "./structured-output-repair.js";
|
|
19
|
+
import { readFrontendCanonicalCandidate } from "./frontend-implementation-contract.js";
|
|
18
20
|
import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
19
21
|
import { buildProjectGovernanceContext, readCompletedWriterChangeManifests, writeProjectGovernanceContext, } from "./project-governance-context.js";
|
|
20
22
|
import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
|
|
@@ -135,6 +137,11 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
|
135
137
|
"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
138
|
"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
139
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
140
|
+
"The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
|
|
141
|
+
"```openspec-citations",
|
|
142
|
+
'{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
|
|
143
|
+
'{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
|
|
144
|
+
"```",
|
|
138
145
|
];
|
|
139
146
|
}
|
|
140
147
|
if (schemaId === "frontend-implementation-contract-revision-patch-v1") {
|
|
@@ -142,6 +149,11 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
|
142
149
|
"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
150
|
"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
151
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
152
|
+
"The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
|
|
153
|
+
"```openspec-citations",
|
|
154
|
+
'{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
|
|
155
|
+
'{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
|
|
156
|
+
"```",
|
|
145
157
|
];
|
|
146
158
|
}
|
|
147
159
|
return [
|
|
@@ -248,6 +260,16 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
248
260
|
"</retry_instruction>",
|
|
249
261
|
].join("\n");
|
|
250
262
|
}
|
|
263
|
+
if (previousFailureCategory === "writer-clean-timeout") {
|
|
264
|
+
return [
|
|
265
|
+
basePrompt,
|
|
266
|
+
"",
|
|
267
|
+
"<retry_instruction>",
|
|
268
|
+
"Previous attempt ended in a clean provider/transport failure with zero attributed workspace writes and zero write/edit tool calls.",
|
|
269
|
+
"Do not invent existing implementation progress. Continue from the original task only if the workspace is still unchanged.",
|
|
270
|
+
"</retry_instruction>",
|
|
271
|
+
].join("\n");
|
|
272
|
+
}
|
|
251
273
|
if (previousFailureCategory !== "output-too-large") {
|
|
252
274
|
return basePrompt;
|
|
253
275
|
}
|
|
@@ -684,12 +706,18 @@ export async function executeDagNode(input) {
|
|
|
684
706
|
// Invalid or missing harness preserves the previous no-override behavior.
|
|
685
707
|
}
|
|
686
708
|
}
|
|
687
|
-
const
|
|
709
|
+
const specRetryPolicy = task.retryPolicy &&
|
|
688
710
|
(isSafeReadOnlyPiRetryCandidate(task) ||
|
|
689
711
|
isWriterEmptyDiffRetryCandidate(task) ||
|
|
690
712
|
isWriterTransportRetryCandidate(task))
|
|
691
713
|
? task.retryPolicy
|
|
692
714
|
: undefined;
|
|
715
|
+
const implicitVerifyRetryPolicy = specRetryPolicy === undefined &&
|
|
716
|
+
isCanonicalFinalVerifyShellRetryCandidate(task) &&
|
|
717
|
+
isTargetTemplateTransientRetryNode(task)
|
|
718
|
+
? VERIFY_SHELL_RETRY_POLICY
|
|
719
|
+
: undefined;
|
|
720
|
+
const retryPolicy = specRetryPolicy ?? implicitVerifyRetryPolicy;
|
|
693
721
|
const maxAttempts = retryPolicy?.maxAttempts ?? 1;
|
|
694
722
|
const attempts = [];
|
|
695
723
|
let totalAttemptWallDurationMs = 0;
|
|
@@ -731,10 +759,17 @@ export async function executeDagNode(input) {
|
|
|
731
759
|
let terminalResult;
|
|
732
760
|
let previousFailureCategory;
|
|
733
761
|
let previousProtocolReason;
|
|
762
|
+
let pendingStructuredArtifact;
|
|
763
|
+
let lastRepairAllowlist = [];
|
|
734
764
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
735
765
|
const attemptStartedAt = new Date().toISOString();
|
|
736
766
|
node.currentAttempt = attemptNumber;
|
|
737
767
|
node.livenessStatus = "active";
|
|
768
|
+
// Capture the session-events.jsonl length BEFORE the executor appends this
|
|
769
|
+
// attempt's events, so the post-attempt repair tool audit can be scoped to
|
|
770
|
+
// exactly this attempt's segment (B1: earlier attempts legitimately use
|
|
771
|
+
// ls/find and must not be attributed to the repair attempt).
|
|
772
|
+
const attemptSessionEventsOffset = await sessionEventsByteLength(runDir, nodeId);
|
|
738
773
|
let result;
|
|
739
774
|
try {
|
|
740
775
|
validateRepairArtifactGateBeforeShell({
|
|
@@ -757,12 +792,53 @@ export async function executeDagNode(input) {
|
|
|
757
792
|
(acc[i.path] ??= []).push(i.detail);
|
|
758
793
|
return acc;
|
|
759
794
|
}, {});
|
|
795
|
+
let attemptPrompt = buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics);
|
|
796
|
+
if (attemptNumber > 1 &&
|
|
797
|
+
isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
|
|
798
|
+
isStructuredRepairableFailureCategory(previousFailureCategory) &&
|
|
799
|
+
(await hasNonEmptyStructuredCandidate({
|
|
800
|
+
runDir,
|
|
801
|
+
nodeId,
|
|
802
|
+
beforeAttempt: attemptNumber,
|
|
803
|
+
}))) {
|
|
804
|
+
const originalCanonical = task.structuredContractOutput?.schemaId ===
|
|
805
|
+
"frontend-implementation-contract-revision-patch-v1"
|
|
806
|
+
? (await readFrontendCanonicalCandidate(runDir, "frontend-plan-pi"))
|
|
807
|
+
: undefined;
|
|
808
|
+
const frozen = await freezeStructuredOutputRepairContext({
|
|
809
|
+
runDir,
|
|
810
|
+
runId: state.runId,
|
|
811
|
+
taskId: spec.sourceBinding?.taskId ?? task.id,
|
|
812
|
+
nodeId,
|
|
813
|
+
nodeAttempt: attemptNumber,
|
|
814
|
+
targetSchemaId: task.structuredContractOutput.schemaId,
|
|
815
|
+
skeleton: task.structuredContractOutput?.skeleton,
|
|
816
|
+
originalCanonical,
|
|
817
|
+
latestStopReason: previousFailureCategory === "structured-output-truncated"
|
|
818
|
+
? "length"
|
|
819
|
+
: previousFailureCategory,
|
|
820
|
+
});
|
|
821
|
+
lastRepairAllowlist = allowedRepairReadPaths({
|
|
822
|
+
runDir,
|
|
823
|
+
context: frozen.context,
|
|
824
|
+
contextPath: frozen.relativePath,
|
|
825
|
+
});
|
|
826
|
+
attemptPrompt = await buildStructuredOutputRepairPrompt({
|
|
827
|
+
runDir,
|
|
828
|
+
context: frozen.context,
|
|
829
|
+
contextPath: frozen.relativePath,
|
|
830
|
+
contextSha256: frozen.sha256,
|
|
831
|
+
maxAttempts,
|
|
832
|
+
schemaGuidance: frontendStructuredArtifactRetryGuidance(task.structuredContractOutput.schemaId),
|
|
833
|
+
previousReason: previousProtocolReason,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
760
836
|
result = await executeNode({
|
|
761
837
|
task,
|
|
762
838
|
cwd,
|
|
763
839
|
model,
|
|
764
840
|
...(thinking ? { thinking } : {}),
|
|
765
|
-
prompt:
|
|
841
|
+
prompt: attemptPrompt,
|
|
766
842
|
attempt: attemptNumber,
|
|
767
843
|
reportActivity,
|
|
768
844
|
timeoutMs: livenessPolicy.absoluteMaxWallClockMs,
|
|
@@ -778,6 +854,35 @@ export async function executeDagNode(input) {
|
|
|
778
854
|
durationMs: 0,
|
|
779
855
|
};
|
|
780
856
|
}
|
|
857
|
+
if (isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId)) {
|
|
858
|
+
const rawText = canonicalNodeOutput(result);
|
|
859
|
+
if (rawText.trim().length > 0) {
|
|
860
|
+
await persistStructuredAttemptRaw({
|
|
861
|
+
runDir,
|
|
862
|
+
nodeId,
|
|
863
|
+
attempt: attemptNumber,
|
|
864
|
+
text: rawText,
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
if (attemptNumber > 1 && lastRepairAllowlist.length > 0) {
|
|
868
|
+
const audit = await auditRepairAttemptToolUse({
|
|
869
|
+
runDir,
|
|
870
|
+
nodeId,
|
|
871
|
+
allowedPaths: lastRepairAllowlist,
|
|
872
|
+
fromByteOffset: attemptSessionEventsOffset,
|
|
873
|
+
});
|
|
874
|
+
if (!audit.ok) {
|
|
875
|
+
result = {
|
|
876
|
+
...result,
|
|
877
|
+
ok: false,
|
|
878
|
+
failureCategory: GOVERNANCE_BLOCKED_CATEGORY,
|
|
879
|
+
stderr: [result.stderr, audit.reason]
|
|
880
|
+
.filter(Boolean)
|
|
881
|
+
.join("\n"),
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
781
886
|
// R0: executor ok=true still fails closed when outputProtocol is violated.
|
|
782
887
|
// Valid semantic results (e.g. VERDICT: request-revision) pass validation.
|
|
783
888
|
if (result.ok && task.outputProtocol) {
|
|
@@ -814,16 +919,17 @@ export async function executeDagNode(input) {
|
|
|
814
919
|
structuredContractOutput: task.structuredContractOutput,
|
|
815
920
|
});
|
|
816
921
|
if (!contractCheck.ok) {
|
|
817
|
-
// A provider stopReason=length means the response was cut before the
|
|
818
|
-
// JSON contract could complete; separate it from an ordinary bad
|
|
819
|
-
// contract so the retry switches to a JSON-only output strategy.
|
|
820
922
|
const attemptStopReason = result.stopReason;
|
|
923
|
+
const failureCategory = contractCheck.classification === "governance-blocked"
|
|
924
|
+
? GOVERNANCE_BLOCKED_CATEGORY
|
|
925
|
+
: attemptStopReason === "length" ||
|
|
926
|
+
contractCheck.classification === "truncated"
|
|
927
|
+
? "structured-output-truncated"
|
|
928
|
+
: "invalid-output";
|
|
821
929
|
result = {
|
|
822
930
|
...result,
|
|
823
931
|
ok: false,
|
|
824
|
-
failureCategory
|
|
825
|
-
? "structured-output-truncated"
|
|
826
|
-
: "invalid-output",
|
|
932
|
+
failureCategory,
|
|
827
933
|
stderr: [result.stderr, contractCheck.reason]
|
|
828
934
|
.filter(Boolean)
|
|
829
935
|
.join("\n"),
|
|
@@ -838,6 +944,9 @@ export async function executeDagNode(input) {
|
|
|
838
944
|
stdout: contractCheck.normalizedText,
|
|
839
945
|
};
|
|
840
946
|
}
|
|
947
|
+
if (contractCheck.artifact) {
|
|
948
|
+
pendingStructuredArtifact = contractCheck.artifact;
|
|
949
|
+
}
|
|
841
950
|
previousProtocolReason = undefined;
|
|
842
951
|
}
|
|
843
952
|
}
|
|
@@ -850,6 +959,7 @@ export async function executeDagNode(input) {
|
|
|
850
959
|
const attemptFinishedAt = new Date().toISOString();
|
|
851
960
|
const attemptWallDurationMs = durationBetween(attemptStartedAt, attemptFinishedAt);
|
|
852
961
|
totalAttemptWallDurationMs += attemptWallDurationMs;
|
|
962
|
+
const rawFailureCategory = result.rawFailureCategory ?? result.failureCategory;
|
|
853
963
|
const attemptRecord = {
|
|
854
964
|
attempt: attemptNumber,
|
|
855
965
|
startedAt: attemptStartedAt,
|
|
@@ -861,6 +971,7 @@ export async function executeDagNode(input) {
|
|
|
861
971
|
stderr: result.stderr,
|
|
862
972
|
assistantText: result.assistantText,
|
|
863
973
|
failureCategory: result.failureCategory,
|
|
974
|
+
...(rawFailureCategory ? { rawFailureCategory } : {}),
|
|
864
975
|
backend: result.backend,
|
|
865
976
|
sdkAttempted: result.sdkAttempted,
|
|
866
977
|
tokensUsed: result.tokensUsed,
|
|
@@ -908,13 +1019,26 @@ export async function executeDagNode(input) {
|
|
|
908
1019
|
break;
|
|
909
1020
|
const canRetry = retryPolicy !== undefined && attemptNumber < maxAttempts;
|
|
910
1021
|
const retryable = retryPolicy !== undefined &&
|
|
1022
|
+
result.failureCategory !== GOVERNANCE_BLOCKED_CATEGORY &&
|
|
911
1023
|
(isRetryablePiFailureCategory(result.failureCategory, {
|
|
912
1024
|
retryCategories: retryPolicy.retryCategories,
|
|
913
1025
|
}) ||
|
|
914
1026
|
(result.failureCategory === "protocol-invalid" &&
|
|
915
1027
|
task.outputProtocol?.retryOnInvalid === true));
|
|
916
|
-
if (!canRetry || !retryable)
|
|
1028
|
+
if (!canRetry || !retryable) {
|
|
1029
|
+
if (!canRetry &&
|
|
1030
|
+
isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
|
|
1031
|
+
isStructuredRepairableFailureCategory(result.failureCategory)) {
|
|
1032
|
+
result = {
|
|
1033
|
+
...result,
|
|
1034
|
+
failureCategory: STRUCTURED_REPAIR_EXHAUSTED_CATEGORY,
|
|
1035
|
+
};
|
|
1036
|
+
terminalResult = result;
|
|
1037
|
+
previousFailureCategory = result.failureCategory;
|
|
1038
|
+
node.failureCategory = result.failureCategory;
|
|
1039
|
+
}
|
|
917
1040
|
break;
|
|
1041
|
+
}
|
|
918
1042
|
const delayMs = computeBackoffDelayMs(attemptNumber + 1, retryPolicy);
|
|
919
1043
|
if (delayMs > 0) {
|
|
920
1044
|
const backoffStartedAt = new Date().toISOString();
|
|
@@ -1063,6 +1187,11 @@ export async function executeDagNode(input) {
|
|
|
1063
1187
|
node.structuredArtifactSchemaId = "backend-test-result-v1";
|
|
1064
1188
|
}
|
|
1065
1189
|
}
|
|
1190
|
+
else if (pendingStructuredArtifact) {
|
|
1191
|
+
node.structuredArtifactPath = pendingStructuredArtifact.path;
|
|
1192
|
+
node.structuredArtifactSha256 = pendingStructuredArtifact.sha256;
|
|
1193
|
+
node.structuredArtifactSchemaId = pendingStructuredArtifact.schemaId;
|
|
1194
|
+
}
|
|
1066
1195
|
}
|
|
1067
1196
|
const finishedAt = new Date().toISOString();
|
|
1068
1197
|
node.finishedAt = finishedAt;
|
|
@@ -116,7 +116,21 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
|
|
|
116
116
|
].join("\n"));
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
119
|
-
if (!record || record.status !== "FINISHED"
|
|
119
|
+
if (!record || record.status !== "FINISHED")
|
|
120
|
+
continue;
|
|
121
|
+
if (record.structuredArtifactPath &&
|
|
122
|
+
record.structuredArtifactSchemaId ===
|
|
123
|
+
"frontend-implementation-contract-v1") {
|
|
124
|
+
sections.push([
|
|
125
|
+
`## Upstream output: ${depId}`,
|
|
126
|
+
"Validated structured artifact (read-only runner evidence — use read tool to fetch; do not edit). Do not infer the contract from stdout.",
|
|
127
|
+
`- path: ${record.structuredArtifactPath}`,
|
|
128
|
+
`- schema: ${record.structuredArtifactSchemaId}`,
|
|
129
|
+
`- sha256: ${record.structuredArtifactSha256 ?? "unknown"}`,
|
|
130
|
+
].join("\n"));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (!upstreamText)
|
|
120
134
|
continue;
|
|
121
135
|
const artifactKind = stdout ? "stdout" : "assistant";
|
|
122
136
|
const artifactPath = stdout
|
|
@@ -3,6 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { assertTaskContractBindingFresh } from "./task-contract-binding.js";
|
|
4
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",
|
|
@@ -306,6 +307,9 @@ export function computeResetClosure(spec, effectiveFromNodeId) {
|
|
|
306
307
|
...collectReachableDescendants(spec, effectiveFromNodeId),
|
|
307
308
|
].sort();
|
|
308
309
|
}
|
|
310
|
+
function isNeverStartedNodeStatus(status) {
|
|
311
|
+
return status === undefined || status === "SKIPPED" || status === "PENDING";
|
|
312
|
+
}
|
|
309
313
|
export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
|
|
310
314
|
const tasks = taskById(spec);
|
|
311
315
|
const blockingNodes = [];
|
|
@@ -314,9 +318,17 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
|
|
|
314
318
|
const task = tasks.get(nodeId);
|
|
315
319
|
if (!task)
|
|
316
320
|
continue;
|
|
321
|
+
if (options.parentNodes !== undefined &&
|
|
322
|
+
isNeverStartedNodeStatus(options.parentNodes[nodeId]?.status)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
317
325
|
if (isWriterTask(task, spec)) {
|
|
318
326
|
if (nodeId === options.allowedTransportWriterNodeId)
|
|
319
327
|
continue;
|
|
328
|
+
if (nodeId === options.allowedExclusiveShellFromNodeId &&
|
|
329
|
+
task.executor === "shell") {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
320
332
|
blockingNodes.push({
|
|
321
333
|
nodeId,
|
|
322
334
|
reasonCode: "restart-subgraph-contains-writer",
|
|
@@ -370,6 +382,100 @@ export function assessResetClosureSafety(spec, resetNodeIds, options = {}) {
|
|
|
370
382
|
reasonCodes: [...new Set(reasonCodes)],
|
|
371
383
|
};
|
|
372
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
|
+
}
|
|
373
479
|
export function computePlanHash(planWithoutHash) {
|
|
374
480
|
const canonical = JSON.stringify(sortKeysDeep(planWithoutHash));
|
|
375
481
|
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
@@ -594,9 +700,17 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
594
700
|
blockedReasons.push("ambiguous-skipped-rewrite");
|
|
595
701
|
}
|
|
596
702
|
}
|
|
597
|
-
const closureSafety = assessResetClosureSafety(input.parentSpec, resetNodeIds,
|
|
598
|
-
|
|
599
|
-
|
|
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
|
+
});
|
|
600
714
|
blockingNodes.push(...closureSafety.blockingNodes);
|
|
601
715
|
for (const code of closureSafety.reasonCodes) {
|
|
602
716
|
reasonCodes.push(code);
|
|
@@ -629,6 +743,22 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
629
743
|
blockingNodes.push({ nodeId, reasonCode: "parent-facts-invalid" });
|
|
630
744
|
}
|
|
631
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
|
+
}
|
|
632
762
|
const uniqueReasonCodes = [...new Set(reasonCodes)];
|
|
633
763
|
const uniqueBlockedReasons = [...new Set(blockedReasons)];
|
|
634
764
|
const eligible = uniqueBlockedReasons.length === 0 &&
|
|
@@ -672,6 +802,7 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
672
802
|
parentLifecycle: input.parentLifecycle,
|
|
673
803
|
workerManaged: input.workerManaged,
|
|
674
804
|
}),
|
|
805
|
+
...(promptOverrideEntries ? { promptOverrides: promptOverrideEntries } : {}),
|
|
675
806
|
};
|
|
676
807
|
return {
|
|
677
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 = {
|
|
@@ -482,7 +532,7 @@ async function stageContinuationRun(input) {
|
|
|
482
532
|
await writeJsonAtomic(path.join(runDir, IMPORT_MANIFEST_REL_PATH), importManifest);
|
|
483
533
|
await writeJsonAtomic(path.join(runDir, RERUN_PLAN_REL_PATH), input.plan);
|
|
484
534
|
await writeRunState(runDir, state);
|
|
485
|
-
return { runId, runDir, state };
|
|
535
|
+
return { runId, runDir, state, spec: specForRun };
|
|
486
536
|
}
|
|
487
537
|
export async function executeDagRerun(input) {
|
|
488
538
|
const existing = await readExistingOperatorRequest(input.cwd, input.requestId);
|
|
@@ -493,6 +543,7 @@ export async function executeDagRerun(input) {
|
|
|
493
543
|
cwd: input.cwd,
|
|
494
544
|
parentRunId: input.parentRunId,
|
|
495
545
|
selectedNodeId: input.selectedNodeId,
|
|
546
|
+
promptOverrides: input.promptOverrides,
|
|
496
547
|
});
|
|
497
548
|
if (plan.planHash !== input.planHash) {
|
|
498
549
|
return {
|
|
@@ -530,10 +581,11 @@ export async function executeDagRerun(input) {
|
|
|
530
581
|
plan,
|
|
531
582
|
reason: input.reason,
|
|
532
583
|
requestId: input.requestId,
|
|
584
|
+
promptOverrides: input.promptOverrides,
|
|
533
585
|
});
|
|
534
586
|
const continuationOptions = {
|
|
535
587
|
cwd: input.cwd,
|
|
536
|
-
spec:
|
|
588
|
+
spec: staged.spec,
|
|
537
589
|
state: staged.state,
|
|
538
590
|
runDir: staged.runDir,
|
|
539
591
|
...(input.maxConcurrent !== undefined
|