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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (331) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +248 -95
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +12 -0
  6. package/dist/application/task-lifecycle/recommendations.js +13 -2
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +23 -5
  9. package/dist/cli/program.js +21 -2
  10. package/dist/cli/update/notifier.js +2 -2
  11. package/dist/cli/update/runtime-activity.js +1 -29
  12. package/dist/cli.js +2 -1
  13. package/dist/commands/client-recovery.js +3 -0
  14. package/dist/commands/dag-follow-up.js +138 -0
  15. package/dist/commands/dag-rerun.js +55 -1
  16. package/dist/commands/init-model-catalog.js +464 -0
  17. package/dist/commands/init-upgrade.js +265 -97
  18. package/dist/commands/init.js +455 -28
  19. package/dist/commands/inspect-next.js +8 -0
  20. package/dist/commands/task-advance.js +19 -0
  21. package/dist/executors/dag-pi-executor.js +2306 -61
  22. package/dist/executors/pi-executor.js +7 -0
  23. package/dist/executors/pi-extension-resolver.js +233 -0
  24. package/dist/executors/pi-playwright-cli-tool.js +74 -28
  25. package/dist/executors/pi-read-budget-policy.js +239 -0
  26. package/dist/executors/pi-sdk-executor.js +180 -33
  27. package/dist/executors/shell-executor.js +1249 -224
  28. package/dist/executors/shell-write-guard.js +14 -0
  29. package/dist/executors/workspace-write-snapshot.js +68 -0
  30. package/dist/infrastructure/harness/atomic-write.js +23 -0
  31. package/dist/shared/dag-failure-category.js +150 -0
  32. package/dist/shared/dag-prompt-override.js +27 -0
  33. package/dist/shared/openspec-spec.js +70 -4
  34. package/dist/shared/operator/capabilities.js +39 -0
  35. package/dist/shared/playwright-cli-command-policy.js +15 -0
  36. package/dist/shared/update/console-notifier.js +100 -0
  37. package/dist/{cli → shared}/update/npm-client.js +70 -15
  38. package/dist/{cli → shared}/update/state.js +41 -13
  39. package/dist/task/config-types.js +50 -5
  40. package/dist/task/contract/apply.js +11 -1
  41. package/dist/task/contract/constants.js +2 -0
  42. package/dist/task/contract/hash.js +3 -0
  43. package/dist/task/contract/observe.js +25 -4
  44. package/dist/task/contract/paths.js +2 -1
  45. package/dist/task/contract/project.js +18 -1
  46. package/dist/task/contract/schema.js +4 -1
  47. package/dist/task/contract/transaction.js +12 -0
  48. package/dist/task/frontend-project-capability.js +199 -18
  49. package/dist/task/source-prepare/completeness.js +188 -0
  50. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  51. package/dist/task/source-prepare/index.js +3 -0
  52. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  53. package/dist/task/source-prepare/ledger-review.js +214 -0
  54. package/dist/task/source-prepare/ledger.js +545 -0
  55. package/dist/task/source-prepare/parse-intent.js +24 -4
  56. package/dist/task/source-prepare/prepare.js +298 -1
  57. package/dist/task/source-prepare/semantic-intake.js +25 -5
  58. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  59. package/dist/task/source-prepare/types.js +22 -0
  60. package/dist/task/source-references.js +22 -1
  61. package/dist/worker/cli.js +18 -2
  62. package/dist/worker/console/chat/chat-event-store.js +62 -4
  63. package/dist/worker/console/chat/context-panel.js +7 -0
  64. package/dist/worker/console/chat/deferred-turn.js +12 -0
  65. package/dist/worker/console/chat/operation-card.js +8 -1
  66. package/dist/worker/console/chat/pi-console-config.js +81 -10
  67. package/dist/worker/console/chat/pi-runtime.js +264 -16
  68. package/dist/worker/console/chat/provider-error.js +98 -0
  69. package/dist/worker/console/chat/routes.js +298 -32
  70. package/dist/worker/console/chat/session-stats.js +179 -0
  71. package/dist/worker/console/chat/session-store.js +93 -63
  72. package/dist/worker/console/chat/todos.js +115 -0
  73. package/dist/worker/console/chat/tool-preview.js +42 -0
  74. package/dist/worker/console/chat/turn-process.js +20 -55
  75. package/dist/worker/console/chat/user-questions.js +266 -0
  76. package/dist/worker/console/chat/workspace-landing.js +14 -0
  77. package/dist/worker/console/console-handoff.js +82 -0
  78. package/dist/worker/console/console-update-and-init.js +115 -0
  79. package/dist/worker/console/console-update-runtime.js +84 -0
  80. package/dist/worker/console/dag-execution-receipt.js +33 -0
  81. package/dist/worker/console/draft-store.js +49 -0
  82. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  83. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  84. package/dist/worker/console/index.js +3 -0
  85. package/dist/worker/console/inspect-split.js +18 -0
  86. package/dist/worker/console/native-directory-picker.js +13 -0
  87. package/dist/worker/console/operation-run-facts.js +19 -3
  88. package/dist/worker/console/operation-store.js +227 -7
  89. package/dist/worker/console/operator-actions.js +388 -0
  90. package/dist/worker/console/operator-surface-health.js +1 -0
  91. package/dist/worker/console/operator-user-error.js +2 -2
  92. package/dist/worker/console/prd-intake-bridge.js +13 -0
  93. package/dist/worker/console/routes.js +355 -3
  94. package/dist/worker/console/security.js +67 -13
  95. package/dist/worker/console/server.js +609 -164
  96. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-BAsjg7ew.js} +1 -1
  97. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-CyIlrwZw.js} +1 -1
  98. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-Bo4tobhH.js} +1 -1
  99. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-Dfpow9gu.js} +1 -1
  100. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-DPmzG_B4.js} +1 -1
  101. package/dist/worker/console/static/assets/channel-DSrJAn0-.js +1 -0
  102. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-pKQpB1UW.js} +1 -1
  103. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-5n9YYquZ.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-DP3CtA2a.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-DYxMJPUy.js} +1 -1
  106. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-CS3D50Io.js} +1 -1
  107. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-gSdDfo5_.js} +1 -1
  108. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-A48q0ZNt.js} +1 -1
  109. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-ow8Mm3DY.js} +1 -1
  110. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-CMKfLWYE.js} +1 -1
  111. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BiTBdQPX.js} +1 -1
  112. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DeMmPl0_.js +1 -0
  113. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DeMmPl0_.js +1 -0
  114. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-Cm7exRz_.js} +1 -1
  115. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-D9O5qGOu.js} +1 -1
  116. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-CkvQnako.js} +1 -1
  117. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-B1rzp7CB.js} +1 -1
  118. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-BP07Ipcc.js} +1 -1
  119. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BlPzweQb.js} +1 -1
  120. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-CxPa7n0S.js} +1 -1
  121. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-CMTZs53A.js} +1 -1
  122. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-CxG_X52r.js} +1 -1
  123. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-D8axtJM0.js} +1 -1
  124. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-DIsGzn7b.js} +1 -1
  125. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CU9zkDG0.js} +1 -1
  126. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-DCjTmz5P.js} +1 -1
  127. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-BksdmFzg.js} +1 -1
  128. package/dist/worker/console/static/assets/index-B-O5ChYq.css +1 -0
  129. package/dist/worker/console/static/assets/index-CfnY8AQx.js +407 -0
  130. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-CGzzzJtL.js} +1 -1
  131. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-BdseB80N.js} +1 -1
  132. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-DM9gsgha.js} +1 -1
  133. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-3q-_9R2b.js} +1 -1
  134. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-B8N-Mxsd.js} +1 -1
  135. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-EPIHUUsu.js} +5 -5
  136. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-CxwO12aa.js} +1 -1
  137. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-bPqbVX6Y.js} +1 -1
  138. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-KzqIvzeX.js} +1 -1
  139. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-CyofpWiV.js} +1 -1
  140. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-cTWJvLTP.js} +1 -1
  141. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-BK--q27B.js} +1 -1
  142. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-C0SDOHXK.js} +1 -1
  143. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-Cong6YIo.js} +1 -1
  144. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DUVkgZx8.js} +1 -1
  145. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C7XgxuZg.js} +1 -1
  146. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dzbnd5oX.js +1 -0
  147. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-D9Ayvwcy.js} +2 -2
  148. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BY0hHk6G.js +8 -0
  149. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-Byb000cH.js} +1 -1
  150. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DMLegPt4.js} +1 -1
  151. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-ChMMeOsP.js} +1 -1
  152. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-bEMEPisW.js} +1 -1
  153. package/dist/worker/console/static/index.html +2 -2
  154. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  155. package/dist/worker/console/static-src/app/console-types.js +68 -4
  156. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  157. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  158. package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
  159. package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
  160. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  161. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  162. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  163. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +140 -8
  164. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  165. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  166. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  167. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  168. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  169. package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
  170. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +168 -72
  171. package/dist/worker/console/static-src/operator-chat/useChatStream.js +102 -21
  172. package/dist/worker/console/static-src/operator-chat/useChatThread.js +184 -4
  173. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  174. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  175. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  176. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  177. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  178. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  179. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  180. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  181. package/dist/worker/console/static-src/shell/workspace-route.js +337 -0
  182. package/dist/worker/console/workspace-context.js +344 -0
  183. package/dist/worker/console/workspace-registry.js +214 -0
  184. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  185. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  186. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  187. package/dist/worker/observability/init-runtime-activity.js +26 -0
  188. package/dist/worker/observability/read-model.js +22 -0
  189. package/dist/worker/observe/node-input.js +160 -3
  190. package/dist/worker/observe/node-process.js +377 -0
  191. package/dist/worker/observe/routes.js +224 -3
  192. package/dist/worker/observe/server.js +4 -0
  193. package/dist/worker/observe/shell-handler-keys.js +34 -0
  194. package/dist/worker/observe/static/api.js +90 -4
  195. package/dist/worker/observe/static/app.js +18 -70
  196. package/dist/worker/observe/static/constants.js +8 -1
  197. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  198. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  199. package/dist/worker/observe/static/dag-node-purpose.js +208 -0
  200. package/dist/worker/observe/static/format.js +60 -0
  201. package/dist/worker/observe/static/index.html +6 -1
  202. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  203. package/dist/worker/observe/static/markdown-render.js +20 -1
  204. package/dist/worker/observe/static/operator-chrome.css +145 -20
  205. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  206. package/dist/worker/observe/static/operator-chrome.js +330 -96
  207. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  208. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  209. package/dist/worker/observe/static/router.d.ts +46 -0
  210. package/dist/worker/observe/static/router.js +25 -1
  211. package/dist/worker/observe/static/state.d.ts +50 -0
  212. package/dist/worker/observe/static/state.js +184 -1
  213. package/dist/worker/observe/static/styles.css +395 -0
  214. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  215. package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
  216. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  217. package/dist/worker/observe/static/views/dag.js +14 -5
  218. package/dist/worker/observe/static/views/dashboard.js +6 -4
  219. package/dist/worker/observe/static/views/failures.js +5 -2
  220. package/dist/worker/observe/static/views/night.js +2 -2
  221. package/dist/worker/observe/static/views/pool.js +1 -0
  222. package/dist/worker/observe/static/views/run.js +3 -1
  223. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  224. package/dist/worker/observe/static/views/task.js +56 -1
  225. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
  226. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  227. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  228. package/dist/workflows/dag/backend-test-writer-completeness.js +10 -5
  229. package/dist/workflows/dag/contract-output-registry.js +15 -0
  230. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  231. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  232. package/dist/workflows/dag/dynamic-runtime/map.js +1 -1
  233. package/dist/workflows/dag/failure-category.js +7 -116
  234. package/dist/workflows/dag/frontend-closeout.js +221 -0
  235. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  236. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  237. package/dist/workflows/dag/frontend-implementation-contract.js +534 -178
  238. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  239. package/dist/workflows/dag/frontend-prewrite-gate.js +371 -393
  240. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  241. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  242. package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
  243. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  244. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  245. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  246. package/dist/workflows/dag/frontend-repair.js +7 -424
  247. package/dist/workflows/dag/frontend-review-context.js +288 -14
  248. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  249. package/dist/workflows/dag/frontend-risk.js +15 -2
  250. package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
  251. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  252. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  253. package/dist/workflows/dag/frontend-shape.js +427 -0
  254. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  255. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  256. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  257. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  258. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  259. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  260. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  261. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  262. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  263. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  264. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  265. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  266. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  267. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  268. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  269. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  270. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  271. package/dist/workflows/dag/frontend-writer-admission.js +272 -0
  272. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  273. package/dist/workflows/dag/init-hybrid.js +984 -618
  274. package/dist/workflows/dag/interrupt-request.js +10 -3
  275. package/dist/workflows/dag/lifecycle.js +3 -3
  276. package/dist/workflows/dag/node-execution.js +468 -34
  277. package/dist/workflows/dag/prompt.js +44 -19
  278. package/dist/workflows/dag/recovery-lease.js +80 -0
  279. package/dist/workflows/dag/report.js +37 -1
  280. package/dist/workflows/dag/rerun-feedback.js +124 -1
  281. package/dist/workflows/dag/rerun-plan.js +144 -5
  282. package/dist/workflows/dag/rerun-run.js +81 -6
  283. package/dist/workflows/dag/rerun-task.js +29 -0
  284. package/dist/workflows/dag/retry-policy.js +318 -8
  285. package/dist/workflows/dag/runner.js +429 -122
  286. package/dist/workflows/dag/scheduler.js +114 -16
  287. package/dist/workflows/dag/structured-output-repair.js +712 -0
  288. package/dist/workflows/dag/types.js +321 -31
  289. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  290. package/dist/workflows/dag/validate.js +92 -32
  291. package/docs/README.md +2 -0
  292. package/docs/architecture/evolution.md +45 -0
  293. package/docs/init-surface.manifest.json +9 -0
  294. package/docs/operations/README.md +1 -1
  295. package/docs/operations/local-development-environment.md +21 -0
  296. package/docs/templates/README.md +2 -1
  297. package/docs/templates/agent-dag-report.schema.json +8 -2
  298. package/docs/templates/agent-dag.schema.json +45 -1
  299. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  300. package/docs/templates/frontend-test-case-checklist.md +2 -2
  301. package/docs/templates/frontend-test-dag.json +8 -10
  302. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  303. package/docs/templates/init-managed-agents.md +13 -3
  304. package/docs/templates/spec-registry.schema.json +45 -0
  305. package/harness.json +3 -3
  306. package/package.json +7 -1
  307. package/scripts/next-info.mjs +356 -0
  308. package/scripts/next-publish-gate.mjs +238 -0
  309. package/scripts/release-source-binding.mjs +251 -0
  310. package/skills/codebase-scout/SKILL.md +1 -1
  311. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  312. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  313. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  314. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  315. package/skills/frontend-design-review/SKILL.md +20 -41
  316. package/skills/frontend-implementation/SKILL.md +3 -4
  317. package/skills/frontend-implementation/references/design-spec.md +15 -10
  318. package/skills/frontend-implementation/references/node-contracts.md +23 -13
  319. package/skills/frontend-review/SKILL.md +22 -14
  320. package/skills/frontend-review/references/review-findings.md +6 -7
  321. package/skills/loop-agent/references/command-reference.md +12 -4
  322. package/skills/loop-agent/references/hybrid-dag.md +8 -1
  323. package/skills/playwright-cli/SKILL.md +23 -1
  324. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  325. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  326. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  327. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  328. package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
  329. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  330. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  331. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -10,7 +10,9 @@ import { parseAssistantContent } from "../../chat/assistant-content.js";
