@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
@@ -24,10 +24,14 @@ import {
24
24
  buildClaudeUserContent,
25
25
  normalizeImageDescriptors
26
26
  } from '@/shared/image-attachments.js';
27
- import { CLAUDE_PREDEFINED_MODELS } from '@/modules/providers/list/claude/claude-models.provider.js';
27
+ import {
28
+ CLAUDE_PREDEFINED_MODELS,
29
+ CLAUDE_ULTRACODE_EFFORT
30
+ } from '@/modules/providers/list/claude/claude-models.provider.js';
28
31
  import { resolveClaudeCodeExecutablePath } from '@/shared/claude-cli-path.js';
29
32
  import {
30
33
  createNotificationEvent,
34
+ notifyBackgroundWorkCompleted,
31
35
  notifyRunFailed,
32
36
  notifyRunStopped,
33
37
  notifyUserIfEnabled
@@ -40,11 +44,40 @@ const pendingToolApprovals = new Map();
40
44
  // terminal `complete` (aborted: true) to the client, so the run loop must not
41
45
  // emit a second one when its generator winds down.
42
46
  const abortedSessionIds = new Set();
47
+ // Query instances interrupted because a newer run took over their session id
48
+ // (see addSession). Their run loops must stay silent on wind-down: the map
49
+ // entry, the abort flag, and all client-facing events belong to the new run.
50
+ const supersededInstances = new WeakSet();
43
51
 
44
52
  const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROVAL_TIMEOUT_MS, 10) || 55000;
45
53
 
54
+ // How long background work is allowed to keep running after a turn ends. This drives
55
+ // two halves of the same behaviour:
56
+ //
57
+ // 1. Passed to the spawned CLI as CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS, which is how
58
+ // long it waits for still-running background *agents* before killing them.
59
+ // 2. A backstop on how long we hold the SDK's stdin open after a turn's `result`.
60
+ // The SDK closes stdin as soon as a turn ends, and the CLI reads that EOF as
61
+ // "print wind-down" — killing background *shells* after a short grace period,
62
+ // which the ceiling above does not cover. Holding stdin open also lets the CLI
63
+ // push follow-up turns (background-task completions, Monitor notifications,
64
+ // scheduled wake-ups).
65
+ //
66
+ // The hold normally ends long before this: a turn with nothing outstanding closes
67
+ // stdin immediately, background work releases it as soon as it reports back, and a
68
+ // new turn supersedes the previous hold. This ceiling only catches background work
69
+ // that never reports at all, so an abandoned session cannot leak a CLI process
70
+ // forever. The timer resets on every message, so it measures silence, not total time.
71
+ const BG_WAIT_CEILING_MS = 30 * 60 * 1000;
72
+
46
73
  const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion', 'ExitPlanMode']);
47
74
 
75
+ // Ultracode is a session-scoped setting rather than an SDK effort level: it pairs xhigh
76
+ // effort with standing dynamic-workflow orchestration, and the CLI only honours it when
77
+ // Workflows are enabled. The catalog offers it as an effort choice for the picker, so the
78
+ // selection is translated back into the two options the SDK actually understands here.
79
+ const ULTRACODE_SDK_EFFORT = 'xhigh';
80
+
48
81
  function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED_MODELS) {
49
82
  const selectedModel = modelsDefinition?.OPTIONS?.find((option) => option.value === model) || null;
50
83
  const allowedEfforts = selectedModel?.effort?.values
@@ -54,6 +87,30 @@ function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED
54
87
  : undefined;
55
88
  }
56
89
 
