@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
@@ -1,59 +1,146 @@
1
- import { scanStateDb } from '@/modules/database/index.js';
1
+ import path from 'node:path';
2
+ import { access } from 'node:fs/promises';
3
+
4
+ import { scanStateDb, sessionsDb } from '@/modules/database/index.js';
2
5
  import { providerRegistry } from '@/modules/providers/provider.registry.js';
3
6
  import type { LLMProvider } from '@/shared/types.js';
4
7
 
5
8
  type SessionSynchronizeResult = {
6
9
  processedByProvider: Record<LLMProvider, number>;
10
+ /** Indexed sessions dropped because their transcript file no longer exists. */
11
+ prunedOrphans: number;
7
12
  failures: string[];
8
13
  };
9
14
 
15
+ const pathExists = async (target: string): Promise<boolean> => {
16
+ try {
17
+ await access(target);
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ };
23
+
24
+ /**
25
+ * Removes indexed sessions whose transcript file has disappeared from disk.
26
+ *
27
+ * Nothing else deletes these rows: the synchronizers only ever upsert, and the
28
+ * watcher reacts to `add`/`change` but not `unlink`. A transcript removed by
29
+ * hand — or written by a test run that pointed at the real `~/.claude` — left a
30
+ * permanent sidebar entry that opened an empty "Untitled" session.
31
+ *
32
+ * A row is only dropped when its *containing directory* still exists. That
33
+ * keeps an unmounted or not-yet-created home from being read as "every
34
+ * transcript was deleted" and wiping the whole index.
35
+ */
36
+ const pruneOrphanedSessions = async (): Promise<number> => {
37
+ const knownDirectoryExists = new Map<string, boolean>();
38
+ let pruned = 0;
39
+
40
+ for (const { session_id: sessionId, jsonl_path: jsonlPath } of sessionsDb.getSessionsWithTranscriptPath()) {
41
+ if (await pathExists(jsonlPath)) {
42
+ continue;
43
+ }
44
+
45
+ const directory = path.dirname(jsonlPath);
46
+ let directoryExists = knownDirectoryExists.get(directory);
47
+ if (directoryExists === undefined) {
48
+ directoryExists = await pathExists(directory);
49
+ knownDirectoryExists.set(directory, directoryExists);
50
+ }
51
+
52
+ if (!directoryExists) {
53
+ continue;
54
+ }
55
+
56
+ if (sessionsDb.deleteSessionById(sessionId)) {
57
+ pruned += 1;
58
+ }
59
+ }
60
+
61
+ return pruned;
62
+ };
63
+
64
+ /**
65
+ * The scan that every `synchronizeSessions()` caller shares while it runs.
66
+ *
67
+ * Opening the UI fires `/api/projects` and `/api/projects/archived` at once,
68
+ * and each used to start its own full provider scan from the same
69
+ * `last_scanned_at` cursor: identical work over identical transcripts,
70
+ * contending for the same synchronous SQLite writes and doubling how long the
71
+ * sidebar sits on its loading screen. Callers that arrive while a scan is
72
+ * already running now await that scan instead of starting another.
73
+ */
74
+ let inFlightSynchronization: Promise<SessionSynchronizeResult> | null = null;
75
+
76
+ /**
77
+ * Runs all provider synchronizers and updates scan_state.last_scanned_at.
78
+ */
79
+ async function runSessionSynchronization(): Promise<SessionSynchronizeResult> {
80
+ const lastScanAt = scanStateDb.getLastScannedAt();
81
+ const scanBoundary = new Date();
82
+ const processedByProvider: Record<LLMProvider, number> = {
83
+ claude: 0,
84
+ codex: 0,
85
+ cursor: 0,
86
+ opencode: 0,
87
+ };
88
+ const failures: string[] = [];
89
+
90
+ const results = await Promise.allSettled(
91
+ providerRegistry.listProviders().map(async (provider) => ({
92
+ provider: provider.id,
93
+ processed: await provider.sessionSynchronizer.synchronize(lastScanAt ?? undefined),
94
+ }))
95
+ );
96
+
97
+ for (const result of results) {
98
+ if (result.status === 'fulfilled') {
99
+ processedByProvider[result.value.provider] = result.value.processed;
100
+ continue;
101
+ }
102
+
103
+ const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
104
+ failures.push(reason);
105
+ }
106
+
107
+ // Pruning is skipped after a partial sync: a provider that just failed may
108
+ // not have re-indexed transcripts it would otherwise have re-created.
109
+ const prunedOrphans = failures.length === 0 ? await pruneOrphanedSessions() : 0;
110
+
111
+ if (failures.length === 0) {
112
+ scanStateDb.updateLastScannedAt(scanBoundary);
113
+ } else {
114
+ console.warn(
115
+ `[Sessions] Skipping scan_state cursor advance because ${failures.length} provider sync(s) failed.`,
116
+ );
117
+ }
118
+
119
+ return {
120
+ processedByProvider,
121
+ prunedOrphans,
122
+ failures,
123
+ };
124
+ }
125
+
10
126
  /**
11
127
  * Orchestrates provider-specific session indexers and indexed-session lifecycle operations.
12
128
  */
13
129
  export const sessionSynchronizerService = {
14
130
  /**
15
- * Runs all provider synchronizers and updates scan_state.last_scanned_at.
131
+ * Scans every provider for new or changed sessions, coalescing concurrent
132
+ * callers onto a single scan.
16
133
  */
17
134
  async synchronizeSessions(): Promise<SessionSynchronizeResult> {
18
- const lastScanAt = scanStateDb.getLastScannedAt();
19
- const scanBoundary = new Date();
20
- const processedByProvider: Record<LLMProvider, number> = {
21
- claude: 0,
22
- codex: 0,
23
- cursor: 0,
24
- opencode: 0,
25
- };
26
- const failures: string[] = [];
27
-
28
- const results = await Promise.allSettled(
29
- providerRegistry.listProviders().map(async (provider) => ({
30
- provider: provider.id,
31
- processed: await provider.sessionSynchronizer.synchronize(lastScanAt ?? undefined),
32
- }))
33
- );
34
-
35
- for (const result of results) {
36
- if (result.status === 'fulfilled') {
37
- processedByProvider[result.value.provider] = result.value.processed;
38
- continue;
39
- }
40
-
41
- const reason = result.reason instanceof Error ? result.reason.message : String(result.reason);
42
- failures.push(reason);
135
+ if (inFlightSynchronization) {
136
+ return inFlightSynchronization;
43
137
  }
44
138
 
45
- if (failures.length === 0) {
46
- scanStateDb.updateLastScannedAt(scanBoundary);
47
- } else {
48
- console.warn(
49
- `[Sessions] Skipping scan_state cursor advance because ${failures.length} provider sync(s) failed.`,
50
- );
51
- }
139
+ inFlightSynchronization = runSessionSynchronization().finally(() => {
140
+ inFlightSynchronization = null;
141
+ });
52
142
 
53
- return {
54
- processedByProvider,
55
- failures,
56
- };
143
+ return inFlightSynchronization;
57
144
  },
58
145
 
59
146
  /**
@@ -4,11 +4,9 @@ import { promises as fsPromises } from 'node:fs';
4
4
 
5
5
  import chokidar, { type FSWatcher } from 'chokidar';
6
6
 
7
- import { projectsDb, sessionsDb } from '@/modules/database/index.js';
8
7
  import { sessionSynchronizerService } from '@/modules/providers/services/session-synchronizer.service.js';
9
- import { WS_OPEN_STATE, connectedClients } from '@/modules/websocket/index.js';
8
+ import { broadcastSessionUpsertedBatch } from '@/modules/websocket/index.js';
10
9
  import type { LLMProvider } from '@/shared/types.js';
11
- import { generateDisplayName } from '@/modules/projects/index.js';
12
10
 
13
11
  type WatcherEventType = 'add' | 'change';
14
12
 
@@ -125,50 +123,6 @@ function queuePendingWatcherUpdate(
125
123
  schedulePendingWatcherFlush();
126
124
  }
127
125
 
128
- /**
129
- * Builds one `session_upserted` delta event for a provider-native session id.
130
- *
131
- * The event carries everything a sidebar needs to upsert the session in place
132
- * (session summary plus owning-project metadata), so clients never need a full
133
- * project-list refetch when a transcript file changes on disk. Returns `null`
134
- * when the id cannot be resolved to an indexed session row.
135
- */
136
- async function buildSessionUpsertedEvent(updatedProviderSessionId: string): Promise<string | null> {
137
- const row = sessionsDb.getSessionByProviderSessionId(updatedProviderSessionId)
138
- ?? sessionsDb.getSessionById(updatedProviderSessionId);
139
- if (!row || row.isArchived) {
140
- return null;
141
- }
142
-
143
- const projectPath = row.project_path;
144
- const project = projectPath ? projectsDb.getProjectPath(projectPath) : null;
145
- const displayName = project?.custom_project_name?.trim()
146
- ? project.custom_project_name
147
- : await generateDisplayName(path.basename(projectPath ?? '') || (projectPath ?? ''), projectPath);
148
-
149
- return JSON.stringify({
150
- kind: 'session_upserted',
151
- sessionId: row.session_id,
152
- provider: row.provider,
153
- session: {
154
- id: row.session_id,
155
- summary: row.custom_name || '',
156
- messageCount: 0,
157
- lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
158
- },
159
- project: project
160
- ? {
161
- projectId: project.project_id,
162
- path: project.project_path,
163
- fullPath: project.project_path,
164
- displayName,
165
- isStarred: Boolean(project.isStarred),
166
- }
167
- : null,
168
- timestamp: new Date().toISOString(),
169
- });
170
- }
171
-
172
126
  async function flushPendingWatcherUpdate(): Promise<void> {
173
127
  clearPendingWatcherFlushTimer();
174
128
 
@@ -190,23 +144,7 @@ async function flushPendingWatcherUpdate(): Promise<void> {
190
144
  // Per-session deltas instead of full project snapshots: an upsert of one
191
145
  // session can never clobber unrelated client state, so the frontend needs
192
146
  // no "suppress updates while a run is active" protection logic.
193
- const events: string[] = [];
194
- for (const updatedSessionId of queuedUpdate.updatedSessionIds) {
195
- const event = await buildSessionUpsertedEvent(updatedSessionId);
196
- if (event) {
197
- events.push(event);
198
- }
199
- }
200
-
201
- if (events.length > 0) {
202
- connectedClients.forEach(client => {
203
- if (client.readyState === WS_OPEN_STATE) {
204
- for (const event of events) {
205
- client.send(event);
206
- }
207
- }
208
- });
209
- }
147
+ await broadcastSessionUpsertedBatch(queuedUpdate.updatedSessionIds);
210
148
  } catch (error) {
211
149
  const message = error instanceof Error ? error.message : String(error);
212
150
  console.error('Session watcher refresh failed while broadcasting session_upserted', { error: message });
@@ -262,6 +200,7 @@ export async function initializeSessionsWatcher(): Promise<void> {
262
200
  const initialSync = await sessionSynchronizerService.synchronizeSessions();
263
201
  console.log('Initial session synchronization complete', {
264
202
  processedByProvider: initialSync.processedByProvider,
203
+ prunedOrphans: initialSync.prunedOrphans,
265
204
  failures: initialSync.failures,
266
205
  });
267
206
 
@@ -3,15 +3,45 @@ import fsp from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
 
5
5
  import { projectsDb, sessionsDb } from '@/modules/database/index.js';
6
- import { chatRunRegistry } from '@/modules/websocket/index.js';
6
+ import { broadcastSessionUpserted, chatRunRegistry } from '@/modules/websocket/index.js';
7
7
  import { providerRegistry } from '@/modules/providers/provider.registry.js';
8
+ import { listBusyClaudeCliSessions } from '@/modules/providers/services/claude-cli-liveness.service.js';
9
+ import { sessionHistoryCache } from '@/modules/providers/services/session-history-cache.service.js';
8
10
  import type {
11
+ BackgroundTaskSummary,
9
12
  FetchHistoryOptions,
10
13
  FetchHistoryResult,
11
14
  LLMProvider,
12
15
  NormalizedMessage,
16
+ WorkflowAgentActivity,
13
17
  } from '@/shared/types.js';
14
- import { AppError } from '@/shared/utils.js';
18
+ import { AppError, sliceTailPage } from '@/shared/utils.js';
19
+
20
+ /**
21
+ * One session the running-sessions poll reports as busy.
22
+ *
23
+ * A chat run in progress is listed as before. A session whose turn has ended
24
+ * but whose background tasks are still outstanding is listed with
25
+ * `background: true` and `canInterrupt: false`: the composer stays usable
26
+ * (the runtime accepts a new turn while the work runs) and there is no run to
27
+ * abort — a task is stopped by id through `chat.stop-task` instead. `tasks`
28
+ * rides along on both kinds whenever the session has any.
29
+ *
30
+ * A turn driven outside CloudCLI — the Claude CLI in the Shell view, or a
31
+ * terminal the user opened themselves — is listed the same way, with
32
+ * `canInterrupt: false` because there is no run here to abort, and a
33
+ * `statusText` saying where the work is happening.
34
+ */
35
+ type RunningSessionEntry = {
36
+ sessionId: string;
37
+ provider: LLMProvider;
38
+ startedAt: number;
39
+ lastSeq: number;
40
+ background?: true;
41
+ canInterrupt?: false;
42
+ statusText?: string;
43
+ tasks?: BackgroundTaskSummary[];
44
+ };
15
45
 
16
46
  type CreateAppSessionResult = {
17
47
  sessionId: string;
@@ -124,18 +154,61 @@ export const sessionsService = {
124
154
  },
125
155
 
126
156
  /**
127
- * Returns app-facing ids for provider runs that are currently processing.
157
+ * Returns app-facing ids for provider runs that are currently processing,
158
+ * plus every session whose background work outlived its turn.
128
159
  *
129
160
  * This is intentionally status-only: callers that only need sidebar activity
130
161
  * indicators should not attach to chat streams or request replayed messages.
131
162
  */
132
- listRunningSessions(): Array<{
133
- sessionId: string;
134
- provider: LLMProvider;
135
- startedAt: number;
136
- lastSeq: number;
137
- }> {
138
- return chatRunRegistry.listRunningRuns();
163
+ async listRunningSessions(): Promise<RunningSessionEntry[]> {
164
+ const entries: RunningSessionEntry[] = chatRunRegistry.listRunningRuns();
165
+ const runningById = new Map(entries.map((entry) => [entry.sessionId, entry]));
166
+
167
+ for (const provider of providerRegistry.listProviders()) {
168
+ for (const { sessionId, tasks } of provider.runtime.listBackgroundWork?.() ?? []) {
169
+ const running = runningById.get(sessionId);
170
+ if (running) {
171
+ running.tasks = tasks;
172
+ continue;
173
+ }
174
+ entries.push({
175
+ sessionId,
176
+ provider: provider.id,
177
+ startedAt: Math.min(...tasks.map((task) => task.startedAt)),
178
+ // The completed run stays in the registry for a while, and a client
179
+ // that subscribes with its lastSeq replays the tail it missed.
180
+ lastSeq: chatRunRegistry.getRun(sessionId)?.lastSeq ?? 0,
181
+ background: true,
182
+ canInterrupt: false,
183
+ tasks,
184
+ });
185
+ }
186
+ }
187
+
188
+ // The chat-run registry only knows about turns CloudCLI drives itself, so
189
+ // without this a session working under the Claude CLI — including one
190
+ // CloudCLI spawned for its own Shell view — looks idle. A session already
191
+ // listed above keeps its richer entry.
192
+ for (const live of await listBusyClaudeCliSessions()) {
193
+ const session = sessionsDb.getSessionByProviderSessionId(live.providerSessionId);
194
+ if (!session || runningById.has(session.session_id)) {
195
+ continue;
196
+ }
197
+
198
+ entries.push({
199
+ sessionId: session.session_id,
200
+ provider: session.provider as LLMProvider,
201
+ startedAt: live.startedAt,
202
+ lastSeq: chatRunRegistry.getRun(session.session_id)?.lastSeq ?? 0,
203
+ // There is no run here to abort: the turn belongs to a CLI process
204
+ // CloudCLI does not own, so it can be reported but not interrupted.
205
+ canInterrupt: false,
206
+ statusText: 'Running in the Claude CLI',
207
+ });
208
+ runningById.set(session.session_id, entries[entries.length - 1]);
209
+ }
210
+
211
+ return entries;
139
212
  },
140
213
 
141
214
  /**
@@ -236,6 +309,79 @@ export const sessionsService = {
236
309
  };
237
310
  },
238
311
 
312
+ /**
313
+ * Branches a session into an independent one containing its conversation up
314
+ * to `upToAnchorId` (the whole thing when omitted).
315
+ *
316
+ * The source is left completely untouched — this is the "try two approaches"
317
+ * action, not a destructive one.
318
+ */
319
+ async forkSessionById(
320
+ sessionId: string,
321
+ options: { upToAnchorId?: string; title?: string } = {},
322
+ ): Promise<CreateAppSessionResult> {
323
+ const source = sessionsDb.getSessionById(sessionId);
324
+ if (!source) {
325
+ throw new AppError(`Session "${sessionId}" was not found.`, {
326
+ code: 'SESSION_NOT_FOUND',
327
+ statusCode: 404,
328
+ });
329
+ }
330
+
331
+ const provider = source.provider as LLMProvider;
332
+ const fork = providerRegistry.resolveProvider(provider).fork;
333
+ if (!fork) {
334
+ throw new AppError(`Sessions cannot be forked for provider "${provider}".`, {
335
+ code: 'FORK_NOT_SUPPORTED',
336
+ statusCode: 409,
337
+ });
338
+ }
339
+
340
+ // A session that has never run has no transcript to copy, so there is
341
+ // nothing a fork of it could resume from.
342
+ if (!source.provider_session_id || !source.jsonl_path) {
343
+ throw new AppError('This session has not produced a transcript yet.', {
344
+ code: 'FORK_SOURCE_NOT_READY',
345
+ statusCode: 409,
346
+ });
347
+ }
348
+
349
+ const sessionName = options.title?.trim()
350
+ || `${source.custom_name?.trim() || 'Session'} (fork)`;
351
+
352
+ const forked = await fork.forkSession({
353
+ providerSessionId: source.provider_session_id,
354
+ jsonlPath: source.jsonl_path,
355
+ projectPath: source.project_path ?? '',
356
+ upToAnchorId: options.upToAnchorId,
357
+ title: sessionName,
358
+ });
359
+
360
+ const forkSessionId = randomUUID();
361
+ sessionsDb.createForkedSession({
362
+ sessionId: forkSessionId,
363
+ provider,
364
+ projectPath: source.project_path ?? '',
365
+ customName: sessionName,
366
+ providerSessionId: forked.providerSessionId,
367
+ jsonlPath: forked.jsonlPath,
368
+ forkedFromSessionId: sessionId,
369
+ // A fork that silently dropped to the catalog default would answer
370
+ // differently from the conversation it was branched from.
371
+ model: source.model,
372
+ effort: source.effort,
373
+ });
374
+
375
+ await broadcastSessionUpserted(forkSessionId);
376
+
377
+ return {
378
+ sessionId: forkSessionId,
379
+ provider,
380
+ projectPath: source.project_path ?? '',
381
+ sessionName,
382
+ };
383
+ },
384
+
239
385
  /**
240
386
  * Resolves the provider-native id only for an explicit user copy action.
241
387
  * Normal session payloads continue to expose only the stable app id.
@@ -268,6 +414,73 @@ export const sessionsService = {
268
414
  * and every returned message is remapped back to the app session id so
269
415
  * provider ids never reach the frontend.
270
416
  */
417
+ /**
418
+ * Resolves where a conversation must resume from so that one already-sent
419
+ * message, and everything after it, is replaced.
420
+ *
421
+ * Returns `null` when the provider cannot do this at all, which is how the
422
+ * chat gateway knows to refuse the request rather than silently sending the
423
+ * edit as a new message at the end of the conversation.
424
+ */
425
+ async resolveEditAnchor(
426
+ sessionId: string,
427
+ anchorId: string,
428
+ ): Promise<{ found: boolean; resumeThroughId: string | null } | null> {
429
+ const session = sessionsDb.getSessionById(sessionId);
430
+ if (!session) {
431
+ throw new AppError(`Session "${sessionId}" was not found.`, {
432
+ code: 'SESSION_NOT_FOUND',
433
+ statusCode: 404,
434
+ });
435
+ }
436
+
437
+ const sessions = providerRegistry.resolveProvider(session.provider as LLMProvider).sessions;
438
+ if (!sessions.resolveEditAnchor) {
439
+ return null;
440
+ }
441
+
442
+ return sessions.resolveEditAnchor(sessionId, anchorId);
443
+ },
444
+
445
+ /**
446
+ * Whether editing a message on this session's provider means rewinding it on
447
+ * disk first, rather than handing the anchor to the runtime as a resume
448
+ * option.
449
+ *
450
+ * Answering this without doing anything is the point: the rewind moves the
451
+ * session onto a different provider transcript and cannot be undone, so the
452
+ * gateway has to know which shape the run takes before it commits to one.
453
+ */
454
+ providerRewindsForEdit(sessionId: string): boolean {
455
+ const session = sessionsDb.getSessionById(sessionId);
456
+ if (!session) {
457
+ throw new AppError(`Session "${sessionId}" was not found.`, {
458
+ code: 'SESSION_NOT_FOUND',
459
+ statusCode: 404,
460
+ });
461
+ }
462
+
463
+ return Boolean(providerRegistry.resolveProvider(session.provider as LLMProvider).sessions.rewindSession);
464
+ },
465
+
466
+ /**
467
+ * Rewinds a session on disk so `keepThroughId` is the last row it holds.
468
+ *
469
+ * Only call this once the run is admitted — see `providerRewindsForEdit`.
470
+ */
471
+ async rewindSessionForEdit(sessionId: string, keepThroughId: string | null): Promise<void> {
472
+ const session = sessionsDb.getSessionById(sessionId);
473
+ if (!session) {
474
+ throw new AppError(`Session "${sessionId}" was not found.`, {
475
+ code: 'SESSION_NOT_FOUND',
476
+ statusCode: 404,
477
+ });
478
+ }
479
+
480
+ const sessions = providerRegistry.resolveProvider(session.provider as LLMProvider).sessions;
481
+ await sessions.rewindSession?.(sessionId, keepThroughId);
482
+ },
483
+
271
484
  async fetchHistory(
272
485
  sessionId: string,
273
486
  options: Pick<FetchHistoryOptions, 'limit' | 'offset'> = {},
@@ -293,13 +506,52 @@ export const sessionsService = {
293
506
  }
294
507
 
295
508
  const provider = session.provider as LLMProvider;
296
- const result = await providerRegistry.resolveProvider(provider).sessions.fetchHistory(sessionId, {
297
- limit: options.limit ?? null,
298
- offset: options.offset ?? 0,
299
- projectPath: session.project_path ?? '',
300
- providerSessionId: session.provider_session_id,
509
+ const providerSessions = providerRegistry.resolveProvider(provider).sessions;
510
+ const providerSessionId = session.provider_session_id;
511
+ const projectPath = session.project_path ?? '';
512
+ const requestedLimit = options.limit ?? null;
513
+ const requestedOffset = options.offset ?? 0;
514
+
515
+ // Claude and Codex history readers parse `jsonl_path` itself, so a page
516
+ // can be sliced from the stat-validated full-transcript cache instead of
517
+ // re-parsing the whole file per request. Cursor and OpenCode read their
518
+ // messages from elsewhere (store.db / shared SQLite), so that file's stat
519
+ // says nothing about their history — they stay on the direct path.
520
+ const transcriptPath = provider === 'claude' || provider === 'codex'
521
+ ? session.jsonl_path
522
+ : null;
523
+ const fullHistory = await sessionHistoryCache.getFullHistory({
524
+ sessionId,
525
+ transcriptPath,
526
+ loadFull: () => providerSessions.fetchHistory(sessionId, {
527
+ limit: null,
528
+ offset: 0,
529
+ projectPath,
530
+ providerSessionId,
531
+ }),
301
532
  });
302
533
 
534
+ let result: FetchHistoryResult;
535
+ if (fullHistory) {
536
+ // Providers slice with this same helper, so a cached page is identical
537
+ // to what a direct `(limit, offset)` read would have returned.
538
+ const { page, hasMore } = sliceTailPage(fullHistory.messages, requestedLimit, Math.max(0, requestedOffset));
539
+ result = {
540
+ ...fullHistory,
541
+ messages: page,
542
+ hasMore,
543
+ offset: requestedOffset,
544
+ limit: requestedLimit,
545
+ };
546
+ } else {
547
+ result = await providerSessions.fetchHistory(sessionId, {
548
+ limit: requestedLimit,
549
+ offset: requestedOffset,
550
+ projectPath,
551
+ providerSessionId,
552
+ });
553
+ }
554
+
303
555
  return {
304
556
  ...result,
305
557
  messages: result.messages.map((message) => ({
@@ -309,6 +561,34 @@ export const sessionsService = {
309
561
  };
310
562
  },
311
563
 
564
+ /**
565
+ * Reads what one agent of a workflow run did, for the card that opened it.
566
+ *
567
+ * Not found covers both a provider that spawns no workflow agents and a run
568
+ * that left no transcript for this agent: either way there is nothing to
569
+ * show, and the card says so in one line.
570
+ */
571
+ async readWorkflowAgentActivity(sessionId: string, runId: string, agentId: string): Promise<WorkflowAgentActivity> {
572
+ const session = sessionsDb.getSessionById(sessionId);
573
+ if (!session) {
574
+ throw new AppError(`Session "${sessionId}" was not found.`, {
575
+ code: 'SESSION_NOT_FOUND',
576
+ statusCode: 404,
577
+ });
578
+ }
579
+
580
+ const sessions = providerRegistry.resolveProvider(session.provider as LLMProvider).sessions;
581
+ const activity = await sessions.readWorkflowAgentActivity?.(sessionId, runId, agentId);
582
+ if (!activity) {
583
+ throw new AppError(`Workflow agent "${agentId}" was not found.`, {
584
+ code: 'WORKFLOW_AGENT_NOT_FOUND',
585
+ statusCode: 404,
586
+ });
587
+ }
588
+
589
+ return activity;
590
+ },
591
+
312
592
  /**
313
593
  * Resolves one session (by app id, falling back to the provider-native id)
314
594
  * to its metadata plus the owning project.
@@ -418,10 +698,22 @@ export const sessionsService = {
418
698
  }
419
699
 
420
700
  let removedFromDisk = false;
421
- if (options.deletedFromDisk && session.jsonl_path) {
422
- removedFromDisk = await removeFileIfExists(session.jsonl_path);
701
+ if (options.deletedFromDisk) {
702
+ // Every file the conversation has lived in, not just the one the row
703
+ // points at now: editing a message on a provider that rewinds by
704
+ // branching moves the session onto a copy and leaves the earlier
705
+ // transcript behind. Deleting only the current one would leave the
706
+ // replaced turns on disk, and unreachable through the app.
707
+ const transcripts = [
708
+ ...(session.jsonl_path ? [session.jsonl_path] : []),
709
+ ...sessionsDb.getSupersededTranscriptPaths(sessionId),
710
+ ];
711
+ for (const transcript of transcripts) {
712
+ removedFromDisk = (await removeFileIfExists(transcript)) || removedFromDisk;
713
+ }
423
714
  }
424
715
 
716
+ sessionsDb.clearSupersededProviderSessions(sessionId);
425
717
  const deleted = sessionsDb.deleteSessionById(sessionId);
426
718
  if (!deleted) {
427
719
  throw new AppError(`Session "${sessionId}" was not found.`, {