@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
@@ -166,6 +166,7 @@ describe("identity routes — health endpoint", () => {
166
166
  memoryOptOut: true,
167
167
  retryLastTurn: true,
168
168
  appPins: true,
169
+ chatsSettings: true,
169
170
  });
170
171
 
171
172
  // Profiler should either be absent or show enabled: false
@@ -492,9 +493,9 @@ describe("identity routes — /readyz readiness gate", () => {
492
493
  expect(body.ready).toBe(false);
493
494
  expect(body.reason).toBe("db_migrations_failed");
494
495
  expect((body.dbMigrations as Record<string, unknown>).state).toBe("failed");
495
- expect((body.dbMigrations as Record<string, unknown>).failedMigrations).toEqual(
496
- [{ name: "flakyStep", error: "transient failure" }],
497
- );
496
+ expect(
497
+ (body.dbMigrations as Record<string, unknown>).failedMigrations,
498
+ ).toEqual([{ name: "flakyStep", error: "transient failure" }]);
498
499
  });
499
500
 
500
501
  test("returns 200 even if CES is down", async () => {
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { beforeEach, describe, expect, test } from "bun:test";
10
10
 
11
+ import { MESSAGE_KEYS } from "../i18n/index.js";
11
12
  import { setConfig } from "./helpers/set-config.js";
12
13
 
13
14
  // Keep the memory system off so addMessage skips indexing side effects.
@@ -31,6 +32,7 @@ function resetTables() {
31
32
 
32
33
  interface MessagePayload {
33
34
  role: string;
35
+ textSegments?: string[];
34
36
  clientMessageId?: string;
35
37
  }
36
38
 
@@ -59,6 +61,47 @@ describe("handleListMessages clientMessageId", () => {
59
61
  expect(body.messages[0].clientMessageId).toBe("nonce-abc");
60
62
  });
61
63
 
64
+ test("localizes persisted assistant keys without rewriting legacy text", async () => {
65
+ const localizedConv = createConversation();
66
+ await addMessage(
67
+ localizedConv.id,
68
+ "assistant",
69
+ JSON.stringify([
70
+ {
71
+ type: "text",
72
+ text: MESSAGE_KEYS.AGENT_LOOP_UNPARSEABLE_TOOL_RETRY_STOP,
73
+ },
74
+ ]),
75
+ { skipIndexing: true },
76
+ );
77
+
78
+ const localizedResponse = await handleListMessages({
79
+ queryParams: { conversationId: localizedConv.id },
80
+ headers: { "accept-language": "es" },
81
+ });
82
+ const localizedBody = localizedResponse as { messages: MessagePayload[] };
83
+ expect(localizedBody.messages[0]?.textSegments).toEqual([
84
+ "Me detuve después de varias llamadas de herramienta con formato incorrecto para evitar que esta conversación creciera indefinidamente. Inténtalo de nuevo o cambia de modelo si el problema continúa.",
85
+ ]);
86
+
87
+ const legacyConv = createConversation();
88
+ const legacy =
89
+ "I stopped after repeated malformed tool calls to keep this conversation from growing indefinitely. Please try again, or switch models if the problem continues.";
90
+ await addMessage(
91
+ legacyConv.id,
92
+ "assistant",
93
+ JSON.stringify([{ type: "text", text: legacy }]),
94
+ { skipIndexing: true },
95
+ );
96
+
97
+ const legacyResponse = await handleListMessages({
98
+ queryParams: { conversationId: legacyConv.id },
99
+ headers: { "accept-language": "es" },
100
+ });
101
+ const legacyBody = legacyResponse as { messages: MessagePayload[] };
102
+ expect(legacyBody.messages[0]?.textSegments).toEqual([legacy]);
103
+ });
104
+
62
105
  test("omits clientMessageId when the row was persisted without one", async () => {
63
106
  // GIVEN a user message persisted without a client nonce (e.g. a channel send)
64
107
  const conv = createConversation();
@@ -390,9 +390,9 @@ describe("LLM catalog parity: daemon vs client", () => {
390
390
  });
391
391
  });
392
392
 
393
- test("Fireworks catalog includes GLM 5.2", () => {
393
+ test("Fireworks catalog includes GLM 5.3", () => {
394
394
  expect(
395
- isModelInCatalog("fireworks", "accounts/fireworks/models/glm-5p2"),
395
+ isModelInCatalog("fireworks", "accounts/fireworks/models/glm-5p3"),
396
396
  ).toBe(true);
397
397
 
398
398
  const fireworks = PROVIDER_CATALOG.find(
@@ -400,10 +400,10 @@ describe("LLM catalog parity: daemon vs client", () => {
400
400
  );
401
401
  expect(
402
402
  fireworks?.models.find(
403
- (model) => model.id === "accounts/fireworks/models/glm-5p2",
403
+ (model) => model.id === "accounts/fireworks/models/glm-5p3",
404
404
  ),
405
405
  ).toMatchObject({
406
- displayName: "GLM 5.2",
406
+ displayName: "GLM 5.3",
407
407
  contextWindowTokens: 1040000,
408
408
  maxOutputTokens: 131072,
409
409
  supportsToolUse: true,
@@ -198,7 +198,7 @@ describe("LLMSchema", () => {
198
198
  profiles: {
199
199
  "glm-default": {
200
200
  provider: "vellum",
201
- model: "accounts/fireworks/models/glm-5p2",
201
+ model: "accounts/fireworks/models/glm-5p3",
202
202
  },
203
203
  },
204
204
  activeProfile: "glm-default",
@@ -1,11 +1,14 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
+ import { saveRawConfig } from "../config/loader.js";
4
+
3
5
  // ── Mock logger ──────────────────────────────────────────────────────────────
4
6
 
5
7
  // ── Mock sleep so retry tests don't slow down the suite ──────────────────────
6
8
 
9
+ let onRetrySleep: () => Promise<void> = async () => {};
7
10
  mock.module("../util/retry.js", () => ({
8
- sleep: async (_ms: number): Promise<void> => {},
11
+ sleep: async (_ms: number): Promise<void> => onRetrySleep(),
9
12
  isRetryableStatus: (status: number): boolean =>
10
13
  status === 429 || status >= 500,
11
14
  isRetryableNetworkError: (error: unknown): boolean => {
@@ -41,10 +44,12 @@ const fetchResponses: Array<{ ok: boolean; status: number; body?: string }> =
41
44
  [];
42
45
  const fetchErrors: Error[] = [];
43
46
  let clientAvailable = true;
47
+ let onClientCreation: () => Promise<void> = async () => {};
44
48
 
45
49
  mock.module("../platform/client.js", () => ({
46
50
  VellumPlatformClient: {
47
51
  create: async () => {
52
+ await onClientCreation();
48
53
  if (!clientAvailable) {
49
54
  return null;
50
55
  }
@@ -117,12 +122,89 @@ function makeDestination(
117
122
 
118
123
  describe("PlatformPushAdapter", () => {
119
124
  beforeEach(() => {
125
+ saveRawConfig({});
120
126
  fetchCalls.length = 0;
121
127
  fetchResponses.length = 0;
122
128
  fetchErrors.length = 0;
123
129
  clientAvailable = true;
130
+ onRetrySleep = async () => {};
131
+ onClientCreation = async () => {};
132
+ });
133
+
134
+ test("skips a disabled chat reply without sending a native push", async () => {
135
+ saveRawConfig({ notifications: { newMessageEnabled: false } });
136
+ const result = await new PlatformPushAdapter().send(
137
+ makePayload({ sourceEventName: "chat.assistant_reply" }),
138
+ makeDestination(),
139
+ );
140
+ expect(result).toMatchObject({
141
+ success: false,
142
+ skipped: true,
143
+ remotePushAccepted: false,
144
+ });
145
+ expect(fetchCalls).toHaveLength(0);
146
+ });
147
+
148
+ test("rechecks the setting after platform client creation", async () => {
149
+ onClientCreation = async () => {
150
+ saveRawConfig({ notifications: { newMessageEnabled: false } });
151
+ };
152
+ const result = await new PlatformPushAdapter().send(
153
+ makePayload({ sourceEventName: "chat.assistant_reply" }),
154
+ makeDestination(),
155
+ );
156
+ expect(result).toMatchObject({
157
+ success: false,
158
+ skipped: true,
159
+ remotePushAccepted: false,
160
+ });
161
+ expect(fetchCalls).toHaveLength(0);
124
162
  });
125
163
 
164
+ test.each([false, true])(
165
+ "stops retries after disabling replies (accepted push: %s)",
166
+ async (accepted) => {
167
+ fetchResponses.push({
168
+ ok: false,
169
+ status: 503,
170
+ body: accepted ? '{"accepted_platforms":["ios"]}' : "{}",
171
+ });
172
+ onRetrySleep = async () => {
173
+ saveRawConfig({ notifications: { newMessageEnabled: false } });
174
+ };
175
+ const result = await new PlatformPushAdapter().send(
176
+ makePayload({ sourceEventName: "chat.assistant_reply" }),
177
+ makeDestination(),
178
+ );
179
+ expect(fetchCalls).toHaveLength(1);
180
+ expect(result).toMatchObject({
181
+ success: accepted,
182
+ skipped: !accepted,
183
+ remotePushAccepted: accepted,
184
+ remotePushPlatforms: accepted ? ["ios"] : undefined,
185
+ });
186
+ },
187
+ );
188
+
189
+ test.each([
190
+ "schedule.result",
191
+ "schedule.notify",
192
+ "guardian.question",
193
+ "activity.failed",
194
+ "chat.assistant_reply.extra",
195
+ ])(
196
+ "keeps %s native push enabled when ordinary reply alerts are off",
197
+ async (sourceEventName) => {
198
+ saveRawConfig({ notifications: { newMessageEnabled: false } });
199
+ const result = await new PlatformPushAdapter().send(
200
+ makePayload({ sourceEventName }),
201
+ makeDestination(),
202
+ );
203
+ expect(result.success).toBe(true);
204
+ expect(fetchCalls).toHaveLength(1);
205
+ },
206
+ );
207
+
126
208
  test("channel is 'platform'", () => {
127
209
  expect(new PlatformPushAdapter().channel).toBe("platform");
128
210
  });
@@ -48,6 +48,8 @@ let mockOAuthCallbackUrl = "";
48
48
  // getPublicBaseUrl.
49
49
  mock.module("../inbound/public-ingress-urls.js", () => ({
50
50
  getOAuthCallbackUrl: () => mockOAuthCallbackUrl,
51
+ getMcpOAuthClientMetadataUrl: () =>
52
+ "http://localhost/oauth/client-metadata.json",
51
53
  getPublicBaseUrl: (config?: { ingress?: { publicBaseUrl?: string } }) => {
52
54
  const url = config?.ingress?.publicBaseUrl ?? "";
53
55
  if (!url) {
@@ -1178,6 +1178,33 @@ describe("OpenAIProvider", () => {
1178
1178
  });
1179
1179
  });
1180
1180
 
1181
+ test("bounds malformed arguments in the diagnostic raw response only", async () => {
1182
+ const raw = '{"content":"' + "x".repeat(1_000);
1183
+ fakeChunks = [
1184
+ ...toolCallChunks([{ id: "call_large_bad", name: "test", args: raw }]),
1185
+ usageChunk(10, 5),
1186
+ ];
1187
+
1188
+ const result = await provider.sendMessage([userMsg("test")]);
1189
+
1190
+ expect(result.content[0]).toEqual({
1191
+ type: "tool_use",
1192
+ id: "call_large_bad",
1193
+ name: "test",
1194
+ input: { _raw: raw },
1195
+ });
1196
+ const rawResponse = result.rawResponse as {
1197
+ choices: Array<{
1198
+ message: {
1199
+ tool_calls: Array<{ function: { arguments: string } }>;
1200
+ };
1201
+ }>;
1202
+ };
1203
+ expect(
1204
+ rawResponse.choices[0]!.message.tool_calls[0]!.function.arguments,
1205
+ ).toBe(raw.slice(0, 200) + "…");
1206
+ });
1207
+
1181
1208
  // -----------------------------------------------------------------------
1182
1209
  // stream_options and model
1183
1210
  // -----------------------------------------------------------------------
@@ -1960,10 +1987,10 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
1960
1987
  lastCreateParams = null;
1961
1988
  });
1962
1989
 
1963
- test('DeepSeek V4 Pro accepts "max" unclamped', async () => {
1990
+ test('DeepSeek V4.1 Flash accepts "max" unclamped', async () => {
1964
1991
  const fw = new FireworksProvider(
1965
1992
  "fw-key",
1966
- "accounts/fireworks/models/deepseek-v4-pro-0813",
1993
+ "accounts/fireworks/models/deepseek-v4p1-flash",
1967
1994
  );
1968
1995
  await fw.sendMessage([userMsg("hi")], {
1969
1996
  systemPrompt: "system",
@@ -1972,10 +1999,10 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
1972
1999
  expect(lastCreateParams!.reasoning_effort).toBe("max");
1973
2000
  });
1974
2001
 
1975
- test('DeepSeek V4 Pro accepts "xhigh" unclamped', async () => {
2002
+ test('DeepSeek V4.1 Flash accepts "xhigh" unclamped', async () => {
1976
2003
  const fw = new FireworksProvider(
1977
2004
  "fw-key",
1978
- "accounts/fireworks/models/deepseek-v4-pro-0813",
2005
+ "accounts/fireworks/models/deepseek-v4p1-flash",
1979
2006
  );
1980
2007
  await fw.sendMessage([userMsg("hi")], {
1981
2008
  systemPrompt: "system",
@@ -1984,10 +2011,10 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
1984
2011
  expect(lastCreateParams!.reasoning_effort).toBe("xhigh");
1985
2012
  });
1986
2013
 
1987
- test('Kimi K2.6 clamps "xhigh"/"max" down to "high"', async () => {
2014
+ test('MiniMax M3 clamps "xhigh"/"max" down to "high"', async () => {
1988
2015
  const fw = new FireworksProvider(
1989
2016
  "fw-key",
1990
- "accounts/fireworks/models/kimi-k2p6",
2017
+ "accounts/fireworks/models/minimax-m3",
1991
2018
  );
1992
2019
  await fw.sendMessage([userMsg("hi")], {
1993
2020
  systemPrompt: "system",
@@ -2016,7 +2043,7 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
2016
2043
  test("effort below ceiling is forwarded verbatim", async () => {
2017
2044
  const fw = new FireworksProvider(
2018
2045
  "fw-key",
2019
- "accounts/fireworks/models/deepseek-v4-pro-0813",
2046
+ "accounts/fireworks/models/deepseek-v4p1-flash",
2020
2047
  );
2021
2048
  await fw.sendMessage([userMsg("hi")], {
2022
2049
  systemPrompt: "system",
@@ -2028,7 +2055,7 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
2028
2055
  test('effort: "none" passes through regardless of ceiling', async () => {
2029
2056
  const fw = new FireworksProvider(
2030
2057
  "fw-key",
2031
- "accounts/fireworks/models/kimi-k2p6",
2058
+ "accounts/fireworks/models/minimax-m3",
2032
2059
  );
2033
2060
  await fw.sendMessage([userMsg("hi")], {
2034
2061
  systemPrompt: "system",
@@ -2040,13 +2067,13 @@ describe("FireworksProvider reasoning_effort ceiling", () => {
2040
2067
  test("model override picks ceiling from runtime model, not constructor model", async () => {
2041
2068
  const fw = new FireworksProvider(
2042
2069
  "fw-key",
2043
- "accounts/fireworks/models/kimi-k2p6",
2070
+ "accounts/fireworks/models/minimax-m3",
2044
2071
  );
2045
2072
  await fw.sendMessage([userMsg("hi")], {
2046
2073
  systemPrompt: "system",
2047
2074
  config: {
2048
2075
  effort: "max",
2049
- model: "accounts/fireworks/models/deepseek-v4-pro-0813",
2076
+ model: "accounts/fireworks/models/deepseek-v4p1-flash",
2050
2077
  },
2051
2078
  });
2052
2079
  expect(lastCreateParams!.reasoning_effort).toBe("max");
@@ -2111,7 +2138,7 @@ describe("FireworksProvider sparse reasoning_effort support (GLM 5.3)", () => {
2111
2138
 
2112
2139
  test("models without supportedEfforts are unaffected", async () => {
2113
2140
  expect(
2114
- await send("accounts/fireworks/models/deepseek-v4-pro-0813", "medium"),
2141
+ await send("accounts/fireworks/models/deepseek-v4p1-flash", "medium"),
2115
2142
  ).toBe("medium");
2116
2143
  });
2117
2144
  });
@@ -10,6 +10,17 @@ import {
10
10
  } from "../util/pricing.js";
11
11
 
12
12
  describe("resolvePricing", () => {
13
+ test.each([
14
+ ["accounts/fireworks/models/glm-5p2", 1.4 + 4.4],
15
+ ["accounts/fireworks/models/kimi-k2p6", 0.95 + 4.0],
16
+ ["accounts/fireworks/models/deepseek-v4-pro-0813", 1.32 + 3.96],
17
+ ["accounts/fireworks/models/deepseek-v4-flash-0731", 0.14 + 0.28],
18
+ ])("prices stored usage for retired Fireworks model %s", (model, cost) => {
19
+ const result = resolvePricing("fireworks", model, 1_000_000, 1_000_000);
20
+ expect(result.pricingStatus).toBe("priced");
21
+ expect(result.estimatedCostUsd).toBeCloseTo(cost, 10);
22
+ });
23
+
13
24
  describe("Anthropic models", () => {
14
25
  test("returns priced for claude-opus-4-6", () => {
15
26
  const result = resolvePricing(
@@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
6
6
  import { setIngressPublicBaseUrl } from "../config/env.js";
7
7
  import { IngressConfigSchema } from "../config/schemas/ingress.js";
8
8
  import {
9
+ getMcpOAuthClientMetadataUrl,
9
10
  getOAuthCallbackUrl,
10
11
  getPublicBaseUrl,
11
12
  getTelegramWebhookUrl,
@@ -307,6 +308,31 @@ describe("getOAuthCallbackUrl", () => {
307
308
  });
308
309
  expect(url).toBe("https://example.com/webhooks/oauth/callback");
309
310
  });
311
+
312
+ test("canonicalizes the base before building OAuth URLs", () => {
313
+ const config = {
314
+ ingress: {
315
+ publicBaseUrl: "https://EXAMPLE.com:443/a/../assistant-123///",
316
+ },
317
+ };
318
+
319
+ expect(getOAuthCallbackUrl(config)).toBe(
320
+ "https://example.com/assistant-123/webhooks/oauth/callback",
321
+ );
322
+ expect(getMcpOAuthClientMetadataUrl(config)).toBe(
323
+ "https://example.com/assistant-123/oauth/client-metadata.json",
324
+ );
325
+ });
326
+ });
327
+
328
+ describe("getMcpOAuthClientMetadataUrl", () => {
329
+ test("builds the public metadata URL", () => {
330
+ expect(
331
+ getMcpOAuthClientMetadataUrl({
332
+ ingress: { publicBaseUrl: "https://example.com/assistant-123/" },
333
+ }),
334
+ ).toBe("https://example.com/assistant-123/oauth/client-metadata.json");
335
+ });
310
336
  });
311
337
 
312
338
  // ---------------------------------------------------------------------------
@@ -144,6 +144,53 @@ describe("retry normalization: thinking + forced tool_choice", () => {
144
144
  expect(lastConfig()?.thinking).toEqual({ type: "adaptive" });
145
145
  });
146
146
 
147
+ for (const [providerName, model] of [
148
+ ["anthropic", "claude-sonnet-5-5"],
149
+ ["anthropic", "claude-opus-5-5"],
150
+ ["openrouter", "anthropic/claude-sonnet-5.5"],
151
+ ["vercel-ai-gateway", "anthropic/claude-sonnet-5.5"],
152
+ ] as const) {
153
+ test(`downgrades forced tool_choice to auto for ${providerName} ${model}`, async () => {
154
+ // These models always reason and reject forced tool use, so stripping
155
+ // `thinking` is not enough: the forced choice itself has to go.
156
+ setLlmConfig({
157
+ callSites: {
158
+ memoryExtraction: { provider: providerName, model },
159
+ },
160
+ });
161
+ const { provider, lastConfig } = makePipeline(providerName);
162
+ await provider.sendMessage([userMessage], {
163
+ config: {
164
+ callSite: "memoryExtraction",
165
+ tool_choice: { type: "tool", name: "extract_graph_diff" },
166
+ },
167
+ });
168
+ expect(lastConfig()?.tool_choice).toEqual({ type: "auto" });
169
+ });
170
+ }
171
+
172
+ test("keeps forced tool_choice for an adaptive-only model off the Anthropic wire", async () => {
173
+ setLlmConfig({
174
+ callSites: {
175
+ memoryExtraction: {
176
+ provider: "fireworks",
177
+ model: "accounts/fireworks/models/glm-5p3",
178
+ },
179
+ },
180
+ });
181
+ const { provider, lastConfig } = makePipeline("fireworks");
182
+ await provider.sendMessage([userMessage], {
183
+ config: {
184
+ callSite: "memoryExtraction",
185
+ tool_choice: { type: "tool", name: "extract_graph_diff" },
186
+ },
187
+ });
188
+ expect(lastConfig()?.tool_choice).toEqual({
189
+ type: "tool",
190
+ name: "extract_graph_diff",
191
+ });
192
+ });
193
+
147
194
  test("preserves explicit thinking: disabled with forced tool_choice", async () => {
148
195
  // Callers like the retriever explicitly set thinking: { type: "disabled" }
149
196
  // alongside forced tool_choice. This should pass through unchanged since
@@ -188,6 +188,17 @@ describe("recoverSkillExecuteEnvelope", () => {
188
188
  expect(recoverSkillExecuteEnvelope(wrapped)).toBe(wrapped);
189
189
  });
190
190
 
191
+ test("keeps recovered JSON wrapped when it lacks a string tool name", () => {
192
+ for (const raw of [
193
+ JSON.stringify({ input: { content: "hi" }, activity: "x" }),
194
+ JSON.stringify({ tool: 42, input: {}, activity: "x" }),
195
+ JSON.stringify(["document_update"]),
196
+ ]) {
197
+ const wrapped = { _raw: raw };
198
+ expect(recoverSkillExecuteEnvelope(wrapped)).toBe(wrapped);
199
+ }
200
+ });
201
+
191
202
  test("passes a normal envelope through untouched", () => {
192
203
  const envelope = {
193
204
  tool: "document_update",
@@ -1357,6 +1357,10 @@ describe("ToolApprovalHandler / unparseable tool args gate", () => {
1357
1357
  }
1358
1358
  expect(result.result.content).not.toContain(raw);
1359
1359
  expect(result.result.content).toContain("…");
1360
+ expect(auditCalls.error).toHaveLength(1);
1361
+ expect(auditCalls.error[0].input).toEqual({
1362
+ _raw: raw.slice(0, 200) + "…",
1363
+ });
1360
1364
  });
1361
1365
 
1362
1366
  test("legitimate input containing a _raw field among others is not rejected", async () => {
@@ -341,6 +341,32 @@ describe("tool preview lifecycle", () => {
341
341
  expect((emitted as any).conversationId).toBe("test-session-id");
342
342
  });
343
343
 
344
+ test("caps cumulative app preview payloads and stops forwarding growth", () => {
345
+ const collector = createEventCollector();
346
+ const deps = createMockDeps({ onEvent: collector.onEvent });
347
+ const accumulatedJson = '{"code":"' + "x".repeat(300_000);
348
+
349
+ handleInputJsonDelta(state, deps, {
350
+ type: "input_json_delta",
351
+ toolName: "app_create",
352
+ toolUseId: "toolu_large_app",
353
+ accumulatedJson,
354
+ });
355
+ handleInputJsonDelta(state, deps, {
356
+ type: "input_json_delta",
357
+ toolName: "app_create",
358
+ toolUseId: "toolu_large_app",
359
+ accumulatedJson: accumulatedJson + "more",
360
+ });
361
+
362
+ expect(collector.events).toHaveLength(1);
363
+ const emitted = collector.events[0] as Extract<
364
+ AssistantEvent,
365
+ { type: "tool_input_delta" }
366
+ >;
367
+ expect(emitted.content).toBe(accumulatedJson.slice(0, 256_000));
368
+ });
369
+
344
370
  test("a streaming ui_show visual announces its surface exactly once", () => {
345
371
  const collector = createEventCollector();
346
372
  const deps = createMockDeps({ onEvent: collector.onEvent });
@@ -109,6 +109,37 @@ describe("resolveUsageAttribution", () => {
109
109
  expectResolvedProviderModelMatchesResolver("mainAgent", "pinned");
110
110
  });
111
111
 
112
+ test("reports an override the Auto router chose as the auto source", () => {
113
+ setLlmConfig({
114
+ profiles: {
115
+ active: { provider: "openai", model: "gpt-5.4" },
116
+ routed: { provider: "gemini", model: "gemini-3-pro" },
117
+ },
118
+ activeProfile: "active",
119
+ });
120
+
121
+ const snapshot = resolveUsageAttribution({
122
+ callSite: "mainAgent",
123
+ overrideProfile: "routed",
124
+ overrideProfileOrigin: "auto",
125
+ });
126
+
127
+ expect(snapshot).toMatchObject({
128
+ appliedProfile: "routed",
129
+ profileSource: "auto",
130
+ resolvedProvider: "gemini",
131
+ resolvedModel: "gemini-3-pro",
132
+ });
133
+ // The origin only relabels an override that actually won.
134
+ expect(
135
+ resolveUsageAttribution({
136
+ callSite: "mainAgent",
137
+ overrideProfile: "missing",
138
+ overrideProfileOrigin: "auto",
139
+ }).profileSource,
140
+ ).toBe("active");
141
+ });
142
+
112
143
  test("resolves call-site profile attribution", () => {
113
144
  setLlmConfig({
114
145
  profiles: {
@@ -100,7 +100,7 @@ describe("133-collapse-provider-connections migration", () => {
100
100
  source: "user",
101
101
  provider: "fireworks",
102
102
  provider_connection: "vellum",
103
- model: "accounts/fireworks/models/glm-5p2",
103
+ model: "accounts/fireworks/models/minimax-m3",
104
104
  },
105
105
  },
106
106
  },
@@ -257,7 +257,7 @@ describe("133-collapse-provider-connections migration", () => {
257
257
  encoded: {
258
258
  source: "user",
259
259
  provider: "vellum",
260
- model: "fireworks/accounts/fireworks/models/glm-5p2",
260
+ model: "fireworks/accounts/fireworks/models/minimax-m3",
261
261
  },
262
262
  },
263
263
  },
@@ -272,7 +272,7 @@ describe("133-collapse-provider-connections migration", () => {
272
272
  expect(llm.profiles.encoded).toEqual({
273
273
  source: "user",
274
274
  provider: "vellum",
275
- model: "accounts/fireworks/models/glm-5p2",
275
+ model: "accounts/fireworks/models/minimax-m3",
276
276
  });
277
277
  });
278
278