@vellumai/assistant 0.12.1 → 0.12.2-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (577) 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/node_modules/@vellumai/slack-text/src/index.ts +13 -8
  6. package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
  7. package/openapi.yaml +360 -51
  8. package/package.json +3 -1
  9. package/scripts/smoke-desktop-window-theme.ts +186 -0
  10. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  11. package/src/__tests__/anthropic-provider.test.ts +0 -63
  12. package/src/__tests__/app-compiler.test.ts +6 -0
  13. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  14. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  15. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  16. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  17. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  18. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  19. package/src/__tests__/computer-use-tools.test.ts +19 -7
  20. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  21. package/src/__tests__/config-schema.test.ts +28 -29
  22. package/src/__tests__/config-watcher.test.ts +23 -8
  23. package/src/__tests__/connection-model-compat.test.ts +2 -2
  24. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  25. package/src/__tests__/conversation-pairing.test.ts +26 -0
  26. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  27. package/src/__tests__/conversation-queue.test.ts +48 -207
  28. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  29. package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
  30. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  31. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  32. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  35. package/src/__tests__/conversation-title-service.test.ts +14 -10
  36. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  37. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  38. package/src/__tests__/credential-record-retire.test.ts +171 -1
  39. package/src/__tests__/credential-routes.test.ts +250 -3
  40. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  41. package/src/__tests__/cu-unified-flow.test.ts +14 -4
  42. package/src/__tests__/document-sync-tags.test.ts +25 -0
  43. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  44. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  45. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  46. package/src/__tests__/git-porcelain.test.ts +47 -0
  47. package/src/__tests__/heartbeat-service.test.ts +56 -40
  48. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  49. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  50. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  51. package/src/__tests__/host-cu-proxy.test.ts +596 -0
  52. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  53. package/src/__tests__/host-file-proxy.test.ts +11 -23
  54. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  55. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  56. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  57. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  58. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  59. package/src/__tests__/mcp-health-check.test.ts +8 -11
  60. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  61. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  62. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  63. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  64. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  65. package/src/__tests__/model-intents.test.ts +3 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  67. package/src/__tests__/oauth-commands-routes.test.ts +168 -0
  68. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  69. package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
  70. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  71. package/src/__tests__/openai-provider.test.ts +1 -0
  72. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  73. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  74. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  75. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  76. package/src/__tests__/qdrant-manager.test.ts +132 -0
  77. package/src/__tests__/resolve-app.test.ts +135 -0
  78. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  79. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  80. package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
  81. package/src/__tests__/service-account-token.test.ts +247 -0
  82. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  83. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +9 -4
  85. package/src/__tests__/source-fingerprint.test.ts +184 -0
  86. package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
  87. package/src/__tests__/subagent-tools.test.ts +3 -2
  88. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  89. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  90. package/src/__tests__/ui-channel-variants.test.ts +1 -56
  91. package/src/__tests__/unicode.test.ts +13 -0
  92. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  93. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  94. package/src/__tests__/workspace-git-service.test.ts +26 -0
  95. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
  97. package/src/acp/__tests__/acp-credentials.test.ts +13 -0
  98. package/src/acp/__tests__/agent-process.test.ts +258 -4
  99. package/src/acp/__tests__/auth-required.test.ts +96 -0
  100. package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
  101. package/src/acp/__tests__/client-handler.test.ts +90 -2
  102. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  103. package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
  104. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  105. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  106. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  107. package/src/acp/__tests__/session-manager.test.ts +895 -4
  108. package/src/acp/acp-claude-oauth.ts +328 -14
  109. package/src/acp/acp-credentials.ts +19 -0
  110. package/src/acp/agent-process.test.ts +4 -1
  111. package/src/acp/agent-process.ts +96 -10
  112. package/src/acp/auth-required.ts +50 -1
  113. package/src/acp/auto-install.test.ts +9 -7
  114. package/src/acp/claude-token-refresh.ts +150 -0
  115. package/src/acp/client-handler.ts +16 -3
  116. package/src/acp/model-config.test.ts +166 -0
  117. package/src/acp/model-config.ts +138 -0
  118. package/src/acp/prepare-agent-env.ts +21 -6
  119. package/src/acp/resolve-agent.test.ts +220 -6
  120. package/src/acp/resolve-agent.ts +88 -23
  121. package/src/acp/session-manager.test.ts +196 -3
  122. package/src/acp/session-manager.ts +441 -18
  123. package/src/acp/types.ts +50 -5
  124. package/src/agent/loop.ts +1 -0
  125. package/src/api/events/acp-session-model-update.test.ts +88 -0
  126. package/src/api/events/acp-session-model-update.ts +46 -0
  127. package/src/api/events/app-files-changed.ts +4 -3
  128. package/src/api/events/notification-intent.ts +2 -0
  129. package/src/api/events/user-message-echo.ts +7 -1
  130. package/src/api/index.ts +6 -0
  131. package/src/api/responses/conversation-message.ts +5 -0
  132. package/src/apps/app-store.ts +1 -1
  133. package/src/apps/resolve-app.ts +81 -0
  134. package/src/apps/source-fingerprint.ts +266 -0
  135. package/src/avatar/identity-avatar.ts +7 -2
  136. package/src/bundler/app-compiler.ts +15 -0
  137. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  138. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  139. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  140. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  141. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  142. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  143. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  144. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  145. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  146. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  147. package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
  148. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  149. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  150. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  151. package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
  152. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  153. package/src/calls/barge-in-guard.ts +118 -0
  154. package/src/calls/call-constants.ts +24 -4
  155. package/src/calls/call-controller.ts +499 -199
  156. package/src/calls/call-transport.ts +16 -0
  157. package/src/calls/media-stream-output.ts +32 -0
  158. package/src/calls/media-stream-server.ts +82 -30
  159. package/src/calls/media-stream-stt-session.ts +15 -0
  160. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  161. package/src/calls/resolve-call-tts-provider.ts +33 -2
  162. package/src/calls/tts-call-strategy.ts +9 -5
  163. package/src/calls/types.ts +2 -0
  164. package/src/calls/voice-control-protocol.ts +155 -21
  165. package/src/calls/voice-leg-coordinator.ts +189 -0
  166. package/src/calls/voice-progress-cadence.ts +423 -0
  167. package/src/calls/voice-session-bridge.ts +92 -72
  168. package/src/calls/voice-triage-escalate.ts +178 -59
  169. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  170. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  171. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  172. package/src/cli/commands/apps.help.ts +67 -13
  173. package/src/cli/commands/apps.ts +183 -0
  174. package/src/cli/commands/bash.help.ts +8 -13
  175. package/src/cli/commands/bash.ts +7 -25
  176. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  177. package/src/cli/commands/channels/index.help.ts +70 -2
  178. package/src/cli/commands/channels/index.ts +16 -3
  179. package/src/cli/commands/channels/send.ts +128 -0
  180. package/src/cli/commands/credentials.help.ts +6 -2
  181. package/src/cli/commands/credentials.ts +2 -2
  182. package/src/cli/commands/mcp.help.ts +8 -8
  183. package/src/cli/commands/mcp.ts +1 -1
  184. package/src/cli/commands/oauth/status.ts +5 -0
  185. package/src/cli/commands/roadmap.help.ts +5 -1
  186. package/src/cli/commands/roadmap.ts +1 -1
  187. package/src/cli/lib/bundled-marketplace.json +103 -90
  188. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  189. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  190. package/src/config/__tests__/platform-identity.test.ts +255 -0
  191. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  192. package/src/config/acp-agent-write.ts +43 -0
  193. package/src/config/acp-defaults.test.ts +13 -3
  194. package/src/config/acp-defaults.ts +23 -6
  195. package/src/config/acp-schema.test.ts +81 -0
  196. package/src/config/acp-schema.ts +30 -2
  197. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  198. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  199. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  200. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  201. package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
  202. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  203. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  204. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  205. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  206. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  207. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  208. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  209. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  210. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  211. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  212. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  213. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  214. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  215. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  216. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  217. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  218. package/src/config/default-profile-catalog.ts +1 -1
  219. package/src/config/env.ts +2 -2
  220. package/src/config/feature-flag-registry.json +33 -13
  221. package/src/config/loader.ts +2 -0
  222. package/src/config/memory-v3-gate.test.ts +31 -0
  223. package/src/config/memory-v3-gate.ts +20 -16
  224. package/src/config/platform-identity.ts +201 -0
  225. package/src/config/platform-rehydration.ts +11 -27
  226. package/src/config/schema.ts +2 -2
  227. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  228. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  229. package/src/config/schemas/calls.ts +1 -28
  230. package/src/config/schemas/live-voice.ts +1 -173
  231. package/src/config/schemas/mcp.ts +1 -1
  232. package/src/config/schemas/memory-retrospective.ts +10 -0
  233. package/src/config/schemas/services.ts +18 -0
  234. package/src/config/schemas/skills.ts +0 -14
  235. package/src/config/schemas/voice.ts +150 -0
  236. package/src/credential-execution/process-manager.test.ts +18 -19
  237. package/src/credential-health/credential-health-service.ts +11 -17
  238. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  239. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  240. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  241. package/src/daemon/app-change-notify.ts +45 -0
  242. package/src/daemon/config-watcher.ts +13 -194
  243. package/src/daemon/conversation-error.ts +3 -2
  244. package/src/daemon/conversation-media-retry.ts +2 -1
  245. package/src/daemon/conversation-process.ts +35 -64
  246. package/src/daemon/conversation-runtime-assembly.ts +20 -7
  247. package/src/daemon/conversation-skill-tools.ts +13 -31
  248. package/src/daemon/conversation-store.ts +1 -1
  249. package/src/daemon/conversation-surfaces.ts +21 -10
  250. package/src/daemon/conversation-tool-setup.ts +45 -59
  251. package/src/daemon/conversation.ts +0 -3
  252. package/src/daemon/dictation-profile-store.ts +2 -1
  253. package/src/daemon/handlers/shared.ts +2 -1
  254. package/src/daemon/handlers/skills.ts +6 -3
  255. package/src/daemon/host-bash-proxy.ts +8 -12
  256. package/src/daemon/host-browser-proxy.ts +15 -1
  257. package/src/daemon/host-cu-proxy.ts +224 -19
  258. package/src/daemon/host-file-proxy.ts +8 -10
  259. package/src/daemon/host-proxy-preactivation.ts +2 -1
  260. package/src/daemon/host-transfer-proxy.ts +14 -18
  261. package/src/daemon/lifecycle.ts +0 -5
  262. package/src/daemon/mcp-reload-service.ts +5 -5
  263. package/src/daemon/message-types/sync.ts +1 -0
  264. package/src/daemon/orphan-reaper.ts +1 -1
  265. package/src/daemon/providers-setup.ts +1 -1
  266. package/src/daemon/reaction-history-render.ts +2 -1
  267. package/src/daemon/shutdown-handlers.ts +1 -9
  268. package/src/daemon/skill-memory-refresh.ts +64 -0
  269. package/src/daemon/tool-side-effects.ts +7 -18
  270. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  271. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  272. package/src/desktop/desktop-chrome-policy.ts +47 -0
  273. package/src/desktop/desktop-chrome-session.ts +37 -0
  274. package/src/desktop/desktop-dependencies.test.ts +102 -0
  275. package/src/desktop/desktop-dependencies.ts +308 -0
  276. package/src/desktop/desktop-feature.test.ts +18 -0
  277. package/src/desktop/desktop-feature.ts +19 -0
  278. package/src/desktop/desktop-panel-config.test.ts +135 -0
  279. package/src/desktop/desktop-panel-config.ts +149 -0
  280. package/src/desktop/desktop-session-manager.test.ts +730 -0
  281. package/src/desktop/desktop-session-manager.ts +850 -0
  282. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  283. package/src/desktop/desktop-stream-bridge.ts +214 -0
  284. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  285. package/src/desktop/desktop-wallpaper.ts +113 -0
  286. package/src/desktop/desktop-window-drag.ts +177 -0
  287. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  288. package/src/desktop/desktop-window-manager-config.ts +238 -0
  289. package/src/desktop/desktop-window-theme.ts +170 -0
  290. package/src/heartbeat/heartbeat-service.ts +21 -3
  291. package/src/home/__tests__/feed-writer.test.ts +22 -0
  292. package/src/home/feed-writer.ts +11 -2
  293. package/src/hooks/types.ts +33 -0
  294. package/src/i18n/AGENTS.md +22 -0
  295. package/src/i18n/__tests__/i18n.test.ts +108 -0
  296. package/src/i18n/index.ts +16 -0
  297. package/src/i18n/locales.ts +93 -0
  298. package/src/i18n/messages.ts +61 -0
  299. package/src/i18n/title.ts +25 -0
  300. package/src/inbound/platform-callback-registration.ts +4 -5
  301. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
  302. package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
  303. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  304. package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
  305. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  306. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  307. package/src/live-voice/__tests__/protocol.test.ts +39 -0
  308. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  309. package/src/live-voice/__tests__/session-controls.test.ts +79 -0
  310. package/src/live-voice/live-voice-photo.ts +25 -6
  311. package/src/live-voice/live-voice-session.ts +297 -699
  312. package/src/live-voice/protocol.ts +60 -2
  313. package/src/live-voice/session-controls.ts +111 -0
  314. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  315. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  316. package/src/mcp/effective-config.ts +3 -3
  317. package/src/mcp/mcp-header-store.ts +29 -45
  318. package/src/mcp/spec-schema.ts +111 -0
  319. package/src/mcp/startup.ts +8 -4
  320. package/src/mcp/workspace-mcp-config.ts +188 -0
  321. package/src/messaging/provider.ts +10 -3
  322. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  323. package/src/messaging/providers/callback-routing.ts +18 -0
  324. package/src/messaging/providers/channel-transport.ts +56 -0
  325. package/src/messaging/providers/discord/transport.ts +17 -0
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  327. package/src/messaging/providers/slack/adapter.ts +7 -44
  328. package/src/messaging/providers/slack/transport.ts +16 -0
  329. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  330. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  331. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  332. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  333. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  334. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  335. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  336. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  337. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  338. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  339. package/src/notifications/adapters/macos.ts +3 -0
  340. package/src/notifications/assistant-reply-producer.ts +8 -3
  341. package/src/notifications/conversation-pairing.ts +36 -16
  342. package/src/notifications/delivered-post-record.ts +11 -1
  343. package/src/notifications/guardian-feed-projection.ts +125 -5
  344. package/src/notifications/home-feed-side-effect.ts +55 -7
  345. package/src/notifications/schedule-result-producer.ts +66 -22
  346. package/src/oauth/AGENTS.md +2 -0
  347. package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
  348. package/src/oauth/byo-connection.test.ts +105 -0
  349. package/src/oauth/byo-connection.ts +36 -6
  350. package/src/oauth/connection-resolver.test.ts +100 -0
  351. package/src/oauth/connection-resolver.ts +120 -3
  352. package/src/oauth/provider-serializer.ts +9 -0
  353. package/src/oauth/scope-utils.ts +24 -0
  354. package/src/oauth/seed-providers.ts +314 -27
  355. package/src/outbound-proxy/certs.ts +3 -5
  356. package/src/permissions/checker.test.ts +11 -6
  357. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  358. package/src/persistence/conversation-crud.ts +71 -63
  359. package/src/persistence/conversation-lineage.ts +12 -4
  360. package/src/persistence/conversation-queries.ts +2 -2
  361. package/src/persistence/conversation-title-placeholders.ts +23 -0
  362. package/src/persistence/conversation-title-service.ts +9 -26
  363. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  364. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  365. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  366. package/src/persistence/message-cursor.ts +78 -0
  367. package/src/persistence/message-reads.ts +30 -0
  368. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  369. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  370. package/src/persistence/schema/memory-core.ts +5 -0
  371. package/src/platform/client.test.ts +4 -2
  372. package/src/platform/client.ts +2 -10
  373. package/src/plugin-api/constants.ts +2 -0
  374. package/src/plugin-api/index.ts +1 -0
  375. package/src/plugin-api/types.ts +2 -0
  376. package/src/plugins/defaults/index.ts +2 -0
  377. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  378. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  379. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  380. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  381. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  382. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  383. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  384. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  385. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  386. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  387. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  388. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  389. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  390. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  391. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  392. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  393. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  394. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  395. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  396. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  397. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  398. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  399. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  400. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  401. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  402. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  403. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  404. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  405. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  406. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  407. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  408. package/src/plugins/defaults/memory/startup.ts +28 -2
  409. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  410. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  411. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  412. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  413. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  414. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  415. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  416. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  417. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  418. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  419. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  420. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  421. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  422. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  423. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  424. package/src/plugins/defaults/memory/worker.ts +6 -0
  425. package/src/plugins/mcp-servers.ts +15 -75
  426. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  427. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  428. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  429. package/src/providers/anthropic/client.ts +0 -47
  430. package/src/providers/gemini/client.ts +9 -23
  431. package/src/providers/gemini-thought-signature.test.ts +63 -0
  432. package/src/providers/gemini-thought-signature.ts +44 -0
  433. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  434. package/src/providers/inference/auth.ts +2 -3
  435. package/src/providers/inference/connection-availability.ts +27 -8
  436. package/src/providers/inference/endpoint-probe.ts +10 -1
  437. package/src/providers/inference/resolve-auth.ts +22 -4
  438. package/src/providers/inference/service-account-token.ts +248 -0
  439. package/src/providers/model-catalog.ts +34 -0
  440. package/src/providers/model-intents.ts +10 -5
  441. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  442. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  443. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  444. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  445. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  446. package/src/providers/openai/chat-completions-provider.ts +212 -79
  447. package/src/providers/openai/codex-models.ts +0 -4
  448. package/src/providers/openai/google-thought-signature.ts +320 -0
  449. package/src/providers/opencode/client.test.ts +73 -4
  450. package/src/providers/opencode/client.ts +60 -3
  451. package/src/providers/openrouter/client.ts +20 -0
  452. package/src/providers/outbound-request-sanitize.ts +96 -0
  453. package/src/providers/retry.ts +30 -19
  454. package/src/providers/unparseable-tool-args.ts +5 -1
  455. package/src/runtime/AGENTS.md +3 -1
  456. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  457. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  458. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  459. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  460. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  461. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  462. package/src/runtime/agent-wake.ts +2 -1
  463. package/src/runtime/assistant-event-hub.ts +21 -3
  464. package/src/runtime/auth/same-actor.ts +38 -3
  465. package/src/runtime/channel-send.ts +316 -0
  466. package/src/runtime/host-observe.ts +11 -1
  467. package/src/runtime/http-server.ts +160 -151
  468. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
  469. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  470. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
  471. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  472. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  473. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  474. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  475. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  476. package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
  477. package/src/runtime/routes/acp-routes.test.ts +15 -0
  478. package/src/runtime/routes/acp-routes.ts +72 -9
  479. package/src/runtime/routes/app-management-routes.ts +90 -2
  480. package/src/runtime/routes/app-routes.ts +1 -1
  481. package/src/runtime/routes/channel-send-routes.ts +85 -0
  482. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  483. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  484. package/src/runtime/routes/conversation-routes.ts +16 -3
  485. package/src/runtime/routes/credential-routes.ts +20 -14
  486. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  487. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  488. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  489. package/src/runtime/routes/events-routes.ts +4 -0
  490. package/src/runtime/routes/host-cu-routes.ts +6 -0
  491. package/src/runtime/routes/index.ts +4 -2
  492. package/src/runtime/routes/log-export-routes.ts +2 -0
  493. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  494. package/src/runtime/routes/migration-routes.ts +6 -20
  495. package/src/runtime/routes/oauth-commands-routes.ts +50 -0
  496. package/src/runtime/routes/oauth-providers.ts +7 -0
  497. package/src/runtime/routes/platform-routes.ts +18 -15
  498. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  499. package/src/runtime/routes/workspace-routes.ts +46 -63
  500. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  501. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  502. package/src/runtime/routes/workspace-utils.ts +17 -0
  503. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  504. package/src/runtime/services/conversation-serializer.ts +16 -3
  505. package/src/runtime/sync/resource-sync-events.ts +2 -1
  506. package/src/schedule/run-script.ts +2 -1
  507. package/src/schedule/schedule-store.ts +9 -2
  508. package/src/schedule/worker.ts +1 -1
  509. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  510. package/src/security/ces-http-record-backend.ts +120 -0
  511. package/src/security/ces-rpc-record-backend.ts +7 -0
  512. package/src/security/untrusted-content.ts +3 -1
  513. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  514. package/src/skills/workspace-skill-md-signature.ts +50 -0
  515. package/src/subagent/consult-context.ts +12 -25
  516. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  517. package/src/tools/acp/list-agents.test.ts +3 -1
  518. package/src/tools/acp/spawn.test.ts +146 -11
  519. package/src/tools/acp/spawn.ts +21 -3
  520. package/src/tools/acp/status.test.ts +94 -0
  521. package/src/tools/acp/status.ts +40 -3
  522. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  523. package/src/tools/browser/browser-execution.ts +1 -1
  524. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  525. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  526. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  527. package/src/tools/browser/network-recorder.ts +2 -1
  528. package/src/tools/capability-offer.test.ts +22 -2
  529. package/src/tools/capability-offer.ts +16 -1
  530. package/src/tools/computer-use/definitions.ts +121 -2
  531. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  532. package/src/tools/credentials/metadata-store.ts +100 -0
  533. package/src/tools/filesystem/search.ts +2 -1
  534. package/src/tools/host-filesystem/edit.test.ts +2 -1
  535. package/src/tools/host-filesystem/edit.ts +2 -2
  536. package/src/tools/host-filesystem/read.test.ts +2 -1
  537. package/src/tools/host-filesystem/read.ts +2 -2
  538. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  539. package/src/tools/host-filesystem/transfer.ts +2 -2
  540. package/src/tools/host-filesystem/write.test.ts +2 -1
  541. package/src/tools/host-filesystem/write.ts +2 -2
  542. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  543. package/src/tools/host-terminal/host-shell.ts +4 -20
  544. package/src/tools/network/web-search.ts +6 -1
  545. package/src/tools/policy-context.ts +6 -7
  546. package/src/tools/skills/scaffold-managed.ts +61 -3
  547. package/src/tools/subagent/spawn.ts +5 -8
  548. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  549. package/src/tools/terminal/sanitized-bash.ts +105 -0
  550. package/src/tools/ui-surface/channel-variants.ts +10 -59
  551. package/src/tools/watch/watch-retro-report.ts +0 -8
  552. package/src/types/host-capabilities.ts +5 -3
  553. package/src/util/__tests__/language-subtag.test.ts +23 -1
  554. package/src/util/ca-bundle.ts +13 -0
  555. package/src/util/language-subtag.ts +16 -0
  556. package/src/util/platform.ts +8 -0
  557. package/src/util/unicode.ts +15 -1
  558. package/src/watch/watch-retro.ts +6 -8
  559. package/src/watch/watch-timeline.ts +2 -2
  560. package/src/workflows/run-manager.ts +2 -1
  561. package/src/workspace/git-porcelain.ts +66 -0
  562. package/src/workspace/git-service.ts +6 -55
  563. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  564. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  565. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  566. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  567. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  568. package/src/workspace/migrations/registry.ts +6 -0
  569. package/src/__tests__/app-source-watcher.test.ts +0 -218
  570. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  571. package/src/__tests__/speaker-identification.test.ts +0 -56
  572. package/src/calls/speaker-identification.ts +0 -233
  573. package/src/daemon/app-source-watcher.ts +0 -218
  574. package/src/messaging/providers/whatsapp/client.ts +0 -29
  575. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  576. package/src/util/fs-watcher-error.ts +0 -36
  577. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -22,6 +22,7 @@ import { join } from "node:path";
