@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
@@ -0,0 +1,74 @@
1
+ import {
2
+ afterAll,
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ spyOn,
8
+ test,
9
+ } from "bun:test";
10
+
11
+ import { setOverridesForTesting } from "../../__tests__/feature-flag-test-helpers.js";
12
+ import { desktopDependencyInstaller } from "../../desktop/desktop-dependencies.js";
13
+ import { ROUTES } from "./desktop-setup-routes.js";
14
+ import { NotFoundError } from "./errors.js";
15
+
16
+ const originalContainerized = process.env.IS_CONTAINERIZED;
17
+ const status = spyOn(desktopDependencyInstaller, "getStatus");
18
+ const start = spyOn(desktopDependencyInstaller, "start");
19
+
20
+ afterAll(() => {
21
+ status.mockRestore();
22
+ start.mockRestore();
23
+ });
24
+
25
+ beforeEach(() => {
26
+ process.env.IS_CONTAINERIZED = "true";
27
+ status.mockReturnValue({ state: "required" });
28
+ start.mockReturnValue({ state: "installing", stage: "packages" });
29
+ });
30
+
31
+ afterEach(() => {
32
+ if (originalContainerized === undefined) {
33
+ delete process.env.IS_CONTAINERIZED;
34
+ } else {
35
+ process.env.IS_CONTAINERIZED = originalContainerized;
36
+ }
37
+ setOverridesForTesting({});
38
+ status.mockClear();
39
+ start.mockClear();
40
+ });
41
+
42
+ describe("desktop setup route feature gate", () => {
43
+ for (const route of ROUTES) {
44
+ for (const flag of [false, undefined]) {
45
+ test(`${route.method} refuses a disabled or missing flag (${flag}) without touching setup`, () => {
46
+ setOverridesForTesting(
47
+ flag === undefined ? {} : { "assistant-desktop": flag },
48
+ );
49
+ expect(() => route.handler({})).toThrow(NotFoundError);
50
+ expect(status).not.toHaveBeenCalled();
51
+ expect(start).not.toHaveBeenCalled();
52
+ });
53
+ }
54
+
55
+ test(`${route.method} refuses non-containerized assistants even with the flag enabled`, () => {
56
+ process.env.IS_CONTAINERIZED = "false";
57
+ setOverridesForTesting({ "assistant-desktop": true });
58
+ expect(() => route.handler({})).toThrow(NotFoundError);
59
+ expect(status).not.toHaveBeenCalled();
60
+ expect(start).not.toHaveBeenCalled();
61
+ });
62
+
63
+ test(`${route.method} serves setup only with the flag enabled in a container`, () => {
64
+ setOverridesForTesting({ "assistant-desktop": true });
65
+ expect(route.handler({})).toEqual(
66
+ route.method === "GET"
67
+ ? { state: "required" }
68
+ : { state: "installing", stage: "packages" },
69
+ );
70
+ expect(route.method === "GET" ? status : start).toHaveBeenCalledTimes(1);
71
+ expect(route.method === "GET" ? start : status).not.toHaveBeenCalled();
72
+ });
73
+ }
74
+ });
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+
3
+ import { getConfig } from "../../config/loader.js";
4
+ import { desktopDependencyInstaller } from "../../desktop/desktop-dependencies.js";
5
+ import { isAssistantDesktopEnabled } from "../../desktop/desktop-feature.js";
6
+ import { GATEWAY_PRINCIPALS } from "../auth/route-policy.js";
7
+ import { NotFoundError } from "./errors.js";
8
+ import type { RouteDefinition } from "./types.js";
9
+
10
+ const statusSchema = z.object({
11
+ state: z.enum(["required", "installing", "ready", "failed", "unsupported"]),
12
+ stage: z.enum(["packages", "chrome", "checking"]).optional(),
13
+ });
14
+
15
+ export const ROUTES: RouteDefinition[] = ["GET", "POST"].map((method) => ({
16
+ operationId:
17
+ method === "GET" ? "desktop_setup_status" : "desktop_setup_install",
18
+ endpoint: "desktop/setup",
19
+ method,
20
+ policy: { requiredScopes: [], allowedPrincipalTypes: GATEWAY_PRINCIPALS },
21
+ handler: () => {
22
+ if (!isAssistantDesktopEnabled(getConfig())) {
23
+ throw new NotFoundError("Desktop is not available on this assistant");
24
+ }
25
+ return method === "GET"
26
+ ? desktopDependencyInstaller.getStatus()
27
+ : desktopDependencyInstaller.start();
28
+ },
29
+ summary:
30
+ method === "GET"
31
+ ? "Get desktop setup status"
32
+ : "Install desktop components",
33
+ tags: ["desktop"],
34
+ responseBody: statusSchema,
35
+ }));
@@ -22,7 +22,9 @@ import {
22
22
  getConfiguredProvider,
23
23
  userMessage,
24
24
  } from "../../providers/provider-send-message.js";
