@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
@@ -4,11 +4,12 @@ import path from 'path';
4
4
  import express from 'express';
5
5
 
6
6
  import type { ProviderRunFunction } from '@/shared/types.js';
7
- import { AppError } from '@/shared/utils.js';
7
+ import { AppError, getGitErrorDetails } from '@/shared/utils.js';
8
8
 
9
9
  // cross-spawn: drop-in spawn with Windows .cmd/PATHEXT resolution.
10
10
  import { parseGitLogWithStats, parseGitStatusOutput } from './git-parsing.service.js';
11
11
  import { deleteLocalBranch } from './git-branch.service.js';
12
+ import { getBranchDiffForFile, listBranchDiffFiles } from './git-branch-diff.service.js';
12
13
 
13
14
  type GitRouterDependencies = {
14
15
  fileSystem: typeof import('node:fs/promises');
@@ -207,10 +208,6 @@ async function validateGitRepository(projectPath) {
207
208
  }
208
209
  }
209
210
 
210
- function getGitErrorDetails(error) {
211
- return `${error?.message || ''} ${error?.stderr || ''} ${error?.stdout || ''}`;
212
- }
213
-
214
211
  function isMissingHeadRevisionError(error) {
215
212
  const errorDetails = getGitErrorDetails(error).toLowerCase();
216
213
  return errorDetails.includes('unknown revision')
@@ -787,13 +784,110 @@ router.get('/branches', async (req, res) => {
787
784
  const branches = [...localBranches, ...remoteBranches]
788
785
  .filter((b, i, arr) => arr.indexOf(b) === i);
789
786
 
790
- res.json({ branches, localBranches, remoteBranches });
787
+ // Full remote-tracking names (`origin/main`) for callers that need an
788
+ // unambiguous ref, e.g. the Compare tab's base selector.
789
+ const remoteRefs = rawLines
790
+ .filter(b => b.startsWith('remotes/'))
791
+ .map(b => b.replace(/^remotes\//, ''));
792
+
793
+ res.json({ branches, localBranches, remoteBranches, remoteRefs });
791
794
  } catch (error) {
792
795
  console.error('Git branches error:', error);
793
796
  res.json({ error: error.message });
794
797
  }
795
798
  });
796
799
 
800
+ // Shared error shaping for the two compare routes: service AppErrors (unknown
801
+ // ref, no merge base) keep their status/code; anything else follows the
802
+ // module's GET convention of a 200 with `error`/`details`.
803
+ function sendBranchDiffError(res, error, context) {
804
+ if (error instanceof AppError) {
805
+ return res.status(error.statusCode).json({ error: error.message, code: error.code, details: error.details });
806
+ }
807
+ console.error(`Git ${context} error:`, error);
808
+ res.json({ error: 'Git operation failed', details: error.message });
809
+ }
810
+
811
+ // List files the working copy changed relative to the merge base with `base`
812
+ router.get('/branch-diff', async (req, res) => {
813
+ const { project, base } = req.query;
814
+
815
+ if (!project || !base) {
816
+ return res.status(400).json({ error: 'Project id and base branch are required' });
817
+ }
818
+
819
+ try {
820
+ validateBranchName(base);
821
+ } catch (error) {
822
+ return res.status(400).json({ error: error.message });
823
+ }
824
+
825
+ try {
826
+ const projectPath = await getActualProjectPath(project);
827
+ await validateGitRepository(projectPath);
828
+ // Run from the repository root so untracked paths are root-relative like
829
+ // everything else the panel shows, even for projects nested in a repo.
830
+ const repositoryRootPath = await getRepositoryRootPath(projectPath);
831
+ const result = await listBranchDiffFiles({
832
+ projectPath: repositoryRootPath,
833
+ base,
834
+ runCommand: spawnAsync,
835
+ });
836
+ res.json(result);
837
+ } catch (error) {
838
+ sendBranchDiffError(res, error, 'branch diff');
839
+ }
840
+ });
841
+
842
+ // Diff of one file between the merge base with `base` and the working copy.
843
+ // `oldPath` is the pre-rename path of an `R` entry from `/branch-diff`.
844
+ router.get('/branch-diff/file', async (req, res) => {
845
+ const { project, base, file, oldPath } = req.query;
846
+
847
+ if (!project || !base || !file) {
848
+ return res.status(400).json({ error: 'Project id, base branch and file path are required' });
849
+ }
850
+
851
+ try {
852
+ validateBranchName(base);
853
+ validateFilePath(file);
854
+ if (oldPath !== undefined) {
855
+ validateFilePath(oldPath);
856
+ }
857
+ } catch (error) {
858
+ return res.status(400).json({ error: error.message });
859
+ }
860
+
861
+ try {
862
+ const projectPath = await getActualProjectPath(project);
863
+ await validateGitRepository(projectPath);
864
+
865
+ // `/branch-diff` already reports repository-root-relative paths, and
866
+ // compare files need not appear in `git status` (committed-only changes),
867
+ // so the status-based resolver is skipped: its bare-filename fallback
868
+ // could remap e.g. `README.md` onto a changed `docs/README.md`.
869
+ const repositoryRootPath = await getRepositoryRootPath(projectPath);
870
+ const repositoryRelativeFilePath = normalizeRepositoryRelativeFilePath(file);
871
+ validateFilePath(repositoryRelativeFilePath, repositoryRootPath);
872
+ const repositoryRelativeOldPath = oldPath === undefined ? undefined : normalizeRepositoryRelativeFilePath(oldPath);
873
+ if (repositoryRelativeOldPath !== undefined) {
874
+ validateFilePath(repositoryRelativeOldPath, repositoryRootPath);
875
+ }
876
+
877
+ const diff = await getBranchDiffForFile({
878
+ projectPath: repositoryRootPath,
879
+ base,
880
+ file: repositoryRelativeFilePath,
881
+ oldPath: repositoryRelativeOldPath,
882
+ runCommand: spawnAsync,
883
+ readFile: (absolutePath) => fs.readFile(absolutePath, 'utf-8'),
884
+ });
885
+ res.json({ diff: stripDiffHeaders(diff) });
886
+ } catch (error) {
887
+ sendBranchDiffError(res, error, 'branch diff file');
888
+ }
889
+ });
890
+
797
891
  // Checkout branch
798
892
  router.post('/checkout', async (req, res) => {
799
893
  const { project, branch } = req.body;
@@ -0,0 +1,210 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import type { GitCommandResult } from '@/shared/types.js';
5
+
6
+ import { getBranchDiffForFile, listBranchDiffFiles } from '../git-branch-diff.service.js';
7
+
8
+ const MERGE_BASE = 'ad449053aa935934160725092c5ce8d63d336894';
9
+
10
+ // Joins NUL-separated tokens with a trailing NUL, exactly as `-z` output looks.
11
+ const nul = (...tokens: string[]) => tokens.join('\0') + '\0';
12
+
13
+ type FakeRunnerOptions = {
14
+ nameStatus?: string;
15
+ untracked?: string;
16
+ diff?: string;
17
+ failRevParse?: boolean;
18
+ failMergeBase?: boolean;
19
+ };
20
+
21
+ /**
22
+ * Fake `spawnAsync`: records every git invocation and answers the handful of
23
+ * commands the service issues from canned output.
24
+ */
25
+ function createRunner(options: FakeRunnerOptions = {}) {
26
+ const calls: string[][] = [];
27
+ const runCommand = async (_command: string, args: string[]): Promise<GitCommandResult> => {
28
+ calls.push(args);
29
+ const subcommand = args[0];
30
+
31
+ if (subcommand === 'rev-parse') {
32
+ if (options.failRevParse) {
33
+ throw Object.assign(new Error('Command failed'), { stderr: '' });
34
+ }
35
+ return { stdout: `${MERGE_BASE}\n`, stderr: '' };
36
+ }
37
+ if (subcommand === 'merge-base') {
38
+ if (options.failMergeBase) {
39
+ throw Object.assign(new Error('Command failed'), { stderr: 'fatal: no merge base' });
40
+ }
41
+ return { stdout: `${MERGE_BASE}\n`, stderr: '' };
42
+ }
43
+ if (subcommand === 'diff') {
44
+ return { stdout: args.includes('--name-status') ? options.nameStatus ?? '' : options.diff ?? '', stderr: '' };
45
+ }
46
+ if (subcommand === 'ls-files') {
47
+ return { stdout: options.untracked ?? '', stderr: '' };
48
+ }
49
+ throw new Error(`Unexpected git command: ${args.join(' ')}`);
50
+ };
51
+ return { calls, runCommand };
52
+ }
53
+
54
+ test('listBranchDiffFiles verifies the ref before asking git for the merge base', async () => {
55
+ const { calls, runCommand } = createRunner();
56
+
57
+ const result = await listBranchDiffFiles({ projectPath: '/repo', base: 'origin/main', runCommand });
58
+
59
+ assert.equal(result.mergeBase, MERGE_BASE);
60
+ assert.deepEqual(calls.slice(0, 2), [
61
+ ['rev-parse', '--verify', '--quiet', '--end-of-options', 'origin/main^{commit}'],
62
+ ['merge-base', '--', 'origin/main', 'HEAD'],
63
+ ]);
64
+ });
65
+
66
+ test('listBranchDiffFiles rejects a base that would be parsed as a git option without running git', async () => {
67
+ const { calls, runCommand } = createRunner();
68
+
69
+ await assert.rejects(
70
+ listBranchDiffFiles({ projectPath: '/repo', base: '--output=/tmp/x', runCommand }),
71
+ (error: unknown) => {
72
+ assert.equal((error as { code?: string }).code, 'GIT_INVALID_BASE_REF');
73
+ assert.equal((error as { statusCode?: number }).statusCode, 400);
74
+ return true;
75
+ },
76
+ );
77
+ assert.deepEqual(calls, []);
78
+ });
79
+
80
+ test('listBranchDiffFiles reports an unknown ref with a stable code', async () => {
81
+ const { runCommand } = createRunner({ failRevParse: true });
82
+
83
+ await assert.rejects(
84
+ listBranchDiffFiles({ projectPath: '/repo', base: 'origin/nope', runCommand }),
85
+ (error: unknown) => {
86
+ assert.equal((error as { code?: string }).code, 'GIT_UNKNOWN_BASE_REF');
87
+ assert.equal((error as { statusCode?: number }).statusCode, 400);
88
+ return true;
89
+ },
90
+ );
91
+ });
92
+
93
+ test('getBranchDiffForFile reports branches without common history as a 400 AppError', async () => {
94
+ const { runCommand } = createRunner({ failMergeBase: true });
95
+
96
+ await assert.rejects(
97
+ getBranchDiffForFile({ projectPath: '/repo', base: 'orphan', file: 'a.txt', runCommand, readFile: async () => '' }),
98
+ (error: unknown) => {
99
+ assert.equal((error as { name?: string }).name, 'AppError');
100
+ assert.equal((error as { code?: string }).code, 'GIT_NO_MERGE_BASE');
101
+ assert.equal((error as { statusCode?: number }).statusCode, 400);
102
+ return true;
103
+ },
104
+ );
105
+ });
106
+
107
+ test('listBranchDiffFiles parses NUL-separated name-status output including renames', async () => {
108
+ const { calls, runCommand } = createRunner({
109
+ nameStatus: nul('M', 'src/a.txt', 'R100', 'old name.txt', 'new name.txt', 'A', 'b.txt', 'D', 'gone.txt', 'C75', 'base.txt', 'copy.txt'),
110
+ });
111
+
112
+ const result = await listBranchDiffFiles({ projectPath: '/repo', base: 'main', runCommand });
113
+
114
+ assert.equal(result.base, 'main');
115
+ assert.equal(result.mergeBase, MERGE_BASE);
116
+ assert.deepEqual(result.files, [
117
+ { path: 'b.txt', status: 'A' },
118
+ { path: 'copy.txt', status: 'A' },
119
+ { path: 'gone.txt', status: 'D' },
120
+ { path: 'new name.txt', oldPath: 'old name.txt', status: 'R' },
121
+ { path: 'src/a.txt', status: 'M' },
122
+ ]);
123
+ // The working tree is diffed against the merge base, never the base tip.
124
+ assert.deepEqual(calls[2], ['diff', '--name-status', '-M', '-z', MERGE_BASE, '--']);
125
+ assert.deepEqual(calls[3], ['ls-files', '--others', '--exclude-standard', '-z']);
126
+ });
127
+
128
+ test('listBranchDiffFiles merges untracked files as U and sorts everything by path', async () => {
129
+ const { runCommand } = createRunner({
130
+ nameStatus: nul('M', 'a.txt', 'A', 'b.txt'),
131
+ untracked: nul('c.txt', 'aa.txt'),
132
+ });
133
+
134
+ const result = await listBranchDiffFiles({ projectPath: '/repo', base: 'main', runCommand });
135
+
136
+ assert.deepEqual(result.files, [
137
+ { path: 'a.txt', status: 'M' },
138
+ { path: 'aa.txt', status: 'U' },
139
+ { path: 'b.txt', status: 'A' },
140
+ { path: 'c.txt', status: 'U' },
141
+ ]);
142
+ });
143
+
144
+ test('listBranchDiffFiles returns an empty list when nothing differs', async () => {
145
+ const { runCommand } = createRunner();
146
+
147
+ const result = await listBranchDiffFiles({ projectPath: '/repo', base: 'main', runCommand });
148
+
149
+ assert.deepEqual(result.files, []);
150
+ });
151
+
152
+ test('getBranchDiffForFile diffs a tracked file against the merge base with the path after --', async () => {
153
+ const diff = 'diff --git a/a.txt b/a.txt\n--- a/a.txt\n+++ b/a.txt\n@@ -1,2 +1,3 @@\n line1\n-line2\n+line2 changed\n+line3\n';
154
+ const { calls, runCommand } = createRunner({ diff });
155
+ let readFileCalls = 0;
156
+
157
+ const result = await getBranchDiffForFile({
158
+ projectPath: '/repo',
159
+ base: 'main',
160
+ file: 'a.txt',
161
+ runCommand,
162
+ readFile: async () => {
163
+ readFileCalls += 1;
164
+ return '';
165
+ },
166
+ });
167
+
168
+ assert.equal(result, diff);
169
+ assert.equal(readFileCalls, 0);
170
+ assert.deepEqual(calls[2], ['ls-files', '--others', '--exclude-standard', '-z', '--', 'a.txt']);
171
+ assert.deepEqual(calls[3], ['diff', '-M', MERGE_BASE, '--', 'a.txt']);
172
+ });
173
+
174
+ test('getBranchDiffForFile hands both paths of a rename to git so it diffs the rename, not a new file', async () => {
175
+ const diff = 'diff --git a/keep.txt b/kept.txt\nsimilarity index 90%\nrename from keep.txt\nrename to kept.txt\n@@ -1 +1 @@\n-x\n+y\n';
176
+ const { calls, runCommand } = createRunner({ diff });
177
+
178
+ const result = await getBranchDiffForFile({
179
+ projectPath: '/repo',
180
+ base: 'main',
181
+ file: 'kept.txt',
182
+ oldPath: 'keep.txt',
183
+ runCommand,
184
+ readFile: async () => '',
185
+ });
186
+
187
+ assert.equal(result, diff);
188
+ assert.deepEqual(calls[3], ['diff', '-M', MERGE_BASE, '--', 'keep.txt', 'kept.txt']);
189
+ });
190
+
191
+ test('getBranchDiffForFile synthesizes an all-additions diff for an untracked file', async () => {
192
+ const { calls, runCommand } = createRunner({ untracked: nul('c.txt') });
193
+ const readPaths: string[] = [];
194
+
195
+ const result = await getBranchDiffForFile({
196
+ projectPath: '/repo',
197
+ base: 'main',
198
+ file: 'c.txt',
199
+ runCommand,
200
+ readFile: async (absolutePath) => {
201
+ readPaths.push(absolutePath);
202
+ return 'first\nsecond';
203
+ },
204
+ });
205
+
206
+ assert.deepEqual(readPaths, ['/repo/c.txt']);
207
+ assert.equal(result, '--- /dev/null\n+++ b/c.txt\n@@ -0,0 +1,2 @@\n+first\n+second');
208
+ // No `git diff` is run for untracked files.
209
+ assert.equal(calls.some((args) => args[0] === 'diff'), false);
210
+ });
@@ -0,0 +1,142 @@
1
+ import express, { type Request } from 'express';
2
+
3
+ import type { PendingCloneRequests } from '@/modules/projects/services/project-clone-request.service.js';
4
+ import type { startCloneProject } from '@/modules/projects/services/project-clone.service.js';
5
+ import { AppError, asyncHandler } from '@/shared/utils.js';
6
+
7
+ type ProjectCloneRouterDependencies = {
8
+ startCloneProject: typeof startCloneProject;
9
+ pendingCloneRequests: PendingCloneRequests;
10
+ };
11
+
12
+ type AuthenticatedUser = {
13
+ id?: number | string;
14
+ };
15
+
16
+ function requireAuthenticatedUserId(req: Request): number | string {
17
+ const authenticatedUser = (req as Request & { user?: AuthenticatedUser }).user;
18
+ const userId = authenticatedUser?.id;
19
+ if (userId === undefined || userId === null) {
20
+ throw new AppError('Authenticated user is required', {
21
+ code: 'AUTHENTICATION_REQUIRED',
22
+ statusCode: 401,
23
+ });
24
+ }
25
+
26
+ return userId;
27
+ }
28
+
29
+ function resolveRouteErrorMessage(error: unknown): string {
30
+ if (error instanceof AppError) {
31
+ return error.message;
32
+ }
33
+
34
+ if (error instanceof Error && error.message) {
35
+ return error.message;
36
+ }
37
+
38
+ return 'Failed to clone repository';
39
+ }
40
+
41
+ /**
42
+ * Creates the two halves of a repository clone. The browser's EventSource can
43
+ * only issue a bare GET, and a GET's URL is written to access logs, proxy logs
44
+ * and browser history — so the request that carries the GitHub token is a
45
+ * POST, and the stream's URL names it by an opaque id.
46
+ */
47
+ export function createProjectCloneRouter(dependencies: ProjectCloneRouterDependencies): express.Router {
48
+ const router = express.Router();
49
+
50
+ /**
51
+ * Parks the request under a fresh id for `/clone-progress` to pick up.
52
+ * Nothing is spawned here; the stream that follows runs and reports it.
53
+ */
54
+ router.post(
55
+ '/clone',
56
+ asyncHandler(async (req, res) => {
57
+ const userId = requireAuthenticatedUserId(req);
58
+ const requestBody = req.body as Record<string, unknown>;
59
+ const cloneId = dependencies.pendingCloneRequests.add(userId, {
60
+ workspacePath: typeof requestBody.path === 'string' ? requestBody.path : '',
61
+ githubUrl: typeof requestBody.githubUrl === 'string' ? requestBody.githubUrl : '',
62
+ githubTokenId: typeof requestBody.githubTokenId === 'number' ? requestBody.githubTokenId : null,
63
+ newGithubToken: typeof requestBody.newGithubToken === 'string' ? requestBody.newGithubToken : null,
64
+ });
65
+
66
+ res.json({ cloneId });
67
+ }),
68
+ );
69
+
70
+ /**
71
+ * Claims the request the id names before any stream headers go out, so a
72
+ * stale or foreign id gets an ordinary 404 instead of an empty stream.
73
+ */
74
+ router.get(
75
+ '/clone-progress',
76
+ asyncHandler(async (req, res) => {
77
+ const userId = requireAuthenticatedUserId(req);
78
+ const cloneId = typeof req.query.cloneId === 'string' ? req.query.cloneId : '';
79
+ const cloneRequest = dependencies.pendingCloneRequests.claim(cloneId, userId);
80
+ if (!cloneRequest) {
81
+ throw new AppError('Clone request not found or expired', {
82
+ code: 'CLONE_REQUEST_NOT_FOUND',
83
+ statusCode: 404,
84
+ });
85
+ }
86
+
87
+ res.setHeader('Content-Type', 'text/event-stream');
88
+ res.setHeader('Cache-Control', 'no-cache');
89
+ res.setHeader('Connection', 'keep-alive');
90
+ res.flushHeaders();
91
+
92
+ const sendEvent = (type: string, data: Record<string, unknown>) => {
93
+ if (res.writableEnded) {
94
+ return;
95
+ }
96
+
97
+ res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
98
+ };
99
+
100
+ // The client can go away while the clone is still validating its
101
+ // input and preparing the directory — before there is an operation to
102
+ // cancel. Remember that it did, so the git process is cancelled the
103
+ // moment it exists rather than left cloning for nobody: the id was
104
+ // claimed above, so no reconnect can ever reach this run.
105
+ let cloneOperation: Awaited<ReturnType<typeof startCloneProject>> | null = null;
106
+ let clientGone = false;
107
+ const closeListener = () => {
108
+ clientGone = true;
109
+ cloneOperation?.cancel();
110
+ };
111
+ req.on('close', closeListener);
112
+
113
+ try {
114
+ cloneOperation = await dependencies.startCloneProject(
115
+ { ...cloneRequest, userId },
116
+ {
117
+ onProgress: (message) => {
118
+ sendEvent('progress', { message });
119
+ },
120
+ onComplete: ({ project, message }) => {
121
+ sendEvent('complete', { project, message });
122
+ },
123
+ },
124
+ );
125
+ if (clientGone) {
126
+ cloneOperation.cancel();
127
+ }
128
+
129
+ await cloneOperation.waitForCompletion;
130
+ } catch (error) {
131
+ sendEvent('error', { message: resolveRouteErrorMessage(error) });
132
+ } finally {
133
+ req.off('close', closeListener);
134
+ if (!res.writableEnded) {
135
+ res.end();
136
+ }
137
+ }
138
+ }),
139
+ );
140
+
141
+ return router;
142
+ }
@@ -1,6 +1,8 @@
1
1
  import express from 'express';
2
2
 
3
3
  import { createProject, updateProjectDisplayName } from '@/modules/projects/services/project-management.service.js';
4
+ import { createProjectCloneRouter } from '@/modules/projects/project-clone.routes.js';
5
+ import { createPendingCloneRequests } from '@/modules/projects/services/project-clone-request.service.js';
4
6
  import { startCloneProject } from '@/modules/projects/services/project-clone.service.js';
5
7
  import { getProjectTaskMaster } from '@/modules/projects/services/projects-has-taskmaster.service.js';
6
8
  import { AppError, asyncHandler, createApiSuccessResponse } from '@/shared/utils.js';
@@ -8,11 +10,14 @@ import { getArchivedProjectsWithSessions, getProjectSessionsPage, getProjectsWit
8
10
  import { deleteOrArchiveProject, restoreArchivedProject } from '@/modules/projects/services/project-delete.service.js';
9
11
  import { applyLegacyStarredProjectIds, toggleProjectStar } from '@/modules/projects/services/project-star.service.js';
10
12
 
11
- const router = express.Router();
13
+ /**
14
+ * How long a posted clone request waits for its progress stream to be opened.
15
+ * The client opens the stream as soon as the POST resolves, so this only has
16
+ * to outlast a slow round trip.
17
+ */
18
+ const PENDING_CLONE_REQUEST_TTL_MS = 60_000;
12
19
 
13
- type AuthenticatedUser = {
14
- id?: number | string;
15
- };
20
+ const router = express.Router();
16
21
 
17
22
  function readQueryStringValue(value: unknown): string {
18
23
  if (typeof value === 'string') {
@@ -53,18 +58,6 @@ function parseNonNegativeIntQuery(value: unknown, name: string, fallback: number
53
58
  return parsedValue;
54
59
  }
55
60
 
56
- function resolveRouteErrorMessage(error: unknown): string {
57
- if (error instanceof AppError) {
58
- return error.message;
59
- }
60
-
61
- if (error instanceof Error && error.message) {
62
- return error.message;
63
- }
64
-
65
- return 'Failed to clone repository';
66
- }
67
-
68
61
  router.get(
69
62
  '/',
70
63
  asyncHandler(async (req, res) => {
@@ -119,7 +112,7 @@ router.post(
119
112
  throw new AppError('Repository cloning is not supported on create-project', {
120
113
  code: 'CLONE_NOT_SUPPORTED_ON_CREATE_PROJECT',
121
114
  statusCode: 400,
122
- details: 'Use /api/projects/clone-progress for cloning workflows',
115
+ details: 'Use /api/projects/clone for cloning workflows',
123
116
  });
124
117
  }
125
118
 
@@ -153,70 +146,12 @@ router.post(
153
146
  }),
154
147
  );
155
148
 
156
- router.get('/clone-progress', async (req, res) => {
157
- res.setHeader('Content-Type', 'text/event-stream');
158
- res.setHeader('Cache-Control', 'no-cache');
159
- res.setHeader('Connection', 'keep-alive');
160
- res.flushHeaders();
161
-
162
- const sendEvent = (type: string, data: Record<string, unknown>) => {
163
- if (res.writableEnded) {
164
- return;
165
- }
166
-
167
- res.write(`data: ${JSON.stringify({ type, ...data })}\n\n`);
168
- };
169
-
170
- let cloneOperation: Awaited<ReturnType<typeof startCloneProject>> | null = null;
171
- const closeListener = () => {
172
- cloneOperation?.cancel();
173
- };
174
- req.on('close', closeListener);
175
-
176
- try {
177
- const queryParams = req.query as Record<string, unknown>;
178
- const workspacePath = readQueryStringValue(queryParams.path);
179
- const githubUrl = readQueryStringValue(queryParams.githubUrl);
180
- const githubTokenId = readOptionalNumericQueryValue(queryParams.githubTokenId);
181
- const newGithubToken = readQueryStringValue(queryParams.newGithubToken) || null;
182
-
183
- const authenticatedUser = (req as typeof req & { user?: AuthenticatedUser }).user;
184
- const userId = authenticatedUser?.id;
185
- if (userId === undefined || userId === null) {
186
- throw new AppError('Authenticated user is required', {
187
- code: 'AUTHENTICATION_REQUIRED',
188
- statusCode: 401,
189
- });
190
- }
191
-
192
- cloneOperation = await startCloneProject(
193
- {
194
- workspacePath,
195
- githubUrl,
196
- githubTokenId,
197
- newGithubToken,
198
- userId,
199
- },
200
- {
201
- onProgress: (message) => {
202
- sendEvent('progress', { message });
203
- },
204
- onComplete: ({ project, message }) => {
205
- sendEvent('complete', { project, message });
206
- },
207
- },
208
- );
209
-
210
- await cloneOperation.waitForCompletion;
211
- } catch (error) {
212
- sendEvent('error', { message: resolveRouteErrorMessage(error) });
213
- } finally {
214
- req.off('close', closeListener);
215
- if (!res.writableEnded) {
216
- res.end();
217
- }
218
- }
219
- });
149
+ router.use(
150
+ createProjectCloneRouter({
151
+ startCloneProject,
152
+ pendingCloneRequests: createPendingCloneRequests(PENDING_CLONE_REQUEST_TTL_MS),
153
+ }),
154
+ );
220
155
 
221
156
  router.get(
222
157
  '/:projectId/taskmaster',
@@ -0,0 +1,56 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ export type PendingCloneRequest = {
4
+ workspacePath: string;
5
+ githubUrl: string;
6
+ githubTokenId: number | null;
7
+ newGithubToken: string | null;
8
+ };
9
+
10
+ export type PendingCloneRequests = {
11
+ add: (userId: number | string, request: PendingCloneRequest) => string;
12
+ claim: (cloneId: string, userId: number | string) => PendingCloneRequest | null;
13
+ };
14
+
15
+ type PendingCloneEntry = {
16
+ userId: number | string;
17
+ request: PendingCloneRequest;
18
+ expiry: NodeJS.Timeout;
19
+ };
20
+
21
+ /**
22
+ * Holds clone requests between the POST that submits them and the EventSource
23
+ * GET that runs them. The browser's EventSource can only issue a bare GET, so
24
+ * whatever it needs has to travel in the URL — and a URL is written to access
25
+ * logs, proxy logs and browser history. The token therefore arrives in a POST
26
+ * body and waits here, and the GET carries nothing but the opaque id.
27
+ *
28
+ * Every entry is bound to the user who posted it, is handed out once, and is
29
+ * dropped after `ttlMs` whether or not it was claimed.
30
+ */
31
+ export function createPendingCloneRequests(ttlMs: number): PendingCloneRequests {
32
+ const pending = new Map<string, PendingCloneEntry>();
33
+
34
+ return {
35
+ add(userId, request) {
36
+ const cloneId = randomUUID();
37
+ const expiry = setTimeout(() => {
38
+ pending.delete(cloneId);
39
+ }, ttlMs);
40
+ // An abandoned request must not keep the process alive on shutdown.
41
+ expiry.unref();
42
+ pending.set(cloneId, { userId, request, expiry });
43
+ return cloneId;
44
+ },
45
+ claim(cloneId, userId) {
46
+ const entry = pending.get(cloneId);
47
+ if (!entry || entry.userId !== userId) {
48
+ return null;
49
+ }
50
+
51
+ clearTimeout(entry.expiry);
52
+ pending.delete(cloneId);
53
+ return entry.request;
54
+ },
55
+ };
56
+ }