@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
@@ -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 = {
@@ -0,0 +1,121 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { setConfig } from "./helpers/set-config.js";
4
+
5
+ setConfig("memory", { enabled: false });
6
+
7
+ import {
8
+ type ConversationMessage,
9
+ ConversationMessageSchema,
10
+ } from "../api/responses/conversation-message.js";
11
+ import {
12
+ addMessage,
13
+ createConversation,
14
+ } from "../persistence/conversation-crud.js";
15
+ import { getDb } from "../persistence/db-connection.js";
16
+ import { initializeDb } from "../persistence/db-init.js";
17
+ import { handleListMessages } from "../runtime/routes/conversation-routes.js";
18
+
19
+ await initializeDb();
20
+
21
+ describe("handleListMessages camera-frame projection", () => {
22
+ beforeEach(() => {
23
+ const db = getDb();
24
+ db.run("DELETE FROM message_attachments");
25
+ db.run("DELETE FROM attachments");
26
+ db.run("DELETE FROM messages");
27
+ db.run("DELETE FROM conversations");
28
+ });
29
+
30
+ test.each([
31
+ {
32
+ name: "an ambient keep",
33
+ metadata: { scripted: true, sightFrameAttachmentIds: ["att-keep"] },
34
+ cameraFrame: true,
35
+ },
36
+ {
37
+ name: "a spoken turn carrying a parked frame",
38
+ metadata: {
39
+ sightFrameAttachmentIds: ["att-parked"],
40
+ voiceSessionTurn: true,
41
+ },
42
+ cameraFrame: false,
43
+ },
44
+ {
45
+ name: "a shutter photo",
46
+ metadata: { livePhoto: true, voiceSessionTurn: true, scripted: false },
47
+ cameraFrame: false,
48
+ },
49
+ {
50
+ name: "a plain user row with literal camera-frame text",
51
+ metadata: undefined,
52
+ cameraFrame: false,
53
+ },
54
+ {
55
+ name: "a scripted row with no frame tag",
56
+ metadata: { scripted: true },
57
+ cameraFrame: false,
58
+ },
59
+ {
60
+ name: "a scripted row with an empty frame tag",
61
+ metadata: { scripted: true, sightFrameAttachmentIds: [] },
62
+ cameraFrame: false,
63
+ },
64
+ {
65
+ name: "a scripted row with invalid frame ids",
66
+ metadata: { scripted: true, sightFrameAttachmentIds: [""] },
67
+ cameraFrame: false,
68
+ },
69
+ ])("classifies $name from metadata", async ({ metadata, cameraFrame }) => {
70
+ const conv = createConversation();
71
+ const row = await addMessage(
72
+ conv.id,
73
+ "user",
74
+ JSON.stringify([{ type: "text", text: "(camera frame)" }]),
75
+ { metadata },
76
+ );
77
+
78
+ const response = (await handleListMessages({
79
+ queryParams: { conversationId: conv.id },
80
+ })) as { messages: ConversationMessage[] };
81
+
82
+ expect(response.messages).toHaveLength(1);
83
+ const projected = response.messages[0]!;
84
+ expect(projected.id).toBe(row.id);
85
+ expect(() => ConversationMessageSchema.parse(projected)).not.toThrow();
86
+ if (cameraFrame) {
87
+ expect(projected.cameraFrame).toBe(true);
88
+ } else {
89
+ expect(projected).not.toHaveProperty("cameraFrame");
90
+ }
91
+ expect(
92
+ ConversationMessageSchema.safeParse({ ...projected, cameraFrame: false })
93
+ .success,
94
+ ).toBe(false);
95
+ });
96
+
97
+ test.each(["not-json", "null", "", "{}"])(
98
+ "omits cameraFrame for unreadable or empty metadata %j",
99
+ async (metadata) => {
100
+ const conv = createConversation();
101
+ const row = await addMessage(
102
+ conv.id,
103
+ "user",
104
+ JSON.stringify([{ type: "text", text: "(camera frame)" }]),
105
+ );
106
+ getDb()
107
+ .$client.prepare("UPDATE messages SET metadata = ? WHERE id = ?")
108
+ .run(metadata, row.id);
109
+
110
+ const response = (await handleListMessages({
111
+ queryParams: { conversationId: conv.id },
112
+ })) as { messages: ConversationMessage[] };
113
+
114
+ expect(response.messages).toHaveLength(1);
115
+ const projected = response.messages[0]!;
116
+ expect(projected.id).toBe(row.id);
117
+ expect(projected).not.toHaveProperty("cameraFrame");
118
+ expect(() => ConversationMessageSchema.parse(projected)).not.toThrow();
119
+ },
120
+ );
121
+ });
@@ -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
 
@@ -1,4 +1,4 @@
1
- import { readFileSync, utimesSync, writeFileSync } from "node:fs";
1
+ import { mkdirSync, readFileSync, rmSync, utimesSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { beforeEach, describe, expect, mock, test } from "bun:test";
4
4
 
@@ -14,14 +14,6 @@ mock.module("../util/logger.js", () => ({
14
14
  truncateForLog: (value: string, maxLen = 500) => value.slice(0, maxLen),
15
15
  }));
16
16
 
17
- // The config routes under test read and write the workspace config.json via
18
- // the real loader (`loadRawConfig`/`saveRawConfig`). Tests seed the raw file
19
- // directly (the fixtures are raw-file shapes, including deliberately
20
- // malformed trees, so the whole file is replaced rather than composed via
21
- // `setConfig`) and detect commits by comparing the on-disk text against the
22
- // seeded snapshot — `saveRawConfig` pretty-prints, so any commit changes the
23
- // text.
24
- let rawConfig: Record<string, unknown> = {};
25
17
  let seededRawText = "";
26
18
  let mtimeSeq = 0;
27
19
 
@@ -29,19 +21,23 @@ function configJsonPath(): string {
29
21
  return join(process.env.VELLUM_WORKSPACE_DIR!, "config.json");
30
22
  }
31
23
 
32
- /** Write `raw` to the workspace config.json as the seeded pre-test state. */
24
+ function mcpJsonPath(): string {
25
+ return join(process.env.VELLUM_WORKSPACE_DIR!, "mcp.json");
26
+ }
27
+
33
28
  function seedRawConfig(raw: Record<string, unknown>): void {
34
- rawConfig = raw;
35
29
  seededRawText = JSON.stringify(raw);
30
+ mkdirSync(process.env.VELLUM_WORKSPACE_DIR!, { recursive: true });
36
31
  writeFileSync(configJsonPath(), seededRawText);
37
- // Distinct mtime per write so the loader's file-signature cache can never
38
- // read two consecutive seeds as identical.
39
32
  mtimeSeq += 1;
40
33
  const stamp = new Date(Date.now() + mtimeSeq);
41
34
  utimesSync(configJsonPath(), stamp, stamp);
42
35
  }
43
36
 
44
- /** The raw config a route commit persisted, or null when nothing was saved. */
37
+ function seedWorkspaceMcp(document: unknown): void {
38
+ writeFileSync(mcpJsonPath(), JSON.stringify(document, null, 2) + "\n");
39
+ }
40
+
45
41
  function committedRaw(): Record<string, unknown> | null {
46
42
  const text = readFileSync(configJsonPath(), "utf8");
47
43
  if (text === seededRawText) {
@@ -119,11 +115,6 @@ const configGetRoute = findRoute("config_get");
119
115
  const configPatchRoute = findRoute("config_patch");
120
116
  const configSetRoute = findRoute("config_set");
121
117
 
122
- /**
123
- * Config responses inject the code-catalog default profiles into
124
- * `llm.profiles` (the effective wire view). These tests pin the MCP secret
125
- * boundary, so drop the injected block before whole-response comparisons.
126
- */
127
118
  function withoutWireProfiles(
128
119
  result: Record<string, unknown>,
129
120
  ): Record<string, unknown> {
@@ -134,21 +125,18 @@ function withoutWireProfiles(
134
125
  describe("MCP config secret boundary", () => {
135
126
  beforeEach(() => {
136
127
  seedRawConfig({});
128
+ rmSync(mcpJsonPath(), { force: true });
137
129
  });
138
130
 
139
- test("config_get omits legacy MCP transport headers from settings-read responses", () => {
140
- seedRawConfig({
141
- mcp: {
142
- servers: {
143
- remote: {
144
- transport: {
145
- type: "streamable-http",
146
- url: "https://mcp.example.com",
147
- headers: {
148
- Authorization: "Bearer mcp-secret",
149
- "X-API-Key": "mcp-api-secret",
150
- },
151
- },
131
+ test("config_get omits MCP headers from the mcp.json overlay", () => {
132
+ seedWorkspaceMcp({
133
+ mcpServers: {
134
+ remote: {
135
+ type: "streamable-http",
136
+ url: "https://mcp.example.com",
137
+ headers: {
138
+ Authorization: "Bearer mcp-secret",
139
+ "X-API-Key": "mcp-api-secret",
152
140
  },
153
141
  },
154
142
  },
@@ -167,64 +155,61 @@ describe("MCP config secret boundary", () => {
167
155
  });
168
156
  });
169
157
 
170
- test("config_get omits headers inside malformed MCP server trees", () => {
158
+ test("config_get ignores leftover config.json mcp so its headers cannot leak", () => {
171
159
  seedRawConfig({
172
160
  mcp: {
173
- servers: [
174
- {
161
+ servers: {
162
+ remote: {
175
163
  transport: {
176
- headers: { Authorization: "Bearer malformed-secret" },
164
+ type: "streamable-http",
165
+ url: "https://mcp.example.com",
166
+ headers: { Authorization: "Bearer leftover-secret" },
177
167
  },
178
168
  },
179
- ],
169
+ },
180
170
  },
181
171
  });
182
172
 
183
173
  const result = configGetRoute.handler({}) as Record<string, unknown>;
184
174
 
185
- expect(JSON.stringify(result)).not.toContain("malformed-secret");
186
- expect(withoutWireProfiles(result)).toEqual({
187
- mcp: {
188
- servers: [
189
- {
190
- transport: {},
191
- },
192
- ],
193
- },
194
- });
175
+ expect(JSON.stringify(result)).not.toContain("leftover-secret");
176
+ expect(
177
+ (result.mcp as { servers: Record<string, unknown> }).servers,
178
+ ).toEqual({});
195
179
  });
196
180
 
197
181
  test("config_get preserves an MCP server named headers", () => {
198
- seedRawConfig({
199
- mcp: {
200
- servers: {
201
- headers: {
202
- transport: {
203
- type: "streamable-http",
204
- url: "https://mcp.example.com",
205
- },
206
- },
182
+ seedWorkspaceMcp({
183
+ mcpServers: {
184
+ headers: {
185
+ type: "streamable-http",
186
+ url: "https://mcp.example.com",
207
187
  },
208
188
  },
209
189
  });
210
190
 
211
191
  const result = configGetRoute.handler({}) as Record<string, unknown>;
212
192
 
213
- expect(withoutWireProfiles(result)).toEqual(rawConfig);
193
+ expect(withoutWireProfiles(result).mcp).toEqual({
194
+ servers: {
195
+ headers: {
196
+ transport: {
197
+ type: "streamable-http",
198
+ url: "https://mcp.example.com",
199
+ },
200
+ },
201
+ },
202
+ });
214
203
  });
215
204
 
216
205
  test("config_get preserves non-credential headers env vars", () => {
217
- seedRawConfig({
218
- mcp: {
219
- servers: {
220
- local: {
221
- transport: {
222
- type: "stdio",
223
- command: "npx",
224
- env: {
225
- headers: "not-a-transport-header",
226
- },
227
- },
206
+ seedWorkspaceMcp({
207
+ mcpServers: {
208
+ local: {
209
+ type: "stdio",
210
+ command: "npx",
211
+ env: {
212
+ headers: "not-a-transport-header",
228
213
  },
229
214
  },
230
215
  },
@@ -232,7 +217,20 @@ describe("MCP config secret boundary", () => {
232
217
 
233
218
  const result = configGetRoute.handler({}) as Record<string, unknown>;
234
219
 
235
- expect(withoutWireProfiles(result)).toEqual(rawConfig);
220
+ expect(withoutWireProfiles(result).mcp).toEqual({
221
+ servers: {
222
+ local: {
223
+ transport: {
224
+ type: "stdio",
225
+ command: "npx",
226
+ args: [],
227
+ env: {
228
+ headers: "not-a-transport-header",
229
+ },
230
+ },
231
+ },
232
+ },
233
+ });
236
234
  });
237
235
 
238
236
  test("config_patch rejects MCP transport headers so generic writes cannot reintroduce plaintext credentials", async () => {
@@ -256,7 +254,7 @@ describe("MCP config secret boundary", () => {
256
254
  expect(committedRaw()).toBeNull();
257
255
  });
258
256
 
259
- test("config_patch allows an MCP server named headers when its value has no header credentials", async () => {
257
+ test("config_patch strips mcp so a full-config round trip cannot write it back", async () => {
260
258
  const result = await configPatchRoute.handler({
261
259
  body: {
262
260
  mcp: {
@@ -272,68 +270,25 @@ describe("MCP config secret boundary", () => {
272
270
  },
273
271
  });
274
272
 
275
- expect(withoutWireProfiles(result as Record<string, unknown>)).toEqual({
276
- mcp: {
277
- servers: {
278
- headers: {
279
- transport: {
280
- type: "streamable-http",
281
- url: "https://mcp.example.com",
282
- },
283
- },
284
- },
285
- },
286
- });
287
- });
288
-
289
- test("config_patch allows non-credential headers env vars", async () => {
290
- const result = await configPatchRoute.handler({
291
- body: {
292
- mcp: {
293
- servers: {
294
- local: {
295
- transport: {
296
- type: "stdio",
297
- command: "npx",
298
- env: {
299
- headers: "not-a-transport-header",
300
- },
301
- },
302
- },
303
- },
304
- },
305
- },
306
- });
307
-
308
- expect(withoutWireProfiles(result as Record<string, unknown>)).toEqual({
309
- mcp: {
310
- servers: {
311
- local: {
312
- transport: {
313
- type: "stdio",
314
- command: "npx",
315
- env: {
316
- headers: "not-a-transport-header",
317
- },
318
- },
319
- },
320
- },
321
- },
322
- });
273
+ expect(committedRaw()?.mcp).toBeUndefined();
274
+ expect(
275
+ (result as { mcp: { servers: Record<string, unknown> } }).mcp.servers,
276
+ ).toEqual({});
323
277
  });
324
278
 
325
- test("config_set rejects malformed MCP server trees containing headers", async () => {
279
+ test("config_set rejects MCP server writes", async () => {
326
280
  await expect(
327
281
  configSetRoute.handler({
328
282
  body: {
329
283
  path: "mcp.servers",
330
- value: [
331
- {
284
+ value: {
285
+ remote: {
332
286
  transport: {
333
- headers: { Authorization: "Bearer malformed-secret" },
287
+ type: "streamable-http",
288
+ url: "https://mcp.example.com",
334
289
  },
335
290
  },
336
- ],
291
+ },
337
292
  },
338
293
  }),
339
294
  ).rejects.toThrow(BadRequestError);
@@ -341,19 +296,6 @@ describe("MCP config secret boundary", () => {
341
296
  });
342
297
 
343
298
  test("config_set rejects direct MCP transport header paths", async () => {
344
- seedRawConfig({
345
- mcp: {
346
- servers: {
347
- remote: {
348
- transport: {
349
- type: "streamable-http",
350
- url: "https://mcp.example.com",
351
- },
352
- },
353
- },
354
- },
355
- });
356
-
357
299
  await expect(
358
300
  configSetRoute.handler({
359
301
  body: {
@@ -18,17 +18,14 @@ mock.module("../mcp/client.js", () => ({
18
18
  },
19
19
  }));
20
20
 
21
- import { setConfig } from "./helpers/set-config.js";
22
-
23
- // Seed the MCP server the list route reads via `loadRawConfig()` into the
24
- // workspace config for real.
25
- setConfig("mcp", {
26
- servers: {
27
- test: {
28
- transport: {
29
- type: "streamable-http",
30
- url: "https://example.com/mcp",
31
- },
21
+ import { setWorkspaceMcp } from "./helpers/set-workspace-mcp.js";
22
+
23
+ // Seed the MCP server the list route reads from workspace mcp.json.
24
+ setWorkspaceMcp({
25
+ test: {
26
+ transport: {
27
+ type: "streamable-http",
28
+ url: "https://example.com/mcp",
32
29
  },
33
30
  },
34
31
  });