@cloudcli-ai/cloudcli 1.37.1 → 1.37.3

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 (402) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/abnfDiagram-N423BO3Z-CJntskwA.js +1 -0
  3. package/dist/assets/arc-CIfH4uMN.js +1 -0
  4. package/dist/assets/architectureDiagram-T3A2C74G-_IpEkukg.js +36 -0
  5. package/dist/assets/blockDiagram-VBNYF7ZC-BttxrpgZ.js +132 -0
  6. package/dist/assets/c4Diagram-5PPSVZJV-g51dSRr1.js +10 -0
  7. package/dist/assets/channel-C5xU9jMw.js +1 -0
  8. package/dist/assets/chunk-2GRJ4B5K-D_-LbQUa.js +1 -0
  9. package/dist/assets/chunk-2Q5K7J3B-BtlHzwaO.js +1 -0
  10. package/dist/assets/chunk-5RXB4S5H-UzkpvpAT.js +231 -0
  11. package/dist/assets/chunk-5VM5RSS4-BKwx5Gag.js +15 -0
  12. package/dist/assets/chunk-6Q2QTUOP-D_KEBH5f.js +88 -0
  13. package/dist/assets/chunk-GF5L2VYU-rK2DGyto.js +206 -0
  14. package/dist/assets/chunk-JWPE2WC7-CeBl_N9i.js +1 -0
  15. package/dist/assets/chunk-KBJHAD2P-BiQLWVIr.js +1 -0
  16. package/dist/assets/chunk-RYQCIY6F-C1knVGV5.js +1 -0
  17. package/dist/assets/chunk-XXDRQBXY-CtKbJkvF.js +1 -0
  18. package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +1 -0
  19. package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +1 -0
  20. package/dist/assets/cose-bilkent-JH36ORCC-DjX8y40Z.js +1 -0
  21. package/dist/assets/cynefin-VYW2F7L2-DM6YeRba.js +178 -0
  22. package/dist/assets/cynefinDiagram-MW4NZA55-wZJ-vy5B.js +62 -0
  23. package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
  24. package/dist/assets/dagre-VZM6K2ZE-BzP0ejkH.js +4 -0
  25. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  26. package/dist/assets/diagram-7IWD3JNH-BTL_j23J.js +30 -0
  27. package/dist/assets/diagram-B4RE2ZJO-Cf1Norgu.js +3 -0
  28. package/dist/assets/diagram-LBJQPF4R-DPiUEKr-.js +24 -0
  29. package/dist/assets/diagram-Q27KOJAE-CTcTpiz5.js +24 -0
  30. package/dist/assets/diagram-UB23O5K3-DPMSdYm0.js +41 -0
  31. package/dist/assets/ebnfDiagram-BXEA7PRR-7yvD6uiF.js +1 -0
  32. package/dist/assets/erDiagram-JOGREHBK-CfgeDoV6.js +85 -0
  33. package/dist/assets/flowDiagram-UKHOOZJN-B27ng5zu.js +156 -0
  34. package/dist/assets/ganttDiagram-PKOTCBZU-CcC6b_7t.js +292 -0
  35. package/dist/assets/gitGraphDiagram-DS77QQ5N-BX5kPGrO.js +106 -0
  36. package/dist/assets/graph-Cx-EqisW.js +1 -0
  37. package/dist/assets/index-BTDHQ6Id.css +32 -0
  38. package/dist/assets/index-sFfz8iBR.js +4931 -0
  39. package/dist/assets/infoDiagram-6WML65LV-CUP-SlN4.js +2 -0
  40. package/dist/assets/init-Gi6I4Gst.js +1 -0
  41. package/dist/assets/ishikawaDiagram-WSZJBQD7-BTTDhOpX.js +70 -0
  42. package/dist/assets/journeyDiagram-NVQOT4AX-ARXcc61m.js +139 -0
  43. package/dist/assets/kanban-definition-27J2QSJJ-BieMpqMA.js +89 -0
  44. package/dist/assets/layout-D6BstSzF.js +1 -0
  45. package/dist/assets/linear-61vxjRnP.js +1 -0
  46. package/dist/assets/map-CV9uif0g.js +1 -0
  47. package/dist/assets/mermaid.core-nhnwkfQx.js +312 -0
  48. package/dist/assets/mindmap-definition-FAOFIHXS-Cm6x5vT1.js +96 -0
  49. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  50. package/dist/assets/pegDiagram-VL7TDLO6-CcYBq_9H.js +1 -0
  51. package/dist/assets/pieDiagram-7S7Q4E2Y-CfvIjfzo.js +39 -0
  52. package/dist/assets/quadrantDiagram-CIZ2JOQS-SgYoWPNb.js +7 -0
  53. package/dist/assets/railroadDiagram-AXF67PYL-D2Mu5E_f.js +1 -0
  54. package/dist/assets/requirementDiagram-LRYGKXZP-Azwe_k4Q.js +84 -0
  55. package/dist/assets/sankeyDiagram-W5VNT64P-DN1Ju3PA.js +40 -0
  56. package/dist/assets/sequenceDiagram-SI44F4Z6-Bj4qC34S.js +162 -0
  57. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  58. package/dist/assets/sizeCapture-X5ZJPWSS-Co4PdklG.js +1 -0
  59. package/dist/assets/stateDiagram-OKZ733FA-nOvL9dRC.js +1 -0
  60. package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
  61. package/dist/assets/swimlanes-SLNWSIFB-CCK3wMid.js +2 -0
  62. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
  63. package/dist/assets/timeline-definition-Z64GVDOM-BC29RNFm.js +120 -0
  64. package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
  65. package/dist/assets/vennDiagram-T6HMQDX7-pZlZCtEo.js +34 -0
  66. package/dist/assets/wardleyDiagram-T6FBY63Y-De8k2-zM.js +78 -0
  67. package/dist/assets/xychartDiagram-ELKLHX3M-Bk8ykH0w.js +7 -0
  68. package/dist/index.html +3 -3
  69. package/dist-server/server/index.js +8 -5
  70. package/dist-server/server/index.js.map +1 -1
  71. package/dist-server/server/modules/agent/agent.module.js +2 -1
  72. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  73. package/dist-server/server/modules/agent/agent.routes.js +2 -2
  74. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  75. package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
  76. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  77. package/dist-server/server/modules/auth/auth.middleware.js +1 -1
  78. package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
  79. package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
  80. package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
  81. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  82. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  83. package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
  84. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  85. package/dist-server/server/modules/browser-use/index.js +2 -0
  86. package/dist-server/server/modules/browser-use/index.js.map +1 -1
  87. package/dist-server/server/modules/cli/cli.js +9 -3
  88. package/dist-server/server/modules/cli/cli.js.map +1 -1
  89. package/dist-server/server/modules/cli/cli.module.js +0 -1
  90. package/dist-server/server/modules/cli/cli.module.js.map +1 -1
  91. package/dist-server/server/modules/cli/cli.service.js +0 -18
  92. package/dist-server/server/modules/cli/cli.service.js.map +1 -1
  93. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
  94. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
  95. package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
  96. package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
  97. package/dist-server/server/modules/database/connection.js +1 -1
  98. package/dist-server/server/modules/database/connection.js.map +1 -1
  99. package/dist-server/server/modules/database/index.js +5 -0
  100. package/dist-server/server/modules/database/index.js.map +1 -1
  101. package/dist-server/server/modules/database/migrations.js +32 -1
  102. package/dist-server/server/modules/database/migrations.js.map +1 -1
  103. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  104. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  105. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  106. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  107. package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
  108. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  109. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  110. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  111. package/dist-server/server/modules/database/schema.js +92 -0
  112. package/dist-server/server/modules/database/schema.js.map +1 -1
  113. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  114. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  115. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  116. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  117. package/dist-server/server/modules/file-tree/file-tree.service.js +18 -10
  118. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  119. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +62 -11
  120. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  121. package/dist-server/server/modules/git/git-branch.service.js +15 -0
  122. package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
  123. package/dist-server/server/modules/git/git.routes.js +15 -7
  124. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  125. package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
  126. package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
  127. package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
  128. package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
  129. package/dist-server/server/modules/notifications/index.js +3 -1
  130. package/dist-server/server/modules/notifications/index.js.map +1 -1
  131. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
  132. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  133. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  134. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  135. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  136. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  137. package/dist-server/server/modules/providers/index.js +3 -0
  138. package/dist-server/server/modules/providers/index.js.map +1 -1
  139. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
  140. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  141. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  142. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  143. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
  144. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  145. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +422 -86
  146. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  147. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
  148. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  149. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  150. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  151. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  152. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  153. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  154. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  155. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  156. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  157. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -0
  158. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  159. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +60 -49
  160. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  161. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  162. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  163. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
  164. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  165. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  166. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  167. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  168. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  169. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  170. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
  171. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  172. package/dist-server/server/modules/providers/provider.routes.js +29 -0
  173. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  174. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
  175. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  176. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  177. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  178. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +63 -7
  179. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  180. package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
  181. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  182. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  183. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  184. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  185. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  186. package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
  187. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  188. package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
  189. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  190. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  191. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  192. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
  193. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  194. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  195. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  196. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  197. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  198. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  199. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  200. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
  201. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  202. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  203. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  204. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
  205. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  206. package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
  207. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  208. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  209. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  210. package/dist-server/server/modules/providers/tests/provider.routes.test.js +35 -0
  211. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  212. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  213. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  214. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
  215. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  216. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  217. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  218. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  219. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  220. package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
  221. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  222. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  223. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  224. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  225. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  226. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  227. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  228. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
  229. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  230. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  231. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  232. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
  233. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  234. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  235. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  236. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  237. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  238. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  239. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  240. package/dist-server/server/modules/user/user.module.js +10 -1
  241. package/dist-server/server/modules/user/user.module.js.map +1 -1
  242. package/dist-server/server/modules/user/user.routes.js +44 -0
  243. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  244. package/dist-server/server/modules/user/user.service.js +75 -0
  245. package/dist-server/server/modules/user/user.service.js.map +1 -1
  246. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  247. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  248. package/dist-server/server/modules/websocket/index.js +7 -0
  249. package/dist-server/server/modules/websocket/index.js.map +1 -1
  250. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
  251. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  252. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  253. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  254. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
  255. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  256. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  257. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  258. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
  259. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  260. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  261. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  262. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  263. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  264. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  265. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  266. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
  267. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  268. package/dist-server/server/shared/claude-cli-path.js +22 -5
  269. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  270. package/dist-server/server/shared/index.js +3 -0
  271. package/dist-server/server/shared/index.js.map +1 -0
  272. package/dist-server/server/shared/message-unification.js +376 -0
  273. package/dist-server/server/shared/message-unification.js.map +1 -0
  274. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  275. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  276. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  277. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  278. package/dist-server/server/shared/utils.js +42 -0
  279. package/dist-server/server/shared/utils.js.map +1 -1
  280. package/package.json +23 -16
  281. package/scripts/promote-dist-server.mjs +50 -0
  282. package/server/index.ts +12 -6
  283. package/server/modules/agent/agent.module.ts +2 -1
  284. package/server/modules/agent/agent.routes.ts +2 -2
  285. package/server/modules/agent/tests/agent.routes.test.ts +33 -1
  286. package/server/modules/auth/auth.middleware.ts +2 -2
  287. package/server/modules/browser-use/browser-use-runtime.ts +10 -0
  288. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  289. package/server/modules/browser-use/browser-use.service.ts +5 -8
  290. package/server/modules/browser-use/index.ts +3 -0
  291. package/server/modules/cli/cli.module.ts +0 -1
  292. package/server/modules/cli/cli.service.ts +0 -20
  293. package/server/modules/cli/cli.ts +9 -3
  294. package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
  295. package/server/modules/cli/tests/cli.service.test.ts +0 -3
  296. package/server/modules/database/connection.ts +2 -1
  297. package/server/modules/database/index.ts +10 -0
  298. package/server/modules/database/migrations.ts +37 -0
  299. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  300. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  301. package/server/modules/database/repositories/sessions.db.ts +196 -3
  302. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  303. package/server/modules/database/schema.ts +96 -0
  304. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  305. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  306. package/server/modules/file-tree/file-tree.service.ts +21 -10
  307. package/server/modules/file-tree/tests/file-tree.service.test.ts +65 -11
  308. package/server/modules/git/git-branch.service.ts +43 -0
  309. package/server/modules/git/git.routes.ts +16 -9
  310. package/server/modules/git/tests/git-branch.service.test.ts +60 -0
  311. package/server/modules/git/tests/git-init.routes.test.ts +83 -0
  312. package/server/modules/notifications/index.ts +2 -0
  313. package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
  314. package/server/modules/projects/services/project-delete.service.ts +13 -1
  315. package/server/modules/projects/services/project-star.service.ts +2 -2
  316. package/server/modules/providers/index.ts +4 -0
  317. package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
  318. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  319. package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
  320. package/server/modules/providers/list/claude/claude-runtime.provider.js +463 -93
  321. package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
  322. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  323. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  324. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  325. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  326. package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
  327. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
  328. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  329. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
  330. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  331. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  332. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  333. package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
  334. package/server/modules/providers/provider.routes.ts +36 -0
  335. package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
  336. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  337. package/server/modules/providers/services/session-conversations-search.service.ts +94 -7
  338. package/server/modules/providers/services/session-history-cache.service.ts +139 -0
  339. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  340. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  341. package/server/modules/providers/services/sessions.service.ts +201 -9
  342. package/server/modules/providers/tests/claude-auth.test.ts +58 -0
  343. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  344. package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
  345. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  346. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  347. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  348. package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
  349. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  350. package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
  351. package/server/modules/providers/tests/opencode-models.test.ts +166 -8
  352. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  353. package/server/modules/providers/tests/provider.routes.test.ts +61 -0
  354. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  355. package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
  356. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  357. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  358. package/server/modules/providers/tests/sessions.service.test.ts +67 -1
  359. package/server/modules/providers/tests/skills.test.ts +21 -5
  360. package/server/modules/scheduled-messages/index.ts +8 -0
  361. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  362. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
  363. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  364. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
  365. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  366. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  367. package/server/modules/user/tests/user.service.test.ts +90 -0
  368. package/server/modules/user/user.module.ts +10 -1
  369. package/server/modules/user/user.routes.ts +44 -0
  370. package/server/modules/user/user.service.ts +105 -0
  371. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  372. package/server/modules/websocket/index.ts +8 -0
  373. package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
  374. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  375. package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
  376. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  377. package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
  378. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  379. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  380. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  381. package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
  382. package/server/shared/claude-cli-path.ts +24 -6
  383. package/server/shared/index.ts +3 -0
  384. package/server/shared/interfaces.ts +62 -0
  385. package/server/shared/message-unification.ts +449 -0
  386. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  387. package/server/shared/tests/message-unification.test.ts +218 -0
  388. package/server/shared/types.ts +123 -3
  389. package/server/shared/utils.ts +50 -0
  390. package/server/tsconfig.json +6 -2
  391. package/README.de.md +0 -257
  392. package/README.ja.md +0 -249
  393. package/README.ko.md +0 -249
  394. package/README.ru.md +0 -257
  395. package/README.tr.md +0 -258
  396. package/README.zh-CN.md +0 -249
  397. package/README.zh-TW.md +0 -249
  398. package/dist/assets/index-D3jfzuKN.css +0 -32
  399. package/dist/assets/index-DTMWjJj8.js +0 -1550
  400. package/dist-server/server/constants/config.js +0 -6
  401. package/dist-server/server/constants/config.js.map +0 -1
  402. package/server/constants/config.ts +0 -5
