@cloudcli-ai/cloudcli 1.37.3 → 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 (295) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{abnfDiagram-N423BO3Z-CJntskwA.js → abnfDiagram-N423BO3Z-Dmkwx38H.js} +1 -1
  3. package/dist/assets/{arc-CIfH4uMN.js → arc-DLe9AYuO.js} +1 -1
  4. package/dist/assets/{architectureDiagram-T3A2C74G-_IpEkukg.js → architectureDiagram-T3A2C74G-CBbbbNUW.js} +1 -1
  5. package/dist/assets/{blockDiagram-VBNYF7ZC-BttxrpgZ.js → blockDiagram-VBNYF7ZC-CaMEaWu4.js} +1 -1
  6. package/dist/assets/{c4Diagram-5PPSVZJV-g51dSRr1.js → c4Diagram-5PPSVZJV-DhjMgNnR.js} +1 -1
  7. package/dist/assets/channel-CWrmwsir.js +1 -0
  8. package/dist/assets/{chunk-2GRJ4B5K-D_-LbQUa.js → chunk-2GRJ4B5K-yjl3-F-i.js} +1 -1
  9. package/dist/assets/{chunk-2Q5K7J3B-BtlHzwaO.js → chunk-2Q5K7J3B-DsAYaWEP.js} +1 -1
  10. package/dist/assets/{chunk-5RXB4S5H-UzkpvpAT.js → chunk-5RXB4S5H-Bt5qmqOX.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-BKwx5Gag.js → chunk-5VM5RSS4-yWrWVvUt.js} +1 -1
  12. package/dist/assets/{chunk-6Q2QTUOP-D_KEBH5f.js → chunk-6Q2QTUOP-Dt66K5xb.js} +1 -1
  13. package/dist/assets/{chunk-GF5L2VYU-rK2DGyto.js → chunk-GF5L2VYU-DA0vzaj4.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-CeBl_N9i.js → chunk-JWPE2WC7-CUkbx96V.js} +1 -1
  15. package/dist/assets/{chunk-KBJHAD2P-BiQLWVIr.js → chunk-KBJHAD2P-DWKxx1WH.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-C1knVGV5.js → chunk-RYQCIY6F-BqluplN0.js} +1 -1
  17. package/dist/assets/{chunk-XXDRQBXY-CtKbJkvF.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-DjX8y40Z.js → cose-bilkent-JH36ORCC-DHV93-Em.js} +1 -1
  21. package/dist/assets/{cynefin-VYW2F7L2-DM6YeRba.js → cynefin-VYW2F7L2-B9HZgoGX.js} +1 -1
  22. package/dist/assets/{cynefinDiagram-MW4NZA55-wZJ-vy5B.js → cynefinDiagram-MW4NZA55-hFjjEQg8.js} +1 -1
  23. package/dist/assets/{dagre-VZM6K2ZE-BzP0ejkH.js → dagre-VZM6K2ZE-CpeVOC7V.js} +1 -1
  24. package/dist/assets/{diagram-7IWD3JNH-BTL_j23J.js → diagram-7IWD3JNH-D0H1UoRA.js} +1 -1
  25. package/dist/assets/{diagram-B4RE2ZJO-Cf1Norgu.js → diagram-B4RE2ZJO-6Y7BQUup.js} +1 -1
  26. package/dist/assets/{diagram-LBJQPF4R-DPiUEKr-.js → diagram-LBJQPF4R-C0nNOwht.js} +1 -1
  27. package/dist/assets/{diagram-Q27KOJAE-CTcTpiz5.js → diagram-Q27KOJAE-En6QlOVX.js} +1 -1
  28. package/dist/assets/{diagram-UB23O5K3-DPMSdYm0.js → diagram-UB23O5K3-CmPiNeGD.js} +1 -1
  29. package/dist/assets/{ebnfDiagram-BXEA7PRR-7yvD6uiF.js → ebnfDiagram-BXEA7PRR-Bygzp1Qt.js} +1 -1
  30. package/dist/assets/{erDiagram-JOGREHBK-CfgeDoV6.js → erDiagram-JOGREHBK-CwScRfzl.js} +1 -1
  31. package/dist/assets/{flowDiagram-UKHOOZJN-B27ng5zu.js → flowDiagram-UKHOOZJN-bKX-0xP6.js} +1 -1
  32. package/dist/assets/{ganttDiagram-PKOTCBZU-CcC6b_7t.js → ganttDiagram-PKOTCBZU-DtZPKXGd.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-DS77QQ5N-BX5kPGrO.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-CUP-SlN4.js → infoDiagram-6WML65LV-ciRQTS0h.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-WSZJBQD7-BTTDhOpX.js → ishikawaDiagram-WSZJBQD7-CgjhQZ84.js} +1 -1
  38. package/dist/assets/{journeyDiagram-NVQOT4AX-ARXcc61m.js → journeyDiagram-NVQOT4AX-BsbQSeIW.js} +1 -1
  39. package/dist/assets/{kanban-definition-27J2QSJJ-BieMpqMA.js → kanban-definition-27J2QSJJ-CrwzY9Jw.js} +1 -1
  40. package/dist/assets/{linear-61vxjRnP.js → linear-BUaCDr6l.js} +1 -1
  41. package/dist/assets/{mermaid.core-nhnwkfQx.js → mermaid.core-ClgdUU0j.js} +5 -5
  42. package/dist/assets/{mindmap-definition-FAOFIHXS-Cm6x5vT1.js → mindmap-definition-FAOFIHXS-BictljY1.js} +1 -1
  43. package/dist/assets/{pegDiagram-VL7TDLO6-CcYBq_9H.js → pegDiagram-VL7TDLO6-BXdByy_U.js} +1 -1
  44. package/dist/assets/{pieDiagram-7S7Q4E2Y-CfvIjfzo.js → pieDiagram-7S7Q4E2Y-uZsMfkRX.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-CIZ2JOQS-SgYoWPNb.js → quadrantDiagram-CIZ2JOQS-7lCv4QIj.js} +1 -1
  46. package/dist/assets/{railroadDiagram-AXF67PYL-D2Mu5E_f.js → railroadDiagram-AXF67PYL-07dxIWbi.js} +1 -1
  47. package/dist/assets/{requirementDiagram-LRYGKXZP-Azwe_k4Q.js → requirementDiagram-LRYGKXZP-Cb31Yv1_.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-W5VNT64P-DN1Ju3PA.js → sankeyDiagram-W5VNT64P-X0YPSyF9.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-SI44F4Z6-Bj4qC34S.js → sequenceDiagram-SI44F4Z6-BdBxSIlP.js} +1 -1
  50. package/dist/assets/{sizeCapture-X5ZJPWSS-Co4PdklG.js → sizeCapture-X5ZJPWSS-DQM5xAyD.js} +1 -1
  51. package/dist/assets/{stateDiagram-OKZ733FA-nOvL9dRC.js → stateDiagram-OKZ733FA-DJ3xkLUF.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-UEYNNEHI-DNv8ppw7.js → stateDiagram-v2-UEYNNEHI-vAaE4zNo.js} +1 -1
  53. package/dist/assets/{swimlanes-SLNWSIFB-CCK3wMid.js → swimlanes-SLNWSIFB-rjfYdW0w.js} +2 -2
  54. package/dist/assets/swimlanesDiagram-ULZ7WXOC-TV2IF4Nq.js +8 -0
  55. package/dist/assets/{timeline-definition-Z64GVDOM-BC29RNFm.js → timeline-definition-Z64GVDOM-DZMra5do.js} +1 -1
  56. package/dist/assets/{vennDiagram-T6HMQDX7-pZlZCtEo.js → vennDiagram-T6HMQDX7-BevD8uu0.js} +1 -1
  57. package/dist/assets/{wardleyDiagram-T6FBY63Y-De8k2-zM.js → wardleyDiagram-T6FBY63Y-DsIyXB7z.js} +1 -1
  58. package/dist/assets/{xychartDiagram-ELKLHX3M-Bk8ykH0w.js → xychartDiagram-ELKLHX3M-G3PlF3ko.js} +1 -1
  59. package/dist/index.html +2 -2
  60. package/dist-server/server/index.js +4 -1
  61. package/dist-server/server/index.js.map +1 -1
  62. package/dist-server/server/modules/agent/agent.module.js +9 -2
  63. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  64. package/dist-server/server/modules/agent/agent.routes.js +175 -128
  65. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  66. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js +306 -0
  67. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js.map +1 -0
  68. package/dist-server/server/modules/agent/tests/agent.routes.test.js +18 -1
  69. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  70. package/dist-server/server/modules/database/migrations.js +13 -0
  71. package/dist-server/server/modules/database/migrations.js.map +1 -1
  72. package/dist-server/server/modules/database/repositories/provider-models.js +60 -6
  73. package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -1
  74. package/dist-server/server/modules/database/repositories/sessions.db.js +7 -0
  75. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  76. package/dist-server/server/modules/database/schema.js +7 -0
  77. package/dist-server/server/modules/database/schema.js.map +1 -1
  78. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +99 -0
  79. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -1
  80. package/dist-server/server/modules/file-tree/file-tree.module.js +2 -1
  81. package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
  82. package/dist-server/server/modules/file-tree/file-tree.service.js +44 -7
  83. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  84. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +126 -1
  85. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  86. package/dist-server/server/modules/git/git-branch-diff.service.js +142 -0
  87. package/dist-server/server/modules/git/git-branch-diff.service.js.map +1 -0
  88. package/dist-server/server/modules/git/git-branch.service.js.map +1 -1
  89. package/dist-server/server/modules/git/git.routes.js +92 -5
  90. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  91. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js +160 -0
  92. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js.map +1 -0
  93. package/dist-server/server/modules/projects/project-clone.routes.js +108 -0
  94. package/dist-server/server/modules/projects/project-clone.routes.js.map +1 -0
  95. package/dist-server/server/modules/projects/projects.routes.js +13 -66
  96. package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
  97. package/dist-server/server/modules/projects/services/project-clone-request.service.js +36 -0
  98. package/dist-server/server/modules/projects/services/project-clone-request.service.js.map +1 -0
  99. package/dist-server/server/modules/projects/services/project-clone.service.js +86 -32
  100. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
  101. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js +191 -0
  102. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js.map +1 -0
  103. package/dist-server/server/modules/projects/tests/project-clone.service.test.js +275 -0
  104. package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -1
  105. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +42 -8
  106. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  107. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +6 -16
  108. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  109. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +305 -16
  110. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  111. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +38 -11
  112. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  113. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +686 -77
  114. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  115. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +31 -0
  116. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  117. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +11 -4
  118. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  119. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +57 -0
  120. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  121. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +9 -2
  122. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
  123. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -2
  124. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  125. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +16 -3
  126. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  127. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  128. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -1
  129. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +19 -2
  130. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  131. package/dist-server/server/modules/providers/provider.routes.js +110 -2
  132. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  133. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js +100 -0
  134. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js.map +1 -0
  135. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js +97 -0
  136. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js.map +1 -0
  137. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +9 -1
  138. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  139. package/dist-server/server/modules/providers/services/provider-models.service.js +59 -5
  140. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  141. package/dist-server/server/modules/providers/services/provider-runtime.service.js +8 -0
  142. package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
  143. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +2 -4
  144. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  145. package/dist-server/server/modules/providers/services/session-history-cache.service.js +4 -0
  146. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -1
  147. package/dist-server/server/modules/providers/services/sessions.service.js +73 -3
  148. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  149. package/dist-server/server/modules/providers/tests/claude-auth.test.js +131 -0
  150. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  151. package/dist-server/server/modules/providers/tests/claude-background-work.test.js +172 -0
  152. package/dist-server/server/modules/providers/tests/claude-background-work.test.js.map +1 -0
  153. package/dist-server/server/modules/providers/tests/claude-compaction.test.js +84 -0
  154. package/dist-server/server/modules/providers/tests/claude-compaction.test.js.map +1 -0
  155. package/dist-server/server/modules/providers/tests/claude-message-model.test.js +123 -0
  156. package/dist-server/server/modules/providers/tests/claude-message-model.test.js.map +1 -0
  157. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js +265 -0
  158. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js.map +1 -0
  159. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +1368 -12
  160. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  161. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js +228 -0
  162. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js.map +1 -0
  163. package/dist-server/server/modules/providers/tests/codex-models.test.js +25 -0
  164. package/dist-server/server/modules/providers/tests/codex-models.test.js.map +1 -0
  165. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +32 -0
  166. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -1
  167. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +50 -0
  168. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  169. package/dist-server/server/modules/providers/tests/cursor-auth.test.js +167 -0
  170. package/dist-server/server/modules/providers/tests/cursor-auth.test.js.map +1 -0
  171. package/dist-server/server/modules/providers/tests/opencode-models.test.js +79 -0
  172. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  173. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +19 -1
  174. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  175. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js +30 -0
  176. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js.map +1 -0
  177. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +79 -1
  178. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
  179. package/dist-server/server/modules/providers/tests/provider.routes.test.js +190 -1
  180. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  181. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +19 -0
  182. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -1
  183. package/dist-server/server/modules/providers/tests/sessions.service.test.js +175 -1
  184. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  185. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +13 -1
  186. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -1
  187. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +36 -1
  188. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -1
  189. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +27 -5
  190. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  191. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +38 -1
  192. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  193. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +230 -20
  194. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  195. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js +137 -0
  196. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js.map +1 -0
  197. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js +96 -0
  198. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js.map +1 -0
  199. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +420 -0
  200. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  201. package/dist-server/server/shared/message-unification.js +1 -1
  202. package/dist-server/server/shared/message-unification.js.map +1 -1
  203. package/dist-server/server/shared/tests/read-only-roots.test.js +124 -0
  204. package/dist-server/server/shared/tests/read-only-roots.test.js.map +1 -0
  205. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js +31 -0
  206. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js.map +1 -0
  207. package/dist-server/server/shared/utils.js +126 -0
  208. package/dist-server/server/shared/utils.js.map +1 -1
  209. package/electron/desktopWindow.js +6 -0
  210. package/electron/viewHost.js +25 -2
  211. package/package.json +4 -4
  212. package/server/index.ts +5 -1
  213. package/server/modules/agent/agent.module.ts +11 -1
  214. package/server/modules/agent/agent.routes.ts +198 -140
  215. package/server/modules/agent/tests/agent-run-registration.test.ts +348 -0
  216. package/server/modules/agent/tests/agent.routes.test.ts +18 -1
  217. package/server/modules/database/migrations.ts +14 -0
  218. package/server/modules/database/repositories/provider-models.ts +79 -6
  219. package/server/modules/database/repositories/sessions.db.ts +8 -0
  220. package/server/modules/database/schema.ts +7 -0
  221. package/server/modules/database/tests/provider-models.db.integration.test.ts +113 -0
  222. package/server/modules/file-tree/file-tree.module.ts +2 -1
  223. package/server/modules/file-tree/file-tree.service.ts +49 -7
  224. package/server/modules/file-tree/tests/file-tree.service.test.ts +163 -1
  225. package/server/modules/git/git-branch-diff.service.ts +204 -0
  226. package/server/modules/git/git-branch.service.ts +2 -12
  227. package/server/modules/git/git.routes.ts +100 -6
  228. package/server/modules/git/tests/git-branch-diff.service.test.ts +210 -0
  229. package/server/modules/projects/project-clone.routes.ts +142 -0
  230. package/server/modules/projects/projects.routes.ts +16 -81
  231. package/server/modules/projects/services/project-clone-request.service.ts +56 -0
  232. package/server/modules/projects/services/project-clone.service.ts +102 -34
  233. package/server/modules/projects/tests/project-clone.routes.test.ts +246 -0
  234. package/server/modules/projects/tests/project-clone.service.test.ts +376 -0
  235. package/server/modules/providers/list/claude/claude-auth.provider.ts +60 -10
  236. package/server/modules/providers/list/claude/claude-models.provider.ts +10 -22
  237. package/server/modules/providers/list/claude/claude-runtime.provider.js +322 -15
  238. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +40 -14
  239. package/server/modules/providers/list/claude/claude-sessions.provider.ts +796 -88
  240. package/server/modules/providers/list/codex/codex-models.provider.ts +31 -0
  241. package/server/modules/providers/list/codex/codex-runtime.provider.ts +12 -4
  242. package/server/modules/providers/list/codex/codex-sessions.provider.ts +63 -0
  243. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +10 -2
  244. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +9 -2
  245. package/server/modules/providers/list/opencode/opencode-models.provider.ts +18 -3
  246. package/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  247. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +30 -2
  248. package/server/modules/providers/provider.routes.ts +131 -2
  249. package/server/modules/providers/services/claude-cli-liveness.service.ts +121 -0
  250. package/server/modules/providers/services/claude-settings-permissions.service.ts +127 -0
  251. package/server/modules/providers/services/provider-capabilities.service.ts +17 -1
  252. package/server/modules/providers/services/provider-models.service.ts +71 -4
  253. package/server/modules/providers/services/provider-runtime.service.ts +11 -0
  254. package/server/modules/providers/services/session-conversations-search.service.ts +2 -5
  255. package/server/modules/providers/services/session-history-cache.service.ts +13 -2
  256. package/server/modules/providers/services/sessions.service.ts +108 -8
  257. package/server/modules/providers/tests/claude-auth.test.ts +150 -0
  258. package/server/modules/providers/tests/claude-background-work.test.ts +213 -0
  259. package/server/modules/providers/tests/claude-compaction.test.ts +101 -0
  260. package/server/modules/providers/tests/claude-message-model.test.ts +160 -0
  261. package/server/modules/providers/tests/claude-runtime-hold.test.ts +309 -0
  262. package/server/modules/providers/tests/claude-sessions.test.ts +1630 -12
  263. package/server/modules/providers/tests/claude-settings-permissions.test.ts +271 -0
  264. package/server/modules/providers/tests/codex-models.test.ts +37 -0
  265. package/server/modules/providers/tests/codex-runtime.test.ts +36 -0
  266. package/server/modules/providers/tests/codex-sessions.test.ts +59 -0
  267. package/server/modules/providers/tests/cursor-auth.test.ts +187 -0
  268. package/server/modules/providers/tests/opencode-models.test.ts +100 -0
  269. package/server/modules/providers/tests/opencode-sessions.test.ts +40 -1
  270. package/server/modules/providers/tests/provider-capabilities.service.test.ts +37 -0
  271. package/server/modules/providers/tests/provider-models.service.test.ts +99 -1
  272. package/server/modules/providers/tests/provider.routes.test.ts +230 -1
  273. package/server/modules/providers/tests/session-history-cache.service.test.ts +22 -0
  274. package/server/modules/providers/tests/sessions.service.test.ts +237 -1
  275. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +14 -1
  276. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +45 -1
  277. package/server/modules/websocket/README.md +4 -2
  278. package/server/modules/websocket/services/chat-run-registry.service.ts +29 -5
  279. package/server/modules/websocket/services/chat-websocket.service.ts +58 -1
  280. package/server/modules/websocket/services/shell-websocket.service.ts +269 -22
  281. package/server/modules/websocket/tests/chat-background-subscribe.test.ts +167 -0
  282. package/server/modules/websocket/tests/chat-stop-task.test.ts +127 -0
  283. package/server/modules/websocket/tests/shell-websocket.service.test.ts +521 -0
  284. package/server/shared/interfaces.ts +29 -0
  285. package/server/shared/message-unification.ts +1 -1
  286. package/server/shared/tests/read-only-roots.test.ts +139 -0
  287. package/server/shared/tests/strip-ansi-sequences.test.ts +42 -0
  288. package/server/shared/types.ts +261 -7
  289. package/server/shared/utils.ts +136 -0
  290. package/dist/assets/channel-C5xU9jMw.js +0 -1
  291. package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +0 -1
  292. package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +0 -1
  293. package/dist/assets/index-BTDHQ6Id.css +0 -32
  294. package/dist/assets/index-sFfz8iBR.js +0 -4931
  295. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +0 -8
