@tea-agent/loop-agent 0.39.0-beta.2 → 0.39.0-beta.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 +187 -92
- package/README.md +9 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- 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 +429 -28
- package/dist/commands/inspect-next.js +8 -0
- package/dist/executors/dag-pi-executor.js +1691 -29
- 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 +180 -33
- package/dist/executors/shell-executor.js +1152 -171
- package/dist/executors/shell-write-guard.js +14 -0
- package/dist/shared/dag-prompt-override.js +28 -0
- package/dist/shared/openspec-spec.js +70 -4
- 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/config-types.js +19 -5
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +60 -17
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +36 -0
- package/dist/worker/cli.js +31 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/routes.js +1 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operation-store.js +226 -6
- package/dist/worker/console/operator-actions.js +263 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/routes.js +43 -2
- package/dist/worker/console/security.js +63 -9
- package/dist/worker/console/server.js +151 -6
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-zsNyvGaH.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BDjZ5kE1.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-BkgaujnN.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-CqxOJi0j.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-C2JHLdRi.js} +1 -1
- package/dist/worker/console/static/assets/channel-BuqaGKAT.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-DF_YSvOS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-D3Ort5Vo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-XfAfZeUC.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-SLBibhf5.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-Dz29Ye_f.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DB2G4H8V.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-CIoGkxdu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-CG78FGkn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-ZDHAvXPd.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BKkkC4VA.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DNdSHjH9.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DNdSHjH9.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-SposUZsO.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-BSCtQpD7.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-DgFHrw9l.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-BVBasYbj.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-6r_4rN_c.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-JmuaB41P.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-xXvAY3sk.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-D2B3lY2o.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-tK54tMEX.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CNtELzV-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-C1xKMKcW.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CZ7dlPMz.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BqGb42Xe.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-D_BlXEtt.js} +1 -1
- package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-D5zGX6fG.js} +68 -68
- package/dist/worker/console/static/assets/index-DBbhESQ_.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Vce2vmIe.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DDwVBAvI.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-y_Ew-wv4.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-CjXJFp1K.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-1itMst1Q.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-C-D3ZRUa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BiYywmZc.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-l2fydNWA.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-Dz0hNbYW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-BujI_mLZ.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-pWe84beb.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-ByYu2frg.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-CoPnilFP.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-1Md2LnS0.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DqqqAoSH.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C4sGkf--.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8rmop_9.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-ByxJrUzW.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B0ky-kBM.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-D8MLgM0O.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DnBis-wZ.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-uuyYT7UO.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-BjgabhwK.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/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 +20 -2
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/failure-category.js +12 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +366 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +378 -160
- package/dist/workflows/dag/frontend-prewrite-gate.js +343 -391
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +201 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +7 -424
- package/dist/workflows/dag/frontend-review-context.js +195 -8
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +665 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +425 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +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-typed-event-store.js +439 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +52 -17
- package/dist/workflows/dag/frontend-worktree-diff.js +97 -18
- package/dist/workflows/dag/frontend-writer-admission.js +260 -0
- package/dist/workflows/dag/frontend-writer-status.js +244 -0
- package/dist/workflows/dag/init-hybrid.js +572 -500
- package/dist/workflows/dag/node-execution.js +307 -70
- package/dist/workflows/dag/prompt.js +10 -18
- package/dist/workflows/dag/rerun-plan.js +144 -5
- package/dist/workflows/dag/rerun-run.js +58 -6
- package/dist/workflows/dag/retry-policy.js +228 -4
- package/dist/workflows/dag/runner.js +314 -119
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +235 -23
- package/dist/workflows/dag/upstream-artifacts.js +0 -4
- package/dist/workflows/dag/validate.js +60 -10
- package/docs/init-surface.manifest.json +5 -0
- package/docs/operations/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- 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 +3 -3
- package/package.json +5 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-implementation/SKILL.md +4 -4
- package/skills/frontend-implementation/references/design-spec.md +11 -7
- package/skills/frontend-implementation/references/node-contracts.md +20 -11
- package/skills/loop-agent/references/command-reference.md +11 -4
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- 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-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
|
@@ -10,11 +10,15 @@ 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, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, 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 { computeNormalizedFailureFingerprint } from "./frontend-recovery-lineage.js";
|
|
19
|
+
import { readTypedEventStoreFromJsonl } from "./frontend-typed-event-store.js";
|
|
20
|
+
import { allowedRepairReadPaths, auditRepairAttemptToolUse, buildStructuredOutputRepairPrompt, freezeStructuredOutputRepairContext, hasNonEmptyStructuredCandidate, isFrontendStructuredRepairSchemaId, isStructuredRepairableFailureCategory, persistStructuredAttemptRaw, sessionEventsByteLength, GOVERNANCE_BLOCKED_CATEGORY, STRUCTURED_REPAIR_EXHAUSTED_CATEGORY, } from "./structured-output-repair.js";
|
|
21
|
+
import { readFrontendCanonicalCandidate } from "./frontend-implementation-contract.js";
|
|
18
22
|
import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
19
23
|
import { buildProjectGovernanceContext, readCompletedWriterChangeManifests, writeProjectGovernanceContext, } from "./project-governance-context.js";
|
|
20
24
|
import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
|
|
@@ -129,26 +133,29 @@ export function buildNodePrompt(spec, task, upstream, options) {
|
|
|
129
133
|
convergenceFeedback: options?.convergenceFeedback,
|
|
130
134
|
});
|
|
131
135
|
}
|
|
132
|
-
function frontendStructuredArtifactRetryGuidance(schemaId
|
|
133
|
-
const format = citationsRequired
|
|
134
|
-
? "one fenced json block immediately followed by one fenced openspec-citations block"
|
|
135
|
-
: "one fenced json block and no openspec-citations block";
|
|
136
|
+
function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
136
137
|
if (schemaId === "frontend-implementation-contract-plan-patch-v1") {
|
|
137
138
|
return [
|
|
138
|
-
|
|
139
|
-
? "Return exactly one fenced json block containing only the editable RFC 7386 plan patch, immediately followed by one fenced openspec-citations block, and nothing else."
|
|
140
|
-
: `Return exactly ${format} and nothing else; the json block contains only the editable RFC 7386 plan patch.`,
|
|
139
|
+
"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.",
|
|
141
140
|
"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.",
|
|
142
141
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
142
|
+
"The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
|
|
143
|
+
"```openspec-citations",
|
|
144
|
+
'{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
|
|
145
|
+
'{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
|
|
146
|
+
"```",
|
|
143
147
|
];
|
|
144
148
|
}
|
|
145
149
|
if (schemaId === "frontend-implementation-contract-revision-patch-v1") {
|
|
146
150
|
return [
|
|
147
|
-
|
|
148
|
-
? "Return exactly one fenced json block containing the RFC 7386 merge-patch delta, immediately followed by one fenced openspec-citations block, and nothing else."
|
|
149
|
-
: `Return exactly ${format} and nothing else; the json block contains the RFC 7386 merge-patch delta.`,
|
|
151
|
+
"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.",
|
|
150
152
|
"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.",
|
|
151
153
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
154
|
+
"The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
|
|
155
|
+
"```openspec-citations",
|
|
156
|
+
'{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
|
|
157
|
+
'{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
|
|
158
|
+
"```",
|
|
152
159
|
];
|
|
153
160
|
}
|
|
154
161
|
return [
|
|
@@ -156,30 +163,62 @@ function frontendStructuredArtifactRetryGuidance(schemaId, citationsRequired = t
|
|
|
156
163
|
"The contract JSON fields are the complete implementation plan. Do not emit a separate plan document, Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
157
164
|
];
|
|
158
165
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
const FRONTEND_PLAN_NODE_ID = "frontend-plan-pi";
|
|
167
|
+
function isFrontendPlanLadderTask(task) {
|
|
168
|
+
return task.id === FRONTEND_PLAN_NODE_ID;
|
|
162
169
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
"
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Read the committed plan ledger snapshot for the ladder's "no new committed
|
|
172
|
+
* fact" check. The digest is over the sorted committed payload hashes, and
|
|
173
|
+
* `hasTerminalFact` reflects a committed `finalize_plan` terminal fact.
|
|
174
|
+
*/
|
|
175
|
+
async function readFrontendPlanCommittedSnapshot(runDir, nodeId) {
|
|
176
|
+
const records = await readTypedEventStoreFromJsonl(path.join(runDir, nodeId, "plan-typed-facts.jsonl"));
|
|
177
|
+
const committed = records.filter((record) => record.phase === "committed");
|
|
178
|
+
const digest = createHash("sha256")
|
|
179
|
+
.update(committed
|
|
180
|
+
.map((record) => record.payloadSha256)
|
|
181
|
+
.sort()
|
|
182
|
+
.join("\n"))
|
|
183
|
+
.digest("hex");
|
|
184
|
+
const hasTerminalFact = committed.some((record) => record.fact.kind === "finalize_plan");
|
|
185
|
+
return { digest, hasTerminalFact };
|
|
179
186
|
}
|
|
180
|
-
function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics) {
|
|
187
|
+
function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep) {
|
|
181
188
|
if (attemptNumber <= 1)
|
|
182
189
|
return basePrompt;
|
|
190
|
+
if (frontendPlanRetryStep === "compact-terminal-first") {
|
|
191
|
+
return [
|
|
192
|
+
basePrompt,
|
|
193
|
+
"",
|
|
194
|
+
"<retry_instruction>",
|
|
195
|
+
"Frontend plan retry ladder step: compact-terminal-first.",
|
|
196
|
+
"Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts, fill the missing required facts, then call finalize_plan exactly once.",
|
|
197
|
+
"Keep the existing committed ledger intact; do not re-derive already committed facts.",
|
|
198
|
+
"</retry_instruction>",
|
|
199
|
+
].join("\n");
|
|
200
|
+
}
|
|
201
|
+
if (frontendPlanRetryStep === "bounded-tool-only") {
|
|
202
|
+
return [
|
|
203
|
+
basePrompt,
|
|
204
|
+
"",
|
|
205
|
+
"<retry_instruction>",
|
|
206
|
+
"Frontend plan retry ladder step: bounded-tool-only.",
|
|
207
|
+
"Reduce reasoning and wall-clock time. Use only read / fact / terminal tools and only for the necessary missing facts, then call finalize_plan exactly once.",
|
|
208
|
+
"Do not expand scope or re-derive already committed facts.",
|
|
209
|
+
"</retry_instruction>",
|
|
210
|
+
].join("\n");
|
|
211
|
+
}
|
|
212
|
+
if (frontendPlanRetryStep === "backup-model") {
|
|
213
|
+
return [
|
|
214
|
+
basePrompt,
|
|
215
|
+
"",
|
|
216
|
+
"<retry_instruction>",
|
|
217
|
+
"Frontend plan retry ladder step: backup-model.",
|
|
218
|
+
"A backup provider/model route was selected after repeated non-converging failures. Re-derive only the missing committed facts, then call finalize_plan exactly once; do not repeat the failed strategy.",
|
|
219
|
+
"</retry_instruction>",
|
|
220
|
+
].join("\n");
|
|
221
|
+
}
|
|
183
222
|
if (previousFailureCategory === "protocol-invalid" &&
|
|
184
223
|
task.outputProtocol &&
|
|
185
224
|
previousProtocolReason) {
|
|
@@ -192,13 +231,23 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
192
231
|
if (previousFailureCategory === "invalid-output" &&
|
|
193
232
|
task.structuredContractOutput &&
|
|
194
233
|
previousProtocolReason) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
234
|
+
// The frontend plan node's compile authority is the committed typed
|
|
235
|
+
// ledger, not a fenced JSON text artifact: its retry guidance must
|
|
236
|
+
// direct the model to re-commit corrected record_* facts and
|
|
237
|
+
// finalize_plan. The legacy full-contract JSON guidance below applies
|
|
238
|
+
// only to nodes whose authority is still a text contract artifact.
|
|
239
|
+
if (task.structuredContractOutput.schemaId ===
|
|
240
|
+
"frontend-implementation-contract-plan-patch-v1") {
|
|
241
|
+
return [
|
|
242
|
+
basePrompt,
|
|
243
|
+
"",
|
|
244
|
+
"<retry_instruction>",
|
|
245
|
+
"Previous plan ledger facts failed canonical contract validation:",
|
|
246
|
+
previousProtocolReason,
|
|
247
|
+
"Fix the reported violations by re-committing corrected record_* facts and calling finalize_plan exactly once. The committed typed ledger is the only compile authority.",
|
|
248
|
+
"Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
|
|
249
|
+
"</retry_instruction>",
|
|
250
|
+
].join("\n");
|
|
202
251
|
}
|
|
203
252
|
return [
|
|
204
253
|
basePrompt,
|
|
@@ -213,14 +262,17 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
213
262
|
}
|
|
214
263
|
if (previousFailureCategory === "structured-output-truncated" &&
|
|
215
264
|
task.structuredContractOutput) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
265
|
+
if (task.structuredContractOutput.schemaId ===
|
|
266
|
+
"frontend-implementation-contract-plan-patch-v1") {
|
|
267
|
+
return [
|
|
268
|
+
basePrompt,
|
|
269
|
+
"",
|
|
270
|
+
"<retry_instruction>",
|
|
271
|
+
"Previous attempt was truncated by the provider (stopReason=length) before the plan facts were fully committed.",
|
|
272
|
+
"Re-commit the missing record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority.",
|
|
273
|
+
"Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
|
|
274
|
+
"</retry_instruction>",
|
|
275
|
+
].join("\n");
|
|
224
276
|
}
|
|
225
277
|
return [
|
|
226
278
|
basePrompt,
|
|
@@ -293,17 +345,32 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
293
345
|
"</retry_instruction>",
|
|
294
346
|
].join("\n");
|
|
295
347
|
}
|
|
348
|
+
if (previousFailureCategory === "writer-clean-timeout") {
|
|
349
|
+
return [
|
|
350
|
+
basePrompt,
|
|
351
|
+
"",
|
|
352
|
+
"<retry_instruction>",
|
|
353
|
+
"Previous attempt ended in a clean provider/transport failure with zero attributed workspace writes and zero write/edit tool calls.",
|
|
354
|
+
"Do not invent existing implementation progress. Continue from the original task only if the workspace is still unchanged.",
|
|
355
|
+
"</retry_instruction>",
|
|
356
|
+
].join("\n");
|
|
357
|
+
}
|
|
296
358
|
if (previousFailureCategory !== "output-too-large") {
|
|
297
359
|
return basePrompt;
|
|
298
360
|
}
|
|
299
361
|
if (task.outputMode === "structured-required") {
|
|
300
362
|
if (task.structuredContractOutput) {
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
363
|
+
if (task.structuredContractOutput.schemaId ===
|
|
364
|
+
"frontend-implementation-contract-plan-patch-v1") {
|
|
365
|
+
return [
|
|
366
|
+
basePrompt,
|
|
367
|
+
"",
|
|
368
|
+
"<retry_instruction>",
|
|
369
|
+
"Previous attempt exceeded the structured output size limit.",
|
|
370
|
+
"Re-commit corrected record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority.",
|
|
371
|
+
"Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
|
|
372
|
+
"</retry_instruction>",
|
|
373
|
+
].join("\n");
|
|
307
374
|
}
|
|
308
375
|
return [
|
|
309
376
|
basePrompt,
|
|
@@ -725,7 +792,7 @@ export async function executeDagNode(input) {
|
|
|
725
792
|
}
|
|
726
793
|
node.resolvedSkills = resolvedSkills;
|
|
727
794
|
await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
|
|
728
|
-
|
|
795
|
+
let model = resolveModelForTask(task, spec.executorModels);
|
|
729
796
|
let thinking;
|
|
730
797
|
if (task.executor === "pi") {
|
|
731
798
|
try {
|
|
@@ -736,16 +803,25 @@ export async function executeDagNode(input) {
|
|
|
736
803
|
// Invalid or missing harness preserves the previous no-override behavior.
|
|
737
804
|
}
|
|
738
805
|
}
|
|
739
|
-
const
|
|
806
|
+
const specRetryPolicy = task.retryPolicy &&
|
|
740
807
|
(isSafeReadOnlyPiRetryCandidate(task) ||
|
|
741
808
|
isWriterEmptyDiffRetryCandidate(task) ||
|
|
742
809
|
isWriterTransportRetryCandidate(task))
|
|
743
810
|
? task.retryPolicy
|
|
744
811
|
: undefined;
|
|
812
|
+
const implicitVerifyRetryPolicy = specRetryPolicy === undefined &&
|
|
813
|
+
isCanonicalFinalVerifyShellRetryCandidate(task) &&
|
|
814
|
+
isTargetTemplateTransientRetryNode(task)
|
|
815
|
+
? VERIFY_SHELL_RETRY_POLICY
|
|
816
|
+
: undefined;
|
|
817
|
+
const retryPolicy = specRetryPolicy ?? implicitVerifyRetryPolicy;
|
|
745
818
|
const maxAttempts = retryPolicy?.maxAttempts ?? 1;
|
|
746
819
|
const attempts = [];
|
|
747
820
|
let totalAttemptWallDurationMs = 0;
|
|
748
821
|
let totalBackoffMs = 0;
|
|
822
|
+
const frontendPlanLadderEnabled = isFrontendPlanLadderTask(task);
|
|
823
|
+
let frontendPlanRetryStep = "normal";
|
|
824
|
+
const frontendPriorFingerprints = [];
|
|
749
825
|
const livenessPolicy = resolveLivenessPolicy(spec.defaults?.livenessPolicy, task.livenessPolicy);
|
|
750
826
|
/**
|
|
751
827
|
* Attempt-fenced, throttled activity sink. Late events from a previous
|
|
@@ -783,10 +859,24 @@ export async function executeDagNode(input) {
|
|
|
783
859
|
let terminalResult;
|
|
784
860
|
let previousFailureCategory;
|
|
785
861
|
let previousProtocolReason;
|
|
862
|
+
let pendingStructuredArtifact;
|
|
863
|
+
let lastRepairAllowlist = [];
|
|
786
864
|
for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
|
|
787
865
|
const attemptStartedAt = new Date().toISOString();
|
|
788
866
|
node.currentAttempt = attemptNumber;
|
|
789
867
|
node.livenessStatus = "active";
|
|
868
|
+
const beforeCommittedDigest = frontendPlanLadderEnabled
|
|
869
|
+
? (await readFrontendPlanCommittedSnapshot(runDir, nodeId)).digest
|
|
870
|
+
: undefined;
|
|
871
|
+
if (frontendPlanLadderEnabled &&
|
|
872
|
+
frontendPlanRetryStep === "backup-model") {
|
|
873
|
+
model = resolveModelForTask(task, spec.executorModels);
|
|
874
|
+
}
|
|
875
|
+
// Capture the session-events.jsonl length BEFORE the executor appends this
|
|
876
|
+
// attempt's events, so the post-attempt repair tool audit can be scoped to
|
|
877
|
+
// exactly this attempt's segment (B1: earlier attempts legitimately use
|
|
878
|
+
// ls/find and must not be attributed to the repair attempt).
|
|
879
|
+
const attemptSessionEventsOffset = await sessionEventsByteLength(runDir, nodeId);
|
|
790
880
|
let result;
|
|
791
881
|
try {
|
|
792
882
|
validateRepairArtifactGateBeforeShell({
|
|
@@ -809,12 +899,60 @@ export async function executeDagNode(input) {
|
|
|
809
899
|
(acc[i.path] ??= []).push(i.detail);
|
|
810
900
|
return acc;
|
|
811
901
|
}, {});
|
|
902
|
+
let attemptPrompt = buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep);
|
|
903
|
+
if (attemptNumber > 1 &&
|
|
904
|
+
// The plan node (frontend-plan-pi) is a typed-facts ladder task:
|
|
905
|
+
// its retry is driven by the §5.1 ladder (compact-terminal-first
|
|
906
|
+
// / backup-model) and its compile authority is the committed
|
|
907
|
+
// ledger — never the legacy frozen fenced-JSON repair. The
|
|
908
|
+
// frozen repair path below applies only to non-ladder
|
|
909
|
+
// structured nodes whose output authority is a text artifact.
|
|
910
|
+
!frontendPlanLadderEnabled &&
|
|
911
|
+
isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
|
|
912
|
+
isStructuredRepairableFailureCategory(previousFailureCategory) &&
|
|
913
|
+
(await hasNonEmptyStructuredCandidate({
|
|
914
|
+
runDir,
|
|
915
|
+
nodeId,
|
|
916
|
+
beforeAttempt: attemptNumber,
|
|
917
|
+
}))) {
|
|
918
|
+
const originalCanonical = task.structuredContractOutput?.schemaId ===
|
|
919
|
+
"frontend-implementation-contract-revision-patch-v1"
|
|
920
|
+
? (await readFrontendCanonicalCandidate(runDir, "frontend-plan-pi"))
|
|
921
|
+
: undefined;
|
|
922
|
+
const frozen = await freezeStructuredOutputRepairContext({
|
|
923
|
+
runDir,
|
|
924
|
+
runId: state.runId,
|
|
925
|
+
taskId: spec.sourceBinding?.taskId ?? task.id,
|
|
926
|
+
nodeId,
|
|
927
|
+
nodeAttempt: attemptNumber,
|
|
928
|
+
targetSchemaId: task.structuredContractOutput.schemaId,
|
|
929
|
+
skeleton: task.structuredContractOutput?.skeleton,
|
|
930
|
+
originalCanonical,
|
|
931
|
+
latestStopReason: previousFailureCategory === "structured-output-truncated"
|
|
932
|
+
? "length"
|
|
933
|
+
: previousFailureCategory,
|
|
934
|
+
});
|
|
935
|
+
lastRepairAllowlist = allowedRepairReadPaths({
|
|
936
|
+
runDir,
|
|
937
|
+
context: frozen.context,
|
|
938
|
+
contextPath: frozen.relativePath,
|
|
939
|
+
});
|
|
940
|
+
attemptPrompt = await buildStructuredOutputRepairPrompt({
|
|
941
|
+
runDir,
|
|
942
|
+
context: frozen.context,
|
|
943
|
+
contextPath: frozen.relativePath,
|
|
944
|
+
contextSha256: frozen.sha256,
|
|
945
|
+
maxAttempts,
|
|
946
|
+
schemaGuidance: frontendStructuredArtifactRetryGuidance(task.structuredContractOutput.schemaId),
|
|
947
|
+
previousReason: previousProtocolReason,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
812
950
|
result = await executeNode({
|
|
813
951
|
task,
|
|
814
952
|
cwd,
|
|
815
953
|
model,
|
|
816
954
|
...(thinking ? { thinking } : {}),
|
|
817
|
-
prompt:
|
|
955
|
+
prompt: attemptPrompt,
|
|
818
956
|
attempt: attemptNumber,
|
|
819
957
|
reportActivity,
|
|
820
958
|
timeoutMs: livenessPolicy.absoluteMaxWallClockMs,
|
|
@@ -830,6 +968,35 @@ export async function executeDagNode(input) {
|
|
|
830
968
|
durationMs: 0,
|
|
831
969
|
};
|
|
832
970
|
}
|
|
971
|
+
if (isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId)) {
|
|
972
|
+
const rawText = canonicalNodeOutput(result);
|
|
973
|
+
if (rawText.trim().length > 0) {
|
|
974
|
+
await persistStructuredAttemptRaw({
|
|
975
|
+
runDir,
|
|
976
|
+
nodeId,
|
|
977
|
+
attempt: attemptNumber,
|
|
978
|
+
text: rawText,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
if (attemptNumber > 1 && lastRepairAllowlist.length > 0) {
|
|
982
|
+
const audit = await auditRepairAttemptToolUse({
|
|
983
|
+
runDir,
|
|
984
|
+
nodeId,
|
|
985
|
+
allowedPaths: lastRepairAllowlist,
|
|
986
|
+
fromByteOffset: attemptSessionEventsOffset,
|
|
987
|
+
});
|
|
988
|
+
if (!audit.ok) {
|
|
989
|
+
result = {
|
|
990
|
+
...result,
|
|
991
|
+
ok: false,
|
|
992
|
+
failureCategory: GOVERNANCE_BLOCKED_CATEGORY,
|
|
993
|
+
stderr: [result.stderr, audit.reason]
|
|
994
|
+
.filter(Boolean)
|
|
995
|
+
.join("\n"),
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
833
1000
|
// R0: executor ok=true still fails closed when outputProtocol is violated.
|
|
834
1001
|
// Valid semantic results (e.g. VERDICT: request-revision) pass validation.
|
|
835
1002
|
if (result.ok && task.outputProtocol) {
|
|
@@ -866,16 +1033,17 @@ export async function executeDagNode(input) {
|
|
|
866
1033
|
structuredContractOutput: task.structuredContractOutput,
|
|
867
1034
|
});
|
|
868
1035
|
if (!contractCheck.ok) {
|
|
869
|
-
// A provider stopReason=length means the response was cut before the
|
|
870
|
-
// JSON contract could complete; separate it from an ordinary bad
|
|
871
|
-
// contract so the retry switches to a JSON-only output strategy.
|
|
872
1036
|
const attemptStopReason = result.stopReason;
|
|
1037
|
+
const failureCategory = contractCheck.classification === "governance-blocked"
|
|
1038
|
+
? GOVERNANCE_BLOCKED_CATEGORY
|
|
1039
|
+
: attemptStopReason === "length" ||
|
|
1040
|
+
contractCheck.classification === "truncated"
|
|
1041
|
+
? "structured-output-truncated"
|
|
1042
|
+
: "invalid-output";
|
|
873
1043
|
result = {
|
|
874
1044
|
...result,
|
|
875
1045
|
ok: false,
|
|
876
|
-
failureCategory
|
|
877
|
-
? "structured-output-truncated"
|
|
878
|
-
: "invalid-output",
|
|
1046
|
+
failureCategory,
|
|
879
1047
|
stderr: [result.stderr, contractCheck.reason]
|
|
880
1048
|
.filter(Boolean)
|
|
881
1049
|
.join("\n"),
|
|
@@ -883,14 +1051,6 @@ export async function executeDagNode(input) {
|
|
|
883
1051
|
previousProtocolReason = contractCheck.reason;
|
|
884
1052
|
}
|
|
885
1053
|
else {
|
|
886
|
-
if (contractCheck.structuredArtifact) {
|
|
887
|
-
node.structuredArtifactPath = contractCheck.structuredArtifact.path;
|
|
888
|
-
node.structuredArtifactSha256 = contractCheck.structuredArtifact.sha256;
|
|
889
|
-
node.structuredArtifactSchemaId = contractCheck.structuredArtifact.schemaId;
|
|
890
|
-
}
|
|
891
|
-
if (contractCheck.frontendPlanOutput) {
|
|
892
|
-
node.frontendPlanOutput = contractCheck.frontendPlanOutput;
|
|
893
|
-
}
|
|
894
1054
|
if (contractCheck.normalizedText) {
|
|
895
1055
|
result = {
|
|
896
1056
|
...result,
|
|
@@ -898,6 +1058,9 @@ export async function executeDagNode(input) {
|
|
|
898
1058
|
stdout: contractCheck.normalizedText,
|
|
899
1059
|
};
|
|
900
1060
|
}
|
|
1061
|
+
if (contractCheck.artifact) {
|
|
1062
|
+
pendingStructuredArtifact = contractCheck.artifact;
|
|
1063
|
+
}
|
|
901
1064
|
previousProtocolReason = undefined;
|
|
902
1065
|
}
|
|
903
1066
|
}
|
|
@@ -910,6 +1073,7 @@ export async function executeDagNode(input) {
|
|
|
910
1073
|
const attemptFinishedAt = new Date().toISOString();
|
|
911
1074
|
const attemptWallDurationMs = durationBetween(attemptStartedAt, attemptFinishedAt);
|
|
912
1075
|
totalAttemptWallDurationMs += attemptWallDurationMs;
|
|
1076
|
+
const rawFailureCategory = result.rawFailureCategory ?? result.failureCategory;
|
|
913
1077
|
const attemptRecord = {
|
|
914
1078
|
attempt: attemptNumber,
|
|
915
1079
|
startedAt: attemptStartedAt,
|
|
@@ -921,6 +1085,7 @@ export async function executeDagNode(input) {
|
|
|
921
1085
|
stderr: result.stderr,
|
|
922
1086
|
assistantText: result.assistantText,
|
|
923
1087
|
failureCategory: result.failureCategory,
|
|
1088
|
+
...(rawFailureCategory ? { rawFailureCategory } : {}),
|
|
924
1089
|
backend: result.backend,
|
|
925
1090
|
sdkAttempted: result.sdkAttempted,
|
|
926
1091
|
tokensUsed: result.tokensUsed,
|
|
@@ -935,6 +1100,60 @@ export async function executeDagNode(input) {
|
|
|
935
1100
|
await writeDagNodeJsonArtifact(runDir, nodeId, `attempt-${attemptNumber}.json`, attemptRecord);
|
|
936
1101
|
node.attempts = attempts;
|
|
937
1102
|
}
|
|
1103
|
+
// Frontend plan retry ladder: project the 7-value protocol failure
|
|
1104
|
+
// reason, detect new committed facts, and escalate instead of re-running.
|
|
1105
|
+
if (frontendPlanLadderEnabled && !result.ok) {
|
|
1106
|
+
const afterSnapshot = await readFrontendPlanCommittedSnapshot(runDir, nodeId);
|
|
1107
|
+
const hasNewCommittedFact = beforeCommittedDigest !== undefined &&
|
|
1108
|
+
afterSnapshot.digest !== beforeCommittedDigest;
|
|
1109
|
+
const protocolReason = projectFrontendNodeProtocolFailureReason({
|
|
1110
|
+
failureCategory: result.failureCategory,
|
|
1111
|
+
stopReason: result.stopReason,
|
|
1112
|
+
assistantText: result.assistantText ?? result.stdout,
|
|
1113
|
+
hasTerminalFact: afterSnapshot.hasTerminalFact,
|
|
1114
|
+
});
|
|
1115
|
+
const fingerprint = computeNormalizedFailureFingerprint({
|
|
1116
|
+
failureOwner: "plan",
|
|
1117
|
+
protocolFailureReason: protocolReason ?? "",
|
|
1118
|
+
});
|
|
1119
|
+
const backupRoute = resolveFrontendPlanBackupRoute(protocolReason);
|
|
1120
|
+
const nextStep = resolveFrontendPlanRetryStep({
|
|
1121
|
+
currentStep: frontendPlanRetryStep,
|
|
1122
|
+
protocolFailureReason: protocolReason,
|
|
1123
|
+
hasNewCommittedFact,
|
|
1124
|
+
fingerprint,
|
|
1125
|
+
priorFingerprints: frontendPriorFingerprints,
|
|
1126
|
+
backupRouteAvailable: backupRoute.ok,
|
|
1127
|
+
});
|
|
1128
|
+
frontendPriorFingerprints.push(fingerprint);
|
|
1129
|
+
frontendPlanRetryStep = nextStep;
|
|
1130
|
+
if (nextStep === "unsupported-provider-capability") {
|
|
1131
|
+
result = {
|
|
1132
|
+
...result,
|
|
1133
|
+
ok: false,
|
|
1134
|
+
failureCategory: "unsupported-provider-capability",
|
|
1135
|
+
stderr: [
|
|
1136
|
+
result.stderr,
|
|
1137
|
+
"frontend plan retry ladder: no compatible provider capability route for the protocol failure reason",
|
|
1138
|
+
]
|
|
1139
|
+
.filter(Boolean)
|
|
1140
|
+
.join("\n"),
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
else if (nextStep === "non-converging") {
|
|
1144
|
+
result = {
|
|
1145
|
+
...result,
|
|
1146
|
+
ok: false,
|
|
1147
|
+
failureCategory: "non-converging",
|
|
1148
|
+
stderr: [
|
|
1149
|
+
result.stderr,
|
|
1150
|
+
"frontend plan retry ladder: repeated failure fingerprint with no new committed fact",
|
|
1151
|
+
]
|
|
1152
|
+
.filter(Boolean)
|
|
1153
|
+
.join("\n"),
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
938
1157
|
// Reflect the latest attempt on the node so progress is observable,
|
|
939
1158
|
// but keep node.status RUNNING while retry is still possible.
|
|
940
1159
|
node.durationMs = durationBetween(node.startedAt, attemptFinishedAt);
|
|
@@ -968,13 +1187,26 @@ export async function executeDagNode(input) {
|
|
|
968
1187
|
break;
|
|
969
1188
|
const canRetry = retryPolicy !== undefined && attemptNumber < maxAttempts;
|
|
970
1189
|
const retryable = retryPolicy !== undefined &&
|
|
1190
|
+
result.failureCategory !== GOVERNANCE_BLOCKED_CATEGORY &&
|
|
971
1191
|
(isRetryablePiFailureCategory(result.failureCategory, {
|
|
972
1192
|
retryCategories: retryPolicy.retryCategories,
|
|
973
1193
|
}) ||
|
|
974
1194
|
(result.failureCategory === "protocol-invalid" &&
|
|
975
1195
|
task.outputProtocol?.retryOnInvalid === true));
|
|
976
|
-
if (!canRetry || !retryable)
|
|
1196
|
+
if (!canRetry || !retryable) {
|
|
1197
|
+
if (!canRetry &&
|
|
1198
|
+
isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
|
|
1199
|
+
isStructuredRepairableFailureCategory(result.failureCategory)) {
|
|
1200
|
+
result = {
|
|
1201
|
+
...result,
|
|
1202
|
+
failureCategory: STRUCTURED_REPAIR_EXHAUSTED_CATEGORY,
|
|
1203
|
+
};
|
|
1204
|
+
terminalResult = result;
|
|
1205
|
+
previousFailureCategory = result.failureCategory;
|
|
1206
|
+
node.failureCategory = result.failureCategory;
|
|
1207
|
+
}
|
|
977
1208
|
break;
|
|
1209
|
+
}
|
|
978
1210
|
const delayMs = computeBackoffDelayMs(attemptNumber + 1, retryPolicy);
|
|
979
1211
|
if (delayMs > 0) {
|
|
980
1212
|
const backoffStartedAt = new Date().toISOString();
|
|
@@ -1123,6 +1355,11 @@ export async function executeDagNode(input) {
|
|
|
1123
1355
|
node.structuredArtifactSchemaId = "backend-test-result-v1";
|
|
1124
1356
|
}
|
|
1125
1357
|
}
|
|
1358
|
+
else if (pendingStructuredArtifact) {
|
|
1359
|
+
node.structuredArtifactPath = pendingStructuredArtifact.path;
|
|
1360
|
+
node.structuredArtifactSha256 = pendingStructuredArtifact.sha256;
|
|
1361
|
+
node.structuredArtifactSchemaId = pendingStructuredArtifact.schemaId;
|
|
1362
|
+
}
|
|
1126
1363
|
}
|
|
1127
1364
|
const finishedAt = new Date().toISOString();
|
|
1128
1365
|
node.finishedAt = finishedAt;
|
|
@@ -67,7 +67,6 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
|
|
|
67
67
|
const toleratedFailures = new Set(task.failureAwareDependsOn ?? []);
|
|
68
68
|
for (const depId of task.depends_on) {
|
|
69
69
|
const record = upstream[depId];
|
|
70
|
-
const artifactFirst = task.artifactFirstUpstreamNodeIds?.includes(depId);
|
|
71
70
|
const stdout = record?.stdout?.trim() ? record.stdout : "";
|
|
72
71
|
const assistantText = record?.assistantText?.trim()
|
|
73
72
|
? record.assistantText
|
|
@@ -119,23 +118,16 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
|
|
|
119
118
|
}
|
|
120
119
|
if (!record || record.status !== "FINISHED")
|
|
121
120
|
continue;
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
record.
|
|
129
|
-
record.structuredArtifactSchemaId
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
lines.push(`- payload kind: ${record.frontendPlanOutput.payloadKind}`, `- citations: ${record.frontendPlanOutput.citationsArtifactPath}`, `- citations sha256: ${record.frontendPlanOutput.citationsArtifactSha256}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
lines.push("- artifact: unavailable (do not reconstruct the contract from node prose; treat this as an upstream artifact integrity failure)");
|
|
137
|
-
}
|
|
138
|
-
sections.push(lines.join("\n"));
|
|
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"));
|
|
139
131
|
continue;
|
|
140
132
|
}
|
|
141
133
|
if (!upstreamText)
|