22
22
  import { and, desc, eq, gte, lte } from "drizzle-orm";
23
23
  import { z } from "zod";
24
24
 
25
+ import { overlayWorkspaceMcpForConfigRead } from "../../mcp/workspace-mcp-config.js";
25
26
  import { getDb, getLogsDb } from "../../persistence/db-connection.js";
26
27
  import {
27
28
  llmRequestLogs,
@@ -568,6 +569,7 @@ function readSanitizedConfig(): Record<string, unknown> | undefined {
568
569
  }
569
570
  }
570
571
 
572
+ overlayWorkspaceMcpForConfigRead(config);
571
573
  const mcpServers = asRecord(asRecord(config.mcp)?.servers);
572
574
  for (const server of Object.values(mcpServers ?? {})) {
573
575
  const transport = asRecord(asRecord(server)?.transport);
@@ -14,8 +14,7 @@
14
14
 
15
15
  import { z } from "zod";
16
16
 
17
- import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
18
- import type { McpConfig, McpServerConfig } from "../../config/schemas/mcp.js";
17
+ import type { McpServerConfig } from "../../config/schemas/mcp.js";
19
18
  import { estimateToolDefinitionTokens } from "../../context/token-estimator.js";
20
19
  import { reloadMcpServers } from "../../daemon/mcp-reload-service.js";
21
20
  import { McpClient } from "../../mcp/client.js";
@@ -31,6 +30,10 @@ import {
31
30
  deleteMcpOAuthCredentials,
32
31
  hasMcpOAuthTokens,
33
32
  } from "../../mcp/mcp-oauth-provider.js";
33
+ import {
34
+ loadWorkspaceMcpConfig,
35
+ saveWorkspaceMcpConfig,
36
+ } from "../../mcp/workspace-mcp-config.js";
34
37
  import { readPluginMcpServers } from "../../plugins/mcp-servers.js";
35
38
  import { getMcpToolsByServer } from "../../tools/registry.js";
36
39
  import { getLogger } from "../../util/logger.js";
@@ -65,6 +68,18 @@ const McpAddParams = z.object({
65
68
 
66
69
  const McpRemoveParams = z.object({ name: z.string() });
67
70
 
71
+ function persistWorkspaceMcpConfig(config: {
72
+ servers: Record<string, McpServerConfig>;
73
+ }): void {
74
+ try {
75
+ saveWorkspaceMcpConfig(config);
76
+ } catch (err) {
77
+ throw new InternalError(
78
+ err instanceof Error ? err.message : String(err),
79
+ );
80
+ }
81
+ }
82
+
68
83
  async function handleMcpAuthStart({
69
84
  body,
70
85
  }: {
@@ -76,8 +91,7 @@ async function handleMcpAuthStart({
76
91
  }> {
77
92
  const { serverId } = parseBody(McpServerIdParams, body);
78
93
 
79
- const raw = loadRawConfig();
80
- const servers = (raw.mcp as Partial<McpConfig> | undefined)?.servers ?? {};
94
+ const servers = loadWorkspaceMcpConfig().servers;
81
95
  const serverConfig = servers[serverId];
82
96
 
83
97
  if (!serverConfig) {
@@ -253,9 +267,7 @@ function detectAuthType(headers: Record<string, string>): "bearer" | "api-key" {
253
267
  async function handleMcpList(_args: {
254
268
  body?: Record<string, unknown>;
255
269
  }): Promise<{ servers: McpServerEntry[] }> {
256
- const raw = loadRawConfig();
257
- const mcpConfig = raw.mcp as Partial<McpConfig> | undefined;
258
- const servers = mcpConfig?.servers ?? {};
270
+ const servers = loadWorkspaceMcpConfig().servers;
259
271
  const configEntries = (
260
272
  Object.entries(servers) as [string, McpServerConfig][]
261
273
  ).filter(([, config]) => config && typeof config === "object");
@@ -434,18 +446,13 @@ async function handleMcpUpdate({
434
446
  }): Promise<{ updated: true }> {
435
447
  const { name, headers } = parseBody(McpUpdateParams, body);
436
448
 
437
- const raw = loadRawConfig();
438
- const mcpConfig = raw.mcp as Record<string, unknown> | undefined;
439
- const serverMap = mcpConfig?.servers as
440
- | Record<string, Record<string, unknown>>
441
- | undefined;
449
+ const servers = loadWorkspaceMcpConfig().servers;
450
+ const server = servers[name];
442
451
 
443
- if (!serverMap || !serverMap[name]) {
452
+ if (!server) {
444
453
  throw new NotFoundError(`MCP server "${name}" not found.`);
445
454
  }
446
455
 
447
- const server = serverMap[name];
448
-
449
456
  if (headers !== undefined) {
450
457
  const transport = server.transport as Record<string, unknown> | undefined;
451
458
  if (
@@ -474,7 +481,7 @@ async function handleMcpUpdate({
474
481
  }
475
482
  }
476
483
 
477
- saveRawConfig(raw);
484
+ persistWorkspaceMcpConfig({ servers });
478
485
  triggerReload("internal_mcp_update");
479
486
 
480
487
  return { updated: true };
@@ -494,7 +501,7 @@ async function handleMcpAdd({
494
501
  body,
495
502
  );
496
503
 
497
- let transport: Record<string, unknown>;
504
+ let transport: McpServerConfig["transport"];
498
505
  switch (transportType) {
499
506
  case "stdio":
500
507
  if (!command) {
@@ -517,27 +524,16 @@ async function handleMcpAdd({
517
524
  );
518
525
  }
519
526
 
520
- const raw = loadRawConfig();
521
- if (!raw.mcp) {
522
- raw.mcp = { servers: {} };
523
- }
524
- const mcpConfig = raw.mcp as Record<string, unknown>;
525
- if (!mcpConfig.servers) {
526
- mcpConfig.servers = {};
527
- }
528
- const serverMap = mcpConfig.servers as Record<string, unknown>;
527
+ const servers = { ...loadWorkspaceMcpConfig().servers };
529
528
 
530
- if (serverMap[name]) {
529
+ if (servers[name]) {
531
530
  throw new BadRequestError(
532
531
  `MCP server "${name}" already exists. Remove it first with: assistant mcp remove ${name}`,
533
532
  );
534
533
  }
535
534
 
536
- serverMap[name] = {
537
- transport,
538
- };
535
+ servers[name] = { transport };
539
536
 
540
- // Store auth headers in credential store, not config
541
537
  if (headers && Object.keys(headers).length > 0) {
542
538
  const ok = await setMcpHeaders(name, headers);
543
539
  if (!ok) {
@@ -547,7 +543,7 @@ async function handleMcpAdd({
547
543
  }
548
544
  }
549
545
 
550
- saveRawConfig(raw);
546
+ persistWorkspaceMcpConfig({ servers });
551
547
  triggerReload("internal_mcp_add");
552
548
 
553
549
  return { added: true };
@@ -564,8 +560,7 @@ async function handleMcpAuthRevoke({
564
560
  }): Promise<{ revoked: true }> {
565
561
  const { serverId } = parseBody(McpServerIdParams, body);
566
562
 
567
- const raw = loadRawConfig();
568
- const servers = (raw.mcp as Partial<McpConfig> | undefined)?.servers ?? {};
563
+ const servers = loadWorkspaceMcpConfig().servers;
569
564
  const serverConfig = servers[serverId];
570
565
 
571
566
  if (!serverConfig) {
@@ -602,32 +597,29 @@ async function handleMcpRemove({
602
597
  }): Promise<{ removed: true }> {
603
598
  const { name } = parseBody(McpRemoveParams, body);
604
599
 
605
- const raw = loadRawConfig();
606
- const mcpConfig = raw.mcp as Record<string, unknown> | undefined;
607
- const serverMap = mcpConfig?.servers as Record<string, unknown> | undefined;
600
+ const servers = { ...loadWorkspaceMcpConfig().servers };
601
+ const serverConfig = servers[name];
608
602
 
609
- if (!serverMap || !serverMap[name]) {
603
+ if (!serverConfig) {
610
604
  throw new NotFoundError(`MCP server "${name}" not found.`);
611
605
  }
612
606
 
613
- // Best-effort cleanup of credentials stored for this server
614
- const serverConfig = serverMap[name] as Record<string, unknown>;
615
- const transport = serverConfig?.transport as
616
- | Record<string, unknown>
617
- | undefined;
618
- if (transport?.type === "sse" || transport?.type === "streamable-http") {
607
+ if (
608
+ serverConfig.transport.type === "sse" ||
609
+ serverConfig.transport.type === "streamable-http"
610
+ ) {
619
611
  try {
620
612
  await Promise.all([
621
613
  deleteMcpOAuthCredentials(name),
622
614
  deleteMcpHeaders(name),
623
615
  ]);
624
616
  } catch {
625
- // Ignore — credentials may not exist
617
+ // Credentials may not exist.
626
618
  }
627
619
  }
628
620
 
629
- delete serverMap[name];
630
- saveRawConfig(raw);
621
+ delete servers[name];
622
+ persistWorkspaceMcpConfig({ servers });
631
623
  triggerReload("internal_mcp_remove");
632
624
 
633
625
  return { removed: true };
@@ -22,8 +22,8 @@ import { PassThrough, Readable } from "node:stream";
22
22
 
23
23
  import { z } from "zod";
24
24
 
25
- import { getPlatformAssistantId } from "../../config/env.js";
26
25
  import { invalidateConfigCache } from "../../config/loader.js";
26
+ import { resolvePlatformAssistantId } from "../../config/platform-identity.js";
27
27
  import { getAssistantName } from "../../daemon/identity-helpers.js";
28
28
  import { runAsyncSqlite } from "../../persistence/db-async-query.js";
29
29
  import {
@@ -236,28 +236,14 @@ interface ExportManifestInputs {
236
236
  /**
237
237
  * Resolve the `assistant.id` for an export.
238
238
  *
239
- * Mirrors `platform/client.ts`'s precedence: in-memory override (set at
240
- * daemon startup or by secret-routes) credential store daemon-internal
241
- * fallback. The schema requires `id` to be non-empty, so we fall back to
239
+ * In-memory identity (validate rehydration or secret-routes). The schema
240
+ * requires `id` to be non-empty, so we fall back to
242
241
  * `DAEMON_INTERNAL_ASSISTANT_ID` rather than the empty string.
243
242
  */
244
243
  async function resolveAssistantId(): Promise<string> {
245
- const inMemory = getPlatformAssistantId();
246
- if (inMemory) {
247
- return inMemory;
248
- }
249
- try {
250
- const stored = await getSecureKeyAsync(
251
- credentialKey("vellum", "platform_assistant_id"),
252
- );
253
- if (stored) {
254
- return stored;
255
- }
256
- } catch (err) {
257
- log.warn(
258
- { err },
259
- "Failed to read platform_assistant_id from credential store; falling back to daemon-internal id",
260
- );
244
+ const resolved = await resolvePlatformAssistantId();
245
+ if (resolved) {
246
+ return resolved;
261
247
  }
262
248
  return DAEMON_INTERNAL_ASSISTANT_ID;
263
249
  }
@@ -41,9 +41,11 @@ import {
41
41
  listConnections,
42
42
  type OAuthProviderRow,
43
43
  } from "../../oauth/oauth-store.js";
44
+ import { missingScopesForStoredToken } from "../../oauth/scope-utils.js";
44
45
  import { VellumPlatformClient } from "../../platform/client.js";
45
46
  import { withValidToken } from "../../security/token-manager.js";
46
47
  import { matchHostPattern } from "../../tools/credentials/host-pattern-match.js";
48
+ import { parseJsonSafe } from "../../util/json.js";
47
49
  import { getLogger } from "../../util/logger.js";
48
50
  import {
49
51
  findContentTypeHeader,
@@ -64,6 +66,7 @@ interface PlatformConnectionEntry {
64
66
  id: string;
65
67
  account_label?: string;
66
68
  scopes_granted?: string[];
69
+ provider_params?: Record<string, string> | null;
67
70
  status?: string;
68
71
  }
69
72
 
@@ -230,6 +233,31 @@ function assertOAuthRequestUrlAllowed(
230
233
  }
231
234
  }
232
235
 
236
+ /**
237
+ * Required scopes the resolved connection's stored grant lacks. Credential
238
+ * health measures the same thing on the heartbeat; measuring it on the
239
+ * request names the gap at the moment a call may depend on it, since a
240
+ * connection made before a scope was required keeps working for every call
241
+ * that does not need it. A managed connection has no local row and reports
242
+ * nothing.
243
+ */
244
+ function missingScopesForConnection(
245
+ providerRow: OAuthProviderRow,
246
+ connectionId: string,
247
+ ): string[] {
248
+ const row = getConnection(connectionId);
249
+ if (!row) {
250
+ return [];
251
+ }
252
+ return missingScopesForStoredToken(
253
+ parseJsonSafe<string[]>(providerRow.defaultScopes ?? "[]") ?? [],
254
+ parseJsonSafe<Record<string, string>>(providerRow.authorizeParams ?? "") ??
255
+ undefined,
256
+ providerRow.scopeSeparator,
257
+ parseJsonSafe<string[]>(row.grantedScopes ?? "[]") ?? [],
258
+ );
259
+ }
260
+
233
261
  // ---------------------------------------------------------------------------
234
262
  // Disconnect handler
235
263
  // ---------------------------------------------------------------------------
@@ -547,6 +575,9 @@ async function handleStatus({ queryParams = {} }: RouteHandlerArgs) {
547
575
  account: c.account_label ?? null,
548
576
  grantedScopes: c.scopes_granted ?? [],
549
577
  status: c.status ?? "ACTIVE",
578
+ // Values the provider scopes the connection by (QuickBooks' realm id),
579
+ // so a caller can address resources the proxy's base URL does not.
580
+ providerParams: c.provider_params ?? {},
550
581
  }));
551
582
 
552
583
  return {
@@ -1002,6 +1033,15 @@ export async function handleRequest({ body = {} }: RouteHandlerArgs) {
1002
1033
  `(e.g. https://host/full/path) so the host and full path are set explicitly.`;
1003
1034
  }
1004
1035
 
1036
+ const missingScopes = missingScopesForConnection(providerRow, connection.id);
1037
+ if (missingScopes.length > 0) {
1038
+ const scopeHint =
1039
+ `The ${b.provider} connection is missing required scopes: ${missingScopes.join(", ")}. ` +
1040
+ `It was connected before they were required, so calls that need them fail. ` +
1041
+ `Reconnect it from Integrations, or run 'assistant oauth connect ${b.provider}', to grant them.`;
1042
+ result.hint = result.hint ? `${scopeHint}\n\n${result.hint}` : scopeHint;
1043
+ }
1044
+
1005
1045
  return result;
1006
1046
  }
1007
1047
 
@@ -1024,6 +1064,8 @@ async function handleManagedConnect({ body = {} }: RouteHandlerArgs) {
1024
1064
  provider: string;
1025
1065
  scopes?: string[];
1026
1066
  redirect_after_connect?: string;
1067
+ /** Per-tenant providers (Shopify): the customer's own host. */
1068
+ tenant_host?: string;
1027
1069
  };
1028
1070
 
1029
1071
  if (!b.provider) {
@@ -1040,6 +1082,13 @@ async function handleManagedConnect({ body = {} }: RouteHandlerArgs) {
1040
1082
  }
1041
1083
  reqBody.redirect_after_connect =
1042
1084
  b.redirect_after_connect ?? "/account/oauth/complete";
1085
+ // Only forwarded when present: the platform validates it against the
1086
+ // provider's pattern and rejects per-tenant providers that omit it.
1087
+ const tenantHost =
1088
+ typeof b.tenant_host === "string" ? b.tenant_host.trim() : "";
1089
+ if (tenantHost) {
1090
+ reqBody.tenant_host = tenantHost;
1091
+ }
1043
1092
 
1044
1093
  const response = await client.fetch(startPath, {
1045
1094
  method: "POST",
@@ -1086,6 +1135,7 @@ async function handleManagedConnectPoll({
1086
1135
  id: e.id,
1087
1136
  account_label: e.account_label ?? null,
1088
1137
  scopes_granted: e.scopes_granted ?? [],
1138
+ provider_params: e.provider_params ?? {},
1089
1139
  })),
1090
1140
  };
1091
1141
  }
@@ -397,6 +397,13 @@ export const oauthProviderSummarySchema = z.object({
397
397
  supports_managed_mode: z.boolean(),
398
398
  managed_service_is_paid: z.boolean(),
399
399
  feature_flag: z.string().nullable(),
400
+ tenant_host: z
401
+ .object({
402
+ pattern: z.string(),
403
+ label: z.string(),
404
+ placeholder: z.string(),
405
+ })
406
+ .nullable(),
400
407
  acts_as: z.enum(["user", "assistant"]),
401
408
  });
402
409
 
@@ -39,7 +39,17 @@ import {
39
39
  } from "@vellumai/service-contracts/platform-credential";
40
40
  import { z } from "zod";
41
41
 
42
+ import {
43
+ setPlatformAssistantId,
44
+ setPlatformBaseUrl,
45
+ setPlatformOrganizationId,
46
+ setPlatformUserId,
47
+ } from "../../config/env.js";
42
48
  import { isPlatformRemote } from "../../config/env-registry.js";
49
+ import {
50
+ resolvePlatformOrganizationId,
51
+ resolvePlatformUserId,
52
+ } from "../../config/platform-identity.js";
43
53
  import {
44
54
  registerCallbackRoute,
45
55
  resolvePlatformCallbackRegistrationContext,
@@ -289,24 +299,12 @@ async function handlePlatformStatus(
289
299
  ): Promise<PlatformStatusResponse> {
290
300
  const context = await resolvePlatformCallbackRegistrationContext();
291
301
 
292
- const [orgIdRaw, userIdRaw, webhookSecretRaw] = await Promise.all([
293
- getSecureKeyAsync(
294
- credentialKey(
295
- CREDENTIAL_KEYS.organizationId.service,
296
- CREDENTIAL_KEYS.organizationId.field,
297
- ),
298
- ),
299
- getSecureKeyAsync(
300
- credentialKey(
301
- CREDENTIAL_KEYS.userId.service,
302
- CREDENTIAL_KEYS.userId.field,
303
- ),
304
- ),
302
+ const [organizationId, userId, webhookSecretRaw] = await Promise.all([
303
+ resolvePlatformOrganizationId(),
304
+ resolvePlatformUserId(),
305
305
  getSecureKeyAsync(credentialKey("vellum", "webhook_secret")),
306
306
  ]);
307
307
 
308
- const organizationId = orgIdRaw?.trim() ?? "";
309
- const userId = userIdRaw?.trim() ?? "";
310
308
  const hasWebhookSecret = !!webhookSecretRaw;
311
309
 
312
310
  return {
@@ -467,6 +465,11 @@ async function handlePlatformDisconnect(
467
465
  );
468
466
  }
469
467
 
468
+ setPlatformBaseUrl(undefined);
469
+ setPlatformAssistantId(undefined);
470
+ setPlatformOrganizationId(undefined);
471
+ setPlatformUserId(undefined);
472
+
470
473
  // Notify connected clients
471
474
  broadcastMessage({ type: "platform_disconnected" });
472
475
 
@@ -211,8 +211,8 @@ describe("isTextMimeType", () => {
211
211
  describe("GET /v1/workspace/tree", () => {
212
212
  const { handler } = getRoute("workspace_tree");
213
213
 
214
- test("root listing returns entries", () => {
215
- const result = handler({ queryParams: {} }) as {
214
+ test("root listing returns entries", async () => {
215
+ const result = (await handler({ queryParams: {} })) as {
216
216
  path: string;
217
217
  entries: Array<{ name: string; type: string }>;
218
218
  };
@@ -222,8 +222,8 @@ describe("GET /v1/workspace/tree", () => {
222
222
  expect(names).toContain("subdir");
223
223
  });
224
224
 
225
- test("subdirectory listing returns child entries", () => {
226
- const result = handler({ queryParams: { path: "subdir" } }) as {
225
+ test("subdirectory listing returns child entries", async () => {
226
+ const result = (await handler({ queryParams: { path: "subdir" } })) as {
227
227
  path: string;
228
228
  entries: Array<{ name: string; type: string }>;
229
229
  };
@@ -232,20 +232,20 @@ describe("GET /v1/workspace/tree", () => {
232
232
  expect(result.entries[0].type).toBe("file");
233
233
  });
234
234
 
235
- test("non-existent directory throws NotFoundError", () => {
236
- expect(() => handler({ queryParams: { path: "nope" } })).toThrow(
235
+ test("non-existent directory throws NotFoundError", async () => {
236
+ await expect(handler({ queryParams: { path: "nope" } })).rejects.toThrow(
237
237
  NotFoundError,
238
238
  );
239
239
  });
240
240
 
241
- test("path traversal attempt throws BadRequestError", () => {
242
- expect(() => handler({ queryParams: { path: "../../etc" } })).toThrow(
243
- BadRequestError,
244
- );
241
+ test("path traversal attempt throws BadRequestError", async () => {
242
+ await expect(
243
+ handler({ queryParams: { path: "../../etc" } }),
244
+ ).rejects.toThrow(BadRequestError);
245
245
  });
246
246
 
247
- test("entries have correct type field", () => {
248
- const result = handler({ queryParams: {} }) as {
247
+ test("entries have correct type field", async () => {
248
+ const result = (await handler({ queryParams: {} })) as {
249
249
  entries: Array<{ name: string; type: "file" | "directory" }>;
250
250
  };
251
251
  const subdirEntry = result.entries.find((e) => e.name === "subdir");
@@ -254,8 +254,8 @@ describe("GET /v1/workspace/tree", () => {
254
254
  expect(fileEntry?.type).toBe("file");
255
255
  });
256
256
 
257
- test("dotfiles and dot-directories are excluded", () => {
258
- const result = handler({ queryParams: {} }) as {
257
+ test("dotfiles and dot-directories are excluded", async () => {
258
+ const result = (await handler({ queryParams: {} })) as {
259
259
  entries: Array<{ name: string }>;
260
260
  };
261
261
  const names = result.entries.map((e) => e.name);
@@ -263,8 +263,8 @@ describe("GET /v1/workspace/tree", () => {
263
263
  expect(names).not.toContain(".hidden");
264
264
  });
265
265
 
266
- test("directory entries have null size and mimeType", () => {
267
- const result = handler({ queryParams: {} }) as {
266
+ test("directory entries have null size and mimeType", async () => {
267
+ const result = (await handler({ queryParams: {} })) as {
268
268
  entries: Array<{
269
269
  name: string;
270
270
  type: string;
@@ -278,8 +278,8 @@ describe("GET /v1/workspace/tree", () => {
278
278
  expect(dirEntry!.mimeType).toBeNull();
279
279
  });
280
280
 
281
- test("directories sorted before files", () => {
282
- const result = handler({ queryParams: {} }) as {
281
+ test("directories sorted before files", async () => {
282
+ const result = (await handler({ queryParams: {} })) as {
283
283
  entries: Array<{ type: string }>;
284
284
  };
285
285
  const firstFileIdx = result.entries.findIndex((e) => e.type === "file");
@@ -295,8 +295,10 @@ describe("GET /v1/workspace/tree", () => {
295
295
  }
296
296
  });
297
297
 
298
- test("includeDirSizes=true returns recursive byte size for directories", () => {
299
- const result = handler({ queryParams: { includeDirSizes: "true" } }) as {
298
+ test("includeDirSizes=true returns recursive byte size for directories", async () => {
299
+ const result = (await handler({
300
+ queryParams: { includeDirSizes: "true" },
301
+ })) as {
300
302
  entries: Array<{ name: string; type: string; size: number | null }>;
301
303
  };
302
304
  const subdirEntry = result.entries.find((e) => e.name === "subdir");
@@ -307,8 +309,10 @@ describe("GET /v1/workspace/tree", () => {
307
309
  expect(subdirEntry!.size).toBe("nested content".length);
308
310
  });
309
311
 
310
- test("includeDirSizes=true preserves accurate file sizes", () => {
311
- const result = handler({ queryParams: { includeDirSizes: "true" } }) as {
312
+ test("includeDirSizes=true preserves accurate file sizes", async () => {
313
+ const result = (await handler({
314
+ queryParams: { includeDirSizes: "true" },
315
+ })) as {
312
316
  entries: Array<{ name: string; type: string; size: number | null }>;
313
317
  };
314
318
  const fileEntry = result.entries.find((e) => e.name === "hello.txt");
@@ -316,8 +320,8 @@ describe("GET /v1/workspace/tree", () => {
316
320
  expect(fileEntry!.size).toBe("Hello, world!".length);
317
321
  });
318
322
 
319
- test("includeDirSizes omitted preserves null size for directories", () => {
320
- const result = handler({ queryParams: {} }) as {
323
+ test("includeDirSizes omitted preserves null size for directories", async () => {
324
+ const result = (await handler({ queryParams: {} })) as {
321
325
  entries: Array<{ type: string; size: number | null }>;
322
326
  };
323
327
  const dirEntry = result.entries.find((e) => e.type === "directory");
@@ -325,11 +329,36 @@ describe("GET /v1/workspace/tree", () => {
325
329
  expect(dirEntry!.size).toBeNull();
326
330
  });
327
331
 
328
- test("includeDirSizes=true returns 0 for empty directory", () => {
332
+ test("recursive=true lists nested entries and the bound fields", async () => {
333
+ const result = (await handler({ queryParams: { recursive: "true" } })) as {
334
+ entries: Array<{ path: string }>;
335
+ truncated: boolean;
336
+ skipped: string[];
337
+ };
338
+ expect(result.entries.map((e) => e.path)).toContain("subdir/nested.txt");
339
+ expect(result.truncated).toBe(false);
340
+ expect(Array.isArray(result.skipped)).toBe(true);
341
+ });
342
+
343
+ test("a non-recursive listing carries the same fields, unset", async () => {
344
+ const result = (await handler({ queryParams: {} })) as {
345
+ entries: Array<{ path: string }>;
346
+ truncated: boolean;
347
+ skipped: string[];
348
+ };
349
+ expect(result.entries.map((e) => e.path)).not.toContain(
350
+ "subdir/nested.txt",
351
+ );
352
+ expect(result).toMatchObject({ truncated: false, skipped: [] });
353
+ });
354
+
355
+ test("includeDirSizes=true returns 0 for empty directory", async () => {
329
356
  const emptyDir = join(testWorkspaceDir, "empty-for-size");
330
357
  mkdirSync(emptyDir, { recursive: true });
331
358
 
332
- const result = handler({ queryParams: { includeDirSizes: "true" } }) as {
359
+ const result = (await handler({
360
+ queryParams: { includeDirSizes: "true" },
361
+ })) as {
333
362
  entries: Array<{ name: string; size: number | null }>;
334
363
  };
335
364
  const emptyEntry = result.entries.find((e) => e.name === "empty-for-size");