@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +349 -97
  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 +2370 -64
  22. package/dist/executors/pi-executor.js +11 -2
  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 +225 -33
  27. package/dist/executors/pi-writer-tool-policy.js +57 -1
  28. package/dist/executors/shell-executor.js +1258 -224
  29. package/dist/executors/shell-write-guard.js +14 -0
  30. package/dist/executors/workspace-write-snapshot.js +68 -0
  31. package/dist/infrastructure/harness/atomic-write.js +23 -0
  32. package/dist/shared/dag-failure-category.js +150 -0
  33. package/dist/shared/dag-prompt-override.js +27 -0
  34. package/dist/shared/openspec-spec.js +70 -4
  35. package/dist/shared/operator/capabilities.js +39 -0
  36. package/dist/shared/playwright-cli-command-policy.js +15 -0
  37. package/dist/shared/update/console-notifier.js +100 -0
  38. package/dist/{cli → shared}/update/npm-client.js +70 -15
  39. package/dist/{cli → shared}/update/state.js +41 -13
  40. package/dist/task/config-types.js +50 -5
  41. package/dist/task/contract/apply.js +11 -1
  42. package/dist/task/contract/constants.js +2 -0
  43. package/dist/task/contract/hash.js +3 -0
  44. package/dist/task/contract/observe.js +25 -4
  45. package/dist/task/contract/paths.js +2 -1
  46. package/dist/task/contract/project.js +18 -1
  47. package/dist/task/contract/schema.js +4 -1
  48. package/dist/task/contract/transaction.js +12 -0
  49. package/dist/task/frontend-project-capability.js +199 -18
  50. package/dist/task/source-prepare/completeness.js +188 -0
  51. package/dist/task/source-prepare/fragment-inventory.js +477 -0
  52. package/dist/task/source-prepare/index.js +3 -0
  53. package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
  54. package/dist/task/source-prepare/ledger-review.js +214 -0
  55. package/dist/task/source-prepare/ledger.js +545 -0
  56. package/dist/task/source-prepare/parse-intent.js +24 -4
  57. package/dist/task/source-prepare/prepare.js +298 -1
  58. package/dist/task/source-prepare/semantic-intake.js +25 -5
  59. package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
  60. package/dist/task/source-prepare/types.js +22 -0
  61. package/dist/task/source-references.js +22 -1
  62. package/dist/worker/cli.js +18 -2
  63. package/dist/worker/console/chat/chat-event-store.js +75 -5
  64. package/dist/worker/console/chat/context-panel.js +7 -0
  65. package/dist/worker/console/chat/deferred-turn.js +12 -0
  66. package/dist/worker/console/chat/operation-card.js +8 -1
  67. package/dist/worker/console/chat/pi-console-config.js +81 -10
  68. package/dist/worker/console/chat/pi-runtime.js +331 -18
  69. package/dist/worker/console/chat/provider-error.js +98 -0
  70. package/dist/worker/console/chat/routes.js +395 -59
  71. package/dist/worker/console/chat/session-stats.js +179 -0
  72. package/dist/worker/console/chat/session-store.js +93 -63
  73. package/dist/worker/console/chat/todos.js +115 -0
  74. package/dist/worker/console/chat/tool-preview.js +42 -0
  75. package/dist/worker/console/chat/turn-process.js +20 -55
  76. package/dist/worker/console/chat/user-questions.js +266 -0
  77. package/dist/worker/console/chat/workspace-landing.js +79 -0
  78. package/dist/worker/console/console-handoff.js +82 -0
  79. package/dist/worker/console/console-update-and-init.js +115 -0
  80. package/dist/worker/console/console-update-runtime.js +84 -0
  81. package/dist/worker/console/dag-execution-receipt.js +33 -0
  82. package/dist/worker/console/draft-store.js +49 -0
  83. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  84. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  85. package/dist/worker/console/index.js +3 -0
  86. package/dist/worker/console/inspect-split.js +18 -0
  87. package/dist/worker/console/native-directory-picker.js +13 -0
  88. package/dist/worker/console/operation-run-facts.js +19 -3
  89. package/dist/worker/console/operation-store.js +227 -7
  90. package/dist/worker/console/operator-actions.js +388 -0
  91. package/dist/worker/console/operator-surface-health.js +1 -0
  92. package/dist/worker/console/operator-user-error.js +2 -2
  93. package/dist/worker/console/prd-intake-bridge.js +13 -0
  94. package/dist/worker/console/routes.js +355 -3
  95. package/dist/worker/console/security.js +67 -13
  96. package/dist/worker/console/server.js +609 -164
  97. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-CXj_GnSb.js} +1 -1
  98. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BZp6JAp7.js} +1 -1
  99. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
  100. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
  101. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
  102. package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
  103. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
  104. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
  105. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
  106. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
  107. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
  108. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
  109. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
  110. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
  111. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
  112. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
  113. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
  114. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
  115. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
  116. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
  117. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
  118. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
  119. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
  120. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
  121. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
  122. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
  123. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
  124. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
  125. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
  126. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
  127. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
  128. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
  129. package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
  130. package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
  131. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
  132. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
  133. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
  134. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
  135. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-BsjbDoXi.js} +1 -1
  136. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-0B7NnWKk.js} +5 -5
  137. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
  138. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
  139. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
  140. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
  141. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
  142. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
  143. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
  144. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
  145. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
  146. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
  147. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
  148. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
  149. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
  150. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
  151. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
  152. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
  153. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-B32Ark0D.js} +1 -1
  154. package/dist/worker/console/static/index.html +2 -2
  155. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  156. package/dist/worker/console/static-src/app/console-types.js +68 -4
  157. package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
  158. package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
  159. package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
  160. package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
  161. package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
  162. package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
  163. package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
  164. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
  165. package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
  166. package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
  167. package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
  168. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  169. package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
  170. package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
  171. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  172. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
  173. package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
  174. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
  175. package/dist/worker/console/static-src/operator-chat/useChatStream.js +183 -48
  176. package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
  177. package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
  178. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
  179. package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
  180. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
  181. package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
  182. package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
  183. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
  184. package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
  185. package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
  186. package/dist/worker/console/workspace-context.js +344 -0
  187. package/dist/worker/console/workspace-registry.js +214 -0
  188. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  189. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  190. package/dist/worker/materialize/harness-task-lineage.js +5 -2
  191. package/dist/worker/observability/init-runtime-activity.js +26 -0
  192. package/dist/worker/observability/read-model.js +22 -0
  193. package/dist/worker/observe/node-input.js +160 -3
  194. package/dist/worker/observe/node-process.js +377 -0
  195. package/dist/worker/observe/routes.js +224 -3
  196. package/dist/worker/observe/server.js +4 -0
  197. package/dist/worker/observe/shell-handler-keys.js +34 -0
  198. package/dist/worker/observe/static/api.js +90 -4
  199. package/dist/worker/observe/static/app.js +18 -70
  200. package/dist/worker/observe/static/constants.js +8 -1
  201. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  202. package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
  203. package/dist/worker/observe/static/dag-node-purpose.js +208 -0
  204. package/dist/worker/observe/static/format.js +60 -0
  205. package/dist/worker/observe/static/index.html +6 -1
  206. package/dist/worker/observe/static/inspect-workspace.js +307 -0
  207. package/dist/worker/observe/static/markdown-render.js +20 -1
  208. package/dist/worker/observe/static/operator-chrome.css +145 -20
  209. package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
  210. package/dist/worker/observe/static/operator-chrome.js +330 -96
  211. package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
  212. package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
  213. package/dist/worker/observe/static/router.d.ts +46 -0
  214. package/dist/worker/observe/static/router.js +25 -1
  215. package/dist/worker/observe/static/state.d.ts +50 -0
  216. package/dist/worker/observe/static/state.js +184 -1
  217. package/dist/worker/observe/static/styles.css +395 -0
  218. package/dist/worker/observe/static/views/dag-graph.js +4 -2
  219. package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
  220. package/dist/worker/observe/static/views/dag.d.ts +13 -0
  221. package/dist/worker/observe/static/views/dag.js +14 -5
  222. package/dist/worker/observe/static/views/dashboard.js +6 -4
  223. package/dist/worker/observe/static/views/failures.js +5 -2
  224. package/dist/worker/observe/static/views/night.js +2 -2
  225. package/dist/worker/observe/static/views/pool.js +1 -0
  226. package/dist/worker/observe/static/views/run.js +3 -1
  227. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  228. package/dist/worker/observe/static/views/task.js +56 -1
  229. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
  230. package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
  231. package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
  232. package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
  233. package/dist/workflows/dag/contract-output-registry.js +15 -0
  234. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  235. package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
  236. package/dist/workflows/dag/dynamic-runtime/map.js +2 -1
  237. package/dist/workflows/dag/failure-category.js +7 -116
  238. package/dist/workflows/dag/frontend-closeout.js +221 -0
  239. package/dist/workflows/dag/frontend-design-policy.js +400 -0
  240. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  241. package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
  242. package/dist/workflows/dag/frontend-plan-render.js +24 -0
  243. package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
  244. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  245. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  246. package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
  247. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  248. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  249. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  250. package/dist/workflows/dag/frontend-repair.js +7 -424
  251. package/dist/workflows/dag/frontend-review-context.js +288 -14
  252. package/dist/workflows/dag/frontend-review-findings.js +270 -0
  253. package/dist/workflows/dag/frontend-risk.js +15 -2
  254. package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
  255. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  256. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  257. package/dist/workflows/dag/frontend-shape.js +427 -0
  258. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
  259. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  260. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  261. package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
  262. package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
  263. package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
  264. package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
  265. package/dist/workflows/dag/frontend-test-html-report.js +3 -1
  266. package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
  267. package/dist/workflows/dag/frontend-test-layout.js +159 -0
  268. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  269. package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
  270. package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
  271. package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
  272. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  273. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  274. package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
  275. package/dist/workflows/dag/frontend-writer-admission.js +272 -0
  276. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  277. package/dist/workflows/dag/init-hybrid.js +1072 -660
  278. package/dist/workflows/dag/interrupt-request.js +10 -3
  279. package/dist/workflows/dag/lifecycle.js +3 -3
  280. package/dist/workflows/dag/node-execution.js +552 -58
  281. package/dist/workflows/dag/prompt.js +44 -19
  282. package/dist/workflows/dag/recovery-lease.js +80 -0
  283. package/dist/workflows/dag/report.js +37 -1
  284. package/dist/workflows/dag/rerun-feedback.js +124 -1
  285. package/dist/workflows/dag/rerun-plan.js +144 -5
  286. package/dist/workflows/dag/rerun-run.js +81 -6
  287. package/dist/workflows/dag/rerun-task.js +29 -0
  288. package/dist/workflows/dag/retry-policy.js +318 -8
  289. package/dist/workflows/dag/runner.js +429 -122
  290. package/dist/workflows/dag/scheduler.js +114 -16
  291. package/dist/workflows/dag/structured-output-repair.js +712 -0
  292. package/dist/workflows/dag/types.js +338 -31
  293. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  294. package/dist/workflows/dag/validate.js +92 -32
  295. package/docs/README.md +2 -0
  296. package/docs/architecture/evolution.md +45 -0
  297. package/docs/init-surface.manifest.json +9 -0
  298. package/docs/operations/README.md +1 -1
  299. package/docs/operations/local-development-environment.md +21 -0
  300. package/docs/templates/README.md +2 -1
  301. package/docs/templates/agent-dag-report.schema.json +8 -2
  302. package/docs/templates/agent-dag.schema.json +63 -1
  303. package/docs/templates/backend-test-dag.json +265 -237
  304. package/docs/templates/frontend-implementation-contract.schema.json +684 -24
  305. package/docs/templates/frontend-test-case-checklist.md +2 -2
  306. package/docs/templates/frontend-test-dag.json +8 -10
  307. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  308. package/docs/templates/init-managed-agents.md +13 -3
  309. package/docs/templates/spec-registry.schema.json +45 -0
  310. package/harness.json +1 -1
  311. package/package.json +7 -1
  312. package/scripts/next-info.mjs +356 -0
  313. package/scripts/next-publish-gate.mjs +238 -0
  314. package/scripts/release-source-binding.mjs +251 -0
  315. package/skills/codebase-scout/SKILL.md +1 -1
  316. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  317. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  318. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  319. package/skills/frontend-bounded-implement/SKILL.md +8 -14
  320. package/skills/frontend-design-review/SKILL.md +20 -41
  321. package/skills/frontend-implementation/SKILL.md +3 -4
  322. package/skills/frontend-implementation/references/design-spec.md +15 -10
  323. package/skills/frontend-implementation/references/node-contracts.md +23 -13
  324. package/skills/frontend-review/SKILL.md +22 -14
  325. package/skills/frontend-review/references/review-findings.md +6 -7
  326. package/skills/loop-agent/references/command-reference.md +12 -4
  327. package/skills/loop-agent/references/hybrid-dag.md +8 -1
  328. package/skills/playwright-cli/SKILL.md +23 -1
  329. package/skills/playwright-cli-case-generator/SKILL.md +29 -8
  330. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  331. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  332. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  333. package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
  334. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  335. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  336. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
  337. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  338. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
