@cloudcli-ai/cloudcli 1.37.2 → 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 (328) hide show
  1. package/README.md +269 -0
  2. package/dist/assets/{abnfDiagram-N423BO3Z-C4UAoHJb.js → abnfDiagram-N423BO3Z-CJntskwA.js} +1 -1
  3. package/dist/assets/{arc-B8jF-ja9.js → arc-CIfH4uMN.js} +1 -1
  4. package/dist/assets/{architectureDiagram-T3A2C74G-C_Wur_CU.js → architectureDiagram-T3A2C74G-_IpEkukg.js} +1 -1
  5. package/dist/assets/{blockDiagram-VBNYF7ZC-Cq4P1i8t.js → blockDiagram-VBNYF7ZC-BttxrpgZ.js} +1 -1
  6. package/dist/assets/{c4Diagram-5PPSVZJV-C1wG4eUj.js → c4Diagram-5PPSVZJV-g51dSRr1.js} +1 -1
  7. package/dist/assets/channel-C5xU9jMw.js +1 -0
  8. package/dist/assets/{chunk-2GRJ4B5K-B8fnBG6E.js → chunk-2GRJ4B5K-D_-LbQUa.js} +1 -1
  9. package/dist/assets/{chunk-2Q5K7J3B-WdfPooPJ.js → chunk-2Q5K7J3B-BtlHzwaO.js} +1 -1
  10. package/dist/assets/{chunk-5RXB4S5H-DhZJuG2E.js → chunk-5RXB4S5H-UzkpvpAT.js} +1 -1
  11. package/dist/assets/{chunk-5VM5RSS4-B0rjslal.js → chunk-5VM5RSS4-BKwx5Gag.js} +1 -1
  12. package/dist/assets/{chunk-6Q2QTUOP-CG2Oj4jL.js → chunk-6Q2QTUOP-D_KEBH5f.js} +1 -1
  13. package/dist/assets/{chunk-GF5L2VYU-rrl2lRxY.js → chunk-GF5L2VYU-rK2DGyto.js} +1 -1
  14. package/dist/assets/{chunk-JWPE2WC7-CjVbrVMA.js → chunk-JWPE2WC7-CeBl_N9i.js} +1 -1
  15. package/dist/assets/{chunk-KBJHAD2P-CRzKgU8S.js → chunk-KBJHAD2P-BiQLWVIr.js} +1 -1
  16. package/dist/assets/{chunk-RYQCIY6F-DBz4GTZF.js → chunk-RYQCIY6F-C1knVGV5.js} +1 -1
  17. package/dist/assets/{chunk-XXDRQBXY-wHxalsXk.js → chunk-XXDRQBXY-CtKbJkvF.js} +1 -1
  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-s1hygY8e.js → cose-bilkent-JH36ORCC-DjX8y40Z.js} +1 -1
  21. package/dist/assets/{cynefin-VYW2F7L2-C4LGV15i.js → cynefin-VYW2F7L2-DM6YeRba.js} +1 -1
  22. package/dist/assets/{cynefinDiagram-MW4NZA55-C5uhu7B3.js → cynefinDiagram-MW4NZA55-wZJ-vy5B.js} +1 -1
  23. package/dist/assets/{dagre-VZM6K2ZE-B-OBTewm.js → dagre-VZM6K2ZE-BzP0ejkH.js} +1 -1
  24. package/dist/assets/{diagram-7IWD3JNH-BjGKQxih.js → diagram-7IWD3JNH-BTL_j23J.js} +1 -1
  25. package/dist/assets/{diagram-B4RE2ZJO-BMKz1X2L.js → diagram-B4RE2ZJO-Cf1Norgu.js} +1 -1
  26. package/dist/assets/{diagram-LBJQPF4R-DNh-GhKC.js → diagram-LBJQPF4R-DPiUEKr-.js} +1 -1
  27. package/dist/assets/{diagram-Q27KOJAE-BaTG9jPT.js → diagram-Q27KOJAE-CTcTpiz5.js} +1 -1
  28. package/dist/assets/{diagram-UB23O5K3-BUwo2OvJ.js → diagram-UB23O5K3-DPMSdYm0.js} +1 -1
  29. package/dist/assets/{ebnfDiagram-BXEA7PRR-DAZgfzX2.js → ebnfDiagram-BXEA7PRR-7yvD6uiF.js} +1 -1
  30. package/dist/assets/{erDiagram-JOGREHBK-1w1CcGEb.js → erDiagram-JOGREHBK-CfgeDoV6.js} +1 -1
  31. package/dist/assets/{flowDiagram-UKHOOZJN-D4EII8Jj.js → flowDiagram-UKHOOZJN-B27ng5zu.js} +1 -1
  32. package/dist/assets/{ganttDiagram-PKOTCBZU-ChvJZAgB.js → ganttDiagram-PKOTCBZU-CcC6b_7t.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DtOn41S8.js → gitGraphDiagram-DS77QQ5N-BX5kPGrO.js} +1 -1
  34. package/dist/assets/index-BTDHQ6Id.css +32 -0
  35. package/dist/assets/index-sFfz8iBR.js +4931 -0
  36. package/dist/assets/{infoDiagram-6WML65LV-DN_PVEJZ.js → infoDiagram-6WML65LV-CUP-SlN4.js} +1 -1
  37. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DbkF1kXb.js → ishikawaDiagram-WSZJBQD7-BTTDhOpX.js} +1 -1
  38. package/dist/assets/{journeyDiagram-NVQOT4AX-pJwmhddE.js → journeyDiagram-NVQOT4AX-ARXcc61m.js} +1 -1
  39. package/dist/assets/{kanban-definition-27J2QSJJ-PPAPgNp5.js → kanban-definition-27J2QSJJ-BieMpqMA.js} +1 -1
  40. package/dist/assets/{linear-iPR-J9f7.js → linear-61vxjRnP.js} +1 -1
  41. package/dist/assets/{mermaid.core-BEUL86Z6.js → mermaid.core-nhnwkfQx.js} +5 -5
  42. package/dist/assets/{mindmap-definition-FAOFIHXS-BvZoBib7.js → mindmap-definition-FAOFIHXS-Cm6x5vT1.js} +1 -1
  43. package/dist/assets/{pegDiagram-VL7TDLO6-DhYreaUd.js → pegDiagram-VL7TDLO6-CcYBq_9H.js} +1 -1
  44. package/dist/assets/{pieDiagram-7S7Q4E2Y-CrOHlyD0.js → pieDiagram-7S7Q4E2Y-CfvIjfzo.js} +1 -1
  45. package/dist/assets/{quadrantDiagram-CIZ2JOQS-D4g5yH3t.js → quadrantDiagram-CIZ2JOQS-SgYoWPNb.js} +1 -1
  46. package/dist/assets/{railroadDiagram-AXF67PYL-Dj39lHlf.js → railroadDiagram-AXF67PYL-D2Mu5E_f.js} +1 -1
  47. package/dist/assets/{requirementDiagram-LRYGKXZP-1AS4Zgm_.js → requirementDiagram-LRYGKXZP-Azwe_k4Q.js} +1 -1
  48. package/dist/assets/{sankeyDiagram-W5VNT64P-CKlMn19P.js → sankeyDiagram-W5VNT64P-DN1Ju3PA.js} +1 -1
  49. package/dist/assets/{sequenceDiagram-SI44F4Z6-B_j3qyvo.js → sequenceDiagram-SI44F4Z6-Bj4qC34S.js} +1 -1
  50. package/dist/assets/server.browser-CtX3Hdl5.js +27 -0
  51. package/dist/assets/{sizeCapture-X5ZJPWSS-Bp2Op16v.js → sizeCapture-X5ZJPWSS-Co4PdklG.js} +1 -1
  52. package/dist/assets/{stateDiagram-OKZ733FA-C0cN6DoY.js → stateDiagram-OKZ733FA-nOvL9dRC.js} +1 -1
  53. package/dist/assets/stateDiagram-v2-UEYNNEHI-DNv8ppw7.js +1 -0
  54. package/dist/assets/{swimlanes-SLNWSIFB-BEvXOV9b.js → swimlanes-SLNWSIFB-CCK3wMid.js} +2 -2
  55. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DnfbNQEp.js +8 -0
  56. package/dist/assets/{timeline-definition-Z64GVDOM-zdJQinvZ.js → timeline-definition-Z64GVDOM-BC29RNFm.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-pZlZCtEo.js} +1 -1
  59. package/dist/assets/{wardleyDiagram-T6FBY63Y-B9AlLQup.js → wardleyDiagram-T6FBY63Y-De8k2-zM.js} +1 -1
  60. package/dist/assets/{xychartDiagram-ELKLHX3M-DTJX48Qg.js → xychartDiagram-ELKLHX3M-Bk8ykH0w.js} +1 -1
  61. package/dist/index.html +3 -3
  62. package/dist-server/server/index.js +7 -3
  63. package/dist-server/server/index.js.map +1 -1
  64. package/dist-server/server/modules/agent/agent.routes.js +2 -2
  65. package/dist-server/server/modules/agent/agent.routes.js.map +1 -1
  66. package/dist-server/server/modules/agent/tests/agent.routes.test.js +30 -1
  67. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -1
  68. package/dist-server/server/modules/browser-use/browser-use.routes.js +39 -33
  69. package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -1
  70. package/dist-server/server/modules/database/connection.js +1 -1
  71. package/dist-server/server/modules/database/connection.js.map +1 -1
  72. package/dist-server/server/modules/database/index.js +5 -0
  73. package/dist-server/server/modules/database/index.js.map +1 -1
  74. package/dist-server/server/modules/database/migrations.js +32 -1
  75. package/dist-server/server/modules/database/migrations.js.map +1 -1
  76. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js +71 -0
  77. package/dist-server/server/modules/database/repositories/scheduled-messages.db.js.map +1 -0
  78. package/dist-server/server/modules/database/repositories/session-drafts.db.js +105 -0
  79. package/dist-server/server/modules/database/repositories/session-drafts.db.js.map +1 -0
  80. package/dist-server/server/modules/database/repositories/sessions.db.js +137 -5
  81. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  82. package/dist-server/server/modules/database/repositories/user-preferences.db.js +65 -0
  83. package/dist-server/server/modules/database/repositories/user-preferences.db.js.map +1 -0
  84. package/dist-server/server/modules/database/schema.js +92 -0
  85. package/dist-server/server/modules/database/schema.js.map +1 -1
  86. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +39 -0
  87. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  88. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js +105 -0
  89. package/dist-server/server/modules/database/tests/user-preferences.db.integration.test.js.map +1 -0
  90. package/dist-server/server/modules/projects/services/project-delete.service.js +12 -1
  91. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  92. package/dist-server/server/modules/projects/services/project-star.service.js +2 -2
  93. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -1
  94. package/dist-server/server/modules/providers/index.js +3 -0
  95. package/dist-server/server/modules/providers/index.js.map +1 -1
  96. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +20 -1
  97. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  98. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js +46 -0
  99. package/dist-server/server/modules/providers/list/claude/claude-fork.provider.js.map +1 -0
  100. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +35 -6
  101. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  102. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +168 -38
  103. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  104. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +388 -50
  105. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  106. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +4 -1
  107. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
  108. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  109. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  110. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js +204 -0
  111. package/dist-server/server/modules/providers/list/codex/codex-app-server.client.js.map +1 -0
  112. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js +32 -0
  113. package/dist-server/server/modules/providers/list/codex/codex-fork.provider.js.map +1 -0
  114. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +16 -0
  115. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  116. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js +60 -49
  117. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -1
  118. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +8 -0
  119. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  120. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +1565 -432
  121. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  122. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  123. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  124. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  125. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -1
  126. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  127. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +258 -6
  128. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  129. package/dist-server/server/modules/providers/provider.routes.js +24 -0
  130. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  131. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +14 -0
  132. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  133. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +139 -35
  134. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -1
  135. package/dist-server/server/modules/providers/services/session-history-cache.service.js +87 -0
  136. package/dist-server/server/modules/providers/services/session-history-cache.service.js.map +1 -0
  137. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +105 -32
  138. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  139. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +3 -60
  140. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  141. package/dist-server/server/modules/providers/services/sessions.service.js +179 -9
  142. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  143. package/dist-server/server/modules/providers/tests/claude-auth.test.js +52 -0
  144. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -1
  145. package/dist-server/server/modules/providers/tests/claude-models.test.js +39 -0
  146. package/dist-server/server/modules/providers/tests/claude-models.test.js.map +1 -0
  147. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +465 -39
  148. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -1
  149. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js +16 -0
  150. package/dist-server/server/modules/providers/tests/claude-subagent-echo.test.js.map +1 -0
  151. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js +85 -0
  152. package/dist-server/server/modules/providers/tests/claude-token-budget.test.js.map +1 -0
  153. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js +338 -0
  154. package/dist-server/server/modules/providers/tests/codex-message-editing.test.js.map +1 -0
  155. package/dist-server/server/modules/providers/tests/codex-runtime.test.js +51 -0
  156. package/dist-server/server/modules/providers/tests/codex-runtime.test.js.map +1 -0
  157. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js +54 -0
  158. package/dist-server/server/modules/providers/tests/codex-session-fork.test.js.map +1 -0
  159. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +304 -15
  160. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  161. package/dist-server/server/modules/providers/tests/opencode-models.test.js +117 -4
  162. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  163. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +164 -1
  164. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  165. package/dist-server/server/modules/providers/tests/session-fork.test.js +113 -0
  166. package/dist-server/server/modules/providers/tests/session-fork.test.js.map +1 -0
  167. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js +120 -0
  168. package/dist-server/server/modules/providers/tests/session-history-cache.service.test.js.map +1 -0
  169. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js +90 -0
  170. package/dist-server/server/modules/providers/tests/session-orphan-prune.test.js.map +1 -0
  171. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js +122 -0
  172. package/dist-server/server/modules/providers/tests/session-synchronizer-coalescing.test.js.map +1 -0
  173. package/dist-server/server/modules/providers/tests/sessions.service.test.js +45 -1
  174. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -1
  175. package/dist-server/server/modules/providers/tests/skills.test.js +15 -3
  176. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  177. package/dist-server/server/modules/scheduled-messages/index.js +5 -0
  178. package/dist-server/server/modules/scheduled-messages/index.js.map +1 -0
  179. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js +47 -0
  180. package/dist-server/server/modules/scheduled-messages/scheduled-messages.routes.js.map +1 -0
  181. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js +163 -0
  182. package/dist-server/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.js.map +1 -0
  183. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js +95 -0
  184. package/dist-server/server/modules/scheduled-messages/services/scheduled-messages.service.js.map +1 -0
  185. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js +255 -0
  186. package/dist-server/server/modules/scheduled-messages/tests/scheduled-messages.test.js.map +1 -0
  187. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +23 -23
  188. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -1
  189. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +48 -0
  190. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -1
  191. package/dist-server/server/modules/user/tests/user.service.test.js +70 -0
  192. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -1
  193. package/dist-server/server/modules/user/user.module.js +10 -1
  194. package/dist-server/server/modules/user/user.module.js.map +1 -1
  195. package/dist-server/server/modules/user/user.routes.js +44 -0
  196. package/dist-server/server/modules/user/user.routes.js.map +1 -1
  197. package/dist-server/server/modules/user/user.service.js +75 -0
  198. package/dist-server/server/modules/user/user.service.js.map +1 -1
  199. package/dist-server/server/modules/voice/tests/voice.service.test.js +3 -2
  200. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -1
  201. package/dist-server/server/modules/websocket/index.js +7 -0
  202. package/dist-server/server/modules/websocket/index.js.map +1 -1
  203. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +10 -44
  204. package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -1
  205. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +35 -5
  206. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -1
  207. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +166 -9
  208. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  209. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js +94 -0
  210. package/dist-server/server/modules/websocket/services/session-upsert-broadcast.service.js.map +1 -0
  211. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -3
  212. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  213. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js +263 -0
  214. package/dist-server/server/modules/websocket/tests/chat-edit-send.test.js.map +1 -0
  215. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +29 -3
  216. package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -1
  217. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js +106 -0
  218. package/dist-server/server/modules/websocket/tests/session-upsert-broadcast.test.js.map +1 -0
  219. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +80 -0
  220. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -1
  221. package/dist-server/server/shared/claude-cli-path.js +22 -5
  222. package/dist-server/server/shared/claude-cli-path.js.map +1 -1
  223. package/dist-server/server/shared/index.js +3 -0
  224. package/dist-server/server/shared/index.js.map +1 -0
  225. package/dist-server/server/shared/message-unification.js +376 -0
  226. package/dist-server/server/shared/message-unification.js.map +1 -0
  227. package/dist-server/server/shared/tests/claude-cli-path.test.js +45 -1
  228. package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -1
  229. package/dist-server/server/shared/tests/message-unification.test.js +174 -0
  230. package/dist-server/server/shared/tests/message-unification.test.js.map +1 -0
  231. package/dist-server/server/shared/utils.js +34 -0
  232. package/dist-server/server/shared/utils.js.map +1 -1
  233. package/package.json +19 -16
  234. package/server/index.ts +11 -4
  235. package/server/modules/agent/agent.routes.ts +2 -2
  236. package/server/modules/agent/tests/agent.routes.test.ts +33 -1
  237. package/server/modules/browser-use/browser-use.routes.ts +96 -33
  238. package/server/modules/database/connection.ts +2 -1
  239. package/server/modules/database/index.ts +10 -0
  240. package/server/modules/database/migrations.ts +37 -0
  241. package/server/modules/database/repositories/scheduled-messages.db.ts +127 -0
  242. package/server/modules/database/repositories/session-drafts.db.ts +171 -0
  243. package/server/modules/database/repositories/sessions.db.ts +196 -3
  244. package/server/modules/database/repositories/user-preferences.db.ts +80 -0
  245. package/server/modules/database/schema.ts +96 -0
  246. package/server/modules/database/tests/sessions.db.integration.test.ts +52 -0
  247. package/server/modules/database/tests/user-preferences.db.integration.test.ts +135 -0
  248. package/server/modules/projects/services/project-delete.service.ts +13 -1
  249. package/server/modules/projects/services/project-star.service.ts +2 -2
  250. package/server/modules/providers/index.ts +4 -0
  251. package/server/modules/providers/list/claude/claude-auth.provider.ts +21 -1
  252. package/server/modules/providers/list/claude/claude-fork.provider.ts +57 -0
  253. package/server/modules/providers/list/claude/claude-models.provider.ts +38 -6
  254. package/server/modules/providers/list/claude/claude-runtime.provider.js +188 -39
  255. package/server/modules/providers/list/claude/claude-sessions.provider.ts +478 -54
  256. package/server/modules/providers/list/claude/claude-skills.provider.ts +4 -1
  257. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  258. package/server/modules/providers/list/codex/codex-app-server.client.ts +243 -0
  259. package/server/modules/providers/list/codex/codex-fork.provider.ts +40 -0
  260. package/server/modules/providers/list/codex/codex-models.provider.ts +16 -0
  261. package/server/modules/providers/list/codex/{codex-runtime.provider.js → codex-runtime.provider.ts} +93 -66
  262. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +9 -0
  263. package/server/modules/providers/list/codex/codex-sessions.provider.ts +1806 -477
  264. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  265. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +8 -6
  266. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +0 -1
  267. package/server/modules/providers/list/opencode/opencode-models.provider.ts +279 -6
  268. package/server/modules/providers/provider.routes.ts +31 -0
  269. package/server/modules/providers/services/provider-capabilities.service.ts +23 -0
  270. package/server/modules/providers/services/provider-token-usage.service.ts +166 -39
  271. package/server/modules/providers/services/session-history-cache.service.ts +139 -0
  272. package/server/modules/providers/services/session-synchronizer.service.ts +125 -38
  273. package/server/modules/providers/services/sessions-watcher.service.ts +3 -64
  274. package/server/modules/providers/services/sessions.service.ts +201 -9
  275. package/server/modules/providers/tests/claude-auth.test.ts +58 -0
  276. package/server/modules/providers/tests/claude-models.test.ts +87 -0
  277. package/server/modules/providers/tests/claude-sessions.test.ts +552 -40
  278. package/server/modules/providers/tests/claude-subagent-echo.test.ts +25 -0
  279. package/server/modules/providers/tests/claude-token-budget.test.ts +104 -0
  280. package/server/modules/providers/tests/codex-message-editing.test.ts +419 -0
  281. package/server/modules/providers/tests/codex-runtime.test.ts +58 -0
  282. package/server/modules/providers/tests/codex-session-fork.test.ts +59 -0
  283. package/server/modules/providers/tests/codex-sessions.test.ts +337 -15
  284. package/server/modules/providers/tests/opencode-models.test.ts +166 -8
  285. package/server/modules/providers/tests/provider-token-usage.service.test.ts +188 -1
  286. package/server/modules/providers/tests/session-fork.test.ts +173 -0
  287. package/server/modules/providers/tests/session-history-cache.service.test.ts +143 -0
  288. package/server/modules/providers/tests/session-orphan-prune.test.ts +107 -0
  289. package/server/modules/providers/tests/session-synchronizer-coalescing.test.ts +136 -0
  290. package/server/modules/providers/tests/sessions.service.test.ts +67 -1
  291. package/server/modules/providers/tests/skills.test.ts +21 -5
  292. package/server/modules/scheduled-messages/index.ts +8 -0
  293. package/server/modules/scheduled-messages/scheduled-messages.routes.ts +68 -0
  294. package/server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts +203 -0
  295. package/server/modules/scheduled-messages/services/scheduled-messages.service.ts +121 -0
  296. package/server/modules/scheduled-messages/tests/scheduled-messages.test.ts +310 -0
  297. package/server/modules/taskmaster/taskmaster.routes.ts +23 -33
  298. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +57 -0
  299. package/server/modules/user/tests/user.service.test.ts +90 -0
  300. package/server/modules/user/user.module.ts +10 -1
  301. package/server/modules/user/user.routes.ts +44 -0
  302. package/server/modules/user/user.service.ts +105 -0
  303. package/server/modules/voice/tests/voice.service.test.ts +3 -2
  304. package/server/modules/websocket/index.ts +8 -0
  305. package/server/modules/websocket/services/chat-run-registry.service.ts +17 -50
  306. package/server/modules/websocket/services/chat-session-writer.service.ts +36 -6
  307. package/server/modules/websocket/services/chat-websocket.service.ts +244 -15
  308. package/server/modules/websocket/services/session-upsert-broadcast.service.ts +108 -0
  309. package/server/modules/websocket/services/shell-websocket.service.ts +10 -3
  310. package/server/modules/websocket/tests/chat-edit-send.test.ts +309 -0
  311. package/server/modules/websocket/tests/chat-run-registry.test.ts +34 -3
  312. package/server/modules/websocket/tests/session-upsert-broadcast.test.ts +138 -0
  313. package/server/modules/websocket/tests/shell-websocket.service.test.ts +108 -0
  314. package/server/shared/claude-cli-path.ts +24 -6
  315. package/server/shared/index.ts +3 -0
  316. package/server/shared/interfaces.ts +62 -0
  317. package/server/shared/message-unification.ts +449 -0
  318. package/server/shared/tests/claude-cli-path.test.ts +58 -1
  319. package/server/shared/tests/message-unification.test.ts +218 -0
  320. package/server/shared/types.ts +123 -2
  321. package/server/shared/utils.ts +40 -0
  322. package/dist/assets/channel-_falYbU1.js +0 -1
  323. package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +0 -1
  324. package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +0 -1
  325. package/dist/assets/index-CzPLBvXT.css +0 -32
  326. package/dist/assets/index-DFkBBuSA.js +0 -1549
  327. package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +0 -1
  328. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +0 -8