@@ -13,6 +13,15 @@ function createDependencies(overrides: Partial<UserDependencies> = {}): UserDepe
13
13
  completeOnboarding: () => undefined,
14
14
  hasCompletedOnboarding: () => false,
15
15
  },
16
+ preferences: {
17
+ getPreferences: () => ({}),
18
+ savePreferences: () => undefined,
19
+ },
20
+ drafts: {
21
+ getDrafts: () => [],
22
+ saveDraft: () => undefined,
23
+ deleteDraft: () => undefined,
24
+ },
16
25
  readSystemGitConfig: async () => ({ git_name: null, git_email: null }),
17
26
  applyGlobalGitConfig: async () => undefined,
18
27
  logInfo: () => undefined,
@@ -59,3 +68,84 @@ test('updateGitConfig persists valid input and invokes the Git adapter', async (
59
68
  'git:Alice:alice@example.com',
60
69
  ]);
61
70
  });
71
+
72
+ test('savePreferences forwards only the keys the client sent', () => {
73
+ const saved: Array<Record<string, unknown>> = [];
74
+ const service = createUserService(createDependencies({
75
+ preferences: {
76
+ getPreferences: () => ({ theme: 'dark' }),
77
+ savePreferences: (_id, updates) => saved.push(updates),
78
+ },
79
+ }));
80
+
81
+ const result = service.savePreferences(3, { theme: 'light' });
82
+
83
+ assert.deepEqual(saved, [{ theme: 'light' }]);
84
+ assert.deepEqual(result.preferences, { theme: 'dark' });
85
+ });
86
+
87
+ test('savePreferences rejects a non-object body', () => {
88
+ const service = createUserService(createDependencies());
89
+
90
+ assert.throws(() => service.savePreferences(3, ['theme']), /object/i);
91
+ assert.throws(() => service.savePreferences(3, 'theme'), /object/i);
92
+ });
93
+
94
+ test('savePreferences rejects an over-long preference key', () => {
95
+ const service = createUserService(createDependencies());
96
+
97
+ assert.throws(() => service.savePreferences(3, { ['k'.repeat(201)]: 1 }), /1-200/);
98
+ });
99
+
100
+ test('saveDraft stores the text and queued message under the given scope', () => {
101
+ const saved: unknown[][] = [];
102
+ const service = createUserService(createDependencies({
103
+ drafts: {
104
+ getDrafts: () => [],
105
+ saveDraft: (...args) => saved.push(args),
106
+ deleteDraft: () => undefined,
107
+ },
108
+ }));
109
+
110
+ service.saveDraft(5, 'session-1', {
111
+ scope: 'session-1',
112
+ text: 'half a thought',
113
+ queuedMessage: { content: 'later' },
114
+ });
115
+
116
+ assert.deepEqual(saved, [[5, 'session-1', {
117
+ text: 'half a thought',
118
+ queuedMessage: { content: 'later' },
119
+ }]]);
120
+ });
121
+
122
+ test('saveDraft defaults a missing queued message to null rather than dropping the row', () => {
123
+ const saved: unknown[][] = [];
124
+ const service = createUserService(createDependencies({
125
+ drafts: {
126
+ getDrafts: () => [],
127
+ saveDraft: (...args) => saved.push(args),
128
+ deleteDraft: () => undefined,
129
+ },
130
+ }));
131
+
132
+ service.saveDraft(5, 'session-1', { scope: 'session-1', text: 'typing' });
133
+
134
+ assert.deepEqual(saved, [[5, 'session-1', { text: 'typing', queuedMessage: null }]]);
135
+ });
136
+
137
+ test('saveDraft rejects a blank or over-long scope', () => {
138
+ const service = createUserService(createDependencies());
139
+
140
+ assert.throws(() => service.saveDraft(5, ' ', { text: 'x' }), /scope/i);
141
+ assert.throws(() => service.saveDraft(5, 'x'.repeat(201), { text: 'x' }), /scope/i);
142
+ });
143
+
144
+ test('saveDraft rejects text past the storage limit', () => {
145
+ const service = createUserService(createDependencies());
146
+
147
+ assert.throws(
148
+ () => service.saveDraft(5, 'session-1', { text: 'x'.repeat(100_001) }),
149
+ /too long/i,
150
+ );
151
+ });
@@ -1,6 +1,6 @@
1
1
  import spawn from 'cross-spawn';
