@vellumai/assistant 0.12.5 → 0.12.6-dev.202609292019.a6f6d85

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 (264) hide show
  1. package/.env.example +2 -0
  2. package/ARCHITECTURE.md +23 -0
  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__/llm-catalog-parity.test.ts +4 -4
  46. package/src/__tests__/llm-schema.test.ts +1 -1
  47. package/src/__tests__/notification-platform-adapter.test.ts +83 -1
  48. package/src/__tests__/oauth2-gateway-transport.test.ts +2 -0
  49. package/src/__tests__/openai-provider.test.ts +11 -11
  50. package/src/__tests__/pricing.test.ts +11 -0
  51. package/src/__tests__/public-ingress-urls.test.ts +26 -0
  52. package/src/__tests__/retry-thinking-tool-choice.test.ts +47 -0
  53. package/src/__tests__/usage-attribution.test.ts +31 -0
  54. package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
  55. package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +262 -0
  56. package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +233 -0
  57. package/src/acp/agent-process.ts +6 -1
  58. package/src/acp/client-handler.ts +6 -1
  59. package/src/agent/loop.ts +29 -6
  60. package/src/api/events/document-editor-show.ts +5 -0
  61. package/src/api/events/document-editor-update.ts +10 -0
  62. package/src/api/events/tool-result.ts +1 -0
  63. package/src/calls/__tests__/voice-session-bridge.test.ts +54 -0
  64. package/src/calls/channel-admission-reader.ts +15 -9
  65. package/src/calls/twilio-provider.ts +14 -31
  66. package/src/calls/voice-session-bridge.ts +18 -1
  67. package/src/cli/commands/browser.ts +1 -2
  68. package/src/cli/lib/__tests__/plugin-artifact.test.ts +23 -0
  69. package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +2 -0
  70. package/src/cli/lib/__tests__/plugin-details.test.ts +16 -0
  71. package/src/cli/lib/bundled-marketplace.json +41 -2
  72. package/src/cli/lib/inspect-plugin.ts +15 -15
  73. package/src/cli/lib/list-installed-plugins.ts +29 -28
  74. package/src/cli/lib/plugin-artifact.ts +74 -63
  75. package/src/cli/lib/plugin-catalog-visibility.ts +1 -0
  76. package/src/cli/lib/plugin-details.ts +27 -42
  77. package/src/cli/lib/plugin-marketplace.ts +6 -8
  78. package/src/cli/lib/plugin-surfaces.ts +8 -7
  79. package/src/cli.ts +1 -3
  80. package/src/config/__tests__/chat-settings.test.ts +121 -0
  81. package/src/config/assistant-feature-flags.ts +28 -23
  82. package/src/config/bundled-skills/computer-use/SKILL.md +20 -0
  83. package/src/config/bundled-skills/computer-use/TOOLS.json +11 -11
  84. package/src/config/chat-settings.ts +100 -0
  85. package/src/config/default-profile-catalog.ts +1 -1
  86. package/src/config/feature-flag-cache.ts +7 -25
  87. package/src/config/feature-flag-registry.json +1 -9
  88. package/src/config/loader.ts +5 -1
  89. package/src/config/profile-text-generation.ts +25 -21
  90. package/src/config/schemas/conversations.ts +14 -0
  91. package/src/config/schemas/llm.ts +1 -0
  92. package/src/config/schemas/notifications.ts +8 -1
  93. package/src/contacts/gateway-channel-read.ts +1 -1
  94. package/src/conversations/__tests__/auto-archive-activity.test.ts +136 -0
  95. package/src/conversations/__tests__/auto-archive.test.ts +304 -0
  96. package/src/conversations/auto-archive-activity.ts +61 -0
  97. package/src/conversations/auto-archive.ts +201 -0
  98. package/src/daemon/computer-use-observation.test.ts +81 -0
  99. package/src/daemon/computer-use-observation.ts +23 -0
  100. package/src/daemon/conversation-agent-loop.ts +11 -6
  101. package/src/daemon/conversation-evictor.ts +50 -16
  102. package/src/daemon/doordash-steps.ts +1 -3
  103. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +43 -1
  104. package/src/daemon/handlers/__tests__/config-channels.test.ts +1 -1
  105. package/src/daemon/handlers/config-a2a.ts +75 -2
  106. package/src/daemon/host-cu-proxy.ts +34 -3
  107. package/src/daemon/lifecycle.ts +15 -2
  108. package/src/daemon/shutdown-handlers.ts +3 -0
  109. package/src/daemon/turn-finalization.ts +4 -0
  110. package/src/desktop/desktop-browser-endpoint.ts +11 -2
  111. package/src/desktop/desktop-chrome-policy.test.ts +1 -0
  112. package/src/desktop/desktop-chrome-policy.ts +7 -1
  113. package/src/desktop/desktop-computer-use.test.ts +14 -4
  114. package/src/desktop/desktop-session-manager.ts +5 -3
  115. package/src/documents/document-revisions-store.ts +153 -0
  116. package/src/documents/document-store.ts +471 -133
  117. package/src/followups/followup-store.ts +0 -4
  118. package/src/inbound/__tests__/oauth-callback-url.test.ts +25 -2
  119. package/src/inbound/oauth-callback-url.ts +30 -1
  120. package/src/inbound/public-ingress-urls.ts +12 -2
  121. package/src/ipc/gateway-client.test.ts +40 -0
  122. package/src/ipc/gateway-client.ts +12 -29
  123. package/src/ipc/gateway-flag-listener.ts +7 -10
  124. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +71 -0
  125. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +83 -0
  126. package/src/live-voice/__tests__/protocol.test.ts +77 -0
  127. package/src/live-voice/__tests__/share-targets.test.ts +163 -0
  128. package/src/live-voice/live-voice-session.ts +23 -6
  129. package/src/live-voice/protocol.ts +22 -0
  130. package/src/live-voice/share-targets.ts +225 -0
  131. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +144 -3
  132. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -0
  133. package/src/mcp/client.ts +1 -19
  134. package/src/mcp/mcp-oauth-provider.ts +66 -49
  135. package/src/media/openrouter-image-service.ts +39 -22
  136. package/src/monitoring/__tests__/oom-inheritance-sweep.test.ts +86 -0
  137. package/src/monitoring/__tests__/oom-kill-reporter.test.ts +374 -0
  138. package/src/monitoring/control.ts +2 -1
  139. package/src/monitoring/oom-inheritance-sweep.ts +96 -0
  140. package/src/monitoring/oom-kill-reporter.ts +469 -0
  141. package/src/monitoring/resource-sampler.ts +30 -0
  142. package/src/notifications/README.md +53 -4
  143. package/src/notifications/__tests__/assistant-initiated-thread.test.ts +254 -0
  144. package/src/notifications/__tests__/broadcaster.test.ts +558 -6
  145. package/src/notifications/__tests__/completion-policy.test.ts +37 -0
  146. package/src/notifications/__tests__/from-me-alert-integration.test.ts +192 -0
  147. package/src/notifications/adapters/platform.ts +14 -0
  148. package/src/notifications/assistant-initiated-thread.ts +101 -0
  149. package/src/notifications/broadcaster.ts +94 -16
  150. package/src/notifications/chat-reply-policy.ts +11 -0
  151. package/src/notifications/completion-policy.ts +12 -2
  152. package/src/notifications/conversation-pairing.ts +15 -70
  153. package/src/notifications/emit-signal.ts +34 -3
  154. package/src/notifications/types.ts +2 -0
  155. package/src/oauth/connect-orchestrator.ts +1 -2
  156. package/src/persistence/__tests__/conversation-auto-archive.test.ts +330 -0
  157. package/src/persistence/conversation-auto-archive.ts +170 -0
  158. package/src/persistence/conversation-crud.ts +2 -1
  159. package/src/persistence/conversation-queries.ts +15 -8
  160. package/src/persistence/embeddings/embed.test.ts +96 -0
  161. package/src/persistence/embeddings/embed.ts +20 -2
  162. package/src/persistence/embeddings/embedding-gemini.test.ts +39 -3
  163. package/src/persistence/embeddings/embedding-gemini.ts +39 -1
  164. package/src/persistence/embeddings/embedding-local.ts +6 -0
  165. package/src/persistence/embeddings/qdrant-manager.ts +5 -0
  166. package/src/persistence/job-utils.ts +10 -0
  167. package/src/persistence/migrations/384-add-document-revision.test.ts +83 -0
  168. package/src/persistence/migrations/384-add-document-revision.ts +21 -0
  169. package/src/persistence/migrations/385-create-document-revisions.test.ts +67 -0
  170. package/src/persistence/migrations/385-create-document-revisions.ts +28 -0
  171. package/src/persistence/migrations/386-conversations-last-reopened-at.ts +10 -0
  172. package/src/persistence/migrations/__tests__/386-conversations-last-reopened-at.test.ts +32 -0
  173. package/src/persistence/schema/conversations.ts +1 -0
  174. package/src/persistence/schema/documents.ts +20 -0
  175. package/src/persistence/steps.ts +6 -0
  176. package/src/platform/managed-speech.ts +42 -46
  177. package/src/plugin-api/vision-support.test.ts +21 -21
  178. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +11 -6
  179. package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
  180. package/src/providers/__tests__/search-provider-catalog.test.ts +8 -0
  181. package/src/providers/anthropic/client.ts +1 -1
  182. package/src/providers/fireworks/client.test.ts +168 -0
  183. package/src/providers/fireworks/client.ts +24 -0
  184. package/src/providers/inference/adapter-factory.ts +6 -0
  185. package/src/providers/ionet/client.test.ts +143 -0
  186. package/src/providers/ionet/client.ts +37 -0
  187. package/src/providers/model-catalog.ts +141 -84
  188. package/src/providers/model-intents.ts +4 -4
  189. package/src/providers/openai/chat-completions-provider.ts +23 -1
  190. package/src/providers/retry.ts +56 -4
  191. package/src/providers/search-provider-catalog.ts +10 -0
  192. package/src/providers/types.ts +10 -2
  193. package/src/providers/usage-tracking.ts +5 -3
  194. package/src/runtime/assistant-event-hub.ts +23 -0
  195. package/src/runtime/auth/__tests__/guard-tests.test.ts +0 -7
  196. package/src/runtime/auth/__tests__/route-policy.test.ts +1 -1
  197. package/src/runtime/channel-retry-sweep.ts +1 -2
  198. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
  199. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
  200. package/src/runtime/migrations/vbundle-builder.ts +19 -3
  201. package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
  202. package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
  203. package/src/runtime/migrations/vbundle-validator.ts +54 -1
  204. package/src/runtime/pending-interactions.ts +20 -2
  205. package/src/runtime/routes/__tests__/contact-routes.test.ts +5 -3
  206. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +36 -0
  207. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +375 -2
  208. package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
  209. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +12 -0
  210. package/src/runtime/routes/contact-routes.ts +2 -2
  211. package/src/runtime/routes/conversation-list-routes.ts +10 -2
  212. package/src/runtime/routes/conversation-query-routes.ts +48 -10
  213. package/src/runtime/routes/documents-routes.ts +185 -47
  214. package/src/runtime/routes/events-routes.ts +1 -3
  215. package/src/runtime/routes/host-app-control-routes.ts +1 -0
  216. package/src/runtime/routes/host-bash-routes.ts +1 -0
  217. package/src/runtime/routes/host-browser-routes.ts +15 -0
  218. package/src/runtime/routes/host-cu-routes.ts +4 -1
  219. package/src/runtime/routes/host-file-routes.ts +1 -0
  220. package/src/runtime/routes/host-proxy-result-binding.test.ts +83 -0
  221. package/src/runtime/routes/host-proxy-result-binding.ts +25 -0
  222. package/src/runtime/routes/host-transfer-routes.ts +1 -0
  223. package/src/runtime/routes/identity-routes.ts +3 -3
  224. package/src/runtime/routes/integrations/a2a.ts +4 -1
  225. package/src/runtime/routes/migration-routes.ts +3 -1
  226. package/src/runtime/routes/notification-routes.ts +1 -2
  227. package/src/runtime/routes/ui-snapshot-routes.test.ts +1 -0
  228. package/src/runtime/routes/user-route-dispatcher.ts +5 -1
  229. package/src/runtime/verification-templates.ts +1 -2
  230. package/src/sequence/analytics.ts +0 -4
  231. package/src/sequence/engine.ts +1 -11
  232. package/src/sequence/guardrails.ts +1 -6
  233. package/src/sequence/importer.ts +0 -8
  234. package/src/sequence/store.ts +0 -10
  235. package/src/signals/user-message.ts +0 -6
  236. package/src/skills/catalog-install.ts +52 -23
  237. package/src/tools/browser/browser-execution.ts +20 -23
  238. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +1 -3
  239. package/src/tools/document/document-tool.ts +13 -11
  240. package/src/tools/executor.ts +1 -2
  241. package/src/tools/host-terminal/host-shell.ts +4 -2
  242. package/src/tools/network/__tests__/web-search.test.ts +105 -0
  243. package/src/tools/network/web-fetch.ts +2 -4
  244. package/src/tools/network/web-search.ts +239 -12
  245. package/src/tools/shared/filesystem/format-diff.ts +1 -2
  246. package/src/tools/shared/shell-output.test.ts +42 -0
  247. package/src/tools/shared/shell-output.ts +27 -4
  248. package/src/tools/terminal/shell.ts +3 -3
  249. package/src/tools/ui-surface/visual-validation.ts +1 -2
  250. package/src/usage/attribution.ts +18 -1
  251. package/src/util/__tests__/oom-priority.test.ts +55 -0
  252. package/src/util/__tests__/worker-memory.test.ts +16 -0
  253. package/src/util/debounce.ts +0 -4
  254. package/src/util/oom-priority.ts +79 -0
  255. package/src/util/platform.ts +0 -2
  256. package/src/util/plugin-manifest.ts +5 -7
  257. package/src/util/pricing.ts +25 -0
  258. package/src/util/worker-memory.ts +9 -3
  259. package/src/util/worker-process.ts +8 -0
  260. package/src/workspace/byok-default-profile-ensure.ts +11 -0
  261. package/src/workspace/hatched-date.ts +11 -4
  262. package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +209 -0
  263. package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +197 -0
  264. 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,