@@ -40,7 +40,7 @@ export function applyLegacyStarredProjectIds(projectIds: string[]): ApplyLegacyS
40
40
  continue;
41
41
  }
42
42
 
43
- if (Boolean(project.isStarred)) {
43
+ if (project.isStarred) {
44
44
  continue;
45
45
  }
46
46
 
@@ -71,7 +71,7 @@ export function toggleProjectStar(projectId: string): ToggleProjectStarResult {
71
71
  });
72
72
  }
73
73
 
74
- const nextStarredState = !Boolean(project.isStarred);
74
+ const nextStarredState = !project.isStarred;
75
75
  projectsDb.updateProjectIsStarredById(normalizedProjectId, nextStarredState);
76
76
 
77
77
  return { isStarred: nextStarredState };
@@ -6,5 +6,9 @@ export { providerRuntimeService } from './services/provider-runtime.service.js';
6
6
  // providerModelsService: used by Commands to list models and resolve the active session model.
7
7
  export { providerModelsService } from './services/provider-models.service.js';
8
8
 
9
+ // sessionsService: used by the websocket module's chat gateway to resolve an
10
+ // edited message's resume point, which only the providers module can read.
11
+ export { sessionsService } from './services/sessions.service.js';
12
+
9
13
  export { initializeSessionsWatcher } from './services/sessions-watcher.service.js';