2
2
 
3
- import { userDb } from '@/modules/database/index.js';
3
+ import { sessionDraftsDb, userDb, userPreferencesDb } from '@/modules/database/index.js';
4
4
 
5
5
  import { createUserRouter } from './user.routes.js';
6
6
  import { createUserService } from './user.service.js';
@@ -45,6 +45,15 @@ const userService = createUserService({
45
45
  completeOnboarding: (userId) => userDb.completeOnboarding(userId),
46
46
  hasCompletedOnboarding: (userId) => userDb.hasCompletedOnboarding(userId),
47
47
  },
48
+ preferences: {
49
+ getPreferences: (userId) => userPreferencesDb.getPreferences(userId),
50
+ savePreferences: (userId, updates) => userPreferencesDb.savePreferences(userId, updates),
51
+ },
52
+ drafts: {
53
+ getDrafts: (userId) => sessionDraftsDb.getDrafts(userId),
54
+ saveDraft: (userId, scope, draft) => sessionDraftsDb.saveDraft(userId, scope, draft),
55
+ deleteDraft: (userId, scope) => sessionDraftsDb.deleteDraft(userId, scope),
56
+ },
48
57
  readSystemGitConfig,
49
58
  applyGlobalGitConfig: async (gitName, gitEmail) => {
50
59
  await runGit(['config', '--global', 'user.name', gitName]);
@@ -46,5 +46,49 @@ export function createUserRouter(service: ReturnType<typeof createUserService>):
46
46
  }
47
47
  });
