@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
@@ -20,9 +20,10 @@
20
20
 
21
21
  import { type IntervalHistogram, monitorEventLoopDelay } from "node:perf_hooks";
22
22
 
23
+ import { sanitizeClientMetadataValue } from "@vellumai/service-contracts/client-metadata";
23
24
  import { z } from "zod";
24
25
 
25
- import type { HostProxyCapability } from "../../channels/types.js";
26
+ import type { HostProxyCapability, InterfaceId } from "../../channels/types.js";
26
27
  import { parseInterfaceId, supportsHostProxy } from "../../channels/types.js";
27
28
  import { notifyContactsChanged } from "../../contacts/notify-contacts-changed.js";
28
29
  import { getConversation } from "../../persistence/conversation-crud.js";
@@ -38,7 +39,7 @@ import {
38
39
  AssistantEventHub,
39
40
  assistantEventHub,
40
41
  } from "../assistant-event-hub.js";
41
- import type { ReplaySubscriber } from "../assistant-stream-state.js";
42
+ import type { SubscriberIdentity } from "../assistant-event-targeting.js";
42
43
  import { getReplayWindow } from "../assistant-stream-state.js";
43
44
  import { ACTOR_PRINCIPALS, GATEWAY_PRINCIPALS } from "../auth/route-policy.js";
44
45
  import { DEFAULT_HEARTBEAT_INTERVAL_MS } from "../client-health.js";
