@cloudcli-ai/cloudcli 1.37.1 → 1.37.3

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 (402) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/abnfDiagram-N423BO3Z-CJntskwA.js +1 -0
  3. package/dist/assets/arc-CIfH4uMN.js +1 -0
  4. package/dist/assets/architectureDiagram-T3A2C74G-_IpEkukg.js +36 -0
  5. package/dist/assets/blockDiagram-VBNYF7ZC-BttxrpgZ.js +132 -0
  6. package/dist/assets/c4Diagram-5PPSVZJV-g51dSRr1.js +10 -0
  7. package/dist/assets/channel-C5xU9jMw.js +1 -0
  8. package/dist/assets/chunk-2GRJ4B5K-D_-LbQUa.js +1 -0
  9. package/dist/assets/chunk-2Q5K7J3B-BtlHzwaO.js +1 -0
  10. package/dist/assets/chunk-5RXB4S5H-UzkpvpAT.js +231 -0
  11. package/dist/assets/chunk-5VM5RSS4-BKwx5Gag.js +15 -0
  12. package/dist/assets/chunk-6Q2QTUOP-D_KEBH5f.js +88 -0
  13. package/dist/assets/chunk-GF5L2VYU-rK2DGyto.js +206 -0
  14. package/dist/assets/chunk-JWPE2WC7-CeBl_N9i.js +1 -0
  15. package/dist/assets/chunk-KBJHAD2P-BiQLWVIr.js +1 -0
  16. package/dist/assets/chunk-RYQCIY6F-C1knVGV5.js +1 -0
  17. package/dist/assets/chunk-XXDRQBXY-CtKbJkvF.js +1 -0
  18. package/dist/assets/classDiagram-JCYQIIEL-CRrL3vp7.js +1 -0
  19. package/dist/assets/classDiagram-v2-OCEON4UE-CRrL3vp7.js +1 -0
  20. package/dist/assets/cose-bilkent-JH36ORCC-DjX8y40Z.js +1 -0
  21. package/dist/assets/cynefin-VYW2F7L2-DM6YeRba.js +178 -0
  22. package/dist/assets/cynefinDiagram-MW4NZA55-wZJ-vy5B.js +62 -0
  23. package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
  24. package/dist/assets/dagre-VZM6K2ZE-BzP0ejkH.js +4 -0
  25. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  26. package/dist/assets/diagram-7IWD3JNH-BTL_j23J.js +30 -0
  27. package/dist/assets/diagram-B4RE2ZJO-Cf1Norgu.js +3 -0
  28. package/dist/assets/diagram-LBJQPF4R-DPiUEKr-.js +24 -0
  29. package/dist/assets/diagram-Q27KOJAE-CTcTpiz5.js +24 -0
  30. package/dist/assets/diagram-UB23O5K3-DPMSdYm0.js +41 -0
  31. package/dist/assets/ebnfDiagram-BXEA7PRR-7yvD6uiF.js +1 -0
  32. package/dist/assets/erDiagram-JOGREHBK-CfgeDoV6.js +85 -0
  33. package/dist/assets/flowDiagram-UKHOOZJN-B27ng5zu.js +156 -0
  34. package/dist/assets/ganttDiagram-PKOTCBZU-CcC6b_7t.js +292 -0
  35. package/dist/assets/gitGraphDiagram-DS77QQ5N-BX5kPGrO.js +106 -0
  36. package/dist/assets/graph-Cx-EqisW.js +1 -0
  37. package/dist/assets/index-BTDHQ6Id.css +32 -0
  38. package/dist/assets/index-sFfz8iBR.js +4931 -0
  39. package/dist/assets/infoDiagram-6WML65LV-CUP-SlN4.js +2 -0
  40. package/dist/assets/init-Gi6I4Gst.js +1 -0
  41. package/dist/assets/ishikawaDiagram-WSZJBQD7-BTTDhOpX.js +70 -0
  42. package/dist/assets/journeyDiagram-NVQOT4AX-ARXcc61m.js +139 -0
  43. package/dist/assets/kanban-definition-27J2QSJJ-BieMpqMA.js +89 -0
  44. package/dist/assets/layout-D6BstSzF.js +1 -0
  45. package/dist/assets/linear-61vxjRnP.js +1 -0
  46. package/dist/assets/map-CV9uif0g.js +1 -0
  47. package/dist/assets/mermaid.core-nhnwkfQx.js +312 -0
  48. package/dist/assets/mindmap-definition-FAOFIHXS-Cm6x5vT1.js +96 -0
  49. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  50. package/dist/assets/pegDiagram-VL7TDLO6-CcYBq_9H.js +1 -0
  51. package/dist/assets/pieDiagram-7S7Q4E2Y-CfvIjfzo.js +39 -0
  52. package/dist/assets/quadrantDiagram-CIZ2JOQS-SgYoWPNb.js +7 -0
  53. package/dist/assets/railroadDiagram-AXF67PYL-D2Mu5E_f.js +1 -0
  54. package/dist/assets/requirementDiagram-LRYGKXZP-Azwe_k4Q.js +84 -0
  55. package/dist/assets/sankeyDiagram-W5VNT64P-DN1Ju3PA.js +40 -0
  56. package/dist/assets/sequenceDiagram-SI44F4Z6-Bj4qC34S.js +162 -0
  57. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  58. package/dist/assets/sizeCapture-X5ZJPWSS-Co4PdklG.js +1 -0
  59. package/dist/assets/stateDiagram-OKZ733FA-nOvL9dRC.js +1 -0
  60. package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
  61. package/dist/assets/swimlanes-SLNWSIFB-CCK3wMid.js +2 -0
  62. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
  63. package/dist/assets/timeline-definition-Z64GVDOM-BC29RNFm.js +120 -0
  64. package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
  65. package/dist/assets/vennDiagram-T6HMQDX7-pZlZCtEo.js +34 -0
  66. package/dist/assets/wardleyDiagram-T6FBY63Y-De8k2-zM.js +78 -0
  67. package/dist/assets/xychartDiagram-ELKLHX3M-Bk8ykH0w.js +7 -0
  68. package/dist/index.html +3 -3
  69. package/dist-server/server/index.js +8 -5
  70. package/dist-server/server/index.js.map +1 -1
  71. package/dist-server/server/modules/agent/agent.module.js +2 -1
  72. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  73. package/dist-server/server/modules/agent/agent.routes.js +2 -2
  74. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  75. package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
  76. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  77. package/dist-server/server/modules/auth/auth.middleware.js +1 -1
  78. package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
  79. package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
  80. package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
  81. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  82. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  83. package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
  84. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  85. package/dist-server/server/modules/browser-use/index.js +2 -0
  86. package/dist-server/server/modules/browser-use/index.js.map +1 -1
  87. package/dist-server/server/modules/cli/cli.js +9 -3
  88. package/dist-server/server/modules/cli/cli.js.map +1 -1
  89. package/dist-server/server/modules/cli/cli.module.js +0 -1
  90. package/dist-server/server/modules/cli/cli.module.js.map +1 -1
  91. package/dist-server/server/modules/cli/cli.service.js +0 -18
  92. package/dist-server/server/modules/cli/cli.service.js.map +1 -1
  93. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
  94. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
  95. package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
  96. package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
  97. package/dist-server/server/modules/database/connection.js +1 -1
  98. package/dist-server/server/modules/database/connection.js.map +1 -1
  99. package/dist-server/server/modules/database/index.js +5 -0
  100. package/dist-server/server/modules/database/index.js.map +1 -1
  101. package/dist-server/server/modules/database/migrations.js +32 -1
  102. package/dist-server/server/modules/database/migrations.js.map +1 -1
  103. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  104. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  105. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  106. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  107. package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
  108. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  109. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  110. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  111. package/dist-server/server/modules/database/schema.js +92 -0
  112. package/dist-server/server/modules/database/schema.js.map +1 -1
  113. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  114. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  115. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  116. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  117. package/dist-server/server/modules/file-tree/file-tree.service.js +18 -10
  118. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  119. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +62 -11
  120. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  121. package/dist-server/server/modules/git/git-branch.service.js +15 -0
  122. package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
  123. package/dist-server/server/modules/git/git.routes.js +15 -7
  124. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  125. package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
  126. package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
  127. package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
  128. package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
  129. package/dist-server/server/modules/notifications/index.js +3 -1
  130. package/dist-server/server/modules/notifications/index.js.map +1 -1
  131. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
  132. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  133. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  134. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  135. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  136. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  137. package/dist-server/server/modules/providers/index.js +3 -0
  138. package/dist-server/server/modules/providers/index.js.map +1 -1
  139. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
  140. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  141. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  142. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  143. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
  144. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  145. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +422 -86
  146. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  147. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
  148. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  149. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  150. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  151. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  152. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  153. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  154. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  155. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  156. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  157. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -0
  158. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  159. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +60 -49
  160. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  161. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  162. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  163. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
  164. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  165. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  166. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  167. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  168. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  169. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  170. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
  171. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  172. package/dist-server/server/modules/providers/provider.routes.js +29 -0
  173. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  174. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
  175. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  176. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  177. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  178. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +63 -7
  179. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  180. package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
  181. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  182. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  183. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  184. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  185. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  186. package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
  187. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  188. package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
  189. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  190. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  191. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  192. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
  193. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  194. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  195. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  196. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  197. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  198. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  199. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  200. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
  201. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  202. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  203. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  204. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
  205. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  206. package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
  207. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  208. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  209. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  210. package/dist-server/server/modules/providers/tests/provider.routes.test.js +35 -0
  211. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  212. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  213. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  214. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
  215. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  216. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  217. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  218. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  219. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  220. package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
  221. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  222. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  223. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  224. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  225. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  226. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  227. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  228. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
  229. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  230. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  231. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  232. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
  233. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  234. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  235. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  236. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  237. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  238. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  239. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  240. package/dist-server/server/modules/user/user.module.js +10 -1
  241. package/dist-server/server/modules/user/user.module.js.map +1 -1
  242. package/dist-server/server/modules/user/user.routes.js +44 -0
  243. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  244. package/dist-server/server/modules/user/user.service.js +75 -0
  245. package/dist-server/server/modules/user/user.service.js.map +1 -1
  246. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  247. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  248. package/dist-server/server/modules/websocket/index.js +7 -0
  249. package/dist-server/server/modules/websocket/index.js.map +1 -1
  250. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
  251. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  252. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  253. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  254. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
  255. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  256. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  257. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  258. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
  259. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  260. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  261. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  262. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  263. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  264. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  265. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  266. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
  267. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  268. package/dist-server/server/shared/claude-cli-path.js +22 -5
  269. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  270. package/dist-server/server/shared/index.js +3 -0
  271. package/dist-server/server/shared/index.js.map +1 -0
  272. package/dist-server/server/shared/message-unification.js +376 -0
  273. package/dist-server/server/shared/message-unification.js.map +1 -0
  274. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  275. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  276. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  277. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  278. package/dist-server/server/shared/utils.js +42 -0
  279. package/dist-server/server/shared/utils.js.map +1 -1
  280. package/package.json +23 -16
  281. package/scripts/promote-dist-server.mjs +50 -0
  282. package/server/index.ts +12 -6
  283. package/server/modules/agent/agent.module.ts +2 -1
  284. package/server/modules/agent/agent.routes.ts +2 -2
  285. package/server/modules/agent/tests/agent.routes.test.ts +33 -1
  286. package/server/modules/auth/auth.middleware.ts +2 -2
  287. package/server/modules/browser-use/browser-use-runtime.ts +10 -0
  288. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  289. package/server/modules/browser-use/browser-use.service.ts +5 -8
  290. package/server/modules/browser-use/index.ts +3 -0
  291. package/server/modules/cli/cli.module.ts +0 -1
  292. package/server/modules/cli/cli.service.ts +0 -20
  293. package/server/modules/cli/cli.ts +9 -3
  294. package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
  295. package/server/modules/cli/tests/cli.service.test.ts +0 -3
  296. package/server/modules/database/connection.ts +2 -1
  297. package/server/modules/database/index.ts +10 -0
  298. package/server/modules/database/migrations.ts +37 -0
  299. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  300. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  301. package/server/modules/database/repositories/sessions.db.ts +196 -3
  302. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  303. package/server/modules/database/schema.ts +96 -0
  304. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  305. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  306. package/server/modules/file-tree/file-tree.service.ts +21 -10
  307. package/server/modules/file-tree/tests/file-tree.service.test.ts +65 -11
  308. package/server/modules/git/git-branch.service.ts +43 -0
  309. package/server/modules/git/git.routes.ts +16 -9
  310. package/server/modules/git/tests/git-branch.service.test.ts +60 -0
  311. package/server/modules/git/tests/git-init.routes.test.ts +83 -0
  312. package/server/modules/notifications/index.ts +2 -0
  313. package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
  314. package/server/modules/projects/services/project-delete.service.ts +13 -1
  315. package/server/modules/projects/services/project-star.service.ts +2 -2
  316. package/server/modules/providers/index.ts +4 -0
  317. package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
  318. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  319. package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
  320. package/server/modules/providers/list/claude/claude-runtime.provider.js +463 -93
  321. package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
  322. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  323. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  324. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  325. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  326. package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
  327. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
  328. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  329. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
  330. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  331. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  332. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  333. package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
  334. package/server/modules/providers/provider.routes.ts +36 -0
  335. package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
  336. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  337. package/server/modules/providers/services/session-conversations-search.service.ts +94 -7
  338. package/server/modules/providers/services/session-history-cache.service.ts +139 -0
  339. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  340. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  341. package/server/modules/providers/services/sessions.service.ts +201 -9
  342. package/server/modules/providers/tests/claude-auth.test.ts +58 -0
  343. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  344. package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
  345. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  346. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  347. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  348. package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
  349. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  350. package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
  351. package/server/modules/providers/tests/opencode-models.test.ts +166 -8
  352. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  353. package/server/modules/providers/tests/provider.routes.test.ts +61 -0
  354. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  355. package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
  356. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  357. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  358. package/server/modules/providers/tests/sessions.service.test.ts +67 -1
  359. package/server/modules/providers/tests/skills.test.ts +21 -5
  360. package/server/modules/scheduled-messages/index.ts +8 -0
  361. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  362. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
  363. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  364. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
  365. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  366. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  367. package/server/modules/user/tests/user.service.test.ts +90 -0
  368. package/server/modules/user/user.module.ts +10 -1
  369. package/server/modules/user/user.routes.ts +44 -0
  370. package/server/modules/user/user.service.ts +105 -0
  371. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  372. package/server/modules/websocket/index.ts +8 -0
  373. package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
  374. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  375. package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
  376. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  377. package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
  378. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  379. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  380. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  381. package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
  382. package/server/shared/claude-cli-path.ts +24 -6
  383. package/server/shared/index.ts +3 -0
  384. package/server/shared/interfaces.ts +62 -0
  385. package/server/shared/message-unification.ts +449 -0
  386. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  387. package/server/shared/tests/message-unification.test.ts +218 -0
  388. package/server/shared/types.ts +123 -3
  389. package/server/shared/utils.ts +50 -0
  390. package/server/tsconfig.json +6 -2
  391. package/README.de.md +0 -257
  392. package/README.ja.md +0 -249
  393. package/README.ko.md +0 -249
  394. package/README.ru.md +0 -257
  395. package/README.tr.md +0 -258
  396. package/README.zh-CN.md +0 -249
  397. package/README.zh-TW.md +0 -249
  398. package/dist/assets/index-D3jfzuKN.css +0 -32
  399. package/dist/assets/index-DTMWjJj8.js +0 -1550
  400. package/dist-server/server/constants/config.js +0 -6
  401. package/dist-server/server/constants/config.js.map +0 -1
  402. package/server/constants/config.ts +0 -5
