@vellumai/assistant 0.12.3-staging.1 → 0.12.3

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 (560) hide show
  1. package/ARCHITECTURE.md +43 -29
  2. package/Dockerfile +63 -0
  3. package/docs/architecture/security.md +10 -5
  4. package/docs/architecture/turn-actor.md +2 -0
  5. package/docs/desktop-browser-cli.md +45 -4
  6. package/docs/live-voice-task-outcomes.md +100 -0
  7. package/docs/mode-sessions.md +183 -0
  8. package/docs/voice-input-diagnostics.md +58 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
  17. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
  18. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
  19. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  20. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  22. package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
  23. package/openapi.yaml +665 -14
  24. package/package.json +1 -1
  25. package/scripts/smoke-desktop-browser-cli.ts +10 -26
  26. package/scripts/smoke-desktop-window-theme.ts +2 -3
  27. package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
  28. package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
  29. package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
  30. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  31. package/src/__tests__/answered-question-persistence.test.ts +2 -0
  32. package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
  33. package/src/__tests__/approval-message-composer.test.ts +1 -118
  34. package/src/__tests__/approval-routes-http.test.ts +3 -0
  35. package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
  36. package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
  37. package/src/__tests__/assistant-event-targeting.test.ts +186 -0
  38. package/src/__tests__/assistant-stream-state.test.ts +29 -9
  39. package/src/__tests__/browser-fill-credential.test.ts +11 -22
  40. package/src/__tests__/btw-routes.test.ts +4 -1
  41. package/src/__tests__/build-persisted-content.test.ts +1 -1
  42. package/src/__tests__/call-conversation-messages.test.ts +5 -0
  43. package/src/__tests__/call-recovery.test.ts +7 -2
  44. package/src/__tests__/call-routes-http.test.ts +12 -0
  45. package/src/__tests__/call-store.test.ts +7 -2
  46. package/src/__tests__/channel-retry-sweep.test.ts +9 -0
  47. package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
  48. package/src/__tests__/checker.test.ts +11 -3
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
  50. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
  51. package/src/__tests__/computer-use-tools.test.ts +1 -233
  52. package/src/__tests__/config-model-image-provider.test.ts +39 -0
  53. package/src/__tests__/config-schema.test.ts +16 -0
  54. package/src/__tests__/config-watcher.test.ts +24 -0
  55. package/src/__tests__/context-search-agent-runner.test.ts +260 -1
  56. package/src/__tests__/context-search-fanout.test.ts +1 -1
  57. package/src/__tests__/context-search-types.test.ts +7 -7
  58. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
  59. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
  60. package/src/__tests__/conversation-agent-loop.test.ts +546 -16
  61. package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
  62. package/src/__tests__/conversation-evictor.test.ts +62 -37
  63. package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
  64. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
  65. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  66. package/src/__tests__/conversation-retry-route.test.ts +22 -0
  67. package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
  68. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
  69. package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
  70. package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
  73. package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
  74. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
  75. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
  76. package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
  77. package/src/__tests__/conversation-undo.test.ts +61 -0
  78. package/src/__tests__/credential-execution-client.test.ts +6 -6
  79. package/src/__tests__/credential-prompt-route.test.ts +35 -3
  80. package/src/__tests__/cu-unified-flow.test.ts +82 -1
  81. package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
  82. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
  83. package/src/__tests__/dm-backfill.test.ts +91 -1
  84. package/src/__tests__/document-create-route.test.ts +88 -0
  85. package/src/__tests__/document-sync-tags.test.ts +27 -0
  86. package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
  87. package/src/__tests__/events-tail-route.test.ts +25 -0
  88. package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
  89. package/src/__tests__/guardian-action-sweep.test.ts +16 -0
  90. package/src/__tests__/guardian-dispatch.test.ts +13 -0
  91. package/src/__tests__/headless-browser-interactions.test.ts +64 -20
  92. package/src/__tests__/heartbeat-service.test.ts +2 -59
  93. package/src/__tests__/helpers/mock-conversation.ts +21 -0
  94. package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
  95. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
  96. package/src/__tests__/host-cu-proxy.test.ts +46 -2
  97. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  98. package/src/__tests__/image-credentials.test.ts +49 -0
  99. package/src/__tests__/image-service-dispatcher.test.ts +45 -0
  100. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  101. package/src/__tests__/interrupt-on-send.test.ts +57 -43
  102. package/src/__tests__/interrupt-turn-note.test.ts +5 -6
  103. package/src/__tests__/llm-catalog-parity.test.ts +2 -2
  104. package/src/__tests__/media-generate-image.test.ts +49 -1
  105. package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
  106. package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
  107. package/src/__tests__/mode-session-history.test.ts +245 -0
  108. package/src/__tests__/mode-session-recovery.test.ts +222 -0
  109. package/src/__tests__/mode-session-wire.test.ts +141 -0
  110. package/src/__tests__/notification-guardian-path.test.ts +7 -0
  111. package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
  112. package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  115. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  116. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +5 -0
  118. package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
  119. package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
  120. package/src/__tests__/process-message-display-content.test.ts +56 -0
  121. package/src/__tests__/reaction-persistence.test.ts +5 -0
  122. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
  123. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
  124. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
  125. package/src/__tests__/secret-ingress-http.test.ts +2 -0
  126. package/src/__tests__/secure-keys.test.ts +43 -1
  127. package/src/__tests__/send-endpoint-busy.test.ts +163 -1
  128. package/src/__tests__/server-history-render.test.ts +91 -1
  129. package/src/__tests__/settings-routes.test.ts +50 -1
  130. package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
  132. package/src/__tests__/subagent-manager-notify.test.ts +1 -2
  133. package/src/__tests__/subagent-notify-parent.test.ts +146 -39
  134. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
  135. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
  136. package/src/__tests__/thread-backfill.test.ts +53 -0
  137. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  138. package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
  139. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  140. package/src/__tests__/twilio-routes.test.ts +1 -0
  141. package/src/__tests__/web-search-backend-failure.test.ts +1 -1
  142. package/src/activation/progress-store.test.ts +12 -7
  143. package/src/agent/loop.ts +99 -41
  144. package/src/api/events/assistant-turn-start.ts +3 -0
  145. package/src/api/events/generation-handoff.ts +2 -0
  146. package/src/api/events/message-complete.ts +2 -0
  147. package/src/api/events/notification-conversation-created.ts +14 -16
  148. package/src/api/events/notification-intent.ts +4 -2
  149. package/src/api/events/tool-result.ts +85 -0
  150. package/src/api/events/tool-use-start.ts +3 -0
  151. package/src/api/events/user-message-echo.ts +3 -0
  152. package/src/api/index.ts +31 -6
  153. package/src/api/mode-session.test.ts +131 -0
  154. package/src/api/mode-session.ts +148 -0
  155. package/src/api/remember-tool.ts +15 -0
  156. package/src/api/responses/conversation-message.ts +6 -0
  157. package/src/approvals/AGENTS.md +4 -2
  158. package/src/browser/__tests__/operations.test.ts +29 -0
  159. package/src/browser/operation-meta.ts +2 -1
  160. package/src/browser/operations.ts +29 -0
  161. package/src/browser/virtual-desktop-target.ts +2 -8
  162. package/src/bundler/bundle-signer.ts +1 -3
  163. package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
  164. package/src/calls/__tests__/call-controller.test.ts +94 -0
  165. package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
  166. package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
  167. package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
  168. package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
  169. package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
  170. package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
  171. package/src/calls/barge-in-guard.ts +5 -0
  172. package/src/calls/call-controller.ts +196 -1
  173. package/src/calls/call-domain.ts +4 -0
  174. package/src/calls/call-store.ts +87 -2
  175. package/src/calls/guardian-action-sweep.ts +22 -8
  176. package/src/calls/media-stream-audio-transcode.ts +1 -3
  177. package/src/calls/typesafe-noul.ts +156 -0
  178. package/src/calls/voice-control-protocol.ts +6 -0
  179. package/src/calls/voice-escalation-judge.ts +145 -0
  180. package/src/calls/voice-escalation-target.ts +10 -0
  181. package/src/calls/voice-leg-coordinator.ts +121 -3
  182. package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
  183. package/src/calls/voice-session-bridge.ts +256 -112
  184. package/src/calls/voice-triage-escalate.ts +7 -0
  185. package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
  186. package/src/cli/commands/__tests__/notifications.test.ts +35 -10
  187. package/src/cli/commands/browser.help.ts +4 -5
  188. package/src/cli/commands/browser.ts +0 -1
  189. package/src/cli/commands/clients.help.ts +46 -0
  190. package/src/cli/commands/clients.ts +116 -0
  191. package/src/cli/commands/credentials.help.ts +6 -1
  192. package/src/cli/commands/notifications.help.ts +16 -2
  193. package/src/cli/commands/notifications.ts +30 -8
  194. package/src/cli/commands/roadmap.help.ts +18 -21
  195. package/src/cli/commands/roadmap.ts +4 -21
  196. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
  197. package/src/cli/lib/bundled-marketplace.json +84 -34
  198. package/src/cli/lib/plugin-marketplace.ts +8 -0
  199. package/src/cli/lib/publish-plugin.ts +1 -2
  200. package/src/config/__tests__/default-provider.test.ts +3 -1
  201. package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
  202. package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
  203. package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
  204. package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
  205. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
  206. package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
  207. package/src/config/call-site-defaults.ts +9 -0
  208. package/src/config/feature-flag-registry.json +16 -0
  209. package/src/config/profile-text-generation.test.ts +2 -2
  210. package/src/config/schemas/__tests__/tools.test.ts +31 -0
  211. package/src/config/schemas/call-site-catalog.ts +14 -0
  212. package/src/config/schemas/channels.ts +0 -18
  213. package/src/config/schemas/llm.ts +3 -1
  214. package/src/config/schemas/mcp.ts +2 -1
  215. package/src/config/schemas/memory-retrospective.ts +11 -2
  216. package/src/config/schemas/services.ts +15 -6
  217. package/src/config/schemas/tools.ts +10 -0
  218. package/src/config/session-groups-gate.ts +21 -0
  219. package/src/config/skills.ts +2 -6
  220. package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
  221. package/src/conversations/message-consolidation.ts +102 -5
  222. package/src/credential-execution/ces-connect.test.ts +69 -0
  223. package/src/credential-execution/ces-connect.ts +115 -0
  224. package/src/credential-execution/ces-runtime.ts +55 -93
  225. package/src/credential-execution/executable-discovery.ts +1 -11
  226. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
  227. package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
  228. package/src/daemon/browser-mode-session.test.ts +402 -0
  229. package/src/daemon/browser-mode-session.ts +186 -0
  230. package/src/daemon/computer-use-mode-session.test.ts +210 -0
  231. package/src/daemon/computer-use-mode-session.ts +105 -0
  232. package/src/daemon/config-watcher.ts +10 -0
  233. package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
  234. package/src/daemon/conversation-agent-loop.ts +219 -25
  235. package/src/daemon/conversation-evictor.ts +5 -24
  236. package/src/daemon/conversation-history.ts +10 -0
  237. package/src/daemon/conversation-interrupt-repair.ts +4 -43
  238. package/src/daemon/conversation-interrupt.ts +7 -11
  239. package/src/daemon/conversation-messaging.ts +58 -5
  240. package/src/daemon/conversation-mode-session.test.ts +1667 -0
  241. package/src/daemon/conversation-mode-session.ts +1008 -0
  242. package/src/daemon/conversation-process.ts +5 -0
  243. package/src/daemon/conversation-runtime-assembly.ts +10 -20
  244. package/src/daemon/conversation-skill-tools.ts +1 -0
  245. package/src/daemon/conversation-store.ts +4 -4
  246. package/src/daemon/conversation-surface-snapshots.ts +135 -0
  247. package/src/daemon/conversation-surfaces.ts +113 -5
  248. package/src/daemon/conversation-tool-setup.ts +8 -3
  249. package/src/daemon/conversation.ts +143 -41
  250. package/src/daemon/handlers/config-model.ts +7 -5
  251. package/src/daemon/handlers/shared.ts +55 -16
  252. package/src/daemon/host-cu-proxy.ts +51 -22
  253. package/src/daemon/lifecycle.ts +30 -13
  254. package/src/daemon/message-protocol.ts +0 -1
  255. package/src/daemon/message-provenance.ts +28 -4
  256. package/src/daemon/mode-session-startup-recovery.ts +23 -0
  257. package/src/daemon/mode-session-tracking.ts +54 -0
  258. package/src/daemon/process-message.ts +13 -4
  259. package/src/daemon/virtual-desktop-context.ts +21 -0
  260. package/src/desktop/__tests__/fake-desktop.ts +1 -0
  261. package/src/desktop/desktop-accessibility-script.ts +88 -0
  262. package/src/desktop/desktop-accessibility.test.ts +160 -0
  263. package/src/desktop/desktop-accessibility.ts +169 -0
  264. package/src/desktop/desktop-automation-lease.test.ts +76 -68
  265. package/src/desktop/desktop-automation-lease.ts +41 -50
  266. package/src/desktop/desktop-browser-endpoint.ts +4 -0
  267. package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
  268. package/src/desktop/desktop-computer-use.test.ts +449 -0
  269. package/src/desktop/desktop-computer-use.ts +502 -0
  270. package/src/desktop/desktop-dependencies.ts +47 -293
  271. package/src/desktop/desktop-display.ts +2 -2
  272. package/src/desktop/desktop-panel-config.test.ts +13 -3
  273. package/src/desktop/desktop-panel-config.ts +109 -15
  274. package/src/desktop/desktop-session-manager.test.ts +69 -21
  275. package/src/desktop/desktop-session-manager.ts +76 -22
  276. package/src/desktop/desktop-stream-bridge.test.ts +11 -122
  277. package/src/desktop/desktop-stream-bridge.ts +6 -8
  278. package/src/desktop/desktop-window-theme.ts +5 -25
  279. package/src/desktop/virtual-desktop-feature.ts +52 -2
  280. package/src/desktop/virtual-desktop-platform.test.ts +5 -9
  281. package/src/documents/document-store.ts +23 -0
  282. package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
  283. package/src/heartbeat/heartbeat-service.ts +5 -33
  284. package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
  285. package/src/ipc/assistant-server.ts +2 -0
  286. package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
  287. package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
  288. package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
  289. package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
  290. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
  291. package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
  292. package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
  293. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
  294. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
  295. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
  296. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
  297. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
  298. package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
  299. package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
  300. package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
  301. package/src/live-voice/__tests__/session-controls.test.ts +23 -0
  302. package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
  303. package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
  304. package/src/live-voice/camera-mode-session.test.ts +265 -0
  305. package/src/live-voice/camera-mode-session.ts +277 -0
  306. package/src/live-voice/continuation-judge.ts +123 -0
  307. package/src/live-voice/live-activity-reporter.ts +115 -6
  308. package/src/live-voice/live-voice-manager.ts +36 -1
  309. package/src/live-voice/live-voice-photo.ts +88 -7
  310. package/src/live-voice/live-voice-session-manager.ts +15 -0
  311. package/src/live-voice/live-voice-session.ts +1443 -747
  312. package/src/live-voice/live-voice-tts.ts +22 -4
  313. package/src/live-voice/microphone-idle-gate.ts +56 -0
  314. package/src/live-voice/protocol.ts +171 -4
  315. package/src/live-voice/session-controls.ts +11 -3
  316. package/src/live-voice/task-outcomes.ts +131 -0
  317. package/src/live-voice/voice-input-diagnostics.ts +117 -0
  318. package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
  319. package/src/mcp/__tests__/tool-caps.test.ts +28 -1
  320. package/src/mcp/manager.ts +3 -0
  321. package/src/mcp/tool-caps.ts +12 -4
  322. package/src/media/__tests__/image-models.test.ts +58 -0
  323. package/src/media/image-credentials.ts +5 -0
  324. package/src/media/image-models.ts +52 -0
  325. package/src/media/image-service.ts +8 -0
  326. package/src/media/openrouter-image-service.test.ts +133 -0
  327. package/src/media/openrouter-image-service.ts +153 -0
  328. package/src/media/types.ts +1 -1
  329. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
  330. package/src/messaging/providers/discord/send.ts +4 -17
  331. package/src/messaging/providers/discord/transport.ts +2 -2
  332. package/src/messaging/providers/outlook/client.ts +16 -22
  333. package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
  334. package/src/messaging/providers/telegram-bot/send.ts +10 -7
  335. package/src/messaging/providers/telegram-bot/transport.ts +3 -10
  336. package/src/messaging/providers/whatsapp/send.test.ts +169 -4
  337. package/src/monitoring/__tests__/control.test.ts +17 -5
  338. package/src/monitoring/control.ts +15 -5
  339. package/src/notifications/AGENTS.md +1 -1
  340. package/src/notifications/README.md +83 -99
  341. package/src/notifications/__tests__/broadcaster.test.ts +1 -1
  342. package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
  343. package/src/notifications/__tests__/copy-composer.test.ts +1 -1
  344. package/src/notifications/__tests__/decision-engine.test.ts +79 -2
  345. package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
  346. package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
  347. package/src/notifications/__tests__/notification-utils.test.ts +18 -0
  348. package/src/notifications/adapters/macos.ts +37 -46
  349. package/src/notifications/broadcaster.ts +5 -6
  350. package/src/notifications/channel-allowlist.ts +44 -0
  351. package/src/notifications/copy-composer.ts +0 -23
  352. package/src/notifications/decision-engine.ts +24 -14
  353. package/src/notifications/deterministic-checks.ts +1 -1
  354. package/src/notifications/emit-signal.ts +123 -37
  355. package/src/notifications/home-feed-side-effect.ts +9 -7
  356. package/src/notifications/notification-utils.ts +19 -0
  357. package/src/notifications/signal.ts +4 -19
  358. package/src/notifications/types.ts +15 -0
  359. package/src/oauth/connection-resolver.ts +1 -2
  360. package/src/oauth/oauth-store.ts +1 -3
  361. package/src/oauth/provider-serializer.ts +7 -0
  362. package/src/oauth/seed-providers.ts +30 -0
  363. package/src/onboarding/onboarding-events-store.ts +99 -12
  364. package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
  365. package/src/persistence/client-connection-events-store.ts +377 -0
  366. package/src/persistence/conversation-crud.ts +55 -6
  367. package/src/persistence/conversation-mode-sessions.test.ts +424 -0
  368. package/src/persistence/conversation-mode-sessions.ts +460 -0
  369. package/src/persistence/conversation-queries.ts +6 -19
  370. package/src/persistence/llm-usage-store.ts +3 -9
  371. package/src/persistence/message-metadata.ts +25 -0
  372. package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
  373. package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
  374. package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
  375. package/src/persistence/schema/conversations.ts +67 -0
  376. package/src/persistence/schema/infrastructure.ts +29 -0
  377. package/src/persistence/steps.ts +17 -0
  378. package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
  379. package/src/plugin-api/effective-context-window.ts +33 -0
  380. package/src/plugin-api/index.ts +24 -5
  381. package/src/plugin-api/plugin-name-env.ts +125 -0
  382. package/src/plugin-api/resolve-credential.ts +79 -20
  383. package/src/plugin-api/vision-support.test.ts +22 -0
  384. package/src/plugin-api/vision-support.ts +17 -5
  385. package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
  386. package/src/plugins/active-plugin-env.ts +51 -0
  387. package/src/plugins/defaults/index.ts +24 -0
  388. package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
  389. package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
  390. package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
  391. package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
  392. package/src/plugins/defaults/memory/context-search/format.ts +51 -7
  393. package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
  394. package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
  395. package/src/plugins/defaults/memory/context-search/search.ts +8 -14
  396. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
  397. package/src/plugins/defaults/memory/context-search/types.ts +1 -11
  398. package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
  399. package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
  400. package/src/plugins/defaults/memory/graph/tools.ts +35 -64
  401. package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
  402. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
  403. package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
  404. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
  405. package/src/plugins/defaults/memory/tools.test.ts +124 -9
  406. package/src/plugins/defaults/memory/tools.ts +38 -11
  407. package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
  408. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
  409. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
  410. package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
  411. package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
  412. package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
  413. package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
  414. package/src/plugins/defaults/memory/worker-control.ts +15 -5
  415. package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
  416. package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
  417. package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
  418. package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
  419. package/src/plugins/defaults/task-progress/package.json +14 -0
  420. package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
  421. package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
  422. package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
  423. package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
  424. package/src/providers/anthropic/client.ts +10 -1
  425. package/src/providers/anthropic-gateway-shared.ts +6 -0
  426. package/src/providers/fetch-provider-catalog.ts +11 -0
  427. package/src/providers/gemini/client.ts +25 -8
  428. package/src/providers/inference/adapter-factory.ts +1 -1
  429. package/src/providers/jev/client.test.ts +10 -0
  430. package/src/providers/jev/client.ts +20 -4
  431. package/src/providers/model-catalog.ts +4 -3
  432. package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
  433. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
  434. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
  435. package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
  436. package/src/providers/openai/chat-completions-provider.ts +100 -29
  437. package/src/providers/openai/google-thought-signature.ts +0 -6
  438. package/src/providers/openai/responses-provider.ts +23 -1
  439. package/src/providers/ratelimit.ts +1 -3
  440. package/src/providers/search-provider-catalog.ts +24 -0
  441. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
  442. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
  443. package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
  444. package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
  445. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
  446. package/src/providers/types.ts +5 -0
  447. package/src/routes/control.ts +7 -5
  448. package/src/runtime/AGENTS.md +4 -2
  449. package/src/runtime/actor-trust-resolver.ts +3 -3
  450. package/src/runtime/agent-wake.ts +1 -1
  451. package/src/runtime/approval-message-composer.ts +3 -69
  452. package/src/runtime/assistant-event-hub.ts +73 -75
  453. package/src/runtime/assistant-event-publish-options.ts +7 -0
  454. package/src/runtime/assistant-event-targeting.ts +79 -0
  455. package/src/runtime/assistant-stream-state.ts +13 -100
  456. package/src/runtime/background-job-runner.ts +5 -5
  457. package/src/runtime/channel-retry-sweep.ts +1 -0
  458. package/src/runtime/guardian-action-message-composer.ts +8 -161
  459. package/src/runtime/guardian-reply-router.ts +28 -32
  460. package/src/runtime/http-types.ts +6 -0
  461. package/src/runtime/message-composer-types.ts +3 -61
  462. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
  463. package/src/runtime/migrations/vbundle-builder.ts +24 -1
  464. package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
  465. package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
  466. package/src/runtime/migrations/vbundle-importer.ts +4 -1
  467. package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
  468. package/src/runtime/migrations/vbundle-validator.ts +1 -0
  469. package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
  470. package/src/runtime/pending-interactions.ts +29 -0
  471. package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
  472. package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
  473. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
  474. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
  475. package/src/runtime/routes/browser-context.ts +31 -11
  476. package/src/runtime/routes/browser-routes.ts +43 -4
  477. package/src/runtime/routes/btw-routes.ts +9 -4
  478. package/src/runtime/routes/client-routes.ts +108 -0
  479. package/src/runtime/routes/conversation-management-routes.ts +4 -1
  480. package/src/runtime/routes/conversation-routes.ts +113 -27
  481. package/src/runtime/routes/credential-prompt-routes.ts +4 -3
  482. package/src/runtime/routes/credential-routes.ts +1 -2
  483. package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
  484. package/src/runtime/routes/desktop-setup-routes.ts +4 -6
  485. package/src/runtime/routes/documents-routes.ts +51 -0
  486. package/src/runtime/routes/email-routes.ts +3 -7
  487. package/src/runtime/routes/events-routes.ts +75 -54
  488. package/src/runtime/routes/identity-routes.ts +1 -5
  489. package/src/runtime/routes/image-generation-routes.ts +8 -14
  490. package/src/runtime/routes/inbound-message-handler.ts +35 -38
  491. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
  492. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
  493. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
  494. package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
  495. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
  496. package/src/runtime/routes/llm-context-normalization.ts +1 -7
  497. package/src/runtime/routes/migration-routes.ts +73 -2
  498. package/src/runtime/routes/notification-routes.ts +10 -0
  499. package/src/runtime/routes/oauth-providers.ts +7 -0
  500. package/src/runtime/routes/plugins-routes.ts +6 -0
  501. package/src/runtime/routes/roadmap-routes.ts +47 -8
  502. package/src/runtime/routes/secret-routes.ts +1 -1
  503. package/src/runtime/routes/settings-routes.ts +10 -4
  504. package/src/runtime/routes/watcher-routes.ts +1 -3
  505. package/src/runtime/trust-class.test.ts +7 -6
  506. package/src/runtime/trust-class.ts +9 -17
  507. package/src/schedule/__tests__/worker-control.test.ts +36 -5
  508. package/src/schedule/worker-control.ts +15 -5
  509. package/src/security/secure-keys.ts +53 -103
  510. package/src/skills/clawhub.ts +3 -8
  511. package/src/skills/managed-store.ts +1 -5
  512. package/src/stt/types.ts +4 -0
  513. package/src/subagent/notify.ts +54 -34
  514. package/src/subagent/parent-notification.ts +6 -0
  515. package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
  516. package/src/telemetry/phone-call-funnel.ts +144 -0
  517. package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
  518. package/src/tools/ask-question/ask-question-tool.ts +6 -4
  519. package/src/tools/browser/browser-execution.ts +22 -59
  520. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
  521. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
  522. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
  523. package/src/tools/client-os.ts +15 -0
  524. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  525. package/src/tools/computer-use/target.ts +42 -0
  526. package/src/tools/document/document-tool.ts +13 -18
  527. package/src/tools/execution-target.ts +14 -16
  528. package/src/tools/host-terminal/host-shell.ts +4 -1
  529. package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
  530. package/src/tools/network/__tests__/web-search.test.ts +203 -0
  531. package/src/tools/network/web-fetch.ts +274 -7
  532. package/src/tools/network/web-search.ts +468 -6
  533. package/src/tools/permission-checker.ts +3 -3
  534. package/src/tools/policy-context.ts +3 -1
  535. package/src/tools/skills/load.ts +2 -1
  536. package/src/tools/skills/sandbox-runner.ts +11 -2
  537. package/src/tools/skills/skill-script-runner.ts +7 -0
  538. package/src/tools/skills/skill-tool-factory.ts +15 -1
  539. package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
  540. package/src/tools/terminal/safe-env.ts +4 -0
  541. package/src/tools/terminal/shell.ts +2 -0
  542. package/src/tools/tool-approval-handler.ts +5 -1
  543. package/src/tools/tool-types.ts +3 -3
  544. package/src/tools/types.ts +8 -2
  545. package/src/util/__tests__/worker-process-command.test.ts +5 -4
  546. package/src/util/abort-reasons.ts +18 -26
  547. package/src/util/canonicalize-identity.test.ts +21 -0
  548. package/src/util/canonicalize-identity.ts +16 -0
  549. package/src/util/errors.ts +13 -3
  550. package/src/util/provider-error-patterns.ts +3 -0
  551. package/src/util/worker-process.ts +77 -35
  552. package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
  553. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
  554. package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
  555. package/src/workspace/migrations/registry.ts +2 -0
  556. package/src/__tests__/computer-use-window-schema.test.ts +0 -24
  557. package/src/api/events/heartbeat-alert.ts +0 -20
  558. package/src/daemon/message-types/web-activity.ts +0 -65
  559. package/src/desktop/desktop-dependencies.test.ts +0 -131
  560. package/src/tools/computer-use/definitions.ts +0 -607