@@ -3,6 +3,7 @@ import { humanGateCardFromEventPayload, mergeHumanGateCardState, } from "../../c
3
3
  import { mergeOperationCardState, operationFromEventPayload, } from "../../chat/operation-card.js";
4
4
  import { requestId } from "./format.js";
5
5
  import { parseAssistantContent } from "../../chat/assistant-content.js";
6
+ import { upsertCompactionMessage } from "./compaction-message.js";
6
7
  /** Shared handler for a single parsed Chat SSE event (M1 reconnect).
7
8
  *
8
9
  * Used by BOTH the inline prompt POST SSE and the background reconnect
@@ -10,7 +11,9 @@ import { parseAssistantContent } from "../../chat/assistant-content.js";
10
11
  * original request would have. `assistantId` is the in-flight assistant
11
12
  * message id to patch for streaming text; reconnect creates one if none. */
12
13
  export function createChatSseEventApplier(deps) {
13
- const { refs, onReconcile, setError, setInterview, setHumanGateCards, setArtifactCards, setStreaming, setTurnPhase = () => undefined, setDeferredTurn = () => undefined, setStreamingAssistantId, setUnread, setOperationCards, setUsage, setMessages, setToolCalls, } = deps;
14
+ 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;
15
+ let queueOwner;
16
+ let queueRevision = -1;
14
17
  return (eventName, data, assistantId, options) => {
15
18
  if (options?.generation !== undefined &&
16
19
  refs.sessionGenerationRef.current !== options.generation)
@@ -42,6 +45,26 @@ export function createChatSseEventApplier(deps) {
42
45
  const payload = data.data && typeof data.data === "object"
43
46
  ? data.data
44
47
  : data;
48
+ if (eventName === "todo_updated" || eventName === "todo_cleared") {
49
+ const todos = Array.isArray(payload.todos) ? payload.todos : [];
50
+ setTodos(todos.filter((item) => Boolean(item) && typeof item === "object" && typeof item.content === "string" && (item.status === "pending" || item.status === "in_progress" || item.status === "completed")));
51
+ return;
52
+ }
53
+ if ((eventName === "question_requested" ||
54
+ eventName === "question_resolved" ||
55
+ eventName === "question_cancelled") &&
56
+ payload.question &&
57
+ typeof payload.question === "object") {
58
+ const question = payload.question;
59
+ if (typeof question.requestId === "string" &&
60
+ typeof question.sessionId === "string" &&
61
+ question.sessionId === refs.sessionRef.current?.sessionId) {
62
+ setPendingQuestion(eventName === "question_requested" && question.status === "pending"
63
+ ? question
64
+ : null);
65
+ }
66
+ return;
67
+ }
45
68
  const turnId = typeof data.turnId === "string" ? data.turnId : undefined;
46
69
  const eventSeq = typeof data.seq === "number" && Number.isFinite(data.seq)
47
70
  ? data.seq
@@ -51,13 +74,20 @@ export function createChatSseEventApplier(deps) {
51
74
  : Date.now();
52
75
  if (eventName === "queue_update") {
53
76
  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
- });
77
+ const owner = refs.sessionRef.current?.sessionId;
78
+ if (owner !== queueOwner) {
79
+ queueOwner = owner;
80
+ queueRevision = -1;
81
+ }
82
+ if (revision < queueRevision)
83
+ return;
84
+ queueRevision = revision;
85
+ setDeferredTurn(payload.deferredTurn && typeof payload.deferredTurn === "object"
86
+ ? payload.deferredTurn
87
+ : null);
88
+ if (Array.isArray(payload.deferredTurns)) {
89
+ setDeferredTurns(payload.deferredTurns);
90
+ }
61
91
  return;
62
92
  }
63
93
  if (eventName === "user_message") {
@@ -84,6 +114,17 @@ export function createChatSseEventApplier(deps) {
84
114
  }
85
115
  return;
86
116
  }
117
+ if (eventName === "compact" ||
118
+ eventName === "compaction_start" ||
119
+ eventName === "compaction_end") {
120
+ setMessages((messages) => upsertCompactionMessage(messages, {
121
+ eventId: typeof data.eventId === "string" ? data.eventId : undefined,
122
+ kind: eventName,
123
+ at: new Date(eventAt).toISOString(),
124
+ data: payload,
125
+ }));
126
+ return;
127
+ }
87
128
  if (turnId &&
88
129
  (eventName === "agent_start" || eventName === "message_update") &&
89
130
  refs.settledTurnIdsRef.current.has(turnId)) {
@@ -186,6 +227,16 @@ export function createChatSseEventApplier(deps) {
186
227
  refs.settledTurnIdsRef.current.delete(turnId);
187
228
  refs.activeTurnIdRef.current = turnId;
188
229
  }
230
+ if (refs.stopRequestedRef.current &&
231
+ (!turnId ||
232
+ turnId === refs.stoppingTurnIdRef.current ||
233
+ turnId === refs.activeTurnIdRef.current)) {
234
+ refs.streamingRef.current = false;
235
+ setStreaming(false);
236
+ setTurnPhase("idle");
237
+ setStreamingAssistantId(null);
238
+ return;
239
+ }
189
240
  refs.streamingRef.current = true;
190
241
  setStreaming(true);
191
242
  setTurnPhase("running");
@@ -200,9 +251,22 @@ export function createChatSseEventApplier(deps) {
200
251
  // (M4.4). It must not prematurely close the visible turn.
201
252
  if (eventName === "agent_end")
202
253
  return;
254
+ const pending = refs.pendingSubmissionRef.current;
255
+ if (pending && turnId && pending.acceptedTurnId === turnId) {
256
+ refs.releasedClientRequestIdsRef.current.add(pending.clientRequestId);
257
+ refs.pendingSubmissionRef.current = null;
258
+ refs.submitInFlightRef.current = false;
259
+ }
260
+ if (!turnId ||
261
+ turnId === refs.stoppingTurnIdRef.current ||
262
+ turnId === refs.activeTurnIdRef.current) {
263
+ refs.stoppingTurnIdRef.current = null;
264
+ refs.stopRequestedRef.current = false;
265
+ }
203
266
  if (turnId === refs.activeTurnIdRef.current)
204
267
  refs.activeTurnIdRef.current = null;
205
268
  refs.streamingRef.current = false;
269
+ refs.assistantIdRef.current = null;
206
270
  setStreaming(false);
207
271
  setTurnPhase("idle");
208
272
  setStreamingAssistantId(null);
@@ -236,10 +300,21 @@ export function createChatSseEventApplier(deps) {
236
300
  return true;
237
301
  return assistantOwnsTools;
238
302
  }));
303
+ if (!replay)
304
+ onTelemetryRefresh?.();
239
305
  return;
240
306
  }
241
307
  if (eventName === "operation-ref" || eventName === "operation-status") {
242
308
  const operation = operationFromEventPayload(payload);
309
+ // R9: tombstones are session-scoped. The same operation may be linked
310
+ // to another session, but delayed frames cannot resurrect it here.
311
+ const currentSessionId = refs.sessionRef.current?.sessionId;
312
+ if (operation &&
313
+ currentSessionId &&
314
+ refs.unlinkedOperationIdsRef.current
315
+ .get(currentSessionId)
316
+ ?.has(operation.operationId))
317
+ return;
243
318
  if (operation) {
244
319
  const toolCallId = typeof payload.toolCallId === "string"
245
320
  ? payload.toolCallId
@@ -256,6 +331,23 @@ export function createChatSseEventApplier(deps) {
256
331
  }
257
332
  return;
258
333
  }
334
+ if (eventName === "operation-unlinked") {
335
+ // R8 AC-001/AC-R8-001: durable tombstone — the server persisted an
336
+ // operation-unlinked event that OVERSHADOWS any earlier operation-ref.
337
+ // Drop the card from the projection; replay/recovery can never
338
+ // resurrect it because the tombstone is appended after the ref in
339
+ // the same ring.
340
+ const operationId = typeof payload.operationId === "string" ? payload.operationId : "";
341
+ const currentSessionId = refs.sessionRef.current?.sessionId;
342
+ if (operationId && currentSessionId) {
343
+ const sessionTombstones = refs.unlinkedOperationIdsRef.current.get(currentSessionId) ??
344
+ new Set();
345
+ sessionTombstones.add(operationId);
346
+ refs.unlinkedOperationIdsRef.current.set(currentSessionId, sessionTombstones);
347
+ setOperationCards((current) => current.filter((card) => card.operationId !== operationId));
348
+ }
349
+ return;
350
+ }
259
351
  if (eventName === "usage") {
260
352
  if (replay)
261
353
  return;
@@ -451,6 +543,47 @@ export function createChatSseEventApplier(deps) {
451
543
  });
452
544
  }
453
545
  else if (eventName === "error") {
546
+ const candidate = payload.providerError && typeof payload.providerError === "object"
547
+ ? payload.providerError
548
+ : undefined;
549
+ if (candidate &&
550
+ typeof candidate.message === "string" &&
551
+ typeof candidate.raw === "string" &&
552
+ (candidate.category === "authentication" ||
553
+ candidate.category === "context-overflow" ||
554
+ candidate.category === "rate-limit" ||
555
+ candidate.category === "provider")) {
556
+ // Session hydration already restores the durable card; retained replay
557
+ // must not add a second synthetic row on top of it.
558
+ if (replay)
559
+ return;
560
+ const providerError = candidate;
561
+ const errorContent = parseAssistantContent(payload.content);
562
+ const id = turnId && eventSeq !== undefined
563
+ ? `provider-error-${turnId}-${eventSeq}`
564
+ : requestId("provider-error");
565
+ setMessages((messages) => {
566
+ if (messages.some((message) => message.id === id))
567
+ return messages;
568
+ const activeAlreadyOwnsContent = messages.some((message) => message.id === resolvedAssistantId &&
569
+ Boolean(message.content?.length));
570
+ return [
571
+ ...messages,
572
+ {
573
+ id,
574
+ role: "assistant",
575
+ kind: "provider-error",
576
+ text: providerError.message,
577
+ createdAt: eventAt,
578
+ providerError,
579
+ ...(!activeAlreadyOwnsContent && errorContent
580
+ ? { content: errorContent }
581
+ : {}),
582
+ },
583
+ ];
584
+ });
585
+ return;
586
+ }
454
587
  setError(typeof payload.message === "string" ? payload.message : "chat error");
455
588
  }
456
589
  };
