@vellumai/assistant 0.12.1 → 0.12.2-staging.1

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 (548) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/openapi.yaml +327 -52
  6. package/package.json +3 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  254. package/src/desktop/desktop-chrome-policy.ts +47 -0
  255. package/src/desktop/desktop-chrome-session.ts +37 -0
  256. package/src/desktop/desktop-dependencies.test.ts +102 -0
  257. package/src/desktop/desktop-dependencies.ts +308 -0
  258. package/src/desktop/desktop-feature.test.ts +18 -0
  259. package/src/desktop/desktop-feature.ts +19 -0
  260. package/src/desktop/desktop-panel-config.test.ts +135 -0
  261. package/src/desktop/desktop-panel-config.ts +149 -0
  262. package/src/desktop/desktop-session-manager.test.ts +730 -0
  263. package/src/desktop/desktop-session-manager.ts +850 -0
  264. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  265. package/src/desktop/desktop-stream-bridge.ts +214 -0
  266. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  267. package/src/desktop/desktop-wallpaper.ts +113 -0
  268. package/src/desktop/desktop-window-drag.ts +177 -0
  269. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  270. package/src/desktop/desktop-window-manager-config.ts +238 -0
  271. package/src/desktop/desktop-window-theme.ts +170 -0
  272. package/src/heartbeat/heartbeat-service.ts +21 -3
  273. package/src/home/__tests__/feed-writer.test.ts +22 -0
  274. package/src/home/feed-writer.ts +11 -2
  275. package/src/hooks/types.ts +33 -0
  276. package/src/i18n/AGENTS.md +22 -0
  277. package/src/i18n/__tests__/i18n.test.ts +108 -0
  278. package/src/i18n/index.ts +16 -0
  279. package/src/i18n/locales.ts +93 -0
  280. package/src/i18n/messages.ts +61 -0
  281. package/src/i18n/title.ts +25 -0
  282. package/src/inbound/platform-callback-registration.ts +4 -5
  283. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  284. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  285. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  286. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  287. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  288. package/src/live-voice/live-voice-photo.ts +25 -6
  289. package/src/live-voice/live-voice-session.ts +213 -693
  290. package/src/live-voice/protocol.ts +0 -2
  291. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  292. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  293. package/src/mcp/effective-config.ts +3 -3
  294. package/src/mcp/mcp-header-store.ts +29 -45
  295. package/src/mcp/spec-schema.ts +111 -0
  296. package/src/mcp/startup.ts +8 -4
  297. package/src/mcp/workspace-mcp-config.ts +188 -0
  298. package/src/messaging/provider.ts +10 -3
  299. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  300. package/src/messaging/providers/callback-routing.ts +18 -0
  301. package/src/messaging/providers/channel-transport.ts +56 -0
  302. package/src/messaging/providers/discord/transport.ts +17 -0
  303. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  304. package/src/messaging/providers/slack/adapter.ts +7 -44
  305. package/src/messaging/providers/slack/transport.ts +16 -0
  306. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  307. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  308. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  309. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  310. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  311. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  312. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  313. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  314. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  315. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  316. package/src/notifications/adapters/macos.ts +3 -0
  317. package/src/notifications/assistant-reply-producer.ts +8 -3
  318. package/src/notifications/conversation-pairing.ts +36 -16
  319. package/src/notifications/delivered-post-record.ts +11 -1
  320. package/src/notifications/guardian-feed-projection.ts +125 -5
  321. package/src/notifications/home-feed-side-effect.ts +55 -7
  322. package/src/notifications/schedule-result-producer.ts +66 -22
  323. package/src/oauth/AGENTS.md +2 -0
  324. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  325. package/src/oauth/byo-connection.test.ts +105 -0
  326. package/src/oauth/byo-connection.ts +36 -6
  327. package/src/oauth/connection-resolver.test.ts +73 -0
  328. package/src/oauth/connection-resolver.ts +95 -2
  329. package/src/oauth/scope-utils.ts +24 -0
  330. package/src/oauth/seed-providers.ts +204 -25
  331. package/src/outbound-proxy/certs.ts +3 -5
  332. package/src/permissions/checker.test.ts +11 -6
  333. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  334. package/src/persistence/conversation-crud.ts +71 -63
  335. package/src/persistence/conversation-lineage.ts +12 -4
  336. package/src/persistence/conversation-queries.ts +2 -2
  337. package/src/persistence/conversation-title-placeholders.ts +23 -0
  338. package/src/persistence/conversation-title-service.ts +9 -26
  339. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  340. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  341. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  342. package/src/persistence/message-cursor.ts +78 -0
  343. package/src/persistence/message-reads.ts +30 -0
  344. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  345. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  346. package/src/persistence/schema/memory-core.ts +5 -0
  347. package/src/platform/client.test.ts +4 -2
  348. package/src/platform/client.ts +2 -10
  349. package/src/plugin-api/constants.ts +2 -0
  350. package/src/plugin-api/index.ts +1 -0
  351. package/src/plugin-api/types.ts +2 -0
  352. package/src/plugins/defaults/index.ts +2 -0
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  358. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  359. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  360. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  361. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  362. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  363. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  364. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  365. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  366. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  367. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  368. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  369. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  370. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  371. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  372. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  374. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  375. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  376. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  377. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  378. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  379. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  380. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  381. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  382. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  383. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  384. package/src/plugins/defaults/memory/startup.ts +28 -2
  385. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  386. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  387. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  388. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  389. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  390. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  391. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  392. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  393. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  394. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  395. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  396. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  397. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  398. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  399. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  400. package/src/plugins/defaults/memory/worker.ts +6 -0
  401. package/src/plugins/mcp-servers.ts +15 -75
  402. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  403. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  404. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  405. package/src/providers/anthropic/client.ts +0 -47
  406. package/src/providers/gemini/client.ts +9 -23
  407. package/src/providers/gemini-thought-signature.test.ts +63 -0
  408. package/src/providers/gemini-thought-signature.ts +44 -0
  409. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  410. package/src/providers/inference/auth.ts +2 -3
  411. package/src/providers/inference/connection-availability.ts +27 -8
  412. package/src/providers/inference/endpoint-probe.ts +10 -1
  413. package/src/providers/inference/resolve-auth.ts +22 -4
  414. package/src/providers/inference/service-account-token.ts +248 -0
  415. package/src/providers/model-catalog.ts +34 -0
  416. package/src/providers/model-intents.ts +10 -5
  417. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  418. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  419. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  420. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  421. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  422. package/src/providers/openai/chat-completions-provider.ts +212 -79
  423. package/src/providers/openai/codex-models.ts +0 -4
  424. package/src/providers/openai/google-thought-signature.ts +320 -0
  425. package/src/providers/opencode/client.test.ts +73 -4
  426. package/src/providers/opencode/client.ts +60 -3
  427. package/src/providers/openrouter/client.ts +20 -0
  428. package/src/providers/outbound-request-sanitize.ts +96 -0
  429. package/src/providers/retry.ts +30 -19
  430. package/src/providers/unparseable-tool-args.ts +5 -1
  431. package/src/runtime/AGENTS.md +3 -1
  432. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  433. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  434. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  435. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  436. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  437. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  438. package/src/runtime/agent-wake.ts +2 -1
  439. package/src/runtime/assistant-event-hub.ts +21 -3
  440. package/src/runtime/auth/same-actor.ts +38 -3
  441. package/src/runtime/channel-send.ts +316 -0
  442. package/src/runtime/host-observe.ts +11 -1
  443. package/src/runtime/http-server.ts +160 -151
  444. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  445. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  446. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  447. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  448. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  449. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  450. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  451. package/src/runtime/routes/acp-routes.test.ts +15 -0
  452. package/src/runtime/routes/acp-routes.ts +72 -9
  453. package/src/runtime/routes/app-management-routes.ts +73 -0
  454. package/src/runtime/routes/app-routes.ts +1 -1
  455. package/src/runtime/routes/channel-send-routes.ts +85 -0
  456. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  457. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  458. package/src/runtime/routes/conversation-routes.ts +16 -3
  459. package/src/runtime/routes/credential-routes.ts +19 -10
  460. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  461. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  462. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  463. package/src/runtime/routes/events-routes.ts +4 -0
  464. package/src/runtime/routes/host-cu-routes.ts +6 -0
  465. package/src/runtime/routes/index.ts +4 -2
  466. package/src/runtime/routes/log-export-routes.ts +2 -0
  467. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  468. package/src/runtime/routes/migration-routes.ts +6 -20
  469. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  470. package/src/runtime/routes/platform-routes.ts +18 -15
  471. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  472. package/src/runtime/routes/workspace-routes.ts +46 -63
  473. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  474. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  475. package/src/runtime/routes/workspace-utils.ts +17 -0
  476. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  477. package/src/runtime/services/conversation-serializer.ts +16 -3
  478. package/src/runtime/sync/resource-sync-events.ts +2 -1
  479. package/src/schedule/run-script.ts +2 -1
  480. package/src/schedule/schedule-store.ts +9 -2
  481. package/src/schedule/worker.ts +1 -1
  482. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  483. package/src/security/ces-http-record-backend.ts +120 -0
  484. package/src/security/ces-rpc-record-backend.ts +7 -0
  485. package/src/security/untrusted-content.ts +3 -1
  486. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  487. package/src/skills/workspace-skill-md-signature.ts +50 -0
  488. package/src/subagent/consult-context.ts +12 -25
  489. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  490. package/src/tools/acp/list-agents.test.ts +3 -1
  491. package/src/tools/acp/spawn.test.ts +146 -11
  492. package/src/tools/acp/spawn.ts +21 -3
  493. package/src/tools/acp/status.test.ts +94 -0
  494. package/src/tools/acp/status.ts +40 -3
  495. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/browser/browser-execution.ts +1 -1
  497. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  498. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  499. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  500. package/src/tools/browser/network-recorder.ts +2 -1
  501. package/src/tools/capability-offer.test.ts +22 -2
  502. package/src/tools/capability-offer.ts +16 -1
  503. package/src/tools/computer-use/definitions.ts +121 -2
  504. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  505. package/src/tools/credentials/metadata-store.ts +100 -0
  506. package/src/tools/filesystem/search.ts +2 -1
  507. package/src/tools/host-filesystem/edit.test.ts +2 -1
  508. package/src/tools/host-filesystem/edit.ts +2 -2
  509. package/src/tools/host-filesystem/read.test.ts +2 -1
  510. package/src/tools/host-filesystem/read.ts +2 -2
  511. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  512. package/src/tools/host-filesystem/transfer.ts +2 -2
  513. package/src/tools/host-filesystem/write.test.ts +2 -1
  514. package/src/tools/host-filesystem/write.ts +2 -2
  515. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  516. package/src/tools/host-terminal/host-shell.ts +4 -20
  517. package/src/tools/network/web-search.ts +6 -1
  518. package/src/tools/policy-context.ts +6 -7
  519. package/src/tools/skills/scaffold-managed.ts +61 -3
  520. package/src/tools/subagent/spawn.ts +5 -8
  521. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  522. package/src/tools/terminal/sanitized-bash.ts +105 -0
  523. package/src/tools/watch/watch-retro-report.ts +0 -8
  524. package/src/types/host-capabilities.ts +5 -3
  525. package/src/util/__tests__/language-subtag.test.ts +23 -1
  526. package/src/util/ca-bundle.ts +13 -0
  527. package/src/util/language-subtag.ts +16 -0
  528. package/src/util/platform.ts +8 -0
  529. package/src/util/unicode.ts +15 -1
  530. package/src/watch/watch-timeline.ts +2 -2
  531. package/src/workflows/run-manager.ts +2 -1
  532. package/src/workspace/git-porcelain.ts +66 -0
  533. package/src/workspace/git-service.ts +6 -55
  534. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  535. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  536. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  537. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  538. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  539. package/src/workspace/migrations/registry.ts +6 -0
  540. package/src/__tests__/app-source-watcher.test.ts +0 -218
  541. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  542. package/src/__tests__/speaker-identification.test.ts +0 -56
  543. package/src/calls/speaker-identification.ts +0 -233
  544. package/src/daemon/app-source-watcher.ts +0 -218
  545. package/src/messaging/providers/whatsapp/client.ts +0 -29
  546. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  547. package/src/util/fs-watcher-error.ts +0 -36
  548. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -28,6 +28,9 @@ describe("model intents", () => {
28
28
  expect(resolveModelIntent("openai", "latency-optimized")).toBe(
29
29
  "gpt-5.6-luna",
30
30
  );
31
+ expect(resolveModelIntent("gemini", "cost-optimized")).toBe(
32
+ "gemini-3.1-flash-lite",
33
+ );
31
34
  expect(resolveModelIntent("gemini", "latency-optimized")).toBe(
32
35
  "gemini-3.1-flash-lite",
33
36
  );
@@ -3,6 +3,11 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
3
3
  // ── Mocks: declared before imports that depend on them ──────────────
4
4
 
5
5
  let updateMessageContentShouldThrow = false;
6
+ let assistantName: string | null = null;
7
+
8
+ mock.module("../daemon/identity-helpers.js", () => ({
9
+ getAssistantName: () => assistantName,
10
+ }));
6
11
 
7
12
  const updateMessageContentMock = mock(
8
13
  (_messageId: string, _content: string) => {
@@ -55,12 +60,21 @@ function makeDestination(
55
60
  function captureBroadcast(): {
56
61
  adapter: VellumAdapter;
57
62
  sent: AssistantEvent[];
63
+ conversationIds: Array<string | undefined>;
58
64
  } {
59
65
  const sent: AssistantEvent[] = [];
60
- const adapter = new VellumAdapter((msg) => sent.push(msg));
61
- return { adapter, sent };
66
+ const conversationIds: Array<string | undefined> = [];
67
+ const adapter = new VellumAdapter((msg, conversationId) => {
68
+ sent.push(msg);
69
+ conversationIds.push(conversationId);
70
+ });
71
+ return { adapter, sent, conversationIds };
62
72
  }
63
73
 
74
+ beforeEach(() => {
75
+ assistantName = null;
76
+ });
77
+
64
78
  describe("VellumAdapter silent flag", () => {
65
79
  test("non-urgent (low) urgency broadcasts silent: true", async () => {
66
80
  const { adapter, sent } = captureBroadcast();
@@ -181,6 +195,75 @@ describe("VellumAdapter remotePushDispatched pass-through", () => {
181
195
  });
182
196
  });
183
197
 
198
+ describe("VellumAdapter assistant name", () => {
199
+ test("broadcasts the current verified assistant name after trimming it", async () => {
200
+ assistantName = " Example Assistant ";
201
+ const { adapter, sent } = captureBroadcast();
202
+
203
+ await adapter.send(makePayload(), makeDestination());
204
+
205
+ const intent = sent[0] as Extract<
206
+ AssistantEvent,
207
+ { type: "notification_intent" }
208
+ >;
209
+ expect(intent.assistantName).toBe("Example Assistant");
210
+ });
211
+
212
+ test("omits the assistant name when it is unavailable", async () => {
213
+ const { adapter, sent } = captureBroadcast();
214
+
215
+ await adapter.send(makePayload(), makeDestination());
216
+
217
+ const intent = sent[0] as Extract<
218
+ AssistantEvent,
219
+ { type: "notification_intent" }
220
+ >;
221
+ expect(intent.assistantName).toBeUndefined();
222
+ expect("assistantName" in intent).toBe(false);
223
+ });
224
+
225
+ test("omits a blank assistant name", async () => {
226
+ assistantName = " \t\n ";
227
+ const { adapter, sent } = captureBroadcast();
228
+
229
+ await adapter.send(makePayload(), makeDestination());
230
+
231
+ const intent = sent[0] as Extract<
232
+ AssistantEvent,
233
+ { type: "notification_intent" }
234
+ >;
235
+ expect(intent.assistantName).toBeUndefined();
236
+ expect("assistantName" in intent).toBe(false);
237
+ });
238
+
239
+ test("reflects a renamed assistant on the next intent", async () => {
240
+ assistantName = "Assistant A";
241
+ const { adapter, sent } = captureBroadcast();
242
+ await adapter.send(makePayload(), makeDestination());
243
+
244
+ assistantName = "Assistant B";
245
+ await adapter.send(makePayload(), makeDestination());
246
+
247
+ const intents = sent as Array<
248
+ Extract<AssistantEvent, { type: "notification_intent" }>
249
+ >;
250
+ expect(intents.map((intent) => intent.assistantName)).toEqual([
251
+ "Assistant A",
252
+ "Assistant B",
253
+ ]);
254
+ });
255
+
256
+ test("keeps notification intents outside conversation replay scope", async () => {
257
+ assistantName = "Example Assistant";
258
+ const { adapter, sent, conversationIds } = captureBroadcast();
259
+
260
+ await adapter.send(makePayload(), makeDestination());
261
+
262
+ expect(conversationIds).toEqual([undefined]);
263
+ expect("conversationId" in sent[0]!).toBe(false);
264
+ });
265
+ });
266
+
184
267
  describe("VellumAdapter update", () => {
185
268
  beforeEach(() => {
186
269
  updateMessageContentMock.mockClear();
@@ -25,6 +25,9 @@ interface MockProviderRow {
25
25
  managedServiceConfigKey: string | null;
26
26
  baseUrl: string | null;
27
27
  injectionTemplates: string | null;
28
+ defaultScopes?: string;
29
+ authorizeParams?: string | null;
30
+ scopeSeparator?: string;
28
31
  pingUrl: string | null;
29
32
  pingMethod: string | null;
30
33
  pingHeaders: string | null;
@@ -109,10 +112,15 @@ mock.module("../oauth/oauth-store.js", () => ({
109
112
  listActiveConnectionsByProvider: (provider: string) =>
110
113
  mockActiveConnectionsByProvider[provider] ?? [],
111
114
  listConnections: (provider: string) => mockAllConnections[provider] ?? [],
115
+ // Imported by seed-providers.js at module load; the seed data is read here,
116
+ // never written.
117
+ migrateProviderBaseUrl: () => {},
118
+ seedProviders: () => {},
112
119
  }));
113
120
 
114
121
  mock.module("../oauth/connection-resolver.js", () => {
115
122
  const makeConnection = () => ({
123
+ id: "conn-1",
116
124
  accountInfo: "user@example.com",
117
125
  request: async (req: unknown) => {
118
126
  mockResolveRequests.push(req);
@@ -155,6 +163,7 @@ mock.module("../security/token-manager.js", () => ({
155
163
  }));
156
164
 
157
165
  import { loadRawConfig } from "../config/loader.js";
166
+ import { PROVIDER_SEED_DATA } from "../oauth/seed-providers.js";
158
167
  import {
159
168
  BadRequestError,
160
169
  InternalError,
@@ -899,6 +908,110 @@ describe("POST oauth/request", () => {
899
908
  ]);
900
909
  });
901
910
 
911
+ /** The provider row exactly as seeding writes it, so the seed data is what the guard reads. */
912
+ function seededProvider(provider: keyof typeof PROVIDER_SEED_DATA) {
913
+ const seed = PROVIDER_SEED_DATA[provider];
914
+ return {
915
+ ...baseProvider,
916
+ provider,
917
+ managedServiceConfigKey: null,
918
+ baseUrl: seed.baseUrl ?? null,
919
+ injectionTemplates: JSON.stringify(seed.injectionTemplates),
920
+ };
921
+ }
922
+
923
+ test("admits a Slack file URL on both seeded Slack providers", async () => {
924
+ // Each Slack credential reads messages, and a file shared in a message is
925
+ // fetched from its `url_private_download` on files.slack.com with the
926
+ // same token.
927
+ for (const provider of ["slack", "slack_channel"] as const) {
928
+ mockProviders[provider] = seededProvider(provider);
929
+ mockResolveRequests = [];
930
+
931
+ await getRoute("POST", "oauth/request").handler(
932
+ makeArgs({
933
+ body: {
934
+ provider,
935
+ url: "https://files.slack.com/files-pri/T0123-F0456/download/shot.png",
936
+ },
937
+ }),
938
+ );
939
+
940
+ expect(mockResolveRequests).toEqual([
941
+ {
942
+ method: "GET",
943
+ path: "/files-pri/T0123-F0456/download/shot.png",
944
+ baseUrl: "https://files.slack.com",
945
+ },
946
+ ]);
947
+ }
948
+ });
949
+
950
+ test("the seeded Slack host policy admits nothing beyond the documented hosts", async () => {
951
+ mockProviders.slack_channel = seededProvider("slack_channel");
952
+
953
+ // A lookalike, an unrelated host, and the CDN host Slack redirects file
954
+ // downloads to: the guard sees only the URL the caller names, and that
955
+ // URL is the documented file host or nothing.
956
+ for (const url of [
957
+ "https://files.slack.com.attacker.example/files-pri/T0123-F0456/x.png",
958
+ "https://attacker.example/files-pri/T0123-F0456/x.png",
959
+ "https://files-origin.slack.com/files-pri/T0123-F0456/x.png",
960
+ ]) {
961
+ await expect(
962
+ getRoute("POST", "oauth/request").handler(
963
+ makeArgs({ body: { provider: "slack_channel", url } }),
964
+ ),
965
+ ).rejects.toBeInstanceOf(BadRequestError);
966
+ }
967
+ expect(mockResolveRequests).toHaveLength(0);
968
+ });
969
+
970
+ test("names the required scopes a stored grant lacks on the request itself", async () => {
971
+ // A connection made before a scope was required keeps working for every
972
+ // call that does not need it, so the caller must be told on the request,
973
+ // not only by the next health check.
974
+ const seed = PROVIDER_SEED_DATA.slack;
975
+ mockProviders.slack = {
976
+ ...seededProvider("slack"),
977
+ defaultScopes: JSON.stringify(seed.defaultScopes),
978
+ authorizeParams: JSON.stringify(seed.authorizeParams),
979
+ scopeSeparator: " ",
980
+ };
981
+ const grantedBeforeFilesRead = seed
982
+ .authorizeParams!.user_scope.split(",")
983
+ .filter((scope) => scope !== "files:read");
984
+ mockAllConnections.slack = [
985
+ {
986
+ id: "conn-1",
987
+ provider: "slack",
988
+ grantedScopes: JSON.stringify(grantedBeforeFilesRead),
989
+ },
990
+ ];
991
+
992
+ const stale = (await getRoute("POST", "oauth/request").handler(
993
+ makeArgs({ body: { provider: "slack", url: "/conversations.history" } }),
994
+ )) as { ok: boolean; hint?: string };
995
+
996
+ expect(stale.ok).toBe(true);
997
+ expect(stale.hint).toContain("files:read");
998
+ expect(stale.hint).toContain("oauth connect slack");
999
+
1000
+ mockAllConnections.slack = [
1001
+ {
1002
+ id: "conn-1",
1003
+ provider: "slack",
1004
+ grantedScopes: JSON.stringify(
1005
+ seed.authorizeParams!.user_scope.split(","),
1006
+ ),
1007
+ },
1008
+ ];
1009
+ const current = (await getRoute("POST", "oauth/request").handler(
1010
+ makeArgs({ body: { provider: "slack", url: "/conversations.history" } }),
1011
+ )) as { hint?: string };
1012
+ expect(current.hint).toBeUndefined();
1013
+ });
1014
+
902
1015
  test("allows cross-host absolute URLs declared by provider injection templates", async () => {
903
1016
  mockProviders.google = {
904
1017
  ...baseProvider,
@@ -7,6 +7,7 @@ mock.module("../security/secure-keys.js", () => ({
7
7
  }));
8
8
 
9
9
  import { getProvider } from "../oauth/oauth-store.js";
10
+ import { expectedScopesForStoredToken } from "../oauth/scope-utils.js";
10
11
  import { seedOAuthProviders } from "../oauth/seed-providers.js";
11
12
  import { initializeDb } from "../persistence/db-init.js";
12
13
 
@@ -22,6 +23,23 @@ describe("oauth provider profiles (DB-seeded)", () => {
22
23
  expect(scopes).toContain("https://www.googleapis.com/auth/drive");
23
24
  });
24
25
 
26
+ test("slack provider row requests files:read for the token it stores", () => {
27
+ // The flow persists the authed_user token, and credential health measures
28
+ // its grant through the same function used here, so this is the scope set
29
+ // an integration connection must carry to fetch a file from files.slack.com.
30
+ const provider = getProvider("slack");
31
+
32
+ expect(provider).toBeDefined();
33
+ const expected = expectedScopesForStoredToken(
34
+ JSON.parse(provider!.defaultScopes) as string[],
35
+ provider!.authorizeParams
36
+ ? (JSON.parse(provider!.authorizeParams) as Record<string, string>)
37
+ : undefined,
38
+ provider!.scopeSeparator ?? undefined,
39
+ );
40
+ expect(expected).toContain("files:read");
41
+ });
42
+
25
43
  test("google provider row contains bearer injection templates for 8 Google API hosts", () => {
26
44
  const provider = getProvider("google");
27
45
 
@@ -982,6 +982,7 @@ describe("OpenAIProvider", () => {
982
982
  );
983
983
 
984
984
  expect(lastCreateParams!.max_completion_tokens).toBe(64000);
985
+ expect(lastCreateParams).not.toHaveProperty("max_tokens");
985
986
  });
986
987
 
987
988
  // -----------------------------------------------------------------------
@@ -429,7 +429,7 @@ describe("Outlook messaging provider", () => {
429
429
  describe("sendMessage", () => {
430
430
  test("sends a new message with correct recipient, subject, and body", async () => {
431
431
  const conn = createMockConnection();
432
- const result = await outlookMessagingProvider.sendMessage(
432
+ const result = await outlookMessagingProvider.sendMessage!(
433
433
  conn,
434
434
  "recipient@example.com",
435
435
  "Hello!",
@@ -452,7 +452,7 @@ describe("Outlook messaging provider", () => {
452
452
 
453
453
  test("uses empty string for subject when not provided", async () => {
454
454
  const conn = createMockConnection();
455
- await outlookMessagingProvider.sendMessage(
455
+ await outlookMessagingProvider.sendMessage!(
456
456
  conn,
457
457
  "recipient@example.com",
458
458
  "Hello!",
@@ -471,7 +471,7 @@ describe("Outlook messaging provider", () => {
471
471
 
472
472
  test("calls replyToMessage when inReplyTo is provided", async () => {
473
473
  const conn = createMockConnection();
474
- const result = await outlookMessagingProvider.sendMessage(
474
+ const result = await outlookMessagingProvider.sendMessage!(
475
475
  conn,
476
476
  "conv-id",
477
477
  "Reply text",
@@ -493,7 +493,7 @@ describe("Outlook messaging provider", () => {
493
493
 
494
494
  test("attaches files to a new message as Graph file attachments", async () => {
495
495
  const conn = createMockConnection();
496
- await outlookMessagingProvider.sendMessage(
496
+ await outlookMessagingProvider.sendMessage!(
497
497
  conn,
498
498
  "recipient@example.com",
499
499
  "See attached",
@@ -530,7 +530,7 @@ describe("Outlook messaging provider", () => {
530
530
 
531
531
  test("forwards attachments to replyToMessage when replying", async () => {
532
532
  const conn = createMockConnection();
533
- await outlookMessagingProvider.sendMessage(
533
+ await outlookMessagingProvider.sendMessage!(
534
534
  conn,
535
535
  "conv-id",
536
536
  "Reply body",
@@ -563,7 +563,7 @@ describe("Outlook messaging provider", () => {
563
563
 
564
564
  test("throws when connection is undefined", async () => {
565
565
  await expect(
566
- outlookMessagingProvider.sendMessage(
566
+ outlookMessagingProvider.sendMessage!(
567
567
  undefined,
568
568
  "recipient@example.com",
569
569
  "Hello!",
@@ -110,8 +110,7 @@ describe("platform callback registration", () => {
110
110
  test("resolves managed callback context from stored credentials", async () => {
111
111
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
112
112
  "https://platform.example.com";
113
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
114
- "11111111-2222-4333-8444-555555555555";
113
+ mockPlatformAssistantId = "11111111-2222-4333-8444-555555555555";
115
114
  mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
116
115
  "ast-managed-key";
117
116
 
@@ -129,8 +128,7 @@ describe("platform callback registration", () => {
129
128
  mockIsPlatform = false;
130
129
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
131
130
  "https://platform.example.com";
132
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
133
- "22222222-3333-4444-8555-666666666666";
131
+ mockPlatformAssistantId = "22222222-3333-4444-8555-666666666666";
134
132
  mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
135
133
  "ast-self-hosted-key";
136
134
 
@@ -161,8 +159,7 @@ describe("platform callback registration", () => {
161
159
  test("registerCallbackRoute falls back to assistant API key auth", async () => {
162
160
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
163
161
  "https://platform.example.com";
164
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
165
- "11111111-2222-4333-8444-555555555555";
162
+ mockPlatformAssistantId = "11111111-2222-4333-8444-555555555555";
166
163
  mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
167
164
  "ast-managed-key";
168
165
 
@@ -209,8 +206,7 @@ describe("platform callback registration", () => {
209
206
  };
210
207
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
211
208
  "https://platform.example.com";
212
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
213
- "22222222-3333-4444-8555-666666666666";
209
+ mockPlatformAssistantId = "22222222-3333-4444-8555-666666666666";
214
210
  mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
215
211
  "ast-self-hosted-key";
216
212
 
@@ -251,8 +247,7 @@ describe("platform callback registration", () => {
251
247
  setIngressPublicBaseUrl("https://detected.example.com/");
252
248
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
253
249
  "https://platform.example.com";
254
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
255
- "22222222-3333-4444-8555-666666666666";
250
+ mockPlatformAssistantId = "22222222-3333-4444-8555-666666666666";
256
251
  mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
257
252
  "ast-self-hosted-key";
258
253
 
@@ -292,8 +287,7 @@ describe("platform callback registration", () => {
292
287
  mockConfig = {};
293
288
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
294
289
  "https://platform.example.com";
295
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
296
- "22222222-3333-4444-8555-666666666666";
290
+ mockPlatformAssistantId = "22222222-3333-4444-8555-666666666666";
297
291
  mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
298
292
  "ast-self-hosted-key";
299
293
 
@@ -334,8 +328,7 @@ describe("platform callback registration", () => {
334
328
  mockConfig = { ingress: { publicBaseUrl: "https://velay.example.com" } };
335
329
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
336
330
  "https://platform.example.com";
337
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
338
- "11111111-2222-4333-8444-555555555555";
331
+ mockPlatformAssistantId = "11111111-2222-4333-8444-555555555555";
339
332
  mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
340
333
  "ast-managed-key";
341
334
 
@@ -396,8 +389,7 @@ describe("resolveCallbackUrl resolution order", () => {
396
389
  function seedPlatformCredentials(): void {
397
390
  mockSecureKeys[credentialKey("vellum", "platform_base_url")] =
398
391
  "https://platform.example.com";
399
- mockSecureKeys[credentialKey("vellum", "platform_assistant_id")] =
400
- "11111111-2222-4333-8444-555555555555";
392
+ mockPlatformAssistantId = "11111111-2222-4333-8444-555555555555";
401
393
  mockSecureKeys[credentialKey("vellum", "assistant_api_key")] =
402
394
  "ast-managed-key";
403
395
  }
@@ -19,7 +19,9 @@ mock.module("../providers/platform-proxy/context.js", () => ({
19
19
  }),
20
20
  }));
21
21
 
22
+ const actualEnv = await import("../config/env.js");
22
23
  mock.module("../config/env.js", () => ({
24
+ ...actualEnv,
23
25
  getPlatformAssistantId: () => platformAssistantId,
24
26
  }));
25
27
 
@@ -129,6 +129,9 @@ const BASELINE: Record<string, readonly string[]> = {
129
129
  "../../../../daemon/embedding-reconcile.js",
130
130
  "../../../../daemon/trust-context.js",
131
131
  "../../../../daemon/turn-latency-sub-spans.js",
132
+ // mcp-setup capability cards list configured server names from the
133
+ // workspace mcp.json document. No plugin-api equivalent.
134
+ "../../../../mcp/workspace-mcp-config.js",
132
135
  "../../../../notifications/background-failure-signal.js",
133
136
  "../../../../persistence/checkpoints.js",
134
137
  "../../../../persistence/conversation-types.js",
@@ -210,6 +213,16 @@ const BASELINE: Record<string, readonly string[]> = {
210
213
  "../../../persistence/job-handlers/message-lexical.js",
211
214
  "../../../persistence/job-utils.js",
212
215
  "../../../persistence/jobs-store.js",
216
+ // The retrospective cursor is a `(createdAt, id)` bound that must keep
217
+ // working after its message row is deleted. The resolver lives beside
218
+ // the persistence after-reads it also drives, so the plugin's
219
+ // accounting imports it rather than re-deriving the bound. No
220
+ // plugin-api equivalent.
221
+ "../../../persistence/message-cursor.js",
222
+ // Named any-state `createdAt` lookup the cursor bookkeeping uses for
223
+ // forks and the timestamp backfill; the messages-read boundary guard
224
+ // requires the read to live in persistence. No plugin-api equivalent.
225
+ "../../../persistence/message-reads.js",
213
226
  "../../../persistence/raw-query.js",
214
227
  "../../../persistence/schema/index.js",
215
228
  "../../../prompts/persona-resolver.js",
@@ -235,6 +248,7 @@ const BASELINE: Record<string, readonly string[]> = {
235
248
  "../../../util/sqlite-retry.js",
236
249
  "../../../util/strip-comment-lines.js",
237
250
  "../../../util/truncate.js",
251
+ "../../../util/unicode.js",
238
252
  "../../../util/worker-compute.js",
239
253
  "../../../util/worker-memory.js",
240
254
  "../../../util/worker-process.js",
@@ -21,7 +21,9 @@ import {
21
21
  const testDataDir = process.env.VELLUM_WORKSPACE_DIR!;
22
22
 
23
23
  import {
24
+ describeQdrantStartFailure,
24
25
  QdrantManager,
26
+ QdrantStartError,
25
27
  resolveQdrantReleaseAsset,
26
28
  } from "../persistence/embeddings/qdrant-manager.js";
27
29
 
@@ -395,6 +397,136 @@ describe("QdrantManager", () => {
395
397
  // THEN the error carries the stdout explanation, not just the noise
396
398
  await expect(mgr.start()).rejects.toThrow("Failed to load local shard");
397
399
  }, 10_000);
400
+
401
+ test("classifies a crash as an exit with its code and captured output", async () => {
402
+ // The reporter must not have to parse the message: the step, the exit
403
+ // code and the raw streams travel as fields.
404
+ placeFakeBinary(
405
+ '#!/bin/sh\necho "Panic occurred: no such shard"\nexit 101',
406
+ );
407
+
408
+ const mgr = new QdrantManager({
409
+ url: `http://127.0.0.1:${getTestPort()}`,
410
+ ...FAST_TIMEOUTS,
411
+ });
412
+
413
+ let caught: unknown;
414
+ await mgr.start().catch((err) => {
415
+ caught = err;
416
+ });
417
+
418
+ expect(caught).toBeInstanceOf(QdrantStartError);
419
+ const err = caught as QdrantStartError;
420
+ expect(err.kind).toBe("exited");
421
+ expect(err.exitCode).toBe(101);
422
+ expect(err.stdout).toContain("no such shard");
423
+ }, 10_000);
424
+
425
+ test("classifies a readiness timeout as not_ready with no exit code", async () => {
426
+ placeFakeBinary("#!/bin/sh\nexec sleep 300");
427
+
428
+ const mgr = new QdrantManager({
429
+ url: `http://127.0.0.1:${getTestPort()}`,
430
+ ...FAST_TIMEOUTS,
431
+ });
432
+
433
+ let caught: unknown;
434
+ await mgr.start().catch((err) => {
435
+ caught = err;
436
+ });
437
+
438
+ expect(caught).toBeInstanceOf(QdrantStartError);
439
+ expect((caught as QdrantStartError).kind).toBe("not_ready");
440
+ expect((caught as QdrantStartError).exitCode).toBeNull();
441
+ }, 10_000);
442
+ });
443
+
444
+ describe("describeQdrantStartFailure", () => {
445
+ const dataDir = "/home/example/workspace-data";
446
+ const failure = (stdout: string, kind: "exited" | "not_ready" = "exited") =>
447
+ new QdrantStartError(
448
+ "boom",
449
+ kind,
450
+ kind === "exited" ? 101 : null,
451
+ stdout,
452
+ "",
453
+ );
454
+
455
+ test("takes the reason from the last panic marker, after the backtrace", () => {
456
+ const stdout =
457
+ " 0: backtrace frame\n 1: another frame\n" +
458
+ "ERROR qdrant::startup: Panic occurred in file mod.rs at line 301: " +
459
+ "Failed to load local shard";
460
+
461
+ expect(describeQdrantStartFailure(failure(stdout), dataDir)).toEqual({
462
+ kind: "exited",
463
+ exit_code: 101,
464
+ panic:
465
+ "Panic occurred in file mod.rs at line 301: Failed to load local shard",
466
+ });
467
+ });
468
+
469
+ test("replaces the data directory wherever it appears", () => {
470
+ const stdout = `Panic occurred: cannot open ${dataDir}/qdrant/collections/x and ${dataDir}/qdrant/wal`;
471
+
472
+ expect(describeQdrantStartFailure(failure(stdout), dataDir).panic).toBe(
473
+ "Panic occurred: cannot open <data>/qdrant/collections/x and <data>/qdrant/wal",
474
+ );
475
+ });
476
+
477
+ test("falls back to the last stdout line when Qdrant died without panicking", () => {
478
+ const stdout =
479
+ "INFO starting\nERROR Address already in use (os error 48)\n";
480
+
481
+ expect(describeQdrantStartFailure(failure(stdout), dataDir).panic).toBe(
482
+ "ERROR Address already in use (os error 48)",
483
+ );
484
+ });
485
+
486
+ test("reports no reason for a silent timeout", () => {
487
+ expect(
488
+ describeQdrantStartFailure(failure("", "not_ready"), dataDir),
489
+ ).toEqual({ kind: "not_ready", exit_code: null, panic: null });
490
+ });
491
+
492
+ /**
493
+ * The telemetry server's measure: JSON with every code unit above 0x7E
494
+ * escaped to six ASCII bytes (`jsonByteLength` in
495
+ * `telemetry-wire.generated.ts`), minus the enclosing quotes.
496
+ */
497
+ const serverBytes = (s: string) =>
498
+ JSON.stringify(s).replace(/[^\x00-\x7e]/g, "\\uxxxx").length - 2;
499
+
500
+ test("caps an ASCII reason at the byte budget", () => {
501
+ const { panic } = describeQdrantStartFailure(
502
+ failure("Panic occurred: " + "x".repeat(5_000)),
503
+ dataDir,
504
+ );
505
+
506
+ expect(panic?.length).toBe(1_024);
507
+ expect(serverBytes(panic!)).toBe(1_024);
508
+ });
509
+
510
+ test("caps a non-ASCII reason by the server's escaped size, not by characters", () => {
511
+ // Each CJK character serializes to six bytes on the server, so a
512
+ // character cap would overshoot the budget six-fold and the event would
513
+ // be dropped at ingest.
514
+ const { panic } = describeQdrantStartFailure(
515
+ failure("Panic occurred: " + "測".repeat(2_000)),
516
+ dataDir,
517
+ );
518
+
519
+ expect(serverBytes(panic!)).toBeLessThanOrEqual(1_024);
520
+ expect(serverBytes(panic! + "測")).toBeGreaterThan(1_024);
521
+ });
522
+
523
+ test("never drops an unclassified failure", () => {
524
+ expect(describeQdrantStartFailure(new Error("nope"), dataDir)).toEqual({
525
+ kind: "unknown",
526
+ exit_code: null,
527
+ panic: null,
528
+ });
529
+ });
398
530
  });
399
531
 
400
532
  // ── Binary Detection ─────────────────────────────────────────