@cloudcli-ai/cloudcli 1.37.2 → 1.37.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (453) hide show
  1. package/README.md +269 -0
  2. package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-Dmkwx38H.js} +1 -1
  3. package/dist/assets/{arc-B8jF-ja9.js → arc-DLe9AYuO.js} +1 -1
  4. package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-CBbbbNUW.js} +1 -1
  5. package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-CaMEaWu4.js} +1 -1
  6. package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-DhjMgNnR.js} +1 -1
  7. package/dist/assets/channel-CWrmwsir.js +1 -0
  8. package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-yjl3-F-i.js} +1 -1
  9. package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-DsAYaWEP.js} +1 -1
  10. package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-Bt5qmqOX.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-yWrWVvUt.js} +1 -1
  12. package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-Dt66K5xb.js} +1 -1
  13. package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-DA0vzaj4.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CUkbx96V.js} +1 -1
  15. package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-DWKxx1WH.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-BqluplN0.js} +1 -1
  17. package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-DelKg3Xf.js} +1 -1
  18. package/dist/assets/classDiagram-JCYQIIEL-COAGCC6w.js +1 -0
  19. package/dist/assets/classDiagram-v2-OCEON4UE-COAGCC6w.js +1 -0
  20. package/dist/assets/{cose-bilkent-JH36ORCC-s1hygY8e.js → cose-bilkent-JH36ORCC-DHV93-Em.js} +1 -1
  21. package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-B9HZgoGX.js} +1 -1
  22. package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-hFjjEQg8.js} +1 -1
  23. package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-CpeVOC7V.js} +1 -1
  24. package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-D0H1UoRA.js} +1 -1
  25. package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-6Y7BQUup.js} +1 -1
  26. package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-C0nNOwht.js} +1 -1
  27. package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-En6QlOVX.js} +1 -1
  28. package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-CmPiNeGD.js} +1 -1
  29. package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-Bygzp1Qt.js} +1 -1
  30. package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CwScRfzl.js} +1 -1
  31. package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-bKX-0xP6.js} +1 -1
  32. package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-DtZPKXGd.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-CUOtdL1o.js} +1 -1
  34. package/dist/assets/index-CdjIFxM2.js +5004 -0
  35. package/dist/assets/index-DgafhGTF.css +32 -0
  36. package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-ciRQTS0h.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-CgjhQZ84.js} +1 -1
  38. package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-BsbQSeIW.js} +1 -1
  39. package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-CrwzY9Jw.js} +1 -1
  40. package/dist/assets/{linear-iPR-J9f7.js → linear-BUaCDr6l.js} +1 -1
  41. package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-ClgdUU0j.js} +5 -5
  42. package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-BictljY1.js} +1 -1
  43. package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-BXdByy_U.js} +1 -1
  44. package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-uZsMfkRX.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-7lCv4QIj.js} +1 -1
  46. package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-07dxIWbi.js} +1 -1
  47. package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Cb31Yv1_.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-X0YPSyF9.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-BdBxSIlP.js} +1 -1
  50. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  51. package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-DQM5xAyD.js} +1 -1
  52. package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-DJ3xkLUF.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-UEYNNEHI-vAaE4zNo.js +1 -0
  54. package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-rjfYdW0w.js} +2 -2
  55. package/dist/assets/swimlanesDiagram-ULZ7WXOC-TV2IF4Nq.js +8 -0
  56. package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-DZMra5do.js} +1 -1
  57. package/dist/assets/{vendor-codemirror-OwyKSvPE.js → vendor-codemirror-CYrSt7-U.js} +18 -18
  58. package/dist/assets/{vennDiagram-T6HMQDX7-DuF1uZMY.js → vennDiagram-T6HMQDX7-BevD8uu0.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-DsIyXB7z.js} +1 -1
  60. package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-G3PlF3ko.js} +1 -1
  61. package/dist/index.html +3 -3
  62. package/dist-server/server/index.js +11 -4
  63. package/dist-server/server/index.js.map +1 -1
  64. package/dist-server/server/modules/agent/agent.module.js +9 -2
  65. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  66. package/dist-server/server/modules/agent/agent.routes.js +177 -130
  67. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  68. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js +306 -0
  69. package/dist-server/server/modules/agent/tests/agent-run-registration.test.js.map +1 -0
  70. package/dist-server/server/modules/agent/tests/agent.routes.test.js +48 -2
  71. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  72. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  73. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  74. package/dist-server/server/modules/database/connection.js +1 -1
  75. package/dist-server/server/modules/database/connection.js.map +1 -1
  76. package/dist-server/server/modules/database/index.js +5 -0
  77. package/dist-server/server/modules/database/index.js.map +1 -1
  78. package/dist-server/server/modules/database/migrations.js +45 -1
  79. package/dist-server/server/modules/database/migrations.js.map +1 -1
  80. package/dist-server/server/modules/database/repositories/provider-models.js +60 -6
  81. package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -1
  82. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  83. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  84. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  85. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  86. package/dist-server/server/modules/database/repositories/sessions.db.js +144 -5
  87. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  88. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  89. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  90. package/dist-server/server/modules/database/schema.js +99 -0
  91. package/dist-server/server/modules/database/schema.js.map +1 -1
  92. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +99 -0
  93. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -1
  94. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  95. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  96. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  97. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  98. package/dist-server/server/modules/file-tree/file-tree.module.js +2 -1
  99. package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
  100. package/dist-server/server/modules/file-tree/file-tree.service.js +44 -7
  101. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  102. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +126 -1
  103. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  104. package/dist-server/server/modules/git/git-branch-diff.service.js +142 -0
  105. package/dist-server/server/modules/git/git-branch-diff.service.js.map +1 -0
  106. package/dist-server/server/modules/git/git-branch.service.js.map +1 -1
  107. package/dist-server/server/modules/git/git.routes.js +92 -5
  108. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  109. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js +160 -0
  110. package/dist-server/server/modules/git/tests/git-branch-diff.service.test.js.map +1 -0
  111. package/dist-server/server/modules/projects/project-clone.routes.js +108 -0
  112. package/dist-server/server/modules/projects/project-clone.routes.js.map +1 -0
  113. package/dist-server/server/modules/projects/projects.routes.js +13 -66
  114. package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
  115. package/dist-server/server/modules/projects/services/project-clone-request.service.js +36 -0
  116. package/dist-server/server/modules/projects/services/project-clone-request.service.js.map +1 -0
  117. package/dist-server/server/modules/projects/services/project-clone.service.js +86 -32
  118. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -1
  119. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  120. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  121. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  122. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  123. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js +191 -0
  124. package/dist-server/server/modules/projects/tests/project-clone.routes.test.js.map +1 -0
  125. package/dist-server/server/modules/projects/tests/project-clone.service.test.js +275 -0
  126. package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -1
  127. package/dist-server/server/modules/providers/index.js +3 -0
  128. package/dist-server/server/modules/providers/index.js.map +1 -1
  129. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +62 -9
  130. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  131. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  132. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  133. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +31 -12
  134. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  135. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +472 -53
  136. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  137. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +38 -11
  138. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  139. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +1007 -60
  140. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  141. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  142. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  143. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  144. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  145. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  146. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  147. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  148. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  149. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +47 -0
  150. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  151. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +71 -53
  152. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  153. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  154. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  155. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1621 -431
  156. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  157. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  158. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  159. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +9 -2
  160. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -1
  161. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +16 -8
  162. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  163. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  164. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +274 -9
  165. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  166. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  167. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -1
  168. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +19 -2
  169. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  170. package/dist-server/server/modules/providers/provider.routes.js +134 -2
  171. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  172. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js +100 -0
  173. package/dist-server/server/modules/providers/services/claude-cli-liveness.service.js.map +1 -0
  174. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js +97 -0
  175. package/dist-server/server/modules/providers/services/claude-settings-permissions.service.js.map +1 -0
  176. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +23 -1
  177. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  178. package/dist-server/server/modules/providers/services/provider-models.service.js +59 -5
  179. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  180. package/dist-server/server/modules/providers/services/provider-runtime.service.js +8 -0
  181. package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
  182. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  183. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  184. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +2 -4
  185. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  186. package/dist-server/server/modules/providers/services/session-history-cache.service.js +91 -0
  187. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  188. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  189. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  190. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  191. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  192. package/dist-server/server/modules/providers/services/sessions.service.js +252 -12
  193. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  194. package/dist-server/server/modules/providers/tests/claude-auth.test.js +183 -0
  195. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  196. package/dist-server/server/modules/providers/tests/claude-background-work.test.js +172 -0
  197. package/dist-server/server/modules/providers/tests/claude-background-work.test.js.map +1 -0
  198. package/dist-server/server/modules/providers/tests/claude-compaction.test.js +84 -0
  199. package/dist-server/server/modules/providers/tests/claude-compaction.test.js.map +1 -0
  200. package/dist-server/server/modules/providers/tests/claude-message-model.test.js +123 -0
  201. package/dist-server/server/modules/providers/tests/claude-message-model.test.js.map +1 -0
  202. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  203. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  204. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js +265 -0
  205. package/dist-server/server/modules/providers/tests/claude-runtime-hold.test.js.map +1 -0
  206. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +1815 -33
  207. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  208. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js +228 -0
  209. package/dist-server/server/modules/providers/tests/claude-settings-permissions.test.js.map +1 -0
  210. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  211. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  212. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  213. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  214. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  215. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  216. package/dist-server/server/modules/providers/tests/codex-models.test.js +25 -0
  217. package/dist-server/server/modules/providers/tests/codex-models.test.js.map +1 -0
  218. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +83 -0
  219. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  220. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  221. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  222. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +354 -15
  223. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  224. package/dist-server/server/modules/providers/tests/cursor-auth.test.js +167 -0
  225. package/dist-server/server/modules/providers/tests/cursor-auth.test.js.map +1 -0
  226. package/dist-server/server/modules/providers/tests/opencode-models.test.js +196 -4
  227. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  228. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +19 -1
  229. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  230. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js +30 -0
  231. package/dist-server/server/modules/providers/tests/provider-capabilities.service.test.js.map +1 -0
  232. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +79 -1
  233. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
  234. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  235. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  236. package/dist-server/server/modules/providers/tests/provider.routes.test.js +190 -1
  237. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -1
  238. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  239. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  240. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +139 -0
  241. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  242. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  243. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  244. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  245. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  246. package/dist-server/server/modules/providers/tests/sessions.service.test.js +219 -1
  247. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  248. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  249. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  250. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  251. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  252. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  253. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  254. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +175 -0
  255. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  256. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  257. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  258. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +290 -0
  259. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  260. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  261. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  262. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  263. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  264. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  265. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  266. package/dist-server/server/modules/user/user.module.js +10 -1
  267. package/dist-server/server/modules/user/user.module.js.map +1 -1
  268. package/dist-server/server/modules/user/user.routes.js +44 -0
  269. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  270. package/dist-server/server/modules/user/user.service.js +75 -0
  271. package/dist-server/server/modules/user/user.service.js.map +1 -1
  272. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  273. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  274. package/dist-server/server/modules/websocket/index.js +7 -0
  275. package/dist-server/server/modules/websocket/index.js.map +1 -1
  276. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +37 -49
  277. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  278. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  279. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  280. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +204 -10
  281. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  282. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  283. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  284. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +236 -20
  285. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  286. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js +137 -0
  287. package/dist-server/server/modules/websocket/tests/chat-background-subscribe.test.js.map +1 -0
  288. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  289. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  290. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  291. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  292. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js +96 -0
  293. package/dist-server/server/modules/websocket/tests/chat-stop-task.test.js.map +1 -0
  294. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  295. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  296. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +500 -0
  297. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  298. package/dist-server/server/shared/claude-cli-path.js +22 -5
  299. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  300. package/dist-server/server/shared/index.js +3 -0
  301. package/dist-server/server/shared/index.js.map +1 -0
  302. package/dist-server/server/shared/message-unification.js +376 -0
  303. package/dist-server/server/shared/message-unification.js.map +1 -0
  304. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  305. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  306. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  307. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  308. package/dist-server/server/shared/tests/read-only-roots.test.js +124 -0
  309. package/dist-server/server/shared/tests/read-only-roots.test.js.map +1 -0
  310. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js +31 -0
  311. package/dist-server/server/shared/tests/strip-ansi-sequences.test.js.map +1 -0
  312. package/dist-server/server/shared/utils.js +160 -0
  313. package/dist-server/server/shared/utils.js.map +1 -1
  314. package/electron/desktopWindow.js +6 -0
  315. package/electron/viewHost.js +25 -2
  316. package/package.json +20 -17
  317. package/server/index.ts +16 -5
  318. package/server/modules/agent/agent.module.ts +11 -1
  319. package/server/modules/agent/agent.routes.ts +200 -142
  320. package/server/modules/agent/tests/agent-run-registration.test.ts +348 -0
  321. package/server/modules/agent/tests/agent.routes.test.ts +51 -2
  322. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  323. package/server/modules/database/connection.ts +2 -1
  324. package/server/modules/database/index.ts +10 -0
  325. package/server/modules/database/migrations.ts +51 -0
  326. package/server/modules/database/repositories/provider-models.ts +79 -6
  327. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  328. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  329. package/server/modules/database/repositories/sessions.db.ts +204 -3
  330. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  331. package/server/modules/database/schema.ts +103 -0
  332. package/server/modules/database/tests/provider-models.db.integration.test.ts +113 -0
  333. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  334. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  335. package/server/modules/file-tree/file-tree.module.ts +2 -1
  336. package/server/modules/file-tree/file-tree.service.ts +49 -7
  337. package/server/modules/file-tree/tests/file-tree.service.test.ts +163 -1
  338. package/server/modules/git/git-branch-diff.service.ts +204 -0
  339. package/server/modules/git/git-branch.service.ts +2 -12
  340. package/server/modules/git/git.routes.ts +100 -6
  341. package/server/modules/git/tests/git-branch-diff.service.test.ts +210 -0
  342. package/server/modules/projects/project-clone.routes.ts +142 -0
  343. package/server/modules/projects/projects.routes.ts +16 -81
  344. package/server/modules/projects/services/project-clone-request.service.ts +56 -0
  345. package/server/modules/projects/services/project-clone.service.ts +102 -34
  346. package/server/modules/projects/services/project-delete.service.ts +13 -1
  347. package/server/modules/projects/services/project-star.service.ts +2 -2
  348. package/server/modules/projects/tests/project-clone.routes.test.ts +246 -0
  349. package/server/modules/projects/tests/project-clone.service.test.ts +376 -0
  350. package/server/modules/providers/index.ts +4 -0
  351. package/server/modules/providers/list/claude/claude-auth.provider.ts +81 -11
  352. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  353. package/server/modules/providers/list/claude/claude-models.provider.ts +37 -17
  354. package/server/modules/providers/list/claude/claude-runtime.provider.js +510 -54
  355. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +40 -14
  356. package/server/modules/providers/list/claude/claude-sessions.provider.ts +1195 -63
  357. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  358. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  359. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  360. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  361. package/server/modules/providers/list/codex/codex-models.provider.ts +47 -0
  362. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +105 -70
  363. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  364. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1847 -455
  365. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  366. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +10 -2
  367. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +17 -8
  368. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  369. package/server/modules/providers/list/opencode/opencode-models.provider.ts +297 -9
  370. package/server/modules/providers/list/opencode/opencode-runtime.provider.js +5 -2
  371. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +30 -2
  372. package/server/modules/providers/provider.routes.ts +162 -2
  373. package/server/modules/providers/services/claude-cli-liveness.service.ts +121 -0
  374. package/server/modules/providers/services/claude-settings-permissions.service.ts +127 -0
  375. package/server/modules/providers/services/provider-capabilities.service.ts +40 -1
  376. package/server/modules/providers/services/provider-models.service.ts +71 -4
  377. package/server/modules/providers/services/provider-runtime.service.ts +11 -0
  378. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  379. package/server/modules/providers/services/session-conversations-search.service.ts +2 -5
  380. package/server/modules/providers/services/session-history-cache.service.ts +150 -0
  381. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  382. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  383. package/server/modules/providers/services/sessions.service.ts +309 -17
  384. package/server/modules/providers/tests/claude-auth.test.ts +208 -0
  385. package/server/modules/providers/tests/claude-background-work.test.ts +213 -0
  386. package/server/modules/providers/tests/claude-compaction.test.ts +101 -0
  387. package/server/modules/providers/tests/claude-message-model.test.ts +160 -0
  388. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  389. package/server/modules/providers/tests/claude-runtime-hold.test.ts +309 -0
  390. package/server/modules/providers/tests/claude-sessions.test.ts +2167 -37
  391. package/server/modules/providers/tests/claude-settings-permissions.test.ts +271 -0
  392. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  393. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  394. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  395. package/server/modules/providers/tests/codex-models.test.ts +37 -0
  396. package/server/modules/providers/tests/codex-runtime.test.ts +94 -0
  397. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  398. package/server/modules/providers/tests/codex-sessions.test.ts +396 -15
  399. package/server/modules/providers/tests/cursor-auth.test.ts +187 -0
  400. package/server/modules/providers/tests/opencode-models.test.ts +266 -8
  401. package/server/modules/providers/tests/opencode-sessions.test.ts +40 -1
  402. package/server/modules/providers/tests/provider-capabilities.service.test.ts +37 -0
  403. package/server/modules/providers/tests/provider-models.service.test.ts +99 -1
  404. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  405. package/server/modules/providers/tests/provider.routes.test.ts +230 -1
  406. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  407. package/server/modules/providers/tests/session-history-cache.service.test.ts +165 -0
  408. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  409. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  410. package/server/modules/providers/tests/sessions.service.test.ts +303 -1
  411. package/server/modules/providers/tests/skills.test.ts +21 -5
  412. package/server/modules/scheduled-messages/index.ts +8 -0
  413. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  414. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +216 -0
  415. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  416. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +354 -0
  417. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  418. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  419. package/server/modules/user/tests/user.service.test.ts +90 -0
  420. package/server/modules/user/user.module.ts +10 -1
  421. package/server/modules/user/user.routes.ts +44 -0
  422. package/server/modules/user/user.service.ts +105 -0
  423. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  424. package/server/modules/websocket/README.md +4 -2
  425. package/server/modules/websocket/index.ts +8 -0
  426. package/server/modules/websocket/services/chat-run-registry.service.ts +45 -54
  427. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  428. package/server/modules/websocket/services/chat-websocket.service.ts +302 -16
  429. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  430. package/server/modules/websocket/services/shell-websocket.service.ts +276 -22
  431. package/server/modules/websocket/tests/chat-background-subscribe.test.ts +167 -0
  432. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  433. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  434. package/server/modules/websocket/tests/chat-stop-task.test.ts +127 -0
  435. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  436. package/server/modules/websocket/tests/shell-websocket.service.test.ts +629 -0
  437. package/server/shared/claude-cli-path.ts +24 -6
  438. package/server/shared/index.ts +3 -0
  439. package/server/shared/interfaces.ts +91 -0
  440. package/server/shared/message-unification.ts +449 -0
  441. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  442. package/server/shared/tests/message-unification.test.ts +218 -0
  443. package/server/shared/tests/read-only-roots.test.ts +139 -0
  444. package/server/shared/tests/strip-ansi-sequences.test.ts +42 -0
  445. package/server/shared/types.ts +382 -7
  446. package/server/shared/utils.ts +176 -0
  447. package/dist/assets/channel-_falYbU1.js +0 -1
  448. package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
  449. package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
  450. package/dist/assets/index-CzPLBvXT.css +0 -32
  451. package/dist/assets/index-DFkBBuSA.js +0 -1549
  452. package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
  453. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