@@ -17,9 +17,9 @@ import os from 'os';
17
17
  import path from 'path';
18
18
  import { query } from '@anthropic-ai/claude-agent-sdk';
19
19
  import { appendFilesInputTag, buildClaudeUserContent, normalizeImageDescriptors } from '../../../../shared/image-attachments.js';
20
- import { CLAUDE_PREDEFINED_MODELS } from '../../../../modules/providers/list/claude/claude-models.provider.js';
20
+ import { CLAUDE_PREDEFINED_MODELS, CLAUDE_ULTRACODE_EFFORT } from '../../../../modules/providers/list/claude/claude-models.provider.js';
21
21
  import { resolveClaudeCodeExecutablePath } from '../../../../shared/claude-cli-path.js';
22
- import { createNotificationEvent, notifyRunFailed, notifyRunStopped, notifyUserIfEnabled } from '../../../../modules/notifications/index.js';
22
+ import { createNotificationEvent, notifyBackgroundWorkCompleted, notifyRunFailed, notifyRunStopped, notifyUserIfEnabled } from '../../../../modules/notifications/index.js';
23
23
  import { createCompleteMessage, createNormalizedMessage } from '../../../../shared/utils.js';
24
24
  const activeSessions = new Map();
25
25
  const pendingToolApprovals = new Map();