48
48
 
49
+ router.get('/preferences', (req, res, next) => {
50
+ try {
51
+ res.json(service.getPreferences(readUserId(req)));
52
+ } catch (error) {
53
+ next(error);
54
+ }
55
+ });
56
+
57
+ router.patch('/preferences', (req, res, next) => {
58
+ try {
59
+ res.json(service.savePreferences(readUserId(req), req.body));
60
+ } catch (error) {
61
+ next(error);
62
+ }
63
+ });
64
+
65
+ router.get('/drafts', (req, res, next) => {
66
+ try {
67
+ res.json(service.getDrafts(readUserId(req)));
68
+ } catch (error) {
69
+ next(error);
70
+ }
71
+ });
72
+
73
+ // The scope is a session id or `project:<id>`, so it is read from the body
74
+ // rather than the path: neither form is guaranteed to be URL-path-safe.
75
+ router.put('/drafts', (req, res, next) => {
76
+ try {
77
+ const body = req.body as { scope?: unknown };
78
+ res.json(service.saveDraft(readUserId(req), body?.scope, req.body));
79
+ } catch (error) {
80
+ next(error);
81
+ }
82
+ });
83
+
84
+ router.delete('/drafts', (req, res, next) => {
85
+ try {
86
+ const body = req.body as { scope?: unknown };
87
+ res.json(service.deleteDraft(readUserId(req), body?.scope));
88
+ } catch (error) {
89
+ next(error);
90
+ }
91
+ });
92
+
49
93
  return router;
