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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/ARCHITECTURE.md +43 -29
  2. package/Dockerfile +63 -0
  3. package/docs/architecture/security.md +10 -5
  4. package/docs/architecture/turn-actor.md +2 -0
  5. package/docs/desktop-browser-cli.md +45 -4
  6. package/docs/live-voice-task-outcomes.md +100 -0
  7. package/docs/mode-sessions.md +183 -0
  8. package/docs/voice-input-diagnostics.md +58 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
  17. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
  18. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
  19. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  20. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  22. package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
  23. package/openapi.yaml +665 -14
  24. package/package.json +1 -1
  25. package/scripts/smoke-desktop-browser-cli.ts +10 -26
  26. package/scripts/smoke-desktop-window-theme.ts +2 -3
  27. package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
  28. package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
  29. package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
  30. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  31. package/src/__tests__/answered-question-persistence.test.ts +2 -0
  32. package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
  33. package/src/__tests__/approval-message-composer.test.ts +1 -118
  34. package/src/__tests__/approval-routes-http.test.ts +3 -0
  35. package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
  36. package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
  37. package/src/__tests__/assistant-event-targeting.test.ts +186 -0
  38. package/src/__tests__/assistant-stream-state.test.ts +29 -9
  39. package/src/__tests__/browser-fill-credential.test.ts +11 -22
  40. package/src/__tests__/btw-routes.test.ts +4 -1
  41. package/src/__tests__/build-persisted-content.test.ts +1 -1
  42. package/src/__tests__/call-conversation-messages.test.ts +5 -0
  43. package/src/__tests__/call-recovery.test.ts +7 -2
  44. package/src/__tests__/call-routes-http.test.ts +12 -0
  45. package/src/__tests__/call-store.test.ts +7 -2
  46. package/src/__tests__/channel-retry-sweep.test.ts +9 -0
  47. package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
  48. package/src/__tests__/checker.test.ts +11 -3
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
  50. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
  51. package/src/__tests__/computer-use-tools.test.ts +1 -233
  52. package/src/__tests__/config-model-image-provider.test.ts +39 -0
  53. package/src/__tests__/config-schema.test.ts +16 -0
  54. package/src/__tests__/config-watcher.test.ts +24 -0
  55. package/src/__tests__/context-search-agent-runner.test.ts +260 -1
  56. package/src/__tests__/context-search-fanout.test.ts +1 -1
  57. package/src/__tests__/context-search-types.test.ts +7 -7
  58. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
  59. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
  60. package/src/__tests__/conversation-agent-loop.test.ts +546 -16
  61. package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
  62. package/src/__tests__/conversation-evictor.test.ts +62 -37
  63. package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
  64. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
  65. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  66. package/src/__tests__/conversation-retry-route.test.ts +22 -0
  67. package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
  68. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
  69. package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
  70. package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
  73. package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
  74. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
  75. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
  76. package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
  77. package/src/__tests__/conversation-undo.test.ts +61 -0
  78. package/src/__tests__/credential-execution-client.test.ts +6 -6
  79. package/src/__tests__/credential-prompt-route.test.ts +35 -3
  80. package/src/__tests__/cu-unified-flow.test.ts +82 -1
  81. package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
  82. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
  83. package/src/__tests__/dm-backfill.test.ts +91 -1
  84. package/src/__tests__/document-create-route.test.ts +88 -0
  85. package/src/__tests__/document-sync-tags.test.ts +27 -0
  86. package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
  87. package/src/__tests__/events-tail-route.test.ts +25 -0
  88. package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
  89. package/src/__tests__/guardian-action-sweep.test.ts +16 -0
  90. package/src/__tests__/guardian-dispatch.test.ts +13 -0
  91. package/src/__tests__/headless-browser-interactions.test.ts +64 -20
  92. package/src/__tests__/heartbeat-service.test.ts +2 -59
  93. package/src/__tests__/helpers/mock-conversation.ts +21 -0
  94. package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
  95. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
  96. package/src/__tests__/host-cu-proxy.test.ts +46 -2
  97. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  98. package/src/__tests__/image-credentials.test.ts +49 -0
  99. package/src/__tests__/image-service-dispatcher.test.ts +45 -0
  100. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  101. package/src/__tests__/interrupt-on-send.test.ts +57 -43
  102. package/src/__tests__/interrupt-turn-note.test.ts +5 -6
  103. package/src/__tests__/llm-catalog-parity.test.ts +2 -2
  104. package/src/__tests__/media-generate-image.test.ts +49 -1
  105. package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
  106. package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
  107. package/src/__tests__/mode-session-history.test.ts +245 -0
  108. package/src/__tests__/mode-session-recovery.test.ts +222 -0
  109. package/src/__tests__/mode-session-wire.test.ts +141 -0
  110. package/src/__tests__/notification-guardian-path.test.ts +7 -0
  111. package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
  112. package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  115. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  116. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +5 -0
  118. package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
  119. package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
  120. package/src/__tests__/process-message-display-content.test.ts +56 -0
  121. package/src/__tests__/reaction-persistence.test.ts +5 -0
  122. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
  123. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
  124. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
  125. package/src/__tests__/secret-ingress-http.test.ts +2 -0
  126. package/src/__tests__/secure-keys.test.ts +43 -1
  127. package/src/__tests__/send-endpoint-busy.test.ts +163 -1
  128. package/src/__tests__/server-history-render.test.ts +91 -1
  129. package/src/__tests__/settings-routes.test.ts +50 -1
  130. package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
  132. package/src/__tests__/subagent-manager-notify.test.ts +1 -2
  133. package/src/__tests__/subagent-notify-parent.test.ts +146 -39
  134. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
  135. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
  136. package/src/__tests__/thread-backfill.test.ts +53 -0
  137. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  138. package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
  139. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  140. package/src/__tests__/twilio-routes.test.ts +1 -0
  141. package/src/__tests__/web-search-backend-failure.test.ts +1 -1
  142. package/src/activation/progress-store.test.ts +12 -7
  143. package/src/agent/loop.ts +99 -41
  144. package/src/api/events/assistant-turn-start.ts +3 -0
  145. package/src/api/events/generation-handoff.ts +2 -0
  146. package/src/api/events/message-complete.ts +2 -0
  147. package/src/api/events/notification-conversation-created.ts +14 -16
  148. package/src/api/events/notification-intent.ts +4 -2
  149. package/src/api/events/tool-result.ts +85 -0
  150. package/src/api/events/tool-use-start.ts +3 -0
  151. package/src/api/events/user-message-echo.ts +3 -0
  152. package/src/api/index.ts +31 -6
  153. package/src/api/mode-session.test.ts +131 -0
  154. package/src/api/mode-session.ts +148 -0
  155. package/src/api/remember-tool.ts +15 -0
  156. package/src/api/responses/conversation-message.ts +6 -0
  157. package/src/approvals/AGENTS.md +4 -2
  158. package/src/browser/__tests__/operations.test.ts +29 -0
  159. package/src/browser/operation-meta.ts +2 -1
  160. package/src/browser/operations.ts +29 -0
  161. package/src/browser/virtual-desktop-target.ts +2 -8
  162. package/src/bundler/bundle-signer.ts +1 -3
  163. package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
  164. package/src/calls/__tests__/call-controller.test.ts +94 -0
  165. package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
  166. package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
  167. package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
  168. package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
  169. package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
  170. package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
  171. package/src/calls/barge-in-guard.ts +5 -0
  172. package/src/calls/call-controller.ts +196 -1
  173. package/src/calls/call-domain.ts +4 -0
  174. package/src/calls/call-store.ts +87 -2
  175. package/src/calls/guardian-action-sweep.ts +22 -8
  176. package/src/calls/media-stream-audio-transcode.ts +1 -3
  177. package/src/calls/typesafe-noul.ts +156 -0
  178. package/src/calls/voice-control-protocol.ts +6 -0
  179. package/src/calls/voice-escalation-judge.ts +145 -0
  180. package/src/calls/voice-escalation-target.ts +10 -0
  181. package/src/calls/voice-leg-coordinator.ts +121 -3
  182. package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
  183. package/src/calls/voice-session-bridge.ts +256 -112
  184. package/src/calls/voice-triage-escalate.ts +7 -0
  185. package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
  186. package/src/cli/commands/__tests__/notifications.test.ts +35 -10
  187. package/src/cli/commands/browser.help.ts +4 -5
  188. package/src/cli/commands/browser.ts +0 -1
  189. package/src/cli/commands/clients.help.ts +46 -0
  190. package/src/cli/commands/clients.ts +116 -0
  191. package/src/cli/commands/credentials.help.ts +6 -1
  192. package/src/cli/commands/notifications.help.ts +16 -2
  193. package/src/cli/commands/notifications.ts +30 -8
  194. package/src/cli/commands/roadmap.help.ts +18 -21
  195. package/src/cli/commands/roadmap.ts +4 -21
  196. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
  197. package/src/cli/lib/bundled-marketplace.json +84 -34
  198. package/src/cli/lib/plugin-marketplace.ts +8 -0
  199. package/src/cli/lib/publish-plugin.ts +1 -2
  200. package/src/config/__tests__/default-provider.test.ts +3 -1
  201. package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
  202. package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
  203. package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
  204. package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
  205. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
  206. package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
  207. package/src/config/call-site-defaults.ts +9 -0
  208. package/src/config/feature-flag-registry.json +16 -0
  209. package/src/config/profile-text-generation.test.ts +2 -2
  210. package/src/config/schemas/__tests__/tools.test.ts +31 -0
  211. package/src/config/schemas/call-site-catalog.ts +14 -0
  212. package/src/config/schemas/channels.ts +0 -18
  213. package/src/config/schemas/llm.ts +3 -1
  214. package/src/config/schemas/mcp.ts +2 -1
  215. package/src/config/schemas/memory-retrospective.ts +11 -2
  216. package/src/config/schemas/services.ts +15 -6
  217. package/src/config/schemas/tools.ts +10 -0
  218. package/src/config/session-groups-gate.ts +21 -0
  219. package/src/config/skills.ts +2 -6
  220. package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
  221. package/src/conversations/message-consolidation.ts +102 -5
  222. package/src/credential-execution/ces-connect.test.ts +69 -0
  223. package/src/credential-execution/ces-connect.ts +115 -0
  224. package/src/credential-execution/ces-runtime.ts +55 -93
  225. package/src/credential-execution/executable-discovery.ts +1 -11
  226. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
  227. package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
  228. package/src/daemon/browser-mode-session.test.ts +402 -0
  229. package/src/daemon/browser-mode-session.ts +186 -0
  230. package/src/daemon/computer-use-mode-session.test.ts +210 -0
  231. package/src/daemon/computer-use-mode-session.ts +105 -0
  232. package/src/daemon/config-watcher.ts +10 -0
  233. package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
  234. package/src/daemon/conversation-agent-loop.ts +219 -25
  235. package/src/daemon/conversation-evictor.ts +5 -24
  236. package/src/daemon/conversation-history.ts +10 -0
  237. package/src/daemon/conversation-interrupt-repair.ts +4 -43
  238. package/src/daemon/conversation-interrupt.ts +7 -11
  239. package/src/daemon/conversation-messaging.ts +58 -5
  240. package/src/daemon/conversation-mode-session.test.ts +1667 -0
  241. package/src/daemon/conversation-mode-session.ts +1008 -0
  242. package/src/daemon/conversation-process.ts +5 -0
  243. package/src/daemon/conversation-runtime-assembly.ts +10 -20
  244. package/src/daemon/conversation-skill-tools.ts +1 -0
  245. package/src/daemon/conversation-store.ts +4 -4
  246. package/src/daemon/conversation-surface-snapshots.ts +135 -0
  247. package/src/daemon/conversation-surfaces.ts +113 -5
  248. package/src/daemon/conversation-tool-setup.ts +8 -3
  249. package/src/daemon/conversation.ts +143 -41
  250. package/src/daemon/handlers/config-model.ts +7 -5
  251. package/src/daemon/handlers/shared.ts +55 -16
  252. package/src/daemon/host-cu-proxy.ts +51 -22
  253. package/src/daemon/lifecycle.ts +30 -13
  254. package/src/daemon/message-protocol.ts +0 -1
  255. package/src/daemon/message-provenance.ts +28 -4
  256. package/src/daemon/mode-session-startup-recovery.ts +23 -0
  257. package/src/daemon/mode-session-tracking.ts +54 -0
  258. package/src/daemon/process-message.ts +13 -4
  259. package/src/daemon/virtual-desktop-context.ts +21 -0
  260. package/src/desktop/__tests__/fake-desktop.ts +1 -0
  261. package/src/desktop/desktop-accessibility-script.ts +88 -0
  262. package/src/desktop/desktop-accessibility.test.ts +160 -0
  263. package/src/desktop/desktop-accessibility.ts +169 -0
  264. package/src/desktop/desktop-automation-lease.test.ts +76 -68
  265. package/src/desktop/desktop-automation-lease.ts +41 -50
  266. package/src/desktop/desktop-browser-endpoint.ts +4 -0
  267. package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
  268. package/src/desktop/desktop-computer-use.test.ts +449 -0
  269. package/src/desktop/desktop-computer-use.ts +502 -0
  270. package/src/desktop/desktop-dependencies.ts +47 -293
  271. package/src/desktop/desktop-display.ts +2 -2
  272. package/src/desktop/desktop-panel-config.test.ts +13 -3
  273. package/src/desktop/desktop-panel-config.ts +109 -15
  274. package/src/desktop/desktop-session-manager.test.ts +69 -21
  275. package/src/desktop/desktop-session-manager.ts +76 -22
  276. package/src/desktop/desktop-stream-bridge.test.ts +11 -122
  277. package/src/desktop/desktop-stream-bridge.ts +6 -8
  278. package/src/desktop/desktop-window-theme.ts +5 -25
  279. package/src/desktop/virtual-desktop-feature.ts +52 -2
  280. package/src/desktop/virtual-desktop-platform.test.ts +5 -9
  281. package/src/documents/document-store.ts +23 -0
  282. package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
  283. package/src/heartbeat/heartbeat-service.ts +5 -33
  284. package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
  285. package/src/ipc/assistant-server.ts +2 -0
  286. package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
  287. package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
  288. package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
  289. package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
  290. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
  291. package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
  292. package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
  293. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
  294. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
  295. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
  296. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
  297. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
  298. package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
  299. package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
  300. package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
  301. package/src/live-voice/__tests__/session-controls.test.ts +23 -0
  302. package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
  303. package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
  304. package/src/live-voice/camera-mode-session.test.ts +265 -0
  305. package/src/live-voice/camera-mode-session.ts +277 -0
  306. package/src/live-voice/continuation-judge.ts +123 -0
  307. package/src/live-voice/live-activity-reporter.ts +115 -6
  308. package/src/live-voice/live-voice-manager.ts +36 -1
  309. package/src/live-voice/live-voice-photo.ts +88 -7
  310. package/src/live-voice/live-voice-session-manager.ts +15 -0
  311. package/src/live-voice/live-voice-session.ts +1443 -747
  312. package/src/live-voice/live-voice-tts.ts +22 -4
  313. package/src/live-voice/microphone-idle-gate.ts +56 -0
  314. package/src/live-voice/protocol.ts +171 -4
  315. package/src/live-voice/session-controls.ts +11 -3
  316. package/src/live-voice/task-outcomes.ts +131 -0
  317. package/src/live-voice/voice-input-diagnostics.ts +117 -0
  318. package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
  319. package/src/mcp/__tests__/tool-caps.test.ts +28 -1
  320. package/src/mcp/manager.ts +3 -0
  321. package/src/mcp/tool-caps.ts +12 -4
  322. package/src/media/__tests__/image-models.test.ts +58 -0
  323. package/src/media/image-credentials.ts +5 -0
  324. package/src/media/image-models.ts +52 -0
  325. package/src/media/image-service.ts +8 -0
  326. package/src/media/openrouter-image-service.test.ts +133 -0
  327. package/src/media/openrouter-image-service.ts +153 -0
  328. package/src/media/types.ts +1 -1
  329. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
  330. package/src/messaging/providers/discord/send.ts +4 -17
  331. package/src/messaging/providers/discord/transport.ts +2 -2
  332. package/src/messaging/providers/outlook/client.ts +16 -22
  333. package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
  334. package/src/messaging/providers/telegram-bot/send.ts +10 -7
  335. package/src/messaging/providers/telegram-bot/transport.ts +3 -10
  336. package/src/messaging/providers/whatsapp/send.test.ts +169 -4
  337. package/src/monitoring/__tests__/control.test.ts +17 -5
  338. package/src/monitoring/control.ts +15 -5
  339. package/src/notifications/AGENTS.md +1 -1
  340. package/src/notifications/README.md +83 -99
  341. package/src/notifications/__tests__/broadcaster.test.ts +1 -1
  342. package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
  343. package/src/notifications/__tests__/copy-composer.test.ts +1 -1
  344. package/src/notifications/__tests__/decision-engine.test.ts +79 -2
  345. package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
  346. package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
  347. package/src/notifications/__tests__/notification-utils.test.ts +18 -0
  348. package/src/notifications/adapters/macos.ts +37 -46
  349. package/src/notifications/broadcaster.ts +5 -6
  350. package/src/notifications/channel-allowlist.ts +44 -0
  351. package/src/notifications/copy-composer.ts +0 -23
  352. package/src/notifications/decision-engine.ts +24 -14
  353. package/src/notifications/deterministic-checks.ts +1 -1
  354. package/src/notifications/emit-signal.ts +123 -37
  355. package/src/notifications/home-feed-side-effect.ts +9 -7
  356. package/src/notifications/notification-utils.ts +19 -0
  357. package/src/notifications/signal.ts +4 -19
  358. package/src/notifications/types.ts +15 -0
  359. package/src/oauth/connection-resolver.ts +1 -2
  360. package/src/oauth/oauth-store.ts +1 -3
  361. package/src/oauth/provider-serializer.ts +7 -0
  362. package/src/oauth/seed-providers.ts +30 -0
  363. package/src/onboarding/onboarding-events-store.ts +99 -12
  364. package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
  365. package/src/persistence/client-connection-events-store.ts +377 -0
  366. package/src/persistence/conversation-crud.ts +55 -6
  367. package/src/persistence/conversation-mode-sessions.test.ts +424 -0
  368. package/src/persistence/conversation-mode-sessions.ts +460 -0
  369. package/src/persistence/conversation-queries.ts +6 -19
  370. package/src/persistence/llm-usage-store.ts +3 -9
  371. package/src/persistence/message-metadata.ts +25 -0
  372. package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
  373. package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
  374. package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
  375. package/src/persistence/schema/conversations.ts +67 -0
  376. package/src/persistence/schema/infrastructure.ts +29 -0
  377. package/src/persistence/steps.ts +17 -0
  378. package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
  379. package/src/plugin-api/effective-context-window.ts +33 -0
  380. package/src/plugin-api/index.ts +24 -5
  381. package/src/plugin-api/plugin-name-env.ts +125 -0
  382. package/src/plugin-api/resolve-credential.ts +79 -20
  383. package/src/plugin-api/vision-support.test.ts +22 -0
  384. package/src/plugin-api/vision-support.ts +17 -5
  385. package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
  386. package/src/plugins/active-plugin-env.ts +51 -0
  387. package/src/plugins/defaults/index.ts +24 -0
  388. package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
  389. package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
  390. package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
  391. package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
  392. package/src/plugins/defaults/memory/context-search/format.ts +51 -7
  393. package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
  394. package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
  395. package/src/plugins/defaults/memory/context-search/search.ts +8 -14
  396. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
  397. package/src/plugins/defaults/memory/context-search/types.ts +1 -11
  398. package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
  399. package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
  400. package/src/plugins/defaults/memory/graph/tools.ts +35 -64
  401. package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
  402. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
  403. package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
  404. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
  405. package/src/plugins/defaults/memory/tools.test.ts +124 -9
  406. package/src/plugins/defaults/memory/tools.ts +38 -11
  407. package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
  408. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
  409. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
  410. package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
  411. package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
  412. package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
  413. package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
  414. package/src/plugins/defaults/memory/worker-control.ts +15 -5
  415. package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
  416. package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
  417. package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
  418. package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
  419. package/src/plugins/defaults/task-progress/package.json +14 -0
  420. package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
  421. package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
  422. package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
  423. package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
  424. package/src/providers/anthropic/client.ts +10 -1
  425. package/src/providers/anthropic-gateway-shared.ts +6 -0
  426. package/src/providers/fetch-provider-catalog.ts +11 -0
  427. package/src/providers/gemini/client.ts +25 -8
  428. package/src/providers/inference/adapter-factory.ts +1 -1
  429. package/src/providers/jev/client.test.ts +10 -0
  430. package/src/providers/jev/client.ts +20 -4
  431. package/src/providers/model-catalog.ts +4 -3
  432. package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
  433. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
  434. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
  435. package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
  436. package/src/providers/openai/chat-completions-provider.ts +100 -29
  437. package/src/providers/openai/google-thought-signature.ts +0 -6
  438. package/src/providers/openai/responses-provider.ts +23 -1
  439. package/src/providers/ratelimit.ts +1 -3
  440. package/src/providers/search-provider-catalog.ts +24 -0
  441. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
  442. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
  443. package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
  444. package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
  445. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
  446. package/src/providers/types.ts +5 -0
  447. package/src/routes/control.ts +7 -5
  448. package/src/runtime/AGENTS.md +4 -2
  449. package/src/runtime/actor-trust-resolver.ts +3 -3
  450. package/src/runtime/agent-wake.ts +1 -1
  451. package/src/runtime/approval-message-composer.ts +3 -69
  452. package/src/runtime/assistant-event-hub.ts +73 -75
  453. package/src/runtime/assistant-event-publish-options.ts +7 -0
  454. package/src/runtime/assistant-event-targeting.ts +79 -0
  455. package/src/runtime/assistant-stream-state.ts +13 -100
  456. package/src/runtime/background-job-runner.ts +5 -5
  457. package/src/runtime/channel-retry-sweep.ts +1 -0
  458. package/src/runtime/guardian-action-message-composer.ts +8 -161
  459. package/src/runtime/guardian-reply-router.ts +28 -32
  460. package/src/runtime/http-types.ts +6 -0
  461. package/src/runtime/message-composer-types.ts +3 -61
  462. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
  463. package/src/runtime/migrations/vbundle-builder.ts +24 -1
  464. package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
  465. package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
  466. package/src/runtime/migrations/vbundle-importer.ts +4 -1
  467. package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
  468. package/src/runtime/migrations/vbundle-validator.ts +1 -0
  469. package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
  470. package/src/runtime/pending-interactions.ts +29 -0
  471. package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
  472. package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
  473. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
  474. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
  475. package/src/runtime/routes/browser-context.ts +31 -11
  476. package/src/runtime/routes/browser-routes.ts +43 -4
  477. package/src/runtime/routes/btw-routes.ts +9 -4
  478. package/src/runtime/routes/client-routes.ts +108 -0
  479. package/src/runtime/routes/conversation-management-routes.ts +4 -1
  480. package/src/runtime/routes/conversation-routes.ts +113 -27
  481. package/src/runtime/routes/credential-prompt-routes.ts +4 -3
  482. package/src/runtime/routes/credential-routes.ts +1 -2
  483. package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
  484. package/src/runtime/routes/desktop-setup-routes.ts +4 -6
  485. package/src/runtime/routes/documents-routes.ts +51 -0
  486. package/src/runtime/routes/email-routes.ts +3 -7
  487. package/src/runtime/routes/events-routes.ts +75 -54
  488. package/src/runtime/routes/identity-routes.ts +1 -5
  489. package/src/runtime/routes/image-generation-routes.ts +8 -14
  490. package/src/runtime/routes/inbound-message-handler.ts +35 -38
  491. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
  492. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
  493. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
  494. package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
  495. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
  496. package/src/runtime/routes/llm-context-normalization.ts +1 -7
  497. package/src/runtime/routes/migration-routes.ts +73 -2
  498. package/src/runtime/routes/notification-routes.ts +10 -0
  499. package/src/runtime/routes/oauth-providers.ts +7 -0
  500. package/src/runtime/routes/plugins-routes.ts +6 -0
  501. package/src/runtime/routes/roadmap-routes.ts +47 -8
  502. package/src/runtime/routes/secret-routes.ts +1 -1
  503. package/src/runtime/routes/settings-routes.ts +10 -4
  504. package/src/runtime/routes/watcher-routes.ts +1 -3
  505. package/src/runtime/trust-class.test.ts +7 -6
  506. package/src/runtime/trust-class.ts +9 -17
  507. package/src/schedule/__tests__/worker-control.test.ts +36 -5
  508. package/src/schedule/worker-control.ts +15 -5
  509. package/src/security/secure-keys.ts +53 -103
  510. package/src/skills/clawhub.ts +3 -8
  511. package/src/skills/managed-store.ts +1 -5
  512. package/src/stt/types.ts +4 -0
  513. package/src/subagent/notify.ts +54 -34
  514. package/src/subagent/parent-notification.ts +6 -0
  515. package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
  516. package/src/telemetry/phone-call-funnel.ts +144 -0
  517. package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
  518. package/src/tools/ask-question/ask-question-tool.ts +6 -4
  519. package/src/tools/browser/browser-execution.ts +22 -59
  520. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
  521. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
  522. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
  523. package/src/tools/client-os.ts +15 -0
  524. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  525. package/src/tools/computer-use/target.ts +42 -0
  526. package/src/tools/document/document-tool.ts +13 -18
  527. package/src/tools/execution-target.ts +14 -16
  528. package/src/tools/host-terminal/host-shell.ts +4 -1
  529. package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
  530. package/src/tools/network/__tests__/web-search.test.ts +203 -0
  531. package/src/tools/network/web-fetch.ts +274 -7
  532. package/src/tools/network/web-search.ts +468 -6
  533. package/src/tools/permission-checker.ts +3 -3
  534. package/src/tools/policy-context.ts +3 -1
  535. package/src/tools/skills/load.ts +2 -1
  536. package/src/tools/skills/sandbox-runner.ts +11 -2
  537. package/src/tools/skills/skill-script-runner.ts +7 -0
  538. package/src/tools/skills/skill-tool-factory.ts +15 -1
  539. package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
  540. package/src/tools/terminal/safe-env.ts +4 -0
  541. package/src/tools/terminal/shell.ts +2 -0
  542. package/src/tools/tool-approval-handler.ts +5 -1
  543. package/src/tools/tool-types.ts +3 -3
  544. package/src/tools/types.ts +8 -2
  545. package/src/util/__tests__/worker-process-command.test.ts +5 -4
  546. package/src/util/abort-reasons.ts +18 -26
  547. package/src/util/canonicalize-identity.test.ts +21 -0
  548. package/src/util/canonicalize-identity.ts +16 -0
  549. package/src/util/errors.ts +13 -3
  550. package/src/util/provider-error-patterns.ts +3 -0
  551. package/src/util/worker-process.ts +77 -35
  552. package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
  553. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
  554. package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
  555. package/src/workspace/migrations/registry.ts +2 -0
  556. package/src/__tests__/computer-use-window-schema.test.ts +0 -24
  557. package/src/api/events/heartbeat-alert.ts +0 -20
  558. package/src/daemon/message-types/web-activity.ts +0 -65
  559. package/src/desktop/desktop-dependencies.test.ts +0 -131
  560. package/src/tools/computer-use/definitions.ts +0 -607
