@tea-agent/loop-agent 0.39.0-beta.0 → 0.39.0-beta.10

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 (331) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +248 -95
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +12 -0
  6. package/dist/application/task-lifecycle/recommendations.js +13 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +23 -5
  9. package/dist/cli/program.js +21 -2
  10. package/dist/cli/update/notifier.js +2 -2
  11. package/dist/cli/update/runtime-activity.js +1 -29
  12. package/dist/cli.js +2 -1
  13. package/dist/commands/client-recovery.js +3 -0
  14. package/dist/commands/dag-follow-up.js +138 -0
  15. package/dist/commands/dag-rerun.js +55 -1
  16. package/dist/commands/init-model-catalog.js +464 -0
  17. package/dist/commands/init-upgrade.js +265 -97
  18. package/dist/commands/init.js +455 -28
  19. package/dist/commands/inspect-next.js +8 -0
  20. package/dist/commands/task-advance.js +19 -0
  21. package/dist/executors/dag-pi-executor.js +2306 -61
  22. package/dist/executors/pi-executor.js +7 -0
  23. package/dist/executors/pi-extension-resolver.js +233 -0
  24. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  25. package/dist/executors/pi-read-budget-policy.js +239 -0
  26. package/dist/executors/pi-sdk-executor.js +180 -33
  27. package/dist/executors/shell-executor.js +1249 -224
  28. package/dist/executors/shell-write-guard.js +14 -0
  29. package/dist/executors/workspace-write-snapshot.js +68 -0
  30. package/dist/infrastructure/harness/atomic-write.js +23 -0
  31. package/dist/shared/dag-failure-category.js +150 -0
  32. package/dist/shared/dag-prompt-override.js +27 -0
  33. package/dist/shared/openspec-spec.js +70 -4
  34. package/dist/shared/operator/capabilities.js +39 -0
  35. package/dist/shared/playwright-cli-command-policy.js +15 -0
  36. package/dist/shared/update/console-notifier.js +100 -0
  37. package/dist/{cli → shared}/update/npm-client.js +70 -15
  38. package/dist/{cli → shared}/update/state.js +41 -13
  39. package/dist/task/config-types.js +50 -5
  40. package/dist/task/contract/apply.js +11 -1
  41. package/dist/task/contract/constants.js +2 -0
  42. package/dist/task/contract/hash.js +3 -0
  43. package/dist/task/contract/observe.js +25 -4
  44. package/dist/task/contract/paths.js +2 -1
  45. package/dist/task/contract/project.js +18 -1
  46. package/dist/task/contract/schema.js +4 -1
  47. package/dist/task/contract/transaction.js +12 -0
  48. package/dist/task/frontend-project-capability.js +199 -18
  49. package/dist/task/source-prepare/completeness.js +188 -0
  50. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  51. package/dist/task/source-prepare/index.js +3 -0
  52. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  53. package/dist/task/source-prepare/ledger-review.js +214 -0
  54. package/dist/task/source-prepare/ledger.js +545 -0
  55. package/dist/task/source-prepare/parse-intent.js +24 -4
  56. package/dist/task/source-prepare/prepare.js +298 -1
  57. package/dist/task/source-prepare/semantic-intake.js +25 -5
  58. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  59. package/dist/task/source-prepare/types.js +22 -0
  60. package/dist/task/source-references.js +22 -1
  61. package/dist/worker/cli.js +18 -2
  62. package/dist/worker/console/chat/chat-event-store.js +62 -4
  63. package/dist/worker/console/chat/context-panel.js +7 -0
  64. package/dist/worker/console/chat/deferred-turn.js +12 -0
  65. package/dist/worker/console/chat/operation-card.js +8 -1
  66. package/dist/worker/console/chat/pi-console-config.js +81 -10
  67. package/dist/worker/console/chat/pi-runtime.js +264 -16
  68. package/dist/worker/console/chat/provider-error.js +98 -0
  69. package/dist/worker/console/chat/routes.js +298 -32
  70. package/dist/worker/console/chat/session-stats.js +179 -0
  71. package/dist/worker/console/chat/session-store.js +93 -63
  72. package/dist/worker/console/chat/todos.js +115 -0
  73. package/dist/worker/console/chat/tool-preview.js +42 -0
  74. package/dist/worker/console/chat/turn-process.js +20 -55
  75. package/dist/worker/console/chat/user-questions.js +266 -0
  76. package/dist/worker/console/chat/workspace-landing.js +14 -0
  77. package/dist/worker/console/console-handoff.js +82 -0
  78. package/dist/worker/console/console-update-and-init.js +115 -0
  79. package/dist/worker/console/console-update-runtime.js +84 -0
  80. package/dist/worker/console/dag-execution-receipt.js +33 -0
  81. package/dist/worker/console/draft-store.js +49 -0
  82. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  83. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  84. package/dist/worker/console/index.js +3 -0
  85. package/dist/worker/console/inspect-split.js +18 -0
  86. package/dist/worker/console/native-directory-picker.js +13 -0
  87. package/dist/worker/console/operation-run-facts.js +19 -3
  88. package/dist/worker/console/operation-store.js +227 -7
  89. package/dist/worker/console/operator-actions.js +388 -0
  90. package/dist/worker/console/operator-surface-health.js +1 -0
  91. package/dist/worker/console/operator-user-error.js +2 -2
  92. package/dist/worker/console/prd-intake-bridge.js +13 -0
  93. package/dist/worker/console/routes.js +355 -3
  94. package/dist/worker/console/security.js +67 -13
  95. package/dist/worker/console/server.js +609 -164
  96. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-BAsjg7ew.js} +1 -1
  97. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-CyIlrwZw.js} +1 -1
  98. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-Bo4tobhH.js} +1 -1
  99. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dfpow9gu.js} +1 -1
  100. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-DPmzG_B4.js} +1 -1
  101. package/dist/worker/console/static/assets/channel-DSrJAn0-.js +1 -0
  102. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-pKQpB1UW.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-5n9YYquZ.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DP3CtA2a.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DYxMJPUy.js} +1 -1
  106. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-CS3D50Io.js} +1 -1
  107. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-gSdDfo5_.js} +1 -1
  108. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-A48q0ZNt.js} +1 -1
  109. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-ow8Mm3DY.js} +1 -1
  110. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-CMKfLWYE.js} +1 -1
  111. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BiTBdQPX.js} +1 -1
  112. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +1 -0
  113. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +1 -0
  114. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-Cm7exRz_.js} +1 -1
  115. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-D9O5qGOu.js} +1 -1
  116. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-CkvQnako.js} +1 -1
  117. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-B1rzp7CB.js} +1 -1
  118. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BP07Ipcc.js} +1 -1
  119. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BlPzweQb.js} +1 -1
  120. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-CxPa7n0S.js} +1 -1
  121. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CMTZs53A.js} +1 -1
  122. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-CxG_X52r.js} +1 -1
  123. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D8axtJM0.js} +1 -1
  124. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DIsGzn7b.js} +1 -1
  125. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CU9zkDG0.js} +1 -1
  126. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-DCjTmz5P.js} +1 -1
  127. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BksdmFzg.js} +1 -1
  128. package/dist/worker/console/static/assets/index-B-O5ChYq.css +1 -0
  129. package/dist/worker/console/static/assets/index-CfnY8AQx.js +407 -0
  130. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-CGzzzJtL.js} +1 -1
  131. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BdseB80N.js} +1 -1
  132. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-DM9gsgha.js} +1 -1
  133. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-3q-_9R2b.js} +1 -1
  134. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-B8N-Mxsd.js} +1 -1
  135. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-EPIHUUsu.js} +5 -5
  136. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CxwO12aa.js} +1 -1
  137. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-bPqbVX6Y.js} +1 -1
  138. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-KzqIvzeX.js} +1 -1
  139. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-CyofpWiV.js} +1 -1
  140. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-cTWJvLTP.js} +1 -1
  141. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-BK--q27B.js} +1 -1
  142. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-C0SDOHXK.js} +1 -1
  143. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-Cong6YIo.js} +1 -1
  144. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DUVkgZx8.js} +1 -1
  145. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C7XgxuZg.js} +1 -1
  146. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +1 -0
  147. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-D9Ayvwcy.js} +2 -2
  148. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +8 -0
  149. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-Byb000cH.js} +1 -1
  150. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DMLegPt4.js} +1 -1
  151. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-ChMMeOsP.js} +1 -1
  152. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-bEMEPisW.js} +1 -1
  153. package/dist/worker/console/static/index.html +2 -2
  154. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  155. package/dist/worker/console/static-src/app/console-types.js +68 -4
  156. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  157. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  158. package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
  159. package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
  160. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  161. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  162. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  163. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
  164. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  165. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  166. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  167. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  168. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  169. package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
  170. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
  171. package/dist/worker/console/static-src/operator-chat/useChatStream.js +102 -21
  172. package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
  173. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  174. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  175. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  176. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  177. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  178. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  179. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  180. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  181. package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
  182. package/dist/worker/console/workspace-context.js +344 -0
  183. package/dist/worker/console/workspace-registry.js +214 -0
  184. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  185. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  186. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  187. package/dist/worker/observability/init-runtime-activity.js +26 -0
  188. package/dist/worker/observability/read-model.js +22 -0
  189. package/dist/worker/observe/node-input.js +160 -3
  190. package/dist/worker/observe/node-process.js +377 -0
  191. package/dist/worker/observe/routes.js +224 -3
  192. package/dist/worker/observe/server.js +4 -0
  193. package/dist/worker/observe/shell-handler-keys.js +34 -0
  194. package/dist/worker/observe/static/api.js +90 -4
  195. package/dist/worker/observe/static/app.js +18 -70
  196. package/dist/worker/observe/static/constants.js +8 -1
  197. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  198. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  199. package/dist/worker/observe/static/dag-node-purpose.js +208 -0
  200. package/dist/worker/observe/static/format.js +60 -0
  201. package/dist/worker/observe/static/index.html +6 -1
  202. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  203. package/dist/worker/observe/static/markdown-render.js +20 -1
  204. package/dist/worker/observe/static/operator-chrome.css +145 -20
  205. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  206. package/dist/worker/observe/static/operator-chrome.js +330 -96
  207. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  208. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  209. package/dist/worker/observe/static/router.d.ts +46 -0
  210. package/dist/worker/observe/static/router.js +25 -1
  211. package/dist/worker/observe/static/state.d.ts +50 -0
  212. package/dist/worker/observe/static/state.js +184 -1
  213. package/dist/worker/observe/static/styles.css +395 -0
  214. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  215. package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
  216. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  217. package/dist/worker/observe/static/views/dag.js +14 -5
  218. package/dist/worker/observe/static/views/dashboard.js +6 -4
  219. package/dist/worker/observe/static/views/failures.js +5 -2
  220. package/dist/worker/observe/static/views/night.js +2 -2
  221. package/dist/worker/observe/static/views/pool.js +1 -0
  222. package/dist/worker/observe/static/views/run.js +3 -1
  223. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  224. package/dist/worker/observe/static/views/task.js +56 -1
  225. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
  226. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  227. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  228. package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
  229. package/dist/workflows/dag/contract-output-registry.js +15 -0
  230. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  231. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  232. package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
  233. package/dist/workflows/dag/failure-category.js +7 -116
  234. package/dist/workflows/dag/frontend-closeout.js +221 -0
  235. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  236. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  237. package/dist/workflows/dag/frontend-implementation-contract.js +534 -178
  238. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  239. package/dist/workflows/dag/frontend-prewrite-gate.js +371 -393
  240. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  241. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  242. package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
  243. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  244. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  245. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  246. package/dist/workflows/dag/frontend-repair.js +7 -424
  247. package/dist/workflows/dag/frontend-review-context.js +288 -14
  248. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  249. package/dist/workflows/dag/frontend-risk.js +15 -2
  250. package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
  251. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  252. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  253. package/dist/workflows/dag/frontend-shape.js +427 -0
  254. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  255. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  256. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  257. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  258. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  259. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  260. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  261. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  262. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  263. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  264. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  265. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  266. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  267. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  268. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  269. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  270. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  271. package/dist/workflows/dag/frontend-writer-admission.js +272 -0
  272. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  273. package/dist/workflows/dag/init-hybrid.js +984 -618
  274. package/dist/workflows/dag/interrupt-request.js +10 -3
  275. package/dist/workflows/dag/lifecycle.js +3 -3
  276. package/dist/workflows/dag/node-execution.js +468 -34
  277. package/dist/workflows/dag/prompt.js +44 -19
  278. package/dist/workflows/dag/recovery-lease.js +80 -0
  279. package/dist/workflows/dag/report.js +37 -1
  280. package/dist/workflows/dag/rerun-feedback.js +124 -1
  281. package/dist/workflows/dag/rerun-plan.js +144 -5
  282. package/dist/workflows/dag/rerun-run.js +81 -6
  283. package/dist/workflows/dag/rerun-task.js +29 -0
  284. package/dist/workflows/dag/retry-policy.js +318 -8
  285. package/dist/workflows/dag/runner.js +429 -122
  286. package/dist/workflows/dag/scheduler.js +114 -16
  287. package/dist/workflows/dag/structured-output-repair.js +712 -0
  288. package/dist/workflows/dag/types.js +321 -31
  289. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  290. package/dist/workflows/dag/validate.js +92 -32
  291. package/docs/README.md +2 -0
  292. package/docs/architecture/evolution.md +45 -0
  293. package/docs/init-surface.manifest.json +9 -0
  294. package/docs/operations/README.md +1 -1
  295. package/docs/operations/local-development-environment.md +21 -0
  296. package/docs/templates/README.md +2 -1
  297. package/docs/templates/agent-dag-report.schema.json +8 -2
  298. package/docs/templates/agent-dag.schema.json +45 -1
  299. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  300. package/docs/templates/frontend-test-case-checklist.md +2 -2
  301. package/docs/templates/frontend-test-dag.json +8 -10
  302. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  303. package/docs/templates/init-managed-agents.md +13 -3
  304. package/docs/templates/spec-registry.schema.json +45 -0
  305. package/harness.json +3 -3
  306. package/package.json +7 -1
  307. package/scripts/next-info.mjs +356 -0
  308. package/scripts/next-publish-gate.mjs +238 -0
  309. package/scripts/release-source-binding.mjs +251 -0
  310. package/skills/codebase-scout/SKILL.md +1 -1
  311. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  312. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  313. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  314. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  315. package/skills/frontend-design-review/SKILL.md +20 -41
  316. package/skills/frontend-implementation/SKILL.md +3 -4
  317. package/skills/frontend-implementation/references/design-spec.md +15 -10
  318. package/skills/frontend-implementation/references/node-contracts.md +23 -13
  319. package/skills/frontend-review/SKILL.md +22 -14
  320. package/skills/frontend-review/references/review-findings.md +6 -7
  321. package/skills/loop-agent/references/command-reference.md +12 -4
  322. package/skills/loop-agent/references/hybrid-dag.md +8 -1
  323. package/skills/playwright-cli/SKILL.md +23 -1
  324. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  325. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  326. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  327. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  328. package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
  329. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  330. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  331. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { pathMatchesPattern } from "../../shared/git-progress.js";
