@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,111 @@
1
+ /**
2
+ * The gateway's replies to messages it answered itself reach the person
3
+ * through the channel transport, the same one a turn's reply uses.
4
+ *
5
+ * The Bot API call is the boundary: everything between the gateway's request
6
+ * and Telegram's `sendMessage` is the real code, so the assertion is that the
7
+ * text a person would read left for their chat, in the topic they wrote from.
8
+ */
9
+
10
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ import { DELIVER_GATEWAY_REPLY_IPC_METHOD } from "@vellumai/gateway-client";
13
+
14
+ type CallTelegramBotApi =
15
+ typeof import("../../../messaging/providers/telegram-bot/api.js").callTelegramBotApi;
16
+
17
+ const callTelegramBotApiMock = mock<CallTelegramBotApi>(
18
+ async () => ({ message_id: 42 }) as never,
19
+ );
20
+
21
+ // Spread the actual module so exports this suite does not touch stay
22
+ // importable by files that share the bun process.
23
+ const actualTelegramApi =
24
+ await import("../../../messaging/providers/telegram-bot/api.js");
25
+ mock.module("../../../messaging/providers/telegram-bot/api.js", () => ({
26
+ ...actualTelegramApi,
27
+ callTelegramBotApi: (method: string, body: Record<string, unknown>) =>
28
+ callTelegramBotApiMock(method, body),
29
+ }));
30
+
31
+ const { CHANNEL_REPLY_IPC_METHODS } =
32
+ await import("../channel-reply-ipc-routes.js");
33
+ const { BadGatewayError, BadRequestError } =
34
+ await import("../../../runtime/routes/errors.js");
35
+
36
+ const deliverGatewayReply =
37
+ CHANNEL_REPLY_IPC_METHODS[DELIVER_GATEWAY_REPLY_IPC_METHOD]!;
38
+
39
+ // The callback URL exactly as the gateway's Telegram webhook builds it for a
40
+ // message sent in a topic.
41
+ const TELEGRAM_CALLBACK = "http://127.0.0.1:7830/deliver/telegram?threadId=7";
42
+
43
+ beforeEach(() => {
44
+ callTelegramBotApiMock.mockClear();
45
+ });
46
+
47
+ describe("deliver_gateway_reply", () => {
48
+ test("sends the reply to the person's Telegram chat, in their topic", async () => {
49
+ const result = await deliverGatewayReply({
50
+ body: {
51
+ callbackUrl: TELEGRAM_CALLBACK,
52
+ chatId: "12345",
53
+ text: "Welcome! You've been granted access.",
54
+ assistantId: "self",
55
+ },
56
+ });
57
+
58
+ const sends = callTelegramBotApiMock.mock.calls.filter(
59
+ ([method]) => method === "sendMessage",
60
+ );
61
+ expect(sends).toHaveLength(1);
62
+ expect(sends[0]![1]).toMatchObject({
63
+ chat_id: "12345",
64
+ text: "Welcome! You've been granted access.",
65
+ message_thread_id: 7,
66
+ });
67
+ expect(result).toEqual({ ok: true, messageIds: ["42"] });
68
+ });
69
+
70
+ test("answers a send the channel refuses with a status, not a bare error", async () => {
71
+ // Only a RouteError crosses IPC with a status; without one the gateway
72
+ // reads the refusal as a daemon that never answered.
73
+ callTelegramBotApiMock.mockImplementationOnce(async () => {
74
+ throw new Error("Bad Request: chat not found");
75
+ });
76
+
77
+ const rejection = expect(
78
+ deliverGatewayReply({
79
+ body: {
80
+ callbackUrl: TELEGRAM_CALLBACK,
81
+ chatId: "12345",
82
+ text: "Welcome! You've been granted access.",
83
+ },
84
+ }),
85
+ ).rejects;
86
+ await rejection.toBeInstanceOf(BadGatewayError);
87
+ await rejection.toMatchObject({ statusCode: 502 });
88
+ });
89
+
90
+ test("refuses a callback no channel transport owns, sending nothing", async () => {
91
+ await expect(
92
+ deliverGatewayReply({
93
+ body: {
94
+ callbackUrl: "http://127.0.0.1:7830/deliver/email",
95
+ chatId: "someone@example.com",
96
+ text: "Verification successful.",
97
+ },
98
+ }),
99
+ ).rejects.toBeInstanceOf(BadRequestError);
100
+ expect(callTelegramBotApiMock).not.toHaveBeenCalled();
101
+ });
102
+
103
+ test("refuses a request with no text, sending nothing", async () => {
104
+ await expect(
105
+ deliverGatewayReply({
106
+ body: { callbackUrl: TELEGRAM_CALLBACK, chatId: "12345", text: "" },
107
+ }),
108
+ ).rejects.toBeInstanceOf(BadRequestError);
109
+ expect(callTelegramBotApiMock).not.toHaveBeenCalled();
110
+ });
111
+ });
@@ -0,0 +1,73 @@
1
+ /**
2
+ * IPC-only channel reply method called by the gateway over the assistant IPC
3
+ * socket (`ipcCallAssistant`).
4
+ *
5
+ * The gateway answers some inbound messages itself without forwarding them
6
+ * (a verification code, an invite redemption), so the assistant never sees
7
+ * them. Its reply still has to reach the person, and the channel transports
8
+ * that can send it live here. This hands the reply to the transport the
9
+ * inbound message's callback URL names, which is how the daemon's own
10
+ * intercepts answer an inbound message: the gateway sends through the same
11
+ * transports and never to a provider itself.
12
+ *
13
+ * Nothing is recorded in a conversation. The message the reply answers never
14
+ * entered one, so a row for the reply would stand alone in a transcript that
15
+ * does not hold what it responds to.
16
+ */
17
+
18
+ import {
19
+ DELIVER_GATEWAY_REPLY_IPC_METHOD,
20
+ GatewayReplyRequestSchema,
21
+ } from "@vellumai/gateway-client";
22
+
23
+ import {
24
+ deliverDirect,
25
+ isDirectDelivery,
26
+ } from "../../messaging/providers/index.js";
27
+ import {
28
+ BadGatewayError,
29
+ BadRequestError,
30
+ } from "../../runtime/routes/errors.js";
31
+ import { parseBody } from "../../runtime/routes/parse-body.js";
32
+ import type { RouteHandlerArgs } from "../../runtime/routes/types.js";
33
+
34
+ /**
35
+ * Deliver a gateway-composed reply through the channel transport its callback
36
+ * URL names. Refuses a callback no transport owns rather than fetching it, so
37
+ * the gateway learns the channel cannot be answered this way.
38
+ *
39
+ * A send the channel refuses is answered as a `BadGatewayError`, as
40
+ * `channels/send` answers it: only a `RouteError` carries a status over IPC,
41
+ * and the gateway reads an error without one as a daemon that never answered.
42
+ */
43
+ export async function handleDeliverGatewayReply({
44
+ body = {},
45
+ }: RouteHandlerArgs) {
46
+ const { callbackUrl, chatId, text, assistantId } = parseBody(
47
+ GatewayReplyRequestSchema,
48
+ body,
49
+ );
50
+ if (!isDirectDelivery(callbackUrl)) {
51
+ throw new BadRequestError("No channel transport owns this callback URL");
52
+ }
53
+ try {
54
+ return await deliverDirect(callbackUrl, {
55
+ chatId,
56
+ text,
57
+ ...(assistantId ? { assistantId } : {}),
58
+ });
59
+ } catch (err) {
60
+ throw new BadGatewayError(err instanceof Error ? err.message : String(err));
61
+ }
62
+ }
63
+
64
+ /**
65
+ * IPC-only channel reply methods, keyed by IPC operationId. Registered
66
+ * directly on the assistant IPC server (see `assistant-server.ts`).
67
+ */
68
+ export const CHANNEL_REPLY_IPC_METHODS: Record<
69
+ string,
70
+ (args: RouteHandlerArgs) => unknown
71
+ > = {
72
+ [DELIVER_GATEWAY_REPLY_IPC_METHOD]: handleDeliverGatewayReply,
73
+ };
@@ -0,0 +1,151 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type {
4
+ Message,
5
+ Provider,
6
+ ProviderResponse,
7
+ SendMessageOptions,
8
+ } from "../../providers/types.js";
9
+ import {
10
+ CONTINUATION_JUDGE_KEEP_THRESHOLD,
11
+ createContinuationJudge,
12
+ VOICE_CONTINUATION_JUDGE_CALL_SITE,
13
+ } from "../continuation-judge.js";
14
+
15
+ function jevResponse(noul: number): ProviderResponse {
16
+ const answers = { answer: { type: "noul", noul } };
17
+ return {
18
+ content: [{ type: "text", text: JSON.stringify(answers) }],
19
+ model: "jev-latest",
20
+ usage: { inputTokens: 1, outputTokens: 1 },
21
+ stopReason: "end_turn",
22
+ rawResponse: { model: "jev-latest", answers },
23
+ };
24
+ }
25
+
26
+ function fakeProvider(
27
+ respond: (
28
+ messages: Message[],
29
+ options?: SendMessageOptions,
30
+ ) => Promise<ProviderResponse>,
31
+ ): Provider {
32
+ return { name: "typesafe", sendMessage: respond } as Provider;
33
+ }
34
+
35
+ const baseArgs = {
36
+ parentConversationId: "conv-1",
37
+ interruptedRequest: "Can you hit join now for me?",
38
+ signal: new AbortController().signal,
39
+ };
40
+
41
+ describe("createContinuationJudge", () => {
42
+ test("drops below the threshold and asks with both utterances", async () => {
43
+ let seen: { body: unknown; options?: SendMessageOptions } | null = null;
44
+ const judge = createContinuationJudge({
45
+ resolveProvider: async () =>
46
+ fakeProvider(async (messages, options) => {
47
+ const block = messages[0]!.content[0]!;
48
+ seen = {
49
+ body: JSON.parse(block.type === "text" ? block.text : ""),
50
+ options,
51
+ };
52
+ return jevResponse(0.09);
53
+ }),
54
+ });
55
+
56
+ const judgement = await judge({
57
+ ...baseArgs,
58
+ interruption: Promise.resolve("Actually just point it out for me."),
59
+ });
60
+
61
+ expect(judgement).toMatchObject({
62
+ keep: false,
63
+ outcome: "drop",
64
+ noul: 0.09,
65
+ });
66
+ const request = seen as unknown as {
67
+ body: { state: Record<string, string> };
68
+ options: SendMessageOptions;
69
+ };
70
+ expect(request.body.state).toEqual({
71
+ interrupted_request: "Can you hit join now for me?",
72
+ caller_then_said: "Actually just point it out for me.",
73
+ });
74
+ expect(
75
+ (request.options.config as { callSite?: string } | undefined)?.callSite,
76
+ ).toBe(VOICE_CONTINUATION_JUDGE_CALL_SITE);
77
+ });
78
+
79
+ test("keeps at or above the threshold", async () => {
80
+ const judge = createContinuationJudge({
81
+ resolveProvider: async () =>
82
+ fakeProvider(async () =>
83
+ jevResponse(CONTINUATION_JUDGE_KEEP_THRESHOLD),
84
+ ),
85
+ });
86
+
87
+ const judgement = await judge({
88
+ ...baseArgs,
89
+ interruption: Promise.resolve("Oh, also, what's the weather?"),
90
+ });
91
+
92
+ expect(judgement).toMatchObject({ keep: true, outcome: "keep" });
93
+ });
94
+
95
+ test("without a TypeSafe provider it keeps and never waits on the interruption", async () => {
96
+ const judge = createContinuationJudge({
97
+ resolveProvider: async () => null,
98
+ });
99
+
100
+ const judgement = await judge({
101
+ ...baseArgs,
102
+ // Never settles: the judge must not await it.
103
+ interruption: new Promise<string | null>(() => {}),
104
+ });
105
+
106
+ expect(judgement).toMatchObject({ keep: true, outcome: "unavailable" });
107
+ });
108
+
109
+ test("keeps when no interruption arrived in time", async () => {
110
+ let asked = false;
111
+ const judge = createContinuationJudge({
112
+ resolveProvider: async () =>
113
+ fakeProvider(async () => {
114
+ asked = true;
115
+ return jevResponse(0.05);
116
+ }),
117
+ });
118
+
119
+ const judgement = await judge({
120
+ ...baseArgs,
121
+ interruption: Promise.resolve(null),
122
+ });
123
+
124
+ expect(judgement).toMatchObject({
125
+ keep: true,
126
+ outcome: "no_interruption",
127
+ });
128
+ expect(asked).toBe(false);
129
+ });
130
+
131
+ test("a provider error or timeout keeps the continuation", async () => {
132
+ const failing = createContinuationJudge({
133
+ resolveProvider: async () =>
134
+ fakeProvider(async () => {
135
+ throw new Error("503");
136
+ }),
137
+ });
138
+ const hanging = createContinuationJudge({
139
+ timeoutMs: 20,
140
+ resolveProvider: async () =>
141
+ fakeProvider(async () => new Promise<ProviderResponse>(() => {})),
142
+ });
143
+
144
+ expect(
145
+ await failing({ ...baseArgs, interruption: Promise.resolve("stop") }),
146
+ ).toMatchObject({ keep: true, outcome: "error" });
147
+ expect(
148
+ await hanging({ ...baseArgs, interruption: Promise.resolve("stop") }),
149
+ ).toMatchObject({ keep: true, outcome: "timeout" });
150
+ });
151
+ });
@@ -37,6 +37,10 @@ class RecordingReporter extends LiveActivityReporter {
37
37
  readonly dispatched: Array<{ phase: string; event: string; detail: string }> =
38
38
  [];
39
39
 
40
+ waitForDispatches(): Promise<void> {
41
+ return this.waitForPendingDispatches();
42
+ }
43
+
40
44
  protected override async dispatch(
41
45
  phase: string,
42
46
  event: "update" | "end",
@@ -46,11 +50,55 @@ class RecordingReporter extends LiveActivityReporter {
46
50
  }
47
51
  }
48
52
 
49
- function activity(label: string): LiveVoiceServerFramePayload {
53
+ class BlockingReporter extends LiveActivityReporter {
54
+ readonly dispatched: string[] = [];
55
+ private releaseFirstDispatch: (() => void) | null = null;
56
+ private readonly firstDispatch = new Promise<void>((resolve) => {
57
+ this.releaseFirstDispatch = resolve;
58
+ });
59
+
60
+ releaseFirst(): void {
61
+ this.releaseFirstDispatch?.();
62
+ }
63
+
64
+ waitForDispatches(): Promise<void> {
65
+ return this.waitForPendingDispatches();
66
+ }
67
+
68
+ protected override async dispatch(phase: string): Promise<void> {
69
+ this.dispatched.push(phase);
70
+ if (this.dispatched.length === 1) {
71
+ await this.firstDispatch;
72
+ }
73
+ }
74
+ }
75
+
76
+ class TimeoutReporter extends LiveActivityReporter {
77
+ readonly dispatched: string[] = [];
78
+ protected override readonly dispatchTimeoutMs = 1;
79
+
80
+ waitForDispatches(): Promise<void> {
81
+ return this.waitForPendingDispatches();
82
+ }
83
+
84
+ protected override async dispatch(phase: string): Promise<void> {
85
+ this.dispatched.push(phase);
86
+ if (this.dispatched.length > 1) {
87
+ return;
88
+ }
89
+ await new Promise<void>(() => undefined);
90
+ }
91
+ }
92
+
93
+ function activity(
94
+ label: string,
95
+ kind?: "escalation",
96
+ ): LiveVoiceServerFramePayload {
50
97
  return {
51
98
  type: "activity",
52
99
  turnId: "t1",
53
100
  label,
101
+ ...(kind ? { kind } : {}),
54
102
  } as LiveVoiceServerFramePayload;
55
103
  }
56
104
 
@@ -61,6 +109,9 @@ describe("phaseForFrame", () => {
61
109
  );
62
110
  expect(phaseForFrame(frame("thinking"), "transcribing")).toBe("thinking");
63
111
  expect(phaseForFrame(frame("tts_audio"), "thinking")).toBe("speaking");
112
+ expect(
113
+ phaseForFrame(activity("Working on that", "escalation"), "speaking"),
114
+ ).toBe("thinking");
64
115
  });
65
116
 
66
117
  test("a discarded utterance returns the floor to the user", () => {
@@ -96,10 +147,11 @@ describe("phaseForFrame", () => {
96
147
  });
97
148
 
98
149
  describe("LiveActivityReporter", () => {
99
- test("reports a phase change once", () => {
150
+ test("reports a phase change once", async () => {
100
151
  const reporter = new RecordingReporter("conv-1");
101
152
 
102
153
  reporter.report(frame("thinking"));
154
+ await reporter.waitForDispatches();
103
155
 
104
156
  expect(reporter.dispatched).toEqual([
105
157
  { phase: "thinking", event: "update", detail: "" },
@@ -107,26 +159,31 @@ describe("LiveActivityReporter", () => {
107
159
  });
108
160
 
109
161
  // The whole reason this class exists rather than a bare fetch per frame.
110
- test("does not re-report a phase it is already in", () => {
162
+ test("does not re-report a phase it is already in", async () => {
111
163
  const reporter = new RecordingReporter("conv-1");
112
164
 
113
165
  reporter.report(frame("tts_audio"));
114
166
  reporter.report(frame("tts_audio"));
115
167
  reporter.report(frame("tts_audio"));
168
+ await reporter.waitForDispatches();
116
169
 
117
170
  expect(reporter.dispatched).toEqual([
118
171
  { phase: "speaking", event: "update", detail: "" },
119
172
  ]);
120
173
  });
121
174
 
122
- test("reports each genuine transition through a turn", () => {
175
+ test("reports each genuine transition through a turn", async () => {
123
176
  const reporter = new RecordingReporter("conv-1");
124
177
 
125
178
  reporter.report(frame("utterance_end"));
179
+ await reporter.waitForDispatches();
126
180
  reporter.report(sttFinal("hello there"));
181
+ await reporter.waitForDispatches();
127
182
  reporter.report(frame("tts_audio"));
128
183
  reporter.report(frame("tts_audio"));
184
+ await reporter.waitForDispatches();
129
185
  reporter.report(frame("tts_done"));
186
+ await reporter.waitForDispatches();
130
187
 
131
188
  expect(reporter.dispatched.map((d) => d.phase)).toEqual([
132
189
  "transcribing",
@@ -136,20 +193,63 @@ describe("LiveActivityReporter", () => {
136
193
  ]);
137
194
  });
138
195
 
139
- test("frames that do not move the phase are ignored", () => {
196
+ test("serializes platform dispatches in frame order", async () => {
197
+ const reporter = new BlockingReporter("conv-1");
198
+
199
+ reporter.report(frame("tts_audio"));
200
+ reporter.report(activity("", "escalation"));
201
+ await Promise.resolve();
202
+
203
+ expect(reporter.dispatched).toEqual(["speaking"]);
204
+
205
+ reporter.releaseFirst();
206
+ await reporter.waitForDispatches();
207
+ expect(reporter.dispatched).toEqual(["speaking", "thinking"]);
208
+ });
209
+
210
+ test("a timed-out dispatch does not pin later phases", async () => {
211
+ const reporter = new TimeoutReporter("conv-1");
212
+
213
+ reporter.report(frame("tts_audio"));
214
+ reporter.report(activity("", "escalation"));
215
+ await reporter.waitForDispatches();
216
+
217
+ expect(reporter.dispatched).toEqual(["speaking", "thinking"]);
218
+ });
219
+
220
+ test("coalesces a queued backlog and keeps end next", async () => {
221
+ const reporter = new BlockingReporter("conv-1");
222
+
223
+ reporter.report(frame("utterance_end"));
224
+ reporter.report(frame("thinking"));
225
+ reporter.report(activity("Running a command"));
226
+ reporter.report(frame("tts_audio"));
227
+ reporter.end();
228
+ await Promise.resolve();
229
+
230
+ expect(reporter.dispatched).toEqual(["transcribing"]);
231
+
232
+ reporter.releaseFirst();
233
+ await reporter.waitForDispatches();
234
+ expect(reporter.dispatched).toEqual(["transcribing", "ending"]);
235
+ });
236
+
237
+ test("frames that do not move the phase are ignored", async () => {
140
238
  const reporter = new RecordingReporter("conv-1");
141
239
 
142
240
  reporter.report(frame("stt_partial"));
143
241
  reporter.report(frame("ready"));
242
+ await reporter.waitForDispatches();
144
243
 
145
244
  expect(reporter.dispatched).toEqual([]);
146
245
  });
147
246
 
148
- test("ending retires the activity", () => {
247
+ test("ending retires the activity", async () => {
149
248
  const reporter = new RecordingReporter("conv-1");
150
249
  reporter.report(frame("tts_audio"));
151
250
 
152
251
  reporter.end();
252
+ await reporter.waitForDispatches();
153
253
 
154
254
  expect(reporter.dispatched.at(-1)).toEqual({
155
255
  phase: "ending",
@@ -161,20 +261,22 @@ describe("LiveActivityReporter", () => {
161
261
  // A session can end more than one way and the platform deletes the
162
262
  // registration on the first, so a second end would push at an activity that
163
263
  // is already gone.
164
- test("ending twice pushes once", () => {
264
+ test("ending twice pushes once", async () => {
165
265
  const reporter = new RecordingReporter("conv-1");
166
266
 
167
267
  reporter.end();
168
268
  reporter.end();
269
+ await reporter.waitForDispatches();
169
270
 
170
271
  expect(reporter.dispatched).toHaveLength(1);
171
272
  });
172
273
 
173
- test("a frame after the end is not reported", () => {
274
+ test("a frame after the end is not reported", async () => {
174
275
  const reporter = new RecordingReporter("conv-1");
175
276
 
176
277
  reporter.end();
177
278
  reporter.report(frame("tts_audio"));
279
+ await reporter.waitForDispatches();
178
280
 
179
281
  expect(reporter.dispatched).toEqual([
180
282
  { phase: "ending", event: "end", detail: "" },
@@ -183,12 +285,26 @@ describe("LiveActivityReporter", () => {
183
285
  });
184
286
 
185
287
  describe("LiveActivityReporter activity line", () => {
186
- test("carries the activity label alongside the phase it holds", () => {
288
+ test("keeps escalation out of the Live Activity detail line", async () => {
289
+ const reporter = new RecordingReporter("conv-1");
290
+
291
+ reporter.report(frame("tts_audio"));
292
+ reporter.report(activity("Working on that", "escalation"));
293
+ await reporter.waitForDispatches();
294
+
295
+ expect(reporter.dispatched).toEqual([
296
+ { phase: "speaking", event: "update", detail: "" },
297
+ { phase: "thinking", event: "update", detail: "" },
298
+ ]);
299
+ });
300
+
301
+ test("carries the activity label alongside the phase it holds", async () => {
187
302
  const reporter = new RecordingReporter("conv-1");
188
303
 
189
304
  reporter.report(frame("utterance_end"));
190
305
  reporter.report(frame("thinking"));
191
306
  reporter.report(activity("Running a command"));
307
+ await reporter.waitForDispatches();
192
308
 
193
309
  expect(reporter.dispatched.at(-1)).toEqual({
194
310
  phase: "thinking",
@@ -199,12 +315,13 @@ describe("LiveActivityReporter activity line", () => {
199
315
 
200
316
  // A push replaces the whole content state, so the label has to ride along on
201
317
  // phase changes too, or the next phase would blank it.
202
- test("keeps the label through a later phase change", () => {
318
+ test("keeps the label through a later phase change", async () => {
203
319
  const reporter = new RecordingReporter("conv-1");
204
320
 
205
321
  reporter.report(frame("thinking"));
206
322
  reporter.report(activity("Reading a file"));
207
323
  reporter.report(frame("tts_audio"));
324
+ await reporter.waitForDispatches();
208
325
 
209
326
  expect(reporter.dispatched.at(-1)).toEqual({
210
327
  phase: "speaking",
@@ -213,36 +330,56 @@ describe("LiveActivityReporter activity line", () => {
213
330
  });
214
331
  });
215
332
 
216
- test("an empty label clears the line", () => {
333
+ test("restores thinking after bridge speech without clearing an overlay", async () => {
334
+ const reporter = new RecordingReporter("conv-1");
335
+
336
+ reporter.report(frame("thinking"));
337
+ reporter.report(frame("tts_audio"));
338
+ reporter.report(activity("Searching the web"));
339
+ reporter.restoreThinkingPhase();
340
+ await reporter.waitForDispatches();
341
+
342
+ expect(reporter.dispatched.at(-1)).toEqual({
343
+ phase: "thinking",
344
+ event: "update",
345
+ detail: "Searching the web",
346
+ });
347
+ });
348
+
349
+ test("an empty label clears the line", async () => {
217
350
  const reporter = new RecordingReporter("conv-1");
218
351
 
219
352
  reporter.report(frame("thinking"));
220
353
  reporter.report(activity("Reading a file"));
221
354
  reporter.report(activity(""));
355
+ await reporter.waitForDispatches();
222
356
 
223
357
  expect(reporter.dispatched.at(-1)?.detail).toBe("");
224
358
  });
225
359
 
226
- test("does not dispatch a label it already sent", () => {
360
+ test("does not dispatch a label it already sent", async () => {
227
361
  const reporter = new RecordingReporter("conv-1");
228
362
 
229
363
  reporter.report(frame("thinking"));
230
364
  reporter.report(activity("Reading a file"));
365
+ await reporter.waitForDispatches();
231
366
  const before = reporter.dispatched.length;
232
367
  reporter.report(activity("Reading a file"));
368
+ await reporter.waitForDispatches();
233
369
 
234
370
  expect(reporter.dispatched.length).toBe(before);
235
371
  });
236
372
 
237
373
  // A tool can start before any phase-bearing frame lands. There is no content
238
374
  // state to attach a label to yet, and the phase that follows carries it.
239
- test("holds a label that arrives before any phase", () => {
375
+ test("holds a label that arrives before any phase", async () => {
240
376
  const reporter = new RecordingReporter("conv-1");
241
377
 
242
378
  reporter.report(activity("Searching the web"));
243
379
  expect(reporter.dispatched).toEqual([]);
244
380
 
245
381
  reporter.report(frame("thinking"));
382
+ await reporter.waitForDispatches();
246
383
 
247
384
  expect(reporter.dispatched).toEqual([
248
385
  { phase: "thinking", event: "update", detail: "Searching the web" },
@@ -253,19 +390,21 @@ describe("LiveActivityReporter activity line", () => {
253
390
  describe("LiveActivityReporter and held utterances", () => {
254
391
  // End to end through the reporter, not just the mapping: a session using
255
392
  // semantic endpointing emits finals while the user still holds the floor.
256
- test("a final during a held utterance pushes nothing", () => {
393
+ test("a final during a held utterance pushes nothing", async () => {
257
394
  const reporter = new RecordingReporter("conv-1");
258
395
 
259
396
  reporter.report(sttFinal("i was thinking maybe"));
397
+ await reporter.waitForDispatches();
260
398
 
261
399
  expect(reporter.dispatched).toEqual([]);
262
400
  });
263
401
 
264
- test("the same final after utterance_end advances to thinking", () => {
402
+ test("the same final after utterance_end advances to thinking", async () => {
265
403
  const reporter = new RecordingReporter("conv-1");
266
404
 
267
405
  reporter.report(frame("utterance_end"));
268
406
  reporter.report(sttFinal("i was thinking maybe"));
407
+ await reporter.waitForDispatches();
269
408
 
270
409
  expect(reporter.dispatched.map((d) => d.phase)).toEqual([
271
410
  "transcribing",