@cloudcli-ai/cloudcli 1.37.2 → 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 (328) hide show
  1. package/README.md +269 -0
  2. package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-CJntskwA.js} +1 -1
  3. package/dist/assets/{arc-B8jF-ja9.js → arc-CIfH4uMN.js} +1 -1
  4. package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-_IpEkukg.js} +1 -1
  5. package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-BttxrpgZ.js} +1 -1
  6. package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-g51dSRr1.js} +1 -1
  7. package/dist/assets/channel-C5xU9jMw.js +1 -0
  8. package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-D_-LbQUa.js} +1 -1
  9. package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-BtlHzwaO.js} +1 -1
  10. package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-UzkpvpAT.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-BKwx5Gag.js} +1 -1
  12. package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-D_KEBH5f.js} +1 -1
  13. package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-rK2DGyto.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CeBl_N9i.js} +1 -1
  15. package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-BiQLWVIr.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-C1knVGV5.js} +1 -1
  17. package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-CtKbJkvF.js} +1 -1
  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-s1hygY8e.js → cose-bilkent-JH36ORCC-DjX8y40Z.js} +1 -1
  21. package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-DM6YeRba.js} +1 -1
  22. package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-wZJ-vy5B.js} +1 -1
  23. package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-BzP0ejkH.js} +1 -1
  24. package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-BTL_j23J.js} +1 -1
  25. package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-Cf1Norgu.js} +1 -1
  26. package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-DPiUEKr-.js} +1 -1
  27. package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-CTcTpiz5.js} +1 -1
  28. package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-DPMSdYm0.js} +1 -1
  29. package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-7yvD6uiF.js} +1 -1
  30. package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CfgeDoV6.js} +1 -1
  31. package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-B27ng5zu.js} +1 -1
  32. package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-CcC6b_7t.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-BX5kPGrO.js} +1 -1
  34. package/dist/assets/index-BTDHQ6Id.css +32 -0
  35. package/dist/assets/index-sFfz8iBR.js +4931 -0
  36. package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-CUP-SlN4.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-BTTDhOpX.js} +1 -1
  38. package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-ARXcc61m.js} +1 -1
  39. package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-BieMpqMA.js} +1 -1
  40. package/dist/assets/{linear-iPR-J9f7.js → linear-61vxjRnP.js} +1 -1
  41. package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-nhnwkfQx.js} +5 -5
  42. package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-Cm6x5vT1.js} +1 -1
  43. package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-CcYBq_9H.js} +1 -1
  44. package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-CfvIjfzo.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-SgYoWPNb.js} +1 -1
  46. package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-D2Mu5E_f.js} +1 -1
  47. package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Azwe_k4Q.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-DN1Ju3PA.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-Bj4qC34S.js} +1 -1
  50. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  51. package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-Co4PdklG.js} +1 -1
  52. package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-nOvL9dRC.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
  54. package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-CCK3wMid.js} +2 -2
  55. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
  56. package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-BC29RNFm.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-pZlZCtEo.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-De8k2-zM.js} +1 -1
  60. package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-Bk8ykH0w.js} +1 -1
  61. package/dist/index.html +3 -3
  62. package/dist-server/server/index.js +7 -3
  63. package/dist-server/server/index.js.map +1 -1
  64. package/dist-server/server/modules/agent/agent.routes.js +2 -2
  65. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  66. package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
  67. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  68. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  69. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  70. package/dist-server/server/modules/database/connection.js +1 -1
  71. package/dist-server/server/modules/database/connection.js.map +1 -1
  72. package/dist-server/server/modules/database/index.js +5 -0
  73. package/dist-server/server/modules/database/index.js.map +1 -1
  74. package/dist-server/server/modules/database/migrations.js +32 -1
  75. package/dist-server/server/modules/database/migrations.js.map +1 -1
  76. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  77. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  78. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  79. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  80. package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
  81. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  82. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  83. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  84. package/dist-server/server/modules/database/schema.js +92 -0
  85. package/dist-server/server/modules/database/schema.js.map +1 -1
  86. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  87. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  88. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  89. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  90. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  91. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  92. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  93. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  94. package/dist-server/server/modules/providers/index.js +3 -0
  95. package/dist-server/server/modules/providers/index.js.map +1 -1
  96. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
  97. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  98. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  99. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  100. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
  101. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  102. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +168 -38
  103. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  104. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
  105. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  106. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  107. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  108. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  109. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  110. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  111. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  112. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  113. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  114. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -0
  115. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  116. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +60 -49
  117. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  118. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  119. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  120. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
  121. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  122. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  123. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  124. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  125. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  126. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  127. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
  128. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  129. package/dist-server/server/modules/providers/provider.routes.js +24 -0
  130. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  131. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
  132. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  133. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  134. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  135. package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
  136. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  137. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  138. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  139. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  140. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  141. package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
  142. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  143. package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
  144. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  145. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  146. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  147. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
  148. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  149. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  150. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  151. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  152. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  153. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  154. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  155. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
  156. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  157. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  158. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  159. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
  160. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  161. package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
  162. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  163. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  164. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  165. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  166. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  167. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
  168. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  169. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  170. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  171. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  172. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  173. package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
  174. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  175. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  176. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  177. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  178. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  179. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  180. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  181. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
  182. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  183. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  184. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  185. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
  186. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  187. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  188. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  189. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  190. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  191. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  192. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  193. package/dist-server/server/modules/user/user.module.js +10 -1
  194. package/dist-server/server/modules/user/user.module.js.map +1 -1
  195. package/dist-server/server/modules/user/user.routes.js +44 -0
  196. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  197. package/dist-server/server/modules/user/user.service.js +75 -0
  198. package/dist-server/server/modules/user/user.service.js.map +1 -1
  199. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  200. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  201. package/dist-server/server/modules/websocket/index.js +7 -0
  202. package/dist-server/server/modules/websocket/index.js.map +1 -1
  203. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
  204. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  205. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  206. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  207. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
  208. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  209. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  210. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  211. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
  212. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  213. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  214. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  215. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  216. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  217. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  218. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  219. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
  220. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  221. package/dist-server/server/shared/claude-cli-path.js +22 -5
  222. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  223. package/dist-server/server/shared/index.js +3 -0
  224. package/dist-server/server/shared/index.js.map +1 -0
  225. package/dist-server/server/shared/message-unification.js +376 -0
  226. package/dist-server/server/shared/message-unification.js.map +1 -0
  227. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  228. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  229. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  230. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  231. package/dist-server/server/shared/utils.js +34 -0
  232. package/dist-server/server/shared/utils.js.map +1 -1
  233. package/package.json +19 -16
  234. package/server/index.ts +11 -4
  235. package/server/modules/agent/agent.routes.ts +2 -2
  236. package/server/modules/agent/tests/agent.routes.test.ts +33 -1
  237. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  238. package/server/modules/database/connection.ts +2 -1
  239. package/server/modules/database/index.ts +10 -0
  240. package/server/modules/database/migrations.ts +37 -0
  241. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  242. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  243. package/server/modules/database/repositories/sessions.db.ts +196 -3
  244. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  245. package/server/modules/database/schema.ts +96 -0
  246. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  247. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  248. package/server/modules/projects/services/project-delete.service.ts +13 -1
  249. package/server/modules/projects/services/project-star.service.ts +2 -2
  250. package/server/modules/providers/index.ts +4 -0
  251. package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
  252. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  253. package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
  254. package/server/modules/providers/list/claude/claude-runtime.provider.js +188 -39
  255. package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
  256. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  257. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  258. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  259. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  260. package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
  261. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
  262. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  263. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
  264. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  265. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  266. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  267. package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
  268. package/server/modules/providers/provider.routes.ts +31 -0
  269. package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
  270. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  271. package/server/modules/providers/services/session-history-cache.service.ts +139 -0
  272. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  273. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  274. package/server/modules/providers/services/sessions.service.ts +201 -9
  275. package/server/modules/providers/tests/claude-auth.test.ts +58 -0
  276. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  277. package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
  278. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  279. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  280. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  281. package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
  282. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  283. package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
  284. package/server/modules/providers/tests/opencode-models.test.ts +166 -8
  285. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  286. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  287. package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
  288. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  289. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  290. package/server/modules/providers/tests/sessions.service.test.ts +67 -1
  291. package/server/modules/providers/tests/skills.test.ts +21 -5
  292. package/server/modules/scheduled-messages/index.ts +8 -0
  293. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  294. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
  295. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  296. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
  297. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  298. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  299. package/server/modules/user/tests/user.service.test.ts +90 -0
  300. package/server/modules/user/user.module.ts +10 -1
  301. package/server/modules/user/user.routes.ts +44 -0
  302. package/server/modules/user/user.service.ts +105 -0
  303. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  304. package/server/modules/websocket/index.ts +8 -0
  305. package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
  306. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  307. package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
  308. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  309. package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
  310. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  311. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  312. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  313. package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
  314. package/server/shared/claude-cli-path.ts +24 -6
  315. package/server/shared/index.ts +3 -0
  316. package/server/shared/interfaces.ts +62 -0
  317. package/server/shared/message-unification.ts +449 -0
  318. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  319. package/server/shared/tests/message-unification.test.ts +218 -0
  320. package/server/shared/types.ts +123 -2
  321. package/server/shared/utils.ts +40 -0
  322. package/dist/assets/channel-_falYbU1.js +0 -1
  323. package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
  324. package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
  325. package/dist/assets/index-CzPLBvXT.css +0 -32
  326. package/dist/assets/index-DFkBBuSA.js +0 -1549
  327. package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
  328. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