@@ -17,11 +17,16 @@ 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
22
  import { createNotificationEvent, notifyBackgroundWorkCompleted, notifyRunFailed, notifyRunStopped, notifyUserIfEnabled } from '../../../../modules/notifications/index.js';
23
+ import { sessionHistoryCache } from '../../../../modules/providers/services/session-history-cache.service.js';
23
24
  import { createCompleteMessage, createNormalizedMessage } from '../../../../shared/utils.js';
24
25
  const activeSessions = new Map();
26
+ // Outstanding background tasks per live session, keyed like activeSessions. An
27
+ // entry lives exactly as long as the map entry it shadows: cleared when the
28
+ // session is removed, and reset when a newer run takes the key over.
29
+ const backgroundWork = createBackgroundWorkTracker();
25
30
  const pendingToolApprovals = new Map();
26
31
  // Sessions cancelled via abort-session. The abort handler already sent the
27
32
  // terminal `complete` (aborted: true) to the client, so the run loop must not
@@ -51,6 +56,11 @@ const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROVAL_TIMEO
51
56
  // forever. The timer resets on every message, so it measures silence, not total time.
52
57
  const BG_WAIT_CEILING_MS = 30 * 60 * 1000;
53
58
  const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion', 'ExitPlanMode']);
59
+ // Ultracode is a session-scoped setting rather than an SDK effort level: it pairs xhigh
60
+ // effort with standing dynamic-workflow orchestration, and the CLI only honours it when
61
+ // Workflows are enabled. The catalog offers it as an effort choice for the picker, so the
62
+ // selection is translated back into the two options the SDK actually understands here.
63
+ const ULTRACODE_SDK_EFFORT = 'xhigh';
54
64
  function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED_MODELS) {
55
65
  const selectedModel = modelsDefinition?.OPTIONS?.find((option) => option.value === model) || null;
56
66
  const allowedEfforts = selectedModel?.effort?.values
@@ -59,6 +69,27 @@ function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED
59
69
  ? effort
60
70
  : undefined;
61
71
  }
