@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
@@ -1112,7 +1112,7 @@ export async function wakeAgentForOpportunity(
1112
1112
  try {
1113
1113
  recordRequestLog(
1114
1114
  conversationId,
1115
- JSON.stringify(record.rawRequest),
1115
+ JSON.stringify(record.rawRequest ?? null),
1116
1116
  JSON.stringify(record.rawResponse),
1117
1117
  undefined,
1118
1118
  record.provider,
@@ -2,9 +2,8 @@
2
2
  * Layered approval message composition system.
3
3
  *
4
4
  * Generates approval prompt text through a priority chain:
5
- * 1. Assistant preface (macOS parity — reuse existing assistant text)
6
- * 2. Generator-produced rewrite of deterministic fallback text (when provided by daemon)
7
- * 3. Deterministic fallback templates (natural, scenario-specific messages)
5
+ * 1. Generator-produced rewrite of deterministic fallback text (when provided by daemon)
6
+ * 2. Deterministic fallback templates (natural, scenario-specific messages)
8
7
  */
9
8
  import { getLogger } from "../util/logger.js";
10
9
  import type {
@@ -25,18 +24,10 @@ const log = getLogger("approval-message-composer");
25
24
  // Public API
26
25
  // ---------------------------------------------------------------------------
27
26
 
28
- /**
29
- * Compose an approval message using layered source selection:
30
- * 1. If an assistant preface is provided and non-empty, return it directly.
31
- * 2. Otherwise fall back to a deterministic scenario-specific template.
32
- */
27
+ /** Compose an approval message from its deterministic scenario template. */
33
28
  export function composeApprovalMessage(
34
29
  context: ApprovalMessageContext,
35
30
  ): string {
36
- if (context.assistantPreface && context.assistantPreface.trim().length > 0) {
37
- return context.assistantPreface;
38
- }
39
-
40
31
  return getFallbackMessage(context);
41
32
  }
42
33
 
@@ -99,10 +90,6 @@ export async function composeApprovalMessageGenerative(
99
90
  options: ComposeApprovalMessageGenerativeOptions = {},
100
91
  generator?: ApprovalCopyGenerator,
101
92
  ): Promise<string> {
102
- if (context.assistantPreface && context.assistantPreface.trim().length > 0) {
103
- return context.assistantPreface;
104
- }
105
-
106
93
  const fallbackText =
107
94
  options.fallbackText?.trim() || getFallbackMessage(context);
108
95
 
@@ -155,45 +142,12 @@ export function getFallbackMessage(context: ApprovalMessageContext): string {
155
142
  case "reminder_prompt":
156
143
  return "There is a pending approval request. Ask a follow-up question or say approve/deny when you are ready.";
157
144
 
158
- case "guardian_delivery_failed":
159
- return context.toolName
160
- ? `Your request to run "${context.toolName}" could not be sent to the guardian for approval. The request has been denied for safety.`
161
- : "I wasn't able to reach the guardian to request approval. The request has been denied for safety.";
162
-
163
- case "guardian_request_forwarded":
164
- return `Your request to use "${
165
- context.toolName ?? "unknown"
166
- }" has been forwarded to the guardian for approval. I'll let you know once they decide.`;
167
-
168
- case "guardian_disambiguation":
169
- return `There are ${
170
- context.pendingCount ?? "multiple"
171
- } pending approval requests. Please use the approval buttons to specify which request you're responding to.`;
172
-
173
145
  case "guardian_identity_mismatch":
174
146
  return "This approval request can only be handled by the designated guardian.";
175
147
 
176
148
  case "request_pending_guardian":
177
149
  return "Your request is pending guardian approval. Please wait for the guardian to respond.";
178
150
 
179
- case "guardian_decision_outcome":
180
- return `The guardian has ${
181
- context.decision ?? "decided on"
182
- } your request to use "${context.toolName ?? "unknown"}".`;
183
-
184
- case "guardian_expired_requester":
185
- return `The approval request for "${
186
- context.toolName ?? "unknown"
187
- }" has expired without a guardian response. The request has been denied.`;
188
-
189
- case "guardian_expired_guardian":
190
- return `The approval request from ${
191
- context.requesterIdentifier ?? "the requester"
192
- } for "${context.toolName ?? "unknown"}" has expired.`;
193
-
194
- case "guardian_verify_success":
195
- return "Guardian verification successful! You are now set as the guardian for this channel.";
196
-
197
151
  case "guardian_verify_failed":
198
152
  return `Verification failed. ${
199
153
  context.failureReason ?? "Please try again."
@@ -220,29 +174,9 @@ export function getFallbackMessage(context: ApprovalMessageContext): string {
220
174
  return `To complete guardian verification, send the code: ${code}.`;
221
175
  }
222
176
 
223
- case "guardian_verify_status_bound":
224
- return "A guardian is currently active for this channel.";
225
-
226
- case "guardian_verify_status_unbound":
227
- return "No guardian is currently configured for this channel.";
228
-
229
- case "guardian_deny_no_identity":
230
- return "This action requires approval, but your identity could not be verified. The request has been denied for safety.";
231
-
232
- case "guardian_deny_no_binding":
233
- return "This action requires guardian approval, but no guardian has been configured for this channel. The request has been denied for safety.";
234
-
235
- case "requester_cancel":
236
- return context.toolName
237
- ? `Your request to use "${context.toolName}" has been cancelled.`
238
- : "Your pending request has been cancelled.";
239
-
240
177
  case "approval_already_resolved":
241
178
  return "This approval request has already been resolved.";
242
179
 
243
- case "guardian_text_unavailable":
244
- return "I can't process text replies for approvals right now. Please use the approve/deny buttons above to respond.";
245
-
246
180
  default: {
247
181
  // Exhaustive check — TypeScript will flag if a scenario is missing.
248
182
  const _exhaustive: never = context.scenario;
@@ -49,10 +49,15 @@ export function capabilityForMessageType(
49
49
  }
50
50
  import type { AssistantEventEnvelope } from "../api/index.js";
51
51
  import { forwardEventPublishToDaemon } from "../ipc/events-publish-client.js";
52
+ import {
53
+ type ClientConnectionEventReason,
54
+ recordClientConnectionEvent,
55
+ } from "../persistence/client-connection-events-store.js";
52
56
  import { appendEventToStream } from "../signals/event-stream.js";
53
57
  import { getLogger } from "../util/logger.js";
54
58
  import { buildAssistantEvent } from "./assistant-event.js";
55
59
  import type { AssistantEventPublishOptions } from "./assistant-event-publish-options.js";
60
+ import { matchesTargeting } from "./assistant-event-targeting.js";
56
61
  import { stampAndBuffer } from "./assistant-stream-state.js";
57
62
  import { isMainDaemonProcess } from "./process-role.js";
58
63
 
@@ -72,7 +77,7 @@ export type AssistantEventCallback = (
72
77
 
73
78
  /** Opaque handle returned by `subscribe`. Call `dispose()` to remove the subscription. */
74
79
  export interface AssistantEventSubscription {
75
- dispose(): void;
80
+ dispose(reason?: ClientConnectionEventReason): void;
76
81
  /** True until `dispose()` has been called. */
77
82
  readonly active: boolean;
78
83
  /**
@@ -142,6 +147,10 @@ interface ClientEntry extends BaseSubscriberEntry {
142
147
  * service-token connections that have no principal.
143
148
  */
144
149
  actorPrincipalId?: string;
150
+ /** Client-reported build version (e.g. extension manifest version). */
151
+ clientVersion?: string;
152
+ /** Whether the client advertised an SSE idle watchdog on this connection. */
153
+ sseWatchdog?: boolean;
145
154
  /**
146
155
  * Last desktop presence reported by this client, for clients that report it.
147
156
  * In-memory only, so consumers must fail open when it is absent.
@@ -193,14 +202,22 @@ type SubscriberInput = DistributiveOmit<
193
202
  * Client connections register as subscribers with metadata and are queryable
194
203
  * via `listClients()`, `getMostRecentClientByCapability()`, etc.
195
204
  */
205
+ export type ClientConnectionRecorder = typeof recordClientConnectionEvent;
206
+
196
207
  export class AssistantEventHub {
197
208
  private readonly subscribers = new Set<SubscriberEntry>();
198
209
  private readonly maxSubscribers: number;
199
210
  /** Monotonic source for per-connection ids, scoped to this hub. */
200
211
  private connectionCounter = 0;
212
+ private readonly recordConnection: ClientConnectionRecorder;
201
213
 
202
- constructor(options?: { maxSubscribers?: number }) {
214
+ constructor(options?: {
215
+ maxSubscribers?: number;
216
+ recordConnection?: ClientConnectionRecorder;
217
+ }) {
203
218
  this.maxSubscribers = options?.maxSubscribers ?? Infinity;
219
+ this.recordConnection =
220
+ options?.recordConnection ?? recordClientConnectionEvent;
204
221
  }
205
222
 
206
223
  /**
@@ -231,6 +248,7 @@ export class AssistantEventHub {
231
248
  for (const entry of stale) {
232
249
  entry.active = false;
233
250
  this.subscribers.delete(entry);
251
+ this.recordClientLifecycle(entry, "stale_replaced");
234
252
  try {
235
253
  entry.onEvict();
236
254
  } catch {
@@ -258,6 +276,7 @@ export class AssistantEventHub {
258
276
  }
259
277
  oldest.active = false;
260
278
  this.subscribers.delete(oldest);
279
+ this.recordClientLifecycle(oldest, "cap_evicted");
261
280
  try {
262
281
  oldest.onEvict();
263
282
  } catch {
@@ -287,6 +306,7 @@ export class AssistantEventHub {
287
306
  },
288
307
  "subscriber registered (client)",
289
308
  );
309
+ this.recordClientLifecycle(entry, "sse_open");
290
310
  } else {
291
311
  log.info({ connectionId }, "subscriber registered (process)");
292
312
  }
@@ -294,11 +314,12 @@ export class AssistantEventHub {
294
314
  this.subscribers.add(entry);
295
315
 
296
316
  return {
297
- dispose: () => {
317
+ dispose: (reason) => {
298
318
  if (entry.active) {
299
319
  entry.active = false;
300
320
  this.subscribers.delete(entry);
301
321
  if (entry.type === "client") {
322
+ this.recordClientLifecycle(entry, reason ?? "sse_close");
302
323
  log.info(
303
324
  {
304
325
  clientId: entry.clientId,
@@ -323,21 +344,14 @@ export class AssistantEventHub {
323
344
  * Publish an event to all matching subscribers.
324
345
  *
325
346
  * Matching rules:
326
- * - if `excludeClientId` is set, the subscriber with that clientId is
327
- * skipped regardless of every other rule (self-echo suppression — the
328
- * client that originated the mutation does not receive its own
329
- * invalidation back through the hub).
330
- * - if `targetClientId` is set, deliver only to the subscriber with that
331
- * clientId, bypassing the conversation-id filter entirely (the web-origin
332
- * event's conversationId differs from the macOS client's subscribed
333
- * conversation).
334
- * - if `filter.conversationId` is set (and `targetClientId` is not), the
335
- * `event.conversationId` must equal it
336
- * - if `targetCapability` is set, only subscribers whose capabilities include
337
- * it receive the event; untargeted events go to all
338
- * - if `targetInterfaceId` is set, only client subscribers whose
339
- * `interfaceId` matches receive the event; process subscribers and
340
- * non-matching clients are skipped.
347
+ * - the event's targeting (`excludeClientId`, `targetInterfaceId`,
348
+ * `targetActorPrincipalId`, `targetClientId`, `targetCapability`) must
349
+ * admit the subscriber, per {@link matchesTargeting}, the same check
350
+ * replay applies
351
+ * - a subscriber's `filter.conversationId` must equal `event.conversationId`,
352
+ * unless the event names a client with `targetClientId` (a web-origin
353
+ * event's conversationId can differ from the conversation the targeted
354
+ * macOS client subscribed to)
341
355
  *
342
356
  * Fanout is isolated: a throwing or rejecting subscriber does not abort
343
357
  * delivery to remaining subscribers.
@@ -362,10 +376,6 @@ export class AssistantEventHub {
362
376
  }
363
377
  }
364
378
 
365
- const targetCapability = options?.targetCapability;
366
- const targetClientId = options?.targetClientId;
367
- const targetInterfaceId = options?.targetInterfaceId;
368
- const excludeClientId = options?.excludeClientId;
369
379
  const snapshot = Array.from(this.subscribers);
370
380
  const errors: unknown[] = [];
371
381
 
@@ -373,63 +383,18 @@ export class AssistantEventHub {
373
383
  if (!entry.active) {
374
384
  continue;
375
385
  }
376
-
377
- // Self-echo suppression: the originating client never receives the
378
- // event back. Checked before every other rule so it composes with
379
- // both targeted and untargeted broadcasts.
386
+ if (!matchesTargeting(options, entry)) {
387
+ continue;
388
+ }
380
389
  if (
381
- excludeClientId != null &&
382
- entry.type === "client" &&
383
- entry.clientId === excludeClientId
390
+ options?.targetClientId == null &&
391
+ event.conversationId != null &&
392
+ entry.filter.conversationId != null &&
393
+ entry.filter.conversationId !== event.conversationId
384
394
  ) {
385
395
  continue;
386
396
  }
387
397
 
388
- // Interface targeting: skip any subscriber that is not a client of
389
- // the requested interface. Composes with `targetClientId` and
390
- // `targetCapability` below.
391
- if (targetInterfaceId != null) {
392
- if (
393
- entry.type !== "client" ||
394
- entry.interfaceId !== targetInterfaceId
395
- ) {
396
- continue;
397
- }
398
- }
399
-
400
- if (targetClientId != null) {
401
- // Targeted: bypass conversation filter, deliver only to the named client.
402
- if (entry.type !== "client" || entry.clientId !== targetClientId) {
403
- continue;
404
- }
405
- if (
406
- targetCapability != null &&
407
- !entry.capabilities.includes(targetCapability)
408
- ) {
409
- continue;
410
- }
411
- } else {
412
- // Untargeted: existing conversation-scoped + capability logic.
413
- if (
414
- event.conversationId != null &&
415
- entry.filter.conversationId != null &&
416
- entry.filter.conversationId !== event.conversationId
417
- ) {
418
- continue;
419
- }
420
-
421
- // Capability targeting: targeted events only go to subscribers that
422
- // declare the required capability.
423
- if (targetCapability != null) {
424
- if (
425
- entry.type !== "client" ||
426
- !entry.capabilities.includes(targetCapability)
427
- ) {
428
- continue;
429
- }
430
- }
431
- }
432
-
433
398
  try {
434
399
  await entry.callback(event);
435
400
  } catch (err) {
@@ -673,6 +638,7 @@ export class AssistantEventHub {
673
638
  for (const entry of targets) {
674
639
  entry.active = false;
675
640
  this.subscribers.delete(entry);
641
+ this.recordClientLifecycle(entry, "force_disconnect");
676
642
  try {
677
643
  entry.onEvict();
678
644
  } catch {
@@ -688,6 +654,29 @@ export class AssistantEventHub {
688
654
  return targets.length;
689
655
  }
690
656
 
657
+ /**
658
+ * Best-effort history row for a client subscriber. Process subscribers
659
+ * and an unready database are skipped; a write failure never throws.
660
+ */
661
+ private recordClientLifecycle(
662
+ entry: SubscriberEntry,
663
+ reason: ClientConnectionEventReason,
664
+ ): void {
665
+ if (entry.type !== "client") {
666
+ return;
667
+ }
668
+ this.recordConnection({
669
+ clientId: entry.clientId,
670
+ interfaceId: entry.interfaceId,
671
+ connectionId: entry.connectionId,
672
+ reason,
673
+ actorPrincipalId: entry.actorPrincipalId,
674
+ clientVersion: entry.clientVersion,
675
+ sseWatchdog: entry.sseWatchdog,
676
+ machineName: entry.machineName,
677
+ });
678
+ }
679
+
691
680
  /** Number of currently active subscribers (useful for tests and caps). */
692
681
  subscriberCount(): number {
693
682
  return this.subscribers.size;
@@ -716,6 +705,12 @@ export const assistantEventHub = new AssistantEventHub({ maxSubscribers: 100 });
716
705
  */
717
706
  let _hubChain = Promise.resolve();
718
707
 
708
+ /** Targeting a caller of {@link broadcastMessage} may request. */
709
+ export type BroadcastMessageOptions = Pick<
710
+ AssistantEventPublishOptions,
711
+ "targetClientId" | "targetInterfaceId" | "targetActorPrincipalId"
712
+ >;
713
+
719
714
  /**
720
715
  * Wraps a `AssistantEvent` in an `AssistantEventEnvelope` envelope and publishes it
721
716
  * to the process-level hub.
@@ -735,11 +730,12 @@ let _hubChain = Promise.resolve();
735
730
  export function broadcastMessage(
736
731
  msg: AssistantEvent,
737
732
  conversationId?: string,
738
- options?: { targetClientId?: string; targetInterfaceId?: InterfaceId },
733
+ options?: BroadcastMessageOptions,
739
734
  ): void {
740
735
  const resolvedConversationId = conversationId ?? extractConversationId(msg);
741
736
  const targetClientId = options?.targetClientId;
742
737
  const targetInterfaceId = options?.targetInterfaceId;
738
+ const targetActorPrincipalId = options?.targetActorPrincipalId;
743
739
 
744
740
  const event = buildAssistantEvent(msg, resolvedConversationId);
745
741
  // A reconnect can overlap an older executor with the same device ID.
@@ -765,11 +761,13 @@ export function broadcastMessage(
765
761
  targetCapability != null ||
766
762
  targetClientId != null ||
767
763
  targetInterfaceId != null ||
764
+ targetActorPrincipalId != null ||
768
765
  excludeClientId != null
769
766
  ? {
770
767
  targetCapability,
771
768
  targetClientId,
772
769
  targetInterfaceId,
770
+ targetActorPrincipalId,
773
771
  excludeClientId,
774
772
  }
775
773
  : undefined;
@@ -15,6 +15,13 @@ export const AssistantEventPublishOptionsSchema = z.object({
15
15
  targetCapability: z.enum(HOST_PROXY_CAPABILITIES).optional(),
16
16
  targetClientId: z.string().optional(),
17
17
  targetInterfaceId: z.enum(INTERFACE_IDS).optional(),
18
+ /**
19
+ * Deliver only to client subscribers whose verified `actorPrincipalId`
20
+ * equals this value. Subscribers without a principal never match, so an
21
+ * event scoped to one person fails closed rather than reaching a connection
22
+ * whose identity is unknown.
23
+ */
24
+ targetActorPrincipalId: z.string().optional(),
18
25
  /**
19
26
  * Skip the subscriber with this `clientId`. Used for self-echo suppression on
20
27
  * `sync_changed`: the route handler echoes the originating tab's
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Who receives a targeted assistant event. Live fanout
3
+ * (`AssistantEventHub.publish`) and replay (`getReplayWindow`, reached from
4
+ * the SSE reconnect and the `events/tail` route) both decide delivery with
5
+ * {@link matchesTargeting}, so a targeting rule added here reaches every
6
+ * path at once.
7
+ *
8
+ * Conversation scoping is deliberately not part of this check. Live fanout
9
+ * applies a subscriber's conversation filter only to events that do not name
10
+ * a client; replay scopes to the conversation its caller asked for. Each path
11
+ * applies its own scoping after this check.
12
+ */
13
+
14
+ import type { HostProxyCapability, InterfaceId } from "../channels/types.js";
15
+ import type { AssistantEventPublishOptions } from "./assistant-event-publish-options.js";
16
+
17
+ /** The connection an event is being delivered to, live or on replay. */
18
+ export interface SubscriberIdentity {
19
+ type: "client" | "process";
20
+ clientId?: string;
21
+ interfaceId?: InterfaceId;
22
+ capabilities?: readonly HostProxyCapability[];
23
+ actorPrincipalId?: string;
24
+ }
25
+
26
+ /**
27
+ * Whether `subscriber` receives an event carrying `targeting`. An event with
28
+ * no targeting reaches every subscriber. Every rule a targeted event sets must
29
+ * hold, and a process subscriber never matches a rule that names a client
30
+ * property.
31
+ */
32
+ export function matchesTargeting(
33
+ targeting: AssistantEventPublishOptions | undefined,
34
+ subscriber: SubscriberIdentity,
35
+ ): boolean {
36
+ if (!targeting) {
37
+ return true;
38
+ }
39
+ const isClient = subscriber.type === "client";
40
+
41
+ // Self-echo suppression: the client that originated a mutation does not
42
+ // receive its own invalidation back.
43
+ if (
44
+ targeting.excludeClientId != null &&
45
+ isClient &&
46
+ subscriber.clientId === targeting.excludeClientId
47
+ ) {
48
+ return false;
49
+ }
50
+ if (
51
+ targeting.targetInterfaceId != null &&
52
+ (!isClient || subscriber.interfaceId !== targeting.targetInterfaceId)
53
+ ) {
54
+ return false;
55
+ }
56
+ // An event scoped to one person reaches only that person's connections; a
57
+ // connection with no verified principal never matches.
58
+ if (
59
+ targeting.targetActorPrincipalId != null &&
60
+ (!isClient ||
61
+ subscriber.actorPrincipalId !== targeting.targetActorPrincipalId)
62
+ ) {
63
+ return false;
64
+ }
65
+ if (
66
+ targeting.targetClientId != null &&
67
+ (!isClient || subscriber.clientId !== targeting.targetClientId)
68
+ ) {
69
+ return false;
70
+ }
71
+ if (
72
+ targeting.targetCapability != null &&
73
+ (!isClient ||
74
+ !subscriber.capabilities?.includes(targeting.targetCapability))
75
+ ) {
76
+ return false;
77
+ }
78
+ return true;
79
+ }
@@ -70,6 +70,11 @@ import {
70
70
  import type { AssistantEventEnvelope } from "../api/index.js";
71
71
  import { getLogger } from "../util/logger.js";
72
72
  import { getWorkspaceDir } from "../util/platform.js";
73
+ import type { AssistantEventPublishOptions } from "./assistant-event-publish-options.js";
74
+ import {
75
+ matchesTargeting,
76
+ type SubscriberIdentity,
77
+ } from "./assistant-event-targeting.js";
73
78
 
74
79
  const log = getLogger("assistant-stream-state");
75
80
 
@@ -93,39 +98,13 @@ const SEQ_RESERVATION_BLOCK = 1024;
93
98
 
94
99
  // ── Types ────────────────────────────────────────────────────────────
95
100
 
96
- /**
97
- * Targeting / exclusion modifiers attached to an event at publish time.
98
- * Stored on ring entries so replay can re-apply the same delivery
99
- * filter that the live `publish()` path used.
100
- *
101
- * Fields use plain `string` rather than branded channel types so
102
- * this module stays independent of the `channels/` package.
103
- */
104
- export interface EventTargeting {
105
- targetCapability?: string;
106
- targetClientId?: string;
107
- targetInterfaceId?: string;
108
- excludeClientId?: string;
109
- }
110
-
111
- /**
112
- * Identity of the subscriber requesting a replay window. Replay
113
- * filtering mirrors the live `publish()` logic in `AssistantEventHub`:
114
- * targeted entries are only delivered when the subscriber matches.
115
- */
116
- export interface ReplaySubscriber {
117
- type: "client" | "process";
118
- clientId?: string;
119
- interfaceId?: string;
120
- capabilities?: readonly string[];
121
- }
122
-
123
101
  interface RingEntry {
124
102
  seq: number;
125
103
  event: AssistantEventEnvelope;
126
104
  emittedAt: number;
127
105
  sizeBytes: number;
128
- targeting?: EventTargeting;
106
+ /** The publish options the event went out with, re-applied on replay. */
107
+ targeting?: AssistantEventPublishOptions;
129
108
  }
130
109
 
131
110
  interface AssistantStreamState {
@@ -215,7 +194,7 @@ export function isStreamSeqStampingDisabled(): boolean {
215
194
  */
216
195
  export function stampAndBuffer(
217
196
  event: AssistantEventEnvelope,
218
- options?: { targeting?: EventTargeting },
197
+ options?: { targeting?: AssistantEventPublishOptions },
219
198
  ): void {
220
199
  if (stampingDisabled) {
221
200
  return;
@@ -272,9 +251,9 @@ export function stampAndBuffer(
272
251
  * buffered entry -- callers should fall back to a snapshot resync.
273
252
  *
274
253
  * When `subscriber` is provided, entries carrying targeting metadata
275
- * are filtered using the same rules as the live `publish()` path in
276
- * `AssistantEventHub`, so targeted events do not leak to subscribers
277
- * outside their intended delivery set on reconnect.
254
+ * are filtered with {@link matchesTargeting}, the check the live
255
+ * `publish()` path in `AssistantEventHub` applies, so targeted events do
256
+ * not leak to subscribers outside their intended delivery set on reconnect.
278
257
  *
279
258
  * When `conversationId` is provided, only that conversation's events are
280
259
  * returned -- a conversation-scoped subscription only delivers its own
@@ -287,7 +266,7 @@ export function stampAndBuffer(
287
266
  */
288
267
  export function getReplayWindow(
289
268
  lastSeenSeq: number,
290
- subscriber?: ReplaySubscriber,
269
+ subscriber?: SubscriberIdentity,
291
270
  conversationId?: string,
292
271
  ): readonly AssistantEventEnvelope[] | null {
293
272
  evict();
@@ -315,7 +294,7 @@ export function getReplayWindow(
315
294
  entry.seq > lastSeenSeq &&
316
295
  (conversationId == null ||
317
296
  entry.event.conversationId === conversationId) &&
318
- (subscriber == null || matchesSubscriber(entry, subscriber)),
297
+ (subscriber == null || matchesTargeting(entry.targeting, subscriber)),
319
298
  )
320
299
  .map((entry) => entry.event);
321
300
  }
@@ -518,72 +497,6 @@ function readReservedCeiling(): number {
518
497
  return 0;
519
498
  }
520
499
 
521
- /**
522
- * Mirrors the delivery logic in `AssistantEventHub.publish()`. Returns
523
- * `true` when `subscriber` would have received the entry during live
524
- * fanout.
525
- */
526
- function matchesSubscriber(
527
- entry: RingEntry,
528
- subscriber: ReplaySubscriber,
529
- ): boolean {
530
- const t = entry.targeting;
531
- if (!t) {
532
- return true;
533
- }
534
-
535
- // Self-echo suppression: the originating client never receives the
536
- // event back.
537
- if (
538
- t.excludeClientId != null &&
539
- subscriber.type === "client" &&
540
- subscriber.clientId === t.excludeClientId
541
- ) {
542
- return false;
543
- }
544
-
545
- // Interface targeting: only clients of the requested interface.
546
- if (t.targetInterfaceId != null) {
547
- if (
548
- subscriber.type !== "client" ||
549
- subscriber.interfaceId !== t.targetInterfaceId
550
- ) {
551
- return false;
552
- }
553
- }
554
-
555
- if (t.targetClientId != null) {
556
- // Client targeting: bypass conversation filter, deliver only to the
557
- // named client.
558
- if (
559
- subscriber.type !== "client" ||
560
- subscriber.clientId !== t.targetClientId
561
- ) {
562
- return false;
563
- }
564
- if (
565
- t.targetCapability != null &&
566
- !subscriber.capabilities?.includes(t.targetCapability)
567
- ) {
568
- return false;
569
- }
570
- return true;
571
- }
572
-
573
- // Capability targeting (without client targeting): only subscribers
574
- // that declare the required capability.
575
- if (t.targetCapability != null) {
576
- if (
577
- subscriber.type !== "client" ||
578
- !subscriber.capabilities?.includes(t.targetCapability)
579
- ) {
580
- return false;
581
- }
582
- }
583
-
584
- return true;
585
- }
586
-
587
500
  function evict(): void {
588
501
  const now = Date.now();
589
502
  while (state.ring.length > 0) {