@vellumai/assistant 0.12.3-staging.1 → 0.12.3-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -0,0 +1,277 @@
1
+ import type { ModeSession, ModeSessionMode } from "../api/mode-session.js";
2
+ import type {
3
+ ConversationModeSessionCoordinator,
4
+ ModeSessionSourceHandle,
5
+ ModeSessionTerminalDisposition,
6
+ } from "../daemon/conversation-mode-session.js";
7
+ import type { LiveVoiceSightSource } from "./protocol.js";
8
+
9
+ type ModeSessionCoordinator = Pick<
10
+ ConversationModeSessionCoordinator,
11
+ | "activateSource"
12
+ | "claimTurn"
13
+ | "recordActivity"
14
+ | "releaseTurn"
15
+ | "retireSource"
16
+ | "trackPersistedRow"
17
+ >;
18
+
19
+ interface CameraRun {
20
+ readonly epoch: number;
21
+ readonly source: LiveVoiceSightSource;
22
+ readonly handle: ModeSessionSourceHandle;
23
+ readonly syntheticTurnId: string;
24
+ pendingFrames: number;
25
+ retired: boolean;
26
+ }
27
+
28
+ export interface CameraFrameToken {
29
+ readonly owner: ModeSessionSourceHandle;
30
+ readonly epoch: number;
31
+ readonly activation: number;
32
+ }
33
+
34
+ function modeFor(source: LiveVoiceSightSource): ModeSessionMode {
35
+ return source === "ambient" ? "ambient" : "live_vision";
36
+ }
37
+
38
+ /** Maps one voice session's negotiated camera epochs onto durable sessions. */
39
+ export class CameraModeSessionProducer {
40
+ readonly #coordinator: ModeSessionCoordinator;
41
+ readonly #sourceId: string;
42
+ readonly #runs = new Map<number, CameraRun>();
43
+ readonly #pendingFrameTokens = new Set<CameraFrameToken>();
44
+ #activeRun?: CameraRun;
45
+ #highestEpoch = 0;
46
+
47
+ constructor(coordinator: ModeSessionCoordinator, voiceSessionId: string) {
48
+ this.#coordinator = coordinator;
49
+ this.#sourceId = `live-voice-camera:${voiceSessionId}`;
50
+ }
51
+
52
+ start(
53
+ epoch: number,
54
+ source: LiveVoiceSightSource = "live",
55
+ at = Date.now(),
56
+ ): ModeSessionSourceHandle | undefined {
57
+ const known = this.#runs.get(epoch);
58
+ if (known) {
59
+ return !known.retired && known.source === source
60
+ ? known.handle
61
+ : undefined;
62
+ }
63
+ if (epoch <= this.#highestEpoch) {
64
+ return undefined;
65
+ }
66
+
67
+ if (this.#activeRun) {
68
+ this.#retire(this.#activeRun, {
69
+ status: "interrupted",
70
+ endReason: "camera_source_replaced",
71
+ });
72
+ }
73
+
74
+ const handle = this.#coordinator.activateSource({
75
+ sourceId: this.#sourceId,
76
+ generation: epoch,
77
+ mode: modeFor(source),
78
+ sourceStartedAt: at,
79
+ lifetime: "source",
80
+ });
81
+ if (!handle) {
82
+ return undefined;
83
+ }
84
+
85
+ const syntheticTurnId = `${this.#sourceId}:${epoch}:${handle.activation}`;
86
+ let owner: ModeSession | undefined;
87
+ try {
88
+ owner = this.#coordinator.claimTurn(syntheticTurnId, handle, at);
89
+ } catch (err) {
90
+ const rollbackError = this.#rollbackStart(handle, syntheticTurnId);
91
+ if (rollbackError) {
92
+ throw new AggregateError(
93
+ [err, rollbackError],
94
+ "Camera session claim and rollback both failed",
95
+ );
96
+ }
97
+ throw err;
98
+ }
99
+ if (!owner || owner.id !== handle.id) {
100
+ const rollbackError = this.#rollbackStart(handle, syntheticTurnId);
101
+ if (rollbackError) {
102
+ throw rollbackError;
103
+ }
104
+ return undefined;
105
+ }
106
+
107
+ const run: CameraRun = {
108
+ epoch,
109
+ source,
110
+ handle,
111
+ syntheticTurnId,
112
+ pendingFrames: 0,
113
+ retired: false,
114
+ };
115
+ this.#runs.set(epoch, run);
116
+ this.#highestEpoch = epoch;
117
+ this.#activeRun = run;
118
+ return handle;
119
+ }
120
+
121
+ #rollbackStart(
122
+ handle: ModeSessionSourceHandle,
123
+ syntheticTurnId: string,
124
+ ): unknown {
125
+ let rollbackError: unknown;
126
+ try {
127
+ this.#coordinator.retireSource(handle, {
128
+ status: "interrupted",
129
+ endReason: "camera_start_refused",
130
+ });
131
+ } catch (err) {
132
+ rollbackError = err;
133
+ }
134
+ try {
135
+ this.#coordinator.releaseTurn(syntheticTurnId);
136
+ } catch (err) {
137
+ rollbackError ??= err;
138
+ }
139
+ return rollbackError;
140
+ }
141
+
142
+ captureTurn(): ModeSessionSourceHandle | undefined {
143
+ return this.#activeRun?.handle;
144
+ }
145
+
146
+ holdDelivery(
147
+ turnId: string,
148
+ at = Date.now(),
149
+ ): ModeSessionSourceHandle | undefined {
150
+ const handle = this.captureTurn();
151
+ if (!handle) {
152
+ return undefined;
153
+ }
154
+ const owner = this.#coordinator.claimTurn(turnId, handle, at);
155
+ return owner?.id === handle.id ? handle : undefined;
156
+ }
157
+
158
+ releaseDelivery(turnId: string): void {
159
+ this.#coordinator.releaseTurn(turnId);
160
+ }
161
+
162
+ beginFrame(
163
+ epoch: number,
164
+ source: LiveVoiceSightSource | undefined,
165
+ ): CameraFrameToken | undefined {
166
+ const run = this.#activeRun;
167
+ if (
168
+ !run ||
169
+ run.retired ||
170
+ run.epoch !== epoch ||
171
+ (source !== undefined && source !== run.source)
172
+ ) {
173
+ return undefined;
174
+ }
175
+ run.pendingFrames += 1;
176
+ const token = {
177
+ owner: run.handle,
178
+ epoch: run.epoch,
179
+ activation: run.handle.activation,
180
+ };
181
+ this.#pendingFrameTokens.add(token);
182
+ return token;
183
+ }
184
+
185
+ finishFrame(
186
+ token: CameraFrameToken | undefined,
187
+ persisted?: { messageId: string; at: number },
188
+ ): void {
189
+ if (!token || !this.#pendingFrameTokens.delete(token)) {
190
+ return;
191
+ }
192
+ const run = this.#runs.get(token.epoch);
193
+ if (!run || run.handle.activation !== token.activation) {
194
+ return;
195
+ }
196
+ try {
197
+ if (
198
+ persisted &&
199
+ this.#coordinator.recordActivity(run.syntheticTurnId, persisted.at)
200
+ ) {
201
+ this.#coordinator.trackPersistedRow(
202
+ run.syntheticTurnId,
203
+ persisted.messageId,
204
+ persisted.at,
205
+ );
206
+ this.#coordinator.recordActivity(run.syntheticTurnId, persisted.at);
207
+ }
208
+ } finally {
209
+ run.pendingFrames = Math.max(0, run.pendingFrames - 1);
210
+ this.#releaseIfSettled(run);
211
+ }
212
+ }
213
+
214
+ end(
215
+ epoch: number,
216
+ disposition: ModeSessionTerminalDisposition = {
217
+ status: "completed",
218
+ endReason: "camera_stopped",
219
+ },
220
+ ): boolean {
221
+ const run = this.#runs.get(epoch);
222
+ if (!run || run.retired) {
223
+ return false;
224
+ }
225
+ return this.#retire(run, disposition);
226
+ }
227
+
228
+ close(disposition: ModeSessionTerminalDisposition): boolean {
229
+ const run = this.#activeRun;
230
+ return run ? this.#retire(run, disposition) : false;
231
+ }
232
+
233
+ #retire(
234
+ run: CameraRun,
235
+ disposition: ModeSessionTerminalDisposition,
236
+ ): boolean {
237
+ if (run.retired) {
238
+ return false;
239
+ }
240
+ let retired = false;
241
+ let retirementError: unknown;
242
+ try {
243
+ retired = this.#coordinator.retireSource(run.handle, disposition);
244
+ } catch (err) {
245
+ retirementError = err;
246
+ }
247
+ run.retired = true;
248
+ if (this.#activeRun === run) {
249
+ this.#activeRun = undefined;
250
+ }
251
+ let releaseError: unknown;
252
+ try {
253
+ this.#releaseIfSettled(run);
254
+ } catch (err) {
255
+ releaseError = err;
256
+ }
257
+ if (retirementError && releaseError) {
258
+ throw new AggregateError(
259
+ [retirementError, releaseError],
260
+ "Camera session retirement and turn release both failed",
261
+ );
262
+ }
263
+ if (retirementError) {
264
+ throw retirementError;
265
+ }
266
+ if (releaseError) {
267
+ throw releaseError;
268
+ }
269
+ return retired;
270
+ }
271
+
272
+ #releaseIfSettled(run: CameraRun): void {
273
+ if (run.retired && run.pendingFrames === 0) {
274
+ this.#coordinator.releaseTurn(run.syntheticTurnId);
275
+ }
276
+ }
277
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Decides whether a barged-in request should keep running as a background
3
+ * continuation, from what the caller said when they interrupted.
4
+ *
5
+ * A barge-in hands the interrupted turn's work to a background subagent so
6
+ * it is not lost. That is right when the caller moved on to something else
7
+ * ("oh, also, what's the weather?") or only acknowledged it ("okay, thanks"),
8
+ * and wrong when the interruption cancels or changes the request ("actually,
9
+ * just point it out for me"): the continuation then does work the caller
10
+ * no longer wants, while the foreground turn handles the changed request.
11
+ *
12
+ * The judge runs only when the `voiceContinuationJudge` call site resolves to
13
+ * the TypeSafe provider. Without one, the session never waits on it. Every
14
+ * failure keeps the continuation, which is the behavior without a judge.
15
+ */
16
+
17
+ import {
18
+ askTypesafeNoul,
19
+ resolveTypesafeProvider,
20
+ } from "../calls/typesafe-noul.js";
21
+ import type { Provider } from "../providers/types.js";
22
+
23
+ export const VOICE_CONTINUATION_JUDGE_CALL_SITE = "voiceContinuationJudge";
24
+
25
+ /**
26
+ * Minimum P(yes) that keeps the continuation. Calibrated on labeled
27
+ * interruptions: cancels, corrections, and repeats scored 0.05 to 0.21;
28
+ * unrelated requests, acknowledgements, and progress questions scored 0.56
29
+ * to 0.89.
30
+ */
31
+ export const CONTINUATION_JUDGE_KEEP_THRESHOLD = 0.4;
32
+
33
+ export const CONTINUATION_JUDGE_TIMEOUT_MS = 1_500;
34
+
35
+ const KEEP_QUESTION =
36
+ "The voice assistant was working on the caller's earlier request when the caller interrupted it. Should the assistant still finish that earlier request in the background, exactly as originally asked? Answer yes if the caller's new words are unrelated to it (a separate question, request, or remark), an acknowledgement, or a question about its progress. Answer no if the new words cancel it, change the caller's mind about it, correct or change what should be done, ask for it differently, or simply repeat it.";
37
+
38
+ export type ContinuationJudgeOutcome =
39
+ | "keep"
40
+ | "drop"
41
+ | "unavailable"
42
+ | "no_interruption"
43
+ | "timeout"
44
+ | "error";
45
+
46
+ export interface ContinuationJudgement {
47
+ keep: boolean;
48
+ outcome: ContinuationJudgeOutcome;
49
+ noul?: number;
50
+ latencyMs: number;
51
+ }
52
+
53
+ /**
54
+ * Judges one barge-in. `interruption` resolves with the caller's interrupting
55
+ * words once the next turn dispatches, or null when none arrived in time;
56
+ * the judge awaits it only when a TypeSafe profile is configured. Never
57
+ * rejects.
58
+ */
59
+ export type LiveVoiceContinuationJudge = (args: {
60
+ parentConversationId: string;
61
+ interruptedRequest: string;
62
+ interruption: Promise<string | null>;
63
+ signal: AbortSignal;
64
+ }) => Promise<ContinuationJudgement>;
65
+
66
+ export function createContinuationJudge(options?: {
67
+ /** Provider resolver, injectable for tests. */
68
+ resolveProvider?: () => Promise<Provider | null>;
69
+ timeoutMs?: number;
70
+ }): LiveVoiceContinuationJudge {
71
+ const resolveProvider =
72
+ options?.resolveProvider ??
73
+ (() => resolveTypesafeProvider(VOICE_CONTINUATION_JUDGE_CALL_SITE));
74
+ return async (args) => {
75
+ const startedAt = Date.now();
76
+ const kept = (
77
+ outcome: ContinuationJudgeOutcome,
78
+ noul?: number,
79
+ ): ContinuationJudgement => ({
80
+ keep: true,
81
+ outcome,
82
+ ...(noul !== undefined ? { noul } : {}),
83
+ latencyMs: Date.now() - startedAt,
84
+ });
85
+ let provider: Provider | null;
86
+ try {
87
+ provider = await resolveProvider();
88
+ } catch {
89
+ return kept("error");
90
+ }
91
+ if (!provider || args.interruptedRequest.trim().length === 0) {
92
+ return kept("unavailable");
93
+ }
94
+ const interruption = (await args.interruption)?.trim() ?? "";
95
+ if (interruption.length === 0) {
96
+ return kept("no_interruption");
97
+ }
98
+ const result = await askTypesafeNoul({
99
+ callSite: VOICE_CONTINUATION_JUDGE_CALL_SITE,
100
+ conversationId: args.parentConversationId,
101
+ state: {
102
+ interrupted_request: args.interruptedRequest.trim(),
103
+ caller_then_said: interruption,
104
+ },
105
+ instructions: KEEP_QUESTION,
106
+ timeoutMs: options?.timeoutMs ?? CONTINUATION_JUDGE_TIMEOUT_MS,
107
+ signal: args.signal,
108
+ resolveProvider: async () => provider,
109
+ });
110
+ if (result.outcome !== "answered" || result.noul === undefined) {
111
+ return kept(result.outcome === "answered" ? "error" : result.outcome);
112
+ }
113
+ if (result.noul >= CONTINUATION_JUDGE_KEEP_THRESHOLD) {
114
+ return kept("keep", result.noul);
115
+ }
116
+ return {
117
+ keep: false,
118
+ outcome: "drop",
119
+ noul: result.noul,
120
+ latencyMs: Date.now() - startedAt,
121
+ };
122
+ };
123
+ }
@@ -33,6 +33,9 @@ import type { LiveVoiceServerFramePayload } from "./protocol.js";
33
33
 
34
34
  const log = getLogger("live-activity-reporter");
35
35
 
36
+ /** A stale phase is preferable to letting one request pin every later phase. */
37
+ const LIVE_ACTIVITY_DISPATCH_TIMEOUT_MS = 5_000;
38
+
36
39
  /** The phases an iOS Live Activity can render. Mirrors the client's union. */
37
40
  export type LiveActivityPhase =
38
41
  | "connecting"
@@ -42,6 +45,12 @@ export type LiveActivityPhase =
42
45
  | "speaking"
43
46
  | "ending";
44
47
 
48
+ interface PendingDispatch {
49
+ phase: LiveActivityPhase;
50
+ event: "update" | "end";
51
+ detail: string;
52
+ }
53
+
45
54
  /**
46
55
  * The phase a frame puts the session into, or `null` for frames that do not
47
56
  * move it.
@@ -88,6 +97,10 @@ export function phaseForFrame(
88
97
  // Unconditional, unlike `stt_final`: the session sends this frame
89
98
  // precisely when it has committed to a turn.
90
99
  return "thinking";
100
+ case "activity":
101
+ // Keep model-routing detail inside the conversation, while ensuring a
102
+ // silent escalation leaves any preceding speaking phase behind.
103
+ return frame.kind === "escalation" ? "thinking" : null;
91
104
  case "tts_audio":
92
105
  return "speaking";
93
106
  case "tts_done":
@@ -111,6 +124,11 @@ export class LiveActivityReporter {
111
124
  private lastPhase: LiveActivityPhase | null = null;
112
125
  private lastDetail = "";
113
126
  private ended = false;
127
+ private dispatchInFlight = false;
128
+ private pendingDispatch: PendingDispatch | null = null;
129
+ private dispatchIdleWaiters: Array<() => void> = [];
130
+ protected readonly dispatchTimeoutMs: number =
131
+ LIVE_ACTIVITY_DISPATCH_TIMEOUT_MS;
114
132
 
115
133
  constructor(private readonly conversationId: string) {}
116
134
 
@@ -128,10 +146,14 @@ export class LiveActivityReporter {
128
146
  return;
129
147
  }
130
148
  const phase = phaseForFrame(frame, this.lastPhase);
131
- // The session sends this frame only on a change it wants surfaced, and
132
- // sends an empty label when the turn stops working, so it is taken
133
- // verbatim rather than derived.
134
- const detail = frame.type === "activity" ? frame.label : this.lastDetail;
149
+ // Tool and approval activity is server-owned wording. Escalation is an
150
+ // in-conversation status, so keep it out of the system-level Live Activity.
151
+ const detail =
152
+ frame.type === "activity"
153
+ ? frame.kind === "escalation"
154
+ ? ""
155
+ : frame.label
156
+ : this.lastDetail;
135
157
  const phaseMoved = phase !== null && phase !== this.lastPhase;
136
158
  if (!phaseMoved && detail === this.lastDetail) {
137
159
  return;
@@ -147,7 +169,19 @@ export class LiveActivityReporter {
147
169
  if (this.lastPhase === null) {
148
170
  return;
149
171
  }
150
- void this.dispatch(this.lastPhase, "update", this.lastDetail);
172
+ this.enqueueDispatch(this.lastPhase, "update", this.lastDetail);
173
+ }
174
+
175
+ /**
176
+ * Move the system surface past escalation bridge speech without replacing a
177
+ * tool or approval line that became active while that speech was draining.
178
+ */
179
+ restoreThinkingPhase(): void {
180
+ if (this.ended || this.lastPhase === "thinking") {
181
+ return;
182
+ }
183
+ this.lastPhase = "thinking";
184
+ this.enqueueDispatch("thinking", "update", this.lastDetail);
151
185
  }
152
186
 
153
187
  /**
@@ -166,7 +200,80 @@ export class LiveActivityReporter {
166
200
  // No detail on the way out: whatever the turn was doing, it is not doing
167
201
  // it any more, and this state is the one that lingers on the Lock Screen
168
202
  // through the dismissal window.
169
- void this.dispatch("ending", "end", "");
203
+ this.enqueueDispatch("ending", "end", "");
204
+ }
205
+
206
+ /**
207
+ * Preserve frame order across asynchronous platform requests without making
208
+ * the session wait for them. A later phase must never arrive before an older
209
+ * request finishes and then be overwritten by that older content state.
210
+ */
211
+ private enqueueDispatch(
212
+ phase: LiveActivityPhase,
213
+ event: "update" | "end",
214
+ detail: string,
215
+ ): void {
216
+ const pending = { phase, event, detail } satisfies PendingDispatch;
217
+ if (this.dispatchInFlight) {
218
+ // ActivityKit renders snapshots, so only the newest state behind the
219
+ // current request matters. In particular, `end` replaces every queued
220
+ // update and is always the next request sent.
221
+ this.pendingDispatch = pending;
222
+ return;
223
+ }
224
+ this.startDispatch(pending);
225
+ }
226
+
227
+ private startDispatch(pending: PendingDispatch): void {
228
+ this.dispatchInFlight = true;
229
+ const signal = AbortSignal.timeout(this.dispatchTimeoutMs);
230
+ const deadline = new Promise<void>((resolve) => {
231
+ if (signal.aborted) {
232
+ resolve();
233
+ return;
234
+ }
235
+ signal.addEventListener("abort", () => resolve(), { once: true });
236
+ });
237
+ // Race the whole operation, including platform-client and credential
238
+ // resolution. The same signal reaches `fetch`, so a timed-out operation
239
+ // that resolves credentials later cannot send its stale snapshot.
240
+ void Promise.race([
241
+ this.dispatch(pending.phase, pending.event, pending.detail, signal),
242
+ deadline,
243
+ ])
244
+ .catch((err: unknown) => {
245
+ // `dispatch` contains its own best-effort error boundary. Keep this
246
+ // guard for subclasses and future implementations so one rejection
247
+ // cannot poison every later phase in the queue.
248
+ log.debug(
249
+ { err, phase: pending.phase, event: pending.event },
250
+ "Live Activity dispatch queue failed",
251
+ );
252
+ })
253
+ .finally(() => {
254
+ const next = this.pendingDispatch;
255
+ this.pendingDispatch = null;
256
+ if (next !== null) {
257
+ this.startDispatch(next);
258
+ return;
259
+ }
260
+ this.dispatchInFlight = false;
261
+ const waiters = this.dispatchIdleWaiters;
262
+ this.dispatchIdleWaiters = [];
263
+ for (const resolve of waiters) {
264
+ resolve();
265
+ }
266
+ });
267
+ }
268
+
269
+ /** Resolve after every queued dispatch has settled. */
270
+ protected waitForPendingDispatches(): Promise<void> {
271
+ if (!this.dispatchInFlight) {
272
+ return Promise.resolve();
273
+ }
274
+ return new Promise((resolve) => {
275
+ this.dispatchIdleWaiters.push(resolve);
276
+ });
170
277
  }
171
278
 
172
279
  /** `protected` so a test can observe what would be sent without sending it. */
@@ -174,6 +281,7 @@ export class LiveActivityReporter {
174
281
  phase: LiveActivityPhase,
175
282
  event: "update" | "end",
176
283
  detail: string,
284
+ signal: AbortSignal,
177
285
  ): Promise<void> {
178
286
  try {
179
287
  const client = await VellumPlatformClient.create();
@@ -192,6 +300,7 @@ export class LiveActivityReporter {
192
300
  event,
193
301
  detail,
194
302
  }),
303
+ signal,
195
304
  });
