@tea-agent/loop-agent 0.42.0-next.9 → 0.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -45
- package/dist/application/dag/run-dag.js +8 -2
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +26 -8
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/cli/program.js +1 -1
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/task-source-prepare.js +3 -1
- package/dist/executors/dag-pi-executor.js +2584 -600
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-extension-resolver.js +14 -2
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +135 -55
- package/dist/shared/dag-failure-category.js +6 -0
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/contract/apply.js +36 -2
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/parse-intent.js +7 -0
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +143 -0
- package/dist/worker/console/chat/browser-routes.js +148 -0
- package/dist/worker/console/chat/chat-event-store.js +4 -2
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime.js +173 -94
- package/dist/worker/console/chat/resource-loader.js +4 -1
- package/dist/worker/console/chat/routes.js +208 -66
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +78 -11
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +27 -7
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
- package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
- package/dist/worker/console/chat/subagents/index.js +5 -0
- package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
- package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
- package/dist/worker/console/chat/subagents/types.js +13 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +284 -0
- package/dist/worker/console/chat/terminal-tools.js +199 -0
- package/dist/worker/console/chat/tool-preview.js +21 -0
- package/dist/worker/console/chat/tools.js +13 -1
- package/dist/worker/console/chat/turn-process.js +1 -0
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
- package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
- package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
- package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/chat-view-types.js +1 -1
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/constants.js +22 -22
- package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
- package/dist/worker/observe/static/dag-history-labels.js +1 -0
- package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
- package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
- package/dist/worker/observe/static/dag-node-purpose.js +10 -10
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/index.html +4 -4
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
- package/dist/worker/observe/static/relations.js +7 -5
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +431 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +51 -4
- package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +64 -22
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +194 -243
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/dag-retry-schema.js +3 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-contract-facts.js +130 -0
- package/dist/workflows/dag/frontend-design-policy.js +103 -17
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +21 -3
- package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
- package/dist/workflows/dag/frontend-recovery-run.js +146 -16
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-risk.js +2 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +46 -7
- package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
- package/dist/workflows/dag/frontend-verification-trace.js +38 -4
- package/dist/workflows/dag/frontend-writer-admission.js +2 -2
- package/dist/workflows/dag/init-hybrid.js +70 -35
- package/dist/workflows/dag/node-execution.js +225 -226
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/recovery-lease.js +106 -16
- package/dist/workflows/dag/rerun-feedback.js +1 -0
- package/dist/workflows/dag/rerun-plan.js +96 -4
- package/dist/workflows/dag/rerun-run.js +28 -6
- package/dist/workflows/dag/rerun-task.js +221 -18
- package/dist/workflows/dag/retry-policy.js +27 -10
- package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
- package/dist/workflows/dag/runner.js +246 -19
- package/dist/workflows/dag/structured-output-repair.js +4 -1
- package/dist/workflows/dag/types.js +10 -3
- package/dist/workflows/dag/validate.js +10 -8
- package/dist/workflows/dag/workspace-checkpoint.js +66 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +6 -4
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +68 -4
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/harness.json +1 -1
- package/package.json +8 -6
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +22 -2
- package/skills/frontend-plan/references/decision-contract.md +114 -5
- package/skills/frontend-plan/references/design-decisions.md +32 -0
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
- package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { collectFrontendExecutionGroups } from "./frontend-execution-groups.js";
|
|
2
|
+
import { reserveDagProviderRequest } from "./budget-enforcement.js";
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import { existsSync } from "node:fs";
|
|
3
5
|
import { readFile } from "node:fs/promises";
|
|
@@ -13,7 +15,7 @@ import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./p
|
|
|
13
15
|
import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
|
|
14
16
|
import { materializeDeclaredArtifactFacts } from "./artifact-bindings.js";
|
|
15
17
|
import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
|
|
16
|
-
import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
|
|
18
|
+
import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, OUTPUT_LIMIT_RETRY_CATEGORY, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
|
|
17
19
|
import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
|
|
18
20
|
import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
|
|
19
21
|
import { getStructuredContractValidator } from "./contract-output-registry.js";
|
|
@@ -21,6 +23,7 @@ import "./contract-validator-registrations.js";
|
|
|
21
23
|
import { computeNormalizedFailureFingerprint } from "./frontend-recovery-lineage.js";
|
|
22
24
|
import { parseLedgerJson } from "../../task/source-prepare/ledger.js";
|
|
23
25
|
import { readTypedEventStoreFromJsonl } from "./frontend-typed-event-store.js";
|
|
26
|
+
import { collectCanonicalStateFlowNames, extractRequiredDeliverablePaths, resolveFrontendContractRequirements, } from "./frontend-contract-facts.js";
|
|
24
27
|
import { allowedRepairReadPaths, auditRepairAttemptToolUse, buildStructuredOutputRepairPrompt, freezeStructuredOutputRepairContext, hasNonEmptyStructuredCandidate, isFrontendStructuredRepairSchemaId, isStructuredRepairableFailureCategory, persistStructuredAttemptRaw, sessionEventsByteLength, GOVERNANCE_BLOCKED_CATEGORY, STRUCTURED_REPAIR_EXHAUSTED_CATEGORY, } from "./structured-output-repair.js";
|
|
25
28
|
import { readFrontendCanonicalCandidate } from "./frontend-implementation-contract.js";
|
|
26
29
|
import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
@@ -256,6 +259,9 @@ function frontendPlanValidationRetryGuidance(reason) {
|
|
|
256
259
|
if (/verificationTargets.*uiStates|uiStates.*verificationTargets/i.test(reason)) {
|
|
257
260
|
guidance.push("For this retry, provide verificationTargets[].uiStates as an array; use [] when the target has no named UI state.");
|
|
258
261
|
}
|
|
262
|
+
if (/duplicate|already recorded/i.test(reason)) {
|
|
263
|
+
guidance.push("For this retry, re-commit the corrected entry with the same id and replace=true; the ledger compiles the latest submission as the full replacement. A duplicate receipt is a correction invitation, not a prohibition, and backfilling a cited fact's reverse reference in-node does not re-derive committed facts.");
|
|
264
|
+
}
|
|
259
265
|
return guidance;
|
|
260
266
|
}
|
|
261
267
|
function compactRetryText(text, maxChars) {
|
|
@@ -293,6 +299,12 @@ export function buildContextOverflowRetryPrompt(task, basePrompt) {
|
|
|
293
299
|
compactRetrySection(basePrompt, "task", COMPACT_RETRY_TASK_MAX_CHARS),
|
|
294
300
|
"</task>",
|
|
295
301
|
].join("\n"),
|
|
302
|
+
// Plan still needs the complete frozen obligations for ledger ownership
|
|
303
|
+
// and scoped projection. Its executor packs this block before dispatch;
|
|
304
|
+
// dropping or character-truncating it prevents safe progress recovery.
|
|
305
|
+
...(isFrontendPlanLadderTask(task)
|
|
306
|
+
? [basePrompt.match(/<frontend_plan_input>[\s\S]*?<\/frontend_plan_input>/)?.[0] ?? ""]
|
|
307
|
+
: []),
|
|
296
308
|
].join("\n\n");
|
|
297
309
|
}
|
|
298
310
|
function isFrontendPlanLadderTask(task) {
|
|
@@ -325,6 +337,8 @@ const FRONTEND_CONTRACT_RECORD_TOOL_NAMES_LOCAL = new Set([
|
|
|
325
337
|
"record_handoff_intent",
|
|
326
338
|
"record_open_question",
|
|
327
339
|
"record_split_proposal",
|
|
340
|
+
"record_ui_state",
|
|
341
|
+
"record_required_deliverables",
|
|
328
342
|
]);
|
|
329
343
|
async function countContractRecordSubmissions(runDir, nodeId) {
|
|
330
344
|
const eventsPath = path.join(runDir, nodeId, "session-events.jsonl");
|
|
@@ -374,27 +388,14 @@ async function readFrontendPlanCommittedSnapshot(runDir, nodeId) {
|
|
|
374
388
|
function planInputRecord(value) {
|
|
375
389
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
376
390
|
}
|
|
377
|
-
function planInputText(value,
|
|
378
|
-
|
|
379
|
-
return undefined;
|
|
380
|
-
const normalized = value.trim();
|
|
381
|
-
return normalized.length <= maxChars
|
|
382
|
-
? normalized
|
|
383
|
-
: `${normalized.slice(0, maxChars - 1)}…`;
|
|
391
|
+
function planInputText(value, _maxChars) {
|
|
392
|
+
return typeof value === "string" && value.trim().length ? value.trim() : undefined;
|
|
384
393
|
}
|
|
385
394
|
function planInputStrings(value) {
|
|
386
395
|
return Array.isArray(value)
|
|
387
396
|
? value.filter((item) => typeof item === "string")
|
|
388
397
|
: [];
|
|
389
398
|
}
|
|
390
|
-
/** Input bound for the planner evidence block; protects the model input budget. */
|
|
391
|
-
const FRONTEND_PLAN_INPUT_MAX_CHARS = 12_000;
|
|
392
|
-
const FRONTEND_PLAN_INPUT_CAP_LADDER = [
|
|
393
|
-
{ text: 240, array: 40 },
|
|
394
|
-
{ text: 120, array: 20 },
|
|
395
|
-
{ text: 60, array: 10 },
|
|
396
|
-
{ text: 24, array: 4 },
|
|
397
|
-
];
|
|
398
399
|
/**
|
|
399
400
|
* Frozen requirement→PRD citation map for the plan review checklist: lets the
|
|
400
401
|
* model declare sourceRequirementIds whose section/line match the component
|
|
@@ -440,78 +441,19 @@ async function resolveComponentSourceCitations(spec, cwd) {
|
|
|
440
441
|
return new Map();
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
|
-
/** Input bound for the contract node's compiled ledger block. */
|
|
444
|
-
const FRONTEND_CONTRACT_INPUT_MAX_CHARS = 12_000;
|
|
445
444
|
/**
|
|
446
|
-
* Render the contract node's complete
|
|
445
|
+
* Render the contract node's complete semantic ledger handoff. The
|
|
447
446
|
* source-fidelity ledger already extracted canonical requirements with source
|
|
448
447
|
* spans; the contract node confirms and commits them incrementally through
|
|
449
448
|
* record_requirement instead of re-reading the raw source (extreme-environment:
|
|
450
449
|
* a small output window cannot absorb a full source re-read).
|
|
451
450
|
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
451
|
+
* Whole obligations are retained here; the executor selects complete scopes
|
|
452
|
+
* before dispatch. References and conditions are never clipped.
|
|
454
453
|
*/
|
|
455
454
|
export function renderFrontendContractInputContext(input) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// sourceFragmentIds entirely, the model (correctly refusing to invent ids)
|
|
459
|
-
// committed empty bindings, and the plan compile failed the ledger-binding
|
|
460
|
-
// gate for every requirement. Bindings therefore bypass the cap ladder and
|
|
461
|
-
// every degradation level; only requirement TEXTS and fragment CONTEXT
|
|
462
|
-
// (path/headingPath) may degrade. Fragment context is rendered only for
|
|
463
|
-
// fragments actually referenced by a requirement and shrinks first.
|
|
464
|
-
const referencedFragmentIds = new Set(input.canonicalRequirements.flatMap((requirement) => planInputStrings(requirement.sourceFragmentIds)));
|
|
465
|
-
const referencedFragments = input.fragments.filter((fragment) => referencedFragmentIds.has(fragment.id));
|
|
466
|
-
const serializeAtCap = (cap) => JSON.stringify({
|
|
467
|
-
requirements: input.canonicalRequirements.map((requirement) => ({
|
|
468
|
-
id: requirement.id,
|
|
469
|
-
text: planInputText(requirement.text, cap.text),
|
|
470
|
-
sourceFragmentIds: planInputStrings(requirement.sourceFragmentIds),
|
|
471
|
-
})),
|
|
472
|
-
fragments: referencedFragments.map((fragment) => ({
|
|
473
|
-
id: fragment.id,
|
|
474
|
-
path: planInputText(fragment.path, 200),
|
|
475
|
-
headingPath: planInputText(fragment.headingPath, 120),
|
|
476
|
-
lineRange: fragment.lineRange,
|
|
477
|
-
})),
|
|
478
|
-
});
|
|
479
|
-
let serialized = serializeAtCap(FRONTEND_PLAN_INPUT_CAP_LADDER[0]);
|
|
480
|
-
for (const cap of FRONTEND_PLAN_INPUT_CAP_LADDER.slice(1)) {
|
|
481
|
-
if (serialized.length <= FRONTEND_CONTRACT_INPUT_MAX_CHARS)
|
|
482
|
-
break;
|
|
483
|
-
serialized = serializeAtCap(cap);
|
|
484
|
-
}
|
|
485
|
-
if (serialized.length > FRONTEND_CONTRACT_INPUT_MAX_CHARS) {
|
|
486
|
-
// Last resort: keep every requirement id AND its fragment bindings,
|
|
487
|
-
// degrade texts, and shrink referenced fragment context first (halve,
|
|
488
|
-
// then drop context fields, then drop the fragment list entirely).
|
|
489
|
-
// Requirement ids and sourceFragmentIds are never dropped.
|
|
490
|
-
let fragments = referencedFragments.map((fragment) => ({
|
|
491
|
-
id: fragment.id,
|
|
492
|
-
path: planInputText(fragment.path, 120),
|
|
493
|
-
}));
|
|
494
|
-
let requirements = input.canonicalRequirements.map((requirement) => {
|
|
495
|
-
const sourceFragmentIds = planInputStrings(requirement.sourceFragmentIds);
|
|
496
|
-
return {
|
|
497
|
-
id: requirement.id,
|
|
498
|
-
text: "(truncated)",
|
|
499
|
-
// Empty bindings carry no information; omit them so the payload
|
|
500
|
-
// stays inside the char bound when no requirement is bound.
|
|
501
|
-
...(sourceFragmentIds.length > 0 ? { sourceFragmentIds } : {}),
|
|
502
|
-
};
|
|
503
|
-
});
|
|
504
|
-
let bounded = JSON.stringify({ degraded: "requirement-texts-truncated", requirements, fragments });
|
|
505
|
-
while (bounded.length > FRONTEND_CONTRACT_INPUT_MAX_CHARS && fragments.length > 0) {
|
|
506
|
-
fragments = fragments.slice(0, Math.floor(fragments.length / 2));
|
|
507
|
-
bounded = JSON.stringify({
|
|
508
|
-
degraded: "requirement-texts-truncated",
|
|
509
|
-
requirements,
|
|
510
|
-
fragments,
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
serialized = bounded;
|
|
514
|
-
}
|
|
455
|
+
const referenced = new Set(input.canonicalRequirements.flatMap(r => r.sourceFragmentIds));
|
|
456
|
+
const serialized = JSON.stringify({ requirements: input.canonicalRequirements, fragments: input.fragments.filter(f => referenced.has(f.id)) });
|
|
515
457
|
return [
|
|
516
458
|
"<frontend_contract_input>",
|
|
517
459
|
"Canonical requirements extracted by the source-fidelity ledger, compiled by the runner. Treat them as the authoritative requirement inventory: confirm and commit each requirement through record_requirement (one per tool call); the ledger already binds source fragments, so do NOT re-read the raw source files.",
|
|
@@ -536,16 +478,13 @@ export async function buildFrontendContractInputContext(input) {
|
|
|
536
478
|
});
|
|
537
479
|
}
|
|
538
480
|
/**
|
|
539
|
-
* Render the planner's complete
|
|
481
|
+
* Render the planner's complete semantic evidence handoff from committed
|
|
540
482
|
* typed facts. It deliberately excludes upstream response prose and artifact
|
|
541
483
|
* paths: Contract and Scout have already established these facts, so Plan
|
|
542
484
|
* should decide and commit rather than spend another model turn reading them.
|
|
543
485
|
*
|
|
544
|
-
*
|
|
545
|
-
*
|
|
546
|
-
* fallback keeps every requirement id (with `text: "(truncated)"`) while
|
|
547
|
-
* declaring the degradation, so the planner records targeted evidence gaps
|
|
548
|
-
* instead of receiving a silently corrupted tail.
|
|
486
|
+
* Whole obligations are retained. Session packing and scoped projection happen
|
|
487
|
+
* in the executor before dispatch, without dropping text or source identities.
|
|
549
488
|
*/
|
|
550
489
|
export function renderFrontendPlanInputContext(input) {
|
|
551
490
|
const committedFacts = (records) => records.flatMap((record) => record.phase === "committed" && planInputRecord(record.fact)
|
|
@@ -553,8 +492,9 @@ export function renderFrontendPlanInputContext(input) {
|
|
|
553
492
|
: []);
|
|
554
493
|
const contractFacts = committedFacts(input.contractRecords);
|
|
555
494
|
const scoutFacts = committedFacts(input.scoutRecords);
|
|
556
|
-
const
|
|
557
|
-
|
|
495
|
+
const requirementFacts = resolveFrontendContractRequirements(contractFacts);
|
|
496
|
+
const requiredDeliverables = extractRequiredDeliverablePaths(contractFacts);
|
|
497
|
+
const requirements = requirementFacts
|
|
558
498
|
.map((fact) => ({
|
|
559
499
|
id: planInputText(fact.id, 80),
|
|
560
500
|
text: planInputText(fact.text),
|
|
@@ -580,92 +520,50 @@ export function renderFrontendPlanInputContext(input) {
|
|
|
580
520
|
paths: fact.paths,
|
|
581
521
|
conflicts: fact.conflicts,
|
|
582
522
|
}));
|
|
523
|
+
// Authoritative UI states (contract-declared): the source's UI-state table
|
|
524
|
+
// extracted by the contract node. The planner binds these ids instead of
|
|
525
|
+
// inventing list-visibility variants.
|
|
526
|
+
const declaredUiStates = contractFacts
|
|
527
|
+
.filter((fact) => fact.kind === "ui-state-declaration" && fact.origin === "contract")
|
|
528
|
+
.map((fact) => ({
|
|
529
|
+
id: planInputText(fact.id, 80),
|
|
530
|
+
trigger: planInputText(fact.trigger),
|
|
531
|
+
observableOutcome: planInputText(fact.observableOutcome),
|
|
532
|
+
}))
|
|
533
|
+
.filter((state) => state.id !== undefined);
|
|
534
|
+
// Replay registry edits and state-flow removals/additions in commit order.
|
|
535
|
+
const committedUxNames = collectCanonicalStateFlowNames(input.planRecords ?? [], true);
|
|
536
|
+
const committedUiStateNames = [...committedUxNames.uiStateNames];
|
|
537
|
+
const committedInteractionNames = [...committedUxNames.interactionNames];
|
|
583
538
|
// Reviewer-rubric scaffold: the design reviewer re-runs the design-policy
|
|
584
539
|
// checks on the committed facts, so publish the checklist to the producer.
|
|
585
540
|
// Requirements whose contract evidence expects behavioural verification are
|
|
586
541
|
// enumerated explicitly — those are the slots the reviewer finds missing
|
|
587
542
|
// when the plan models interactions ad hoc (r8/r9 findings).
|
|
588
|
-
const behaviorRequiredIds =
|
|
589
|
-
.filter((requirement) =>
|
|
590
|
-
const fact = contractFacts.find((candidate) => candidate.kind === "requirement" &&
|
|
591
|
-
candidate.origin === "contract" &&
|
|
592
|
-
candidate.id === requirement.id);
|
|
593
|
-
const evidence = fact?.evidence;
|
|
594
|
-
return evidence?.behavior === "required";
|
|
595
|
-
})
|
|
543
|
+
const behaviorRequiredIds = requirementFacts
|
|
544
|
+
.filter((requirement) => requirement.evidence.behavior === "required")
|
|
596
545
|
.map((requirement) => requirement.id);
|
|
597
|
-
const
|
|
598
|
-
requirements
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
})),
|
|
603
|
-
targetSurface: targetSurface.map((surface) => ({
|
|
604
|
-
completeness: planInputText(surface.completeness, 32),
|
|
605
|
-
entrypoint: planInputText(surface.entrypoint, cap.text),
|
|
606
|
-
routeOrMount: planInputText(surface.routeOrMount, cap.text),
|
|
607
|
-
implementationPaths: planInputStrings(surface.implementationPaths).slice(0, cap.array),
|
|
608
|
-
testPaths: planInputStrings(surface.testPaths).slice(0, cap.array),
|
|
609
|
-
dataSource: planInputText(surface.dataSource, cap.text),
|
|
610
|
-
allowedPathConflicts: planInputStrings(surface.allowedPathConflicts).slice(0, cap.array),
|
|
611
|
-
unresolvedPaths: planInputStrings(surface.unresolvedPaths).slice(0, cap.array),
|
|
612
|
-
})),
|
|
613
|
-
designEvidence: designEvidence.map((evidence) => ({
|
|
614
|
-
source: planInputText(evidence.source, cap.text),
|
|
615
|
-
paths: planInputStrings(evidence.paths).slice(0, cap.array),
|
|
616
|
-
conflicts: planInputStrings(evidence.conflicts).slice(0, cap.array),
|
|
617
|
-
})),
|
|
546
|
+
const serialized = JSON.stringify({
|
|
547
|
+
requirements, requiredDeliverables, targetSurface, designEvidence, declaredUiStates,
|
|
548
|
+
executionGroups: collectFrontendExecutionGroups(contractFacts.filter(f => f.kind === "requirement").map(f => ({ id: String(f.id), execution: f.execution }))),
|
|
549
|
+
constraints: contractFacts.filter(f => ["constraint", "open-question", "split-proposal", "handoff-intent"].includes(String(f.kind))),
|
|
550
|
+
committedUx: committedUiStateNames.length || committedInteractionNames.length ? { uiStateNames: committedUiStateNames, interactionNames: committedInteractionNames } : undefined,
|
|
618
551
|
});
|
|
619
|
-
let serialized = serializeAtCap(FRONTEND_PLAN_INPUT_CAP_LADDER[0]);
|
|
620
|
-
for (const cap of FRONTEND_PLAN_INPUT_CAP_LADDER.slice(1)) {
|
|
621
|
-
if (serialized.length <= FRONTEND_PLAN_INPUT_MAX_CHARS)
|
|
622
|
-
break;
|
|
623
|
-
serialized = serializeAtCap(cap);
|
|
624
|
-
}
|
|
625
|
-
if (serialized.length > FRONTEND_PLAN_INPUT_MAX_CHARS) {
|
|
626
|
-
// Last resort: keep every requirement id (ids are short and the plan
|
|
627
|
-
// prompt separately lists them) but drop their texts, shrink scout facts
|
|
628
|
-
// to the minimum, and declare the degradation instead of corrupting JSON.
|
|
629
|
-
let fallback = {
|
|
630
|
-
degraded: "requirement-texts-truncated",
|
|
631
|
-
requirements: requirements.map((requirement) => ({
|
|
632
|
-
id: requirement.id,
|
|
633
|
-
text: "(truncated)",
|
|
634
|
-
})),
|
|
635
|
-
targetSurface: targetSurface.map((surface) => ({
|
|
636
|
-
completeness: planInputText(surface.completeness, 32),
|
|
637
|
-
implementationPaths: planInputStrings(surface.implementationPaths).slice(0, FRONTEND_PLAN_INPUT_CAP_LADDER[3].array),
|
|
638
|
-
})),
|
|
639
|
-
designEvidence: [],
|
|
640
|
-
};
|
|
641
|
-
let bounded = JSON.stringify(fallback);
|
|
642
|
-
let keep = fallback.requirements.length;
|
|
643
|
-
while (bounded.length > FRONTEND_PLAN_INPUT_MAX_CHARS &&
|
|
644
|
-
keep > 0) {
|
|
645
|
-
keep = Math.max(0, Math.floor(keep / 2));
|
|
646
|
-
fallback = { ...fallback, requirements: fallback.requirements.slice(0, keep) };
|
|
647
|
-
bounded = JSON.stringify({
|
|
648
|
-
...fallback,
|
|
649
|
-
requirementIdsTruncated: keep < requirements.length,
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
serialized = bounded;
|
|
653
|
-
}
|
|
654
552
|
const checklistLines = [
|
|
655
|
-
"1.
|
|
656
|
-
"2. Every
|
|
657
|
-
"3. Every
|
|
658
|
-
"4.
|
|
553
|
+
"1. Record the GLOBAL UX vocabulary with record_state_registry BEFORE any record_state_flow: one stable behavior-domain name per state/interaction (e.g. planner-task-edit), never one name per AC number, and never a rename of an already-recorded concept. Coverage slices by AC; UX does not.",
|
|
554
|
+
"2. Every recorded interaction/uiState name must be in that registry, and uiState names must use the contract's declared authoritative ids (declaredUiStates below) when present.",
|
|
555
|
+
"3. Every interaction and every applicable UI state must be covered by a uiComponentChoices entry: its purpose equals the name, or the choice lists the name in covers (one choice may cover many ids). A reuse-existing decision without evidencePath is rejected; stylingStrategy alone covers nothing.",
|
|
556
|
+
"4. Every requirement marked (behavior) below needs modelled interactions plus at least one verification target that references it.",
|
|
659
557
|
"5. Verification targets may only reference UI states and requirements you actually recorded (the record_* tools reject unknown references).",
|
|
660
558
|
`Requirements requiring behavioural coverage: ${behaviorRequiredIds.length > 0 ? behaviorRequiredIds.join(", ") : "(none)"}`,
|
|
661
|
-
"6. A decision=new component must declare sourceRequirementIds and pass sourceFragmentId for the frozen PRD fragment whose section matches the component's purpose — the runtime validates that binding and derives specReference (path/section/line); the reviewer checks purpose↔citation consistency.",
|
|
559
|
+
"6. A decision=new component must declare sourceRequirementIds and pass sourceFragmentId for the frozen PRD fragment whose section matches the component's purpose — the runtime validates that binding and derives specReference (path/section/line); the reviewer checks purpose↔citation consistency. A decision=reuse-existing component must pass evidencePath pointing at the existing repo file that proves the reuse.",
|
|
662
560
|
...[...input.componentSourceCitations ?? []]
|
|
663
561
|
.filter(([id]) => behaviorRequiredIds.includes(id))
|
|
664
562
|
.flatMap(([id, citations]) => citations.map((citation) => ` ${id} + ${citation.fragmentId} → ${citation.section}${citation.line ? ` (line ${citation.line})` : ""}`)),
|
|
665
563
|
];
|
|
666
564
|
return [
|
|
667
565
|
"<frontend_plan_input>",
|
|
668
|
-
"Committed Contract/Scout facts, compiled by the runner. Treat them as the complete planning evidence.",
|
|
566
|
+
"Committed Contract/Scout facts, compiled by the runner. Treat them as the complete planning evidence. declaredUiStates are the authoritative UI states from the task source; committedUx (retry attempts) is the UX vocabulary already recorded — reuse those names, never re-invent them.",
|
|
669
567
|
serialized,
|
|
670
568
|
"Do not read upstream artifacts, task sources, or repository files. If this input cannot support a decision, record a genuine evidence gap.",
|
|
671
569
|
"</frontend_plan_input>",
|
|
@@ -692,6 +590,16 @@ export async function buildFrontendPlanInputContext(runDir, componentSourceCitat
|
|
|
692
590
|
// forbids reading anything.
|
|
693
591
|
throw new Error(`frontend-plan-input-unavailable: cannot read committed typed facts (${error instanceof Error ? error.message : String(error)})`);
|
|
694
592
|
}
|
|
593
|
+
// Retry-attempt continuity (UX slice visibility): the plan node's own
|
|
594
|
+
// committed facts are absent on the first attempt and present on retries;
|
|
595
|
+
// a missing file is normal there, not a broken pipeline.
|
|
596
|
+
let planRecords = [];
|
|
597
|
+
try {
|
|
598
|
+
planRecords = await readTypedEventStoreFromJsonl(path.join(runDir, "frontend-plan-pi", "plan-typed-facts.jsonl"));
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
planRecords = [];
|
|
602
|
+
}
|
|
695
603
|
const committedCount = [...contractRecords, ...scoutRecords].filter((record) => record.phase === "committed").length;
|
|
696
604
|
if (committedCount === 0) {
|
|
697
605
|
throw new Error(`frontend-plan-input-unavailable: no committed Contract/Scout facts in ${contractFactsPath} / ${scoutFactsPath}`);
|
|
@@ -699,6 +607,7 @@ export async function buildFrontendPlanInputContext(runDir, componentSourceCitat
|
|
|
699
607
|
return renderFrontendPlanInputContext({
|
|
700
608
|
contractRecords,
|
|
701
609
|
scoutRecords,
|
|
610
|
+
planRecords,
|
|
702
611
|
componentSourceCitations,
|
|
703
612
|
});
|
|
704
613
|
}
|
|
@@ -721,77 +630,27 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
|
|
|
721
630
|
"</retry_instruction>",
|
|
722
631
|
].join("\n");
|
|
723
632
|
}
|
|
724
|
-
if (
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
"",
|
|
728
|
-
"<retry_instruction>",
|
|
729
|
-
"Frontend plan retry ladder step: compact-terminal-first.",
|
|
730
|
-
"Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts, fill the missing required facts, then call finalize_plan exactly once.",
|
|
731
|
-
"Keep the existing committed ledger intact; do not re-derive already committed facts.",
|
|
732
|
-
"</retry_instruction>",
|
|
733
|
-
].join("\n");
|
|
734
|
-
}
|
|
735
|
-
if (frontendPlanRetryStep === "bounded-tool-only") {
|
|
633
|
+
if (task.id === "generate-backend-md-plan-pi" &&
|
|
634
|
+
(previousFailureCategory === "output-too-large" ||
|
|
635
|
+
previousFailureCategory === "invalid-output")) {
|
|
736
636
|
return [
|
|
737
637
|
basePrompt,
|
|
738
638
|
"",
|
|
739
639
|
"<retry_instruction>",
|
|
740
|
-
"
|
|
741
|
-
|
|
742
|
-
"Do not
|
|
743
|
-
"
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
if (frontendPlanRetryStep === "backup-model") {
|
|
747
|
-
return [
|
|
748
|
-
basePrompt,
|
|
749
|
-
"",
|
|
750
|
-
"<retry_instruction>",
|
|
751
|
-
"Frontend plan retry ladder step: backup-model.",
|
|
752
|
-
"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.",
|
|
753
|
-
"</retry_instruction>",
|
|
754
|
-
].join("\n");
|
|
755
|
-
}
|
|
756
|
-
if (previousFailureCategory === "protocol-invalid" &&
|
|
757
|
-
task.outputProtocol &&
|
|
758
|
-
previousProtocolReason) {
|
|
759
|
-
return [
|
|
760
|
-
basePrompt,
|
|
761
|
-
"",
|
|
762
|
-
buildProtocolRetryInstruction(task.outputProtocol, previousProtocolReason),
|
|
763
|
-
].join("\n");
|
|
764
|
-
}
|
|
765
|
-
if (previousFailureCategory === "review-terminal-missing") {
|
|
766
|
-
return [
|
|
767
|
-
basePrompt,
|
|
768
|
-
"",
|
|
769
|
-
"<retry_instruction>",
|
|
770
|
-
"The review emitted a verdict in response text but never committed the authoritative typed terminal tool call (approve_review / request_review_changes). The response text is NOT the authority: no branch or gate reads it.",
|
|
771
|
-
"Call exactly one typed terminal tool to finish: approve_review (implementation passes, no Critical/Important findings) or request_review_changes (with typed issueCategory, at least one evidenceRef, and non-empty findings). Do not repeat the review analysis; commit the terminal tool once and stop.",
|
|
772
|
-
"</retry_instruction>",
|
|
773
|
-
].join("\n");
|
|
774
|
-
}
|
|
775
|
-
if (previousFailureCategory === "read-burst") {
|
|
776
|
-
if (task.id !== FRONTEND_PLAN_NODE_ID) {
|
|
777
|
-
return [
|
|
778
|
-
basePrompt,
|
|
779
|
-
"",
|
|
780
|
-
"<retry_instruction>",
|
|
781
|
-
"The previous attempt exceeded its read budget. Start a fresh, evidence-minimal pass: do not re-read task sources, upstream stdout, skills, or full diff artifacts already represented by a canonical context artifact.",
|
|
782
|
-
"Read the smallest relevant summary first, then only the specific source file or diff fragment needed for the required decision. Finish the required terminal/output protocol as soon as evidence is sufficient.",
|
|
783
|
-
"</retry_instruction>",
|
|
784
|
-
].join("\n");
|
|
785
|
-
}
|
|
786
|
-
return [
|
|
787
|
-
basePrompt,
|
|
788
|
-
"",
|
|
789
|
-
"<retry_instruction>",
|
|
790
|
-
"Previous plan attempt issued too many read-only tool calls (read/grep/ls/find) and blew up the context window. Trust the upstream frontend-contract-pi typed requirement facts and frontend-scout-pi target surface already provided — do NOT re-read contract/scout stdout, PRD/source files, or component sources you already inspected.",
|
|
791
|
-
"Minimize discovery reads: only read what you genuinely need, once. Commit record_plan_requirement / record_plan_verification_target / record_* facts directly from the facts already in context (one tool call per message), then call finalize_plan exactly once.",
|
|
640
|
+
"The previous backend-test plan attempt was truncated or failed its mandatory Markdown protocol.",
|
|
641
|
+
previousProtocolReason ?? "The previous plan artifact was incomplete.",
|
|
642
|
+
"Return the final Markdown artifact immediately. Do not output analysis, reasoning, source summaries, or planning narration.",
|
|
643
|
+
"Emit the complete section skeleton first, including exactly one ## Coverage Scope, exactly one ## Coverage Matrix, optional ## Scenario Partitions only when applicable, and exactly one ## Module Index with its required 8-column table and at least one module row.",
|
|
644
|
+
"After the complete skeleton exists, fill only concise table rows within the remaining output budget. Do not use code fences.",
|
|
792
645
|
"</retry_instruction>",
|
|
793
646
|
].join("\n");
|
|
794
647
|
}
|
|
648
|
+
// Repair-category guidance must outrank the retry ladder position: the
|
|
649
|
+
// ladder advances monotonically on transport failures (e.g. length →
|
|
650
|
+
// compact-terminal-first), and its "keep the committed ledger intact"
|
|
651
|
+
// instruction directly contradicts the repair action for invalid-output /
|
|
652
|
+
// truncated ledger facts (re-commit corrected record_* facts). When both
|
|
653
|
+
// apply, the model receives the repair instruction, not the rung script.
|
|
795
654
|
if (previousFailureCategory === "invalid-output" &&
|
|
796
655
|
task.structuredContractOutput &&
|
|
797
656
|
previousProtocolReason) {
|
|
@@ -805,7 +664,7 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
|
|
|
805
664
|
const splitGuidance = /write-set-too-large|split the task/.test(previousProtocolReason ?? "")
|
|
806
665
|
? [
|
|
807
666
|
"",
|
|
808
|
-
"The writeSet is too large for one implement node.
|
|
667
|
+
"The writeSet is too large for one implement node. Preserve the complete requirement and file coverage. This needs a Contract-level task split, not a Plan formatting repair. Report the write-set-too-large finding and the affected paths for the controller to resume Contract/split orchestration. Plan cannot change protected targets.files or call Contract-only tools; do not invent a split tool or discard required files.",
|
|
809
668
|
]
|
|
810
669
|
: [];
|
|
811
670
|
return [
|
|
@@ -867,8 +726,96 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
|
|
|
867
726
|
"</retry_instruction>",
|
|
868
727
|
].join("\n");
|
|
869
728
|
}
|
|
729
|
+
if (frontendPlanRetryStep === "compact-terminal-first") {
|
|
730
|
+
return [
|
|
731
|
+
basePrompt,
|
|
732
|
+
"",
|
|
733
|
+
"<retry_instruction>",
|
|
734
|
+
"Frontend plan retry ladder step: compact-terminal-first.",
|
|
735
|
+
"Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts, fill the missing required facts, then call finalize_plan exactly once.",
|
|
736
|
+
"Keep the existing committed ledger intact; do not re-derive already committed facts.",
|
|
737
|
+
"</retry_instruction>",
|
|
738
|
+
].join("\n");
|
|
739
|
+
}
|
|
740
|
+
if (frontendPlanRetryStep === "bounded-tool-only") {
|
|
741
|
+
return [
|
|
742
|
+
basePrompt,
|
|
743
|
+
"",
|
|
744
|
+
"<retry_instruction>",
|
|
745
|
+
"Frontend plan retry ladder step: bounded-tool-only.",
|
|
746
|
+
"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.",
|
|
747
|
+
"Do not expand scope or re-derive already committed facts.",
|
|
748
|
+
"</retry_instruction>",
|
|
749
|
+
].join("\n");
|
|
750
|
+
}
|
|
751
|
+
if (frontendPlanRetryStep === "backup-model") {
|
|
752
|
+
return [
|
|
753
|
+
basePrompt,
|
|
754
|
+
"",
|
|
755
|
+
"<retry_instruction>",
|
|
756
|
+
"Frontend plan retry ladder step: backup-model.",
|
|
757
|
+
"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.",
|
|
758
|
+
"</retry_instruction>",
|
|
759
|
+
].join("\n");
|
|
760
|
+
}
|
|
761
|
+
if (previousFailureCategory === "protocol-invalid" &&
|
|
762
|
+
task.outputProtocol &&
|
|
763
|
+
previousProtocolReason) {
|
|
764
|
+
return [
|
|
765
|
+
basePrompt,
|
|
766
|
+
"",
|
|
767
|
+
buildProtocolRetryInstruction(task.outputProtocol, previousProtocolReason),
|
|
768
|
+
].join("\n");
|
|
769
|
+
}
|
|
770
|
+
if (previousFailureCategory === "review-terminal-missing") {
|
|
771
|
+
return [
|
|
772
|
+
basePrompt,
|
|
773
|
+
"",
|
|
774
|
+
"<retry_instruction>",
|
|
775
|
+
"The review emitted a verdict in response text but never committed the authoritative typed terminal tool call (approve_review / request_review_changes). The response text is NOT the authority: no branch or gate reads it.",
|
|
776
|
+
"Call exactly one typed terminal tool to finish: approve_review (implementation passes, no Critical/Important findings) or request_review_changes (with typed issueCategory, at least one evidenceRef, and non-empty findings). Do not repeat the review analysis; commit the terminal tool once and stop.",
|
|
777
|
+
"</retry_instruction>",
|
|
778
|
+
].join("\n");
|
|
779
|
+
}
|
|
780
|
+
if (previousFailureCategory === "read-burst") {
|
|
781
|
+
if (task.id !== FRONTEND_PLAN_NODE_ID) {
|
|
782
|
+
return [
|
|
783
|
+
basePrompt,
|
|
784
|
+
"",
|
|
785
|
+
"<retry_instruction>",
|
|
786
|
+
"The previous attempt exceeded its read budget. Start a fresh, evidence-minimal pass: do not re-read task sources, upstream stdout, skills, or full diff artifacts already represented by a canonical context artifact.",
|
|
787
|
+
"Read the smallest relevant summary first, then only the specific source file or diff fragment needed for the required decision. Finish the required terminal/output protocol as soon as evidence is sufficient.",
|
|
788
|
+
"</retry_instruction>",
|
|
789
|
+
].join("\n");
|
|
790
|
+
}
|
|
791
|
+
return [
|
|
792
|
+
basePrompt,
|
|
793
|
+
"",
|
|
794
|
+
"<retry_instruction>",
|
|
795
|
+
"Previous plan attempt issued too many read-only tool calls (read/grep/ls/find) and blew up the context window. Trust the upstream frontend-contract-pi typed requirement facts and frontend-scout-pi target surface already provided — do NOT re-read contract/scout stdout, PRD/source files, or component sources you already inspected.",
|
|
796
|
+
"Minimize discovery reads: only read what you genuinely need, once. Commit record_plan_requirement / record_plan_verification_target / record_* facts directly from the facts already in context (one tool call per message), then call finalize_plan exactly once.",
|
|
797
|
+
"</retry_instruction>",
|
|
798
|
+
].join("\n");
|
|
799
|
+
}
|
|
800
|
+
// Generic output-limit fallback. Every branch above this one carries a
|
|
801
|
+
// more precise instruction for the same capacity signal (contract repair
|
|
802
|
+
// reasons, ladder rungs — compact-terminal-first is the reason-mandated
|
|
803
|
+
// rung for length-before-terminal —, protocol/review/read-burst repair),
|
|
804
|
+
// so output-limit must not shadow them.
|
|
805
|
+
if (previousFailureCategory === OUTPUT_LIMIT_RETRY_CATEGORY) {
|
|
806
|
+
return [
|
|
807
|
+
basePrompt,
|
|
808
|
+
"",
|
|
809
|
+
"<retry_instruction>",
|
|
810
|
+
"The previous turn ended with stopReason=length before the required output was complete. This is output-capacity truncation, not empty output.",
|
|
811
|
+
"Continue incrementally from already committed typed facts and current in-scope workspace files. Do not repeat completed discovery, decisions, facts, or writes.",
|
|
812
|
+
"Generate the smallest unfinished unit next, validate its required format immediately, repair any format error in this node, and only then continue to the next unfinished unit or terminal tool.",
|
|
813
|
+
"Keep prose minimal and finish the required terminal/output protocol as soon as the remaining work is valid.",
|
|
814
|
+
"</retry_instruction>",
|
|
815
|
+
].join("\n");
|
|
816
|
+
}
|
|
870
817
|
if (previousFailureCategory === "writer-empty-diff") {
|
|
871
|
-
const maxAttempts = task.retryPolicy?.maxAttempts ??
|
|
818
|
+
const maxAttempts = task.retryPolicy?.maxAttempts ?? 5;
|
|
872
819
|
// When a completeness progress exists for this writer, fold the concrete
|
|
873
820
|
// target paths into the empty-diff retry so the model does not guess and
|
|
874
821
|
// does not need to read a forbidden `.harness/**` evidence file.
|
|
@@ -896,7 +843,7 @@ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFail
|
|
|
896
843
|
].join("\n");
|
|
897
844
|
}
|
|
898
845
|
if (previousFailureCategory === "incomplete-write-set") {
|
|
899
|
-
const maxAttempts = task.retryPolicy?.maxAttempts ??
|
|
846
|
+
const maxAttempts = task.retryPolicy?.maxAttempts ?? 5;
|
|
900
847
|
const bindingOnly = task.id?.startsWith("generate-backend-md-case-") === true &&
|
|
901
848
|
(recoveryTargetPaths?.length ?? 0) === 1 &&
|
|
902
849
|
Object.values(recoveryDiagnostics ?? {}).flat().some((detail) => /(?:unclassified Test Points|duplicate Test Point bindings)/i.test(detail));
|
|
@@ -1504,6 +1451,20 @@ export async function executeDagNode(input) {
|
|
|
1504
1451
|
return;
|
|
1505
1452
|
}
|
|
1506
1453
|
}
|
|
1454
|
+
if (task.id === "frontend-scout-pi") {
|
|
1455
|
+
try {
|
|
1456
|
+
const { readTypedEventStoreFromJsonl } = await import("./frontend-typed-event-store.js");
|
|
1457
|
+
const facts = (await readTypedEventStoreFromJsonl(path.join(runDir, "frontend-contract-pi", "contract-typed-facts.jsonl"))).filter(r => r.phase === "committed").map(r => r.fact);
|
|
1458
|
+
const requirements = [...new Map(facts.filter(f => f.kind === "requirement").map(f => [String(f.id), f])).values()];
|
|
1459
|
+
if (!requirements.length)
|
|
1460
|
+
throw Error("FRONTEND_INPUT_MISSING: Scout needs committed Contract obligations");
|
|
1461
|
+
prompt += `\n<frontend_scout_input>\n${JSON.stringify({ requirements, sharedFacts: facts.filter(f => !["requirement", "contract-scope-completed", "contract-finalized"].includes(String(f.kind))) })}\n</frontend_scout_input>`;
|
|
1462
|
+
}
|
|
1463
|
+
catch (error) {
|
|
1464
|
+
await failBeforePrompt(error, "frontend-scout-input-unavailable");
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1507
1468
|
if (FRONTEND_WRITER_NODE_IDS.includes(nodeId)) {
|
|
1508
1469
|
// Design-review findings are not reliable in the provider's prose output
|
|
1509
1470
|
// (typed terminal nodes commonly return an empty assistant message). Inject
|
|
@@ -1691,6 +1652,7 @@ export async function executeDagNode(input) {
|
|
|
1691
1652
|
attemptPrompt,
|
|
1692
1653
|
});
|
|
1693
1654
|
result = await executeNode({
|
|
1655
|
+
...(state.budgetLedger?.mode === "hard" && state.budgetLedger.limits.maxProviderRequests !== undefined ? { reserveProviderRequest: () => reserveDagProviderRequest({ state, nodeId, attempt: attemptNumber, persist: input.persistState }) } : {}),
|
|
1694
1656
|
task,
|
|
1695
1657
|
cwd,
|
|
1696
1658
|
model,
|
|
@@ -1712,6 +1674,43 @@ export async function executeDagNode(input) {
|
|
|
1712
1674
|
durationMs: 0,
|
|
1713
1675
|
};
|
|
1714
1676
|
}
|
|
1677
|
+
// stopReason=length on top of a bare empty-output verdict is a provider
|
|
1678
|
+
// capacity signal, not a true empty response: reroute it through the
|
|
1679
|
+
// dedicated output-limit retry path while keeping the raw category for
|
|
1680
|
+
// diagnostics. Bare empty-output and transport aliases (network,
|
|
1681
|
+
// nonzero-exit, unknown) are rerouted — categories that already carry a
|
|
1682
|
+
// precise repair instruction (output-too-large, invalid-output,
|
|
1683
|
+
// protocol-invalid, structured-output-truncated via the validators below,
|
|
1684
|
+
// writer categories) keep their classification so their exact retry
|
|
1685
|
+
// guidance still reaches the model.
|
|
1686
|
+
if (task.executor === "pi" &&
|
|
1687
|
+
!result.ok &&
|
|
1688
|
+
result.stopReason === "length" &&
|
|
1689
|
+
(result.failureCategory === undefined ||
|
|
1690
|
+
["empty-output", "network", "nonzero-exit", "unknown"].includes(result.failureCategory))) {
|
|
1691
|
+
result = {
|
|
1692
|
+
...result,
|
|
1693
|
+
rawFailureCategory: result.rawFailureCategory ?? result.failureCategory,
|
|
1694
|
+
failureCategory: OUTPUT_LIMIT_RETRY_CATEGORY,
|
|
1695
|
+
stderr: [
|
|
1696
|
+
result.stderr,
|
|
1697
|
+
"output-limit: stopReason=length; preserve completed work and retry only the unfinished output",
|
|
1698
|
+
]
|
|
1699
|
+
.filter(Boolean)
|
|
1700
|
+
.join("\n"),
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
if (task.id === "generate-backend-md-plan-pi" &&
|
|
1704
|
+
!result.ok &&
|
|
1705
|
+
(result.failureCategory === "output-too-large" ||
|
|
1706
|
+
result.failureCategory === "invalid-output")) {
|
|
1707
|
+
const marker = "backend-test Markdown plan";
|
|
1708
|
+
const markerIndex = result.stderr?.lastIndexOf(marker) ?? -1;
|
|
1709
|
+
previousProtocolReason =
|
|
1710
|
+
markerIndex >= 0
|
|
1711
|
+
? result.stderr.slice(markerIndex, markerIndex + 4_000).trim()
|
|
1712
|
+
: `backend-test Markdown plan attempt failed with ${result.failureCategory}`;
|
|
1713
|
+
}
|
|
1715
1714
|
if (isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId)) {
|
|
1716
1715
|
const rawText = canonicalNodeOutput(result);
|
|
1717
1716
|
if (rawText.trim().length > 0) {
|
|
@@ -1834,7 +1833,7 @@ export async function executeDagNode(input) {
|
|
|
1834
1833
|
!result.ok &&
|
|
1835
1834
|
retryPolicy !== undefined) {
|
|
1836
1835
|
const submissions = await countContractRecordSubmissions(runDir, nodeId);
|
|
1837
|
-
if (submissions === 0) {
|
|
1836
|
+
if (submissions === 0 && result.stopReason !== "length") {
|
|
1838
1837
|
result = {
|
|
1839
1838
|
...result,
|
|
1840
1839
|
failureCategory: "empty-output",
|