@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
@@ -32,6 +32,12 @@ import {
32
32
  } from "../daemon/daemon-readiness.js";
33
33
  import { processMessage } from "../daemon/process-message.js";
34
34
  import { makeAddrInUseError } from "../daemon/startup-error.js";
35
+ import { isAssistantDesktopEnabled } from "../desktop/desktop-feature.js";
36
+ import {
37
+ DESKTOP_CLOSE,
38
+ destroyDesktopSessionManager,
39
+ } from "../desktop/desktop-session-manager.js";
40
+ import { DesktopStreamBridge } from "../desktop/desktop-stream-bridge.js";
35
41
  import {
36
42
  createLiveVoiceConnection,
37
43
  type LiveVoiceConnection,
@@ -229,6 +235,32 @@ interface WatchStreamWebSocketData {
229
235
  session?: WatchStreamSession;
230
236
  }
231
237
 
238
+ /**
239
+ * WebSocket data for `/v1/desktop/stream`: raw RFB bytes to the assistant
240
+ * desktop.
241
+ */
242
+ interface DesktopStreamWebSocketData {
243
+ wsType: "desktop-stream";
244
+ /** Bound at open time so message/close handlers reach this socket's pump. */
245
+ bridge?: DesktopStreamBridge;
246
+ }
247
+
248
+ type AllWebSocketData =
249
+ | MediaStreamWebSocketData
250
+ | SttStreamWebSocketData
251
+ | LiveVoiceWebSocketData
252
+ | WatchStreamWebSocketData
253
+ | DesktopStreamWebSocketData;
254
+
255
+ function assistantDesktopEnabled(): boolean {
256
+ try {
257
+ return isAssistantDesktopEnabled(getConfig());
258
+ } catch (err) {
259
+ log.warn({ err }, "Failed to read config for desktop stream gate");
260
+ return false;
261
+ }
262
+ }
263
+
232
264
  export class RuntimeHttpServer {
233
265
  private server: ReturnType<typeof Bun.serve> | null = null;
234
266
  private port: number;
@@ -264,11 +296,6 @@ export class RuntimeHttpServer {
264
296
  }
265
297
 
266
298
  async start(): Promise<void> {
267
- type AllWebSocketData =
268
- | MediaStreamWebSocketData
269
- | SttStreamWebSocketData
270
- | LiveVoiceWebSocketData
271
- | WatchStreamWebSocketData;
272
299
  this.server = Bun.serve<AllWebSocketData>({
273
300
  port: this.port,
274
301
  hostname: this.hostname,
@@ -424,6 +451,22 @@ export class RuntimeHttpServer {
424
451
  void session.start();
425
452
  return;
426
453
  }
454
+ if (data.wsType === "desktop-stream") {
455
+ log.info("Desktop stream WebSocket opened");
456
+ if (!assistantDesktopEnabled()) {
457
+ ws.close(
458
+ DESKTOP_CLOSE.unavailable,
459
+ "Desktop is not available on this assistant",
460
+ );
461
+ return;
462
+ }
463
+ const bridge = new DesktopStreamBridge(ws, {
464
+ isEnabled: assistantDesktopEnabled,
465
+ });
466
+ data.bridge = bridge;
467
+ void bridge.start();
468
+ return;
469
+ }
427
470
  log.warn("WebSocket opened with unknown data type — closing");
428
471
  ws.close(1008, "Unknown WebSocket type");
429
472
  },
@@ -474,6 +517,10 @@ export class RuntimeHttpServer {
474
517
  }
475
518
  return;
476
519
  }
520
+ if (data.wsType === "desktop-stream") {
521
+ data.bridge?.handleClientFrame(message);
522
+ return;
523
+ }
477
524
  log.warn("WebSocket message on unknown data type — closing");
478
525
  ws.close(1008, "Unknown WebSocket type");
479
526
  },
@@ -567,6 +614,14 @@ export class RuntimeHttpServer {
567
614
  }
568
615
  return;
569
616
  }
617
+ if (data.wsType === "desktop-stream") {
618
+ log.info(
619
+ { code, reason: reason?.toString() },
620
+ "Desktop stream WebSocket closed",
621
+ );
622
+ data.bridge?.handleClose();
623
+ return;
624
+ }
570
625
  log.warn(
571
626
  { code, reason: reason?.toString() },
572
627
  "WebSocket with unknown data type closed",
@@ -707,6 +762,9 @@ export class RuntimeHttpServer {
707
762
  // that a socket closing just before shutdown had already under way.
708
763
  await drainWatchRetros();
709
764
 
765
+ // Bounded by the manager's kill grace, so shutdown never hangs on X.
766
+ await destroyDesktopSessionManager();
767
+
710
768
  const liveVoiceManager = getLiveVoiceSessionManager();
711
769
  const liveVoiceSessionId = liveVoiceManager.activeSessionId;
712
770
  if (liveVoiceSessionId) {
@@ -810,6 +868,15 @@ export class RuntimeHttpServer {
810
868
  return this.handleWatchStreamUpgrade(req, server);
811
869
  }
812
870
 
871
+ // WebSocket upgrade for the assistant desktop RFB stream, under the same
872
+ // private-network restrictions and gateway-service token verification.
873
+ if (
874
+ path === "/v1/desktop/stream" &&
875
+ req.headers.get("upgrade")?.toLowerCase() === "websocket"
876
+ ) {
877
+ return this.handleDesktopStreamUpgrade(req, server);
878
+ }
879
+
813
880
  // Twilio webhook endpoints — before auth check because Twilio
814
881
  // webhook POSTs don't include bearer tokens.
815
882
  const twilioResponse = await this.handleTwilioWebhook(req, path);
@@ -949,199 +1016,141 @@ export class RuntimeHttpServer {
949
1016
  return null;
950
1017
  }
951
1018
 
952
- private handleMediaStreamUpgrade(
1019
+ /**
1020
+ * Shared path for gateway-proxied WebSocket upgrades: private network peers
1021
+ * and origins only, then a gateway service token, then `parse` builds the
1022
+ * socket data (or a 4xx Response) and the upgrade happens. The gateway owns
1023
+ * downstream client auth and dials these upstreams on the client's behalf.
1024
+ * Returns a Response on failure. On success Bun has consumed the request and
1025
+ * nothing is returned; the type is `Response` only to fit the router.
1026
+ */
1027
+ private upgradeRuntimeStream(
953
1028
  req: Request,
954
1029
  server: ReturnType<typeof Bun.serve>,
1030
+ label: string,
1031
+ parse: (query: URLSearchParams) => AllWebSocketData | Response,
955
1032
  ): Response {
956
1033
  if (!isPrivateNetworkPeer(server, req) || !isPrivateNetworkOrigin(req)) {
957
1034
  return httpError(
958
1035
  "FORBIDDEN",
959
- "Direct media-stream access disabled only private network peers allowed",
1036
+ `Direct ${label} access disabled: only private network peers allowed`,
960
1037
  403,
961
1038
  );
962
1039
  }
963
-
964
- // Verify the gateway service token before accepting the upgrade.
965
1040
  const tokenError = this.verifyGatewayServiceToken(req);
966
1041
  if (tokenError) {
967
1042
  return tokenError;
968
1043
  }
969
-
970
- const wsUrl = new URL(req.url);
971
- const callSessionId = wsUrl.searchParams.get("callSessionId");
972
- if (!callSessionId) {
973
- return new Response("Missing callSessionId", { status: 400 });
1044
+ const data = parse(new URL(req.url).searchParams);
1045
+ if (data instanceof Response) {
1046
+ return data;
974
1047
  }
975
- // Media-stream connections use a distinct wsType so the open/message/close
976
- // handlers route them to MediaStreamCallSession.
977
- const upgraded = server.upgrade(req, {
978
- data: {
979
- wsType: "media-stream",
980
- callSessionId,
981
- } satisfies MediaStreamWebSocketData,
982
- });
983
- if (!upgraded) {
1048
+ if (!server.upgrade(req, { data })) {
984
1049
  return new Response("WebSocket upgrade failed", { status: 500 });
985
1050
  }
986
- // Bun's WebSocket upgrade consumes the request — no Response is sent.
987
1051
  return undefined!;
988
1052
  }
989
1053
 
990
- /**
991
- * Handle WebSocket upgrade for `/v1/stt/stream`.
992
- *
993
- * Private-network restrictions apply (same as media-stream) so the
994
- * runtime remains unreachable from the public internet. The gateway
995
- * authenticates the downstream client and proxies the upgrade with a
996
- * short-lived gateway service token.
997
- */
998
- private handleSttStreamUpgrade(
1054
+ private handleMediaStreamUpgrade(
999
1055
  req: Request,
1000
1056
  server: ReturnType<typeof Bun.serve>,
1001
1057
  ): Response {
1002
- if (!isPrivateNetworkPeer(server, req) || !isPrivateNetworkOrigin(req)) {
1003
- return httpError(
1004
- "FORBIDDEN",
1005
- "Direct STT stream access disabled only private network peers allowed",
1006
- 403,
1007
- );
1008
- }
1009
-
1010
- // Verify the gateway service token before accepting the upgrade.
1011
- const tokenError = this.verifyGatewayServiceToken(req);
1012
- if (tokenError) {
1013
- return tokenError;
1014
- }
1015
-
1016
- const wsUrl = new URL(req.url);
1017
- // provider is optional compatibility metadata — the runtime resolves
1018
- // the streaming transcriber from config (`services.stt.provider`).
1019
- const provider = wsUrl.searchParams.get("provider") ?? undefined;
1020
- const mimeType = wsUrl.searchParams.get("mimeType");
1021
- if (!mimeType) {
1022
- return new Response("Missing required query parameter: mimeType", {
1023
- status: 400,
1024
- });
1025
- }
1026
-
1027
- const sampleRateRaw = wsUrl.searchParams.get("sampleRate");
1028
- const sampleRate = sampleRateRaw ? parseInt(sampleRateRaw, 10) : undefined;
1058
+ return this.upgradeRuntimeStream(req, server, "media-stream", (query) => {
1059
+ const callSessionId = query.get("callSessionId");
1060
+ if (!callSessionId) {
1061
+ return new Response("Missing callSessionId", { status: 400 });
1062
+ }
1063
+ // A distinct wsType routes these to MediaStreamCallSession.
1064
+ return {
1065
+ wsType: "media-stream",
1066
+ callSessionId,
1067
+ } satisfies MediaStreamWebSocketData;
1068
+ });
1069
+ }
1029
1070
 
1030
- const sessionId = crypto.randomUUID();
1031
- const upgraded = server.upgrade(req, {
1032
- data: {
1071
+ /** Handle WebSocket upgrade for `/v1/stt/stream`. */
1072
+ private handleSttStreamUpgrade(
1073
+ req: Request,
1074
+ server: ReturnType<typeof Bun.serve>,
1075
+ ): Response {
1076
+ return this.upgradeRuntimeStream(req, server, "STT stream", (query) => {
1077
+ // provider is optional compatibility metadata; the runtime resolves
1078
+ // the streaming transcriber from config (`services.stt.provider`).
1079
+ const provider = query.get("provider") ?? undefined;
1080
+ const mimeType = query.get("mimeType");
1081
+ if (!mimeType) {
1082
+ return new Response("Missing required query parameter: mimeType", {
1083
+ status: 400,
1084
+ });
1085
+ }
1086
+ const sampleRateRaw = query.get("sampleRate");
1087
+ return {
1033
1088
  wsType: "stt-stream",
1034
1089
  provider,
1035
1090
  mimeType,
1036
- sampleRate,
1037
- sessionId,
1038
- } satisfies SttStreamWebSocketData,
1091
+ sampleRate: sampleRateRaw ? parseInt(sampleRateRaw, 10) : undefined,
1092
+ sessionId: crypto.randomUUID(),
1093
+ } satisfies SttStreamWebSocketData;
1039
1094
  });
1040
- if (!upgraded) {
1041
- return new Response("WebSocket upgrade failed", { status: 500 });
1042
- }
1043
- // Bun's WebSocket upgrade consumes the request — no Response is sent.
1044
- return undefined!;
1045
1095
  }
1046
1096
 
1047
- /**
1048
- * Handle WebSocket upgrade for `/v1/live-voice`.
1049
- *
1050
- * The gateway owns downstream client auth and forwards this upstream with
1051
- * a short-lived gateway service token. The runtime accepts only private
1052
- * network peers/origins so the shell is not publicly reachable.
1053
- */
1097
+ /** Handle WebSocket upgrade for `/v1/live-voice`. */
1054
1098
  private handleLiveVoiceUpgrade(
1055
1099
  req: Request,
1056
1100
  server: ReturnType<typeof Bun.serve>,
1057
1101
  ): Response {
1058
- if (!isPrivateNetworkPeer(server, req) || !isPrivateNetworkOrigin(req)) {
1059
- return httpError(
1060
- "FORBIDDEN",
1061
- "Direct live voice access disabled — only private network peers allowed",
1062
- 403,
1063
- );
1064
- }
1065
-
1066
- const tokenError = this.verifyGatewayServiceToken(req);
1067
- if (tokenError) {
1068
- return tokenError;
1069
- }
1070
-
1071
- const guardianPrincipalId =
1072
- new URL(req.url).searchParams.get("guardianPrincipalId")?.trim() ||
1073
- undefined;
1074
-
1075
- const upgraded = server.upgrade(req, {
1076
- data: {
1102
+ return this.upgradeRuntimeStream(req, server, "live voice", (query) => {
1103
+ const guardianPrincipalId = query.get("guardianPrincipalId")?.trim();
1104
+ return {
1077
1105
  wsType: "live-voice",
1078
1106
  ...(guardianPrincipalId ? { guardianPrincipalId } : {}),
1079
- } satisfies LiveVoiceWebSocketData,
1107
+ } satisfies LiveVoiceWebSocketData;
1080
1108
  });
1081
- if (!upgraded) {
1082
- return new Response("WebSocket upgrade failed", { status: 500 });
1083
- }
1084
- return undefined!;
1085
1109
  }
1086
1110
 
1087
- /**
1088
- * Handle WebSocket upgrade for `/v1/watch/stream`.
1089
- *
1090
- * Gated exactly as `/v1/stt/stream` is: private network peers and origins
1091
- * only, then a gateway service token. The gateway owns downstream client
1092
- * auth and dials this upstream on the client's behalf.
1093
- */
1111
+ /** Handle WebSocket upgrade for `/v1/watch/stream`. */
1094
1112
  private handleWatchStreamUpgrade(
1095
1113
  req: Request,
1096
1114
  server: ReturnType<typeof Bun.serve>,
1097
1115
  ): Response {
1098
- if (!isPrivateNetworkPeer(server, req) || !isPrivateNetworkOrigin(req)) {
1099
- return httpError(
1100
- "FORBIDDEN",
1101
- "Direct watch stream access disabled: only private network peers allowed",
1102
- 403,
1103
- );
1104
- }
1105
-
1106
- const tokenError = this.verifyGatewayServiceToken(req);
1107
- if (tokenError) {
1108
- return tokenError;
1109
- }
1110
-
1111
- const wsUrl = new URL(req.url);
1112
- const mimeType = wsUrl.searchParams.get("mimeType");
1113
- if (!mimeType) {
1114
- return new Response("Missing required query parameter: mimeType", {
1115
- status: 400,
1116
- });
1117
- }
1118
-
1119
- const sampleRateRaw = wsUrl.searchParams.get("sampleRate");
1120
- const sampleRate = sampleRateRaw ? parseInt(sampleRateRaw, 10) : undefined;
1121
- const conversationId =
1122
- wsUrl.searchParams.get("conversationId")?.trim() || undefined;
1123
- const clientId = wsUrl.searchParams.get("clientId")?.trim() || undefined;
1124
- const parsedTarget = parseWatchCaptureTarget(wsUrl.searchParams);
1125
- if ("error" in parsedTarget) {
1126
- return new Response(parsedTarget.error, { status: 400 });
1127
- }
1128
-
1129
- const upgraded = server.upgrade(req, {
1130
- data: {
1116
+ return this.upgradeRuntimeStream(req, server, "watch stream", (query) => {
1117
+ const mimeType = query.get("mimeType");
1118
+ if (!mimeType) {
1119
+ return new Response("Missing required query parameter: mimeType", {
1120
+ status: 400,
1121
+ });
1122
+ }
1123
+ const sampleRateRaw = query.get("sampleRate");
1124
+ const parsedTarget = parseWatchCaptureTarget(query);
1125
+ if ("error" in parsedTarget) {
1126
+ return new Response(parsedTarget.error, { status: 400 });
1127
+ }
1128
+ return {
1131
1129
  wsType: "watch-stream",
1132
1130
  mimeType,
1133
- sampleRate,
1134
- conversationId,
1135
- clientId,
1131
+ sampleRate: sampleRateRaw ? parseInt(sampleRateRaw, 10) : undefined,
1132
+ conversationId: query.get("conversationId")?.trim() || undefined,
1133
+ clientId: query.get("clientId")?.trim() || undefined,
1136
1134
  captureTarget: parsedTarget.captureTarget,
1137
1135
  sessionId: crypto.randomUUID(),
1138
- } satisfies WatchStreamWebSocketData,
1136
+ } satisfies WatchStreamWebSocketData;
1139
1137
  });
1140
- if (!upgraded) {
1141
- return new Response("WebSocket upgrade failed", { status: 500 });
1142
- }
1143
- // Bun's WebSocket upgrade consumes the request, so no Response is sent.
1144
- return undefined!;
1138
+ }
1139
+
1140
+ /**
1141
+ * Handle WebSocket upgrade for `/v1/desktop/stream`. The feature gate runs
1142
+ * in the open handler, as close code 4008.
1143
+ */
1144
+ private handleDesktopStreamUpgrade(
1145
+ req: Request,
1146
+ server: ReturnType<typeof Bun.serve>,
1147
+ ): Response {
1148
+ return this.upgradeRuntimeStream(
1149
+ req,
1150
+ server,
1151
+ "desktop stream",
1152
+ () => ({ wsType: "desktop-stream" }) satisfies DesktopStreamWebSocketData,
1153
+ );
1145
1154
  }
1146
1155
 
1147
1156
  private async handleTwilioWebhook(
@@ -11,7 +11,8 @@
11
11
  * silently auto-installs allowlisted adapter packages via a sandboxed `bun`
12
12
  * global install before failing with the install hint. `execFile` is stubbed
13
13
  * via the shared `installExecFileStub` helper so tests can script
14
- * `bun add --global` outcomes.
14
+ * `bun add --global` outcomes. It also threads the optional `model` through to
15
+ * the session manager and relays the warning a refused model comes back with.
15
16
  */
16
17
 
17
18
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
@@ -43,6 +44,10 @@ interface FakeSessionState {
43
44
  completedAt?: number;
44
45
  error?: string;
45
46
  stopReason?: string;
47
+ model?: string;
48
+ availableModels?: Array<{ value: string; label: string }>;
49
+ modelRevisionEpoch?: string;
50
+ modelRevision?: number;
46
51
  latestUsage?: {
47
52
  usedTokens: number;
48
53
  contextSize: number;
@@ -55,10 +60,27 @@ interface FakeSessionState {
55
60
 
56
61
  let fakeInMemorySessions: FakeSessionState[] = [];
57
62
 
58
- const spawnMock = mock(async () => ({
63
+ interface SpawnResult {
64
+ acpSessionId: string;
65
+ protocolSessionId: string;
66
+ requestedModel?: string;
67
+ effectiveModel?: string;
68
+ modelWarning?: string;
69
+ }
70
+
71
+ const DEFAULT_SPAWN_RESULT: SpawnResult = {
59
72
  acpSessionId: "acp-route-session",
60
73
  protocolSessionId: "proto-route-session",
61
- }));
74
+ effectiveModel: "opus",
75
+ };
76
+ let spawnResult: SpawnResult = DEFAULT_SPAWN_RESULT;
77
+ const spawnMock = mock(async (...args: unknown[]) => {
78
+ const options = args[6] as { model?: string } | undefined;
79
+ return {
80
+ ...spawnResult,
81
+ requestedModel: options?.model?.trim() || undefined,
82
+ };
83
+ });
62
84
 
63
85
  const defaultSteerOrResumeImpl = async (
64
86
  _id: string,
@@ -182,6 +204,10 @@ interface ResponseShape {
182
204
  outputTokens?: number;
183
205
  eventLog?: unknown[];
184
206
  authErrorCode?: string;
207
+ model?: string;
208
+ availableModels?: Array<{ value: string; label: string }>;
209
+ modelRevisionEpoch?: string;
210
+ modelRevision?: number;
185
211
  }>;
186
212
  }
187
213
 
@@ -190,6 +216,7 @@ beforeEach(() => {
190
216
  clearHistory();
191
217
  resetExecFileStub();
192
218
  spawnMock.mockClear();
219
+ spawnResult = DEFAULT_SPAWN_RESULT;
193
220
  steerOrResumeMock.mockClear();
194
221
  steerOrResumeImpl = defaultSteerOrResumeImpl;
195
222
  _resetAdapterInstallCacheForTests();
@@ -328,6 +355,60 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
328
355
  expect(s!.outputTokens).toBeUndefined();
329
356
  });
330
357
 
358
+ test("carries the model for both layers and the picker for live ones only", async () => {
359
+ fakeInMemorySessions = [
360
+ {
361
+ id: "live-model",
362
+ agentId: "agent-live",
363
+ acpSessionId: "proto-live",
364
+ parentConversationId: "conv-model",
365
+ status: "running",
366
+ startedAt: 9000,
367
+ model: "opus",
368
+ modelRevisionEpoch: "01900000-0000-7000-8000-000000000001",
369
+ modelRevision: 17,
370
+ availableModels: [
371
+ { value: "sonnet", label: "Sonnet" },
372
+ { value: "opus", label: "Opus" },
373
+ ],
374
+ },
375
+ ];
376
+ insertHistoryRow({
377
+ id: "hist-model",
378
+ agentId: "agent-hist",
379
+ acpSessionId: "proto-hist",
380
+ parentConversationId: "conv-model",
381
+ startedAt: 1000,
382
+ status: "completed",
383
+ });
384
+
385
+ const handler = getSessionsHandler();
386
+ const body = (await handler({})) as ResponseShape;
387
+ const live = body.sessions.find((s) => s.id === "live-model");
388
+ const hist = body.sessions.find((s) => s.id === "hist-model");
389
+ expect(live).toMatchObject({
390
+ model: "opus",
391
+ modelRevisionEpoch: "01900000-0000-7000-8000-000000000001",
392
+ modelRevision: 17,
393
+ availableModels: [
394
+ { value: "sonnet", label: "Sonnet" },
395
+ { value: "opus", label: "Opus" },
396
+ ],
397
+ });
398
+ // A finished run has no live process to ask, so it reports neither.
399
+ expect(hist?.model).toBeUndefined();
400
+ expect(hist?.availableModels).toBeUndefined();
401
+ });
402
+
403
+ test("omits the model for history rows written without one", async () => {
404
+ insertHistoryRow({ id: "hist-no-model", status: "completed" });
405
+
406
+ const handler = getSessionsHandler();
407
+ const body = (await handler({})) as ResponseShape;
408
+ const s = body.sessions.find((row) => row.id === "hist-no-model");
409
+ expect(s?.model).toBeUndefined();
410
+ });
411
+
331
412
  test("dedupes by id with in-memory winning on collision", async () => {
332
413
  // Same id in both layers — in-memory entry should win and its eventLog
333
414
  // comes from the live ring buffer (empty here), not the stale history row.
@@ -579,6 +660,8 @@ describe("POST /v1/acp/spawn: sandboxed bun auto-install on missing binary", ()
579
660
  acpSessionId: "acp-route-session",
580
661
  protocolSessionId: "proto-route-session",
581
662
  agent: "claude",
663
+ requestedModel: null,
664
+ effectiveModel: "opus",
582
665
  });
583
666
  expect(spawnMock).toHaveBeenCalledTimes(1);
584
667
  // The real adapter binary is spawned, not a `bun x` wrapper.
@@ -593,7 +676,7 @@ describe("POST /v1/acp/spawn: sandboxed bun auto-install on missing binary", ()
593
676
  expect(args).toEqual([
594
677
  "add",
595
678
  "--global",
596
- "@agentclientprotocol/claude-agent-acp",
679
+ "@agentclientprotocol/claude-agent-acp@0.75.1",
597
680
  ]);
598
681
  });
599
682
 
@@ -680,6 +763,78 @@ describe("POST /v1/acp/spawn: sandboxed bun auto-install on missing binary", ()
680
763
  });
681
764
  });
682
765
 
766
+ describe("POST /v1/acp/spawn: model selection", () => {
767
+ test("threads the requested model to the session manager", async () => {
768
+ const handler = getSpawnHandler();
769
+ const body = (await handler({
770
+ body: { ...SPAWN_BODY, model: "opus" },
771
+ })) as Record<string, unknown>;
772
+
773
+ expect(confirmationRequests).toHaveLength(1);
774
+ expect(confirmationRequests[0]?.input).toEqual({
775
+ agent: "claude",
776
+ task: "do something",
777
+ cwd: process.cwd(),
778
+ model: "opus",
779
+ });
780
+ expect(spawnMock).toHaveBeenCalledTimes(1);
781
+ expect((spawnMock.mock.calls[0] as unknown[])[6]).toEqual({
782
+ model: "opus",
783
+ });
784
+
785
+ expect(body.requestedModel).toBe("opus");
786
+ expect(body.effectiveModel).toBe("opus");
787
+ });
788
+
789
+ test("reports the manager-normalized requested model", async () => {
790
+ const handler = getSpawnHandler();
791
+ const body = (await handler({
792
+ body: { ...SPAWN_BODY, model: " opus " },
793
+ })) as Record<string, unknown>;
794
+
795
+ expect((spawnMock.mock.calls[0] as unknown[])[6]).toEqual({
796
+ model: " opus ",
797
+ });
798
+ expect(body.requestedModel).toBe("opus");
799
+ });
800
+
801
+ test("a spawn that names no model asks the manager for none", async () => {
802
+ const handler = getSpawnHandler();
803
+ await handler({ body: SPAWN_BODY });
804
+
805
+ const options = (spawnMock.mock.calls[0] as unknown[])[6] as {
806
+ model?: string;
807
+ };
808
+ const approvalInput = confirmationRequests[0]?.input as Record<
809
+ string,
810
+ unknown
811
+ >;
812
+ expect(approvalInput).not.toHaveProperty("model");
813
+ expect(options.model).toBeUndefined();
814
+ });
815
+
816
+ test("a refused model is a warning on an otherwise successful spawn", async () => {
817
+ spawnResult = {
818
+ ...DEFAULT_SPAWN_RESULT,
819
+ modelWarning: "Invalid value for config option model: nope",
820
+ };
821
+
822
+ const handler = getSpawnHandler();
823
+ const body = (await handler({
824
+ body: { ...SPAWN_BODY, model: "nope" },
825
+ })) as Record<string, unknown>;
826
+
827
+ expect(body).toEqual({
828
+ acpSessionId: "acp-route-session",
829
+ protocolSessionId: "proto-route-session",
830
+ agent: "claude",
831
+ requestedModel: "nope",
832
+ effectiveModel: "opus",
833
+ modelWarning: "Invalid value for config option model: nope",
834
+ });
835
+ });
836
+ });
837
+
683
838
  // ---------------------------------------------------------------------------
684
839
  // POST /v1/acp/:id/steer: transparent resume of sessions not in memory
685
840
  // ---------------------------------------------------------------------------