@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.11

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.
Files changed (245) hide show
  1. package/CHANGELOG.md +379 -416
  2. package/dist/application/evaluation/budget.js +21 -0
  3. package/dist/application/evaluation/corpus-hash.js +10 -15
  4. package/dist/application/evaluation/corpus.js +2 -1
  5. package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
  6. package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
  7. package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
  8. package/dist/application/evaluation/frontend-pair-registration.js +143 -0
  9. package/dist/application/evaluation/frontend-paired-summary.js +150 -0
  10. package/dist/application/evaluation/frontend-run-observation.js +144 -0
  11. package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
  12. package/dist/application/evaluation/types.js +45 -12
  13. package/dist/application/task-lifecycle/observe.js +26 -1
  14. package/dist/build-stamp.json +3 -3
  15. package/dist/cli/command-definitions.js +11 -0
  16. package/dist/cli/program.js +4 -0
  17. package/dist/commands/task-advance.js +3 -5
  18. package/dist/commands/task-source-prepare.js +13 -1
  19. package/dist/executors/dag-pi/guards.js +4 -0
  20. package/dist/executors/dag-pi/model-config.js +30 -0
  21. package/dist/executors/dag-pi/plan/fact-adoption.js +55 -0
  22. package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +22 -28
  23. package/dist/executors/dag-pi/plan/finalize-tools.js +259 -0
  24. package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
  25. package/dist/executors/dag-pi/plan/record-tools.js +861 -0
  26. package/dist/executors/dag-pi/plan/schema.js +129 -0
  27. package/dist/executors/dag-pi/plan/session.js +1 -0
  28. package/dist/executors/dag-pi/plan/tool-helpers.js +16 -0
  29. package/dist/executors/dag-pi/sessions/index.js +6 -0
  30. package/dist/executors/dag-pi/sessions/plan-batches.js +280 -0
  31. package/dist/executors/dag-pi/sessions/plan-prompts.js +367 -0
  32. package/dist/executors/dag-pi/sessions/scout-parallel.js +197 -0
  33. package/dist/executors/dag-pi/sessions/segmented-plan.js +1184 -0
  34. package/dist/executors/dag-pi/sessions/writer-evidence.js +60 -0
  35. package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
  36. package/dist/executors/dag-pi/tools/design-terminal-tools.js +159 -0
  37. package/dist/executors/dag-pi/tools/plan-decision-tools.js +529 -0
  38. package/dist/executors/dag-pi/tools/review-terminal-tools.js +159 -0
  39. package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
  40. package/dist/executors/dag-pi-executor.js +733 -5782
  41. package/dist/executors/pi-executor.js +6 -1
  42. package/dist/executors/pi-sdk-executor.js +112 -19
  43. package/dist/executors/shell-executor.js +88 -12
  44. package/dist/executors/shell-presets.js +1 -1
  45. package/dist/executors/shell-write-guard.js +26 -3
  46. package/dist/infrastructure/console/operation-store.js +48 -14
  47. package/dist/infrastructure/evaluation/corpus-store.js +37 -12
  48. package/dist/shared/file-identity.js +23 -0
  49. package/dist/shared/git-workspace-status.js +482 -0
  50. package/dist/shared/operator/capabilities.js +10 -5
  51. package/dist/shared/path-safety.js +2 -2
  52. package/dist/shared/wizard-local-telemetry.js +51 -0
  53. package/dist/task/config-types.js +22 -8
  54. package/dist/task/contract/project.js +13 -0
  55. package/dist/task/contract/schema.js +2 -7
  56. package/dist/task/frontend-preflight.js +1 -1
  57. package/dist/task/source-prepare/parse-intent.js +124 -5
  58. package/dist/task/source-prepare/prepare.js +6 -1
  59. package/dist/task/source-prepare/semantic-intake.js +18 -3
  60. package/dist/task/source-prepare/source-execution.js +65 -0
  61. package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
  62. package/dist/task/source-prepare/source-provider-budget.js +290 -0
  63. package/dist/worker/console/chat/assistant-content.js +5 -34
  64. package/dist/worker/console/chat/chat-event-store.js +62 -10
  65. package/dist/worker/console/chat/context-insights.js +723 -0
  66. package/dist/worker/console/chat/conversation-nodes.js +383 -0
  67. package/dist/worker/console/chat/db-connections.js +854 -0
  68. package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
  69. package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +655 -0
  70. package/dist/worker/console/chat/pi-runtime.js +445 -36
  71. package/dist/worker/console/chat/provider-error.js +0 -12
  72. package/dist/worker/console/chat/repo-browser.js +52 -4
  73. package/dist/worker/console/chat/routes.js +577 -72
  74. package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
  75. package/dist/worker/console/chat/session-store.js +147 -19
  76. package/dist/worker/console/chat/shortcuts.js +7 -0
  77. package/dist/worker/console/chat/timeline-message-order.js +22 -0
  78. package/dist/worker/console/chat/tools.js +34 -0
  79. package/dist/worker/console/chat/turn-process.js +237 -66
  80. package/dist/worker/console/chat/user-turn-outline.js +19 -0
  81. package/dist/worker/console/console-update-runtime.js +2 -2
  82. package/dist/worker/console/dag-confirmation.js +3 -0
  83. package/dist/worker/console/dag-execution-receipt.js +3 -0
  84. package/dist/worker/console/operator-actions.js +32 -3
  85. package/dist/worker/console/operator-user-error.js +28 -1
  86. package/dist/worker/console/pi-readiness.js +4 -4
  87. package/dist/worker/console/prd-intake-bridge.js +5 -12
  88. package/dist/worker/console/recovery-cta.js +14 -14
  89. package/dist/worker/console/server.js +5 -1
  90. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
  91. package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-dT5T4sVL.js} +1 -1
  92. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
  93. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
  94. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
  95. package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
  96. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
  97. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
  98. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
  99. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
  100. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
  101. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
  102. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
  106. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
  107. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
  108. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
  109. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
  110. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
  111. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
  112. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
  113. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
  114. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
  115. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
  116. package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
  117. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
  118. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
  119. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
  120. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
  121. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
  122. package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
  123. package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
  124. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
  125. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
  126. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
  127. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
  128. package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DGRlieMH.js} +1 -1
  129. package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-4P9yPC9c.js} +5 -5
  130. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
  131. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
  132. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
  133. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
  134. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
  135. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
  136. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
  137. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
  138. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
  139. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
  140. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
  141. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
  142. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
  143. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
  144. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
  145. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
  146. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
  147. package/dist/worker/console/static/index.html +2 -2
  148. package/dist/worker/console/static-src/app/console-types.js +9 -1
  149. package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
  150. package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
  151. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +186 -5
  152. package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
  153. package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
  154. package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
  155. package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
  156. package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
  157. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
  158. package/dist/worker/console/static-src/operator-chat/process-label.js +27 -5
  159. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
  160. package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
  161. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +4 -1
  162. package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
  163. package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
  164. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
  165. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +211 -40
  166. package/dist/worker/console/static-src/operator-chat/useChatStream.js +124 -27
  167. package/dist/worker/console/static-src/operator-chat/useChatThread.js +88 -20
  168. package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
  169. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
  170. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
  171. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
  172. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  173. package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
  174. package/dist/worker/console/workspace-context.js +2 -0
  175. package/dist/worker/console/workspace-initialization.js +3 -3
  176. package/dist/worker/observability/read-model.js +23 -2
  177. package/dist/worker/observe/node-context-insights.js +479 -0
  178. package/dist/worker/observe/static/console-theme.css +174 -0
  179. package/dist/worker/observe/static/console-theme.js +36 -0
  180. package/dist/worker/observe/static/operator-chrome.css +85 -0
  181. package/dist/worker/observe/static/operator-chrome.js +51 -0
  182. package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
  183. package/dist/worker/scheduler/scheduled-goal-evidence.js +66 -3
  184. package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
  185. package/dist/workflows/dag/budget-enforcement.js +31 -2
  186. package/dist/workflows/dag/checkpoint.js +686 -0
  187. package/dist/workflows/dag/frontend-closeout.js +3 -1
  188. package/dist/workflows/dag/frontend-contract-facts.js +11 -8
  189. package/dist/workflows/dag/frontend-design-policy.js +6 -6
  190. package/dist/workflows/dag/frontend-durable-tools.js +90 -70
  191. package/dist/workflows/dag/frontend-implementation-contract.js +146 -57
  192. package/dist/workflows/dag/frontend-plan-canary.js +66 -16
  193. package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
  194. package/dist/workflows/dag/frontend-plan-progress.js +92 -0
  195. package/dist/workflows/dag/frontend-recovery-plan.js +10 -5
  196. package/dist/workflows/dag/frontend-recovery-run.js +51 -46
  197. package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
  198. package/dist/workflows/dag/frontend-review-context.js +29 -1
  199. package/dist/workflows/dag/frontend-review-scopes.js +92 -2
  200. package/dist/workflows/dag/frontend-risk.js +8 -3
  201. package/dist/workflows/dag/frontend-root-observation.js +261 -0
  202. package/dist/workflows/dag/frontend-session-budget.js +52 -39
  203. package/dist/workflows/dag/frontend-session-context.js +10 -0
  204. package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
  205. package/dist/workflows/dag/frontend-typed-event-store.js +11 -1
  206. package/dist/workflows/dag/frontend-verification-trace.js +6 -0
  207. package/dist/workflows/dag/frontend-writer-admission.js +3 -1
  208. package/dist/workflows/dag/hybrid/sources.js +1812 -0
  209. package/dist/workflows/dag/hybrid/templates/backend-test.js +1807 -0
  210. package/dist/workflows/dag/hybrid/templates/frontend-test.js +702 -0
  211. package/dist/workflows/dag/hybrid/templates/frontend.js +1529 -0
  212. package/dist/workflows/dag/hybrid/templates/index.js +8 -0
  213. package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +449 -0
  214. package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +495 -0
  215. package/dist/workflows/dag/hybrid/templates/shared.js +101 -0
  216. package/dist/workflows/dag/hybrid/templates/standard.js +265 -0
  217. package/dist/workflows/dag/hybrid/types.js +32 -0
  218. package/dist/workflows/dag/init-hybrid.js +57 -7106
  219. package/dist/workflows/dag/node-execution.js +11 -11
  220. package/dist/workflows/dag/output-protocol.js +1 -0
  221. package/dist/workflows/dag/rerun-feedback.js +35 -4
  222. package/dist/workflows/dag/rerun-task.js +62 -66
  223. package/dist/workflows/dag/runner.js +38 -1159
  224. package/dist/workflows/dag/terminal-status.js +502 -0
  225. package/dist/workflows/dag/types.js +15 -8
  226. package/dist/workflows/loop/benchmark.js +11 -8
  227. package/docs/README.md +1 -1
  228. package/docs/architecture/dag-execution.md +1 -1
  229. package/docs/architecture/runtime-boundaries.md +1 -1
  230. package/docs/governance/harness-methodology-verification.md +11 -0
  231. package/docs/templates/backend-test-dag.json +5 -4
  232. package/docs/templates/frontend-implementation-contract.schema.json +31 -11
  233. package/package.json +6 -4
  234. package/skills/frontend-design-review/SKILL.md +8 -11
  235. package/skills/frontend-design-review/references/review-checklist.md +3 -4
  236. package/skills/frontend-review/SKILL.md +5 -1
  237. package/skills/loop-agent/references/command-reference.md +8 -0
  238. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  239. package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
  240. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
  241. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
  242. package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
  243. package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
  244. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
  245. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