6
+ import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
6
7
  import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
7
8
  import { FRONTEND_PREWRITE_RESULT_SOURCE_ARTIFACT, FRONTEND_WRITER_NODE_IDS, isFrontendWriterAuthorized, readFrontendPrewriteResult, } from "./scheduler.js";
8
9
  import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
@@ -10,11 +11,15 @@ import { resolveContextPolicy } from "./context-policy.js";
10
11
  import { buildDagNodePromptEnvelope, formatConvergenceFeedbackBlock, } from "./prompt.js";
11
12
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
12
13
  import { buildOutputLimitRecoverySection, loadBackendTestWriterProgressForRetry, } from "./backend-test-writer-completeness.js";
13
- import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
14
+ import { computeBackoffDelayMs, isCanonicalFinalVerifyShellRetryCandidate, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, isWriterTransportRetryCandidate, projectFrontendNodeProtocolFailureReason, resolveFrontendPlanBackupRoute, resolveFrontendPlanRetryStep, VERIFY_SHELL_RETRY_POLICY, } from "./retry-policy.js";
14
15
  import { applyNodeActivity, evaluateNodeLiveness, resolveLivenessPolicy, } from "./liveness-policy.js";
15
16
  import { buildProtocolRetryInstruction, normalizeReviewVerdictAfterRetries, parseJsonReviewVerdict, validateOutputProtocol, } from "./output-protocol.js";