@@ -0,0 +1,175 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import {
7
+ downCreateConversationModeSessions,
8
+ migrateCreateConversationModeSessions,
9
+ } from "../persistence/migrations/381-create-conversation-mode-sessions.js";
10
+ import * as schema from "../persistence/schema.js";
11
+
12
+ function createPreMigrationDb() {
13
+ const sqlite = new Database(":memory:");
14
+ sqlite.exec(/* sql */ `
15
+ PRAGMA foreign_keys = ON;
16
+ CREATE TABLE conversations (
17
+ id TEXT PRIMARY KEY
18
+ );
19
+ `);
20
+ return { sqlite, db: drizzle(sqlite, { schema }) };
21
+ }
22
+
23
+ function tableExists(sqlite: Database): boolean {
24
+ return (
25
+ sqlite
26
+ .query(
27
+ "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'conversation_mode_sessions'",
28
+ )
29
+ .get() != null
30
+ );
31
+ }
32
+
33
+ describe("migration 380: conversation mode sessions", () => {
34
+ test("creates the table and indexed conversation lookup", () => {
35
+ const { sqlite, db } = createPreMigrationDb();
36
+ migrateCreateConversationModeSessions(db);
37
+
38
+ expect(tableExists(sqlite)).toBe(true);
39
+ const indexes = sqlite
40
+ .query("PRAGMA index_list(conversation_mode_sessions)")
41
+ .all() as Array<{ name: string }>;
42
+ expect(indexes.map((index) => index.name)).toContain(
43
+ "idx_conversation_mode_sessions_conversation_status",
44
+ );
45
+ });
46
+
47
+ test("is idempotent on an existing migrated database", () => {
48
+ const { sqlite, db } = createPreMigrationDb();
49
+ migrateCreateConversationModeSessions(db);
50
+ expect(() => migrateCreateConversationModeSessions(db)).not.toThrow();
51
+
52
+ expect(tableExists(sqlite)).toBe(true);
53
+ });
54
+
55
+ test("supports rollback and reapply", () => {
56
+ const { sqlite, db } = createPreMigrationDb();
57
+ migrateCreateConversationModeSessions(db);
58
+ downCreateConversationModeSessions(db);
59
+ expect(tableExists(sqlite)).toBe(false);
60
+
61
+ migrateCreateConversationModeSessions(db);
62
+ expect(tableExists(sqlite)).toBe(true);
63
+ });
64
+
65
+ test("enforces status values and deletes rows with their conversation", () => {
66
+ const { sqlite, db } = createPreMigrationDb();
67
+ migrateCreateConversationModeSessions(db);
68
+ sqlite.exec(/* sql */ `
69
+ INSERT INTO conversations (id) VALUES ('conv-123');
70
+ INSERT INTO conversation_mode_sessions (
71
+ id, conversation_id, mode, status, source_started_at, last_activity_at
72
+ ) VALUES (
73
+ 'session-123', 'conv-123', 'browser', 'active', 100, 100
74
+ );
75
+ `);
76
+
77
+ expect(() =>
78
+ sqlite
79
+ .query(
80
+ /* sql */ `
81
+ INSERT INTO conversation_mode_sessions (
82
+ id, conversation_id, mode, status, source_started_at, last_activity_at
83
+ ) VALUES (
84
+ 'session-456', 'conv-123', 'browser', 'waiting', 100, 100
85
+ )
86
+ `,
87
+ )
88
+ .run(),
89
+ ).toThrow();
90
+
91
+ expect(() =>
92
+ sqlite
93
+ .query(
94
+ /* sql */ `
95
+ INSERT INTO conversation_mode_sessions (
96
+ id, conversation_id, mode, status, source_started_at, last_activity_at,
97
+ ended_at, end_reason
98
+ ) VALUES (
99
+ 'session-completed-without-end', 'conv-123', 'browser', 'completed',
100
+ 100, 100, NULL, 'settled'
101
+ )
102
+ `,
103
+ )
104
+ .run(),
105
+ ).toThrow();
106
+
107
+ expect(() =>
108
+ sqlite
109
+ .query(
110
+ /* sql */ `
111
+ INSERT INTO conversation_mode_sessions (
112
+ id, conversation_id, mode, status, source_started_at, last_activity_at,
113
+ ended_at, end_reason
114
+ ) VALUES (
115
+ 'session-empty-reason', 'conv-123', 'browser', 'completed',
116
+ 100, 100, 125, ''
117
+ )
118
+ `,
119
+ )
120
+ .run(),
121
+ ).toThrow();
122
+
123
+ expect(() =>
124
+ sqlite
125
+ .query(
126
+ /* sql */ `
127
+ INSERT INTO conversation_mode_sessions (
128
+ id, conversation_id, mode, status, source_started_at, last_activity_at,
129
+ ended_at, end_reason
130
+ ) VALUES (
131
+ 'session-null-reason', 'conv-123', 'browser', 'completed',
132
+ 100, 100, 125, NULL
133
+ )
134
+ `,
135
+ )
136
+ .run(),
137
+ ).toThrow();
138
+
139
+ expect(() =>
140
+ sqlite
141
+ .query(
142
+ /* sql */ `
143
+ INSERT INTO conversation_mode_sessions (
144
+ id, conversation_id, mode, status, source_started_at, last_activity_at,
145
+ ended_at, end_reason
146
+ ) VALUES (
147
+ 'session-backwards-end', 'conv-123', 'browser', 'interrupted',
148
+ 100, 150, 125, 'connection_lost'
149
+ )
150
+ `,
151
+ )
152
+ .run(),
153
+ ).toThrow();
154
+
155
+ expect(() =>
156
+ sqlite
157
+ .query(
158
+ /* sql */ `
159
+ INSERT INTO conversation_mode_sessions (
160
+ id, conversation_id, mode, status, source_started_at,
161
+ first_included_at, last_activity_at
162
+ ) VALUES (
163
+ 'session-half-boundary', 'conv-123', 'browser', 'active', 100, 110, 110
164
+ )
165
+ `,
166
+ )
167
+ .run(),
168
+ ).toThrow();
169
+
170
+ sqlite.exec("DELETE FROM conversations WHERE id = 'conv-123'");
171
+ expect(
172
+ sqlite.query("SELECT id FROM conversation_mode_sessions").all(),
173
+ ).toEqual([]);
174
+ });
175
+ });
@@ -153,6 +153,7 @@ describe("Call session mode metadata", () => {
153
153
 
154
154
  const { conversationId } = getOrCreateConversation("test-conv-mode");
155
155
  const session = createCallSession({
156
+ direction: "inbound",
156
157
  conversationId,
157
158
  provider: "twilio",
158
159
  fromNumber: "+15551234567",
@@ -181,6 +182,7 @@ describe("Call session mode metadata", () => {
181
182
  "test-conv-mode-default",
182
183
  );
183
184
  const session = createCallSession({
185
+ direction: "inbound",
184
186
  conversationId,
185
187
  provider: "twilio",
186
188
  fromNumber: "+15551234567",
@@ -11,7 +11,15 @@
11
11
  * - When `backfillDm` throws, the turn proceeds without a crash and
12
12
  * nothing extra is persisted.
13
13
  */
14
- import { beforeEach, describe, expect, mock, test } from "bun:test";
14
+ import {
15
+ afterAll,
16
+ beforeEach,
17
+ describe,
18
+ expect,
19
+ mock,
20
+ spyOn,
21
+ test,
22
+ } from "bun:test";
15
23
 
16
24
  // ---------------------------------------------------------------------------
17
25
  // Mocks (must precede module imports under test)
@@ -68,6 +76,7 @@ mock.module("../messaging/providers/slack/backfill.js", () => ({
68
76
  backfillThread: () => backfillThreadMock(),
69
77
  }));
70
78
 
79
+ import * as inboundTrustReader from "../calls/inbound-trust-reader.js";
71
80
  import {
72
81
  loadRawConfig,
73
82
  saveRawConfig,
@@ -87,6 +96,15 @@ import {
87
96
 
88
97
  await initializeDb();
89
98
 
99
+ // Backfilled senders are classified by the gateway verdict; with no gateway
100
+ // in the test process the read reports unreachable unless a test says so.
101
+ // A spy rather than mock.module, so the stub does not leak into other files.
102
+ const readInboundTrustMock = spyOn(inboundTrustReader, "readInboundTrust");
103
+
104
+ afterAll(() => {
105
+ readInboundTrustMock.mockRestore();
106
+ });
107
+
90
108
  // ---------------------------------------------------------------------------
91
109
  // Helpers
92
110
  // ---------------------------------------------------------------------------
@@ -180,6 +198,8 @@ function readPersistedSlackRows(): Array<{
180
198
  provenanceSourceChannel: string | undefined;
181
199
  provenanceGuardianExternalUserId: string | undefined;
182
200
  provenanceRequesterIdentifier: string | undefined;
201
+ provenanceContactId: string | undefined;
202
+ provenanceLookupFailed: boolean | undefined;
183
203
  sentAt: number | undefined;
184
204
  createdAt: number;
185
205
  }> {
@@ -234,6 +254,14 @@ function readPersistedSlackRows(): Array<{
234
254
  typeof envelope.provenanceRequesterIdentifier === "string"
235
255
  ? envelope.provenanceRequesterIdentifier
236
256
  : undefined,
257
+ provenanceContactId:
258
+ typeof envelope.provenanceContactId === "string"
259
+ ? envelope.provenanceContactId
260
+ : undefined,
261
+ provenanceLookupFailed:
262
+ typeof envelope.provenanceLookupFailed === "boolean"
263
+ ? envelope.provenanceLookupFailed
264
+ : undefined,
237
265
  sentAt:
238
266
  typeof envelope.sentAt === "number" ? envelope.sentAt : undefined,
239
267
  createdAt: row.createdAt,
@@ -279,6 +307,8 @@ describe("PR 23 — Slack DM cold-start backfill", () => {
279
307
  backfillDmMock.mockReset();
280
308
  backfillDmMock.mockImplementation(async () => []);
281
309
  backfillThreadMock.mockReset();
310
+ readInboundTrustMock.mockReset();
311
+ readInboundTrustMock.mockImplementation(async () => ({ ok: false }));
282
312
  });
283
313
 
284
314
  test("first DM in cold conversation triggers backfill exactly once and persists history", async () => {
@@ -335,6 +365,8 @@ describe("PR 23 — Slack DM cold-start backfill", () => {
335
365
  expect(meta!.channelId).toBe(SLACK_DM_CHANNEL_ID);
336
366
  expect(meta!.actorExternalUserId).toBe(SLACK_DM_USER_ID);
337
367
  expect(r.provenanceTrustClass).toBe("unknown");
368
+ // No gateway in the test process: the lookup failed, and the row says so.
369
+ expect(r.provenanceLookupFailed).toBe(true);
338
370
  expect(r.provenanceSourceChannel).toBe("slack");
339
371
  expect(r.provenanceRequesterIdentifier).toBe(SLACK_DM_USER_ID);
340
372
  return meta!.channelTs;
@@ -523,6 +555,64 @@ describe("PR 23 — Slack DM cold-start backfill", () => {
523
555
  expect(botRow?.provenanceRequesterIdentifier).toBe("B_BOT");
524
556
  });
525
557
 
558
+ test("backfilled rows record the sender's gateway trust class and contact id", async () => {
559
+ readInboundTrustMock.mockImplementation(async (input) =>
560
+ input.actorExternalId === SLACK_DM_USER_ID
561
+ ? {
562
+ ok: true,
563
+ verdict: {
564
+ trustClass: "trusted_contact",
565
+ canonicalSenderId: SLACK_DM_USER_ID,
566
+ contactId: "contact-dm-user",
567
+ channelId: "channel-dm-user",
568
+ status: "active",
569
+ policy: "allow",
570
+ },
571
+ admissionPolicy: null,
572
+ }
573
+ : { ok: false },
574
+ );
575
+ backfillDmMock.mockImplementation(async () => [
576
+ makeBackfilledMessage({
577
+ id: "1700000000.000001",
578
+ text: "first from member",
579
+ }),
580
+ makeBackfilledMessage({
581
+ id: "1700000000.000002",
582
+ text: "assistant's own post",
583
+ sender: { id: "U_BOT", name: "assistant-bot" },
584
+ metadata: { isBot: true },
585
+ }),
586
+ makeBackfilledMessage({
587
+ id: "1700000000.000003",
588
+ text: "second from member",
589
+ }),
590
+ ]);
591
+
592
+ await handleChannelInbound(
593
+ buildDmRequest("live new DM"),
594
+ noopProcessMessage,
595
+ TEST_BEARER_TOKEN,
596
+ );
597
+
598
+ const rows = readPersistedSlackRows();
599
+ const memberRows = rows.filter((r) => r.role === "user");
600
+ expect(memberRows.map((r) => r.content).sort()).toEqual([
601
+ "first from member",
602
+ "second from member",
603
+ ]);
604
+ for (const row of memberRows) {
605
+ expect(row.provenanceTrustClass).toBe("trusted_contact");
606
+ expect(row.provenanceContactId).toBe("contact-dm-user");
607
+ expect(row.provenanceLookupFailed).toBeUndefined();
608
+ }
609
+ const ownPost = rows.find((r) => r.role === "assistant");
610
+ expect(ownPost?.provenanceTrustClass).toBe("unknown");
611
+ expect(ownPost?.provenanceContactId).toBeUndefined();
612
+ // One read per distinct sender, the member's rows sharing theirs.
613
+ expect(readInboundTrustMock).toHaveBeenCalledTimes(2);
614
+ });
615
+
526
616
  test("skips Slack assistant new-thread placeholder during DM backfill", async () => {
527
617
  backfillDmMock.mockImplementation(async () => [
528
618
  makeBackfilledMessage({
@@ -0,0 +1,88 @@
1
+ import { afterAll, beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ DEFAULT_DOCUMENT_TITLE,
5
+ getDocumentsForConversation,
6
+ } from "../documents/document-store.js";
7
+ import { createConversation } from "../persistence/conversation-crud.js";
8
+ import { getDb } from "../persistence/db-connection.js";
9
+ import { initializeDb } from "../persistence/db-init.js";
10
+ import { ROUTES as DOCUMENT_ROUTES } from "../runtime/routes/documents-routes.js";
11
+ import type { RouteDefinition } from "../runtime/routes/types.js";
12
+ import { resetDbForTesting } from "./db-test-helpers.js";
13
+
14
+ await initializeDb();
15
+
16
+ const CONVERSATION_ID = "conv-doc-create";
17
+
18
+ async function create(body: Record<string, unknown>): Promise<unknown> {
19
+ const route = DOCUMENT_ROUTES.find(
20
+ (r: RouteDefinition) => r.operationId === "createDocument",
21
+ )!;
22
+ return await route.handler({ body });
23
+ }
24
+
25
+ beforeEach(() => {
26
+ const db = getDb();
27
+ db.run("DELETE FROM document_conversations");
28
+ db.run("DELETE FROM documents");
29
+ db.run("DELETE FROM conversations");
30
+ createConversation({ id: CONVERSATION_ID });
31
+ });
32
+
33
+ afterAll(() => {
34
+ resetDbForTesting();
35
+ });
36
+
37
+ describe("createDocument route", () => {
38
+ test("creates an empty document under a new surface ID and returns it", async () => {
39
+ const result = await create({
40
+ conversationId: CONVERSATION_ID,
41
+ title: "Trip plan",
42
+ });
43
+
44
+ expect(result).toMatchObject({
45
+ success: true,
46
+ conversationId: CONVERSATION_ID,
47
+ title: "Trip plan",
48
+ content: "",
49
+ wordCount: 0,
50
+ });
51
+ const { surfaceId } = result as { surfaceId: string };
52
+ expect(surfaceId).toMatch(/^doc-[0-9a-f-]{36}$/);
53
+ expect(
54
+ getDocumentsForConversation(CONVERSATION_ID).map((d) => d.surfaceId),
55
+ ).toEqual([surfaceId]);
56
+ });
57
+
58
+ test("a missing or blank title falls back to the default", async () => {
59
+ await expect(
60
+ create({ conversationId: CONVERSATION_ID }),
61
+ ).resolves.toMatchObject({ title: DEFAULT_DOCUMENT_TITLE });
62
+ await expect(
63
+ create({ conversationId: CONVERSATION_ID, title: " " }),
64
+ ).resolves.toMatchObject({ title: DEFAULT_DOCUMENT_TITLE });
65
+ });
66
+
67
+ test("each create mints a distinct document", async () => {
68
+ const first = (await create({ conversationId: CONVERSATION_ID })) as {
69
+ surfaceId: string;
70
+ };
71
+ const second = (await create({ conversationId: CONVERSATION_ID })) as {
72
+ surfaceId: string;
73
+ };
74
+
75
+ expect(first.surfaceId).not.toBe(second.surfaceId);
76
+ expect(getDocumentsForConversation(CONVERSATION_ID)).toHaveLength(2);
77
+ });
78
+
79
+ test("rejects a request without a conversation", async () => {
80
+ await expect(create({})).rejects.toThrow(/conversationId is required/);
81
+ });
82
+
83
+ test("rejects a conversation that does not exist", async () => {
84
+ await expect(create({ conversationId: "conv-missing" })).rejects.toThrow(
85
+ /Conversation not found/,
86
+ );
87
+ });
88
+ });
@@ -203,6 +203,22 @@ describe("document write routes publish documents:list", () => {
203
203
  });
204
204
  });
205
205
 
206
+ test("creating a document publishes with the caller's client id", async () => {
207
+ const events = await captureSyncEvents(() =>
208
+ invoke("createDocument", {
209
+ body: { conversationId: CONVERSATION_ID },
210
+ headers: { "x-vellum-client-id": "client-abc" },
211
+ }),
212
+ );
213
+
214
+ expect(events).toHaveLength(1);
215
+ expect(events[0].message).toMatchObject({
216
+ type: "sync_changed",
217
+ tags: [SYNC_TAGS.documentsList],
218
+ originClientId: "client-abc",
219
+ });
220
+ });
221
+
206
222
  test("linking a document to a conversation publishes", async () => {
207
223
  await saveViaRoute();
208
224
 
@@ -251,6 +267,17 @@ describe("document routes that change nothing the list shows stay silent", () =>
251
267
  expect(events).toEqual([]);
252
268
  });
253
269
 
270
+ test("a create for a conversation that does not exist publishes nothing", async () => {
271
+ const events = await captureSyncEvents(async () => {
272
+ await expect(
273
+ invoke("createDocument", {
274
+ body: { conversationId: MISSING_CONVERSATION_ID },
275
+ }),
276
+ ).rejects.toThrow(/Conversation not found/);
277
+ });
278
+ expect(events).toEqual([]);
279
+ });
280
+
254
281
  test("a link to a document that does not exist publishes nothing", async () => {
255
282
  const events = await captureSyncEvents(async () => {
256
283
  await expect(
@@ -95,7 +95,10 @@ mock.module("../platform/client.js", () => ({
95
95
  isPlatformClientConfigured: () => isPlatformClientConfiguredMock(),
96
96
  }));
97
97
 
98
- import { emitNotificationSignal } from "../notifications/emit-signal.js";
98
+ import {
99
+ classifyNotificationReceipt,
100
+ emitNotificationSignal,
101
+ } from "../notifications/emit-signal.js";
99
102
 
100
103
  beforeEach(() => {
101
104
  evaluateSignalMock.mockReset();
@@ -662,7 +665,7 @@ describe("high/critical urgency channel force", () => {
662
665
  sourceChannel: "watcher" | "assistant_tool",
663
666
  ) {
664
667
  return emitNotificationSignal({
665
- sourceEventName: "watcher.escalation",
668
+ sourceEventName: "watcher.notification",
666
669
  sourceChannel,
667
670
  sourceContextId: "watch-1",
668
671
  routingIntent: "single_channel",
@@ -707,3 +710,140 @@ describe("high/critical urgency channel force", () => {
707
710
  });
708
711
  });
709
712
  });
713
+
714
+ describe("exclusive channelAllowlist policy", () => {
715
+ function makeDecision(overrides: Record<string, unknown>) {
716
+ return {
717
+ shouldNotify: true,
718
+ selectedChannels: ["telegram"],
719
+ reasoningSummary: "LLM selected telegram only",
720
+ renderedCopy: {},
721
+ dedupeKey: "dedupe-allow-1",
722
+ confidence: 0.9,
723
+ fallbackUsed: false,
724
+ persistedDecisionId: "dec-allow-1",
725
+ ...overrides,
726
+ };
727
+ }
728
+
729
+ test("skips urgent vellum and platform force", async () => {
730
+ evaluateSignalMock.mockResolvedValue(
731
+ makeDecision({ selectedChannels: ["vellum"] }),
732
+ );
733
+
734
+ const result = await emitNotificationSignal({
735
+ sourceEventName: "user.send_notification",
736
+ sourceChannel: "assistant_tool",
737
+ sourceContextId: "cli-allowlist-1",
738
+ attentionHints: {
739
+ requiresAction: true,
740
+ urgency: "critical",
741
+ isAsyncBackground: false,
742
+ visibleInSourceNow: false,
743
+ },
744
+ contextPayload: {
745
+ requestedMessage: "telegram only",
746
+ channelAllowlist: ["vellum"],
747
+ },
748
+ });
749
+
750
+ expect(enforceRoutingIntentMock).not.toHaveBeenCalled();
751
+ const dispatched = dispatchDecisionMock.mock.calls[0][1] as {
752
+ selectedChannels: string[];
753
+ reasoningSummary: string;
754
+ };
755
+ expect(dispatched.selectedChannels).toEqual(["vellum"]);
756
+ expect(dispatched.reasoningSummary).not.toContain("forced");
757
+ expect(result.selectedChannels).toEqual(["vellum"]);
758
+ expect(result.receiptClass).toBe("unknown");
759
+ });
760
+
761
+ test("replaces an LLM selection with the connected allowlist", async () => {
762
+ evaluateSignalMock.mockResolvedValue(
763
+ makeDecision({ selectedChannels: ["vellum", "telegram", "platform"] }),
764
+ );
765
+
766
+ await emitNotificationSignal({
767
+ sourceEventName: "user.send_notification",
768
+ sourceChannel: "assistant_tool",
769
+ sourceContextId: "cli-allowlist-2",
770
+ attentionHints: {
771
+ requiresAction: true,
772
+ urgency: "high",
773
+ isAsyncBackground: false,
774
+ visibleInSourceNow: false,
775
+ },
776
+ contextPayload: { channelAllowlist: ["vellum"] },
777
+ });
778
+
779
+ const dispatched = dispatchDecisionMock.mock.calls[0][1] as {
780
+ selectedChannels: string[];
781
+ shouldNotify: boolean;
782
+ reasoningSummary: string;
783
+ };
784
+ expect(dispatched.selectedChannels).toEqual(["vellum"]);
785
+ expect(dispatched.shouldNotify).toBe(true);
786
+ expect(dispatched.reasoningSummary).toContain("channelAllowlist: vellum");
787
+ });
788
+
789
+ test("access-request floor still adds vellum when the allowlist omitted it", async () => {
790
+ evaluateSignalMock.mockResolvedValue(
791
+ makeDecision({ shouldNotify: false, selectedChannels: [] }),
792
+ );
793
+
794
+ await emitNotificationSignal<string>({
795
+ sourceEventName: "ingress.access_request",
796
+ sourceChannel: "telegram",
797
+ sourceContextId: "access-req-allowlist-1",
798
+ requiresConversation: true,
799
+ attentionHints: {
800
+ requiresAction: true,
801
+ urgency: "high",
802
+ isAsyncBackground: false,
803
+ visibleInSourceNow: false,
804
+ },
805
+ contextPayload: {
806
+ requestId: "req-allowlist-1",
807
+ channelAllowlist: ["telegram"],
808
+ },
809
+ });
810
+
811
+ const dispatched = dispatchDecisionMock.mock.calls[0][1] as {
812
+ shouldNotify: boolean;
813
+ selectedChannels: string[];
814
+ reasoningSummary: string;
815
+ };
816
+ expect(dispatched.shouldNotify).toBe(true);
817
+ expect(dispatched.selectedChannels).toContain("vellum");
818
+ expect(dispatched.reasoningSummary).toContain(
819
+ "vellum forced: decisionable access request",
820
+ );
821
+ });
822
+ });
823
+
824
+ describe("classifyNotificationReceipt", () => {
825
+ test("reports provider_accepted when an external channel is sent", () => {
826
+ expect(
827
+ classifyNotificationReceipt([
828
+ { channel: "telegram", destination: "chat-1", status: "sent" },
829
+ { channel: "vellum", destination: "vellum", status: "sent" },
830
+ ]),
831
+ ).toBe("provider_accepted");
832
+ });
833
+
834
+ test("reports gateway_accepted when only platform is accepted", () => {
835
+ expect(
836
+ classifyNotificationReceipt([
837
+ { channel: "platform", destination: "push", status: "pending" },
838
+ ]),
839
+ ).toBe("gateway_accepted");
840
+ });
841
+
842
+ test("does not claim client_os_posted from a vellum sent row", () => {
843
+ expect(
844
+ classifyNotificationReceipt([
845
+ { channel: "vellum", destination: "vellum", status: "sent" },
846
+ ]),
847
+ ).toBe("unknown");
848
+ });
849
+ });
@@ -170,6 +170,31 @@ describe("GET events/tail", () => {
170
170
  expect(other.events.map((e) => e.seq)).toEqual([1, 2]);
171
171
  });
172
172
 
173
+ test("returns a principal-targeted event only to that principal's client", () => {
174
+ stampAndBuffer(mkEvent()); // seq 1, untargeted
175
+ stampAndBuffer(mkEvent(), {
176
+ targeting: { targetActorPrincipalId: "principal-g" },
177
+ }); // seq 2
178
+
179
+ const client = {
180
+ "x-vellum-client-id": "c",
181
+ "x-vellum-interface-id": "web",
182
+ };
183
+ const guardian = callTail(
184
+ { conversationId: CONV, fromSeq: "0" },
185
+ { ...client, "x-vellum-actor-principal-id": "principal-g" },
186
+ );
187
+ const other = callTail(
188
+ { conversationId: CONV, fromSeq: "0" },
189
+ { ...client, "x-vellum-actor-principal-id": "principal-x" },
190
+ );
191
+ const anonymous = callTail({ conversationId: CONV, fromSeq: "0" });
192
+
193
+ expect(guardian.events.map((e) => e.seq)).toEqual([1, 2]);
194
+ expect(other.events.map((e) => e.seq)).toEqual([1]);
195
+ expect(anonymous.events.map((e) => e.seq)).toEqual([1]);
196
+ });
197
+
173
198
  test("toSeq bounds the window inclusively and moves the frontier", () => {
174
199
  // GIVEN five buffered events for the conversation (seq 1..5)
175
200
  for (let i = 0; i < 5; i++) {