@@ -0,0 +1,502 @@
1
+ /** DAG runner 终态判定、前端恢复策略与恢复操作编排。含取证读取、lease 与 operation 创建;未拆成独立 policy/operation 文件。 */
2
+ import path from "node:path";
3
+ import { readFile } from "node:fs/promises";
4
+ import { createHash } from "node:crypto";
5
+ import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
6
+ import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isConditionSkippedReason, readFrontendPrewriteResult } from "./scheduler.js";
7
+ import { isFrontendWriterTransientPartialWrite } from "./frontend-writer-recovery.js";
8
+ import { classifyReviewIssueCategoryFailureOwner, readCommittedDesignRequestFact, readCommittedReviewRequestFact, writeFrontendReviewFindingsPrd } from "./frontend-review-findings.js";
9
+ import { computeActionPayloadHash } from "../../infrastructure/console/operation-store.js";
10
+ import { classifyFailureOwner, failureOwnerSchema } from "./frontend-recovery-capsule.js";
11
+ import { acquireDagRecoveryLease, bindDagRecoveryLeaseExecution, claimDagRecoveryLease } from "./recovery-lease.js";
12
+ export function buildCandidateContractInvalidResult(state, result, artifactHash) {
13
+ return {
14
+ schemaVersion: 1,
15
+ outcome: "candidate-contract-invalid",
16
+ origin: {
17
+ kind: "frontend-prewrite-gate",
18
+ parentRunId: state.runId,
19
+ requestId: state.runId,
20
+ failedNodeId: result.failureSource ?? "frontend-plan-pi",
21
+ },
22
+ failureClass: {
23
+ code: "candidate-contract-invalid",
24
+ classification: result.classification,
25
+ reason: result.findings.map((finding) => `${finding.code}: ${finding.message}`).join("; ") ??
26
+ "candidate contract invalid",
27
+ },
28
+ evidenceRefs: [
29
+ {
30
+ runId: state.runId,
31
+ relativePath: FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT,
32
+ sha256: artifactHash,
33
+ },
34
+ ],
35
+ };
36
+ }
37
+ export function buildWriterPartialWriteBlockedResult(state, failedNodeId, reason) {
38
+ return {
39
+ schemaVersion: 1,
40
+ outcome: "auto-recovery-blocked",
41
+ origin: {
42
+ kind: "frontend-writer",
43
+ parentRunId: state.runId,
44
+ requestId: state.runId,
45
+ failedNodeId,
46
+ },
47
+ failureClass: {
48
+ code: "writer-transient-partial-write",
49
+ classification: "transient",
50
+ reason,
51
+ },
52
+ evidenceRefs: [],
53
+ };
54
+ }
55
+ export function buildRecoveryResultForTrigger(state, trigger, outcome) {
56
+ return {
57
+ schemaVersion: 1,
58
+ outcome,
59
+ origin: {
60
+ kind: trigger.failureSource === "frontend-implement-pi"
61
+ ? "frontend-writer"
62
+ : "frontend-prewrite-gate",
63
+ parentRunId: state.runId,
64
+ requestId: trigger.requestId,
65
+ failedNodeId: trigger.failureSource,
66
+ },
67
+ evidenceRefs: [],
68
+ };
69
+ }
70
+ export function collectSupersededIntermediateFailures(state) {
71
+ const supersededIds = new Set();
72
+ const supersededFailures = [];
73
+ const soft = state.nodes["soft-verify-shell"];
74
+ if (!soft || soft.status !== "ERROR") {
75
+ return { supersededIds, supersededFailures };
76
+ }
77
+ const repair = state.nodes["repair-pi"];
78
+ const hard = state.nodes["hard-verify-shell"];
79
+ if (repair?.status !== "FINISHED" || hard?.status !== "FINISHED") {
80
+ return { supersededIds, supersededFailures };
81
+ }
82
+ const observesReview = Boolean(state.nodes["review-gate-shell"]);
83
+ const terminalReason = state.convergence?.terminalReason;
84
+ if (observesReview) {
85
+ if (terminalReason !== "review-pass") {
86
+ return { supersededIds, supersededFailures };
87
+ }
88
+ }
89
+ else if (terminalReason && terminalReason !== "hard-verify-pass") {
90
+ return { supersededIds, supersededFailures };
91
+ }
92
+ // Evidence: at least one successful repair pass after soft failure, with hard verify finished in terminal state.
93
+ const hasRepairEvidence = !state.convergence?.enabled ||
94
+ state.convergence.passHistory.some((pass) => pass.reason === "hard-verify-failed" ||
95
+ pass.reason === "review-request-revision" ||
96
+ pass.status === "retrying") ||
97
+ repair.status === "FINISHED";
98
+ if (!hasRepairEvidence) {
99
+ return { supersededIds, supersededFailures };
100
+ }
101
+ supersededIds.add("soft-verify-shell");
102
+ supersededFailures.push({
103
+ nodeId: "soft-verify-shell",
104
+ preservedStatus: "ERROR",
105
+ supersededBy: ["repair-pi", "hard-verify-shell"],
106
+ pass: state.convergence?.currentPass,
107
+ reason: "intermediate-soft-verify-covered-by-repair-and-final-hard",
108
+ });
109
+ return { supersededIds, supersededFailures };
110
+ }
111
+ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd, frontendRecoveryQuota = 1) {
112
+ const repoRoot = cwd ?? path.resolve(runDir, "..", "..", "..", "..");
113
+ if (state.frontendShapeState?.terminalOutcome &&
114
+ state.frontendShapeState.transitionCapsule) {
115
+ state.status = "failed";
116
+ return { recoveryPending: false };
117
+ }
118
+ // Terminal override: a denied frontend writer must never surface as a
119
+ // mechanical partial_failed just because prewrite FINISHED while the writer
120
+ // was SKIPPED. Fail closed on blocked before aggregation.
121
+ let recoveryTrigger;
122
+ const skipFrontendRecovery = state.failureCategory === "controller-interrupted" ||
123
+ state.continuation?.writerTransaction !== undefined;
124
+ const hasFrontendWriter = Object.keys(state.nodes).some((id) => FRONTEND_WRITER_NODE_IDS.includes(id));
125
+ if (hasFrontendWriter && !skipFrontendRecovery) {
126
+ const admission = await readFrontendPrewriteResult(runDir);
127
+ if (admission.ok) {
128
+ // M8: admission uses the accepted/blocked state machine. A blocked admission whose
129
+ // failureSource is frontend-plan-pi is the contract-invalid analog of
130
+ // the old retryable-invalid path (candidate continuation may re-plan);
131
+ // every other blocked admission is terminal.
132
+ const planContractInvalid = admission.result.classification === "blocked" &&
133
+ admission.result.failureSource === "frontend-plan-pi";
134
+ // A blocked admission caused by the design review committing
135
+ // `request_design_changes` is a definite finding (not a protocol or
136
+ // environment error): the design was reviewed and rejected. It is the
137
+ // design-review analog of review-request-changes — not terminal, but
138
+ // re-enters the next plan round with the committed design findings.
139
+ // The failureSource may be frontend-writer-admission-shell or
140
+ // frontend-design-policy-shell; the committed design fact is the
141
+ // authoritative signal.
142
+ const designRequestFact = await readCommittedDesignRequestFact({
143
+ runDir,
144
+ });
145
+ const designRejected = admission.result.classification === "blocked" &&
146
+ designRequestFact !== undefined;
147
+ const terminalAdmissionDenial = (admission.result.classification === "blocked" ||
148
+ admission.result.classification === "stale") &&
149
+ !planContractInvalid &&
150
+ !designRejected;
151
+ if (terminalAdmissionDenial) {
152
+ state.status = "failed";
153
+ return { recoveryPending: false };
154
+ }
155
+ if (designRejected && designRequestFact) {
156
+ state.terminalReason = "design-request-changes";
157
+ try {
158
+ await writeFrontendReviewFindingsPrd({
159
+ runDir,
160
+ parentRunId: state.runId,
161
+ fact: designRequestFact,
162
+ });
163
+ }
164
+ catch (error) {
165
+ console.warn(`[run-dag] warning: failed to write frontend design findings PRD: ${error instanceof Error ? error.message : String(error)}`);
166
+ }
167
+ const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
168
+ if (continuationCount < frontendRecoveryQuota) {
169
+ recoveryTrigger = {
170
+ requestId: state.runId,
171
+ // The rejected design is a plan input defect. Reset from the
172
+ // planner so the next child can incorporate the committed
173
+ // findings before design review runs again.
174
+ failureSource: "frontend-design-review-pi",
175
+ failureOwner: classifyReviewIssueCategoryFailureOwner(designRequestFact.issueCategory),
176
+ protocolFailureReason: `frontend design review request_design_changes (issueCategory=${designRequestFact.issueCategory})`,
177
+ recoveryMode: "new-dag",
178
+ };
179
+ }
180
+ }
181
+ if (planContractInvalid) {
182
+ const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
183
+ if (continuationCount >= frontendRecoveryQuota) {
184
+ // Continuation quota exhausted: a second contract-invalid candidate
185
+ // is authoritative candidate-contract-invalid → forced failed.
186
+ state.frontendRecoveryResult = buildCandidateContractInvalidResult(state, admission.result, admission.artifactHash);
187
+ state.status = "failed";
188
+ return { recoveryPending: false };
189
+ }
190
+ // Root continuation still available: emit a recovery trigger for the
191
+ // M6 operation controller. The parent no longer writes
192
+ // frontendRecoveryState or stays in active/ for a child CAS.
193
+ recoveryTrigger = {
194
+ requestId: state.runId,
195
+ failureSource: admission.result.failureSource ?? "frontend-plan-pi",
196
+ failureOwner: "plan",
197
+ protocolFailureReason: "frontend candidate contract invalid",
198
+ recoveryMode: "new-dag",
199
+ };
200
+ }
201
+ }
202
+ }
203
+ // Phase 5: writer transient partial write → rollback + recovery trigger.
204
+ // Only for frontend-implementation writers with a remaining root
205
+ // continuation, and only when the rollback journal (captured before the
206
+ // provider call) restores cleanly; otherwise the run stays failed with
207
+ // auto-recovery-blocked.
208
+ if (hasFrontendWriter && !recoveryTrigger && !skipFrontendRecovery) {
209
+ const writerNodeId = FRONTEND_WRITER_NODE_IDS.find((id) => {
210
+ const node = state.nodes[id];
211
+ return (node &&
212
+ node.status === "ERROR" &&
213
+ isFrontendWriterTransientPartialWrite({
214
+ failureCategory: node.failureCategory,
215
+ }));
216
+ });
217
+ if (writerNodeId) {
218
+ const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
219
+ if (continuationCount < frontendRecoveryQuota) {
220
+ const { rollbackFrontendWriter } = await import("./frontend-writer-recovery.js");
221
+ const rollback = await rollbackFrontendWriter({
222
+ cwd: repoRoot,
223
+ parentRunId: state.runId,
224
+ });
225
+ if (rollback.ok) {
226
+ recoveryTrigger = {
227
+ requestId: state.runId,
228
+ failureSource: "frontend-implement-pi",
229
+ failureOwner: "implementation",
230
+ protocolFailureReason: "frontend writer transient partial write",
231
+ recoveryMode: "continuation",
232
+ };
233
+ }
234
+ else {
235
+ state.frontendRecoveryResult = buildWriterPartialWriteBlockedResult(state, writerNodeId, rollback.reason);
236
+ state.status = "failed";
237
+ return { recoveryPending: false };
238
+ }
239
+ }
240
+ }
241
+ }
242
+ // Phase 6: verification failure → automatic recovery (re-implement +
243
+ // re-verify). The verify shell is terminal by design so the failed run's
244
+ // evidence stays clean; the M6 recovery operation re-runs implement and
245
+ // verify in a fresh continuation, guarded by the same continuation quota,
246
+ // budget fold and failure fingerprint as the other recovery paths.
247
+ if (hasFrontendWriter && !recoveryTrigger && !skipFrontendRecovery) {
248
+ const verifyFailed = state.nodes["frontend-verify-shell"]?.status === "ERROR";
249
+ if (verifyFailed) {
250
+ const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
251
+ if (continuationCount < frontendRecoveryQuota) {
252
+ let recoveryOwner = classifyFailureOwner({
253
+ nodeId: "frontend-verify-shell",
254
+ rawFailureCategory: state.nodes["frontend-verify-shell"]?.failureCategory,
255
+ });
256
+ try {
257
+ const verifyFailure = JSON.parse(await readFile(path.join(runDir, "contracts/frontend-verify-failure.json"), "utf8"));
258
+ const owner = failureOwnerSchema.safeParse(verifyFailure.failureOwner);
259
+ if (verifyFailure.schemaVersion === 1 &&
260
+ verifyFailure.schemaId === "frontend-verify-failure-v1" &&
261
+ verifyFailure.nodeId === "frontend-verify-shell" &&
262
+ verifyFailure.failureCategory === state.nodes["frontend-verify-shell"]?.failureCategory &&
263
+ owner.success) {
264
+ recoveryOwner = owner.data;
265
+ }
266
+ else {
267
+ recoveryOwner = "unknown";
268
+ }
269
+ }
270
+ catch {
271
+ // The node's classified category remains the fallback; never turn
272
+ // unknown/environment/configuration failures into writer work.
273
+ }
274
+ if (recoveryOwner === "implementation")
275
+ recoveryTrigger = {
276
+ requestId: state.runId,
277
+ failureSource: "frontend-verify-shell",
278
+ failureOwner: recoveryOwner,
279
+ protocolFailureReason: "frontend verification failure",
280
+ recoveryMode: "continuation",
281
+ };
282
+ else
283
+ state.terminalReason = recoveryOwner === "environment"
284
+ ? "frontend-verification-wait-external"
285
+ : `frontend-verification-${recoveryOwner}-requires-contract-review`;
286
+ }
287
+ }
288
+ }
289
+ // Phase 7: frontend review "request changes" → not passed. When the review
290
+ // node FINISHED but committed `request_review_changes` (typed terminal
291
+ // verdict), the implementation was reviewed and rejected. The run must not
292
+ // surface as finished: terminal aggregation below stays partial_failed, a
293
+ // terminal reason is recorded, the committed findings are rendered into a
294
+ // downstream-task PRD draft (run-owned), and a recovery trigger carries the
295
+ // issueCategory-derived owner so a later DAG round consumes the findings
296
+ // instead of repeating the previous flow.
297
+ const reviewRequestFact = await readCommittedReviewRequestFact({
298
+ runDir,
299
+ });
300
+ if (reviewRequestFact) {
301
+ state.terminalReason = "review-request-changes";
302
+ try {
303
+ await writeFrontendReviewFindingsPrd({
304
+ runDir,
305
+ parentRunId: state.runId,
306
+ fact: reviewRequestFact,
307
+ });
308
+ }
309
+ catch (error) {
310
+ // The PRD draft is additive; a write failure must not mask the
311
+ // not-passed verdict. The terminal aggregation below still applies.
312
+ console.warn(`[run-dag] warning: failed to write frontend review findings PRD: ${error instanceof Error ? error.message : String(error)}`);
313
+ }
314
+ if (!recoveryTrigger && !skipFrontendRecovery) {
315
+ const continuationCount = state.frontendRecoveryState?.continuationCount ?? 0;
316
+ if (continuationCount < frontendRecoveryQuota) {
317
+ recoveryTrigger = {
318
+ requestId: state.runId,
319
+ failureSource: "frontend-review-pi",
320
+ failureOwner: classifyReviewIssueCategoryFailureOwner(reviewRequestFact.issueCategory),
321
+ protocolFailureReason: `frontend review request_review_changes (issueCategory=${reviewRequestFact.issueCategory})`,
322
+ recoveryMode: "new-dag",
323
+ };
324
+ }
325
+ }
326
+ }
327
+ const finishedCount = Object.values(state.nodes).filter((n) => n.status === "FINISHED").length;
328
+ const { supersededIds, supersededFailures } = collectSupersededIntermediateFailures(state);
329
+ if (state.convergence && supersededFailures.length > 0) {
330
+ state.convergence.supersededFailures = supersededFailures;
331
+ }
332
+ // Prefer map keys: DagNodeRecord may not always mirror the map key on `.id`.
333
+ const effectiveErrorCount = Object.entries(state.nodes).filter(([nodeId, n]) => n.status === "ERROR" && !supersededIds.has(nodeId)).length;
334
+ const supersededErrorCount = Object.entries(state.nodes).filter(([nodeId, n]) => n.status === "ERROR" && supersededIds.has(nodeId)).length;
335
+ const conditionSkippedCount = Object.values(state.nodes).filter((n) => n.status === "SKIPPED" && isConditionSkippedReason(n.skippedReason)).length;
336
+ const successTerminal = isSuccessfulConvergenceTerminal(state);
337
+ // Superseded intermediate ERRORs remain ERROR in history but count as settled
338
+ // for terminal aggregation only (they do not inflate effectiveErrorCount).
339
+ if (effectiveErrorCount === 0 &&
340
+ successTerminal &&
341
+ finishedCount + conditionSkippedCount + supersededErrorCount === taskCount) {
342
+ // Phase 7 guard: a committed `request_review_changes` means the review
343
+ // rejected the implementation; the run must never surface as finished
344
+ // even though every node technically FINISHED.
345
+ state.status =
346
+ state.terminalReason === "review-request-changes"
347
+ ? "partial_failed"
348
+ : "finished";
349
+ }
350
+ else if (finishedCount > 0) {
351
+ state.status = "partial_failed";
352
+ }
353
+ else {
354
+ state.status = "failed";
355
+ }
356
+ return recoveryTrigger
357
+ ? { recoveryPending: true, recovery: recoveryTrigger }
358
+ : { recoveryPending: false };
359
+ }
360
+ export function isSuccessfulConvergenceTerminal(state) {
361
+ const reason = state.convergence?.terminalReason;
362
+ if (!reason)
363
+ return true;
364
+ return SUCCESSFUL_CONVERGENCE_TERMINAL_REASONS.has(reason);
365
+ }
366
+ export async function createRecoveryOperation(input) {
367
+ const { cwd, runDir, spec, state, trigger } = input;
368
+ const { computeFrontendRecoveryPlanForSource } = await import("./frontend-recovery-plan.js");
369
+ let plan;
370
+ try {
371
+ plan = computeFrontendRecoveryPlanForSource({
372
+ spec,
373
+ parentRunId: state.runId,
374
+ requestId: trigger.requestId,
375
+ failureSource: trigger.failureSource,
376
+ failureOwner: trigger.failureOwner,
377
+ });
378
+ }
379
+ catch (error) {
380
+ return { kind: "blocked", reason: error instanceof Error ? error.message : String(error) };
381
+ }
382
+ const recoveryLease = await acquireDagRecoveryLease({
383
+ cwd,
384
+ parentRunId: state.runId,
385
+ requestId: trigger.requestId,
386
+ kind: "auto-frontend-recovery",
387
+ });
388
+ let effectiveLease = recoveryLease;
389
+ let observeOnly = false;
390
+ if (recoveryLease.kind === "held") {
391
+ if (recoveryLease.lease.kind !== "auto-frontend-recovery" ||
392
+ recoveryLease.lease.requestId !== trigger.requestId) {
393
+ return { kind: "lease-held", lease: recoveryLease.lease };
394
+ }
395
+ const claimed = await claimDagRecoveryLease({
396
+ leasePath: recoveryLease.leasePath,
397
+ lease: recoveryLease.lease,
398
+ });
399
+ effectiveLease = claimed;
400
+ if (claimed.kind === "held") {
401
+ if (!claimed.lease.executionRef) {
402
+ return { kind: "lease-held", lease: claimed.lease };
403
+ }
404
+ observeOnly = true;
405
+ }
406
+ }
407
+ const planHash = createHash("sha256")
408
+ .update(JSON.stringify(plan))
409
+ .digest("hex");
410
+ const continuationPath = path.join(runDir, ".runtime", "frontend-recovery", `${trigger.requestId}.json`);
411
+ await writeJsonAtomic(continuationPath, {
412
+ schemaVersion: 1,
413
+ parentRunId: state.runId,
414
+ requestId: trigger.requestId,
415
+ failureSource: trigger.failureSource,
416
+ failureOwner: plan.failureOwner,
417
+ resetRootNodeId: plan.resetRootNodeId,
418
+ recoveryMode: trigger.recoveryMode,
419
+ planHash,
420
+ plan,
421
+ });
422
+ const operationInput = {
423
+ clientRequestId: `dag-recovery-${state.runId}`,
424
+ action: "dagRunTask",
425
+ actionParams: {
426
+ parentRunId: state.runId,
427
+ requestId: trigger.requestId,
428
+ failureSource: trigger.failureSource,
429
+ failureOwner: plan.failureOwner,
430
+ resetRootNodeId: plan.resetRootNodeId,
431
+ protocolFailureReason: trigger.protocolFailureReason,
432
+ recoveryMode: trigger.recoveryMode,
433
+ planHash,
434
+ },
435
+ cliArgs: [
436
+ "dag",
437
+ "rerun-task",
438
+ "--run-id",
439
+ state.runId,
440
+ "--reason",
441
+ trigger.protocolFailureReason ?? "frontend automatic recovery",
442
+ "--request-id",
443
+ trigger.requestId,
444
+ "--frontend-recovery",
445
+ "--json",
446
+ ],
447
+ };
448
+ let created;
449
+ if (effectiveLease.lease.executionRef) {
450
+ // Replay the exact operation bound by the durable lease. A missing record
451
+ // cannot be replaced with a new operation id without violating the fence.
452
+ const existing = await input.deps.store.get(effectiveLease.lease.executionRef);
453
+ if (!existing) {
454
+ throw new Error(`frontend recovery lease operation not found: ${effectiveLease.lease.executionRef}`);
455
+ }
456
+ if (existing.clientRequestId !== operationInput.clientRequestId ||
457
+ existing.action !== operationInput.action ||
458
+ existing.payloadHash !==
459
+ computeActionPayloadHash(operationInput.action, operationInput.actionParams)) {
460
+ throw new Error("frontend recovery lease execution identity drift: referenced operation payload does not match the recovery request");
461
+ }
462
+ created = { kind: "idempotent", operation: existing };
463
+ }
464
+ else {
465
+ created = await input.deps.store.create(operationInput);
466
+ }
467
+ if (created.kind !== "conflict" && !observeOnly) {
468
+ // The durable lease must name the operation before controller execution.
469
+ // This also repairs a lease left behind by a crash before the original bind.
470
+ await bindDagRecoveryLeaseExecution({
471
+ leasePath: effectiveLease.leasePath,
472
+ lease: effectiveLease.lease,
473
+ executionRef: created.operation.operationId,
474
+ });
475
+ // Repair CLI args on both fresh and replayed operations. This closes the
476
+ // crash window after operation creation but before args were persisted.
477
+ await input.deps.store.update(created.operation.operationId, {
478
+ cliArgs: [
479
+ "dag",
480
+ "rerun-task",
481
+ "--run-id",
482
+ state.runId,
483
+ "--reason",
484
+ trigger.protocolFailureReason ?? "frontend automatic recovery",
485
+ "--request-id",
486
+ trigger.requestId,
487
+ "--frontend-recovery",
488
+ "--recovery-operation-id",
489
+ created.operation.operationId,
490
+ "--json",
491
+ ],
492
+ });
493
+ }
494
+ if (created.kind !== "conflict" && observeOnly) {
495
+ return { kind: "observed", operation: created.operation };
496
+ }
497
+ return created;
498
+ }
499
+ export const SUCCESSFUL_CONVERGENCE_TERMINAL_REASONS = new Set([
500
+ "review-pass",
501
+ "hard-verify-pass",
502
+ ]);
@@ -1,7 +1,12 @@
1
+ import { frontendPairExecutionBindingSchema } from "../../application/evaluation/frontend-pair-registration.js";
2
+ import { sourceBudgetContractIdentity } from "../../application/evaluation/budget.js";
3
+ import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
4
+ import { frontendTestObservationSchema } from "./frontend-test-execution-evidence.js";
5
+ import { capabilityBoundarySchema } from "../../task/config-types.js";
1
6
  import { z } from "zod";
