@vellumai/assistant 0.12.1 → 0.12.2-staging.2

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 (577) 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/node_modules/@vellumai/slack-text/src/index.ts +13 -8
  6. package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
  7. package/openapi.yaml +360 -51
  8. package/package.json +3 -1
  9. package/scripts/smoke-desktop-window-theme.ts +186 -0
  10. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  11. package/src/__tests__/anthropic-provider.test.ts +0 -63
  12. package/src/__tests__/app-compiler.test.ts +6 -0
  13. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  14. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  15. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  16. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  17. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  18. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  19. package/src/__tests__/computer-use-tools.test.ts +19 -7
  20. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  21. package/src/__tests__/config-schema.test.ts +28 -29
  22. package/src/__tests__/config-watcher.test.ts +23 -8
  23. package/src/__tests__/connection-model-compat.test.ts +2 -2
  24. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  25. package/src/__tests__/conversation-pairing.test.ts +26 -0
  26. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  27. package/src/__tests__/conversation-queue.test.ts +48 -207
  28. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  29. package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
  30. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  31. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  32. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  35. package/src/__tests__/conversation-title-service.test.ts +14 -10
  36. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  37. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  38. package/src/__tests__/credential-record-retire.test.ts +171 -1
  39. package/src/__tests__/credential-routes.test.ts +250 -3
  40. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  41. package/src/__tests__/cu-unified-flow.test.ts +14 -4
  42. package/src/__tests__/document-sync-tags.test.ts +25 -0
  43. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  44. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  45. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  46. package/src/__tests__/git-porcelain.test.ts +47 -0
  47. package/src/__tests__/heartbeat-service.test.ts +56 -40
  48. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  49. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  50. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  51. package/src/__tests__/host-cu-proxy.test.ts +596 -0
  52. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  53. package/src/__tests__/host-file-proxy.test.ts +11 -23
  54. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  55. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  56. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  57. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  58. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  59. package/src/__tests__/mcp-health-check.test.ts +8 -11
  60. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  61. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  62. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  63. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  64. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  65. package/src/__tests__/model-intents.test.ts +3 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  67. package/src/__tests__/oauth-commands-routes.test.ts +168 -0
  68. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  69. package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
  70. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  71. package/src/__tests__/openai-provider.test.ts +1 -0
  72. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  73. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  74. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  75. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  76. package/src/__tests__/qdrant-manager.test.ts +132 -0
  77. package/src/__tests__/resolve-app.test.ts +135 -0
  78. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  79. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  80. package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
  81. package/src/__tests__/service-account-token.test.ts +247 -0
  82. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  83. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +9 -4
  85. package/src/__tests__/source-fingerprint.test.ts +184 -0
  86. package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
  87. package/src/__tests__/subagent-tools.test.ts +3 -2
  88. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  89. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  90. package/src/__tests__/ui-channel-variants.test.ts +1 -56
  91. package/src/__tests__/unicode.test.ts +13 -0
  92. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  93. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  94. package/src/__tests__/workspace-git-service.test.ts +26 -0
  95. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
  97. package/src/acp/__tests__/acp-credentials.test.ts +13 -0
  98. package/src/acp/__tests__/agent-process.test.ts +258 -4
  99. package/src/acp/__tests__/auth-required.test.ts +96 -0
  100. package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
  101. package/src/acp/__tests__/client-handler.test.ts +90 -2
  102. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  103. package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
  104. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  105. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  106. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  107. package/src/acp/__tests__/session-manager.test.ts +895 -4
  108. package/src/acp/acp-claude-oauth.ts +328 -14
  109. package/src/acp/acp-credentials.ts +19 -0
  110. package/src/acp/agent-process.test.ts +4 -1
  111. package/src/acp/agent-process.ts +96 -10
  112. package/src/acp/auth-required.ts +50 -1
  113. package/src/acp/auto-install.test.ts +9 -7
  114. package/src/acp/claude-token-refresh.ts +150 -0
  115. package/src/acp/client-handler.ts +16 -3
  116. package/src/acp/model-config.test.ts +166 -0
  117. package/src/acp/model-config.ts +138 -0
  118. package/src/acp/prepare-agent-env.ts +21 -6
  119. package/src/acp/resolve-agent.test.ts +220 -6
  120. package/src/acp/resolve-agent.ts +88 -23
  121. package/src/acp/session-manager.test.ts +196 -3
  122. package/src/acp/session-manager.ts +441 -18
  123. package/src/acp/types.ts +50 -5
  124. package/src/agent/loop.ts +1 -0
  125. package/src/api/events/acp-session-model-update.test.ts +88 -0
  126. package/src/api/events/acp-session-model-update.ts +46 -0
  127. package/src/api/events/app-files-changed.ts +4 -3
  128. package/src/api/events/notification-intent.ts +2 -0
  129. package/src/api/events/user-message-echo.ts +7 -1
  130. package/src/api/index.ts +6 -0
  131. package/src/api/responses/conversation-message.ts +5 -0
  132. package/src/apps/app-store.ts +1 -1
  133. package/src/apps/resolve-app.ts +81 -0
  134. package/src/apps/source-fingerprint.ts +266 -0
  135. package/src/avatar/identity-avatar.ts +7 -2
  136. package/src/bundler/app-compiler.ts +15 -0
  137. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  138. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  139. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  140. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  141. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  142. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  143. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  144. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  145. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  146. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  147. package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
  148. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  149. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  150. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  151. package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
  152. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  153. package/src/calls/barge-in-guard.ts +118 -0
  154. package/src/calls/call-constants.ts +24 -4
  155. package/src/calls/call-controller.ts +499 -199
  156. package/src/calls/call-transport.ts +16 -0
  157. package/src/calls/media-stream-output.ts +32 -0
  158. package/src/calls/media-stream-server.ts +82 -30
  159. package/src/calls/media-stream-stt-session.ts +15 -0
  160. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  161. package/src/calls/resolve-call-tts-provider.ts +33 -2
  162. package/src/calls/tts-call-strategy.ts +9 -5
  163. package/src/calls/types.ts +2 -0
  164. package/src/calls/voice-control-protocol.ts +155 -21
  165. package/src/calls/voice-leg-coordinator.ts +189 -0
  166. package/src/calls/voice-progress-cadence.ts +423 -0
  167. package/src/calls/voice-session-bridge.ts +92 -72
  168. package/src/calls/voice-triage-escalate.ts +178 -59
  169. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  170. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  171. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  172. package/src/cli/commands/apps.help.ts +67 -13
  173. package/src/cli/commands/apps.ts +183 -0
  174. package/src/cli/commands/bash.help.ts +8 -13
  175. package/src/cli/commands/bash.ts +7 -25
  176. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  177. package/src/cli/commands/channels/index.help.ts +70 -2
  178. package/src/cli/commands/channels/index.ts +16 -3
  179. package/src/cli/commands/channels/send.ts +128 -0
  180. package/src/cli/commands/credentials.help.ts +6 -2
  181. package/src/cli/commands/credentials.ts +2 -2
  182. package/src/cli/commands/mcp.help.ts +8 -8
  183. package/src/cli/commands/mcp.ts +1 -1
  184. package/src/cli/commands/oauth/status.ts +5 -0
  185. package/src/cli/commands/roadmap.help.ts +5 -1
  186. package/src/cli/commands/roadmap.ts +1 -1
  187. package/src/cli/lib/bundled-marketplace.json +103 -90
  188. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  189. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  190. package/src/config/__tests__/platform-identity.test.ts +255 -0
  191. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  192. package/src/config/acp-agent-write.ts +43 -0
  193. package/src/config/acp-defaults.test.ts +13 -3
  194. package/src/config/acp-defaults.ts +23 -6
  195. package/src/config/acp-schema.test.ts +81 -0
  196. package/src/config/acp-schema.ts +30 -2
  197. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  198. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  199. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  200. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  201. package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
  202. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  203. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  204. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  205. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  206. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  207. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  208. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  209. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  210. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  211. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  212. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  213. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  214. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  215. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  216. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  217. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  218. package/src/config/default-profile-catalog.ts +1 -1
  219. package/src/config/env.ts +2 -2
  220. package/src/config/feature-flag-registry.json +33 -13
  221. package/src/config/loader.ts +2 -0
  222. package/src/config/memory-v3-gate.test.ts +31 -0
  223. package/src/config/memory-v3-gate.ts +20 -16
  224. package/src/config/platform-identity.ts +201 -0
  225. package/src/config/platform-rehydration.ts +11 -27
  226. package/src/config/schema.ts +2 -2
  227. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  228. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  229. package/src/config/schemas/calls.ts +1 -28
  230. package/src/config/schemas/live-voice.ts +1 -173
  231. package/src/config/schemas/mcp.ts +1 -1
  232. package/src/config/schemas/memory-retrospective.ts +10 -0
  233. package/src/config/schemas/services.ts +18 -0
  234. package/src/config/schemas/skills.ts +0 -14
  235. package/src/config/schemas/voice.ts +150 -0
  236. package/src/credential-execution/process-manager.test.ts +18 -19
  237. package/src/credential-health/credential-health-service.ts +11 -17
  238. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  239. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  240. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  241. package/src/daemon/app-change-notify.ts +45 -0
  242. package/src/daemon/config-watcher.ts +13 -194
  243. package/src/daemon/conversation-error.ts +3 -2
  244. package/src/daemon/conversation-media-retry.ts +2 -1
  245. package/src/daemon/conversation-process.ts +35 -64
  246. package/src/daemon/conversation-runtime-assembly.ts +20 -7
  247. package/src/daemon/conversation-skill-tools.ts +13 -31
  248. package/src/daemon/conversation-store.ts +1 -1
  249. package/src/daemon/conversation-surfaces.ts +21 -10
  250. package/src/daemon/conversation-tool-setup.ts +45 -59
  251. package/src/daemon/conversation.ts +0 -3
  252. package/src/daemon/dictation-profile-store.ts +2 -1
  253. package/src/daemon/handlers/shared.ts +2 -1
  254. package/src/daemon/handlers/skills.ts +6 -3
  255. package/src/daemon/host-bash-proxy.ts +8 -12
  256. package/src/daemon/host-browser-proxy.ts +15 -1
  257. package/src/daemon/host-cu-proxy.ts +224 -19
  258. package/src/daemon/host-file-proxy.ts +8 -10
  259. package/src/daemon/host-proxy-preactivation.ts +2 -1
  260. package/src/daemon/host-transfer-proxy.ts +14 -18
  261. package/src/daemon/lifecycle.ts +0 -5
  262. package/src/daemon/mcp-reload-service.ts +5 -5
  263. package/src/daemon/message-types/sync.ts +1 -0
  264. package/src/daemon/orphan-reaper.ts +1 -1
  265. package/src/daemon/providers-setup.ts +1 -1
  266. package/src/daemon/reaction-history-render.ts +2 -1
  267. package/src/daemon/shutdown-handlers.ts +1 -9
  268. package/src/daemon/skill-memory-refresh.ts +64 -0
  269. package/src/daemon/tool-side-effects.ts +7 -18
  270. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  271. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  272. package/src/desktop/desktop-chrome-policy.ts +47 -0
  273. package/src/desktop/desktop-chrome-session.ts +37 -0
  274. package/src/desktop/desktop-dependencies.test.ts +102 -0
  275. package/src/desktop/desktop-dependencies.ts +308 -0
  276. package/src/desktop/desktop-feature.test.ts +18 -0
  277. package/src/desktop/desktop-feature.ts +19 -0
  278. package/src/desktop/desktop-panel-config.test.ts +135 -0
  279. package/src/desktop/desktop-panel-config.ts +149 -0
  280. package/src/desktop/desktop-session-manager.test.ts +730 -0
  281. package/src/desktop/desktop-session-manager.ts +850 -0
  282. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  283. package/src/desktop/desktop-stream-bridge.ts +214 -0
  284. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  285. package/src/desktop/desktop-wallpaper.ts +113 -0
  286. package/src/desktop/desktop-window-drag.ts +177 -0
  287. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  288. package/src/desktop/desktop-window-manager-config.ts +238 -0
  289. package/src/desktop/desktop-window-theme.ts +170 -0
  290. package/src/heartbeat/heartbeat-service.ts +21 -3
  291. package/src/home/__tests__/feed-writer.test.ts +22 -0
  292. package/src/home/feed-writer.ts +11 -2
  293. package/src/hooks/types.ts +33 -0
  294. package/src/i18n/AGENTS.md +22 -0
  295. package/src/i18n/__tests__/i18n.test.ts +108 -0
  296. package/src/i18n/index.ts +16 -0
  297. package/src/i18n/locales.ts +93 -0
  298. package/src/i18n/messages.ts +61 -0
  299. package/src/i18n/title.ts +25 -0
  300. package/src/inbound/platform-callback-registration.ts +4 -5
  301. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
  302. package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
  303. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  304. package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
  305. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  306. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  307. package/src/live-voice/__tests__/protocol.test.ts +39 -0
  308. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  309. package/src/live-voice/__tests__/session-controls.test.ts +79 -0
  310. package/src/live-voice/live-voice-photo.ts +25 -6
  311. package/src/live-voice/live-voice-session.ts +297 -699
  312. package/src/live-voice/protocol.ts +60 -2
  313. package/src/live-voice/session-controls.ts +111 -0
  314. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  315. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  316. package/src/mcp/effective-config.ts +3 -3
  317. package/src/mcp/mcp-header-store.ts +29 -45
  318. package/src/mcp/spec-schema.ts +111 -0
  319. package/src/mcp/startup.ts +8 -4
  320. package/src/mcp/workspace-mcp-config.ts +188 -0
  321. package/src/messaging/provider.ts +10 -3
  322. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  323. package/src/messaging/providers/callback-routing.ts +18 -0
  324. package/src/messaging/providers/channel-transport.ts +56 -0
  325. package/src/messaging/providers/discord/transport.ts +17 -0
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  327. package/src/messaging/providers/slack/adapter.ts +7 -44
  328. package/src/messaging/providers/slack/transport.ts +16 -0
  329. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  330. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  331. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  332. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  333. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  334. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  335. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  336. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  337. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  338. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  339. package/src/notifications/adapters/macos.ts +3 -0
  340. package/src/notifications/assistant-reply-producer.ts +8 -3
  341. package/src/notifications/conversation-pairing.ts +36 -16
  342. package/src/notifications/delivered-post-record.ts +11 -1
  343. package/src/notifications/guardian-feed-projection.ts +125 -5
  344. package/src/notifications/home-feed-side-effect.ts +55 -7
  345. package/src/notifications/schedule-result-producer.ts +66 -22
  346. package/src/oauth/AGENTS.md +2 -0
  347. package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
  348. package/src/oauth/byo-connection.test.ts +105 -0
  349. package/src/oauth/byo-connection.ts +36 -6
  350. package/src/oauth/connection-resolver.test.ts +100 -0
  351. package/src/oauth/connection-resolver.ts +120 -3
  352. package/src/oauth/provider-serializer.ts +9 -0
  353. package/src/oauth/scope-utils.ts +24 -0
  354. package/src/oauth/seed-providers.ts +314 -27
  355. package/src/outbound-proxy/certs.ts +3 -5
  356. package/src/permissions/checker.test.ts +11 -6
  357. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  358. package/src/persistence/conversation-crud.ts +71 -63
  359. package/src/persistence/conversation-lineage.ts +12 -4
  360. package/src/persistence/conversation-queries.ts +2 -2
  361. package/src/persistence/conversation-title-placeholders.ts +23 -0
  362. package/src/persistence/conversation-title-service.ts +9 -26
  363. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  364. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  365. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  366. package/src/persistence/message-cursor.ts +78 -0
  367. package/src/persistence/message-reads.ts +30 -0
  368. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  369. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  370. package/src/persistence/schema/memory-core.ts +5 -0
  371. package/src/platform/client.test.ts +4 -2
  372. package/src/platform/client.ts +2 -10
  373. package/src/plugin-api/constants.ts +2 -0
  374. package/src/plugin-api/index.ts +1 -0
  375. package/src/plugin-api/types.ts +2 -0
  376. package/src/plugins/defaults/index.ts +2 -0
  377. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  378. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  379. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  380. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  381. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  382. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  383. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  384. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  385. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  386. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  387. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  388. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  389. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  390. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  391. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  392. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  393. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  394. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  395. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  396. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  397. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  398. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  399. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  400. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  401. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  402. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  403. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  404. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  405. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  406. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  407. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  408. package/src/plugins/defaults/memory/startup.ts +28 -2
  409. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  410. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  411. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  412. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  413. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  414. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  415. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  416. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  417. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  418. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  419. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  420. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  421. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  422. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  423. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  424. package/src/plugins/defaults/memory/worker.ts +6 -0
  425. package/src/plugins/mcp-servers.ts +15 -75
  426. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  427. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  428. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  429. package/src/providers/anthropic/client.ts +0 -47
  430. package/src/providers/gemini/client.ts +9 -23
  431. package/src/providers/gemini-thought-signature.test.ts +63 -0
  432. package/src/providers/gemini-thought-signature.ts +44 -0
  433. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  434. package/src/providers/inference/auth.ts +2 -3
  435. package/src/providers/inference/connection-availability.ts +27 -8
  436. package/src/providers/inference/endpoint-probe.ts +10 -1
  437. package/src/providers/inference/resolve-auth.ts +22 -4
  438. package/src/providers/inference/service-account-token.ts +248 -0
  439. package/src/providers/model-catalog.ts +34 -0
  440. package/src/providers/model-intents.ts +10 -5
  441. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  442. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  443. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  444. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  445. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  446. package/src/providers/openai/chat-completions-provider.ts +212 -79
  447. package/src/providers/openai/codex-models.ts +0 -4
  448. package/src/providers/openai/google-thought-signature.ts +320 -0
  449. package/src/providers/opencode/client.test.ts +73 -4
  450. package/src/providers/opencode/client.ts +60 -3
  451. package/src/providers/openrouter/client.ts +20 -0
  452. package/src/providers/outbound-request-sanitize.ts +96 -0
  453. package/src/providers/retry.ts +30 -19
  454. package/src/providers/unparseable-tool-args.ts +5 -1
  455. package/src/runtime/AGENTS.md +3 -1
  456. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  457. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  458. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  459. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  460. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  461. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  462. package/src/runtime/agent-wake.ts +2 -1
  463. package/src/runtime/assistant-event-hub.ts +21 -3
  464. package/src/runtime/auth/same-actor.ts +38 -3
  465. package/src/runtime/channel-send.ts +316 -0
  466. package/src/runtime/host-observe.ts +11 -1
  467. package/src/runtime/http-server.ts +160 -151
  468. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
  469. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  470. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
  471. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  472. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  473. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  474. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  475. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  476. package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
  477. package/src/runtime/routes/acp-routes.test.ts +15 -0
  478. package/src/runtime/routes/acp-routes.ts +72 -9
  479. package/src/runtime/routes/app-management-routes.ts +90 -2
  480. package/src/runtime/routes/app-routes.ts +1 -1
  481. package/src/runtime/routes/channel-send-routes.ts +85 -0
  482. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  483. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  484. package/src/runtime/routes/conversation-routes.ts +16 -3
  485. package/src/runtime/routes/credential-routes.ts +20 -14
  486. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  487. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  488. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  489. package/src/runtime/routes/events-routes.ts +4 -0
  490. package/src/runtime/routes/host-cu-routes.ts +6 -0
  491. package/src/runtime/routes/index.ts +4 -2
  492. package/src/runtime/routes/log-export-routes.ts +2 -0
  493. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  494. package/src/runtime/routes/migration-routes.ts +6 -20
  495. package/src/runtime/routes/oauth-commands-routes.ts +50 -0
  496. package/src/runtime/routes/oauth-providers.ts +7 -0
  497. package/src/runtime/routes/platform-routes.ts +18 -15
  498. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  499. package/src/runtime/routes/workspace-routes.ts +46 -63
  500. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  501. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  502. package/src/runtime/routes/workspace-utils.ts +17 -0
  503. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  504. package/src/runtime/services/conversation-serializer.ts +16 -3
  505. package/src/runtime/sync/resource-sync-events.ts +2 -1
  506. package/src/schedule/run-script.ts +2 -1
  507. package/src/schedule/schedule-store.ts +9 -2
  508. package/src/schedule/worker.ts +1 -1
  509. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  510. package/src/security/ces-http-record-backend.ts +120 -0
  511. package/src/security/ces-rpc-record-backend.ts +7 -0
  512. package/src/security/untrusted-content.ts +3 -1
  513. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  514. package/src/skills/workspace-skill-md-signature.ts +50 -0
  515. package/src/subagent/consult-context.ts +12 -25
  516. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  517. package/src/tools/acp/list-agents.test.ts +3 -1
  518. package/src/tools/acp/spawn.test.ts +146 -11
  519. package/src/tools/acp/spawn.ts +21 -3
  520. package/src/tools/acp/status.test.ts +94 -0
  521. package/src/tools/acp/status.ts +40 -3
  522. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  523. package/src/tools/browser/browser-execution.ts +1 -1
  524. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  525. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  526. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  527. package/src/tools/browser/network-recorder.ts +2 -1
  528. package/src/tools/capability-offer.test.ts +22 -2
  529. package/src/tools/capability-offer.ts +16 -1
  530. package/src/tools/computer-use/definitions.ts +121 -2
  531. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  532. package/src/tools/credentials/metadata-store.ts +100 -0
  533. package/src/tools/filesystem/search.ts +2 -1
  534. package/src/tools/host-filesystem/edit.test.ts +2 -1
  535. package/src/tools/host-filesystem/edit.ts +2 -2
  536. package/src/tools/host-filesystem/read.test.ts +2 -1
  537. package/src/tools/host-filesystem/read.ts +2 -2
  538. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  539. package/src/tools/host-filesystem/transfer.ts +2 -2
  540. package/src/tools/host-filesystem/write.test.ts +2 -1
  541. package/src/tools/host-filesystem/write.ts +2 -2
  542. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  543. package/src/tools/host-terminal/host-shell.ts +4 -20
  544. package/src/tools/network/web-search.ts +6 -1
  545. package/src/tools/policy-context.ts +6 -7
  546. package/src/tools/skills/scaffold-managed.ts +61 -3
  547. package/src/tools/subagent/spawn.ts +5 -8
  548. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  549. package/src/tools/terminal/sanitized-bash.ts +105 -0
  550. package/src/tools/ui-surface/channel-variants.ts +10 -59
  551. package/src/tools/watch/watch-retro-report.ts +0 -8
  552. package/src/types/host-capabilities.ts +5 -3
  553. package/src/util/__tests__/language-subtag.test.ts +23 -1
  554. package/src/util/ca-bundle.ts +13 -0
  555. package/src/util/language-subtag.ts +16 -0
  556. package/src/util/platform.ts +8 -0
  557. package/src/util/unicode.ts +15 -1
  558. package/src/watch/watch-retro.ts +6 -8
  559. package/src/watch/watch-timeline.ts +2 -2
  560. package/src/workflows/run-manager.ts +2 -1
  561. package/src/workspace/git-porcelain.ts +66 -0
  562. package/src/workspace/git-service.ts +6 -55
  563. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  564. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  565. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  566. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  567. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  568. package/src/workspace/migrations/registry.ts +6 -0
  569. package/src/__tests__/app-source-watcher.test.ts +0 -218
  570. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  571. package/src/__tests__/speaker-identification.test.ts +0 -56
  572. package/src/calls/speaker-identification.ts +0 -233
  573. package/src/daemon/app-source-watcher.ts +0 -218
  574. package/src/messaging/providers/whatsapp/client.ts +0 -29
  575. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  576. package/src/util/fs-watcher-error.ts +0 -36
  577. /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(
@@ -48,7 +48,7 @@ mock.module("../../../security/oauth2.js", () => ({
48
48
 
49
49
  const actualClaudeOauth = await import("../../../acp/acp-claude-oauth.js");
50
50
  const { CLAUDE_MANUAL_REDIRECT_URI, CLAUDE_OAUTH_CONFIG } = actualClaudeOauth;
51
- const storeAcpClaudeTokenMock = mock(async (_token: string) => {});
51
+ const storeAcpClaudeTokenMock = mock(async (_tokens: unknown) => {});
52
52
  // The connect-status route reads token presence; mock it so the route test
53
53
  // doesn't reach real secure storage.
54
54
  const hasAcpClaudeTokenMock = mock(async () => false);
@@ -228,9 +228,12 @@ describe("loopback capture", () => {
228
228
  const status = await waitForStatus(state, "connected");
229
229
  expect(status).toEqual({ status: "connected" });
230
230
 
231
- // Access token persisted via storeAcpClaudeToken.
232
231
  expect(storeAcpClaudeTokenMock).toHaveBeenCalledTimes(1);
233
- expect(storeAcpClaudeTokenMock).toHaveBeenCalledWith("sk-ant-oat-access");
232
+ expect(storeAcpClaudeTokenMock).toHaveBeenCalledWith({
233
+ accessToken: "sk-ant-oat-access",
234
+ refreshToken: "refresh-xyz",
235
+ expiresIn: 3600,
236
+ });
234
237
  });
235
238
 
236
239
  test("flips status to error when the exchange fails", async () => {
@@ -339,7 +342,11 @@ describe("acp_claude_auth_exchange", () => {
339
342
  expect(call[1]).toBe("auth-code-123");
340
343
  expect(call[2]).toBe(CLAUDE_MANUAL_REDIRECT_URI);
341
344
  expect(call[3]).toBeTruthy(); // PKCE verifier from the start call
342
- expect(storeAcpClaudeTokenMock).toHaveBeenCalledWith("sk-ant-oat-manual");
345
+ expect(storeAcpClaudeTokenMock).toHaveBeenCalledWith({
346
+ accessToken: "sk-ant-oat-manual",
347
+ refreshToken: "refresh-manual",
348
+ expiresIn: 3600,
349
+ });
343
350
 
344
351
  // The pending entry is consumed — a second exchange fails.
345
352
  await expect(
@@ -360,7 +367,11 @@ describe("acp_claude_auth_exchange", () => {
360
367
  string,
361
368
  ];
362
369
  expect(call[1]).toBe("raw-code-xyz");
363
- expect(storeAcpClaudeTokenMock).toHaveBeenCalledWith("sk-ant-oat-manual");
370
+ expect(storeAcpClaudeTokenMock).toHaveBeenCalledWith({
371
+ accessToken: "sk-ant-oat-manual",
372
+ refreshToken: "refresh-manual",
373
+ expiresIn: 3600,
374
+ });
364
375
  });
365
376
 
366
377
  test("malformed paste (no `#`, no state) is rejected", async () => {