@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292116.41f7caa

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 (295) hide show
  1. package/.env.example +2 -0
  2. package/ARCHITECTURE.md +26 -3
  3. package/docs/automatic-done.md +32 -0
  4. package/docs/desktop-browser-cli.md +3 -2
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
  9. package/node_modules/@vellumai/environments/src/shell.test.ts +13 -1
  10. package/node_modules/@vellumai/environments/src/shell.ts +22 -1
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
  15. package/node_modules/@vellumai/gateway-client/src/__tests__/status-contracts.test.ts +39 -0
  16. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +25 -0
  17. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  18. package/node_modules/@vellumai/service-contracts/src/__tests__/mcp-oauth.test.ts +53 -0
  19. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  20. package/node_modules/@vellumai/service-contracts/src/mcp-oauth.ts +60 -0
  21. package/openapi.yaml +270 -10
  22. package/package.json +1 -1
  23. package/src/__tests__/agent-loop.test.ts +76 -0
  24. package/src/__tests__/anthropic-provider.test.ts +2 -0
  25. package/src/__tests__/byok-default-profile-ensure.test.ts +7 -0
  26. package/src/__tests__/config-get-vision-flag.test.ts +1 -1
  27. package/src/__tests__/config-loader-validation-reset-notice.test.ts +2 -2
  28. package/src/__tests__/conversation-agent-loop-overflow.test.ts +160 -154
  29. package/src/__tests__/conversation-evictor.test.ts +54 -0
  30. package/src/__tests__/conversation-pairing.test.ts +112 -0
  31. package/src/__tests__/document-append-idempotency.test.ts +13 -0
  32. package/src/__tests__/document-create-dedupe.test.ts +13 -0
  33. package/src/__tests__/document-find-replace.test.ts +13 -0
  34. package/src/__tests__/document-revision-history.test.ts +390 -0
  35. package/src/__tests__/document-revision.test.ts +425 -0
  36. package/src/__tests__/document-tool-security.test.ts +13 -0
  37. package/src/__tests__/document-update-default-surface.test.ts +13 -0
  38. package/src/__tests__/emit-signal-routing-intent.test.ts +270 -0
  39. package/src/__tests__/gateway-flag-listener.test.ts +59 -6
  40. package/src/__tests__/headless-browser-navigate.test.ts +20 -30
  41. package/src/__tests__/host-cu-proxy.test.ts +79 -0
  42. package/src/__tests__/host-cu-routes-targeted.test.ts +14 -5
  43. package/src/__tests__/host-shell-tool.test.ts +6 -2
  44. package/src/__tests__/identity-routes.test.ts +4 -3
  45. package/src/__tests__/list-messages-client-message-id.test.ts +43 -0
  46. package/src/__tests__/llm-catalog-parity.test.ts +4 -4
  47. package/src/__tests__/llm-schema.test.ts +1 -1
  48. package/src/__tests__/notification-platform-adapter.test.ts +83 -1
  49. package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
  50. package/src/__tests__/openai-provider.test.ts +38 -11
  51. package/src/__tests__/pricing.test.ts +11 -0
  52. package/src/__tests__/public-ingress-urls.test.ts +26 -0
  53. package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
  54. package/src/__tests__/skill-execute-input.test.ts +11 -0
  55. package/src/__tests__/tool-approval-handler.test.ts +4 -0
  56. package/src/__tests__/tool-preview-lifecycle.test.ts +26 -0
  57. package/src/__tests__/usage-attribution.test.ts +31 -0
  58. package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
  59. package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
  60. package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
  61. package/src/acp/agent-process.ts +6 -1
  62. package/src/acp/client-handler.ts +6 -1
  63. package/src/agent/loop-unparseable-tool-retry.test.ts +333 -0
  64. package/src/agent/loop.ts +161 -17
  65. package/src/api/events/document-editor-show.ts +5 -0
  66. package/src/api/events/document-editor-update.ts +10 -0
  67. package/src/api/events/tool-result.ts +1 -0
  68. package/src/calls/__tests__/call-controller.test.ts +273 -1
  69. package/src/calls/__tests__/media-stream-server-integration.test.ts +40 -0
  70. package/src/calls/__tests__/media-stream-stt-session.test.ts +33 -0
  71. package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
  72. package/src/calls/call-constants.ts +10 -0
  73. package/src/calls/call-controller.ts +359 -25
  74. package/src/calls/channel-admission-reader.ts +15 -9
  75. package/src/calls/media-stream-server.ts +18 -0
  76. package/src/calls/media-stream-stt-session.ts +30 -9
  77. package/src/calls/twilio-provider.ts +14 -31
  78. package/src/calls/voice-session-bridge.ts +18 -1
  79. package/src/cli/commands/browser.ts +1 -2
  80. package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
  81. package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
  82. package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
  83. package/src/cli/lib/bundled-marketplace.json +54 -2
  84. package/src/cli/lib/inspect-plugin.ts +15 -15
  85. package/src/cli/lib/list-installed-plugins.ts +29 -28
  86. package/src/cli/lib/plugin-artifact.ts +74 -63
  87. package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
  88. package/src/cli/lib/plugin-details.ts +27 -42
  89. package/src/cli/lib/plugin-marketplace.ts +6 -8
  90. package/src/cli/lib/plugin-surfaces.ts +8 -7
  91. package/src/cli.ts +1 -3
  92. package/src/config/__tests__/chat-settings.test.ts +121 -0
  93. package/src/config/assistant-feature-flags.ts +28 -23
  94. package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
  95. package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
  96. package/src/config/chat-settings.ts +100 -0
  97. package/src/config/default-profile-catalog.ts +1 -1
  98. package/src/config/feature-flag-cache.ts +7 -25
  99. package/src/config/feature-flag-registry.json +9 -9
  100. package/src/config/loader.ts +5 -1
  101. package/src/config/profile-text-generation.ts +25 -21
  102. package/src/config/schemas/conversations.ts +14 -0
  103. package/src/config/schemas/llm.ts +1 -0
  104. package/src/config/schemas/notifications.ts +8 -1
  105. package/src/contacts/gateway-channel-read.ts +1 -1
  106. package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
  107. package/src/conversations/__tests__/auto-archive.test.ts +304 -0
  108. package/src/conversations/auto-archive-activity.ts +61 -0
  109. package/src/conversations/auto-archive.ts +201 -0
  110. package/src/daemon/computer-use-observation.test.ts +81 -0
  111. package/src/daemon/computer-use-observation.ts +23 -0
  112. package/src/daemon/conversation-agent-loop-handlers.ts +18 -2
  113. package/src/daemon/conversation-agent-loop.ts +14 -6
  114. package/src/daemon/conversation-evictor.ts +50 -16
  115. package/src/daemon/conversation-messaging.ts +4 -0
  116. package/src/daemon/conversation-process.ts +15 -0
  117. package/src/daemon/conversation-queue-manager.ts +2 -0
  118. package/src/daemon/conversation.ts +2 -0
  119. package/src/daemon/doordash-steps.ts +1 -3
  120. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
  121. package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
  122. package/src/daemon/handlers/config-a2a.ts +75 -2
  123. package/src/daemon/handlers/shared.ts +13 -2
  124. package/src/daemon/host-cu-proxy.ts +34 -3
  125. package/src/daemon/lifecycle.ts +15 -2
  126. package/src/daemon/process-message.ts +5 -0
  127. package/src/daemon/shutdown-handlers.ts +3 -0
  128. package/src/daemon/turn-finalization.ts +4 -0
  129. package/src/desktop/desktop-browser-endpoint.ts +11 -2
  130. package/src/desktop/desktop-chrome-policy.test.ts +1 -0
  131. package/src/desktop/desktop-chrome-policy.ts +7 -1
  132. package/src/desktop/desktop-computer-use.test.ts +14 -4
  133. package/src/desktop/desktop-session-manager.ts +5 -3
  134. package/src/documents/document-revisions-store.ts +153 -0
  135. package/src/documents/document-store.ts +471 -133
  136. package/src/followups/followup-store.ts +0 -4
  137. package/src/i18n/__tests__/i18n.test.ts +25 -0
  138. package/src/i18n/assistant-text.ts +18 -0
  139. package/src/i18n/index.ts +1 -0
  140. package/src/i18n/messages.ts +11 -0
  141. package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
  142. package/src/inbound/oauth-callback-url.ts +30 -1
  143. package/src/inbound/public-ingress-urls.ts +12 -2
  144. package/src/ipc/gateway-client.test.ts +40 -0
  145. package/src/ipc/gateway-client.ts +12 -29
  146. package/src/ipc/gateway-flag-listener.ts +7 -10
  147. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
  148. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
  149. package/src/live-voice/__tests__/protocol.test.ts +77 -0
  150. package/src/live-voice/__tests__/share-targets.test.ts +163 -0
  151. package/src/live-voice/live-voice-session.ts +23 -6
  152. package/src/live-voice/protocol.ts +22 -0
  153. package/src/live-voice/share-targets.ts +225 -0
  154. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
  155. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
  156. package/src/mcp/client.ts +1 -19
  157. package/src/mcp/mcp-oauth-provider.ts +66 -49
  158. package/src/media/openrouter-image-service.ts +39 -22
  159. package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
  160. package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
  161. package/src/monitoring/control.ts +2 -1
  162. package/src/monitoring/oom-inheritance-sweep.ts +96 -0
  163. package/src/monitoring/oom-kill-reporter.ts +469 -0
  164. package/src/monitoring/resource-sampler.ts +30 -0
  165. package/src/notifications/README.md +53 -4
  166. package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
  167. package/src/notifications/__tests__/broadcaster.test.ts +558 -6
  168. package/src/notifications/__tests__/completion-policy.test.ts +37 -0
  169. package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
  170. package/src/notifications/adapters/platform.ts +14 -0
  171. package/src/notifications/assistant-initiated-thread.ts +101 -0
  172. package/src/notifications/broadcaster.ts +94 -16
  173. package/src/notifications/chat-reply-policy.ts +11 -0
  174. package/src/notifications/completion-policy.ts +12 -2
  175. package/src/notifications/conversation-pairing.ts +15 -70
  176. package/src/notifications/emit-signal.ts +34 -3
  177. package/src/notifications/types.ts +2 -0
  178. package/src/oauth/connect-orchestrator.ts +1 -2
  179. package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
  180. package/src/persistence/conversation-auto-archive.ts +170 -0
  181. package/src/persistence/conversation-crud.ts +2 -1
  182. package/src/persistence/conversation-queries.ts +15 -8
  183. package/src/persistence/embeddings/embed.test.ts +96 -0
  184. package/src/persistence/embeddings/embed.ts +20 -2
  185. package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
  186. package/src/persistence/embeddings/embedding-gemini.ts +39 -1
  187. package/src/persistence/embeddings/embedding-local.ts +6 -0
  188. package/src/persistence/embeddings/qdrant-manager.ts +5 -0
  189. package/src/persistence/job-utils.ts +10 -0
  190. package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
  191. package/src/persistence/migrations/384-add-document-revision.ts +21 -0
  192. package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
  193. package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
  194. package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
  195. package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
  196. package/src/persistence/schema/conversations.ts +1 -0
  197. package/src/persistence/schema/documents.ts +20 -0
  198. package/src/persistence/steps.ts +6 -0
  199. package/src/platform/managed-speech.ts +42 -46
  200. package/src/plugin-api/vision-support.test.ts +21 -21
  201. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
  202. package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
  203. package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
  204. package/src/providers/__tests__/unparseable-tool-args.test.ts +25 -0
  205. package/src/providers/anthropic/client.ts +1 -1
  206. package/src/providers/fireworks/client.test.ts +168 -0
  207. package/src/providers/fireworks/client.ts +24 -0
  208. package/src/providers/inference/adapter-factory.ts +6 -0
  209. package/src/providers/ionet/client.test.ts +143 -0
  210. package/src/providers/ionet/client.ts +37 -0
  211. package/src/providers/model-catalog.ts +141 -84
  212. package/src/providers/model-intents.ts +4 -4
  213. package/src/providers/openai/chat-completions-provider.ts +33 -2
  214. package/src/providers/retry.ts +56 -4
  215. package/src/providers/search-provider-catalog.ts +10 -0
  216. package/src/providers/types.ts +10 -2
  217. package/src/providers/unparseable-tool-args.ts +29 -5
  218. package/src/providers/usage-tracking.ts +5 -3
  219. package/src/runtime/assistant-event-hub.ts +23 -0
  220. package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
  221. package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
  222. package/src/runtime/channel-reply-delivery.ts +2 -0
  223. package/src/runtime/channel-retry-sweep.ts +1 -2
  224. package/src/runtime/http-types.ts +3 -0
  225. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
  226. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
  227. package/src/runtime/migrations/vbundle-builder.ts +19 -3
  228. package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
  229. package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
  230. package/src/runtime/migrations/vbundle-validator.ts +54 -1
  231. package/src/runtime/pending-interactions.ts +20 -2
  232. package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
  233. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
  234. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
  235. package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
  236. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
  237. package/src/runtime/routes/contact-routes.ts +2 -2
  238. package/src/runtime/routes/conversation-list-routes.ts +10 -2
  239. package/src/runtime/routes/conversation-query-routes.ts +48 -10
  240. package/src/runtime/routes/conversation-routes.ts +7 -0
  241. package/src/runtime/routes/documents-routes.ts +185 -47
  242. package/src/runtime/routes/events-routes.ts +1 -3
  243. package/src/runtime/routes/host-app-control-routes.ts +1 -0
  244. package/src/runtime/routes/host-bash-routes.ts +1 -0
  245. package/src/runtime/routes/host-browser-routes.ts +15 -0
  246. package/src/runtime/routes/host-cu-routes.ts +4 -1
  247. package/src/runtime/routes/host-file-routes.ts +1 -0
  248. package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
  249. package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
  250. package/src/runtime/routes/host-transfer-routes.ts +1 -0
  251. package/src/runtime/routes/identity-routes.ts +3 -3
  252. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
  253. package/src/runtime/routes/integrations/a2a.ts +4 -1
  254. package/src/runtime/routes/migration-routes.ts +3 -1
  255. package/src/runtime/routes/notification-routes.ts +1 -2
  256. package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
  257. package/src/runtime/routes/user-route-dispatcher.ts +5 -1
  258. package/src/runtime/verification-templates.ts +1 -2
  259. package/src/sequence/analytics.ts +0 -4
  260. package/src/sequence/engine.ts +1 -11
  261. package/src/sequence/guardrails.ts +1 -6
  262. package/src/sequence/importer.ts +0 -8
  263. package/src/sequence/store.ts +0 -10
  264. package/src/signals/user-message.ts +0 -6
  265. package/src/skills/catalog-install.ts +52 -23
  266. package/src/tools/browser/browser-execution.ts +20 -23
  267. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
  268. package/src/tools/document/document-tool.ts +13 -11
  269. package/src/tools/executor.ts +1 -2
  270. package/src/tools/host-terminal/host-shell.ts +4 -2
  271. package/src/tools/network/__tests__/web-search.test.ts +105 -0
  272. package/src/tools/network/web-fetch.ts +2 -4
  273. package/src/tools/network/web-search.ts +239 -12
  274. package/src/tools/shared/filesystem/format-diff.ts +1 -2
  275. package/src/tools/shared/shell-output.test.ts +42 -0
  276. package/src/tools/shared/shell-output.ts +27 -4
  277. package/src/tools/skills/execute.ts +10 -7
  278. package/src/tools/terminal/shell.ts +3 -3
  279. package/src/tools/tool-approval-handler.ts +9 -1
  280. package/src/tools/ui-surface/visual-validation.ts +1 -2
  281. package/src/usage/attribution.ts +18 -1
  282. package/src/util/__tests__/oom-priority.test.ts +55 -0
  283. package/src/util/__tests__/worker-memory.test.ts +16 -0
  284. package/src/util/debounce.ts +0 -4
  285. package/src/util/oom-priority.ts +79 -0
  286. package/src/util/platform.ts +0 -2
  287. package/src/util/plugin-manifest.ts +5 -7
  288. package/src/util/pricing.ts +25 -0
  289. package/src/util/worker-memory.ts +9 -3
  290. package/src/util/worker-process.ts +8 -0
  291. package/src/workspace/byok-default-profile-ensure.ts +11 -0
  292. package/src/workspace/hatched-date.ts +11 -4
  293. package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
  294. package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
  295. package/src/workspace/migrations/registry.ts +4 -0
