@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
@@ -1,5 +1,6 @@
1
1
  import { providerModelsDb, sessionsDb } from '@/modules/database/index.js';
2
2
  import { providerRegistry } from '@/modules/providers/provider.registry.js';
3
+ import { providerCapabilitiesService } from '@/modules/providers/services/provider-capabilities.service.js';
3
4
  import type { IProvider } from '@/shared/interfaces.js';
4
5
  import type {
5
6
  CustomProviderModelInput,
@@ -36,6 +37,15 @@ type ProviderModelsServiceDependencies = {
36
37
  sessions?: ProviderModelsSessionStore;
37
38
  };
38
39
 
40
+ /**
41
+ * Converts a stored custom row into a catalog option.
42
+ *
43
+ * `effort` is emitted in the same shape predefined models use, so the composer
44
+ * and every runtime's effort validation treat declared levels identically.
45
+ * Rows without declared levels omit `effort` on purpose: the composer then
46
+ * hides Reasoning and runtimes send no effort, instead of guessing levels the
47
+ * model might reject.
48
+ */
39
49
  const toCustomProviderModelOption = (
40
50
  record: CustomProviderModelRecord,
41
51
  ): ProviderModelOption => ({
@@ -43,9 +53,32 @@ const toCustomProviderModelOption = (
43
53
  label: record.model,
44
54
  recordId: record.recordId,
45
55
  isCustom: true,
56
+ ...(record.effort
57
+ ? {
58
+ effort: {
59
+ ...(record.effort.default ? { default: record.effort.default } : {}),
60
+ values: record.effort.values.map((value) => ({ value })),
61
+ },
62
+ }
63
+ : {}),
46
64
  });
47
65
 
66
+ /**
67
+ * Effort levels a custom model of this provider may declare, weakest first:
68
+ * the levels its runtime can pass on, from the capability matrix. Empty
69
+ * (Cursor) means no effort support at all.
70
+ *
71
+ * Deliberately not derived from the predefined models: OpenCode narrows its
72
+ * catalog to the upstream providers this machine has connected, and only its
73
+ * OpenCode Go models declare effort, so a catalog-derived set would vanish for
74
+ * Zen, Anthropic, or OpenAI users and change with unrelated credentials.
75
+ */
76
+ const readCustomModelEffortLevels = (provider: LLMProvider): string[] => (
77
+ providerCapabilitiesService.getProviderCapabilities(provider).effortLevels
78
+ );
79
+
48
80
  const mergeProviderModels = (
81
+ provider: LLMProvider,
49
82
  predefined: ProviderModelsDefinition,
50
83
  custom: CustomProviderModelRecord[],
51
84
  ): ProviderModelsDefinition => {
@@ -55,14 +88,46 @@ const mergeProviderModels = (
55
88
  ...custom.map(toCustomProviderModelOption),
56
89
  ],
57
90
  DEFAULT: predefined.DEFAULT,
91
+ // Copied so no caller can mutate the shared capability matrix.
92
+ EFFORT_LEVELS: [...readCustomModelEffortLevels(provider)],
58
93
  };
59
94
  };
60
95
 
61
96
  const normalizeCustomModelInput = (input: CustomProviderModelInput): CustomProviderModelInput => ({
62
97
  id: input.id.trim(),
63
98
  model: input.model.trim(),
99
+ ...(input.effort === undefined ? {} : { effort: input.effort }),
64
100
  });
65
101
 
102
+ const assertEffortLevelsSupported = (
103
+ provider: LLMProvider,
104
+ input: CustomProviderModelInput,
105
+ ): void => {
106
+ const declaredLevels = input.effort?.values ?? [];
107
+ if (declaredLevels.length === 0) {
108
+ return;
109
+ }
110
+
111
+ const supportedLevels = readCustomModelEffortLevels(provider);
112
+ if (supportedLevels.length === 0) {
113
+ throw new AppError(`${provider} models do not support reasoning effort.`, {
114
+ code: 'MODEL_EFFORT_NOT_SUPPORTED',
115
+ statusCode: 400,
116
+ });
117
+ }
118
+
119
+ const unsupportedLevel = declaredLevels.find((level) => !supportedLevels.includes(level));
120
+ if (unsupportedLevel) {
121
+ throw new AppError(
122
+ `"${unsupportedLevel}" is not a ${provider} effort level. Use one of: ${supportedLevels.join(', ')}.`,
123
+ {
124
+ code: 'INVALID_MODEL_EFFORT',
125
+ statusCode: 400,
126
+ },
127
+ );
128
+ }
129
+ };
130
+
66
131
  const isUniqueConstraintError = (error: unknown): boolean => (
67
132
  error !== null
68
133
  && error !== undefined
@@ -87,7 +152,7 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
87
152
 
88
153
  const getProviderModels = async (provider: LLMProvider): Promise<ProviderModelsDefinition> => {
89
154
  const predefined = await resolveProvider(provider).models.getSupportedModels();
90
- return mergeProviderModels(predefined, catalog.listCustomProviderModels(provider));
155
+ return mergeProviderModels(provider, predefined, catalog.listCustomProviderModels(provider));
91
156
  };
92
157
 
93
158
  const getCurrentActiveModel = async (
@@ -139,12 +204,13 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
139
204
  const predefined = await resolveProvider(provider).models.getSupportedModels();
140
205
  const normalized = normalizeCustomModelInput(input);
141
206
  assertModelIdAvailable(provider, predefined, normalized.id);
207
+ assertEffortLevelsSupported(provider, normalized);
142
208
 
143
209
  try {
144
210
  const created = catalog.createCustomProviderModel(provider, normalized);
145
211
  return {
146
212
  model: toCustomProviderModelOption(created),
147
- models: mergeProviderModels(predefined, catalog.listCustomProviderModels(provider)),
213
+ models: mergeProviderModels(provider, predefined, catalog.listCustomProviderModels(provider)),
148
214
  };
149
215
  } catch (error) {
150
216
  if (isUniqueConstraintError(error)) {
@@ -166,6 +232,7 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
166
232
  readCustomModel(provider, recordId);
167
233
  const normalized = normalizeCustomModelInput(input);
168
234
  assertModelIdAvailable(provider, predefined, normalized.id, recordId);
235
+ assertEffortLevelsSupported(provider, normalized);
169
236
 
170
237
  try {
171
238
  const updated = catalog.updateCustomProviderModel(provider, recordId, normalized);
@@ -178,7 +245,7 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
178
245
 
179
246
  return {
180
247
  model: toCustomProviderModelOption(updated),
181
- models: mergeProviderModels(predefined, catalog.listCustomProviderModels(provider)),
248
+ models: mergeProviderModels(provider, predefined, catalog.listCustomProviderModels(provider)),
182
249
  };
183
250
  } catch (error) {
184
251
  if (isUniqueConstraintError(error)) {
@@ -207,7 +274,7 @@ export const createProviderModelsService = (dependencies: ProviderModelsServiceD
207
274
 
208
275
  return {
209
276
  model: toCustomProviderModelOption(removed),
210
- models: mergeProviderModels(predefined, catalog.listCustomProviderModels(provider)),
277
+ models: mergeProviderModels(provider, predefined, catalog.listCustomProviderModels(provider)),
211
278
  };
212
279
  };
213
280
 
@@ -91,6 +91,17 @@ export function createProviderRuntimeService(
91
91
  return Boolean(await dependencies.resolveProvider(providerName).runtime.abort(sessionId));
92
92
  },
93
93
 
94
+ async stopBackgroundTask(providerName: LLMProvider, sessionId: string, taskId: string): Promise<boolean> {
95
+ // A runtime that never holds background work has no task to stop.
96
+ const { runtime } = dependencies.resolveProvider(providerName);
97
+ return Boolean(await runtime.stopBackgroundTask?.(sessionId, taskId));
98
+ },
99
+
100
+ hasBackgroundWork(sessionId: string): boolean {
101
+ return dependencies.listProviders().some((provider) =>
102
+ (provider.runtime.listBackgroundWork?.() ?? []).some((entry) => entry.sessionId === sessionId));
103
+ },
104
+
94
105
  resolveToolApproval(requestId: string, decision: ProviderPermissionDecision): void {
95
106
  for (const provider of dependencies.listProviders()) {
96
107
  provider.runtime.permissions?.resolve(requestId, decision);
@@ -11,6 +11,12 @@ import { AppError, getOpenCodeDatabasePath } from '@/shared/utils.js';
11
11
 
12
12
  type SessionRow = NonNullable<ReturnType<typeof sessionsDb.getSessionById>>;
13
13
 
14
+ type FileTail = {
15
+ content: string;
16
+ /** True when `content` is the whole file, not just its trailing bytes. */
17
+ isComplete: boolean;
18
+ };
19
+
14
20
  type ProviderTokenUsageServiceDependencies = {
15
21
  getSessionById: (sessionId: string) => SessionRow | null | undefined;
16
22
  getHomeDirectory: () => string;
@@ -18,7 +24,9 @@ type ProviderTokenUsageServiceDependencies = {
18
24
  fileExists: (filePath: string) => boolean;
19
25
  readDirectory: (directoryPath: string) => Promise<Dirent[]>;
20
26
  readTextFile: (filePath: string) => Promise<string>;
27
+ readTextFileTail: (filePath: string, maxBytes: number) => Promise<FileTail>;
21
28
  getClaudeContextWindow: () => string | undefined;
29
+ isProviderSessionSuperseded: (providerSessionId: string, provider: string) => boolean;
22
30
  };
23
31
 
24
32
  type TokenUsageResult = {
@@ -45,6 +53,14 @@ type OpenCodeTokenRow = {
45
53
  cacheWriteTokens: number | null;
46
54
  };
47
55
 
56
+ /**
57
+ * Both JSONL usage readers below only need the newest usage row, which sits at
58
+ * or near the end of the transcript. Reading just this much of the tail keeps
59
+ * a session-open usage lookup O(1) in the transcript's size; the rare tail
60
+ * with no usage row at all falls back to the full read.
61
+ */
62
+ const TOKEN_USAGE_TAIL_BYTES = 4 * 1024 * 1024;
63
+
48
64
  const defaultDependencies: ProviderTokenUsageServiceDependencies = {
49
65
  getSessionById: (sessionId) => sessionsDb.getSessionById(sessionId),
50
66
  getHomeDirectory: () => os.homedir(),
@@ -52,7 +68,32 @@ const defaultDependencies: ProviderTokenUsageServiceDependencies = {
52
68
  fileExists: (filePath) => fsSync.existsSync(filePath),
53
69
  readDirectory: (directoryPath) => fsp.readdir(directoryPath, { withFileTypes: true }),
54
70
  readTextFile: (filePath) => fsp.readFile(filePath, 'utf8'),
71
+ readTextFileTail: async (filePath, maxBytes) => {
72
+ const handle = await fsp.open(filePath, 'r');
73
+ try {
74
+ const { size } = await handle.stat();
75
+ if (size <= maxBytes) {
76
+ return { content: await handle.readFile({ encoding: 'utf8' }), isComplete: true };
77
+ }
78
+
79
+ const buffer = Buffer.alloc(maxBytes);
80
+ await handle.read(buffer, 0, maxBytes, size - maxBytes);
81
+ const content = buffer.toString('utf8');
82
+ // The window almost never starts on a row boundary; dropping everything
83
+ // up to the first newline discards the partial row (and any split
84
+ // multi-byte character with it).
85
+ const firstNewline = content.indexOf('\n');
86
+ return {
87
+ content: firstNewline === -1 ? '' : content.slice(firstNewline + 1),
88
+ isComplete: false,
89
+ };
90
+ } finally {
91
+ await handle.close();
92
+ }
93
+ },
55
94
  getClaudeContextWindow: () => process.env.CONTEXT_WINDOW,
95
+ isProviderSessionSuperseded: (providerSessionId, provider) =>
96
+ sessionsDb.isProviderSessionSuperseded(providerSessionId, provider),
56
97
  };
57
98
 
58
99
  function readUsageNumber(value: unknown): number {
@@ -92,11 +133,8 @@ async function findCodexSessionFile(
92
133
  return null;
93
134
  }
94
135
 
95
- function readCodexTokenUsage(fileContent: string): TokenUsageResult {
96
- let inputTokens = 0;
97
- let outputTokens = 0;
98
- let totalTokens = 0;
99
- let contextWindow = 200_000;
136
+ /** Newest `token_count` snapshot in the given JSONL text, or null when it has none. */
137
+ function findCodexTokenUsage(fileContent: string): TokenUsageResult | null {
100
138
  const lines = fileContent.trim().split('\n');
101
139
 
102
140
  for (let index = lines.length - 1; index >= 0; index -= 1) {
@@ -109,58 +147,101 @@ function readCodexTokenUsage(fileContent: string): TokenUsageResult {
109
147
  continue;
110
148
  }
111
149
 
150
+ let inputTokens = 0;
151
+ let outputTokens = 0;
152
+ let totalTokens = 0;
112
153
  if (tokenInfo.total_token_usage) {
113
154
  inputTokens = readUsageNumber(tokenInfo.total_token_usage.input_tokens);
114
155
  outputTokens = readUsageNumber(tokenInfo.total_token_usage.output_tokens);
115
156
  totalTokens = readUsageNumber(tokenInfo.total_token_usage.total_tokens)
116
157
  || inputTokens + outputTokens;
117
158
  }
118
- contextWindow = readUsageNumber(tokenInfo.model_context_window) || contextWindow;
119
- break;
159
+ return {
160
+ used: totalTokens,
161
+ total: readUsageNumber(tokenInfo.model_context_window) || 200_000,
162
+ inputTokens,
163
+ outputTokens,
164
+ breakdown: { input: inputTokens, output: outputTokens },
165
+ };
120
166
  } catch {
121
167
  // A provider may be writing the last JSONL line while this read happens.
122
168
  }
123
169
  }
124
170
 
171
+ return null;
172
+ }
173
+
174
+ function emptyCodexTokenUsage(): TokenUsageResult {
125
175
  return {
126
- used: totalTokens,
127
- total: contextWindow,
128
- inputTokens,
129
- outputTokens,
130
- breakdown: { input: inputTokens, output: outputTokens },
176
+ used: 0,
177
+ total: 200_000,
178
+ inputTokens: 0,
179
+ outputTokens: 0,
180
+ breakdown: { input: 0, output: 0 },
131
181
  };
132
182
  }
133
183
 
134
- function readClaudeTokenUsage(fileContent: string, configuredContextWindow: string | undefined): TokenUsageResult {
184
+ /**
185
+ * Latest context-window usage from a Claude transcript's already-parsed rows.
186
+ *
187
+ * Exported because the session-messages reader hands the same usage back on
188
+ * every history page, the way the Codex and OpenCode readers do. Without that,
189
+ * a Claude session's counter only moved when the session was reselected, and
190
+ * the store's "this provider reports no usage" path overwrote it with zero.
191
+ *
192
+ * Reads the newest assistant turn only: `input_tokens + cache_read +
193
+ * cache_creation` is that one request's whole prompt, i.e. what the context
194
+ * window currently holds. Summing turns would count the same cached prefix
195
+ * once per turn.
196
+ */
197
+ export function summarizeClaudeTokenUsage(
198
+ entries: AnyRecord[],
199
+ configuredContextWindow: string | undefined = process.env.CONTEXT_WINDOW,
200
+ ): TokenUsageResult {
135
201
  let inputTokens = 0;
136
202
  let outputTokens = 0;
137
203
  let cacheReadTokens = 0;
138
204
  let cacheCreationTokens = 0;
139
- const lines = fileContent.trim().split('\n');
140
205
 
141
- for (let index = lines.length - 1; index >= 0; index -= 1) {
142
- try {
143
- const entry = JSON.parse(lines[index]) as AnyRecord;
144
- const usage = entry.type === 'assistant' ? entry.message?.usage : null;
145
- if (!usage) {
146
- continue;
147
- }
206
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
207
+ const entry = entries[index];
208
+ // A subagent's turns report the subagent's context window, not this
209
+ // conversation's; reading one makes the counter drop to the subagent's
210
+ // number and bounce back on the next main-thread turn.
211
+ if (entry?.isSidechain === true) {
212
+ continue;
213
+ }
148
214
 
149
- const directInputTokens = readUsageNumber(usage.input_tokens ?? usage.inputTokens);
150
- cacheReadTokens = readUsageNumber(
151
- usage.cache_read_input_tokens ?? usage.cacheReadInputTokens ?? usage.cacheReadTokens,
152
- );
153
- cacheCreationTokens = readUsageNumber(
154
- usage.cache_creation_input_tokens
155
- ?? usage.cacheCreationInputTokens
156
- ?? usage.cacheCreationTokens,
157
- );
158
- inputTokens = directInputTokens + cacheReadTokens + cacheCreationTokens;
159
- outputTokens = readUsageNumber(usage.output_tokens ?? usage.outputTokens);
160
- break;
161
- } catch {
162
- // Skip malformed lines without discarding usage from earlier messages.
215
+ const usage = entry?.type === 'assistant' ? entry.message?.usage : null;
216
+ if (!usage) {
217
+ continue;
218
+ }
219
+
220
+ const directInputTokens = readUsageNumber(usage.input_tokens ?? usage.inputTokens);
221
+ const rowCacheReadTokens = readUsageNumber(
222
+ usage.cache_read_input_tokens ?? usage.cacheReadInputTokens ?? usage.cacheReadTokens,
223
+ );
224
+ const rowCacheCreationTokens = readUsageNumber(
225
+ usage.cache_creation_input_tokens
226
+ ?? usage.cacheCreationInputTokens
227
+ ?? usage.cacheCreationTokens,
228
+ );
229
+ const rowInputTokens = directInputTokens + rowCacheReadTokens + rowCacheCreationTokens;
230
+ const rowOutputTokens = readUsageNumber(usage.output_tokens ?? usage.outputTokens);
231
+
232
+ // `<synthetic>` rows — interrupts, API errors, "No response requested" —
233
+ // are written with an all-zero usage block rather than none at all. They
234
+ // never carried a prompt, so treating one as the newest turn zeroed a
235
+ // counter that a live event had just set correctly.
236
+ if (rowInputTokens === 0 && rowOutputTokens === 0) {
237
+ continue;
163
238
  }
239
+
240
+ cacheReadTokens = rowCacheReadTokens;
241
+ cacheCreationTokens = rowCacheCreationTokens;
242
+ inputTokens = rowInputTokens;
243
+ outputTokens = rowOutputTokens;
244
+ break;
164
245
  }
165
246
 
166
247
  const parsedContextWindow = Number.parseInt(configuredContextWindow ?? '', 10);
@@ -179,6 +260,22 @@ function readClaudeTokenUsage(fileContent: string, configuredContextWindow: stri
179
260
  };
180
261
  }
181
262
 
263
+ function parseClaudeUsageEntries(fileContent: string): AnyRecord[] {
264
+ const entries: AnyRecord[] = [];
265
+ for (const line of fileContent.trim().split('\n')) {
266
+ try {
267
+ entries.push(JSON.parse(line) as AnyRecord);
268
+ } catch {
269
+ // Skip malformed lines without discarding usage from earlier messages.
270
+ }
271
+ }
272
+ return entries;
273
+ }
274
+
275
+ function claudeEntriesHaveUsage(entries: AnyRecord[]): boolean {
276
+ return entries.some((entry) => entry?.type === 'assistant' && entry.message?.usage);
277
+ }
278
+
182
279
  function readOpenCodeTokenUsage(databasePath: string, providerSessionId: string): TokenUsageResult {
183
280
  const database = new Database(databasePath, { readonly: true, fileMustExist: true });
184
281
  try {
@@ -264,6 +361,24 @@ export function createProviderTokenUsageService(
264
361
  });
265
362
  }
266
363
 
364
+ // The fallback covers rows whose provider id was never recorded, and for
365
+ // a session discovered from disk the app id *is* its provider id. That
366
+ // stops being true the moment an edit rewinds the conversation off a
367
+ // thread: until the replacement run announces its own id, the fallback
368
+ // would resolve the retired transcript and report the discarded
369
+ // conversation's usage against an empty one.
370
+ if (
371
+ !session.provider_session_id
372
+ && dependencies.isProviderSessionSuperseded(sessionId, session.provider)
373
+ ) {
374
+ return {
375
+ used: 0,
376
+ inputTokens: 0,
377
+ outputTokens: 0,
378
+ breakdown: { input: 0, output: 0 },
379
+ };
380
+ }
381
+
267
382
  const providerSessionId = session.provider_session_id || sessionId;
268
383
 
269
384
  if (session.provider === 'cursor') {
@@ -307,8 +422,16 @@ export function createProviderTokenUsageService(
307
422
  });
308
423
  }
309
424
 
310
- const fileContent = await dependencies.readTextFile(sessionFilePath);
311
- return readCodexTokenUsage(fileContent);
425
+ const tail = await dependencies.readTextFileTail(sessionFilePath, TOKEN_USAGE_TAIL_BYTES);
426
+ const tailUsage = findCodexTokenUsage(tail.content);
427
+ if (tailUsage || tail.isComplete) {
428
+ return tailUsage ?? emptyCodexTokenUsage();
429
+ }
430
+
431
+ // A tail this large with no token_count row is pathological, but the
432
+ // whole file is still authoritative when it happens.
433
+ return findCodexTokenUsage(await dependencies.readTextFile(sessionFilePath))
434
+ ?? emptyCodexTokenUsage();
312
435
  }
313
436
 
314
437
  let sessionFilePath = session.jsonl_path;
@@ -345,8 +468,12 @@ export function createProviderTokenUsageService(
345
468
  });
346
469
  }
347
470
 
348
- const fileContent = await dependencies.readTextFile(sessionFilePath);
349
- return readClaudeTokenUsage(fileContent, dependencies.getClaudeContextWindow());
471
+ const tail = await dependencies.readTextFileTail(sessionFilePath, TOKEN_USAGE_TAIL_BYTES);
472
+ let entries = parseClaudeUsageEntries(tail.content);
473
+ if (!claudeEntriesHaveUsage(entries) && !tail.isComplete) {
474
+ entries = parseClaudeUsageEntries(await dependencies.readTextFile(sessionFilePath));
475
+ }
476
+ return summarizeClaudeTokenUsage(entries, dependencies.getClaudeContextWindow());
350
477
  },
351
478
  };
352
479
  }
@@ -5,6 +5,7 @@ import readline from 'node:readline';
5
5
  import { spawn } from 'cross-spawn';
6
6
  import { rgPath } from '@vscode/ripgrep';
7
7
 
8
+ import { stripAnsiSequences } from '@/shared/utils.js';
8
9
  import { projectsDb, sessionsDb } from '@/modules/database/index.js';
9
10
 
10
11
  type AnyRecord = Record<string, any>;
@@ -409,10 +410,6 @@ function buildClaudeLocalCommandDisplayText(payload: ClaudeLocalCommandPayload):
409
410
  return commandArgs ? `${baseCommand} ${commandArgs}` : baseCommand;
410
411
  }
411
412
 
412
- function stripAnsiFormatting(text: string): string {
413
- return text.replace(/\u001B\[[0-9;?]*[ -/]*[@-~]/g, '');
414
- }
415
-
416
413
  type ClaudeSearchableMessage = {
417
414
  text: string;
418
415
  role: 'user' | 'assistant';
@@ -456,7 +453,7 @@ function extractClaudeSearchableMessage(entry: AnyRecord): ClaudeSearchableMessa
456
453
 
457
454
  const localCommandStdout = extractTaggedContent(content, 'local-command-stdout');
458
455
  if (localCommandStdout !== null) {
459
- const stdoutText = stripAnsiFormatting(localCommandStdout).trim();
456
+ const stdoutText = stripAnsiSequences(localCommandStdout).trim();
460
457
  return stdoutText
461
458
  ? {
462
459
  text: stdoutText,
@@ -0,0 +1,150 @@
1
+ import fsp from 'node:fs/promises';
2
+
3
+ import type { FetchHistoryResult } from '@/shared/types.js';
4
+
5
+ /**
6
+ * Full-transcript cache for session history reads.
7
+ *
8
+ * Every provider history reader materializes the complete normalized
9
+ * transcript and then slices out the requested page, so serving a 20-row page
10
+ * of a large session re-read and re-parsed the whole transcript file on every
11
+ * request — opening a session, each older page while scrolling up, and every
12
+ * post-turn refresh. For a multi-megabyte JSONL that is most of a second of
13
+ * CPU per request.
14
+ *
15
+ * Entries are keyed by app session id and validated with one `stat` per
16
+ * request against the transcript file's identity (path + mtime + size), so
17
+ * only the first read after the file changes pays the parse. Anything that
18
+ * rewrites history (a new turn, an edit, a rewind, a fork) touches the file
19
+ * and invalidates naturally.
20
+ *
21
+ * One thing the file cannot tell: whether the CLI process behind the session
22
+ * is still running. Claude's history reader answers a background agent's
23
+ * status from that, so a session's entry is also dropped explicitly when its
24
+ * process starts or ends (`invalidate`) — otherwise a page read while the
25
+ * process was up would keep reporting `running` after it had gone, until the
26
+ * next row happened to land in the file.
27
+ *
28
+ * Only history readers that read `jsonl_path` itself may use this cache —
29
+ * callers pass `transcriptPath: null` for providers whose messages live
30
+ * elsewhere (Cursor's store.db, OpenCode's shared SQLite), which bypasses
31
+ * caching entirely.
32
+ */
33
+
34
+ type CacheEntry = {
35
+ transcriptPath: string;
36
+ mtimeMs: number;
37
+ /** File size in bytes; doubles as the entry's cost against the byte budget. */
38
+ size: number;
39
+ full: FetchHistoryResult;
40
+ };
41
+
42
+ type GetFullHistoryArgs = {
43
+ sessionId: string;
44
+ /** Path of the file the provider's history reader actually parses, or null to bypass. */
45
+ transcriptPath: string | null | undefined;
46
+ /** Loads the complete transcript (`limit: null, offset: 0`) from the provider. */
47
+ loadFull: () => Promise<FetchHistoryResult>;
48
+ };
49
+
50
+ /**
51
+ * A transcript entry's heap cost is roughly the file it was parsed from, so
52
+ * the budget is expressed in file bytes. The newest entry is always retained
53
+ * even when it alone exceeds the budget — evicting it would just re-parse the
54
+ * same file on the next request.
55
+ */
56
+ const MAX_CACHED_TRANSCRIPT_FILE_BYTES = 256 * 1024 * 1024;
57
+ const MAX_CACHE_ENTRIES = 8;
58
+
59
+ export function createSessionHistoryCache(
60
+ maxTotalFileBytes = MAX_CACHED_TRANSCRIPT_FILE_BYTES,
61
+ maxEntries = MAX_CACHE_ENTRIES,
62
+ ) {
63
+ const entries = new Map<string, CacheEntry>();
64
+ const pendingLoads = new Map<string, Promise<FetchHistoryResult>>();
65
+
66
+ function evictOverBudget(): void {
67
+ let totalBytes = 0;
68
+ for (const entry of entries.values()) {
69
+ totalBytes += entry.size;
70
+ }
71
+ for (const key of entries.keys()) {
72
+ if (entries.size <= 1 || (totalBytes <= maxTotalFileBytes && entries.size <= maxEntries)) {
73
+ break;
74
+ }
75
+ totalBytes -= entries.get(key)!.size;
76
+ entries.delete(key);
77
+ }
78
+ }
79
+
80
+ return {
81
+ /**
82
+ * Returns the session's full transcript through the cache, or null when
83
+ * the session is not cacheable (no transcript path, or the file cannot be
84
+ * stat'ed) — the caller then falls back to a plain provider read.
85
+ */
86
+ async getFullHistory({ sessionId, transcriptPath, loadFull }: GetFullHistoryArgs): Promise<FetchHistoryResult | null> {
87
+ if (!transcriptPath) {
88
+ return null;
89
+ }
90
+
91
+ let stat;
92
+ try {
93
+ stat = await fsp.stat(transcriptPath);
94
+ } catch {
95
+ entries.delete(sessionId);
96
+ return null;
97
+ }
98
+ if (!stat.isFile()) {
99
+ entries.delete(sessionId);
100
+ return null;
101
+ }
102
+
103
+ const cached = entries.get(sessionId);
104
+ if (
105
+ cached
106
+ && cached.transcriptPath === transcriptPath
107
+ && cached.mtimeMs === stat.mtimeMs
108
+ && cached.size === stat.size
109
+ ) {
110
+ // Re-insert to mark as most recently used.
111
+ entries.delete(sessionId);
112
+ entries.set(sessionId, cached);
113
+ return cached.full;
114
+ }
115
+
116
+ // Concurrent requests for the same session share one parse. The file may
117
+ // gain rows while the load runs; the pre-load stat is what the entry is
118
+ // keyed by, so the next request would see a changed stat and re-read.
119
+ const pending = pendingLoads.get(sessionId);
120
+ if (pending) {
121
+ return pending;
122
+ }
123
+
124
+ const load = loadFull().then((full) => {
125
+ entries.delete(sessionId);
126
+ entries.set(sessionId, {
127
+ transcriptPath,
128
+ mtimeMs: stat.mtimeMs,
129
+ size: stat.size,
130
+ full,
131
+ });
132
+ evictOverBudget();
133
+ return full;
134
+ });
135
+ pendingLoads.set(sessionId, load);
136
+ try {
137
+ return await load;
138
+ } finally {
139
+ pendingLoads.delete(sessionId);
140
+ }
141
+ },
142
+
143
+ /** Forgets a session's entry so the next read re-parses its transcript. */
144
+ invalidate(sessionId: string): void {
145
+ entries.delete(sessionId);
146
+ },
147
+ };
148
+ }
149
+
150
+ export const sessionHistoryCache = createSessionHistoryCache();