@@ -0,0 +1,79 @@
1
+ function finiteToken(value) {
2
+ return typeof value === "number" && Number.isFinite(value)
3
+ ? Math.max(0, Math.floor(value))
4
+ : undefined;
5
+ }
6
+ /** Project legacy final snapshots and lifecycle events to one stable row. */
7
+ export function compactionMessageFromEvent(item) {
8
+ const data = item.data && typeof item.data === "object"
9
+ ? item.data
10
+ : {};
11
+ const eventId = typeof item.eventId === "string" ? item.eventId : undefined;
12
+ const compactionId = typeof data.compactionId === "string" && data.compactionId
13
+ ? data.compactionId
14
+ : eventId;
15
+ if (!compactionId)
16
+ return undefined;
17
+ const status = data.status === "running" ||
18
+ data.status === "completed" ||
19
+ data.status === "failed" ||
20
+ data.status === "aborted"
21
+ ? data.status
22
+ : "completed";
23
+ const reason = data.reason === "manual" ||
24
+ data.reason === "threshold" ||
25
+ data.reason === "overflow"
26
+ ? data.reason
27
+ : "manual";
28
+ const summary = typeof data.summary === "string" ? data.summary : "";
29
+ const errorMessage = typeof data.errorMessage === "string" ? data.errorMessage : undefined;
30
+ return {
31
+ id: `compaction-${compactionId}`,
32
+ role: "assistant",
33
+ kind: "compaction",
34
+ text: summary ||
35
+ errorMessage ||
36
+ (status === "completed"
37
+ ? "历史上下文已精简,关键决策与当前任务已保留。"
38
+ : ""),
39
+ createdAt: Date.parse(item.at ?? "") || Date.now(),
40
+ compaction: {
41
+ compactionId,
42
+ status,
43
+ reason,
44
+ willRetry: data.willRetry === true,
45
+ ...(finiteToken(data.tokensBefore) === undefined
46
+ ? {}
47
+ : { tokensBefore: finiteToken(data.tokensBefore) }),
48
+ ...(finiteToken(data.estimatedTokensAfter) === undefined
49
+ ? {}
50
+ : { estimatedTokensAfter: finiteToken(data.estimatedTokensAfter) }),
51
+ ...(typeof data.firstKeptEntryId === "string" && data.firstKeptEntryId
52
+ ? { firstKeptEntryId: data.firstKeptEntryId }
53
+ : {}),
54
+ ...(errorMessage ? { errorMessage } : {}),
55
+ ...(eventId ? { eventId } : {}),
56
+ },
57
+ };
58
+ }
59
+ export function upsertCompactionMessage(messages, item) {
60
+ const next = compactionMessageFromEvent(item);
61
+ if (!next)
62
+ return messages;
63
+ const existing = messages.find((message) => message.id === next.id);
64
+ const result = existing
65
+ ? messages.map((message) => message.id === next.id
66
+ ? {
67
+ ...message,
68
+ ...next,
69
+ createdAt: Math.min(message.createdAt, next.createdAt),
70
+ text: next.text || message.text,
71
+ compaction: {
72
+ ...message.compaction,
73
+ ...next.compaction,
74
+ },
75
+ }
76
+ : message)
77
+ : [...messages, next];
78
+ return result.sort((a, b) => a.createdAt - b.createdAt);
79
+ }
@@ -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
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Mutual-exclusion state for large spatial overlays in Operator Chat.
3
- * At most one of: sidebar drawer, process, runtime context, tools, activity.
3
+ * At most one of: sidebar drawer, process, runtime context, tools.
4
4
  * Small composer menus (model/thinking) are out of scope.
