@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
@@ -0,0 +1,160 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
8
+ import { ClaudeSessionsProvider } from '@/modules/providers/list/claude/claude-sessions.provider.js';
9
+
10
+ const SESSION_ID = 'claude-model-session';
11
+
12
+ async function withIsolatedDatabase(runTest: () => void | Promise<void>): Promise<void> {
13
+ const previousDatabasePath = process.env.DATABASE_PATH;
14
+ const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'claude-model-db-'));
15
+
16
+ closeConnection();
17
+ process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db');
18
+ await initializeDatabase();
19
+
20
+ try {
21
+ await runTest();
22
+ } finally {
23
+ closeConnection();
24
+ if (previousDatabasePath === undefined) {
25
+ delete process.env.DATABASE_PATH;
26
+ } else {
27
+ process.env.DATABASE_PATH = previousDatabasePath;
28
+ }
29
+ await rm(tempDirectory, { recursive: true, force: true });
30
+ }
31
+ }
32
+
33
+ /**
34
+ * One assistant transcript row in the shape Claude Code writes it: the model
35
+ * that answered sits on `message.model`, beside the content parts.
36
+ */
37
+ const assistantRow = (uuid: string, model: string | undefined, content: unknown) => ({
38
+ parentUuid: 'user-1',
39
+ isSidechain: false,
40
+ type: 'assistant',
41
+ uuid,
42
+ timestamp: '2026-07-10T00:00:01.000Z',
43
+ cwd: '/workspace/demo',
44
+ sessionId: SESSION_ID,
45
+ message: { role: 'assistant', ...(model === undefined ? {} : { model }), content },
46
+ });
47
+
48
+ test('an assistant reply carries the model that answered it', () => {
49
+ const messages = new ClaudeSessionsProvider().normalizeMessage(
50
+ assistantRow('asst-1', 'claude-opus-5', [
51
+ { type: 'thinking', thinking: 'weighing the options' },
52
+ { type: 'text', text: 'Here is the plan.' },
53
+ { type: 'tool_use', id: 'toolu_1', name: 'Read', input: { file_path: '/repo/a.ts' } },
54
+ ]),
55
+ SESSION_ID,
56
+ );
57
+
58
+ const prose = messages.find((message) => message.kind === 'text');
59
+ assert.equal(prose?.model, 'claude-opus-5');
60
+
61
+ // Only prose has a footer to show it in; a tool call and a reasoning block
62
+ // are drawn as cards, so stamping them would carry a model nothing renders.
63
+ assert.equal(messages.find((message) => message.kind === 'tool_use')?.model, undefined);
64
+ assert.equal(messages.find((message) => message.kind === 'thinking')?.model, undefined);
65
+ });
66
+
67
+ test('an assistant reply whose content is a bare string carries the model too', () => {
68
+ const [prose] = new ClaudeSessionsProvider().normalizeMessage(
69
+ assistantRow('asst-2', 'claude-haiku-4-5-20251001', 'Done.'),
70
+ SESSION_ID,
71
+ );
72
+
73
+ assert.equal(prose?.kind, 'text');
74
+ assert.equal(prose?.model, 'claude-haiku-4-5-20251001');
75
+ });
76
+
77
+ test('the reported model id is passed through verbatim, suffix and all', () => {
78
+ // Claude Code writes the base id on assistant rows today, even in 1M-context
79
+ // sessions. The normalizer must still never rewrite what the provider wrote:
80
+ // if a `[1m]` id ever appears, it has to reach the client as itself.
81
+ const [prose] = new ClaudeSessionsProvider().normalizeMessage(
82
+ assistantRow('asst-3', 'claude-opus-5[1m]', [{ type: 'text', text: 'Reading the whole repo.' }]),
83
+ SESSION_ID,
84
+ );
85
+
86
+ assert.equal(prose?.model, 'claude-opus-5[1m]');
87
+ });
88
+
89
+ test('a locally fabricated row reports no model at all', () => {
90
+ // Claude Code stamps rows it synthesized itself — the usage-limit notice, an
91
+ // API-error placeholder — with `<synthetic>`. No request ran, so there is no
92
+ // model to name, and a guessed one would be a lie about what was charged.
93
+ const [prose] = new ClaudeSessionsProvider().normalizeMessage(
94
+ assistantRow('asst-4', '<synthetic>', [{ type: 'text', text: 'Claude usage limit reached.' }]),
95
+ SESSION_ID,
96
+ );
97
+
98
+ assert.equal(prose?.kind, 'text');
99
+ assert.equal(prose?.model, undefined);
100
+ });
101
+
102
+ test('a user turn carries no model, because no transcript records one', () => {
103
+ const [turn] = new ClaudeSessionsProvider().normalizeMessage(
104
+ {
105
+ type: 'user',
106
+ uuid: 'user-1',
107
+ timestamp: '2026-07-10T00:00:00.000Z',
108
+ sessionId: SESSION_ID,
109
+ message: { role: 'user', content: 'Plan the refactor.' },
110
+ },
111
+ SESSION_ID,
112
+ );
113
+
114
+ assert.equal(turn?.role, 'user');
115
+ assert.equal(turn?.model, undefined);
116
+ });
117
+
118
+ test('Claude history replays the model of every assistant turn from the transcript', { concurrency: false }, async () => {
119
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'claude-model-history-'));
120
+
121
+ try {
122
+ const transcriptPath = path.join(tempRoot, `${SESSION_ID}.jsonl`);
123
+ await writeFile(
124
+ transcriptPath,
125
+ [
126
+ JSON.stringify({
127
+ parentUuid: null,
128
+ isSidechain: false,
129
+ type: 'user',
130
+ uuid: 'user-1',
131
+ timestamp: '2026-07-10T00:00:00.000Z',
132
+ cwd: '/workspace/demo',
133
+ sessionId: SESSION_ID,
134
+ message: { role: 'user', content: 'Plan the refactor.' },
135
+ }),
136
+ JSON.stringify(assistantRow('asst-1', 'claude-opus-5', [{ type: 'text', text: 'Here is the plan.' }])),
137
+ JSON.stringify(assistantRow('asst-2', '<synthetic>', [{ type: 'text', text: 'Claude usage limit reached.' }])),
138
+ '',
139
+ ].join('\n'),
140
+ 'utf8',
141
+ );
142
+
143
+ await withIsolatedDatabase(async () => {
144
+ const now = new Date().toISOString();
145
+ sessionsDb.createSession(SESSION_ID, 'claude', tempRoot, 'Model session', now, now, transcriptPath);
146
+
147
+ const history = await new ClaudeSessionsProvider().fetchHistory(SESSION_ID, {
148
+ providerSessionId: SESSION_ID,
149
+ });
150
+
151
+ const replies = history.messages.filter((message) => message.kind === 'text' && message.role === 'assistant');
152
+ assert.equal(replies.length, 2);
153
+ assert.equal(replies[0]?.model, 'claude-opus-5');
154
+ assert.equal(replies[1]?.model, undefined);
155
+ assert.equal(history.messages.find((message) => message.role === 'user')?.model, undefined);
156
+ });
157
+ } finally {
158
+ await rm(tempRoot, { recursive: true, force: true });
159
+ }
160
+ });
@@ -0,0 +1,87 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import { extractClaudeEventModel } from '@/modules/providers/list/claude/claude-models.provider.js';
5
+
6
+ const SESSION_ID = 'session-1';
7
+
8
+ test('ignores the <synthetic> placeholder Claude Code stamps on synthesized rows', () => {
9
+ assert.equal(
10
+ extractClaudeEventModel(
11
+ { sessionId: SESSION_ID, message: { model: '<synthetic>' } },
12
+ SESSION_ID,
13
+ ),
14
+ null,
15
+ );
16
+ assert.equal(
17
+ extractClaudeEventModel({ sessionId: SESSION_ID, model: '<synthetic>' }, SESSION_ID),
18
+ null,
19
+ );
20
+ });
21
+
22
+ test('still surfaces real model ids from message and event fields', () => {
23
+ assert.equal(
24
+ extractClaudeEventModel(
25
+ { sessionId: SESSION_ID, message: { model: 'claude-sonnet-5' } },
26
+ SESSION_ID,
27
+ ),
28
+ 'claude-sonnet-5',
29
+ );
30
+ assert.equal(
31
+ extractClaudeEventModel({ sessionId: SESSION_ID, model: 'opus' }, SESSION_ID),
32
+ 'opus',
33
+ );
34
+ });
35
+
36
+ test('skips a placeholder content part so a later real model tag still wins', () => {
37
+ assert.equal(
38
+ extractClaudeEventModel(
39
+ {
40
+ sessionId: SESSION_ID,
41
+ message: {
42
+ content: [
43
+ { text: '<model><synthetic></model>' },
44
+ { text: '<model>claude-sonnet-5</model>' },
45
+ ],
46
+ },
47
+ },
48
+ SESSION_ID,
49
+ ),
50
+ 'claude-sonnet-5',
51
+ );
52
+ });
53
+
54
+ test('a placeholder stdout hit does not shadow a real <model> tag in the same text', () => {
55
+ const text = '<local-command-stdout>Set model to <synthetic></local-command-stdout>'
56
+ + '<model>claude-sonnet-5</model>';
57
+ assert.equal(
58
+ extractClaudeEventModel(
59
+ { sessionId: SESSION_ID, message: { content: text } },
60
+ SESSION_ID,
61
+ ),
62
+ 'claude-sonnet-5',
63
+ );
64
+ assert.equal(
65
+ extractClaudeEventModel(
66
+ { sessionId: SESSION_ID, message: { content: [{ text }] } },
67
+ SESSION_ID,
68
+ ),
69
+ 'claude-sonnet-5',
70
+ );
71
+ });
72
+
73
+ test('falls back to the message model when every content hit is a placeholder', () => {
74
+ assert.equal(
75
+ extractClaudeEventModel(
76
+ {
77
+ sessionId: SESSION_ID,
78
+ message: {
79
+ content: '<model><synthetic></model>',
80
+ model: 'claude-sonnet-5',
81
+ },
82
+ },
83
+ SESSION_ID,
84
+ ),
85
+ 'claude-sonnet-5',
86
+ );
87
+ });
@@ -0,0 +1,309 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtemp, rm } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import { ClaudeSessionsProvider } from '@/modules/providers/list/claude/claude-sessions.provider.js';
8
+ import { CLAUDE_PREDEFINED_MODELS } from '@/modules/providers/list/claude/claude-models.provider.js';
9
+ import {
10
+ listClaudeSDKBackgroundWork,
11
+ queryClaudeSDK,
12
+ stopClaudeSDKTask,
13
+ } from '@/modules/providers/list/claude/claude-runtime.provider.js';
14
+ import type { NormalizedMessage, ProviderRuntimeContext } from '@/shared/types.js';
15
+
16
+ /**
17
+ * The runtime keeps the CLI's stdin open after a turn's `result` while the
18
+ * turn's background work is outstanding, and lets go when that work has
19
+ * reported. These drive `queryClaudeSDK` with a scripted SDK stream — the
20
+ * seam is `context.createQuery` — and watch the held prompt stream: the CLI
21
+ * exits when it ends, so "released" is the whole outcome.
22
+ */
23
+
24
+ const SESSION_ID = 'app-hold-session';
25
+ const NATIVE_ID = 'native-hold-session';
26
+
27
+ type Scripted = {
28
+ emit: (message: Record<string, unknown>) => void;
29
+ end: () => void;
30
+ released: () => boolean;
31
+ stopped: string[];
32
+ };
33
+
34
+ /** A stand-in for the SDK query: yields what the test emits, and reads the held prompt to notice its release. */
35
+ function createScriptedQuery(): { createQuery: NonNullable<ProviderRuntimeContext['createQuery']>; script: Scripted } {
36
+ const queue: Array<Record<string, unknown> | null> = [];
37
+ let wake: (() => void) | null = null;
38
+ let released = false;
39
+ const stopped: string[] = [];
40
+
41
+ const script: Scripted = {
42
+ emit: (message) => { queue.push(message); wake?.(); },
43
+ end: () => { queue.push(null); wake?.(); },
44
+ released: () => released,
45
+ stopped,
46
+ };
47
+
48
+ const createQuery: NonNullable<ProviderRuntimeContext['createQuery']> = ({ prompt }) => {
49
+ void (async () => {
50
+ for await (const _message of prompt) { /* the CLI reads its stdin */ }
51
+ released = true;
52
+ })();
53
+
54
+ const iterator = (async function* () {
55
+ for (;;) {
56
+ if (queue.length === 0) {
57
+ await new Promise<void>((resolve) => { wake = resolve; });
58
+ wake = null;
59
+ continue;
60
+ }
61
+ const next = queue.shift();
62
+ if (next === null || next === undefined) {
63
+ return;
64
+ }
65
+ yield next;
66
+ }
67
+ })();
68
+
69
+ return Object.assign(iterator, {
70
+ interrupt: async () => {},
71
+ stopTask: async (taskId: string) => { stopped.push(taskId); },
72
+ });
73
+ };
74
+
75
+ return { createQuery, script };
76
+ }
77
+
78
+ async function withRun(
79
+ runTest: (context: { script: Scripted; sent: NormalizedMessage[]; done: Promise<unknown> }) => Promise<void>,
80
+ ): Promise<void> {
81
+ const cwd = await mkdtemp(path.join(os.tmpdir(), 'claude-runtime-hold-'));
82
+ const { createQuery, script } = createScriptedQuery();
83
+ const sent: NormalizedMessage[] = [];
84
+ const writer = { send: (message: NormalizedMessage) => { sent.push(message); }, userId: null };
85
+ const sessions = new ClaudeSessionsProvider({ getLiveRunStartTime: () => null });
86
+ const context: ProviderRuntimeContext = {
87
+ resolveProviderSessionId: () => null,
88
+ resolveResumeModel: async () => undefined,
89
+ getProviderModels: async () => CLAUDE_PREDEFINED_MODELS as never,
90
+ normalizeMessage: (raw, sessionId) => sessions.normalizeMessage(raw, sessionId),
91
+ isProviderInstalled: async () => true,
92
+ createQuery,
93
+ };
94
+
95
+ try {
96
+ const done = queryClaudeSDK('hello', { sessionId: SESSION_ID, cwd }, writer as never, context);
97
+ await runTest({ script, sent, done });
98
+ script.end();
99
+ await done;
100
+ } finally {
101
+ await rm(cwd, { recursive: true, force: true });
102
+ }
103
+ }
104
+
105
+ const settle = () => new Promise((resolve) => { setTimeout(resolve, 25); });
106
+
107
+ // A fixed settle can be too short on a loaded machine; the supersede tests
108
+ // wait for the run to have actually processed its turn instead.
109
+ async function waitFor(condition: () => boolean): Promise<void> {
110
+ for (let attempt = 0; attempt < 200 && !condition(); attempt += 1) {
111
+ await settle();
112
+ }
113
+ assert.ok(condition(), 'condition never became true');
114
+ }
115
+
116
+ const init = () => ({ type: 'system', subtype: 'init', session_id: NATIVE_ID });
117
+ const toolUse = (id: string, name: string, input: Record<string, unknown>) => ({
118
+ type: 'assistant', session_id: NATIVE_ID, parent_tool_use_id: null,
119
+ message: { role: 'assistant', content: [{ type: 'tool_use', id, name, input }] },
120
+ });
121
+ const ack = (id: string, text: string, toolUseResult: Record<string, unknown>) => ({
122
+ type: 'user', session_id: NATIVE_ID, parent_tool_use_id: null,
123
+ message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: id, content: text }] },
124
+ tool_use_result: toolUseResult,
125
+ });
126
+ const taskStarted = (taskId: string, toolUseId: string, taskType: string) => ({
127
+ type: 'system', subtype: 'task_started', session_id: NATIVE_ID, task_id: taskId, tool_use_id: toolUseId, description: `Task ${taskId}`, task_type: taskType,
128
+ });
129
+ const taskNotification = (taskId: string, toolUseId: string, status: string) => ({
130
+ type: 'system', subtype: 'task_notification', session_id: NATIVE_ID, task_id: taskId, tool_use_id: toolUseId, status, summary: `Task ${taskId} ${status}`, output_file: '',
131
+ });
132
+ const result = () => ({ type: 'result', subtype: 'success', session_id: NATIVE_ID, result: 'launched', duration_ms: 1, num_turns: 1 });
133
+
134
+ test('stopping the last outstanding task releases the held process', async () => {
135
+ await withRun(async ({ script, sent, done }) => {
136
+ script.emit(init());
137
+ script.emit(toolUse('toolu_wf', 'Workflow', { script: 'export const meta = {}' }));
138
+ script.emit(taskStarted('wf1', 'toolu_wf', 'local_workflow'));
139
+ script.emit(ack('toolu_wf', 'Workflow launched in background. Task ID: wf1', { status: 'async_launched', taskId: 'wf1', taskType: 'local_workflow' }));
140
+ script.emit(result());
141
+ await settle();
142
+
143
+ // The turn is over for the client, the process is held for the workflow.
144
+ assert.ok(sent.some((message) => message.kind === 'complete'));
145
+ assert.deepEqual(listClaudeSDKBackgroundWork().map((entry) => [entry.sessionId, entry.tasks.map((task) => task.taskId)]), [[SESSION_ID, ['wf1']]]);
146
+ assert.equal(script.released(), false, 'stdin stays open while the workflow runs');
147
+
148
+ // The user stops it. The CLI answers with a `stopped` notification and
149
+ // pushes no follow-up turn, so nothing else would ever end the hold.
150
+ assert.equal(await stopClaudeSDKTask(SESSION_ID, 'wf1'), true);
151
+ assert.deepEqual(script.stopped, ['wf1']);
152
+ script.emit(taskNotification('wf1', 'toolu_wf', 'stopped'));
153
+ await settle();
154
+
155
+ assert.deepEqual(listClaudeSDKBackgroundWork(), []);
156
+ assert.equal(script.released(), true, 'the process is let go once nothing is outstanding');
157
+ void done;
158
+ });
159
+ });
160
+
161
+ test('a task that reported completed keeps the hold for the turn that relays its result', async () => {
162
+ await withRun(async ({ script }) => {
163
+ script.emit(init());
164
+ script.emit(toolUse('toolu_wf', 'Workflow', { script: 'export const meta = {}' }));
165
+ script.emit(taskStarted('wf1', 'toolu_wf', 'local_workflow'));
166
+ script.emit(ack('toolu_wf', 'Workflow launched in background. Task ID: wf1', { status: 'async_launched', taskId: 'wf1', taskType: 'local_workflow' }));
167
+ script.emit(result());
168
+ await settle();
169
+
170
+ // Completed, unlike stopped, is followed by a turn the CLI pushes to relay
171
+ // the result; closing stdin at the notification would cut it short.
172
+ script.emit(taskNotification('wf1', 'toolu_wf', 'completed'));
173
+ await settle();
174
+ assert.equal(script.released(), false);
175
+
176
+ script.emit(result());
177
+ await settle();
178
+ assert.equal(script.released(), true, 'the follow-up turn\'s result ends the hold');
179
+ });
180
+ });
181
+
182
+ test('an agent that ran in the foreground and settled before the result does not hold the process', async () => {
183
+ await withRun(async ({ script }) => {
184
+ // An Agent call without `run_in_background` is scored as background by
185
+ // the static rule, but the CLI ran it in the foreground: its task started
186
+ // and settled before the turn ended. The task events know that.
187
+ script.emit(init());
188
+ script.emit(toolUse('toolu_agent', 'Agent', { prompt: 'Return the word FOUR', subagent_type: 'general-purpose' }));
189
+ script.emit(taskStarted('a1', 'toolu_agent', 'local_agent'));
190
+ script.emit(taskNotification('a1', 'toolu_agent', 'completed'));
191
+ script.emit(ack('toolu_agent', 'FOUR', { status: 'completed', agentId: 'a1' }));
192
+ script.emit(result());
193
+ await settle();
194
+
195
+ assert.deepEqual(listClaudeSDKBackgroundWork(), []);
196
+ assert.equal(script.released(), true, 'nothing is outstanding, so nothing to hold for');
197
+ });
198
+ });
199
+
200
+ test('a new turn stops the tasks the held process still has going before letting it go', async () => {
201
+ // Two runs on one session, each with its own scripted CLI: the first is
202
+ // held for a background agent, the second is the user's next message.
203
+ const cwd = await mkdtemp(path.join(os.tmpdir(), 'claude-runtime-hold-'));
204
+ const first = createScriptedQuery();
205
+ const second = createScriptedQuery();
206
+ const sent: NormalizedMessage[] = [];
207
+ const writer = { send: (message: NormalizedMessage) => { sent.push(message); }, userId: null };
208
+ const sessions = new ClaudeSessionsProvider({ getLiveRunStartTime: () => null });
209
+ const context: Omit<ProviderRuntimeContext, 'createQuery'> = {
210
+ resolveProviderSessionId: () => null,
211
+ resolveResumeModel: async () => undefined,
212
+ getProviderModels: async () => CLAUDE_PREDEFINED_MODELS as never,
213
+ normalizeMessage: (raw, sessionId) => sessions.normalizeMessage(raw, sessionId),
214
+ isProviderInstalled: async () => true,
215
+ };
216
+
217
+ try {
218
+ const firstRun = queryClaudeSDK('start an agent', { sessionId: SESSION_ID, cwd }, writer as never, { ...context, createQuery: first.createQuery });
219
+ await settle();
220
+ first.script.emit(init());
221
+ first.script.emit(toolUse('toolu_agent', 'Agent', { prompt: 'sleep 90', subagent_type: 'general-purpose', run_in_background: true }));
222
+ first.script.emit(taskStarted('a1', 'toolu_agent', 'local_agent'));
223
+ first.script.emit(ack('toolu_agent', 'Async agent launched successfully.', { status: 'async_launched', agentId: 'a1' }));
224
+ first.script.emit(result());
225
+ await waitFor(() => sent.some((message) => message.kind === 'complete'));
226
+ assert.equal(first.script.released(), false, 'the first process is held for its agent');
227
+
228
+ // Closing stdin alone would leave the CLI waiting for the agent, so the
229
+ // superseded process would live on next to the new one for as long as
230
+ // the agent runs. The stop has to reach it before stdin ends, because the
231
+ // SDK drops control requests written to an ended stdin.
232
+ const secondRun = queryClaudeSDK('Continue', { sessionId: SESSION_ID, cwd }, writer as never, { ...context, createQuery: second.createQuery });
233
+ await settle();
234
+ assert.deepEqual(first.script.stopped, ['a1'], 'the held process is told to stop its agent');
235
+ assert.equal(first.script.released(), true, 'and is then let go');
236
+ assert.deepEqual(second.script.stopped, [], 'the new process is untouched');
237
+
238
+ first.script.emit(taskNotification('a1', 'toolu_agent', 'stopped'));
239
+ first.script.end();
240
+ await firstRun;
241
+ second.script.emit(init());
242
+ second.script.emit(result());
243
+ second.script.end();
244
+ await secondRun;
245
+ assert.deepEqual(listClaudeSDKBackgroundWork(), []);
246
+ } finally {
247
+ await rm(cwd, { recursive: true, force: true });
248
+ }
249
+ });
250
+
251
+ test('a held process is still let go when stopping its tasks throws', async () => {
252
+ // Stopping is best effort; a stop that throws on the spot must not keep the
253
+ // held process alive or take the new turn down with it.
254
+ const cwd = await mkdtemp(path.join(os.tmpdir(), 'claude-runtime-hold-'));
255
+ const first = createScriptedQuery();
256
+ const second = createScriptedQuery();
257
+ const sent: NormalizedMessage[] = [];
258
+ const writer = { send: (message: NormalizedMessage) => { sent.push(message); }, userId: null };
259
+ const sessions = new ClaudeSessionsProvider({ getLiveRunStartTime: () => null });
260
+ const context: Omit<ProviderRuntimeContext, 'createQuery'> = {
261
+ resolveProviderSessionId: () => null,
262
+ resolveResumeModel: async () => undefined,
263
+ getProviderModels: async () => CLAUDE_PREDEFINED_MODELS as never,
264
+ normalizeMessage: (raw, sessionId) => sessions.normalizeMessage(raw, sessionId),
265
+ isProviderInstalled: async () => true,
266
+ };
267
+ const throwingStop: NonNullable<ProviderRuntimeContext['createQuery']> = (options) =>
268
+ Object.assign(first.createQuery(options), {
269
+ stopTask: () => { throw new Error('stop failed'); },
270
+ });
271
+
272
+ try {
273
+ const firstRun = queryClaudeSDK('start an agent', { sessionId: SESSION_ID, cwd }, writer as never, { ...context, createQuery: throwingStop });
274
+ await settle();
275
+ first.script.emit(init());
276
+ first.script.emit(toolUse('toolu_agent', 'Agent', { prompt: 'sleep 90', subagent_type: 'general-purpose', run_in_background: true }));
277
+ first.script.emit(taskStarted('a1', 'toolu_agent', 'local_agent'));
278
+ first.script.emit(ack('toolu_agent', 'Async agent launched successfully.', { status: 'async_launched', agentId: 'a1' }));
279
+ first.script.emit(result());
280
+ await waitFor(() => sent.some((message) => message.kind === 'complete'));
281
+ assert.equal(first.script.released(), false, 'the first process is held for its agent');
282
+
283
+ const secondRun = queryClaudeSDK('Continue', { sessionId: SESSION_ID, cwd }, writer as never, { ...context, createQuery: second.createQuery });
284
+ await settle();
285
+ assert.equal(first.script.released(), true, 'the held process is let go anyway');
286
+
287
+ first.script.end();
288
+ await firstRun;
289
+ second.script.emit(init());
290
+ second.script.emit(result());
291
+ second.script.end();
292
+ await secondRun;
293
+ assert.equal(second.script.released(), true, 'the new turn ran to completion');
294
+ } finally {
295
+ await rm(cwd, { recursive: true, force: true });
296
+ }
297
+ });
298
+
299
+ test('a turn whose tool emits no task events still holds on the static rule', async () => {
300
+ await withRun(async ({ script }) => {
301
+ script.emit(init());
302
+ script.emit(toolUse('toolu_monitor', 'Monitor', { command: 'tail -f x', description: 'watch', timeout_ms: 1000 }));
303
+ script.emit(ack('toolu_monitor', 'Monitor started', {}));
304
+ script.emit(result());
305
+ await settle();
306
+
307
+ assert.equal(script.released(), false, 'Monitor reports no task, so the launch rule decides');
308
+ });
309
+ });