@@ -0,0 +1,127 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import { readObjectRecord, readStringArray } from '@/shared/utils.js';
6
+
7
+ /**
8
+ * The permission rules one Claude settings file contributes.
9
+ *
10
+ * `status` says whether the rules could be read at all: `missing` means there
11
+ * is no such file, `invalid` means it exists but the CLI would not load it
12
+ * either (unreadable, over its size cap, or not a JSON object). Both report
13
+ * empty rule lists rather than failing the request, so one bad file never hides
14
+ * the others.
15
+ */
16
+ type ClaudeSettingsPermissionSource = {
17
+ scope: 'user' | 'managed';
18
+ path: string;
19
+ status: 'ok' | 'missing' | 'invalid';
20
+ allow: string[];
21
+ deny: string[];
22
+ ask: string[];
23
+ };
24
+
25
+ /**
26
+ * The user's own settings file. The CLI reads it from `CLAUDE_CONFIG_DIR` when
27
+ * that is set, and the server forwards its whole env to every CLI it spawns,
28
+ * so the same override has to apply here.
29
+ */
30
+ const userSettingsPath = (): string => {
31
+ const configDirectory = process.env.CLAUDE_CONFIG_DIR?.trim();
32
+ const baseDirectory = configDirectory && path.isAbsolute(configDirectory)
33
+ ? configDirectory
34
+ : path.join(os.homedir(), '.claude');
35
+ return path.join(baseDirectory, 'settings.json');
36
+ };
37
+
38
+ /** The administrator-managed settings file, at the fixed path the CLI uses per OS. */
39
+ const managedSettingsPath = (): string => {
40
+ if (process.platform === 'darwin') {
41
+ return '/Library/Application Support/ClaudeCode/managed-settings.json';
42
+ }
43
+ if (process.platform === 'win32') {
44
+ return 'C:\\Program Files\\ClaudeCode\\managed-settings.json';
45
+ }
46
+ return '/etc/claude-code/managed-settings.json';
47
+ };
48
+
49
+ // The CLI refuses to load a settings file larger than this (2 MB).
50
+ const MAX_SETTINGS_FILE_BYTES = 2 * 1024 * 1024;
51
+
52
+ /**
53
+ * Decodes a settings file the way the CLI does: UTF-16LE when it starts with
54
+ * that byte order mark, UTF-8 otherwise, without the leading BOM. Windows
55
+ * editors and PowerShell often save one, and the CLI still applies such files.
56
+ */
57
+ const decodeSettingsFile = (bytes: Buffer): string => {
58
+ const isUtf16LittleEndian = bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe;
59
+ return bytes.toString(isUtf16LittleEndian ? 'utf16le' : 'utf8').replace(/^\uFEFF/, '');
60
+ };
61
+
62
+ /** Keeps non-empty string rules only, without duplicates, in file order. */
63
+ const readRuleList = (value: unknown): string[] => [
64
+ ...new Set((readStringArray(value) ?? []).map((rule) => rule.trim()).filter(Boolean)),
65
+ ];
66
+
67
+ async function readPermissionSource(
68
+ scope: ClaudeSettingsPermissionSource['scope'],
69
+ filePath: string,
70
+ ): Promise<ClaudeSettingsPermissionSource> {
71
+ const empty = { scope, path: filePath, allow: [], deny: [], ask: [] };
72
+
73
+ let content: string;
74
+ try {
75
+ if ((await stat(filePath)).size > MAX_SETTINGS_FILE_BYTES) {
76
+ return { ...empty, status: 'invalid' };
77
+ }
78
+ content = decodeSettingsFile(await readFile(filePath));
79
+ } catch (error) {
80
+ const code = (error as NodeJS.ErrnoException).code;
81
+ return { ...empty, status: code === 'ENOENT' || code === 'ENOTDIR' ? 'missing' : 'invalid' };
82
+ }
83
+
84
+ // The CLI loads an empty or whitespace-only file as `{}`.
85
+ if (!content.trim()) {
86
+ return { ...empty, status: 'ok' };
87
+ }
88
+
89
+ let settings: Record<string, unknown> | null;
90
+ try {
91
+ settings = readObjectRecord(JSON.parse(content));
92
+ } catch {
93
+ settings = null;
94
+ }
95
+ if (!settings) {
96
+ return { ...empty, status: 'invalid' };
97
+ }
98
+
99
+ // A file without a `permissions` block is perfectly valid; it just adds no rules.
100
+ const permissions = readObjectRecord(settings.permissions) ?? {};
101
+ return {
102
+ ...empty,
103
+ status: 'ok',
104
+ allow: readRuleList(permissions.allow),
105
+ deny: readRuleList(permissions.deny),
106
+ ask: readRuleList(permissions.ask),
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Read-only view of the permission rules the Claude CLI applies from its own
112
+ * settings files, on top of the allowed/blocked lists CloudCLI passes it.
113
+ *
114
+ * Used by provider.routes.ts for the Claude permissions panel in Settings.
115
+ * Project-level files are left out on purpose: the panel has no project, and
116
+ * those files only apply to chats started inside their project. CloudCLI never
117
+ * writes to any of these files.
118
+ */
119
+ export const claudeSettingsPermissionsService = {
120
+ /** `managedPath` is only overridden by tests, which cannot write to the system path. */
121
+ async listRuleSources(managedPath: string = managedSettingsPath()): Promise<ClaudeSettingsPermissionSource[]> {
122
+ return Promise.all([
123
+ readPermissionSource('user', userSettingsPath()),
124
+ readPermissionSource('managed', managedPath),
125
+ ]);
126
+ },
127
+ };
@@ -25,6 +25,14 @@ type ProviderCapabilities = {
25
25
  supportsTokenUsage: boolean;
26
26
  /** Whether the provider runtime can accept model-level reasoning effort. */
27
27
  supportsEffort: boolean;
28
+ /**
29
+ * Reasoning-effort levels the runtime can pass on, weakest first, and so the
30
+ * levels a custom model-library entry may declare. Empty exactly when
31
+ * `supportsEffort` is false. Covers every level a predefined model declares
32
+ * (a providers test enforces this) and, unlike the model catalog, does not
33
+ * depend on which upstream providers this machine has connected.
34
+ */
35
+ effortLevels: string[];
28
36
  /**
29
37
  * Whether an already-sent message can be replaced, which requires the
30
38
  * provider to re-run a conversation truncated at a chosen point.
@@ -53,6 +61,7 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
53
61
  supportsPermissionRequests: true,
54
62
  supportsTokenUsage: true,
55
63
  supportsEffort: true,
64
+ effortLevels: ['low', 'medium', 'high', 'xhigh', 'max', 'ultracode'],
56
65
  // `resumeSessionAt` re-runs a conversation truncated at a message, and
57
66
  // `forkSession` copies a transcript prefix into a new session file.
58
67
  supportsMessageEditing: true,
@@ -68,6 +77,7 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
68
77
  supportsPermissionRequests: false,
69
78
  supportsTokenUsage: false,
70
79
  supportsEffort: false,
80
+ effortLevels: [],
71
81
  supportsMessageEditing: false,
72
82
  supportsSessionForking: false,
73
83
  },
@@ -81,6 +91,7 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
81
91
  supportsPermissionRequests: false,
82
92
  supportsTokenUsage: true,
83
93
  supportsEffort: true,
94
+ effortLevels: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
84
95
  // Not from the Codex SDK, which only starts and resumes threads: both ride
85
96
  // the same CLI's `app-server` protocol, whose `thread/fork` copies a
86
97
  // thread up to a chosen turn. Editing is that fork plus a new prompt,
@@ -101,13 +112,18 @@ const PROVIDER_CAPABILITIES: Record<LLMProvider, ProviderCapabilities> = {
101
112
  supportsPermissionRequests: false,
102
113
  supportsTokenUsage: true,
103
114
  supportsEffort: true,
115
+ // Passed on as OpenCode's `--variant`. `thinking` (MiniMax's on/off
116
+ // variant, paired with `none`) is not a strength, so it sorts last.
117
+ effortLevels: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'thinking'],
104
118
  supportsMessageEditing: false,
105
119
  supportsSessionForking: false,
106
120
  },
107
121
  };
108
122
 
109
123
  /**
110
- * Application service exposing the provider capability matrix.
124
+ * Application service exposing the provider capability matrix. Used by the
125
+ * Providers routes (the capabilities endpoints) and by the Providers model
126
+ * service, which reads `effortLevels` to validate custom-model effort.
111
127
  */
112
128
  export const providerCapabilitiesService = {
113
129
  getProviderCapabilities(provider: LLMProvider): ProviderCapabilities {
@@ -1,5 +1,6 @@
1
1
  import { providerModelsDb, sessionsDb } from '@/modules/database/index.js';
2
2
  import { providerRegistry } from '@/modules/providers/provider.registry.js';
3
+ import { providerCapabilitiesService } from '@/modules/providers/services/provider-capabilities.service.js';
3
4
  import type { IProvider } from '@/shared/interfaces.js';
4
5
  import type {
5
6
  CustomProviderModelInput,
@@ -36,6 +37,15 @@ type ProviderModelsServiceDependencies = {
36
37
  sessions?: ProviderModelsSessionStore;
37
38
  };
38
39
 
40
+ /**
41
+ * Converts a stored custom row into a catalog option.
42
+ *
43
+ * `effort` is emitted in the same shape predefined models use, so the composer
44
+ * and every runtime's effort validation treat declared levels identically.
45
+ * Rows without declared levels omit `effort` on purpose: the composer then
46
+ * hides Reasoning and runtimes send no effort, instead of guessing levels the
47
+ * model might reject.
48
+ */
39
49
  const toCustomProviderModelOption = (
40
50
  record: CustomProviderModelRecord,
41
51
  ): ProviderModelOption => ({
@@ -43,9 +53,32 @@ const toCustomProviderModelOption = (
43
53
  label: record.model,
44
54
  recordId: record.recordId,
45
55
  isCustom: true,
56
+ ...(record.effort
57
+ ? {
58
+ effort: {
59
+ ...(record.effort.default ? { default: record.effort.default } : {}),
60
+ values: record.effort.values.map((value) => ({ value })),
61
+ },
62
+ }
63
+ : {}),
46
64
  });
47
65
 
66
+ /**
67
+ * Effort levels a custom model of this provider may declare, weakest first:
68
+ * the levels its runtime can pass on, from the capability matrix. Empty
69
+ * (Cursor) means no effort support at all.
70
+ *
71
+ * Deliberately not derived from the predefined models: OpenCode narrows its
72
+ * catalog to the upstream providers this machine has connected, and only its
73
+ * OpenCode Go models declare effort, so a catalog-derived set would vanish for
74
+ * Zen, Anthropic, or OpenAI users and change with unrelated credentials.
75
+ */
76
+ const readCustomModelEffortLevels = (provider: LLMProvider): string[] => (
77
+ providerCapabilitiesService.getProviderCapabilities(provider).effortLevels
78
+ );
79
+
48
80
  const mergeProviderModels = (
81
+ provider: LLMProvider,
49
82
  predefined: ProviderModelsDefinition,
50
83
  custom: CustomProviderModelRecord[],
51
84
  ): ProviderModelsDefinition => {
@@ -55,14 +88,46 @@ const mergeProviderModels = (
55
88
  ...custom.map(toCustomProviderModelOption),
56
89
  ],
57
90
  DEFAULT: predefined.DEFAULT,
91
+ // Copied so no caller can mutate the shared capability matrix.
92
+ EFFORT_LEVELS: [...readCustomModelEffortLevels(provider)],
58
93
  };
59
94
  };
60
95
 
61
96
  const normalizeCustomModelInput = (input: CustomProviderModelInput): CustomProviderModelInput => ({
62
97
  id: input.id.trim(),
63
98
  model: input.model.trim(),
99
+ ...(input.effort === undefined ? {} : { effort: input.effort }),
64
100
  });
65
101
 
102
+ const assertEffortLevelsSupported = (
103
+ provider: LLMProvider,
104
+ input: CustomProviderModelInput,
105
+ ): void => {
106
+ const declaredLevels = input.effort?.values ?? [];
107
+ if (declaredLevels.length === 0) {
108
+ return;
109
+ }
110
+
111
+ const supportedLevels = readCustomModelEffortLevels(provider);
112
+ if (supportedLevels.length === 0) {
113
+ throw new AppError(`${provider} models do not support reasoning effort.`, {
114
+ code: 'MODEL_EFFORT_NOT_SUPPORTED',
115
+ statusCode: 400,
116
+ });
117
+ }
118
+
119
+ const unsupportedLevel = declaredLevels.find((level) => !supportedLevels.includes(level));
120
+ if (unsupportedLevel) {
121
+ throw new AppError(
122
+ `"${unsupportedLevel}" is not a ${provider} effort level. Use one of: ${supportedLevels.join(', ')}.`,
123
+ {
124
+ code: 'INVALID_MODEL_EFFORT',
125
+ statusCode: 400,
126
+ },
127
+ );
128
+ }
129
+ };
130
+
66
131
  const isUniqueConstraintError = (error: unknown): boolean => (
67
132
  error !== null
68
133
  && error !== undefined
@@ -87,7 +152,7 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
87
152
 
88
153
  const getProviderModels = async (provider: LLMProvider): Promise<ProviderModelsDefinition> => {
89
154
  const predefined = await resolveProvider(provider).models.getSupportedModels();
90
- return mergeProviderModels(predefined, catalog.listCustomProviderModels(provider));
155
+ return mergeProviderModels(provider, predefined, catalog.listCustomProviderModels(provider));
91
156
  };
92
157
 
93
158
  const getCurrentActiveModel = async (
@@ -139,12 +204,13 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
139
204
  const predefined = await resolveProvider(provider).models.getSupportedModels();
140
205
  const normalized = normalizeCustomModelInput(input);
141
206
  assertModelIdAvailable(provider, predefined, normalized.id);
207
+ assertEffortLevelsSupported(provider, normalized);
142
208
 
143
209
  try {
144
210
  const created = catalog.createCustomProviderModel(provider, normalized);
145
211
  return {
146
212
  model: toCustomProviderModelOption(created),
147
- models: mergeProviderModels(predefined, catalog.listCustomProviderModels(provider)),
213
+ models: mergeProviderModels(provider, predefined, catalog.listCustomProviderModels(provider)),
148
214
  };
149
215
  } catch (error) {
150
216
  if (isUniqueConstraintError(error)) {
@@ -166,6 +232,7 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
166
232
  readCustomModel(provider, recordId);
167
233
  const normalized = normalizeCustomModelInput(input);
168
234
  assertModelIdAvailable(provider, predefined, normalized.id, recordId);
235
+ assertEffortLevelsSupported(provider, normalized);
169
236
 
170
237
  try {
171
238
  const updated = catalog.updateCustomProviderModel(provider, recordId, normalized);
@@ -178,7 +245,7 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
178
245
 
179
246
  return {
180
247
  model: toCustomProviderModelOption(updated),
181
- models: mergeProviderModels(predefined, catalog.listCustomProviderModels(provider)),
248
+ models: mergeProviderModels(provider, predefined, catalog.listCustomProviderModels(provider)),
182
249
  };
183
250
  } catch (error) {
184
251
  if (isUniqueConstraintError(error)) {
@@ -207,7 +274,7 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
207
274
 
208
275
  return {
209
276
  model: toCustomProviderModelOption(removed),
210
- models: mergeProviderModels(predefined, catalog.listCustomProviderModels(provider)),
277
+ models: mergeProviderModels(provider, predefined, catalog.listCustomProviderModels(provider)),
211
278
  };
212
279
  };
213
280
 
@@ -91,6 +91,17 @@ export function createProviderRuntimeService(
91
91
  return Boolean(await dependencies.resolveProvider(providerName).runtime.abort(sessionId));
92
92
  },
93
93
 
94
+ async stopBackgroundTask(providerName: LLMProvider, sessionId: string, taskId: string): Promise<boolean> {
95
+ // A runtime that never holds background work has no task to stop.
96
+ const { runtime } = dependencies.resolveProvider(providerName);
97
+ return Boolean(await runtime.stopBackgroundTask?.(sessionId, taskId));
98
+ },
99
+
100
+ hasBackgroundWork(sessionId: string): boolean {
101
+ return dependencies.listProviders().some((provider) =>
102
+ (provider.runtime.listBackgroundWork?.() ?? []).some((entry) => entry.sessionId === sessionId));
103
+ },
104
+
94
105
  resolveToolApproval(requestId: string, decision: ProviderPermissionDecision): void {
95
106
  for (const provider of dependencies.listProviders()) {
96
107
  provider.runtime.permissions?.resolve(requestId, decision);
@@ -5,6 +5,7 @@ import readline from 'node:readline';
5
5
  import { spawn } from 'cross-spawn';
6
6
  import { rgPath } from '@vscode/ripgrep';
7
7
 
8
+ import { stripAnsiSequences } from '@/shared/utils.js';
8
9
  import { projectsDb, sessionsDb } from '@/modules/database/index.js';
9
10
 
10
11
  type AnyRecord = Record<string, any>;
@@ -409,10 +410,6 @@ function buildClaudeLocalCommandDisplayText(payload: ClaudeLocalCommandPayload):
409
410
  return commandArgs ? `${baseCommand} ${commandArgs}` : baseCommand;
410
411
  }
411
412
 
412
- function stripAnsiFormatting(text: string): string {
413
- return text.replace(/\u001B\[[0-9;?]*[ -/]*[@-~]/g, '');
414
- }
415
-
416
413
  type ClaudeSearchableMessage = {
417
414
  text: string;
418
415
  role: 'user' | 'assistant';
@@ -456,7 +453,7 @@ function extractClaudeSearchableMessage(entry: AnyRecord): ClaudeSearchableMessa
456
453
 
457
454
  const localCommandStdout = extractTaggedContent(content, 'local-command-stdout');
458
455
  if (localCommandStdout !== null) {
459
- const stdoutText = stripAnsiFormatting(localCommandStdout).trim();
456
+ const stdoutText = stripAnsiSequences(localCommandStdout).trim();
460
457
  return stdoutText
461
458
  ? {
462
459
  text: stdoutText,
@@ -16,8 +16,14 @@ import type { FetchHistoryResult } from '@/shared/types.js';
16
16
  * request against the transcript file's identity (path + mtime + size), so
17
17
  * only the first read after the file changes pays the parse. Anything that
18
18
  * rewrites history (a new turn, an edit, a rewind, a fork) touches the file
19
- * and invalidates naturally; no explicit invalidation hooks exist or are
20
- * needed.
19
+ * and invalidates naturally.
20
+ *
21
+ * One thing the file cannot tell: whether the CLI process behind the session
22
+ * is still running. Claude's history reader answers a background agent's
23
+ * status from that, so a session's entry is also dropped explicitly when its
24
+ * process starts or ends (`invalidate`) — otherwise a page read while the
25
+ * process was up would keep reporting `running` after it had gone, until the
26
+ * next row happened to land in the file.
21
27
  *
22
28
  * Only history readers that read `jsonl_path` itself may use this cache —
23
29
  * callers pass `transcriptPath: null` for providers whose messages live
@@ -133,6 +139,11 @@ export function createSessionHistoryCache(
133
139
  pendingLoads.delete(sessionId);
134
140
  }
135
141
  },
142
+
143
+ /** Forgets a session's entry so the next read re-parses its transcript. */
144
+ invalidate(sessionId: string): void {
145
+ entries.delete(sessionId);
146
+ },
136
147
  };
137
148
  }
138
149
 
@@ -5,15 +5,44 @@ import path from 'node:path';
5
5
  import { projectsDb, sessionsDb } from '@/modules/database/index.js';
6
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';
8
9
  import { sessionHistoryCache } from '@/modules/providers/services/session-history-cache.service.js';
9
10
  import type {
11
+ BackgroundTaskSummary,
10
12
  FetchHistoryOptions,
11
13
  FetchHistoryResult,
12
14
  LLMProvider,
13
15
  NormalizedMessage,
16
+ WorkflowAgentActivity,
14
17
  } from '@/shared/types.js';
15
18
  import { AppError, sliceTailPage } from '@/shared/utils.js';
16
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
+ };
45
+
17
46
  type CreateAppSessionResult = {
18
47
  sessionId: string;
19
48
  provider: LLMProvider;
@@ -125,18 +154,61 @@ export const sessionsService = {
125
154
  },
126
155
 
127
156
  /**
128
- * 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.
129
159
  *
130
160
  * This is intentionally status-only: callers that only need sidebar activity
131
161
  * indicators should not attach to chat streams or request replayed messages.
132
162
  */
133
- listRunningSessions(): Array<{
134
- sessionId: string;
135
- provider: LLMProvider;
136
- startedAt: number;
137
- lastSeq: number;
138
- }> {
139
- 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;
140
212
  },
141
213
 
142
214
  /**
@@ -489,6 +561,34 @@ export const sessionsService = {
489
561
  };
490
562
  },
491
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
+
492
592
  /**
493
593
  * Resolves one session (by app id, falling back to the provider-native id)
494
594
  * to its metadata plus the owning project.