10
14
  export { closeSessionsWatcher } from './services/sessions-watcher.service.js';
@@ -25,7 +25,9 @@ export class ClaudeProviderAuth implements IProviderAuth {
25
25
  * Checks whether the Claude Code CLI is available on this host.
26
26
  */
27
27
  private checkInstalled(): boolean {
28
- const cliPath = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH);
28
+ // cross-spawn resolves shims and PATHEXT itself, so the bare command is a
29
+ // usable fallback here even where the SDK's raw spawn could not use it.
30
+ const cliPath = resolveClaudeCodeExecutablePath(process.env.CLAUDE_CLI_PATH) ?? 'claude';
29
31
  try {
30
32
  spawn.sync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 });
31
33
  return true;
@@ -126,6 +128,24 @@ export class ClaudeProviderAuth implements IProviderAuth {
126
128
  };
127
129
  }
128
130
 
131
+ // `accessToken` is short-lived (hours). Claude Code renews it silently
132
+ // from `refreshToken` on the next CLI invocation, so an expired access
133
+ // token alongside a live refresh token is still a working login. Before
134
+ // this check, a still-signed-in account read as "login has expired"
135
+ // until something else happened to run the CLI — which is why opening
136
+ // the Shell tab and coming back made Settings flip to Connected.
137
+ const refreshToken = readOptionalString(oauth?.refreshToken);
138
+ const refreshTokenExpiresAt = typeof oauth?.refreshTokenExpiresAt === 'number'
139
+ ? oauth.refreshTokenExpiresAt
140
+ : undefined;
141
+ if (refreshToken && (!refreshTokenExpiresAt || Date.now() < refreshTokenExpiresAt)) {
142
+ return {
143
+ authenticated: true,
144
+ email,
145
+ method: 'credentials_file',
146
+ };
147
+ }
148
+
129
149
  return {
130
150
  authenticated: false,
131
151
  email: null,
@@ -0,0 +1,57 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { forkSession as forkClaudeSession } from '@anthropic-ai/claude-agent-sdk';
5
+
6
+ import type { IProviderFork } from '@/shared/interfaces.js';
7
+ import { AppError } from '@/shared/utils.js';
8
+
9
+ /**
10
+ * Branches a Claude conversation by copying its transcript into a new session
11
+ * file.
12
+ *
13
+ * The SDK owns this: it remaps every message uuid and rewrites the parentUuid
14
+ * chain, which is what makes the copy resumable rather than just a duplicate
15
+ * file. `upToMessageId` is inclusive of the row it names.
16
+ */
17
+ export class ClaudeForkProvider implements IProviderFork {
18
+ async forkSession(input: {
19
+ providerSessionId: string;
20
+ jsonlPath: string;
21
+ projectPath: string;
22
+ upToAnchorId?: string;
23
+ title?: string;
24
+ }): Promise<{ providerSessionId: string; jsonlPath: string }> {
25
+ // `dir` is the session's working directory, which the SDK encodes into the
26
+ // `~/.claude/projects/<encoded>` folder name itself — passing that folder
27
+ // makes it encode an already-encoded path and find nothing.
28
+ const { sessionId } = await forkClaudeSession(input.providerSessionId, {
29
+ dir: input.projectPath,
30
+ upToMessageId: input.upToAnchorId,
31
+ title: input.title,
32
+ });
33
+
34
+ if (!sessionId) {
35
+ throw new AppError('Claude did not return a session id for the fork.', {
36
+ code: 'FORK_FAILED',
37
+ statusCode: 502,
38
+ });
39
+ }
40
+
41
+ // Confirmed rather than assumed: the caller is about to write a database
42
+ // row claiming this file exists, and a half-created row would show up in
43
+ // the sidebar as a session that can never be opened.
44
+ // The fork lands beside the transcript it was copied from.
45
+ const forkedPath = path.join(path.dirname(input.jsonlPath), `${sessionId}.jsonl`);
46
+ try {
47
+ await stat(forkedPath);
48
+ } catch {
49
+ throw new AppError('Claude reported a fork but wrote no transcript for it.', {
50
+ code: 'FORK_FAILED',
51
+ statusCode: 502,
52
+ });
53
+ }
54
+
55
+ return { providerSessionId: sessionId, jsonlPath: forkedPath };
56
+ }
57
+ }
@@ -9,6 +9,19 @@ import type {
9
9
  } from '@/shared/types.js';
10
10
  import { buildDefaultProviderCurrentActiveModel } from '@/shared/utils.js';
11
11
 
12
+ /**
13
+ * Ultracode is not one of the SDK's reasoning-effort levels. Selecting it runs the turn at
14
+ * `xhigh` effort with standing dynamic-workflow orchestration, which the Claude runtime
15
+ * translates into the session-scoped `ultracode` setting. It is therefore only offered on
16
+ * models this catalog already marks as xhigh-capable.
17
+ */
18
+ export const CLAUDE_ULTRACODE_EFFORT = 'ultracode';
19
+
20
+ const ULTRACODE_EFFORT_OPTION = {
21
+ value: CLAUDE_ULTRACODE_EFFORT,
22
+ description: 'Highest effort plus standing workflow orchestration.',
23
+ };
24
+
12
25
  export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
13
26
  OPTIONS: [
14
27
  {
@@ -37,6 +50,7 @@ export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
37
50
  { value: 'high' },
38
51
  { value: 'xhigh' },
39
52
  { value: 'max' },
53
+ ULTRACODE_EFFORT_OPTION,
40
54
  ],
41
55
  },
42
56
  },