16
17
  import { getStructuredContractValidator } from "./contract-output-registry.js";
17
18
  import "./contract-validator-registrations.js";
19
+ import { computeNormalizedFailureFingerprint } from "./frontend-recovery-lineage.js";
20
+ import { readTypedEventStoreFromJsonl } from "./frontend-typed-event-store.js";
21
+ import { allowedRepairReadPaths, auditRepairAttemptToolUse, buildStructuredOutputRepairPrompt, freezeStructuredOutputRepairContext, hasNonEmptyStructuredCandidate, isFrontendStructuredRepairSchemaId, isStructuredRepairableFailureCategory, persistStructuredAttemptRaw, sessionEventsByteLength, GOVERNANCE_BLOCKED_CATEGORY, STRUCTURED_REPAIR_EXHAUSTED_CATEGORY, } from "./structured-output-repair.js";
22
+ import { readFrontendCanonicalCandidate } from "./frontend-implementation-contract.js";
18
23
  import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
19
24
  import { buildProjectGovernanceContext, readCompletedWriterChangeManifests, writeProjectGovernanceContext, } from "./project-governance-context.js";
20
25
  import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
@@ -135,6 +140,11 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
135
140
  "Return exactly this artifact and nothing else: one short lead-in line, then exactly one fenced json block containing only the editable RFC 7386 plan patch, immediately followed by exactly one fenced openspec-citations block.",
136
141
  "The runtime applies your patch to its protected contract skeleton. Do not emit schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff; do not emit the full contract.",
137
142
  "Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
143
+ "The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
144
+ "```openspec-citations",
145
+ '{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
146
+ '{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
147
+ "```",
138
148
  ];
139
149
  }
140
150
  if (schemaId === "frontend-implementation-contract-revision-patch-v1") {
@@ -142,6 +152,11 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
142
152
  "Return exactly this artifact and nothing else: one short lead-in line, then exactly one fenced json block containing the RFC 7386 merge-patch delta, immediately followed by exactly one fenced openspec-citations block.",
143
153
  "The delta contains only the fields you change; null deletes a key, arrays and scalars replace, and plain objects merge recursively. Do not emit a full contract or a schemaVersion/targets section.",
144
154
  "Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
155
+ "The openspec-citations fenced block contains one JSON object per line, each with path/section/line — for example:",
156
+ "```openspec-citations",
157
+ '{"path":".harness/tasks/<task>/source/需求.md","section":"Objective","line":5}',
158
+ '{"path":".harness/tasks/<task>/source/references/product-requirement.md","section":"FR-1","line":16}',
159
+ "```",
145
160
  ];
146
161
  }
147
162
  return [
@@ -149,9 +164,131 @@ function frontendStructuredArtifactRetryGuidance(schemaId) {
149
164
  "The contract JSON fields are the complete implementation plan. Do not emit a separate plan document, Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
150
165
  ];
151
166
  }
