@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
@@ -5,7 +5,7 @@ import path from 'node:path';
5
5
  import pty, { type IPty } from 'node-pty';
6
6
  import { WebSocket, type RawData } from 'ws';
7
7
 
8
- import { parseIncomingJsonObject } from '@/shared/utils.js';
8
+ import { parseIncomingJsonObject, stripAnsiSequences } from '@/shared/utils.js';
9
9
 
10
10
  type ShellIncomingMessage = {
11
11
  type?: string;
@@ -19,8 +19,12 @@ type ShellIncomingMessage = {
19
19
  initialCommand?: string;
20
20
  isPlainShell?: boolean;
21
21
  forceRestart?: boolean;
22
+ bypassPermissions?: boolean;
23
+ colorScheme?: string;
22
24
  };
23
25
 
26
+ type ShellColorScheme = 'light' | 'dark';
27
+
24
28
  type PtySessionEntry = {
25
29
  pty: IPty;
26
30
  ws: WebSocket | null;
@@ -28,18 +32,17 @@ type PtySessionEntry = {
28
32
  timeoutId: NodeJS.Timeout | null;
29
33
  projectPath: string;
30
34
  sessionId: string | null;
35
+ // The app theme a Claude CLI in this pty was launched for; null for any other
36
+ // program. A running CLI keeps its launch-time colours, so a reattaching
37
+ // client needs this, not its own current theme, to know if they differ.
38
+ claudeColorScheme: ShellColorScheme | null;
31
39
  };
32
40
 
33
41
  const ptySessionsMap = new Map<string, PtySessionEntry>();
34
42
  const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
35
43
  const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
36
- const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
37
44
  const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
38
45
 
39
- function stripAnsiSequences(value: string): string {
40
- return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
41
- }
42
-
43
46
  function normalizeDetectedUrl(url: string): string | null {
44
47
  const cleanedUrl = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
45
48
  if (!cleanedUrl) {
@@ -89,6 +92,17 @@ function extractUrlsFromText(value: string): string[] {
89
92
  return Array.from(new Set([...directMatches, ...wrappedMatches]));
90
93
  }
91
94
 
95
+ /**
96
+ * Tells the client which app theme the Claude CLI in its pty was launched for,
97
+ * so the Shell can suggest a restart once the app theme no longer matches it.
98
+ * Clients that predate this frame ignore unknown frame types.
99
+ */
100
+ function sendClaudeColorScheme(ws: WebSocket, colorScheme: ShellColorScheme | null): void {
101
+ if (colorScheme && ws.readyState === WebSocket.OPEN) {
102
+ ws.send(JSON.stringify({ type: 'claude_theme', colorScheme }));
103
+ }
104
+ }
105
+
92
106
  function shouldAutoOpenUrlFromOutput(value: string): boolean {
93
107
  const normalizedOutput = value.toLowerCase();
94
108
  return (
@@ -129,6 +143,14 @@ function readNumber(value: unknown, fallback: number): number {
129
143
  return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
130
144
  }
131
145
 
146
+ /**
147
+ * Reads the app theme the Shell tab is painted in. Older clients send none and
148
+ * anything unexpected is ignored, which keeps the launch exactly as before.
149
+ */
150
+ function readColorScheme(value: unknown): ShellColorScheme | null {
151
+ return value === 'light' || value === 'dark' ? value : null;
152
+ }
153
+
132
154
  /**
133
155
  * Parses incoming websocket shell messages and keeps processing safe when
134
156
  * malformed payloads are received.
@@ -172,13 +194,23 @@ function resolveResumeSessionId(
172
194
  return resolvedSessionId;
173
195
  }
174
196
 
197
+ type ShellLaunch = {
198
+ command: string;
199
+ /**
200
+ * Whether the command starts a Claude CLI that paints in the app's colour
201
+ * scheme, so a later theme toggle can be applied by restarting it.
202
+ */
203
+ claudeFollowsAppTheme: boolean;
204
+ };
205
+
175
206
  /**
176
207
  * Resolves provider command line for plain shell and agent-backed shell modes.
177
208
  */
178
209
  function buildShellCommand(
179
210
  message: ShellIncomingMessage,
180
- dependencies: ShellWebSocketDependencies
181
- ): string {
211
+ dependencies: ShellWebSocketDependencies,
212
+ projectPath: string
213
+ ): ShellLaunch {
182
214
  const hasSession = readBoolean(message.hasSession);
183
215
  const initialCommand = readString(message.initialCommand);
184
216
  const provider = readString(message.provider, 'claude');
@@ -188,42 +220,251 @@ function buildShellCommand(
188
220
  (!!initialCommand && !hasSession) ||
189
221
  provider === 'plain-shell';
190
222
 
223
+ const otherProgram = (command: string): ShellLaunch => ({ command, claudeFollowsAppTheme: false });
224
+
191
225
  if (isPlainShell) {
192
- return initialCommand;
226
+ return otherProgram(initialCommand);
193
227
  }
194
228
 
195
229
  if (provider === 'cursor') {
196
230
  if (resumeSessionId) {
197
- return `cursor-agent --resume="${resumeSessionId}"`;
231
+ return otherProgram(`cursor-agent --resume="${resumeSessionId}"`);
198
232
  }
199
- return 'cursor-agent';
233
+ return otherProgram('cursor-agent');
200
234
  }
201
235
 
202
236
  if (provider === 'codex') {
203
237
  if (resumeSessionId) {
204
238
  if (os.platform() === 'win32') {
205
- return `codex resume "${resumeSessionId}"; if ($LASTEXITCODE -ne 0) { codex }`;
239
+ return otherProgram(`codex resume "${resumeSessionId}"; if ($LASTEXITCODE -ne 0) { codex }`);
206
240
  }
207
- return `codex resume "${resumeSessionId}" || codex`;
241
+ return otherProgram(`codex resume "${resumeSessionId}" || codex`);
208
242
  }
209
- return 'codex';
243
+ return otherProgram('codex');
210
244
  }
211
245
 
212
246
  if (provider === 'opencode') {
213
247
  if (resumeSessionId) {
214
- return `opencode --session "${resumeSessionId}"`;
248
+ return otherProgram(`opencode --session "${resumeSessionId}"`);
215
249
  }
216
- return initialCommand || 'opencode';
250
+ return otherProgram(initialCommand || 'opencode');
217
251
  }
218
252
 
219
- const command = initialCommand || 'claude';
253
+ // Launching with the flag is what unlocks "bypass permissions" in the CLI's
254
+ // shift+tab permission-mode cycle; it cannot be enabled from inside a
255
+ // session started without it.
256
+ const bypassFlag = readBoolean(message.bypassPermissions)
257
+ ? ' --dangerously-skip-permissions'
258
+ : '';
259
+ const theme = buildClaudeThemeLaunch(readColorScheme(message.colorScheme), projectPath);
260
+ const launchFlags = `${bypassFlag}${theme.flag}`;
220
261
  if (resumeSessionId) {
221
- if (os.platform() === 'win32') {
222
- return `claude --resume "${resumeSessionId}"; if ($LASTEXITCODE -ne 0) { claude }`;
262
+ const command = os.platform() === 'win32'
263
+ ? `claude --resume "${resumeSessionId}"${launchFlags}; if ($LASTEXITCODE -ne 0) { claude${launchFlags} }`
264
+ : `claude --resume "${resumeSessionId}"${launchFlags} || claude${launchFlags}`;
265
+ return { command, claudeFollowsAppTheme: theme.followsAppTheme };
266
+ }
267
+ if (initialCommand) {
268
+ // A caller's own command (e.g. `claude /login`) is launched as given.
269
+ return otherProgram(initialCommand);
270
+ }
271
+ return { command: `claude${launchFlags}`, claudeFollowsAppTheme: theme.followsAppTheme };
272
+ }
273
+
274
+ // Claude Code's built-in theme ids (the `/theme` picker of CLI 2.1.280). A
275
+ // `custom:<slug>` theme is valid too; any other value is ignored by the CLI.
276
+ const CLAUDE_THEME_IDS = new Set([
277
+ 'auto',
278
+ 'dark',
279
+ 'light',
280
+ 'dark-daltonized',
281
+ 'light-daltonized',
282
+ 'dark-ansi',
283
+ 'light-ansi',
284
+ ]);
285
+
286
+ // Dark themes and their light counterparts. A light theme, `auto` (it asks the
287
+ // terminal for its background, which xterm answers from the live theme) or a
288
+ // custom theme is the user's own choice and is left alone.
289
+ const LIGHT_CLAUDE_THEME_BY_DARK_THEME = new Map([
290
+ ['dark', 'light'],
291
+ ['dark-daltonized', 'light-daltonized'],
292
+ ['dark-ansi', 'light-ansi'],
293
+ ]);
294
+
295
+ // The CLI refuses settings files over 2 MiB, so a bigger one holds no theme it
296
+ // would use. The legacy global config (`~/.claude.json`) is not a settings file
297
+ // and can grow to several MB; its cap is only there so a huge file is never
298
+ // pulled into the server's memory.
299
+ const MAX_CLAUDE_SETTINGS_FILE_BYTES = 2 * 1024 * 1024;
300
+ const MAX_CLAUDE_GLOBAL_CONFIG_FILE_BYTES = 64 * 1024 * 1024;
301
+
302
+ /**
303
+ * Reads a config file only when it is a regular, non-empty file of sane size,
304
+ * else returns null. The project can be a cloned repository whose
305
+ * `.claude/settings.local.json` is a symlink to a FIFO or to a device such as
306
+ * /dev/zero: reading one would stall the server's event loop or exhaust its
307
+ * memory. `statSync` follows the symlink without opening the target. Empty
308
+ * files are skipped too: they hold no theme, and kernel pseudo-files (procfs,
309
+ * tracefs) report a size of 0 even when a read would block.
310
+ */
311
+ function readRegularConfigFile(filePath: string, maxBytes = MAX_CLAUDE_SETTINGS_FILE_BYTES): string | null {
312
+ try {
313
+ const stats = fs.statSync(filePath);
314
+ if (!stats.isFile() || stats.size === 0 || stats.size > maxBytes) {
315
+ return null;
223
316
  }
224
- return `claude --resume "${resumeSessionId}" || claude`;
317
+ return fs.readFileSync(filePath, 'utf8');
318
+ } catch {
319
+ return null;
225
320
  }
226
- return command;
321
+ }
322
+
323
+ /**
324
+ * Reads the `theme` key of one Claude config file. A missing, unreadable,
325
+ * special (FIFO, device) or unparsable file, or a value the CLI would reject,
326
+ * counts as unset.
327
+ */
328
+ function readClaudeThemeFile(filePath: string, maxBytes: number): string | null {
329
+ const text = readRegularConfigFile(filePath, maxBytes);
330
+ if (text === null) {
331
+ return null;
332
+ }
333
+ try {
334
+ const config: unknown = JSON.parse(text);
335
+ const theme = config && typeof config === 'object' ? (config as Record<string, unknown>).theme : undefined;
336
+ if (typeof theme !== 'string') {
337
+ return null;
338
+ }
339
+ return CLAUDE_THEME_IDS.has(theme) || theme.startsWith('custom:') ? theme : null;
340
+ } catch {
341
+ return null;
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Finds the theme Claude Code would start with in `projectPath`, read-only and
347
+ * in the CLI's own order: local, project and user settings, then the legacy
348
+ * global config key, then its "dark" default. Managed settings are not read:
349
+ * they outrank `--settings`, so no flag could change their theme anyway.
350
+ */
351
+ function resolveClaudeTheme(projectPath: string): string {
352
+ const configDirOverride = process.env.CLAUDE_CONFIG_DIR;
353
+ const configHome = configDirOverride || path.join(os.homedir(), '.claude');
354
+ const legacyGlobalConfigPath = path.join(configHome, '.config.json');
355
+ const configFiles: Array<[string, number]> = [
356
+ [path.join(projectPath, '.claude', 'settings.local.json'), MAX_CLAUDE_SETTINGS_FILE_BYTES],
357
+ [path.join(projectPath, '.claude', 'settings.json'), MAX_CLAUDE_SETTINGS_FILE_BYTES],
358
+ [path.join(configHome, 'settings.json'), MAX_CLAUDE_SETTINGS_FILE_BYTES],
359
+ [
360
+ fs.existsSync(legacyGlobalConfigPath)
361
+ ? legacyGlobalConfigPath
362
+ : path.join(configDirOverride || os.homedir(), '.claude.json'),
363
+ MAX_CLAUDE_GLOBAL_CONFIG_FILE_BYTES,
364
+ ],
365
+ ];
366
+
367
+ for (const [filePath, maxBytes] of configFiles) {
368
+ const theme = readClaudeThemeFile(filePath, maxBytes);
369
+ if (theme) {
370
+ return theme;
371
+ }
372
+ }
373
+ return 'dark';
374
+ }
375
+
376
+ let hasLoggedThemeSettingsFileError = false;
377
+
378
+ /**
379
+ * Returns a settings file that holds only `{"theme": theme}`, writing it when
380
+ * it is missing or differs. It lives in the app's own data folder
381
+ * (`~/.cloudcli`, like the chat assets); the user's Claude config is never
382
+ * written. The write goes through a temp file and a rename, so a CLI starting
383
+ * at the same time never reads half a file. Returns null, and logs once, when
384
+ * the file cannot be written (a read-only home, say): the launch then simply
385
+ * goes ahead without a theme.
386
+ */
387
+ function ensureClaudeThemeSettingsFile(theme: string): string | null {
388
+ const contents = `${JSON.stringify({ theme })}\n`;
389
+ let tempPath: string | null = null;
390
+ try {
391
+ const filePath = path.join(os.homedir(), '.cloudcli', `claude-shell-theme-${theme}.json`);
392
+ // Anything but the expected regular file is replaced; the rename never
393
+ // opens what it replaces.
394
+ if (readRegularConfigFile(filePath) !== contents) {
395
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
396
+ tempPath = `${filePath}.${process.pid}.tmp`;
397
+ fs.writeFileSync(tempPath, contents);
398
+ fs.renameSync(tempPath, filePath);
399
+ }
400
+ return filePath;
401
+ } catch (error) {
402
+ if (tempPath) {
403
+ // Asynchronous and unchecked: cleaning up must not throw into the launch.
404
+ fs.rm(tempPath, { force: true }, () => undefined);
405
+ }
406
+ if (!hasLoggedThemeSettingsFileError) {
407
+ hasLoggedThemeSettingsFileError = true;
408
+ const message = error instanceof Error ? error.message : String(error);
409
+ console.warn(`[WARN] Shell: cannot write the Claude theme settings file, launching without it: ${message}`);
410
+ }
411
+ return null;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Quotes one argument for the shell that runs the launch command. bash takes
417
+ * everything between single quotes literally, so a `'` closes the quote, adds
418
+ * an escaped one and reopens it. PowerShell (win32) single quotes are literal
419
+ * too, backslashes included; a quote is doubled, and PowerShell also treats
420
+ * the typographic single quotes as quote characters.
421
+ */
422
+ function quoteShellArgument(value: string): string {
423
+ if (os.platform() === 'win32') {
424
+ return `'${value.replace(/['\u2018\u2019\u201A\u201B]/g, '$&$&')}'`;
425
+ }
426
+ return `'${value.replace(/'/g, `'\\''`)}'`;
427
+ }
428
+
429
+ type ClaudeThemeLaunch = {
430
+ flag: string;
431
+ /** Whether the CLI will paint in the app's colour scheme. */
432
+ followsAppTheme: boolean;
433
+ };
434
+
435
+ /**
436
+ * Claude Code paints its own colours in its own theme, dark by default, so in
437
+ * a light Shell tab its prompt rows became dark bands. Only a light tab gets a
438
+ * flag, and only when the user's theme is a dark one: it is swapped for the
439
+ * matching light variant. `--settings` applies it to this launch only. It is
440
+ * given a file path rather than inline JSON because PowerShell strips the
441
+ * JSON's double quotes when it passes an argument to a native program.
442
+ *
443
+ * The CLI follows the app theme when it gets that flag, when a dark tab runs a
444
+ * dark theme, and with `auto` (it asks the terminal, which xterm answers from
445
+ * the live theme). A light or custom theme the user chose, or a flag that
446
+ * could not be written, paints the same in either app theme, so restarting
447
+ * after a toggle would change nothing.
448
+ */
449
+ function buildClaudeThemeLaunch(colorScheme: ShellColorScheme | null, projectPath: string): ClaudeThemeLaunch {
450
+ if (!colorScheme) {
451
+ return { flag: '', followsAppTheme: false };
452
+ }
453
+ const userTheme = resolveClaudeTheme(projectPath);
454
+ if (userTheme === 'auto') {
455
+ return { flag: '', followsAppTheme: true };
456
+ }
457
+ const lightTheme = LIGHT_CLAUDE_THEME_BY_DARK_THEME.get(userTheme);
458
+ if (!lightTheme) {
459
+ return { flag: '', followsAppTheme: false };
460
+ }
461
+ if (colorScheme === 'dark') {
462
+ return { flag: '', followsAppTheme: true };
463
+ }
464
+ const settingsPath = ensureClaudeThemeSettingsFile(lightTheme);
465
+ return settingsPath
466
+ ? { flag: ` --settings ${quoteShellArgument(settingsPath)}`, followsAppTheme: true }
467
+ : { flag: '', followsAppTheme: false };
227
468
  }
228
469
 
229
470
  function readEnvValue(env: NodeJS.ProcessEnv, key: string): string | undefined {
@@ -311,6 +552,7 @@ export function handleShellConnection(
311
552
  const provider = readString(data.provider, 'claude');
312
553
  const initialCommand = readString(data.initialCommand);
313
554
  const forceRestart = readBoolean(data.forceRestart);
555
+ const colorScheme = readColorScheme(data.colorScheme);
314
556
  const isPlainShell =
315
557
  readBoolean(data.isPlainShell) ||
316
558
  (!!initialCommand && !hasSession) ||
@@ -370,6 +612,7 @@ export function handleShellConnection(
370
612
  }
371
613
 
372
614
  existingSession.ws = ws;
615
+ sendClaudeColorScheme(ws, existingSession.claudeColorScheme);
373
616
  return;
374
617
  }
375
618
 
@@ -390,8 +633,13 @@ export function handleShellConnection(
390
633
  return;
391
634
  }
392
635
 
393
- const shellCommand = buildShellCommand(data, dependencies);
636
+ const { command: shellCommand, claudeFollowsAppTheme } = buildShellCommand(
637
+ data,
638
+ dependencies,
639
+ resolvedProjectPath,
640
+ );
394
641
  const resumeSessionId = resolveResumeSessionId(data, dependencies);
642
+ const claudeColorScheme = claudeFollowsAppTheme ? colorScheme : null;
395
643
  const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
396
644
  const shellArgs =
397
645
  os.platform() === 'win32' ? ['-Command', shellCommand] : ['-c', shellCommand];
@@ -410,6 +658,10 @@ export function handleShellConnection(
410
658
  TERM: 'xterm-256color',
411
659
  COLORTERM: 'truecolor',
412
660
  FORCE_COLOR: '3',
661
+ // rxvt's "foreground;background" palette indices (15 = white, 0 = black).
662
+ // Programs that cannot query the terminal (vim, Claude Code's "auto"
663
+ // theme before its OSC 11 reply arrives) read it to pick light or dark.
664
+ ...(colorScheme ? { COLORFGBG: colorScheme === 'light' ? '0;15' : '15;0' } : {}),
413
665
  },
414
666
  });
415
667
 
@@ -420,6 +672,7 @@ export function handleShellConnection(
420
672
  timeoutId: null,
421
673
  projectPath,
422
674
  sessionId,
675
+ claudeColorScheme,
423
676
  });
424
677
 
425
678
  shellProcess.onData((chunk) => {
@@ -548,6 +801,7 @@ export function handleShellConnection(
548
801
  data: welcomeMsg,
549
802
  })
550
803
  );
804
+ sendClaudeColorScheme(ws, claudeColorScheme);
551
805
  return;
552
806
  }
553
807
 
@@ -0,0 +1,167 @@
1
+ import assert from 'node:assert/strict';
2
+ import { EventEmitter } from 'node:events';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import test, { mock } from 'node:test';
7
+
8
+ import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
9
+ import { handleChatConnection } from '@/modules/websocket/services/chat-websocket.service.js';
10
+ import { chatRunRegistry } from '@/modules/websocket/services/chat-run-registry.service.js';
11
+ import { connectedClients } from '@/modules/websocket/services/websocket-state.service.js';
12
+
13
+ /**
14
+ * A session's turn ends while its agents, workflow or backgrounded command are
15
+ * still going. The run registry marks the run completed at the turn's
16
+ * `complete`, but the runtime keeps producing events through that run's
17
+ * writer — task progress, and the turn the CLI pushes when the task reports.
18
+ * A tab opened during that work has to attach to the run to see them, and the
19
+ * run has to still be there to attach to.
20
+ */
21
+
22
+ const SESSION_ID = 'held-session';
23
+ const FIVE_MINUTES = 5 * 60 * 1000;
24
+
25
+ function createFakeSocket() {
26
+ const socket = new EventEmitter() as EventEmitter & {
27
+ readyState: number;
28
+ frames: Array<Record<string, unknown>>;
29
+ send: (data: string) => void;
30
+ };
31
+ socket.readyState = 1;
32
+ socket.frames = [];
33
+ socket.send = (data: string) => socket.frames.push(JSON.parse(data) as Record<string, unknown>);
34
+ return socket;
35
+ }
36
+
37
+ async function withIsolatedDatabase(runTest: () => Promise<void>): Promise<void> {
38
+ const previousDatabasePath = process.env.DATABASE_PATH;
39
+ const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'chat-background-subscribe-'));
40
+ closeConnection();
41
+ process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db');
42
+ await initializeDatabase();
43
+ try {
44
+ sessionsDb.createAppSession(SESSION_ID, 'claude', tempDirectory);
45
+ await runTest();
46
+ } finally {
47
+ connectedClients.clear();
48
+ chatRunRegistry.clearAll();
49
+ chatRunRegistry.setRetentionGuard(() => false);
50
+ closeConnection();
51
+ if (previousDatabasePath === undefined) {
52
+ delete process.env.DATABASE_PATH;
53
+ } else {
54
+ process.env.DATABASE_PATH = previousDatabasePath;
55
+ }
56
+ await rm(tempDirectory, { recursive: true, force: true });
57
+ }
58
+ }
59
+
60
+ /** Starts a run for the session and ends its turn, leaving the run completed but registered. */
61
+ function startAndCompleteRun(connection: ReturnType<typeof createFakeSocket>) {
62
+ const run = chatRunRegistry.startRun({
63
+ appSessionId: SESSION_ID,
64
+ provider: 'claude',
65
+ providerSessionId: 'native-1',
66
+ connection: connection as never,
67
+ userId: 1,
68
+ });
69
+ assert.ok(run);
70
+ run.writer.send({ kind: 'complete', provider: 'claude', sessionId: 'native-1', exitCode: 0 });
71
+ assert.equal(chatRunRegistry.isProcessing(SESSION_ID), false);
72
+ return run;
73
+ }
74
+
75
+ const settle = () => new Promise((resolve) => { setTimeout(resolve, 30); });
76
+
77
+ test('a tab that subscribes during background work receives the run\'s later events', async () => {
78
+ await withIsolatedDatabase(async () => {
79
+ const run = startAndCompleteRun(createFakeSocket());
80
+
81
+ const newTab = createFakeSocket();
82
+ handleChatConnection(
83
+ newTab as never,
84
+ { user: { id: 1 } } as never,
85
+ { runtime: { hasBackgroundWork: (sessionId: string) => sessionId === SESSION_ID, getPendingApprovalsForSession: () => [] } as never },
86
+ );
87
+ newTab.emit('message', JSON.stringify({ type: 'chat.subscribe', sessions: [{ sessionId: SESSION_ID, lastSeq: 0 }] }));
88
+ await settle();
89
+
90
+ // Not processing — the composer stays usable — but attached.
91
+ assert.equal(newTab.frames[0]?.kind, 'chat_subscribed');
92
+ assert.equal(newTab.frames[0]?.isProcessing, false);
93
+
94
+ run.writer.send({ kind: 'task_status', provider: 'claude', sessionId: 'native-1', event: 'notification', taskId: 't1', status: 'completed' } as never);
95
+ assert.ok(
96
+ newTab.frames.some((frame) => frame.kind === 'task_status'),
97
+ 'the task event must reach the tab that subscribed after the turn ended',
98
+ );
99
+ });
100
+ });
101
+
102
+ test('a tab that subscribes to an idle session is not attached to its old run', async () => {
103
+ await withIsolatedDatabase(async () => {
104
+ const run = startAndCompleteRun(createFakeSocket());
105
+
106
+ const newTab = createFakeSocket();
107
+ handleChatConnection(
108
+ newTab as never,
109
+ { user: { id: 1 } } as never,
110
+ { runtime: { hasBackgroundWork: () => false, getPendingApprovalsForSession: () => [] } as never },
111
+ );
112
+ newTab.emit('message', JSON.stringify({ type: 'chat.subscribe', sessions: [{ sessionId: SESSION_ID, lastSeq: 0 }] }));
113
+ await settle();
114
+
115
+ run.writer.send({ kind: 'task_status', provider: 'claude', sessionId: 'native-1', event: 'notification', taskId: 't1', status: 'completed' } as never);
116
+ assert.ok(!newTab.frames.some((frame) => frame.kind === 'task_status'));
117
+ });
118
+ });
119
+
120
+ test('a completed run stays registered while its session still has background work', async () => {
121
+ mock.timers.enable({ apis: ['setTimeout'] });
122
+ try {
123
+ await withIsolatedDatabase(async () => {
124
+ let working = true;
125
+ chatRunRegistry.setRetentionGuard((sessionId) => sessionId === SESSION_ID && working);
126
+ startAndCompleteRun(createFakeSocket());
127
+
128
+ mock.timers.tick(FIVE_MINUTES);
129
+ assert.ok(chatRunRegistry.getRun(SESSION_ID), 'the run must outlive the retention window while work is outstanding');
130
+
131
+ working = false;
132
+ mock.timers.tick(FIVE_MINUTES);
133
+ assert.equal(chatRunRegistry.getRun(SESSION_ID), undefined, 'once the work is gone the run is evicted as before');
134
+ });
135
+ } finally {
136
+ mock.timers.reset();
137
+ }
138
+ });
139
+
140
+ test('a retained run\'s eviction timer cannot evict the session\'s next run early', async () => {
141
+ // Run one completes with work outstanding and is re-armed at five minutes;
142
+ // run two takes the slot and completes with two minutes of retention to
143
+ // go. Run one's timer firing on the slot alone would evict run two.
144
+ mock.timers.enable({ apis: ['setTimeout'] });
145
+ try {
146
+ await withIsolatedDatabase(async () => {
147
+ let working = true;
148
+ chatRunRegistry.setRetentionGuard((sessionId) => sessionId === SESSION_ID && working);
149
+ const first = startAndCompleteRun(createFakeSocket());
150
+ mock.timers.tick(FIVE_MINUTES);
151
+ assert.equal(chatRunRegistry.getRun(SESSION_ID), first);
152
+
153
+ working = false;
154
+ mock.timers.tick(FIVE_MINUTES - 2 * 60 * 1000);
155
+ const second = startAndCompleteRun(createFakeSocket());
156
+ assert.equal(chatRunRegistry.getRun(SESSION_ID), second);
157
+
158
+ // Run one's re-armed timer fires now; run two's has two minutes left.
159
+ mock.timers.tick(2 * 60 * 1000);
160
+ assert.equal(chatRunRegistry.getRun(SESSION_ID), second, 'run two keeps its own retention window');
161
+ mock.timers.tick(FIVE_MINUTES);
162
+ assert.equal(chatRunRegistry.getRun(SESSION_ID), undefined);
163
+ });
164
+ } finally {
165
+ mock.timers.reset();
166
+ }
167
+ });