196
305
  if (!response.ok) {
197
306
  log.debug(
@@ -11,6 +11,8 @@
11
11
 
12
12
  import { createRequire } from "node:module";
13
13
 
14
+ import { isSessionGroupsEnabled } from "../config/session-groups-gate.js";
15
+ import type { SubagentParentNotification } from "../subagent/parent-notification.js";
14
16
  import { getLogger } from "../util/logger.js";
15
17
  import { LiveVoiceSessionManager } from "./live-voice-session-manager.js";
16
18
 
@@ -24,6 +26,15 @@ type LiveVoiceSessionFactory =
24
26
  let manager: LiveVoiceSessionManager | null = null;
25
27
  let bundledSessionFactory: LiveVoiceSessionFactory | null = null;
26
28
 
29
+ export function deliverSubagentNotificationToLiveVoice(
30
+ conversationId: string,
31
+ notification: SubagentParentNotification,
32
+ ): boolean {
33
+ return (
34
+ manager?.deliverSubagentNotification(conversationId, notification) ?? false
35
+ );
36
+ }
37
+
27
38
  export function setBundledLiveVoiceSessionFactory(
28
39
  factory: LiveVoiceSessionFactory | null,
29
40
  ): void {
@@ -50,7 +61,31 @@ export function getLiveVoiceSessionManager(): LiveVoiceSessionManager {
50
61
  (
51
62
  require("./live-voice-session.js") as typeof import("./live-voice-session.js")
52
63
  ).createLiveVoiceSession;
53
- return createSession(context);
64
+ const modeSessionOptions = isSessionGroupsEnabled()
65
+ ? {
66
+ acquireModeSessionResidency: async (conversationId: string) => {
67
+ const { ensureConversationExists } =
68
+ require("../persistence/conversation-crud.js") as typeof import("../persistence/conversation-crud.js");
69
+ const { publishConversationListAndMetadataChanged } =
70
+ require("../runtime/sync/resource-sync-events.js") as typeof import("../runtime/sync/resource-sync-events.js");
71
+ if (ensureConversationExists(conversationId, "vellum")) {
72
+ publishConversationListAndMetadataChanged(
73
+ "created",
74
+ conversationId,
75
+ );
76
+ }
77
+ const { getOrCreateConversation } =
78
+ require("../daemon/conversation-store.js") as typeof import("../daemon/conversation-store.js");
79
+ const conversation =
80
+ await getOrCreateConversation(conversationId);
81
+ return {
82
+ coordinator: conversation.modeSessions,
83
+ release: conversation.acquireLiveVoiceResidency(),
84
+ };
85
+ },
86
+ }
87
+ : {};
88
+ return createSession(context, modeSessionOptions);
54
89
  },
55
90
  // The manager reclaims a slot on its own only when a session stopped
56
91
  // behaving: either its teardown overran the close budget, or its client