@cloudcli-ai/cloudcli 1.37.0 → 1.37.2

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 (242) hide show
  1. package/dist/assets/abnfDiagram-N423BO3Z-C4UAoHJb.js +1 -0
  2. package/dist/assets/arc-B8jF-ja9.js +1 -0
  3. package/dist/assets/architectureDiagram-T3A2C74G-C_Wur_CU.js +36 -0
  4. package/dist/assets/blockDiagram-VBNYF7ZC-Cq4P1i8t.js +132 -0
  5. package/dist/assets/c4Diagram-5PPSVZJV-C1wG4eUj.js +10 -0
  6. package/dist/assets/channel-_falYbU1.js +1 -0
  7. package/dist/assets/chunk-2GRJ4B5K-B8fnBG6E.js +1 -0
  8. package/dist/assets/chunk-2Q5K7J3B-WdfPooPJ.js +1 -0
  9. package/dist/assets/chunk-5RXB4S5H-DhZJuG2E.js +231 -0
  10. package/dist/assets/chunk-5VM5RSS4-B0rjslal.js +15 -0
  11. package/dist/assets/chunk-6Q2QTUOP-CG2Oj4jL.js +88 -0
  12. package/dist/assets/chunk-GF5L2VYU-rrl2lRxY.js +206 -0
  13. package/dist/assets/chunk-JWPE2WC7-CjVbrVMA.js +1 -0
  14. package/dist/assets/chunk-KBJHAD2P-CRzKgU8S.js +1 -0
  15. package/dist/assets/chunk-RYQCIY6F-DBz4GTZF.js +1 -0
  16. package/dist/assets/chunk-XXDRQBXY-wHxalsXk.js +1 -0
  17. package/dist/assets/classDiagram-JCYQIIEL-Dn-3CfpR.js +1 -0
  18. package/dist/assets/classDiagram-v2-OCEON4UE-Dn-3CfpR.js +1 -0
  19. package/dist/assets/cose-bilkent-JH36ORCC-s1hygY8e.js +1 -0
  20. package/dist/assets/cynefin-VYW2F7L2-C4LGV15i.js +178 -0
  21. package/dist/assets/cynefinDiagram-MW4NZA55-C5uhu7B3.js +62 -0
  22. package/dist/assets/cytoscape.esm-C10L9Icf.js +331 -0
  23. package/dist/assets/dagre-VZM6K2ZE-B-OBTewm.js +4 -0
  24. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  25. package/dist/assets/diagram-7IWD3JNH-BjGKQxih.js +30 -0
  26. package/dist/assets/diagram-B4RE2ZJO-BMKz1X2L.js +3 -0
  27. package/dist/assets/diagram-LBJQPF4R-DNh-GhKC.js +24 -0
  28. package/dist/assets/diagram-Q27KOJAE-BaTG9jPT.js +24 -0
  29. package/dist/assets/diagram-UB23O5K3-BUwo2OvJ.js +41 -0
  30. package/dist/assets/ebnfDiagram-BXEA7PRR-DAZgfzX2.js +1 -0
  31. package/dist/assets/erDiagram-JOGREHBK-1w1CcGEb.js +85 -0
  32. package/dist/assets/flowDiagram-UKHOOZJN-D4EII8Jj.js +156 -0
  33. package/dist/assets/ganttDiagram-PKOTCBZU-ChvJZAgB.js +292 -0
  34. package/dist/assets/gitGraphDiagram-DS77QQ5N-DtOn41S8.js +106 -0
  35. package/dist/assets/graph-Cx-EqisW.js +1 -0
  36. package/dist/assets/index-CzPLBvXT.css +32 -0
  37. package/dist/assets/index-DFkBBuSA.js +1549 -0
  38. package/dist/assets/infoDiagram-6WML65LV-DN_PVEJZ.js +2 -0
  39. package/dist/assets/init-Gi6I4Gst.js +1 -0
  40. package/dist/assets/ishikawaDiagram-WSZJBQD7-DbkF1kXb.js +70 -0
  41. package/dist/assets/journeyDiagram-NVQOT4AX-pJwmhddE.js +139 -0
  42. package/dist/assets/kanban-definition-27J2QSJJ-PPAPgNp5.js +89 -0
  43. package/dist/assets/layout-D6BstSzF.js +1 -0
  44. package/dist/assets/linear-iPR-J9f7.js +1 -0
  45. package/dist/assets/map-CV9uif0g.js +1 -0
  46. package/dist/assets/mermaid.core-BEUL86Z6.js +312 -0
  47. package/dist/assets/mindmap-definition-FAOFIHXS-BvZoBib7.js +96 -0
  48. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  49. package/dist/assets/pegDiagram-VL7TDLO6-DhYreaUd.js +1 -0
  50. package/dist/assets/pieDiagram-7S7Q4E2Y-CrOHlyD0.js +39 -0
  51. package/dist/assets/quadrantDiagram-CIZ2JOQS-D4g5yH3t.js +7 -0
  52. package/dist/assets/railroadDiagram-AXF67PYL-Dj39lHlf.js +1 -0
  53. package/dist/assets/requirementDiagram-LRYGKXZP-1AS4Zgm_.js +84 -0
  54. package/dist/assets/sankeyDiagram-W5VNT64P-CKlMn19P.js +40 -0
  55. package/dist/assets/sequenceDiagram-SI44F4Z6-B_j3qyvo.js +162 -0
  56. package/dist/assets/sizeCapture-X5ZJPWSS-Bp2Op16v.js +1 -0
  57. package/dist/assets/stateDiagram-OKZ733FA-C0cN6DoY.js +1 -0
  58. package/dist/assets/stateDiagram-v2-UEYNNEHI-BvBxUUrZ.js +1 -0
  59. package/dist/assets/swimlanes-SLNWSIFB-BEvXOV9b.js +2 -0
  60. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CsE-x4YK.js +8 -0
  61. package/dist/assets/timeline-definition-Z64GVDOM-zdJQinvZ.js +120 -0
  62. package/dist/assets/vennDiagram-T6HMQDX7-DuF1uZMY.js +34 -0
  63. package/dist/assets/wardleyDiagram-T6FBY63Y-B9AlLQup.js +78 -0
  64. package/dist/assets/xychartDiagram-ELKLHX3M-DTJX48Qg.js +7 -0
  65. package/dist/index.html +2 -2
  66. package/dist-server/server/index.js +1 -2
  67. package/dist-server/server/index.js.map +1 -1
  68. package/dist-server/server/modules/agent/agent.module.js +2 -1
  69. package/dist-server/server/modules/agent/agent.module.js.map +1 -1
  70. package/dist-server/server/modules/auth/auth.middleware.js +1 -1
  71. package/dist-server/server/modules/auth/auth.middleware.js.map +1 -1
  72. package/dist-server/server/modules/browser-use/browser-use-runtime.js +10 -0
  73. package/dist-server/server/modules/browser-use/browser-use-runtime.js.map +1 -0
  74. package/dist-server/server/modules/browser-use/browser-use.service.js +3 -6
  75. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  76. package/dist-server/server/modules/browser-use/index.js +2 -0
  77. package/dist-server/server/modules/browser-use/index.js.map +1 -1
  78. package/dist-server/server/modules/cli/cli.js +9 -3
  79. package/dist-server/server/modules/cli/cli.js.map +1 -1
  80. package/dist-server/server/modules/cli/cli.module.js +0 -1
  81. package/dist-server/server/modules/cli/cli.module.js.map +1 -1
  82. package/dist-server/server/modules/cli/cli.service.js +0 -18
  83. package/dist-server/server/modules/cli/cli.service.js.map +1 -1
  84. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +154 -0
  85. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js.map +1 -0
  86. package/dist-server/server/modules/cli/tests/cli.service.test.js +0 -3
  87. package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -1
  88. package/dist-server/server/modules/commands/commands.routes.js +3 -3
  89. package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
  90. package/dist-server/server/modules/commands/tests/commands.test.js +2 -7
  91. package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -1
  92. package/dist-server/server/modules/database/index.js +2 -0
  93. package/dist-server/server/modules/database/index.js.map +1 -1
  94. package/dist-server/server/modules/database/migrations.js +18 -1
  95. package/dist-server/server/modules/database/migrations.js.map +1 -1
  96. package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
  97. package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
  98. package/dist-server/server/modules/database/repositories/sessions.db.js +63 -7
  99. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  100. package/dist-server/server/modules/database/schema.js +28 -3
  101. package/dist-server/server/modules/database/schema.js.map +1 -1
  102. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
  103. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
  104. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -1
  105. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
  106. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +22 -0
  107. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  108. package/dist-server/server/modules/file-tree/file-tree.module.js +5 -1
  109. package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
  110. package/dist-server/server/modules/file-tree/file-tree.service.js +60 -20
  111. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
  112. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +143 -16
  113. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
  114. package/dist-server/server/modules/git/git-branch.service.js +15 -0
  115. package/dist-server/server/modules/git/git-branch.service.js.map +1 -0
  116. package/dist-server/server/modules/git/git.routes.js +15 -7
  117. package/dist-server/server/modules/git/git.routes.js.map +1 -1
  118. package/dist-server/server/modules/git/tests/git-branch.service.test.js +52 -0
  119. package/dist-server/server/modules/git/tests/git-branch.service.test.js.map +1 -0
  120. package/dist-server/server/modules/git/tests/git-init.routes.test.js +84 -0
  121. package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -1
  122. package/dist-server/server/modules/notifications/index.js +3 -1
  123. package/dist-server/server/modules/notifications/index.js.map +1 -1
  124. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +22 -1
  125. package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -1
  126. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +46 -14
  127. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  128. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +259 -60
  129. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
  130. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +58 -71
  131. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  132. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +1 -54
  133. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  134. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +7 -7
  135. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  136. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +32 -669
  137. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
  138. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +105 -328
  139. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  140. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +0 -12
  141. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  142. package/dist-server/server/modules/providers/provider.routes.js +144 -31
  143. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  144. package/dist-server/server/modules/providers/services/provider-models.service.js +167 -199
  145. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  146. package/dist-server/server/modules/providers/services/provider-runtime.service.js +2 -2
  147. package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
  148. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +75 -11
  149. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  150. package/dist-server/server/modules/providers/services/sessions.service.js +62 -3
  151. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  152. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +53 -17
  153. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  154. package/dist-server/server/modules/providers/tests/opencode-models.test.js +15 -126
  155. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  156. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +5 -5
  157. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  158. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +175 -292
  159. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
  160. package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +2 -6
  161. package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -1
  162. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +1 -0
  163. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
  164. package/dist-server/server/modules/providers/tests/provider.routes.test.js +161 -0
  165. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
  166. package/dist-server/server/modules/providers/tests/sessions.service.test.js +87 -0
  167. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
  168. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +5 -1
  169. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  170. package/dist-server/server/shared/utils.js +8 -0
  171. package/dist-server/server/shared/utils.js.map +1 -1
  172. package/package.json +8 -4
  173. package/scripts/promote-dist-server.mjs +50 -0
  174. package/server/index.ts +1 -2
  175. package/server/modules/agent/agent.module.ts +2 -1
  176. package/server/modules/auth/auth.middleware.ts +2 -2
  177. package/server/modules/browser-use/browser-use-runtime.ts +10 -0
  178. package/server/modules/browser-use/browser-use.service.ts +5 -8
  179. package/server/modules/browser-use/index.ts +3 -0
  180. package/server/modules/cli/cli.module.ts +0 -1
  181. package/server/modules/cli/cli.service.ts +0 -20
  182. package/server/modules/cli/cli.ts +9 -3
  183. package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -0
  184. package/server/modules/cli/tests/cli.service.test.ts +0 -3
  185. package/server/modules/commands/commands.routes.ts +3 -3
  186. package/server/modules/commands/tests/commands.test.ts +2 -7
  187. package/server/modules/database/index.ts +2 -0
  188. package/server/modules/database/migrations.ts +20 -0
  189. package/server/modules/database/repositories/provider-models.ts +168 -0
  190. package/server/modules/database/repositories/sessions.db.ts +85 -8
  191. package/server/modules/database/schema.ts +29 -3
  192. package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
  193. package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -1
  194. package/server/modules/database/tests/sessions.db.integration.test.ts +32 -0
  195. package/server/modules/file-tree/file-tree.module.ts +5 -1
  196. package/server/modules/file-tree/file-tree.service.ts +81 -22
  197. package/server/modules/file-tree/tests/file-tree.service.test.ts +167 -16
  198. package/server/modules/git/git-branch.service.ts +43 -0
  199. package/server/modules/git/git.routes.ts +16 -9
  200. package/server/modules/git/tests/git-branch.service.test.ts +60 -0
  201. package/server/modules/git/tests/git-init.routes.test.ts +83 -0
  202. package/server/modules/notifications/index.ts +2 -0
  203. package/server/modules/notifications/services/notification-orchestrator.service.js +24 -1
  204. package/server/modules/providers/list/claude/claude-models.provider.ts +46 -14
  205. package/server/modules/providers/list/claude/claude-runtime.provider.js +280 -67
  206. package/server/modules/providers/list/codex/codex-models.provider.ts +58 -99
  207. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +1 -60
  208. package/server/modules/providers/list/codex/codex-sessions.provider.ts +7 -7
  209. package/server/modules/providers/list/cursor/cursor-models.provider.ts +32 -710
  210. package/server/modules/providers/list/opencode/opencode-models.provider.ts +105 -413
  211. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +0 -13
  212. package/server/modules/providers/provider.routes.ts +190 -33
  213. package/server/modules/providers/services/provider-models.service.ts +223 -288
  214. package/server/modules/providers/services/provider-runtime.service.ts +2 -3
  215. package/server/modules/providers/services/session-conversations-search.service.ts +106 -11
  216. package/server/modules/providers/services/sessions.service.ts +87 -3
  217. package/server/modules/providers/tests/codex-sessions.test.ts +57 -18
  218. package/server/modules/providers/tests/opencode-models.test.ts +34 -135
  219. package/server/modules/providers/tests/opencode-sessions.test.ts +5 -5
  220. package/server/modules/providers/tests/provider-models.service.test.ts +353 -450
  221. package/server/modules/providers/tests/provider-runtime.service.test.ts +2 -6
  222. package/server/modules/providers/tests/provider-token-usage.service.test.ts +1 -0
  223. package/server/modules/providers/tests/provider.routes.test.ts +244 -0
  224. package/server/modules/providers/tests/sessions.service.test.ts +127 -0
  225. package/server/modules/websocket/services/chat-websocket.service.ts +5 -1
  226. package/server/shared/interfaces.ts +4 -6
  227. package/server/shared/types.ts +29 -18
  228. package/server/shared/utils.ts +10 -0
  229. package/server/tsconfig.json +6 -2
  230. package/README.de.md +0 -257
  231. package/README.ja.md +0 -249
  232. package/README.ko.md +0 -249
  233. package/README.md +0 -269
  234. package/README.ru.md +0 -257
  235. package/README.tr.md +0 -258
  236. package/README.zh-CN.md +0 -249
  237. package/README.zh-TW.md +0 -249
  238. package/dist/assets/index-DSlds633.css +0 -32
  239. package/dist/assets/index-eGqPeIEu.js +0 -1531
  240. package/dist-server/server/constants/config.js +0 -6
  241. package/dist-server/server/constants/config.js.map +0 -1
  242. package/server/constants/config.ts +0 -5
