@tea-agent/loop-agent 0.42.0-next.1 → 0.42.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 (247) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +321 -1
  3. package/dist/application/dag/generate-task-dag.js +75 -19
  4. package/dist/application/dag/run-dag.js +41 -0
  5. package/dist/application/task-lifecycle/advance.js +24 -5
  6. package/dist/application/task-lifecycle/observe.js +171 -17
  7. package/dist/application/task-lifecycle/plan-transitions.js +42 -7
  8. package/dist/application/task-lifecycle/recommendations.js +13 -2
  9. package/dist/build-stamp.json +3 -3
  10. package/dist/cli/command-definitions.js +7 -0
  11. package/dist/cli/program.js +6 -1
  12. package/dist/commands/client-recovery.js +3 -0
  13. package/dist/commands/dag-follow-up.js +138 -0
  14. package/dist/commands/dag-rerun-task.js +2 -0
  15. package/dist/commands/init.js +27 -1
  16. package/dist/commands/task-advance.js +19 -0
  17. package/dist/executors/dag-pi-executor.js +4010 -78
  18. package/dist/executors/pi-executor.js +15 -4
  19. package/dist/executors/pi-extension-resolver.js +14 -2
  20. package/dist/executors/pi-read-budget-policy.js +239 -0
  21. package/dist/executors/shell-executor.js +978 -188
  22. package/dist/executors/shell-write-guard.js +7 -0
  23. package/dist/infrastructure/console/operation-store.js +226 -6
  24. package/dist/shared/dag-failure-category.js +12 -0
  25. package/dist/shared/openspec-spec.js +70 -4
  26. package/dist/shared/operator/capabilities.js +7 -0
  27. package/dist/task/config-types.js +105 -5
  28. package/dist/task/contract/adopt.js +4 -0
  29. package/dist/task/contract/import-revision.js +4 -0
  30. package/dist/task/contract/project.js +3 -0
  31. package/dist/task/contract/schema.js +2 -1
  32. package/dist/task/frontend-project-capability.js +203 -20
  33. package/dist/task/runtime.js +5 -2
  34. package/dist/task/source-prepare/build-draft.js +3 -3
  35. package/dist/task/source-prepare/fragment-inventory.js +64 -25
  36. package/dist/task/source-prepare/prepare.js +126 -21
  37. package/dist/task/source-prepare/semantic-intake.js +6 -2
  38. package/dist/task/source-references.js +22 -1
  39. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  40. package/dist/worker/console/chat/model-resolver.js +7 -4
  41. package/dist/worker/console/chat/pi-runtime.js +258 -11
  42. package/dist/worker/console/chat/repo-browser.js +10 -3
  43. package/dist/worker/console/chat/routes.js +165 -71
  44. package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
  45. package/dist/worker/console/chat/session-catalog.js +2 -0
  46. package/dist/worker/console/chat/session-store.js +159 -70
  47. package/dist/worker/console/chat/shortcuts.js +19 -3
  48. package/dist/worker/console/chat/turn-process.js +111 -56
  49. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  50. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  51. package/dist/worker/console/index.js +3 -0
  52. package/dist/worker/console/operator-actions.js +169 -7
  53. package/dist/worker/console/pi-readiness.js +3 -2
  54. package/dist/worker/console/server.js +23 -1
  55. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
  56. package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
  57. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
  58. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
  59. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
  60. package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
  61. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
  71. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
  72. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
  73. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
  74. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
  75. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
  76. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
  77. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
  78. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
  81. package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
  82. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
  83. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
  84. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
  85. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
  86. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
  87. package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
  88. package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
  89. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
  90. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
  91. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
  92. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
  93. package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
  94. package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
  95. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
  96. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
  97. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
  98. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
  99. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
  100. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
  101. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
  102. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
  103. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
  104. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
  105. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
  106. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
  107. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
  108. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
  109. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
  110. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
  111. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
  112. package/dist/worker/console/static/index.html +2 -2
  113. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  114. package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
  115. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  116. package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
  117. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  118. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
  119. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
  120. package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
  121. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
  122. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
  123. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
  124. package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
  125. package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
  126. package/dist/worker/console/workspace-context.js +115 -1
  127. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  128. package/dist/worker/observe/routes.js +4 -0
  129. package/dist/worker/observe/static/constants.js +22 -22
  130. package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
  131. package/dist/worker/observe/static/dag-helpers.js +4 -2
  132. package/dist/worker/observe/static/dag-history-labels.js +1 -0
  133. package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
  134. package/dist/worker/observe/static/dag-inspector-humanize.js +339 -0
  135. package/dist/worker/observe/static/dag-node-purpose.js +13 -8
  136. package/dist/worker/observe/static/index.html +4 -4
  137. package/dist/worker/observe/static/inspect-workspace.js +23 -0
  138. package/dist/worker/observe/static/kpi.js +2 -2
  139. package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
  140. package/dist/worker/observe/static/operator-chrome.js +37 -23
  141. package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
  142. package/dist/worker/observe/static/relations.js +7 -7
  143. package/dist/worker/observe/static/router.d.ts +12 -1
  144. package/dist/worker/observe/static/router.js +48 -6
  145. package/dist/worker/observe/static/run-processing.js +4 -2
  146. package/dist/worker/observe/static/shell-chrome.js +2 -2
  147. package/dist/worker/observe/static/state.js +5 -0
  148. package/dist/worker/observe/static/styles.css +231 -0
  149. package/dist/worker/observe/static/views/dag-graph.js +1 -1
  150. package/dist/worker/observe/static/views/dag-inspector.js +336 -181
  151. package/dist/worker/observe/static/views/dag.js +26 -14
  152. package/dist/worker/observe/static/views/dags.js +3 -1
  153. package/dist/worker/observe/static/views/dashboard.js +14 -6
  154. package/dist/worker/observe/static/views/pool.js +7 -2
  155. package/dist/worker/observe/static/views/run.js +12 -3
  156. package/dist/worker/observe/static/views/session-timeline.js +74 -13
  157. package/dist/worker/observe/static/views/task.js +7 -2
  158. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
  159. package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
  160. package/dist/workflows/dag/backend-test-plan-protocol.js +210 -0
  161. package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
  162. package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
  163. package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
  164. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  165. package/dist/workflows/dag/dag-retry-schema.js +138 -0
  166. package/dist/workflows/dag/frontend-closeout.js +221 -0
  167. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  168. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  169. package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
  170. package/dist/workflows/dag/frontend-plan-render.js +2 -1
  171. package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
  172. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  173. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  174. package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
  175. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  176. package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
  177. package/dist/workflows/dag/frontend-recovery-run.js +166 -34
  178. package/dist/workflows/dag/frontend-repair.js +1 -432
  179. package/dist/workflows/dag/frontend-review-context.js +261 -15
  180. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  181. package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
  182. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  183. package/dist/workflows/dag/frontend-shape-facts.js +419 -0
  184. package/dist/workflows/dag/frontend-shape.js +435 -0
  185. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  186. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  187. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  188. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  189. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  190. package/dist/workflows/dag/frontend-verification-trace.js +252 -25
  191. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  192. package/dist/workflows/dag/frontend-writer-admission.js +319 -0
  193. package/dist/workflows/dag/frontend-writer-status.js +256 -0
  194. package/dist/workflows/dag/init-hybrid.js +1031 -564
  195. package/dist/workflows/dag/interrupt-request.js +7 -0
  196. package/dist/workflows/dag/node-execution.js +854 -4
  197. package/dist/workflows/dag/prompt.js +70 -3
  198. package/dist/workflows/dag/recovery-lease.js +170 -0
  199. package/dist/workflows/dag/report.js +37 -1
  200. package/dist/workflows/dag/rerun-feedback.js +315 -1
  201. package/dist/workflows/dag/rerun-plan.js +17 -6
  202. package/dist/workflows/dag/rerun-run.js +44 -6
  203. package/dist/workflows/dag/rerun-task.js +251 -13
  204. package/dist/workflows/dag/retry-policy.js +230 -104
  205. package/dist/workflows/dag/runner.js +580 -124
  206. package/dist/workflows/dag/scheduler.js +133 -20
  207. package/dist/workflows/dag/types.js +244 -16
  208. package/dist/workflows/dag/validate.js +28 -12
  209. package/docs/examples/README.md +5 -0
  210. package/docs/init-surface.manifest.json +30 -12
  211. package/docs/skills/vetted-skill-registry.md +4 -2
  212. package/docs/templates/README.md +2 -0
  213. package/docs/templates/agent-dag-report.schema.json +8 -2
  214. package/docs/templates/agent-dag.schema.json +1 -1
  215. package/docs/templates/backend-test-dag.json +25 -20
  216. package/docs/templates/frontend-implementation-contract.schema.json +4 -1
  217. package/docs/templates/frontend-implementation-dag.json +89 -0
  218. package/docs/templates/spec-registry.schema.json +45 -0
  219. package/harness.json +1 -1
  220. package/package.json +4 -3
  221. package/skills/frontend-bounded-implement/SKILL.md +15 -14
  222. package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
  223. package/skills/frontend-contract/SKILL.md +23 -0
  224. package/skills/frontend-contract/references/contract-protocol.md +34 -0
  225. package/skills/frontend-design-review/SKILL.md +22 -41
  226. package/skills/frontend-plan/SKILL.md +26 -0
  227. package/skills/frontend-plan/references/decision-contract.md +37 -0
  228. package/skills/frontend-plan/references/design-decisions.md +17 -0
  229. package/skills/frontend-review/SKILL.md +20 -15
  230. package/skills/frontend-review/references/review-findings.md +6 -7
  231. package/skills/frontend-scout/SKILL.md +25 -0
  232. package/skills/frontend-scout/references/design-evidence.md +16 -0
  233. package/skills/frontend-scout/references/scout-evidence.md +23 -0
  234. package/skills/frontend-verification/SKILL.md +1 -1
  235. package/skills/loop-agent/references/command-reference.md +1 -0
  236. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  237. package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
  238. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
  239. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
  240. package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
  241. package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
  242. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
  243. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
  244. package/skills/frontend-implementation/SKILL.md +0 -52
  245. package/skills/frontend-implementation/references/code-standards.md +0 -33
  246. package/skills/frontend-implementation/references/design-spec.md +0 -56
  247. package/skills/frontend-implementation/references/node-contracts.md +0 -31