@@ -38,8 +38,16 @@ interface ProcessMessageCall {
38
38
 
39
39
  function makeContext(sent: AssistantEvent[] = []): Conversation & {
40
40
  processMessageCalls: ProcessMessageCall[];
41
+ acceptedSurfaceResponses: Array<{
42
+ requestId: string;
43
+ response: { kind: "surface"; responseId: string };
44
+ }>;
41
45
  } {
42
46
  const processMessageCalls: ProcessMessageCall[] = [];
47
+ const acceptedSurfaceResponses: Array<{
48
+ requestId: string;
49
+ response: { kind: "surface"; responseId: string };
50
+ }> = [];
43
51
  return asConversation({
44
52
  conversationId: "conv-1",
45
53
  sendToClient: (msg: AssistantEvent) => sent.push(msg),
@@ -54,6 +62,18 @@ function makeContext(sent: AssistantEvent[] = []): Conversation & {
54
62
  accumulatedSurfaceState: new Map<string, Record<string, unknown>>(),
55
63
  surfaceActionRequestIds: new Set<string>(),
56
64
  currentTurnSurfaces: [],
65
+ modeSessions: {
66
+ acceptTurn: (
67
+ requestId: string,
68
+ response?: { kind: "surface"; responseId: string },
69
+ ) => {
70
+ if (response?.kind === "surface") {
71
+ acceptedSurfaceResponses.push({ requestId, response });
72
+ }
73
+ return undefined;
74
+ },
75
+ getTurnOwner: () => undefined,
76
+ } as unknown as Conversation["modeSessions"],
57
77
  isProcessing: () => false,
58
78
  enqueueMessage: () => ({ queued: false, requestId: "req-1" }),
59
79
  getQueueDepth: () => 0,
@@ -70,6 +90,7 @@ function makeContext(sent: AssistantEvent[] = []): Conversation & {
70
90
  },
71
91
  withSurface: createSurfaceMutex(),
72
92
  processMessageCalls,
93
+ acceptedSurfaceResponses,
73
94
  });
74
95
  }
75
96
 
@@ -78,6 +99,129 @@ describe("surface action delivery to assistant", () => {
78
99
  broadcastedMessages = [];
79
100
  });
80
101
 
102
+ test.each([true, false])(
103
+ "matching surface answers retain their session across cleanup (pending=%s)",
104
+ async (hasPending) => {
105
+ const { initializeDb } = await import("../persistence/db-init.js");
106
+ const { createConversation } =
107
+ await import("../persistence/conversation-crud.js");
108
+ const { ConversationModeSessionCoordinator } =
109
+ await import("../daemon/conversation-mode-session.js");
110
+ const { cleanupStandaloneSurface } =
111
+ await import("../daemon/conversation-surfaces.js");
112
+ await initializeDb();
113
+ const conversation = createConversation();
114
+ const coordinator = new ConversationModeSessionCoordinator(
115
+ conversation.id,
116
+ );
117
+ const handle = coordinator.activateSource({
118
+ sourceId: "browser-source",
119
+ generation: 1,
120
+ mode: "browser",
121
+ sourceStartedAt: 100,
122
+ })!;
123
+ coordinator.claimTurn("turn-origin", handle, 110);
124
+ coordinator.recordStructuralWait("turn-origin", {
125
+ kind: "surface",
126
+ responseId: "surface-123",
127
+ });
128
+ coordinator.releaseTurn("turn-origin");
129
+ const ctx = Object.assign(makeContext(), {
130
+ conversationId: conversation.id,
131
+ modeSessions: coordinator,
132
+ });
133
+ ctx.surfaceState.set("surface-123", {
134
+ surfaceType: "form",
135
+ data: { fields: [] },
136
+ });
137
+ if (hasPending) {
138
+ ctx.pendingSurfaceActions.set("surface-123", { surfaceType: "form" });
139
+ }
140
+
141
+ await handleSurfaceAction(ctx, "surface-123", "submit", {});
142
+
143
+ expect(ctx.processMessageCalls).toHaveLength(1);
144
+ const acceptedTurnId = ctx.processMessageCalls[0]!.requestId!;
145
+ expect(coordinator.getTurnOwner(acceptedTurnId)?.id).toBe(handle.id);
146
+ expect(ctx.pendingSurfaceActions.has("surface-123")).toBe(false);
147
+ cleanupStandaloneSurface(ctx, "surface-123");
148
+ expect(
149
+ coordinator.getTerminalDisposition(acceptedTurnId),
150
+ ).toBeUndefined();
151
+ expect(coordinator.getTurnOwner(acceptedTurnId)?.id).toBe(handle.id);
152
+ expect(
153
+ coordinator.acceptTurn("turn-replayed", {
154
+ kind: "surface",
155
+ responseId: "surface-123",
156
+ }),
157
+ ).toBeUndefined();
158
+ coordinator.releaseTurn(acceptedTurnId, {
159
+ status: "completed",
160
+ endReason: "turn_settled",
161
+ });
162
+ expect(coordinator.hasResidentWork()).toBe(false);
163
+ },
164
+ );
165
+
166
+ test.each([false, true])(
167
+ "queued surface answers retain handoff ownership without pinning the old turn (wait=%s)",
168
+ async (hasRecordedWait) => {
169
+ const { initializeDb } = await import("../persistence/db-init.js");
170
+ const { createConversation } =
171
+ await import("../persistence/conversation-crud.js");
172
+ const { ConversationModeSessionCoordinator } =
173
+ await import("../daemon/conversation-mode-session.js");
174
+ await initializeDb();
175
+ const conversation = createConversation();
176
+ const coordinator = new ConversationModeSessionCoordinator(
177
+ conversation.id,
178
+ );
179
+ const handle = coordinator.activateSource({
180
+ sourceId: "browser-source",
181
+ generation: 1,
182
+ mode: "browser",
183
+ sourceStartedAt: 100,
184
+ })!;
185
+ coordinator.claimTurn("turn-origin", handle, 110);
186
+ if (hasRecordedWait) {
187
+ coordinator.recordStructuralWait("turn-origin", {
188
+ kind: "surface",
189
+ responseId: "surface-123",
190
+ });
191
+ }
192
+ const ctx = Object.assign(makeContext(), {
193
+ conversationId: conversation.id,
194
+ modeSessions: coordinator,
195
+ });
196
+ let queuedRequestId: string | undefined;
197
+ ctx.enqueueMessage = (message) => {
198
+ queuedRequestId = message.requestId;
199
+ return { queued: true, requestId: message.requestId! };
200
+ };
201
+ ctx.surfaceState.set("surface-123", {
202
+ surfaceType: "form",
203
+ data: { fields: [] },
204
+ });
205
+ ctx.pendingSurfaceActions.set("surface-123", { surfaceType: "form" });
206
+
207
+ await handleSurfaceAction(ctx, "surface-123", "submit", {});
208
+
209
+ expect(ctx.processMessageCalls).toHaveLength(0);
210
+ expect(queuedRequestId).toBeDefined();
211
+ expect(coordinator.getTurnOwner(queuedRequestId!)?.id).toBe(
212
+ hasRecordedWait ? handle.id : undefined,
213
+ );
214
+ coordinator.transferTurn("turn-origin", queuedRequestId!);
215
+ expect(coordinator.getTurnOwner("turn-origin")).toBeUndefined();
216
+ expect(coordinator.getTurnOwner(queuedRequestId!)?.id).toBe(handle.id);
217
+ coordinator.releaseTurn(queuedRequestId!, {
218
+ status: "completed",
219
+ endReason: "turn_settled",
220
+ });
221
+ expect(coordinator.hasResidentWork()).toBe(false);
222
+ },
223
+ );
224
+
81
225
  test("table action button click triggers processMessage with action content", async () => {
82
226
  const sent: AssistantEvent[] = [];
83
227
  const ctx = makeContext(sent);
@@ -131,6 +275,13 @@ describe("surface action delivery to assistant", () => {
131
275
  expect(call.content).toContain("row-1");
132
276
  expect(call.content).toContain("row-2");
133
277
  expect(call.activeSurfaceId).toBe(surfaceId);
278
+ expect(call.requestId).toBeDefined();
279
+ expect(ctx.acceptedSurfaceResponses).toEqual([
280
+ {
281
+ requestId: call.requestId!,
282
+ response: { kind: "surface", responseId: surfaceId },
283
+ },
284
+ ]);
134
285
 
135
286
  // Verify pending action was cleared
136
287
  expect(ctx.pendingSurfaceActions.has(surfaceId)).toBe(false);
@@ -15,12 +15,20 @@ const { assistantEventHub } = await import("../runtime/assistant-event-hub.js");
15
15
  */
16
16
  function proxyDouble() {
17
17
  const recordAction = mock(() => {});
18
- const request = mock(async () => ({ content: "ok", isError: false }));
18
+ const request = mock(async (...args: unknown[]) => {
19
+ const onValidatedDispatch = args[8];
20
+ if (typeof onValidatedDispatch === "function") {
21
+ onValidatedDispatch();
22
+ }
23
+ return { content: "ok", isError: false };
24
+ });
19
25
  return {
20
26
  recordAction,
21
27
  request,
22
28
  proxy: {
23
29
  isAvailable: () => true,
30
+ sourceId: "proxy-123",
31
+ resetGeneration: 4,
24
32
  recordAction,
25
33
  request,
26
34
  reset: () => {},
@@ -39,6 +47,22 @@ const context = (proxy: unknown) =>
39
47
  currentTurnSourceActorPrincipalId: ACTOR,
40
48
  });
41
49
 
50
+ function modeSessionContext(proxy: unknown) {
51
+ const recordAction = mock(() => undefined);
52
+ const endTask = mock(() => true);
53
+ return {
54
+ recordAction,
55
+ endTask,
56
+ ctx: asConversation({
57
+ conversationId: "conv-1",
58
+ currentRequestId: "turn-123",
59
+ hostCuProxy: proxy as never,
60
+ currentTurnSourceActorPrincipalId: ACTOR,
61
+ computerUseModeSessions: { recordAction, endTask } as never,
62
+ }),
63
+ };
64
+ }
65
+
42
66
  /**
43
67
  * A client that can actually draw. Pointing resolves on `host_cu_annotate`
44
68
  * and refuses outright when nothing holds it, so without this the requests
@@ -148,4 +172,42 @@ describe("the computer-use step budget", () => {
148
172
 
149
173
  expect(recordAction).toHaveBeenCalledTimes(1);
150
174
  });
175
+
176
+ test("claims a mode session only after target validation", async () => {
177
+ const { proxy } = proxyDouble();
178
+ const valid = modeSessionContext(proxy);
179
+
180
+ await surfaceProxyResolver(valid.ctx, "computer_use_click", {
181
+ element_id: 3,
182
+ });
183
+ expect(valid.recordAction).toHaveBeenCalledWith({
184
+ turnId: "turn-123",
185
+ source: { sourceId: "proxy-123", generation: 4 },
186
+ at: expect.any(Number),
187
+ });
188
+
189
+ const rejected = modeSessionContext(proxy);
190
+ await surfaceProxyResolver(rejected.ctx, "computer_use_click", {
191
+ element_id: 3,
192
+ target_client_id: "missing-client",
193
+ });
194
+ expect(rejected.recordAction).not.toHaveBeenCalled();
195
+ });
196
+
197
+ test("terminal respond retires the exact run and begins draining", async () => {
198
+ const { proxy } = proxyDouble();
199
+ const state = modeSessionContext(proxy);
200
+
201
+ const result = await surfaceProxyResolver(
202
+ state.ctx,
203
+ "computer_use_respond",
204
+ { answer: "Finished" },
205
+ );
206
+
207
+ expect(result).toEqual({ content: "Finished", isError: false });
208
+ expect(state.endTask).toHaveBeenCalledWith({
209
+ turnId: "turn-123",
210
+ source: { sourceId: "proxy-123", generation: 4 },
211
+ });
212
+ });
151
213
  });
@@ -10,6 +10,7 @@
10
10
  import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
11
11
 
12
12
  import type { Conversation } from "../daemon/conversation.js";
13
+ import { shouldUseVirtualDesktop } from "../desktop/virtual-desktop-feature.js";
13
14
  import type { PermissionPrompter } from "../permissions/prompter.js";
14
15
  import type { SecretPrompter } from "../permissions/secret-prompter.js";
15
16
  import type { ToolExecutor } from "../tools/executor.js";
@@ -307,6 +308,28 @@ describe("createToolExecutor attribution threading", () => {
307
308
  });
308
309
  });
309
310
 
311
+ test("desktop routing honors the pinned interface when the live client changes", async () => {
312
+ for (const transportInterface of ["web", "macos"] as const) {
313
+ const { executor, calls } = makeCapturingExecutor();
314
+ await makeToolFn(
315
+ executor,
316
+ makeCtx({
317
+ transportInterface: transportInterface === "web" ? "macos" : "web",
318
+ toolContextPin: { hasNoClient: false, transportInterface },
319
+ getTurnActorPrincipalId: () => "user-123",
320
+ currentTurnTrustContext: {
321
+ sourceChannel: "vellum",
322
+ trustClass: "guardian",
323
+ },
324
+ }),
325
+ )("computer_use_observe", {});
326
+
327
+ expect(shouldUseVirtualDesktop(calls[0].context, true)).toBe(
328
+ transportInterface === "web",
329
+ );
330
+ }
331
+ });
332
+
310
333
  describe("createToolExecutor isInteractive threading", () => {
311
334
  test("uses the resolved turn-level interactivity over live client state", async () => {
312
335
  // A scheduled/background turn (currentTurnIsNonInteractive=true) must read
@@ -87,3 +87,64 @@ describe("findLastUndoableUserMessageIndex", () => {
87
87
  expect(findLastUndoableUserMessageIndex([spoofMessage])).toBe(0);
88
88
  });
89
89
  });
90
+
91
+ describe("undo mode-session cleanup", () => {
92
+ test.each([false, true])(
93
+ "undo completes when tracking failure is %s",
94
+ async (trackingFails) => {
95
+ const { initializeDb } = await import("../persistence/db-init.js");
96
+ const { createConversation, addMessage, getMessages } =
97
+ await import("../persistence/conversation-crud.js");
98
+ const { ConversationModeSessionCoordinator } =
99
+ await import("../daemon/conversation-mode-session.js");
100
+ const { undo } = await import("../daemon/conversation-history.js");
101
+ await initializeDb();
102
+ const conversation = createConversation();
103
+ const messages = [
104
+ textMessage("user", "Choose an option"),
105
+ textMessage("assistant", "Please choose"),
106
+ ];
107
+ for (const message of messages) {
108
+ await addMessage(
109
+ conversation.id,
110
+ message.role,
111
+ JSON.stringify(message.content),
112
+ { skipIndexing: true },
113
+ );
114
+ }
115
+ const coordinator = new ConversationModeSessionCoordinator(
116
+ conversation.id,
117
+ );
118
+ const source = coordinator.activateSource({
119
+ sourceId: "browser-source",
120
+ generation: 1,
121
+ mode: "browser",
122
+ sourceStartedAt: 100,
123
+ })!;
124
+ coordinator.claimTurn("turn-origin", source, 110);
125
+ coordinator.recordStructuralWait("turn-origin", {
126
+ kind: "surface",
127
+ responseId: "surface-123",
128
+ });
129
+ coordinator.releaseTurn("turn-origin");
130
+ const context = {
131
+ conversationId: conversation.id,
132
+ messages,
133
+ isProcessing: () => false,
134
+ modeSessions: {
135
+ invalidateAllStructuralWaits: () => {
136
+ const count = coordinator.invalidateAllStructuralWaits();
137
+ if (trackingFails) {
138
+ throw new Error("session tracking unavailable");
139
+ }
140
+ return count;
141
+ },
142
+ },
143
+ };
144
+ expect(undo(context)).toBe(2);
145
+ expect(context.messages).toEqual([]);
146
+ expect(getMessages(conversation.id)).toEqual([]);
147
+ expect(coordinator.hasResidentWork()).toBe(false);
148
+ },
149
+ );
150
+ });
@@ -31,8 +31,8 @@ import {
31
31
  createCesClient,
32
32
  } from "../credential-execution/client.js";
33
33
  import {
34
+ discoverCes,
34
35
  discoverCesWithRetry,
35
- discoverManagedCes,
36
36
  } from "../credential-execution/executable-discovery.js";
37
37
 
38
38
  // ---------------------------------------------------------------------------
@@ -95,12 +95,12 @@ function withBootstrapDir(dir: string): () => void {
95
95
  };
96
96
  }
97
97
 
98
- describe("managed CES discovery", () => {
98
+ describe("CES discovery", () => {
99
99
  test("returns unavailable when bootstrap socket does not exist", () => {
100
100
  const bootstrapDir = mkdtempSync(join(tmpdir(), "ces-missing-"));
101
101
  const restore = withBootstrapDir(bootstrapDir);
102
102
  try {
103
- const result = discoverManagedCes();
103
+ const result = discoverCes();
104
104
  expect(result.mode).toBe("unavailable");
105
105
  expect((result as { reason: string }).reason).toContain(
106
106
  "CES bootstrap socket not found",
@@ -115,7 +115,7 @@ describe("managed CES discovery", () => {
115
115
  const bootstrapDir = mkdtempSync(join(tmpdir(), "ces-missing-"));
116
116
  const restore = withBootstrapDir(bootstrapDir);
117
117
  try {
118
- const result = discoverManagedCes();
118
+ const result = discoverCes();
119
119
  expect(["managed", "unavailable"]).toContain(result.mode);
120
120
  } finally {
121
121
  restore();
@@ -132,7 +132,7 @@ describe("CES bootstrap socket discovery", () => {
132
132
  const socketPath = resolveIpcEndpoint("ces", {
133
133
  workspaceDir: bootstrapDir,
134
134
  }).path;
135
- const result = discoverManagedCes();
135
+ const result = discoverCes();
136
136
  expect(result.mode).toBe("unavailable");
137
137
  expect((result as { reason: string }).reason).toContain(socketPath);
138
138
  } finally {
@@ -145,7 +145,7 @@ describe("CES bootstrap socket discovery", () => {
145
145
  const bootstrapDir = mkdtempSync(join(tmpdir(), "ces-bootstrap-"));
146
146
  const restore = withBootstrapDir(bootstrapDir);
147
147
  try {
148
- const result = discoverManagedCes();
148
+ const result = discoverCes();
149
149
  const workspaceDir = process.env.VELLUM_WORKSPACE_DIR;
150
150
  expect(workspaceDir).toBeDefined();
151
151
  const workspaceSocket = resolveIpcEndpoint("ces", {
@@ -509,9 +509,14 @@ describe("credentials/prompt route", () => {
509
509
  error: "unsupported_channel",
510
510
  };
511
511
 
512
- // WHEN the route handles the prompt
512
+ // WHEN the route handles a conversation-scoped prompt
513
513
  const result = (await promptRoute!.handler({
514
- body: { service: "stripe", field: "api_key", label: "Stripe API Key" },
514
+ body: {
515
+ service: "stripe",
516
+ field: "api_key",
517
+ label: "Stripe API Key",
518
+ conversationId: "conv-1",
519
+ },
515
520
  })) as PromptResponse;
516
521
 
517
522
  // THEN it is a plain failure with no cancel flag
@@ -522,6 +527,32 @@ describe("credentials/prompt route", () => {
522
527
  );
523
528
  });
524
529
 
530
+ test("names the missing conversation when a headless prompt cannot be delivered", async () => {
531
+ /**
532
+ * With no conversationId the failure is not about a channel: no client can
533
+ * render the card at all. The model-facing text must say so and steer to
534
+ * the in-app credentials page, never to pasting the value into chat.
535
+ */
536
+ // GIVEN a conversation-less prompt that could not mint a collection link
537
+ secretResult = {
538
+ value: null,
539
+ delivery: "store",
540
+ error: "unsupported_channel",
541
+ };
542
+
543
+ // WHEN the route handles a prompt with no conversationId
544
+ const result = (await promptRoute!.handler({
545
+ body: { service: "stripe", field: "api_key", label: "Stripe API Key" },
546
+ })) as PromptResponse;
547
+
548
+ // THEN it fails fast with headless-specific guidance
549
+ expect(result.ok).toBe(false);
550
+ expect(result.cancelled).toBeUndefined();
551
+ expect(result.error).toContain("No conversation is attached");
552
+ expect(result.error).toContain("Settings > Credentials");
553
+ expect(result.error).toContain("Never ask them to paste it into chat");
554
+ });
555
+
525
556
  test("returns a pending collection link for unsupported channels when minted", async () => {
526
557
  /**
527
558
  * When the channel cannot render the secure prompt but the gateway minted
@@ -721,7 +752,8 @@ describe("credentials/prompt route", () => {
721
752
  test("still prompts for the ACP Claude token outside any conversation (headless CLI)", async () => {
722
753
  /**
723
754
  * A bare CLI `credentials prompt` with no conversation (headless) can't show
724
- * an inline card, so the guard must not fire and the prompt proceeds.
755
+ * an inline card, so the guard must not fire and the prompt proceeds (where
756
+ * `requestSecretStandalone` fails fast into the collection-link fallback).
725
757
  */
726
758
  // GIVEN no conversation resolves (headless CLI, no conversationId)
727
759
  conversationExists = false;
@@ -5,8 +5,11 @@
5
5
  * point between the agent loop and the HostCuProxy.
6
6
  */
7
7
 
8
- import { afterEach, describe, expect, mock, test } from "bun:test";
8
+ import { afterEach, describe, expect, mock, spyOn, test } from "bun:test";
9
9
 
10
+ import { isSessionGroupsEnabled } from "../config/session-groups-gate.js";
11
+ import { ComputerUseModeSessionProducer } from "../daemon/computer-use-mode-session.js";
12
+ import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
10
13
  import { asConversation } from "./helpers/mock-conversation.js";
11
14
 
12
15
  const sentMessages: unknown[] = [];
@@ -76,6 +79,10 @@ function buildMockContext(
76
79
  // attach one (the gate denies, or the context predates the hook).
77
80
  ensureHostProxiesForTurn?: () => void,
78
81
  ): Conversation {
82
+ const computerUseModeSessions = {
83
+ recordAction: () => false,
84
+ endTask: () => false,
85
+ } as unknown as Conversation["computerUseModeSessions"];
79
86
  const ctx: Conversation = asConversation({
80
87
  conversationId: "test-session",
81
88
  trustContext:
@@ -105,6 +112,7 @@ function buildMockContext(
105
112
  accumulatedSurfaceState: new Map(),
106
113
  surfaceActionRequestIds: new Set(),
107
114
  currentTurnSurfaces: [],
115
+ computerUseModeSessions,
108
116
  hostCuProxy,
109
117
  transportInterface: "web",
110
118
  ensureHostProxiesForTurn,
@@ -139,6 +147,7 @@ describe("surfaceProxyResolver — CU tool routing", () => {
139
147
  }
140
148
 
141
149
  afterEach(() => {
150
+ setOverridesForTesting({});
142
151
  proxy?.dispose();
143
152
  });
144
153
 
@@ -337,6 +346,78 @@ describe("surfaceProxyResolver — CU tool routing", () => {
337
346
  // -------------------------------------------------------------------------
338
347
 
339
348
  describe("action tools proxy to client", () => {
349
+ test("dispatches without mode-session side effects while session groups are disabled", async () => {
350
+ setOverridesForTesting({ "session-groups": false });
351
+ const ctx = setupProxy();
352
+ ctx.currentRequestId = "turn-123";
353
+ const activateSource = mock(() => {
354
+ throw new Error("disabled tracking must not activate a source");
355
+ });
356
+ (
357
+ ctx as unknown as {
358
+ computerUseModeSessions: Conversation["computerUseModeSessions"];
359
+ }
360
+ ).computerUseModeSessions = new ComputerUseModeSessionProducer(
361
+ {
362
+ activateSource,
363
+ beginDraining: mock(() => false),
364
+ claimTurn: mock(() => undefined),
365
+ getTurnOwner: mock(() => undefined),
366
+ recordActivity: mock(() => false),
367
+ retireSource: mock(() => false),
368
+ },
369
+ isSessionGroupsEnabled,
370
+ );
371
+
372
+ const resultPromise = surfaceProxyResolver(ctx, "computer_use_click", {
373
+ element_id: 42,
374
+ });
375
+ const sent = sentMessages[0] as { requestId: string; type: string };
376
+ expect(sent.type).toBe("host_cu_request");
377
+ expect(activateSource).not.toHaveBeenCalled();
378
+
379
+ proxy.processObservation(sent.requestId, { executionResult: "clicked" });
380
+ const result = await resultPromise;
381
+ expect(result.isError).toBe(false);
382
+ });
383
+
384
+ test("dispatches once after tracking admission throws and still cleans up the task", async () => {
385
+ const ctx = setupProxy();
386
+ ctx.currentRequestId = "turn-123";
387
+ const recordAction = spyOn(
388
+ ctx.computerUseModeSessions,
389
+ "recordAction",
390
+ ).mockImplementation(() => {
391
+ throw new Error("tracking unavailable");
392
+ });
393
+ const endTask = spyOn(
394
+ ctx.computerUseModeSessions,
395
+ "endTask",
396
+ ).mockImplementation(() => {
397
+ throw new Error("tracking unavailable");
398
+ });
399
+ try {
400
+ const resultPromise = surfaceProxyResolver(ctx, "computer_use_click", {
401
+ element_id: 42,
402
+ });
403
+ expect(sentMessages).toHaveLength(1);
404
+ const sent = sentMessages[0] as { requestId: string };
405
+ proxy.processObservation(sent.requestId, {
406
+ executionResult: "clicked",
407
+ });
408
+ expect((await resultPromise).isError).toBe(false);
409
+ expect(recordAction).toHaveBeenCalledTimes(1);
410
+ expect(
411
+ (await surfaceProxyResolver(ctx, "computer_use_done", {})).isError,
412
+ ).toBe(false);
413
+ expect(endTask).toHaveBeenCalledTimes(1);
414
+ expect(proxy.stepCount).toBe(0);
415
+ } finally {
416
+ recordAction.mockRestore();
417
+ endTask.mockRestore();
418
+ }
419
+ });
420
+
340
421
  test("computer_use_click routes through proxy and returns observation", async () => {
341
422
  const ctx = setupProxy();
342
423