@@ -52,6 +66,7 @@ export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
52
66
  { value: 'high' },
53
67
  { value: 'xhigh' },
54
68
  { value: 'max' },
69
+ ULTRACODE_EFFORT_OPTION,
55
70
  ],
56
71
  },
57
72
  },
@@ -67,6 +82,7 @@ export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
67
82
  { value: 'high' },
68
83
  { value: 'xhigh' },
69
84
  { value: 'max' },
85
+ ULTRACODE_EFFORT_OPTION,
70
86
  ],
71
87
  },
72
88
  },
@@ -82,6 +98,7 @@ export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
82
98
  { value: 'high' },
83
99
  { value: 'xhigh' },
84
100
  { value: 'max' },
101
+ ULTRACODE_EFFORT_OPTION,
85
102
  ],
86
103
  },
87
104
  },
@@ -97,6 +114,7 @@ export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
97
114
  { value: 'high' },
98
115
  { value: 'xhigh' },
99
116
  { value: 'max' },
117
+ ULTRACODE_EFFORT_OPTION,
100
118
  ],
101
119
  },
102
120
  },
@@ -112,6 +130,7 @@ export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
112
130
  { value: 'high' },
113
131
  { value: 'xhigh' },
114
132
  { value: 'max' },
133
+ ULTRACODE_EFFORT_OPTION,
115
134
  ],
