@tea-agent/loop-agent 0.42.0-next.9 → 0.42.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/CHANGELOG.md +111 -45
  2. package/dist/application/dag/run-dag.js +8 -2
  3. package/dist/application/evaluation/budget.js +19 -1
  4. package/dist/application/task-lifecycle/advance.js +26 -8
  5. package/dist/application/task-lifecycle/observe.js +43 -29
  6. package/dist/application/task-lifecycle/plan-transitions.js +5 -4
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/program.js +1 -1
  9. package/dist/commands/dag-rerun-task.js +2 -0
  10. package/dist/commands/task-source-prepare.js +3 -1
  11. package/dist/executors/dag-pi-executor.js +2584 -600
  12. package/dist/executors/pi-executor.js +22 -1
  13. package/dist/executors/pi-extension-resolver.js +14 -2
  14. package/dist/executors/pi-sdk-executor.js +140 -39
  15. package/dist/executors/shell-executor.js +135 -55
  16. package/dist/shared/dag-failure-category.js +6 -0
  17. package/dist/shared/frontend-execution-policy.js +23 -0
  18. package/dist/task/config-types.js +4 -0
  19. package/dist/task/contract/apply.js +36 -2
  20. package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
  21. package/dist/task/source-prepare/ledger-review.js +6 -9
  22. package/dist/task/source-prepare/parse-intent.js +7 -0
  23. package/dist/task/source-prepare/semantic-intake.js +16 -26
  24. package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
  25. package/dist/task/source-references.js +48 -23
  26. package/dist/worker/console/chat/assistant-content.js +23 -2
  27. package/dist/worker/console/chat/browser-policy.js +143 -0
  28. package/dist/worker/console/chat/browser-routes.js +148 -0
  29. package/dist/worker/console/chat/chat-event-store.js +4 -2
  30. package/dist/worker/console/chat/explore-tools.js +13 -0
  31. package/dist/worker/console/chat/pi-runtime.js +173 -94
  32. package/dist/worker/console/chat/resource-loader.js +4 -1
  33. package/dist/worker/console/chat/routes.js +208 -66
  34. package/dist/worker/console/chat/scm-routes.js +217 -0
  35. package/dist/worker/console/chat/scm-service.js +283 -0
  36. package/dist/worker/console/chat/scm-tools.js +111 -0
  37. package/dist/worker/console/chat/sdd-data-alignment.js +78 -11
  38. package/dist/worker/console/chat/session-catalog.js +32 -0
  39. package/dist/worker/console/chat/session-mode-view.js +48 -0
  40. package/dist/worker/console/chat/session-mode.js +218 -0
  41. package/dist/worker/console/chat/session-store.js +27 -7
  42. package/dist/worker/console/chat/shortcuts.js +6 -0
  43. package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
  44. package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
  45. package/dist/worker/console/chat/subagents/index.js +5 -0
  46. package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
  47. package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
  48. package/dist/worker/console/chat/subagents/types.js +13 -0
  49. package/dist/worker/console/chat/terminal-routes.js +216 -0
  50. package/dist/worker/console/chat/terminal-sessions.js +284 -0
  51. package/dist/worker/console/chat/terminal-tools.js +199 -0
  52. package/dist/worker/console/chat/tool-preview.js +21 -0
  53. package/dist/worker/console/chat/tools.js +13 -1
  54. package/dist/worker/console/chat/turn-process.js +1 -0
  55. package/dist/worker/console/interview/tools.js +1 -0
  56. package/dist/worker/console/server.js +2 -28
  57. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
  58. package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
  59. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
  60. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
  61. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
  62. package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
  63. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
  73. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
  74. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
  75. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
  76. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
  77. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
  78. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
  81. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
  82. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
  83. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
  84. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
  85. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
  86. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
  87. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
  88. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
  89. package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
  90. package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
  91. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
  92. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
  93. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
  94. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
  95. package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
  96. package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
  97. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
  98. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
  99. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
  100. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
  101. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
  102. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
  103. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
  104. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
  105. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
  106. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
  107. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
  108. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
  109. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
  110. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
  111. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
  112. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
  113. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
  114. package/dist/worker/console/static/index.html +2 -2
  115. package/dist/worker/console/static-src/chat-view-types.js +1 -1
  116. package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
  117. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
  118. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
  119. package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
  120. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
  121. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
  122. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
  123. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
  124. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
  125. package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
  126. package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
  127. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
  128. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
  129. package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
  130. package/dist/worker/console/workspace-context.js +15 -1
  131. package/dist/worker/observe/node-transparency.js +81 -72
  132. package/dist/worker/observe/routes.js +20 -1
  133. package/dist/worker/observe/static/constants.js +22 -22
  134. package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
  135. package/dist/worker/observe/static/dag-history-labels.js +1 -0
  136. package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
  137. package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
  138. package/dist/worker/observe/static/dag-node-purpose.js +10 -10
  139. package/dist/worker/observe/static/dom.js +20 -1
  140. package/dist/worker/observe/static/format-pool.d.ts +2 -0
  141. package/dist/worker/observe/static/format-pool.js +6 -0
  142. package/dist/worker/observe/static/format.js +7 -0
  143. package/dist/worker/observe/static/index.html +4 -4
  144. package/dist/worker/observe/static/inspect-workspace.js +34 -7
  145. package/dist/worker/observe/static/inspector-submission.js +32 -0
  146. package/dist/worker/observe/static/kpi.js +1 -0
  147. package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
  148. package/dist/worker/observe/static/relations.js +7 -5
  149. package/dist/worker/observe/static/router.js +13 -0
  150. package/dist/worker/observe/static/run-processing.js +2 -0
  151. package/dist/worker/observe/static/shell-chrome.js +36 -3
  152. package/dist/worker/observe/static/state.js +35 -2
  153. package/dist/worker/observe/static/styles.css +431 -39
  154. package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
  155. package/dist/worker/observe/static/task-failure-labels.js +67 -0
  156. package/dist/worker/observe/static/task-history.js +12 -0
  157. package/dist/worker/observe/static/views/batch.js +6 -13
  158. package/dist/worker/observe/static/views/dag-graph.js +51 -4
  159. package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
  160. package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
  161. package/dist/worker/observe/static/views/dag.d.ts +6 -0
  162. package/dist/worker/observe/static/views/dag.js +64 -22
  163. package/dist/worker/observe/static/views/dags.js +2 -0
  164. package/dist/worker/observe/static/views/dashboard.js +21 -12
  165. package/dist/worker/observe/static/views/failures.js +21 -11
  166. package/dist/worker/observe/static/views/feature.js +11 -29
  167. package/dist/worker/observe/static/views/pool.js +37 -28
  168. package/dist/worker/observe/static/views/run.js +48 -5
  169. package/dist/worker/observe/static/views/session-timeline.js +194 -243
  170. package/dist/worker/observe/static/views/task.js +81 -62
  171. package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
  172. package/dist/workflows/dag/budget-enforcement.js +53 -3
  173. package/dist/workflows/dag/dag-retry-schema.js +3 -0
  174. package/dist/workflows/dag/frontend-capacity.js +9 -0
  175. package/dist/workflows/dag/frontend-contract-facts.js +130 -0
  176. package/dist/workflows/dag/frontend-design-policy.js +103 -17
  177. package/dist/workflows/dag/frontend-durable-tools.js +193 -0
  178. package/dist/workflows/dag/frontend-execution-groups.js +24 -0
  179. package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
  180. package/dist/workflows/dag/frontend-input-projection.js +76 -0
  181. package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
  182. package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
  183. package/dist/workflows/dag/frontend-plan-render.js +21 -3
  184. package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
  185. package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
  186. package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
  187. package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
  188. package/dist/workflows/dag/frontend-recovery-run.js +146 -16
  189. package/dist/workflows/dag/frontend-review-scopes.js +139 -0
  190. package/dist/workflows/dag/frontend-risk.js +2 -0
  191. package/dist/workflows/dag/frontend-session-budget.js +249 -0
  192. package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
  193. package/dist/workflows/dag/frontend-shape-facts.js +12 -2
  194. package/dist/workflows/dag/frontend-shape.js +46 -7
  195. package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
  196. package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
  197. package/dist/workflows/dag/frontend-verification-trace.js +38 -4
  198. package/dist/workflows/dag/frontend-writer-admission.js +2 -2
  199. package/dist/workflows/dag/init-hybrid.js +70 -35
  200. package/dist/workflows/dag/node-execution.js +225 -226
  201. package/dist/workflows/dag/prompt.js +4 -0
  202. package/dist/workflows/dag/recovery-lease.js +106 -16
  203. package/dist/workflows/dag/rerun-feedback.js +1 -0
  204. package/dist/workflows/dag/rerun-plan.js +96 -4
  205. package/dist/workflows/dag/rerun-run.js +28 -6
  206. package/dist/workflows/dag/rerun-task.js +221 -18
  207. package/dist/workflows/dag/retry-policy.js +27 -10
  208. package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
  209. package/dist/workflows/dag/runner.js +246 -19
  210. package/dist/workflows/dag/structured-output-repair.js +4 -1
  211. package/dist/workflows/dag/types.js +10 -3
  212. package/dist/workflows/dag/validate.js +10 -8
  213. package/dist/workflows/dag/workspace-checkpoint.js +66 -0
  214. package/docs/operations/README.md +2 -0
  215. package/docs/templates/agent-dag.schema.json +2 -2
  216. package/docs/templates/backend-test-dag.json +6 -4
  217. package/docs/templates/frontend-design-contract.md +4 -4
  218. package/docs/templates/frontend-implementation-contract.schema.json +68 -4
  219. package/docs/templates/frontend-implementation-dag.json +5 -5
  220. package/harness.json +1 -1
  221. package/package.json +8 -6
  222. package/skills/frontend-contract/SKILL.md +2 -1
  223. package/skills/frontend-contract/references/contract-protocol.md +19 -3
  224. package/skills/frontend-design-review/SKILL.md +12 -11
  225. package/skills/frontend-plan/SKILL.md +22 -2
  226. package/skills/frontend-plan/references/decision-contract.md +114 -5
  227. package/skills/frontend-plan/references/design-decisions.md +32 -0
  228. package/skills/frontend-review/SKILL.md +10 -11
  229. package/skills/frontend-scout/references/scout-evidence.md +4 -0
  230. package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
  231. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
  232. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
  233. package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
  234. package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
  235. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
  236. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