152
- function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics) {
167
+ const FRONTEND_PLAN_NODE_ID = "frontend-plan-pi";
168
+ const COMPACT_RETRY_SECTION_MAX_CHARS = 1_200;
169
+ const COMPACT_RETRY_TASK_MAX_CHARS = 4_800;
170
+ /** Keep alias/field-shape remediation out of the initial Plan prompt. */
171
+ function frontendPlanValidationRetryGuidance(reason) {
172
+ const guidance = [];
173
+ if (/notApplicableReason|uiState.*reason/i.test(reason)) {
174
+ guidance.push("For this retry, use uiStates[].notApplicableReason for a non-applicable state; omit expectedBehavior instead of supplying an empty value.");
175
+ }
176
+ if (/interaction.*(?:\bid\b|\bname\b)|requires non-empty interaction\.name/i.test(reason)) {
177
+ guidance.push("For this retry, use interactions[].name with non-empty trigger and expectedBehavior.");
178
+ }
179
+ if (/verificationTargets.*uiStates|uiStates.*verificationTargets/i.test(reason)) {
180
+ guidance.push("For this retry, provide verificationTargets[].uiStates as an array; use [] when the target has no named UI state.");
181
+ }
182
+ return guidance;
183
+ }
184
+ function compactRetryText(text, maxChars) {
185
+ if (text.length <= maxChars)
186
+ return text;
187
+ const headChars = Math.floor(maxChars * 0.78);
188
+ const tailChars = Math.max(0, maxChars - headChars - 52);
189
+ return `${text.slice(0, headChars)}\n… [omitted after context-overflow] …\n${text.slice(-tailChars)}`;
190
+ }
191
+ function compactRetrySection(prompt, name, maxChars) {
192
+ const match = prompt.match(new RegExp(`<${name}>([\\s\\S]*?)</${name}>`));
193
+ return compactRetryText(match?.[1]?.trim() ?? "(not available)", maxChars);
194
+ }
195
+ /**
196
+ * A context-overflow retry must not resend the full resolved skills, governance
197
+ * payload, repeated source excerpts, and upstream previews that caused the
198
+ * previous session to overflow. Keep the immutable node contract plus bounded
199
+ * objective/criteria/task excerpts; canonical artifacts remain readable by
200
+ * pointer from the retained task instructions.
201
+ */
202
+ export function buildContextOverflowRetryPrompt(task, basePrompt) {
203
+ return [
204
+ "<compact_retry_context>",
205
+ "This is a fresh, compact retry after provider context-overflow. The original full prompt remains audit evidence but is intentionally not resent.",
206
+ "Do not re-read task sources, upstream stdout, skills, or full diff artifacts that are already represented by canonical artifacts. Start from the smallest retained artifact and expand only the exact file or diff fragment needed.",
207
+ "</compact_retry_context>",
208
+ `<dag_objective>\n${compactRetrySection(basePrompt, "dag_objective", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</dag_objective>`,
209
+ `<success_criteria>\n${compactRetrySection(basePrompt, "success_criteria", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</success_criteria>`,
210
+ `<global_constraints>\n${compactRetrySection(basePrompt, "global_constraints", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</global_constraints>`,
211
+ `<node_contract>\n${compactRetrySection(basePrompt, "node_contract", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</node_contract>`,
212
+ `<upstream_context>\n${compactRetrySection(basePrompt, "upstream_context", COMPACT_RETRY_SECTION_MAX_CHARS)}\n</upstream_context>`,
213
+ [
214
+ "<task>",
215
+ `Original task id: ${task.id}`,
216
+ compactRetrySection(basePrompt, "task", COMPACT_RETRY_TASK_MAX_CHARS),
217
+ "</task>",
218
+ ].join("\n"),
219
+ ].join("\n\n");
220
+ }
221
+ function isFrontendPlanLadderTask(task) {
222
+ return task.id === FRONTEND_PLAN_NODE_ID;
223
+ }
224
+ /**
225
+ * Read the committed plan ledger snapshot for the ladder's "no new committed
226
+ * fact" check. The digest is over the sorted committed payload hashes, and
227
+ * `hasTerminalFact` reflects a committed `finalize_plan` terminal fact.
228
+ */
229
+ async function readFrontendPlanCommittedSnapshot(runDir, nodeId) {
230
+ const records = await readTypedEventStoreFromJsonl(path.join(runDir, nodeId, "plan-typed-facts.jsonl"));
231
+ const committed = records.filter((record) => record.phase === "committed");
232
+ const digest = createHash("sha256")
233
+ .update(committed
234
+ .map((record) => record.payloadSha256)
235
+ .sort()
236
+ .join("\n"))
237
+ .digest("hex");
238
+ const hasTerminalFact = committed.some((record) => record.fact.kind === "finalize_plan");
239
+ return { digest, hasTerminalFact };
240
+ }
241
+ export function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep) {
153
242
  if (attemptNumber <= 1)
154
243
  return basePrompt;
244
+ // Context overflow is a transport/session failure, not a plan-protocol
245
+ // failure. It must take precedence over the plan retry ladder so every
246
+ // retry, including a repeated plan overflow, starts from the compact
247
+ // envelope rather than re-sending the original prompt.
248
+ if (previousFailureCategory === "context-overflow") {
249
+ return [
250
+ buildContextOverflowRetryPrompt(task, basePrompt),
251
+ "",
252
+ "<retry_instruction>",
253
+ "The previous attempt exceeded the provider context window. This retry starts a fresh session: keep the evidence surface narrow.",
254
+ "Do not re-read task sources, upstream stdout, skills, or full diff artifacts that are already represented by the canonical contract/review context. Read the smallest relevant artifact first, then only the specific source file or diff fragment needed to decide a finding.",
255
+ "Do not read artifacts/diff_patch.patch in full. For frontend review, read the bounded diff summary and then at most the specific per-file diff fragments it identifies, in part order.",
256
+ "Use concise tool calls and finish the required terminal/output protocol as soon as the evidence is sufficient.",
257
+ "</retry_instruction>",
258
+ ].join("\n");
259
+ }
260
+ if (frontendPlanRetryStep === "compact-terminal-first") {
261
+ return [
262
+ basePrompt,
263
+ "",
264
+ "<retry_instruction>",
265
+ "Frontend plan retry ladder step: compact-terminal-first.",
266
+ "Do NOT rewrite the narrative. Only adopt already staged/quarantined fresh facts, fill the missing required facts, then call finalize_plan exactly once.",
267
+ "Keep the existing committed ledger intact; do not re-derive already committed facts.",
268
+ "</retry_instruction>",
269
+ ].join("\n");
270
+ }
271
+ if (frontendPlanRetryStep === "bounded-tool-only") {
272
+ return [
273
+ basePrompt,
274
+ "",
275
+ "<retry_instruction>",
276
+ "Frontend plan retry ladder step: bounded-tool-only.",
277
+ "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.",
278
+ "Do not expand scope or re-derive already committed facts.",
279
+ "</retry_instruction>",
280
+ ].join("\n");
281
+ }
282
+ if (frontendPlanRetryStep === "backup-model") {
283
+ return [
284
+ basePrompt,
285
+ "",
286
+ "<retry_instruction>",
287
+ "Frontend plan retry ladder step: backup-model.",
288
+ "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.",
289
+ "</retry_instruction>",
290
+ ].join("\n");
291
+ }
155
292
  if (previousFailureCategory === "protocol-invalid" &&
156
293
  task.outputProtocol &&
157
294
  previousProtocolReason) {
@@ -161,9 +298,65 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
161
298
  buildProtocolRetryInstruction(task.outputProtocol, previousProtocolReason),
162
299
  ].join("\n");
163
300
  }
