@cloudcli-ai/cloudcli 1.37.2 → 1.37.4

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 (453) hide show
  1. package/README.md +269 -0
  2. package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-Dmkwx38H.js} +1 -1
  3. package/dist/assets/{arc-B8jF-ja9.js → arc-DLe9AYuO.js} +1 -1
  4. package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-CBbbbNUW.js} +1 -1
  5. package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-CaMEaWu4.js} +1 -1
  6. package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-DhjMgNnR.js} +1 -1
  7. package/dist/assets/channel-CWrmwsir.js +1 -0
  8. package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-yjl3-F-i.js} +1 -1
  9. package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-DsAYaWEP.js} +1 -1
  10. package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-Bt5qmqOX.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-yWrWVvUt.js} +1 -1
  12. package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-Dt66K5xb.js} +1 -1
  13. package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-DA0vzaj4.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CUkbx96V.js} +1 -1
  15. package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-DWKxx1WH.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-BqluplN0.js} +1 -1
  17. package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-DelKg3Xf.js} +1 -1
  18. package/dist/assets/classDiagram-JCYQIIEL-COAGCC6w.js +1 -0
  19. package/dist/assets/classDiagram-v2-OCEON4UE-COAGCC6w.js +1 -0
  20. package/dist/assets/{cose-bilkent-JH36ORCC-s1hygY8e.js → cose-bilkent-JH36ORCC-DHV93-Em.js} +1 -1
  21. package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-B9HZgoGX.js} +1 -1
  22. package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-hFjjEQg8.js} +1 -1
  23. package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-CpeVOC7V.js} +1 -1
  24. package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-D0H1UoRA.js} +1 -1
  25. package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-6Y7BQUup.js} +1 -1
  26. package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-C0nNOwht.js} +1 -1
  27. package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-En6QlOVX.js} +1 -1
  28. package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-CmPiNeGD.js} +1 -1
  29. package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-Bygzp1Qt.js} +1 -1
  30. package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CwScRfzl.js} +1 -1
  31. package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-bKX-0xP6.js} +1 -1
  32. package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-DtZPKXGd.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-CUOtdL1o.js} +1 -1
  34. package/dist/assets/index-CdjIFxM2.js +5004 -0
  35. package/dist/assets/index-DgafhGTF.css +32 -0
  36. package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-ciRQTS0h.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-CgjhQZ84.js} +1 -1
  38. package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-BsbQSeIW.js} +1 -1
  39. package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-CrwzY9Jw.js} +1 -1
  40. package/dist/assets/{linear-iPR-J9f7.js → linear-BUaCDr6l.js} +1 -1
  41. package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-ClgdUU0j.js} +5 -5
  42. package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-BictljY1.js} +1 -1
  43. package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-BXdByy_U.js} +1 -1
  44. package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-uZsMfkRX.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-7lCv4QIj.js} +1 -1
  46. package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-07dxIWbi.js} +1 -1
  47. package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Cb31Yv1_.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-X0YPSyF9.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-BdBxSIlP.js} +1 -1
  50. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  51. package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-DQM5xAyD.js} +1 -1
  52. package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-DJ3xkLUF.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-UEYNNEHI-vAaE4zNo.js +1 -0
  54. package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-rjfYdW0w.js} +2 -2
  55. package/dist/assets/swimlanesDiagram-ULZ7WXOC-TV2IF4Nq.js +8 -0
  56. package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-DZMra5do.js} +1 -1
  57. package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
  58. package/dist/assets/{vennDiagram-T6HMQDX7-DuF1uZMY.js → vennDiagram-T6HMQDX7-BevD8uu0.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-DsIyXB7z.js} +1 -1
  60. package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-G3PlF3ko.js} +1 -1
  61. package/dist/index.html +3 -3
  62. package/dist-server/server/index.js +11 -4
  63. package/dist-server/server/index.js.map +1 -1
  64. package/dist-server/server/modules/agent/agent.module.js +9 -2
  65. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  66. package/dist-server/server/modules/agent/agent.routes.js +177 -130
  67. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  68. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js +306 -0
  69. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js.map +1 -0
  70. package/dist-server/server/modules/agent/tests/agent.routes.test.js +48 -2
  71. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  72. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  73. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  74. package/dist-server/server/modules/database/connection.js +1 -1
  75. package/dist-server/server/modules/database/connection.js.map +1 -1
  76. package/dist-server/server/modules/database/index.js +5 -0
  77. package/dist-server/server/modules/database/index.js.map +1 -1
  78. package/dist-server/server/modules/database/migrations.js +45 -1
  79. package/dist-server/server/modules/database/migrations.js.map +1 -1
  80. package/dist-server/server/modules/database/repositories/provider-models.js +60 -6
  81. package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -1
  82. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  83. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  84. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  85. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  86. package/dist-server/server/modules/database/repositories/sessions.db.js +144 -5
  87. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  88. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  89. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  90. package/dist-server/server/modules/database/schema.js +99 -0
  91. package/dist-server/server/modules/database/schema.js.map +1 -1
  92. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +99 -0
  93. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -1
  94. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  95. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  96. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  97. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  98. package/dist-server/server/modules/file-tree/file-tree.module.js +2 -1
  99. package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
  100. package/dist-server/server/modules/file-tree/file-tree.service.js +44 -7
  101. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  102. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +126 -1
  103. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  104. package/dist-server/server/modules/git/git-branch-diff.service.js +142 -0
  105. package/dist-server/server/modules/git/git-branch-diff.service.js.map +1 -0
  106. package/dist-server/server/modules/git/git-branch.service.js.map +1 -1
  107. package/dist-server/server/modules/git/git.routes.js +92 -5
  108. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  109. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js +160 -0
  110. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js.map +1 -0
  111. package/dist-server/server/modules/projects/project-clone.routes.js +108 -0
  112. package/dist-server/server/modules/projects/project-clone.routes.js.map +1 -0
  113. package/dist-server/server/modules/projects/projects.routes.js +13 -66
  114. package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
  115. package/dist-server/server/modules/projects/services/project-clone-request.service.js +36 -0
  116. package/dist-server/server/modules/projects/services/project-clone-request.service.js.map +1 -0
  117. package/dist-server/server/modules/projects/services/project-clone.service.js +86 -32
  118. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
  119. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  120. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  121. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  122. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  123. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js +191 -0
  124. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js.map +1 -0
  125. package/dist-server/server/modules/projects/tests/project-clone.service.test.js +275 -0
  126. package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -1
  127. package/dist-server/server/modules/providers/index.js +3 -0
  128. package/dist-server/server/modules/providers/index.js.map +1 -1
  129. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +62 -9
  130. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  131. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  132. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  133. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +31 -12
  134. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  135. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +472 -53
  136. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  137. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +38 -11
  138. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  139. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +1007 -60
  140. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  141. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  142. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  143. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  144. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  145. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  146. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  147. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  148. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  149. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +47 -0
  150. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  151. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +71 -53
  152. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  153. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  154. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  155. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1621 -431
  156. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  157. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  158. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  159. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +9 -2
  160. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
  161. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +16 -8
  162. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  163. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  164. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +274 -9
  165. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  166. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  167. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -1
  168. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +19 -2
  169. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  170. package/dist-server/server/modules/providers/provider.routes.js +134 -2
  171. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  172. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js +100 -0
  173. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js.map +1 -0
  174. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js +97 -0
  175. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js.map +1 -0
  176. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +23 -1
  177. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  178. package/dist-server/server/modules/providers/services/provider-models.service.js +59 -5
  179. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  180. package/dist-server/server/modules/providers/services/provider-runtime.service.js +8 -0
  181. package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
  182. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  183. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  184. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +2 -4
  185. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  186. package/dist-server/server/modules/providers/services/session-history-cache.service.js +91 -0
  187. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  188. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  189. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  190. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  191. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  192. package/dist-server/server/modules/providers/services/sessions.service.js +252 -12
  193. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  194. package/dist-server/server/modules/providers/tests/claude-auth.test.js +183 -0
  195. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  196. package/dist-server/server/modules/providers/tests/claude-background-work.test.js +172 -0
  197. package/dist-server/server/modules/providers/tests/claude-background-work.test.js.map +1 -0
  198. package/dist-server/server/modules/providers/tests/claude-compaction.test.js +84 -0
  199. package/dist-server/server/modules/providers/tests/claude-compaction.test.js.map +1 -0
  200. package/dist-server/server/modules/providers/tests/claude-message-model.test.js +123 -0
  201. package/dist-server/server/modules/providers/tests/claude-message-model.test.js.map +1 -0
  202. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  203. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  204. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js +265 -0
  205. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js.map +1 -0
  206. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +1815 -33
  207. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  208. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js +228 -0
  209. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js.map +1 -0
  210. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  211. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  212. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  213. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  214. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  215. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  216. package/dist-server/server/modules/providers/tests/codex-models.test.js +25 -0
  217. package/dist-server/server/modules/providers/tests/codex-models.test.js.map +1 -0
  218. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +83 -0
  219. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  220. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  221. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  222. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +354 -15
  223. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  224. package/dist-server/server/modules/providers/tests/cursor-auth.test.js +167 -0
  225. package/dist-server/server/modules/providers/tests/cursor-auth.test.js.map +1 -0
  226. package/dist-server/server/modules/providers/tests/opencode-models.test.js +196 -4
  227. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  228. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +19 -1
  229. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  230. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js +30 -0
  231. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js.map +1 -0
  232. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +79 -1
  233. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
  234. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  235. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  236. package/dist-server/server/modules/providers/tests/provider.routes.test.js +190 -1
  237. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  238. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  239. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  240. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +139 -0
  241. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  242. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  243. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  244. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  245. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  246. package/dist-server/server/modules/providers/tests/sessions.service.test.js +219 -1
  247. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  248. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  249. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  250. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  251. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  252. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  253. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  254. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +175 -0
  255. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  256. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  257. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  258. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +290 -0
  259. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  260. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  261. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  262. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  263. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  264. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  265. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  266. package/dist-server/server/modules/user/user.module.js +10 -1
  267. package/dist-server/server/modules/user/user.module.js.map +1 -1
  268. package/dist-server/server/modules/user/user.routes.js +44 -0
  269. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  270. package/dist-server/server/modules/user/user.service.js +75 -0
  271. package/dist-server/server/modules/user/user.service.js.map +1 -1
  272. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  273. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  274. package/dist-server/server/modules/websocket/index.js +7 -0
  275. package/dist-server/server/modules/websocket/index.js.map +1 -1
  276. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +37 -49
  277. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  278. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  279. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  280. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +204 -10
  281. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  282. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  283. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  284. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +236 -20
  285. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  286. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js +137 -0
  287. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js.map +1 -0
  288. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  289. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  290. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  291. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  292. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js +96 -0
  293. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js.map +1 -0
  294. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  295. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  296. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +500 -0
  297. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  298. package/dist-server/server/shared/claude-cli-path.js +22 -5
  299. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  300. package/dist-server/server/shared/index.js +3 -0
  301. package/dist-server/server/shared/index.js.map +1 -0
  302. package/dist-server/server/shared/message-unification.js +376 -0
  303. package/dist-server/server/shared/message-unification.js.map +1 -0
  304. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  305. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  306. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  307. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  308. package/dist-server/server/shared/tests/read-only-roots.test.js +124 -0
  309. package/dist-server/server/shared/tests/read-only-roots.test.js.map +1 -0
  310. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js +31 -0
  311. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js.map +1 -0
  312. package/dist-server/server/shared/utils.js +160 -0
  313. package/dist-server/server/shared/utils.js.map +1 -1
  314. package/electron/desktopWindow.js +6 -0
  315. package/electron/viewHost.js +25 -2
  316. package/package.json +20 -17
  317. package/server/index.ts +16 -5
  318. package/server/modules/agent/agent.module.ts +11 -1
  319. package/server/modules/agent/agent.routes.ts +200 -142
  320. package/server/modules/agent/tests/agent-run-registration.test.ts +348 -0
  321. package/server/modules/agent/tests/agent.routes.test.ts +51 -2
  322. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  323. package/server/modules/database/connection.ts +2 -1
  324. package/server/modules/database/index.ts +10 -0
  325. package/server/modules/database/migrations.ts +51 -0
  326. package/server/modules/database/repositories/provider-models.ts +79 -6
  327. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  328. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  329. package/server/modules/database/repositories/sessions.db.ts +204 -3
  330. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  331. package/server/modules/database/schema.ts +103 -0
  332. package/server/modules/database/tests/provider-models.db.integration.test.ts +113 -0
  333. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  334. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  335. package/server/modules/file-tree/file-tree.module.ts +2 -1
  336. package/server/modules/file-tree/file-tree.service.ts +49 -7
  337. package/server/modules/file-tree/tests/file-tree.service.test.ts +163 -1
  338. package/server/modules/git/git-branch-diff.service.ts +204 -0
  339. package/server/modules/git/git-branch.service.ts +2 -12
  340. package/server/modules/git/git.routes.ts +100 -6
  341. package/server/modules/git/tests/git-branch-diff.service.test.ts +210 -0
  342. package/server/modules/projects/project-clone.routes.ts +142 -0
  343. package/server/modules/projects/projects.routes.ts +16 -81
  344. package/server/modules/projects/services/project-clone-request.service.ts +56 -0
  345. package/server/modules/projects/services/project-clone.service.ts +102 -34
  346. package/server/modules/projects/services/project-delete.service.ts +13 -1
  347. package/server/modules/projects/services/project-star.service.ts +2 -2
  348. package/server/modules/projects/tests/project-clone.routes.test.ts +246 -0
  349. package/server/modules/projects/tests/project-clone.service.test.ts +376 -0
  350. package/server/modules/providers/index.ts +4 -0
  351. package/server/modules/providers/list/claude/claude-auth.provider.ts +81 -11
  352. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  353. package/server/modules/providers/list/claude/claude-models.provider.ts +37 -17
  354. package/server/modules/providers/list/claude/claude-runtime.provider.js +510 -54
  355. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +40 -14
  356. package/server/modules/providers/list/claude/claude-sessions.provider.ts +1195 -63
  357. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  358. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  359. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  360. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  361. package/server/modules/providers/list/codex/codex-models.provider.ts +47 -0
  362. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +105 -70
  363. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  364. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1847 -455
  365. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  366. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +10 -2
  367. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +17 -8
  368. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  369. package/server/modules/providers/list/opencode/opencode-models.provider.ts +297 -9
  370. package/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  371. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +30 -2
  372. package/server/modules/providers/provider.routes.ts +162 -2
  373. package/server/modules/providers/services/claude-cli-liveness.service.ts +121 -0
  374. package/server/modules/providers/services/claude-settings-permissions.service.ts +127 -0
  375. package/server/modules/providers/services/provider-capabilities.service.ts +40 -1
  376. package/server/modules/providers/services/provider-models.service.ts +71 -4
  377. package/server/modules/providers/services/provider-runtime.service.ts +11 -0
  378. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  379. package/server/modules/providers/services/session-conversations-search.service.ts +2 -5
  380. package/server/modules/providers/services/session-history-cache.service.ts +150 -0
  381. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  382. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  383. package/server/modules/providers/services/sessions.service.ts +309 -17
  384. package/server/modules/providers/tests/claude-auth.test.ts +208 -0
  385. package/server/modules/providers/tests/claude-background-work.test.ts +213 -0
  386. package/server/modules/providers/tests/claude-compaction.test.ts +101 -0
  387. package/server/modules/providers/tests/claude-message-model.test.ts +160 -0
  388. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  389. package/server/modules/providers/tests/claude-runtime-hold.test.ts +309 -0
  390. package/server/modules/providers/tests/claude-sessions.test.ts +2167 -37
  391. package/server/modules/providers/tests/claude-settings-permissions.test.ts +271 -0
  392. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  393. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  394. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  395. package/server/modules/providers/tests/codex-models.test.ts +37 -0
  396. package/server/modules/providers/tests/codex-runtime.test.ts +94 -0
  397. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  398. package/server/modules/providers/tests/codex-sessions.test.ts +396 -15
  399. package/server/modules/providers/tests/cursor-auth.test.ts +187 -0
  400. package/server/modules/providers/tests/opencode-models.test.ts +266 -8
  401. package/server/modules/providers/tests/opencode-sessions.test.ts +40 -1
  402. package/server/modules/providers/tests/provider-capabilities.service.test.ts +37 -0
  403. package/server/modules/providers/tests/provider-models.service.test.ts +99 -1
  404. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  405. package/server/modules/providers/tests/provider.routes.test.ts +230 -1
  406. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  407. package/server/modules/providers/tests/session-history-cache.service.test.ts +165 -0
  408. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  409. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  410. package/server/modules/providers/tests/sessions.service.test.ts +303 -1
  411. package/server/modules/providers/tests/skills.test.ts +21 -5
  412. package/server/modules/scheduled-messages/index.ts +8 -0
  413. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  414. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +216 -0
  415. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  416. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +354 -0
  417. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  418. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  419. package/server/modules/user/tests/user.service.test.ts +90 -0
  420. package/server/modules/user/user.module.ts +10 -1
  421. package/server/modules/user/user.routes.ts +44 -0
  422. package/server/modules/user/user.service.ts +105 -0
  423. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  424. package/server/modules/websocket/README.md +4 -2
  425. package/server/modules/websocket/index.ts +8 -0
  426. package/server/modules/websocket/services/chat-run-registry.service.ts +45 -54
  427. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  428. package/server/modules/websocket/services/chat-websocket.service.ts +302 -16
  429. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  430. package/server/modules/websocket/services/shell-websocket.service.ts +276 -22
  431. package/server/modules/websocket/tests/chat-background-subscribe.test.ts +167 -0
  432. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  433. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  434. package/server/modules/websocket/tests/chat-stop-task.test.ts +127 -0
  435. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  436. package/server/modules/websocket/tests/shell-websocket.service.test.ts +629 -0
  437. package/server/shared/claude-cli-path.ts +24 -6
  438. package/server/shared/index.ts +3 -0
  439. package/server/shared/interfaces.ts +91 -0
  440. package/server/shared/message-unification.ts +449 -0
  441. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  442. package/server/shared/tests/message-unification.test.ts +218 -0
  443. package/server/shared/tests/read-only-roots.test.ts +139 -0
  444. package/server/shared/tests/strip-ansi-sequences.test.ts +42 -0
  445. package/server/shared/types.ts +382 -7
  446. package/server/shared/utils.ts +176 -0
  447. package/dist/assets/channel-_falYbU1.js +0 -1
  448. package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
  449. package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
  450. package/dist/assets/index-CzPLBvXT.css +0 -32
  451. package/dist/assets/index-DFkBBuSA.js +0 -1549
  452. package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
  453. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import type { WebSocket } from 'ws';
