@vellumai/assistant 0.12.0 → 0.12.1-dev.202609141913.5e16607

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 (441) hide show
  1. package/AGENTS.md +5 -1
  2. package/ARCHITECTURE.md +11 -12
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/openapi.yaml +292 -50
  6. package/package.json +1 -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__/app-compiler.test.ts +6 -0
  10. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  11. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  12. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  13. package/src/__tests__/computer-use-tools.test.ts +17 -5
  14. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  15. package/src/__tests__/config-schema.test.ts +28 -29
  16. package/src/__tests__/config-watcher.test.ts +23 -8
  17. package/src/__tests__/connection-model-compat.test.ts +2 -2
  18. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  19. package/src/__tests__/conversation-pairing.test.ts +26 -0
  20. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  21. package/src/__tests__/conversation-queue.test.ts +48 -207
  22. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  23. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  24. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  25. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  26. package/src/__tests__/conversation-title-service.test.ts +14 -10
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  28. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  29. package/src/__tests__/credential-record-retire.test.ts +171 -1
  30. package/src/__tests__/credential-routes.test.ts +212 -3
  31. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  32. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  33. package/src/__tests__/document-sync-tags.test.ts +25 -0
  34. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  35. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  36. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  37. package/src/__tests__/git-porcelain.test.ts +47 -0
  38. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  39. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  40. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  41. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  42. package/src/__tests__/host-file-proxy.test.ts +11 -23
  43. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  44. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  45. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  46. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  47. package/src/__tests__/mcp-health-check.test.ts +8 -11
  48. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  49. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  50. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  51. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  52. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  53. package/src/__tests__/model-intents.test.ts +3 -0
  54. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  55. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  56. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  57. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  58. package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
  59. package/src/__tests__/resolve-app.test.ts +135 -0
  60. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  61. package/src/__tests__/service-account-token.test.ts +247 -0
  62. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  63. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  64. package/src/__tests__/source-fingerprint.test.ts +184 -0
  65. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  66. package/src/__tests__/subagent-tools.test.ts +3 -2
  67. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  68. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  69. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  70. package/src/__tests__/workspace-git-service.test.ts +26 -0
  71. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  72. package/src/acp/__tests__/agent-process.test.ts +258 -4
  73. package/src/acp/__tests__/auth-required.test.ts +96 -0
  74. package/src/acp/__tests__/client-handler.test.ts +90 -2
  75. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  76. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  77. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  78. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  79. package/src/acp/__tests__/session-manager.test.ts +895 -4
  80. package/src/acp/agent-process.test.ts +4 -1
  81. package/src/acp/agent-process.ts +94 -9
  82. package/src/acp/auth-required.ts +50 -1
  83. package/src/acp/auto-install.test.ts +9 -7
  84. package/src/acp/client-handler.ts +16 -3
  85. package/src/acp/model-config.test.ts +166 -0
  86. package/src/acp/model-config.ts +138 -0
  87. package/src/acp/resolve-agent.test.ts +220 -6
  88. package/src/acp/resolve-agent.ts +88 -23
  89. package/src/acp/session-manager.test.ts +196 -3
  90. package/src/acp/session-manager.ts +441 -18
  91. package/src/acp/types.ts +50 -5
  92. package/src/api/events/acp-session-model-update.test.ts +88 -0
  93. package/src/api/events/acp-session-model-update.ts +46 -0
  94. package/src/api/events/app-files-changed.ts +4 -3
  95. package/src/api/events/notification-intent.ts +2 -0
  96. package/src/api/index.ts +6 -0
  97. package/src/apps/app-store.ts +1 -1
  98. package/src/apps/resolve-app.ts +81 -0
  99. package/src/apps/source-fingerprint.ts +266 -0
  100. package/src/bundler/app-compiler.ts +15 -0
  101. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  102. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  103. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  104. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  105. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  106. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  107. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  108. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  109. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  110. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  111. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  112. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  113. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  114. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  115. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  116. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  117. package/src/calls/barge-in-guard.ts +118 -0
  118. package/src/calls/call-constants.ts +24 -4
  119. package/src/calls/call-controller.ts +499 -199
  120. package/src/calls/call-transport.ts +16 -0
  121. package/src/calls/media-stream-output.ts +32 -0
  122. package/src/calls/media-stream-server.ts +82 -30
  123. package/src/calls/media-stream-stt-session.ts +15 -0
  124. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  125. package/src/calls/resolve-call-tts-provider.ts +33 -2
  126. package/src/calls/tts-call-strategy.ts +9 -5
  127. package/src/calls/types.ts +2 -0
  128. package/src/calls/voice-control-protocol.ts +53 -21
  129. package/src/calls/voice-leg-coordinator.ts +189 -0
  130. package/src/calls/voice-progress-cadence.ts +423 -0
  131. package/src/calls/voice-session-bridge.ts +59 -46
  132. package/src/calls/voice-triage-escalate.ts +175 -56
  133. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  134. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  135. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  136. package/src/cli/commands/apps.help.ts +67 -13
  137. package/src/cli/commands/apps.ts +183 -0
  138. package/src/cli/commands/bash.help.ts +8 -13
  139. package/src/cli/commands/bash.ts +7 -25
  140. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  141. package/src/cli/commands/channels/index.help.ts +70 -2
  142. package/src/cli/commands/channels/index.ts +16 -3
  143. package/src/cli/commands/channels/send.ts +128 -0
  144. package/src/cli/commands/credentials.help.ts +6 -2
  145. package/src/cli/commands/credentials.ts +2 -2
  146. package/src/cli/commands/mcp.help.ts +8 -8
  147. package/src/cli/commands/mcp.ts +1 -1
  148. package/src/cli/commands/roadmap.help.ts +5 -1
  149. package/src/cli/commands/roadmap.ts +1 -1
  150. package/src/cli/lib/bundled-marketplace.json +103 -90
  151. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  152. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  153. package/src/config/__tests__/platform-identity.test.ts +255 -0
  154. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  155. package/src/config/acp-agent-write.ts +43 -0
  156. package/src/config/acp-defaults.test.ts +13 -3
  157. package/src/config/acp-defaults.ts +23 -6
  158. package/src/config/acp-schema.test.ts +81 -0
  159. package/src/config/acp-schema.ts +30 -2
  160. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  161. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  162. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  163. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  164. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  165. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  166. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  167. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  168. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  169. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  170. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  171. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  172. package/src/config/default-profile-catalog.ts +1 -1
  173. package/src/config/env.ts +2 -2
  174. package/src/config/feature-flag-registry.json +24 -12
  175. package/src/config/loader.ts +1 -0
  176. package/src/config/memory-v3-gate.test.ts +31 -0
  177. package/src/config/memory-v3-gate.ts +20 -16
  178. package/src/config/platform-identity.ts +201 -0
  179. package/src/config/platform-rehydration.ts +11 -27
  180. package/src/config/schema.ts +2 -2
  181. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  182. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  183. package/src/config/schemas/calls.ts +1 -28
  184. package/src/config/schemas/live-voice.ts +1 -173
  185. package/src/config/schemas/mcp.ts +1 -1
  186. package/src/config/schemas/memory-retrospective.ts +10 -0
  187. package/src/config/schemas/services.ts +8 -0
  188. package/src/config/schemas/skills.ts +0 -14
  189. package/src/config/schemas/voice.ts +150 -0
  190. package/src/credential-execution/process-manager.test.ts +18 -19
  191. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  192. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  193. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  194. package/src/daemon/app-change-notify.ts +45 -0
  195. package/src/daemon/config-watcher.ts +13 -194
  196. package/src/daemon/conversation-process.ts +35 -64
  197. package/src/daemon/conversation-skill-tools.ts +13 -31
  198. package/src/daemon/conversation-store.ts +1 -1
  199. package/src/daemon/conversation-surfaces.ts +10 -8
  200. package/src/daemon/conversation-tool-setup.ts +37 -43
  201. package/src/daemon/conversation.ts +0 -3
  202. package/src/daemon/host-bash-proxy.ts +8 -12
  203. package/src/daemon/host-browser-proxy.ts +15 -1
  204. package/src/daemon/host-file-proxy.ts +8 -10
  205. package/src/daemon/host-transfer-proxy.ts +14 -18
  206. package/src/daemon/lifecycle.ts +0 -5
  207. package/src/daemon/mcp-reload-service.ts +5 -5
  208. package/src/daemon/message-types/sync.ts +1 -0
  209. package/src/daemon/orphan-reaper.ts +1 -1
  210. package/src/daemon/providers-setup.ts +1 -1
  211. package/src/daemon/shutdown-handlers.ts +1 -9
  212. package/src/daemon/skill-memory-refresh.ts +64 -0
  213. package/src/daemon/tool-side-effects.ts +7 -18
  214. package/src/desktop/__tests__/fake-desktop.ts +119 -0
  215. package/src/desktop/desktop-chrome-policy.test.ts +71 -0
  216. package/src/desktop/desktop-chrome-policy.ts +42 -0
  217. package/src/desktop/desktop-dependencies.test.ts +102 -0
  218. package/src/desktop/desktop-dependencies.ts +306 -0
  219. package/src/desktop/desktop-feature.test.ts +18 -0
  220. package/src/desktop/desktop-feature.ts +19 -0
  221. package/src/desktop/desktop-panel-config.test.ts +135 -0
  222. package/src/desktop/desktop-panel-config.ts +149 -0
  223. package/src/desktop/desktop-session-manager.test.ts +627 -0
  224. package/src/desktop/desktop-session-manager.ts +814 -0
  225. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  226. package/src/desktop/desktop-stream-bridge.ts +214 -0
  227. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  228. package/src/desktop/desktop-wallpaper.ts +113 -0
  229. package/src/desktop/desktop-window-theme.ts +170 -0
  230. package/src/home/__tests__/feed-writer.test.ts +22 -0
  231. package/src/home/feed-writer.ts +11 -2
  232. package/src/hooks/types.ts +33 -0
  233. package/src/i18n/AGENTS.md +22 -0
  234. package/src/i18n/__tests__/i18n.test.ts +108 -0
  235. package/src/i18n/index.ts +16 -0
  236. package/src/i18n/locales.ts +93 -0
  237. package/src/i18n/messages.ts +61 -0
  238. package/src/i18n/title.ts +25 -0
  239. package/src/inbound/platform-callback-registration.ts +4 -5
  240. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  241. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  242. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  243. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  244. package/src/live-voice/live-voice-session.ts +213 -693
  245. package/src/live-voice/protocol.ts +0 -2
  246. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  247. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  248. package/src/mcp/effective-config.ts +3 -3
  249. package/src/mcp/mcp-header-store.ts +29 -45
  250. package/src/mcp/spec-schema.ts +111 -0
  251. package/src/mcp/startup.ts +8 -4
  252. package/src/mcp/workspace-mcp-config.ts +188 -0
  253. package/src/messaging/provider.ts +10 -3
  254. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  255. package/src/messaging/providers/callback-routing.ts +18 -0
  256. package/src/messaging/providers/channel-transport.ts +56 -0
  257. package/src/messaging/providers/discord/transport.ts +17 -0
  258. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  259. package/src/messaging/providers/slack/adapter.ts +7 -44
  260. package/src/messaging/providers/slack/transport.ts +16 -0
  261. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  262. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  263. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  264. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  265. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  266. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  267. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  268. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  269. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  270. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  271. package/src/notifications/adapters/macos.ts +3 -0
  272. package/src/notifications/assistant-reply-producer.ts +8 -3
  273. package/src/notifications/conversation-pairing.ts +36 -16
  274. package/src/notifications/delivered-post-record.ts +11 -1
  275. package/src/notifications/guardian-feed-projection.ts +125 -5
  276. package/src/notifications/home-feed-side-effect.ts +55 -7
  277. package/src/notifications/schedule-result-producer.ts +66 -22
  278. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  279. package/src/oauth/byo-connection.test.ts +45 -0
  280. package/src/oauth/byo-connection.ts +36 -6
  281. package/src/oauth/connection-resolver.test.ts +73 -0
  282. package/src/oauth/connection-resolver.ts +95 -2
  283. package/src/oauth/seed-providers.ts +165 -0
  284. package/src/outbound-proxy/certs.ts +3 -5
  285. package/src/permissions/checker.test.ts +11 -6
  286. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  287. package/src/persistence/conversation-crud.ts +71 -63
  288. package/src/persistence/conversation-lineage.ts +12 -4
  289. package/src/persistence/conversation-title-placeholders.ts +23 -0
  290. package/src/persistence/conversation-title-service.ts +9 -26
  291. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  292. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  293. package/src/persistence/message-cursor.ts +78 -0
  294. package/src/persistence/message-reads.ts +30 -0
  295. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  296. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  297. package/src/persistence/schema/memory-core.ts +5 -0
  298. package/src/platform/client.test.ts +4 -2
  299. package/src/platform/client.ts +2 -10
  300. package/src/plugin-api/constants.ts +2 -0
  301. package/src/plugin-api/index.ts +1 -0
  302. package/src/plugin-api/types.ts +2 -0
  303. package/src/plugins/defaults/index.ts +2 -0
  304. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  305. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  306. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  307. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  308. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  309. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  310. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  311. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  312. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  313. package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
  314. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  315. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  316. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  317. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  318. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  319. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  320. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  321. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  322. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  323. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  324. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  325. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  326. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  327. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  328. package/src/plugins/defaults/memory/startup.ts +6 -1
  329. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  330. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  331. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
  332. package/src/plugins/defaults/memory/worker.ts +6 -0
  333. package/src/plugins/mcp-servers.ts +15 -75
  334. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  335. package/src/providers/gemini/client.ts +9 -23
  336. package/src/providers/gemini-thought-signature.test.ts +63 -0
  337. package/src/providers/gemini-thought-signature.ts +44 -0
  338. package/src/providers/inference/auth.ts +2 -3
  339. package/src/providers/inference/connection-availability.ts +27 -8
  340. package/src/providers/inference/resolve-auth.ts +22 -4
  341. package/src/providers/inference/service-account-token.ts +248 -0
  342. package/src/providers/model-catalog.ts +34 -0
  343. package/src/providers/model-intents.ts +10 -5
  344. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  345. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  346. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  347. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  348. package/src/providers/openai/chat-completions-provider.ts +192 -78
  349. package/src/providers/openai/codex-models.ts +0 -4
  350. package/src/providers/openai/google-thought-signature.ts +320 -0
  351. package/src/runtime/AGENTS.md +2 -0
  352. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  353. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  354. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  355. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  356. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  357. package/src/runtime/auth/same-actor.ts +38 -3
  358. package/src/runtime/channel-send.ts +316 -0
  359. package/src/runtime/http-server.ts +160 -151
  360. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  361. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  362. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  363. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  364. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  365. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  366. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  367. package/src/runtime/routes/acp-routes.test.ts +15 -0
  368. package/src/runtime/routes/acp-routes.ts +72 -9
  369. package/src/runtime/routes/app-management-routes.ts +73 -0
  370. package/src/runtime/routes/app-routes.ts +1 -1
  371. package/src/runtime/routes/channel-send-routes.ts +85 -0
  372. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  373. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  374. package/src/runtime/routes/credential-routes.ts +19 -10
  375. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  376. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  377. package/src/runtime/routes/diagnostics-routes.ts +55 -3
  378. package/src/runtime/routes/index.ts +4 -2
  379. package/src/runtime/routes/log-export-routes.ts +2 -0
  380. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  381. package/src/runtime/routes/migration-routes.ts +6 -20
  382. package/src/runtime/routes/platform-routes.ts +18 -15
  383. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  384. package/src/runtime/services/conversation-serializer.ts +16 -3
  385. package/src/runtime/sync/resource-sync-events.ts +2 -1
  386. package/src/schedule/worker.ts +1 -1
  387. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  388. package/src/security/ces-http-record-backend.ts +120 -0
  389. package/src/security/ces-rpc-record-backend.ts +7 -0
  390. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  391. package/src/skills/workspace-skill-md-signature.ts +50 -0
  392. package/src/subagent/consult-context.ts +12 -25
  393. package/src/tools/acp/list-agents.test.ts +3 -1
  394. package/src/tools/acp/spawn.test.ts +146 -11
  395. package/src/tools/acp/spawn.ts +21 -3
  396. package/src/tools/acp/status.test.ts +94 -0
  397. package/src/tools/acp/status.ts +40 -3
  398. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  399. package/src/tools/capability-offer.test.ts +22 -2
  400. package/src/tools/capability-offer.ts +16 -1
  401. package/src/tools/computer-use/definitions.ts +4 -2
  402. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  403. package/src/tools/credentials/metadata-store.ts +100 -0
  404. package/src/tools/host-filesystem/edit.test.ts +2 -1
  405. package/src/tools/host-filesystem/edit.ts +2 -2
  406. package/src/tools/host-filesystem/read.test.ts +2 -1
  407. package/src/tools/host-filesystem/read.ts +2 -2
  408. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  409. package/src/tools/host-filesystem/transfer.ts +2 -2
  410. package/src/tools/host-filesystem/write.test.ts +2 -1
  411. package/src/tools/host-filesystem/write.ts +2 -2
  412. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  413. package/src/tools/host-terminal/host-shell.ts +4 -20
  414. package/src/tools/policy-context.ts +6 -7
  415. package/src/tools/subagent/spawn.ts +5 -8
  416. package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
  417. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  418. package/src/tools/terminal/safe-env.ts +23 -3
  419. package/src/tools/terminal/sanitized-bash.ts +105 -0
  420. package/src/tools/watch/watch-retro-report.ts +0 -8
  421. package/src/util/__tests__/language-subtag.test.ts +23 -1
  422. package/src/util/ca-bundle.ts +13 -0
  423. package/src/util/language-subtag.ts +16 -0
  424. package/src/util/platform.ts +8 -0
  425. package/src/workspace/git-porcelain.ts +66 -0
  426. package/src/workspace/git-service.ts +5 -54
  427. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  428. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  429. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  430. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  431. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  432. package/src/workspace/migrations/registry.ts +6 -0
  433. package/src/__tests__/app-source-watcher.test.ts +0 -218
  434. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  435. package/src/__tests__/speaker-identification.test.ts +0 -56
  436. package/src/calls/speaker-identification.ts +0 -233
  437. package/src/daemon/app-source-watcher.ts +0 -218
  438. package/src/messaging/providers/whatsapp/client.ts +0 -29
  439. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  440. package/src/util/fs-watcher-error.ts +0 -36
  441. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -224,7 +224,10 @@ describe("surfaceProxyResolver — CU tool routing", () => {
224
224
  );
225
225
 
226
226
  expect(result.isError).toBe(true);
227
- expect(result.content).toContain("no desktop client connected");
227
+ expect(result.content).toContain(
228
+ "The Vellum desktop app is needed to view or control your screen",
229
+ );
230
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
228
231
  });