301
+ if (previousFailureCategory === "review-terminal-missing") {
302
+ return [
303
+ basePrompt,
304
+ "",
305
+ "<retry_instruction>",
306
+ "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.",
307
+ "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.",
308
+ "</retry_instruction>",
309
+ ].join("\n");
310
+ }
311
+ if (previousFailureCategory === "read-burst") {
312
+ if (task.id !== FRONTEND_PLAN_NODE_ID) {
313
+ return [
314
+ basePrompt,
315
+ "",
316
+ "<retry_instruction>",
317
+ "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.",
318
+ "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.",
319
+ "</retry_instruction>",
320
+ ].join("\n");
321
+ }
322
+ return [
323
+ basePrompt,
324
+ "",
325
+ "<retry_instruction>",
326
+ "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.",
327
+ "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.",
328
+ "</retry_instruction>",
329
+ ].join("\n");
330
+ }
164
331
  if (previousFailureCategory === "invalid-output" &&
165
332
  task.structuredContractOutput &&
166
333
  previousProtocolReason) {
334
+ // The frontend plan node's compile authority is the committed typed
335
+ // ledger, not a fenced JSON text artifact: its retry guidance must
336
+ // direct the model to re-commit corrected record_* facts and
337
+ // finalize_plan. The legacy full-contract JSON guidance below applies
338
+ // only to nodes whose authority is still a text contract artifact.
339
+ if (task.structuredContractOutput.schemaId ===
340
+ "frontend-implementation-contract-plan-patch-v1") {
341
+ const splitGuidance = /write-set-too-large|split the task/.test(previousProtocolReason ?? "")
342
+ ? [
343
+ "",
344
+ "The writeSet is too large for one implement node. Do NOT delete implementation files to squeeze under the limit — that drops required work. Split the task via record_split_proposal (or narrow targets.files to a genuine subset) so each implement node stays bounded; the full file set must remain covered across the split.",
345
+ ]
346
+ : [];
347
+ return [
348
+ basePrompt,
349
+ "",
350
+ "<retry_instruction>",
351
+ "Previous plan ledger facts failed canonical contract validation:",
352
+ previousProtocolReason,
353
+ "Fix the reported violations by re-committing corrected record_* facts and calling finalize_plan exactly once. The committed typed ledger is the only compile authority.",
354
+ "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
355
+ ...frontendPlanValidationRetryGuidance(previousProtocolReason),
356
+ ...splitGuidance,
357
+ "</retry_instruction>",
358
+ ].join("\n");
359
+ }
167
360
  return [
168
361
  basePrompt,
169
362
  "",
@@ -175,25 +368,30 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
175
368
  "</retry_instruction>",
176
369
  ].join("\n");
177
370
  }
371
+ if (previousFailureCategory === "invalid-output" &&
372
+ task.id === "frontend-scout-pi") {
373
+ return [
374
+ basePrompt,
375
+ "",
376
+ "<retry_instruction>",
377
+ "The previous Scout attempt did not commit a complete, runtime-evidenced target surface.",
378
+ "Search the repository only as needed to establish the real entrypoint, implementation ownership, and applicable test path. Commit record_target_surface with completeness=complete and unresolvedPaths=[] only after at least one named target has fresh runtime evidence. If ownership truly cannot be established, record completeness=blocked with each unresolved path; do not make Plan discover it.",
379
+ "</retry_instruction>",
380
+ ].join("\n");
381
+ }
178
382
  if (previousFailureCategory === "structured-output-truncated" &&
179
383
  task.structuredContractOutput) {
180
- const schemaId = task.structuredContractOutput.schemaId;
181
- if (schemaId === "frontend-implementation-contract-plan-patch-v1" ||
182
- schemaId === "frontend-implementation-contract-revision-patch-v1") {
183
- const payloadKind = schemaId === "frontend-implementation-contract-plan-patch-v1"
184
- ? "editable plan patch"
185
- : "RFC 7386 revision patch";
384
+ if (task.structuredContractOutput.schemaId ===
385
+ "frontend-implementation-contract-plan-patch-v1") {
186
386
  return [
387
+ basePrompt,
388
+ "",
187
389
  "<retry_instruction>",
188
- "Previous response was truncated (stopReason=length).",
189
- previousProtocolReason ? `Validation error: ${previousProtocolReason}` : "",
190
- `Return one short lead-in, one complete fenced json ${payloadKind}, then one fenced openspec-citations block.`,
191
- "Protected: schemaVersion, sourceBinding, riskLevel, targets.files, mockApi.productionDefaultOff. Required semantics: requirement expectedOutcome; interaction trigger + expectedBehavior; frozen verification command labels.",
192
- "No prose, examples, schema copy, Markdown headings, raw JSON, or other fences.",
390
+ "Previous attempt was truncated by the provider (stopReason=length) before the plan facts were fully committed.",
391
+ "Re-commit the missing record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority. Do NOT re-read contract/scout outputs or source files — use the facts already in context. Commit record_* facts one tool call per message, then finalize_plan immediately.",
392
+ "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
193
393
  "</retry_instruction>",
194
- ]
195
- .filter(Boolean)
196
- .join("\n");
394
+ ].join("\n");
197
395
  }
198
396
  return [
199
397
  basePrompt,
@@ -266,11 +464,33 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
266
464
  "</retry_instruction>",
267
465
  ].join("\n");
268
466
  }
467
+ if (previousFailureCategory === "writer-clean-timeout") {
468
+ return [
469
+ basePrompt,
470
+ "",
471
+ "<retry_instruction>",
472
+ "Previous attempt ended in a clean provider/transport failure with zero attributed workspace writes and zero write/edit tool calls.",
473
+ "Do not invent existing implementation progress. Continue from the original task only if the workspace is still unchanged.",
474
+ "</retry_instruction>",
475
+ ].join("\n");
476
+ }
269
477
  if (previousFailureCategory !== "output-too-large") {
270
478
  return basePrompt;
271
479
  }