10
10
  * original request would have. `assistantId` is the in-flight assistant
11
11
  * message id to patch for streaming text; reconnect creates one if none. */
12
12
  export function createChatSseEventApplier(deps) {
13
- const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, setMessages, setToolCalls, } = deps;
13
+ const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setDeferredTurns = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, onTelemetryRefresh, setMessages, setToolCalls, setPendingQuestion = () => undefined, setTodos = () => undefined, } = deps;
14
+ let queueOwner;
15
+ let queueRevision = -1;
14
16
  return (eventName, data, assistantId, options) => {
15
17
  if (options?.generation !== undefined &&
16
18
  refs.sessionGenerationRef.current !== options.generation)
@@ -42,6 +44,26 @@ export function createChatSseEventApplier(deps) {
42
44
  const payload = data.data && typeof data.data === "object"
43
45
  ? data.data
44
46
  : data;
47
+ if (eventName === "todo_updated" || eventName === "todo_cleared") {
48
+ const todos = Array.isArray(payload.todos) ? payload.todos : [];
49
+ setTodos(todos.filter((item) => Boolean(item) && typeof item === "object" && typeof item.content === "string" && (item.status === "pending" || item.status === "in_progress" || item.status === "completed")));
50
+ return;
51
+ }
52
+ if ((eventName === "question_requested" ||
53
+ eventName === "question_resolved" ||
54
+ eventName === "question_cancelled") &&
55
+ payload.question &&
56
+ typeof payload.question === "object") {
57
+ const question = payload.question;
58
+ if (typeof question.requestId === "string" &&
59
+ typeof question.sessionId === "string" &&
60
+ question.sessionId === refs.sessionRef.current?.sessionId) {
61
+ setPendingQuestion(eventName === "question_requested" && question.status === "pending"
62
+ ? question
63
+ : null);
64
+ }
65
+ return;
66
+ }
45
67
  const turnId = typeof data.turnId === "string" ? data.turnId : undefined;
46
68
  const eventSeq = typeof data.seq === "number" && Number.isFinite(data.seq)
47
69
  ? data.seq
@@ -51,13 +73,20 @@ export function createChatSseEventApplier(deps) {
51
73
  : Date.now();
52
74
  if (eventName === "queue_update") {
53
75
  const revision = typeof payload.revision === "number" ? payload.revision : -1;
54
- setDeferredTurn((current) => {
55
- if (current && revision < current.revision)
56
- return current;
57
- return payload.deferredTurn && typeof payload.deferredTurn === "object"
58
- ? payload.deferredTurn
59
- : null;
60
- });
76
+ const owner = refs.sessionRef.current?.sessionId;
77
+ if (owner !== queueOwner) {
78
+ queueOwner = owner;
79
+ queueRevision = -1;
80
+ }
81
+ if (revision < queueRevision)
82
+ return;
83
+ queueRevision = revision;
84
+ setDeferredTurn(payload.deferredTurn && typeof payload.deferredTurn === "object"
85
+ ? payload.deferredTurn
86
+ : null);
87
+ if (Array.isArray(payload.deferredTurns)) {
88
+ setDeferredTurns(payload.deferredTurns);
89
+ }
61
90
  return;
62
91
  }
63
92
  if (eventName === "user_message") {
@@ -84,6 +113,40 @@ export function createChatSseEventApplier(deps) {
84
113
  }
85
114
  return;
86
115
  }
116
+ if (eventName === "compact") {
117
+ const summary = typeof payload.summary === "string" ? payload.summary : "";
118
+ const tokensBefore = typeof payload.tokensBefore === "number" && Number.isFinite(payload.tokensBefore)
119
+ ? Math.max(0, Math.floor(payload.tokensBefore))
120
+ : undefined;
121
+ const firstKeptEntryId = typeof payload.firstKeptEntryId === "string" && payload.firstKeptEntryId
122
+ ? payload.firstKeptEntryId
123
+ : undefined;
124
+ const stableId = typeof data.eventId === "string" && data.eventId
125
+ ? `compaction-${data.eventId}`
126
+ : turnId && eventSeq !== undefined
127
+ ? `compaction-${turnId}-${eventSeq}`
128
+ : requestId("compaction");
129
+ setMessages((messages) => {
130
+ if (messages.some((message) => message.id === stableId))
131
+ return messages;
132
+ return [
133
+ ...messages,
134
+ {
135
+ id: stableId,
136
+ role: "assistant",
137
+ kind: "compaction",
138
+ text: summary || "历史上下文已精简,关键决策与当前任务已保留。",
139
+ createdAt: eventAt,
140
+ compaction: {
141
+ ...(tokensBefore === undefined ? {} : { tokensBefore }),
142
+ ...(firstKeptEntryId ? { firstKeptEntryId } : {}),
143
+ ...(typeof data.eventId === "string" ? { eventId: data.eventId } : {}),
144
+ },
145
+ },
146
+ ];
147
+ });
148
+ return;
149
+ }
87
150
  if (turnId &&
88
151
  (eventName === "agent_start" || eventName === "message_update") &&
89
152
  refs.settledTurnIdsRef.current.has(turnId)) {
@@ -236,10 +299,21 @@ export function createChatSseEventApplier(deps) {
236
299
  return true;
237
300
  return assistantOwnsTools;
238
301
  }));
302
+ if (!replay)
303
+ onTelemetryRefresh?.();
239
304
  return;
240
305
  }
241
306
  if (eventName === "operation-ref" || eventName === "operation-status") {
242
307
  const operation = operationFromEventPayload(payload);
308
+ // R9: tombstones are session-scoped. The same operation may be linked
309
+ // to another session, but delayed frames cannot resurrect it here.
310
+ const currentSessionId = refs.sessionRef.current?.sessionId;
311
+ if (operation &&
312
+ currentSessionId &&
313
+ refs.unlinkedOperationIdsRef.current
314
+ .get(currentSessionId)
315
+ ?.has(operation.operationId))
316
+ return;
243
317
  if (operation) {
244
318
  const toolCallId = typeof payload.toolCallId === "string"
245
319
  ? payload.toolCallId
@@ -256,6 +330,23 @@ export function createChatSseEventApplier(deps) {
256
330
  }
257
331
  return;
258
332
  }
333
+ if (eventName === "operation-unlinked") {
334
+ // R8 AC-001/AC-R8-001: durable tombstone — the server persisted an
335
+ // operation-unlinked event that OVERSHADOWS any earlier operation-ref.
336
+ // Drop the card from the projection; replay/recovery can never
337
+ // resurrect it because the tombstone is appended after the ref in
338
+ // the same ring.
339
+ const operationId = typeof payload.operationId === "string" ? payload.operationId : "";
340
+ const currentSessionId = refs.sessionRef.current?.sessionId;
341
+ if (operationId && currentSessionId) {
342
+ const sessionTombstones = refs.unlinkedOperationIdsRef.current.get(currentSessionId) ??
343
+ new Set();
344
+ sessionTombstones.add(operationId);
345
+ refs.unlinkedOperationIdsRef.current.set(currentSessionId, sessionTombstones);
346
+ setOperationCards((current) => current.filter((card) => card.operationId !== operationId));
347
+ }
348
+ return;
349
+ }
259
350
  if (eventName === "usage") {
260
351
  if (replay)
261
352
  return;
@@ -451,6 +542,47 @@ export function createChatSseEventApplier(deps) {
451
542
  });
452
543
  }
453
544
  else if (eventName === "error") {
545
+ const candidate = payload.providerError && typeof payload.providerError === "object"
546
+ ? payload.providerError
547
+ : undefined;
548
+ if (candidate &&
549
+ typeof candidate.message === "string" &&
550
+ typeof candidate.raw === "string" &&
551
+ (candidate.category === "authentication" ||
552
+ candidate.category === "context-overflow" ||
553
+ candidate.category === "rate-limit" ||
554
+ candidate.category === "provider")) {
555
+ // Session hydration already restores the durable card; retained replay
556
+ // must not add a second synthetic row on top of it.
557
+ if (replay)
558
+ return;
559
+ const providerError = candidate;
560
+ const errorContent = parseAssistantContent(payload.content);
561
+ const id = turnId && eventSeq !== undefined
562
+ ? `provider-error-${turnId}-${eventSeq}`
563
+ : requestId("provider-error");
564
+ setMessages((messages) => {
565
+ if (messages.some((message) => message.id === id))
566
+ return messages;
567
+ const activeAlreadyOwnsContent = messages.some((message) => message.id === resolvedAssistantId &&
568
+ Boolean(message.content?.length));
569
+ return [
570
+ ...messages,
571
+ {
572
+ id,
573
+ role: "assistant",
574
+ kind: "provider-error",
575
+ text: providerError.message,
576
+ createdAt: eventAt,
577
+ providerError,
578
+ ...(!activeAlreadyOwnsContent && errorContent
579
+ ? { content: errorContent }
580
+ : {}),
581
+ },
582
+ ];
583
+ });
584
+ return;
585
+ }
454
586
  setError(typeof payload.message === "string" ? payload.message : "chat error");
455
587
  }
456
588
  };
