@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
@@ -6,10 +6,10 @@
6
6
  * has to hold here: `isProcessing()` reads false in the window between a turn
7
7
  * releasing and its queued successor being dispatched.
8
8
  *
9
- * In-flight subagents are the other non-idle case: an async spawn leaves the
10
- * parent idle between its own tool calls while children are still running.
11
- * Reload marks that parent stale without aborting the children, then
12
- * `getOrCreateConversation` rebuilds it once every child is terminal.
9
+ * In-flight subagents and resident mode-session state are other non-idle
10
+ * cases. Reload marks the conversation stale without discarding that state,
11
+ * then `getOrCreateConversation` rebuilds it once every retained lifecycle is
12
+ * settled.
13
13
  */
14
14
  import { beforeEach, describe, expect, mock, test } from "bun:test";
15
15
 
@@ -26,7 +26,7 @@ mock.module("../subagent/index.js", () => ({
26
26
  }),
27
27
  }));
28
28
 
29
- import type { Conversation } from "../daemon/conversation.js";
29
+ import { Conversation } from "../daemon/conversation.js";
30
30
  import {
31
31
  clearConversations,
32
32
  conversationIds,
@@ -45,17 +45,29 @@ interface FakeConversation {
45
45
 
46
46
  function register(
47
47
  id: string,
48
- state: { processing: boolean; queued: boolean; stale?: boolean },
48
+ state: {
49
+ processing: boolean;
50
+ queued: boolean;
51
+ modeSessionWork?: boolean;
52
+ stale?: boolean;
53
+ },
49
54
  ): FakeConversation {
50
55
  const fake: FakeConversation & Record<string, unknown> = {
51
56
  disposed: false,
52
57
  markedStale: false,
53
58
  conversationId: id,
59
+ liveVoiceResidencyLeases: 0,
54
60
  isProcessing: () => state.processing,
55
61
  hasQueuedMessages: () => state.queued,
62
+ modeSessions: {
63
+ hasResidentWork: () => state.modeSessionWork === true,
64
+ },
56
65
  isStale: () => state.stale === true || fake.markedStale,
57
- hasInFlightWork: () =>
58
- state.processing || state.queued || activeParents.has(id),
66
+ hasInFlightWork() {
67
+ return Conversation.prototype.hasInFlightWork.call(
68
+ fake as unknown as Conversation,
69
+ );
70
+ },
59
71
  dispose() {
60
72
  fake.disposed = true;
61
73
  },
@@ -125,6 +137,43 @@ describe("evictConversationsForReload", () => {
125
137
  expect(abortedParents).toEqual([]);
126
138
  });
127
139
 
140
+ test("keeps resident mode-session ownership usable across reload eviction", () => {
141
+ const resident = register("reload-mode-active", {
142
+ processing: false,
143
+ queued: false,
144
+ modeSessionWork: true,
145
+ });
146
+
147
+ evictConversationsForReload();
148
+
149
+ expect(resident.disposed).toBe(false);
150
+ expect(resident.markedStale).toBe(true);
151
+ expect(findConversation("reload-mode-active")).toBeDefined();
152
+ expect(abortedParents).toEqual([]);
153
+ });
154
+
155
+ test("keeps a Live voice residency lease until the socket releases it", () => {
156
+ const resident = register("reload-live-voice", {
157
+ processing: false,
158
+ queued: false,
159
+ });
160
+ const release = Conversation.prototype.acquireLiveVoiceResidency.call(
161
+ resident as unknown as Conversation,
162
+ );
163
+
164
+ evictConversationsForReload();
165
+
166
+ expect(resident.disposed).toBe(false);
167
+ expect(resident.markedStale).toBe(true);
168
+ expect(findConversation("reload-live-voice")).toBeDefined();
169
+
170
+ release();
171
+ evictConversationsForReload();
172
+
173
+ expect(resident.disposed).toBe(true);
174
+ expect(findConversation("reload-live-voice")).toBeUndefined();
175
+ });
176
+
128
177
  test("still evicts other idle conversations when one parent is protected", () => {
129
178
  const protectedParent = register("reload-protected", {
130
179
  processing: false,
@@ -73,6 +73,22 @@ describe("sendGuardianExpiryNotices", () => {
73
73
  expect(deliveredMessages[1].body.chatId).toBe("chat-456");
74
74
  });
75
75
 
76
+ test("opens the guardian's DM for a Discord delivery addressed to their user id", async () => {
77
+ await sendGuardianExpiryNotices(
78
+ [
79
+ makeDelivery({
80
+ destinationChannel: "discord",
81
+ destinationChatId: "111111111111111111",
82
+ }),
83
+ ],
84
+ "assistant-1",
85
+ );
86
+
87
+ expect(deliveredMessages).toHaveLength(1);
88
+ expect(deliveredMessages[0].url).toBe("/deliver/discord?dm=1");
89
+ expect(deliveredMessages[0].body.chatId).toBe("111111111111111111");
90
+ });
91
+
76
92
  test("skips deliveries that are not sent or pending", async () => {
77
93
  await sendGuardianExpiryNotices(
78
94
  [
@@ -158,6 +158,7 @@ describe("guardian-dispatch", () => {
158
158
  ensureConversation(convId);
159
159
 
160
160
  const session = createCallSession({
161
+ direction: "inbound",
161
162
  conversationId: convId,
162
163
  provider: "twilio",
163
164
  fromNumber: "+15550001111",
@@ -209,6 +210,7 @@ describe("guardian-dispatch", () => {
209
210
  ensureConversation(convId);
210
211
 
211
212
  const session = createCallSession({
213
+ direction: "inbound",
212
214
  conversationId: convId,
213
215
  provider: "twilio",
214
216
  fromNumber: "+15550001111",
@@ -238,6 +240,7 @@ describe("guardian-dispatch", () => {
238
240
  ensureConversation(convId);
239
241
 
240
242
  const session = createCallSession({
243
+ direction: "inbound",
241
244
  conversationId: convId,
242
245
  provider: "twilio",
243
246
  fromNumber: "+15550001111",
@@ -282,6 +285,7 @@ describe("guardian-dispatch", () => {
282
285
  };
283
286
 
284
287
  const session = createCallSession({
288
+ direction: "inbound",
285
289
  conversationId: convId,
286
290
  provider: "twilio",
287
291
  fromNumber: "+15550001111",
@@ -324,6 +328,7 @@ describe("guardian-dispatch", () => {
324
328
  };
325
329
 
326
330
  const session = createCallSession({
331
+ direction: "inbound",
327
332
  conversationId: convId,
328
333
  provider: "twilio",
329
334
  fromNumber: "+15550001111",
@@ -369,6 +374,7 @@ describe("guardian-dispatch", () => {
369
374
  };
370
375
 
371
376
  const session = createCallSession({
377
+ direction: "inbound",
372
378
  conversationId: convId,
373
379
  provider: "twilio",
374
380
  fromNumber: "+15550001111",
@@ -396,6 +402,7 @@ describe("guardian-dispatch", () => {
396
402
  ensureConversation(convId);
397
403
 
398
404
  const session = createCallSession({
405
+ direction: "inbound",
399
406
  conversationId: convId,
400
407
  provider: "twilio",
401
408
  fromNumber: "+15550001111",
@@ -431,6 +438,7 @@ describe("guardian-dispatch", () => {
431
438
  ensureConversation(convId);
432
439
 
433
440
  const session = createCallSession({
441
+ direction: "inbound",
434
442
  conversationId: convId,
435
443
  provider: "twilio",
436
444
  fromNumber: "+15550001111",
@@ -456,6 +464,7 @@ describe("guardian-dispatch", () => {
456
464
  ensureConversation(convId);
457
465
 
458
466
  const session = createCallSession({
467
+ direction: "inbound",
459
468
  conversationId: convId,
460
469
  provider: "twilio",
461
470
  fromNumber: "+15550001111",
@@ -488,6 +497,7 @@ describe("guardian-dispatch", () => {
488
497
  const sharedConversationId = "conv-shared-guardian";
489
498
 
490
499
  const session = createCallSession({
500
+ direction: "inbound",
491
501
  conversationId: convId,
492
502
  provider: "twilio",
493
503
  fromNumber: "+15550001111",
@@ -576,6 +586,7 @@ describe("guardian-dispatch", () => {
576
586
  const sharedConversationId = "conv-affinity-guardian";
577
587
 
578
588
  const session = createCallSession({
589
+ direction: "inbound",
579
590
  conversationId: convId,
580
591
  provider: "twilio",
581
592
  fromNumber: "+15550001111",
@@ -648,6 +659,7 @@ describe("guardian-dispatch", () => {
648
659
  const sharedConversationId = "conv-affinity-tool-guardian";
649
660
 
650
661
  const session = createCallSession({
662
+ direction: "inbound",
651
663
  conversationId: convId,
652
664
  provider: "twilio",
653
665
  fromNumber: "+15550001111",
@@ -727,6 +739,7 @@ describe("guardian-dispatch", () => {
727
739
  const sharedConversationId = "conv-affinity-triple";
728
740
 
729
741
  const session = createCallSession({
742
+ direction: "inbound",
730
743
  conversationId: convId,
731
744
  provider: "twilio",
732
745
  fromNumber: "+15550001111",
@@ -163,7 +163,7 @@ function resetMockPage() {
163
163
  */
164
164
  function defaultCdpHandler(
165
165
  method: string,
166
- _params: Record<string, unknown> | undefined,
166
+ params: Record<string, unknown> | undefined,
167
167
  ): unknown {
168
168
  switch (method) {
169
169
  case "DOM.getDocument":
@@ -177,12 +177,28 @@ function defaultCdpHandler(
177
177
  case "Runtime.evaluate":
178
178
  return { result: { value: { w: 800, h: 600 } } };
179
179
  case "Runtime.callFunctionOn":
180
+ if (
181
+ params?.returnByValue === true &&
182
+ String(params.functionDeclaration).includes("isConnected")
183
+ ) {
184
+ return {
185
+ result: { value: { connected: true, matches: true } },
186
+ };
187
+ }
188
+ if (
189
+ params?.returnByValue === true &&
190
+ String(params.functionDeclaration).includes("clearFirst")
191
+ ) {
192
+ return { result: { value: { needsRefocus: false } } };
193
+ }
180
194
  // executeBrowserSelectOption invokes a function that returns
181
195
  // a `matched` boolean — default to true so wrapper-contract
182
196
  // tests don't need to know the inner select-option matching
183
197
  // shape. Tests that exercise the no-match path override the
184
198
  // handler explicitly.
185
199
  return { result: { value: true } };
200
+ case "Input.insertText":
201
+ return {};
186
202
  default:
187
203
  return {};
188
204
  }
@@ -485,20 +501,21 @@ describe("executeBrowserType", () => {
485
501
  expect(result.content).toContain('Typed into element: element_id "e3"');
486
502
  expect(result.content).toContain("cleared existing content");
487
503
 
488
- // Expected CDP sequence when resolving by backendNodeId + clearFirst:
489
- // DOM.focus → DOM.resolveNode → Runtime.callFunctionOn (clear) →
490
- // DOM.focus → Input.insertText
504
+ // The editor selection precedes insertion, and a live-node read verifies
505
+ // that the requested text persisted.
491
506
  const methods = sendCalls.map((c) => c.method);
492
507
  expect(methods).toEqual([
493
508
  "DOM.focus",
494
509
  "DOM.resolveNode",
495
510
  "Runtime.callFunctionOn",
496
- "DOM.focus",
497
511
  "Input.insertText",
512
+ "Runtime.callFunctionOn",
498
513
  ]);
499
514
  const focusCall = sendCalls[0]!;
500
515
  expect(focusCall.params).toEqual({ backendNodeId: 555 });
501
- const insertCall = sendCalls[sendCalls.length - 1]!;
516
+ const insertCall = sendCalls.find(
517
+ (call) => call.method === "Input.insertText",
518
+ )!;
502
519
  expect(insertCall.params).toEqual({ text: "hello" });
503
520
  });
504
521
 
@@ -524,11 +541,10 @@ describe("executeBrowserType", () => {
524
541
  );
525
542
  expect(result.isError).toBe(false);
526
543
  expect(result.content).not.toContain("cleared");
527
- // clear_first=false skips DOM.resolveNode + Runtime.callFunctionOn
528
- // and the re-focus call, so we should see focus + insertText only.
544
+ // Append mode collapses the editor selection at the end before insertion.
529
545
  const methods = sendCalls.map((c) => c.method);
530
- expect(methods).not.toContain("DOM.resolveNode");
531
- expect(methods).not.toContain("Runtime.callFunctionOn");
546
+ expect(methods).toContain("DOM.resolveNode");
547
+ expect(methods).toContain("Runtime.callFunctionOn");
532
548
  const focusCount = methods.filter((m) => m === "DOM.focus").length;
533
549
  expect(focusCount).toBe(1);
534
550
  expect(methods).toContain("Input.insertText");
@@ -542,22 +558,22 @@ describe("executeBrowserType", () => {
542
558
  expect(result.isError).toBe(false);
543
559
  expect(result.content).toContain("pressed Enter");
544
560
  const methods = sendCalls.map((c) => c.method);
545
- // Input.insertText must come before the Enter keyDown/char/keyUp.
561
+ // Input.insertText must come before the Enter rawKeyDown/char/keyUp.
546
562
  const insertIdx = methods.indexOf("Input.insertText");
547
563
  const keyDownIdx = methods.findIndex(
548
564
  (m, i) =>
549
565
  m === "Input.dispatchKeyEvent" &&
550
- (sendCalls[i]!.params as { type: string }).type === "keyDown",
566
+ (sendCalls[i]!.params as { type: string }).type === "rawKeyDown",
551
567
  );
552
568
  expect(insertIdx).toBeGreaterThanOrEqual(0);
553
569
  expect(keyDownIdx).toBeGreaterThan(insertIdx);
554
- // Enter is text-producing → keyDown + char + keyUp.
570
+ // Enter is text-producing, so it emits rawKeyDown + char + keyUp.
555
571
  const keyEvents = sendCalls.filter(
556
572
  (c) => c.method === "Input.dispatchKeyEvent",
557
573
  );
558
574
  expect(keyEvents).toHaveLength(3);
559
575
  expect((keyEvents[0]!.params as { key: string }).key).toBe("Enter");
560
- expect((keyEvents[0]!.params as { type: string }).type).toBe("keyDown");
576
+ expect((keyEvents[0]!.params as { type: string }).type).toBe("rawKeyDown");
561
577
  expect((keyEvents[1]!.params as { type: string }).type).toBe("char");
562
578
  expect((keyEvents[2]!.params as { type: string }).type).toBe("keyUp");
563
579
  });
@@ -689,7 +705,7 @@ describe("executeBrowserPressKey", () => {
689
705
  expect(result.content).toContain('Pressed "Enter"');
690
706
  // No target => no DOM.focus, no selector resolution. Enter is a
691
707
  // text-producing key (text "\r") so dispatchKeyPress emits
692
- // keyDown + char + keyUp.
708
+ // rawKeyDown + char + keyUp.
693
709
  const methods = sendCalls.map((c) => c.method);
694
710
  expect(methods).toEqual([
695
711
  "Input.dispatchKeyEvent",
@@ -699,7 +715,7 @@ describe("executeBrowserPressKey", () => {
699
715
  const keyDown = sendCalls[0]!.params as Record<string, unknown>;
700
716
  const charEvt = sendCalls[1]!.params as Record<string, unknown>;
701
717
  const keyUp = sendCalls[2]!.params as Record<string, unknown>;
702
- expect(keyDown.type).toBe("keyDown");
718
+ expect(keyDown.type).toBe("rawKeyDown");
703
719
  expect(keyDown.key).toBe("Enter");
704
720
  expect(keyDown.windowsVirtualKeyCode).toBe(13);
705
721
  expect(charEvt.type).toBe("char");
@@ -707,6 +723,36 @@ describe("executeBrowserPressKey", () => {
707
723
  expect(keyUp.key).toBe("Enter");
708
724
  });
709
725
 
726
+ test("presses modifier chords on the focused element", async () => {
727
+ const result = await executeBrowserPressKey({ key: "Meta+v" }, ctx);
728
+ expect(result.isError).toBe(false);
729
+ expect(result.content).toContain('Pressed "Meta+v"');
730
+
731
+ const keyEvents = sendCalls.filter(
732
+ (call) => call.method === "Input.dispatchKeyEvent",
733
+ );
734
+ expect(keyEvents).toHaveLength(4);
735
+ expect(keyEvents[0]!.params).toMatchObject({
736
+ type: "rawKeyDown",
737
+ key: "Meta",
738
+ modifiers: 4,
739
+ });
740
+ expect(keyEvents[1]!.params).toMatchObject({
741
+ type: "rawKeyDown",
742
+ key: "v",
743
+ modifiers: 4,
744
+ });
745
+ expect(keyEvents[2]!.params).toMatchObject({
746
+ type: "keyUp",
747
+ key: "v",
748
+ modifiers: 4,
749
+ });
750
+ expect(keyEvents[3]!.params).toMatchObject({
751
+ type: "keyUp",
752
+ key: "Meta",
753
+ });
754
+ });
755
+
710
756
  test("presses key on targeted element via element_id", async () => {
711
757
  snapshotBackendNodeMaps.set("test-conversation", new Map([["e5", 555]]));
712
758
  const result = await executeBrowserPressKey(
@@ -716,14 +762,12 @@ describe("executeBrowserPressKey", () => {
716
762
  expect(result.isError).toBe(false);
717
763
  expect(result.content).toContain('Pressed "Tab" on element');
718
764
  expect(result.content).toContain('element_id "e5"');
719
- // Backend-resolved path: focus → dispatchKeyEvent × 3 (Tab is
720
- // text-producing so we also dispatch a char event).
765
+ // Backend-resolved path: focus, then Tab key down and key up.
721
766
  const methods = sendCalls.map((c) => c.method);
722
767
  expect(methods).toEqual([
723
768
  "DOM.focus",
724
769
  "Input.dispatchKeyEvent",
725
770
  "Input.dispatchKeyEvent",
726
- "Input.dispatchKeyEvent",
727
771
  ]);
728
772
  expect(sendCalls[0]!.params).toEqual({ backendNodeId: 555 });
729
773
  });
@@ -767,7 +811,7 @@ describe("executeBrowserPressKey", () => {
767
811
 
768
812
  test("surfaces CDP failure as a press-key error", async () => {
769
813
  sendHandler = () => new Error("Key not recognized");
770
- const result = await executeBrowserPressKey({ key: "InvalidKey" }, ctx);
814
+ const result = await executeBrowserPressKey({ key: "Enter" }, ctx);
771
815
  expect(result.isError).toBe(true);
772
816
  expect(result.content).toContain("Press key failed");
773
817
  expect(result.content).toContain("Key not recognized");
@@ -334,11 +334,10 @@ const SCAFFOLD_PERSONA = stripCommentLines(GUARDIAN_PERSONA_TEMPLATE).trim();
334
334
  const { resolveCallSiteConfig } = await import("../config/llm-resolver.js");
335
335
  const { LLMSchema } = await import("../config/schemas/llm.js");
336
336
 
337
- // Capture broadcastMessage so tests can observe the alerts and
338
- // conversation-created events the heartbeat service emits directly.
337
+ // Capture broadcastMessage so tests can observe the conversation-created
338
+ // events the heartbeat service emits directly.
339
339
  type BroadcastedMessage = { type: string; [key: string]: unknown };
340
340
  const broadcastedMessages: BroadcastedMessage[] = [];
341
- let onBroadcast: ((msg: BroadcastedMessage) => void) | null = null;
342
341
 
343
342
  mock.module("../runtime/assistant-event-hub.js", () => ({
344
343
  assistantEventHub: {
@@ -347,7 +346,6 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
347
346
  },
348
347
  broadcastMessage: (msg: BroadcastedMessage) => {
349
348
  broadcastedMessages.push(msg);
350
- onBroadcast?.(msg);
351
349
  },
352
350
  }));
353
351
 
@@ -357,7 +355,6 @@ describe("HeartbeatService", () => {
357
355
  content: string;
358
356
  options?: { callSite?: string };
359
357
  }>;
360
- let alerterCalls: Array<{ type: string; title: string; body: string }>;
361
358
 
362
359
  afterEach(() => {
363
360
  // Clean up workspace files between tests so file-existence tests don't leak
@@ -368,13 +365,7 @@ describe("HeartbeatService", () => {
368
365
 
369
366
  beforeEach(() => {
370
367
  processMessageCalls = [];
371
- alerterCalls = [];
372
368
  broadcastedMessages.length = 0;
373
- onBroadcast = (msg) => {
374
- if (msg.type === "heartbeat_alert") {
375
- alerterCalls.push(msg as { type: string; title: string; body: string });
376
- }
377
- };
378
369
  createdConversations.length = 0;
379
370
  conversationIdCounter = 0;
380
371
  mockStoredMessages.length = 0;
@@ -693,21 +684,6 @@ describe("HeartbeatService", () => {
693
684
  expect(processMessageCalls).toHaveLength(1);
694
685
  });
695
686
 
696
- test("alerts on processMessage failure", async () => {
697
- const service = createService({
698
- processMessage: async () => {
699
- throw new Error("LLM timeout");
700
- },
701
- });
702
-
703
- await service.runOnce();
704
-
705
- expect(alerterCalls).toHaveLength(1);
706
- expect(alerterCalls[0].type).toBe("heartbeat_alert");
707
- expect(alerterCalls[0].title).toBe("Heartbeat Failed");
708
- expect(alerterCalls[0].body).toBe("LLM timeout");
709
- });
710
-
711
687
  test("successful run updates lastRunAt and nextRunAt", async () => {
712
688
  const service = createService();
713
689
  expect(service.lastRunAt).toBeNull();
@@ -724,24 +700,6 @@ describe("HeartbeatService", () => {
724
700
  );
725
701
  });
726
702
 
727
- test("alerts on conversation creation failure", async () => {
728
- // Override createConversation to throw via a fresh import trick:
729
- // Since createConversation is mocked at module level, we simulate
730
- // this by having processMessage throw before it's called — but the
731
- // real fix is that executeRun wraps createConversation in the try/catch.
732
- // We verify by checking that any error in executeRun triggers the alert.
733
- const service = createService({
734
- processMessage: async () => {
735
- throw new Error("DB locked");
736
- },
737
- });
738
-
739
- await service.runOnce();
740
-
741
- expect(alerterCalls).toHaveLength(1);
742
- expect(alerterCalls[0].body).toBe("DB locked");
743
- });
744
-
745
703
  test("resetTimer() pushes nextRunAt forward", () => {
746
704
  const service = createService();
747
705
  service.start();
@@ -1467,21 +1425,6 @@ describe("HeartbeatService", () => {
1467
1425
  });
1468
1426
  });
1469
1427
 
1470
- test("CAS false suppresses failure alerter and feed event", async () => {
1471
- mockCompleteHeartbeatRun.mockImplementation(() => false);
1472
-
1473
- const service = createService({
1474
- processMessage: async () => {
1475
- throw new Error("LLM timeout");
1476
- },
1477
- });
1478
-
1479
- await service.runOnce();
1480
-
1481
- // completeHeartbeatRun returned false, so alerter should NOT be called
1482
- expect(alerterCalls).toHaveLength(0);
1483
- });
1484
-
1485
1428
  test("active-hours skip calls skipHeartbeatRun", async () => {
1486
1429
  setHeartbeatConfig({ activeHoursStart: 9, activeHoursEnd: 17 });
1487
1430
 
@@ -18,6 +18,26 @@
18
18
  import type { Conversation } from "../../daemon/conversation.js";
19
19
  import type { ChannelCapabilities } from "../../daemon/conversation-runtime-assembly.js";
20
20
 
21
+ /** Build a coordinator double representing a turn with no mode owner. */
22
+ export function mockUnownedModeSessions(): Conversation["modeSessions"] {
23
+ return {
24
+ acceptTurn: () => undefined,
25
+ getTurnOwner: () => undefined,
26
+ getTerminalDisposition: () => undefined,
27
+ hasResidentWork: () => false,
28
+ keepsSessionOpenAfterTurn: () => false,
29
+ trackPersistedRow: () => false,
30
+ recordStructuralWait: () => false,
31
+ invalidateStructuralWait: () => false,
32
+ settleStructuralWait: () => false,
33
+ invalidateAllStructuralWaits: () => 0,
34
+ beginDraining: () => false,
35
+ finalizeTurn: () => false,
36
+ releaseTurn: () => {},
37
+ transferTurn: () => undefined,
38
+ } as unknown as Conversation["modeSessions"];
39
+ }
40
+
21
41
  /**
22
42
  * `TExtra` carries test-only fields a double hangs off the same object
23
43
  * (recorded calls, captured messages) through to the return type, so a caller
@@ -38,6 +58,7 @@ export function asConversation<TExtra extends object = object>(
38
58
  merged.currentTurnTrustContext ?? merged.trustContext,
39
59
  isStale: () => false,
40
60
  hasInFlightWork: () => false,
61
+ modeSessions: mockUnownedModeSessions(),
41
62
  ...mock,
42
63
  };
43
64
  return merged as unknown as Conversation & TExtra;
@@ -20,6 +20,7 @@ import type {
20
20
  EventHandlerState,
21
21
  } from "../../daemon/conversation-agent-loop-handlers.js";
22
22
  import { dispatchAgentEvent } from "../../daemon/conversation-agent-loop-handlers.js";
23
+ import { mockUnownedModeSessions } from "./mock-conversation.js";
23
24
 
24
25
  /** A `tool_result` `AssistantEvent` emitted by the handler. */
25
26
  export type ToolResultEvent = Extract<AssistantEvent, { type: "tool_result" }>;
@@ -59,6 +60,7 @@ export function createHandlerDeps(reqId = "req-web-search"): HandlerHarness {
59
60
  emitActivityState: () => {},
60
61
  markWorkspaceTopLevelDirty: () => {},
61
62
  currentTurnSurfaces: [],
63
+ modeSessions: mockUnownedModeSessions(),
62
64
  } as unknown as EventHandlerDeps["ctx"],
63
65
  onEvent: (msg: AssistantEvent) => events.push(msg),
64
66
  reqId,
@@ -276,6 +276,11 @@ export function createOutboundSessionGuarded(
276
276
  }
277
277
  source.status = "revoked";
278
278
  source.updatedAt = Date.now();
279
+ // Like the gateway, the replacement keeps the claimed session's purpose.
280
+ return createOutboundSession({
281
+ ...createParams,
282
+ verificationPurpose: source.verificationPurpose,
283
+ });
279
284
  }
280
285
 
281
286
  return createOutboundSession(createParams);
@@ -464,6 +464,43 @@ describe("HostCuProxy", () => {
464
464
  const result = await resultPromise;
465
465
  expect(result.isError).toBe(false);
466
466
  });
467
+
468
+ test("runs the lifecycle callback only for a validated dispatch", async () => {
469
+ setup(0);
470
+ const rejectedCallback = mock(() => {});
471
+ proxy.recordAction("computer_use_click", { element_id: 1 });
472
+ const rejected = await proxy.request(
473
+ "computer_use_click",
474
+ { element_id: 1 },
475
+ "session-1",
476
+ proxy.stepCount,
477
+ undefined,
478
+ undefined,
479
+ undefined,
480
+ undefined,
481
+ rejectedCallback,
482
+ );
483
+ expect(rejected.isError).toBe(true);
484
+ expect(rejectedCallback).not.toHaveBeenCalled();
485
+
486
+ setup();
487
+ const acceptedCallback = mock(() => {});
488
+ const pending = proxy.request(
489
+ "computer_use_click",
490
+ { element_id: 1 },
491
+ "session-1",
492
+ 1,
493
+ undefined,
494
+ undefined,
495
+ undefined,
496
+ undefined,
497
+ acceptedCallback,
498
+ );
499
+ expect(acceptedCallback).toHaveBeenCalledTimes(1);
500
+ const requestId = (sentMessages[0] as { requestId: string }).requestId;
501
+ proxy.processObservation(requestId, { executionResult: "Clicked" });
502
+ await pending;
503
+ });
467
504
  });
468
505
 
469
506
  // -------------------------------------------------------------------------
@@ -1333,7 +1370,13 @@ describe("HostCuProxy", () => {
1333
1370
  proxy.processObservation(requestId, { axTree: "Button [1]" });
1334
1371
  await resultPromise;
1335
1372
 
1336
- proxy.endTask("session-1");
1373
+ const sourceId = proxy.sourceId;
1374
+ expect(sourceId).not.toBe("");
1375
+ expect(proxy.resetGeneration).toBe(0);
1376
+
1377
+ expect(proxy.endTask("session-1")).toBe(0);
1378
+ expect(proxy.sourceId).toBe(sourceId);
1379
+ expect(proxy.resetGeneration).toBe(1);
1337
1380
  const cancels = sentMessages.filter(
1338
1381
  (m) => (m as Record<string, unknown>).type === "host_cu_cancel",
1339
1382
  ) as Array<Record<string, unknown>>;
@@ -1341,7 +1384,8 @@ describe("HostCuProxy", () => {
1341
1384
  expect(cancels[0].conversationId).toBe("session-1");
1342
1385
  expect(cancels[0].requestId).not.toBe(requestId);
1343
1386
 
1344
- proxy.endTask("session-1");
1387
+ expect(proxy.endTask("session-1")).toBe(1);
1388
+ expect(proxy.resetGeneration).toBe(2);
1345
1389
  expect(
1346
1390
  sentMessages.filter(
1347
1391
  (m) => (m as Record<string, unknown>).type === "host_cu_cancel",
@@ -161,6 +161,7 @@ mock.module("../runtime/trust-context-resolver.js", () => ({
161
161
  import type { AuthContext } from "../runtime/auth/types.js";
162
162
  import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
163
163
  import { callHandler } from "./helpers/call-route-handler.js";
164
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
164
165
 
165
166
  const _testAuthContext: AuthContext = {
166
167
  subject: "actor:self:test-actor",
@@ -194,6 +195,7 @@ function makeConversation(overrides: Record<string, unknown> = {}) {
194
195
  setHostCuProxy: () => {},
195
196
  setHostAppControlProxy: () => {},
196
197
  addPreactivatedSkillId: () => {},
198
+ modeSessions: mockUnownedModeSessions(),
197
199
  emitConfirmationStateChanged: () => {},
198
200
  emitActivityState: () => {},
199
201
  setTurnChannelContext: () => {},