package/src/agent/loop.ts CHANGED
@@ -14,7 +14,10 @@ import {
14
14
  getCalibrationProviderKey,
15
15
  } from "../context/token-estimator.js";
16
16
  import { spoolAndStubOversizedToolResults } from "../context/tool-result-spool.js";
17
- import { parseActualTokensFromError } from "../daemon/parse-actual-tokens-from-error.js";
17
+ import {
18
+ looksLikeContextOverflowError,
19
+ parseActualTokensFromError,
20
+ } from "../daemon/parse-actual-tokens-from-error.js";
18
21
  import type { TrustContext } from "../daemon/trust-context-types.js";
19
22
  import type {
20
23
  AgentLoopExitReason,
@@ -48,10 +51,7 @@ import type {
48
51
  ToolDefinition,
49
52
  ToolResultContent,
50
53
  } from "../providers/types.js";
51
- import {
52
- isContextOverflowError,
53
- NATIVE_WEB_SEARCH_TOOL_NAME,
54
- } from "../providers/types.js";
54
+ import { NATIVE_WEB_SEARCH_TOOL_NAME } from "../providers/types.js";
55
55
  import { recordWatchdogEvent } from "../telemetry/watchdog-events-store.js";
56
56
  import {
57
57
  ABORT_SETTLE_GRACE_MS,
@@ -747,6 +747,12 @@ interface AgentLoopRunOptionsBase {
747
747
  * call-site named profile. Missing profile names silently fall through.
748
748
  */
749
749
  overrideProfile?: string;
750
+ /**
751
+ * Who chose `overrideProfile`: `"auto"` when the Auto profile's router
752
+ * picked it for this turn. Threaded onto each send's config so usage
753
+ * attribution reports the routed profile under the `auto` source.
754
+ */
755
+ overrideProfileOrigin?: "auto";
750
756
  /**
751
757
  * Float the override profile above the call-site layers (named site
752
758
  * profile + call-site override) for non-main-agent call sites — the
@@ -1454,6 +1460,7 @@ export class AgentLoop {
1454
1460
  supportsDynamicUi = true,
1455
1461
  trust,
1456
1462
  overrideProfile,
1463
+ overrideProfileOrigin,
1457
1464
  forceOverrideProfile = false,
1458
1465
  resolveOverrideProfile,
1459
1466
  onModelCallPrepared,
@@ -2036,6 +2043,16 @@ export class AgentLoop {
2036
2043
  const effectiveOverrideProfile = resolveEffectiveOverrideProfile();
2037
2044
  if (effectiveOverrideProfile) {
2038
2045
  providerConfig.overrideProfile = effectiveOverrideProfile;
2046
+ // The origin describes the turn-start override. A profile switched
2047
+ // in mid-turn (a confirmed profile session) is the user's pick, so
2048
+ // the origin applies only while the effective override is still
2049
+ // the one the turn started with.
2050
+ if (
2051
+ overrideProfileOrigin &&
2052
+ effectiveOverrideProfile === overrideProfile
2053
+ ) {
2054
+ providerConfig.overrideProfileOrigin = overrideProfileOrigin;
2055
+ }
2039
2056
  if (forceOverrideProfile) {
2040
2057
  providerConfig.forceOverrideProfile = true;
2041
2058
  }
@@ -2236,7 +2253,13 @@ export class AgentLoop {
2236
2253
  // resolver layers `llm.profiles[overrideProfile]` at the top of
2237
2254
  // precedence for the user-facing call, so a model router can pick
2238
2255
  // the profile per message; clearing it drops any seeded override.
2256
+ // The hook context is seeded with the effective override, so an
2257
+ // unchanged profile is still the router's pick and keeps its
2258
+ // origin; a profile the hook changed or cleared is the hook's.
2239
2259
  const hookModelProfile = finalPreModelCtx.modelProfile?.trim();
2260
+ if (hookModelProfile !== effectiveOverrideProfile) {
2261
+ delete providerConfig.overrideProfileOrigin;
2262
+ }
2240
2263
  if (hookModelProfile) {
2241
2264
  providerConfig.overrideProfile = hookModelProfile;
2242
2265
  if (forceOverrideProfile) {
@@ -3170,7 +3193,7 @@ export class AgentLoop {
3170
3193
  // it is disabled (e.g. agent wakes) there is no ladder to drive, so the
3171
3194
  // overflow falls through to the generic error path below.
3172
3195
  if (
3173
- isContextOverflowError(error) &&
3196
+ looksLikeContextOverflowError(error) &&
3174
3197
  (options.resolveContextWindow?.().overflowRecovery.enabled ?? false)
3175
3198
  ) {
3176
3199
  if (overflowLadderExhausted) {
@@ -19,6 +19,11 @@ export const DocumentEditorShowEventSchema = z.object({
19
19
  surfaceId: z.string(),
20
20
  title: z.string(),
21
21
  initialContent: z.string(),
22
+ /**
23
+ * The document's revision once this content is in place. A client that
24
+ * saves with `baseRevision` adopts it as its new base.
25
+ */
26
+ revision: z.number().optional(),
22
27
  });
23
28
 
24
29
  export type DocumentEditorShowEvent = z.infer<
@@ -20,6 +20,16 @@ export const DocumentEditorUpdateEventSchema = z.object({
20
20
  surfaceId: z.string(),
21
21
  markdown: z.string(),
22
22
  mode: z.string(),
23
+ /**
24
+ * The document's revision once this content is in place. A client that
25
+ * saves with `baseRevision` adopts it as its new base.
26
+ */
27
+ revision: z.number().optional(),
28
+ /**
29
+ * The document's title when this update also sets it (a restore). A client
30
+ * renames the open document to it.
31
+ */
32
+ title: z.string().optional(),
23
33
  });
24
34
 
25
35
  export type DocumentEditorUpdateEvent = z.infer<
@@ -55,6 +55,7 @@ export const WebSearchProviderIdSchema = z.enum([
55
55
  "searxng",
56
56
  "tinyfish",
57
57
  "exa",
58
+ "serply",
58
59
  ]);
59
60
 
60
61
  export type WebSearchProviderId = z.infer<typeof WebSearchProviderIdSchema>;