@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
@@ -136,6 +136,34 @@ describe("resolveImageGenCredentials", () => {
136
136
  expect(result.errorHint).toBeDefined();
137
137
  expect(result.errorHint).toContain("OpenAI API key");
138
138
  });
139
+
140
+ test("returns direct credentials for openrouter when key is present", async () => {
141
+ mockProviderKey = "openrouter-api-key";
142
+
143
+ const result = await resolveImageGenCredentials({
144
+ provider: "openrouter",
145
+ managed: false,
146
+ });
147
+
148
+ expect(result.errorHint).toBeUndefined();
149
+ expect(result.credentials).toEqual({
150
+ type: "direct",
151
+ apiKey: "openrouter-api-key",
152
+ });
153
+ });
154
+
155
+ test("returns errorHint mentioning 'OpenRouter API key' when no key is set", async () => {
156
+ mockProviderKey = undefined;
157
+
158
+ const result = await resolveImageGenCredentials({
159
+ provider: "openrouter",
160
+ managed: false,
161
+ });
162
+
163
+ expect(result.credentials).toBeUndefined();
164
+ expect(result.errorHint).toBeDefined();
165
+ expect(result.errorHint).toContain("OpenRouter API key");
166
+ });
139
167
  });
140
168
  });
141
169
 
@@ -173,6 +201,27 @@ describe("resolveImageGenRouting", () => {
173
201
  ).toEqual({ backendProvider: "openai", managed: false });
174
202
  });
175
203
 