package/server/index.ts CHANGED
@@ -9,7 +9,7 @@ import http from 'http';
9
9
  import express, { type NextFunction, type Request, type Response } from 'express';
10
10
  import cors from 'cors';
11
11
 
12
- import { AppError, findApplicationRoot, getModuleDirectory, terminalTextStyles } from '@/shared/utils.js';
12
+ import { AppError, findApplicationRoot, getModuleDirectory, IS_PLATFORM, terminalTextStyles } from '@/shared/utils.js';
13
13
  import {
14
14
  closeSessionsWatcher,
15
15
  initializeSessionsWatcher,
@@ -50,7 +50,6 @@ import browserUseMcpRoutes from './modules/browser-use/browser-use-mcp.routes.js
50
50
  import { browserUseService } from './modules/browser-use/browser-use.service.js';
51
51
  import { initializeDatabase, sessionsDb } from './modules/database/index.js';
52
52
  import { configureWebPush } from './modules/notifications/index.js';
53
- import { IS_PLATFORM } from './constants/config.js';
54
53
 
55
54
  const __dirname = getModuleDirectory(import.meta.url);
56
55
  // The server source runs from /server, while the compiled output runs from /dist-server/server.
@@ -12,6 +12,7 @@ import {
12
12
  userDb,
13
13
  } from '@/modules/database/index.js';
14
14
  import { providerModelsService } from '@/modules/providers/index.js';
15
+ import { IS_PLATFORM } from '@/shared/utils.js';
15
16
 
16
17
  import { createAgentRouter } from './agent.routes.js';
17
18
 
@@ -30,7 +31,7 @@ export function createAgentModule(externalDependencies: AgentExternalDependencie
30
31
  crypto,
31
32
  homeDirectory: os.homedir,
32
33
  spawnProcess: spawn,
33
- platformMode: process.env.VITE_IS_PLATFORM === 'true',
34
+ platformMode: IS_PLATFORM,
34
35
  users: {
35
36
  getFirstUser: () => userDb.getFirstUser(),
36
37
  },
@@ -1,9 +1,9 @@
1
1
  // @ts-nocheck -- JWT request augmentation is narrowed by Auth route contracts.
2
2
  import jwt from 'jsonwebtoken';
3
3
 
4
- import { userDb, appConfigDb } from '../database/index.js';
4
+ import { IS_PLATFORM } from '@/shared/utils.js';
5
5
 
6
- const IS_PLATFORM = process.env.VITE_IS_PLATFORM === 'true';
6
+ import { userDb, appConfigDb } from '../database/index.js';
7
7
 
8
8
  // Use env var if set, otherwise auto-generate a unique secret per installation
9
9
  const JWT_SECRET = process.env.JWT_SECRET || appConfigDb.getOrCreateJwtSecret();
@@ -0,0 +1,10 @@
1
+ import { IS_PLATFORM } from '@/shared/utils.js';
2
+
3
+ /**
4
+ * Resolves the runtime label exposed by Browser Use status and session APIs.
5
+ * The Browser Use service and CLI environment-mode regression coverage consume
6
+ * this after the server or CLI entrypoint has completed environment bootstrapping.
7
+ */
8
+ export function getBrowserUseRuntime(): 'cloud' | 'local' {
9
+ return IS_PLATFORM ? 'cloud' : 'local';
10
+ }
@@ -11,15 +11,16 @@ import { appConfigDb } from '@/modules/database/index.js';
11
11
  import { providerMcpService } from '@/modules/providers/index.js';
12
12
  import { getModuleDirectory } from '@/shared/utils.js';
13
13
 
14
+ import { getBrowserUseRuntime } from './browser-use-runtime.js';
15
+
14
16
  const require = createRequire(import.meta.url);
15
17
  const __dirname = getModuleDirectory(import.meta.url);
16
- const IS_PLATFORM = process.env.VITE_IS_PLATFORM === 'true';
17
18
  const MAX_SESSIONS_PER_OWNER = Number.parseInt(process.env.CLOUDCLI_BROWSER_USE_MAX_SESSIONS_PER_OWNER || '3', 10);
18
19
  const SESSION_TTL_MS = Number.parseInt(process.env.CLOUDCLI_BROWSER_USE_SESSION_TTL_MS || String(30 * 60 * 1000), 10);
19
20
  const BROWSER_USE_SETTINGS_KEY = 'browser_use_settings';
20
21
  const BROWSER_USE_MCP_TOKEN_KEY = 'browser_use_mcp_token';
21
22
 
22
- type BrowserUseRuntime = 'cloud' | 'local';
23
+ type BrowserUseRuntime = ReturnType<typeof getBrowserUseRuntime>;
23
24
  type BrowserUseSessionStatus = 'ready' | 'stopped' | 'unavailable';
24
25
 
25
26
  type BrowserUseSession = {
@@ -85,10 +86,6 @@ const MCP_SERVER_NAME = 'cloudcli-browser';
85
86
  const LEGACY_MCP_SERVER_NAMES = ['cloudcli-browser-use'];
86
87
  const RUNTIME_READINESS_CACHE_TTL_MS = 30_000;
87
88
 
88
- function getRuntime(): BrowserUseRuntime {
89
- return IS_PLATFORM ? 'cloud' : 'local';
90
- }
91
-
92
89
  function readSettings(): BrowserUseSettings {
93
90
  try {
94
91
  const raw = appConfigDb.get(BROWSER_USE_SETTINGS_KEY);
@@ -446,7 +443,7 @@ export const browserUseService = {
446
443
 
447
444
  return {
448
445
  enabled: settings.enabled,
449
- runtime: getRuntime(),
446
+ runtime: getBrowserUseRuntime(),
450
447
  available,
451
448
  playwrightInstalled: readiness.playwrightInstalled,
452
449
  chromiumInstalled: readiness.chromiumInstalled,
@@ -515,7 +512,7 @@ export const browserUseService = {
515
512
  id: randomUUID(),
516
513
  ownerId: AGENT_OWNER_ID,
517
514
  createdBy: 'agent',
518
- runtime: getRuntime(),
515
+ runtime: getBrowserUseRuntime(),
519
516
  status: 'unavailable',
520
517
  url: null,
521
518
  title: null,
@@ -6,3 +6,6 @@
6
6
  export async function startBrowserUseMcp(): Promise<void> {
7
7
  await import('./browser-use-mcp.js');
8
8
  }
9
+
10
+ // getBrowserUseRuntime: used by Browser Use services and environment-mode regression coverage.
11
+ export { getBrowserUseRuntime } from './browser-use-runtime.js';
@@ -28,7 +28,6 @@ export function createCliApplication(): CliApplication {
28
28
  bugsUrl: packageMetadataJson.bugs?.url,
29
29
  };
30
30
  const fileSystem = {
31
- readTextFile: (filePath: string) => fs.readFileSync(filePath, 'utf8'),
32
31
  pathExists: (filePath: string) => fs.existsSync(filePath),
33
32
  getFileStats: (filePath: string) => {
34
33
  const stats = fs.statSync(filePath);
@@ -77,27 +77,7 @@ function isNewerVersion(candidateVersion: string, currentVersion: string): boole
77
77
  return false;
78
78
  }
79
79
 
80
- function loadEnvironmentFile(dependencies: CliServiceDependencies): void {
81
- try {
82
- const environmentFile = dependencies.fileSystem.readTextFile(
83
- path.join(dependencies.applicationRoot, '.env'),
84
- );
85
- for (const line of environmentFile.split('\n')) {
86
- const trimmedLine = line.trim();
87
- if (!trimmedLine || trimmedLine.startsWith('#')) continue;
88
-
89
- const [key, ...valueParts] = trimmedLine.split('=');
90
- if (key && valueParts.length > 0 && !dependencies.environment[key]) {
91
- dependencies.environment[key] = valueParts.join('=').trim();
92
- }
93
- }
94
- } catch {
95
- // The repository-level .env file is optional for every CLI command.
96
- }
97
- }
98
-
99
80
  function showStatus(dependencies: CliServiceDependencies): void {
100
- loadEnvironmentFile(dependencies);
101
81
  const { environment, fileSystem, output } = dependencies;
102
82
  const databasePath = environment.DATABASE_PATH || dependencies.defaultDatabasePath;
103
83
  const databaseExists = fileSystem.pathExists(databasePath);
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createCliApplication } from './index.js';
3
+ // The CLI can reach shared environment constants through its composition root,
4
+ // so finish the root environment bootstrap before loading that import graph.
5
+ // eslint-disable-next-line boundaries/no-unknown
6
+ import '../../load-env.js';
4
7
 
5
- const cliApplication = createCliApplication();
8
+ async function runCli(): Promise<number> {
9
+ const { createCliApplication } = await import('./index.js');
10
+ return createCliApplication().run(process.argv.slice(2));
11
+ }
6
12
 
7
- cliApplication.run(process.argv.slice(2)).then((exitCode) => {
13
+ runCli().then((exitCode) => {
8
14
  process.exitCode = exitCode;
9
15
  }).catch((error: unknown) => {
10
16
  const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,186 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { copyFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import test from 'node:test';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+
9
+ type BootstrapResult = {
10
+ command: string | null;
11
+ environmentFileReads: number;
12
+ mode: string | null;
13
+ authNextCalled: boolean;
14
+ authStatus: number;
15
+ authenticatedUsername: string | null;
16
+ runtime: string;
17
+ };
18
+
19
+ const RESULT_PREFIX = '__CLOUDCLI_BOOTSTRAP_RESULT__';
20
+ const testDirectory = path.dirname(fileURLToPath(import.meta.url));
21
+ const applicationRoot = path.resolve(testDirectory, '..', '..', '..', '..');
22
+
23
+ function sourceModuleUrl(...segments: string[]): string {
24
+ return pathToFileURL(path.join(applicationRoot, ...segments)).href;
25
+ }
26
+
27
+ async function runCliBootstrapFixture(isPlatform: boolean): Promise<BootstrapResult> {
28
+ const fixtureRoot = await mkdtemp(path.join(os.tmpdir(), 'cloudcli-cli-bootstrap-'));
29
+ const fixtureServerDirectory = path.join(fixtureRoot, 'server');
30
+ const fixtureCliDirectory = path.join(fixtureServerDirectory, 'modules', 'cli');
31
+ const fixtureDatabasePath = path.join(fixtureRoot, 'auth.db');
32
+
33
+ try {
34
+ await mkdir(fixtureCliDirectory, { recursive: true });
35
+ await Promise.all([
36
+ copyFile(
37
+ path.join(applicationRoot, 'server', 'load-env.ts'),
38
+ path.join(fixtureServerDirectory, 'load-env.ts'),
39
+ ),
40
+ copyFile(
41
+ path.join(applicationRoot, 'server', 'modules', 'cli', 'cli.ts'),
42
+ path.join(fixtureCliDirectory, 'cli.ts'),
43
+ ),
44
+ ]);
45
+ await writeFile(
46
+ path.join(fixtureRoot, '.env'),
47
+ [
48
+ `VITE_IS_PLATFORM=${String(isPlatform)}`,
49
+ `DATABASE_PATH=${fixtureDatabasePath.replace(/\\/g, '/')}`,
50
+ ].join('\n'),
51
+ 'utf8',
52
+ );
53
+
54
+ const fixtureIndexSource = `
55
+ import { createCliService } from ${JSON.stringify(sourceModuleUrl('server', 'modules', 'cli', 'cli.service.ts'))};
56
+ import { authenticateToken } from ${JSON.stringify(sourceModuleUrl('server', 'modules', 'auth', 'index.ts'))};
57
+ import { getBrowserUseRuntime } from ${JSON.stringify(sourceModuleUrl('server', 'modules', 'browser-use', 'index.ts'))};
58
+ import { closeConnection, initializeDatabase, userDb } from ${JSON.stringify(sourceModuleUrl('server', 'modules', 'database', 'index.ts'))};
59
+
60
+ let environmentFileReads = 0;
61
+
62
+ export function createCliApplication() {
63
+ const service = createCliService({
64
+ applicationRoot: ${JSON.stringify(fixtureRoot)},
65
+ defaultDatabasePath: ${JSON.stringify(fixtureDatabasePath)},
66
+ homeDirectory: ${JSON.stringify(fixtureRoot)},
67
+ packageMetadata: { version: '1.0.0' },
68
+ environment: process.env,
69
+ fileSystem: {
70
+ readTextFile: () => {
71
+ environmentFileReads += 1;
72
+ return '';
73
+ },
74
+ pathExists: () => false,
75
+ getFileStats: () => ({ size: 0, modifiedAt: new Date(0) }),
76
+ },
77
+ output: { log: () => undefined, error: () => undefined },
78
+ sandboxService: { execute: async () => 0 },
79
+ getLatestPackageVersion: async () => '1.0.0',
80
+ updateGlobalPackage: () => undefined,
81
+ startServer: async () => undefined,
82
+ startBrowserUseMcp: async () => undefined,
83
+ });
84
+
85
+ return {
86
+ async run(argumentsList: string[]) {
87
+ const exitCode = await service.run(argumentsList);
88
+ await initializeDatabase();
89
+ userDb.createUser('bootstrap-user', 'password-hash');
90
+
91
+ const request: any = { headers: {}, query: {} };
92
+ const response: any = {
93
+ statusCode: 200,
94
+ body: null,
95
+ headers: {},
96
+ setHeader(name: string, value: string) {
97
+ this.headers[name] = value;
98
+ },
99
+ status(statusCode: number) {
100
+ this.statusCode = statusCode;
101
+ return this;
102
+ },
103
+ json(body: unknown) {
104
+ this.body = body;
105
+ return this;
106
+ },
107
+ };
108
+ let authNextCalled = false;
109
+ await authenticateToken(request, response, () => {
110
+ authNextCalled = true;
111
+ });
112
+ const runtime = getBrowserUseRuntime();
113
+
114
+ console.log(${JSON.stringify(RESULT_PREFIX)} + JSON.stringify({
115
+ command: argumentsList[0] ?? null,
116
+ environmentFileReads,
117
+ mode: process.env.VITE_IS_PLATFORM ?? null,
118
+ authNextCalled,
119
+ authStatus: response.statusCode,
120
+ authenticatedUsername: request.user?.username ?? null,
121
+ runtime,
122
+ }));
123
+ closeConnection();
124
+ return exitCode;
125
+ },
126
+ };
127
+ }
128
+ `;
129
+ await writeFile(path.join(fixtureCliDirectory, 'index.ts'), fixtureIndexSource, 'utf8');
130
+
131
+ const childEnvironment = { ...process.env };
132
+ delete childEnvironment.VITE_IS_PLATFORM;
133
+ delete childEnvironment.DATABASE_PATH;
134
+ const stdout = execFileSync(
135
+ process.execPath,
136
+ [
137
+ path.join(applicationRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs'),
138
+ '--tsconfig',
139
+ path.join(applicationRoot, 'server', 'tsconfig.json'),
140
+ path.join(fixtureCliDirectory, 'cli.ts'),
141
+ 'status',
142
+ ],
143
+ {
144
+ cwd: applicationRoot,
145
+ encoding: 'utf8',
146
+ env: childEnvironment,
147
+ timeout: 30_000,
148
+ },
149
+ );
150
+ const resultLine = stdout
151
+ .split(/\r?\n/)
152
+ .find((line) => line.startsWith(RESULT_PREFIX));
153
+ assert.ok(resultLine, `Missing bootstrap result in child output:\n${stdout}`);
154
+ return JSON.parse(resultLine.slice(RESULT_PREFIX.length)) as BootstrapResult;
155
+ } finally {
156
+ await rm(fixtureRoot, { recursive: true, force: true });
157
+ }
158
+ }
159
+
160
+ test('CLI bootstrap selects Platform authentication and cloud runtime from .env', async () => {
161
+ const result = await runCliBootstrapFixture(true);
162
+
163
+ assert.deepEqual(result, {
164
+ command: 'status',
165
+ environmentFileReads: 0,
166
+ mode: 'true',
167
+ authNextCalled: true,
168
+ authStatus: 200,
169
+ authenticatedUsername: 'bootstrap-user',
170
+ runtime: 'cloud',
171
+ });
172
+ });
173
+
174
+ test('CLI bootstrap selects OSS authentication and local runtime from .env', async () => {
175
+ const result = await runCliBootstrapFixture(false);
176
+
177
+ assert.deepEqual(result, {
178
+ command: 'status',
179
+ environmentFileReads: 0,
180
+ mode: 'false',
181
+ authNextCalled: false,
182
+ authStatus: 401,
183
+ authenticatedUsername: null,
184
+ runtime: 'local',
185
+ });
186
+ });
@@ -26,9 +26,6 @@ function createHarness() {
26
26
  },
27
27
  environment,
28
28
  fileSystem: {
29
- readTextFile: () => {
30
- throw new Error('missing');
31
- },
32
29
  pathExists: () => false,
33
30
  getFileStats: () => ({ size: 0, modifiedAt: new Date(0) }),
34
31
  },
@@ -63,14 +63,15 @@ const resolveCommandModel = async (modelsService, provider, context) => {
63
63
 
64
64
  const executeModelsCommand = async (args, context, modelsService) => {
65
65
  const currentProvider = readModelProvider(context?.provider);
66
- const result = await modelsService.getProviderModels(currentProvider);
67
- const catalog = result.models;
66
+ const catalog = await modelsService.getProviderModels(currentProvider);
68
67
  const currentModel = await resolveCommandModel(modelsService, currentProvider, context);
69
68
  const availableModels = catalog.OPTIONS.map((option) => option.value);
70
69
  const availableOptions = catalog.OPTIONS.map((option) => ({
71
70
  value: option.value,
72
71
  label: option.label,
73
72
  description: option.description,
73
+ recordId: option.recordId,
74
+ isCustom: option.isCustom,
74
75
  }));
75
76
 
76
77
  return {
@@ -88,7 +89,6 @@ const executeModelsCommand = async (args, context, modelsService) => {
88
89
  availableModels,
89
90
  availableOptions,
90
91
  defaultModel: catalog.DEFAULT,
91
- cache: result.cache,
92
92
  message: `Current model: ${currentModel}`,
93
93
  },
94
94
  };
@@ -16,12 +16,8 @@ import { createCommandsRouter } from '../commands.routes.js';
16
16
  function createModelsService(sessionModels: Record<string, string> = {}) {
17
17
  return {
18
18
  getProviderModels: async () => ({
19
- models: { OPTIONS: [{ value: 'default', label: 'Default' }], DEFAULT: 'default' },
20
- cache: {
21
- updatedAt: '2026-01-01T00:00:00.000Z',
22
- expiresAt: '2026-01-02T00:00:00.000Z',
23
- source: 'fresh' as const,
24
- },
19
+ OPTIONS: [{ value: 'default', label: 'Default' }],
20
+ DEFAULT: 'default',
25
21
  }),
26
22
  getCurrentActiveModel: async () => ({ model: 'default' }),
27
23
  setSessionModel: () => null,
@@ -39,7 +35,6 @@ function createModelsService(sessionModels: Record<string, string> = {}) {
39
35
  };
40
36
  },
41
37
  resolveResumeModel: async () => undefined,
42
- clearCache: () => undefined,
43
38
  };
44
39
  }
45
40
 
@@ -6,6 +6,8 @@ export { credentialsDb } from '@/modules/database/repositories/credentials.js';
6
6
  export { githubTokensDb } from '@/modules/database/repositories/github-tokens.js';
7
7
  export { notificationChannelEndpointsDb } from '@/modules/database/repositories/notification-channel-endpoints.js';
8
8
  export { notificationPreferencesDb } from '@/modules/database/repositories/notification-preferences.js';
9
+ // providerModelsDb: used by Providers to persist user-managed custom model rows.
10
+ export { providerModelsDb } from '@/modules/database/repositories/provider-models.js';
9
11
  // projectsDb: used by Projects, Worktrees, Git, WebSocket, and notification modules to persist and resolve project records.
10
12
  export { projectsDb } from '@/modules/database/repositories/projects.db.js';
11
13
  export { pushSubscriptionsDb } from '@/modules/database/repositories/push-subscriptions.js';
@@ -5,6 +5,7 @@ import {
5
5
  LAST_SCANNED_AT_SQL,
6
6
  NOTIFICATION_CHANNEL_ENDPOINTS_TABLE_SCHEMA_SQL,
7
7
  PROJECTS_TABLE_SCHEMA_SQL,
8
+ PROVIDER_MODELS_TABLE_SCHEMA_SQL,
8
9
  PUSH_SUBSCRIPTIONS_TABLE_SCHEMA_SQL,
9
10
  SESSIONS_TABLE_SCHEMA_SQL,
10
11
  USER_NOTIFICATION_PREFERENCES_TABLE_SCHEMA_SQL,
@@ -416,6 +417,19 @@ const addSessionModelColumn = (db: Database): void => {
416
417
  addColumnToTableIfNotExists(db, 'sessions', columnNames, 'model', 'TEXT');
417
418
  };
418
419
 
420
+ /**
421
+ * Adds the `effort` column that records a session's reasoning-effort choice.
422
+ *
423
+ * Existing rows stay NULL so clients can continue falling back to their
424
+ * per-provider preference until the user selects an effort or sends a turn.
425
+ */
426
+ const addSessionEffortColumn = (db: Database): void => {
427
+ const sessionsTableInfo = getTableInfo(db, 'sessions');
428
+ const columnNames = sessionsTableInfo.map((column) => column.name);
429
+
430
+ addColumnToTableIfNotExists(db, 'sessions', columnNames, 'effort', 'TEXT');
431
+ };
432
+
419
433
  const ensureProjectsForSessionPaths = (db: Database): void => {
420
434
  if (!tableExists(db, 'sessions')) {
421
435
  return;
@@ -458,6 +472,11 @@ export const runMigrations = (db: Database) => {
458
472
  db.exec(NOTIFICATION_CHANNEL_ENDPOINTS_TABLE_SCHEMA_SQL);
459
473
  db.exec('CREATE INDEX IF NOT EXISTS idx_notification_channel_endpoints_user_channel ON notification_channel_endpoints(user_id, channel)');
460
474
  db.exec('CREATE INDEX IF NOT EXISTS idx_notification_channel_endpoints_enabled ON notification_channel_endpoints(enabled)');
475
+ db.exec(PROVIDER_MODELS_TABLE_SCHEMA_SQL);
476
+ db.exec(`
477
+ CREATE INDEX IF NOT EXISTS idx_provider_models_provider_order
478
+ ON provider_models(provider, sort_order, id)
479
+ `);
461
480
 
462
481
  db.exec(PROJECTS_TABLE_SCHEMA_SQL);
463
482
  rebuildProjectsTableWithPrimaryKeySchema(db);
@@ -467,6 +486,7 @@ export const runMigrations = (db: Database) => {
467
486
  migrateLegacySessionNames(db);
468
487
  addProviderSessionIdMapping(db);
469
488
  addSessionModelColumn(db);
489
+ addSessionEffortColumn(db);
470
490
  ensureProjectsForSessionPaths(db);
471
491
 
472
492
  db.exec('CREATE INDEX IF NOT EXISTS idx_session_ids_lookup ON sessions(session_id)');
@@ -0,0 +1,168 @@
1
+ import { getConnection } from '@/modules/database/connection.js';
2
+ import type {
3
+ CustomProviderModelInput,
4
+ CustomProviderModelRecord,
5
+ LLMProvider,
6
+ } from '@/shared/types.js';
7
+
8
+ type CustomProviderModelRow = {
9
+ id: number;
10
+ provider: LLMProvider;
11
+ model_id: string;
12
+ model_name: string;
13
+ sort_order: number;
14
+ };
15
+
16
+ const toCustomProviderModelRecord = (
17
+ row: CustomProviderModelRow,
18
+ ): CustomProviderModelRecord => ({
19
+ recordId: row.id,
20
+ provider: row.provider,
21
+ modelId: row.model_id,
22
+ model: row.model_name,
23
+ sortOrder: row.sort_order,
24
+ });
25
+
26
+ const readCustomProviderModelRow = (
27
+ provider: LLMProvider,
28
+ recordId: number,
29
+ ): CustomProviderModelRow | null => {
30
+ const row = getConnection().prepare(`
31
+ SELECT id, provider, model_id, model_name, sort_order
32
+ FROM provider_models
33
+ WHERE provider = ? AND id = ?
34
+ `).get(provider, recordId) as CustomProviderModelRow | undefined;
35
+
36
+ return row ?? null;
37
+ };
38
+
39
+ /**
40
+ * Custom provider-model persistence API consumed by the Providers module.
41
+ *
42
+ * Every row in this repository is user-created. Predefined models are owned by
43
+ * provider adapters and never enter this table. Model-id changes are propagated
44
+ * to stored sessions, while deletion replaces affected session selections with
45
+ * the predefined default supplied by the Providers service.
46
+ */
47
+ export const providerModelsDb = {
48
+ listCustomProviderModels(provider: LLMProvider): CustomProviderModelRecord[] {
49
+ const rows = getConnection().prepare(`
50
+ SELECT id, provider, model_id, model_name, sort_order
51
+ FROM provider_models
52
+ WHERE provider = ?
53
+ ORDER BY sort_order ASC, lower(model_name) ASC, id ASC
54
+ `).all(provider) as CustomProviderModelRow[];
55
+
56
+ return rows.map(toCustomProviderModelRecord);
57
+ },
58
+
59
+ getCustomProviderModel(
60
+ provider: LLMProvider,
61
+ recordId: number,
62
+ ): CustomProviderModelRecord | null {
63
+ const row = readCustomProviderModelRow(provider, recordId);
64
+ return row ? toCustomProviderModelRecord(row) : null;
65
+ },
66
+
67
+ findCustomProviderModelByModelId(
68
+ provider: LLMProvider,
69
+ modelId: string,
70
+ ): CustomProviderModelRecord | null {
71
+ const row = getConnection().prepare(`
72
+ SELECT id, provider, model_id, model_name, sort_order
73
+ FROM provider_models
74
+ WHERE provider = ? AND model_id = ?
75
+ `).get(provider, modelId) as CustomProviderModelRow | undefined;
76
+
77
+ return row ? toCustomProviderModelRecord(row) : null;
78
+ },
79
+
80
+ createCustomProviderModel(
81
+ provider: LLMProvider,
82
+ input: CustomProviderModelInput,
83
+ ): CustomProviderModelRecord {
84
+ const db = getConnection();
85
+ const nextOrder = db.prepare(`
86
+ SELECT COALESCE(MAX(sort_order), -1) + 1 AS next_order
87
+ FROM provider_models
88
+ WHERE provider = ?
89
+ `).get(provider) as { next_order: number };
90
+
91
+ const result = db.prepare(`
92
+ INSERT INTO provider_models (provider, model_id, model_name, sort_order)
93
+ VALUES (?, ?, ?, ?)
94
+ `).run(provider, input.id, input.model, nextOrder.next_order);
95
+
96
+ const row = readCustomProviderModelRow(provider, Number(result.lastInsertRowid));
97
+ if (!row) {
98
+ throw new Error('Created provider model could not be read back.');
99
+ }
100
+
101
+ return toCustomProviderModelRecord(row);
102
+ },
103
+
104
+ updateCustomProviderModel(
105
+ provider: LLMProvider,
106
+ recordId: number,
107
+ input: CustomProviderModelInput,
108
+ ): CustomProviderModelRecord | null {
109
+ const db = getConnection();
110
+ const update = db.transaction(() => {
111
+ const previous = readCustomProviderModelRow(provider, recordId);
112
+ if (!previous) {
113
+ return null;
114
+ }
115
+
116
+ db.prepare(`
117
+ UPDATE provider_models
118
+ SET model_id = ?, model_name = ?, updated_at = CURRENT_TIMESTAMP
119
+ WHERE provider = ? AND id = ?
120
+ `).run(input.id, input.model, provider, recordId);
121
+
122
+ if (previous.model_id !== input.id) {
123
+ db.prepare(`
124
+ UPDATE sessions
125
+ SET model = ?, updated_at = CURRENT_TIMESTAMP
126
+ WHERE provider = ? AND model = ?
127
+ `).run(input.id, provider, previous.model_id);
128
+ }
129
+
130
+ return readCustomProviderModelRow(provider, recordId);
131
+ });
132
+
133
+ const row = update();
134
+ return row ? toCustomProviderModelRecord(row) : null;
135
+ },
136
+
137
+ deleteCustomProviderModel(
138
+ provider: LLMProvider,
139
+ recordId: number,
140
+ fallbackModelId: string,
141
+ ): CustomProviderModelRecord | null {
142
+ const db = getConnection();
143
+ const remove = db.transaction(() => {
144
+ const row = readCustomProviderModelRow(provider, recordId);
145
+ if (!row) {
146
+ return null;
147
+ }
148
+
149
+ // The effort is cleared alongside the model because it was chosen for the
150
+ // deleted model; replaced sessions resume on the fallback model's default.
151
+ db.prepare(`
152
+ UPDATE sessions
153
+ SET model = ?, effort = NULL, updated_at = CURRENT_TIMESTAMP
154
+ WHERE provider = ? AND model = ?
155
+ `).run(fallbackModelId, provider, row.model_id);
156
+
157
+ db.prepare(`
158
+ DELETE FROM provider_models
159
+ WHERE provider = ? AND id = ?
160
+ `).run(provider, recordId);
161
+
162
+ return row;
163
+ });
164
+
165
+ const row = remove();
166
+ return row ? toCustomProviderModelRecord(row) : null;
167
+ },
168
+ };