@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
@@ -1,3 +1,5 @@
1
+ import { inspectRouteStates } from "../state.js";
2
+ import { mergeTaskHistoryPage } from "../task-history.js";
1
3
  /** Observe UI view module (R5). */
2
4
  import {
3
5
  UI_TEXT,
@@ -16,7 +18,7 @@ import {
16
18
  DAG_EFFECTIVE_STATUS_LABELS,
17
19
  STATUS_LABELS,
18
20
  } from "../constants.js";
19
- import { clearNode, el, metaGrid, buildTable } from "../dom.js";
21
+ import { clearNode, el, metaGrid, buildTable, reconcileKeyed } from "../dom.js";
20
22
  import {
21
23
  badge,
22
24
  badgeClass,
@@ -45,6 +47,7 @@ import {
45
47
  import {
46
48
  fetchJson,
47
49
  fetchJsonResult,
50
+ summarizeFetchError,
48
51
  artifactUrl,
49
52
  parseArtifactPreviewResponse,
50
53
  } from "../api.js";
@@ -66,7 +69,7 @@ import {
66
69
  renderViewState,
67
70
  mountViewState,
68
71
  } from "../state.js";
69
- import { uiState } from "../state.js";
72
+ import { uiState, taskHistoryStates, workspaceScope } from "../state.js";
70
73
  import { sortPoolTasks } from "../format-pool.js";
71
74
  import {
72
75
  dagMetaVisibility,
@@ -75,7 +78,7 @@ import {
75
78
  findDagForRun,
76
79
  } from "../dag-model.js";
77
80
  import { layoutDag } from "../dag-layout.js";
78
- import { showView, setBreadcrumb, updateHeaderRefresh, mountRelationBar } from "../shell-chrome.js";
81
+ import { showView, setBreadcrumb, updateHeaderRefresh, mountRelationBar, bindManualViewRefresh } from "../shell-chrome.js";
79
82
  import { artifactLink } from "../kpi.js";
80
83
  import { findBatchRunIdForTask, taskRunHistoryRow } from "../run-processing.js";
81
84
 
@@ -91,7 +94,7 @@ export async function renderTaskDetail(featureId, taskId) {
91
94
  showView("task");
92
95
  setBreadcrumb([
93
96
  { label: UI_TEXT.dashboard, href: "#/" },
94
- { label: "资源池", href: "/#/inspect/pool" },
97
+ { label: "资源池", href: inspectRouteStates.get("return-list") ?? "/#/inspect/pool" },
95
98
  { label: displayLabel },
96
99
  ]);
97
100
 
@@ -103,13 +106,29 @@ export async function renderTaskDetail(featureId, taskId) {
103
106
  const ledgerEl = document.getElementById("task-ledger");
104
107
  if (!metaEl || !linksEl || !evidenceEl || !runsEl) return false;
105
108
  if (titleEl) titleEl.textContent = displayLabel;
106
- mountViewState(metaEl, "loading", "加载 Task…");
109
+ const identity = `${workspaceScope() ?? ""}:${featureId ?? ""}:${taskId}`;
110
+ const requestGeneration = uiState.pollingGeneration;
111
+ if (metaEl.dataset.taskIdentity !== identity) {
112
+ metaEl.dataset.taskIdentity = identity;
113
+ mountViewState(metaEl, "loading", "加载 Task…");
114
+ clearNode(runsEl);
115
+ for (const element of [linksEl, evidenceEl, ledgerEl]) if (element) clearNode(element);
116
+ }
117
+ if (!taskHistoryStates.has(identity)) taskHistoryStates.set(identity, { runs: [], nextBefore: null, loadedOlder: false, loadingOlder: false });
118
+ const historyState = taskHistoryStates.get(identity);
107
119
 
108
120
  const [taskResult, historyResult, snapshot] = await Promise.all([
109
121
  fetchJsonResult(taskApi),
110
122
  fetchJsonResult(`${taskApi}/runs?limit=20`),
111
123
  fetchJson("/api/snapshot"),
112
124
  ]);
125
+ if (requestGeneration !== uiState.pollingGeneration || metaEl.dataset.taskIdentity !== identity) return false;
126
+ if (!taskResult.ok && ![404, 409].includes(taskResult.status)) {
127
+ let error = metaEl.querySelector(".task-refresh-error");
128
+ if (!error) { error = el("p", "task-refresh-error node-input-error"); metaEl.appendChild(error); }
129
+ error.textContent = `更新失败,保留上次内容。${summarizeFetchError(taskResult)}`;
130
+ return true;
131
+ }
113
132
  const task = taskResult.ok ? taskResult.body : null;
114
133
  const history = historyResult.ok ? historyResult.body : null;
115
134
  if (snapshot) {
@@ -120,7 +139,6 @@ export async function renderTaskDetail(featureId, taskId) {
120
139
  clearNode(metaEl);
121
140
  clearNode(linksEl);
122
141
  clearNode(evidenceEl);
123
- clearNode(runsEl);
124
142
 
125
143
  if (!task) {
126
144
  if (taskResult.status === 409) {
@@ -234,27 +252,9 @@ export async function renderTaskDetail(featureId, taskId) {
234
252
  evidenceEl.appendChild(evidenceLinks);
235
253
  }
236
254
 
237
- runsEl.appendChild(el("h3", "section-heading", "运行历史"));
238
- const runs = history?.runs ?? [];
239
- if (runs.length === 0) {
240
- mountViewState(runsEl, "empty", "暂无 ledger 运行记录。");
241
- } else {
242
- const rows = runs.map(taskRunHistoryRow);
243
- runsEl.appendChild(
244
- buildTable(
245
- ["worker run", "状态", "时间", "batch", "retryOf", "失败分类"],
246
- rows,
247
- ),
248
- );
249
- if (history?.nextBefore) {
250
- const more = el("button", "link-button", "加载更早的运行");
251
- more.type = "button";
252
- more.addEventListener("click", () => {
253
- void loadOlderTaskRuns(featureId, taskId, history.nextBefore, runsEl);
254
- });
255
- runsEl.appendChild(more);
256
- }
257
- }
255
+ if (history) mergeTaskHistoryPage(historyState, history);
256
+ else historyState.error = summarizeFetchError(historyResult);
257
+ renderTaskHistory(runsEl, historyState, featureId, taskId, identity);
258
258
 
259
259
  const active = ["running", "pending", "blocked", "stale"].includes(
260
260
  (task.status ?? "").toLowerCase(),
@@ -262,6 +262,8 @@ export async function renderTaskDetail(featureId, taskId) {
262
262
  if (ledgerEl) {
263
263
  void renderTaskLedger(ledgerEl, taskId);
264
264
  }
265
+ updateHeaderRefresh(snapshot?.generatedAt ?? uiState.lastSnapshot?.generatedAt, { mode: active ? "auto" : "ended" });
266
+ bindManualViewRefresh("task", () => renderTaskDetail(featureId, taskId));
265
267
  return active;
266
268
  }
267
269
 
@@ -315,42 +317,59 @@ async function renderTaskLedger(ledgerEl, taskId) {
315
317
  ledgerEl.appendChild(pre);
316
318
  }
317
319
 
318
- export async function loadOlderTaskRuns(featureId, taskId, before, runsEl) {
319
- if (runsEl === undefined) {
320
- runsEl = before;
321
- before = taskId;
322
- taskId = featureId;
323
- featureId = undefined;
320
+ function renderTaskHistory(runsEl, state, featureId, taskId, identity) {
321
+ if (runsEl.dataset.historyIdentity !== identity) { clearNode(runsEl); runsEl.dataset.historyIdentity = identity; }
322
+ let table = runsEl.querySelector("table");
323
+ if (!table) {
324
+ runsEl.appendChild(el("h3", "section-heading", "运行历史"));
325
+ table = buildTable(["worker run", "状态", "时间", "batch", "retryOf", "失败分类"], []);
326
+ runsEl.appendChild(table);
327
+ const status = el("p", "muted task-history-status");
328
+ status.setAttribute("role", "status");
329
+ runsEl.appendChild(status);
330
+ const more = el("button", "link-button", "加载更早的运行");
331
+ more.type = "button";
332
+ more.addEventListener("click", () => { void loadOlderTaskRuns(featureId, taskId, state.nextBefore, runsEl); });
333
+ runsEl.appendChild(more);
324
334
  }
325
- const taskApi = featureId
326
- ? `/api/features/${encodeURIComponent(featureId)}/tasks/${encodeURIComponent(taskId)}`
327
- : `/api/tasks/${encodeURIComponent(taskId)}`;
328
- const page = await fetchJson(
329
- `${taskApi}/runs?limit=20&before=${encodeURIComponent(before)}`,
330
- );
331
- if (!page?.runs?.length) return;
332
- const table = runsEl.querySelector("table");
333
- if (!table) return;
334
- const tbody = table.querySelector("tbody") || table;
335
- for (const run of page.runs) {
336
- const tr = document.createElement("tr");
337
- for (const cell of taskRunHistoryRow(run).cells) {
338
- const td = document.createElement("td");
339
- if (cell instanceof Node) td.appendChild(cell);
340
- else td.textContent = cell;
341
- tr.appendChild(td);
342
- }
343
- tbody.appendChild(tr);
344
- }
345
- const btn = runsEl.querySelector("button.link-button");
346
- if (btn) {
347
- if (page.nextBefore) {
348
- btn.onclick = () => {
349
- void loadOlderTaskRuns(featureId, taskId, page.nextBefore, runsEl);
350
- };
351
- } else {
352
- btn.remove();
353
- }
335
+ reconcileKeyed(table.querySelector("tbody"), state.runs, {
336
+ getKey: (run) => run.workerRunId,
337
+ create: () => el("tr"),
338
+ update: (row, run) => {
339
+ const signature = JSON.stringify(run);
340
+ if (row.dataset.signature === signature) return;
341
+ clearNode(row);
342
+ for (const cell of taskRunHistoryRow(run).cells) {
343
+ const td = el("td");
344
+ if (cell instanceof Node) td.appendChild(cell); else td.textContent = cell ?? "—";
345
+ row.appendChild(td);
346
+ }
347
+ row.dataset.signature = signature;
348
+ },
349
+ });
350
+ const status = runsEl.querySelector(".task-history-status");
351
+ status.textContent = state.error ? `历史加载失败:${state.error}` : state.runs.length ? `已加载 ${state.runs.length} 次运行` : "暂无 ledger 运行记录。";
352
+ const more = runsEl.querySelector("button.link-button");
353
+ more.hidden = !state.nextBefore && !state.error;
354
+ more.disabled = state.loadingOlder;
355
+ more.textContent = state.loadingOlder ? "正在加载…" : state.error ? "重试加载" : "加载更早的运行";
356
+ }
357
+
358
+ export async function loadOlderTaskRuns(featureId, taskId, before, runsEl) {
359
+ const identity = `${workspaceScope() ?? ""}:${featureId ?? ""}:${taskId}`;
360
+ const state = taskHistoryStates.get(identity);
361
+ if (!state || state.loadingOlder) return;
362
+ state.loadingOlder = true;
363
+ renderTaskHistory(runsEl, state, featureId, taskId, identity);
364
+ const taskApi = featureId ? `/api/features/${encodeURIComponent(featureId)}/tasks/${encodeURIComponent(taskId)}` : `/api/tasks/${encodeURIComponent(taskId)}`;
365
+ try {
366
+ const result = await fetchJsonResult(`${taskApi}/runs?limit=20${state.nextBefore ? `&before=${encodeURIComponent(state.nextBefore)}` : ""}`);
367
+ if (taskHistoryStates.get(identity) !== state) return;
368
+ if (result.ok && result.body) mergeTaskHistoryPage(state, result.body, Boolean(state.nextBefore));
369
+ else state.error = summarizeFetchError(result);
370
+ } finally {
371
+ state.loadingOlder = false;
372
+ if (runsEl.dataset.historyIdentity === identity && taskHistoryStates.get(identity) === state) renderTaskHistory(runsEl, state, featureId, taskId, identity);
354
373
  }
355
374
  }
356
375
 
@@ -101,6 +101,110 @@ export function lookupRequiredVariantSlotsForMarkdownPath(inventory, markdownPat
101
101
  .replace(/\.md$/i, "");
102
102
  return inventory.modules.find((item) => item.stem === stem)?.requiredVariantSlots ?? [];
103
103
  }
104
+ const MODULE_INDEX_HEADER = [
105
+ "Module Stem",
106
+ "Business Resource",
107
+ "Owned Operations",
108
+ "Owned Rule Keys",
109
+ "Case IDs",
110
+ "Split Reason",
111
+ "Markdown Path",
112
+ "Pytest Path",
113
+ ];
114
+ function markdownTableCells(line) {
115
+ const trimmed = line.trim();
116
+ if (!trimmed.startsWith("|") || !trimmed.endsWith("|"))
117
+ return undefined;
118
+ return trimmed
119
+ .slice(1, -1)
120
+ .split("|")
121
+ .map((cell) => cell.trim().replace(/^`|`$/g, ""));
122
+ }
123
+ function isMarkdownSeparatorRow(cells) {
124
+ return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
125
+ }
126
+ /** Validate the minimum complete run-owned Markdown protocol before N2 succeeds. */
127
+ export function assessBackendTestPlanProtocol(markdown) {
128
+ const canonical = canonicalizeBackendTestPlanProtocolHeadings(markdown).markdown;
129
+ const lines = canonical.split("\n");
130
+ const issues = [];
131
+ if (lines.some((line) => /^(?:```|~~~)/.test(line.trim()))) {
132
+ issues.push({
133
+ code: "fenced-plan-output",
134
+ detail: "return the final Markdown plan directly, without a code fence",
135
+ });
136
+ }
137
+ const headingIndexes = new Map();
138
+ for (const heading of [
139
+ "Coverage Scope",
140
+ "Coverage Matrix",
141
+ "Scenario Partitions",
142
+ "Module Index",
143
+ ]) {
144
+ headingIndexes.set(heading, lines.flatMap((line, index) => line.trim() === `## ${heading}` ? [index] : []));
145
+ }
146
+ const requireUnique = (heading, missingCode, duplicateCode) => {
147
+ const count = headingIndexes.get(heading)?.length ?? 0;
148
+ if (count === 0) {
149
+ issues.push({
150
+ code: missingCode,
151
+ detail: `require exactly one exact ## ${heading} section`,
152
+ });
153
+ }
154
+ else if (count > 1) {
155
+ issues.push({
156
+ code: duplicateCode,
157
+ detail: `found ${count}; require exactly one exact ## ${heading} section`,
158
+ });
159
+ }
160
+ };
161
+ // Module Index is first so retry diagnostics lead with the shard authority.
162
+ requireUnique("Module Index", "missing-module-index", "duplicate-module-index");
163
+ requireUnique("Coverage Scope", "missing-coverage-scope", "duplicate-coverage-scope");
164
+ requireUnique("Coverage Matrix", "missing-coverage-matrix", "duplicate-coverage-matrix");
165
+ const scenarioCount = headingIndexes.get("Scenario Partitions")?.length ?? 0;
166
+ if (scenarioCount > 1) {
167
+ issues.push({
168
+ code: "duplicate-scenario-partitions",
169
+ detail: `found ${scenarioCount}; require at most one exact ## Scenario Partitions section`,
170
+ });
171
+ }
172
+ const moduleIndexes = headingIndexes.get("Module Index") ?? [];
173
+ if (moduleIndexes.length === 1) {
174
+ const start = moduleIndexes[0] + 1;
175
+ const nextHeading = lines
176
+ .slice(start)
177
+ .findIndex((line) => /^##\s+\S/.test(line.trim()));
178
+ const end = nextHeading < 0 ? lines.length : start + nextHeading;
179
+ const rows = lines
180
+ .slice(start, end)
181
+ .map(markdownTableCells)
182
+ .filter((cells) => cells !== undefined);
183
+ const headerIndex = rows.findIndex((cells) => cells.length === MODULE_INDEX_HEADER.length &&
184
+ MODULE_INDEX_HEADER.every((name, index) => cells[index] === name));
185
+ if (headerIndex < 0) {
186
+ issues.push({
187
+ code: "invalid-module-index-header",
188
+ detail: `require exact Module Index header: ${MODULE_INDEX_HEADER.join(" | ")}`,
189
+ });
190
+ }
191
+ else {
192
+ const dataRows = rows
193
+ .slice(headerIndex + 1)
194
+ .filter((cells) => !isMarkdownSeparatorRow(cells));
195
+ if (!dataRows.some((cells) => cells.length === MODULE_INDEX_HEADER.length &&
196
+ Boolean(cells[0]?.trim()) &&
197
+ Boolean(cells[6]?.trim()) &&
198
+ Boolean(cells[7]?.trim()))) {
199
+ issues.push({
200
+ code: "empty-module-index",
201
+ detail: "Module Index must contain at least one complete 8-cell module row",
202
+ });
203
+ }
204
+ }
205
+ }
206
+ return { ok: issues.length === 0, issues };
207
+ }
104
208
  export function isCanonicalPlanHeadingLine(line) {
105
209
  return CANONICAL_HEADING_LINE.test(line.trim());
106
210
  }
@@ -1,6 +1,56 @@
1
1
  import { writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { checkBudgetPreNode, createBudgetLedger, formatBudgetReportMarkdown, isHardBudgetBreached, recordNodeBudgetSample, skipPendingNodesForBudgetBreach, } from "../../application/evaluation/budget.js";
3
+ import { checkBudgetPreNode, createBudgetLedger, formatBudgetReportMarkdown, isHardBudgetBreached, recordNodeBudgetSample, reserveProviderBudget, skipPendingNodesForBudgetBreach, } from "../../application/evaluation/budget.js";
4
+ import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
5
+ /** A continuation already contains its ancestors' consumption. Never sum both. */
6
+ export function validateCumulativeBudgetSuccessor(parent, child) {
7
+ const lineage = child.continuation?.budgetLineage ?? child.frontendRecoveryState?.budgetLineage;
8
+ const previous = parent.budgetLedger;
9
+ const next = child.budgetLedger;
10
+ if (!previous || !next || !lineage || (child.continuation?.parentRunId ?? child.frontendRecoveryState?.parentRunId) !== parent.runId || lineage.parentLedgerSha256 !== sha256OfCanonicalJson(previous) || sha256OfCanonicalJson(lineage.inheritedConsumed) !== sha256OfCanonicalJson(previous.consumed) || sha256OfCanonicalJson(parent.budget ?? null) !== sha256OfCanonicalJson(child.budget ?? null))
11
+ throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: unbound cumulative continuation");
12
+ for (const key of ["executorCalls", "providerRequests", "wallTimeMs", "repairPasses", "peakContextChars"])
13
+ if ((next.consumed[key] ?? 0) < (previous.consumed[key] ?? 0))
14
+ throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: consumption decreased");
15
+ return next;
16
+ }
17
+ const requestQueues = new WeakMap();
18
+ /** One shared, attempt-fenced, durable admission queue for every request in a run. */
19
+ export async function reserveDagProviderRequest(input) {
20
+ const ledger = input.state.budgetLedger;
21
+ if (!ledger || ledger.limits.maxProviderRequests === undefined)
22
+ throw Error("FRONTEND_PROVIDER_BUDGET_INVALID: frozen request budget unavailable");
23
+ const previous = requestQueues.get(ledger) ?? Promise.resolve();
24
+ const current = previous.catch(() => { }).then(async () => {
25
+ const active = () => input.state.status === "running" && input.state.nodes[input.nodeId]?.status === "RUNNING" && input.state.nodes[input.nodeId]?.currentAttempt === input.attempt;
26
+ if (!active())
27
+ throw Error("FRONTEND_PROVIDER_BUDGET_STALE: request belongs to an inactive attempt");
28
+ if (isHardBudgetBreached(ledger))
29
+ throw Error("FRONTEND_PROVIDER_BUDGET_EXHAUSTED: run budget already breached");
30
+ const breach = reserveProviderBudget(ledger, input.nodeId);
31
+ if (breach)
32
+ applyHardBudgetBreach(input.state, breach);
33
+ try {
34
+ await input.persist();
35
+ }
36
+ catch (error) {
37
+ ledger.status = "breached";
38
+ throw Error(`FRONTEND_PROVIDER_BUDGET_PERSIST_FAILED: ${error instanceof Error ? error.message : String(error)}`);
39
+ }
40
+ if (breach)
41
+ throw Error("FRONTEND_PROVIDER_BUDGET_EXHAUSTED: request quota reached");
42
+ if (!active())
43
+ throw Error("FRONTEND_PROVIDER_BUDGET_STALE: attempt stopped while reserving request");
44
+ });
45
+ requestQueues.set(ledger, current);
46
+ try {
47
+ await current;
48
+ }
49
+ finally {
50
+ if (requestQueues.get(ledger) === current)
51
+ requestQueues.delete(ledger);
52
+ }
53
+ }
4
54
  export function initRunBudgetLedger(state, budget) {
5
55
  if (!budget)
6
56
  return;
@@ -8,7 +58,7 @@ export function initRunBudgetLedger(state, budget) {
8
58
  state.budgetLedger = createBudgetLedger(budget);
9
59
  }
10
60
  function wallTimeMs(state, now = Date.now()) {
11
- const lineage = state.continuation?.budgetLineage;
61
+ const lineage = state.continuation?.budgetLineage ?? state.frontendRecoveryState?.budgetLineage;
12
62
  if (lineage) {
13
63
  return (lineage.inheritedConsumed.wallTimeMs +
14
64
  Math.max(0, now - new Date(lineage.inheritedAt).getTime()));
@@ -16,7 +66,7 @@ function wallTimeMs(state, now = Date.now()) {
16
66
  return Math.max(0, now - new Date(state.startedAt).getTime());
17
67
  }
18
68
  function repairPasses(state) {
19
- const lineage = state.continuation?.budgetLineage;
69
+ const lineage = state.continuation?.budgetLineage ?? state.frontendRecoveryState?.budgetLineage;
20
70
  if (lineage) {
21
71
  return (lineage.inheritedConsumed.repairPasses +
22
72
  Math.max(0, (state.convergence?.currentPass ?? 1) - 1));
@@ -32,7 +32,10 @@ export const DEFAULT_DAG_RETRY_CATEGORIES = [
32
32
  "rate-limit",
33
33
  "unavailable",
34
34
  "empty-output",
35
+ "output-limit",
35
36
  ];
37
+ /** Provider stopReason=length: output/context capacity ended the turn early. */
38
+ export const OUTPUT_LIMIT_RETRY_CATEGORY = "output-limit";
36
39
  export const STRUCTURED_OUTPUT_RETRY_CATEGORY = "output-too-large";
37
40
  export const PROTOCOL_INVALID_RETRY_CATEGORY = "protocol-invalid";
38
41
  /** Recoverable model artifact/schema formatting failure on read-only structured nodes. */
@@ -0,0 +1,9 @@
1
+ /** Shared Contract/Plan bound. Permissions and directory globs are not files. */
2
+ export const FRONTEND_MAX_IMPLEMENTATION_FILES = 15;
3
+ export function frontendConcreteDeliveryFiles(files) {
4
+ if (!Array.isArray(files))
5
+ return [];
6
+ return [...new Set(files.filter((file) => typeof file === "string")
7
+ .map(file => file.trim().replace(/\\/g, "/").replace(/^(\.\/)+/, ""))
8
+ .filter(file => file.length > 0 && !/[*?{}]/.test(file) && !file.endsWith("/")))];
9
+ }
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ /** Shared projections of committed frontend facts. Callers filter commit phase. */
3
+ function object(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value)
5
+ ? value
6
+ : undefined;
7
+ }
8
+ /** Join requirement-owned identity with separately committed evidence by id.
9
+ * Later evidence declarations replace only the lanes they explicitly name. */
10
+ export function resolveFrontendContractRequirements(facts) {
11
+ const contractFacts = facts.map(object).filter((fact) => fact?.origin === "contract");
12
+ const evidenceById = new Map();
13
+ for (const fact of contractFacts) {
14
+ if (fact.kind !== "evidence-expectation" ||
15
+ typeof fact.requirementId !== "string")
16
+ continue;
17
+ evidenceById.set(fact.requirementId, {
18
+ ...evidenceById.get(fact.requirementId),
19
+ ...object(fact.evidence),
20
+ });
21
+ }
22
+ return contractFacts.flatMap((fact) => fact.kind === "requirement" && typeof fact.id === "string"
23
+ ? [{
24
+ ...fact,
25
+ id: fact.id,
26
+ evidence: { ...object(fact.evidence), ...evidenceById.get(fact.id) },
27
+ }]
28
+ : []);
29
+ }
30
+ function names(value) {
31
+ return Array.isArray(value)
32
+ ? value.filter((item) => typeof item === "string" && item.trim().length > 0)
33
+ : [];
34
+ }
35
+ const evidenceStatus = z.enum(["required", "optional", "not-applicable"]);
36
+ export const frontendEvidenceExpectationSchema = z.object({
37
+ requirementId: z.string().min(1),
38
+ evidence: z.object({
39
+ static: evidenceStatus.optional(),
40
+ behavior: evidenceStatus.optional(),
41
+ mock: evidenceStatus.optional(),
42
+ "real-integration": evidenceStatus.optional(),
43
+ }).strict().refine((evidence) => Object.keys(evidence).length > 0, "declare at least one evidence lane"),
44
+ }).strict();
45
+ const concretePath = z.string().min(1).refine((value) => value === value.trim() &&
46
+ !/^[A-Za-z]:/.test(value) &&
47
+ !/[\\*?\[\]{}\0]/.test(value) &&
48
+ value.split("/").every((part) => part !== "" && part !== "." && part !== ".."), "must be a concrete relative POSIX file path without traversal");
49
+ export const frontendRequiredDeliverablesSchema = z.object({
50
+ items: z.array(z.object({
51
+ path: concretePath,
52
+ requirementId: z.string().min(1),
53
+ sourceFragmentId: z.string().min(1),
54
+ }).strict()),
55
+ }).strict();
56
+ /** Validate structure and provenance; the Contract model owns the distinction
57
+ * between delivery obligations, permissions, prohibitions and references. */
58
+ export function validateFrontendRequiredDeliverables(value, requirements) {
59
+ const declaration = frontendRequiredDeliverablesSchema.parse(value);
60
+ for (const item of declaration.items) {
61
+ const requirement = requirements.get(item.requirementId);
62
+ if (!requirement?.sourceFragmentIds.includes(item.sourceFragmentId)) {
63
+ throw new Error(`required deliverable ${item.path} has an unknown requirement/fragment binding`);
64
+ }
65
+ const text = requirement.sourceFragments?.get(item.sourceFragmentId);
66
+ const escaped = item.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
67
+ // A sentence-ending period is punctuation; a dot followed by filename
68
+ // characters is still part of the token (app.ts must not match app.ts.map).
69
+ const exactPath = new RegExp(`(^|[^\\p{L}\\p{N}_./-])${escaped}(?=$|[^\\p{L}\\p{N}_./-]|\\.(?=$|[^\\p{L}\\p{N}_./-]))`, "u");
70
+ if (!text || !exactPath.test(text)) {
71
+ throw new Error(`required deliverable ${item.path} is not an exact path in frozen source fragment ${item.sourceFragmentId}`);
72
+ }
73
+ }
74
+ return declaration;
75
+ }
76
+ /** Read the latest explicit inventory. Never infer obligations from prose. */
77
+ export function extractRequiredDeliverablePaths(facts) {
78
+ const declaration = facts.map(object).filter((fact) => fact?.origin === "contract" && fact.kind === "required-deliverables").at(-1);
79
+ if (!declaration)
80
+ return [];
81
+ const { items } = frontendRequiredDeliverablesSchema.parse({ items: declaration.items });
82
+ return [...new Set(items.map((item) => item.path))].sort();
83
+ }
84
+ /** Apply state-flow replacement/removal semantics exactly as the plan compiler does. */
85
+ export function collectCanonicalStateFlowNames(committedFacts, includeRegistry = false) {
86
+ const uiStateNames = new Set();
87
+ const interactionNames = new Set();
88
+ for (const value of committedFacts) {
89
+ const record = object(value);
90
+ if (!record || (record.phase !== undefined && record.phase !== "committed"))
91
+ continue;
92
+ const fact = object(record.fact) ?? record;
93
+ if (fact.origin !== "plan")
94
+ continue;
95
+ if (includeRegistry && fact.kind === "state-registry") {
96
+ uiStateNames.clear();
97
+ interactionNames.clear();
98
+ for (const name of names(fact.uiStateNames))
99
+ uiStateNames.add(name);
100
+ for (const name of names(fact.interactionNames))
101
+ interactionNames.add(name);
102
+ }
103
+ if (fact.kind !== "state-flow")
104
+ continue;
105
+ for (const name of names(fact.removeUiStateNames)) {
106
+ uiStateNames.delete(name);
107
+ }
108
+ for (const name of names(fact.removeInteractionNames)) {
109
+ interactionNames.delete(name);
110
+ }
111
+ for (const state of Array.isArray(fact.uiStates) ? fact.uiStates : []) {
112
+ if (!state || typeof state !== "object" || Array.isArray(state))
113
+ continue;
114
+ const name = state.name;
115
+ if (typeof name === "string" && name.trim())
116
+ uiStateNames.add(name);
117
+ }
118
+ for (const interaction of Array.isArray(fact.interactions)
119
+ ? fact.interactions
120
+ : []) {
121
+ if (!interaction || typeof interaction !== "object" || Array.isArray(interaction)) {
122
+ continue;
123
+ }
124
+ const name = interaction.name;
125
+ if (typeof name === "string" && name.trim())
126
+ interactionNames.add(name);
127
+ }
128
+ }
129
+ return { uiStateNames, interactionNames };
130
+ }