@@ -0,0 +1,226 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { isTerminalOperationState, } from "../../infrastructure/console/operation-store.js";
3
+ import { runOperation, } from "../../worker/console/operation-runner.js";
4
+ import { buildFailureCapsule, classifyFailureOwner, projectProtocolFailureReason, } from "./frontend-recovery-capsule.js";
5
+ import { computeNormalizedFailureFingerprint, evaluateNonConvergence, foldOperationBudgets, } from "./frontend-recovery-lineage.js";
6
+ import { projectedInterruptStatus, } from "./interrupt-request.js";
7
+ import { evaluateNodeLiveness, } from "./liveness-policy.js";
8
+ /**
9
+ * Deterministic legacy Console operation-state → canonical frontend
10
+ * operation-state/terminal projection. This is the single adapter-layer
11
+ * projection at the controller boundary; the generic Console operation schema
12
+ * is not changed (AC-001).
13
+ */
14
+ export function projectLegacyOperationState(state) {
15
+ switch (state) {
16
+ case "queued":
17
+ case "starting":
18
+ case "running":
19
+ case "restarting":
20
+ return { operationState: "active" };
21
+ case "succeeded":
22
+ return { operationState: "success", operationTerminal: "success" };
23
+ case "failed":
24
+ case "timed-out":
25
+ case "needs-reconcile":
26
+ return {
27
+ operationState: "failed-internal",
28
+ operationTerminal: "failed-internal",
29
+ };
30
+ }
31
+ }
32
+ /**
33
+ * Conservative default recovery mode by owner. Non-writer owners restart as a
34
+ * new DAG; environment issues wait for the external dependency; unknown never
35
+ * auto-selects writer recovery (AC-003).
36
+ */
37
+ export function projectDefaultRecoveryMode(owner) {
38
+ switch (owner) {
39
+ case "contract":
40
+ case "scout":
41
+ case "plan":
42
+ case "implementation":
43
+ case "verification-config":
44
+ case "scope":
45
+ return "new-dag";
46
+ case "environment":
47
+ return "wait-external";
48
+ case "unknown":
49
+ return "none";
50
+ }
51
+ }
52
+ function buildCapsuleOnTerminal(input) {
53
+ const protocolFailureReason = projectProtocolFailureReason(input.protocolFailureReason);
54
+ if (input.terminalReason === "non-converging") {
55
+ return buildFailureCapsule({
56
+ parentRunId: input.parentRunId,
57
+ operationState: "non-converging",
58
+ recoveryMode: "none",
59
+ protocolFailureReason,
60
+ failureOwner: input.owner,
61
+ evidenceRefs: [],
62
+ });
63
+ }
64
+ return buildFailureCapsule({
65
+ parentRunId: input.parentRunId,
66
+ operationState: "recoverable",
67
+ recoveryMode: input.deps.recoveryMode ?? projectDefaultRecoveryMode(input.owner),
68
+ protocolFailureReason,
69
+ failureOwner: input.owner,
70
+ evidenceRefs: [],
71
+ });
72
+ }
73
+ export async function runRecoveryOperationController(input) {
74
+ const { operationId, parentRunId, deps } = input;
75
+ const maxAttempts = Math.max(1, deps.maxAttempts ?? 5);
76
+ const now = deps.now ?? (() => new Date());
77
+ const readInterrupt = deps.readInterrupt ?? (async () => undefined);
78
+ const readLedgers = deps.readLedgers ?? (async () => []);
79
+ const readChangedFiles = deps.readChangedFiles ?? (async () => []);
80
+ const fingerprints = [...(deps.previousFingerprints ?? [])];
81
+ let budgetAggregate;
82
+ const pollIntervalMs = Math.max(10, deps.operationPollIntervalMs ?? 1_000);
83
+ const waitTimeoutMs = Math.max(pollIntervalMs, deps.operationWaitTimeoutMs ??
84
+ deps.operationRunnerDeps.defaultTimeoutMs ??
85
+ 30 * 60_000);
86
+ const waitStartedAt = Date.now();
87
+ let attempt = 1;
88
+ while (attempt <= maxAttempts) {
89
+ const operation = await deps.store.get(operationId);
90
+ if (!operation) {
91
+ return {
92
+ terminalReason: "operation-not-found",
93
+ attempts: attempt,
94
+ fingerprints,
95
+ detail: `operation not found: ${operationId}`,
96
+ };
97
+ }
98
+ // Total budget fold + hard stop (resource protection, fail-closed).
99
+ const ledgers = await readLedgers();
100
+ if (deps.budget) {
101
+ budgetAggregate = foldOperationBudgets({
102
+ ledgers,
103
+ budget: deps.budget,
104
+ now,
105
+ });
106
+ }
107
+ if (budgetAggregate?.status === "breached") {
108
+ return {
109
+ terminalReason: "budget-exhausted",
110
+ attempts: attempt,
111
+ budgetAggregate,
112
+ fingerprints,
113
+ detail: "operation total budget breached",
114
+ };
115
+ }
116
+ const owner = deps.failureOwner ??
117
+ classifyFailureOwner({
118
+ rawFailureCategory: operation.errorCode,
119
+ nodeId: operationId,
120
+ });
121
+ const protocolFailureReason = deps.protocolFailureReason ??
122
+ operation.errorMessage ??
123
+ operation.errorCode ??
124
+ "unknown failure";
125
+ if (isTerminalOperationState(operation.state)) {
126
+ if (operation.state === "succeeded") {
127
+ return {
128
+ terminalReason: "succeeded",
129
+ attempts: attempt,
130
+ budgetAggregate,
131
+ fingerprints,
132
+ };
133
+ }
134
+ // Failed/timed-out: fingerprint the failure and stop if it repeats.
135
+ fingerprints.push(computeNormalizedFailureFingerprint({
136
+ failureOwner: owner,
137
+ protocolFailureReason,
138
+ invalidatedFactIds: [],
139
+ }));
140
+ const nonConvergence = evaluateNonConvergence({
141
+ fingerprints,
142
+ changedFiles: await readChangedFiles(),
143
+ // Budget breach already short-circuited above; fail-closed here.
144
+ budgetBreached: false,
145
+ });
146
+ if (nonConvergence.terminal) {
147
+ return {
148
+ terminalReason: nonConvergence.terminalReason,
149
+ attempts: attempt,
150
+ budgetAggregate,
151
+ fingerprints,
152
+ capsule: buildCapsuleOnTerminal({
153
+ parentRunId,
154
+ deps,
155
+ owner,
156
+ protocolFailureReason,
157
+ terminalReason: "non-converging",
158
+ }),
159
+ detail: nonConvergence.reason,
160
+ };
161
+ }
162
+ return {
163
+ terminalReason: "failed",
164
+ attempts: attempt,
165
+ budgetAggregate,
166
+ fingerprints,
167
+ capsule: buildCapsuleOnTerminal({
168
+ parentRunId,
169
+ deps,
170
+ owner,
171
+ protocolFailureReason,
172
+ terminalReason: "failed",
173
+ }),
174
+ };
175
+ }
176
+ // Non-terminal: gate on liveness + interrupt before advancing.
177
+ const liveness = evaluateNodeLiveness({ node: deps.node ?? {}, now: now() });
178
+ if (liveness.status === "needs-attention") {
179
+ return {
180
+ terminalReason: "needs-attention",
181
+ attempts: attempt,
182
+ budgetAggregate,
183
+ fingerprints,
184
+ detail: "node liveness requires attention; abort recovery loop",
185
+ };
186
+ }
187
+ const interrupt = await readInterrupt();
188
+ const projected = projectedInterruptStatus(interrupt);
189
+ if (projected === "requested" ||
190
+ projected === "acknowledged" ||
191
+ projected === "needs-reconcile") {
192
+ return {
193
+ terminalReason: "interrupted",
194
+ attempts: attempt,
195
+ budgetAggregate,
196
+ fingerprints,
197
+ detail: `interrupt projection: ${projected}`,
198
+ };
199
+ }
200
+ const operationAlreadyInFlight = operation.state === "starting" || operation.state === "running";
201
+ const operationOwnedElsewhere = input.observeOnly &&
202
+ (operation.state === "queued" || operation.state === "needs-reconcile");
203
+ if (operationAlreadyInFlight || operationOwnedElsewhere) {
204
+ if (Date.now() - waitStartedAt >= waitTimeoutMs) {
205
+ return {
206
+ terminalReason: "needs-attention",
207
+ attempts: attempt,
208
+ budgetAggregate,
209
+ fingerprints,
210
+ detail: `recovery operation did not settle within ${waitTimeoutMs}ms`,
211
+ };
212
+ }
213
+ await delay(pollIntervalMs);
214
+ continue;
215
+ }
216
+ await runOperation(operationId, deps.operationRunnerDeps);
217
+ attempt += 1;
218
+ }
219
+ return {
220
+ terminalReason: "max-attempts",
221
+ attempts: maxAttempts,
222
+ budgetAggregate,
223
+ fingerprints,
224
+ detail: `recovery loop exceeded maxAttempts=${maxAttempts}`,
225
+ };
226
+ }
@@ -0,0 +1,178 @@
1
+ import { z } from "zod";
2
+ import { assembleCumulativeDiffContext, } from "./frontend-review-context.js";
3
+ import { deterministicSha256 } from "./frontend-implementation-contract.js";
4
+ /**
5
+ * M6 candidate lineage + operation budget primitives (pure functions).
6
+ *
7
+ * Self-contained types only: this module never extends `DagRunState`/`DagSpec`
8
+ * and never mutates the legacy operation/budget modules — it only imports them.
9
+ * Candidate lineage distinguishes a committed parent candidate (retained into
10
+ * lineage) from an uncommitted partial write (CAS-rolled back, never retained).
11
+ */
12
+ const hash64 = z.string().regex(/^[a-f0-9]{64}$/);
13
+ /* ------------------------------------------------------------------ *
14
+ * Commit state (AC-002). *
15
+ * ------------------------------------------------------------------ */
16
+ export const commitStateSchema = z.enum([
17
+ "uncommitted-partial",
18
+ "committed-candidate",
19
+ ]);
20
+ /**
21
+ * Deterministic commit-state classifier. A journal that reached `completed`
22
+ * (or a restore result that did) means the parent candidate was committed and
23
+ * must be retained into lineage. Anything else — `blocked`, an uncommitted
24
+ * half-write, or a rollback that could not be proven — is fail-closed to
25
+ * `uncommitted-partial` and is never retained.
26
+ */
27
+ export function classifyCommitState(input) {
28
+ if (input.journalState === "completed") {
29
+ return "committed-candidate";
30
+ }
31
+ if (input.restoreResult?.state === "completed") {
32
+ return "committed-candidate";
33
+ }
34
+ return "uncommitted-partial";
35
+ }
36
+ /* ------------------------------------------------------------------ *
37
+ * Candidate lineage (AC-002). *
38
+ * ------------------------------------------------------------------ */
39
+ export const candidateLineageV1Schema = z
40
+ .object({
41
+ schemaVersion: z.literal(1),
42
+ taskBaselineHash: hash64,
43
+ parentRunId: z.string().min(1),
44
+ parentCandidateHash: hash64,
45
+ runBaselineHash: hash64,
46
+ runDeltaHash: hash64,
47
+ cumulativeDiffHash: hash64,
48
+ commitState: commitStateSchema,
49
+ })
50
+ .strict();
51
+ /**
52
+ * taskBaseline → candidate cumulative diff digest (AC-004). Reuses the
53
+ * deterministic `assembleCumulativeDiffContext` and returns only its
54
+ * `cumulativeDigest`, so the lineage hash is stable regardless of input order.
55
+ */
56
+ export function computeCumulativeDiffHash(input) {
57
+ return assembleCumulativeDiffContext(input).cumulativeDigest;
58
+ }
59
+ function pushAggregateBreach(aggregate, dimension, consumed, limit, now) {
60
+ if (limit === undefined || consumed <= limit)
61
+ return;
62
+ const duplicate = aggregate.breaches.some((existing) => existing.dimension === dimension && existing.limit === limit);
63
+ if (!duplicate) {
64
+ aggregate.breaches.push({
65
+ dimension,
66
+ limit,
67
+ consumed,
68
+ at: now().toISOString(),
69
+ });
70
+ }
71
+ if (aggregate.mode === "hard") {
72
+ aggregate.status = "breached";
73
+ }
74
+ }
75
+ /**
76
+ * Fold parent + all continuation/recovery run ledgers into a single operation
77
+ * budget aggregate and evaluate it against the operation total `CampaignBudget`
78
+ * limits. Missing token samples never count as zero (aligned with the ledger
79
+ * contract): if any ledger lacks a token total, the aggregate token total is
80
+ * `null` and no token breach is derived.
81
+ */
82
+ export function foldOperationBudgets(input) {
83
+ const now = input.now ?? (() => new Date());
84
+ const aggregate = {
85
+ schemaVersion: 1,
86
+ mode: input.budget.mode,
87
+ limits: { ...input.budget.limits },
88
+ consumed: {
89
+ executorCalls: 0,
90
+ wallTimeMs: 0,
91
+ tokens: null,
92
+ repairPasses: 0,
93
+ peakContextChars: 0,
94
+ missingTokenNodeIds: [],
95
+ },
96
+ breaches: [],
97
+ status: input.budget.mode === "record-only" ? "record-only" : "ok",
98
+ };
99
+ let anyUnknownTokens = false;
100
+ let knownTokenSum = 0;
101
+ const missing = new Set();
102
+ for (const ledger of input.ledgers) {
103
+ aggregate.consumed.executorCalls += ledger.consumed.executorCalls;
104
+ aggregate.consumed.wallTimeMs += ledger.consumed.wallTimeMs;
105
+ aggregate.consumed.repairPasses += ledger.consumed.repairPasses;
106
+ aggregate.consumed.peakContextChars = Math.max(aggregate.consumed.peakContextChars, ledger.consumed.peakContextChars);
107
+ if (ledger.consumed.tokens === null) {
108
+ anyUnknownTokens = true;
109
+ }
110
+ else {
111
+ knownTokenSum += ledger.consumed.tokens;
112
+ }
113
+ for (const nodeId of ledger.consumed.missingTokenNodeIds) {
114
+ missing.add(nodeId);
115
+ }
116
+ aggregate.breaches.push(...ledger.breaches);
117
+ }
118
+ aggregate.consumed.tokens = anyUnknownTokens ? null : knownTokenSum;
119
+ aggregate.consumed.missingTokenNodeIds = [...missing].sort((a, b) => a.localeCompare(b));
120
+ if (aggregate.mode === "hard") {
121
+ const limits = aggregate.limits;
122
+ pushAggregateBreach(aggregate, "executorCalls", aggregate.consumed.executorCalls, limits.maxExecutorCalls, now);
123
+ pushAggregateBreach(aggregate, "wallTimeMs", aggregate.consumed.wallTimeMs, limits.maxWallTimeMs, now);
124
+ pushAggregateBreach(aggregate, "repairPasses", aggregate.consumed.repairPasses, limits.maxRepairPasses, now);
125
+ pushAggregateBreach(aggregate, "contextChars", aggregate.consumed.peakContextChars, limits.maxContextChars, now);
126
+ if (aggregate.consumed.tokens !== null) {
127
+ pushAggregateBreach(aggregate, "tokens", aggregate.consumed.tokens, limits.maxTokens, now);
128
+ }
129
+ }
130
+ return aggregate;
131
+ }
132
+ /**
133
+ * Stable, pure failure fingerprint. No wall clock, no random bytes: identical
134
+ * inputs always produce the same digest, so repeated failures across recovery
135
+ * runs can be compared for non-convergence detection.
136
+ */
137
+ export function computeNormalizedFailureFingerprint(input) {
138
+ const payload = {
139
+ failureOwner: input.failureOwner ?? "unknown",
140
+ protocolFailureReason: input.protocolFailureReason ?? "",
141
+ restartPhase: input.restartPhase ?? "unknown",
142
+ invalidatedFactIds: [...(input.invalidatedFactIds ?? [])].sort(),
143
+ commitState: input.commitState ?? "uncommitted-partial",
144
+ };
145
+ return deterministicSha256(payload);
146
+ }
147
+ /**
148
+ * Bounded-loop stop decision. A repeated failure fingerprint with an empty
149
+ * change manifest proves no workspace progress → `non-converging`. An exceeded
150
+ * operation total budget → `budget-exhausted`. Otherwise continue (but never
151
+ * unbounded: the controller still caps attempts).
152
+ */
153
+ export function evaluateNonConvergence(input) {
154
+ if (input.budgetBreached) {
155
+ return {
156
+ terminal: true,
157
+ terminalReason: "budget-exhausted",
158
+ reason: "operation total budget breached; stop before exhausting resources",
159
+ };
160
+ }
161
+ const counts = new Map();
162
+ for (const fingerprint of input.fingerprints) {
163
+ counts.set(fingerprint, (counts.get(fingerprint) ?? 0) + 1);
164
+ }
165
+ const repeated = [...counts.values()].some((count) => count >= 2);
166
+ if (repeated && input.changedFiles.length === 0) {
167
+ return {
168
+ terminal: true,
169
+ terminalReason: "non-converging",
170
+ reason: "repeated failure fingerprint with an empty change manifest; recovery would loop forever",
171
+ };
172
+ }
173
+ return {
174
+ terminal: false,
175
+ terminalReason: "continue",
176
+ reason: "progress observable or fingerprint changed; continue bounded recovery",
177
+ };
178
+ }
@@ -14,19 +14,21 @@ export const FRONTEND_RECOVERY_IMPORT_MANIFEST_REL_PATH = ".runtime/import-manif
14
14
  /** Staging directory prefix under `active/` for in-flight child materialization. */