@@ -0,0 +1,122 @@
1
+ function resolveDagId(id) {
2
+ const value = typeof id === "function" ? id() : id;
3
+ return value?.trim() || null;
4
+ }
5
+ export function createDetailsDagActions(params) {
6
+ const fetchImpl = params.fetchFn ?? fetch;
7
+ let pending = false;
8
+ let error = null;
9
+ const completionListeners = new Set();
10
+ const emitState = () => params.onStateChange?.({ pending, error });
11
+ const emitCompletion = (outcome, identity, message) => {
12
+ for (const listener of [...completionListeners]) {
13
+ try {
14
+ listener(outcome, identity, message);
15
+ }
16
+ catch {
17
+ // a listener throwing must never break the request path
18
+ }
19
+ }
20
+ };
21
+ const run = (action, issue) => {
22
+ const sessionId = resolveDagId(params.sessionId);
23
+ const operationId = resolveDagId(params.operationId);
24
+ if (!sessionId || !operationId || pending)
25
+ return false;
26
+ const identity = {
27
+ action,
28
+ workspaceId: resolveDagId(params.workspaceId ?? null),
29
+ sessionId,
30
+ operationId,
31
+ };
32
+ pending = true;
33
+ error = null;
34
+ emitState();
35
+ void (async () => {
36
+ try {
37
+ const result = await issue(identity);
38
+ if (!result.ok) {
39
+ error = result.message ?? `DAG 关联操作失败`;
40
+ params.onFailure?.(identity, error);
41
+ emitCompletion("failure", identity, error);
42
+ return;
43
+ }
44
+ params.onSuccess?.(identity);
45
+ emitCompletion("success", identity);
46
+ }
47
+ catch (err) {
48
+ error = err instanceof Error ? err.message : String(err);
49
+ params.onFailure?.(identity, error);
50
+ emitCompletion("failure", identity, error);
51
+ }
52
+ finally {
53
+ pending = false;
54
+ emitState();
55
+ }
56
+ })();
57
+ return true;
58
+ };
59
+ return {
60
+ get pending() {
61
+ return pending;
62
+ },
63
+ get error() {
64
+ return error;
65
+ },
66
+ subscribe(listener) {
67
+ completionListeners.add(listener);
68
+ return () => completionListeners.delete(listener);
69
+ },
70
+ onAddDag() {
71
+ return run("add", async (identity) => {
72
+ // AC-002: session-scoped mutations carry the session's durable
73
+ // owning workspace so a global active-workspace switch can never
74
+ // retarget the link to another workspace's store.
75
+ const ownerQuery = identity.workspaceId
76
+ ? `?workspaceId=${encodeURIComponent(identity.workspaceId)}`
77
+ : "";
78
+ const res = await fetchImpl(`/api/operator/v1/chat/sessions/${encodeURIComponent(identity.sessionId)}/operation-links${ownerQuery}`, {
79
+ method: "POST",
80
+ credentials: "same-origin",
81
+ headers: {
82
+ "Content-Type": "application/json",
83
+ "x-loop-console-confirmation": params.confirmationToken(),
84
+ },
85
+ body: JSON.stringify({ operationId: identity.operationId }),
86
+ });
87
+ if (!res.ok) {
88
+ const body = (await res.json().catch(() => ({})));
89
+ return {
90
+ ok: false,
91
+ message: body.error?.message ??
92
+ `添加 DAG 关联失败(HTTP ${res.status})`,
93
+ };
94
+ }
95
+ return { ok: true };
96
+ });
97
+ },
98
+ onRemoveDag() {
99
+ return run("remove", async (identity) => {
100
+ const ownerQuery = identity.workspaceId
101
+ ? `?workspaceId=${encodeURIComponent(identity.workspaceId)}`
102
+ : "";
103
+ const res = await fetchImpl(`/api/operator/v1/chat/sessions/${encodeURIComponent(identity.sessionId)}/operation-links/${encodeURIComponent(identity.operationId)}${ownerQuery}`, {
104
+ method: "DELETE",
105
+ credentials: "same-origin",
106
+ headers: {
107
+ "x-loop-console-confirmation": params.confirmationToken(),
108
+ },
109
+ });
110
+ if (!res.ok && res.status !== 404) {
111
+ const body = (await res.json().catch(() => ({})));
112
+ return {
113
+ ok: false,
114
+ message: body.error?.message ??
115
+ `移除 DAG 关联失败(HTTP ${res.status})`,
116
+ };
117
+ }
118
+ return { ok: true };
119
+ });
120
+ },
121
+ };
122
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Pure composer input-history state machine for Operator Chat.
3
+ *
4
+ * Extracted so ArrowUp/ArrowUp recall, unsent-draft retention and history
5
+ * recording can be unit-tested without mounting the React composer.
6
+ */
7
+ export const INPUT_HISTORY_LIMIT = 100;
8
+ export function createInputHistoryState() {
9
+ return { entries: [], index: -1, draft: "" };
10
+ }
11
+ /** Record one sent text and reset browsing back to the present draft. */
12
+ export function recordInputHistoryEntry(state, text) {
13
+ if (!text.trim())
14
+ return state;
15
+ if (state.entries[state.entries.length - 1] === text) {
16
+ return state.index === -1 ? state : { ...state, index: -1, draft: "" };
17
+ }
18
+ const entries = [...state.entries, text].slice(-INPUT_HISTORY_LIMIT);
19
+ return { entries, index: -1, draft: "" };
20
+ }
21
+ /** Leave browse mode (manual typing edits the recalled text in place). */
22
+ export function resetInputHistoryBrowsing(state) {
23
+ return state.index === -1 ? state : { ...state, index: -1 };
24
+ }
25
+ /**
26
+ * Recall an earlier input with the arrow keys (terminal-style).
27
+ *
28
+ * Up only engages while the caret sits on the first line and Down while it
29
+ * sits on the last line, so plain caret movement inside multi-line drafts is
30
+ * never swallowed. The unsent draft is stashed when browsing leaves the
31
+ * present position and restored when Down walks past the newest entry.
32
+ */
33
+ export function navigateInputHistory(state, direction, context) {
34
+ const { text, caretStart, caretEnd } = context;
35
+ if (state.entries.length === 0)
36
+ return { handled: false };
37
+ if (direction === "up") {
38
+ if (text.slice(0, caretStart).includes("\n"))
39
+ return { handled: false };
40
+ if (state.index === -1) {
41
+ const index = state.entries.length - 1;
42
+ return {
43
+ handled: true,
44
+ state: { ...state, index, draft: text },
45
+ text: state.entries[index],
46
+ };
47
+ }
48
+ if (state.index === 0) {
49
+ // Already at the oldest entry: stay there (terminal behaviour).
50
+ return { handled: true, state, text: state.entries[0] };
51
+ }
52
+ const index = state.index - 1;
53
+ return {
54
+ handled: true,
55
+ state: { ...state, index },
56
+ text: state.entries[index],
57
+ };
58
+ }
59
+ if (state.index === -1)
60
+ return { handled: false };
61
+ if (text.slice(caretEnd).includes("\n"))
62
+ return { handled: false };
63
+ if (state.index >= state.entries.length - 1) {
64
+ return {
65
+ handled: true,
66
+ state: { ...state, index: -1 },
67
+ text: state.draft,
68
+ };
69
+ }
70
+ const index = state.index + 1;
71
+ return {
72
+ handled: true,
73
+ state: { ...state, index },
74
+ text: state.entries[index],
75
+ };
76
+ }
@@ -30,7 +30,7 @@ export function isStaleTokenFailure(code) {
30
30
  /** Chinese, actionable copy for mutation-gate rejections. */
31
31
  export function describeMutationGateFailure(code) {
32
32
  if (isStaleTokenFailure(code)) {
33
- return "页面已过期(控制台已重启),请刷新页面(F5)后重试。";
33
+ return "页面凭证已失效(控制台可能已重启),请刷新页面(F5)后重试。";
34
34
  }
35
35
  if (code === "host-not-loopback") {
36
36
  return "请求主机校验失败:非回环绑定时要求 Host 头,请刷新页面或检查访问地址。";
@@ -24,6 +24,7 @@ export function useChatRefs() {
24
24
  const abortPostedTurnIdsRef = useRef(new Set());
25
25
  const releasedClientRequestIdsRef = useRef(new Set());
26
26
  const usageResponseKeysRef = useRef(new Set());
27
+ const unlinkedOperationIdsRef = useRef(new Map());
27
28
  const isComposingRef = useRef(false);
28
29
  const compositionEndedAtRef = useRef(0);
29
30
  // Keep the bag identity stable: individual refs never change, so domain
@@ -53,6 +54,7 @@ export function useChatRefs() {
53
54
  abortPostedTurnIdsRef,
54
55
  releasedClientRequestIdsRef,
55
56
  usageResponseKeysRef,
57
+ unlinkedOperationIdsRef,
56
58
  isComposingRef,
57
59
  compositionEndedAtRef,
58
60
  }), [
@@ -80,6 +82,7 @@ export function useChatRefs() {
80
82
  abortPostedTurnIdsRef,
81
83
  releasedClientRequestIdsRef,
82
84
  usageResponseKeysRef,
85
+ unlinkedOperationIdsRef,
83
86
  isComposingRef,
84
87
  compositionEndedAtRef,
85
88
  ]);
@@ -0,0 +1,27 @@
1
+ import { formatModelRef } from "../../chat/runtime-selection.js";
2
+ const THINKING_SHORT_LABELS = {
3
+ auto: "Auto",
4
+ off: "Off",
5
+ minimal: "Minimal",
6
+ low: "Low",
7
+ medium: "Medium",
8
+ high: "High",
9
+ xhigh: "XHigh",
10
+ max: "Max",
11
+ };
12
+ export function formatThinkingSelectionLabel(selection, effective) {
13
+ const normalized = selection || "auto";
14
+ const label = THINKING_SHORT_LABELS[normalized] ?? normalized;
15
+ if (normalized !== "auto" || !effective)
16
+ return label;
17
+ return `${label} · ${THINKING_SHORT_LABELS[effective] ?? effective}`;
18
+ }
19
+ export function resolveModelDisplayName(models, activeModelRef) {
20
+ if (!activeModelRef)
21
+ return "选择模型";
22
+ const current = models.find((model) => formatModelRef(model.provider, model.id) === activeModelRef);
23
+ if (current)
24
+ return current.name?.trim() || current.id;
25
+ const slash = activeModelRef.indexOf("/");
26
+ return slash === -1 ? activeModelRef : activeModelRef.slice(slash + 1);
27
+ }
@@ -7,6 +7,10 @@
7
7
  * Choose the submission path from the mutable ownership facts, never from a
8
8
  * possibly stale React render. A stopping intent without a bound Turn must
9
9
  * remain held until reconciliation binds or releases that ownership.
10
+ *
11
+ * Since 2026-08-23 a running Turn also accepts a queued follow-up: Enter
12
+ * mid-turn binds the message to the active Turn (blockedByTurnId) and the
13
+ * server auto-dispatches it when the Turn settles or aborts.
10
14
  */
11
15
  export function decideSubmissionRoute(input) {
12
16
  if (input.turnPhase === "stopping") {
@@ -17,7 +21,15 @@ export function decideSubmissionRoute(input) {
17
21
  }
18
22
  : { action: "hold" };
19
23
  }
20
- return input.turnPhase === "idle" ? { action: "send-now" } : { action: "hold" };
24
+ if (input.turnPhase === "running") {
25
+ return input.blockedByTurnId
26
+ ? {
27
+ action: "queue-deferred",
28
+ blockedByTurnId: input.blockedByTurnId,
29
+ }
30
+ : { action: "hold" };
31
+ }
32
+ return { action: "send-now" };
21
33
  }
22
34
  /**
23
35
  * Reconcile a deferred PUT that raced with Stop completion. The same
@@ -28,8 +40,9 @@ export function reconcileDeferredSubmissionFromState(input) {
28
40
  const { clientRequestId, blockedByTurnId, state } = input;
29
41
  if (!state)
30
42
  return { action: "hold" };
31
- if (state.deferredTurn?.clientRequestId === clientRequestId) {
32
- return { action: "deferred", deferredTurn: state.deferredTurn };
43
+ const queued = state.deferredTurns?.find((item) => item.clientRequestId === clientRequestId) ?? (state.deferredTurn?.clientRequestId === clientRequestId ? state.deferredTurn : undefined);
44
+ if (queued) {
45
+ return { action: "deferred", deferredTurn: queued };
33
46
  }
34
47
  if (state.activeTurn?.clientRequestId === clientRequestId) {
35
48
  return { action: "accepted", turn: state.activeTurn };