@@ -0,0 +1,76 @@
1
+ import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
2
+ /** An execution packing target, not a claim about any model's context limit. */
3
+ export const FRONTEND_SCOPE_TARGET_BYTES = 24_000;
4
+ export function packFrontendInputUnits(units, options = {}) {
5
+ const target = options.targetBytes ?? FRONTEND_SCOPE_TARGET_BYTES;
6
+ const maxUnits = options.maxUnits ?? 4;
7
+ if (!Number.isFinite(target) || target <= 0 || !Number.isInteger(maxUnits) || maxUnits < 1)
8
+ throw Error("FRONTEND_INPUT_POLICY_INVALID");
9
+ const batches = [];
10
+ let batch = [];
11
+ let bytes = 2;
12
+ let cost = 0;
13
+ const ids = new Set();
14
+ for (const unit of units) {
15
+ if (!unit.id || ids.has(unit.id))
16
+ throw Error(`FRONTEND_INPUT_ID_CONFLICT: ${unit.id}`);
17
+ ids.add(unit.id);
18
+ const size = Buffer.byteLength(JSON.stringify(unit), "utf8") + 1;
19
+ if (options.maxUnitBytes !== undefined && size > options.maxUnitBytes)
20
+ throw Error(`FRONTEND_INPUT_UNIT_TOO_LARGE: ${unit.id} requires ${size} bytes; split the source obligation without dropping conditions`);
21
+ const unitCost = options.cost?.(unit) ?? 1;
22
+ if (batch.length && (batch.length >= maxUnits || bytes + size > target || cost + unitCost > (options.maxCost ?? Infinity))) {
23
+ batches.push(batch);
24
+ batch = [];
25
+ bytes = 2;
26
+ cost = 0;
27
+ }
28
+ batch.push(unit);
29
+ bytes += size;
30
+ cost += unitCost;
31
+ }
32
+ if (batch.length)
33
+ batches.push(batch);
34
+ return batches;
35
+ }
36
+ /** Select whole obligations before serialization; never clip identities or text. */
37
+ export function projectFrontendInputScope(payload, scopeIds) {
38
+ const byId = new Map(payload.requirements.map(r => [r.id, r]));
39
+ const requirements = [...new Set(scopeIds)].map(id => {
40
+ const unit = byId.get(id);
41
+ if (!unit)
42
+ throw Error(`FRONTEND_INPUT_SCOPE_MISSING: ${id}`);
43
+ return unit;
44
+ });
45
+ const fragments = new Set(requirements.flatMap(r => Array.isArray(r.sourceFragmentIds) ? r.sourceFragmentIds : []));
46
+ const inScope = (fact) => {
47
+ const ids = typeof fact.requirementId === "string" ? [fact.requirementId] : Array.isArray(fact.requirementIds) ? fact.requirementIds : [];
48
+ return !ids.length || ids.some(id => scopeIds.includes(String(id)));
49
+ };
50
+ return {
51
+ ...payload, requirements,
52
+ ...(Array.isArray(payload.sharedFacts) ? { sharedFacts: payload.sharedFacts.filter(f => f && typeof f === "object" && inScope(f)).map(f => f.kind === "required-deliverables" && Array.isArray(f.items) ? { ...f, items: f.items.filter((item) => inScope(item)) } : f) } : {}),
53
+ ...(Array.isArray(payload.fragments) ? { fragments: payload.fragments.filter(f => f && typeof f === "object" && fragments.has(f.id)) } : {}),
54
+ inputManifest: { schemaVersion: 1, semantics: "full", sourceSha256: sha256OfCanonicalJson(payload), scopeIds: requirements.map(r => r.id), excludedIds: payload.requirements.filter(r => !scopeIds.includes(r.id)).map(r => r.id) },
55
+ };
56
+ }
57
+ export function parseFrontendInputBlock(prompt, phase) {
58
+ const block = new RegExp(`<frontend_${phase}_input>[\\s\\S]*?<\\/frontend_${phase}_input>`).exec(prompt)?.[0];
59
+ if (!block)
60
+ return undefined;
61
+ for (const line of block.split(/\r?\n/)) {
62
+ try {
63
+ const payload = JSON.parse(line);
64
+ if (Array.isArray(payload.requirements))
65
+ return { block, payload };
66
+ }
67
+ catch { /* wrapper prose is not JSON */ }
68
+ }
69
+ throw Error(`FRONTEND_INPUT_INVALID: ${phase} compiled inventory is not parseable`);
70
+ }
71
+ export function projectFrontendContractPrompt(prompt, scopeIds) {
72
+ const input = parseFrontendInputBlock(prompt, "contract");
73
+ if (!input)
74
+ throw Error("FRONTEND_INPUT_MISSING: contract compiled inventory unavailable");
75
+ return prompt.replace(input.block, ["<frontend_contract_input>", "Complete canonical obligations for this session only. Confirm each id with record_requirement; preserve all independent conditions.", JSON.stringify(projectFrontendInputScope(input.payload, scopeIds)), "</frontend_contract_input>"].join("\n"));
76
+ }
@@ -0,0 +1,186 @@
1
+ import { collectCanonicalStateFlowNames } from "./frontend-contract-facts.js";
2
+ export function committedFactFromPlanRecord(value) {
3
+ if (!value || typeof value !== "object" || Array.isArray(value))
4
+ return undefined;
5
+ const record = value;
6
+ if (record.phase !== undefined && record.phase !== "committed")
7
+ return undefined;
8
+ const fact = record.fact;
9
+ return fact && typeof fact === "object" && !Array.isArray(fact)
10
+ ? fact
11
+ : typeof record.kind === "string"
12
+ ? record
13
+ : undefined;
14
+ }
15
+ export function planFactStringList(value) {
16
+ if (!Array.isArray(value))
17
+ return [];
18
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0);
19
+ }
20
+ export function planFactScopeIntersects(fact, requirementIds) {
21
+ return planFactStringList(fact.scopeRequirementIds).some((id) => requirementIds.has(id));
22
+ }
23
+ /** Compute the authoritative coverage queue from the committed plan ledger. */
24
+ export function collectFrontendPlanMissingFacts(input) {
25
+ const requirements = new Map();
26
+ const standaloneEvidenceGaps = new Set();
27
+ const verificationTargetIds = new Set();
28
+ const verificationTargetRequirements = new Map();
29
+ for (const value of input.committedFacts) {
30
+ const fact = committedFactFromPlanRecord(value);
31
+ if (!fact || fact.origin !== "plan")
32
+ continue;
33
+ if (fact.kind === "plan-requirement" && fact.entry && typeof fact.entry === "object") {
34
+ const entry = fact.entry;
35
+ if (typeof entry.id === "string" && entry.id.trim())
36
+ requirements.set(entry.id, entry);
37
+ }
38
+ if (fact.kind === "plan-verification-target" && fact.entry && typeof fact.entry === "object") {
39
+ const entry = fact.entry;
40
+ const id = entry.id;
41
+ if (typeof id === "string" && id.trim()) {
42
+ verificationTargetIds.add(id);
43
+ verificationTargetRequirements.set(id, new Set(Array.isArray(entry.requirementIds)
44
+ ? entry.requirementIds.filter((value) => typeof value === "string")
45
+ : []));
46
+ }
47
+ }
48
+ if (fact.kind === "plan-evidence-gap" && fact.entry && typeof fact.entry === "object") {
49
+ const entry = fact.entry;
50
+ const requirementId = entry.requirementId;
51
+ const description = entry.description;
52
+ if (typeof requirementId === "string" && requirementId.trim() && typeof description === "string" && description.trim()) {
53
+ standaloneEvidenceGaps.add(requirementId);
54
+ }
55
+ }
56
+ }
57
+ const missing = [];
58
+ for (const id of input.requirementIds) {
59
+ const entry = requirements.get(id);
60
+ if (!entry) {
61
+ missing.push({
62
+ kind: "plan-requirement",
63
+ id,
64
+ requirementIds: [id],
65
+ reason: `requirement ${id} has no committed plan-requirement fact`,
66
+ });
67
+ continue;
68
+ }
69
+ const targetIds = Array.isArray(entry.verificationTargetIds)
70
+ ? entry.verificationTargetIds.filter((value) => typeof value === "string" && value.trim().length > 0)
71
+ : [];
72
+ const gap = entry.evidenceGap && typeof entry.evidenceGap === "object"
73
+ ? entry.evidenceGap
74
+ : undefined;
75
+ const hasEvidenceGap = (typeof gap?.description === "string" && gap.description.trim().length > 0) ||
76
+ standaloneEvidenceGaps.has(id);
77
+ if (targetIds.length === 0 && !hasEvidenceGap) {
78
+ missing.push({
79
+ kind: "plan-verification-target",
80
+ requirementIds: [id],
81
+ reason: `requirement ${id} declares neither a verification target nor a non-empty evidenceGap`,
82
+ });
83
+ continue;
84
+ }
85
+ for (const targetId of targetIds) {
86
+ if (!verificationTargetIds.has(targetId) ||
87
+ !verificationTargetRequirements.get(targetId)?.has(id)) {
88
+ missing.push({
89
+ kind: "plan-verification-target",
90
+ id: targetId,
91
+ requirementIds: [id],
92
+ reason: `requirement ${id} references verification target ${targetId}, but that target is not committed`,
93
+ });
94
+ }
95
+ }
96
+ }
97
+ return missing;
98
+ }
99
+ /** Completeness checks for phases whose facts are committed incrementally. */
100
+ export function collectFrontendPlanPhaseMissingFacts(input) {
101
+ const facts = input.committedFacts
102
+ .map(committedFactFromPlanRecord)
103
+ .filter((fact) => Boolean(fact && fact.origin === "plan"));
104
+ if (input.phase === "ux-registry") {
105
+ return facts.some((fact) => fact.kind === "state-registry")
106
+ ? []
107
+ : [
108
+ {
109
+ kind: "state-registry",
110
+ requirementIds: [...input.requirementIds],
111
+ reason: "global UX vocabulary phase has no committed state-registry fact",
112
+ },
113
+ ];
114
+ }
115
+ if (input.phase === "ux-local") {
116
+ // One snapshot per check: no cross-revision cache to invalidate.
117
+ const required = new Set(input.behaviorRequiredRequirementIds ?? []);
118
+ const requested = new Set(input.requirementIds.filter(id => required.has(id)));
119
+ if (!requested.size)
120
+ return [];
121
+ const byRequirement = new Map();
122
+ for (const fact of facts) {
123
+ for (const id of new Set(planFactStringList(fact.scopeRequirementIds))) {
124
+ if (!requested.has(id))
125
+ continue;
126
+ const entries = byRequirement.get(id) ?? [];
127
+ entries.push(fact);
128
+ byRequirement.set(id, entries);
129
+ }
130
+ }
131
+ const liveStateFlow = collectCanonicalStateFlowNames(facts);
132
+ const missing = [];
133
+ for (const id of input.requirementIds) {
134
+ if (!required.has(id))
135
+ continue;
136
+ const scopedFacts = byRequirement.get(id) ?? [];
137
+ const hasChoice = scopedFacts.some((fact) => fact.kind === "component-choice" &&
138
+ Array.isArray(fact.uiComponentChoices) &&
139
+ fact.uiComponentChoices.length > 0);
140
+ const canonicalStateFlow = collectCanonicalStateFlowNames(scopedFacts);
141
+ const hasStateFlow = [...canonicalStateFlow.uiStateNames].some(name => liveStateFlow.uiStateNames.has(name)) ||
142
+ [...canonicalStateFlow.interactionNames].some(name => liveStateFlow.interactionNames.has(name));
143
+ if (!hasChoice) {
144
+ missing.push({
145
+ kind: "component-choice",
146
+ requirementIds: [id],
147
+ reason: "behaviour-required UX slice has no committed component-choice fact",
148
+ });
149
+ }
150
+ if (!hasStateFlow) {
151
+ missing.push({
152
+ kind: "state-flow",
153
+ requirementIds: [id],
154
+ reason: "behaviour-required UX slice has no committed state-flow fact",
155
+ });
156
+ }
157
+ }
158
+ return missing;
159
+ }
160
+ const hasMockApi = facts.some((fact) => fact.kind === "mock-api");
161
+ const allInteractions = collectCanonicalStateFlowNames(facts).interactionNames;
162
+ const requested = new Set(input.requirementIds);
163
+ const liveInteractions = new Set([...collectCanonicalStateFlowNames(facts.filter(f => !planFactStringList(f.scopeRequirementIds).length || planFactScopeIntersects(f, requested))).interactionNames].filter(name => allInteractions.has(name)));
164
+ const coveredInteractions = new Set(facts
165
+ .filter((fact) => fact.kind === "data-flow")
166
+ .flatMap((fact) => planFactStringList(fact.interactions)));
167
+ const missing = [];
168
+ if (!hasMockApi) {
169
+ missing.push({
170
+ kind: "mock-api",
171
+ requirementIds: [...input.requirementIds],
172
+ reason: "global Mock/data phase has no committed mock-api fact",
173
+ });
174
+ }
175
+ for (const interaction of liveInteractions) {
176
+ if (coveredInteractions.has(interaction))
177
+ continue;
178
+ missing.push({
179
+ kind: "data-flow",
180
+ id: interaction,
181
+ requirementIds: [...input.requirementIds],
182
+ reason: `interaction ${interaction} has no committed data-flow fact`,
183
+ });
184
+ }
185
+ return missing;
186
+ }
@@ -0,0 +1,18 @@
1
+ /** Precise failures own their recovery. A stale transport length marker must
2
+ * never turn cancellation, provider rejection or policy failure into progress. */
3
+ export function classifyFrontendPlanRecovery(result) {
4
+ const category = result.failureCategory ?? "";
5
+ if (result.timedOut || result.stopReason === "aborted" ||
6
+ /interrupt|termination-unconfirmed|budget_breach/.test(category) ||
7
+ ["cancelled", "auth", "missing-api-key", "rate-limit", "quota", "timeout", "tool-policy", "write-guard", "path-violation", "governance-blocked", "human-rejected"].includes(category))
8
+ return "stop";
9
+ if (result.ok)
10
+ return "none";
11
+ if (category === "output-limit")
12
+ return "output";
13
+ if (["context-overflow", "context-budget-exhausted"].includes(category))
14
+ return "context";
15
+ if (result.stopReason === "length" && ["", "empty-output", "network", "nonzero-exit", "unknown"].includes(category))
16
+ return "output";
17
+ return "none";
18
+ }
@@ -1,3 +1,4 @@
1
+ import { collectFrontendExecutionGroups } from "./frontend-execution-groups.js";
1
2
  /**
2
3
  * Deterministic Markdown renderer for a canonical frontend implementation
3
4
  * contract. The prewrite gate materializes this as `contracts/frontend-plan.md`
@@ -12,8 +13,25 @@ function bulletList(items) {
12
13
  return "";
13
14
  return items.map((item) => `- ${item}`).join("\n");
14
15
  }
16
+ /** The runtime's deterministic fallback for an outcome the plan did not
17
+ * author. It is bookkeeping for the schema's non-empty constraint, not user
18
+ * prose — the renderer must never present it as requirement content. */
19
+ const IMPLIED_OUTCOME_PREFIX = "implied by contract:";
15
20
  function requirementsCoverage(contract) {
16
- const lines = contract.requirements.map((requirement) => `- ${requirement.id}: ${requirement.expectedOutcome}`);
21
+ const renderRequirement = (requirement, includeOutcome = true) => {
22
+ const targets = requirement.implementationTargets.join(", ");
23
+ const verificationTargets = requirement.verificationTargetIds.join(", ");
24
+ const authoredOutcome = !includeOutcome || requirement.expectedOutcome.startsWith(IMPLIED_OUTCOME_PREFIX)
25
+ ? ""
26
+ : ` — ${requirement.expectedOutcome}`;
27
+ return `- ${requirement.id} → ${targets || "(no targets)"} [${verificationTargets || "no VT"}]${authoredOutcome}`;
28
+ };
29
+ const lines = collectFrontendExecutionGroups(contract.requirements).flatMap(group => {
30
+ const members = contract.requirements.filter(r => group.requirementIds.includes(r.id));
31
+ if (group.kind === "unclassified")
32
+ return members.map(r => renderRequirement(r));
33
+ return [`### ${group.id} (${group.kind})`, group.summary, ...members.map(r => renderRequirement(r, false)), "Full independent acceptance conditions remain in contracts/frontend-implementation-contract.json."];
34
+ });
17
35
  return lines.length > 0 ? lines.join("\n") : "_(no requirements)_";
18
36
  }