@@ -27,8 +27,35 @@ const pendingToolApprovals = new Map();
27
27
  // terminal `complete` (aborted: true) to the client, so the run loop must not
28
28
  // emit a second one when its generator winds down.
29
29
  const abortedSessionIds = new Set();
30
+ // Query instances interrupted because a newer run took over their session id
31
+ // (see addSession). Their run loops must stay silent on wind-down: the map
32
+ // entry, the abort flag, and all client-facing events belong to the new run.
33
+ const supersededInstances = new WeakSet();
30
34
  const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROVAL_TIMEOUT_MS, 10) || 55000;
35
+ // How long background work is allowed to keep running after a turn ends. This drives
36
+ // two halves of the same behaviour:
37
+ //
38
+ // 1. Passed to the spawned CLI as CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS, which is how
39
+ // long it waits for still-running background *agents* before killing them.
40
+ // 2. A backstop on how long we hold the SDK's stdin open after a turn's `result`.
41
+ // The SDK closes stdin as soon as a turn ends, and the CLI reads that EOF as
42
+ // "print wind-down" — killing background *shells* after a short grace period,
43
+ // which the ceiling above does not cover. Holding stdin open also lets the CLI
44
+ // push follow-up turns (background-task completions, Monitor notifications,
45
+ // scheduled wake-ups).
46
+ //
47
+ // The hold normally ends long before this: a turn with nothing outstanding closes
48
+ // stdin immediately, background work releases it as soon as it reports back, and a
49
+ // new turn supersedes the previous hold. This ceiling only catches background work
50
+ // that never reports at all, so an abandoned session cannot leak a CLI process
51
+ // forever. The timer resets on every message, so it measures silence, not total time.
52
+ const BG_WAIT_CEILING_MS = 30 * 60 * 1000;
31
53
  const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion', 'ExitPlanMode']);
54
+ // Ultracode is a session-scoped setting rather than an SDK effort level: it pairs xhigh
55
+ // effort with standing dynamic-workflow orchestration, and the CLI only honours it when
56
+ // Workflows are enabled. The catalog offers it as an effort choice for the picker, so the
57
+ // selection is translated back into the two options the SDK actually understands here.
58
+ const ULTRACODE_SDK_EFFORT = 'xhigh';
32
59
  function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED_MODELS) {
33
60
  const selectedModel = modelsDefinition?.OPTIONS?.find((option) => option.value === model) || null;
34
61
  const allowedEfforts = selectedModel?.effort?.values
@@ -37,6 +64,27 @@ function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED
37
64
  ? effort
38
65
  : undefined;
39
66
  }