90
+ /**
91
+ * Writes the resolved effort choice onto the SDK options, expanding `ultracode` into the
92
+ * xhigh effort level plus the session-scoped settings it requires.
93
+ * @param {Object} sdkOptions - SDK options being built
94
+ * @param {string|undefined} resolvedEffort - Catalog-validated effort selection
95
+ */
96
+ function applyClaudeEffort(sdkOptions, resolvedEffort) {
97
+ if (!resolvedEffort) {
98
+ return;
99
+ }
100
+
101
+ if (resolvedEffort !== CLAUDE_ULTRACODE_EFFORT) {
102
+ sdkOptions.effort = resolvedEffort;
103
+ return;
104
+ }
105
+
106
+ sdkOptions.effort = ULTRACODE_SDK_EFFORT;
107
+ sdkOptions.settings = {
108
+ ...(sdkOptions.settings || {}),
109
+ ultracode: true,
110
+ enableWorkflows: true
111
+ };
112
+ }
113
+
57
114
  function createRequestId() {
58
115
  if (typeof crypto.randomUUID === 'function') {
59
116
  return crypto.randomUUID();
@@ -159,20 +216,22 @@ function matchesToolPermission(entry, toolName, input) {
159
216
  }
160
217
 
161
218
  function mapCliOptionsToSDK(options = {}) {
162
- const { providerSessionId, cwd, toolsSettings, permissionMode, effort } = options;
219
+ const { providerSessionId, cwd, toolsSettings, permissionMode, effort, resumeAnchorId, resumeFromScratch } = options;
163
220
 
164
221
  const sdkOptions = {};
165
222
 
166
223
  // Forward all host env vars (e.g. ANTHROPIC_BASE_URL) to the subprocess.
167
224
  // Since SDK 0.2.113, options.env replaces process.env instead of overlaying it.
168
- // This snapshot is also how CLI-side knobs reach the child: e.g. how long the CLI waits for
169
- // still-running background agents after a turn ends is set by CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS
170
- // (default 600000 ms; 0 = wait indefinitely) on this server's own environment.
171
- sdkOptions.env = { ...process.env };
225
+ sdkOptions.env = { ...process.env, CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: String(BG_WAIT_CEILING_MS) };
172
226
 
173
227
  // Resolve the executable eagerly on Windows because the SDK uses raw child_process.spawn,
174
228
  // which does not reliably follow npm's shell wrappers like cross-spawn does.
175
- sdkOptions.pathToClaudeCodeExecutable = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
229
+ // When nothing resolves the option stays unset on purpose: the SDK then falls back to the
230
+ // binary it ships, which beats handing it a bare `claude` that raw spawn can never launch.
231
+ const claudeExecutablePath = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
232
+ if (claudeExecutablePath) {
233
+ sdkOptions.pathToClaudeCodeExecutable = claudeExecutablePath;
234
+ }
176
235
 
177
236
  if (cwd) {
178
237
  sdkOptions.cwd = cwd;
@@ -214,14 +273,11 @@ function mapCliOptionsToSDK(options = {}) {
214
273
 
215
274
  sdkOptions.model = options.model || CLAUDE_PREDEFINED_MODELS.DEFAULT;
216
275
 
217
- const resolvedEffort = resolveClaudeEffort(
276
+ applyClaudeEffort(sdkOptions, resolveClaudeEffort(
218
277
  sdkOptions.model,
219
278
  effort,
220
279
  options.effortModels || CLAUDE_PREDEFINED_MODELS,
221
- );
222
- if (resolvedEffort) {
223
- sdkOptions.effort = resolvedEffort;
224
- }
280
+ ));
225
281
 
226
282
  sdkOptions.systemPrompt = {
227
283
  type: 'preset',
@@ -231,8 +287,19 @@ function mapCliOptionsToSDK(options = {}) {
231
287
  sdkOptions.settingSources = ['project', 'user', 'local'];
232
288
 
233
289
  // The SDK resumes with the provider-native session id, never the app id.
234
- if (providerSessionId) {
290
+ // `resumeFromScratch` is set when the very first prompt of a conversation was
291
+ // edited: there is nothing before it to resume through, so the turn has to
292
+ // start the conversation over instead.
293
+ if (providerSessionId && !resumeFromScratch) {
235
294
  sdkOptions.resume = providerSessionId;
295
+
296
+ // Editing an already-sent message re-runs the conversation truncated just
297
+ // before it. `resumeSessionAt` is inclusive of the uuid it names, so the
298
+ // caller resolves the last row to KEEP and passes that — never the edited
299
+ // turn itself, which would leave the original prompt in context.
300
+ if (resumeAnchorId) {
301
+ sdkOptions.resumeSessionAt = resumeAnchorId;
302
+ }
236
303
  }
237
304
 
238
305
  return sdkOptions;
@@ -243,13 +310,37 @@ function mapCliOptionsToSDK(options = {}) {
243
310
  * @param {string} sessionId - Session identifier
244
311
  * @param {Object} queryInstance - SDK query instance
245
312
  * @param {Object} writer - WebSocket writer for reconnect support
313
+ * @param {Function} releaseInput - Closes the held stdin stream so the CLI can exit
246
314
  */
247
- function addSession(sessionId, queryInstance, writer = null) {
315
+ function addSession(sessionId, queryInstance, writer = null, releaseInput = null) {
316
+ const existing = activeSessions.get(sessionId);
317
+ // A different live instance under the same key means an earlier run was
318
+ // superseded without being stopped (e.g. an abort that raced run setup and
319
+ // found nothing to interrupt). Overwriting it here would strand its
320
+ // generator forever — this map entry is the only handle for interrupting
321
+ // it. Stop it directly rather than via abortClaudeSDKSession, whose
322
+ // session-keyed abortedSessionIds flag would be consumed by the new run
323
+ // and suppress its terminal `complete`.
324
+ const superseding = Boolean(
325
+ existing && existing.status === 'active' && existing.instance && existing.instance !== queryInstance
326
+ );
327
+ if (superseding) {
328
+ supersededInstances.add(existing.instance);
329
+ Promise.resolve()
330
+ .then(() => existing.instance.interrupt())
331
+ .catch((error) => {
332
+ console.error(`Error interrupting superseded run for session ${sessionId}:`, error?.message || error);
333
+ });
334
+ existing.releaseInput?.();
335
+ }
336
+ const carried = superseding ? null : existing;
248
337
  activeSessions.set(sessionId, {
249
338
  instance: queryInstance,
250
- startTime: Date.now(),
339
+ startTime: carried?.startTime || Date.now(),
251
340
  status: 'active',
252
- writer
341
+ writer,
342
+ // Re-registered mid-run once the provider session id lands; keep the closer.
343
+ releaseInput: releaseInput || carried?.releaseInput || null
253
344
  });
254
345
  }
255
346
 
@@ -294,47 +385,129 @@ function transformMessage(sdkMessage) {
294
385
  return sdkMessage;
295
386
  }
296
387
 
388
+ /**
389
+ * True for the user bubble the SDK echoes for a subagent's own prompt.
390
+ *
391
+ * Subagent traffic carries `parent_tool_use_id`, so this echo lands in the main
392
+ * thread and stacks a second copy of the prompt right below the Agent tool card
393
+ * that already displays it. It also disappears on reload, because the transcript
394
+ * keeps that turn in the subagent's sidechain rather than the session file.
395
+ * @param {Object} message - Normalized message about to be sent to the client
396
+ * @returns {boolean}
397
+ */
398
+ export function isSubagentPromptEcho(message) {
399
+ return Boolean(message?.parentToolUseId) && message.role === 'user' && message.kind === 'text';
400
+ }
401
+
297
402
  function readNumber(value) {
298
403
  const parsed = Number(value);
299
404
  return Number.isFinite(parsed) ? parsed : 0;
300
405
  }
301
406
 
302
407
  /**
303
- * Extracts token usage from SDK messages.
304
- * Prefers per-step `message.usage` (Claude message payload), then falls back
305
- * to result-level usage/modelUsage for compatibility across SDK versions.
408
+ * @typedef {Object} TokenBudget
409
+ * @property {number} used
410
+ * @property {number} total
411
+ * @property {number} inputTokens
412
+ * @property {number} outputTokens
413
+ * @property {number} [cacheReadTokens]
414
+ * @property {number} [cacheCreationTokens]
415
+ * @property {number} [cacheTokens]
416
+ * @property {{ input: number, output: number }} breakdown
417
+ */
418
+
419
+ /**
420
+ * Builds a context-window budget from an Anthropic-shaped usage payload.
421
+ *
422
+ * `input_tokens + cache_read + cache_creation` is one request's whole prompt,
423
+ * which is exactly what the context window holds at that moment.
424
+ * @param {Object} messageUsage - Anthropic usage payload
425
+ * @returns {TokenBudget} Token budget object
426
+ */
427
+ function buildTokenBudget(messageUsage) {
428
+ const directInputTokens = readNumber(messageUsage.input_tokens ?? messageUsage.inputTokens);
429
+ const cacheCreationTokens = readNumber(messageUsage.cache_creation_input_tokens ?? messageUsage.cacheCreationInputTokens ?? messageUsage.cacheCreationTokens);
430
+ const cacheReadTokens = readNumber(messageUsage.cache_read_input_tokens ?? messageUsage.cacheReadInputTokens ?? messageUsage.cacheReadTokens);
431
+ const cacheTokens = cacheCreationTokens + cacheReadTokens;
432
+ const inputTokens = directInputTokens + cacheTokens;
433
+ const outputTokens = readNumber(messageUsage.output_tokens ?? messageUsage.outputTokens);
434
+ const contextWindow = parseInt(process.env.CONTEXT_WINDOW, 10) || 160000;
435
+
436
+ return {
437
+ used: inputTokens + outputTokens,
438
+ total: contextWindow,
439
+ inputTokens,
440
+ outputTokens,
441
+ cacheReadTokens,
442
+ cacheCreationTokens,
443
+ cacheTokens,
444
+ breakdown: {
445
+ input: inputTokens,
446
+ output: outputTokens,
447
+ },
448
+ };
449
+ }
450
+
451
+ /**
452
+ * Extracts the session's context-window usage from an SDK stream message.
453
+ *
454
+ * Only assistant messages describe the context window: each one reports the
455
+ * prompt its own request carried. The turn-ending `result` is deliberately not
456
+ * a source here — see `extractCumulativeTokenBudget`.
306
457
  * @param {Object} sdkMessage - SDK stream message
307
- * @returns {Object|null} Token budget object or null
458
+ * @returns {TokenBudget|null} Token budget object or null
308
459
  */
309
460
  function extractTokenBudget(sdkMessage) {
310
461
  if (!sdkMessage || typeof sdkMessage !== 'object') {
311
462
  return null;
312
463
  }
313
464
 
314
- const messageUsage = sdkMessage.message?.usage || sdkMessage.usage;
315
- if (messageUsage && typeof messageUsage === 'object') {
316
- const directInputTokens = readNumber(messageUsage.input_tokens ?? messageUsage.inputTokens);
317
- const cacheCreationTokens = readNumber(messageUsage.cache_creation_input_tokens ?? messageUsage.cacheCreationInputTokens ?? messageUsage.cacheCreationTokens);
318
- const cacheReadTokens = readNumber(messageUsage.cache_read_input_tokens ?? messageUsage.cacheReadInputTokens ?? messageUsage.cacheReadTokens);
319
- const cacheTokens = cacheCreationTokens + cacheReadTokens;
320
- const inputTokens = directInputTokens + cacheTokens;
321
- const outputTokens = readNumber(messageUsage.output_tokens ?? messageUsage.outputTokens);
322
- const totalUsed = inputTokens + outputTokens;
323
- const contextWindow = parseInt(process.env.CONTEXT_WINDOW, 10) || 160000;
465
+ // Subagent traffic (parent_tool_use_id set) reports the subagent's own
466
+ // context window, not this session's — surfacing it makes the counter drop
467
+ // to the subagent's number and bounce back on the next main-thread event.
468
+ if (sdkMessage.parent_tool_use_id) {
469
+ return null;
470
+ }
324
471
 
325
- return {
326
- used: totalUsed,
327
- total: contextWindow,
328
- inputTokens,
329
- outputTokens,
330
- cacheReadTokens,
331
- cacheCreationTokens,
332
- cacheTokens,
333
- breakdown: {
334
- input: inputTokens,
335
- output: outputTokens,
336
- },
337
- };
472
+ // Only assistant messages carry Anthropic-shaped usage. System
473
+ // task_progress/task_notification events have a top-level `usage` too, but
474
+ // shaped {total_tokens, tool_uses, duration_ms} — reading Anthropic keys
475
+ // off it yields an all-zero budget that flashes "0" in the composer.
476
+ if (sdkMessage.type !== 'assistant') {
477
+ return null;
478
+ }
479
+
480
+ const messageUsage = sdkMessage.message?.usage;
481
+ if (!messageUsage || typeof messageUsage !== 'object') {
482
+ return null;
483
+ }
484
+
485
+ return buildTokenBudget(messageUsage);
486
+ }
487
+
488
+ /**
489
+ * Last-resort budget read from a turn's `result` message.
490
+ *
491
+ * `result.usage` and `result.modelUsage` are the turn's *bill*: every request
492
+ * the turn made, summed, including each subagent's. A turn that made four
493
+ * requests therefore reports roughly four times the context the conversation
494
+ * actually holds, so publishing it made the counter leap at the end of a turn
495
+ * and fall back on the next assistant message — worst with subagents running,
496
+ * whose requests inflate the sum without ever entering this session's context.
497
+ *
498
+ * It is still the only usage an SDK build that reports none per assistant
499
+ * message ever emits, so it stays available for the caller to use when a turn
500
+ * produced no assistant budget at all.
501
+ * @param {Object} sdkMessage - SDK stream message
502
+ * @returns {TokenBudget|null} Token budget object or null
503
+ */
504
+ function extractCumulativeTokenBudget(sdkMessage) {
505
+ if (!sdkMessage || typeof sdkMessage !== 'object' || sdkMessage.type !== 'result') {
506
+ return null;
507
+ }
508
+
509
+ if (sdkMessage.usage && typeof sdkMessage.usage === 'object') {
510
+ return buildTokenBudget(sdkMessage.usage);
338
511
  }
339
512
 
340
513
  if (!sdkMessage.modelUsage || typeof sdkMessage.modelUsage !== 'object') {
@@ -366,38 +539,93 @@ function extractTokenBudget(sdkMessage) {
366
539
  };
367
540
  }
368
541
 
542
+ // Tool calls that leave work running past the end of a turn. Bash only counts
543
+ // when it is explicitly backgrounded; the rest defer or watch work by nature.
544
+ const DEFERRED_WORK_TOOLS = new Set(['Monitor', 'ScheduleWakeup', 'CronCreate', 'TaskCreate']);
545
+
546
+ /**
547
+ * Detects tool calls that keep working after the turn's `result` arrives.
548
+ *
549
+ * Only turns that start background work need their CLI process held open; every
550
+ * other turn can let it exit immediately, as it did before the hold existed.
551
+ *
552
+ * @param {Object} sdkMessage - SDK stream message
553
+ * @returns {boolean} True when the message launches work that outlives the turn
554
+ */
555
+ function startsBackgroundWork(sdkMessage) {
556
+ const content = sdkMessage?.message?.content;
557
+ if (!Array.isArray(content)) {
558
+ return false;
559
+ }
560
+
561
+ return content.some((block) => {
562
+ if (block?.type !== 'tool_use') {
563
+ return false;
564
+ }
565
+ if (block.name === 'Bash') {
566
+ return block.input?.run_in_background === true;
567
+ }
568
+ return DEFERRED_WORK_TOOLS.has(block.name);
569
+ });
570
+ }
571
+
369
572
  /**
370
- * Builds the SDK `prompt` payload for one turn.
573
+ * Builds the SDK user messages for one turn.
371
574
  *
372
- * Plain text turns pass the string through unchanged. Turns with image
373
- * attachments use the SDK's streaming-input mode: a single SDKUserMessage
374
- * whose content carries the prompt text plus one base64 `image` block per
375
- * attachment (read from the global `~/.cloudcli/assets` folder).
575
+ * Always returns SDKUserMessage records rather than a bare string: a string
576
+ * prompt makes the SDK flag the query as single-turn and close stdin the moment
577
+ * the turn's `result` arrives, which kills the CLI's background tasks. Plain
578
+ * text turns carry string content; turns with image attachments carry the
579
+ * prompt text plus one base64 `image` block per attachment (read from the
580
+ * global `~/.cloudcli/assets` folder).
376
581
  *
377
582
  * @param {string} command - User prompt
378
583
  * @param {Array} images - Image descriptors ({ path, name?, mimeType? })
379
584
  * @param {Array} files - Non-image attachment descriptors
380
585
  * @param {string} cwd - Project working directory attachment paths resolve against
381
- * @returns {Promise<string|AsyncIterable>} SDK prompt payload
586
+ * @returns {Promise<Array<Object>>} SDKUserMessage records for the turn
382
587
  */
383
- async function buildPromptPayload(command, images, files, cwd) {
588
+ async function buildPromptMessages(command, images, files, cwd) {
384
589
  const promptWithFiles = appendFilesInputTag(command, files);
385
- if (normalizeImageDescriptors(images).length === 0) {
386
- return promptWithFiles;
387
- }
590
+ const content = normalizeImageDescriptors(images).length === 0
591
+ ? promptWithFiles
592
+ : await buildClaudeUserContent(promptWithFiles, images, cwd);
593
+
594
+ return [{
595
+ type: 'user',
596
+ message: {
597
+ role: 'user',
598
+ content
599
+ },
600
+ parent_tool_use_id: null,
601
+ timestamp: new Date().toISOString()
602
+ }];
603
+ }
388
604
 
389
- const content = await buildClaudeUserContent(promptWithFiles, images, cwd);
390
- return (async function* () {
391
- yield {
392
- type: 'user',
393
- message: {
394
- role: 'user',
395
- content
396
- },
397
- parent_tool_use_id: null,
398
- timestamp: new Date().toISOString()
399
- };
605
+ /**
606
+ * Wraps prompt messages in an async iterable that yields them and then parks.
607
+ *
608
+ * The SDK closes the CLI's stdin as soon as its input iterable is exhausted (and
609
+ * immediately on `result` for string prompts). The CLI reads that EOF as the end
610
+ * of the run and kills anything still going in the background, so the iterable
611
+ * has to stay pending until we actually want the process gone.
612
+ *
613
+ * @param {Array<Object>} messages - SDKUserMessage records to send
614
+ * @returns {{ stream: AsyncIterable, release: () => void }} Stream plus its closer
615
+ */
616
+ function createHeldPromptStream(messages) {
617
+ let release;
618
+ const held = new Promise((resolve) => { release = resolve; });
619
+
620
+ const stream = (async function* () {
621
+ for (const message of messages) {
622
+ yield message;
623
+ }
624
+ // Keeps stdin open — the CLI stays alive until release() is called.
625
+ await held;
400
626
  })();
627
+
628
+ return { stream, release };
401
629
  }
402
630
 
403
631
  /**
@@ -486,6 +714,47 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
486
714
  });
487
715
  };
488
716
 
717
+ // Closes the held stdin stream so the CLI can wind down. Replaced once the
718
+ // stream exists; the finally block calls it no matter how the run ends.
719
+ let releasePromptStream = () => {};
720
+ let idleReleaseTimer = null;
721
+ // The client is told the turn is over as soon as `result` lands, even though
722
+ // the process lingers, so the UI never waits out the idle hold.
723
+ let turnCompleteSent = false;
724
+ // Set when a turn starts background work, cleared when the next `result`
725
+ // arrives — only turns with work still outstanding hold their process open.
726
+ let backgroundWorkPending = false;
727
+ // True while the process is being held open for background work, so a later
728
+ // `result` can be recognised as that work reporting back.
729
+ let heldForBackgroundWork = false;
730
+ // Set once a turn publishes a budget read from an assistant message, so the
731
+ // turn-ending `result` is only mined for usage when nothing better arrived.
732
+ let assistantBudgetSent = false;
733
+
734
+ // A new turn supersedes any earlier one still holding this session's process
735
+ // open, so held runs cannot stack up across a conversation.
736
+ if (sessionKey()) {
737
+ getSession(sessionKey())?.releaseInput?.();
738
+ }
739
+
740
+ // Arms (or re-arms) the idle countdown that eventually closes stdin.
741
+ const scheduleRelease = () => {
742
+ if (idleReleaseTimer) {
743
+ clearTimeout(idleReleaseTimer);
744
+ idleReleaseTimer = null;
745
+ }
746
+ idleReleaseTimer = setTimeout(() => {
747
+ idleReleaseTimer = null;
748
+ releasePromptStream();
749
+ }, BG_WAIT_CEILING_MS);
750
+ // Never let the hold keep the server process alive on its own.
751
+ idleReleaseTimer.unref?.();
752
+ };
753
+
754
+ // Hoisted above the try so the catch's cleanup can tell whether this run
755
+ // still owns the activeSessions entry (or was superseded by a newer run).
756
+ let queryInstance = null;
757
+
489
758
  try {
490
759
  const resolvedModel = await context.resolveResumeModel(sessionId, options.model);
491
760
  let effortModels = CLAUDE_PREDEFINED_MODELS;
@@ -507,10 +776,10 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
507
776
  sdkOptions.mcpServers = mcpServers;
508
777
  }
509
778
 
510
- // Turns with image attachments switch to streaming input so the images
511
- // ride along as real content blocks. Built per query attempt because an
512
- // async generator cannot be replayed once consumed.
513
- const createPrompt = () => buildPromptPayload(command, options.images, options.files, options.cwd);
779
+ // Every turn uses streaming input so stdin stays open past the turn's
780
+ // `result`. The message list is reusable, but each query attempt needs its
781
+ // own stream because an async generator cannot be replayed once consumed.
782
+ const promptMessages = await buildPromptMessages(command, options.images, options.files, options.cwd);
514
783
 
515
784
  sdkOptions.hooks = {
516
785
  Notification: [{
@@ -598,6 +867,13 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
598
867
  return { behavior: 'deny', message: 'Permission request cancelled' };
599
868
  }
600
869
 
870
+ // A client answered. Announce it on the run stream so the replay buffer
871
+ // and every other attached tab drop the prompt — resolving happens over
872
+ // the inbound socket only, so without this a mid-run page refresh
873
+ // replays the `permission_request` with nothing to retract it and the
874
+ // already-answered prompt resurrects.
875
+ ws.send(createNormalizedMessage({ kind: 'permission_resolved', requestId, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
876
+
601
877
  if (decision.allow) {
602
878
  if (decision.rememberEntry && typeof decision.rememberEntry === 'string') {
603
879
  if (!sdkOptions.allowedTools.includes(decision.rememberEntry)) {
@@ -613,10 +889,11 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
613
889
  return { behavior: 'deny', message: decision.message ?? 'User denied tool use' };
614
890
  };
615
891
 
616
- let queryInstance;
892
+ let heldPrompt = createHeldPromptStream(promptMessages);
893
+ releasePromptStream = heldPrompt.release;
617
894
  try {
618
895
  queryInstance = query({
619
- prompt: await createPrompt(),
896
+ prompt: heldPrompt.stream,
620
897
  options: sdkOptions
621
898
  });
622
899
  } catch (hookError) {
@@ -624,15 +901,19 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
624
901
  // Keep notification behavior operational via runtime events even if hook registration fails.
625
902
  console.warn('Failed to initialize Claude query with hooks, retrying without hooks:', hookError?.message || hookError);
626
903
  delete sdkOptions.hooks;
904
+ // Discard the abandoned stream and build a fresh one for the retry.
905
+ heldPrompt.release();
906
+ heldPrompt = createHeldPromptStream(promptMessages);
907
+ releasePromptStream = heldPrompt.release;
627
908
  queryInstance = query({
628
- prompt: await createPrompt(),
909
+ prompt: heldPrompt.stream,
629
910
  options: sdkOptions
630
911
  });
631
912
  }
632
913
 
633
914
  // Track the query instance for abort capability
634
915
  if (sessionKey()) {
635
- addSession(sessionKey(), queryInstance, ws);
916
+ addSession(sessionKey(), queryInstance, ws, releasePromptStream);
636
917
  }
637
918
 
638
919
  // Process streaming messages
@@ -642,7 +923,7 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
642
923
  if (message.session_id && !capturedSessionId) {
643
924
 
644
925
  capturedSessionId = message.session_id;
645
- addSession(sessionKey(), queryInstance, ws);
926
+ addSession(sessionKey(), queryInstance, ws, releasePromptStream);
646
927
 
647
928
  // Set session ID on writer
648
929
  if (ws.setSessionId && typeof ws.setSessionId === 'function') {
@@ -669,44 +950,115 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
669
950
  if (transformedMessage.parentToolUseId && !msg.parentToolUseId) {
670
951
  msg.parentToolUseId = transformedMessage.parentToolUseId;
671
952
  }
953
+ if (isSubagentPromptEcho(msg)) {
954
+ continue;
955
+ }
672
956
  ws.send(msg);
673
957
  }
674
958
 
675
- // Extract and send token budget updates from assistant/result usage payloads
676
- const tokenBudgetData = extractTokenBudget(message);
959
+ // Extract and send token budget updates from assistant usage payloads,
960
+ // falling back to the turn's cumulative bill only for SDK builds that
961
+ // report no per-assistant usage at all.
962
+ const tokenBudgetData = extractTokenBudget(message)
963
+ || (assistantBudgetSent ? null : extractCumulativeTokenBudget(message));
677
964
  if (tokenBudgetData) {
965
+ if (message.type === 'assistant') {
966
+ assistantBudgetSent = true;
967
+ }
678
968
  ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
679
969
  }
970
+
971
+ if (startsBackgroundWork(message)) {
972
+ backgroundWorkPending = true;
973
+ }
974
+
975
+ if (message.type === 'result') {
976
+ // The turn is done as far as the client is concerned.
977
+ const abortPending = sessionKey() ? abortedSessionIds.has(sessionKey()) : false;
978
+ if (!turnCompleteSent && !abortPending) {
979
+ turnCompleteSent = true;
980
+ ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
981
+ notifyRunStopped({
982
+ userId: ws?.userId || null,
983
+ provider: 'claude',
984
+ sessionId: sessionId || capturedSessionId || null,
985
+ sessionName: sessionSummary,
986
+ stopReason: 'completed'
987
+ });
988
+ } else if (heldForBackgroundWork && !abortPending) {
989
+ // A result after the turn already reported complete means the work we
990
+ // held the process open for has finished and pushed a follow-up turn.
991
+ notifyBackgroundWorkCompleted({
992
+ userId: ws?.userId || null,
993
+ provider: 'claude',
994
+ sessionId: sessionId || capturedSessionId || null,
995
+ sessionName: sessionSummary
996
+ });
997
+ }
998
+ if (backgroundWorkPending) {
999
+ // Work started during this turn is still running. Hold the process
1000
+ // open so it can finish and report back in a follow-up turn; the
1001
+ // ceiling is only a backstop for work that never reports.
1002
+ backgroundWorkPending = false;
1003
+ heldForBackgroundWork = true;
1004
+ scheduleRelease();
1005
+ } else {
1006
+ // Either nothing was backgrounded, or the background work just
1007
+ // reported in — let the CLI exit now, as it always has.
1008
+ heldForBackgroundWork = false;
1009
+ releasePromptStream();
1010
+ }
1011
+ } else if (idleReleaseTimer) {
1012
+ // Background activity after the turn — push the countdown back out.
1013
+ scheduleRelease();
1014
+ }
680
1015
  }
681
1016
 
682
- // Clean up session on completion
683
- if (sessionKey()) {
1017
+ // Clean up session on completion — only while this run still owns the map
1018
+ // entry. A superseding run may have replaced it, and deleting here would
1019
+ // strand that run.
1020
+ if (sessionKey() && getSession(sessionKey())?.instance === queryInstance) {
684
1021
  removeSession(sessionKey());
685
1022
  }
686
1023
 
1024
+ // A superseded run winds down silently: the map entry, the abort flag,
1025
+ // and all client-facing events belong to the run that replaced it.
1026
+ const superseded = supersededInstances.has(queryInstance);
1027
+
687
1028
  // Send the terminal completion event — skipped for aborted runs, whose
688
- // terminal `complete` (aborted: true) was already sent by abort-session.
689
- const wasAborted = sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
690
- if (!wasAborted) {
691
- ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
1029
+ // terminal `complete` (aborted: true) was already sent by abort-session, and
1030
+ // for runs that already reported completion when their `result` arrived.
1031
+ const wasAborted = !superseded && sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
1032
+ if (!turnCompleteSent && !superseded) {
1033
+ turnCompleteSent = true;
1034
+ if (!wasAborted) {
1035
+ ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
1036
+ }
1037
+ notifyRunStopped({
1038
+ userId: ws?.userId || null,
1039
+ provider: 'claude',
1040
+ sessionId: sessionId || capturedSessionId || null,
1041
+ sessionName: sessionSummary,
1042
+ stopReason: wasAborted ? 'aborted' : 'completed'
1043
+ });
692
1044
  }
693
- notifyRunStopped({
694
- userId: ws?.userId || null,
695
- provider: 'claude',
696
- sessionId: sessionId || capturedSessionId || null,
697
- sessionName: sessionSummary,
698
- stopReason: wasAborted ? 'aborted' : 'completed'
699
- });
700
1045
  // Complete
701
1046
 
702
1047
  } catch (error) {
703
1048
  console.error('SDK query error:', error);
704
1049
 
705
- // Clean up session on error
706
- if (sessionKey()) {
1050
+ // Clean up session on error — only while this run still owns the map entry
1051
+ // (a superseding run may have replaced it).
1052
+ if (sessionKey() && getSession(sessionKey())?.instance === queryInstance) {
707
1053
  removeSession(sessionKey());
708
1054
  }
709
1055
 
1056
+ if (supersededInstances.has(queryInstance)) {
1057
+ // Interrupted because a newer run took over this session id; that run
1058
+ // owns the abort flag and all further client-facing events.
1059
+ return;
1060
+ }
1061
+
710
1062
  const wasAborted = sessionKey() ? abortedSessionIds.delete(sessionKey()) : false;
711
1063
  if (wasAborted) {
712
1064
  // The abort already produced the terminal complete; a generator throw
@@ -720,9 +1072,13 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
720
1072
  ? 'Claude Code is not installed. Please install it first: https://docs.anthropic.com/en/docs/claude-code'
721
1073
  : error.message;
722
1074
 
723
- // Send error to WebSocket, then the terminal complete
1075
+ // Send error to WebSocket, then the terminal complete. A run that already
1076
+ // reported completion and then failed during its post-turn hold still
1077
+ // surfaces the error, but must not emit a second terminal complete.
724
1078
  ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
725
- ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 1 }));
1079
+ if (!turnCompleteSent) {
1080
+ ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 1 }));
1081
+ }
726
1082
  notifyRunFailed({
727
1083
  userId: ws?.userId || null,
728
1084
  provider: 'claude',
@@ -730,6 +1086,14 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
730
1086
  sessionName: sessionSummary,
731
1087
  error
732
1088
  });
1089
+ } finally {
1090
+ // Always close stdin — otherwise an aborted or failed run leaves the CLI
1091
+ // process (and its MCP servers) alive until the server exits.
1092
+ if (idleReleaseTimer) {
1093
+ clearTimeout(idleReleaseTimer);
1094
+ idleReleaseTimer = null;
1095
+ }
1096
+ releasePromptStream();
733
1097
  }
734
1098
  }
735
1099
 
@@ -756,6 +1120,10 @@ async function abortClaudeSDKSession(sessionId) {
756
1120
  // Call interrupt() on the query instance
757
1121
  await session.instance.interrupt();
758
1122
 
1123
+ // Release the held stdin stream; without this the CLI stays up for the rest
1124
+ // of the post-turn hold even though the user cancelled.
1125
+ session.releaseInput?.();
1126
+
759
1127
  // Update session status
760
1128
  session.status = 'aborted';
761
1129
 
@@ -843,5 +1211,7 @@ export {
843
1211
  getActiveClaudeSDKSessions,
844
1212
  resolveToolApproval,
845
1213
  getPendingApprovalsForSession,
846
- reconnectSessionWriter
1214
+ reconnectSessionWriter,
1215
+ extractTokenBudget,
1216
+ extractCumulativeTokenBudget
847
1217
  };