@cloudcli-ai/cloudcli 1.37.2 → 1.37.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (453) hide show
  1. package/README.md +269 -0
  2. package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-Dmkwx38H.js} +1 -1
  3. package/dist/assets/{arc-B8jF-ja9.js → arc-DLe9AYuO.js} +1 -1
  4. package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-CBbbbNUW.js} +1 -1
  5. package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-CaMEaWu4.js} +1 -1
  6. package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-DhjMgNnR.js} +1 -1
  7. package/dist/assets/channel-CWrmwsir.js +1 -0
  8. package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-yjl3-F-i.js} +1 -1
  9. package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-DsAYaWEP.js} +1 -1
  10. package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-Bt5qmqOX.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-yWrWVvUt.js} +1 -1
  12. package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-Dt66K5xb.js} +1 -1
  13. package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-DA0vzaj4.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CUkbx96V.js} +1 -1
  15. package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-DWKxx1WH.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-BqluplN0.js} +1 -1
  17. package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-DelKg3Xf.js} +1 -1
  18. package/dist/assets/classDiagram-JCYQIIEL-COAGCC6w.js +1 -0
  19. package/dist/assets/classDiagram-v2-OCEON4UE-COAGCC6w.js +1 -0
  20. package/dist/assets/{cose-bilkent-JH36ORCC-s1hygY8e.js → cose-bilkent-JH36ORCC-DHV93-Em.js} +1 -1
  21. package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-B9HZgoGX.js} +1 -1
  22. package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-hFjjEQg8.js} +1 -1
  23. package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-CpeVOC7V.js} +1 -1
  24. package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-D0H1UoRA.js} +1 -1
  25. package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-6Y7BQUup.js} +1 -1
  26. package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-C0nNOwht.js} +1 -1
  27. package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-En6QlOVX.js} +1 -1
  28. package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-CmPiNeGD.js} +1 -1
  29. package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-Bygzp1Qt.js} +1 -1
  30. package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CwScRfzl.js} +1 -1
  31. package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-bKX-0xP6.js} +1 -1
  32. package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-DtZPKXGd.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-CUOtdL1o.js} +1 -1
  34. package/dist/assets/index-CdjIFxM2.js +5004 -0
  35. package/dist/assets/index-DgafhGTF.css +32 -0
  36. package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-ciRQTS0h.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-CgjhQZ84.js} +1 -1
  38. package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-BsbQSeIW.js} +1 -1
  39. package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-CrwzY9Jw.js} +1 -1
  40. package/dist/assets/{linear-iPR-J9f7.js → linear-BUaCDr6l.js} +1 -1
  41. package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-ClgdUU0j.js} +5 -5
  42. package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-BictljY1.js} +1 -1
  43. package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-BXdByy_U.js} +1 -1
  44. package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-uZsMfkRX.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-7lCv4QIj.js} +1 -1
  46. package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-07dxIWbi.js} +1 -1
  47. package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Cb31Yv1_.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-X0YPSyF9.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-BdBxSIlP.js} +1 -1
  50. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  51. package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-DQM5xAyD.js} +1 -1
  52. package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-DJ3xkLUF.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-UEYNNEHI-vAaE4zNo.js +1 -0
  54. package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-rjfYdW0w.js} +2 -2
  55. package/dist/assets/swimlanesDiagram-ULZ7WXOC-TV2IF4Nq.js +8 -0
  56. package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-DZMra5do.js} +1 -1
  57. package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
  58. package/dist/assets/{vennDiagram-T6HMQDX7-DuF1uZMY.js → vennDiagram-T6HMQDX7-BevD8uu0.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-DsIyXB7z.js} +1 -1
  60. package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-G3PlF3ko.js} +1 -1
  61. package/dist/index.html +3 -3
  62. package/dist-server/server/index.js +11 -4
  63. package/dist-server/server/index.js.map +1 -1
  64. package/dist-server/server/modules/agent/agent.module.js +9 -2
  65. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  66. package/dist-server/server/modules/agent/agent.routes.js +177 -130
  67. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  68. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js +306 -0
  69. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js.map +1 -0
  70. package/dist-server/server/modules/agent/tests/agent.routes.test.js +48 -2
  71. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  72. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  73. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  74. package/dist-server/server/modules/database/connection.js +1 -1
  75. package/dist-server/server/modules/database/connection.js.map +1 -1
  76. package/dist-server/server/modules/database/index.js +5 -0
  77. package/dist-server/server/modules/database/index.js.map +1 -1
  78. package/dist-server/server/modules/database/migrations.js +45 -1
  79. package/dist-server/server/modules/database/migrations.js.map +1 -1
  80. package/dist-server/server/modules/database/repositories/provider-models.js +60 -6
  81. package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -1
  82. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  83. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  84. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  85. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  86. package/dist-server/server/modules/database/repositories/sessions.db.js +144 -5
  87. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  88. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  89. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  90. package/dist-server/server/modules/database/schema.js +99 -0
  91. package/dist-server/server/modules/database/schema.js.map +1 -1
  92. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +99 -0
  93. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -1
  94. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  95. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  96. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  97. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  98. package/dist-server/server/modules/file-tree/file-tree.module.js +2 -1
  99. package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
  100. package/dist-server/server/modules/file-tree/file-tree.service.js +44 -7
  101. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  102. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +126 -1
  103. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  104. package/dist-server/server/modules/git/git-branch-diff.service.js +142 -0
  105. package/dist-server/server/modules/git/git-branch-diff.service.js.map +1 -0
  106. package/dist-server/server/modules/git/git-branch.service.js.map +1 -1
  107. package/dist-server/server/modules/git/git.routes.js +92 -5
  108. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  109. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js +160 -0
  110. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js.map +1 -0
  111. package/dist-server/server/modules/projects/project-clone.routes.js +108 -0
  112. package/dist-server/server/modules/projects/project-clone.routes.js.map +1 -0
  113. package/dist-server/server/modules/projects/projects.routes.js +13 -66
  114. package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
  115. package/dist-server/server/modules/projects/services/project-clone-request.service.js +36 -0
  116. package/dist-server/server/modules/projects/services/project-clone-request.service.js.map +1 -0
  117. package/dist-server/server/modules/projects/services/project-clone.service.js +86 -32
  118. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
  119. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  120. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  121. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  122. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  123. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js +191 -0
  124. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js.map +1 -0
  125. package/dist-server/server/modules/projects/tests/project-clone.service.test.js +275 -0
  126. package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -1
  127. package/dist-server/server/modules/providers/index.js +3 -0
  128. package/dist-server/server/modules/providers/index.js.map +1 -1
  129. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +62 -9
  130. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  131. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  132. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  133. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +31 -12
  134. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  135. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +472 -53
  136. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  137. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +38 -11
  138. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  139. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +1007 -60
  140. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  141. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  142. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  143. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  144. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  145. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  146. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  147. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  148. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  149. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +47 -0
  150. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  151. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +71 -53
  152. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  153. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  154. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  155. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1621 -431
  156. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  157. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  158. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  159. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +9 -2
  160. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
  161. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +16 -8
  162. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  163. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  164. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +274 -9
  165. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  166. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  167. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -1
  168. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +19 -2
  169. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  170. package/dist-server/server/modules/providers/provider.routes.js +134 -2
  171. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  172. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js +100 -0
  173. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js.map +1 -0
  174. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js +97 -0
  175. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js.map +1 -0
  176. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +23 -1
  177. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  178. package/dist-server/server/modules/providers/services/provider-models.service.js +59 -5
  179. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  180. package/dist-server/server/modules/providers/services/provider-runtime.service.js +8 -0
  181. package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
  182. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  183. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  184. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +2 -4
  185. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  186. package/dist-server/server/modules/providers/services/session-history-cache.service.js +91 -0
  187. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  188. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  189. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  190. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  191. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  192. package/dist-server/server/modules/providers/services/sessions.service.js +252 -12
  193. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  194. package/dist-server/server/modules/providers/tests/claude-auth.test.js +183 -0
  195. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  196. package/dist-server/server/modules/providers/tests/claude-background-work.test.js +172 -0
  197. package/dist-server/server/modules/providers/tests/claude-background-work.test.js.map +1 -0
  198. package/dist-server/server/modules/providers/tests/claude-compaction.test.js +84 -0
  199. package/dist-server/server/modules/providers/tests/claude-compaction.test.js.map +1 -0
  200. package/dist-server/server/modules/providers/tests/claude-message-model.test.js +123 -0
  201. package/dist-server/server/modules/providers/tests/claude-message-model.test.js.map +1 -0
  202. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  203. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  204. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js +265 -0
  205. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js.map +1 -0
  206. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +1815 -33
  207. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  208. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js +228 -0
  209. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js.map +1 -0
  210. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  211. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  212. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  213. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  214. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  215. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  216. package/dist-server/server/modules/providers/tests/codex-models.test.js +25 -0
  217. package/dist-server/server/modules/providers/tests/codex-models.test.js.map +1 -0
  218. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +83 -0
  219. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  220. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  221. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  222. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +354 -15
  223. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  224. package/dist-server/server/modules/providers/tests/cursor-auth.test.js +167 -0
  225. package/dist-server/server/modules/providers/tests/cursor-auth.test.js.map +1 -0
  226. package/dist-server/server/modules/providers/tests/opencode-models.test.js +196 -4
  227. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  228. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +19 -1
  229. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  230. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js +30 -0
  231. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js.map +1 -0
  232. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +79 -1
  233. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
  234. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  235. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  236. package/dist-server/server/modules/providers/tests/provider.routes.test.js +190 -1
  237. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  238. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  239. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  240. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +139 -0
  241. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  242. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  243. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  244. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  245. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  246. package/dist-server/server/modules/providers/tests/sessions.service.test.js +219 -1
  247. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  248. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  249. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  250. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  251. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  252. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  253. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  254. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +175 -0
  255. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  256. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  257. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  258. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +290 -0
  259. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  260. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  261. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  262. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  263. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  264. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  265. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  266. package/dist-server/server/modules/user/user.module.js +10 -1
  267. package/dist-server/server/modules/user/user.module.js.map +1 -1
  268. package/dist-server/server/modules/user/user.routes.js +44 -0
  269. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  270. package/dist-server/server/modules/user/user.service.js +75 -0
  271. package/dist-server/server/modules/user/user.service.js.map +1 -1
  272. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  273. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  274. package/dist-server/server/modules/websocket/index.js +7 -0
  275. package/dist-server/server/modules/websocket/index.js.map +1 -1
  276. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +37 -49
  277. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  278. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  279. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  280. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +204 -10
  281. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  282. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  283. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  284. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +236 -20
  285. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  286. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js +137 -0
  287. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js.map +1 -0
  288. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  289. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  290. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  291. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  292. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js +96 -0
  293. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js.map +1 -0
  294. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  295. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  296. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +500 -0
  297. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  298. package/dist-server/server/shared/claude-cli-path.js +22 -5
  299. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  300. package/dist-server/server/shared/index.js +3 -0
  301. package/dist-server/server/shared/index.js.map +1 -0
  302. package/dist-server/server/shared/message-unification.js +376 -0
  303. package/dist-server/server/shared/message-unification.js.map +1 -0
  304. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  305. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  306. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  307. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  308. package/dist-server/server/shared/tests/read-only-roots.test.js +124 -0
  309. package/dist-server/server/shared/tests/read-only-roots.test.js.map +1 -0
  310. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js +31 -0
  311. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js.map +1 -0
  312. package/dist-server/server/shared/utils.js +160 -0
  313. package/dist-server/server/shared/utils.js.map +1 -1
  314. package/electron/desktopWindow.js +6 -0
  315. package/electron/viewHost.js +25 -2
  316. package/package.json +20 -17
  317. package/server/index.ts +16 -5
  318. package/server/modules/agent/agent.module.ts +11 -1
  319. package/server/modules/agent/agent.routes.ts +200 -142
  320. package/server/modules/agent/tests/agent-run-registration.test.ts +348 -0
  321. package/server/modules/agent/tests/agent.routes.test.ts +51 -2
  322. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  323. package/server/modules/database/connection.ts +2 -1
  324. package/server/modules/database/index.ts +10 -0
  325. package/server/modules/database/migrations.ts +51 -0
  326. package/server/modules/database/repositories/provider-models.ts +79 -6
  327. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  328. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  329. package/server/modules/database/repositories/sessions.db.ts +204 -3
  330. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  331. package/server/modules/database/schema.ts +103 -0
  332. package/server/modules/database/tests/provider-models.db.integration.test.ts +113 -0
  333. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  334. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  335. package/server/modules/file-tree/file-tree.module.ts +2 -1
  336. package/server/modules/file-tree/file-tree.service.ts +49 -7
  337. package/server/modules/file-tree/tests/file-tree.service.test.ts +163 -1
  338. package/server/modules/git/git-branch-diff.service.ts +204 -0
  339. package/server/modules/git/git-branch.service.ts +2 -12
  340. package/server/modules/git/git.routes.ts +100 -6
  341. package/server/modules/git/tests/git-branch-diff.service.test.ts +210 -0
  342. package/server/modules/projects/project-clone.routes.ts +142 -0
  343. package/server/modules/projects/projects.routes.ts +16 -81
  344. package/server/modules/projects/services/project-clone-request.service.ts +56 -0
  345. package/server/modules/projects/services/project-clone.service.ts +102 -34
  346. package/server/modules/projects/services/project-delete.service.ts +13 -1
  347. package/server/modules/projects/services/project-star.service.ts +2 -2
  348. package/server/modules/projects/tests/project-clone.routes.test.ts +246 -0
  349. package/server/modules/projects/tests/project-clone.service.test.ts +376 -0
  350. package/server/modules/providers/index.ts +4 -0
  351. package/server/modules/providers/list/claude/claude-auth.provider.ts +81 -11
  352. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  353. package/server/modules/providers/list/claude/claude-models.provider.ts +37 -17
  354. package/server/modules/providers/list/claude/claude-runtime.provider.js +510 -54
  355. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +40 -14
  356. package/server/modules/providers/list/claude/claude-sessions.provider.ts +1195 -63
  357. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  358. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  359. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  360. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  361. package/server/modules/providers/list/codex/codex-models.provider.ts +47 -0
  362. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +105 -70
  363. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  364. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1847 -455
  365. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  366. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +10 -2
  367. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +17 -8
  368. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  369. package/server/modules/providers/list/opencode/opencode-models.provider.ts +297 -9
  370. package/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  371. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +30 -2
  372. package/server/modules/providers/provider.routes.ts +162 -2
  373. package/server/modules/providers/services/claude-cli-liveness.service.ts +121 -0
  374. package/server/modules/providers/services/claude-settings-permissions.service.ts +127 -0
  375. package/server/modules/providers/services/provider-capabilities.service.ts +40 -1
  376. package/server/modules/providers/services/provider-models.service.ts +71 -4
  377. package/server/modules/providers/services/provider-runtime.service.ts +11 -0
  378. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  379. package/server/modules/providers/services/session-conversations-search.service.ts +2 -5
  380. package/server/modules/providers/services/session-history-cache.service.ts +150 -0
  381. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  382. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  383. package/server/modules/providers/services/sessions.service.ts +309 -17
  384. package/server/modules/providers/tests/claude-auth.test.ts +208 -0
  385. package/server/modules/providers/tests/claude-background-work.test.ts +213 -0
  386. package/server/modules/providers/tests/claude-compaction.test.ts +101 -0
  387. package/server/modules/providers/tests/claude-message-model.test.ts +160 -0
  388. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  389. package/server/modules/providers/tests/claude-runtime-hold.test.ts +309 -0
  390. package/server/modules/providers/tests/claude-sessions.test.ts +2167 -37
  391. package/server/modules/providers/tests/claude-settings-permissions.test.ts +271 -0
  392. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  393. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  394. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  395. package/server/modules/providers/tests/codex-models.test.ts +37 -0
  396. package/server/modules/providers/tests/codex-runtime.test.ts +94 -0
  397. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  398. package/server/modules/providers/tests/codex-sessions.test.ts +396 -15
  399. package/server/modules/providers/tests/cursor-auth.test.ts +187 -0
  400. package/server/modules/providers/tests/opencode-models.test.ts +266 -8
  401. package/server/modules/providers/tests/opencode-sessions.test.ts +40 -1
  402. package/server/modules/providers/tests/provider-capabilities.service.test.ts +37 -0
  403. package/server/modules/providers/tests/provider-models.service.test.ts +99 -1
  404. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  405. package/server/modules/providers/tests/provider.routes.test.ts +230 -1
  406. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  407. package/server/modules/providers/tests/session-history-cache.service.test.ts +165 -0
  408. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  409. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  410. package/server/modules/providers/tests/sessions.service.test.ts +303 -1
  411. package/server/modules/providers/tests/skills.test.ts +21 -5
  412. package/server/modules/scheduled-messages/index.ts +8 -0
  413. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  414. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +216 -0
  415. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  416. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +354 -0
  417. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  418. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  419. package/server/modules/user/tests/user.service.test.ts +90 -0
  420. package/server/modules/user/user.module.ts +10 -1
  421. package/server/modules/user/user.routes.ts +44 -0
  422. package/server/modules/user/user.service.ts +105 -0
  423. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  424. package/server/modules/websocket/README.md +4 -2
  425. package/server/modules/websocket/index.ts +8 -0
  426. package/server/modules/websocket/services/chat-run-registry.service.ts +45 -54
  427. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  428. package/server/modules/websocket/services/chat-websocket.service.ts +302 -16
  429. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  430. package/server/modules/websocket/services/shell-websocket.service.ts +276 -22
  431. package/server/modules/websocket/tests/chat-background-subscribe.test.ts +167 -0
  432. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  433. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  434. package/server/modules/websocket/tests/chat-stop-task.test.ts +127 -0
  435. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  436. package/server/modules/websocket/tests/shell-websocket.service.test.ts +629 -0
  437. package/server/shared/claude-cli-path.ts +24 -6
  438. package/server/shared/index.ts +3 -0
  439. package/server/shared/interfaces.ts +91 -0
  440. package/server/shared/message-unification.ts +449 -0
  441. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  442. package/server/shared/tests/message-unification.test.ts +218 -0
  443. package/server/shared/tests/read-only-roots.test.ts +139 -0
  444. package/server/shared/tests/strip-ansi-sequences.test.ts +42 -0
  445. package/server/shared/types.ts +382 -7
  446. package/server/shared/utils.ts +176 -0
  447. package/dist/assets/channel-_falYbU1.js +0 -1
  448. package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
  449. package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
  450. package/dist/assets/index-CzPLBvXT.css +0 -32
  451. package/dist/assets/index-DFkBBuSA.js +0 -1549
  452. package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
  453. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