2
7
  import { frontendExecutionPolicySchema } from "../../shared/frontend-execution-policy.js";
3
8
  import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, OPENSPEC_SPEC_EXT_RE, } from "../../shared/openspec-spec.js";
4
- import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
9
+ import { campaignBudgetSchema, sourceProviderBudgetBindingSchema, } from "../../application/evaluation/budget.js";
5
10
  import { assertDagPromptSourceRule } from "./prompt-source.js";
6
11
  // Leaf-module import: dagRetryPolicySchema must be defined by a module that
7
12
  // never transitively reaches types.ts during init (retry-policy does, via
@@ -89,6 +94,7 @@ export const dagShellVerifyEvidenceSchema = z.object({
89
94
  .array(z.object({
90
95
  label: z.string(),
91
96
  status: z.enum(["ok", "future-delivery-script", "future-test-target"]),
97
+ testObservation: frontendTestObservationSchema.optional(),
92
98
  }))
93
99
  .default([]),
94
100
  /** Effective timeout applied independently to each shell command. */
@@ -463,13 +469,7 @@ export const dagFrontendDesignPolicySchema = z
463
469
  .optional(),
464
470
  /** Generation-frozen test capability boundary composed into the
465
471
  * canonical dependencyPolicy by the runtime. */
466
- capabilityBoundary: z
467
- .object({
468
- allowed: z.array(z.string().min(1)).optional(),
469
- forbidden: z.array(z.string().min(1)).optional(),
470
- })
471
- .strict()
472
- .optional(),
472
+ capabilityBoundary: capabilityBoundarySchema.optional(),
473
473
  /** Generation-frozen interaction id vocabulary (plan finalize check). */
474
474
  interactionIds: z.array(z.string().min(1)).optional(),
475
475
  /** Generation-frozen roots that bound custom normative directories. */
@@ -1320,6 +1320,7 @@ export const dagEvaluationBindingSchema = z
1320
1320
  seed: z.number().int().nonnegative(),
1321
1321
  split: z.enum(["public", "private", "held_out"]).optional(),
1322
1322
  taskRef: z.string().min(1).optional(),
1323
+ frontendPair: frontendPairExecutionBindingSchema.optional(),
1323
1324
  })