50
94
  }
@@ -5,6 +5,14 @@ type GitConfig = {
5
5
  git_email: string | null;
6
6
  };
7
7
 
8
+ /** One chat scope's unsent composer text and queued message. */
9
+ type DraftRecord = {
10
+ scope: string;
11
+ text: string;
12
+ queuedMessage: unknown | null;
13
+ updatedAt: string;
14
+ };
15
+
8
16
  type UserDependencies = {
9
17
  users: {
10
18
  getGitConfig(userId: number): GitConfig | undefined;
@@ -12,12 +20,65 @@ type UserDependencies = {
12
20
  completeOnboarding(userId: number): void;
13
21
  hasCompletedOnboarding(userId: number): boolean;
14
22
  };
23
+ preferences: {
24
+ getPreferences(userId: number): Record<string, unknown>;
25
+ savePreferences(userId: number, updates: Record<string, unknown>): void;
26
+ };
27
+ drafts: {
28
+ getDrafts(userId: number): DraftRecord[];
29
+ saveDraft(userId: number, scope: string, draft: { text: string; queuedMessage: unknown | null }): void;
30
+ deleteDraft(userId: number, scope: string): void;
31
+ };
15
32
  readSystemGitConfig(): Promise<GitConfig>;
16
33
  applyGlobalGitConfig(gitName: string, gitEmail: string): Promise<void>;
17
34
  logInfo(message: string): void;
18
35
  logError(message: string, error: unknown): void;
19
36
  };
20
37
 
38
+ /**
39
+ * The longest a draft scope or preference key may be.
40
+ *
41
+ * Scopes are session UUIDs or `project:<id>`, and preference keys are fixed
42
+ * identifiers, so anything longer is a client bug or an attempt to use the
43
+ * table as general storage.
44
+ */
45
+ const MAX_KEY_LENGTH = 200;
46
+
47
+ /** Guards against a runaway composer filling the database with one row. */
48
+ const MAX_DRAFT_TEXT_LENGTH = 100_000;
49
+
50
+ const readDraftScope = (value: unknown): string => {
51
+ const scope = typeof value === 'string' ? value.trim() : '';
52
+ if (!scope || scope.length > MAX_KEY_LENGTH) {
53
+ throw new AppError('A draft scope of 1-200 characters is required', {
54
+ code: 'INVALID_DRAFT_SCOPE',
55
+ statusCode: 400,
56
+ });
57
+ }
58
+ return scope;
59
+ };
60
+
61
+ const readPreferenceUpdates = (body: unknown): Record<string, unknown> => {
62
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
63
+ throw new AppError('Preferences must be sent as an object', {
64
+ code: 'INVALID_PREFERENCES',
65
+ statusCode: 400,
66
+ });
67
+ }
68
+
69
+ const updates = body as Record<string, unknown>;
70
+ for (const key of Object.keys(updates)) {
71
+ if (!key || key.length > MAX_KEY_LENGTH) {
72
+ throw new AppError('Preference keys must be 1-200 characters', {
73
+ code: 'INVALID_PREFERENCE_KEY',
74
+ statusCode: 400,
75
+ });
76
+ }
77
+ }
78
+
79
+ return updates;
80
+ };
81
+
21
82
  /** Creates user-profile workflows with explicit repository and Git adapters. */