67
+ /**
68
+ * Writes the resolved effort choice onto the SDK options, expanding `ultracode` into the
69
+ * xhigh effort level plus the session-scoped settings it requires.
70
+ * @param {Object} sdkOptions - SDK options being built
71
+ * @param {string|undefined} resolvedEffort - Catalog-validated effort selection
72
+ */
73
+ function applyClaudeEffort(sdkOptions, resolvedEffort) {
74
+ if (!resolvedEffort) {
75
+ return;
76
+ }
77
+ if (resolvedEffort !== CLAUDE_ULTRACODE_EFFORT) {
78
+ sdkOptions.effort = resolvedEffort;
79
+ return;
80
+ }
81
+ sdkOptions.effort = ULTRACODE_SDK_EFFORT;
82
+ sdkOptions.settings = {
83
+ ...(sdkOptions.settings || {}),
84
+ ultracode: true,
85
+ enableWorkflows: true
86
+ };
87
+ }
40
88
  function createRequestId() {
41
89
  if (typeof crypto.randomUUID === 'function') {
42
90
  return crypto.randomUUID();
@@ -127,17 +175,19 @@ function matchesToolPermission(entry, toolName, input) {
127
175
  return false;
128
176
  }
129
177
  function mapCliOptionsToSDK(options = {}) {
130
- const { providerSessionId, cwd, toolsSettings, permissionMode, effort } = options;
178
+ const { providerSessionId, cwd, toolsSettings, permissionMode, effort, resumeAnchorId, resumeFromScratch } = options;
131
179
  const sdkOptions = {};
132
180
  // Forward all host env vars (e.g. ANTHROPIC_BASE_URL) to the subprocess.
133
181
  // Since SDK 0.2.113, options.env replaces process.env instead of overlaying it.
134
- // This snapshot is also how CLI-side knobs reach the child: e.g. how long the CLI waits for
135
- // still-running background agents after a turn ends is set by CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS
136
- // (default 600000 ms; 0 = wait indefinitely) on this server's own environment.
137
- sdkOptions.env = { ...process.env };
182
+ sdkOptions.env = { ...process.env, CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: String(BG_WAIT_CEILING_MS) };
138
183
  // Resolve the executable eagerly on Windows because the SDK uses raw child_process.spawn,
139
184
  // which does not reliably follow npm's shell wrappers like cross-spawn does.
140
- sdkOptions.pathToClaudeCodeExecutable = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
185
+ // When nothing resolves the option stays unset on purpose: the SDK then falls back to the
186
+ // binary it ships, which beats handing it a bare `claude` that raw spawn can never launch.
187
+ const claudeExecutablePath = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
188
+ if (claudeExecutablePath) {
189
+ sdkOptions.pathToClaudeCodeExecutable = claudeExecutablePath;
190
+ }
141
191
  if (cwd) {
142
192
  sdkOptions.cwd = cwd;
143
193
  }
@@ -168,18 +218,25 @@ function mapCliOptionsToSDK(options = {}) {
168
218
  sdkOptions.tools = { type: 'preset', preset: 'claude_code' };
169
219
  sdkOptions.disallowedTools = settings.disallowedTools || [];
170
220
  sdkOptions.model = options.model || CLAUDE_PREDEFINED_MODELS.DEFAULT;
171
- const resolvedEffort = resolveClaudeEffort(sdkOptions.model, effort, options.effortModels || CLAUDE_PREDEFINED_MODELS);
172
- if (resolvedEffort) {
173
- sdkOptions.effort = resolvedEffort;
174
- }
221
+ applyClaudeEffort(sdkOptions, resolveClaudeEffort(sdkOptions.model, effort, options.effortModels || CLAUDE_PREDEFINED_MODELS));
175
222
  sdkOptions.systemPrompt = {
176
223
  type: 'preset',
177
224
  preset: 'claude_code'
178
225
  };
179
226
  sdkOptions.settingSources = ['project', 'user', 'local'];
180
227
  // The SDK resumes with the provider-native session id, never the app id.
181
- if (providerSessionId) {
228
+ // `resumeFromScratch` is set when the very first prompt of a conversation was
229
+ // edited: there is nothing before it to resume through, so the turn has to
230
+ // start the conversation over instead.
231
+ if (providerSessionId && !resumeFromScratch) {
182
232
  sdkOptions.resume = providerSessionId;
233
+ // Editing an already-sent message re-runs the conversation truncated just
234
+ // before it. `resumeSessionAt` is inclusive of the uuid it names, so the
235
+ // caller resolves the last row to KEEP and passes that — never the edited
236
+ // turn itself, which would leave the original prompt in context.
237
+ if (resumeAnchorId) {
238
+ sdkOptions.resumeSessionAt = resumeAnchorId;
239
+ }
183
240
  }
184
241
  return sdkOptions;
185
242
  }
@@ -188,13 +245,35 @@ function mapCliOptionsToSDK(options = {}) {
188
245
  * @param {string} sessionId - Session identifier
189
246
  * @param {Object} queryInstance - SDK query instance
190
247
  * @param {Object} writer - WebSocket writer for reconnect support
248
+ * @param {Function} releaseInput - Closes the held stdin stream so the CLI can exit
191
249
  */
192
- function addSession(sessionId, queryInstance, writer = null) {
250
+ function addSession(sessionId, queryInstance, writer = null, releaseInput = null) {
251
+ const existing = activeSessions.get(sessionId);
252
+ // A different live instance under the same key means an earlier run was
253
+ // superseded without being stopped (e.g. an abort that raced run setup and
254
+ // found nothing to interrupt). Overwriting it here would strand its
255
+ // generator forever — this map entry is the only handle for interrupting
256
+ // it. Stop it directly rather than via abortClaudeSDKSession, whose
257
+ // session-keyed abortedSessionIds flag would be consumed by the new run
258
+ // and suppress its terminal `complete`.
259
+ const superseding = Boolean(existing && existing.status === 'active' && existing.instance && existing.instance !== queryInstance);
260
+ if (superseding) {
261
+ supersededInstances.add(existing.instance);
262
+ Promise.resolve()
263
+ .then(() => existing.instance.interrupt())
264
+ .catch((error) => {
265
+ console.error(`Error interrupting superseded run for session ${sessionId}:`, error?.message || error);
266
+ });
267
+ existing.releaseInput?.();
268
+ }
269
+ const carried = superseding ? null : existing;
193
270
  activeSessions.set(sessionId, {
194
271
  instance: queryInstance,
195
- startTime: Date.now(),
272
+ startTime: carried?.startTime || Date.now(),
196
273
  status: 'active',
197
- writer
274
+ writer,
275
+ // Re-registered mid-run once the provider session id lands; keep the closer.
276
+ releaseInput: releaseInput || carried?.releaseInput || null
198
277
  });
199
278
  }
200
279
  /**
@@ -234,44 +313,118 @@ function transformMessage(sdkMessage) {
234
313
  }
235
314
  return sdkMessage;
236
315
  }
316
+ /**
317
+ * True for the user bubble the SDK echoes for a subagent's own prompt.
318
+ *
319
+ * Subagent traffic carries `parent_tool_use_id`, so this echo lands in the main
320
+ * thread and stacks a second copy of the prompt right below the Agent tool card
321
+ * that already displays it. It also disappears on reload, because the transcript
322
+ * keeps that turn in the subagent's sidechain rather than the session file.
323
+ * @param {Object} message - Normalized message about to be sent to the client
324
+ * @returns {boolean}
325
+ */
326
+ export function isSubagentPromptEcho(message) {
327
+ return Boolean(message?.parentToolUseId) && message.role === 'user' && message.kind === 'text';
328
+ }
237
329
  function readNumber(value) {
238
330
  const parsed = Number(value);
239
331
  return Number.isFinite(parsed) ? parsed : 0;
240
332
  }
241
333
  /**
242
- * Extracts token usage from SDK messages.
243
- * Prefers per-step `message.usage` (Claude message payload), then falls back
244
- * to result-level usage/modelUsage for compatibility across SDK versions.
334
+ * @typedef {Object} TokenBudget
335
+ * @property {number} used
336
+ * @property {number} total
337
+ * @property {number} inputTokens
338
+ * @property {number} outputTokens
339
+ * @property {number} [cacheReadTokens]
340
+ * @property {number} [cacheCreationTokens]
341
+ * @property {number} [cacheTokens]
342
+ * @property {{ input: number, output: number }} breakdown
343
+ */
344
+ /**
345
+ * Builds a context-window budget from an Anthropic-shaped usage payload.
346
+ *
347
+ * `input_tokens + cache_read + cache_creation` is one request's whole prompt,
348
+ * which is exactly what the context window holds at that moment.
349
+ * @param {Object} messageUsage - Anthropic usage payload
350
+ * @returns {TokenBudget} Token budget object
351
+ */
352
+ function buildTokenBudget(messageUsage) {
353
+ const directInputTokens = readNumber(messageUsage.input_tokens ?? messageUsage.inputTokens);
354
+ const cacheCreationTokens = readNumber(messageUsage.cache_creation_input_tokens ?? messageUsage.cacheCreationInputTokens ?? messageUsage.cacheCreationTokens);
355
+ const cacheReadTokens = readNumber(messageUsage.cache_read_input_tokens ?? messageUsage.cacheReadInputTokens ?? messageUsage.cacheReadTokens);
356
+ const cacheTokens = cacheCreationTokens + cacheReadTokens;
357
+ const inputTokens = directInputTokens + cacheTokens;
358
+ const outputTokens = readNumber(messageUsage.output_tokens ?? messageUsage.outputTokens);
359
+ const contextWindow = parseInt(process.env.CONTEXT_WINDOW, 10) || 160000;
360
+ return {
361
+ used: inputTokens + outputTokens,
362
+ total: contextWindow,
363
+ inputTokens,
364
+ outputTokens,
365
+ cacheReadTokens,
366
+ cacheCreationTokens,
367
+ cacheTokens,
368
+ breakdown: {
369
+ input: inputTokens,
370
+ output: outputTokens,
371
+ },
372
+ };
373
+ }
374
+ /**
375
+ * Extracts the session's context-window usage from an SDK stream message.
376
+ *
377
+ * Only assistant messages describe the context window: each one reports the
378
+ * prompt its own request carried. The turn-ending `result` is deliberately not
379
+ * a source here — see `extractCumulativeTokenBudget`.
245
380
  * @param {Object} sdkMessage - SDK stream message
246
- * @returns {Object|null} Token budget object or null
381
+ * @returns {TokenBudget|null} Token budget object or null
247
382
  */
248
383
  function extractTokenBudget(sdkMessage) {
249
384
  if (!sdkMessage || typeof sdkMessage !== 'object') {
250
385
  return null;
251
386
  }
252
- const messageUsage = sdkMessage.message?.usage || sdkMessage.usage;
253
- if (messageUsage && typeof messageUsage === 'object') {
254
- const directInputTokens = readNumber(messageUsage.input_tokens ?? messageUsage.inputTokens);
255
- const cacheCreationTokens = readNumber(messageUsage.cache_creation_input_tokens ?? messageUsage.cacheCreationInputTokens ?? messageUsage.cacheCreationTokens);
256
- const cacheReadTokens = readNumber(messageUsage.cache_read_input_tokens ?? messageUsage.cacheReadInputTokens ?? messageUsage.cacheReadTokens);
257
- const cacheTokens = cacheCreationTokens + cacheReadTokens;
258
- const inputTokens = directInputTokens + cacheTokens;
259
- const outputTokens = readNumber(messageUsage.output_tokens ?? messageUsage.outputTokens);
260
- const totalUsed = inputTokens + outputTokens;
261
- const contextWindow = parseInt(process.env.CONTEXT_WINDOW, 10) || 160000;
262
- return {
263
- used: totalUsed,
264
- total: contextWindow,
265
- inputTokens,
266
- outputTokens,
267
- cacheReadTokens,
268
- cacheCreationTokens,
269
- cacheTokens,
270
- breakdown: {
271
- input: inputTokens,
272
- output: outputTokens,
273
- },
274
- };
387
+ // Subagent traffic (parent_tool_use_id set) reports the subagent's own
388
+ // context window, not this session's — surfacing it makes the counter drop
389
+ // to the subagent's number and bounce back on the next main-thread event.
390
+ if (sdkMessage.parent_tool_use_id) {
391
+ return null;
392
+ }
393
+ // Only assistant messages carry Anthropic-shaped usage. System
394
+ // task_progress/task_notification events have a top-level `usage` too, but
395
+ // shaped {total_tokens, tool_uses, duration_ms} — reading Anthropic keys
396
+ // off it yields an all-zero budget that flashes "0" in the composer.
397
+ if (sdkMessage.type !== 'assistant') {
398
+ return null;
399
+ }
400
+ const messageUsage = sdkMessage.message?.usage;
401
+ if (!messageUsage || typeof messageUsage !== 'object') {
402
+ return null;
403
+ }
404
+ return buildTokenBudget(messageUsage);
405
+ }
406
+ /**
407
+ * Last-resort budget read from a turn's `result` message.
408
+ *
409
+ * `result.usage` and `result.modelUsage` are the turn's *bill*: every request
410
+ * the turn made, summed, including each subagent's. A turn that made four
411
+ * requests therefore reports roughly four times the context the conversation
412
+ * actually holds, so publishing it made the counter leap at the end of a turn
413
+ * and fall back on the next assistant message — worst with subagents running,
414
+ * whose requests inflate the sum without ever entering this session's context.
415
+ *
416
+ * It is still the only usage an SDK build that reports none per assistant
417
+ * message ever emits, so it stays available for the caller to use when a turn
418
+ * produced no assistant budget at all.
419
+ * @param {Object} sdkMessage - SDK stream message
420
+ * @returns {TokenBudget|null} Token budget object or null
421
+ */
422
+ function extractCumulativeTokenBudget(sdkMessage) {
423
+ if (!sdkMessage || typeof sdkMessage !== 'object' || sdkMessage.type !== 'result') {
424
+ return null;
425
+ }
426
+ if (sdkMessage.usage && typeof sdkMessage.usage === 'object') {
427
+ return buildTokenBudget(sdkMessage.usage);
275
428
  }
276
429
  if (!sdkMessage.modelUsage || typeof sdkMessage.modelUsage !== 'object') {
277
430
  return null;
@@ -297,28 +450,55 @@ function extractTokenBudget(sdkMessage) {
297
450
  },
298
451
  };
299
452
  }
453
+ // Tool calls that leave work running past the end of a turn. Bash only counts
454
+ // when it is explicitly backgrounded; the rest defer or watch work by nature.
455
+ const DEFERRED_WORK_TOOLS = new Set(['Monitor', 'ScheduleWakeup', 'CronCreate', 'TaskCreate']);
456
+ /**
457
+ * Detects tool calls that keep working after the turn's `result` arrives.
458
+ *
459
+ * Only turns that start background work need their CLI process held open; every
460
+ * other turn can let it exit immediately, as it did before the hold existed.
461
+ *
462
+ * @param {Object} sdkMessage - SDK stream message
463
+ * @returns {boolean} True when the message launches work that outlives the turn
464
+ */
465
+ function startsBackgroundWork(sdkMessage) {
466
+ const content = sdkMessage?.message?.content;
467
+ if (!Array.isArray(content)) {
468
+ return false;
469
+ }
470
+ return content.some((block) => {
471
+ if (block?.type !== 'tool_use') {
472
+ return false;
473
+ }
474
+ if (block.name === 'Bash') {
475
+ return block.input?.run_in_background === true;
476
+ }
477
+ return DEFERRED_WORK_TOOLS.has(block.name);
478
+ });
479
+ }
300
480
  /**
301
- * Builds the SDK `prompt` payload for one turn.
481
+ * Builds the SDK user messages for one turn.
302
482
  *
303
- * Plain text turns pass the string through unchanged. Turns with image
304
- * attachments use the SDK's streaming-input mode: a single SDKUserMessage
305
- * whose content carries the prompt text plus one base64 `image` block per
306
- * attachment (read from the global `~/.cloudcli/assets` folder).
483
+ * Always returns SDKUserMessage records rather than a bare string: a string
484
+ * prompt makes the SDK flag the query as single-turn and close stdin the moment
485
+ * the turn's `result` arrives, which kills the CLI's background tasks. Plain
486
+ * text turns carry string content; turns with image attachments carry the
487
+ * prompt text plus one base64 `image` block per attachment (read from the
488
+ * global `~/.cloudcli/assets` folder).
307
489
  *
308
490
  * @param {string} command - User prompt
309
491
  * @param {Array} images - Image descriptors ({ path, name?, mimeType? })
310
492
  * @param {Array} files - Non-image attachment descriptors
311
493
  * @param {string} cwd - Project working directory attachment paths resolve against
312
- * @returns {Promise<string|AsyncIterable>} SDK prompt payload
494
+ * @returns {Promise<Array<Object>>} SDKUserMessage records for the turn
313
495
  */
314
- async function buildPromptPayload(command, images, files, cwd) {
496
+ async function buildPromptMessages(command, images, files, cwd) {
315
497
  const promptWithFiles = appendFilesInputTag(command, files);
316
- if (normalizeImageDescriptors(images).length === 0) {
317
- return promptWithFiles;
318
- }
319
- const content = await buildClaudeUserContent(promptWithFiles, images, cwd);
320
- return (async function* () {
321
- yield {
498
+ const content = normalizeImageDescriptors(images).length === 0
499
+ ? promptWithFiles
500
+ : await buildClaudeUserContent(promptWithFiles, images, cwd);
501
+ return [{
322
502
  type: 'user',
323
503
  message: {
324
504
  role: 'user',
@@ -326,8 +506,30 @@ async function buildPromptPayload(command, images, files, cwd) {
326
506
  },
327
507
  parent_tool_use_id: null,
328
508
  timestamp: new Date().toISOString()
329
- };
509
+ }];
510
+ }
511
+ /**
512
+ * Wraps prompt messages in an async iterable that yields them and then parks.
513
+ *
514
+ * The SDK closes the CLI's stdin as soon as its input iterable is exhausted (and
515
+ * immediately on `result` for string prompts). The CLI reads that EOF as the end
516
+ * of the run and kills anything still going in the background, so the iterable
517
+ * has to stay pending until we actually want the process gone.
518
+ *
519
+ * @param {Array<Object>} messages - SDKUserMessage records to send
520
+ * @returns {{ stream: AsyncIterable, release: () => void }} Stream plus its closer
521
+ */
522
+ function createHeldPromptStream(messages) {
523
+ let release;
524
+ const held = new Promise((resolve) => { release = resolve; });
525
+ const stream = (async function* () {
526
+ for (const message of messages) {
527
+ yield message;
528
+ }
529
+ // Keeps stdin open — the CLI stays alive until release() is called.
530
+ await held;
330
531
  })();
532
+ return { stream, release };
331
533
  }
332
534
  /**
333
535
  * Loads MCP server configurations from ~/.claude.json
@@ -409,6 +611,43 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
409
611
  event
410
612
  });
411
613
  };
614
+ // Closes the held stdin stream so the CLI can wind down. Replaced once the
615
+ // stream exists; the finally block calls it no matter how the run ends.
616
+ let releasePromptStream = () => { };
617
+ let idleReleaseTimer = null;
618
+ // The client is told the turn is over as soon as `result` lands, even though
619
+ // the process lingers, so the UI never waits out the idle hold.
620
+ let turnCompleteSent = false;
621
+ // Set when a turn starts background work, cleared when the next `result`
622
+ // arrives — only turns with work still outstanding hold their process open.
623
+ let backgroundWorkPending = false;
624
+ // True while the process is being held open for background work, so a later
625
+ // `result` can be recognised as that work reporting back.
626
+ let heldForBackgroundWork = false;
627
+ // Set once a turn publishes a budget read from an assistant message, so the
628
+ // turn-ending `result` is only mined for usage when nothing better arrived.
629
+ let assistantBudgetSent = false;
630
+ // A new turn supersedes any earlier one still holding this session's process
631
+ // open, so held runs cannot stack up across a conversation.
632
+ if (sessionKey()) {
633
+ getSession(sessionKey())?.releaseInput?.();
634
+ }
635
+ // Arms (or re-arms) the idle countdown that eventually closes stdin.
636
+ const scheduleRelease = () => {
637
+ if (idleReleaseTimer) {
638
+ clearTimeout(idleReleaseTimer);
639
+ idleReleaseTimer = null;
640
+ }
641
+ idleReleaseTimer = setTimeout(() => {
642
+ idleReleaseTimer = null;
643
+ releasePromptStream();
644
+ }, BG_WAIT_CEILING_MS);
645
+ // Never let the hold keep the server process alive on its own.
646
+ idleReleaseTimer.unref?.();
647
+ };
648
+ // Hoisted above the try so the catch's cleanup can tell whether this run
649
+ // still owns the activeSessions entry (or was superseded by a newer run).
650
+ let queryInstance = null;
412
651
  try {
413
652
  const resolvedModel = await context.resolveResumeModel(sessionId, options.model);
414
653
  let effortModels = CLAUDE_PREDEFINED_MODELS;
@@ -428,10 +667,10 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
428
667
  if (mcpServers) {
429
668
  sdkOptions.mcpServers = mcpServers;
430
669
  }
431
- // Turns with image attachments switch to streaming input so the images
432
- // ride along as real content blocks. Built per query attempt because an
433
- // async generator cannot be replayed once consumed.
434
- const createPrompt = () => buildPromptPayload(command, options.images, options.files, options.cwd);
670
+ // Every turn uses streaming input so stdin stays open past the turn's
671
+ // `result`. The message list is reusable, but each query attempt needs its
672
+ // own stream because an async generator cannot be replayed once consumed.
673
+ const promptMessages = await buildPromptMessages(command, options.images, options.files, options.cwd);
435
674
  sdkOptions.hooks = {
436
675
  Notification: [{
437
676
  matcher: '',
@@ -506,6 +745,12 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
506
745
  if (decision.cancelled) {
507
746
  return { behavior: 'deny', message: 'Permission request cancelled' };
508
747
  }
748
+ // A client answered. Announce it on the run stream so the replay buffer
749
+ // and every other attached tab drop the prompt — resolving happens over
750
+ // the inbound socket only, so without this a mid-run page refresh
751
+ // replays the `permission_request` with nothing to retract it and the
752
+ // already-answered prompt resurrects.
753
+ ws.send(createNormalizedMessage({ kind: 'permission_resolved', requestId, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
509
754
  if (decision.allow) {
510
755
  if (decision.rememberEntry && typeof decision.rememberEntry === 'string') {
511
756
  if (!sdkOptions.allowedTools.includes(decision.rememberEntry)) {
@@ -519,10 +764,11 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
519
764
  }
520
765
  return { behavior: 'deny', message: decision.message ?? 'User denied tool use' };
521
766
  };
522
- let queryInstance;
767
+ let heldPrompt = createHeldPromptStream(promptMessages);
768
+ releasePromptStream = heldPrompt.release;
523
769
  try {
524
770
  queryInstance = query({
525
- prompt: await createPrompt(),
771
+ prompt: heldPrompt.stream,
526
772
  options: sdkOptions
527
773
  });
528
774
  }
@@ -531,14 +777,18 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
531
777
  // Keep notification behavior operational via runtime events even if hook registration fails.
532
778
  console.warn('Failed to initialize Claude query with hooks, retrying without hooks:', hookError?.message || hookError);
533
779
  delete sdkOptions.hooks;
780
+ // Discard the abandoned stream and build a fresh one for the retry.
781
+ heldPrompt.release();
782
+ heldPrompt = createHeldPromptStream(promptMessages);
783
+ releasePromptStream = heldPrompt.release;
534
784
  queryInstance = query({
535
- prompt: await createPrompt(),
785
+ prompt: heldPrompt.stream,
536
786
  options: sdkOptions
537
787
  });
538
788
  }
539
789
  // Track the query instance for abort capability
540
790
  if (sessionKey()) {
541
- addSession(sessionKey(), queryInstance, ws);
791
+ addSession(sessionKey(), queryInstance, ws, releasePromptStream);
542
792
  }
543
793
  // Process streaming messages
544
794
  console.log('Starting async generator loop for session:', capturedSessionId || 'NEW');
@@ -546,7 +796,7 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
546
796
  // Capture session ID from first message
547
797
  if (message.session_id && !capturedSessionId) {
548
798
  capturedSessionId = message.session_id;
549
- addSession(sessionKey(), queryInstance, ws);
799
+ addSession(sessionKey(), queryInstance, ws, releasePromptStream);
550
800
  // Set session ID on writer
551
801
  if (ws.setSessionId && typeof ws.setSessionId === 'function') {
552
802
  ws.setSessionId(capturedSessionId);
@@ -570,39 +820,109 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
570
820
  if (transformedMessage.parentToolUseId && !msg.parentToolUseId) {
571
821
  msg.parentToolUseId = transformedMessage.parentToolUseId;
572
822
  }
823
+ if (isSubagentPromptEcho(msg)) {
824
+ continue;
825
+ }
573
826
  ws.send(msg);
574
827
  }
575
- // Extract and send token budget updates from assistant/result usage payloads
576
- const tokenBudgetData = extractTokenBudget(message);
828
+ // Extract and send token budget updates from assistant usage payloads,
829
+ // falling back to the turn's cumulative bill only for SDK builds that
830
+ // report no per-assistant usage at all.
831
+ const tokenBudgetData = extractTokenBudget(message)
832
+ || (assistantBudgetSent ? null : extractCumulativeTokenBudget(message));
577
833
  if (tokenBudgetData) {
834
+ if (message.type === 'assistant') {
835
+ assistantBudgetSent = true;
836
+ }
578
837
  ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
579
838
  }
839
+ if (startsBackgroundWork(message)) {
840
+ backgroundWorkPending = true;
841
+ }
842
+ if (message.type === 'result') {
843
+ // The turn is done as far as the client is concerned.
844
+ const abortPending = sessionKey() ? abortedSessionIds.has(sessionKey()) : false;
845
+ if (!turnCompleteSent && !abortPending) {
846
+ turnCompleteSent = true;
847
+ ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
848
+ notifyRunStopped({
849
+ userId: ws?.userId || null,
850
+ provider: 'claude',
851
+ sessionId: sessionId || capturedSessionId || null,
852
+ sessionName: sessionSummary,
853
+ stopReason: 'completed'
854
+ });
855
+ }
856
+ else if (heldForBackgroundWork && !abortPending) {
857
+ // A result after the turn already reported complete means the work we
858
+ // held the process open for has finished and pushed a follow-up turn.
859
+ notifyBackgroundWorkCompleted({
860
+ userId: ws?.userId || null,
861
+ provider: 'claude',
862
+ sessionId: sessionId || capturedSessionId || null,
863
+ sessionName: sessionSummary
864
+ });
865
+ }
866
+ if (backgroundWorkPending) {
867
+ // Work started during this turn is still running. Hold the process
868
+ // open so it can finish and report back in a follow-up turn; the
869
+ // ceiling is only a backstop for work that never reports.
870
+ backgroundWorkPending = false;
871
+ heldForBackgroundWork = true;
872
+ scheduleRelease();
873
+ }
874
+ else {
875
+ // Either nothing was backgrounded, or the background work just
876
+ // reported in — let the CLI exit now, as it always has.
877
+ heldForBackgroundWork = false;
878
+ releasePromptStream();
879
+ }
880
+ }
881
+ else if (idleReleaseTimer) {
882
+ // Background activity after the turn — push the countdown back out.
883
+ scheduleRelease();
884
+ }
580
885
  }
581
- // Clean up session on completion
582
- if (sessionKey()) {
886
+ // Clean up session on completion — only while this run still owns the map
887
+ // entry. A superseding run may have replaced it, and deleting here would
888
+ // strand that run.
889
+ if (sessionKey() && getSession(sessionKey())?.instance === queryInstance) {
583
890
  removeSession(sessionKey());
584
891
  }
892
+ // A superseded run winds down silently: the map entry, the abort flag,
893
+ // and all client-facing events belong to the run that replaced it.
894
+ const superseded = supersededInstances.has(queryInstance);
585
895
  // Send the terminal completion event — skipped for aborted runs, whose
586
- // terminal `complete` (aborted: true) was already sent by abort-session.
587
- const wasAborted = sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
588
- if (!wasAborted) {
589
- ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
896
+ // terminal `complete` (aborted: true) was already sent by abort-session, and
897
+ // for runs that already reported completion when their `result` arrived.
898
+ const wasAborted = !superseded && sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
899
+ if (!turnCompleteSent && !superseded) {
900
+ turnCompleteSent = true;
901
+ if (!wasAborted) {
902
+ ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
903
+ }
904
+ notifyRunStopped({
905
+ userId: ws?.userId || null,
906
+ provider: 'claude',
907
+ sessionId: sessionId || capturedSessionId || null,
908
+ sessionName: sessionSummary,
909
+ stopReason: wasAborted ? 'aborted' : 'completed'
910
+ });
590
911
  }
591
- notifyRunStopped({
592
- userId: ws?.userId || null,
593
- provider: 'claude',
594
- sessionId: sessionId || capturedSessionId || null,
595
- sessionName: sessionSummary,
596
- stopReason: wasAborted ? 'aborted' : 'completed'
597
- });
598
912
  // Complete
599
913
  }
600
914
  catch (error) {
601
915
  console.error('SDK query error:', error);
602
- // Clean up session on error
603
- if (sessionKey()) {
916
+ // Clean up session on error — only while this run still owns the map entry
917
+ // (a superseding run may have replaced it).
918
+ if (sessionKey() && getSession(sessionKey())?.instance === queryInstance) {
604
919
  removeSession(sessionKey());
605
920
  }
921
+ if (supersededInstances.has(queryInstance)) {
922
+ // Interrupted because a newer run took over this session id; that run
923
+ // owns the abort flag and all further client-facing events.
924
+ return;
925
+ }
606
926
  const wasAborted = sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
607
927
  if (wasAborted) {
608
928
  // The abort already produced the terminal complete; a generator throw
@@ -614,9 +934,13 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
614
934
  const errorContent = !installed
615
935
  ? 'Claude Code is not installed. Please install it first: https://docs.anthropic.com/en/docs/claude-code'
616
936
  : error.message;
617
- // Send error to WebSocket, then the terminal complete
937
+ // Send error to WebSocket, then the terminal complete. A run that already
938
+ // reported completion and then failed during its post-turn hold still
939
+ // surfaces the error, but must not emit a second terminal complete.
618
940
  ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
619
- ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 1 }));
941
+ if (!turnCompleteSent) {
942
+ ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 1 }));
943
+ }
620
944
  notifyRunFailed({
621
945
  userId: ws?.userId || null,
622
946
  provider: 'claude',
@@ -625,6 +949,15 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
625
949
  error
626
950
  });
627
951
  }
952
+ finally {
953
+ // Always close stdin — otherwise an aborted or failed run leaves the CLI
954
+ // process (and its MCP servers) alive until the server exits.
955
+ if (idleReleaseTimer) {
956
+ clearTimeout(idleReleaseTimer);
957
+ idleReleaseTimer = null;
958
+ }
959
+ releasePromptStream();
960
+ }
628
961
  }
629
962
  /**
630
963
  * Aborts an active SDK session
@@ -644,6 +977,9 @@ async function abortClaudeSDKSession(sessionId) {
644
977
  abortedSessionIds.add(sessionId);
645
978
  // Call interrupt() on the query instance
646
979
  await session.instance.interrupt();
980
+ // Release the held stdin stream; without this the CLI stays up for the rest
981
+ // of the post-turn hold even though the user cancelled.
982
+ session.releaseInput?.();
647
983
  // Update session status
648
984
  session.status = 'aborted';
649
985
  // Clean up session
@@ -718,5 +1054,5 @@ export const claudeRuntime = {
718
1054
  },
719
1055
  };
720
1056
  // Export public API
721
- export { queryClaudeSDK, abortClaudeSDKSession, isClaudeSDKSessionActive, getActiveClaudeSDKSessions, resolveToolApproval, getPendingApprovalsForSession, reconnectSessionWriter };
1057
+ export { queryClaudeSDK, abortClaudeSDKSession, isClaudeSDKSessionActive, getActiveClaudeSDKSessions, resolveToolApproval, getPendingApprovalsForSession, reconnectSessionWriter, extractTokenBudget, extractCumulativeTokenBudget };
722
1058
  //# sourceMappingURL=claude-runtime.provider.js.map