1324
1325
  .strict();
1325
1326
  /** Eval Lab Campaign Budget (W3.4); enforced by runner ledger when mode=hard. */
@@ -1424,6 +1425,7 @@ export const dagSpecSchema = z
1424
1425
  evaluation: dagEvaluationBindingSchema.optional(),
1425
1426
  /** Optional hard/record-only budget; requires version 3 or 4. */
1426
1427
  budget: dagBudgetSchema.optional(),
1428
+ sourceProviderBudget: sourceProviderBudgetBindingSchema.optional(),
1427
1429
  frontendExecutionPolicy: frontendExecutionPolicySchema.optional(),
1428
1430
  sourceBinding: dagSourceBindingSchema.optional(),
1429
1431
  /** v4 managed Task Contract binding; only valid on version 4. */
@@ -1494,6 +1496,9 @@ export const dagSpecSchema = z
1494
1496
  tasks: z.array(dagTaskSchema).min(1),
1495
1497
  })
1496
1498
  .superRefine((spec, ctx) => {
1499
+ if (spec.sourceProviderBudget && (!spec.sourceBinding || spec.sourceProviderBudget.ledger.taskId !== spec.sourceBinding.taskId || sha256OfCanonicalJson(spec.sourceProviderBudget.contract ?? null) !== sha256OfCanonicalJson(sourceBudgetContractIdentity(spec.taskContractBinding) ?? null) || spec.budget?.mode !== "hard" || spec.sourceProviderBudget.ledger.maxProviderRequests !== spec.budget?.limits.maxProviderRequests || spec.sourceProviderBudget.sourceBindingSha256 !== sha256OfCanonicalJson(spec.sourceBinding))) {
1500
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["sourceProviderBudget"], message: "source request budget must match the frozen source and request limit" });
1501
+ }
1497
1502
  const supportsV3Fields = spec.version === 3 || spec.version === 4;