@@ -1,8 +1,15 @@
1
1
  import assert from 'node:assert/strict';
2
+ import { execFile } from 'node:child_process';
2
3
  import { EventEmitter } from 'node:events';
4
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
5
+ import http from 'node:http';
6
+ import type { AddressInfo } from 'node:net';
7
+ import os from 'node:os';
3
8
  import path from 'node:path';
4
9
  import { PassThrough } from 'node:stream';
5
10
  import test from 'node:test';
11
+ import { pathToFileURL } from 'node:url';
12
+ import { promisify } from 'node:util';
6
13
 
7
14
  import { startCloneProject } from '@/modules/projects/services/project-clone.service.js';
8
15
  import { AppError } from '@/shared/utils.js';
@@ -110,6 +117,66 @@ test('startCloneProject rejects github URL values that begin with option prefixe
110
117
  );
111
118
  });
112
119
 
120
+ for (const [label, githubUrl] of [
121
+ ['a token as the user', 'https://ghp_supersecrettoken1234567890abcd@github.com/example/repo.git'],
122
+ ['a user and password', 'https://user:ghp_supersecrettoken1234567890abcd@github.com/example/repo.git'],
123
+ ['a password on an ssh URL', 'ssh://git:secret@github.com/example/repo.git'],
124
+ ] as const) {
125
+ test(`startCloneProject rejects a github URL carrying ${label} before git sees it`, async () => {
126
+ // A credential in the URL would ride git's argv, its stderr and the
127
+ // clone's `.git/config` — every channel the token field keeps it out of.
128
+ let spawned = 0;
129
+ await assert.rejects(
130
+ async () =>
131
+ startCloneProject(
132
+ { workspacePath: '/workspace/root', githubUrl, userId: 1 },
133
+ { onProgress: () => undefined, onComplete: () => undefined },
134
+ buildDependencies({ spawnGitClone: () => { spawned += 1; throw new Error('must not spawn'); } }),
135
+ ),
136
+ (error: unknown) => {
137
+ assert.ok(error instanceof AppError);
138
+ assert.equal(error.code, 'GITHUB_URL_CARRIES_CREDENTIALS');
139
+ assert.ok(!error.message.includes('ghp_supersecrettoken'), 'the error must not echo the credential');
140
+ return true;
141
+ },
142
+ );
143
+ assert.equal(spawned, 0);
144
+ });
145
+ }
146
+
147
+ test('startCloneProject rejects an http(s) URL the parser cannot read before git carries it', async () => {
148
+ // 'https://token@github.com:notaport/x' is no URL to the parser, so the
149
+ // credential check could not see the token — and git would have had it on
150
+ // its command line until curl refused the port.
151
+ let spawned = 0;
152
+ await assert.rejects(
153
+ async () =>
154
+ startCloneProject(
155
+ { workspacePath: '/workspace/root', githubUrl: 'https://ghp_supersecrettoken1234567890abcd@github.com:notaport/example/repo.git', userId: 1 },
156
+ { onProgress: () => undefined, onComplete: () => undefined },
157
+ buildDependencies({ spawnGitClone: () => { spawned += 1; throw new Error('must not spawn'); } }),
158
+ ),
159
+ (error: unknown) => {
160
+ assert.ok(error instanceof AppError);
161
+ assert.equal(error.code, 'INVALID_GITHUB_URL');
162
+ return true;
163
+ },
164
+ );
165
+ assert.equal(spawned, 0);
166
+ });
167
+
168
+ test('startCloneProject accepts the ssh login name that is not a credential', async () => {
169
+ // `git@` on an SSH URL is the login every SSH clone uses, not a secret.
170
+ const gitProcess = createMockGitProcess();
171
+ const operation = await startCloneProject(
172
+ { workspacePath: '/workspace/root', githubUrl: 'ssh://git@github.com/example/repo.git', userId: 1 },
173
+ { onProgress: () => undefined, onComplete: () => undefined },
174
+ buildDependencies({ spawnGitClone: () => gitProcess as never }),
175
+ );
176
+ gitProcess.emit('close', 0);
177
+ await operation.waitForCompletion;
178
+ });
179
+
113
180
  test('startCloneProject rejects when selected github token does not exist', async () => {
114
181
  await assert.rejects(
115
182
  async () =>
@@ -181,3 +248,312 @@ test('startCloneProject completes and emits complete payload when git exits succ
181
248
  assert.equal(resolvedCompletePayload.message, 'Repository cloned successfully');
182
249
  assert.equal((resolvedCompletePayload.project.projectId as string) || '', 'project-1');
183
250
  });
251
+
252
+ type SpawnCall = { cloneUrl: string; clonePath: string; environment: NodeJS.ProcessEnv };
253
+
254
+ /**
255
+ * Runs a clone against a mock git process and captures exactly what would be
256
+ * handed to `spawn`: the URL and path (the only variable argv elements) and
257
+ * the environment. The token has to reach git through the environment alone.
258
+ */
259
+ async function captureSpawnCall(input: {
260
+ githubTokenId?: number;
261
+ newGithubToken?: string;
262
+ storedToken?: string;
263
+ }): Promise<SpawnCall> {
264
+ const gitProcess = createMockGitProcess();
265
+ let spawnCall: SpawnCall | null = null;
266
+
267
+ const operation = await startCloneProject(
268
+ {
269
+ workspacePath: '/workspace/root',
270
+ githubUrl: 'https://github.com/example/repo.git',
271
+ githubTokenId: input.githubTokenId,
272
+ newGithubToken: input.newGithubToken,
273
+ userId: 1,
274
+ },
275
+ {
276
+ onProgress: () => undefined,
277
+ onComplete: () => undefined,
278
+ },
279
+ buildDependencies({
280
+ getGithubTokenById: async () => ({ github_token: input.storedToken ?? 'unused' }),
281
+ spawnGitClone: (cloneUrl, clonePath, environment) => {
282
+ spawnCall = { cloneUrl, clonePath, environment };
283
+ return gitProcess as any;
284
+ },
285
+ }),
286
+ );
287
+
288
+ gitProcess.emit('close', 0);
289
+ await operation.waitForCompletion;
290
+
291
+ assert.notEqual(spawnCall, null, 'git was never spawned');
292
+ return spawnCall as unknown as SpawnCall;
293
+ }
294
+
295
+ for (const [label, input, token] of [
296
+ ['a stored token', { githubTokenId: 7, storedToken: 'ghp_storedsecret1234567890' }, 'ghp_storedsecret1234567890'],
297
+ // Characters `url.username=` would have percent-encoded, so a check for the
298
+ // raw token alone could not have caught the URL channel.
299
+ ['a new token', { newGithubToken: 'ghp_new:secret@with/odd-chars' }, 'ghp_new:secret@with/odd-chars'],
300
+ ] as const) {
301
+ test(`startCloneProject hands git ${label} through the credential helper, never the clone URL`, async () => {
302
+ const { cloneUrl, clonePath, environment } = await captureSpawnCall(input);
303
+
304
+ // The URL git receives is the one the user typed: no username, no password,
305
+ // nothing encoded — so neither argv, stderr nor `.git/config` can carry it.
306
+ assert.equal(cloneUrl, 'https://github.com/example/repo.git');
307
+ assert.equal(clonePath, path.join('/workspace/root', 'repo'));
308
+
309
+ assert.equal(environment.CLOUDCLI_GITHUB_TOKEN, token);
310
+ assert.equal(environment.GIT_CONFIG_COUNT, '2');
311
+ assert.equal(environment.GIT_CONFIG_KEY_0, 'credential.helper');
312
+ assert.equal(environment.GIT_CONFIG_VALUE_0, '');
313
+ assert.equal(environment.GIT_CONFIG_KEY_1, 'credential.https://github.com.helper');
314
+ assert.match(environment.GIT_CONFIG_VALUE_1 ?? '', /\$CLOUDCLI_GITHUB_TOKEN/);
315
+ assert.equal(environment.GIT_TERMINAL_PROMPT, '0');
316
+
317
+ // The helper's command line is itself a process argv, so the token must be
318
+ // read from the variable rather than pasted into the helper text.
319
+ for (const [name, value] of Object.entries(environment)) {
320
+ if (name === 'CLOUDCLI_GITHUB_TOKEN') {
321
+ continue;
322
+ }
323
+ assert.ok(!(value ?? '').includes(token), `token leaked into ${name}`);
324
+ }
325
+ });
326
+ }
327
+
328
+ test('startCloneProject leaves the credential helper unset when no token was given', async () => {
329
+ const { cloneUrl, environment } = await captureSpawnCall({});
330
+
331
+ assert.equal(cloneUrl, 'https://github.com/example/repo.git');
332
+ assert.equal(environment.GIT_TERMINAL_PROMPT, '0');
333
+ assert.equal(environment.GIT_CONFIG_COUNT, undefined);
334
+ assert.equal(environment.CLOUDCLI_GITHUB_TOKEN, undefined);
335
+ });
336
+
337
+ /**
338
+ * `git clone --progress` writes every byte of its progress to stderr — stdout
339
+ * stays empty — so these tests drive stderr, the pipe that carries the progress
340
+ * the user watches and the reason a clone failed.
341
+ */
342
+ async function runCloneWithStderr(
343
+ chunks: string[],
344
+ exitCode: number | null,
345
+ ): Promise<{ progressMessages: string[]; failure: AppError | null }> {
346
+ const gitProcess = createMockGitProcess();
347
+ const progressMessages: string[] = [];
348
+
349
+ const operation = await startCloneProject(
350
+ {
351
+ workspacePath: '/workspace/root',
352
+ githubUrl: 'https://github.com/example/repo.git',
353
+ newGithubToken: 'ghp_supersecrettoken1234567890abcd',
354
+ userId: 1,
355
+ },
356
+ {
357
+ onProgress: (message) => {
358
+ progressMessages.push(message);
359
+ },
360
+ onComplete: () => undefined,
361
+ },
362
+ buildDependencies({ spawnGitClone: () => gitProcess as any }),
363
+ );
364
+
365
+ for (const chunk of chunks) {
366
+ gitProcess.stderr.write(chunk);
367
+ }
368
+ gitProcess.stderr.end();
369
+ // Let the PassThrough deliver every `data` event and its `end` before the
370
+ // process is reported closed, exactly as a real pipe does.
371
+ await new Promise((resolve) => setImmediate(resolve));
372
+ gitProcess.emit('close', exitCode);
373
+
374
+ let failure: AppError | null = null;
375
+ try {
376
+ await operation.waitForCompletion;
377
+ } catch (error) {
378
+ failure = error as AppError;
379
+ }
380
+
381
+ return { progressMessages, failure };
382
+ }
383
+
384
+ test('startCloneProject forwards each stderr chunk to onProgress as it arrives', async () => {
385
+ const gitProcess = createMockGitProcess();
386
+ const progressMessages: string[] = [];
387
+
388
+ const operation = await startCloneProject(
389
+ {
390
+ workspacePath: '/workspace/root',
391
+ githubUrl: 'https://github.com/example/repo.git',
392
+ newGithubToken: 'ghp_supersecrettoken1234567890abcd',
393
+ userId: 1,
394
+ },
395
+ {
396
+ onProgress: (message) => {
397
+ progressMessages.push(message);
398
+ },
399
+ onComplete: () => undefined,
400
+ },
401
+ buildDependencies({ spawnGitClone: () => gitProcess as any }),
402
+ );
403
+
404
+ assert.deepEqual(progressMessages, ["Cloning into 'repo'..."]);
405
+
406
+ // Each chunk has to surface on its own event before the next one is written
407
+ // and long before the process exits: the progress UI is fed by these events,
408
+ // so anything buffered until close would leave the user staring at nothing.
409
+ const chunks = [
410
+ 'remote: Enumerating objects: 20, done.\n',
411
+ 'Receiving objects: 45% (9/20)\r',
412
+ 'Receiving objects: 100% (20/20), done.\n',
413
+ ];
414
+ for (const [index, chunk] of chunks.entries()) {
415
+ gitProcess.stderr.write(chunk);
416
+ await new Promise((resolve) => setImmediate(resolve));
417
+ assert.deepEqual(
418
+ progressMessages,
419
+ ["Cloning into 'repo'...", ...chunks.slice(0, index + 1).map((line) => line.trim())],
420
+ `chunk ${index + 1} did not arrive on its own event`,
421
+ );
422
+ }
423
+
424
+ gitProcess.stderr.end();
425
+ gitProcess.emit('close', 0);
426
+ await operation.waitForCompletion;
427
+
428
+ assert.deepEqual(progressMessages, [
429
+ "Cloning into 'repo'...",
430
+ 'remote: Enumerating objects: 20, done.',
431
+ 'Receiving objects: 45% (9/20)',
432
+ 'Receiving objects: 100% (20/20), done.',
433
+ ]);
434
+ });
435
+
436
+ test('startCloneProject reports the git failure reason from stderr', async () => {
437
+ const { failure } = await runCloneWithStderr(
438
+ ["remote: Repository not found.\nfatal: repository 'https://github.com/example/repo.git/' not found\n"],
439
+ 128,
440
+ );
441
+
442
+ assert.equal(failure?.code, 'GIT_CLONE_FAILED');
443
+ assert.equal(failure?.message, 'Repository not found. Please check the URL and ensure you have access.');
444
+ });
445
+
446
+ test('startCloneProject falls back to the last stderr text for an unrecognised failure', async () => {
447
+ const { failure } = await runCloneWithStderr(
448
+ ["fatal: unable to access 'https://github.com/example/repo.git/': SSL error\n"],
449
+ 128,
450
+ );
451
+
452
+ assert.equal(failure?.code, 'GIT_CLONE_FAILED');
453
+ assert.equal(failure?.message, "fatal: unable to access 'https://github.com/example/repo.git/': SSL error");
454
+ });
455
+
456
+ const execFileAsync = promisify(execFile);
457
+
458
+ /**
459
+ * Real git, real clone: proves the credential-helper environment is something
460
+ * git accepts, and that the resulting repository records a credential-free
461
+ * remote. The `file://` transport never asks the helper for anything, so a
462
+ * placeholder token stands in for the real one.
463
+ */
464
+ test('startCloneProject clones a real repository with the credential helper in git environment', async () => {
465
+ const root = await mkdtemp(path.join(os.tmpdir(), 'project-clone-service-'));
466
+ try {
467
+ const seedPath = path.join(root, 'seed');
468
+ const originPath = path.join(root, 'origin.git');
469
+ const workspacePath = path.join(root, 'workspace');
470
+ await execFileAsync('git', ['init', '-q', seedPath]);
471
+ await execFileAsync('git', [
472
+ '-C', seedPath,
473
+ '-c', 'user.email=clone@example.test',
474
+ '-c', 'user.name=Clone Test',
475
+ 'commit', '-q', '--allow-empty', '-m', 'initial',
476
+ ]);
477
+ await execFileAsync('git', ['clone', '-q', '--bare', seedPath, originPath]);
478
+
479
+ const originUrl = pathToFileURL(originPath).href;
480
+ const token = 'ghp_placeholder-file-transport-never-asks';
481
+ const progressMessages: string[] = [];
482
+ let completeMessage = '';
483
+
484
+ const operation = await startCloneProject(
485
+ {
486
+ workspacePath,
487
+ githubUrl: originUrl,
488
+ newGithubToken: token,
489
+ userId: 1,
490
+ },
491
+ {
492
+ onProgress: (message) => {
493
+ progressMessages.push(message);
494
+ },
495
+ onComplete: ({ message }) => {
496
+ completeMessage = message;
497
+ },
498
+ },
499
+ {
500
+ validatePath: async () => ({ valid: true, resolvedPath: workspacePath }),
501
+ registerProject: async (projectPath, customName) => ({
502
+ project: { path: projectPath, name: customName },
503
+ }),
504
+ },
505
+ );
506
+ await operation.waitForCompletion;
507
+
508
+ assert.equal(completeMessage, 'Repository cloned successfully');
509
+ // The first message is synthesised by the service; everything after it
510
+ // came off git's own stderr, which is what the progress UI relies on.
511
+ assert.ok(
512
+ progressMessages.length >= 2,
513
+ `no progress arrived from git: ${JSON.stringify(progressMessages)}`,
514
+ );
515
+
516
+ const cloneConfig = await readFile(path.join(workspacePath, 'origin', '.git', 'config'), 'utf8');
517
+ assert.match(cloneConfig, new RegExp(`url = ${originUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
518
+ assert.ok(!cloneConfig.includes(token), `token written to .git/config: ${cloneConfig}`);
519
+ } finally {
520
+ await rm(root, { recursive: true, force: true });
521
+ }
522
+ });
523
+
524
+ test('startCloneProject offers the GitHub token to no host but github.com', async () => {
525
+ // The helper used to answer every credential challenge: a clone from any
526
+ // host the user typed — over plain http too — received the GitHub token.
527
+ // Real git against a local server that challenges for Basic auth.
528
+ const root = await mkdtemp(path.join(os.tmpdir(), 'project-clone-scope-'));
529
+ const authorizations: Array<string | null> = [];
530
+ const server = http.createServer((request, response) => {
531
+ authorizations.push(request.headers.authorization ?? null);
532
+ response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="probe"' });
533
+ response.end('auth required');
534
+ });
535
+ await new Promise<void>((resolve) => { server.listen(0, '127.0.0.1', resolve); });
536
+ const { port } = server.address() as AddressInfo;
537
+ const token = 'ghp_supersecrettoken1234567890abcd';
538
+
539
+ try {
540
+ let failure = '';
541
+ const operation = await startCloneProject(
542
+ { workspacePath: root, githubUrl: `http://127.0.0.1:${port}/example/repo.git`, newGithubToken: token, userId: 1 },
543
+ { onProgress: () => undefined, onComplete: () => undefined },
544
+ { validatePath: async () => ({ valid: true, resolvedPath: root }) },
545
+ );
546
+ await operation.waitForCompletion.catch((error: unknown) => { failure = error instanceof Error ? error.message : String(error); });
547
+
548
+ assert.ok(authorizations.length >= 1, 'git never reached the server');
549
+ assert.ok(
550
+ authorizations.every((authorization) => authorization === null),
551
+ `the token was offered to a foreign host: ${JSON.stringify(authorizations)}`,
552
+ );
553
+ assert.match(failure, /Authentication failed/);
554
+ } finally {
555
+ server.close();
556
+ await rm(root, { recursive: true, force: true });
557
+ }
558
+ });
559
+
@@ -6,5 +6,9 @@ export { providerRuntimeService } from './services/provider-runtime.service.js';
6
6
  // providerModelsService: used by Commands to list models and resolve the active session model.
7
7
  export { providerModelsService } from './services/provider-models.service.js';
8
8
 
9
+ // sessionsService: used by the websocket module's chat gateway to resolve an
10
+ // edited message's resume point, which only the providers module can read.
11
+ export { sessionsService } from './services/sessions.service.js';
12
+
9
13
  export { initializeSessionsWatcher } from './services/sessions-watcher.service.js';
10
14
  export { closeSessionsWatcher } from './services/sessions-watcher.service.js';
@@ -6,7 +6,7 @@ import spawn from 'cross-spawn';
6
6
 
7
7
  import { resolveClaudeCodeExecutablePath } from '@/shared/claude-cli-path.js';
8
8
  import type { IProviderAuth } from '@/shared/interfaces.js';
9
- import type { ProviderAuthStatus } from '@/shared/types.js';
9
+ import type { ProviderAuthStatus, ProviderAuthSubscriptionOverride } from '@/shared/types.js';
10
10
  import { readObjectRecord, readOptionalString } from '@/shared/utils.js';
11
11
 
12
12
  type ClaudeCredentialsStatus = {
@@ -14,6 +14,16 @@ type ClaudeCredentialsStatus = {
14
14
  email: string | null;
15
15
  method: string | null;
16
16
  error?: string;
17
+ subscriptionOverride?: ProviderAuthSubscriptionOverride;
18
+ };
19
+
20
+ /**
21
+ * An API-key style credential found in the env or settings.json, plus the
22
+ * user-facing label the settings UI has always shown for it. The labels are
23
+ * kept verbatim so existing consumers of `email` see no change.
24
+ */
25
+ type ClaudeApiKeyCredential = Pick<ProviderAuthSubscriptionOverride, 'variable' | 'source'> & {
26
+ label: string;
17
27
  };
18
28
 
19
29
  const hasErrorCode = (error: unknown, code: string): boolean => (
@@ -25,7 +35,9 @@ export class ClaudeProviderAuth implements IProviderAuth {
25
35
  * Checks whether the Claude Code CLI is available on this host.
26
36
  */
27
37
  private checkInstalled(): boolean {
28
- const cliPath = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
38
+ // cross-spawn resolves shims and PATHEXT itself, so the bare command is a
39
+ // usable fallback here even where the SDK's raw spawn could not use it.
40
+ const cliPath = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH) ?? 'claude';
29
41
  try {
30
42
  spawn.sync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 });
31
43
  return true;
@@ -60,6 +72,7 @@ export class ClaudeProviderAuth implements IProviderAuth {
60
72
  email: credentials.authenticated ? credentials.email || 'Authenticated' : credentials.email,
61
73
  method: credentials.method,
62
74
  error: credentials.authenticated ? undefined : credentials.error || 'Not authenticated',
75
+ ...(credentials.subscriptionOverride ? { subscriptionOverride: credentials.subscriptionOverride } : {}),
63
76
  };
64
77
  }
65
78
 
@@ -78,26 +91,54 @@ export class ClaudeProviderAuth implements IProviderAuth {
78
91
  }
79
92
 
80
93
  /**
81
- * Checks Claude credentials in the same priority order used by Claude Code.
94
+ * Finds an API-key style credential in the same order Claude Code checks them:
95
+ * process env first (auth token, then API key), then the settings.json env block.
82
96
  */
83
- private async checkCredentials(): Promise<ClaudeCredentialsStatus> {
84
- const missingCredentialsError = 'Claude CLI is not authenticated. Run claude /login or configure ANTHROPIC_API_KEY.';
85
-
97
+ private findApiKeyCredential(settingsEnv: Record<string, unknown>): ClaudeApiKeyCredential | null {
86
98
  if (process.env.ANTHROPIC_AUTH_TOKEN?.trim()) {
87
- return { authenticated: true, email: 'Auth Token', method: 'api_key' };
99
+ return { variable: 'ANTHROPIC_AUTH_TOKEN', source: 'process_env', label: 'Auth Token' };
88
100
  }
89
101
 
90
102
  if (process.env.ANTHROPIC_API_KEY?.trim()) {
91
- return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
103
+ return { variable: 'ANTHROPIC_API_KEY', source: 'process_env', label: 'API Key Auth' };
92
104
  }
93
105
 
94
- const settingsEnv = await this.loadSettingsEnv();
95
106
  if (readOptionalString(settingsEnv.ANTHROPIC_API_KEY)) {
96
- return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
107
+ return { variable: 'ANTHROPIC_API_KEY', source: 'settings_file', label: 'API Key Auth' };
97
108
  }
98
109
 
99
110
  if (readOptionalString(settingsEnv.ANTHROPIC_AUTH_TOKEN)) {
100
- return { authenticated: true, email: 'Configured via settings.json', method: 'api_key' };
111
+ return { variable: 'ANTHROPIC_AUTH_TOKEN', source: 'settings_file', label: 'Configured via settings.json' };
112
+ }
113
+
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * Checks Claude credentials in the same priority order used by Claude Code.
119
+ */
120
+ private async checkCredentials(): Promise<ClaudeCredentialsStatus> {
121
+ const settingsEnv = await this.loadSettingsEnv();
122
+
123
+ const apiKey = this.findApiKeyCredential(settingsEnv);
124
+ if (apiKey) {
125
+ const status: ClaudeCredentialsStatus = { authenticated: true, email: apiKey.label, method: 'api_key' };
126
+
127
+ // The key wins, but Claude Code says nothing when it does so while a
128
+ // `claude /login` subscription is also signed in — every request is then
129
+ // billed pay-as-you-go to the key (issue #568). Surface the bypassed
130
+ // login so the settings page can warn about it; an expired or missing
131
+ // login is not being bypassed, so it stays silent.
132
+ const subscription = await this.readCredentialsFile();
133
+ if (subscription.authenticated) {
134
+ status.subscriptionOverride = {
135
+ variable: apiKey.variable,
136
+ source: apiKey.source,
137
+ subscriptionEmail: subscription.email,
138
+ };
139
+ }
140
+
141
+ return status;
101
142
  }
102
143
 
103
144
  if (process.env.CLAUDE_CODE_OAUTH_TOKEN?.trim()) {
@@ -108,6 +149,17 @@ export class ClaudeProviderAuth implements IProviderAuth {
108
149
  return { authenticated: true, email: 'OAuth Token (long-lived)', method: 'environment' };
109
150
  }
110
151
 
152
+ return this.readCredentialsFile();
153
+ }
154
+
155
+ /**
156
+ * Reads the `claude /login` OAuth session from ~/.claude/.credentials.json and
157
+ * reports whether it is still usable. Shared by the fallback path (when no
158
+ * env credential exists) and by the API-key path (to detect a bypassed login).
159
+ */
160
+ private async readCredentialsFile(): Promise<ClaudeCredentialsStatus> {
161
+ const missingCredentialsError = 'Claude CLI is not authenticated. Run claude /login or configure ANTHROPIC_API_KEY.';
162
+
111
163
  try {
112
164
  const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
113
165
  const content = await readFile(credPath, 'utf8');
@@ -126,6 +178,24 @@ export class ClaudeProviderAuth implements IProviderAuth {
126
178
  };
127
179
  }
128
180
 
181
+ // `accessToken` is short-lived (hours). Claude Code renews it silently
182
+ // from `refreshToken` on the next CLI invocation, so an expired access
183
+ // token alongside a live refresh token is still a working login. Before
184
+ // this check, a still-signed-in account read as "login has expired"
185
+ // until something else happened to run the CLI — which is why opening
186
+ // the Shell tab and coming back made Settings flip to Connected.
187
+ const refreshToken = readOptionalString(oauth?.refreshToken);
188
+ const refreshTokenExpiresAt = typeof oauth?.refreshTokenExpiresAt === 'number'
189
+ ? oauth.refreshTokenExpiresAt
190
+ : undefined;
191
+ if (refreshToken && (!refreshTokenExpiresAt || Date.now() < refreshTokenExpiresAt)) {
192
+ return {
193
+ authenticated: true,
194
+ email,
195
+ method: 'credentials_file',
196
+ };
197
+ }
198
+
129
199
  return {
130
200
  authenticated: false,
131
201
  email: null,
@@ -0,0 +1,57 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { forkSession as forkClaudeSession } from '@anthropic-ai/claude-agent-sdk';
5
+
6
+ import type { IProviderFork } from '@/shared/interfaces.js';
7
+ import { AppError } from '@/shared/utils.js';
8
+
9
+ /**
10
+ * Branches a Claude conversation by copying its transcript into a new session
11
+ * file.
12
+ *
13
+ * The SDK owns this: it remaps every message uuid and rewrites the parentUuid
14
+ * chain, which is what makes the copy resumable rather than just a duplicate
15
+ * file. `upToMessageId` is inclusive of the row it names.
16
+ */
17
+ export class ClaudeForkProvider implements IProviderFork {
18
+ async forkSession(input: {
19
+ providerSessionId: string;
20
+ jsonlPath: string;
21
+ projectPath: string;
22
+ upToAnchorId?: string;
23
+ title?: string;
24
+ }): Promise<{ providerSessionId: string; jsonlPath: string }> {
25
+ // `dir` is the session's working directory, which the SDK encodes into the
26
+ // `~/.claude/projects/<encoded>` folder name itself — passing that folder
27
+ // makes it encode an already-encoded path and find nothing.
28
+ const { sessionId } = await forkClaudeSession(input.providerSessionId, {
29
+ dir: input.projectPath,
30
+ upToMessageId: input.upToAnchorId,
31
+ title: input.title,
32
+ });
33
+
34
+ if (!sessionId) {
35
+ throw new AppError('Claude did not return a session id for the fork.', {
36
+ code: 'FORK_FAILED',
37
+ statusCode: 502,
38
+ });
39
+ }
40
+
41
+ // Confirmed rather than assumed: the caller is about to write a database
42
+ // row claiming this file exists, and a half-created row would show up in
43
+ // the sidebar as a session that can never be opened.
44
+ // The fork lands beside the transcript it was copied from.
45
+ const forkedPath = path.join(path.dirname(input.jsonlPath), `${sessionId}.jsonl`);
46
+ try {
47
+ await stat(forkedPath);
48
+ } catch {
49
+ throw new AppError('Claude reported a fork but wrote no transcript for it.', {
50
+ code: 'FORK_FAILED',
51
+ statusCode: 502,
52
+ });
53
+ }
54
+
55
+ return { providerSessionId: sessionId, jsonlPath: forkedPath };
56
+ }
57
+ }