@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
@@ -22,6 +22,7 @@ import type {
22
22
  VoiceTurnCallbacks,
23
23
  VoiceTurnOptions,
24
24
  } from "../../calls/voice-session-bridge.js";
25
+ import { ESCALATION_CONTINUATION_CONTENT } from "../../calls/voice-triage-escalate.js";
25
26
  import { Conversation } from "../../daemon/conversation.js";
26
27
  import {
27
28
  deleteConversation,
@@ -62,6 +63,9 @@ class MockStreamingTranscriber implements StreamingTranscriber {
62
63
  readonly providerId = "deepgram" as const;
63
64
  readonly boundaryId = "daemon-streaming" as const;
64
65
  private onEvent: ((event: SttStreamServerEvent) => void) | null = null;
66
+ stopped = false;
67
+
68
+ constructor(private transcript: string) {}
65
69
 
66
70
  async start(onEvent: (event: SttStreamServerEvent) => void): Promise<void> {
67
71
  this.onEvent = onEvent;
@@ -69,13 +73,28 @@ class MockStreamingTranscriber implements StreamingTranscriber {
69
73
 
70
74
  sendAudio(): void {}
71
75
 
76
+ emitPartial(text: string): void {
77
+ this.transcript = text;
78
+ this.onEvent?.({ type: "partial", text });
79
+ }
80
+
72
81
  stop(): void {
73
- this.onEvent?.({ type: "final", text: "look at my screen" });
82
+ if (this.stopped) {
83
+ return;
84
+ }
85
+ this.stopped = true;
86
+ if (this.transcript.length > 0) {
87
+ this.onEvent?.({ type: "final", text: this.transcript });
88
+ }
74
89
  this.onEvent?.({ type: "closed" });
75
90
  }
76
91
  }
77
92
 
78
- function createHarness(options: { lookFrames: boolean }) {
93
+ function createHarness(options: {
94
+ lookFrames: boolean;
95
+ request?: string;
96
+ handsFree?: boolean;
97
+ }) {
79
98
  const conversation = createConversation("Look follow-up");
80
99
  const { provider } = createMockProvider([textResponse("")]);
81
100
  const activeConversation = new Conversation(
@@ -101,6 +120,7 @@ function createHarness(options: { lookFrames: boolean }) {
101
120
  conversationId: conversation.id,
102
121
  audio: { mimeType: "audio/pcm", sampleRate: 24_000, channels: 1 },
103
122
  textInput: true,
123
+ ...(options.handsFree ? { turnDetection: "server_vad" as const } : {}),
104
124
  sessionControls: ["look_screen", "look_camera", "look_stop"],
105
125
  ...(options.lookFrames ? { lookFrames: true } : {}),
106
126
  },
@@ -114,7 +134,11 @@ function createHarness(options: { lookFrames: boolean }) {
114
134
  const turns: VoiceTurnOptions[] = [];
115
135
  const startVoiceTurn = mock(async (turnOptions: VoiceTurnOptions) => {
116
136
  turns.push(turnOptions);
117
- return { turnId: `bridge-turn-${turns.length}`, abort: mock() };
137
+ return {
138
+ turnId: `bridge-turn-${turns.length}`,
139
+ abort: mock(),
140
+ discard: mock(async () => {}),
141
+ };
118
142
  });
119
143
  const streamTtsAudio: LiveVoiceTtsStreamer = mock(
120
144
  async (ttsOptions: LiveVoiceTtsOptions) => ({
@@ -127,19 +151,27 @@ function createHarness(options: { lookFrames: boolean }) {
127
151
  );
128
152
 
129
153
  let turnCount = 0;
154
+ const transcribers: MockStreamingTranscriber[] = [];
130
155
  const session = new LiveVoiceSession(context, {
131
- resolveTranscriber: mock(async () => new MockStreamingTranscriber()),
156
+ resolveTranscriber: mock(async () => {
157
+ const transcriber = new MockStreamingTranscriber(
158
+ options.handsFree ? "" : (options.request ?? "look at my screen"),
159
+ );
160
+ transcribers.push(transcriber);
161
+ return transcriber;
162
+ }),
132
163
  startVoiceTurn,
133
164
  streamTtsAudio,
134
165
  createTurnId: () => `live-turn-${++turnCount}`,
135
166
  emitMetrics: false,
167
+ turnDetectorConfig: { silenceThresholdMs: 40 },
168
+ frontModelConfig: { endpointDecisionTimeoutMs: 5_000 },
136
169
  });
137
170
 
138
171
  const callbacks = (index: number): VoiceTurnCallbacks | undefined =>
139
172
  turns[index]?.callbacks;
140
173
 
141
- /** Finish turn `index` with `text` and wait for its speech to drain. */
142
- const reply = async (index: number, text: string): Promise<void> => {
174
+ const emitReply = (index: number, text: string): void => {
143
175
  const turnCallbacks = callbacks(index);
144
176
  turnCallbacks?.assistant_text_delta?.({
145
177
  type: "assistant_text_delta",
@@ -151,7 +183,13 @@ function createHarness(options: { lookFrames: boolean }) {
151
183
  conversationId: conversation.id,
152
184
  messageId: `assistant-message-${index}`,
153
185
  });
154
- const doneCount = index + 1;
186
+ };
187
+
188
+ /** Finish turn `index` with `text` and wait for its speech to drain. */
189
+ const reply = async (index: number, text: string): Promise<void> => {
190
+ const doneCount =
191
+ frames.filter((frame) => frame.type === "tts_done").length + 1;
192
+ emitReply(index, text);
155
193
  await waitFor(
156
194
  () =>
157
195
  frames.filter((frame) => frame.type === "tts_done").length >= doneCount,
@@ -162,7 +200,11 @@ function createHarness(options: { lookFrames: boolean }) {
162
200
  /** Ask for a look, and wait for the control to reach the client. */
163
201
  const askForLook = async (): Promise<void> => {
164
202
  await session.start();
165
- await session.handleClientFrame({ type: "ptt_release" });
203
+ await session.handleClientFrame(
204
+ options.handsFree
205
+ ? { type: "text", text: options.request ?? "look at my screen" }
206
+ : { type: "ptt_release" },
207
+ );
166
208
  await waitFor(() => turns.length === 1, {
167
209
  message: "Timed out waiting for the spoken turn",
168
210
  });
@@ -201,6 +243,8 @@ function createHarness(options: { lookFrames: boolean }) {
201
243
  frames,
202
244
  session,
203
245
  turns,
246
+ transcribers,
247
+ emitReply,
204
248
  reply,
205
249
  askForLook,
206
250
  sendFrame,
@@ -232,6 +276,7 @@ describe("live-voice look follow-up", () => {
232
276
  const followUp = harness.turns[1];
233
277
  expect(followUp?.content).toBe(LOOK_FOLLOW_UP_CONTENT);
234
278
  expect(followUp?.hiddenSyntheticPrompt).toBe(true);
279
+ expect(followUp?.routingUtterance).toBe("look at my screen");
235
280
  expect(followUp?.voiceControlPrompt).toContain(
236
281
  "You just took a fresh look at their screen",
237
282
  );
@@ -240,6 +285,53 @@ describe("live-voice look follow-up", () => {
240
285
  }
241
286
  });
242
287
 
288
+ test("carries an annotation request through capture and escalation", async () => {
289
+ const request = "Show me on my screen where to add a new page.";
290
+ const harness = createHarness({ lookFrames: true, request });
291
+ try {
292
+ await harness.askForLook();
293
+ expect(harness.turns).toHaveLength(1);
294
+ expect(harness.turns[0]?.routingUtterance).toBeUndefined();
295
+
296
+ await harness.sendFrame(LOOK_FRAME_REASON);
297
+ await waitFor(() => harness.turns.length === 2);
298
+ const followUp = harness.turns[1];
299
+ expect(followUp?.routingLeg).toBe("front-door");
300
+ expect(followUp?.routingUtterance).toBe(request);
301
+ expect(followUp?.voiceControlPrompt).toContain(JSON.stringify(request));
302
+ expect(followUp?.voiceControlPrompt).toContain("screen-annotation tools");
303
+
304
+ harness.emitReply(1, "[1] I'll point out the new page button.");
305
+ await waitFor(() => harness.turns.length === 3);
306
+ const escalated = harness.turns[2];
307
+ expect(escalated?.routingLeg).toBe("escalated");
308
+ expect(escalated?.content).toBe(ESCALATION_CONTINUATION_CONTENT);
309
+ expect(escalated?.routingUtterance).toBe(request);
310
+ expect(escalated?.voiceControlPrompt).toContain(JSON.stringify(request));
311
+ expect(escalated?.voiceControlPrompt).toContain(
312
+ "screen-annotation tools",
313
+ );
314
+ await harness.reply(2, "The new page button is highlighted.");
315
+ } finally {
316
+ await harness.dispose();
317
+ }
318
+ });
319
+
320
+ test("an observation can answer from the fresh frame without escalating", async () => {
321
+ const request = "What do you see on my screen?";
322
+ const harness = createHarness({ lookFrames: true, request });
323
+ try {
324
+ await harness.askForLook();
325
+ await harness.sendFrame(LOOK_FRAME_REASON);
326
+ await waitFor(() => harness.turns.length === 2);
327
+ expect(harness.turns[1]?.routingUtterance).toBe(request);
328
+ await harness.reply(1, "There is a page editor with an empty document.");
329
+ expect(harness.turns).toHaveLength(2);
330
+ } finally {
331
+ await harness.dispose();
332
+ }
333
+ });
334
+
243
335
  test("the reply that asks for a look is taught to only acknowledge it", async () => {
244
336
  const harness = createHarness({ lookFrames: true });
245
337
  try {
@@ -282,13 +374,13 @@ describe("live-voice look follow-up", () => {
282
374
 
283
375
  // The turn started before the frame was in the conversation, so it could
284
376
  // not have read it: the look is still owed an answer once that turn is done.
285
- test("a turn that started before the frame landed does not answer the look", async () => {
377
+ test("keeps a clarification from a turn started before the frame landed", async () => {
286
378
  const harness = createHarness({ lookFrames: true });
287
379
  try {
288
380
  await harness.askForLook();
289
381
  await harness.session.handleClientFrame({
290
382
  type: "text",
291
- text: "the second dropdown",
383
+ text: "show me where to click",
292
384
  });
293
385
  await waitFor(() => harness.turns.length === 2, {
294
386
  message: "Timed out waiting for the typed turn",
@@ -301,6 +393,77 @@ describe("live-voice look follow-up", () => {
301
393
  message: "Timed out waiting for the look to be answered",
302
394
  });
303
395
  expect(harness.turns[2]?.content).toBe(LOOK_FOLLOW_UP_CONTENT);
396
+ const request = "look at my screen\nshow me where to click";
397
+ expect(harness.turns[2]?.routingUtterance).toBe(request);
398
+ expect(harness.turns[2]?.voiceControlPrompt).toContain(
399
+ JSON.stringify(request),
400
+ );
401
+ } finally {
402
+ await harness.dispose();
403
+ }
404
+ });
405
+
406
+ test("keeps multiple caller clarifications in order", async () => {
407
+ const harness = createHarness({ lookFrames: true });
408
+ try {
409
+ await harness.askForLook();
410
+ const clarifications = ["show me where to click", "the new page button"];
411
+ for (const [index, text] of clarifications.entries()) {
412
+ await harness.session.handleClientFrame({ type: "text", text });
413
+ await waitFor(() => harness.turns.length === index + 2);
414
+ await harness.reply(index + 1, "I'm waiting for the screen view.");
415
+ }
416
+ await harness.sendFrame(LOOK_FRAME_REASON);
417
+ await waitFor(() => harness.turns.length === 4);
418
+ expect(harness.turns[3]?.routingUtterance).toBe(
419
+ ["look at my screen", ...clarifications].join("\n"),
420
+ );
421
+ } finally {
422
+ await harness.dispose();
423
+ }
424
+ });
425
+
426
+ test("keeps a speculative clarification that commits after the frame lands", async () => {
427
+ const harness = createHarness({ lookFrames: true, handsFree: true });
428
+ try {
429
+ await harness.askForLook();
430
+ const audio = Buffer.alloc(480);
431
+ for (let index = 0; index < 240; index += 1) {
432
+ audio.writeInt16LE(8_000, index * 2);
433
+ }
434
+ await harness.session.handleBinaryAudio(audio);
435
+ await waitFor(() => harness.transcribers.at(-1)?.stopped === false);
436
+ harness.transcribers.at(-1)?.emitPartial("show me where to click");
437
+ await waitFor(() => harness.turns.length === 2);
438
+ expect(harness.turns[1]?.unifiedVerdict).toBe(true);
439
+
440
+ await harness.sendFrame(LOOK_FRAME_REASON);
441
+ await harness.reply(1, "I'll use the view when it arrives.");
442
+ await waitFor(() => harness.turns.length === 3);
443
+ expect(harness.turns[2]?.routingUtterance).toBe(
444
+ "look at my screen\nshow me where to click",
445
+ );
446
+ } finally {
447
+ await harness.dispose();
448
+ }
449
+ });
450
+
451
+ test("duplicate look frames do not schedule a second follow-up", async () => {
452
+ const harness = createHarness({ lookFrames: true });
453
+ try {
454
+ await harness.askForLook();
455
+ await harness.session.handleClientFrame({
456
+ type: "text",
457
+ text: "show me where to click",
458
+ });
459
+ await waitFor(() => harness.turns.length === 2);
460
+ await harness.sendFrame(LOOK_FRAME_REASON);
461
+ await harness.sendFrame(LOOK_FRAME_REASON);
462
+ await harness.reply(1, "Let me use that view.");
463
+ await waitFor(() => harness.turns.length === 3);
464
+ await harness.reply(2, "The button is at the top.");
465
+ await settle();
466
+ expect(harness.turns).toHaveLength(3);
304
467
  } finally {
305
468
  await harness.dispose();
306
469
  }
@@ -354,6 +517,24 @@ describe("live-voice look follow-up", () => {
354
517
  }
355
518
  });
356
519
 
520
+ test("an interrupt drops a captured look waiting for another turn to finish", async () => {
521
+ const harness = createHarness({ lookFrames: true });
522
+ try {
523
+ await harness.askForLook();
524
+ await harness.session.handleClientFrame({
525
+ type: "text",
526
+ text: "show me where to click",
527
+ });
528
+ await waitFor(() => harness.turns.length === 2);
529
+ await harness.sendFrame(LOOK_FRAME_REASON);
530
+ await harness.session.handleClientFrame({ type: "interrupt" });
531
+ await settle();
532
+ expect(harness.turns).toHaveLength(2);
533
+ } finally {
534
+ await harness.dispose();
535
+ }
536
+ });
537
+
357
538
  test("the turn that answers a look cannot chain another look", async () => {
358
539
  const harness = createHarness({ lookFrames: true });
359
540
  try {
@@ -0,0 +1,87 @@
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import { setOverridesForTesting } from "../../__tests__/feature-flag-test-helpers.js";
4
+ import { setModeSessionRecoveryHealthy } from "../../config/session-groups-gate.js";
5
+ import {
6
+ getLiveVoiceSessionManager,
7
+ setBundledLiveVoiceSessionFactory,
8
+ setLiveVoiceSessionManagerForTesting,
9
+ } from "../live-voice-manager.js";
10
+ import type { LiveVoiceSessionOptions } from "../live-voice-session.js";
11
+ import type {
12
+ LiveVoiceSession,
13
+ LiveVoiceSessionFactoryContext,
14
+ } from "../live-voice-session-manager.js";
15
+ import type { LiveVoiceClientStartFrame } from "../protocol.js";
16
+
17
+ const START_FRAME: LiveVoiceClientStartFrame = {
18
+ type: "start",
19
+ conversationId: "conversation-123",
20
+ audio: { mimeType: "audio/pcm", sampleRate: 24_000, channels: 1 },
21
+ };
22
+
23
+ function fakeSession(): LiveVoiceSession {
24
+ return {
25
+ start: mock(() => {}),
26
+ handleClientFrame: mock(() => {}),
27
+ handleBinaryAudio: mock(() => {}),
28
+ close: mock(() => {}),
29
+ };
30
+ }
31
+
32
+ async function captureProductionOptions(
33
+ enabled: boolean,
34
+ ): Promise<LiveVoiceSessionOptions> {
35
+ setOverridesForTesting({ "session-groups": enabled });
36
+ let captured: LiveVoiceSessionOptions | undefined;
37
+ setBundledLiveVoiceSessionFactory(
38
+ (
39
+ _context: LiveVoiceSessionFactoryContext,
40
+ options: LiveVoiceSessionOptions = {},
41
+ ) => {
42
+ captured = options;
43
+ return fakeSession() as never;
44
+ },
45
+ );
46
+ setLiveVoiceSessionManagerForTesting(null);
47
+
48
+ const manager = getLiveVoiceSessionManager();
49
+ const result = await manager.startSession(START_FRAME, {
50
+ sendFrame: mock(() => {}),
51
+ });
52
+ expect(result.status).toBe("accepted");
53
+ if (result.status !== "accepted") {
54
+ throw new Error("expected live voice session to be accepted");
55
+ }
56
+ await manager.releaseSession(result.sessionId, "client_end");
57
+ return captured ?? {};
58
+ }
59
+
60
+ afterEach(() => {
61
+ setModeSessionRecoveryHealthy(true);
62
+ setOverridesForTesting({});
63
+ setBundledLiveVoiceSessionFactory(null);
64
+ setLiveVoiceSessionManagerForTesting(null);
65
+ });
66
+
67
+ describe("live voice session-groups gate", () => {
68
+ test.each([false, true])(
69
+ "omits camera tracking after failed recovery with flag %s",
70
+ async (enabled) => {
71
+ setModeSessionRecoveryHealthy(false);
72
+ const options = await captureProductionOptions(enabled);
73
+ expect(options.acquireModeSessionResidency).toBeUndefined();
74
+ },
75
+ );
76
+ test("constructs a normal session without camera tracking while disabled", async () => {
77
+ const options = await captureProductionOptions(false);
78
+
79
+ expect(options.acquireModeSessionResidency).toBeUndefined();
80
+ });
81
+
82
+ test("injects camera tracking for an enabled assistant session", async () => {
83
+ const options = await captureProductionOptions(true);
84
+
85
+ expect(options.acquireModeSessionResidency).toBeFunction();
86
+ });
87
+ });
@@ -446,7 +446,11 @@ describe("LiveVoiceSessionManager", () => {
446
446
  (match) => match[1],
447
447
  );
448
448
 
449
- expect(imports).toEqual(["node:crypto", "./protocol.js"]);
449
+ expect(imports).toEqual([
450
+ "node:crypto",
451
+ "../subagent/parent-notification.js",
452
+ "./protocol.js",
453
+ ]);
450
454
  for (const importPath of imports) {
451
455
  expect(importPath).not.toContain("runtime");
452
456
  expect(importPath).not.toContain("gateway");
@@ -102,6 +102,7 @@ function createHarness() {
102
102
  });
103
103
 
104
104
  return {
105
+ conversation,
105
106
  conversationId: row.id,
106
107
  session,
107
108
  startVoiceTurn,
@@ -151,6 +152,46 @@ async function expectAnnouncement(
151
152
  }
152
153
 
153
154
  describe("standalone image echoes", () => {
155
+ test("a grouped keep echoes the same durable camera owner", async () => {
156
+ const harness = createHarness();
157
+ try {
158
+ const attachmentId = await uploadFrame();
159
+ const source = harness.conversation.modeSessions.activateSource({
160
+ sourceId: "camera:test",
161
+ generation: 1,
162
+ mode: "live_vision",
163
+ sourceStartedAt: Date.now(),
164
+ lifetime: "source",
165
+ });
166
+ expect(source).toBeDefined();
167
+ const modeSession = { id: source!.id, mode: source!.mode };
168
+ expect(
169
+ (
170
+ await persistAmbientSightFrame(
171
+ harness.conversationId,
172
+ attachmentId,
173
+ "voice",
174
+ undefined,
175
+ undefined,
176
+ source,
177
+ )
178
+ ).ok,
179
+ ).toBe(true);
180
+
181
+ await waitFor(() => harness.published.length >= 2);
182
+ const echo = UserMessageEchoEventSchema.parse(
183
+ harness.published[0]!.event.message,
184
+ );
185
+ expect(echo.modeSession).toEqual(modeSession);
186
+ const [row] = conversationCrud.getMessages(harness.conversationId);
187
+ expect(JSON.parse(row!.metadata ?? "{}").modeSession).toEqual(
188
+ modeSession,
189
+ );
190
+ } finally {
191
+ await harness.dispose();
192
+ }
193
+ });
194
+
154
195
  test.each(["sight_frame", "attach_image"] as const)(
155
196
  "%s emits one committed row before the history invalidation",
156
197
  async (type) => {