1498
1503
  validateFrozenArtifactBindings(spec.tasks, ctx);
1499
1504
  if (spec.evaluation && !supportsV3Fields) {
@@ -1618,6 +1623,8 @@ export const frontendRecoveryStateSchema = z
1618
1623
  * frontend-plan-revision-pi / frontend-prewrite-gate-shell /
1619
1624
  * frontend-implement-pi (writer partial write). */
1620
1625
  failureSource: z.string().min(1).optional(),
1626
+ failureOwner: z.enum(["contract", "scout", "plan", "implementation", "verification-config", "environment", "scope", "unknown"]).optional(),
1627
+ resetRootNodeId: z.string().min(1).optional(),
1621
1628
  revision: z.number().int().nonnegative(),
1622
1629
  })
1623
1630
  .strict()
@@ -40,7 +40,7 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
40
40
  regressionPause: false,
41
41
  changedPaths: 2,
42
42
  repairPasses: 1,
43
- }, ["./test/dag-runner.test.ts"], "Single repair exhausts before the third successful hard verification."),
43
+ }, ["./test/dag-runner.test.ts", "./test/dag-runner-convergence.test.ts", "./test/dag-runner-artifacts.test.ts", "./test/dag-runner-decisions.test.ts"], "Single repair exhausts before the third successful hard verification."),
44
44
  result("3-pass-convergence", {
45
45
  pass: true,
46
46
  fullVerifyPass: true,
@@ -51,7 +51,7 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
51
51
  regressionPause: false,
52
52
  changedPaths: 2,
53
53
  repairPasses: 3,
54
- }, ["./test/dag-runner.test.ts"], "Convergence re-enters supervisor, repair, and hard verify until pass 3."),
54
+ }, ["./test/dag-runner.test.ts", "./test/dag-runner-convergence.test.ts", "./test/dag-runner-artifacts.test.ts", "./test/dag-runner-decisions.test.ts"], "Convergence re-enters supervisor, repair, and hard verify until pass 3."),
55
55
  result("3-pass-convergence+quota", {
56
56
  pass: true,
57
57
  fullVerifyPass: true,
@@ -64,6 +64,9 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
64
64
  repairPasses: 3,
65
65
  }, [
66
66
  "./test/dag-runner.test.ts",
67
+ "./test/dag-runner-convergence.test.ts",
68
+ "./test/dag-runner-artifacts.test.ts",
69
+ "./test/dag-runner-decisions.test.ts",
67
70
  "./test/verify-step.test.ts",
68
71
  ], "Intermediate verification uses quota while final hard gate remains full."),