@@ -0,0 +1,87 @@
1
+ import fsp from 'node:fs/promises';
2
+ /**
3
+ * A transcript entry's heap cost is roughly the file it was parsed from, so
4
+ * the budget is expressed in file bytes. The newest entry is always retained
5
+ * even when it alone exceeds the budget — evicting it would just re-parse the
6
+ * same file on the next request.
7
+ */
8
+ const MAX_CACHED_TRANSCRIPT_FILE_BYTES = 256 * 1024 * 1024;
9
+ const MAX_CACHE_ENTRIES = 8;
10
+ export function createSessionHistoryCache(maxTotalFileBytes = MAX_CACHED_TRANSCRIPT_FILE_BYTES, maxEntries = MAX_CACHE_ENTRIES) {
11
+ const entries = new Map();
12
+ const pendingLoads = new Map();
13
+ function evictOverBudget() {
14
+ let totalBytes = 0;
15
+ for (const entry of entries.values()) {
16
+ totalBytes += entry.size;
17
+ }
18
+ for (const key of entries.keys()) {
19
+ if (entries.size <= 1 || (totalBytes <= maxTotalFileBytes && entries.size <= maxEntries)) {
20
+ break;
21
+ }
22
+ totalBytes -= entries.get(key).size;
23
+ entries.delete(key);
24
+ }
25
+ }
26
+ return {
27
+ /**
28
+ * Returns the session's full transcript through the cache, or null when
29
+ * the session is not cacheable (no transcript path, or the file cannot be
30
+ * stat'ed) — the caller then falls back to a plain provider read.
31
+ */
32
+ async getFullHistory({ sessionId, transcriptPath, loadFull }) {
33
+ if (!transcriptPath) {
34
+ return null;
35
+ }
36
+ let stat;
37
+ try {
38
+ stat = await fsp.stat(transcriptPath);
39
+ }
40
+ catch {
41
+ entries.delete(sessionId);
42
+ return null;
43
+ }
44
+ if (!stat.isFile()) {
45
+ entries.delete(sessionId);
46
+ return null;
47
+ }
48
+ const cached = entries.get(sessionId);
49
+ if (cached
50
+ && cached.transcriptPath === transcriptPath
51
+ && cached.mtimeMs === stat.mtimeMs
52
+ && cached.size === stat.size) {
53
+ // Re-insert to mark as most recently used.
54
+ entries.delete(sessionId);
55
+ entries.set(sessionId, cached);
56
+ return cached.full;
57
+ }
58
+ // Concurrent requests for the same session share one parse. The file may
59
+ // gain rows while the load runs; the pre-load stat is what the entry is
60
+ // keyed by, so the next request would see a changed stat and re-read.
61
+ const pending = pendingLoads.get(sessionId);
62
+ if (pending) {
63
+ return pending;
64
+ }
65
+ const load = loadFull().then((full) => {
66
+ entries.delete(sessionId);
67
+ entries.set(sessionId, {
68
+ transcriptPath,
69
+ mtimeMs: stat.mtimeMs,
70
+ size: stat.size,
71
+ full,
72
+ });
73
+ evictOverBudget();
74
+ return full;
75
+ });
76
+ pendingLoads.set(sessionId, load);
77
+ try {
78
+ return await load;
79
+ }
80
+ finally {
81
+ pendingLoads.delete(sessionId);
82
+ }
83
+ },
84
+ };
85
+ }
86
+ export const sessionHistoryCache = createSessionHistoryCache();
87
+ //# sourceMappingURL=session-history-cache.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-history-cache.service.js","sourceRoot":"","sources":["../../../../../server/modules/providers/services/session-history-cache.service.ts"],"names":[],"mappings":"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"}
@@ -1,44 +1,117 @@
1
- import { scanStateDb } from '../../../modules/database/index.js';
1
+ import path from 'node:path';
2
+ import { access } from 'node:fs/promises';
3
+ import { scanStateDb, sessionsDb } from '../../../modules/database/index.js';
2
4
  import { providerRegistry } from '../../../modules/providers/provider.registry.js';