229
232
 
230
233
  test("returns error when proxy exists but client not connected", async () => {
@@ -237,7 +240,10 @@ describe("surfaceProxyResolver — CU tool routing", () => {
237
240
  });
238
241
 
239
242
  expect(result.isError).toBe(true);
240
- expect(result.content).toContain("no desktop client connected");
243
+ expect(result.content).toContain(
244
+ "The Vellum desktop app is needed to view or control your screen",
245
+ );
246
+ expect(result.content).toContain("https://www.vellum.ai/downloads");
241
247
  proxyObj.dispose();
242
248
  });
243
249
 
@@ -143,6 +143,31 @@ describe("document write routes publish documents:list", () => {
143
143
  expectDocumentsChanged(events);
144
144
  });
145
145
 
146
+ test("an existing document with a missing conversation can save before relinking", async () => {
147
+ await saveViaRoute();
148
+ const db = getDb();
149
+ db.run("PRAGMA foreign_keys = OFF");
150
+ try {
151
+ db.run("DELETE FROM conversations");
152
+ } finally {
153
+ db.run("PRAGMA foreign_keys = ON");
154
+ }
155
+
156
+ await expect(
157
+ saveViaRoute({ title: "Renamed notes", content: "Edited body" }),
158
+ ).resolves.toMatchObject({ success: true, surfaceId: "doc-sync" });
159
+ createConversation({ id: OTHER_CONVERSATION_ID });
160
+ await expect(
161
+ linkViaRoute("doc-sync", OTHER_CONVERSATION_ID),
162
+ ).resolves.toMatchObject({ success: true });
163
+ await expect(
164
+ invoke("getDocument", { pathParams: { id: "doc-sync" } }),
165
+ ).resolves.toMatchObject({
166
+ title: "Renamed notes",
167
+ content: "Edited body",
168
+ });
169
+ });
170
+
146
171
  test("a content-only save publishes", async () => {
147
172
  // The save moves `updated_at` and the word count, which order and fill the
148
173
  // Library and the assets list on every other client. The saving client
@@ -79,8 +79,6 @@ function makeFakeConversation(
79
79
  getTurnInterfaceContext: () => null,
80
80
  setTurnInterfaceContext: () => {},
81
81
  setTransportHints: () => {},
82
- setTrustContext: () => {},
83
- ensureActorScopedHistory: async () => {},
84
82
  emitActivityState: () => {
85
83
  if (activityFailures > 0) {
86
84
  activityFailures -= 1;
@@ -78,12 +78,6 @@ function makeFakeConversation(options: {
78
78
  setTransportHints: () => {
79
79
  mutationCalls.push("setTransportHints");
80
80
  },
81
- setTrustContext: () => {
82
- mutationCalls.push("setTrustContext");
83
- },
84
- ensureActorScopedHistory: async () => {
85
- mutationCalls.push("ensureActorScopedHistory");
86
- },
87
81
  emitActivityState: () => {
88
82
  mutationCalls.push("emitActivityState");
89
83
  },
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `evictConversationsForReload` drops in-memory conversations after a
3
- * config/prompt/skills reload so the next turn rebuilds them against the new
3
+ * config/prompt reload so the next turn rebuilds them against the new
4
4
  * config. Queued messages live only on the instance being disposed, so the
5
5
  * same "not idle while a queue is pending" rule the periodic evictor applies
6
6
  * has to hold here: `isProcessing()` reads false in the window between a turn
@@ -0,0 +1,47 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ collectDirtyPathsFromPorcelain,
5
+ parsePorcelainZ,
6
+ } from "../workspace/git-porcelain.js";
7
+
8
+ describe("parsePorcelainZ", () => {
9
+ test("parses ordinary modified and untracked records", () => {
10
+ expect(parsePorcelainZ("M tracked.txt\0?? new.txt\0")).toEqual([
11
+ { status: "M ", path: "tracked.txt" },
12
+ { status: "??", path: "new.txt" },
13
+ ]);
14
+ });
15
+
16
+ test("captures rename origin on the same record", () => {
17
+ expect(parsePorcelainZ("R dest.txt\0src.txt\0")).toEqual([
18
+ { status: "R ", path: "dest.txt", origin: "src.txt" },
19
+ ]);
20
+ });
21
+
22
+ test("captures copy origin on the same record", () => {
23
+ expect(parsePorcelainZ("C dest.txt\0src.txt\0")).toEqual([
24
+ { status: "C ", path: "dest.txt", origin: "src.txt" },
25
+ ]);
26
+ });
27
+
28
+ test("skips short leftover NUL entries", () => {
29
+ expect(parsePorcelainZ("M a.txt\0\0")).toEqual([
30
+ { status: "M ", path: "a.txt" },
31
+ ]);
32
+ });
33
+ });
34
+
35
+ describe("collectDirtyPathsFromPorcelain", () => {
36
+ test("flattens rename origin so both paths are staged", () => {
37
+ expect(
38
+ collectDirtyPathsFromPorcelain("R dest.txt\0src.txt\0?? extra.txt\0"),
39
+ ).toEqual(["dest.txt", "src.txt", "extra.txt"]);
40
+ });
41
+
42
+ test("dedupes a path that appears as both origin and destination", () => {
43
+ expect(collectDirtyPathsFromPorcelain("R same.txt\0same.txt\0")).toEqual([
44
+ "same.txt",
45
+ ]);
46
+ });
47
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Seed `$VELLUM_WORKSPACE_DIR/mcp.json` for tests that go through the
3
+ * real workspace MCP loader.
4
+ */
5
+
6
+ import type { McpConfig } from "../../config/schemas/mcp.js";
7
+ import { saveWorkspaceMcpConfig } from "../../mcp/workspace-mcp-config.js";
8
+
9
+ export function setWorkspaceMcp(servers: McpConfig["servers"]): void {
10
+ saveWorkspaceMcpConfig({ servers });
11
+ }
@@ -669,23 +669,31 @@ describe("HostBashProxy", () => {
669
669
  expect(sentMessages).toHaveLength(0);
670
670
  });
671
671
 
672
- test("rejects ambiguously when multiple same-user capable clients are connected and no targetClientId", async () => {
673
- // Regression: previously fell through to untargeted broadcast, fanning
674
- // a single targeted-style request out across every same-user machine.
672
+ test("selects the most recently active same-user client when several are connected", async () => {
675
673
  setup();
676
674
  setupMultipleClients(["client-1", "client-2", "client-3"]);
677
675
 
678
- const result = await proxy.request(
676
+ const resultPromise = proxy.request(
679
677
  { command: "echo hello" },
680
678
  "session-1",
681
679
  undefined,
682
680
  "user-A",
683
681
  );
684
682
 
685
- expect(result.isError).toBe(true);
686
- expect(result.content).toContain("target_client_id");
687
- // No broadcast happened
688
- expect(sentMessages).toHaveLength(0);
683
+ const sent = sentMessages[0] as Record<string, unknown>;
684
+ expect(sent.targetClientId).toBe("client-1");
685
+ const options = sentMessageOptions[0] as
686
+ | Record<string, unknown>
687
+ | undefined;
688
+ expect(options?.targetClientId).toBe("client-1");
689
+
690
+ proxy.resolveResult(sent.requestId as string, {
691
+ stdout: "hello\n",
692
+ stderr: "",
693
+ exitCode: 0,
694
+ timedOut: false,
695
+ });
696
+ expect((await resultPromise).isError).toBe(false);
689
697
  });
690
698
 
691
699
  test("falls through to broadcast when zero capable clients (existing timeout path)", async () => {
@@ -832,30 +840,19 @@ describe("HostBashProxy", () => {
832
840
  expect(sentMessages).toHaveLength(0);
833
841
  });
834
842
 
835
- test("untargeted local flow unchanged when no auto-resolve match", async () => {
843
+ test("rejects when the actor has no capable client", async () => {
836
844
  setup();
837
- // No capable clients connected — untargeted path runs.
838
845
 
839
- const resultPromise = proxy.request(
846
+ const result = await proxy.request(
840
847
  { command: "echo hello" },
841
848
  "session-1",
842
849
  undefined,
843
850
  "user-A",
844
851
  );
845
852
 
846
- expect(sentMessages).toHaveLength(1);
847
- const sent = sentMessages[0] as Record<string, unknown>;
848
- expect(sent.type).toBe("host_bash_request");
849
- expect(sent.targetClientId).toBeUndefined();
850
-
851
- const requestId = sent.requestId as string;
852
- proxy.resolveResult(requestId, {
853
- stdout: "hello\n",
854
- stderr: "",
855
- exitCode: 0,
856
- timedOut: false,
857
- });
858
- await resultPromise;
853
+ expect(result).toMatchObject({ isError: true });
854
+ expect(result.content).toContain("belongs to this user");
855
+ expect(sentMessages).toHaveLength(0);
859
856
  });
860
857
 
861
858
  test("auto-resolve to same-user client succeeds", async () => {
@@ -883,36 +880,20 @@ describe("HostBashProxy", () => {
883
880
  await resultPromise;
884
881
  });
885
882
 
886
- test("auto-resolve to different-user client falls through to untargeted", async () => {
883
+ test("rejects when only a different user has a capable client", async () => {
887
884
  setup();
888
- // Single capable client owned by user-B; caller is user-A.
889
885
  setupSingleClient("client-abc", "user-B");
890
886
 
891
- const resultPromise = proxy.request(
887
+ const result = await proxy.request(
892
888
  { command: "echo hello" },
893
889
  "session-1",
894
890
  undefined,
895
891
  "user-A",
896
892
  );
897
893
 
898
- // Auto-resolve must NOT pick the cross-user client; the untargeted
899
- // broadcast path runs instead.
900
- expect(sentMessages).toHaveLength(1);
901
- const sent = sentMessages[0] as Record<string, unknown>;
902
- expect(sent.type).toBe("host_bash_request");
903
- expect(sent.targetClientId).toBeUndefined();
904
-
905
- const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
906
- expect(opts?.targetClientId).toBeUndefined();
907
-
908
- const requestId = sent.requestId as string;
909
- proxy.resolveResult(requestId, {
910
- stdout: "hello\n",
911
- stderr: "",
912
- exitCode: 0,
913
- timedOut: false,
914
- });
915
- await resultPromise;
894
+ expect(result).toMatchObject({ isError: true });
895
+ expect(result.content).toContain("belongs to this user");
896
+ expect(sentMessages).toHaveLength(0);
916
897
  });
917
898
  });
918
899
  });
@@ -608,22 +608,17 @@ describe("HostBrowserProxy", () => {
608
608
  },
609
609
  ];
610
610
 
611
- const resultPromise = proxy.request(
611
+ const result = await proxy.request(
612
612
  { cdpMethod: "Page.navigate", cdpParams: { url: "https://a.test" } },
613
613
  "session-1",
614
614
  undefined,
615
615
  "user-1",
616
616
  );
617
617
 
618
- // Auto-resolution filters out the cross-user candidate, so the
619
- // proxy falls into the existing "no active extension connection"
620
- // rejection — we never broadcast to a different actor's client.
621
- await expect(resultPromise).rejects.toThrow(
622
- "no active extension connection",
623
- );
618
+ expect(result).toMatchObject({ isError: true });
619
+ expect(result.content).toContain("belongs to this user");
624
620
  expect(getPublishedMessages()).toHaveLength(0);
625
621
  });
626
-
627
622
  test("prefers the chrome-extension client over a more-recently-active macOS bridge", async () => {
628
623
  // Mock `listClientsByCapability` returns mockClients in array
629
624
  // order, which mirrors production's `lastActiveAt`-desc ordering.
@@ -720,11 +715,6 @@ describe("HostBrowserProxy", () => {
720
715
  });
721
716
 
722
717
  test("rejects when caller has actor but no host_browser-capable client is connected for that actor", async () => {
723
- // Same-user filter returns empty even though listClientsByCapability
724
- // would return a non-empty list (because that list is for a
725
- // different actor). The unfiltered fallback path runs only when
726
- // the caller has no actor — we don't silently broadcast to anyone
727
- // when the caller IS authenticated to a specific actor.
728
718
  mockClients = [
729
719
  {
730
720
  clientId: "other-user-ext",
@@ -734,16 +724,15 @@ describe("HostBrowserProxy", () => {
734
724
  },
735
725
  ];
736
726
 
737
- const resultPromise = proxy.request(
727
+ const result = await proxy.request(
738
728
  { cdpMethod: "Page.navigate", cdpParams: { url: "https://a.test" } },
739
729
  "session-1",
740
730
  undefined,
741
731
  "user-1",
742
732
  );
743
733
 
744
- await expect(resultPromise).rejects.toThrow(
745
- "no active extension connection",
746
- );
734
+ expect(result).toMatchObject({ isError: true });
735
+ expect(result.content).toContain("belongs to this user");
747
736
  expect(getPublishedMessages()).toHaveLength(0);
748
737
  });
749
738
  });
@@ -274,6 +274,29 @@ describe("HostFileProxy — targetClientId (Phase 2)", () => {
274
274
  });
275
275
  });
276
276
 
277
+ test("selects the most recently active same-user client when several are connected", async () => {
278
+ setup();
279
+ setupMultipleClients(["client-1", "client-2"]);
280
+
281
+ const resultPromise = proxy.request(
282
+ { operation: "read", path: "/tmp/file.txt" },
283
+ "session-1",
284
+ undefined,
285
+ undefined,
286
+ TEST_PRINCIPAL,
287
+ );
288
+
289
+ const sent = sentMessages[0] as Record<string, unknown>;
290
+ expect(sent.targetClientId).toBe("client-1");
291
+ const options = sentMessageOptions[0] as
292
+ | Record<string, unknown>
293
+ | undefined;
294
+ expect(options?.targetClientId).toBe("client-1");
295
+
296
+ proxy.resolve(sent.requestId as string, { content: "ok", isError: false });
297
+ expect((await resultPromise).isError).toBe(false);
298
+ });
299
+
277
300
  // ── targetClientId in cancel (abort signal) ──────────────────────────
278
301
 
279
302
  describe("targetClientId in cancel — abort signal", () => {
@@ -746,9 +746,8 @@ describe("HostFileProxy", () => {
746
746
  expect(sentMessages).toHaveLength(0);
747
747
  });
748
748
 
749
- test("untargeted request with no auto-resolve match still broadcasts (legacy path unchanged)", async () => {
749
+ test("rejects when the actor has no capable client", async () => {
750
750
  setup();
751
- // No matching same-user clients available.
752
751
  mockClients = [
753
752
  {
754
753
  clientId: "client-A",
@@ -757,23 +756,18 @@ describe("HostFileProxy", () => {
757
756
  },
758
757
  ];
759
758
 
760
- const resultPromise = proxy.request(
759
+ const result = await proxy.request(
761
760
  { operation: "read", path: "/tmp/test.txt" },
762
761
  "session-1",
763
762
  undefined,
764
763
  undefined,
765
- "user-B", // No same-user match → no auto-resolve, broadcast untargeted.
764
+ "user-B",
766
765
  );
767
766
 
768
- expect(sentMessages).toHaveLength(1);
769
- const sent = sentMessages[0] as Record<string, unknown>;
770
- expect(sent.targetClientId).toBeUndefined();
771
- const requestId = sent.requestId as string;
772
-
773
- proxy.resolve(requestId, { content: "ok", isError: false });
774
- await resultPromise;
767
+ expect(result).toMatchObject({ isError: true });
768
+ expect(result.content).toContain("belongs to this user");
769
+ expect(sentMessages).toHaveLength(0);
775
770
  });
776
-
777
771
  test("auto-resolve picks the same-user client when there's exactly one", async () => {
778
772
  setup();
779
773
  mockClients = [
@@ -807,7 +801,7 @@ describe("HostFileProxy", () => {
807
801
  await resultPromise;
808
802
  });
809
803
 
810
- test("auto-resolve falls through when no client matches the source user", async () => {
804
+ test("rejects when only a different user has a capable client", async () => {
811
805
  setup();
812
806
  mockClients = [
813
807
  {
@@ -817,7 +811,7 @@ describe("HostFileProxy", () => {
817
811
  },
818
812
  ];
819
813
 
820
- const resultPromise = proxy.request(
814
+ const result = await proxy.request(
821
815
  { operation: "read", path: "/tmp/test.txt" },
822
816
  "session-1",
823
817
  undefined,
@@ -825,16 +819,10 @@ describe("HostFileProxy", () => {
825
819
  "user-C",
826
820
  );
827
821
 
828
- // No same-user client → no auto-resolve, broadcast untargeted.
829
- expect(sentMessages).toHaveLength(1);
830
- const sent = sentMessages[0] as Record<string, unknown>;
831
- expect(sent.targetClientId).toBeUndefined();
832
- const requestId = sent.requestId as string;
833
-
834
- proxy.resolve(requestId, { content: "ok", isError: false });
835
- await resultPromise;
822
+ expect(result).toMatchObject({ isError: true });
823
+ expect(result.content).toContain("belongs to this user");
824
+ expect(sentMessages).toHaveLength(0);
836
825
  });
837
-
838
826
  test("legacy embedded targetClientId in input still goes through the same-user gate", async () => {
839
827
  setup();
840
828
  mockClients = [
@@ -812,7 +812,7 @@ describe("HostTransferProxy — targetClientId", () => {
812
812
  await resultPromise;
813
813
  });
814
814
 
815
- test("requestToHost: auto-resolve falls through when no client matches the source user", async () => {
815
+ test("requestToHost: rejects when no client matches the source user", async () => {
816
816
  setup();
817
817
  mockClientRegistry.set("client-A", {
818
818
  clientId: "client-A",
@@ -824,7 +824,7 @@ describe("HostTransferProxy — targetClientId", () => {
824
824
  const srcPath = `/tmp/htp-auto-no-match-${Date.now()}.txt`;
825
825
  await globalThis.Bun.write(srcPath, "data");
826
826
 
827
- const resultPromise = proxy.requestToHost(
827
+ const result = await proxy.requestToHost(
828
828
  {
829
829
  sourcePath: srcPath,
830
830
  destPath: "/host/dest.txt",
@@ -835,13 +835,9 @@ describe("HostTransferProxy — targetClientId", () => {
835
835
  "user-C",
836
836
  );
837
837
 
838
- await waitForMessages(sentMessages, 1);
839
- const sent = sentMessages[0] as Record<string, unknown>;
840
- expect(sent.targetClientId).toBeUndefined();
841
-
842
- const requestId = sent.requestId as string;
843
- proxy.resolveTransferResult(requestId, { isError: false });
844
- await resultPromise;
838
+ expect(result).toMatchObject({ isError: true });
839
+ expect(result.content).toContain("belongs to this user");
840
+ expect(sentMessages).toHaveLength(0);
845
841
  });
846
842
 
847
843
  test("requestToSandbox: targeted request from a different user is rejected", async () => {
@@ -897,6 +893,31 @@ describe("HostTransferProxy — targetClientId", () => {
897
893
  await resultPromise;
898
894
  });
899
895
 
896
+ test("requestToSandbox: rejects when no client matches the source user", async () => {
897
+ setup();
898
+ const a: MockClient = {
899
+ clientId: "client-A",
900
+ capabilities: ["host_file"],
901
+ actorPrincipalId: "user-A",
902
+ };
903
+ mockCapableClients = [a];
904
+ mockClientRegistry.set("client-A", a);
905
+
906
+ const result = await proxy.requestToSandbox(
907
+ {
908
+ sourcePath: "/host/source.txt",
909
+ destPath: "/sandbox/dest.txt",
910
+ conversationId: "conv-same-9-no-match",
911
+ },
912
+ undefined,
913
+ "user-C",
914
+ );
915
+
916
+ expect(result).toMatchObject({ isError: true });
917
+ expect(result.content).toContain("belongs to this user");
918
+ expect(sentMessages).toHaveLength(0);
919
+ });
920
+
900
921
  test("requestToSandbox: auto-resolve picks the same-user client when there's exactly one", async () => {
901
922
  setup();
902
923
  const a: MockClient = {
@@ -6,6 +6,7 @@ import {
6
6
  getCatalogProviderForModel,
7
7
  isModelInCatalog,
8
8
  PROVIDER_CATALOG,
9
+ supportsForcedToolChoiceWithThinking,
9
10
  } from "../providers/model-catalog.js";
10
11
  import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
11
12
  import { resolvePricing, resolvePricingForUsage } from "../util/pricing.js";
@@ -551,6 +552,30 @@ describe("LLM catalog parity: daemon vs client", () => {
551
552
  expect(getCatalogProviderForModel("unknown/model")).toBeUndefined();
552
553
  });
553
554
 
555
+ test("forced tool choice with thinking is scoped to OpenRouter Kimi K2.6", () => {
556
+ expect(
557
+ supportsForcedToolChoiceWithThinking("openrouter", "moonshotai/kimi-k2.6"),
558
+ ).toBe(false);
559
+ expect(
560
+ supportsForcedToolChoiceWithThinking(
561
+ "openrouter",
562
+ "moonshotai/kimi-k2.6-20260420",
563
+ ),
564
+ ).toBe(false);
565
+ expect(
566
+ supportsForcedToolChoiceWithThinking(
567
+ "vercel-ai-gateway",
568
+ "moonshotai/kimi-k2.6",
569
+ ),
570
+ ).toBe(true);
571
+ expect(
572
+ supportsForcedToolChoiceWithThinking("openrouter", "unknown/model"),
573
+ ).toBe(true);
574
+ expect(
575
+ supportsForcedToolChoiceWithThinking("unknown-provider", "unknown/model"),
576
+ ).toBe(true);
577
+ });
578
+
554
579
  test("Gemini 2.5 Pro catalog context matches provider limits", () => {
555
580
  const gemini = PROVIDER_CATALOG.find((entry) => entry.id === "gemini");
556
581
  expect(
@@ -24,15 +24,12 @@ mock.module("../mcp/mcp-auth-state.js", () => ({
24
24
  getMcpAuthState: mockGetMcpAuthState,
25
25
  }));
26
26
 
27
- import { setConfig } from "./helpers/set-config.js";
28
-
29
- // Seed the MCP server the routes look up via `loadRawConfig()` into the
30
- // workspace config for real.
31
- setConfig("mcp", {
32
- servers: {
33
- "my-server": {
34
- transport: { type: "sse", url: "https://mcp.example.com" },
35
- },
27
+ import { setWorkspaceMcp } from "./helpers/set-workspace-mcp.js";
28
+
29
+ // Seed the MCP server the routes look up via workspace mcp.json.
30
+ setWorkspaceMcp({
31
+ "my-server": {
32
+ transport: { type: "sse", url: "https://mcp.example.com" },
36
33
  },
37
34
  });
38
35