@vellumai/assistant 0.12.3-staging.1 → 0.12.3

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
@@ -73,6 +73,7 @@ import type {
73
73
  import type { MessagingConversationContext } from "../daemon/conversation-messaging.js";
74
74
  import { persistQueuedMessageBody } from "../daemon/conversation-messaging.js";
75
75
  import type { MessageQueue } from "../daemon/conversation-queue-manager.js";
76
+ import type { TrustContext } from "../daemon/trust-context-types.js";
76
77
 
77
78
  function createContext(): MessagingConversationContext {
78
79
  const channel: TurnChannelContext = {
@@ -223,3 +224,75 @@ describe("scripted-turn metadata persistence", () => {
223
224
  expect(lastUserMetadata().scripted).toBe(false);
224
225
  });
225
226
  });
227
+
228
+ describe("author contact id on a persisted user message", () => {
229
+ const sender: TrustContext = {
230
+ sourceChannel: "slack",
231
+ trustClass: "trusted_contact",
232
+ requesterContactId: "contact-alice",
233
+ };
234
+
235
+ beforeEach(() => {
236
+ addMessageCalls.length = 0;
237
+ });
238
+
239
+ test("a message relayed from the sender names them as its author", async () => {
240
+ await persistQueuedMessageBody(createContext(), {
241
+ content: "the export endpoint needs a scoped token",
242
+ requestId: "req-author-typed",
243
+ trustContext: sender,
244
+ author: sender,
245
+ });
246
+
247
+ expect(lastUserMetadata().provenanceContactId).toBe("contact-alice");
248
+ });
249
+
250
+ test("a row persisted under the sender's trust but not relayed from them names no author", async () => {
251
+ // Machine-authored rows (ACP and subagent notifications, pointer turns)
252
+ // share this writer and the conversation's trust; only an explicit
253
+ // author names one.
254
+ await persistQueuedMessageBody(createContext(), {
255
+ content: "the delegated task finished",
256
+ requestId: "req-author-machine",
257
+ trustContext: sender,
258
+ metadata: { acpNotification: { acpSessionId: "acp-session-1" } },
259
+ });
260
+
261
+ expect(lastUserMetadata().provenanceContactId).toBeUndefined();
262
+ });
263
+
264
+ test.each([
265
+ ["scripted", { scripted: true }],
266
+ ["automated", { metadata: { automated: true } }],
267
+ ["hidden", { metadata: { hidden: true } }],
268
+ [
269
+ "ACP notification",
270
+ { metadata: { acpNotification: { acpSessionId: "acp-session-1" } } },
271
+ ],
272
+ [
273
+ "subagent notification",
274
+ {
275
+ metadata: {
276
+ subagentNotification: {
277
+ subagentId: "subagent-1",
278
+ label: "Research",
279
+ status: "completed",
280
+ },
281
+ },
282
+ },
283
+ ],
284
+ ] as const)(
285
+ "a %s row speaks in their voice without naming them",
286
+ async (kind, extra) => {
287
+ await persistQueuedMessageBody(createContext(), {
288
+ content: "sent on the sender's behalf",
289
+ requestId: `req-author-${kind}`,
290
+ trustContext: sender,
291
+ author: sender,
292
+ ...extra,
293
+ });
294
+
295
+ expect(lastUserMetadata().provenanceContactId).toBeUndefined();
296
+ },
297
+ );
298
+ });
@@ -100,6 +100,7 @@ mock.module("../runtime/guardian-reply-router.js", () => ({
100
100
  import type { AuthContext } from "../runtime/auth/types.js";
101
101
  import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
102
102
  import { callHandler } from "./helpers/call-route-handler.js";
103
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
103
104
 
104
105
  const testAuthContext: AuthContext = {
105
106
  subject: "actor:self:test-user",
@@ -176,6 +177,7 @@ function makeSendMessageDeps() {
176
177
  setHostCuProxy: () => {},
177
178
  setHostAppControlProxy: () => {},
178
179
  addPreactivatedSkillId: () => {},
180
+ modeSessions: mockUnownedModeSessions(),
179
181
  } as unknown as import("../daemon/conversation.js").Conversation;
180
182
 
181
183
  return {
@@ -1,5 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { existsSync, mkdirSync, rmSync } from "node:fs";
2
+ import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
3
+ import { createServer, type Server } from "node:net";
3
4
  import { tmpdir } from "node:os";
4
5
  import { join } from "node:path";
5
6
  import {
@@ -11,6 +12,8 @@ import {
11
12
  test,
12
13
  } from "bun:test";
13
14
 
15
+ import { resolveIpcEndpoint } from "@vellumai/ipc-server-utils";
16
+
14
17
  // ---------------------------------------------------------------------------
15
18
  // Mock logger (no-op — compatible with other test files' identical mock)
16
19
  // ---------------------------------------------------------------------------
@@ -602,4 +605,43 @@ describe("secure-keys", () => {
602
605
  expect(result.unreachable).toBe(true);
603
606
  });
604
607
  });
608
+
609
+ // -----------------------------------------------------------------------
610
+ // Session ownership: boot claims reconnect before identity reads
611
+ // -----------------------------------------------------------------------
612
+ describe("CES session owner skips a second open", () => {
613
+ test("a registered reconnect owner does not handshake a live CES socket", async () => {
614
+ const dir = mkdtempSync(join(tmpdir(), "ces-owner-"));
615
+ const socketPath = resolveIpcEndpoint("ces", { workspaceDir: dir }).path;
616
+ const savedDir = process.env.CES_BOOTSTRAP_SOCKET_DIR;
617
+ process.env.CES_BOOTSTRAP_SOCKET_DIR = dir;
618
+
619
+ const connections: Array<import("node:net").Socket> = [];
620
+ const server: Server = createServer((socket) => {
621
+ connections.push(socket);
622
+ socket.on("error", () => {});
623
+ });
624
+ await new Promise<void>((resolve) => server.listen(socketPath, resolve));
625
+
626
+ try {
627
+ setCesReconnect(async () => undefined);
628
+ const start = Date.now();
629
+ await getSecureKeyAsync("openai");
630
+ expect(Date.now() - start).toBeLessThan(1_000);
631
+ expect(getActiveBackendName()).toBe("encrypted-store");
632
+ expect(connections.length).toBe(0);
633
+ } finally {
634
+ for (const sock of connections) {
635
+ sock.destroy();
636
+ }
637
+ await new Promise<void>((resolve) => server.close(() => resolve()));
638
+ if (savedDir !== undefined) {
639
+ process.env.CES_BOOTSTRAP_SOCKET_DIR = savedDir;
640
+ } else {
641
+ delete process.env.CES_BOOTSTRAP_SOCKET_DIR;
642
+ }
643
+ rmSync(dir, { recursive: true, force: true });
644
+ }
645
+ });
646
+ });
605
647
  });
@@ -12,6 +12,11 @@ mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
12
12
 
13
13
  import type { AssistantEvent } from "../api/index.js";
14
14
  import type { Conversation } from "../daemon/conversation.js";
15
+ import {
16
+ type MessagingConversationContext,
17
+ persistUserMessage,
18
+ } from "../daemon/conversation-messaging.js";
19
+ import { ConversationModeSessionCoordinator } from "../daemon/conversation-mode-session.js";
15
20
  import {
16
21
  getConversationByKey,
17
22
  getOrCreateConversation,
@@ -91,6 +96,7 @@ import {
91
96
  bridgeState,
92
97
  gatewayGuardianRequestsStoreBridge,
93
98
  } from "./helpers/gateway-guardian-requests-store-bridge.js";
99
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
94
100
 
95
101
  mock.module(
96
102
  "../channels/gateway-guardian-requests.js",
@@ -99,7 +105,7 @@ mock.module(
99
105
 
100
106
  import type { AssistantEventEnvelope } from "../api/index.js";
101
107
  import { __resetGuardianDeliveryCacheForTest } from "../contacts/guardian-delivery-reader.js";
102
- import { getDb } from "../persistence/db-connection.js";
108
+ import { getDb, getSqlite } from "../persistence/db-connection.js";
103
109
  import { initializeDb } from "../persistence/db-init.js";
104
110
  import { RuntimeHttpServer } from "../runtime/http-server.js";
105
111
  import type { ApprovalConversationGenerator } from "../runtime/http-types.js";
@@ -116,6 +122,7 @@ function makeCompletingConversation(): Conversation {
116
122
  let processing = false;
117
123
  const messages: unknown[] = [];
118
124
  return {
125
+ modeSessions: mockUnownedModeSessions(),
119
126
  isProcessing: () => processing,
120
127
  persistUserMessage: (options: { requestId?: string }) => {
121
128
  processing = true;
@@ -166,6 +173,7 @@ function makeHangingConversation(): Conversation {
166
173
  requestId?: string;
167
174
  }> = [];
168
175
  return {
176
+ modeSessions: mockUnownedModeSessions(),
169
177
  isProcessing: () => processing,
170
178
  persistUserMessage: (options: { requestId?: string }) => {
171
179
  processing = true;
@@ -244,6 +252,7 @@ function makePendingApprovalConversation(
244
252
  });
245
253
 
246
254
  const conversation = {
255
+ modeSessions: mockUnownedModeSessions(),
247
256
  isProcessing: () => processing,
248
257
  persistUserMessage: (options: { requestId?: string }) => ({
249
258
  id: options.requestId ?? "msg-1",
@@ -346,6 +355,159 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
346
355
 
347
356
  // ── Idle conversation: immediate processing ─────────────────────────
348
357
 
358
+ test.each([
359
+ { failure: "acceptTurn", surface: false },
360
+ { failure: "trackPersistedRow", surface: false },
361
+ { failure: "acceptTurn", surface: true },
362
+ { failure: "trackPersistedRow", surface: true },
363
+ ] as const)(
364
+ "dispatches committed content once despite $failure failure (surface=$surface)",
365
+ async ({ failure, surface }) => {
366
+ const conversationKey = "conv-tracking-failure";
367
+ const { conversationId } = getOrCreateConversation(conversationKey);
368
+ const modeSessions = new ConversationModeSessionCoordinator(
369
+ conversationId,
370
+ );
371
+ const source = surface
372
+ ? modeSessions.activateSource({
373
+ sourceId: "browser-source",
374
+ generation: 1,
375
+ mode: "browser",
376
+ sourceStartedAt: 100,
377
+ })!
378
+ : undefined;
379
+ if (source) {
380
+ modeSessions.claimTurn("turn-origin", source, 110);
381
+ modeSessions.recordStructuralWait("turn-origin", {
382
+ kind: "surface",
383
+ responseId: "surface-123",
384
+ });
385
+ modeSessions.releaseTurn("turn-origin");
386
+ }
387
+
388
+ const persistedRows = () =>
389
+ getSqlite()
390
+ .query<
391
+ { id: string; content: string },
392
+ [string]
393
+ >("SELECT id, content FROM messages WHERE conversation_id = ? AND role = 'user'")
394
+ .all(conversationId);
395
+ const acceptTurn = modeSessions.acceptTurn.bind(modeSessions);
396
+ const trackPersistedRow =
397
+ modeSessions.trackPersistedRow.bind(modeSessions);
398
+ const committedCounts: number[] = [];
399
+ const trackingFailure = mock(() => {
400
+ committedCounts.push(persistedRows().length);
401
+ throw new Error("optional session tracking failed");
402
+ });
403
+ modeSessions.acceptTurn = (...args) => {
404
+ const owner = acceptTurn(...args);
405
+ if (failure === "acceptTurn") {
406
+ trackingFailure();
407
+ }
408
+ return owner;
409
+ };
410
+ modeSessions.trackPersistedRow = (...args) => {
411
+ trackPersistedRow(...args);
412
+ if (failure === "trackPersistedRow") {
413
+ trackingFailure();
414
+ }
415
+ };
416
+
417
+ let processing = false;
418
+ let processingOwner = 0;
419
+ const releases: number[] = [];
420
+ const ctx = Object.assign(makeCompletingConversation(), {
421
+ conversationId,
422
+ modeSessions,
423
+ messages: [],
424
+ abortController: null as AbortController | null,
425
+ currentRequestId: undefined as string | undefined,
426
+ currentTurnClientMessageId: undefined as string | undefined,
427
+ currentActiveSurfaceId: surface ? "surface-123" : undefined,
428
+ inFlightSendRequestIds: new Map<string, string>(),
429
+ isProcessing: () => processing,
430
+ acquireProcessingFenced: async () => {
431
+ processing = true;
432
+ return ++processingOwner;
433
+ },
434
+ releaseProcessing: (owner: number) => {
435
+ releases.push(owner);
436
+ processing = false;
437
+ return true;
438
+ },
439
+ getTurnChannelContext: () => null,
440
+ getTurnInterfaceContext: () => null,
441
+ });
442
+ ctx.persistUserMessage = (options) =>
443
+ persistUserMessage(
444
+ ctx as unknown as MessagingConversationContext,
445
+ options,
446
+ );
447
+ const runAgentLoop = mock(async (_content: string, messageId: string) => {
448
+ expect(ctx.isProcessing()).toBe(true);
449
+ expect(ctx.messages).toHaveLength(1);
450
+ expect(modeSessions.getTurnOwner(messageId)?.id).toBe(source?.id);
451
+ modeSessions.releaseTurn(messageId, {
452
+ status: "completed",
453
+ endReason: "turn_complete",
454
+ });
455
+ ctx.releaseProcessing(processingOwner);
456
+ ctx.currentRequestId = undefined;
457
+ ctx.currentTurnClientMessageId = undefined;
458
+ ctx.abortController = null;
459
+ });
460
+ ctx.runAgentLoop = runAgentLoop;
461
+ await startServer(() => ctx);
462
+
463
+ const send = () =>
464
+ fetch(messagesUrl(), {
465
+ method: "POST",
466
+ headers: { "Content-Type": "application/json", ...AUTH_HEADERS },
467
+ body: JSON.stringify({
468
+ conversationKey,
469
+ content: "Continue the task",
470
+ clientMessageId: "client-message-123",
471
+ sourceChannel: "vellum",
472
+ interface: "macos",
473
+ }),
474
+ });
475
+ const first = await send();
476
+ const firstBody = (await first.json()) as {
477
+ accepted: boolean;
478
+ messageId: string;
479
+ };
480
+ expect(first.status).toBe(202);
481
+ expect(firstBody.accepted).toBe(true);
482
+ expect(runAgentLoop).toHaveBeenCalledTimes(1);
483
+ await runAgentLoop.mock.results[0]!.value;
484
+ expect(trackingFailure).toHaveBeenCalledTimes(1);
485
+ expect(committedCounts).toEqual([1]);
486
+
487
+ const retry = await send();
488
+ expect(retry.status).toBe(202);
489
+ expect(await retry.json()).toMatchObject(firstBody);
490
+ expect(runAgentLoop).toHaveBeenCalledTimes(1);
491
+ expect(trackingFailure).toHaveBeenCalledTimes(1);
492
+ expect(persistedRows()).toEqual([
493
+ {
494
+ id: firstBody.messageId,
495
+ content: JSON.stringify([
496
+ { type: "text", text: "Continue the task" },
497
+ ]),
498
+ },
499
+ ]);
500
+ expect(ctx.messages).toHaveLength(1);
501
+ expect(ctx.isProcessing()).toBe(false);
502
+ expect(ctx.abortController).toBeNull();
503
+ expect(ctx.currentRequestId).toBeUndefined();
504
+ expect(ctx.currentTurnClientMessageId).toBeUndefined();
505
+ expect(releases).toEqual([1, 2]);
506
+ expect(modeSessions.getTurnOwner(firstBody.messageId)).toBeUndefined();
507
+ expect(modeSessions.hasResidentWork()).toBe(false);
508
+ },
509
+ );
510
+
349
511
  test("returns 202 with accepted: true and messageId when conversation is idle", async () => {
350
512
  await startServer(() => makeCompletingConversation());
351
513
 
@@ -1,8 +1,11 @@
1
1
  import { beforeEach, describe, expect, test } from "bun:test";
2
2
 
3
3
  import type { AnsweredQuestion } from "../api/events/question-answered.js";
4
+ import type {
5
+ ToolActivityMetadata,
6
+ WebFetchMetadata,
7
+ } from "../api/events/tool-result.js";
4
8
  import { renderHistoryContent } from "../daemon/handlers/shared.js";
5
- import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
6
9
  import {
7
10
  getAttachmentsForMessage,
8
11
  linkAttachmentToMessage,
@@ -675,6 +678,93 @@ describe("renderHistoryContent", () => {
675
678
  expect(output.toolCalls[0].activityMetadata).toBeUndefined();
676
679
  });
677
680
 
681
+ test("hydrates persisted recall and remember activity", () => {
682
+ const recall: ToolActivityMetadata = {
683
+ recall: {
684
+ query: "launch plan",
685
+ depth: "standard",
686
+ sources: ["memory", "workspace"],
687
+ answer: "The launch moved to Friday.",
688
+ evidence: [
689
+ {
690
+ source: "workspace",
691
+ title: "notes/launch.md",
692
+ locator: "notes/launch.md:4",
693
+ excerpt: "Launch moves to Friday.",
694
+ },
695
+ ],
696
+ searchedSources: [
697
+ { source: "memory", status: "searched", evidenceCount: 0 },
698
+ {
699
+ source: "workspace",
700
+ status: "degraded",
701
+ evidenceCount: 1,
702
+ error: "index stale",
703
+ },
704
+ ],
705
+ },
706
+ };
707
+ const remember: ToolActivityMetadata = {
708
+ remember: { facts: ["Prefers window seats"] },
709
+ };
710
+
711
+ const output = renderHistoryContent([
712
+ {
713
+ type: "tool_use",
714
+ id: "tu_1",
715
+ name: "recall",
716
+ input: { query: "launch plan" },
717
+ _activityMetadata: recall,
718
+ },
719
+ {
720
+ type: "tool_use",
721
+ id: "tu_2",
722
+ name: "remember",
723
+ input: { content: "Prefers window seats" },
724
+ _activityMetadata: remember,
725
+ },
726
+ ]);
727
+
728
+ expect(output.toolCalls[0].activityMetadata).toEqual(recall);
729
+ expect(output.toolCalls[1].activityMetadata).toEqual(remember);
730
+ });
731
+
732
+ test("drops a malformed activity entry and keeps a valid sibling", () => {
733
+ const webFetch: WebFetchMetadata = {
734
+ url: "https://example.com",
735
+ finalUrl: "https://example.com",
736
+ status: 200,
737
+ byteCount: 1024,
738
+ charCount: 900,
739
+ truncated: false,
740
+ domain: "example.com",
741
+ redirectCount: 0,
742
+ durationMs: 40,
743
+ };
744
+ const output = renderHistoryContent([
745
+ {
746
+ type: "tool_use",
747
+ id: "tu_1",
748
+ name: "web_fetch",
749
+ input: { url: "https://example.com" },
750
+ _activityMetadata: {
751
+ webSearch: { query: "x", results: "not a list" },
752
+ webFetch,
753
+ },
754
+ },
755
+ {
756
+ type: "server_tool_use",
757
+ id: "tu_2",
758
+ name: "web_search",
759
+ input: { query: "x" },
760
+ _activityMetadata: { webSearch: { query: 42 } },
761
+ },
762
+ ]);
763
+
764
+ expect(output.toolCalls[0].activityMetadata).toEqual({ webFetch });
765
+ expect(output.toolCalls[1].activityMetadata).toBeUndefined();
766
+ });
767
+
678
768
  test("ignores non-array _risk*Options annotations", () => {
679
769
  // Defensive: a malformed persisted block should not throw or coerce.
680
770
  const output = renderHistoryContent([
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
- import { beforeEach, describe, expect, mock, test } from "bun:test";
13
+ import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
14
14
 
15
15
  // Guardian identity resolves via the gateway delivery cache, not the local
16
16
  // contacts DB. Seed it per-test via seedGatewayGuardian; persona resolution
@@ -42,17 +42,66 @@ function seedGatewayGuardian(g: {
42
42
  gatewayGuardians.push({ status: "active", ...g });
43
43
  }
44
44
 
45
+ import {
46
+ getBundledSkillsDir,
47
+ type SkillToolManifest,
48
+ } from "../config/skills.js";
49
+ import * as permissions from "../permissions/checker.js";
50
+ import { RiskLevel } from "../permissions/types.js";
45
51
  import { getSqlite } from "../persistence/db-connection.js";
46
52
  import { initializeDb } from "../persistence/db-init.js";
47
53
  import { BadRequestError, NotFoundError } from "../runtime/routes/errors.js";
48
54
  import { ROUTES } from "../runtime/routes/settings-routes.js";
49
55
  import type { RouteHandlerArgs } from "../runtime/routes/types.js";
56
+ import { registerSkillTools, unregisterSkillTools } from "../tools/registry.js";
57
+ import { createSkillToolsFromManifest } from "../tools/skills/skill-tool-factory.js";
50
58
  import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
51
59
 
52
60
  await initializeDb();
53
61
 
54
62
  const testWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR!;
55
63
 
64
+ test("permission simulation resolves the bundled computer-use target from input", async () => {
65
+ const classify = spyOn(permissions, "classifyRisk").mockResolvedValue({
66
+ level: RiskLevel.Low,
67
+ reason: "Test classification",
68
+ matchType: "registry",
69
+ scopeOptions: [],
70
+ });
71
+ const check = spyOn(permissions, "check").mockResolvedValue({
72
+ decision: "allow",
73
+ reason: "Test decision",
74
+ });
75
+ const skillDir = join(getBundledSkillsDir(), "computer-use");
76
+ const manifest = JSON.parse(
77
+ readFileSync(join(skillDir, "TOOLS.json"), "utf8"),
78
+ ) as SkillToolManifest;
79
+ const skillId = "simulation-computer-use";
80
+ registerSkillTools(
81
+ skillId,
82
+ createSkillToolsFromManifest(manifest.tools, skillDir, "", true),
83
+ );
84
+ try {
85
+ for (const target of ["assistant-desktop", "connected-computer"]) {
86
+ const result = await getHandler(
87
+ "tools/simulate-permission",
88
+ "POST",
89
+ )(makeArgs({}, { toolName: "computer_use_observe", input: { target } }));
90
+ expect(result).toMatchObject({
91
+ success: true,
92
+ executionTarget: target === "assistant-desktop" ? "sandbox" : "host",
93
+ });
94
+ expect(check.mock.calls.at(-1)?.[3]?.executionTarget).toBe(
95
+ target === "assistant-desktop" ? "sandbox" : "host",
96
+ );
97
+ }
98
+ } finally {
99
+ unregisterSkillTools(skillId);
100
+ classify.mockRestore();
101
+ check.mockRestore();
102
+ }
103
+ });
104
+
56
105
  function resetContactTables(): void {
57
106
  const sqlite = getSqlite();
58
107
  sqlite.run("DELETE FROM contact_channels");
@@ -67,6 +67,19 @@ beforeAll(async () => {
67
67
  `export async function run() {
68
68
  await new Promise(resolve => setTimeout(resolve, 120_000));
69
69
  return { content: 'should not reach', isError: false };
70
+ }`,
71
+ "utf-8",
72
+ );
73
+
74
+ await writeFile(
75
+ join(tempDir, "plugin-name-echo.ts"),
76
+ `export async function run() {
77
+ return {
78
+ content: JSON.stringify({
79
+ pluginName: process.env.VELLUM_PLUGIN_NAME ?? null,
80
+ }),
81
+ isError: false,
82
+ };
70
83
  }`,
71
84
  "utf-8",
72
85
  );
@@ -113,6 +126,25 @@ describe("runSkillToolScript sandbox — success", () => {
113
126
  expect(parsed.workingDir).toBe("/my/project");
114
127
  expect(parsed.conversationId).toBe("sess-42");
115
128
  }, 15_000);
129
+
130
+ test("sets VELLUM_PLUGIN_NAME for a plugin-owned skill tool", async () => {
131
+ const result = await runSkillToolScript(
132
+ tempDir,
133
+ "plugin-name-echo.ts",
134
+ {},
135
+ makeContext(),
136
+ {
137
+ target: "sandbox",
138
+ pluginOwner: "psk-demo",
139
+ },
140
+ );
141
+
142
+ expect(result.isError).toBe(false);
143
+ const parsed = JSON.parse(result.content) as {
144
+ pluginName: string | null;
145
+ };
146
+ expect(parsed.pluginName).toBe("psk-demo");
147
+ }, 15_000);
116
148
  });
117
149
 
118
150
  // ---------------------------------------------------------------------------
@@ -22,8 +22,7 @@ mock.module("../daemon/conversation-registry.js", () => ({
22
22
  });
23
23
  return { queued: true };
24
24
  },
25
- persistUserMessage: async () => ({ id: "mock-msg", deduplicated: false }),
26
- runAgentLoop: async () => {},
25
+ kickDrainQueue: async () => {},
27
26
  }),
28
27
  }));
29
28
 
@@ -27,8 +27,7 @@ mock.module("../daemon/conversation-registry.js", () => ({
27
27
  });
28
28
  return { queued: true };
29
29
  },
30
- persistUserMessage: async () => ({ id: "mock-msg", deduplicated: false }),
31
- runAgentLoop: async () => {},
30
+ kickDrainQueue: async () => {},
32
31
  }),
33
32
  }));
34
33