15
15
  export const FRONTEND_RECOVERY_STAGING_PREFIX = ".frontend-recovery-staging-";
16
16
  export function deriveFrontendRecoveryFailureSource(result) {
17
- if (result.selectedPlanNodeId === "frontend-plan-revision-pi") {
18
- return "frontend-plan-revision-pi";
17
+ if (result.failureSource === "frontend-plan-pi" ||
18
+ result.failureSource === "frontend-design-policy-shell" ||
19
+ result.failureSource === "frontend-implement-pi" ||
20
+ result.failureSource === "frontend-verify-shell") {
21
+ return result.failureSource;
19
22
  }
20
- if (result.selectedPlanNodeId === "frontend-plan-pi") {
21
- return "frontend-plan-pi";
22
- }
23
- return "frontend-prewrite-gate-shell";
23
+ return "frontend-writer-admission-shell";
24
24
  }
25
25
  /**
26
26
  * Compute the frontend candidate-continuation reset partition:
27
- * - `frontend-plan-pi` failure → reset plan + design-review + prewrite (+ descendants);
28
- * - `frontend-plan-revision-pi` failure → reset revision + final-review + prewrite (+ descendants);
29
- * - prewrite-only materialization → reset prewrite (+ descendants), reuse verified plan/review.
27
+ * - `frontend-plan-pi` failure → reset plan + design-review + policy/admission (+ descendants);
28
+ * - `frontend-design-policy-shell` failure → reset policy/admission (+ descendants);
29
+ * - design-review rejection → reset plan + design review + admission (+ descendants);
30
+ * - final-review rejection → reset implementation + verification/review (+ descendants);
31
+ * - other admission failures → reset writer admission (+ descendants);
30
32
  *
31
33
  * Everything upstream of the failure source is imported as verified parent facts.
32
34
  */
@@ -55,7 +57,16 @@ export function computeFrontendWriterRecoveryPlan(input) {
55
57
  });
56
58
  }
57
59
  function computeFrontendRecoveryPlanForSource(input) {
58
- const resetNodeIds = computeResetClosure(input.spec, input.failureSource);
60
+ // Verification failure means the implementation did not pass the frozen
61
+ // verification commands / trace. Resetting only verify + its descendants
62
+ // would re-run the same failing checks against the same implementation, so
63
+ // reset from implement: the recovery re-implements and re-verifies.
64
+ const resetRoot = input.failureSource === "frontend-verify-shell"
65
+ ? "frontend-implement-pi"
66
+ : input.failureSource === "frontend-review-pi"
67
+ ? "frontend-implement-pi"
68
+ : input.failureSource;
69
+ const resetNodeIds = computeResetClosure(input.spec, resetRoot);
59
70
  const resetSet = new Set(resetNodeIds);
60
71
  const importedNodeIds = input.spec.tasks
61
72
  .map((task) => task.id)