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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/CHANGELOG.md +111 -45
  2. package/dist/application/dag/run-dag.js +8 -2
  3. package/dist/application/evaluation/budget.js +19 -1
  4. package/dist/application/task-lifecycle/advance.js +26 -8
  5. package/dist/application/task-lifecycle/observe.js +43 -29
  6. package/dist/application/task-lifecycle/plan-transitions.js +5 -4
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/program.js +1 -1
  9. package/dist/commands/dag-rerun-task.js +2 -0
  10. package/dist/commands/task-source-prepare.js +3 -1
  11. package/dist/executors/dag-pi-executor.js +2584 -600
  12. package/dist/executors/pi-executor.js +22 -1
  13. package/dist/executors/pi-extension-resolver.js +14 -2
  14. package/dist/executors/pi-sdk-executor.js +140 -39
  15. package/dist/executors/shell-executor.js +135 -55
  16. package/dist/shared/dag-failure-category.js +6 -0
  17. package/dist/shared/frontend-execution-policy.js +23 -0
  18. package/dist/task/config-types.js +4 -0
  19. package/dist/task/contract/apply.js +36 -2
  20. package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
  21. package/dist/task/source-prepare/ledger-review.js +6 -9
  22. package/dist/task/source-prepare/parse-intent.js +7 -0
  23. package/dist/task/source-prepare/semantic-intake.js +16 -26
  24. package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
  25. package/dist/task/source-references.js +48 -23
  26. package/dist/worker/console/chat/assistant-content.js +23 -2
  27. package/dist/worker/console/chat/browser-policy.js +143 -0
  28. package/dist/worker/console/chat/browser-routes.js +148 -0
  29. package/dist/worker/console/chat/chat-event-store.js +4 -2
  30. package/dist/worker/console/chat/explore-tools.js +13 -0
  31. package/dist/worker/console/chat/pi-runtime.js +173 -94
  32. package/dist/worker/console/chat/resource-loader.js +4 -1
  33. package/dist/worker/console/chat/routes.js +208 -66
  34. package/dist/worker/console/chat/scm-routes.js +217 -0
  35. package/dist/worker/console/chat/scm-service.js +283 -0
  36. package/dist/worker/console/chat/scm-tools.js +111 -0
  37. package/dist/worker/console/chat/sdd-data-alignment.js +78 -11
  38. package/dist/worker/console/chat/session-catalog.js +32 -0
  39. package/dist/worker/console/chat/session-mode-view.js +48 -0
  40. package/dist/worker/console/chat/session-mode.js +218 -0
  41. package/dist/worker/console/chat/session-store.js +27 -7
  42. package/dist/worker/console/chat/shortcuts.js +6 -0
  43. package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
  44. package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
  45. package/dist/worker/console/chat/subagents/index.js +5 -0
  46. package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
  47. package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
  48. package/dist/worker/console/chat/subagents/types.js +13 -0
  49. package/dist/worker/console/chat/terminal-routes.js +216 -0
  50. package/dist/worker/console/chat/terminal-sessions.js +284 -0
  51. package/dist/worker/console/chat/terminal-tools.js +199 -0
  52. package/dist/worker/console/chat/tool-preview.js +21 -0
  53. package/dist/worker/console/chat/tools.js +13 -1
  54. package/dist/worker/console/chat/turn-process.js +1 -0
  55. package/dist/worker/console/interview/tools.js +1 -0
  56. package/dist/worker/console/server.js +2 -28
  57. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
  58. package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
  59. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
  60. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
  61. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
  62. package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
  63. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
  68. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
  69. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
  70. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
  71. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
  72. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
  73. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
  74. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
  75. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
  76. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
  77. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
  78. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
  79. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
  80. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
  81. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
  82. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
  83. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
  84. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
  85. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
  86. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
  87. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
  88. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
  89. package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
  90. package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
  91. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
  92. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
  93. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
  94. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
  95. package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
  96. package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
  97. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
  98. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
  99. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
  100. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
  101. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
  102. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
  103. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
  104. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
  105. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
  106. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
  107. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
  108. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
  109. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
  110. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
  111. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
  112. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
  113. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
  114. package/dist/worker/console/static/index.html +2 -2
  115. package/dist/worker/console/static-src/chat-view-types.js +1 -1
  116. package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
  117. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
  118. package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
  119. package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
  120. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
  121. package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
  122. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
  123. package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
  124. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
  125. package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
  126. package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
  127. package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
  128. package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
  129. package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
  130. package/dist/worker/console/workspace-context.js +15 -1
  131. package/dist/worker/observe/node-transparency.js +81 -72
  132. package/dist/worker/observe/routes.js +20 -1
  133. package/dist/worker/observe/static/constants.js +22 -22
  134. package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
  135. package/dist/worker/observe/static/dag-history-labels.js +1 -0
  136. package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
  137. package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
  138. package/dist/worker/observe/static/dag-node-purpose.js +10 -10
  139. package/dist/worker/observe/static/dom.js +20 -1
  140. package/dist/worker/observe/static/format-pool.d.ts +2 -0
  141. package/dist/worker/observe/static/format-pool.js +6 -0
  142. package/dist/worker/observe/static/format.js +7 -0
  143. package/dist/worker/observe/static/index.html +4 -4
  144. package/dist/worker/observe/static/inspect-workspace.js +34 -7
  145. package/dist/worker/observe/static/inspector-submission.js +32 -0
  146. package/dist/worker/observe/static/kpi.js +1 -0
  147. package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
  148. package/dist/worker/observe/static/relations.js +7 -5
  149. package/dist/worker/observe/static/router.js +13 -0
  150. package/dist/worker/observe/static/run-processing.js +2 -0
  151. package/dist/worker/observe/static/shell-chrome.js +36 -3
  152. package/dist/worker/observe/static/state.js +35 -2
  153. package/dist/worker/observe/static/styles.css +431 -39
  154. package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
  155. package/dist/worker/observe/static/task-failure-labels.js +67 -0
  156. package/dist/worker/observe/static/task-history.js +12 -0
  157. package/dist/worker/observe/static/views/batch.js +6 -13
  158. package/dist/worker/observe/static/views/dag-graph.js +51 -4
  159. package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
  160. package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
  161. package/dist/worker/observe/static/views/dag.d.ts +6 -0
  162. package/dist/worker/observe/static/views/dag.js +64 -22
  163. package/dist/worker/observe/static/views/dags.js +2 -0
  164. package/dist/worker/observe/static/views/dashboard.js +21 -12
  165. package/dist/worker/observe/static/views/failures.js +21 -11
  166. package/dist/worker/observe/static/views/feature.js +11 -29
  167. package/dist/worker/observe/static/views/pool.js +37 -28
  168. package/dist/worker/observe/static/views/run.js +48 -5
  169. package/dist/worker/observe/static/views/session-timeline.js +194 -243
  170. package/dist/worker/observe/static/views/task.js +81 -62
  171. package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
  172. package/dist/workflows/dag/budget-enforcement.js +53 -3
  173. package/dist/workflows/dag/dag-retry-schema.js +3 -0
  174. package/dist/workflows/dag/frontend-capacity.js +9 -0
  175. package/dist/workflows/dag/frontend-contract-facts.js +130 -0
  176. package/dist/workflows/dag/frontend-design-policy.js +103 -17
  177. package/dist/workflows/dag/frontend-durable-tools.js +193 -0
  178. package/dist/workflows/dag/frontend-execution-groups.js +24 -0
  179. package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
  180. package/dist/workflows/dag/frontend-input-projection.js +76 -0
  181. package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
  182. package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
  183. package/dist/workflows/dag/frontend-plan-render.js +21 -3
  184. package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
  185. package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
  186. package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
  187. package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
  188. package/dist/workflows/dag/frontend-recovery-run.js +146 -16
  189. package/dist/workflows/dag/frontend-review-scopes.js +139 -0
  190. package/dist/workflows/dag/frontend-risk.js +2 -0
  191. package/dist/workflows/dag/frontend-session-budget.js +249 -0
  192. package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
  193. package/dist/workflows/dag/frontend-shape-facts.js +12 -2
  194. package/dist/workflows/dag/frontend-shape.js +46 -7
  195. package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
  196. package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
  197. package/dist/workflows/dag/frontend-verification-trace.js +38 -4
  198. package/dist/workflows/dag/frontend-writer-admission.js +2 -2
  199. package/dist/workflows/dag/init-hybrid.js +70 -35
  200. package/dist/workflows/dag/node-execution.js +225 -226
  201. package/dist/workflows/dag/prompt.js +4 -0
  202. package/dist/workflows/dag/recovery-lease.js +106 -16
  203. package/dist/workflows/dag/rerun-feedback.js +1 -0
  204. package/dist/workflows/dag/rerun-plan.js +96 -4
  205. package/dist/workflows/dag/rerun-run.js +28 -6
  206. package/dist/workflows/dag/rerun-task.js +221 -18
  207. package/dist/workflows/dag/retry-policy.js +27 -10
  208. package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
  209. package/dist/workflows/dag/runner.js +246 -19
  210. package/dist/workflows/dag/structured-output-repair.js +4 -1
  211. package/dist/workflows/dag/types.js +10 -3
  212. package/dist/workflows/dag/validate.js +10 -8
  213. package/dist/workflows/dag/workspace-checkpoint.js +66 -0
  214. package/docs/operations/README.md +2 -0
  215. package/docs/templates/agent-dag.schema.json +2 -2
  216. package/docs/templates/backend-test-dag.json +6 -4
  217. package/docs/templates/frontend-design-contract.md +4 -4
  218. package/docs/templates/frontend-implementation-contract.schema.json +68 -4
  219. package/docs/templates/frontend-implementation-dag.json +5 -5
  220. package/harness.json +1 -1
  221. package/package.json +8 -6
  222. package/skills/frontend-contract/SKILL.md +2 -1
  223. package/skills/frontend-contract/references/contract-protocol.md +19 -3
  224. package/skills/frontend-design-review/SKILL.md +12 -11
  225. package/skills/frontend-plan/SKILL.md +22 -2
  226. package/skills/frontend-plan/references/decision-contract.md +114 -5
  227. package/skills/frontend-plan/references/design-decisions.md +32 -0
  228. package/skills/frontend-review/SKILL.md +10 -11
  229. package/skills/frontend-scout/references/scout-evidence.md +4 -0
  230. package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
  231. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
  232. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
  233. package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
  234. package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
  235. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
  236. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