22
83
  export function createUserService(dependencies: UserDependencies) {
23
84
  return {
@@ -81,5 +142,49 @@ export function createUserService(dependencies: UserDependencies) {
81
142
  hasCompletedOnboarding: dependencies.users.hasCompletedOnboarding(userId),
82
143
  };
83
144
  },
145
+
146
+ /** Every stored preference at once; the client fills gaps with its defaults. */
147
+ getPreferences(userId: number) {
148
+ return { success: true, preferences: dependencies.preferences.getPreferences(userId) };
149
+ },
150
+
151
+ /**
152
+ * Merge-patch: only the keys in the request body are touched, so two
153
+ * independent settings screens can save concurrently without one erasing
154
+ * the other's work.
155
+ */
156
+ savePreferences(userId: number, body: unknown) {
157
+ const updates = readPreferenceUpdates(body);
158
+ dependencies.preferences.savePreferences(userId, updates);
159
+ return { success: true, preferences: dependencies.preferences.getPreferences(userId) };
160
+ },
161
+
162
+ getDrafts(userId: number) {
163
+ return { success: true, drafts: dependencies.drafts.getDrafts(userId) };
164
+ },
165
+
166
+ saveDraft(userId: number, scopeInput: unknown, body: unknown) {
167
+ const scope = readDraftScope(scopeInput);
168
+ const payload = (body ?? {}) as { text?: unknown; queuedMessage?: unknown };
169
+ const text = typeof payload.text === 'string' ? payload.text : '';
170
+
171
+ if (text.length > MAX_DRAFT_TEXT_LENGTH) {
172
+ throw new AppError('Draft text is too long to store', {
173
+ code: 'DRAFT_TEXT_TOO_LONG',
174
+ statusCode: 413,
175
+ });
176
+ }
177
+
178
+ dependencies.drafts.saveDraft(userId, scope, {
179
+ text,
180
+ queuedMessage: payload.queuedMessage ?? null,
181
+ });
182
+ return { success: true };
183
+ },
184
+
185
+ deleteDraft(userId: number, scopeInput: unknown) {
186
+ dependencies.drafts.deleteDraft(userId, readDraftScope(scopeInput));
187
+ return { success: true };
188
+ },
84
189
  };
85
190
  }
@@ -47,8 +47,9 @@ test('transcribes with injected fetch and request-level credential/model overrid
47
47
 
48
48
  assert.deepEqual(result, { ok: true, value: { text: 'hello' } });
49
49
  assert.equal(requestedUrl, 'https://voice.example/v1/audio/transcriptions');
50
- assert.equal((requestedOptions?.headers as Record<string, string>).Authorization, 'Bearer request-key');
51
- assert.equal((requestedOptions?.body as FormData).get('model'), 'custom-whisper');
50
+ assert.ok(requestedOptions, 'fetchBackend should receive request options');
51
+ assert.equal((requestedOptions.headers as Record<string, string>).Authorization, 'Bearer request-key');
52
+ assert.equal((requestedOptions.body as FormData).get('model'), 'custom-whisper');
52
53
  });