116
135
  },
117
136
  },
@@ -132,6 +151,7 @@ export const CLAUDE_PREDEFINED_MODELS: ProviderModelsDefinition = {
132
151
  { value: 'high' },
133
152
  { value: 'xhigh' },
134
153
  { value: 'max' },
154
+ ULTRACODE_EFFORT_OPTION,
135
155
  ],
136
156
  },
137
157
  },
@@ -166,7 +186,15 @@ const ANSI_PATTERN = new RegExp(
166
186
  'g',
167
187
  );
168
188
 
169
- const extractClaudeEventModel = (event: ClaudeInitEvent, sessionId: string): string | null => {
189
+ /**
190
+ * Claude Code stamps locally-synthesized rows (API-error placeholders and the
191
+ * like) with `model: "<synthetic>"`. Angle-bracketed values are placeholders,
192
+ * never real model ids, and must not be surfaced as the session's model.
193
+ */
194
+ const isPlaceholderModel = (model: string): boolean => model.startsWith('<') && model.endsWith('>');
195
+
196
+ /** Exported for tests. */
197
+ export const extractClaudeEventModel = (event: ClaudeInitEvent, sessionId: string): string | null => {
170
198
  const eventSessionId = event.sessionId ?? event.session_id;
171
199
  if (eventSessionId && eventSessionId !== sessionId) {
172
200
  return null;
@@ -178,12 +206,12 @@ const extractClaudeEventModel = (event: ClaudeInitEvent, sessionId: string): str
178
206
  }
179
207
 
180
208
  const directModel = event.model?.trim();
181
- if (directModel) {
209
+ if (directModel && !isPlaceholderModel(directModel)) {
182
210
  return directModel;
183
211
  }
184
212
 
185
213
  const messageModel = event.message?.model?.trim();
186
- return messageModel || null;
214
+ return messageModel && !isPlaceholderModel(messageModel) ? messageModel : null;
187
215
  };
188
216
 
189
217
  const stripAnsi = (value: string): string => value.replace(ANSI_PATTERN, '');
@@ -199,13 +227,15 @@ const extractClaudeModelFromTextContent = (content: string): string | null => {
199
227
  if (localCommandStdout !== null) {
200
228
  const cleanedStdout = stripAnsi(localCommandStdout).replace(/\s+/g, ' ').trim();
201
229
  const changedModel = /(?:set|changed|switched)\s+model\s+to\s+(.+?)\.?$/i.exec(cleanedStdout);
202
- if (changedModel?.[1]?.trim()) {
203
- return changedModel[1].trim();
230
+ const stdoutModel = changedModel?.[1]?.trim();
231
+ // A placeholder stdout hit must not shadow a real <model> tag further down.
232
+ if (stdoutModel && !isPlaceholderModel(stdoutModel)) {
233
+ return stdoutModel;
204
234
  }
205
235
  }
206
236
 
207
237
  const modelTag = extractTaggedContent(content, 'model')?.trim();
208
- return modelTag || null;
238
+ return modelTag && !isPlaceholderModel(modelTag) ? modelTag : null;
209
239
  };
210
240
 
211
241
  const extractClaudeModelFromMessageContent = (content: unknown): string | null => {
@@ -222,6 +252,8 @@ const extractClaudeModelFromMessageContent = (content: unknown): string | null =
222
252
  continue;
223
253
  }
224
254
 
255
+ // extractClaudeModelFromTextContent rejects placeholders, so a placeholder
256
+ // part yields null here and a later part can still supply the real model.
225
257
  const model = extractClaudeModelFromTextContent(part.text);
226
258
  if (model) {
227
259
  return model;
@@ -24,7 +24,10 @@ 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,
@@ -69,6 +72,12 @@ const BG_WAIT_CEILING_MS = 30 * 60 * 1000;
69
72
 
70
73
  const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion', 'ExitPlanMode']);
71
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
+
72
81
  function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED_MODELS) {
73
82
  const selectedModel = modelsDefinition?.OPTIONS?.find((option) => option.value === model) || null;
74
83
  const allowedEfforts = selectedModel?.effort?.values
@@ -78,6 +87,30 @@ function resolveClaudeEffort(model, effort, modelsDefinition = CLAUDE_PREDEFINED
78
87
  : undefined;
79
88
  }
80
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
+
81
114
  function createRequestId() {
82
115
  if (typeof crypto.randomUUID === 'function') {
83
116
  return crypto.randomUUID();
@@ -183,7 +216,7 @@ function matchesToolPermission(entry, toolName, input) {
183
216
  }
184
217
 
185
218
  function mapCliOptionsToSDK(options = {}) {
186
- const { providerSessionId, cwd, toolsSettings, permissionMode, effort } = options;
219
+ const { providerSessionId, cwd, toolsSettings, permissionMode, effort, resumeAnchorId, resumeFromScratch } = options;
187
220
 
188
221
  const sdkOptions = {};
189
222
 
@@ -193,7 +226,12 @@ function mapCliOptionsToSDK(options = {}) {
193
226
 
194
227
  // Resolve the executable eagerly on Windows because the SDK uses raw child_process.spawn,
195
228
  // which does not reliably follow npm's shell wrappers like cross-spawn does.
196
- 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
+ }
197
235
 
198
236
  if (cwd) {
199
237
  sdkOptions.cwd = cwd;
@@ -235,14 +273,11 @@ function mapCliOptionsToSDK(options = {}) {
235
273
 
236
274
  sdkOptions.model = options.model || CLAUDE_PREDEFINED_MODELS.DEFAULT;
237
275
 
238
- const resolvedEffort = resolveClaudeEffort(
276
+ applyClaudeEffort(sdkOptions, resolveClaudeEffort(
239
277
  sdkOptions.model,
240
278
  effort,
241
279
  options.effortModels || CLAUDE_PREDEFINED_MODELS,
242
- );
243
- if (resolvedEffort) {
244
- sdkOptions.effort = resolvedEffort;
245
- }
280
+ ));
246
281
 
247
282
  sdkOptions.systemPrompt = {
248
283
  type: 'preset',
@@ -252,8 +287,19 @@ function mapCliOptionsToSDK(options = {}) {
252
287
  sdkOptions.settingSources = ['project', 'user', 'local'];
253
288
 
254
289
  // The SDK resumes with the provider-native session id, never the app id.
255
- 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) {
256
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
+ }
257
303
  }
258
304
 
259
305
  return sdkOptions;
@@ -339,47 +385,129 @@ function transformMessage(sdkMessage) {
339
385
  return sdkMessage;
340
386
  }
341
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
+
342
402
  function readNumber(value) {
343
403
  const parsed = Number(value);
344
404
  return Number.isFinite(parsed) ? parsed : 0;
345
405
  }
346
406
 
347
407
  /**
348
- * Extracts token usage from SDK messages.
349
- * Prefers per-step `message.usage` (Claude message payload), then falls back
350
- * 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`.
351
457
  * @param {Object} sdkMessage - SDK stream message
352
- * @returns {Object|null} Token budget object or null
458
+ * @returns {TokenBudget|null} Token budget object or null
353
459
  */
354
460
  function extractTokenBudget(sdkMessage) {
355
461
  if (!sdkMessage || typeof sdkMessage !== 'object') {
356
462
  return null;
357
463
  }
358
464
 
359
- const messageUsage = sdkMessage.message?.usage || sdkMessage.usage;
360
- if (messageUsage && typeof messageUsage === 'object') {
361
- const directInputTokens = readNumber(messageUsage.input_tokens ?? messageUsage.inputTokens);
362
- const cacheCreationTokens = readNumber(messageUsage.cache_creation_input_tokens ?? messageUsage.cacheCreationInputTokens ?? messageUsage.cacheCreationTokens);
363
- const cacheReadTokens = readNumber(messageUsage.cache_read_input_tokens ?? messageUsage.cacheReadInputTokens ?? messageUsage.cacheReadTokens);
364
- const cacheTokens = cacheCreationTokens + cacheReadTokens;
365
- const inputTokens = directInputTokens + cacheTokens;
366
- const outputTokens = readNumber(messageUsage.output_tokens ?? messageUsage.outputTokens);
367
- const totalUsed = inputTokens + outputTokens;
368
- 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
+ }
369
471
 
370
- return {
371
- used: totalUsed,
372
- total: contextWindow,
373
- inputTokens,
374
- outputTokens,
375
- cacheReadTokens,
376
- cacheCreationTokens,
377
- cacheTokens,
378
- breakdown: {
379
- input: inputTokens,
380
- output: outputTokens,
381
- },
382
- };
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);
383
511
  }
384
512
 
385
513
  if (!sdkMessage.modelUsage || typeof sdkMessage.modelUsage !== 'object') {
@@ -599,6 +727,9 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
599
727
  // True while the process is being held open for background work, so a later
600
728
  // `result` can be recognised as that work reporting back.
601
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;
602
733
 
603
734
  // A new turn supersedes any earlier one still holding this session's process
604
735
  // open, so held runs cannot stack up across a conversation.
@@ -736,6 +867,13 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
736
867
  return { behavior: 'deny', message: 'Permission request cancelled' };
737
868
  }
738
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
+
739
877
  if (decision.allow) {
740
878
  if (decision.rememberEntry && typeof decision.rememberEntry === 'string') {
741
879
  if (!sdkOptions.allowedTools.includes(decision.rememberEntry)) {
@@ -812,12 +950,21 @@ async function queryClaudeSDK(command, options = {}, ws, context) {
812
950
  if (transformedMessage.parentToolUseId && !msg.parentToolUseId) {
813
951
  msg.parentToolUseId = transformedMessage.parentToolUseId;
814
952
  }
953
+ if (isSubagentPromptEcho(msg)) {
954
+ continue;
955
+ }
815
956
  ws.send(msg);
816
957
  }
817
958
 
818
- // Extract and send token budget updates from assistant/result usage payloads
819
- 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));
820
964
  if (tokenBudgetData) {
965
+ if (message.type === 'assistant') {
966
+ assistantBudgetSent = true;
967
+ }
821
968
  ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
822
969
  }
823
970
 
@@ -1064,5 +1211,7 @@ export {
1064
1211
  getActiveClaudeSDKSessions,
1065
1212
  resolveToolApproval,
1066
1213
  getPendingApprovalsForSession,
1067
- reconnectSessionWriter
1214
+ reconnectSessionWriter,
1215
+ extractTokenBudget,
1216
+ extractCumulativeTokenBudget
1068
1217
  };