4
4
 
5
5
  import { sessionsDb } from '@/modules/database/index.js';
6
- import { providerModelsService } from '@/modules/providers/index.js';
6
+ import { providerModelsService, sessionsService } from '@/modules/providers/index.js';
7
7
  import { chatRunRegistry } from '@/modules/websocket/services/chat-run-registry.service.js';
8
8
  import { connectedClients, WS_OPEN_STATE } from '@/modules/websocket/services/websocket-state.service.js';
9
9
  import {
@@ -64,7 +64,7 @@ export function filterImagesToUploadStore(
64
64
  }
65
65
 
66
66
  /** Application boundary for dispatching provider runs and approvals. */
67
- type ProviderRuntimeGateway = {
67
+ export type ProviderRuntimeGateway = {
68
68
  hasRuntime(provider: string): boolean;
69
69
  run(
70
70
  provider: LLMProvider,
@@ -73,6 +73,9 @@ type ProviderRuntimeGateway = {
73
73
  writer: ProviderRuntimeWriter,
74
74
  ): Promise<unknown>;
75
75
  abort(provider: LLMProvider, sessionId: string): Promise<boolean>;
76
+ stopBackgroundTask(provider: LLMProvider, sessionId: string, taskId: string): Promise<boolean>;
77
+ /** Whether a provider runtime still holds background work for the session after its turn ended. */
78
+ hasBackgroundWork(sessionId: string): boolean;
76
79
  resolveToolApproval(requestId: string, payload: ProviderPermissionDecision): void;
77
80
  getPendingApprovalsForSession(sessionId: string): unknown[];
78
81
  };
@@ -149,10 +152,34 @@ async function handleChatSend(
149
152
  data: AnyRecord,
150
153
  dependencies: ChatWebSocketDependencies
151
154
  ): Promise<void> {
155
+ const resolved = resolveSendTarget(ws, data, dependencies, 'chat.send');
156
+ if (!resolved) {
157
+ return;
158
+ }
159
+
160
+ await dispatchRun(ws, userId, resolved.sessionId, resolved.session, data, dependencies);
161
+ }
162
+
163
+ type ResolvedSendTarget = {
164
+ sessionId: string;
165
+ session: NonNullable<ReturnType<typeof sessionsDb.getSessionById>>;
166
+ provider: LLMProvider;
167
+ };
168
+
169
+ /**
170
+ * Shared front half of `chat.send` and `chat.edit-send`: the session row and
171
+ * provider come from the database, never from the client.
172
+ */
173
+ function resolveSendTarget(
174
+ ws: WebSocket,
175
+ data: AnyRecord,
176
+ dependencies: ChatWebSocketDependencies,
177
+ frameName: string,
178
+ ): ResolvedSendTarget | null {
152
179
  const sessionId = readRequiredSessionId(data);
153
180
  if (!sessionId) {
154
- sendProtocolError(ws, 'SESSION_ID_REQUIRED', 'chat.send requires a sessionId.');
155
- return;
181
+ sendProtocolError(ws, 'SESSION_ID_REQUIRED', `${frameName} requires a sessionId.`);
182
+ return null;
156
183
  }
157
184
 
158
185
  const session = sessionsDb.getSessionById(sessionId);
@@ -163,15 +190,36 @@ async function handleChatSend(
163
190
  `Session "${sessionId}" was not found. Create it via POST /api/providers/sessions first.`,
164
191
  sessionId
165
192
  );
166
- return;
193
+ return null;
167
194
  }
168
195
 
169
196
  const provider = session.provider as LLMProvider;
170
197
  if (!dependencies.runtime.hasRuntime(provider)) {
171
198
  sendProtocolError(ws, 'UNSUPPORTED_PROVIDER', `Provider "${provider}" is not available.`, sessionId);
172
- return;
199
+ return null;
173
200
  }
174
201
 
202
+ return { sessionId, session, provider };
203
+ }
204
+
205
+ /**
206
+ * Registers the run and hands the turn to the provider runtime.
207
+ *
208
+ * `extraRuntimeOptions` is how an edited message asks the provider to resume
209
+ * partway instead of continuing from the tip; a normal send passes nothing.
210
+ */
211
+ async function dispatchRun(
212
+ ws: WebSocket | null,
213
+ userId: string | number | null,
214
+ sessionId: string,
215
+ session: NonNullable<ReturnType<typeof sessionsDb.getSessionById>>,
216
+ data: AnyRecord,
217
+ dependencies: ChatWebSocketDependencies,
218
+ extraRuntimeOptions: AnyRecord = {},
219
+ beforeRun?: (run: NonNullable<ReturnType<typeof chatRunRegistry.startRun>>) => void | Promise<void>,
220
+ ): Promise<{ started: boolean; error: string | null }> {
221
+ const provider = session.provider as LLMProvider;
222
+
175
223
  const run = chatRunRegistry.startRun({
176
224
  appSessionId: sessionId,
177
225
  provider,
@@ -181,13 +229,15 @@ async function handleChatSend(
181
229
  });
182
230
 
183
231
  if (!run) {
184
- sendProtocolError(
185
- ws,
186
- 'RUN_IN_PROGRESS',
187
- `Session "${sessionId}" already has a run in progress.`,
188
- sessionId
189
- );
190
- return;
232
+ if (ws) {
233
+ sendProtocolError(
234
+ ws,
235
+ 'RUN_IN_PROGRESS',
236
+ `Session "${sessionId}" already has a run in progress.`,
237
+ sessionId
238
+ );
239
+ }
240
+ return { started: false, error: 'A run is already in progress for this session.' };
191
241
  }
192
242
 
193
243
  const clientOptions = (data.options ?? {}) as AnyRecord;
@@ -221,6 +271,7 @@ async function handleChatSend(
221
271
  // app session id.
222
272
  const runtimeOptions: AnyRecord = {
223
273
  ...clientOptions,
274
+ ...extraRuntimeOptions,
224
275
  // Attachments are re-validated server-side: only direct children of the
225
276
  // global upload store may reach provider runtimes or their file tools.
226
277
  attachments: uniqueAttachments,
@@ -231,11 +282,17 @@ async function handleChatSend(
231
282
  projectPath: session.project_path ?? clientOptions.projectPath,
232
283
  };
233
284
 
285
+ let failure: string | null = null;
234
286
  try {
287
+ // Runs only now that the session is reserved, because an edit rewinds the
288
+ // conversation here and a rewind for a run that was never admitted cannot
289
+ // be taken back. Inside the try so a rewind that throws still releases the
290
+ // run instead of leaving the session processing forever.
291
+ await beforeRun?.(run);
235
292
  await dependencies.runtime.run(provider, command, runtimeOptions, run.writer);
236
293
  } catch (error) {
237
- const message = error instanceof Error ? error.message : String(error);
238
- console.error(`[Chat] Provider runtime "${provider}" failed`, { sessionId, error: message });
294
+ failure = error instanceof Error ? error.message : String(error);
295
+ console.error(`[Chat] Provider runtime "${provider}" failed`, { sessionId, error: failure });
239
296
  } finally {
240
297
  // Safety net: a runtime that crashed (or resolved) without emitting its
241
298
  // terminal `complete` would otherwise leave the session stuck in
@@ -244,6 +301,113 @@ async function handleChatSend(
244
301
  // settles, and the session-keyed completeRun would kill that new run.
245
302
  chatRunRegistry.completeRunIfCurrent(run, { exitCode: 1 });
246
303
  }
304
+
305
+ return { started: true, error: failure };
306
+ }
307
+
308
+ /**
309
+ * Handles `chat.edit-send`: replaces an already-sent message and everything
310
+ * after it with a new turn.
311
+ *
312
+ * Nothing is deleted. The provider resumes the conversation partway and
313
+ * appends the replacement, so the abandoned attempt stays in the transcript
314
+ * file and is simply no longer part of the live conversation — the same shape
315
+ * Claude Code's rewind and Codex's fork-with-cut-point produce.
316
+ */
317
+ async function handleChatEditSend(
318
+ ws: WebSocket,
319
+ userId: string | number | null,
320
+ data: AnyRecord,
321
+ dependencies: ChatWebSocketDependencies
322
+ ): Promise<void> {
323
+ const resolved = resolveSendTarget(ws, data, dependencies, 'chat.edit-send');
324
+ if (!resolved) {
325
+ return;
326
+ }
327
+
328
+ const { sessionId, session, provider } = resolved;
329
+ const anchorId = typeof data.anchorId === 'string' ? data.anchorId.trim() : '';
330
+ if (!anchorId) {
331
+ sendProtocolError(ws, 'ANCHOR_REQUIRED', 'chat.edit-send requires the anchorId of the message being replaced.', sessionId);
332
+ return;
333
+ }
334
+
335
+ let resumeThroughId: string | null;
336
+ try {
337
+ const anchor = await sessionsService.resolveEditAnchor(sessionId, anchorId);
338
+ if (!anchor) {
339
+ sendProtocolError(
340
+ ws,
341
+ 'EDIT_NOT_SUPPORTED',
342
+ `Provider "${provider}" cannot replace an already-sent message.`,
343
+ sessionId
344
+ );
345
+ return;
346
+ }
347
+ if (!anchor.found) {
348
+ sendProtocolError(ws, 'ANCHOR_NOT_FOUND', 'That message is no longer in the transcript.', sessionId);
349
+ return;
350
+ }
351
+ resumeThroughId = anchor.resumeThroughId;
352
+ } catch (error) {
353
+ const message = error instanceof Error ? error.message : String(error);
354
+ sendProtocolError(ws, 'ANCHOR_LOOKUP_FAILED', `Could not read the transcript: ${message}`, sessionId);
355
+ return;
356
+ }
357
+
358
+ // Providers split here on what their runtime can do. Claude resumes its
359
+ // transcript partway, so the anchor rides along as a run option. Codex
360
+ // cannot — a thread only grows — so the conversation is rewound on disk and
361
+ // the run that follows is an ordinary resume of whatever the session then
362
+ // points at. Which of the two applies is decided here; the rewind itself
363
+ // waits until the run has actually been admitted.
364
+ const rewinds = sessionsService.providerRewindsForEdit(sessionId);
365
+
366
+ await dispatchRun(
367
+ ws,
368
+ userId,
369
+ sessionId,
370
+ session,
371
+ data,
372
+ dependencies,
373
+ // `null` is meaningful: the edited turn was the first prompt, so the
374
+ // conversation starts over instead of resuming.
375
+ rewinds
376
+ ? {}
377
+ : { resumeAnchorId: resumeThroughId ?? undefined, resumeFromScratch: resumeThroughId === null },
378
+ async (run) => {
379
+ // Emitted through the run's writer so it is sequenced and replayed like
380
+ // any other event — a second tab watching this session has to truncate
381
+ // too.
382
+ //
383
+ // Before the rewind, not after it. A rewind that has to branch spawns a
384
+ // process and waits on a JSON-RPC round trip, and holding the frame
385
+ // until that came back left the message the user had just edited away
386
+ // sitting on screen for about a second — the very flicker this feature
387
+ // exists to avoid. Announcing first is safe because a rewind that fails
388
+ // still ends the run, and the terminal `complete` makes every client
389
+ // re-read the transcript, which puts back anything that turned out not
390
+ // to have been replaced after all.
391
+ run.writer.send({
392
+ kind: 'history_truncated',
393
+ provider,
394
+ sessionId,
395
+ anchorId,
396
+ });
397
+
398
+ if (rewinds) {
399
+ try {
400
+ await sessionsService.rewindSessionForEdit(sessionId, resumeThroughId);
401
+ } catch (error) {
402
+ const message = error instanceof Error ? error.message : String(error);
403
+ sendProtocolError(ws, 'EDIT_REWIND_FAILED', `Could not rewind the conversation: ${message}`, sessionId);
404
+ // Ends the run before the provider is asked to continue a
405
+ // conversation that was not rewound after all.
406
+ throw error;
407
+ }
408
+ }
409
+ },
410
+ );
247
411
  }
248
412
 
249
413
  /**
@@ -276,6 +440,52 @@ async function handleChatAbort(
276
440
  });
277
441
  }
278
442
 
443
+ /**
444
+ * Handles `chat.stop-task`: stops one background task of a session — an
445
+ * agent, a workflow or a backgrounded command that is still going after its
446
+ * turn ended. Unlike `chat.abort` there is no run to consult: the task lives
447
+ * in the provider's held process, which is the only thing that can stop it.
448
+ * The provider then reports the task as stopped on the session's stream, so
449
+ * nothing is echoed back here on success.
450
+ */
451
+ async function handleChatStopTask(
452
+ ws: WebSocket,
453
+ data: AnyRecord,
454
+ dependencies: ChatWebSocketDependencies
455
+ ): Promise<void> {
456
+ const sessionId = readRequiredSessionId(data);
457
+ if (!sessionId) {
458
+ sendProtocolError(ws, 'SESSION_ID_REQUIRED', 'chat.stop-task requires a sessionId.');
459
+ return;
460
+ }
461
+
462
+ const taskId = typeof data.taskId === 'string' ? data.taskId.trim() : '';
463
+ if (!taskId) {
464
+ sendProtocolError(ws, 'TASK_ID_REQUIRED', 'chat.stop-task requires a taskId.', sessionId);
465
+ return;
466
+ }
467
+
468
+ const session = sessionsDb.getSessionById(sessionId);
469
+ if (!session) {
470
+ sendProtocolError(ws, 'SESSION_NOT_FOUND', `Session "${sessionId}" was not found.`, sessionId);
471
+ return;
472
+ }
473
+
474
+ const stopped = await dependencies.runtime.stopBackgroundTask(
475
+ session.provider as LLMProvider,
476
+ sessionId,
477
+ taskId,
478
+ );
479
+ if (!stopped) {
480
+ sendProtocolError(
481
+ ws,
482
+ 'NO_SUCH_TASK',
483
+ `Session "${sessionId}" has no running background task "${taskId}".`,
484
+ sessionId,
485
+ );
486
+ }
487
+ }
488
+
279
489
  /**
280
490
  * Handles `chat.subscribe`: for each requested session, reports whether a run
281
491
  * is processing, re-attaches the live stream to this socket, replays missed
@@ -313,7 +523,11 @@ function handleChatSubscribe(
313
523
 
314
524
  // Future live events for this run should land on the socket that asked —
315
525
  // this is what makes mid-stream page refreshes work for all providers.
316
- if (isProcessing) {
526
+ // A session whose turn ended but whose background work is still going
527
+ // keeps producing events through the same writer (task progress, the
528
+ // turn the CLI pushes when a task reports), so a tab opened during that
529
+ // work attaches too; the registry keeps the run while the work lasts.
530
+ if (isProcessing || dependencies.runtime.hasBackgroundWork(sessionId)) {
317
531
  chatRunRegistry.attachConnection(sessionId, ws);
318
532
  }
319
533
 
@@ -366,6 +580,7 @@ function handlePermissionResponse(data: AnyRecord, dependencies: ChatWebSocketDe
366
580
  * Inbound protocol (client to server):
367
581
  * - `chat.send` { sessionId, content, options? }
368
582
  * - `chat.abort` { sessionId }
583
+ * - `chat.stop-task` { sessionId, taskId }
369
584
  * - `chat.subscribe` { sessions: [{ sessionId, lastSeq? }] }
370
585
  * - `chat.permission-response` { requestId, allow, updatedInput?, message?, rememberEntry? }
371
586
  *
@@ -374,6 +589,71 @@ function handlePermissionResponse(data: AnyRecord, dependencies: ChatWebSocketDe
374
589
  * (`chat_subscribed`, `session_upserted`, `loading_progress`,
375
590
  * `protocol_error`).
376
591
  */
592
+ /**
593
+ * Runs a turn for a session with no client attached.
594
+ *
595
+ * Used by scheduled messages, which fire from a timer: there is no socket to
596
+ * report errors to and no audience to stream to. The run is registered exactly
597
+ * like an interactive one, so anyone who opens the session while it is going
598
+ * subscribes and replays it from the start, and the session shows as busy
599
+ * everywhere in the meantime.
600
+ *
601
+ * Resolves when the provider run settles. Returns false when the session has
602
+ * gone away or is busy without `interruptActiveRun`, which the caller reports
603
+ * on the schedule.
604
+ */
605
+ export async function runDetachedChatTurn(
606
+ input: {
607
+ sessionId: string;
608
+ userId: string | number | null;
609
+ content: string;
610
+ options?: AnyRecord;
611
+ /**
612
+ * Aborts a run already in progress instead of refusing to start. A
613
+ * scheduled message sets this: the user picked the time knowing it might
614
+ * land mid-run, so the timer outranks whatever is running.
615
+ */
616
+ interruptActiveRun?: boolean;
617
+ },
618
+ dependencies: ChatWebSocketDependencies,
619
+ ): Promise<{ started: boolean; error: string | null }> {
620
+ const session = sessionsDb.getSessionById(input.sessionId);
621
+ if (!session) {
622
+ return { started: false, error: 'The session no longer exists.' };
623
+ }
624
+
625
+ const provider = session.provider as LLMProvider;
626
+ if (!dependencies.runtime.hasRuntime(provider)) {
627
+ return { started: false, error: `Provider "${provider}" is not available.` };
628
+ }
629
+
630
+ const activeRun = chatRunRegistry.getRun(input.sessionId);
631
+ if (activeRun && activeRun.status === 'running') {
632
+ if (!input.interruptActiveRun) {
633
+ return { started: false, error: 'A run was already in progress for this session.' };
634
+ }
635
+ // Same shape as `chat.abort`: cancel the provider run and emit the
636
+ // terminal `complete` on its behalf, so every watching client sees the
637
+ // interrupted run end before this turn's stream begins. The interrupted
638
+ // run's own dispatch settles later through completeRunIfCurrent, which is
639
+ // scoped to that run and cannot touch the one started here.
640
+ const aborted = await dependencies.runtime.abort(activeRun.provider, input.sessionId);
641
+ chatRunRegistry.completeRun(input.sessionId, {
642
+ exitCode: aborted ? 0 : 1,
643
+ aborted: true,
644
+ });
645
+ }
646
+
647
+ return dispatchRun(
648
+ null,
649
+ input.userId,
650
+ input.sessionId,
651
+ session,
652
+ { sessionId: input.sessionId, content: input.content, options: input.options ?? {} },
653
+ dependencies,
654
+ );
655
+ }
656
+
377
657
  export function handleChatConnection(
378
658
  ws: WebSocket,
379
659
  request: AuthenticatedWebSocketRequest,
@@ -395,12 +675,18 @@ export function handleChatConnection(
395
675
  const messageType = typeof data.type === 'string' ? data.type : '';
396
676
 
397
677
  switch (messageType) {
678
+ case 'chat.edit-send':
679
+ await handleChatEditSend(ws, userId, data, dependencies);
680
+ return;
398
681
  case 'chat.send':
399
682
  await handleChatSend(ws, userId, data, dependencies);
400
683
  return;
401
684
  case 'chat.abort':
402
685
  await handleChatAbort(ws, data, dependencies);
403
686
  return;
687
+ case 'chat.stop-task':
688
+ await handleChatStopTask(ws, data, dependencies);
689
+ return;
404
690
  case 'chat.subscribe':
405
691
  handleChatSubscribe(ws, data, dependencies);
406
692
  return;
@@ -0,0 +1,108 @@
1
+ import path from 'node:path';
2
+
3
+ import { projectsDb, sessionsDb } from '@/modules/database/index.js';
4
+ import { generateDisplayName } from '@/modules/projects/index.js';
5
+ import { connectedClients, WS_OPEN_STATE } from '@/modules/websocket/services/websocket-state.service.js';
6
+ import type { SessionUpsertedEvent } from '@/shared/types.js';
7
+
8
+ /**
9
+ * The single producer of the `session_upserted` delta.
10
+ *
11
+ * The event carries everything a sidebar needs to upsert a session in place
12
+ * (its summary plus the owning project's metadata), so clients never refetch
13
+ * the whole project list because one transcript changed on disk.
14
+ *
15
+ * It used to be built in two places — the on-disk sessions watcher and the
16
+ * chat run registry — and only the registry's copy set `providerSessionId`.
17
+ * That field is how the client notices a row it is showing has been merged
18
+ * into its canonical app-session row, so a merge announced by any other path
19
+ * left a stale duplicate in the sidebar. One builder, one shape.
20
+ */
21
+ async function buildSessionUpsertedEvent(
22
+ sessionIdOrProviderSessionId: string,
23
+ ): Promise<SessionUpsertedEvent | null> {
24
+ // Resolving by provider id first covers the watcher, which only ever sees
25
+ // the id written in the transcript. For a row where the two ids are equal
26
+ // — legacy rows, and any session indexed from disk before its mapping
27
+ // existed — both lookups land on the same row anyway.
28
+ const row = sessionsDb.getSessionByProviderSessionId(sessionIdOrProviderSessionId)
29
+ ?? sessionsDb.getSessionById(sessionIdOrProviderSessionId);
30
+ if (!row || row.isArchived) {
31
+ return null;
32
+ }
33
+
34
+ const projectPath = row.project_path;
35
+ const project = projectPath ? projectsDb.getProjectPath(projectPath) : null;
36
+ const displayName = project?.custom_project_name?.trim()
37
+ ? project.custom_project_name
38
+ : await generateDisplayName(path.basename(projectPath ?? '') || (projectPath ?? ''), projectPath);
39
+
40
+ return {
41
+ kind: 'session_upserted',
42
+ sessionId: row.session_id,
43
+ providerSessionId: row.provider_session_id ?? null,
44
+ // The column is a plain TEXT and typed as `string` on the row, but only
45
+ // provider adapters ever write it.
46
+ provider: row.provider as SessionUpsertedEvent['provider'],
47
+ session: {
48
+ id: row.session_id,
49
+ summary: row.custom_name || '',
50
+ messageCount: 0,
51
+ lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
52
+ },
53
+ project: project
54
+ ? {
55
+ projectId: project.project_id,
56
+ path: project.project_path,
57
+ fullPath: project.project_path,
58
+ displayName,
59
+ isStarred: Boolean(project.isStarred),
60
+ }
61
+ : null,
62
+ timestamp: new Date().toISOString(),
63
+ };
64
+ }
65
+
66
+ function sendToConnectedClients(payloads: string[]): void {
67
+ if (payloads.length === 0) {
68
+ return;
69
+ }
70
+
71
+ connectedClients.forEach((client) => {
72
+ if (client.readyState === WS_OPEN_STATE) {
73
+ for (const payload of payloads) {
74
+ client.send(payload);
75
+ }
76
+ }
77
+ });
78
+ }
79
+
80
+ /** Announces one session. Used by the chat run registry when a run reports its provider-native id. */
81
+ export async function broadcastSessionUpserted(sessionIdOrProviderSessionId: string): Promise<void> {
82
+ const event = await buildSessionUpsertedEvent(sessionIdOrProviderSessionId);
83
+ if (event) {
84
+ sendToConnectedClients([JSON.stringify(event)]);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Announces a batch of sessions. Used by the providers module's sessions
90
+ * watcher, whose debounced flush can carry dozens of ids at once — the client
91
+ * set is walked once for the whole batch rather than once per session.
92
+ */
93
+ export async function broadcastSessionUpsertedBatch(
94
+ sessionIds: Iterable<string>,
95
+ ): Promise<void> {
96
+ const payloads: string[] = [];
97
+ for (const sessionId of sessionIds) {
98
+ const event = await buildSessionUpsertedEvent(sessionId);
99
+ if (event) {
100
+ payloads.push(JSON.stringify(event));
101
+ }
102
+ }
103
+
104
+ sendToConnectedClients(payloads);
105
+ }
106
+
107
+ /** @internal Exported for the broadcast tests, which assert the payload shape directly. */
108
+ export { buildSessionUpsertedEvent };