@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
@@ -627,8 +627,6 @@ export interface LiveVoiceMetricsServerFrame extends LiveVoiceServerFrameBase {
627
627
  * condition as the two fields above.
628
628
  */
629
629
  readonly endpointDecisionSource?: "front-door" | "provider";
630
- /** Which floor-holding ack actually spoke during the turn, if any. */
631
- readonly ackSpoken?: "first_delta" | "tool_use";
632
630
  /**
633
631
  * Spoken progress narrations during the turn. Present only when at least
634
632
  * one progress update spoke (otherwise the field is absent, keeping frames
@@ -4,8 +4,8 @@
4
4
  * The properties worth pinning are the ones a refactor could quietly
5
5
  * break, each of which fails silently rather than loudly:
6
6
  *
7
- * 1. Plugin servers reach the manager at all. Reading only `config.mcp`
8
- * leaves a plugin's tools missing with nothing logged.
7
+ * 1. Plugin servers reach the manager at all. Reading only the workspace
8
+ * `mcp.json` leaves a plugin's tools missing with nothing logged.
9
9
  * 2. A workspace entry of the same id wins, so a plugin cannot redirect a
10
10
  * server the user configured by hand.
11
11
  * 3. Every server is attributed. `source` is what `McpClient` reads to
@@ -0,0 +1,135 @@
1
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { beforeEach, describe, expect, test } from "bun:test";
4
+
5
+ import { AGENT_PLUGINS_MCP_SCHEMA_URL } from "../spec-schema.js";
6
+ import {
7
+ loadWorkspaceMcpConfig,
8
+ overlayWorkspaceMcpForConfigRead,
9
+ readWorkspaceMcpFile,
10
+ saveWorkspaceMcpConfig,
11
+ } from "../workspace-mcp-config.js";
12
+
13
+ function mcpPath(): string {
14
+ return join(process.env.VELLUM_WORKSPACE_DIR!, "mcp.json");
15
+ }
16
+
17
+ describe("workspace mcp.json", () => {
18
+ beforeEach(() => {
19
+ mkdirSync(process.env.VELLUM_WORKSPACE_DIR!, { recursive: true });
20
+ rmSync(mcpPath(), { force: true });
21
+ });
22
+
23
+ test("missing file is an empty server map", () => {
24
+ expect(loadWorkspaceMcpConfig()).toEqual({ servers: {} });
25
+ expect(readWorkspaceMcpFile().fileState).toBe("missing");
26
+ });
27
+
28
+ test("round-trips a spec document without wrapping transport", () => {
29
+ saveWorkspaceMcpConfig({
30
+ servers: {
31
+ remote: {
32
+ transport: {
33
+ type: "streamable-http",
34
+ url: "https://example.com/mcp",
35
+ },
36
+ },
37
+ local: {
38
+ transport: {
39
+ type: "stdio",
40
+ command: "npx",
41
+ args: ["-y", "srv"],
42
+ },
43
+ },
44
+ },
45
+ });
46
+
47
+ expect(JSON.parse(readFileSync(mcpPath(), "utf8"))).toEqual({
48
+ $schema: AGENT_PLUGINS_MCP_SCHEMA_URL,
49
+ mcpServers: {
50
+ remote: {
51
+ type: "streamable-http",
52
+ url: "https://example.com/mcp",
53
+ },
54
+ local: {
55
+ type: "stdio",
56
+ command: "npx",
57
+ args: ["-y", "srv"],
58
+ },
59
+ },
60
+ });
61
+ expect(loadWorkspaceMcpConfig()).toEqual({
62
+ servers: {
63
+ remote: {
64
+ transport: {
65
+ type: "streamable-http",
66
+ url: "https://example.com/mcp",
67
+ },
68
+ },
69
+ local: {
70
+ transport: {
71
+ type: "stdio",
72
+ command: "npx",
73
+ args: ["-y", "srv"],
74
+ },
75
+ },
76
+ },
77
+ });
78
+ });
79
+
80
+ test("does not persist transport headers", () => {
81
+ saveWorkspaceMcpConfig({
82
+ servers: {
83
+ remote: {
84
+ transport: {
85
+ type: "streamable-http",
86
+ url: "https://example.com/mcp",
87
+ headers: { Authorization: "Bearer secret" },
88
+ },
89
+ },
90
+ },
91
+ });
92
+
93
+ const onDisk = readFileSync(mcpPath(), "utf8");
94
+ expect(onDisk).not.toContain("secret");
95
+ expect(loadWorkspaceMcpConfig().servers.remote?.transport).toEqual({
96
+ type: "streamable-http",
97
+ url: "https://example.com/mcp",
98
+ });
99
+ });
100
+
101
+ test("invalid file does not throw on read and refuses a later write", () => {
102
+ mkdirSync(process.env.VELLUM_WORKSPACE_DIR!, { recursive: true });
103
+ writeFileSync(mcpPath(), "{ not json");
104
+
105
+ expect(loadWorkspaceMcpConfig()).toEqual({ servers: {} });
106
+ expect(readWorkspaceMcpFile().fileState).toBe("invalid");
107
+ expect(() => saveWorkspaceMcpConfig({ servers: {} })).toThrow(/invalid JSON/);
108
+ });
109
+
110
+ test("overlay projects transport-only servers for config reads", () => {
111
+ saveWorkspaceMcpConfig({
112
+ servers: {
113
+ remote: {
114
+ transport: {
115
+ type: "streamable-http",
116
+ url: "https://example.com/mcp",
117
+ },
118
+ },
119
+ },
120
+ });
121
+
122
+ const config: Record<string, unknown> = { llm: { activeProfile: "balanced" } };
123
+ overlayWorkspaceMcpForConfigRead(config);
124
+ expect(config.mcp).toEqual({
125
+ servers: {
126
+ remote: {
127
+ transport: {
128
+ type: "streamable-http",
129
+ url: "https://example.com/mcp",
130
+ },
131
+ },
132
+ },
133
+ });
134
+ });
135
+ });
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * The MCP configuration the daemon actually runs.
3
3
  *
4
- * Two sources contribute servers: the workspace `config.json`, which the
4
+ * Two sources contribute servers: the workspace `mcp.json`, which the
5
5
  * user owns, and each installed plugin's `mcp.json`, which its author
6
6
  * owns. Both must reach the MCP manager, so every consumer that starts
7
- * servers builds its config through here rather than reading
8
- * `config.mcp` directly — a path that only reads the workspace half
7
+ * servers builds its config through here rather than reading the
8
+ * workspace file directly — a path that only reads the workspace half
9
9
  * leaves a plugin's tools silently missing.
10
10
  *
11
11
  * Every server comes out attributed with its {@link McpServerSource}, so
@@ -3,18 +3,21 @@
3
3
  *
4
4
  * Follows the same pattern as mcp-oauth-provider.ts: headers are stored in
5
5
  * the secure credential store (CES or encrypted file fallback) rather than
6
- * in plaintext config.json, keeping secrets out of workspace config files.
6
+ * in plaintext mcp.json, keeping secrets out of workspace config files.
7
7
  *
8
8
  * Key format: mcp:{serverId}:headers — stores JSON-serialized Record<string, string>.
9
9
  */
10
10
 
11
- import { loadRawConfig, saveRawConfig } from "../config/loader.js";
12
11
  import {
13
12
  deleteSecureKeyAsync,
14
13
  getSecureKeyAsync,
15
14
  setSecureKeyAsync,
16
15
  } from "../security/secure-keys.js";
17
16
  import { getLogger } from "../util/logger.js";
17
+ import {
18
+ loadWorkspaceMcpConfig,
19
+ saveWorkspaceMcpConfig,
20
+ } from "./workspace-mcp-config.js";
18
21
 
19
22
  const log = getLogger("mcp-header-store");
20
23
 
@@ -73,64 +76,45 @@ export async function deleteMcpHeaders(serverId: string): Promise<boolean> {
73
76
  }
74
77
 
75
78
  /**
76
- * One-time lazy migration: move any plaintext headers from config.json
77
- * transport entries into the credential store and strip them from config.
78
- * Safe to call on every MCP reload — no-ops when no legacy headers remain.
79
+ * Move leftover plaintext headers from mcp.json into the credential store
80
+ * and rewrite the file without them. Safe to call on every MCP reload.
79
81
  */
80
82
  export async function migrateLegacyMcpHeaders(): Promise<void> {
81
- const raw = loadRawConfig();
82
- const mcpConfig = raw.mcp as
83
- | { servers?: Record<string, Record<string, unknown>> }
84
- | undefined;
85
- const servers = mcpConfig?.servers;
86
- if (!servers) {
87
- return;
88
- }
83
+ const config = loadWorkspaceMcpConfig();
84
+ const servers = config.servers;
85
+ let dirty = false;
89
86
 
90
- let configDirty = false;
91
87
  for (const [id, server] of Object.entries(servers)) {
92
- const transport = server?.transport as Record<string, unknown> | undefined;
93
- if (
94
- !transport ||
95
- (transport.type !== "sse" && transport.type !== "streamable-http")
96
- ) {
88
+ const transport = server.transport;
89
+ if (transport.type !== "sse" && transport.type !== "streamable-http") {
97
90
  continue;
98
91
  }
99
- const legacyHeaders = transport.headers as
100
- | Record<string, string>
101
- | undefined;
102
- if (!legacyHeaders || Object.keys(legacyHeaders).length === 0) {
92
+ const leftoverHeaders = transport.headers;
93
+ if (!leftoverHeaders || Object.keys(leftoverHeaders).length === 0) {
103
94
  continue;
104
95
  }
105
96
 
106
- // Only migrate if credential store doesn't already have headers for
107
- // this server (idempotent — safe to re-run after partial failure).
108
97
  const existing = await getMcpHeaders(id);
109
- if (existing) {
110
- // Credential store already has headers; just strip the config copy.
111
- delete transport.headers;
112
- configDirty = true;
113
- continue;
114
- }
115
-
116
- const ok = await setMcpHeaders(id, legacyHeaders);
117
- if (ok) {
118
- delete transport.headers;
119
- configDirty = true;
98
+ if (!existing) {
99
+ const ok = await setMcpHeaders(id, leftoverHeaders);
100
+ if (!ok) {
101
+ log.warn(
102
+ { serverId: id },
103
+ "Skipping leftover header migration: credential store write failed; will retry on next reload",
104
+ );
105
+ continue;
106
+ }
120
107
  log.info(
121
108
  { serverId: id },
122
- "Migrated legacy MCP headers to credential store",
123
- );
124
- } else {
125
- log.warn(
126
- { serverId: id },
127
- "Skipping legacy header migration — credential store write failed; will retry on next reload",
109
+ "Moved leftover MCP headers to credential store",
128
110
  );
129
111
  }
112
+ delete transport.headers;
113
+ dirty = true;
130
114
  }
131
115
 
132
- if (configDirty) {
133
- saveRawConfig(raw);
134
- log.info("Config updated: legacy MCP headers removed after migration");
116
+ if (dirty) {
117
+ saveWorkspaceMcpConfig(config);
118
+ log.info("mcp.json updated: leftover MCP headers removed after migration");
135
119
  }
136
120
  }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Agent Plugins 1.0.0 `mcp.json` wire schema and projections onto the
3
+ * assistant's in-memory `McpTransport` union.
4
+ *
5
+ * Workspace `/workspace/mcp.json` and each plugin's root `mcp.json` share
6
+ * this document shape. Origin (`workspace` vs `plugin`) is resolved from
7
+ * which file was read, never from a field in the file.
8
+ */
9
+
10
+ import { z } from "zod";
11
+
12
+ import type { McpTransport } from "../config/schemas/mcp.js";
13
+
14
+ /** Canonical `$schema` value for an Agent Plugins 1.0.0 MCP document. */
15
+ export const AGENT_PLUGINS_MCP_SCHEMA_URL =
16
+ "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
17
+
18
+ const SpecStdioServerSchema = z.object({
19
+ type: z.literal("stdio"),
20
+ command: z.string().min(1),
21
+ args: z.array(z.string()).optional(),
22
+ env: z.record(z.string(), z.string()).optional(),
23
+ cwd: z.string().optional(),
24
+ });
25
+
26
+ /**
27
+ * Remote MCP entry. Agent Plugins 1.0.0 requires `type` and names no
28
+ * default. MCP's current remote transport is Streamable HTTP, so this
29
+ * host fills omitted `type` and the Claude-style `http` alias as
30
+ * `streamable-http`. `sse` stays explicit.
31
+ */
32
+ const SpecHttpServerSchema = z.object({
33
+ type: z
34
+ .union([
35
+ z.literal("streamable-http"),
36
+ z.literal("sse"),
37
+ z.literal("http").transform(() => "streamable-http" as const),
38
+ ])
39
+ .default("streamable-http"),
40
+ url: z.string().min(1),
41
+ headers: z.record(z.string(), z.string()).optional(),
42
+ });
43
+
44
+ export const SpecMcpServerSchema = z.union([
45
+ SpecStdioServerSchema,
46
+ SpecHttpServerSchema,
47
+ ]);
48
+
49
+ export const SpecMcpDocumentSchema = z.object({
50
+ $schema: z.string().optional(),
51
+ mcpServers: z.record(z.string(), z.unknown()),
52
+ });
53
+
54
+ export type SpecMcpServer = z.infer<typeof SpecMcpServerSchema>;
55
+ export type SpecMcpDocument = z.infer<typeof SpecMcpDocumentSchema>;
56
+
57
+ /**
58
+ * Project one spec-shaped entry onto the assistant's transport union. The
59
+ * two vocabularies already agree on type names and required fields, so this
60
+ * is a field copy plus an optional expander for plugin path variables.
61
+ */
62
+ export function projectSpecServerToTransport(
63
+ entry: SpecMcpServer,
64
+ expand?: (value: string) => string,
65
+ ): McpTransport {
66
+ if (entry.type === "stdio") {
67
+ const map = expand ?? ((value: string) => value);
68
+ return {
69
+ type: "stdio",
70
+ command: entry.command,
71
+ args: (entry.args ?? []).map(map),
72
+ ...(entry.env && {
73
+ env: Object.fromEntries(
74
+ Object.entries(entry.env).map(([key, value]) => [key, map(value)]),
75
+ ),
76
+ }),
77
+ };
78
+ }
79
+ return {
80
+ type: entry.type,
81
+ url: entry.url,
82
+ ...(entry.headers && { headers: entry.headers }),
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Project an in-memory transport onto a spec server entry. Headers are
88
+ * omitted: workspace secrets live in the credential store, and a write
89
+ * must not copy them into `mcp.json`.
90
+ */
91
+ export function projectTransportToSpecServer(
92
+ transport: McpTransport,
93
+ ): Record<string, unknown> {
94
+ if (transport.type === "stdio") {
95
+ const entry: Record<string, unknown> = {
96
+ type: "stdio",
97
+ command: transport.command,
98
+ };
99
+ if (transport.args && transport.args.length > 0) {
100
+ entry.args = transport.args;
101
+ }
102
+ if (transport.env) {
103
+ entry.env = transport.env;
104
+ }
105
+ return entry;
106
+ }
107
+ return {
108
+ type: transport.type,
109
+ url: transport.url,
110
+ };
111
+ }
@@ -13,13 +13,14 @@
13
13
  * further process that starts hosting turns has one call to make.
14
14
  */
15
15
 
16
- import { getConfig, invalidateConfigCache } from "../config/loader.js";
16
+ import { invalidateConfigCache } from "../config/loader.js";
17
17
  import type { McpConfig } from "../config/schemas/mcp.js";
18
18
  import { createMcpToolsFromServer } from "../tools/mcp/mcp-tool-factory.js";
19
19
  import { registerMcpTools, unregisterAllMcpTools } from "../tools/registry.js";
20
20
  import { getLogger } from "../util/logger.js";
21
21
  import { buildEffectiveMcpConfig } from "./effective-config.js";
22
22
  import { getMcpServerManager, stopMcpServerManager } from "./manager.js";
23
+ import { loadWorkspaceMcpConfig } from "./workspace-mcp-config.js";
23
24
 
24
25
  const log = getLogger("mcp-startup");
25
26
 
@@ -36,7 +37,8 @@ const log = getLogger("mcp-startup");
36
37
  * is a startup step in processes whose other work must not be held hostage to
37
38
  * a third-party server being up.
38
39
  *
39
- * @param workspaceMcpConfig The `mcp` block of the assistant config, if any.
40
+ * @param workspaceMcpConfig The projected workspace MCP config. Callers
41
+ * that omit it read `/workspace/mcp.json`.
40
42
  * @returns The number of tools registered across all servers.
41
43
  */
42
44
  export async function startConfiguredMcpServers(
@@ -44,7 +46,9 @@ export async function startConfiguredMcpServers(
44
46
  ): Promise<number> {
45
47
  let registered = 0;
46
48
  try {
47
- const mcpConfig = buildEffectiveMcpConfig(workspaceMcpConfig);
49
+ const mcpConfig = buildEffectiveMcpConfig(
50
+ workspaceMcpConfig ?? loadWorkspaceMcpConfig(),
51
+ );
48
52
  if (Object.keys(mcpConfig.servers).length === 0) {
49
53
  return 0;
50
54
  }
@@ -97,5 +101,5 @@ export async function restartConfiguredMcpServers(): Promise<number> {
97
101
  }
98
102
  unregisterAllMcpTools();
99
103
  invalidateConfigCache();
100
- return startConfiguredMcpServers(getConfig().mcp);
104
+ return startConfiguredMcpServers(loadWorkspaceMcpConfig());
101
105
  }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * User-owned MCP servers in `$VELLUM_WORKSPACE_DIR/mcp.json`.
3
+ *
4
+ * The file is an Agent Plugins 1.0.0 document (`$schema` + `mcpServers`).
5
+ * This module is the only reader and writer of that file. Presence of a
6
+ * server starts it. Headers are never persisted here.
7
+ */
8
+
9
+ import {
10
+ existsSync,
11
+ mkdirSync,
12
+ readFileSync,
13
+ renameSync,
14
+ writeFileSync,
15
+ } from "node:fs";
16
+ import { dirname } from "node:path";
17
+
18
+ import type { McpConfig, McpServerConfig } from "../config/schemas/mcp.js";
19
+ import { McpConfigSchema } from "../config/schemas/mcp.js";
20
+ import { getLogger } from "../util/logger.js";
21
+ import { getWorkspaceMcpConfigPath } from "../util/platform.js";
22
+ import {
23
+ AGENT_PLUGINS_MCP_SCHEMA_URL,
24
+ projectSpecServerToTransport,
25
+ projectTransportToSpecServer,
26
+ SpecMcpDocumentSchema,
27
+ SpecMcpServerSchema,
28
+ } from "./spec-schema.js";
29
+
30
+ const log = getLogger("workspace-mcp-config");
31
+
32
+ export type WorkspaceMcpFileState = "missing" | "ok" | "invalid";
33
+
34
+ export interface WorkspaceMcpLoadResult {
35
+ readonly config: McpConfig;
36
+ readonly issues: readonly string[];
37
+ readonly fileState: WorkspaceMcpFileState;
38
+ }
39
+
40
+ /**
41
+ * Read `/workspace/mcp.json` and project it onto the in-memory MCP config.
42
+ *
43
+ * A missing file is an empty server map. An invalid file disables the
44
+ * workspace half only (plugin servers still load) and is reported so a
45
+ * write can refuse to overwrite it.
46
+ */
47
+ export function readWorkspaceMcpFile(
48
+ path: string = getWorkspaceMcpConfigPath(),
49
+ ): WorkspaceMcpLoadResult {
50
+ if (!existsSync(path)) {
51
+ return {
52
+ config: McpConfigSchema.parse({}),
53
+ issues: [],
54
+ fileState: "missing",
55
+ };
56
+ }
57
+
58
+ let raw: string;
59
+ try {
60
+ raw = readFileSync(path, "utf8");
61
+ } catch (err) {
62
+ const message = `mcp.json unreadable: ${err instanceof Error ? err.message : String(err)}`;
63
+ return {
64
+ config: McpConfigSchema.parse({}),
65
+ issues: [message],
66
+ fileState: "invalid",
67
+ };
68
+ }
69
+
70
+ let json: unknown;
71
+ try {
72
+ json = JSON.parse(raw);
73
+ } catch (err) {
74
+ const message = `mcp.json invalid JSON: ${err instanceof Error ? err.message : String(err)}`;
75
+ return {
76
+ config: McpConfigSchema.parse({}),
77
+ issues: [message],
78
+ fileState: "invalid",
79
+ };
80
+ }
81
+
82
+ const document = SpecMcpDocumentSchema.safeParse(json);
83
+ if (!document.success) {
84
+ return {
85
+ config: McpConfigSchema.parse({}),
86
+ issues: ['mcp.json is missing a valid "mcpServers" object'],
87
+ fileState: "invalid",
88
+ };
89
+ }
90
+
91
+ const servers: Record<string, McpServerConfig> = {};
92
+ const issues: string[] = [];
93
+
94
+ for (const [serverKey, rawEntry] of Object.entries(document.data.mcpServers)) {
95
+ if (serverKey.trim().length === 0) {
96
+ issues.push("server key must be a non-empty string; skipping");
97
+ continue;
98
+ }
99
+
100
+ const entry = SpecMcpServerSchema.safeParse(rawEntry);
101
+ if (!entry.success) {
102
+ issues.push(
103
+ `invalid server entry "${serverKey}": ${entry.error.issues
104
+ .map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`)
105
+ .join("; ")}`,
106
+ );
107
+ continue;
108
+ }
109
+
110
+ if (entry.data.type === "stdio" && entry.data.cwd) {
111
+ issues.push(
112
+ `"${serverKey}": "cwd" is not supported by this host and was ignored; the server runs in the assistant's working directory`,
113
+ );
114
+ }
115
+
116
+ servers[serverKey] = {
117
+ transport: projectSpecServerToTransport(entry.data),
118
+ };
119
+ }
120
+
121
+ return {
122
+ config: { servers },
123
+ issues,
124
+ fileState: "ok",
125
+ };
126
+ }
127
+
128
+ /** Load the workspace MCP config, logging any declaration problems. */
129
+ export function loadWorkspaceMcpConfig(): McpConfig {
130
+ const result = readWorkspaceMcpFile();
131
+ for (const issue of result.issues) {
132
+ log.warn(issue);
133
+ }
134
+ return result.config;
135
+ }
136
+
137
+ /**
138
+ * Refuse a write when `mcp.json` exists but is not a usable document, so a
139
+ * later add/remove cannot clobber a hand-edit the user still means to fix.
140
+ */
141
+ export function assertWorkspaceMcpWritable(): void {
142
+ const result = readWorkspaceMcpFile();
143
+ if (result.fileState === "invalid") {
144
+ throw new Error(result.issues[0] ?? "mcp.json is not a valid document");
145
+ }
146
+ }
147
+
148
+ /** Persist the in-memory workspace MCP config as a spec-pure document. */
149
+ export function saveWorkspaceMcpConfig(config: McpConfig): void {
150
+ assertWorkspaceMcpWritable();
151
+ const path = getWorkspaceMcpConfigPath();
152
+ const mcpServers: Record<string, unknown> = {};
153
+ for (const [id, server] of Object.entries(config.servers)) {
154
+ mcpServers[id] = projectTransportToSpecServer(server.transport);
155
+ }
156
+
157
+ const document = {
158
+ $schema: AGENT_PLUGINS_MCP_SCHEMA_URL,
159
+ mcpServers,
160
+ };
161
+
162
+ mkdirSync(dirname(path), { recursive: true });
163
+ const tmpPath = `${path}.tmp`;
164
+ writeFileSync(tmpPath, JSON.stringify(document, null, 2) + "\n");
165
+ renameSync(tmpPath, path);
166
+ }
167
+
168
+ /**
169
+ * Replace `config.mcp` on a settings/export payload with the projected
170
+ * workspace servers (transport only, headers stripped). Old clients that
171
+ * still read `mcp.servers` from `/v1/config` keep seeing the same shape.
172
+ */
173
+ export function overlayWorkspaceMcpForConfigRead(config: unknown): void {
174
+ if (config === null || typeof config !== "object" || Array.isArray(config)) {
175
+ return;
176
+ }
177
+ const root = config as Record<string, unknown>;
178
+ const { servers } = loadWorkspaceMcpConfig();
179
+ const projected: Record<string, unknown> = {};
180
+ for (const [id, server] of Object.entries(servers)) {
181
+ const { headers: _headers, ...safeTransport } = server.transport as Record<
182
+ string,
183
+ unknown
184
+ >;
185
+ projected[id] = { transport: safeTransport };
186
+ }
187
+ root.mcp = { servers: projected };
188
+ }
@@ -56,15 +56,22 @@ export interface MessagingProvider {
56
56
  query: string,
57
57
  options?: SearchOptions,
58
58
  ): Promise<SearchResult>;
59
- sendMessage(
59
+
60
+ // ── Optional operations (platforms implement what they support) ───
61
+
62
+ /**
63
+ * Send through the provider's own API. A provider whose channel has a
64
+ * direct transport omits this: its sends go through the transport, which
65
+ * is the one send implementation per addressable channel, and the
66
+ * messaging tool routes there before it looks for this method.
67
+ */
68
+ sendMessage?(
60
69
  connection: OAuthConnection | undefined,
61
70
  conversationId: string,
62
71
  text: string,
63
72
  options?: SendOptions,
64
73
  ): Promise<SendResult>;
65
74
 
66
- // ── Optional operations (platforms implement what they support) ───
67
-
68
75
  getThreadReplies?(
69
76
  connection: OAuthConnection | undefined,
70
77
  conversationId: string,