204
+ test("OpenRouter keeps ownership for google/ and openai/ slugs", () => {
205
+ expect(
206
+ resolveImageGenRouting({
207
+ provider: "openrouter",
208
+ model: "google/gemini-3.1-flash-image-preview",
209
+ }),
210
+ ).toEqual({ backendProvider: "openrouter", managed: false });
211
+ expect(
212
+ resolveImageGenRouting(
213
+ { provider: "openrouter", model: "google/gemini-3.1-flash-image-preview" },
214
+ "openai/gpt-image-2",
215
+ ),
216
+ ).toEqual({ backendProvider: "openrouter", managed: false });
217
+ expect(
218
+ resolveImageGenRouting(
219
+ { provider: "openrouter", model: "google/gemini-3.1-flash-image-preview" },
220
+ "gpt-image-2",
221
+ ),
222
+ ).toEqual({ backendProvider: "openrouter", managed: false });
223
+ });
224
+
176
225
  test("vellum with an unavailable platform is a hard error, not a BYOK fallback", async () => {
177
226
  // Billing rule: an explicit vellum choice never silently spends a stored
178
227
  // provider key.
@@ -17,8 +17,10 @@ interface GenerateCall {
17
17
 
18
18
  let geminiCalls: GenerateCall[] = [];
19
19
  let openaiCalls: GenerateCall[] = [];
20
+ let openrouterCalls: GenerateCall[] = [];
20
21
  let geminiErrorCalls: unknown[] = [];
21
22
  let openaiErrorCalls: unknown[] = [];
23
+ let openrouterErrorCalls: unknown[] = [];
22
24
 
23
25
  let geminiResult: ImageGenerationResult = {
24
26
  images: [],
@@ -28,6 +30,10 @@ let openaiResult: ImageGenerationResult = {
28
30
  images: [],
29
31
  resolvedModel: "openai-mock",
30
32
  };
33
+ let openrouterResult: ImageGenerationResult = {
34
+ images: [],
35
+ resolvedModel: "openrouter-mock",
36
+ };
31
37
 
32
38
  // ---------------------------------------------------------------------------
33
39
  // Mock underlying services — must run before the module under test is imported
@@ -61,6 +67,20 @@ mock.module("../media/openai-image-service.js", () => ({
61
67
  },
62
68
  }));
63
69
 
70
+ mock.module("../media/openrouter-image-service.js", () => ({
71
+ generateImageOpenRouter: async (
72
+ credentials: ImageGenCredentials,
73
+ request: ImageGenerationRequest,
74
+ ): Promise<ImageGenerationResult> => {
75
+ openrouterCalls.push({ credentials, request });
76
+ return openrouterResult;
77
+ },
78
+ mapOpenRouterError: (error: unknown): string => {
79
+ openrouterErrorCalls.push(error);
80
+ return "openrouter-mapped";
81
+ },
82
+ }));
83
+
64
84
  // Import after mocking
65
85
  import {
66
86
  generateImage,
@@ -90,8 +110,10 @@ describe("image-service dispatcher", () => {
90
110
  beforeEach(() => {
91
111
  geminiCalls = [];
92
112
  openaiCalls = [];
113
+ openrouterCalls = [];
93
114
  geminiErrorCalls = [];
94
115
  openaiErrorCalls = [];
116
+ openrouterErrorCalls = [];
95
117
  geminiResult = {
96
118
  images: [{ mimeType: "image/png", dataBase64: "gemini-bytes" }],
97
119
  resolvedModel: "gemini-3.1-flash-image-preview",
@@ -100,6 +122,10 @@ describe("image-service dispatcher", () => {
100
122
  images: [{ mimeType: "image/png", dataBase64: "openai-bytes" }],
101
123
  resolvedModel: "gpt-image-2",
102
124
  };
125
+ openrouterResult = {
126
+ images: [{ mimeType: "image/png", dataBase64: "openrouter-bytes" }],
127
+ resolvedModel: "google/gemini-3.1-flash-image-preview",
128
+ };
103
129
  });
104
130
 
105
131
  test("generateImage('gemini', ...) delegates to the Gemini implementation", async () => {
@@ -131,6 +157,17 @@ describe("image-service dispatcher", () => {
131
157
  expect(mapped).toBe("gemini-mapped");
132
158
  });
133
159
 
160
+ test("generateImage('openrouter', ...) delegates to the OpenRouter implementation", async () => {
161
+ const result = await generateImage("openrouter", directCreds, request);
162
+
163
+ expect(openrouterCalls).toHaveLength(1);
164
+ expect(openrouterCalls[0]?.credentials).toBe(directCreds);
165
+ expect(openrouterCalls[0]?.request).toBe(request);
166
+ expect(geminiCalls).toHaveLength(0);
167
+ expect(openaiCalls).toHaveLength(0);
168
+ expect(result).toEqual(openrouterResult);
169
+ });
170
+
134
171
  test("mapImageGenError('openai', err) delegates to mapOpenAIError", () => {
135
172
  const err = new Error("kapow");
136
173
  const mapped = mapImageGenError("openai", err);
@@ -139,6 +176,14 @@ describe("image-service dispatcher", () => {
139
176
  expect(geminiErrorCalls).toEqual([]);
140
177
  expect(mapped).toBe("openai-mapped");
141
178
  });
179
+
180
+ test("mapImageGenError('openrouter', err) delegates to mapOpenRouterError", () => {
181
+ const err = new Error("or-boom");
182
+ const mapped = mapImageGenError("openrouter", err);
183
+
184
+ expect(openrouterErrorCalls).toEqual([err]);
185
+ expect(mapped).toBe("openrouter-mapped");
186
+ });
142
187
  });
143
188
 
144
189
  describe("providerForModel", () => {
@@ -389,7 +389,7 @@ describe("setInferenceProfileSession", () => {
389
389
  profiles: {
390
390
  jev: {
391
391
  source: "user",
392
- provider: "jev",
392
+ provider: "typesafe",
393
393
  model: "jev-latest",
394
394
  status: "active",
395
395
  },
@@ -21,6 +21,7 @@ import {
21
21
  abortedToolResultText,
22
22
  CANCELLED_TOOL_RESULT_TEXT,
23
23
  createAbortReason,
24
+ INTERRUPTED_TURN_NOTE_TEXT,
24
25
  PREEMPTED_TOOL_RESULT_TEXT,
25
26
  } from "../util/abort-reasons.js";
26
27
 
@@ -378,8 +379,8 @@ describe("interruptRunningTurn", () => {
378
379
 
379
380
  test("arms the interrupted-turn note when the abort caught no tool call", async () => {
380
381
  // The abort landed during the provider call, so there is no `tool_use` to
381
- // answer and nothing in the history says the turn was cut off. The note
382
- // rides on the interrupting user message instead.
382
+ // answer. The note on the interrupting user message is the only thing that
383
+ // tells the model what happened.
383
384
  const turn = registerBusyTurn({
384
385
  messages: [
385
386
  { role: "user", content: [{ type: "text", text: "research this" }] },
@@ -393,9 +394,9 @@ describe("interruptRunningTurn", () => {
393
394
  expect(persisted).toEqual([]);
394
395
  });
395
396
 
396
- test("leaves the note disarmed when the repair answered a tool call", async () => {
397
- // The preempted `tool_result` already tells the model a message cut it
398
- // off, so repeating that in a note on the message is noise.
397
+ test("arms the note alongside the fact-only result of a stopped tool call", async () => {
398
+ // The two texts do different jobs: the `tool_result` states what happened
399
+ // to that one call, the note says what to do about the interruption.
399
400
  const turn = registerBusyTurn({
400
401
  messages: [assistantWithToolUse("tool-1")],
401
402
  });
@@ -404,13 +405,36 @@ describe("interruptRunningTurn", () => {
404
405
 
405
406
  expect(persisted).toHaveLength(1);
406
407
  expect(persisted[0].content).toContain(PREEMPTED_TOOL_RESULT_TEXT);
407
- expect(turn.conversation.pendingInterruptNote).toBe(false);
408
+ expect(turn.conversation.pendingInterruptNote).toBe(true);
409
+ });
410
+
411
+ test("arms exactly one note however many parallel calls were stopped", async () => {
412
+ // Every stopped call gets its own fact-only result; the behavior
413
+ // instruction is read once, off the interrupting message.
414
+ const turn = registerBusyTurn({
415
+ messages: [assistantWithToolUse("tool-1", "tool-2", "tool-3")],
416
+ });
417
+
418
+ await interruptRunningTurn(turn.conversation, { origin: "test" });
419
+
420
+ const results = JSON.parse(persisted[0].content) as Array<{
421
+ tool_use_id: string;
422
+ content: string;
423
+ }>;
424
+ expect(results.map((block) => block.tool_use_id)).toEqual([
425
+ "tool-1",
426
+ "tool-2",
427
+ "tool-3",
428
+ ]);
429
+ for (const block of results) {
430
+ expect(block.content).toBe(PREEMPTED_TOOL_RESULT_TEXT);
431
+ }
432
+ expect(turn.conversation.pendingInterruptNote).toBe(true);
408
433
  });
409
434
 
410
- test("leaves the note disarmed when the loop wrote its own preempted result", async () => {
435
+ test("arms the note when the loop wrote its own preempted result", async () => {
411
436
  // The agent loop's abort handler unwound cleanly and answered the batch
412
- // itself, so the repair finds nothing to do and the notice is already
413
- // there.
437
+ // itself, so the repair finds nothing to do. The note is owed all the same.
414
438
  const turn = registerBusyTurn({
415
439
  messages: [
416
440
  assistantWithToolUse("tool-1"),
@@ -421,12 +445,10 @@ describe("interruptRunningTurn", () => {
421
445
  await interruptRunningTurn(turn.conversation, { origin: "test" });
422
446
 
423
447
  expect(persisted).toEqual([]);
424
- expect(turn.conversation.pendingInterruptNote).toBe(false);
448
+ expect(turn.conversation.pendingInterruptNote).toBe(true);
425
449
  });
426
450
 
427
451
  test("arms the note when the cut-off tool call was an ordinary cancel", async () => {
428
- // A tail answered with the plain cancel wording is a stop the user made
429
- // earlier, not this interrupt's notice, so the note is still owed.
430
452
  const turn = registerBusyTurn({
431
453
  messages: [
432
454
  assistantWithToolUse("tool-1"),
@@ -633,6 +655,9 @@ describe("interruptRunningTurn", () => {
633
655
  // Armed instead, so the drain that runs the queued message repairs it.
634
656
  expect(turn.conversation.pendingInterruptRepair).toBe(true);
635
657
  expect(turn.activityEvents).toEqual([]);
658
+ // The message joins the queue rather than replacing the turn, so it is not
659
+ // the message that interrupted anything and carries no note.
660
+ expect(turn.conversation.pendingInterruptNote).toBe(false);
636
661
  });
637
662
 
638
663
  test("a second interrupt stops the turn the first one started", async () => {
@@ -932,7 +957,7 @@ describe("repairInterruptedToolUseBlocks", () => {
932
957
  } as unknown as Conversation;
933
958
  }
934
959
 
935
- test("writes one result per abandoned call, worded for a preemption", async () => {
960
+ test("writes one fact-only result per abandoned call", async () => {
936
961
  const messages: Message[] = [assistantWithToolUse("tool-1", "tool-2")];
937
962
 
938
963
  await repairInterruptedToolUseBlocks(fakeConversation(messages), {
@@ -961,31 +986,6 @@ describe("repairInterruptedToolUseBlocks", () => {
961
986
  );
962
987
  });
963
988
 
964
- test("reports the preempted result it wrote", async () => {
965
- const messages: Message[] = [assistantWithToolUse("tool-1")];
966
-
967
- const result = await repairInterruptedToolUseBlocks(
968
- fakeConversation(messages),
969
- { force: true },
970
- );
971
-
972
- expect(result.preemptedToolResultOnTail).toBe(true);
973
- });
974
-
975
- test("reports no preempted result on a history that ends on plain text", async () => {
976
- const messages: Message[] = [
977
- { role: "user", content: [{ type: "text", text: "hi" }] },
978
- { role: "assistant", content: [{ type: "text", text: "hello" }] },
979
- ];
980
-
981
- const result = await repairInterruptedToolUseBlocks(
982
- fakeConversation(messages),
983
- { force: true },
984
- );
985
-
986
- expect(result.preemptedToolResultOnTail).toBe(false);
987
- });
988
-
989
989
  test("adds nothing when the loop already wrote its own results", async () => {
990
990
  // The agent loop's abort handler synthesizes results of its own when it
991
991
  // unwinds cleanly, so exactly one result per call exists either way.
@@ -994,14 +994,12 @@ describe("repairInterruptedToolUseBlocks", () => {
994
994
  toolResult("tool-1", PREEMPTED_TOOL_RESULT_TEXT),
995
995
  ];
996
996
 
997
- const result = await repairInterruptedToolUseBlocks(
998
- fakeConversation(messages),
999
- { force: true },
1000
- );
997
+ await repairInterruptedToolUseBlocks(fakeConversation(messages), {
998
+ force: true,
999
+ });
1001
1000
 
1002
1001
  expect(messages).toHaveLength(2);
1003
1002
  expect(persisted).toEqual([]);
1004
- expect(result.preemptedToolResultOnTail).toBe(true);
1005
1003
  });
1006
1004
 
1007
1005
  test("does nothing on a history that ends on an ordinary assistant reply", async () => {
@@ -1083,3 +1081,19 @@ describe("abortedToolResultText", () => {
1083
1081
  );
1084
1082
  });
1085
1083
  });
1084
+
1085
+ describe("the interrupt texts", () => {
1086
+ test("the tool result states the fact and nothing more", () => {
1087
+ // Repeated once per stopped call in a parallel batch, so an instruction
1088
+ // here is read as many times as the batch was wide.
1089
+ expect(PREEMPTED_TOOL_RESULT_TEXT).toBe(
1090
+ "Stopped early because the user sent a new message. This call may have completed anyway; check before repeating it.",
1091
+ );
1092
+ });
1093
+
1094
+ test("the note carries the behavior and leads with the reply", () => {
1095
+ expect(INTERRUPTED_TURN_NOTE_TEXT).toBe(
1096
+ "<interrupted_turn>The user sent this while you were still working, so that work stopped. Reply right away, in a line or two, before any more thinking or tool calls, so the user knows you heard them. Then pick the earlier work back up only if it is still wanted. Never apologize for or mention the interruption.</interrupted_turn>",
1097
+ );
1098
+ });
1099
+ });
@@ -1,10 +1,9 @@
1
1
  /**
2
- * An interrupt whose abort landed during the provider call leaves no
3
- * `tool_use` to answer, so nothing in the history tells the model its turn was
4
- * cut off. The interrupting user message carries the notice instead, on its
5
- * LLM-facing content only: the persisted row stays exactly what the user
6
- * typed, so no client renders it, and `interruptedPriorTurn` in the row's
7
- * metadata is what rebuilds the identical block on every later load.
2
+ * The message that interrupted a turn carries the note saying what to do about
3
+ * the interruption, on its LLM-facing content only: the persisted row stays
4
+ * exactly what the user typed, so no client renders it, and
5
+ * `interruptedPriorTurn` in the row's metadata is what rebuilds the identical
6
+ * block on every later load.
8
7
  */
9
8
  import { beforeEach, describe, expect, test } from "bun:test";
10
9
 
@@ -234,12 +234,12 @@ describe("LLM catalog parity: daemon vs client", () => {
234
234
  });
235
235
 
236
236
  test("jev-latest opts out of chat text generation", () => {
237
- expect(catalogModelSupportsText("jev", "jev-latest")).toBe(false);
237
+ expect(catalogModelSupportsText("typesafe", "jev-latest")).toBe(false);
238
238
  expect(catalogModelSupportsText("anthropic", "claude-opus-4-8")).toBe(true);
239
239
  expect(catalogModelSupportsText("openai-compatible", "local-model")).toBe(
240
240
  true,
241
241
  );
242
- expect(DEFAULT_PROVIDER_CHOICES).not.toContain("jev");
242
+ expect(DEFAULT_PROVIDER_CHOICES).not.toContain("typesafe");
243
243
  expect(DEFAULT_PROVIDER_CHOICES).toContain("poolside");
244
244
  });
245
245
 
@@ -21,6 +21,7 @@ import { setConfig } from "./helpers/set-config.js";
21
21
 
22
22
  let mockGeminiKey: string | undefined = "test-gemini-key";
23
23
  let mockOpenAIKey: string | undefined = "test-openai-key";
24
+ let mockOpenRouterKey: string | undefined = "test-openrouter-key";
24
25
  let mockGenerateResult = {
25
26
  images: [{ mimeType: "image/png", dataBase64: "generated-data" }],
26
27
  text: "A beautiful image",
@@ -40,7 +41,7 @@ let mockGenerateDelayMs = 0;
40
41
  */
41
42
  function seedImageGenService(
42
43
  overrides: {
43
- provider?: "vellum" | "gemini" | "openai";
44
+ provider?: "vellum" | "gemini" | "openai" | "openrouter";
44
45
  model?: string;
45
46
  } = {},
46
47
  ): void {
@@ -64,6 +65,9 @@ mock.module("../security/secure-keys.js", () => ({
64
65
  if (provider === "openai") {
65
66
  return mockOpenAIKey;
66
67
  }
68
+ if (provider === "openrouter") {
69
+ return mockOpenRouterKey;
70
+ }
67
71
  return undefined;
68
72
  },
69
73
  }));
@@ -143,6 +147,7 @@ const CONFIG_DIR = join(
143
147
  beforeEach(() => {
144
148
  mockGeminiKey = "test-gemini-key";
145
149
  mockOpenAIKey = "test-openai-key";
150
+ mockOpenRouterKey = "test-openrouter-key";
146
151
  seedImageGenService();
147
152
  mockGenerateResult = {
148
153
  images: [{ mimeType: "image/png", dataBase64: "generated-data" }],
@@ -308,6 +313,49 @@ describe("image-studio skill script wrapper", () => {
308
313
  expect(result.content).not.toContain("No Gemini API key");
309
314
  });
310
315
 
316
+ test("OpenRouter accepts an arbitrary slug and keeps OpenRouter ownership", async () => {
317
+ seedImageGenService({
318
+ provider: "openrouter",
319
+ model: "google/gemini-3.1-flash-image-preview",
320
+ });
321
+
322
+ const result = await run(
323
+ { prompt: "a nebula", model: "black-forest-labs/flux" },
324
+ fakeContext,
325
+ );
326
+
327
+ expect(result.isError).toBe(false);
328
+ expect(lastGenerateProvider).toBe("openrouter");
329
+ expect(lastGenerateCredentials).toEqual({
330
+ type: "direct",
331
+ apiKey: "test-openrouter-key",
332
+ });
333
+ expect(lastGenerateRequest?.model).toBe("black-forest-labs/flux");
334
+ });
335
+
336
+ test("OpenRouter qualifies a built-in alias and keeps OpenRouter ownership", async () => {
337
+ seedImageGenService({ provider: "openrouter" });
338
+
339
+ const result = await run({ prompt: "a robot", model: "openai" }, fakeContext);
340
+
341
+ expect(result.isError).toBe(false);
342
+ expect(lastGenerateProvider).toBe("openrouter");
343
+ expect(lastGenerateRequest?.model).toBe("openai/gpt-image-2");
344
+ });
345
+
346
+ test("OpenRouter returns a missing-key hint without falling back to Gemini", async () => {
347
+ seedImageGenService({ provider: "openrouter" });
348
+ mockOpenRouterKey = undefined;
349
+ mockGeminiKey = "gemini-key-should-not-be-used";
350
+
351
+ const result = await run({ prompt: "a cat" }, fakeContext);
352
+
353
+ expect(result.isError).toBe(true);
354
+ expect(result.content).toContain("OpenRouter API key");
355
+ expect(result.content).not.toContain("No Gemini API key");
356
+ expect(lastGenerateProvider).toBeNull();
357
+ });
358
+
311
359
  test("explicit model override routes to owning provider (gemini config → openai call)", async () => {
312
360
  // Config says the user's default provider is gemini, but the LLM
313
361
  // explicitly requests a gpt-* model. The tool must dispatch to OpenAI
@@ -0,0 +1,69 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { actorAuthorProvenance } from "../daemon/message-provenance.js";
4
+ import type { TrustContext } from "../daemon/trust-context-types.js";
5
+ import {
6
+ addMessage,
7
+ createConversation,
8
+ getMessageById,
9
+ parseMessageMetadata,
10
+ provenanceFromTrustContext,
11
+ } from "../persistence/conversation-crud.js";
12
+ import { getDb } from "../persistence/db-connection.js";
13
+ import { initializeDb } from "../persistence/db-init.js";
14
+
15
+ await initializeDb();
16
+
17
+ const ALICE: TrustContext = {
18
+ sourceChannel: "slack",
19
+ trustClass: "trusted_contact",
20
+ requesterIdentifier: "@alice",
21
+ requesterContactId: "contact-alice",
22
+ };
23
+
24
+ async function persistWith(
25
+ role: "user" | "assistant",
26
+ metadata: Record<string, unknown>,
27
+ ): Promise<ReturnType<typeof parseMessageMetadata>> {
28
+ const conversation = createConversation("provenance-contact-id");
29
+ const row = await addMessage(conversation.id, role, "hello", { metadata });
30
+ const stored = getMessageById(row.id, conversation.id);
31
+ return parseMessageMetadata(stored?.metadata ?? null);
32
+ }
33
+
34
+ describe("message provenance contact id", () => {
35
+ beforeEach(() => {
36
+ const db = getDb();
37
+ db.run(`DELETE FROM messages`);
38
+ db.run(`DELETE FROM conversations`);
39
+ });
40
+
41
+ test("a person's own row names them as its author", async () => {
42
+ const metadata = await persistWith("user", {
43
+ ...provenanceFromTrustContext(ALICE),
44
+ ...actorAuthorProvenance(ALICE),
45
+ });
46
+
47
+ expect(metadata?.provenanceTrustClass).toBe("trusted_contact");
48
+ expect(metadata?.provenanceContactId).toBe("contact-alice");
49
+ });
50
+
51
+ test("a row written during their turn carries the turn's trust but no author", async () => {
52
+ // The assistant's replies, tool results, and notices are stamped with the
53
+ // turn's provenance alone.
54
+ const metadata = await persistWith(
55
+ "assistant",
56
+ provenanceFromTrustContext(ALICE),
57
+ );
58
+
59
+ expect(metadata?.provenanceTrustClass).toBe("trusted_contact");
60
+ expect(metadata?.provenanceContactId).toBeUndefined();
61
+ });
62
+
63
+ test("an actor with no resolved contact names no author", () => {
64
+ expect(
65
+ actorAuthorProvenance({ sourceChannel: "slack", trustClass: "unknown" }),
66
+ ).toEqual({});
67
+ expect(actorAuthorProvenance(undefined)).toEqual({});
68
+ });
69
+ });