69
72
  ]),
@@ -78,7 +81,7 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
78
81
  regressionPause: false,
79
82
  changedPaths: 3,
80
83
  repairPasses: 1,
81
- }, ["./test/dag-runner.test.ts"], "Single repair ends failed without enough evidence to classify regression."),
84
+ }, ["./test/dag-runner.test.ts", "./test/dag-runner-convergence.test.ts", "./test/dag-runner-artifacts.test.ts", "./test/dag-runner-decisions.test.ts"], "Single repair ends failed without enough evidence to classify regression."),
82
85
  result("3-pass-convergence", {
83
86
  pass: false,
84
87
  fullVerifyPass: false,
@@ -89,7 +92,7 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
89
92
  regressionPause: true,
90
93
  changedPaths: 3,
91
94
  repairPasses: 2,
92
- }, ["./test/dag-runner.test.ts"], "Convergence pauses instead of retrying blindly after regression signal."),
95
+ }, ["./test/dag-runner.test.ts", "./test/dag-runner-convergence.test.ts", "./test/dag-runner-artifacts.test.ts", "./test/dag-runner-decisions.test.ts"], "Convergence pauses instead of retrying blindly after regression signal."),
93
96
  result("3-pass-convergence+quota", {
94
97
  pass: false,
95
98
  fullVerifyPass: false,
@@ -100,7 +103,7 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
100
103
  regressionPause: true,
101
104
  changedPaths: 3,
102
105
  repairPasses: 2,
103
- }, ["./test/dag-runner.test.ts"], "Quota reduces intermediate cost but still pauses on regression."),
106
+ }, ["./test/dag-runner.test.ts", "./test/dag-runner-convergence.test.ts", "./test/dag-runner-artifacts.test.ts", "./test/dag-runner-decisions.test.ts"], "Quota reduces intermediate cost but still pauses on regression."),
104
107
  ]),