@@ -0,0 +1,262 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { Database } from "bun:sqlite";
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
+
13
+ import { repairRetiredFireworksModelIdsMigration } from "../workspace/migrations/160-repair-retired-fireworks-model-ids.js";
14
+ import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
15
+ import { assertNotLiveDb } from "./assert-not-live-db.js";
16
+
17
+ const STALE = "accounts/fireworks/models/deepseek-v4-flash-0731";
18
+ const REPLACEMENT = "accounts/fireworks/models/deepseek-v4p1-flash";
19
+
20
+ let workspaceDir: string;
21
+
22
+ function freshWorkspace(): void {
23
+ workspaceDir = join(
24
+ tmpdir(),
25
+ `vellum-migration-160-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
26
+ );
27
+ mkdirSync(workspaceDir, { recursive: true });
28
+ }
29
+
30
+ function writeConfig(data: Record<string, unknown>): void {
31
+ writeFileSync(
32
+ join(workspaceDir, "config.json"),
33
+ JSON.stringify(data, null, 2) + "\n",
34
+ );
35
+ }
36
+
37
+ function readConfig(): Record<string, unknown> {
38
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
39
+ }
40
+
41
+ function seedRows(rows: Array<{ name: string; provider: string }>): void {
42
+ mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
43
+ const db = new Database(join(workspaceDir, "data", "db", "assistant.db"));
44
+ db.run(`CREATE TABLE IF NOT EXISTS provider_connections (
45
+ name TEXT PRIMARY KEY,
46
+ provider TEXT NOT NULL,
47
+ auth TEXT NOT NULL,
48
+ created_at INTEGER NOT NULL,
49
+ updated_at INTEGER NOT NULL
50
+ )`);
51
+ for (const row of rows) {
52
+ db.query(
53
+ `INSERT INTO provider_connections (name, provider, auth, created_at, updated_at) VALUES (?, ?, '{"type":"api_key"}', 1, 1)`,
54
+ ).run(row.name, row.provider);
55
+ }
56
+ db.close();
57
+ }
58
+
59
+ beforeEach(() => {
60
+ freshWorkspace();
61
+ });
62
+
63
+ afterEach(() => {
64
+ if (existsSync(workspaceDir)) {
65
+ assertNotLiveDb(workspaceDir);
66
+ rmSync(workspaceDir, { recursive: true, force: true });
67
+ }
68
+ });
69
+
70
+ describe("160-repair-retired-fireworks-model-ids migration", () => {
71
+ test("has correct migration id and is registered", () => {
72
+ expect(repairRetiredFireworksModelIdsMigration.id).toBe(
73
+ "160-repair-retired-fireworks-model-ids",
74
+ );
75
+ expect(WORKSPACE_MIGRATIONS.map((m) => m.id)).toContain(
76
+ "160-repair-retired-fireworks-model-ids",
77
+ );
78
+ });
79
+
80
+ test("repairs the stale ID in default, call sites, and profiles", () => {
81
+ writeConfig({
82
+ llm: {
83
+ default: { provider: "fireworks", model: STALE },
84
+ callSites: {
85
+ recall: { model: STALE, maxTokens: 4096 },
86
+ heartbeat: { model: `${STALE}-tuned` },
87
+ malformed: STALE,
88
+ },
89
+ profiles: {
90
+ "cost-optimized": { provider: "vellum", model: STALE },
91
+ legacy: { model: STALE },
92
+ },
93
+ },
94
+ });
95
+
96
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
97
+
98
+ const llm = readConfig().llm as Record<string, any>;
99
+ expect(llm.default.model).toBe(REPLACEMENT);
100
+ expect(llm.callSites.recall.model).toBe(REPLACEMENT);
101
+ expect(llm.callSites.recall.maxTokens).toBe(4096);
102
+ // Non-exact matches and malformed leaves are untouched.
103
+ expect(llm.callSites.heartbeat.model).toBe(`${STALE}-tuned`);
104
+ expect(llm.callSites.malformed).toBe(STALE);
105
+ // Managed profiles stamped provider "vellum" carry Fireworks model IDs.
106
+ expect(llm.profiles["cost-optimized"].model).toBe(REPLACEMENT);
107
+ expect(llm.profiles.legacy.model).toBe(REPLACEMENT);
108
+ });
109
+
110
+ test.each([
111
+ [
112
+ "accounts/fireworks/models/deepseek-v4-pro-0813",
113
+ "accounts/fireworks/models/deepseek-v4p1-flash",
114
+ ],
115
+ [
116
+ "accounts/fireworks/models/kimi-k2p6",
117
+ "accounts/fireworks/models/kimi-k3",
118
+ ],
119
+ ])("repairs %s to %s", (stale, replacement) => {
120
+ writeConfig({
121
+ llm: {
122
+ default: { provider: "fireworks", model: stale },
123
+ profiles: {
124
+ managed: { provider: "vellum", model: stale },
125
+ other: { provider: "openai-compatible", model: stale },
126
+ },
127
+ },
128
+ });
129
+
130
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
131
+
132
+ const llm = readConfig().llm as Record<string, unknown>;
133
+ const profiles = llm.profiles as Record<string, Record<string, unknown>>;
134
+ expect((llm.default as Record<string, unknown>).model).toBe(replacement);
135
+ expect(profiles.managed.model).toBe(replacement);
136
+ expect(profiles.other.model).toBe(stale);
137
+ });
138
+
139
+ test("repairs entry-bound fragments whose row kind is fireworks or vellum", () => {
140
+ seedRows([
141
+ { name: "my-fireworks", provider: "fireworks" },
142
+ { name: "managed-alt", provider: "vellum" },
143
+ ]);
144
+ writeConfig({
145
+ llm: {
146
+ profiles: {
147
+ bound: { provider: "my-fireworks", model: STALE },
148
+ managed: { provider: "managed-alt", model: STALE },
149
+ },
150
+ },
151
+ });
152
+
153
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
154
+
155
+ const llm = readConfig().llm as Record<string, any>;
156
+ expect(llm.profiles.bound.model).toBe(REPLACEMENT);
157
+ expect(llm.profiles.managed.model).toBe(REPLACEMENT);
158
+ });
159
+
160
+ test("leaves fragments with an explicit other provider untouched", () => {
161
+ seedRows([{ name: "byo-compat", provider: "openai-compatible" }]);
162
+ writeConfig({
163
+ llm: {
164
+ default: { provider: "openai-compatible", model: STALE },
165
+ profiles: {
166
+ byo: { provider: "openai-compatible", model: STALE },
167
+ // Entry-bound profile whose row kind is not repairable.
168
+ compatBound: { provider: "byo-compat", model: STALE },
169
+ // Entry name with no row: dangling, so nothing proves it is a
170
+ // fireworks-kind route.
171
+ dangling: { provider: "ghost", model: STALE },
172
+ },
173
+ },
174
+ });
175
+
176
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
177
+
178
+ const llm = readConfig().llm as Record<string, any>;
179
+ expect(llm.default.model).toBe(STALE);
180
+ expect(llm.profiles.byo.model).toBe(STALE);
181
+ expect(llm.profiles.compatBound.model).toBe(STALE);
182
+ expect(llm.profiles.dangling.model).toBe(STALE);
183
+ });
184
+
185
+ test("leaves entry-name providers untouched when no DB file exists", () => {
186
+ writeConfig({
187
+ llm: {
188
+ profiles: { bound: { provider: "my-fireworks", model: STALE } },
189
+ },
190
+ });
191
+
192
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
193
+
194
+ const llm = readConfig().llm as Record<string, any>;
195
+ expect(llm.profiles.bound.model).toBe(STALE);
196
+ });
197
+
198
+ test("throws when an entry-name provider needs rows and the DB is unreadable", () => {
199
+ // A DB file without the provider_connections table is unqueryable, so
200
+ // the run must fail (and retry later) instead of skipping the profile.
201
+ mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
202
+ writeFileSync(join(workspaceDir, "data", "db", "assistant.db"), "");
203
+ writeConfig({
204
+ llm: {
205
+ profiles: { bound: { provider: "my-fireworks", model: STALE } },
206
+ },
207
+ });
208
+
209
+ expect(() =>
210
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir),
211
+ ).toThrow();
212
+ const llm = readConfig().llm as Record<string, any>;
213
+ expect(llm.profiles.bound.model).toBe(STALE);
214
+
215
+ // Vendor and absent providers never need the rows, so the same broken
216
+ // DB does not block their repair.
217
+ writeConfig({
218
+ llm: {
219
+ default: { provider: "fireworks", model: STALE },
220
+ profiles: { legacy: { model: STALE } },
221
+ },
222
+ });
223
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
224
+ const repaired = readConfig().llm as Record<string, any>;
225
+ expect(repaired.default.model).toBe(REPLACEMENT);
226
+ expect(repaired.profiles.legacy.model).toBe(REPLACEMENT);
227
+ });
228
+
229
+ test("is idempotent and a no-op without the stale ID", () => {
230
+ writeConfig({
231
+ llm: {
232
+ default: { provider: "fireworks", model: STALE },
233
+ profiles: { fine: { provider: "fireworks", model: REPLACEMENT } },
234
+ },
235
+ });
236
+
237
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
238
+ const first = readConfig();
239
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
240
+ expect(readConfig()).toEqual(first);
241
+
242
+ const llm = first.llm as Record<string, any>;
243
+ expect(llm.default.model).toBe(REPLACEMENT);
244
+ expect(llm.profiles.fine.model).toBe(REPLACEMENT);
245
+ });
246
+
247
+ test("handles missing config, missing llm block, and invalid JSON", () => {
248
+ // No config.json at all.
249
+ expect(() =>
250
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir),
251
+ ).not.toThrow();
252
+
253
+ writeConfig({ theme: "dark" });
254
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir);
255
+ expect(readConfig()).toEqual({ theme: "dark" });
256
+
257
+ writeFileSync(join(workspaceDir, "config.json"), "{not json");
258
+ expect(() =>
259
+ repairRetiredFireworksModelIdsMigration.run(workspaceDir),
260
+ ).not.toThrow();
261
+ });
262
+ });
@@ -0,0 +1,233 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { Database } from "bun:sqlite";
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
+
13
+ import { repairRetiredFireworksGlm5p2ModelIdMigration } from "../workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.js";
14
+ import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
15
+ import { assertNotLiveDb } from "./assert-not-live-db.js";
16
+
17
+ const STALE = "accounts/fireworks/models/glm-5p2";
18
+ const REPLACEMENT = "accounts/fireworks/models/glm-5p3";
19
+
20
+ let workspaceDir: string;
21
+
22
+ function freshWorkspace(): void {
23
+ workspaceDir = join(
24
+ tmpdir(),
25
+ `vellum-migration-161-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
26
+ );
27
+ mkdirSync(workspaceDir, { recursive: true });
28
+ }
29
+
30
+ function writeConfig(data: Record<string, unknown>): void {
31
+ writeFileSync(
32
+ join(workspaceDir, "config.json"),
33
+ JSON.stringify(data, null, 2) + "\n",
34
+ );
35
+ }
36
+
37
+ function readConfig(): Record<string, unknown> {
38
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
39
+ }
40
+
41
+ function seedRows(rows: Array<{ name: string; provider: string }>): void {
42
+ mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
43
+ const db = new Database(join(workspaceDir, "data", "db", "assistant.db"));
44
+ db.run(`CREATE TABLE IF NOT EXISTS provider_connections (
45
+ name TEXT PRIMARY KEY,
46
+ provider TEXT NOT NULL,
47
+ auth TEXT NOT NULL,
48
+ created_at INTEGER NOT NULL,
49
+ updated_at INTEGER NOT NULL
50
+ )`);
51
+ for (const row of rows) {
52
+ db.query(
53
+ `INSERT INTO provider_connections (name, provider, auth, created_at, updated_at) VALUES (?, ?, '{"type":"api_key"}', 1, 1)`,
54
+ ).run(row.name, row.provider);
55
+ }
56
+ db.close();
57
+ }
58
+
59
+ beforeEach(() => {
60
+ freshWorkspace();
61
+ });
62
+
63
+ afterEach(() => {
64
+ if (existsSync(workspaceDir)) {
65
+ assertNotLiveDb(workspaceDir);
66
+ rmSync(workspaceDir, { recursive: true, force: true });
67
+ }
68
+ });
69
+
70
+ describe("161-repair-retired-fireworks-glm-5p2-model-id migration", () => {
71
+ test("has correct migration id and is registered", () => {
72
+ expect(repairRetiredFireworksGlm5p2ModelIdMigration.id).toBe(
73
+ "161-repair-retired-fireworks-glm-5p2-model-id",
74
+ );
75
+ expect(WORKSPACE_MIGRATIONS.map((m) => m.id)).toContain(
76
+ "161-repair-retired-fireworks-glm-5p2-model-id",
77
+ );
78
+ });
79
+
80
+ test("repairs the stale ID in default, call sites, and profiles", () => {
81
+ writeConfig({
82
+ llm: {
83
+ default: { provider: "fireworks", model: STALE },
84
+ callSites: {
85
+ recall: { model: STALE, maxTokens: 4096 },
86
+ heartbeat: { model: `${STALE}-tuned` },
87
+ malformed: STALE,
88
+ },
89
+ profiles: {
90
+ "cost-optimized": { provider: "vellum", model: STALE },
91
+ legacy: { model: STALE },
92
+ },
93
+ },
94
+ });
95
+
96
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir);
97
+
98
+ const llm = readConfig().llm as Record<string, any>;
99
+ expect(llm.default.model).toBe(REPLACEMENT);
100
+ expect(llm.callSites.recall.model).toBe(REPLACEMENT);
101
+ expect(llm.callSites.recall.maxTokens).toBe(4096);
102
+ // Non-exact matches and malformed leaves are untouched.
103
+ expect(llm.callSites.heartbeat.model).toBe(`${STALE}-tuned`);
104
+ expect(llm.callSites.malformed).toBe(STALE);
105
+ // Managed profiles stamped provider "vellum" carry Fireworks model IDs.
106
+ expect(llm.profiles["cost-optimized"].model).toBe(REPLACEMENT);
107
+ expect(llm.profiles.legacy.model).toBe(REPLACEMENT);
108
+ });
109
+
110
+ test("repairs entry-bound fragments whose row kind is fireworks or vellum", () => {
111
+ seedRows([
112
+ { name: "my-fireworks", provider: "fireworks" },
113
+ { name: "managed-alt", provider: "vellum" },
114
+ ]);
115
+ writeConfig({
116
+ llm: {
117
+ profiles: {
118
+ bound: { provider: "my-fireworks", model: STALE },
119
+ managed: { provider: "managed-alt", model: STALE },
120
+ },
121
+ },
122
+ });
123
+
124
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir);
125
+
126
+ const llm = readConfig().llm as Record<string, any>;
127
+ expect(llm.profiles.bound.model).toBe(REPLACEMENT);
128
+ expect(llm.profiles.managed.model).toBe(REPLACEMENT);
129
+ });
130
+
131
+ test("leaves fragments with an explicit other provider untouched", () => {
132
+ seedRows([{ name: "byo-compat", provider: "openai-compatible" }]);
133
+ writeConfig({
134
+ llm: {
135
+ default: { provider: "openai-compatible", model: STALE },
136
+ profiles: {
137
+ byo: { provider: "openai-compatible", model: STALE },
138
+ // Entry-bound profile whose row kind is not repairable.
139
+ compatBound: { provider: "byo-compat", model: STALE },
140
+ // Entry name with no row: dangling, so nothing proves it is a
141
+ // fireworks-kind route.
142
+ dangling: { provider: "ghost", model: STALE },
143
+ },
144
+ },
145
+ });
146
+
147
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir);
148
+
149
+ const llm = readConfig().llm as Record<string, any>;
150
+ expect(llm.default.model).toBe(STALE);
151
+ expect(llm.profiles.byo.model).toBe(STALE);
152
+ expect(llm.profiles.compatBound.model).toBe(STALE);
153
+ expect(llm.profiles.dangling.model).toBe(STALE);
154
+ });
155
+
156
+ test("leaves entry-name providers untouched when no DB file exists", () => {
157
+ writeConfig({
158
+ llm: {
159
+ profiles: { bound: { provider: "my-fireworks", model: STALE } },
160
+ },
161
+ });
162
+
163
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir);
164
+
165
+ const llm = readConfig().llm as Record<string, any>;
166
+ expect(llm.profiles.bound.model).toBe(STALE);
167
+ });
168
+
169
+ test("throws when an entry-name provider needs rows and the DB is unreadable", () => {
170
+ // A DB file without the provider_connections table is unqueryable, so
171
+ // the run must fail (and retry later) instead of skipping the profile.
172
+ mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
173
+ writeFileSync(join(workspaceDir, "data", "db", "assistant.db"), "");
174
+ writeConfig({
175
+ llm: {
176
+ profiles: { bound: { provider: "my-fireworks", model: STALE } },
177
+ },
178
+ });
179
+
180
+ expect(() =>
181
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir),
182
+ ).toThrow();
183
+ const llm = readConfig().llm as Record<string, any>;
184
+ expect(llm.profiles.bound.model).toBe(STALE);
185
+
186
+ // Vendor and absent providers never need the rows, so the same broken
187
+ // DB does not block their repair.
188
+ writeConfig({
189
+ llm: {
190
+ default: { provider: "fireworks", model: STALE },
191
+ profiles: { legacy: { model: STALE } },
192
+ },
193
+ });
194
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir);
195
+ const repaired = readConfig().llm as Record<string, any>;
196
+ expect(repaired.default.model).toBe(REPLACEMENT);
197
+ expect(repaired.profiles.legacy.model).toBe(REPLACEMENT);
198
+ });
199
+
200
+ test("is idempotent and a no-op without the stale ID", () => {
201
+ writeConfig({
202
+ llm: {
203
+ default: { provider: "fireworks", model: STALE },
204
+ profiles: { fine: { provider: "fireworks", model: REPLACEMENT } },
205
+ },
206
+ });
207
+
208
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir);
209
+ const first = readConfig();
210
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir);
211
+ expect(readConfig()).toEqual(first);
212
+
213
+ const llm = first.llm as Record<string, any>;
214
+ expect(llm.default.model).toBe(REPLACEMENT);
215
+ expect(llm.profiles.fine.model).toBe(REPLACEMENT);
216
+ });
217
+
218
+ test("handles missing config, missing llm block, and invalid JSON", () => {
219
+ // No config.json at all.
220
+ expect(() =>
221
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir),
222
+ ).not.toThrow();
223
+
224
+ writeConfig({ theme: "dark" });
225
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir);
226
+ expect(readConfig()).toEqual({ theme: "dark" });
227
+
228
+ writeFileSync(join(workspaceDir, "config.json"), "{not json");
229
+ expect(() =>
230
+ repairRetiredFireworksGlm5p2ModelIdMigration.run(workspaceDir),
231
+ ).not.toThrow();
232
+ });
233
+ });
@@ -25,6 +25,7 @@ import type {
25
25
  import * as acp from "@agentclientprotocol/sdk";
26
26
 
27
27
  import { getLogger } from "../util/logger.js";
28
+ import { CHILD_OOM_SCORE_ADJ, withOomScoreAdj } from "../util/oom-priority.js";
28
29
  import { safeStringSlice } from "../util/unicode.js";
29
30
  import {
30
31
  AcpAuthRequiredError,
@@ -110,7 +111,11 @@ export class AcpAgentProcess {
110
111
  );
111
112
 
112
113
  this.spawnedEnv = { ...process.env, ...this.config.env };
113
- this.proc = spawn(this.config.command, this.config.args, {
114
+ const [command, ...args] = withOomScoreAdj(
115
+ [this.config.command, ...this.config.args],
116
+ CHILD_OOM_SCORE_ADJ,
117
+ );
118
+ this.proc = spawn(command, args, {
114
119
  cwd,
115
120
  stdio: ["pipe", "pipe", "pipe"],
116
121
  env: this.spawnedEnv,
@@ -38,6 +38,7 @@ import { redactJsonStringLeaves } from "../security/redact-json.js";
38
38
  import { redactSensitiveFields } from "../security/redaction.js";
39
39
  import { redactSecrets } from "../security/secret-scanner.js";
40
40
  import { getLogger } from "../util/logger.js";
41
+ import { CHILD_OOM_SCORE_ADJ, withOomScoreAdj } from "../util/oom-priority.js";
41
42
 
42
43
  const log = getLogger("acp:client-handler");
43
44
 
@@ -389,7 +390,11 @@ export class VellumAcpClientHandler implements Client {
389
390
  }
390
391
  }
391
392
 
392
- const proc = spawn(params.command, args, {
393
+ const [command, ...wrappedArgs] = withOomScoreAdj(
394
+ [params.command, ...args],
395
+ CHILD_OOM_SCORE_ADJ,
396
+ );
397
+ const proc = spawn(command, wrappedArgs, {
393
398
  cwd: params.cwd ?? undefined,
394
399
  stdio: ["ignore", "pipe", "pipe"],
395
400
  env,