25
+ import { isMaxTokensStopReason } from "../../providers/stop-reasons.js";
25
26
  import { getLogger } from "../../util/logger.js";
27
+ import { safeStringSlice } from "../../util/unicode.js";
26
28
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
27
29
  import { BadRequestError } from "./errors.js";
28
30
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -48,7 +50,11 @@ function sanitizeWindowTitle(title: string | undefined): string {
48
50
  if (!title) {
49
51
  return "";
50
52
  }
51
- return title.replace(/[<>]/g, "").slice(0, MAX_WINDOW_TITLE_LENGTH);
53
+ return safeStringSlice(
54
+ title.replace(/[<>]/g, ""),
55
+ 0,
56
+ MAX_WINDOW_TITLE_LENGTH,
57
+ );
52
58
  }
53
59
 
54
60
  interface DictationBody {
@@ -176,9 +182,44 @@ function buildCommandPrompt(body: DictationBody, stylePrompt?: string): string {
176
182
  return sections.join("\n");
177
183
  }
178
184
 
179
- function computeMaxTokens(inputLength: number): number {
185
+ export function computeMaxTokens(inputLength: number): number {
180
186
  const estimatedInputTokens = Math.ceil(inputLength / 3);
181
- return Math.max(256, estimatedInputTokens + 128);
187
+ // The cleanup tool call carries the whole transcript back in its `text`
188
+ // argument, plus a `reasoning` string and the JSON scaffolding around both,
189
+ // so the budget has to cover well more than the input alone. `max_tokens` is
190
+ // a ceiling rather than a target: a call that stops short of it costs no
191
+ // extra latency.
192
+ return Math.max(512, estimatedInputTokens * 2 + 256);
193
+ }
194
+
195
+ export type CleanupRejection = "truncated";
196
+
197
+ /**
198
+ * Decide whether the cleanup model's rewrite is safe to use as the payload.
199
+ *
200
+ * The rewrite replaces what the user actually said. A token-cap stop means the
201
+ * tool JSON was cut mid-argument, so however complete the `text` argument
202
+ * happens to look, it is a fragment; the raw transcript is the safer payload
203
+ * then. Unpolished beats wrong.
204
+ *
205
+ * Whether a shorter rewrite still preserved the meaning is the model's call
206
+ * (the prompt asks it to drop fillers and tighten phrasing), so there is no
207
+ * length check here: a deterministic ratio cannot tell a concise cleanup from
208
+ * a summary.
209
+ */
210
+ export function resolveCleanedDictation(
211
+ raw: string,
212
+ cleaned: string,
213
+ stopReason: string | null | undefined,
214
+ ): { text: string; rejected: CleanupRejection | null } {
215
+ const trimmed = cleaned.trim();
216
+ if (!trimmed) {
217
+ return { text: raw, rejected: null };
218
+ }
219
+ if (isMaxTokensStopReason(stopReason)) {
220
+ return { text: raw, rejected: "truncated" };
221
+ }
222
+ return { text: trimmed, rejected: null };
182
223
  }
183
224
 
184
225
  interface DictationResult {
@@ -329,7 +370,23 @@ async function handleDictation(body: DictationBody): Promise<DictationResult> {
329
370
  ...profileMeta,
330
371
  };
331
372
  }
332
- const cleanedText = input.text?.trim() || transcription;
373
+ const { text: cleanedText, rejected } = resolveCleanedDictation(
374
+ transcription,
375
+ input.text ?? "",
376
+ response.stopReason,
377
+ );
378
+ if (rejected) {
379
+ // Lengths only -- transcript content must never be logged.
380
+ log.warn(
381
+ {
382
+ rejected,
383
+ stopReason: response.stopReason,
384
+ rawChars: transcription.length,
385
+ cleanedChars: input.text?.trim().length ?? 0,
386
+ },
387
+ "Dictation cleanup rejected, using raw transcription",
388
+ );
389
+ }
333
390
  const normalizedText = applyDictionary(cleanedText, profile.dictionary);
334
391
  return {
335
392
  text: normalizedText,
@@ -445,6 +445,10 @@ export function handleSubscribeAssistantEvents(
445
445
  ? ["host_cu_window_capture" as const]
446
446
  : []),
447
447
  ...(interfaceId === "macos" &&
448
+ headers?.["x-vellum-cu-sequence"] === "1"
449
+ ? ["host_cu_sequence" as const]
450
+ : []),
451
+ ...(interfaceId === "macos" &&
448
452
  headers?.["x-vellum-cu-annotate"] === "1"
449
453
  ? ["host_cu_annotate" as const]
450
454
  : []),
@@ -36,6 +36,10 @@ const HostCuResultBodySchema = z.object({
36
36
  executionError: z.string().optional(),
37
37
  secondaryWindows: z.string().optional(),
38
38
  userGuidance: z.string().optional(),
39
+ timings: z
40
+ .record(z.string(), z.number())
41
+ .describe("Per-phase helper timings in milliseconds")
42
+ .optional(),
39
43
  });
40
44
 
41
45
  // ---------------------------------------------------------------------------
@@ -56,6 +60,7 @@ async function handleHostCuResult({ body, headers }: RouteHandlerArgs) {
56
60
  executionError,
57
61
  secondaryWindows,
58
62
  userGuidance,
63
+ timings,
59
64
  } = parseBody(HostCuResultBodySchema, body);
60
65
 
61
66
  const peeked = pendingInteractions.get(requestId);
@@ -119,6 +124,7 @@ async function handleHostCuResult({ body, headers }: RouteHandlerArgs) {
119
124
  executionError,
120
125
  secondaryWindows,
121
126
  userGuidance,
127
+ timings,
122
128
  });
123
129
 
124
130
  return { accepted: true };
@@ -39,6 +39,7 @@ import { ROUTES as CALL_ROUTES } from "./call-routes.js";
39
39
  import { ROUTES as CHANNEL_AVAILABILITY_ROUTES } from "./channel-availability-routes.js";
40
40
  import { ROUTES as CHANNEL_READINESS_ROUTES } from "./channel-readiness-routes.js";
41
41
  import { CHANNEL_ROUTES } from "./channel-route-definitions.js";
42
+ import { ROUTES as CHANNEL_SEND_ROUTES } from "./channel-send-routes.js";
42
43
  import { ROUTES as CHANNEL_VERIFICATION_ROUTES } from "./channel-verification-routes.js";
43
44
  import { ROUTES as CHATGPT_SUBSCRIPTION_AUTH_ROUTES } from "./chatgpt-subscription-auth-routes.js";
44
45
  import { ROUTES as CLIENT_ROUTES } from "./client-routes.js";
@@ -58,10 +59,10 @@ import { ROUTES as CONVERSATIONS_IMPORT_ROUTES } from "./conversations-import-ro
58
59
  import { ROUTES as CREDENTIAL_PROMPT_ROUTES } from "./credential-prompt-routes.js";
59
60
  import { ROUTES as CREDENTIAL_REQUEST_ROUTES } from "./credential-request-routes.js";
60
61
  import { ROUTES as CREDENTIAL_ROUTES } from "./credential-routes.js";
61
- import { ROUTES as DEBUG_BASH_ROUTES } from "./debug-bash-routes.js";
62
62
  import { ROUTES as DEBUG_ROUTES } from "./debug-routes.js";
63
63
  import { ROUTES as DEFAULT_PROVIDER_ROUTES } from "./default-provider-routes.js";
64
64
  import { ROUTES as DEFER_ROUTES } from "./defer-routes.js";
65
+ import { ROUTES as DESKTOP_SETUP_ROUTES } from "./desktop-setup-routes.js";
65
66
  import { ROUTES as DIAGNOSTICS_ROUTES } from "./diagnostics-routes.js";
66
67
  import { ROUTES as DISK_PRESSURE_ROUTES } from "./disk-pressure-routes.js";
67
68
  import { ROUTES as DOCUMENT_COMMENT_ROUTES } from "./document-comments-routes.js";
@@ -191,6 +192,7 @@ export const ROUTES: RouteDefinition[] = [
191
192
  ...CHANNEL_VERIFICATION_ROUTES,
192
193
  ...CHANNEL_AVAILABILITY_ROUTES,
193
194
  ...CHANNEL_READINESS_ROUTES,
195
+ ...CHANNEL_SEND_ROUTES,
194
196
  ...BROWSER_ROUTES,
195
197
  ...BROWSER_TABS_ROUTES,
196
198
  ...BTW_ROUTES,
@@ -215,9 +217,9 @@ export const ROUTES: RouteDefinition[] = [
215
217
  ...CONVERSATION_QUERY_ROUTES,
216
218
  ...CONVERSATION_STARTER_ROUTES,
217
219
  ...ACTIVATION_ROUTES,
218
- ...DEBUG_BASH_ROUTES,
219
220
  ...DEBUG_ROUTES,
220
221
  ...DEFAULT_PROVIDER_ROUTES,
222
+ ...DESKTOP_SETUP_ROUTES,
221
223
  ...DIAGNOSTICS_ROUTES,
222
224
  ...DISK_PRESSURE_ROUTES,
223
225
  ...RESOURCE_PRESSURE_ROUTES,
@@ -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,
@@ -230,6 +232,31 @@ function assertOAuthRequestUrlAllowed(
230
232
  }
231
233
  }
232
234
 
235
+ /**
236
+ * Required scopes the resolved connection's stored grant lacks. Credential
237
+ * health measures the same thing on the heartbeat; measuring it on the
238
+ * request names the gap at the moment a call may depend on it, since a
239
+ * connection made before a scope was required keeps working for every call
240
+ * that does not need it. A managed connection has no local row and reports
241
+ * nothing.
242
+ */
243
+ function missingScopesForConnection(
244
+ providerRow: OAuthProviderRow,
245
+ connectionId: string,
246
+ ): string[] {
247
+ const row = getConnection(connectionId);
248
+ if (!row) {
249
+ return [];
250
+ }
251
+ return missingScopesForStoredToken(
252
+ parseJsonSafe<string[]>(providerRow.defaultScopes ?? "[]") ?? [],
253
+ parseJsonSafe<Record<string, string>>(providerRow.authorizeParams ?? "") ??
254
+ undefined,
255
+ providerRow.scopeSeparator,
256
+ parseJsonSafe<string[]>(row.grantedScopes ?? "[]") ?? [],
257
+ );
258
+ }
259
+
233
260
  // ---------------------------------------------------------------------------
234
261
  // Disconnect handler
235
262
  // ---------------------------------------------------------------------------
@@ -1002,6 +1029,15 @@ export async function handleRequest({ body = {} }: RouteHandlerArgs) {
1002
1029
  `(e.g. https://host/full/path) so the host and full path are set explicitly.`;
1003
1030
  }
1004
1031
 
1032
+ const missingScopes = missingScopesForConnection(providerRow, connection.id);
1033
+ if (missingScopes.length > 0) {
1034
+ const scopeHint =
1035
+ `The ${b.provider} connection is missing required scopes: ${missingScopes.join(", ")}. ` +
1036
+ `It was connected before they were required, so calls that need them fail. ` +
1037
+ `Reconnect it from Integrations, or run 'assistant oauth connect ${b.provider}', to grant them.`;
1038
+ result.hint = result.hint ? `${scopeHint}\n\n${result.hint}` : scopeHint;
1039
+ }
1040
+
1005
1041
  return result;
1006
1042
  }
1007
1043
 
@@ -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