@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
@@ -47,7 +47,7 @@ test('resolveClaudeCodeExecutablePath can parse a wrapper file path containing l
47
47
  assert.equal(resolved, nativePath);
48
48
  });
49
49
 
50
- test('resolveClaudeCodeExecutablePath falls back to the configured command when PATH lookup fails', () => {
50
+ test('resolveClaudeCodeExecutablePath keeps an explicitly configured command when PATH lookup fails', () => {
51
51
  const execFileSync = (() => {
52
52
  throw new Error('not found');
53
53
  }) as unknown as ResolveClaudeCodeExecutablePathDependencies['execFileSync'];
@@ -59,3 +59,60 @@ test('resolveClaudeCodeExecutablePath falls back to the configured command when
59
59
 
60
60
  assert.equal(resolved, 'claude');
61
61
  });
62
+
63
+ test('resolveClaudeCodeExecutablePath returns undefined on Windows when the default resolves to nothing', () => {
64
+ // The SDK spawns this path directly, so a bare `claude` would fail with
65
+ // "native binary not found at claude"; undefined means "use your own binary".
66
+ const execFileSync = (() => {
67
+ throw new Error('not found');
68
+ }) as unknown as ResolveClaudeCodeExecutablePathDependencies['execFileSync'];
69
+
70
+ const resolved = resolveClaudeCodeExecutablePath(undefined, {
71
+ platform: 'win32',
72
+ execFileSync,
73
+ });
74
+
75
+ assert.equal(resolved, undefined);
76
+ });
77
+
78
+ test('resolveClaudeCodeExecutablePath returns undefined when every wrapper on PATH is a JavaScript launcher', () => {
79
+ // An older global install ships cli.js and no bin/claude.exe, so nothing on
80
+ // PATH maps to a native binary the SDK can spawn.
81
+ const wrapperDir = 'C:\\Users\\dev\\AppData\\Roaming\\npm';
82
+ const execFileSync =
83
+ (() => `${wrapperDir}\\claude\r\n${wrapperDir}\\claude.cmd\r\n`) as unknown as ResolveClaudeCodeExecutablePathDependencies['execFileSync'];
84
+ const readFileSync = (() => 'exec node "$basedir/node_modules/@anthropic-ai/claude-code/cli.js" "$@"') as unknown as ResolveClaudeCodeExecutablePathDependencies['readFileSync'];
85
+
86
+ const resolved = resolveClaudeCodeExecutablePath(undefined, {
87
+ platform: 'win32',
88
+ execFileSync,
89
+ existsSync: () => false,
90
+ readFileSync,
91
+ });
92
+
93
+ assert.equal(resolved, undefined);
94
+ });
95
+
96
+ test('resolveClaudeCodeExecutablePath still resolves the native exe when both installs are on PATH', () => {
97
+ const staleDir = 'C:\\Users\\dev\\AppData\\Roaming\\npm';
98
+ const nativeDir = 'C:\\nvm4w\\nodejs';
99
+ const nativePath = `${nativeDir}\\node_modules\\@anthropic-ai\\claude-code\\bin\\claude.exe`;
100
+ const execFileSync =
101
+ (() => `${staleDir}\\claude\r\n${nativeDir}\\claude\r\n`) as unknown as ResolveClaudeCodeExecutablePathDependencies['execFileSync'];
102
+ const readFileSync = (() => 'exec node "$basedir/node_modules/@anthropic-ai/claude-code/cli.js" "$@"') as unknown as ResolveClaudeCodeExecutablePathDependencies['readFileSync'];
103
+
104
+ const resolved = resolveClaudeCodeExecutablePath(undefined, {
105
+ platform: 'win32',
106
+ execFileSync,
107
+ existsSync: (candidate) => candidate === nativePath,
108
+ readFileSync,
109
+ });
110
+
111
+ assert.equal(resolved, nativePath);
112
+ });
113
+
114
+ test('resolveClaudeCodeExecutablePath leaves non-Windows platforms on the bare command', () => {
115
+ const resolved = resolveClaudeCodeExecutablePath(undefined, { platform: 'linux' });
116
+
117
+ assert.equal(resolved, 'claude');
118
+ });
@@ -0,0 +1,218 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import { prepareTranscriptMessages } from '@/shared/message-unification.js';
5
+ import type { NormalizedMessage } from '@/shared/types.js';
6
+
7
+ let nextId = 0;
8
+
9
+ const message = (fields: Partial<NormalizedMessage>): NormalizedMessage => ({
10
+ id: `m-${(nextId += 1)}`,
11
+ sessionId: 'session',
12
+ timestamp: '2026-08-22T00:00:00.000Z',
13
+ provider: 'claude',
14
+ kind: 'text',
15
+ ...fields,
16
+ } as NormalizedMessage);
17
+
18
+ const prose = () => message({ kind: 'text', role: 'assistant', content: 'Working on it.' });
19
+
20
+ const taskCall = (
21
+ toolName: string,
22
+ toolId: string,
23
+ toolInput: Record<string, unknown>,
24
+ toolUseResult: unknown,
25
+ ) => message({ kind: 'tool_use', toolName, toolId, toolInput, toolResult: { content: 'ok', toolUseResult } });
26
+
27
+ const todosOf = (entry: NormalizedMessage) =>
28
+ (entry.toolInput as { todos: Array<{ content: string; status: string }> }).todos
29
+ .map((todo) => `${todo.status}:${todo.content}`);
30
+
31
+ test('Claude task-tracker calls become one evolving checklist', () => {
32
+ const unified = prepareTranscriptMessages([
33
+ taskCall('TaskCreate', 't1', { subject: 'Read notes', activeForm: 'Reading notes' }, { task: { id: '1' } }),
34
+ taskCall('TaskCreate', 't2', { subject: 'Run tests', activeForm: 'Running tests' }, { task: { id: '2' } }),
35
+ prose(),
36
+ taskCall('TaskUpdate', 't3', { taskId: '1', status: 'completed' }, { success: true }),
37
+ ]);
38
+
39
+ const snapshots = unified.filter((entry) => entry.toolName === 'TodoWrite');
40
+ assert.equal(snapshots.length, 2, 'the two adjacent creates collapse into one snapshot');
41
+ assert.deepEqual(todosOf(snapshots[0]), ['pending:Read notes', 'pending:Run tests']);
42
+ assert.deepEqual(todosOf(snapshots[1]), ['completed:Read notes', 'pending:Run tests']);
43
+ assert.equal(
44
+ (snapshots[0].toolInput as { todos: Array<{ activeForm?: string }> }).todos[0].activeForm,
45
+ 'Reading notes',
46
+ );
47
+ });
48
+
49
+ test('a task listing keeps the wording each task was created with', () => {
50
+ const unified = prepareTranscriptMessages([
51
+ taskCall('TaskCreate', 't1', { subject: 'Read notes', activeForm: 'Reading notes' }, { task: { id: '1' } }),
52
+ prose(),
53
+ taskCall('TaskList', 't2', {}, { tasks: [{ id: '1', subject: 'Read notes', status: 'in_progress' }] }),
54
+ ]);
55
+
56
+ const listed = unified.filter((entry) => entry.toolName === 'TodoWrite').at(-1);
57
+ assert.deepEqual(todosOf(listed!), ['in_progress:Read notes']);
58
+ assert.equal(
59
+ (listed!.toolInput as { todos: Array<{ activeForm?: string }> }).todos[0].activeForm,
60
+ 'Reading notes',
61
+ );
62
+ });
63
+
64
+ test('a checklist that has not changed since it was last drawn is dropped', () => {
65
+ const unified = prepareTranscriptMessages([
66
+ message({ kind: 'tool_use', toolName: 'TodoWrite', toolId: 'w1', toolInput: { todos: [{ content: 'Ship it', status: 'pending' }] } }),
67
+ prose(),
68
+ message({ kind: 'tool_use', toolName: 'TodoWrite', toolId: 'w2', toolInput: { todos: [{ content: 'Ship it', status: 'pending' }] } }),
69
+ ]);
70
+
71
+ assert.deepEqual(unified.map((entry) => entry.toolId), ['w1', undefined]);
72
+ });
73
+
74
+ test('a superseded checklist and its result row both go', () => {
75
+ const unified = prepareTranscriptMessages([
76
+ message({ kind: 'tool_use', toolName: 'TodoWrite', toolId: 'w1', toolInput: { todos: [{ content: 'One', status: 'pending' }] } }),
77
+ message({ kind: 'tool_result', toolId: 'w1', content: 'stale' }),
78
+ message({ kind: 'tool_use', toolName: 'TodoWrite', toolId: 'w2', toolInput: { todos: [{ content: 'One', status: 'completed' }] } }),
79
+ message({ kind: 'tool_result', toolId: 'w2', content: 'fresh' }),
80
+ ]);
81
+
82
+ assert.deepEqual(unified.map((entry) => entry.toolId), ['w2']);
83
+ });
84
+
85
+ test('a result that names its call is not shipped alongside it', () => {
86
+ const unified = prepareTranscriptMessages([
87
+ message({ kind: 'tool_use', toolName: 'Bash', toolId: 'b1', toolInput: { command: 'ls' }, toolResult: { content: 'a\nb' } }),
88
+ message({ kind: 'tool_result', toolId: 'b1', content: 'a\nb' }),
89
+ ]);
90
+
91
+ assert.equal(unified.length, 1, 'the result already rides on the call it belongs to');
92
+ assert.equal(unified[0].kind, 'tool_use');
93
+ assert.equal(unified[0].toolResult?.content, 'a\nb');
94
+ });
95
+
96
+ test('a result with no call to attach to is kept', () => {
97
+ const unified = prepareTranscriptMessages([
98
+ message({ kind: 'tool_result', content: 'orphaned output' }),
99
+ ]);
100
+
101
+ assert.deepEqual(unified.map((entry) => entry.kind), ['tool_result']);
102
+ });
103
+
104
+ test("Codex's request_user_input becomes an answered AskUserQuestion", () => {
105
+ const [unified] = prepareTranscriptMessages([
106
+ message({
107
+ provider: 'codex',
108
+ kind: 'tool_use',
109
+ toolName: 'request_user_input',
110
+ toolId: 'call-1',
111
+ toolInput: JSON.stringify({
112
+ questions: [{ id: 'season', header: 'Quick test', question: 'Which season?', options: [{ label: 'Summer' }] }],
113
+ }),
114
+ toolResult: { content: '{"answers":{"season":{"answers":["Summer"]}}}' },
115
+ }),
116
+ ]);
117
+
118
+ assert.equal(unified.toolName, 'AskUserQuestion');
119
+ assert.deepEqual((unified.toolInput as { answers: unknown }).answers, { 'Which season?': 'Summer' });
120
+ });
121
+
122
+ test('a multi-select Codex answer keeps every label it picked', () => {
123
+ const [unified] = prepareTranscriptMessages([
124
+ message({
125
+ provider: 'codex',
126
+ kind: 'tool_use',
127
+ toolName: 'request_user_input',
128
+ toolId: 'call-1',
129
+ toolInput: JSON.stringify({ questions: [{ id: 'scope', question: 'Which areas?', options: [] }] }),
130
+ toolResult: { content: '{"answers":{"scope":{"answers":["Providers","Tests"]}}}' },
131
+ }),
132
+ ]);
133
+
134
+ assert.deepEqual((unified.toolInput as { answers: unknown }).answers, { 'Which areas?': 'Providers, Tests' });
135
+ });
136
+
137
+ test("Claude's AskUserQuestion answer is folded into the question it answers", () => {
138
+ const [unified] = prepareTranscriptMessages([
139
+ message({
140
+ kind: 'tool_use',
141
+ toolName: 'AskUserQuestion',
142
+ toolId: 'call-1',
143
+ toolInput: { questions: [{ question: 'What next?', header: 'Focus', options: [{ label: 'Branch changes' }] }] },
144
+ toolResult: {
145
+ content: 'Your questions have been answered.',
146
+ toolUseResult: { answers: { 'What next?': 'Branch changes' } },
147
+ },
148
+ }),
149
+ ]);
150
+
151
+ assert.deepEqual((unified.toolInput as { answers: unknown }).answers, { 'What next?': 'Branch changes' });
152
+ });
153
+
154
+ test('a live Claude answer is recovered from the acknowledgement sentence', () => {
155
+ const [unified] = prepareTranscriptMessages([
156
+ message({
157
+ kind: 'tool_use',
158
+ toolName: 'AskUserQuestion',
159
+ toolId: 'call-1',
160
+ toolInput: { questions: [{ question: 'What next?', options: [] }] },
161
+ toolResult: {
162
+ content: 'Your questions have been answered: "What next?"="Branch changes". You can now continue.',
163
+ },
164
+ }),
165
+ ]);
166
+
167
+ assert.deepEqual((unified.toolInput as { answers: unknown }).answers, { 'What next?': 'Branch changes' });
168
+ });
169
+
170
+ test('a question that was never answered carries no answers', () => {
171
+ const [unified] = prepareTranscriptMessages([
172
+ message({
173
+ kind: 'tool_use',
174
+ toolName: 'AskUserQuestion',
175
+ toolId: 'call-1',
176
+ toolInput: { questions: [{ question: 'What next?', options: [] }] },
177
+ toolResult: { content: '<tool_use_error>InputValidationError</tool_use_error>', isError: true },
178
+ }),
179
+ ]);
180
+
181
+ assert.equal((unified.toolInput as { answers?: unknown }).answers, undefined);
182
+ });
183
+
184
+ test('a huge tool output is capped, and says how much it dropped', () => {
185
+ const body = 'x'.repeat(120_000);
186
+ const [unified] = prepareTranscriptMessages([
187
+ message({
188
+ kind: 'tool_use',
189
+ toolName: 'Read',
190
+ toolId: 'r1',
191
+ toolInput: { file_path: '/tmp/shot.png' },
192
+ toolResult: { content: body, toolUseResult: { file: { base64: body } } },
193
+ }),
194
+ ]);
195
+
196
+ const content = String(unified.toolResult?.content);
197
+ assert.ok(content.length < body.length / 2, 'the output must not survive at full size');
198
+ assert.match(content, /… 80000 more characters$/);
199
+ const nested = (unified.toolResult?.toolUseResult as { file: { base64: string } }).file.base64;
200
+ assert.equal(nested, content, 'the structured copy is capped the same way');
201
+ });
202
+
203
+ test('a search result keeps every file it found', () => {
204
+ const filenames = Array.from({ length: 900 }, (_, index) => `/repo/src/file-${index}.ts`);
205
+ const [unified] = prepareTranscriptMessages([
206
+ message({
207
+ kind: 'tool_use',
208
+ toolName: 'Glob',
209
+ toolId: 'g1',
210
+ toolInput: { pattern: '**/*.ts' },
211
+ toolResult: { content: 'ok', toolUseResult: { numFiles: filenames.length, filenames } },
212
+ }),
213
+ ]);
214
+
215
+ const result = unified.toolResult?.toolUseResult as { filenames: string[]; numFiles: number };
216
+ assert.equal(result.numFiles, 900);
217
+ assert.deepEqual(result.filenames, filenames);
218
+ });
@@ -0,0 +1,139 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import test, { after } from 'node:test';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ // The Claude projects root is derived from `os.homedir()` when the utils module
9
+ // is first imported, so HOME has to point at the fixture home *before* that
10
+ // import runs. The fixture lives beside this file rather than under the temp
11
+ // directory: a home under `/tmp` would put everything under a read-only root
12
+ // and make the escape tests pass for the wrong reason.
13
+ const testDirectory = path.dirname(fileURLToPath(import.meta.url));
14
+ const fixtureRoot = await realpath(await mkdtemp(path.join(testDirectory, 'read-only-roots-fixture-')));
15
+ const fixtureHome = path.join(fixtureRoot, 'home');
16
+ const claudeProjectsRoot = path.join(fixtureHome, '.claude', 'projects');
17
+ // Under no read-only root: not the temp directory, not the Claude projects dir.
18
+ const outsideDirectory = path.join(fixtureRoot, 'outside');
19
+ await mkdir(claudeProjectsRoot, { recursive: true });
20
+ await mkdir(outsideDirectory);
21
+
22
+ const previousHome = process.env.HOME;
23
+ const previousUserProfile = process.env.USERPROFILE;
24
+ process.env.HOME = fixtureHome;
25
+ process.env.USERPROFILE = fixtureHome;
26
+
27
+ const { resolvePathUnderRoots, resolveReadOnlyRootPath, validateWorkspacePath } = await import('@/shared/utils.js');
28
+
29
+ after(async () => {
30
+ if (previousHome === undefined) {
31
+ delete process.env.HOME;
32
+ } else {
33
+ process.env.HOME = previousHome;
34
+ }
35
+ if (previousUserProfile === undefined) {
36
+ delete process.env.USERPROFILE;
37
+ } else {
38
+ process.env.USERPROFILE = previousUserProfile;
39
+ }
40
+ await rm(fixtureRoot, { recursive: true, force: true });
41
+ });
42
+
43
+ test('a path under the system temp directory resolves as readable', async () => {
44
+ const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'read-only-root-'));
45
+
46
+ try {
47
+ const filePath = path.join(temporaryDirectory, 'agent.output');
48
+ await writeFile(filePath, 'agent output', 'utf8');
49
+
50
+ // A background command's log is quoted straight out of the transcript, so
51
+ // this exact shape has to resolve.
52
+ const resolvedDirectory = await resolveReadOnlyRootPath(temporaryDirectory);
53
+ assert.ok(resolvedDirectory);
54
+ assert.equal(await resolveReadOnlyRootPath(filePath), path.join(resolvedDirectory, 'agent.output'));
55
+ } finally {
56
+ await rm(temporaryDirectory, { recursive: true, force: true });
57
+ }
58
+ });
59
+
60
+ test('a background agent output link resolves to its transcript under the Claude projects directory', async () => {
61
+ const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'read-only-root-'));
62
+
63
+ try {
64
+ // Claude writes `/tmp/claude-<uid>/<project>/<session>/tasks/<task>.output`
65
+ // as a symlink to the agent's transcript under `~/.claude/projects`.
66
+ const transcriptDirectory = path.join(claudeProjectsRoot, '-home-user-project', 'session-1', 'subagents');
67
+ await mkdir(transcriptDirectory, { recursive: true });
68
+ const transcriptPath = path.join(transcriptDirectory, 'agent-1.jsonl');
69
+ await writeFile(transcriptPath, '{"type":"assistant"}\n', 'utf8');
70
+
71
+ const tasksDirectory = path.join(temporaryDirectory, 'tasks');
72
+ await mkdir(tasksDirectory);
73
+ const outputLink = path.join(tasksDirectory, 'task-1.output');
74
+ await symlink(transcriptPath, outputLink);
75
+
76
+ assert.equal(await resolveReadOnlyRootPath(outputLink), transcriptPath);
77
+ assert.equal(await resolveReadOnlyRootPath(transcriptPath), transcriptPath);
78
+ } finally {
79
+ await rm(temporaryDirectory, { recursive: true, force: true });
80
+ }
81
+ });
82
+
83
+ test('the temp directory stays read-only: it is still not a valid workspace location', async () => {
84
+ const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'read-only-root-'));
85
+
86
+ try {
87
+ // Being browsable must not make it writable — the write policy is
88
+ // `validateWorkspacePath` and it does not consult the read-only roots.
89
+ const validation = await validateWorkspacePath(temporaryDirectory);
90
+ assert.equal(validation.valid, false);
91
+ } finally {
92
+ await rm(temporaryDirectory, { recursive: true, force: true });
93
+ }
94
+ });
95
+
96
+ test('paths outside the read-only roots do not resolve', async () => {
97
+ assert.equal(await resolveReadOnlyRootPath(outsideDirectory), null);
98
+ assert.equal(await resolveReadOnlyRootPath('/etc/passwd'), null);
99
+ assert.equal(await resolveReadOnlyRootPath('relative/path'), null);
100
+ assert.equal(await resolveReadOnlyRootPath(''), null);
101
+ });
102
+
103
+ test('a symlink planted in the temp directory cannot read outside every root', async () => {
104
+ const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'read-only-root-'));
105
+
106
+ try {
107
+ await writeFile(path.join(outsideDirectory, 'secret.txt'), 'secret', 'utf8');
108
+ const escapeLink = path.join(temporaryDirectory, 'escape');
109
+ await symlink(outsideDirectory, escapeLink);
110
+
111
+ // The name is under a read-only root but the file is not, so resolving the
112
+ // link before comparing is what keeps this closed.
113
+ assert.equal(await resolveReadOnlyRootPath(escapeLink), null);
114
+ assert.equal(await resolveReadOnlyRootPath(path.join(escapeLink, 'secret.txt')), null);
115
+ } finally {
116
+ await rm(temporaryDirectory, { recursive: true, force: true });
117
+ }
118
+ });
119
+
120
+ test('a root that does not exist does not stop later roots from matching', async () => {
121
+ const temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'read-only-root-'));
122
+
123
+ try {
124
+ // `/tmp` does not exist on Windows; the temp directory listed after it
125
+ // must still be checked rather than the whole search giving up.
126
+ const missingRoot = path.join(fixtureRoot, 'missing');
127
+ assert.equal(await resolvePathUnderRoots(temporaryDirectory, [missingRoot]), null);
128
+ assert.equal(
129
+ await resolvePathUnderRoots(temporaryDirectory, [missingRoot, os.tmpdir()]),
130
+ await realpath(temporaryDirectory),
131
+ );
132
+ } finally {
133
+ await rm(temporaryDirectory, { recursive: true, force: true });
134
+ }
135
+ });
136
+
137
+ test('traversal out of the temp directory does not resolve', async () => {
138
+ assert.equal(await resolveReadOnlyRootPath(`${os.tmpdir()}/../etc/passwd`), null);
139
+ });
@@ -0,0 +1,42 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import { stripAnsiSequences } from '@/shared/utils.js';
5
+
6
+ test('strips the SGR colors a CLI writes around its own warnings', () => {
7
+ const styled = '\u001B[93m\u001B[1m! \u001B[0m\u001B[0mpermission requested: external_directory';
8
+ assert.equal(stripAnsiSequences(styled), '! permission requested: external_directory');
9
+ });
10
+
11
+ test('strips CSI cursor control, including the 8-bit introducer', () => {
12
+ assert.equal(stripAnsiSequences('a\u001B[2Kb\u001B[?25lc'), 'abc');
13
+ assert.equal(stripAnsiSequences('a\u009B31mb'), 'ab');
14
+ });
15
+
16
+ test('strips an OSC sequence with either terminator, without eating the rest', () => {
17
+ assert.equal(stripAnsiSequences('\u001B]0;title\u0007done'), 'done');
18
+ assert.equal(stripAnsiSequences('\u001B]8;;https://example.com\u001B\\link'), 'link');
19
+ });
20
+
21
+ test('strips other ECMA-48 escape sequences such as charset selection', () => {
22
+ assert.equal(stripAnsiSequences('\u001B(Bplain'), 'plain');
23
+ });
24
+
25
+ test('leaves text without escape sequences untouched', () => {
26
+ assert.equal(stripAnsiSequences('plain [93m-looking text'), 'plain [93m-looking text');
27
+ });
28
+
29
+ test('strips a CSI sequence that carries a private parameter byte', () => {
30
+ assert.equal(stripAnsiSequences('a\u001B[>cb'), 'ab');
31
+ });
32
+
33
+ test('stops an unterminated OSC at the next escape, so later styling still strips', () => {
34
+ assert.equal(
35
+ stripAnsiSequences('\u001B]0;unterminated\u001B[31mred\u001B[0m'),
36
+ '0;unterminatedred',
37
+ );
38
+ });
39
+
40
+ test('reduces a styling-only chunk to an empty string', () => {
41
+ assert.equal(stripAnsiSequences('\u001B[0m\u001B[2K'), '');
42
+ });