5
+ const pathExists = async (target) => {
6
+ try {
7
+ await access(target);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ };
14
+ /**
15
+ * Removes indexed sessions whose transcript file has disappeared from disk.
16
+ *
17
+ * Nothing else deletes these rows: the synchronizers only ever upsert, and the
18
+ * watcher reacts to `add`/`change` but not `unlink`. A transcript removed by
19
+ * hand — or written by a test run that pointed at the real `~/.claude` — left a
20
+ * permanent sidebar entry that opened an empty "Untitled" session.
21
+ *
22
+ * A row is only dropped when its *containing directory* still exists. That
23
+ * keeps an unmounted or not-yet-created home from being read as "every
24
+ * transcript was deleted" and wiping the whole index.
25
+ */
26
+ const pruneOrphanedSessions = async () => {
27
+ const knownDirectoryExists = new Map();
28
+ let pruned = 0;
29
+ for (const { session_id: sessionId, jsonl_path: jsonlPath } of sessionsDb.getSessionsWithTranscriptPath()) {
30
+ if (await pathExists(jsonlPath)) {
31
+ continue;
32
+ }
33
+ const directory = path.dirname(jsonlPath);
34
+ let directoryExists = knownDirectoryExists.get(directory);
35
+ if (directoryExists === undefined) {
36
+ directoryExists = await pathExists(directory);
37
+ knownDirectoryExists.set(directory, directoryExists);
38
+ }
39
+ if (!directoryExists) {
40
+ continue;
41
+ }
42
+ if (sessionsDb.deleteSessionById(sessionId)) {
43
+ pruned += 1;
44
+ }
45
+ }
46
+ return pruned;
47
+ };
48
+ /**
49
+ * The scan that every `synchronizeSessions()` caller shares while it runs.
50
+ *
51
+ * Opening the UI fires `/api/projects` and `/api/projects/archived` at once,
52
+ * and each used to start its own full provider scan from the same
53
+ * `last_scanned_at` cursor: identical work over identical transcripts,
54
+ * contending for the same synchronous SQLite writes and doubling how long the
55
+ * sidebar sits on its loading screen. Callers that arrive while a scan is
56
+ * already running now await that scan instead of starting another.
57
+ */
58
+ let inFlightSynchronization = null;
59
+ /**
60
+ * Runs all provider synchronizers and updates scan_state.last_scanned_at.
61
+ */
62
+ async function runSessionSynchronization() {
63
+ const lastScanAt = scanStateDb.getLastScannedAt();
64
+ const scanBoundary = new Date();
65
+ const processedByProvider = {
66
+ claude: 0,
67
+ codex: 0,
68
+ cursor: 0,
69
+ opencode: 0,
70
+ };
71
+ const failures = [];
72
+ const results = await Promise.allSettled(providerRegistry.listProviders().map(async (provider) => ({
73
+ provider: provider.id,
74
+ processed: await provider.sessionSynchronizer.synchronize(lastScanAt ?? undefined),
75
+ })));
76
+ for (const result of results) {
77
+ if (result.status === 'fulfilled') {
78
+ processedByProvider[result.value.provider] = result.value.processed;
79
+ continue;
80
+ }
81
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
82
+ failures.push(reason);
83
+ }
84
+ // Pruning is skipped after a partial sync: a provider that just failed may
85
+ // not have re-indexed transcripts it would otherwise have re-created.
86
+ const prunedOrphans = failures.length === 0 ? await pruneOrphanedSessions() : 0;
87
+ if (failures.length === 0) {
88
+ scanStateDb.updateLastScannedAt(scanBoundary);
89
+ }
90
+ else {
91
+ console.warn(`[Sessions] Skipping scan_state cursor advance because ${failures.length} provider sync(s) failed.`);
92
+ }
93
+ return {
94
+ processedByProvider,
95
+ prunedOrphans,
96
+ failures,
97
+ };
98
+ }
3
99
  /**
4
100
  * Orchestrates provider-specific session indexers and indexed-session lifecycle operations.
5
101
  */
6
102
  export const sessionSynchronizerService = {
7
103
  /**
8
- * Runs all provider synchronizers and updates scan_state.last_scanned_at.
104
+ * Scans every provider for new or changed sessions, coalescing concurrent
105
+ * callers onto a single scan.
9
106
  */
10
107
  async synchronizeSessions() {
11
- const lastScanAt = scanStateDb.getLastScannedAt();
12
- const scanBoundary = new Date();
13
- const processedByProvider = {
14
- claude: 0,
15
- codex: 0,
16
- cursor: 0,
17
- opencode: 0,
18
- };
19
- const failures = [];
20
- const results = await Promise.allSettled(providerRegistry.listProviders().map(async (provider) => ({
21
- provider: provider.id,
22
- processed: await provider.sessionSynchronizer.synchronize(lastScanAt ?? undefined),
23
- })));
24
- for (const result of results) {
25
- if (result.status === 'fulfilled') {
26
- processedByProvider[result.value.provider] = result.value.processed;
27
- continue;
28
- }
29
- const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
30
- failures.push(reason);
108
+ if (inFlightSynchronization) {
109
+ return inFlightSynchronization;
31
110
  }
32
- if (failures.length === 0) {
33
- scanStateDb.updateLastScannedAt(scanBoundary);
34
- }
35
- else {
36
- console.warn(`[Sessions] Skipping scan_state cursor advance because ${failures.length} provider sync(s) failed.`);
37
- }
38
- return {
39
- processedByProvider,
40
- failures,
41
- };
111
+ inFlightSynchronization = runSessionSynchronization().finally(() => {
112
+ inFlightSynchronization = null;
113
+ });
114
+ return inFlightSynchronization;
42
115
  },
43
116
  /**
44
117
  * Indexes one provider artifact file without running a full provider rescan.
@@ -1 +1 @@
1
- {"version":3,"file":"session-synchronizer.service.js","sourceRoot":"","sources":["../../../../../server/modules/providers/services/session-synchronizer.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAQ5E;;GAEG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC;;OAEG;IACH,KAAK,CAAC,mBAAmB;QACvB,MAAM,UAAU,GAAG,WAAW,CAAC,gBAAgB,EAAE,CAAC;QAClD,MAAM,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC;QAChC,MAAM,mBAAmB,GAAgC;YACvD,MAAM,EAAE,CAAC;YACT,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,CAAC;YACT,QAAQ,EAAE,CAAC;SACZ,CAAC;QACF,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,gBAAgB,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;YACxD,QAAQ,EAAE,QAAQ,CAAC,EAAE;YACrB,SAAS,EAAE,MAAM,QAAQ,CAAC,mBAAmB,CAAC,WAAW,CAAC,UAAU,IAAI,SAAS,CAAC;SACnF,CAAC,CAAC,CACJ,CAAC;QAEF,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC;gBACpE,SAAS;YACX,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC9F,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,WAAW,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC;QAChD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CACV,yDAAyD,QAAQ,CAAC,MAAM,2BAA2B,CACpG,CAAC;QACJ,CAAC;QAED,OAAO;YACL,mBAAmB;YACnB,QAAQ;SACT,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,uBAAuB,CAC3B,QAAqB,EACrB,QAAgB;QAEhB,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,mBAAmB,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QACvF,OAAO;YACL,QAAQ;YACR,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC;YAC3B,SAAS;SACV,CAAC;IACJ,CAAC;CACF,CAAC"}
1
+ {"version":3,"file":"session-synchronizer.service.js","sourceRoot":"","sources":["../../../../../server/modules/providers/services/session-synchronizer.service.ts"],"names":[],"mappings":"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"}
@@ -2,10 +2,8 @@ import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  import { promises as fsPromises } from 'node:fs';
4
4
  import chokidar from 'chokidar';
5
- import { projectsDb, sessionsDb } from '../../../modules/database/index.js';
6
5
  import { sessionSynchronizerService } from '../../../modules/providers/services/session-synchronizer.service.js';
7
- import { WS_OPEN_STATE, connectedClients } from '../../../modules/websocket/index.js';
8
- import { generateDisplayName } from '../../../modules/projects/index.js';
6
+ import { broadcastSessionUpsertedBatch } from '../../../modules/websocket/index.js';
9
7
  const PROVIDER_WATCH_PATHS = [
10
8
  {
11
9
  provider: 'claude',
@@ -89,47 +87,6 @@ function queuePendingWatcherUpdate(eventType, provider, updatedSessionId) {
89
87
  }
90
88
  schedulePendingWatcherFlush();
91
89
  }
92
- /**
93
- * Builds one `session_upserted` delta event for a provider-native session id.
94
- *
95
- * The event carries everything a sidebar needs to upsert the session in place
96
- * (session summary plus owning-project metadata), so clients never need a full
97
- * project-list refetch when a transcript file changes on disk. Returns `null`
98
- * when the id cannot be resolved to an indexed session row.
99
- */
100
- async function buildSessionUpsertedEvent(updatedProviderSessionId) {
101
- const row = sessionsDb.getSessionByProviderSessionId(updatedProviderSessionId)
102
- ?? sessionsDb.getSessionById(updatedProviderSessionId);
103
- if (!row || row.isArchived) {
104
- return null;
105
- }
106
- const projectPath = row.project_path;
107
- const project = projectPath ? projectsDb.getProjectPath(projectPath) : null;
108
- const displayName = project?.custom_project_name?.trim()
109
- ? project.custom_project_name
110
- : await generateDisplayName(path.basename(projectPath ?? '') || (projectPath ?? ''), projectPath);
111
- return JSON.stringify({
112
- kind: 'session_upserted',
113
- sessionId: row.session_id,
114
- provider: row.provider,
115
- session: {
116
- id: row.session_id,
117
- summary: row.custom_name || '',
118
- messageCount: 0,
119
- lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
120
- },
121
- project: project
122
- ? {
123
- projectId: project.project_id,
124
- path: project.project_path,
125
- fullPath: project.project_path,
126
- displayName,
127
- isStarred: Boolean(project.isStarred),
128
- }
129
- : null,
130
- timestamp: new Date().toISOString(),
131
- });
132
- }
133
90
  async function flushPendingWatcherUpdate() {
134
91
  clearPendingWatcherFlushTimer();
135
92
  if (!pendingWatcherUpdate) {
@@ -147,22 +104,7 @@ async function flushPendingWatcherUpdate() {
147
104
  // Per-session deltas instead of full project snapshots: an upsert of one
148
105
  // session can never clobber unrelated client state, so the frontend needs
149
106
  // no "suppress updates while a run is active" protection logic.
150
- const events = [];
151
- for (const updatedSessionId of queuedUpdate.updatedSessionIds) {
152
- const event = await buildSessionUpsertedEvent(updatedSessionId);
153
- if (event) {
154
- events.push(event);
155
- }
156
- }
157
- if (events.length > 0) {
158
- connectedClients.forEach(client => {
159
- if (client.readyState === WS_OPEN_STATE) {
160
- for (const event of events) {
161
- client.send(event);
162
- }
163
- }
164
- });
165
- }
107
+ await broadcastSessionUpsertedBatch(queuedUpdate.updatedSessionIds);
166
108
  }
167
109
  catch (error) {
168
110
  const message = error instanceof Error ? error.message : String(error);
@@ -211,6 +153,7 @@ export async function initializeSessionsWatcher() {
211
153
  const initialSync = await sessionSynchronizerService.synchronizeSessions();
212
154
  console.log('Initial session synchronization complete', {
213
155
  processedByProvider: initialSync.processedByProvider,
156
+ prunedOrphans: initialSync.prunedOrphans,
214
157
  failures: initialSync.failures,
215
158
  });
216
159
  for (const { provider, rootPath } of PROVIDER_WATCH_PATHS) {
@@ -1 +1 @@
1
- {"version":3,"file":"sessions-watcher.service.js","sourceRoot":"","sources":["../../../../../server/modules/providers/services/sessions-watcher.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sessions-watcher.service.js","sourceRoot":"","sources":["../../../../../server/modules/providers/services/sessions-watcher.service.ts"],"names":[],"mappings":"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"}
@@ -2,9 +2,10 @@ import { randomUUID } from 'node:crypto';
2
2
  import fsp from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { projectsDb, sessionsDb } from '../../../modules/database/index.js';
5
- import { chatRunRegistry } from '../../../modules/websocket/index.js';
5
+ import { broadcastSessionUpserted, chatRunRegistry } from '../../../modules/websocket/index.js';
6
6
  import { providerRegistry } from '../../../modules/providers/provider.registry.js';
7
- import { AppError } from '../../../shared/utils.js';
7
+ import { sessionHistoryCache } from '../../../modules/providers/services/session-history-cache.service.js';
8
+ import { AppError, sliceTailPage } from '../../../shared/utils.js';
8
9
  const MAX_CLOUDCLI_SESSION_NAME_WORDS = 4;
9
10
  function buildCloudCliSessionName(initialMessage) {
10
11
  const words = initialMessage.trim().split(/\s+/).filter(Boolean);
@@ -145,6 +146,68 @@ export const sessionsService = {
145
146
  sessionName,
146
147
  };
147
148
  },
149
+ /**
150
+ * Branches a session into an independent one containing its conversation up
151
+ * to `upToAnchorId` (the whole thing when omitted).
152
+ *
153
+ * The source is left completely untouched — this is the "try two approaches"
154
+ * action, not a destructive one.
155
+ */
156
+ async forkSessionById(sessionId, options = {}) {
157
+ const source = sessionsDb.getSessionById(sessionId);
158
+ if (!source) {
159
+ throw new AppError(`Session "${sessionId}" was not found.`, {
160
+ code: 'SESSION_NOT_FOUND',
161
+ statusCode: 404,
162
+ });
163
+ }
164
+ const provider = source.provider;
165
+ const fork = providerRegistry.resolveProvider(provider).fork;
166
+ if (!fork) {
167
+ throw new AppError(`Sessions cannot be forked for provider "${provider}".`, {
168
+ code: 'FORK_NOT_SUPPORTED',
169
+ statusCode: 409,
170
+ });
171
+ }
172
+ // A session that has never run has no transcript to copy, so there is
173
+ // nothing a fork of it could resume from.
174
+ if (!source.provider_session_id || !source.jsonl_path) {
175
+ throw new AppError('This session has not produced a transcript yet.', {
176
+ code: 'FORK_SOURCE_NOT_READY',
177
+ statusCode: 409,
178
+ });
179
+ }
180
+ const sessionName = options.title?.trim()
181
+ || `${source.custom_name?.trim() || 'Session'} (fork)`;
182
+ const forked = await fork.forkSession({
183
+ providerSessionId: source.provider_session_id,
184
+ jsonlPath: source.jsonl_path,
185
+ projectPath: source.project_path ?? '',
186
+ upToAnchorId: options.upToAnchorId,
187
+ title: sessionName,
188
+ });
189
+ const forkSessionId = randomUUID();
190
+ sessionsDb.createForkedSession({
191
+ sessionId: forkSessionId,
192
+ provider,
193
+ projectPath: source.project_path ?? '',
194
+ customName: sessionName,
195
+ providerSessionId: forked.providerSessionId,
196
+ jsonlPath: forked.jsonlPath,
197
+ forkedFromSessionId: sessionId,
198
+ // A fork that silently dropped to the catalog default would answer
199
+ // differently from the conversation it was branched from.
200
+ model: source.model,
201
+ effort: source.effort,
202
+ });
203
+ await broadcastSessionUpserted(forkSessionId);
204
+ return {
205
+ sessionId: forkSessionId,
206
+ provider,
207
+ projectPath: source.project_path ?? '',
208
+ sessionName,
209
+ };
210
+ },
148
211
  /**
149
212
  * Resolves the provider-native id only for an explicit user copy action.
150
213
  * Normal session payloads continue to expose only the stable app id.
@@ -174,6 +237,63 @@ export const sessionsService = {
174
237
  * and every returned message is remapped back to the app session id so
175
238
  * provider ids never reach the frontend.
176
239
  */
240
+ /**
241
+ * Resolves where a conversation must resume from so that one already-sent
242
+ * message, and everything after it, is replaced.
243
+ *
244
+ * Returns `null` when the provider cannot do this at all, which is how the
245
+ * chat gateway knows to refuse the request rather than silently sending the
246
+ * edit as a new message at the end of the conversation.
247
+ */
248
+ async resolveEditAnchor(sessionId, anchorId) {
249
+ const session = sessionsDb.getSessionById(sessionId);
250
+ if (!session) {
251
+ throw new AppError(`Session "${sessionId}" was not found.`, {
252
+ code: 'SESSION_NOT_FOUND',
253
+ statusCode: 404,
254
+ });
255
+ }
256
+ const sessions = providerRegistry.resolveProvider(session.provider).sessions;
257
+ if (!sessions.resolveEditAnchor) {
258
+ return null;
259
+ }
260
+ return sessions.resolveEditAnchor(sessionId, anchorId);
261
+ },
262
+ /**
263
+ * Whether editing a message on this session's provider means rewinding it on
264
+ * disk first, rather than handing the anchor to the runtime as a resume
265
+ * option.
266
+ *
267
+ * Answering this without doing anything is the point: the rewind moves the
268
+ * session onto a different provider transcript and cannot be undone, so the
269
+ * gateway has to know which shape the run takes before it commits to one.
270
+ */
271
+ providerRewindsForEdit(sessionId) {
272
+ const session = sessionsDb.getSessionById(sessionId);
273
+ if (!session) {
274
+ throw new AppError(`Session "${sessionId}" was not found.`, {
275
+ code: 'SESSION_NOT_FOUND',
276
+ statusCode: 404,
277
+ });
278
+ }
279
+ return Boolean(providerRegistry.resolveProvider(session.provider).sessions.rewindSession);
280
+ },
281
+ /**
282
+ * Rewinds a session on disk so `keepThroughId` is the last row it holds.
283
+ *
284
+ * Only call this once the run is admitted — see `providerRewindsForEdit`.
285
+ */
286
+ async rewindSessionForEdit(sessionId, keepThroughId) {
287
+ const session = sessionsDb.getSessionById(sessionId);
288
+ if (!session) {
289
+ throw new AppError(`Session "${sessionId}" was not found.`, {
290
+ code: 'SESSION_NOT_FOUND',
291
+ statusCode: 404,
292
+ });
293
+ }
294
+ const sessions = providerRegistry.resolveProvider(session.provider).sessions;
295
+ await sessions.rewindSession?.(sessionId, keepThroughId);
296
+ },
177
297
  async fetchHistory(sessionId, options = {}) {
178
298
  const session = sessionsDb.getSessionById(sessionId);
179
299
  if (!session) {
@@ -194,12 +314,50 @@ export const sessionsService = {
194
314
  };
195
315
  }
196
316
  const provider = session.provider;
197
- const result = await providerRegistry.resolveProvider(provider).sessions.fetchHistory(sessionId, {
198
- limit: options.limit ?? null,
199
- offset: options.offset ?? 0,
200
- projectPath: session.project_path ?? '',
201
- providerSessionId: session.provider_session_id,
317
+ const providerSessions = providerRegistry.resolveProvider(provider).sessions;
318
+ const providerSessionId = session.provider_session_id;
319
+ const projectPath = session.project_path ?? '';
320
+ const requestedLimit = options.limit ?? null;
321
+ const requestedOffset = options.offset ?? 0;
322
+ // Claude and Codex history readers parse `jsonl_path` itself, so a page
323
+ // can be sliced from the stat-validated full-transcript cache instead of
324
+ // re-parsing the whole file per request. Cursor and OpenCode read their
325
+ // messages from elsewhere (store.db / shared SQLite), so that file's stat
326
+ // says nothing about their history — they stay on the direct path.
327
+ const transcriptPath = provider === 'claude' || provider === 'codex'
328
+ ? session.jsonl_path
329
+ : null;
330
+ const fullHistory = await sessionHistoryCache.getFullHistory({
331
+ sessionId,
332
+ transcriptPath,
333
+ loadFull: () => providerSessions.fetchHistory(sessionId, {
334
+ limit: null,
335
+ offset: 0,
336
+ projectPath,
337
+ providerSessionId,
338
+ }),
202
339
  });
340
+ let result;
341
+ if (fullHistory) {
342
+ // Providers slice with this same helper, so a cached page is identical
343
+ // to what a direct `(limit, offset)` read would have returned.
344
+ const { page, hasMore } = sliceTailPage(fullHistory.messages, requestedLimit, Math.max(0, requestedOffset));
345
+ result = {
346
+ ...fullHistory,
347
+ messages: page,
348
+ hasMore,
349
+ offset: requestedOffset,
350
+ limit: requestedLimit,
351
+ };
352
+ }
353
+ else {
354
+ result = await providerSessions.fetchHistory(sessionId, {
355
+ limit: requestedLimit,
356
+ offset: requestedOffset,
357
+ projectPath,
358
+ providerSessionId,
359
+ });
360
+ }
203
361
  return {
204
362
  ...result,
205
363
  messages: result.messages.map((message) => ({
@@ -301,9 +459,21 @@ export const sessionsService = {
301
459
  };
302
460
  }
303
461
  let removedFromDisk = false;
304
- if (options.deletedFromDisk && session.jsonl_path) {
305
- removedFromDisk = await removeFileIfExists(session.jsonl_path);
462
+ if (options.deletedFromDisk) {
463
+ // Every file the conversation has lived in, not just the one the row
464
+ // points at now: editing a message on a provider that rewinds by
465
+ // branching moves the session onto a copy and leaves the earlier
466
+ // transcript behind. Deleting only the current one would leave the
467
+ // replaced turns on disk, and unreachable through the app.
468
+ const transcripts = [
469
+ ...(session.jsonl_path ? [session.jsonl_path] : []),
470
+ ...sessionsDb.getSupersededTranscriptPaths(sessionId),
471
+ ];
472
+ for (const transcript of transcripts) {
473
+ removedFromDisk = (await removeFileIfExists(transcript)) || removedFromDisk;
474
+ }
306
475
  }
476
+ sessionsDb.clearSupersededProviderSessions(sessionId);
307
477
  const deleted = sessionsDb.deleteSessionById(sessionId);
308
478
  if (!deleted) {
309
479
  throw new AppError(`Session "${sessionId}" was not found.`, {