105
108
  sample("LB-03", "write guard hard failure must not retry", "dag-runtime", [
106
109
  result("single-repair", {
@@ -113,7 +116,7 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
113
116
  regressionPause: false,
114
117
  changedPaths: 1,
115
118
  repairPasses: 1,
116
- }, ["./test/dag-runner.test.ts"], "Write-guard failure remains terminal."),
119
+ }, ["./test/dag-runner.test.ts", "./test/dag-runner-convergence.test.ts", "./test/dag-runner-artifacts.test.ts", "./test/dag-runner-decisions.test.ts"], "Write-guard failure remains terminal."),
117
120
  result("3-pass-convergence", {
118
121
  pass: false,
119
122
  fullVerifyPass: false,
@@ -124,7 +127,7 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
124
127
  regressionPause: false,
125
128
  changedPaths: 1,
126
129
  repairPasses: 1,
127
- }, ["./test/dag-runner.test.ts"], "Non-retry class is not re-entered."),
130
+ }, ["./test/dag-runner.test.ts", "./test/dag-runner-convergence.test.ts", "./test/dag-runner-artifacts.test.ts", "./test/dag-runner-decisions.test.ts"], "Non-retry class is not re-entered."),
128
131
  result("3-pass-convergence+quota", {
129
132
  pass: false,
130
133
  fullVerifyPass: false,
@@ -135,7 +138,7 @@ export const LOOP_BENCHMARK_FIXTURE_SAMPLES = [
135
138
  regressionPause: false,
136
139
  changedPaths: 1,
137
140
  repairPasses: 1,
138
- }, ["./test/dag-runner.test.ts"], "Quota strategy does not change write-guard retry policy."),
141
+ }, ["./test/dag-runner.test.ts", "./test/dag-runner-convergence.test.ts", "./test/dag-runner-artifacts.test.ts", "./test/dag-runner-decisions.test.ts"], "Quota strategy does not change write-guard retry policy."),
139
142
  ]),
140
143
  sample("LB-04", "quota final gate must stay full", "loop-runtime", [
141
144
  result("single-repair", {
package/docs/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `docs/` 是 loop-agent 源仓的治理文档根目录。新读者不需要先浏览所有文件,先按任务选择入口,再由目录 README 或 `docs/document-catalog.json` 判断文档身份。
4
4
 
5
- **维护日:2026-08-16** — 对齐 `@tea-agent/loop-agent@0.35.3`。版本事实以根 `CHANGELOG.md` 与 `package.json` 为准;能力短摘要见 `reports/current-capability-summary.md`。
5
+ **维护日:2026-09-16** — 对齐 `@tea-agent/loop-agent@0.41.1-beta.0`。版本事实以根 `CHANGELOG.md` 与 `package.json` 为准;能力短摘要见 `reports/current-capability-summary.md`。
6
6
 
7
7
  ## 我现在要做什么?
8
8