53
54
 
54
55
  test('forwards the explicit TTS format and maps backend authentication failures', async () => {
@@ -1,3 +1,11 @@
1
1
  export { WS_OPEN_STATE, connectedClients } from './services/websocket-state.service.js';
2
2
  export { createWebSocketServer } from './services/websocket-server.service.js';
3
3
  export { chatRunRegistry } from './services/chat-run-registry.service.js';
4
+ // Consumed by the providers module's sessions watcher, which announces the
5
+ // sessions it (re)indexed from disk through the same builder the chat gateway
6
+ // uses, so both paths put the identical delta on the wire.
7
+ export { broadcastSessionUpserted, broadcastSessionUpsertedBatch } from './services/session-upsert-broadcast.service.js';
8
+ // runDetachedChatTurn: used by the scheduled-messages module to run a turn
9
+ // from a timer, with no socket to stream to or report errors on.
10
+ export { runDetachedChatTurn } from './services/chat-websocket.service.js';
11
+ export type { ProviderRuntimeGateway } from './services/chat-websocket.service.js';
@@ -1,9 +1,6 @@
1
- import path from 'node:path';
2
-
3
- import { projectsDb, sessionsDb } from '@/modules/database/index.js';
4
- import { generateDisplayName } from '@/modules/projects/index.js';
1
+ import { sessionsDb } from '@/modules/database/index.js';
5
2
  import { ChatSessionWriter } from '@/modules/websocket/services/chat-session-writer.service.js';
6
- import { connectedClients, WS_OPEN_STATE } from '@/modules/websocket/services/websocket-state.service.js';
3
+ import { broadcastSessionUpserted } from '@/modules/websocket/services/session-upsert-broadcast.service.js';
7
4
  import type {
8
5
  LLMProvider,
9
6
  NormalizedMessage,
@@ -62,48 +59,6 @@ const MAX_BUFFERED_EVENTS_PER_RUN = 5000;
62
59
  */
63
60
  const runs = new Map<string, ChatRun>();
64
61
 
65
- async function broadcastCanonicalSessionUpsert(appSessionId: string): Promise<void> {
66
- const row = sessionsDb.getSessionById(appSessionId);
67
- if (!row || row.isArchived) {
68
- return;
69
- }
70
-
71
- const projectPath = row.project_path;
72
- const project = projectPath ? projectsDb.getProjectPath(projectPath) : null;
73
- const displayName = project?.custom_project_name?.trim()
74
- ? project.custom_project_name
75
- : await generateDisplayName(path.basename(projectPath ?? '') || (projectPath ?? ''), projectPath);
76
-
77
- const payload = JSON.stringify({
78
- kind: 'session_upserted',
79
- sessionId: row.session_id,
80
- providerSessionId: row.provider_session_id,
81
- provider: row.provider,
82
- session: {
83
- id: row.session_id,
84
- summary: row.custom_name || '',
85
- messageCount: 0,
86
- lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
87
- },
88
- project: project
89
- ? {
90
- projectId: project.project_id,
91
- path: project.project_path,
92
- fullPath: project.project_path,
93
- displayName,
94
- isStarred: Boolean(project.isStarred),
95
- }
96
- : null,
97
- timestamp: new Date().toISOString(),
98
- });
99
-
100
- connectedClients.forEach((client) => {
101
- if (client.readyState === WS_OPEN_STATE) {
102
- client.send(payload);
103
- }
104
- });
105
- }
106
-
107
62
  function evictRunLater(appSessionId: string): void {
108
63
  const timer = setTimeout(() => {
109
64
  const run = runs.get(appSessionId);
@@ -178,7 +133,7 @@ function recordProviderSessionId(run: ChatRun, providerSessionId: string): void
178
133
 
179
134
  try {
180
135
  sessionsDb.assignProviderSessionId(run.appSessionId, providerSessionId);
181
- void broadcastCanonicalSessionUpsert(run.appSessionId).catch((error) => {
136
+ void broadcastSessionUpserted(run.appSessionId).catch((error) => {
182
137
  const message = error instanceof Error ? error.message : String(error);
183
138
  console.error('[ChatRunRegistry] Failed to broadcast canonical session mapping', {
184
139
  appSessionId: run.appSessionId,
@@ -213,7 +168,13 @@ export const chatRunRegistry = {
213
168
  appSessionId: string;
214
169
  provider: LLMProvider;
215
170
  providerSessionId: string | null;
216
- connection: RealtimeClientConnection;
171
+ /**
172
+ * The socket that asked for this run, or `null` for one nobody is watching
173
+ * — a scheduled message fires with no browser attached. The writer's event
174
+ * buffer still records everything, so a client that subscribes later
175
+ * replays the run from its start.
176
+ */
177
+ connection: RealtimeClientConnection | null;
217
178
  userId: string | number | null;
218
179
  }): ChatRun | null {
219
180
  const existing = runs.get(input.appSessionId);
@@ -273,11 +234,17 @@ export const chatRunRegistry = {
273
234
  },
274
235
 
275
236
  /**
276
- * Re-attaches a run's outbound stream to a (new) websocket connection.
237
+ * Adds a websocket connection to a run's live audience.
277
238
  *
278
239
  * This is the generic replacement for the Claude-only writer reconnect:
279
240
  * after a page refresh the new socket subscribes and immediately starts
280
241
  * receiving the still-running stream, for every provider.
242
+ *
243
+ * Subscribing does not take the stream away from sockets that were already
244
+ * watching — a session open in two places stays live in both, and the
245
+ * refreshed tab's abandoned socket is dropped when the next event finds it
246
+ * closed. Replay stays per-connection because each client sends its own
247
+ * `lastSeq` with `chat.subscribe`.
281
248
  */
282
249
  attachConnection(appSessionId: string, connection: RealtimeClientConnection): boolean {
283
250
  const run = runs.get(appSessionId);
@@ -7,7 +7,7 @@ import type {
7
7
  import { createCompleteMessage, readObjectRecord } from '@/shared/utils.js';
8
8
 
9
9
  type ChatSessionWriterOptions = {
10
- connection: RealtimeClientConnection;
10
+ connection: RealtimeClientConnection | null;
11
11
  userId: string | number | null;
12
12
  provider: LLMProvider;
13
13
  /** Provider-native id when resuming an existing session, otherwise null. */
@@ -44,7 +44,6 @@ type ChatSessionWriterOptions = {
44
44
  * intercepted and recorded as the provider-id mapping as well.
45
45
  */
46
46
  export class ChatSessionWriter {
47
- ws: RealtimeClientConnection;
48
47
  userId: string | number | null;
49
48
  /**
50
49
  * Some runtimes feature-detect their writer with this flag; keep it so the
@@ -53,6 +52,17 @@ export class ChatSessionWriter {
53
52
  isWebSocketWriter = true;
54
53
 
55
54
  private readonly options: ChatSessionWriterOptions;
55
+ /**
56
+ * Every socket currently watching this run, not just the one that started
57
+ * it. A session can legitimately be open in more than one place — a second
58
+ * browser tab, a phone alongside a laptop, the desktop app beside the web
59
+ * app — and each of those subscribes through `attachConnection`.
60
+ *
61
+ * Holding a single connection here meant the newest subscriber took the
62
+ * stream away from everyone before it, so a second tab silently froze
63
+ * mid-run and only caught up on the next REST history refresh.
64
+ */
65
+ private readonly connections = new Set<RealtimeClientConnection>();
56
66
  /**
57
67
  * The provider-native session id as the runtime knows it. Kept locally
58
68
  * (besides the registry) because runtimes read it back via `getSessionId()`
@@ -63,7 +73,12 @@ export class ChatSessionWriter {
63
73
 
64
74
  constructor(options: ChatSessionWriterOptions) {
65
75
  this.options = options;
66
- this.ws = options.connection;
76
+ // A run started by a timer has no audience yet. Everything still goes
77
+ // through `decorateOutboundEvent` into the run's replay buffer, so a
78
+ // client that subscribes mid-run catches up from the beginning.
79
+ if (options.connection) {
80
+ this.connections.add(options.connection);
81
+ }
67
82
  this.userId = options.userId;
68
83
  this.providerSessionId = options.providerSessionId;
69
84
  }
@@ -116,8 +131,13 @@ export class ChatSessionWriter {
116
131
  }
117
132
  }
118
133
 
134
+ /**
135
+ * Adds a socket to the run's live audience. Named for the `WebSocketWriter`
136
+ * method it stands in for, so runtime adapters keep working unchanged —
137
+ * but it adds rather than replaces.
138
+ */
119
139
  updateWebSocket(newConnection: RealtimeClientConnection): void {
120
- this.ws = newConnection;
140
+ this.connections.add(newConnection);
121
141
  }
122
142
 
123
143
  setSessionId(sessionId: string): void {
@@ -138,8 +158,18 @@ export class ChatSessionWriter {
138
158
  }
139
159
 
140
160
  private forward(message: NormalizedMessage): void {
141
- if (this.ws.readyState === WS_OPEN_STATE) {
142
- this.ws.send(JSON.stringify(message));
161
+ const payload = JSON.stringify(message);
162
+
163
+ // Sending is also when dead sockets get collected: a refreshed tab leaves
164
+ // its old connection behind, and without dropping it here the set would
165
+ // grow for the whole life of the run. Deleting during iteration is safe
166
+ // for a Set — the removed entry is simply not revisited.
167
+ for (const connection of this.connections) {
168
+ if (connection.readyState === WS_OPEN_STATE) {
169
+ connection.send(payload);
170
+ } else {
171
+ this.connections.delete(connection);
172
+ }
143
173
  }
144
174
  }
145
175
  }