@@ -16,6 +16,7 @@ import { randomUUID } from "node:crypto";
16
16
  import { access } from "node:fs/promises";
17
17
  import { parseAssistantContent } from "./assistant-content.js";
18
18
  import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, } from "./tools.js";
19
+ import { InvalidChatSessionModeError, SESSION_MODE_ACTION_DENIED_CODE, SESSION_MODE_ACTION_DENIED_MESSAGE, SESSION_MODE_IMMUTABLE_CODE, UnknownChatSessionModeError, isWorkflowSessionMode, parseCreateSessionMode, readStoredSessionMode, shouldAcceptOperatorSnapshot, } from "./session-mode.js";
19
20
  import { buildModelCallableToolSchemas } from "./tool-adapter.js";
20
21
  import { cleanChatTitle, acceptedOperationIdFromResult, isBlankChatSession } from "./session-store.js";
21
22
  import { hashChatTurnPayload, projectChatToolEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
@@ -44,11 +45,80 @@ import { buildGoalCompactionInstructions, buildGoalContinuationContext, } from "
44
45
  import { classifyGoalRoundAdmission, } from "./goal-round-driver.js";
45
46
  import { isTerminalOperationState } from "../../../infrastructure/console/operation-store.js";
46
47
  import { SddDataAlignmentError, } from "./sdd-data-alignment.js";
48
+ import { TerminalSessionRegistry } from "./terminal-sessions.js";
49
+ import { handleTerminalRequest } from "./terminal-routes.js";
50
+ import { handleScmRequest } from "./scm-routes.js";
51
+ import { handleBrowserRequest } from "./browser-routes.js";
47
52
  function projectBrowserChatSession(record) {
48
53
  const { telemetrySessionId, ...projected } = record;
49
54
  void telemetrySessionId;
50
55
  return projected;
51
56
  }
57
+ function reopenInitFromRecord(record) {
58
+ return {
59
+ sessionId: record.sessionId,
60
+ sessionFile: record.sessionFile,
61
+ ...(record.modelProvider && record.modelId
62
+ ? { model: { provider: record.modelProvider, modelId: record.modelId } }
63
+ : {}),
64
+ sessionMode: record.sessionMode,
65
+ };
66
+ }
67
+ async function readChatSessionMode(deps, sessionId) {
68
+ let record;
69
+ try {
70
+ record = await deps.store.get(sessionId);
71
+ }
72
+ catch (error) {
73
+ if (error instanceof UnknownChatSessionModeError) {
74
+ return {
75
+ ok: false,
76
+ status: 409,
77
+ code: error.code,
78
+ message: error.message,
79
+ };
80
+ }
81
+ throw error;
82
+ }
83
+ if (!record) {
84
+ return {
85
+ ok: false,
86
+ status: 404,
87
+ code: "NOT_FOUND",
88
+ message: `chat session not found: ${sessionId}`,
89
+ };
90
+ }
91
+ // Normalize: the store contract always fills sessionMode, but handler-level
92
+ // fakes / partial records may omit it — treat missing as workflow (the
93
+ // stock-record read rule), while a present-but-unknown value still fails
94
+ // closed in the store layer.
95
+ return {
96
+ ok: true,
97
+ record,
98
+ sessionMode: readStoredSessionMode(record.sessionMode),
99
+ };
100
+ }
101
+ async function requireWorkflowSession(res, deps, sessionId) {
102
+ const resolved = await readChatSessionMode(deps, sessionId);
103
+ if (!resolved.ok) {
104
+ sendJson(res, resolved.status, {
105
+ ok: false,
106
+ error: { code: resolved.code, message: resolved.message },
107
+ });
108
+ return undefined;
109
+ }
110
+ if (!isWorkflowSessionMode(resolved.sessionMode)) {
111
+ sendJson(res, 409, {
112
+ ok: false,
113
+ error: {
114
+ code: SESSION_MODE_ACTION_DENIED_CODE,
115
+ message: SESSION_MODE_ACTION_DENIED_MESSAGE,
116
+ },
117
+ });
118
+ return undefined;
119
+ }
120
+ return resolved.record;
121
+ }
52
122
  /**
53
123
  * Reattach the durable event-ring order to session-store rows. The session
54
124
  * store remains the content authority; the ring only supplies chronology.
@@ -517,6 +587,14 @@ export async function handleChatCapabilities(_req, res, deps) {
517
587
  deniedTools: OPERATOR_CHAT_DENIED_TOOLS,
518
588
  deniedOperatorActions: OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS,
519
589
  tools: toolSchemas,
590
+ builtinSubagents: [
591
+ {
592
+ toolId: "Agent",
593
+ agent: "Explore",
594
+ kind: "subagent",
595
+ description: "Start the built-in read-only Explore subagent for codebase search, symbol lookup, and call-chain tracing.",
596
+ },
597
+ ],
520
598
  // ADR 0011: full Pi tools including bash are default-active.
521
599
  hasBash: true,
522
600
  });
@@ -557,6 +635,22 @@ workspaceId) {
557
635
  });
558
636
  return;
559
637
  }
638
+ let sessionMode;
639
+ try {
640
+ sessionMode = parseCreateSessionMode(body.sessionMode);
641
+ }
642
+ catch (error) {
643
+ sendJson(res, 400, {
644
+ ok: false,
645
+ error: {
646
+ code: error instanceof InvalidChatSessionModeError
647
+ ? error.code
648
+ : "INVALID_SESSION_MODE",
649
+ message: error instanceof Error ? error.message : String(error),
650
+ },
651
+ });
652
+ return;
653
+ }
560
654
  const provider = typeof body.modelProvider === "string" ? body.modelProvider : undefined;
561
655
  const modelId = typeof body.modelId === "string" ? body.modelId : undefined;
562
656
  const snapshotInput = body.snapshot;
@@ -578,6 +672,16 @@ workspaceId) {
578
672
  let createdSessionId;
579
673
  let persisted = false;
580
674
  try {
675
+ if (snapshotInput !== undefined && !shouldAcceptOperatorSnapshot(sessionMode)) {
676
+ sendJson(res, 409, {
677
+ ok: false,
678
+ error: {
679
+ code: SESSION_MODE_ACTION_DENIED_CODE,
680
+ message: "Operator snapshots cannot be imported into Pi sessions",
681
+ },
682
+ });
683
+ return;
684
+ }
581
685
  const snapshot = snapshotInput === undefined ? undefined : projectCompactSnapshot(snapshotInput);
582
686
  // A Console record promises that its Pi JSONL can be reopened after a
583
687
  // Console restart. Do not make that promise until Pi has finished
@@ -585,6 +689,7 @@ workspaceId) {
585
689
  // future JSONL path, leaving an unrecoverable orphan when init failed or
586
690
  // the process restarted in that window.
587
691
  const handle = await deps.runtime.createSession({
692
+ sessionMode,
588
693
  ...(provider && modelId ? { model: { provider, modelId } } : {}),
589
694
  ...(snapshot
590
695
  ? {
@@ -611,6 +716,7 @@ workspaceId) {
611
716
  sessionFile: handle.sessionFile,
612
717
  modelProvider: selection.model?.provider ?? handle.model?.provider,
613
718
  modelId: selection.model?.modelId ?? handle.model?.modelId,
719
+ sessionMode,
614
720
  // AC-R7-002: explicit parameter from the dispatcher — the runtime
615
721
  // store.create wrapper is gone; body workspaceId (if any) only acts as
616
722
  // a fallback for direct (non-dispatcher) handler tests.
@@ -681,6 +787,7 @@ export async function handleForkChatSession(req, res, deps, sourceSessionId) {
681
787
  try {
682
788
  const handle = await deps.runtime.forkSession({
683
789
  sourceSessionFile: source.sessionFile ?? "",
790
+ sessionMode: source.sessionMode,
684
791
  ...(source.modelProvider && source.modelId
685
792
  ? { model: { provider: source.modelProvider, modelId: source.modelId } }
686
793
  : {}),
@@ -747,10 +854,7 @@ export async function handleBranchChatSession(req, res, deps, sessionId) {
747
854
  }
748
855
  try {
749
856
  if (!deps.runtime.hasSession(sessionId)) {
750
- const reopened = await deps.runtime.reopenSession({
751
- sessionId,
752
- sessionFile: record.sessionFile,
753
- });
857
+ const reopened = await deps.runtime.reopenSession(reopenInitFromRecord(record));
754
858
  if (!reopened.ok) {
755
859
  const mapped = await mapFailedReopen(deps, sessionId, reopened);
756
860
  sendJson(res, mapped.status, {
@@ -851,6 +955,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
851
955
  }
852
956
  }
853
957
  await deps.operationLinker.recoverSession(sessionId);
958
+ const messagePage = await deps.store.listMessages(sessionId, { limit: 50 });
854
959
  const composerDraft = await new ComposerDraftStore(deps.appData).get(sessionId);
855
960
  const snapshot = deps.events.snapshot(sessionId);
856
961
  const telemetry = deriveChatSessionTelemetry(snapshot);
@@ -862,7 +967,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
862
967
  eventEpoch: deps.events.epoch(sessionId),
863
968
  session: {
864
969
  ...projectBrowserChatSession(record),
865
- messages: projectStoredMessagesWithEventOrder(record.messages, snapshot),
970
+ messages: projectStoredMessagesWithEventOrder(messagePage.messages, snapshot),
866
971
  composerDraft,
867
972
  activeTurnId: activeTurn?.turnId,
868
973
  turnPhase: activeTurn
@@ -874,6 +979,10 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
874
979
  todos: projectCurrentTodos(snapshot) ?? [],
875
980
  goal: deps.goalRegistry?.get(sessionId) ?? null,
876
981
  },
982
+ history: {
983
+ hasMore: messagePage.hasMore,
984
+ ...(messagePage.nextCursor ? { nextCursor: messagePage.nextCursor } : {}),
985
+ },
877
986
  hasBash: true,
878
987
  sessionStats: telemetry.stats,
879
988
  usage: telemetry.usage,
@@ -1134,19 +1243,18 @@ export async function handleSteerDeferredTurn(req, res, deps, sessionId, clientR
1134
1243
  }
1135
1244
  }
1136
1245
  async function persistAcceptedSteering(deps, sessionId, turnId, userMessage, clientRequestId) {
1246
+ emitUserMessageEvent(deps, sessionId, turnId, userMessage, clientRequestId, true);
1247
+ await emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, 0);
1248
+ }
1249
+ function emitUserMessageEvent(deps, sessionId, turnId, userMessage, clientRequestId, steering = false) {
1137
1250
  deps.events.append(sessionId, turnId, {
1138
1251
  kind: "user_message",
1139
1252
  data: {
1140
- clientRequestId,
1141
- steering: true,
1142
- message: {
1143
- id: userMessage.id,
1144
- text: userMessage.text,
1145
- at: userMessage.at,
1146
- },
1253
+ ...(clientRequestId ? { clientRequestId } : {}),
1254
+ ...(steering ? { steering: true } : {}),
1255
+ message: { id: userMessage.id, text: userMessage.text, at: userMessage.at },
1147
1256
  },
1148
1257
  });
1149
- await emitDeferredTurnUpdate(deps, sessionId, turnId, undefined, 0);
1150
1258
  }
1151
1259
  /** Lightweight busy-truth endpoint for the client reconcile loop (pi-web
1152
1260
  * get_state equivalent): exposes the active turn + the latest turn's durable
@@ -1219,13 +1327,8 @@ export async function handleChatGoal(req, res, deps, sessionId) {
1219
1327
  });
1220
1328
  return;
1221
1329
  }
1222
- if (!(await deps.store.get(sessionId))) {
1223
- sendJson(res, 404, {
1224
- ok: false,
1225
- error: { code: "NOT_FOUND", message: `chat session not found: ${sessionId}` },
1226
- });
1330
+ if (!(await requireWorkflowSession(res, deps, sessionId)))
1227
1331
  return;
1228
- }
1229
1332
  if (req.method === "GET") {
1230
1333
  sendJson(res, 200, { ok: true, goal: registry.get(sessionId) });
1231
1334
  return;
@@ -1439,6 +1542,7 @@ export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
1439
1542
  ...(session.thinkingLevel !== undefined
1440
1543
  ? { thinkingLevel: session.thinkingLevel }
1441
1544
  : {}),
1545
+ sessionMode: session.sessionMode,
1442
1546
  activeTurnId: active?.turnId,
1443
1547
  turnPhase: active?.abortRequestedAt
1444
1548
  ? "stopping"
@@ -1462,6 +1566,16 @@ export async function handlePatchChatSession(req, res, deps, sessionId) {
1462
1566
  }
1463
1567
  try {
1464
1568
  const body = await readJsonBody(req);
1569
+ if ("sessionMode" in body) {
1570
+ sendJson(res, 409, {
1571
+ ok: false,
1572
+ error: {
1573
+ code: SESSION_MODE_IMMUTABLE_CODE,
1574
+ message: "sessionMode cannot be changed after create",
1575
+ },
1576
+ });
1577
+ return;
1578
+ }
1465
1579
  let record;
1466
1580
  if (typeof body.title === "string")
1467
1581
  record = await deps.store.rename(sessionId, body.title);
@@ -1520,13 +1634,7 @@ export async function handleReopenChatSession(req, res, deps, sessionId) {
1520
1634
  });
1521
1635
  return;
1522
1636
  }
1523
- const result = await deps.runtime.reopenSession({
1524
- sessionId,
1525
- sessionFile: record.sessionFile,
1526
- ...(record.modelProvider && record.modelId
1527
- ? { model: { provider: record.modelProvider, modelId: record.modelId } }
1528
- : {}),
1529
- });
1637
+ const result = await deps.runtime.reopenSession(reopenInitFromRecord(record));
1530
1638
  if (!result.ok) {
1531
1639
  const mapped = await mapFailedReopen(deps, sessionId, result);
1532
1640
  sendJson(res, mapped.status, {
@@ -1606,13 +1714,7 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
1606
1714
  });
1607
1715
  return;
1608
1716
  }
1609
- const reopen = await deps.runtime.reopenSession({
1610
- sessionId,
1611
- sessionFile: record.sessionFile,
1612
- ...(record.modelProvider && record.modelId
1613
- ? { model: { provider: record.modelProvider, modelId: record.modelId } }
1614
- : {}),
1615
- });
1717
+ const reopen = await deps.runtime.reopenSession(reopenInitFromRecord(record));
1616
1718
  if (!reopen.ok) {
1617
1719
  const mapped = await mapFailedReopen(deps, sessionId, reopen);
1618
1720
  sendJson(res, mapped.status, {
@@ -1690,7 +1792,8 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
1690
1792
  text,
1691
1793
  images,
1692
1794
  signal: ac.signal,
1693
- onUserMessagePersisted: (record) => {
1795
+ onUserMessagePersisted: (record, userMessage) => {
1796
+ emitUserMessageEvent(deps, sessionId, turnId, userMessage);
1694
1797
  const users = record.messages.filter((message) => message.role === "user");
1695
1798
  if (users.length !== 1)
1696
1799
  return;
@@ -1875,13 +1978,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1875
1978
  });
1876
1979
  return;
1877
1980
  }
1878
- const reopen = await deps.runtime.reopenSession({
1879
- sessionId,
1880
- sessionFile: record.sessionFile,
1881
- ...(record.modelProvider && record.modelId
1882
- ? { model: { provider: record.modelProvider, modelId: record.modelId } }
1883
- : {}),
1884
- });
1981
+ const reopen = await deps.runtime.reopenSession(reopenInitFromRecord(record));
1885
1982
  if (!reopen.ok) {
1886
1983
  const mapped = await mapFailedReopen(deps, sessionId, reopen);
1887
1984
  sendJson(res, mapped.status, {
@@ -2000,9 +2097,12 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
2000
2097
  sessionId,
2001
2098
  turnId: turn.turnId,
2002
2099
  text,
2100
+ clientRequestId,
2003
2101
  images,
2004
2102
  signal: controller.signal,
2005
2103
  onUserMessagePersisted: async (record, userMessage) => {
2104
+ if (!trustedPayload)
2105
+ emitUserMessageEvent(deps, sessionId, turn.turnId, userMessage, clientRequestId);
2006
2106
  await trustedPayload?.onUserMessagePersisted?.(turn.turnId, userMessage);
2007
2107
  const users = record.messages.filter((message) => message.role === "user");
2008
2108
  if (users.length !== 1)
@@ -2156,6 +2256,10 @@ export async function driveGoalRound(deps, sessionId) {
2156
2256
  registry.disarm(sessionId);
2157
2257
  return "idle";
2158
2258
  }
2259
+ if (!isWorkflowSessionMode(readStoredSessionMode(record.sessionMode))) {
2260
+ registry.disarm(sessionId);
2261
+ return "idle";
2262
+ }
2159
2263
  const runtimeFacts = deps.runtime.getSessionBusyFacts(sessionId);
2160
2264
  const operationStore = deps.runtime.actionContext?.operations;
2161
2265
  const linkedOperationIds = linkedOperationIdsForGoal(deps.events.snapshot(sessionId));
@@ -2804,6 +2908,7 @@ export async function handleChatCommands(res, deps, sessionId) {
2804
2908
  const warning = typeof listed.warning === "string" && listed.warning.trim()
2805
2909
  ? listed.warning.trim().slice(0, 160)
2806
2910
  : undefined;
2911
+ const resolvedMode = await readChatSessionMode(deps, sessionId);
2807
2912
  res.statusCode = 200;
2808
2913
  res.setHeader("content-type", "application/json; charset=utf-8");
2809
2914
  res.end(JSON.stringify({
@@ -2811,18 +2916,25 @@ export async function handleChatCommands(res, deps, sessionId) {
2811
2916
  commands,
2812
2917
  capabilities: {
2813
2918
  sddBingo: Boolean(await deps.sddDataAlignment?.isEligible()),
2919
+ ...(resolvedMode.ok ? { sessionMode: resolvedMode.sessionMode } : {}),
2814
2920
  },
2815
2921
  ...(warning ? { warning } : {}),
2816
2922
  }));
2817
2923
  }
2818
2924
  catch (error) {
2819
2925
  // Whole-list / readiness failures stay fail-soft with empty commands + warning.
2926
+ // Keep the two capability shapes symmetric: the fallback mode mirrors the
2927
+ // client-side prop fallback in useComposer.
2928
+ const fallbackMode = await readChatSessionMode(deps, sessionId).catch(() => undefined);
2820
2929
  res.statusCode = 200;
2821
2930
  res.setHeader("content-type", "application/json; charset=utf-8");
2822
2931
  res.end(JSON.stringify({
2823
2932
  ok: true,
2824
2933
  commands: [],
2825
- capabilities: { sddBingo: false },
2934
+ capabilities: {
2935
+ sddBingo: false,
2936
+ ...(fallbackMode?.ok ? { sessionMode: fallbackMode.sessionMode } : {}),
2937
+ },
2826
2938
  warning: error instanceof Error ? error.message : "commands unavailable",
2827
2939
  }));
2828
2940
  }
@@ -2845,8 +2957,8 @@ async function handleSddBingo(req, res, deps, sessionId) {
2845
2957
  if (Object.keys(body).some((key) => key !== "skillName")) {
2846
2958
  throw new SddDataAlignmentError("SDD_INVALID_SKILL_NAME", "请求体只允许 skillName 字段", 400);
2847
2959
  }
2848
- await deps.sddDataAlignment.report(sessionId, body.skillName);
2849
- sendJson(res, 200, { ok: true });
2960
+ const result = await deps.sddDataAlignment.report(sessionId, body.skillName);
2961
+ sendJson(res, 200, { ok: true, skillName: result.skillName });
2850
2962
  }
2851
2963
  catch (error) {
2852
2964
  if (error instanceof SddDataAlignmentError) {
@@ -3067,13 +3179,7 @@ export async function ensureActiveSession(deps, sessionId) {
3067
3179
  message: `chat session not found: ${sessionId}`,
3068
3180
  };
3069
3181
  }
3070
- const reopened = await deps.runtime.reopenSession({
3071
- sessionId,
3072
- sessionFile: record.sessionFile,
3073
- ...(record.modelProvider && record.modelId
3074
- ? { model: { provider: record.modelProvider, modelId: record.modelId } }
3075
- : {}),
3076
- });
3182
+ const reopened = await deps.runtime.reopenSession(reopenInitFromRecord(record));
3077
3183
  if (!reopened.ok) {
3078
3184
  return { ok: false, ...(await mapFailedReopen(deps, sessionId, reopened)) };
3079
3185
  }
@@ -3402,7 +3508,7 @@ async function handleStartInterview(req, res, deps, sessionId) {
3402
3508
  });
3403
3509
  return;
3404
3510
  }
3405
- if (!(await requireChatSession(res, deps, sessionId)))
3511
+ if (!(await requireWorkflowSession(res, deps, sessionId)))
3406
3512
  return;
3407
3513
  try {
3408
3514
  const body = await readJsonBody(req);
@@ -3444,7 +3550,7 @@ async function handleAnswerInterview(req, res, deps, sessionId, interviewSession
3444
3550
  });
3445
3551
  return;
3446
3552
  }
3447
- if (!(await requireChatSession(res, deps, sessionId)))
3553
+ if (!(await requireWorkflowSession(res, deps, sessionId)))
3448
3554
  return;
3449
3555
  try {
3450
3556
  const body = await readJsonBody(req);
@@ -3480,7 +3586,7 @@ async function handleAnswerInterview(req, res, deps, sessionId, interviewSession
3480
3586
  }
3481
3587
  }
3482
3588
  async function handleGetInterviewFact(res, deps, sessionId, interviewSessionId, fact) {
3483
- if (!(await requireChatSession(res, deps, sessionId)))
3589
+ if (!(await requireWorkflowSession(res, deps, sessionId)))
3484
3590
  return;
3485
3591
  const state = await new ChatInterviewAdapter(deps.appData, deps.events).get(interviewSessionId);
3486
3592
  if (!state || state.interview.operatorSessionId !== sessionId) {
@@ -3578,7 +3684,7 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
3578
3684
  });
3579
3685
  return;
3580
3686
  }
3581
- if (!(await requireChatSession(res, deps, sessionId)))
3687
+ if (!(await requireWorkflowSession(res, deps, sessionId)))
3582
3688
  return;
3583
3689
  const ctx = deps.runtime.actionContext;
3584
3690
  if (!ctx) {
@@ -3760,7 +3866,7 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
3760
3866
  });
3761
3867
  return;
3762
3868
  }
3763
- if (!(await requireChatSession(res, deps, sessionId)))
3869
+ if (!(await requireWorkflowSession(res, deps, sessionId)))
3764
3870
  return;
3765
3871
  const ctx = deps.runtime.actionContext;
3766
3872
  if (!ctx) {
@@ -3869,7 +3975,7 @@ async function handleMutationHumanGate(req, res, deps, sessionId) {
3869
3975
  });
3870
3976
  return;
3871
3977
  }
3872
- if (!(await requireChatSession(res, deps, sessionId)))
3978
+ if (!(await requireWorkflowSession(res, deps, sessionId)))
3873
3979
  return;
3874
3980
  const ctx = deps.runtime.actionContext;
3875
3981
  if (!ctx) {
@@ -3958,7 +4064,7 @@ async function handleConfirmTaskKind(req, res, deps, sessionId, interviewSession
3958
4064
  });
3959
4065
  return;
3960
4066
  }
3961
- if (!(await requireChatSession(res, deps, sessionId)))
4067
+ if (!(await requireWorkflowSession(res, deps, sessionId)))
3962
4068
  return;
3963
4069
  try {
3964
4070
  const body = await readJsonBody(req);
@@ -4014,13 +4120,7 @@ export async function handleChatCompact(req, res, deps, sessionId) {
4014
4120
  return;
4015
4121
  }
4016
4122
  if (!deps.runtime.hasSession(sessionId)) {
4017
- const reopened = await deps.runtime.reopenSession({
4018
- sessionId,
4019
- sessionFile: record.sessionFile,
4020
- ...(record.modelProvider && record.modelId
4021
- ? { model: { provider: record.modelProvider, modelId: record.modelId } }
4022
- : {}),
4023
- });
4123
+ const reopened = await deps.runtime.reopenSession(reopenInitFromRecord(record));
4024
4124
  if (!reopened.ok) {
4025
4125
  const mapped = await mapFailedReopen(deps, sessionId, reopened);
4026
4126
  sendJson(res, mapped.status, {
@@ -4044,7 +4144,9 @@ export async function handleChatCompact(req, res, deps, sessionId) {
4044
4144
  const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
4045
4145
  const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
4046
4146
  const todoInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
4047
- const customInstructions = buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null);
4147
+ const customInstructions = isWorkflowSessionMode(record.sessionMode)
4148
+ ? buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null)
4149
+ : todoInstructions;
4048
4150
  const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
4049
4151
  const compactionId = `${sessionId}:compaction:${randomUUID()}`;
4050
4152
  deps.events.append(sessionId, turnId, {
@@ -4130,6 +4232,46 @@ export async function handleChatMessages(res, deps, sessionId, url) {
4130
4232
  export async function handleChatRequest(req, res, deps, pathname, workspaceScope) {
4131
4233
  if (!deps)
4132
4234
  return false;
4235
+ try {
4236
+ return await dispatchChatRequest(req, res, deps, pathname, workspaceScope);
4237
+ }
4238
+ catch (error) {
4239
+ // Store-level fail-closed for corrupt sessionMode: bounded 409 instead of
4240
+ // a generic 500, for any handler that reads the record without its own
4241
+ // UnknownChatSessionModeError mapping (readChatSessionMode covers the rest).
4242
+ if (error instanceof UnknownChatSessionModeError) {
4243
+ sendJson(res, 409, {
4244
+ ok: false,
4245
+ error: { code: error.code, message: error.message },
4246
+ });
4247
+ return true;
4248
+ }
4249
+ throw error;
4250
+ }
4251
+ }
4252
+ async function dispatchChatRequest(req, res, deps, pathname, workspaceScope) {
4253
+ // Terminal management API (model + Console UI shared PTY registry).
4254
+ if (await handleTerminalRequest(req, res, {
4255
+ terminals: deps.terminals ?? new TerminalSessionRegistry(),
4256
+ gateMutation: (r) => gateMutation(r, deps),
4257
+ // Terminals open in the workspace repo root, not the Console process cwd.
4258
+ defaultCwd: deps.appData.repoRoot,
4259
+ }, pathname)) {
4260
+ return true;
4261
+ }
4262
+ // Source control API (model + Console UI shared workspace ScmService).
4263
+ if (deps.scm) {
4264
+ if (await handleScmRequest(req, res, {
4265
+ scm: deps.scm,
4266
+ gateMutation: (r) => gateMutation(r, deps),
4267
+ }, pathname)) {
4268
+ return true;
4269
+ }
4270
+ }
4271
+ // Browser probe API (read-only; explains iframe refusals to the panel).
4272
+ if (await handleBrowserRequest(req, res, {}, pathname)) {
4273
+ return true;
4274
+ }
4133
4275
  if (!pathname.startsWith("/api/operator/v1/chat/") &&
4134
4276
  !pathname.startsWith("/api/operator/v1/pi/"))
4135
4277
  return false;