19
37
  function uiStateHandling(contract) {
@@ -61,7 +79,7 @@ function sourceFidelityLedger(contract) {
61
79
  `- ledger: ${binding.ledgerPath}`,
62
80
  `- ledgerSha256: ${binding.ledgerSha256 ?? "(missing)"}`,
63
81
  `- inputDigest: ${binding.inputDigest ?? "(missing)"}`,
64
- "- 语义权威:原始 requirement/acceptance 文档与 requirement-ledger.json;本计划只消费派生摘要与 bound ledger 指针,长原文不内联。",
82
+ "- 语义权威:原始 requirement/acceptance 文档与 requirement-ledger.json;本计划按执行组展示共享方案;独立验收全文保留在 canonical contract,执行输入按完整义务分批。",
65
83
  ];
66
84
  const boundCount = Object.keys(binding.requirementToFragments ?? {}).length;
67
85
  if (boundCount > 0) {
@@ -98,7 +116,7 @@ export function renderFrontendPlanMarkdown(contract) {
98
116
  interactionNotes(contract),
99
117
  "",
100
118
  "## Mock & API Strategy",
101
- contract.mockApi.strategy,
119
+ contract.mockApi?.strategy || "_(not specified)_",
102
120
  "",
103
121
  "## Dependency Policy",
104
122
  contract.dependencyPolicy || "_(not specified)_",
@@ -734,7 +734,7 @@ export async function runFrontendPrewriteGate(input) {
734
734
  failureCode: "target-outside-write-set",
735
735
  });
736
736
  }
737
- const nonStaticVTs = analysis.canonical.verificationTargets.filter((vt) => vt.type !== "static");
737
+ const nonStaticVTs = analysis.canonical.verificationTargets.filter((vt) => vt.mode !== "static");
738
738
  const uncoveredVTs = nonStaticVTs.filter((vt) => ![...writeSet].some((pattern) => pathMatchesPattern(vt.file, pattern) || vt.file === pattern));
739
739
  if (uncoveredVTs.length > 0) {
740
740
  return finalizePrewrite(input, {
@@ -1,3 +1,4 @@
1
+ import { setTimeout as delay } from "node:timers/promises";
1
2
  import { isTerminalOperationState, } from "../../infrastructure/console/operation-store.js";
2
3
  import { runOperation, } from "../../worker/console/operation-runner.js";
3
4
  import { buildFailureCapsule, classifyFailureOwner, projectProtocolFailureReason, } from "./frontend-recovery-capsule.js";
@@ -78,7 +79,13 @@ export async function runRecoveryOperationController(input) {
78
79
  const readChangedFiles = deps.readChangedFiles ?? (async () => []);
79
80
  const fingerprints = [...(deps.previousFingerprints ?? [])];
80
81
  let budgetAggregate;
81
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
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) {
82
89
  const operation = await deps.store.get(operationId);
83
90
  if (!operation) {
84
91
  return {
@@ -89,13 +96,13 @@ export async function runRecoveryOperationController(input) {
89
96
  };
90
97
  }
91
98
  // Total budget fold + hard stop (resource protection, fail-closed).
92
- const ledgers = await readLedgers();
93
- if (deps.budget) {
94
- budgetAggregate = foldOperationBudgets({
95
- ledgers,
96
- budget: deps.budget,
97
- now,
98
- });
99
+ try {
100
+ const ledgers = await readLedgers();
101
+ if (deps.budget)
102
+ budgetAggregate = foldOperationBudgets({ ledgers, budget: deps.budget, now });
103
+ }
104
+ catch (error) {
105
+ return { terminalReason: "needs-attention", attempts: attempt, budgetAggregate, fingerprints, detail: `Recovery budget evidence unavailable: ${error instanceof Error ? error.message : String(error)}` };
99
106
  }
100
107
  if (budgetAggregate?.status === "breached") {
101
108
  return {
@@ -190,7 +197,24 @@ export async function runRecoveryOperationController(input) {
190
197
  detail: `interrupt projection: ${projected}`,
191
198
  };
192
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
+ }
193
216
  await runOperation(operationId, deps.operationRunnerDeps);
217
+ attempt += 1;
194
218
  }
195
219
  return {
196
220
  terminalReason: "max-attempts",
@@ -87,6 +87,7 @@ export function foldOperationBudgets(input) {
87
87
  limits: { ...input.budget.limits },
88
88
  consumed: {
89
89
  executorCalls: 0,
90
+ ...(input.budget.limits.maxProviderRequests !== undefined ? { providerRequests: 0 } : {}),
90
91
  wallTimeMs: 0,
91
92
  tokens: null,
92
93
  repairPasses: 0,
@@ -100,6 +101,15 @@ export function foldOperationBudgets(input) {
100
101
  let knownTokenSum = 0;
101
102
  const missing = new Set();
102
103
  for (const ledger of input.ledgers) {
104
+ if (input.budget.limits.maxProviderRequests !== undefined) {
105
+ if (ledger.consumed.providerRequests === undefined) {
106
+ if (input.budget.mode === "hard")
107
+ throw Error("FRONTEND_PROVIDER_BUDGET_INVALID: missing operation request consumption");
108
+ delete aggregate.consumed.providerRequests;
109
+ }
110
+ else if (aggregate.consumed.providerRequests !== undefined)
111
+ aggregate.consumed.providerRequests += ledger.consumed.providerRequests;
112
+ }
103
113
  aggregate.consumed.executorCalls += ledger.consumed.executorCalls;
104
114
  aggregate.consumed.wallTimeMs += ledger.consumed.wallTimeMs;
105
115
  aggregate.consumed.repairPasses += ledger.consumed.repairPasses;
@@ -119,6 +129,9 @@ export function foldOperationBudgets(input) {
119
129
  aggregate.consumed.missingTokenNodeIds = [...missing].sort((a, b) => a.localeCompare(b));
120
130
  if (aggregate.mode === "hard") {
121
131
  const limits = aggregate.limits;
132
+ pushAggregateBreach(aggregate, "providerRequests", aggregate.consumed.providerRequests ?? 0, limits.maxProviderRequests, now);
133
+ if (aggregate.breaches.length)
134
+ aggregate.status = "breached";
122
135
  pushAggregateBreach(aggregate, "executorCalls", aggregate.consumed.executorCalls, limits.maxExecutorCalls, now);
123
136
  pushAggregateBreach(aggregate, "wallTimeMs", aggregate.consumed.wallTimeMs, limits.maxWallTimeMs, now);
124
137
  pushAggregateBreach(aggregate, "repairPasses", aggregate.consumed.repairPasses, limits.maxRepairPasses, now);
@@ -27,6 +27,7 @@ export function deriveFrontendRecoveryFailureSource(result) {
27
27
  * - `frontend-plan-pi` failure → reset plan + design-review + policy/admission (+ descendants);
28
28
  * - `frontend-design-policy-shell` failure → reset policy/admission (+ descendants);
29
29
  * - design-review rejection → reset plan + design review + admission (+ descendants);
30
+ * - final-review rejection → reset implementation + verification/review (+ descendants);
30
31
  * - other admission failures → reset writer admission (+ descendants);
31
32
  *
32
33
  * Everything upstream of the failure source is imported as verified parent facts.
@@ -62,7 +63,9 @@ function computeFrontendRecoveryPlanForSource(input) {
62
63
  // reset from implement: the recovery re-implements and re-verifies.
63
64
  const resetRoot = input.failureSource === "frontend-verify-shell"
64
65
  ? "frontend-implement-pi"
65
- : input.failureSource;
66
+ : input.failureSource === "frontend-review-pi"
67
+ ? "frontend-implement-pi"
68
+ : input.failureSource;
66
69
  const resetNodeIds = computeResetClosure(input.spec, resetRoot);
67
70
  const resetSet = new Set(resetNodeIds);
68
71
  const importedNodeIds = input.spec.tasks