@@ -248,12 +249,14 @@ const defaultSseShedReporter: SseShedReporter = (reason, inst) => {
248
249
  * conversations for this assistant.
249
250
  *
250
251
  * Headers (optional):
251
- * X-Vellum-Client-Id -- stable per-install UUID identifying this client.
252
- * X-Vellum-Interface-Id -- interface type (e.g. "macos", "ios", "web").
252
+ * X-Vellum-Client-Id -- stable per-install UUID identifying this client.
253
+ * X-Vellum-Interface-Id -- interface type (e.g. "macos", "ios", "web").
254
+ * X-Vellum-Client-Version -- optional build/manifest version fingerprint.
255
+ * X-Vellum-Sse-Watchdog -- optional "1" when the client has an SSE idle watchdog.
253
256
  *
254
- * When both are present, the subscriber is registered as a client in the
255
- * event hub with metadata (interfaceId, capabilities). The hub handles
256
- * lifecycle — dispose() unregisters the client automatically.
257
+ * When both identity headers are present, the subscriber is registered as a
258
+ * client in the event hub with metadata (interfaceId, capabilities). The hub
259
+ * handles lifecycle. dispose() unregisters the client automatically.
257
260
  *
258
261
  * Options (for testing):
259
262
  * hub -- override the event hub (defaults to process singleton).
@@ -300,25 +303,18 @@ export function handleSubscribeAssistantEvents(
300
303
  }
301
304
 
302
305
  // ── Client identity from headers ──────────────────────────────────────
303
- const rawClientId = headers?.["x-vellum-client-id"];
304
- const rawInterfaceId = headers?.["x-vellum-interface-id"];
305
306
  const rawMachineName = headers?.["x-vellum-machine-name"];
306
307
  const rawActorPrincipalId = headers?.["x-vellum-actor-principal-id"];
307
- const clientId = rawClientId?.trim() || null;
308
- const interfaceId = clientId
309
- ? parseInterfaceId(rawInterfaceId?.trim())
310
- : null;
311
- // Verified by RuntimeHttpServer and forwarded by the http-adapter from the
312
- // bearer token's AuthContext. May be absent for legacy / service-token
313
- // connections that have no principal. See `resolveActorPrincipalId` for the
314
- // dev-bypass translation rationale.
315
- const actorPrincipalId = resolveActorPrincipalIdForLocalGuardianSync(
316
- rawActorPrincipalId?.trim() || undefined,
308
+ const { clientId, interfaceId, actorPrincipalId } =
309
+ readClientIdentity(headers);
310
+ const clientVersion = sanitizeClientMetadataValue(
311
+ headers?.["x-vellum-client-version"],
317
312
  );
313
+ const sseWatchdog = headers?.["x-vellum-sse-watchdog"]?.trim() === "1";
318
314
 
319
315
  if (clientId && !interfaceId) {
320
316
  log.error(
321
- { clientId, rawInterfaceId },
317
+ { clientId, rawInterfaceId: headers?.["x-vellum-interface-id"] },
322
318
  "client registration failed: invalid or missing X-Vellum-Interface-Id",
323
319
  );
324
320
  throw new BadRequestError(
@@ -410,7 +406,7 @@ export function handleSubscribeAssistantEvents(
410
406
  try {
411
407
  if (controller.desiredSize != null && controller.desiredSize <= 0) {
412
408
  shedReporter("callback_backpressure", instrumentation);
413
- sub.dispose();
409
+ sub.dispose("shed_backpressure");
414
410
  cleanup();
415
411
  return;
416
412
  }
@@ -455,6 +451,8 @@ export function handleSubscribeAssistantEvents(
455
451
  ],
456
452
  machineName: rawMachineName?.trim() || undefined,
457
453
  actorPrincipalId,
454
+ clientVersion,
455
+ sseWatchdog: sseWatchdog || undefined,
458
456
  })
459
457
  : hub.subscribe({
460
458
  ...subscriberBase,
@@ -521,20 +519,9 @@ export function handleSubscribeAssistantEvents(
521
519
  // The client detects the gap from the seq jump on its first
522
520
  // live event and refetches via the existing messages API.
523
521
  if (lastSeenSeq != null) {
524
- const replaySubscriber: ReplaySubscriber =
525
- clientId && interfaceId
526
- ? {
527
- type: "client",
528
- clientId,
529
- interfaceId,
530
- capabilities: ALL_CAPABILITIES.filter((cap) =>
531
- supportsHostProxy(interfaceId, cap),
532
- ),
533
- }
534
- : { type: "process" };
535
522
  const window = getReplayWindow(
536
523
  lastSeenSeq,
537
- replaySubscriber,
524
+ replaySubscriberFor(clientId, interfaceId, actorPrincipalId),
538
525
  filter.conversationId,
539
526
  );
540
527
  if (window !== null) {
@@ -555,7 +542,7 @@ export function handleSubscribeAssistantEvents(
555
542
  try {
556
543
  if (controller.desiredSize != null && controller.desiredSize <= 0) {
557
544
  shedReporter("heartbeat_backpressure", instrumentation);
558
- sub.dispose();
545
+ sub.dispose("shed_backpressure");
559
546
  cleanup();
560
547
  return;
561
548
  }
@@ -590,6 +577,53 @@ export function handleSubscribeAssistantEvents(
590
577
  return stream;
591
578
  }
592
579
 
580
+ /**
581
+ * The caller's client identity, read the same way by the SSE subscribe route
582
+ * and the tail route. The principal is verified by RuntimeHttpServer and
583
+ * forwarded by the http-adapter from the bearer token's AuthContext; it is
584
+ * absent for legacy and service-token connections, and a dev-bypass header
585
+ * resolves to the local guardian (see `resolveActorPrincipalIdForLocalGuardianSync`).
586
+ */
587
+ function readClientIdentity(headers: Record<string, string> | undefined): {
588
+ clientId: string | null;
589
+ interfaceId: InterfaceId | null;
590
+ actorPrincipalId: string | undefined;
591
+ } {
592
+ const clientId = headers?.["x-vellum-client-id"]?.trim() || null;
593
+ const interfaceId = clientId
594
+ ? parseInterfaceId(headers?.["x-vellum-interface-id"]?.trim())
595
+ : null;
596
+ const actorPrincipalId = resolveActorPrincipalIdForLocalGuardianSync(
597
+ headers?.["x-vellum-actor-principal-id"]?.trim() || undefined,
598
+ );
599
+ return { clientId, interfaceId, actorPrincipalId };
600
+ }
601
+
602
+ /**
603
+ * The identity a replay filter checks targeted events against: the client a
604
+ * live SSE subscription with these headers would register, or a process
605
+ * subscriber when the caller names no client. Shared by the reconnect replay
606
+ * and the tail route so both deliver exactly what live fanout would have.
607
+ */
608
+ function replaySubscriberFor(
609
+ clientId: string | null,
610
+ interfaceId: InterfaceId | null,
611
+ actorPrincipalId: string | undefined,
612
+ ): SubscriberIdentity {
613
+ if (!clientId || !interfaceId) {
614
+ return { type: "process" };
615
+ }
616
+ return {
617
+ type: "client",
618
+ clientId,
619
+ interfaceId,
620
+ capabilities: ALL_CAPABILITIES.filter((cap) =>
621
+ supportsHostProxy(interfaceId, cap),
622
+ ),
623
+ actorPrincipalId,
624
+ };
625
+ }
626
+
593
627
  /**
594
628
  * Replay-by-request companion to the SSE `lastSeenSeq` resume: return the
595
629
  * ring-buffered event tail for one conversation with `seq > fromSeq`.
@@ -641,26 +675,13 @@ function handleEventsTail({
641
675
  toSeq = parsed;
642
676
  }
643
677
 
644
- // Same client-identity resolution as the SSE subscribe handler, so the
645
- // replay filter matches what a live subscription would have delivered.
646
- const rawClientId = headers?.["x-vellum-client-id"];
647
- const clientId = rawClientId?.trim() || null;
648
- const interfaceId = clientId
649
- ? parseInterfaceId(headers?.["x-vellum-interface-id"]?.trim())
650
- : null;
651
- const subscriber: ReplaySubscriber | undefined =
652
- clientId && interfaceId
653
- ? {
654
- type: "client",
655
- clientId,
656
- interfaceId,
657
- capabilities: ALL_CAPABILITIES.filter((cap) =>
658
- supportsHostProxy(interfaceId, cap),
659
- ),
660
- }
661
- : undefined;
662
-
663
- const window = getReplayWindow(fromSeq, subscriber, conversationId);
678
+ const { clientId, interfaceId, actorPrincipalId } =
679
+ readClientIdentity(headers);
680
+ const window = getReplayWindow(
681
+ fromSeq,
682
+ replaySubscriberFor(clientId, interfaceId, actorPrincipalId),
683
+ conversationId,
684
+ );
664
685
  if (window === null) {
665
686
  return { events: [], complete: false, frontier: null };
666
687
  }
@@ -228,7 +228,7 @@ function getIdentity() {
228
228
 
229
229
  const version = APP_VERSION;
230
230
 
231
- const createdAt = resolveIdentityCreatedAt(identityPath);
231
+ const createdAt = resolveHatchedAtReadOnly(identityPath);
232
232
 
233
233
  return {
234
234
  name: fields.name ?? "",
@@ -241,10 +241,6 @@ function getIdentity() {
241
241
  };
242
242
  }
243
243
 
244
- function resolveIdentityCreatedAt(identityPath: string): string | undefined {
245
- return resolveHatchedAtReadOnly(identityPath);
246
- }
247
-
248
244
  // ---------------------------------------------------------------------------
249
245
  // Zod schemas for profiler health metadata
250
246
  // ---------------------------------------------------------------------------
@@ -5,10 +5,7 @@ import {
5
5
  resolveImageGenCredentials,
6
6
  resolveImageGenRouting,
7
7
  } from "../../media/image-credentials.js";
8
- import {
9
- describeImageModels,
10
- resolveImageModel,
11
- } from "../../media/image-models.js";
8
+ import { resolveRequestedImageModel } from "../../media/image-models.js";
12
9
  import { generateImage, mapImageGenError } from "../../media/image-service.js";
13
10
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
14
11
  import {
@@ -80,18 +77,15 @@ async function handleImageGenerationGenerate(
80
77
  const config = getConfig();
81
78
  const svc = config.services["image-generation"];
82
79
 
83
- // Resolve tier aliases (fast, quality, openai) to concrete model IDs via
84
- // the registry; reject unknown values with the current catalog so the
85
- // error is self-describing rather than a stale enum.
80
+ // Resolve aliases and OpenRouter slugs against the configured provider.
81
+ // Built-in providers still list the current catalog on unknown values.
86
82
  let resolvedModel = model as string | undefined;
87
83
  if (typeof resolvedModel === "string" && resolvedModel) {
88
- const entry = resolveImageModel(resolvedModel);
89
- if (!entry) {
90
- throw new BadRequestError(
91
- `Unknown model "${resolvedModel}". Available models and aliases:\n${describeImageModels()}`,
92
- );
84
+ const resolved = resolveRequestedImageModel(resolvedModel, svc.provider);
85
+ if (resolved.error) {
86
+ throw new BadRequestError(resolved.error);
93
87
  }
94
- resolvedModel = entry.id;
88
+ resolvedModel = resolved.model;
95
89
  }
96
90
 
97
91
  // Backend and managed-ness resolve together; an explicit model override
@@ -154,7 +148,7 @@ export const ROUTES: RouteDefinition[] = [
154
148
  },
155
149
  summary: "Generate or edit images using AI",
156
150
  description:
157
- "Calls the configured image-generation provider (Gemini or OpenAI) to produce one or more images.",
151
+ "Calls the configured image-generation provider (Gemini, OpenAI, or OpenRouter) to produce one or more images.",
158
152
  tags: ["image-generation"],
159
153
  requestBody: ImageGenerationRequestSchema,
160
154
  responseBody: ImageGenerationResponseSchema,
@@ -106,7 +106,10 @@ import { markProcessed } from "../../persistence/delivery-status.js";
106
106
  import { upsertBinding } from "../../persistence/external-conversation-store.js";
107
107
  import type { ContentBlock } from "../../providers/types.js";
108
108
  import { checkIngressForSecrets } from "../../security/secret-ingress.js";
109
- import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
109
+ import {
110
+ canonicalizeInboundIdentity,
111
+ inboundIdentitiesMatch,
112
+ } from "../../util/canonicalize-identity.js";
110
113
  import { safeParseRecord } from "../../util/json.js";
111
114
  import { getLogger } from "../../util/logger.js";
112
115
  import { truncate } from "../../util/truncate.js";
@@ -126,6 +129,11 @@ import {
126
129
  enforceIngressAcl,
127
130
  } from "./inbound-stages/acl-enforcement.js";
128
131
  import { enforceAdmissionPolicy } from "./inbound-stages/admission-policy.js";
132
+ import {
133
+ type BackfilledSenderProvenance,
134
+ type BackfilledSenderProvenanceResolver,
135
+ createBackfilledSenderProvenanceResolver,
136
+ } from "./inbound-stages/backfill-sender-provenance.js";
129
137
  import { processChannelMessageInBackground } from "./inbound-stages/background-dispatch.js";
130
138
  import { handleBootstrapIntercept } from "./inbound-stages/bootstrap-intercept.js";
131
139
  import { handleEditIntercept } from "./inbound-stages/edit-intercept.js";
@@ -1835,6 +1843,7 @@ async function persistBackfilledSlackMessage(params: {
1835
1843
  message: ProviderMessage;
1836
1844
  account?: string;
1837
1845
  guardianExternalUserId?: string;
1846
+ resolveSenderProvenance: BackfilledSenderProvenanceResolver;
1838
1847
  }): Promise<boolean> {
1839
1848
  const { message } = params;
1840
1849
 
@@ -1872,10 +1881,19 @@ async function persistBackfilledSlackMessage(params: {
1872
1881
  message.metadata,
1873
1882
  "actorTimezoneLabel",
1874
1883
  );
1875
- const isGuardian = isBackfilledSlackGuardianMessage(
1884
+ const role = (await isBackfilledSlackAssistantMessage(
1876
1885
  message,
1877
- params.guardianExternalUserId,
1878
- );
1886
+ params.account,
1887
+ ))
1888
+ ? "assistant"
1889
+ : "user";
1890
+ // The assistant's own posts are replayed as assistant history, not as any
1891
+ // person's words, so only a person's row asks who its sender is.
1892
+ const provenance: BackfilledSenderProvenance =
1893
+ role === "user"
1894
+ ? await params.resolveSenderProvenance(actorExternalUserId)
1895
+ : { provenanceTrustClass: "unknown" };
1896
+ const isGuardian = provenance.provenanceTrustClass === "guardian";
1879
1897
  const slackTranscriptTimestampTimezone =
1880
1898
  resolveSlackTranscriptTimestampTimezone();
1881
1899
  const slackTimezoneFields = buildSlackTimezoneMetadata({
@@ -1899,13 +1917,6 @@ async function persistBackfilledSlackMessage(params: {
1899
1917
  ...(slackFiles.length > 0 ? { slackFiles } : {}),
1900
1918
  };
1901
1919
 
1902
- const role = (await isBackfilledSlackAssistantMessage(
1903
- message,
1904
- params.account,
1905
- ))
1906
- ? "assistant"
1907
- : "user";
1908
-
1909
1920
  const rawText = message.text ?? "";
1910
1921
 
1911
1922
  // `sentAt` is when the message was sent; the row's `createdAt` is when the
@@ -1944,7 +1955,7 @@ async function persistBackfilledSlackMessage(params: {
1944
1955
  metadata: {
1945
1956
  ...envelope,
1946
1957
  ...(sentAt !== undefined ? { sentAt } : {}),
1947
- provenanceTrustClass: isGuardian ? "guardian" : "unknown",
1958
+ ...provenance,
1948
1959
  provenanceSourceChannel: "slack",
1949
1960
  ...(params.guardianExternalUserId
1950
1961
  ? { provenanceGuardianExternalUserId: params.guardianExternalUserId }
@@ -2063,22 +2074,6 @@ async function buildBackfilledSlackContentBlocks(
2063
2074
  return blocks;
2064
2075
  }
2065
2076
 
2066
- function isBackfilledSlackGuardianMessage(
2067
- message: ProviderMessage,
2068
- guardianExternalUserId: string | undefined,
2069
- ): boolean {
2070
- const rawSenderId = message.sender?.id?.trim();
2071
- if (!rawSenderId || !guardianExternalUserId) {
2072
- return false;
2073
- }
2074
- const normalizedSender =
2075
- canonicalizeInboundIdentity("slack", rawSenderId) ?? rawSenderId;
2076
- const normalizedGuardian =
2077
- canonicalizeInboundIdentity("slack", guardianExternalUserId) ??
2078
- guardianExternalUserId.trim();
2079
- return normalizedSender === normalizedGuardian;
2080
- }
2081
-
2082
2077
  const SLACK_ASSISTANT_THREAD_PLACEHOLDER_TEXT = "New Assistant Thread";
2083
2078
 
2084
2079
  async function isSlackAssistantThreadPlaceholder(
@@ -2115,7 +2110,7 @@ async function isBackfilledSlackAssistantMessage(
2115
2110
  return false;
2116
2111
  }
2117
2112
 
2118
- if (rawSenderId && slackIdentityMatches(rawSenderId, botUserId)) {
2113
+ if (rawSenderId && inboundIdentitiesMatch("slack", rawSenderId, botUserId)) {
2119
2114
  return true;
2120
2115
  }
2121
2116
 
@@ -2131,7 +2126,7 @@ async function isBackfilledSlackAssistantMessage(
2131
2126
  const resolvedBotUserId = await resolveSlackBotUserId(account, rawBotId);
2132
2127
  return (
2133
2128
  typeof resolvedBotUserId === "string" &&
2134
- slackIdentityMatches(resolvedBotUserId, botUserId)
2129
+ inboundIdentitiesMatch("slack", resolvedBotUserId, botUserId)
2135
2130
  );
2136
2131
  } catch (err) {
2137
2132
  log.warn(
@@ -2142,14 +2137,6 @@ async function isBackfilledSlackAssistantMessage(
2142
2137
  }
2143
2138
  }
2144
2139
 
2145
- function slackIdentityMatches(left: string, right: string): boolean {
2146
- const canonicalSender =
2147
- canonicalizeInboundIdentity("slack", left) ?? left.trim();
2148
- const canonicalBot =
2149
- canonicalizeInboundIdentity("slack", right) ?? right.trim();
2150
- return canonicalSender === canonicalBot;
2151
- }
2152
-
2153
2140
  /**
2154
2141
  * Transient view of `slackFiles` that preserves the download URLs added by
2155
2142
  * `mapSlackFiles` on the in-flight `ProviderMessage`. These URLs never reach
@@ -2290,6 +2277,10 @@ async function runBackfillSlackDmIfCold(params: {
2290
2277
  // and a transcript that reads correctly when the renderer joins on
2291
2278
  // monotonic createdAt.
2292
2279
  const ordered = [...fetched].reverse();
2280
+ const resolveSenderProvenance = createBackfilledSenderProvenanceResolver(
2281
+ params.guardianExternalUserId,
2282
+ ordered,
2283
+ );
2293
2284
  for (const message of ordered) {
2294
2285
  if (seen.has(message.id)) {
2295
2286
  continue;
@@ -2309,6 +2300,7 @@ async function runBackfillSlackDmIfCold(params: {
2309
2300
  ...(params.guardianExternalUserId
2310
2301
  ? { guardianExternalUserId: params.guardianExternalUserId }
2311
2302
  : {}),
2303
+ resolveSenderProvenance,
2312
2304
  });
2313
2305
  seen.add(message.id);
2314
2306
  if (stored) {
@@ -2890,6 +2882,10 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
2890
2882
  }
2891
2883
 
2892
2884
  let persisted = 0;
2885
+ const resolveSenderProvenance = createBackfilledSenderProvenanceResolver(
2886
+ guardianExternalUserId,
2887
+ fetched,
2888
+ );
2893
2889
  for (const message of fetched) {
2894
2890
  if (!message.id) {
2895
2891
  continue;
@@ -2907,6 +2903,7 @@ export async function triggerSlackThreadBackfillIfNeeded(params: {
2907
2903
  message,
2908
2904
  ...(account ? { account } : {}),
2909
2905
  ...(guardianExternalUserId ? { guardianExternalUserId } : {}),
2906
+ resolveSenderProvenance,
2910
2907
  });
2911
2908
  threadState.storedChannelTs.add(message.id);
2912
2909
  if (stored) {
@@ -0,0 +1,168 @@
1
+ import { afterAll, beforeEach, describe, expect, spyOn, test } from "bun:test";
2
+
3
+ import type { TrustVerdict } from "@vellumai/gateway-client";
4
+
5
+ import type { InboundTrustReadResult } from "../../../calls/inbound-trust-reader.js";
6
+ import * as inboundTrustReader from "../../../calls/inbound-trust-reader.js";
7
+ import type { Message as ProviderMessage } from "../../../messaging/provider-types.js";
8
+ import { createBackfilledSenderProvenanceResolver } from "./backfill-sender-provenance.js";
9
+
10
+ const readsBySender = new Map<string, InboundTrustReadResult>();
11
+ const readInboundTrustMock = spyOn(
12
+ inboundTrustReader,
13
+ "readInboundTrust",
14
+ ).mockImplementation(
15
+ async (input) =>
16
+ readsBySender.get(input.actorExternalId ?? "") ?? { ok: false },
17
+ );
18
+
19
+ afterAll(() => {
20
+ readInboundTrustMock.mockRestore();
21
+ });
22
+
23
+ const GUARDIAN_ID = "U_GUARDIAN";
24
+
25
+ function memberVerdict(
26
+ trustClass: TrustVerdict["trustClass"],
27
+ contactId: string,
28
+ status: string,
29
+ ): TrustVerdict {
30
+ return {
31
+ trustClass,
32
+ canonicalSenderId: `U_${contactId}`,
33
+ contactId,
34
+ channelId: `ch-${contactId}`,
35
+ status,
36
+ policy: "allow",
37
+ };
38
+ }
39
+
40
+ function readOf(verdict: TrustVerdict): InboundTrustReadResult {
41
+ return { ok: true, verdict, admissionPolicy: null };
42
+ }
43
+
44
+ beforeEach(() => {
45
+ readsBySender.clear();
46
+ readInboundTrustMock.mockClear();
47
+ });
48
+
49
+ describe("createBackfilledSenderProvenanceResolver", () => {
50
+ test("stamps the gateway verdict's trust class and contact id", async () => {
51
+ readsBySender.set(
52
+ "U_ALICE",
53
+ readOf(memberVerdict("trusted_contact", "c-alice", "active")),
54
+ );
55
+ readsBySender.set(
56
+ "U_BOB",
57
+ readOf(memberVerdict("unverified_contact", "c-bob", "unverified")),
58
+ );
59
+ const resolve = createBackfilledSenderProvenanceResolver(GUARDIAN_ID, []);
60
+
61
+ expect(await resolve("U_ALICE")).toEqual({
62
+ provenanceTrustClass: "trusted_contact",
63
+ provenanceContactId: "c-alice",
64
+ });
65
+ expect(await resolve("U_BOB")).toEqual({
66
+ provenanceTrustClass: "unverified_contact",
67
+ provenanceContactId: "c-bob",
68
+ });
69
+ expect(readInboundTrustMock).toHaveBeenCalledWith({
70
+ channelType: "slack",
71
+ actorExternalId: "U_ALICE",
72
+ });
73
+ });
74
+
75
+ test("a stranger verdict carries no contact id and no lookup-failed marker", async () => {
76
+ readsBySender.set(
77
+ "U_STRANGER",
78
+ readOf({ trustClass: "unknown", canonicalSenderId: "U_STRANGER" }),
79
+ );
80
+ const resolve = createBackfilledSenderProvenanceResolver(GUARDIAN_ID, []);
81
+
82
+ expect(await resolve("U_STRANGER")).toEqual({
83
+ provenanceTrustClass: "unknown",
84
+ });
85
+ });
86
+
87
+ test("an unreadable verdict falls back to the guardian address comparison and marks the failed lookup", async () => {
88
+ const resolve = createBackfilledSenderProvenanceResolver(GUARDIAN_ID, []);
89
+
90
+ expect(await resolve(GUARDIAN_ID)).toEqual({
91
+ provenanceTrustClass: "guardian",
92
+ provenanceLookupFailed: true,
93
+ });
94
+ expect(await resolve("U_ALICE")).toEqual({
95
+ provenanceTrustClass: "unknown",
96
+ provenanceLookupFailed: true,
97
+ });
98
+ });
99
+
100
+ test("a resolver-failure verdict is not trusted for class or contact id", async () => {
101
+ readsBySender.set(
102
+ "U_ALICE",
103
+ readOf({
104
+ ...memberVerdict("trusted_contact", "c-alice", "active"),
105
+ resolutionFailed: true,
106
+ }),
107
+ );
108
+ const resolve = createBackfilledSenderProvenanceResolver(GUARDIAN_ID, []);
109
+
110
+ expect(await resolve("U_ALICE")).toEqual({
111
+ provenanceTrustClass: "unknown",
112
+ provenanceLookupFailed: true,
113
+ });
114
+ });
115
+
116
+ test("reads the gateway once per distinct sender", async () => {
117
+ readsBySender.set(
118
+ "U_ALICE",
119
+ readOf(memberVerdict("trusted_contact", "c-alice", "active")),
120
+ );
121
+ const resolve = createBackfilledSenderProvenanceResolver(GUARDIAN_ID, []);
122
+
123
+ await Promise.all([
124
+ resolve("U_ALICE"),
125
+ resolve(" U_ALICE "),
126
+ resolve("U_BOB"),
127
+ ]);
128
+ await resolve("U_ALICE");
129
+
130
+ expect(readInboundTrustMock).toHaveBeenCalledTimes(2);
131
+ });
132
+
133
+ test("a row with no sender id is unknown without a gateway read or a lookup-failed marker", async () => {
134
+ const resolve = createBackfilledSenderProvenanceResolver(GUARDIAN_ID, []);
135
+
136
+ expect(await resolve(undefined)).toEqual({
137
+ provenanceTrustClass: "unknown",
138
+ });
139
+ expect(readInboundTrustMock).not.toHaveBeenCalled();
140
+ });
141
+
142
+ test("starts every sender's read up front and together, bots included", () => {
143
+ const message = (id: string, senderId: string, isBot = false) =>
144
+ ({
145
+ id,
146
+ conversationId: "C1",
147
+ sender: { id: senderId, name: senderId },
148
+ text: "hi",
149
+ timestamp: 0,
150
+ platform: "slack",
151
+ ...(isBot ? { metadata: { isBot: true } } : {}),
152
+ }) satisfies ProviderMessage;
153
+
154
+ createBackfilledSenderProvenanceResolver(GUARDIAN_ID, [
155
+ message("1", "U_ALICE"),
156
+ message("2", "B_THIRD_PARTY", true),
157
+ message("3", "U_ALICE"),
158
+ message("4", "U_BOB"),
159
+ ]);
160
+
161
+ // Every read is in flight before any of them has settled.
162
+ expect(readInboundTrustMock.mock.calls.map(([input]) => input)).toEqual([
163
+ { channelType: "slack", actorExternalId: "U_ALICE" },
164
+ { channelType: "slack", actorExternalId: "B_THIRD_PARTY" },
165
+ { channelType: "slack", actorExternalId: "U_BOB" },
166
+ ]);
167
+ });
168
+ });