72
+ /**
73
+ * Writes the resolved effort choice onto the SDK options, expanding `ultracode` into the
74
+ * xhigh effort level plus the session-scoped settings it requires.
75
+ * @param {Object} sdkOptions - SDK options being built
76
+ * @param {string|undefined} resolvedEffort - Catalog-validated effort selection
77
+ */
78
+ function applyClaudeEffort(sdkOptions, resolvedEffort) {
79
+ if (!resolvedEffort) {
80
+ return;
81
+ }
82
+ if (resolvedEffort !== CLAUDE_ULTRACODE_EFFORT) {
83
+ sdkOptions.effort = resolvedEffort;
84
+ return;
85
+ }
86
+ sdkOptions.effort = ULTRACODE_SDK_EFFORT;
87
+ sdkOptions.settings = {
88
+ ...(sdkOptions.settings || {}),
89
+ ultracode: true,
90
+ enableWorkflows: true
91
+ };
92
+ }
62
93
  function createRequestId() {
63
94
  if (typeof crypto.randomUUID === 'function') {
64
95
  return crypto.randomUUID();
@@ -149,14 +180,19 @@ function matchesToolPermission(entry, toolName, input) {
149
180
  return false;
150
181
  }
151
182
  function mapCliOptionsToSDK(options = {}) {
152
- const { providerSessionId, cwd, toolsSettings, permissionMode, effort } = options;
183
+ const { providerSessionId, cwd, toolsSettings, permissionMode, effort, resumeAnchorId, resumeFromScratch } = options;
153
184
  const sdkOptions = {};
154
185
  // Forward all host env vars (e.g. ANTHROPIC_BASE_URL) to the subprocess.
155
186
  // Since SDK 0.2.113, options.env replaces process.env instead of overlaying it.
156
187
  sdkOptions.env = { ...process.env, CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: String(BG_WAIT_CEILING_MS) };
157
188
  // Resolve the executable eagerly on Windows because the SDK uses raw child_process.spawn,
158
189
  // which does not reliably follow npm's shell wrappers like cross-spawn does.
159
- sdkOptions.pathToClaudeCodeExecutable = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
190
+ // When nothing resolves the option stays unset on purpose: the SDK then falls back to the
191
+ // binary it ships, which beats handing it a bare `claude` that raw spawn can never launch.
192
+ const claudeExecutablePath = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
193
+ if (claudeExecutablePath) {
194
+ sdkOptions.pathToClaudeCodeExecutable = claudeExecutablePath;
195
+ }
160
196
  if (cwd) {
161
197
  sdkOptions.cwd = cwd;
162
198
  }
@@ -187,18 +223,25 @@ function mapCliOptionsToSDK(options = {}) {
187
223
  sdkOptions.tools = { type: 'preset', preset: 'claude_code' };
188
224
  sdkOptions.disallowedTools = settings.disallowedTools || [];
189
225
  sdkOptions.model = options.model || CLAUDE_PREDEFINED_MODELS.DEFAULT;
190
- const resolvedEffort = resolveClaudeEffort(sdkOptions.model, effort, options.effortModels || CLAUDE_PREDEFINED_MODELS);
191
- if (resolvedEffort) {
192
- sdkOptions.effort = resolvedEffort;
193
- }
226
+ applyClaudeEffort(sdkOptions, resolveClaudeEffort(sdkOptions.model, effort, options.effortModels || CLAUDE_PREDEFINED_MODELS));
194
227
  sdkOptions.systemPrompt = {
195
228
  type: 'preset',
196
229
  preset: 'claude_code'
197
230
  };
198
231
  sdkOptions.settingSources = ['project', 'user', 'local'];
199
232
  // The SDK resumes with the provider-native session id, never the app id.
200
- if (providerSessionId) {
233
+ // `resumeFromScratch` is set when the very first prompt of a conversation was
234
+ // edited: there is nothing before it to resume through, so the turn has to
235
+ // start the conversation over instead.
236
+ if (providerSessionId && !resumeFromScratch) {
201
237
  sdkOptions.resume = providerSessionId;
238
+ // Editing an already-sent message re-runs the conversation truncated just
239
+ // before it. `resumeSessionAt` is inclusive of the uuid it names, so the
240
+ // caller resolves the last row to KEEP and passes that — never the edited
241
+ // turn itself, which would leave the original prompt in context.
242
+ if (resumeAnchorId) {
243
+ sdkOptions.resumeSessionAt = resumeAnchorId;
244
+ }
202
245
  }
203
246
  return sdkOptions;
204
247
  }
@@ -227,6 +270,9 @@ function addSession(sessionId, queryInstance, writer = null, releaseInput = null
227
270
  console.error(`Error interrupting superseded run for session ${sessionId}:`, error?.message || error);
228
271
  });
229
272
  existing.releaseInput?.();
273
+ // Whatever the superseded process had outstanding dies with it and will
274
+ // never report, so the new run starts from an empty task set.
275
+ backgroundWork.clear(sessionId);
230
276
  }
231
277
  const carried = superseding ? null : existing;
232
278
  activeSessions.set(sessionId, {
@@ -237,6 +283,44 @@ function addSession(sessionId, queryInstance, writer = null, releaseInput = null
237
283
  // Re-registered mid-run once the provider session id lands; keep the closer.
238
284
  releaseInput: releaseInput || carried?.releaseInput || null
239
285
  });
286
+ // The history reader reports a background agent as running or stopped by
287
+ // whether this entry exists, and the cached history does not see this map.
288
+ sessionHistoryCache.invalidate(sessionId);
289
+ }
290
+ /**
291
+ * Lets go of the process an earlier run left holding a session open, so the
292
+ * next turn can take the session over.
293
+ *
294
+ * Closing stdin is not enough on its own: the CLI's wind-down waits for the
295
+ * background agents the process still has going — for up to the ceiling it
296
+ * was started with (BG_WAIT_CEILING_MS) — so an abandoned process with an
297
+ * agent outstanding would run next to the process that replaced it for as
298
+ * long as the agent does, both appending to the same session file. Stop the
299
+ * tasks first, while stdin is still open: the SDK drops control requests
300
+ * written after stdin has ended, which is also why the interrupt a later
301
+ * supersede sends to this process cannot do the job.
302
+ *
303
+ * The stops are not awaited — each request is written to the CLI before this
304
+ * returns, and a reply is only missing when the process has already gone.
305
+ * @param {string} sessionId - Session identifier
306
+ */
307
+ function releaseHeldSession(sessionId) {
308
+ const held = getSession(sessionId);
309
+ if (!held) {
310
+ return;
311
+ }
312
+ for (const task of backgroundWork.outstanding(sessionId)) {
313
+ // A stop that cannot be delivered changes nothing about what happens
314
+ // next: the process is on its way out either way. Guard the synchronous
315
+ // call too, so a failing stop can never skip the release below.
316
+ try {
317
+ Promise.resolve(held.instance?.stopTask?.(task.taskId)).catch(() => { });
318
+ }
319
+ catch {
320
+ // Same as a rejected stop.
321
+ }
322
+ }
323
+ held.releaseInput?.();
240
324
  }
241
325
  /**
242
326
  * Removes a session from the active sessions map
@@ -244,6 +328,11 @@ function addSession(sessionId, queryInstance, writer = null, releaseInput = null
244
328
  */
245
329
  function removeSession(sessionId) {
246
330
  activeSessions.delete(sessionId);
331
+ // No process, no background work: anything still tracked was killed with it.
332
+ backgroundWork.clear(sessionId);
333
+ // See addSession: a page cached while the process was up still says
334
+ // `running` for any agent that never reported back.
335
+ sessionHistoryCache.invalidate(sessionId);
247
336
  }
248
337
  /**
249
338
  * Gets a session from the active sessions map
@@ -275,44 +364,118 @@ function transformMessage(sdkMessage) {
275
364
  }
276
365
  return sdkMessage;
277
366
  }
367
+ /**
368
+ * True for the user bubble the SDK echoes for a subagent's own prompt.
369
+ *
370
+ * Subagent traffic carries `parent_tool_use_id`, so this echo lands in the main
371
+ * thread and stacks a second copy of the prompt right below the Agent tool card
372
+ * that already displays it. It also disappears on reload, because the transcript
373
+ * keeps that turn in the subagent's sidechain rather than the session file.
374
+ * @param {Object} message - Normalized message about to be sent to the client
375
+ * @returns {boolean}
376
+ */
377
+ export function isSubagentPromptEcho(message) {
378
+ return Boolean(message?.parentToolUseId) && message.role === 'user' && message.kind === 'text';
379
+ }
278
380
  function readNumber(value) {
279
381
  const parsed = Number(value);
280
382
  return Number.isFinite(parsed) ? parsed : 0;
281
383
  }
282
384
  /**
283
- * Extracts token usage from SDK messages.
284
- * Prefers per-step `message.usage` (Claude message payload), then falls back
285
- * to result-level usage/modelUsage for compatibility across SDK versions.
385
+ * @typedef {Object} TokenBudget
386
+ * @property {number} used
387
+ * @property {number} total
388
+ * @property {number} inputTokens
389
+ * @property {number} outputTokens
390
+ * @property {number} [cacheReadTokens]
391
+ * @property {number} [cacheCreationTokens]
392
+ * @property {number} [cacheTokens]
393
+ * @property {{ input: number, output: number }} breakdown
394
+ */
395
+ /**
396
+ * Builds a context-window budget from an Anthropic-shaped usage payload.
397
+ *
398
+ * `input_tokens + cache_read + cache_creation` is one request's whole prompt,
399
+ * which is exactly what the context window holds at that moment.
400
+ * @param {Object} messageUsage - Anthropic usage payload
401
+ * @returns {TokenBudget} Token budget object
402
+ */
403
+ function buildTokenBudget(messageUsage) {
404
+ const directInputTokens = readNumber(messageUsage.input_tokens ?? messageUsage.inputTokens);
405
+ const cacheCreationTokens = readNumber(messageUsage.cache_creation_input_tokens ?? messageUsage.cacheCreationInputTokens ?? messageUsage.cacheCreationTokens);
406
+ const cacheReadTokens = readNumber(messageUsage.cache_read_input_tokens ?? messageUsage.cacheReadInputTokens ?? messageUsage.cacheReadTokens);
407
+ const cacheTokens = cacheCreationTokens + cacheReadTokens;
408
+ const inputTokens = directInputTokens + cacheTokens;
409
+ const outputTokens = readNumber(messageUsage.output_tokens ?? messageUsage.outputTokens);
410
+ const contextWindow = parseInt(process.env.CONTEXT_WINDOW, 10) || 160000;
411
+ return {
412
+ used: inputTokens + outputTokens,
413
+ total: contextWindow,
414
+ inputTokens,
415
+ outputTokens,
416
+ cacheReadTokens,
417
+ cacheCreationTokens,
418
+ cacheTokens,
419
+ breakdown: {
420
+ input: inputTokens,
421
+ output: outputTokens,
422
+ },
423
+ };
424
+ }
425
+ /**
426
+ * Extracts the session's context-window usage from an SDK stream message.
427
+ *
428
+ * Only assistant messages describe the context window: each one reports the
429
+ * prompt its own request carried. The turn-ending `result` is deliberately not
430
+ * a source here — see `extractCumulativeTokenBudget`.
286
431
  * @param {Object} sdkMessage - SDK stream message
287
- * @returns {Object|null} Token budget object or null
432
+ * @returns {TokenBudget|null} Token budget object or null
288
433
  */
289
434
  function extractTokenBudget(sdkMessage) {
290
435
  if (!sdkMessage || typeof sdkMessage !== 'object') {
291
436
  return null;
292
437
  }
293
- const messageUsage = sdkMessage.message?.usage || sdkMessage.usage;
294
- if (messageUsage && typeof messageUsage === 'object') {
295
- const directInputTokens = readNumber(messageUsage.input_tokens ?? messageUsage.inputTokens);
296
- const cacheCreationTokens = readNumber(messageUsage.cache_creation_input_tokens ?? messageUsage.cacheCreationInputTokens ?? messageUsage.cacheCreationTokens);
297
- const cacheReadTokens = readNumber(messageUsage.cache_read_input_tokens ?? messageUsage.cacheReadInputTokens ?? messageUsage.cacheReadTokens);
298
- const cacheTokens = cacheCreationTokens + cacheReadTokens;
299
- const inputTokens = directInputTokens + cacheTokens;
300
- const outputTokens = readNumber(messageUsage.output_tokens ?? messageUsage.outputTokens);
301
- const totalUsed = inputTokens + outputTokens;
302
- const contextWindow = parseInt(process.env.CONTEXT_WINDOW, 10) || 160000;
303
- return {
304
- used: totalUsed,
305
- total: contextWindow,
306
- inputTokens,
307
- outputTokens,
308
- cacheReadTokens,
309
- cacheCreationTokens,
310
- cacheTokens,
311
- breakdown: {
312
- input: inputTokens,
313
- output: outputTokens,
314
- },
315
- };
438
+ // Subagent traffic (parent_tool_use_id set) reports the subagent's own
439
+ // context window, not this session's — surfacing it makes the counter drop
440
+ // to the subagent's number and bounce back on the next main-thread event.
441
+ if (sdkMessage.parent_tool_use_id) {
442
+ return null;
443
+ }
444
+ // Only assistant messages carry Anthropic-shaped usage. System
445
+ // task_progress/task_notification events have a top-level `usage` too, but
446
+ // shaped {total_tokens, tool_uses, duration_ms} — reading Anthropic keys
447
+ // off it yields an all-zero budget that flashes "0" in the composer.
448
+ if (sdkMessage.type !== 'assistant') {
449
+ return null;
450
+ }
451
+ const messageUsage = sdkMessage.message?.usage;
452
+ if (!messageUsage || typeof messageUsage !== 'object') {
453
+ return null;
454
+ }
455
+ return buildTokenBudget(messageUsage);
456
+ }
457
+ /**
458
+ * Last-resort budget read from a turn's `result` message.
459
+ *
460
+ * `result.usage` and `result.modelUsage` are the turn's *bill*: every request
461
+ * the turn made, summed, including each subagent's. A turn that made four
462
+ * requests therefore reports roughly four times the context the conversation
463
+ * actually holds, so publishing it made the counter leap at the end of a turn
464
+ * and fall back on the next assistant message — worst with subagents running,
465
+ * whose requests inflate the sum without ever entering this session's context.
466
+ *
467
+ * It is still the only usage an SDK build that reports none per assistant
468
+ * message ever emits, so it stays available for the caller to use when a turn
469
+ * produced no assistant budget at all.
470
+ * @param {Object} sdkMessage - SDK stream message
471
+ * @returns {TokenBudget|null} Token budget object or null
472
+ */
473
+ function extractCumulativeTokenBudget(sdkMessage) {
474
+ if (!sdkMessage || typeof sdkMessage !== 'object' || sdkMessage.type !== 'result') {
475
+ return null;
476
+ }
477
+ if (sdkMessage.usage && typeof sdkMessage.usage === 'object') {
478
+ return buildTokenBudget(sdkMessage.usage);
316
479
  }
317
480
  if (!sdkMessage.modelUsage || typeof sdkMessage.modelUsage !== 'object') {
318
481
  return null;
@@ -338,19 +501,26 @@ function extractTokenBudget(sdkMessage) {
338
501
  },
339
502
  };
340
503
  }
341
- // Tool calls that leave work running past the end of a turn. Bash only counts
342
- // when it is explicitly backgrounded; the rest defer or watch work by nature.
343
- const DEFERRED_WORK_TOOLS = new Set(['Monitor', 'ScheduleWakeup', 'CronCreate', 'TaskCreate']);
504
+ // Tool calls that leave work running past the end of a turn. Bash and Agent only
505
+ // count when they are backgrounded; the rest defer or watch work by nature.
506
+ // Workflow belongs here rather than in a branch of its own: its input schema has
507
+ // no foreground option at all, so every call returns a task id immediately and
508
+ // reports back in a later turn.
509
+ const DEFERRED_WORK_TOOLS = new Set(['Monitor', 'ScheduleWakeup', 'CronCreate', 'TaskCreate', 'Workflow']);
344
510
  /**
345
511
  * Detects tool calls that keep working after the turn's `result` arrives.
346
512
  *
347
513
  * Only turns that start background work need their CLI process held open; every
348
514
  * other turn can let it exit immediately, as it did before the hold existed.
349
515
  *
516
+ * Used by the providers module's tests, which pin the tool matching directly:
517
+ * the alternative is driving a whole SDK run to observe whether stdin was held,
518
+ * and the cost of getting this wrong is silently killed background work.
519
+ *
350
520
  * @param {Object} sdkMessage - SDK stream message
351
521
  * @returns {boolean} True when the message launches work that outlives the turn
352
522
  */
353
- function startsBackgroundWork(sdkMessage) {
523
+ export function startsBackgroundWork(sdkMessage) {
354
524
  const content = sdkMessage?.message?.content;
355
525
  if (!Array.isArray(content)) {
356
526
  return false;
@@ -362,9 +532,152 @@ function startsBackgroundWork(sdkMessage) {
362
532
  if (block.name === 'Bash') {
363
533
  return block.input?.run_in_background === true;
364
534
  }
535
+ // A backgrounded subagent outlives the turn exactly like a backgrounded
536
+ // Bash does, so the process has to be held open for it to report back.
537
+ // Agents background by default — `run_in_background` is optional and only
538
+ // an explicit `false` opts out — hence `!== false` rather than `=== true`.
539
+ // A foreground agent must stay out of DEFERRED_WORK_TOOLS: it never pushes
540
+ // a follow-up turn, so it would pin the process for the full ceiling.
541
+ if (block.name === 'Agent') {
542
+ return block.input?.run_in_background !== false;
543
+ }
365
544
  return DEFERRED_WORK_TOOLS.has(block.name);
366
545
  });
367
546
  }
547
+ // `task_updated` patch statuses after which a task is gone for good. `pending`,
548
+ // `running` and `paused` are still outstanding; `killed` is what the CLI
549
+ // writes when it stops a task itself (the task notification spells it
550
+ // `stopped`).
551
+ const TERMINAL_TASK_STATUSES = new Set(['completed', 'failed', 'killed']);
552
+ /**
553
+ * Tracks the background tasks each live session still has outstanding, folded
554
+ * from the `system` task events the SDK stream already carries.
555
+ *
556
+ * `startsBackgroundWork` above only knows that a turn *launched* something
557
+ * lasting; this knows what is still running and which task ids it answers to,
558
+ * which is what the running-sessions list and a stop request need once the
559
+ * turn's `result` has gone out and nothing else remembers the session is busy.
560
+ *
561
+ * Verified against a real query (SDK 0.3.165): `task_started` carries
562
+ * `task_id`, `tool_use_id`, `task_type` and `description` for every agent,
563
+ * workflow and backgrounded command (a foreground Bash emits nothing);
564
+ * `task_notification` settles a task with any status; `task_updated` carries
565
+ * only `task_id` and a patch, and is terminal when the patch's `status` is.
566
+ * Housekeeping tasks the CLI starts on its own have no `tool_use_id` and are
567
+ * not tracked — nothing in the transcript could show them.
568
+ *
569
+ * Exported so the folding can be driven with the four event shapes directly;
570
+ * the runtime keeps one instance keyed like `activeSessions`.
571
+ *
572
+ * @returns {{
573
+ * apply: (sessionKey: string, message: Object) => void,
574
+ * hasOutstanding: (sessionKey: string) => boolean,
575
+ * has: (sessionKey: string, taskId: string) => boolean,
576
+ * outstanding: (sessionKey: string) => Array<import('../../../../shared/types.js').BackgroundTaskSummary>,
577
+ * clear: (sessionKey: string) => void,
578
+ * list: () => Array<{ sessionId: string, tasks: Array<import('../../../../shared/types.js').BackgroundTaskSummary> }>
579
+ * }}
580
+ */
581
+ export function createBackgroundWorkTracker() {
582
+ /** @type {Map<string, Map<string, import('../../../../shared/types.js').BackgroundTaskSummary>>} */
583
+ const sessions = new Map();
584
+ /**
585
+ * Tool-use ids the session's own turns issued. A task started for a call an
586
+ * agent made inside its own transcript — a workflow agent's backgrounded
587
+ * command, say — reaches this stream too, and nothing in the parent
588
+ * transcript could show it; it is kept for stopping but flagged `nested`.
589
+ * @type {Map<string, Set<string>>}
590
+ */
591
+ const ownToolUseIds = new Map();
592
+ const remove = (sessionKey, taskId) => {
593
+ const tasks = sessions.get(sessionKey);
594
+ if (!tasks) {
595
+ return;
596
+ }
597
+ tasks.delete(taskId);
598
+ if (tasks.size === 0) {
599
+ sessions.delete(sessionKey);
600
+ }
601
+ };
602
+ return {
603
+ apply(sessionKey, message) {
604
+ if (message?.type === 'assistant' && !message.parent_tool_use_id) {
605
+ const content = message.message?.content;
606
+ if (Array.isArray(content)) {
607
+ for (const block of content) {
608
+ if (block?.type === 'tool_use' && typeof block.id === 'string') {
609
+ let ids = ownToolUseIds.get(sessionKey);
610
+ if (!ids) {
611
+ ids = new Set();
612
+ ownToolUseIds.set(sessionKey, ids);
613
+ }
614
+ ids.add(block.id);
615
+ }
616
+ }
617
+ }
618
+ return;
619
+ }
620
+ if (message?.type !== 'system' || typeof message.task_id !== 'string') {
621
+ return;
622
+ }
623
+ switch (message.subtype) {
624
+ case 'task_started': {
625
+ if (typeof message.tool_use_id !== 'string') {
626
+ return;
627
+ }
628
+ const task = {
629
+ taskId: message.task_id,
630
+ toolUseId: message.tool_use_id,
631
+ taskType: message.task_type,
632
+ description: message.description,
633
+ startedAt: Date.now()
634
+ };
635
+ if (typeof message.workflow_name === 'string') {
636
+ task.workflowName = message.workflow_name;
637
+ }
638
+ if (!ownToolUseIds.get(sessionKey)?.has(message.tool_use_id)) {
639
+ task.nested = true;
640
+ }
641
+ let tasks = sessions.get(sessionKey);
642
+ if (!tasks) {
643
+ tasks = new Map();
644
+ sessions.set(sessionKey, tasks);
645
+ }
646
+ tasks.set(task.taskId, task);
647
+ return;
648
+ }
649
+ case 'task_notification':
650
+ remove(sessionKey, message.task_id);
651
+ return;
652
+ case 'task_updated':
653
+ if (TERMINAL_TASK_STATUSES.has(message.patch?.status)) {
654
+ remove(sessionKey, message.task_id);
655
+ }
656
+ return;
657
+ default:
658
+ }
659
+ },
660
+ hasOutstanding(sessionKey) {
661
+ return sessions.has(sessionKey);
662
+ },
663
+ has(sessionKey, taskId) {
664
+ return Boolean(sessions.get(sessionKey)?.has(taskId));
665
+ },
666
+ outstanding(sessionKey) {
667
+ return Array.from(sessions.get(sessionKey)?.values() ?? []);
668
+ },
669
+ clear(sessionKey) {
670
+ sessions.delete(sessionKey);
671
+ ownToolUseIds.delete(sessionKey);
672
+ },
673
+ list() {
674
+ return Array.from(sessions, ([sessionId, tasks]) => ({
675
+ sessionId,
676
+ tasks: Array.from(tasks.values())
677
+ }));
678
+ }
679
+ };
680
+ }
368
681
  /**
369
682
  * Builds the SDK user messages for one turn.
370
683
  *
@@ -509,13 +822,22 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
509
822
  // Set when a turn starts background work, cleared when the next `result`
510
823
  // arrives — only turns with work still outstanding hold their process open.
511
824
  let backgroundWorkPending = false;
825
+ // Set when the stream reports a task starting during this turn. Task events
826
+ // are the exact word on what is still running, so when the turn produced
827
+ // any, the tracker decides the hold; `startsBackgroundWork` is the fallback
828
+ // for tools that emit none (Monitor, ScheduleWakeup, CronCreate, TaskCreate)
829
+ // and for an SDK that does not report tasks at all.
830
+ let sawTaskEventThisTurn = false;
512
831
  // True while the process is being held open for background work, so a later
513
832
  // `result` can be recognised as that work reporting back.
514
833
  let heldForBackgroundWork = false;
834
+ // Set once a turn publishes a budget read from an assistant message, so the
835
+ // turn-ending `result` is only mined for usage when nothing better arrived.
836
+ let assistantBudgetSent = false;
515
837
  // A new turn supersedes any earlier one still holding this session's process
516
838
  // open, so held runs cannot stack up across a conversation.
517
839
  if (sessionKey()) {
518
- getSession(sessionKey())?.releaseInput?.();
840
+ releaseHeldSession(sessionKey());
519
841
  }
520
842
  // Arms (or re-arms) the idle countdown that eventually closes stdin.
521
843
  const scheduleRelease = () => {
@@ -630,6 +952,12 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
630
952
  if (decision.cancelled) {
631
953
  return { behavior: 'deny', message: 'Permission request cancelled' };
632
954
  }
955
+ // A client answered. Announce it on the run stream so the replay buffer
956
+ // and every other attached tab drop the prompt — resolving happens over
957
+ // the inbound socket only, so without this a mid-run page refresh
958
+ // replays the `permission_request` with nothing to retract it and the
959
+ // already-answered prompt resurrects.
960
+ ws.send(createNormalizedMessage({ kind: 'permission_resolved', requestId, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
633
961
  if (decision.allow) {
634
962
  if (decision.rememberEntry && typeof decision.rememberEntry === 'string') {
635
963
  if (!sdkOptions.allowedTools.includes(decision.rememberEntry)) {
@@ -643,10 +971,13 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
643
971
  }
644
972
  return { behavior: 'deny', message: decision.message ?? 'User denied tool use' };
645
973
  };
974
+ // The SDK's own `query`, unless the caller supplies one (tests script the
975
+ // stream to drive the hold logic below without a CLI process).
976
+ const createQuery = context.createQuery ?? query;
646
977
  let heldPrompt = createHeldPromptStream(promptMessages);
647
978
  releasePromptStream = heldPrompt.release;
648
979
  try {
649
- queryInstance = query({
980
+ queryInstance = createQuery({
650
981
  prompt: heldPrompt.stream,
651
982
  options: sdkOptions
652
983
  });
@@ -660,7 +991,7 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
660
991
  heldPrompt.release();
661
992
  heldPrompt = createHeldPromptStream(promptMessages);
662
993
  releasePromptStream = heldPrompt.release;
663
- queryInstance = query({
994
+ queryInstance = createQuery({
664
995
  prompt: heldPrompt.stream,
665
996
  options: sdkOptions
666
997
  });
@@ -699,19 +1030,47 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
699
1030
  if (transformedMessage.parentToolUseId && !msg.parentToolUseId) {
700
1031
  msg.parentToolUseId = transformedMessage.parentToolUseId;
701
1032
  }
1033
+ if (isSubagentPromptEcho(msg)) {
1034
+ continue;
1035
+ }
702
1036
  ws.send(msg);
703
1037
  }
704
- // Extract and send token budget updates from assistant/result usage payloads
705
- const tokenBudgetData = extractTokenBudget(message);
1038
+ // Extract and send token budget updates from assistant usage payloads,
1039
+ // falling back to the turn's cumulative bill only for SDK builds that
1040
+ // report no per-assistant usage at all.
1041
+ const tokenBudgetData = extractTokenBudget(message)
1042
+ || (assistantBudgetSent ? null : extractCumulativeTokenBudget(message));
706
1043
  if (tokenBudgetData) {
1044
+ if (message.type === 'assistant') {
1045
+ assistantBudgetSent = true;
1046
+ }
707
1047
  ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
708
1048
  }
709
1049
  if (startsBackgroundWork(message)) {
710
1050
  backgroundWorkPending = true;
711
1051
  }
1052
+ if (message.type === 'system' && message.subtype === 'task_started') {
1053
+ sawTaskEventThisTurn = true;
1054
+ }
1055
+ backgroundWork.apply(sessionKey(), message);
1056
+ // A task the user stopped gets no follow-up turn from the CLI — only its
1057
+ // `stopped` notification — so when that was the last outstanding task
1058
+ // nothing will ever push the `result` the release below waits for, and
1059
+ // the process would sit until the idle ceiling. Release it here. A
1060
+ // completed task is different: the CLI relays its result in a turn of
1061
+ // its own, which closing stdin now would cut short.
1062
+ if (heldForBackgroundWork
1063
+ && message.type === 'system'
1064
+ && message.subtype === 'task_notification'
1065
+ && message.status === 'stopped'
1066
+ && !backgroundWork.hasOutstanding(sessionKey())) {
1067
+ heldForBackgroundWork = false;
1068
+ releasePromptStream();
1069
+ }
712
1070
  if (message.type === 'result') {
713
1071
  // The turn is done as far as the client is concerned.
714
1072
  const abortPending = sessionKey() ? abortedSessionIds.has(sessionKey()) : false;
1073
+ const stillOutstanding = backgroundWork.hasOutstanding(sessionKey());
715
1074
  if (!turnCompleteSent && !abortPending) {
716
1075
  turnCompleteSent = true;
717
1076
  ws.send(createCompleteMessage({ provider: 'claude', sessionId: capturedSessionId || sessionId || null, exitCode: 0 }));
@@ -723,9 +1082,10 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
723
1082
  stopReason: 'completed'
724
1083
  });
725
1084
  }
726
- else if (heldForBackgroundWork && !abortPending) {
1085
+ else if (heldForBackgroundWork && !abortPending && !stillOutstanding) {
727
1086
  // A result after the turn already reported complete means the work we
728
- // held the process open for has finished and pushed a follow-up turn.
1087
+ // held the process open for has finished and pushed a follow-up turn
1088
+ // — the last of it, when nothing else is still running.
729
1089
  notifyBackgroundWorkCompleted({
730
1090
  userId: ws?.userId || null,
731
1091
  provider: 'claude',
@@ -733,11 +1093,26 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
733
1093
  sessionName: sessionSummary
734
1094
  });
735
1095
  }
736
- if (backgroundWorkPending) {
737
- // Work started during this turn is still running. Hold the process
738
- // open so it can finish and report back in a follow-up turn; the
739
- // ceiling is only a backstop for work that never reports.
740
- backgroundWorkPending = false;
1096
+ // Work started during this turn, or work from an earlier turn that
1097
+ // has not settled yet (a follow-up turn reports one task in while
1098
+ // another is still going), is still running. Hold the process open
1099
+ // so it can finish and report back in a follow-up turn; the ceiling
1100
+ // is only a backstop for work that never reports.
1101
+ //
1102
+ // The release when the last task settles is this same branch on the
1103
+ // follow-up turn the CLI pushes for it, not the settling event
1104
+ // itself: closing stdin at that moment would cut the turn that
1105
+ // relays the task's result.
1106
+ //
1107
+ // When the turn reported its tasks, the tracker is the whole truth: an
1108
+ // Agent call without `run_in_background` is scored as background by
1109
+ // `startsBackgroundWork`, but the CLI runs it in the foreground and
1110
+ // it has settled before this `result` — holding for it kept a process
1111
+ // alive for the full ceiling with nothing outstanding.
1112
+ const holdForTurn = sawTaskEventThisTurn ? stillOutstanding : backgroundWorkPending || stillOutstanding;
1113
+ backgroundWorkPending = false;
1114
+ sawTaskEventThisTurn = false;
1115
+ if (holdForTurn) {
741
1116
  heldForBackgroundWork = true;
742
1117
  scheduleRelease();
743
1118
  }
@@ -863,6 +1238,34 @@ async function abortClaudeSDKSession(sessionId) {
863
1238
  return false;
864
1239
  }
865
1240
  }
1241
+ /**
1242
+ * Sessions whose background tasks are still outstanding, with the tasks.
1243
+ *
1244
+ * A session stays here after its turn's `result` for as long as the process
1245
+ * is held open for the work — which is exactly the window in which nothing
1246
+ * else (the chat run registry marks the run completed at `result`) knows the
1247
+ * session is still busy.
1248
+ * @returns {Array<{ sessionId: string, tasks: Array<import('../../../../shared/types.js').BackgroundTaskSummary> }>}
1249
+ */
1250
+ function listClaudeSDKBackgroundWork() {
1251
+ return backgroundWork.list();
1252
+ }
1253
+ /**
1254
+ * Stops one outstanding background task through the SDK, which then emits a
1255
+ * `task_notification` with status `stopped` — the same event that drops the
1256
+ * task from the tracker and settles its card.
1257
+ * @param {string} sessionId - Session identifier
1258
+ * @param {string} taskId - The task's `task_id` as reported on `task_started`
1259
+ * @returns {Promise<boolean>} False when no live process is tracking the task
1260
+ */
1261
+ async function stopClaudeSDKTask(sessionId, taskId) {
1262
+ const session = getSession(sessionId);
1263
+ if (!session || !backgroundWork.has(sessionId, taskId)) {
1264
+ return false;
1265
+ }
1266
+ await session.instance.stopTask(taskId);
1267
+ return true;
1268
+ }
866
1269
  /**
867
1270
  * Checks if an SDK session is currently active
868
1271
  * @param {string} sessionId - Session identifier
@@ -870,7 +1273,21 @@ async function abortClaudeSDKSession(sessionId) {
870
1273
  */
871
1274
  function isClaudeSDKSessionActive(sessionId) {
872
1275
  const session = getSession(sessionId);
873
- return session && session.status === 'active';
1276
+ return Boolean(session && session.status === 'active');
1277
+ }
1278
+ /**
1279
+ * When the run behind a session started, or null when no run is up.
1280
+ *
1281
+ * The history reader uses this to tell a background agent launched by the
1282
+ * live process (still able to report back) from one launched by an earlier
1283
+ * process that has since exited (never will): a launch row older than the
1284
+ * live run cannot belong to it.
1285
+ * @param {string} sessionId - Session identifier
1286
+ * @returns {number|null} Epoch milliseconds the live run started, or null
1287
+ */
1288
+ function getClaudeSDKSessionStartTime(sessionId) {
1289
+ const session = getSession(sessionId);
1290
+ return session && session.status === 'active' ? session.startTime : null;
874
1291
  }
875
1292
  /**
876
1293
  * Gets all active SDK session IDs
@@ -922,7 +1339,9 @@ export const claudeRuntime = {
922
1339
  resolve: resolveToolApproval,
923
1340
  listPending: getPendingApprovalsForSession,
924
1341
  },
1342
+ listBackgroundWork: listClaudeSDKBackgroundWork,
1343
+ stopBackgroundTask: stopClaudeSDKTask,
925
1344
  };
926
1345
  // Export public API
927
- export { queryClaudeSDK, abortClaudeSDKSession, isClaudeSDKSessionActive, getActiveClaudeSDKSessions, resolveToolApproval, getPendingApprovalsForSession, reconnectSessionWriter };
1346
+ export { queryClaudeSDK, abortClaudeSDKSession, listClaudeSDKBackgroundWork, stopClaudeSDKTask, isClaudeSDKSessionActive, getClaudeSDKSessionStartTime, getActiveClaudeSDKSessions, resolveToolApproval, getPendingApprovalsForSession, reconnectSessionWriter, extractTokenBudget, extractCumulativeTokenBudget };
928
1347
  //# sourceMappingURL=claude-runtime.provider.js.map