5
5
  */
6
6
  export function createSpatialOverlayState(active = "none") {
@@ -33,6 +33,5 @@ export function spatialOverlayFlags(state) {
33
33
  processOpen: state.active === "process",
34
34
  runtimeContextOpen: state.active === "runtime-context",
35
35
  toolsOpen: state.active === "tools",
36
- activityOpen: state.active === "activity",
37
36
  };
38
37
  }
@@ -38,6 +38,15 @@ export function turnPhaseFromOwnership(bag) {
38
38
  return "running";
39
39
  return "idle";
40
40
  }
41
+ /**
42
+ * DSH-style visual projection: Stop immediately releases the composer while
43
+ * the ownership projection remains `stopping` for deferred-send routing and
44
+ * duplicate-Turn prevention.
45
+ */
46
+ export function visualTurnPhaseFromOwnership(bag) {
47
+ const phase = turnPhaseFromOwnership(bag);
48
+ return phase === "stopping" ? "idle" : phase;
49
+ }
41
50
  export function createTurnOwnershipBag(seed) {
42
51
  return {
43
52
  submitInFlight: false,
@@ -394,6 +403,9 @@ export function classifyAbortResponse(input) {
394
403
  isTerminalTurnState(terminalState)) {
395
404
  return { action: "terminal-idle", turnId: input.turnId };
396
405
  }
406
+ if (input.httpOk && input.body.accepted === true) {
407
+ return { action: "abort-accepted-background" };
408
+ }
397
409
  if (input.body.error?.code === "TURN_ABORT_PENDING") {
398
410
  return { action: "abort-pending-notice" };
399
411
  }
@@ -416,6 +428,10 @@ export function applyAbortDecision(bag, decision, notice, opts) {
416
428
  if (!current)
417
429
  return;
418
430
  bag.settledTurnIds.add(decision.turnId);
431
+ if (bag.pendingSubmission) {
432
+ releaseSubmitOwnership(bag, bag.pendingSubmission.clientRequestId);
433
+ }
434
+ clearStopRequested(bag);
419
435
  bag.streaming = false;
420
436
  bag.activeTurnId = null;
421
437
  bag.assistantId = null;
@@ -423,6 +439,11 @@ export function applyAbortDecision(bag, decision, notice, opts) {
423
439
  opts?.markIdle?.();
424
440
  return;
425
441
  }
442
+ if (decision.action === "abort-accepted-background") {
443
+ // Visual idle is projected by the caller. Hidden ownership deliberately
444
+ // remains stopping until terminal SSE or /state evidence releases it.
445
+ return;
446
+ }
426
447
  if (decision.action === "abort-pending-notice") {
427
448
  // Non-fatal; keep busy.
428
449
  notice.showNotice(ABORT_PENDING_NOTICE);
@@ -436,6 +457,10 @@ export function applyAbortDecision(bag, decision, notice, opts) {
436
457
  bag.settledTurnIds.add(bag.stoppingTurnId);
437
458
  }
438
459
  bag.streaming = false;
460
+ if (bag.pendingSubmission) {
461
+ releaseSubmitOwnership(bag, bag.pendingSubmission.clientRequestId);
462
+ }
463
+ clearStopRequested(bag);
439
464
  bag.activeTurnId = null;
440
465
  bag.assistantId = null;
441
466
  bag.stoppingTurnId = null;