272
480
  if (task.outputMode === "structured-required") {
273
481
  if (task.structuredContractOutput) {
482
+ if (task.structuredContractOutput.schemaId ===
483
+ "frontend-implementation-contract-plan-patch-v1") {
484
+ return [
485
+ basePrompt,
486
+ "",
487
+ "<retry_instruction>",
488
+ "Previous attempt exceeded the structured output size limit.",
489
+ "Re-commit corrected record_* facts and call finalize_plan exactly once; the committed typed ledger is the only compile authority.",
490
+ "Do not emit a fenced JSON contract, protected skeleton fields, or an openspec-citations block; narrative JSON is not validated and wastes the output budget.",
491
+ "</retry_instruction>",
492
+ ].join("\n");
493
+ }
274
494
  return [
275
495
  basePrompt,
276
496
  "",
@@ -302,6 +522,35 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
302
522
  "</retry_instruction>",
303
523
  ].join("\n");
304
524
  }
525
+ const PROMPT_RESTART_CANDIDATE_MAX_BYTES = 64 * 1024;
526
+ function promptRestartCandidateForAttempt(input) {
527
+ if (input.attemptPrompt === input.baseRuntimePrompt) {
528
+ return input.taskPrompt;
529
+ }
530
+ const appendedPrefix = `${input.baseRuntimePrompt}\n\n`;
531
+ if (input.attemptPrompt.startsWith(appendedPrefix)) {
532
+ return [
533
+ input.taskPrompt,
534
+ input.attemptPrompt.slice(appendedPrefix.length),
535
+ ]
536
+ .filter(Boolean)
537
+ .join("\n\n");
538
+ }
539
+ return input.attemptPrompt;
540
+ }
541
+ async function persistFailedPromptRestartCandidate(input) {
542
+ const redacted = redactSecrets(input.prompt);
543
+ const preview = truncateUtf8Preview(redacted, PROMPT_RESTART_CANDIDATE_MAX_BYTES);
544
+ await writeDagNodeJsonArtifact(input.runDir, input.nodeId, `prompt-candidate-attempt-${input.attempt}.json`, {
545
+ schemaVersion: 1,
546
+ attempt: input.attempt,
547
+ failureCategory: input.failureCategory ?? "unknown",
548
+ prompt: preview,
549
+ truncated: preview !== redacted,
550
+ maxBytes: PROMPT_RESTART_CANDIDATE_MAX_BYTES,
551
+ sha256: createHash("sha256").update(redacted).digest("hex"),
552
+ });
553
+ }
305
554
  export async function buildNodePromptWithResolvedSkillInstructions(spec, task, upstream, cwd, options) {
306
555
  const policy = resolveContextPolicy(spec);
307
556
  const skillNames = policy.resolveSkills(spec, task);
@@ -691,7 +940,7 @@ export async function executeDagNode(input) {
691
940
  }
692
941
  node.resolvedSkills = resolvedSkills;
693
942
  await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
694
- const model = resolveModelForTask(task, spec.executorModels);
943
+ let model = resolveModelForTask(task, spec.executorModels);
695
944
  let thinking;
696
945
  if (task.executor === "pi") {
697
946
  try {
@@ -702,16 +951,25 @@ export async function executeDagNode(input) {
702
951
  // Invalid or missing harness preserves the previous no-override behavior.
703
952
  }
704
953
  }
705
- const retryPolicy = task.retryPolicy &&
954
+ const specRetryPolicy = task.retryPolicy &&
706
955
  (isSafeReadOnlyPiRetryCandidate(task) ||
707
956
  isWriterEmptyDiffRetryCandidate(task) ||
708
957
  isWriterTransportRetryCandidate(task))
709
958
  ? task.retryPolicy
710
959
  : undefined;
960
+ const implicitVerifyRetryPolicy = specRetryPolicy === undefined &&
961
+ isCanonicalFinalVerifyShellRetryCandidate(task) &&
962
+ isTargetTemplateTransientRetryNode(task)
963
+ ? VERIFY_SHELL_RETRY_POLICY
964
+ : undefined;
965
+ const retryPolicy = specRetryPolicy ?? implicitVerifyRetryPolicy;
711
966
  const maxAttempts = retryPolicy?.maxAttempts ?? 1;
712
967
  const attempts = [];
713
968
  let totalAttemptWallDurationMs = 0;
714
969
  let totalBackoffMs = 0;
970
+ const frontendPlanLadderEnabled = isFrontendPlanLadderTask(task);
971
+ let frontendPlanRetryStep = "normal";
972
+ const frontendPriorFingerprints = [];
715
973
  const livenessPolicy = resolveLivenessPolicy(spec.defaults?.livenessPolicy, task.livenessPolicy);
716
974
  /**
717
975
  * Attempt-fenced, throttled activity sink. Late events from a previous
@@ -749,10 +1007,25 @@ export async function executeDagNode(input) {
749
1007
  let terminalResult;
750
1008
  let previousFailureCategory;
751
1009
  let previousProtocolReason;
1010
+ let pendingStructuredArtifact;
1011
+ let lastRepairAllowlist = [];
752
1012
  for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
753
1013
  const attemptStartedAt = new Date().toISOString();
754
1014
  node.currentAttempt = attemptNumber;
755
1015
  node.livenessStatus = "active";
1016
+ const beforeCommittedDigest = frontendPlanLadderEnabled
1017
+ ? (await readFrontendPlanCommittedSnapshot(runDir, nodeId)).digest
1018
+ : undefined;
1019
+ if (frontendPlanLadderEnabled &&
1020
+ frontendPlanRetryStep === "backup-model") {
1021
+ model = resolveModelForTask(task, spec.executorModels);
1022
+ }
1023
+ // Capture the session-events.jsonl length BEFORE the executor appends this
1024
+ // attempt's events, so the post-attempt repair tool audit can be scoped to
1025
+ // exactly this attempt's segment (B1: earlier attempts legitimately use
1026
+ // ls/find and must not be attributed to the repair attempt).
1027
+ const attemptSessionEventsOffset = await sessionEventsByteLength(runDir, nodeId);
1028
+ let promptRestartCandidate = task.subtask_prompt;
756
1029
  let result;
757
1030
  try {
758
1031
  validateRepairArtifactGateBeforeShell({
@@ -775,12 +1048,65 @@ export async function executeDagNode(input) {
775
1048
  (acc[i.path] ??= []).push(i.detail);
776
1049
  return acc;
777
1050
  }, {});
1051
+ let attemptPrompt = buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics, frontendPlanRetryStep);
1052
+ if (attemptNumber > 1 &&
1053
+ // The plan node (frontend-plan-pi) is a typed-facts ladder task:
1054
+ // its retry is driven by the §5.1 ladder (compact-terminal-first
1055
+ // / backup-model) and its compile authority is the committed
1056
+ // ledger — never the legacy frozen fenced-JSON repair. The
1057
+ // frozen repair path below applies only to non-ladder
1058
+ // structured nodes whose output authority is a text artifact.
1059
+ !frontendPlanLadderEnabled &&
1060
+ isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
1061
+ isStructuredRepairableFailureCategory(previousFailureCategory) &&
1062
+ (await hasNonEmptyStructuredCandidate({
1063
+ runDir,
1064
+ nodeId,
1065
+ beforeAttempt: attemptNumber,
1066
+ }))) {
1067
+ const originalCanonical = task.structuredContractOutput?.schemaId ===
1068
+ "frontend-implementation-contract-revision-patch-v1"
1069
+ ? (await readFrontendCanonicalCandidate(runDir, "frontend-plan-pi"))
1070
+ : undefined;
1071
+ const frozen = await freezeStructuredOutputRepairContext({
1072
+ runDir,
1073
+ runId: state.runId,
1074
+ taskId: spec.sourceBinding?.taskId ?? task.id,
1075
+ nodeId,
1076
+ nodeAttempt: attemptNumber,
1077
+ targetSchemaId: task.structuredContractOutput.schemaId,
1078
+ skeleton: task.structuredContractOutput?.skeleton,
1079
+ originalCanonical,
1080
+ latestStopReason: previousFailureCategory === "structured-output-truncated"
1081
+ ? "length"
1082
+ : previousFailureCategory,
1083
+ });
1084
+ lastRepairAllowlist = allowedRepairReadPaths({
1085
+ runDir,
1086
+ context: frozen.context,
1087
+ contextPath: frozen.relativePath,
1088
+ });
1089
+ attemptPrompt = await buildStructuredOutputRepairPrompt({
1090
+ runDir,
1091
+ context: frozen.context,
1092
+ contextPath: frozen.relativePath,
1093
+ contextSha256: frozen.sha256,
1094
+ maxAttempts,
1095
+ schemaGuidance: frontendStructuredArtifactRetryGuidance(task.structuredContractOutput.schemaId),
1096
+ previousReason: previousProtocolReason,
1097
+ });
1098
+ }
1099
+ promptRestartCandidate = promptRestartCandidateForAttempt({
1100
+ taskPrompt: task.subtask_prompt,
1101
+ baseRuntimePrompt: prompt,
1102
+ attemptPrompt,
1103
+ });
778
1104
  result = await executeNode({
779
1105
  task,
780
1106
  cwd,
781
1107
  model,
782
1108
  ...(thinking ? { thinking } : {}),
783
- prompt: buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason, recoveryTargetPaths, recoveryDiagnostics),
1109
+ prompt: attemptPrompt,
784
1110
  attempt: attemptNumber,
785
1111
  reportActivity,
786
1112
  timeoutMs: livenessPolicy.absoluteMaxWallClockMs,
@@ -796,6 +1122,35 @@ export async function executeDagNode(input) {
796
1122
  durationMs: 0,
797
1123
  };
798
1124
  }
1125
+ if (isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId)) {
1126
+ const rawText = canonicalNodeOutput(result);
1127
+ if (rawText.trim().length > 0) {
1128
+ await persistStructuredAttemptRaw({
1129
+ runDir,
1130
+ nodeId,
1131
+ attempt: attemptNumber,
1132
+ text: rawText,
1133
+ });
1134
+ }
1135
+ if (attemptNumber > 1 && lastRepairAllowlist.length > 0) {
1136
+ const audit = await auditRepairAttemptToolUse({
1137
+ runDir,
1138
+ nodeId,
1139
+ allowedPaths: lastRepairAllowlist,
1140
+ fromByteOffset: attemptSessionEventsOffset,
1141
+ });
1142
+ if (!audit.ok) {
1143
+ result = {
1144
+ ...result,
1145
+ ok: false,
1146
+ failureCategory: GOVERNANCE_BLOCKED_CATEGORY,
1147
+ stderr: [result.stderr, audit.reason]
1148
+ .filter(Boolean)
1149
+ .join("\n"),
1150
+ };
1151
+ }
1152
+ }
1153
+ }
799
1154
  // R0: executor ok=true still fails closed when outputProtocol is violated.
800
1155
  // Valid semantic results (e.g. VERDICT: request-revision) pass validation.
801
1156
  if (result.ok && task.outputProtocol) {
@@ -832,16 +1187,17 @@ export async function executeDagNode(input) {
832
1187
  structuredContractOutput: task.structuredContractOutput,
833
1188
  });
834
1189
  if (!contractCheck.ok) {
835
- // A provider stopReason=length means the response was cut before the
836
- // JSON contract could complete; separate it from an ordinary bad
837
- // contract so the retry switches to a JSON-only output strategy.
838
1190
  const attemptStopReason = result.stopReason;
1191
+ const failureCategory = contractCheck.classification === "governance-blocked"
1192
+ ? GOVERNANCE_BLOCKED_CATEGORY
1193
+ : attemptStopReason === "length" ||
1194
+ contractCheck.classification === "truncated"
1195
+ ? "structured-output-truncated"
1196
+ : "invalid-output";
839
1197
  result = {
840
1198
  ...result,
841
1199
  ok: false,
842
- failureCategory: attemptStopReason === "length"
843
- ? "structured-output-truncated"
844
- : "invalid-output",
1200
+ failureCategory,
845
1201
  stderr: [result.stderr, contractCheck.reason]
846
1202
  .filter(Boolean)
847
1203
  .join("\n"),
@@ -849,14 +1205,6 @@ export async function executeDagNode(input) {
849
1205
  previousProtocolReason = contractCheck.reason;
850
1206
  }
851
1207
  else {
852
- if (contractCheck.structuredArtifact) {
853
- node.structuredArtifactPath = contractCheck.structuredArtifact.path;
854
- node.structuredArtifactSha256 = contractCheck.structuredArtifact.sha256;
855
- node.structuredArtifactSchemaId = contractCheck.structuredArtifact.schemaId;
856
- }
857
- if (contractCheck.frontendPlanOutput) {
858
- node.frontendPlanOutput = contractCheck.frontendPlanOutput;
859
- }
860
1208
  if (contractCheck.normalizedText) {
861
1209
  result = {
862
1210
  ...result,
@@ -864,6 +1212,9 @@ export async function executeDagNode(input) {
864
1212
  stdout: contractCheck.normalizedText,
865
1213
  };
866
1214
  }
1215
+ if (contractCheck.artifact) {
1216
+ pendingStructuredArtifact = contractCheck.artifact;
1217
+ }
867
1218
  previousProtocolReason = undefined;
868
1219
  }
869
1220
  }
@@ -876,6 +1227,16 @@ export async function executeDagNode(input) {
876
1227
  const attemptFinishedAt = new Date().toISOString();
877
1228
  const attemptWallDurationMs = durationBetween(attemptStartedAt, attemptFinishedAt);
878
1229
  totalAttemptWallDurationMs += attemptWallDurationMs;
1230
+ const rawFailureCategory = result.rawFailureCategory ?? result.failureCategory;
1231
+ if (task.executor === "pi" && !result.ok) {
1232
+ await persistFailedPromptRestartCandidate({
1233
+ runDir,
1234
+ nodeId,
1235
+ attempt: attemptNumber,
1236
+ failureCategory: result.failureCategory,
1237
+ prompt: promptRestartCandidate,
1238
+ });
1239
+ }
879
1240
  const attemptRecord = {
880
1241
  attempt: attemptNumber,
881
1242
  startedAt: attemptStartedAt,
@@ -887,6 +1248,7 @@ export async function executeDagNode(input) {
887
1248
  stderr: result.stderr,
888
1249
  assistantText: result.assistantText,
889
1250
  failureCategory: result.failureCategory,
1251
+ ...(rawFailureCategory ? { rawFailureCategory } : {}),
890
1252
  backend: result.backend,
891
1253
  sdkAttempted: result.sdkAttempted,
892
1254
  tokensUsed: result.tokensUsed,
@@ -901,6 +1263,60 @@ export async function executeDagNode(input) {
901
1263
  await writeDagNodeJsonArtifact(runDir, nodeId, `attempt-${attemptNumber}.json`, attemptRecord);
902
1264
  node.attempts = attempts;
903
1265
  }
1266
+ // Frontend plan retry ladder: project the 7-value protocol failure
1267
+ // reason, detect new committed facts, and escalate instead of re-running.
1268
+ if (frontendPlanLadderEnabled && !result.ok) {
1269
+ const afterSnapshot = await readFrontendPlanCommittedSnapshot(runDir, nodeId);
1270
+ const hasNewCommittedFact = beforeCommittedDigest !== undefined &&
1271
+ afterSnapshot.digest !== beforeCommittedDigest;
1272
+ const protocolReason = projectFrontendNodeProtocolFailureReason({
1273
+ failureCategory: result.failureCategory,
1274
+ stopReason: result.stopReason,
1275
+ assistantText: result.assistantText ?? result.stdout,
1276
+ hasTerminalFact: afterSnapshot.hasTerminalFact,
1277
+ });
1278
+ const fingerprint = computeNormalizedFailureFingerprint({
1279
+ failureOwner: "plan",
1280
+ protocolFailureReason: protocolReason ?? "",
1281
+ });
1282
+ const backupRoute = resolveFrontendPlanBackupRoute(protocolReason);
1283
+ const nextStep = resolveFrontendPlanRetryStep({
1284
+ currentStep: frontendPlanRetryStep,
1285
+ protocolFailureReason: protocolReason,
1286
+ hasNewCommittedFact,
1287
+ fingerprint,
1288
+ priorFingerprints: frontendPriorFingerprints,
1289
+ backupRouteAvailable: backupRoute.ok,
1290
+ });
1291
+ frontendPriorFingerprints.push(fingerprint);
1292
+ frontendPlanRetryStep = nextStep;
1293
+ if (nextStep === "unsupported-provider-capability") {
1294
+ result = {
1295
+ ...result,
1296
+ ok: false,
1297
+ failureCategory: "unsupported-provider-capability",
1298
+ stderr: [
1299
+ result.stderr,
1300
+ "frontend plan retry ladder: no compatible provider capability route for the protocol failure reason",
1301
+ ]
1302
+ .filter(Boolean)
1303
+ .join("\n"),
1304
+ };
1305
+ }
1306
+ else if (nextStep === "non-converging") {
1307
+ result = {
1308
+ ...result,
1309
+ ok: false,
1310
+ failureCategory: "non-converging",
1311
+ stderr: [
1312
+ result.stderr,
1313
+ "frontend plan retry ladder: repeated failure fingerprint with no new committed fact",
1314
+ ]
1315
+ .filter(Boolean)
1316
+ .join("\n"),
1317
+ };
1318
+ }
1319
+ }
904
1320
  // Reflect the latest attempt on the node so progress is observable,
905
1321
  // but keep node.status RUNNING while retry is still possible.
906
1322
  node.durationMs = durationBetween(node.startedAt, attemptFinishedAt);
@@ -934,13 +1350,26 @@ export async function executeDagNode(input) {
934
1350
  break;
935
1351
  const canRetry = retryPolicy !== undefined && attemptNumber < maxAttempts;
936
1352
  const retryable = retryPolicy !== undefined &&
1353
+ result.failureCategory !== GOVERNANCE_BLOCKED_CATEGORY &&
937
1354
  (isRetryablePiFailureCategory(result.failureCategory, {
938
1355
  retryCategories: retryPolicy.retryCategories,
939
1356
  }) ||
940
1357
  (result.failureCategory === "protocol-invalid" &&
941
1358
  task.outputProtocol?.retryOnInvalid === true));
942
- if (!canRetry || !retryable)
1359
+ if (!canRetry || !retryable) {
1360
+ if (!canRetry &&
1361
+ isFrontendStructuredRepairSchemaId(task.structuredContractOutput?.schemaId) &&
1362
+ isStructuredRepairableFailureCategory(result.failureCategory)) {
1363
+ result = {
1364
+ ...result,
1365
+ failureCategory: STRUCTURED_REPAIR_EXHAUSTED_CATEGORY,
1366
+ };
1367
+ terminalResult = result;
1368
+ previousFailureCategory = result.failureCategory;
1369
+ node.failureCategory = result.failureCategory;
1370
+ }
943
1371
  break;
1372
+ }
944
1373
  const delayMs = computeBackoffDelayMs(attemptNumber + 1, retryPolicy);
945
1374
  if (delayMs > 0) {
946
1375
  const backoffStartedAt = new Date().toISOString();
@@ -1089,6 +1518,11 @@ export async function executeDagNode(input) {
1089
1518
  node.structuredArtifactSchemaId = "backend-test-result-v1";
1090
1519
  }
1091
1520
  }
1521
+ else if (pendingStructuredArtifact) {
1522
+ node.structuredArtifactPath = pendingStructuredArtifact.path;
1523
+ node.structuredArtifactSha256 = pendingStructuredArtifact.sha256;
1524
+ node.structuredArtifactSchemaId = pendingStructuredArtifact.schemaId;
1525
+ }
1092
1526
  }
1093
1527
  const finishedAt = new Date().toISOString();
1094
1528
  node.finishedAt = finishedAt;