@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
@@ -13,7 +13,7 @@
13
13
  * without bringing up the full daemon stack.
14
14
  */
15
15
 
16
- import { describe, expect, mock, test } from "bun:test";
16
+ import { describe, expect, mock, spyOn, test } from "bun:test";
17
17
 
18
18
  mock.module("../providers/registry.js", () => ({
19
19
  getProvider: () => ({ name: "mock-provider" }),
@@ -70,6 +70,7 @@ mock.module("../runtime/services/auto-analysis-guard.js", () => ({
70
70
 
71
71
  import { Conversation } from "../daemon/conversation.js";
72
72
  import type { HostAppControlProxy } from "../daemon/host-app-control-proxy.js";
73
+ import { HostCuProxy } from "../daemon/host-cu-proxy.js";
73
74
 
74
75
  /**
75
76
  * Minimal stand-in for HostAppControlProxy that records dispose() calls.
@@ -111,6 +112,57 @@ function makeConversation(): Conversation {
111
112
  return conv;
112
113
  }
113
114
 
115
+ describe("Conversation HostCuProxy lifecycle", () => {
116
+ test.each(["replace", "remove"] as const)(
117
+ "%s disposes the prior proxy even when mode-session retirement fails",
118
+ async (operation) => {
119
+ const conversation = makeConversation();
120
+ const previous = new HostCuProxy();
121
+ const replacement =
122
+ operation === "replace" ? new HostCuProxy() : undefined;
123
+ conversation.setHostCuProxy(previous);
124
+ const dispose = spyOn(previous, "dispose");
125
+ const endTask = spyOn(
126
+ conversation.computerUseModeSessions,
127
+ "endTask",
128
+ ).mockImplementation(() => {
129
+ throw new Error("session retirement unavailable");
130
+ });
131
+ const pending = previous
132
+ .request(
133
+ "computer_use_click",
134
+ { element_id: 1 },
135
+ conversation.conversationId,
136
+ 1,
137
+ "Clicking the button",
138
+ )
139
+ .catch((error: unknown) => error);
140
+ try {
141
+ expect(() => conversation.setHostCuProxy(previous)).not.toThrow();
142
+ expect(endTask).not.toHaveBeenCalled();
143
+ expect(() => conversation.setHostCuProxy(replacement)).not.toThrow();
144
+ expect(await pending).toMatchObject({
145
+ message: "Host CU proxy disposed",
146
+ });
147
+ expect(endTask).toHaveBeenCalledWith({
148
+ turnId: conversation.currentRequestId,
149
+ source: {
150
+ sourceId: previous.sourceId,
151
+ generation: previous.resetGeneration,
152
+ },
153
+ });
154
+ expect(dispose).toHaveBeenCalledTimes(1);
155
+ expect(conversation.hostCuProxy).toBe(replacement);
156
+ } finally {
157
+ endTask.mockRestore();
158
+ dispose.mockRestore();
159
+ previous.dispose();
160
+ replacement?.dispose();
161
+ }
162
+ },
163
+ );
164
+ });
165
+
114
166
  describe("Conversation — HostAppControlProxy lifecycle", () => {
115
167
  test("setHostAppControlProxy stores the proxy", () => {
116
168
  const conversation = makeConversation();
@@ -4,18 +4,9 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
4
4
  // Stub it so eviction tests run without a real manager: no protected children
5
5
  // by default, and abortAllForParent records the conversations it was called for.
6
6
  const abortedParents: string[] = [];
7
- let protectedChildren: Array<{ status: string }> = [];
8
7
  mock.module("../subagent/index.js", () => ({
9
8
  getSubagentManager: () => ({
10
9
  abortAllForParent: (id: string) => abortedParents.push(id),
11
- getChildrenOf: () => protectedChildren,
12
- hasActiveChildren: () =>
13
- protectedChildren.some(
14
- (c) =>
15
- c.status === "running" ||
16
- c.status === "pending" ||
17
- c.status === "awaiting_input",
18
- ),
19
10
  }),
20
11
  }));
21
12
 
@@ -30,11 +21,8 @@ function createMockSession(
30
21
  ): EvictableConversation & { disposed: boolean } {
31
22
  return {
32
23
  disposed: false,
33
- isProcessing() {
34
- return processing;
35
- },
36
- hasQueuedMessages() {
37
- return queued;
24
+ hasInFlightWork() {
25
+ return processing || queued;
38
26
  },
39
27
  dispose() {
40
28
  this.disposed = true;
@@ -48,7 +36,6 @@ describe("ConversationEvictor", () => {
48
36
 
49
37
  beforeEach(() => {
50
38
  abortedParents.length = 0;
51
- protectedChildren = [];
52
39
  sessions = new Map();
53
40
  evictor = new ConversationEvictor(
54
41
  sessions as Map<string, EvictableConversation>,
@@ -124,6 +111,19 @@ describe("ConversationEvictor", () => {
124
111
  expect(sessions.has("a")).toBe(true);
125
112
  expect(s1.disposed).toBe(false);
126
113
  });
114
+
115
+ test("skips conversations with resident mode-session work", () => {
116
+ const session = createMockSession();
117
+ session.hasInFlightWork = () => true;
118
+ sessions.set("mode-active", session);
119
+
120
+ const result = evictor.sweep();
121
+
122
+ expect(result.ttlEvicted).toBe(0);
123
+ expect(result.skipped).toBe(1);
124
+ expect(sessions.has("mode-active")).toBe(true);
125
+ expect(session.disposed).toBe(false);
126
+ });
127
127
  });
128
128
 
129
129
  describe("LRU eviction", () => {
@@ -217,43 +217,68 @@ describe("ConversationEvictor", () => {
217
217
  expect(s0.disposed).toBe(false);
218
218
  expect(s1.disposed).toBe(true);
219
219
  });
220
- });
221
220
 
222
- describe("onEvict", () => {
223
- test("aborts subagents for each evicted session", () => {
224
- const s1 = createMockSession();
225
- sessions.set("a", s1);
226
- // Never touched — will be TTL evicted
221
+ test("skips resident mode-session work during LRU eviction", () => {
222
+ const protectedSession = createMockSession();
223
+ protectedSession.hasInFlightWork = () => true;
224
+ const evictable = createMockSession();
225
+ const fresh1 = createMockSession();
226
+ const fresh2 = createMockSession();
227
+ sessions.set("mode-active", protectedSession);
228
+ sessions.set("evictable", evictable);
229
+ sessions.set("fresh-1", fresh1);
230
+ sessions.set("fresh-2", fresh2);
227
231
 
228
- evictor.sweep();
232
+ const lastAccess = (
233
+ evictor as unknown as { lastAccess: Map<string, number> }
234
+ ).lastAccess;
235
+ const now = Date.now();
236
+ lastAccess.set("mode-active", now - 50);
237
+ lastAccess.set("evictable", now - 40);
238
+ lastAccess.set("fresh-1", now);
239
+ lastAccess.set("fresh-2", now);
229
240
 
230
- expect(abortedParents).toEqual(["a"]);
241
+ const result = evictor.sweep();
242
+
243
+ expect(result.lruEvicted).toBe(1);
244
+ expect(sessions.has("mode-active")).toBe(true);
245
+ expect(sessions.has("evictable")).toBe(false);
246
+ expect(protectedSession.disposed).toBe(false);
231
247
  });
232
248
  });
233
249
 
234
- describe("shouldProtect", () => {
235
- test("skips conversations with running or pending subagents", () => {
236
- protectedChildren = [{ status: "running" }];
237
- const s1 = createMockSession();
238
- sessions.set("a", s1);
250
+ describe("memory-pressure eviction", () => {
251
+ test("keeps resident mode-session work while evicting idle conversations", () => {
252
+ const resident = createMockSession();
253
+ resident.hasInFlightWork = () => true;
254
+ const idle = createMockSession();
255
+ sessions.set("mode-active", resident);
256
+ sessions.set("idle", idle);
257
+ evictor = new ConversationEvictor(sessions, {
258
+ ttlMs: Number.MAX_SAFE_INTEGER,
259
+ maxConversations: Number.MAX_SAFE_INTEGER,
260
+ memoryThresholdBytes: 0,
261
+ });
239
262
 
240
263
  const result = evictor.sweep();
241
264
 
242
- expect(result.skipped).toBe(1);
243
- expect(sessions.has("a")).toBe(true);
244
- expect(s1.disposed).toBe(false);
265
+ expect(result.memoryEvicted).toBe(1);
266
+ expect(sessions.has("mode-active")).toBe(true);
267
+ expect(sessions.has("idle")).toBe(false);
268
+ expect(resident.disposed).toBe(false);
269
+ expect(idle.disposed).toBe(true);
245
270
  });
271
+ });
246
272
 
247
- test("skips conversations with awaiting_input subagents", () => {
248
- protectedChildren = [{ status: "awaiting_input" }];
273
+ describe("onEvict", () => {
274
+ test("aborts subagents for each evicted session", () => {
249
275
  const s1 = createMockSession();
250
276
  sessions.set("a", s1);
277
+ // Never touched, so it will be TTL evicted.
251
278
 
252
- const result = evictor.sweep();
279
+ evictor.sweep();
253
280
 
254
- expect(result.skipped).toBe(1);
255
- expect(sessions.has("a")).toBe(true);
256
- expect(s1.disposed).toBe(false);
281
+ expect(abortedParents).toEqual(["a"]);
257
282
  });
258
283
  });
259
284
 
@@ -0,0 +1,174 @@
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ mock.module("../plugins/defaults/memory/indexer.js", () => ({
4
+ MIN_SEGMENT_CHARS: 50,
5
+ indexMessageNow: async () => ({ indexedSegments: 0, enqueuedJobs: 0 }),
6
+ enqueueBackfillJob: () => "",
7
+ enqueueRebuildIndexJob: () => "",
8
+ }));
9
+
10
+ import { consolidateAssistantMessages } from "../daemon/conversation-history.js";
11
+ import {
12
+ addMessage,
13
+ createConversation,
14
+ deleteLastExchange,
15
+ getMessages,
16
+ } from "../persistence/conversation-crud.js";
17
+ import {
18
+ beginConversationModeSession,
19
+ updateConversationModeSessionBoundaries,
20
+ } from "../persistence/conversation-mode-sessions.js";
21
+ import { getDb } from "../persistence/db-connection.js";
22
+ import { initializeDb } from "../persistence/db-init.js";
23
+
24
+ await initializeDb();
25
+
26
+ const FAIL_REPAIR_TRIGGER = "fail_mode_session_boundary_repair";
27
+
28
+ function installFailingBoundaryRepairTrigger(): void {
29
+ getDb().run(/* sql */ `
30
+ CREATE TRIGGER ${FAIL_REPAIR_TRIGGER}
31
+ BEFORE UPDATE ON conversation_mode_sessions
32
+ BEGIN
33
+ SELECT RAISE(ABORT, 'injected boundary repair failure');
34
+ END
35
+ `);
36
+ }
37
+
38
+ afterEach(() => {
39
+ getDb().run(`DROP TRIGGER IF EXISTS ${FAIL_REPAIR_TRIGGER}`);
40
+ });
41
+
42
+ describe("message deletion mode-session boundary repair failures", () => {
43
+ test("consolidation deletes every donor without duplicating copied content", async () => {
44
+ const conversation = createConversation("Boundary repair consolidation");
45
+ const sessionId = `session-${conversation.id}`;
46
+ const owner = { id: sessionId, mode: "browser" as const };
47
+ const sourceStartedAt = Date.now() - 10_000;
48
+ expect(
49
+ beginConversationModeSession({
50
+ id: sessionId,
51
+ conversationId: conversation.id,
52
+ mode: owner.mode,
53
+ sourceStartedAt,
54
+ }),
55
+ ).toMatchObject({ ok: true });
56
+
57
+ const user = await addMessage(conversation.id, "user", "Run the tool");
58
+ const retained = await addMessage(
59
+ conversation.id,
60
+ "assistant",
61
+ JSON.stringify([
62
+ { type: "tool_use", id: "tool-1", name: "example", input: {} },
63
+ ]),
64
+ { metadata: { modeSession: owner } },
65
+ );
66
+ const toolResult = await addMessage(
67
+ conversation.id,
68
+ "user",
69
+ JSON.stringify([
70
+ { type: "tool_result", tool_use_id: "tool-1", content: "done" },
71
+ ]),
72
+ { metadata: { modeSession: owner } },
73
+ );
74
+ const donor = await addMessage(
75
+ conversation.id,
76
+ "assistant",
77
+ JSON.stringify([{ type: "text", text: "Finished once." }]),
78
+ { metadata: { modeSession: owner } },
79
+ );
80
+ expect(
81
+ updateConversationModeSessionBoundaries({
82
+ id: sessionId,
83
+ conversationId: conversation.id,
84
+ expectedRevision: 1,
85
+ firstIncluded: {
86
+ at: retained.createdAt,
87
+ messageId: retained.id,
88
+ },
89
+ lastActivityAt: donor.createdAt,
90
+ lastOwnedMessageId: donor.id,
91
+ }),
92
+ ).toMatchObject({ ok: true });
93
+ installFailingBoundaryRepairTrigger();
94
+
95
+ expect(consolidateAssistantMessages(conversation.id, user.id)).toBe(true);
96
+ const rows = getMessages(conversation.id);
97
+ expect(rows.map((row) => row.id)).toEqual([user.id, retained.id]);
98
+ const retainedContent = rows[1]!.content;
99
+ expect(retainedContent).toEqual([
100
+ { type: "tool_use", id: "tool-1", name: "example", input: {} },
101
+ { type: "text", text: "Finished once." },
102
+ { type: "tool_result", tool_use_id: "tool-1", content: "done" },
103
+ ]);
104
+
105
+ expect(consolidateAssistantMessages(conversation.id, user.id)).toBe(false);
106
+ expect(getMessages(conversation.id)[1]!.content).toEqual(retainedContent);
107
+ expect(
108
+ getMessages(conversation.id).some((row) => row.id === donor.id),
109
+ ).toBe(false);
110
+ expect(
111
+ getMessages(conversation.id).some((row) => row.id === toolResult.id),
112
+ ).toBe(false);
113
+ });
114
+
115
+ test("tail deletion returns its committed count when boundary repair fails", async () => {
116
+ const conversation = createConversation("Boundary repair tail delete");
117
+ const sessionId = `session-${conversation.id}`;
118
+ const owner = { id: sessionId, mode: "browser" as const };
119
+ expect(
120
+ beginConversationModeSession({
121
+ id: sessionId,
122
+ conversationId: conversation.id,
123
+ mode: owner.mode,
124
+ sourceStartedAt: Date.now() - 10_000,
125
+ }),
126
+ ).toMatchObject({ ok: true });
127
+ const keptUser = await addMessage(conversation.id, "user", "Keep this");
128
+ const keptAssistant = await addMessage(
129
+ conversation.id,
130
+ "assistant",
131
+ "Kept reply",
132
+ );
133
+ const deletedUser = await addMessage(
134
+ conversation.id,
135
+ "user",
136
+ "Delete this",
137
+ { metadata: { modeSession: owner } },
138
+ );
139
+ const deletedAssistant = await addMessage(
140
+ conversation.id,
141
+ "assistant",
142
+ "Deleted reply",
143
+ { metadata: { modeSession: owner } },
144
+ );
145
+ expect(
146
+ updateConversationModeSessionBoundaries({
147
+ id: sessionId,
148
+ conversationId: conversation.id,
149
+ expectedRevision: 1,
150
+ firstIncluded: {
151
+ at: deletedAssistant.createdAt,
152
+ messageId: deletedAssistant.id,
153
+ },
154
+ lastActivityAt: deletedAssistant.createdAt,
155
+ lastOwnedMessageId: deletedAssistant.id,
156
+ }),
157
+ ).toMatchObject({ ok: true });
158
+ installFailingBoundaryRepairTrigger();
159
+
160
+ expect(deleteLastExchange(conversation.id)).toBe(2);
161
+ expect(getMessages(conversation.id).map((row) => row.id)).toEqual([
162
+ keptUser.id,
163
+ keptAssistant.id,
164
+ ]);
165
+ expect(
166
+ getMessages(conversation.id).some((row) => row.id === deletedUser.id),
167
+ ).toBe(false);
168
+ expect(
169
+ getMessages(conversation.id).some(
170
+ (row) => row.id === deletedAssistant.id,
171
+ ),
172
+ ).toBe(false);
173
+ });
174
+ });
@@ -89,6 +89,7 @@ import {
89
89
  handleMessageComplete,
90
90
  } from "../daemon/conversation-agent-loop-handlers.js";
91
91
  import { getConversationPersistedSeq } from "../persistence/conversation-crud.js";
92
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
92
93
 
93
94
  const CONVERSATION_ID = "test-session-id";
94
95
 
@@ -101,6 +102,7 @@ function createMockDeps(): EventHandlerDeps {
101
102
  emitActivityState: () => {},
102
103
  markWorkspaceTopLevelDirty: () => {},
103
104
  currentTurnSurfaces: [],
105
+ modeSessions: mockUnownedModeSessions(),
104
106
  } as unknown as EventHandlerDeps["ctx"],
105
107
  onEvent: (_msg: AssistantEvent) => {},
106
108
  reqId: "test-req-id",
@@ -87,6 +87,7 @@ import {
87
87
  type QueuedMessage,
88
88
  } from "../daemon/conversation-queue-manager.js";
89
89
  import type { TrustContext } from "../daemon/trust-context-types.js";
90
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
90
91
 
91
92
  // ---------------------------------------------------------------------------
92
93
  // Fake context — captures preactivation calls, satisfies the bare minimum
@@ -117,6 +118,7 @@ function makeFakeContext(opts: {
117
118
  abortController: null,
118
119
  queue: opts.queue,
119
120
  surfaceActionRequestIds: new Set<string>(),
121
+ modeSessions: mockUnownedModeSessions(),
120
122
  usageStats: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
121
123
  get preactivatedSkillIds(): string[] | undefined {
122
124
  return preactivatedSkillIds;
@@ -197,6 +197,7 @@ function makeConversation(opts: { processing?: boolean } = {}) {
197
197
  const emitActivityState = mock(
198
198
  (_phase: string, _reason: string, _options?: unknown) => {},
199
199
  );
200
+ const invalidateAllStructuralWaits = mock(() => 0);
200
201
  const conversation = {
201
202
  conversationId: "conv-retry-test",
202
203
  trustContext: undefined,
@@ -209,6 +210,7 @@ function makeConversation(opts: { processing?: boolean } = {}) {
209
210
  loadFromDb,
210
211
  runAgentLoop,
211
212
  emitActivityState,
213
+ modeSessions: { invalidateAllStructuralWaits },
212
214
  };
213
215
  return {
214
216
  conversation,
@@ -217,6 +219,7 @@ function makeConversation(opts: { processing?: boolean } = {}) {
217
219
  loadFromDb,
218
220
  runAgentLoop,
219
221
  emitActivityState,
222
+ invalidateAllStructuralWaits,
220
223
  };
221
224
  }
222
225
 
@@ -264,6 +267,24 @@ beforeEach(() => {
264
267
  });
265
268
 
266
269
  describe("POST /v1/conversations/:id/retry", () => {
270
+ test("tracking invalidation failure does not prevent retry", async () => {
271
+ const ctx = makeConversation();
272
+ activeConversation = ctx.conversation;
273
+ ctx.invalidateAllStructuralWaits.mockImplementation(() => {
274
+ throw new Error("session tracking unavailable");
275
+ });
276
+ const res = await callHandler(
277
+ retryHandler,
278
+ makeRequest(),
279
+ { id: "conv-retry-test" },
280
+ 202,
281
+ );
282
+ expect(res.status).toBe(202);
283
+ await settle();
284
+ expect(discardMock).toHaveBeenCalledTimes(1);
285
+ expect(ctx.runAgentLoop).toHaveBeenCalledTimes(1);
286
+ });
287
+
267
288
  test("202s, discards the tail, and re-runs the loop from the anchor", async () => {
268
289
  const ctx = makeConversation();
269
290
  activeConversation = ctx.conversation;
@@ -299,6 +320,7 @@ describe("POST /v1/conversations/:id/retry", () => {
299
320
  expect(ctx.conversation.isProcessing()).toBe(true);
300
321
  expect(ctx.conversation.abortController).not.toBeNull();
301
322
  expect(discardMock).toHaveBeenCalledWith("conv-retry-test");
323
+ expect(ctx.invalidateAllStructuralWaits).toHaveBeenCalledTimes(1);
302
324
 
303
325
  // Origin-less invalidation: the initiating client reconciles too.
304
326
  expect(publishConversationMessagesChangedMock).toHaveBeenCalledTimes(1);
@@ -32,6 +32,7 @@ import * as pendingInteractions from "../runtime/pending-interactions.js";
32
32
  import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
33
33
  import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
34
34
  import { callHandler } from "./helpers/call-route-handler.js";
35
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
35
36
  import { setConfig } from "./helpers/set-config.js";
36
37
 
37
38
  const testDir = process.env.VELLUM_WORKSPACE_DIR!;
@@ -88,6 +89,7 @@ function createFakeConversation(conversationId: string): Conversation {
88
89
  conversationId,
89
90
  processing: false,
90
91
  currentRequestId: undefined as string | undefined,
92
+ modeSessions: mockUnownedModeSessions(),
91
93
  abortController: null as AbortController | null,
92
94
  trustContext: undefined as unknown,
93
95
  turnChannelContext: null as {
@@ -135,6 +135,7 @@ mock.module("../ipc/gateway-client.js", () => ({
135
135
  import { getEffectiveEnabledPluginSet } from "../daemon/conversation-tool-setup.js";
136
136
  import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
137
137
  import { callHandler } from "./helpers/call-route-handler.js";
138
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
138
139
 
139
140
  function makeConversation() {
140
141
  const runAgentLoop = mock(async () => undefined);
@@ -156,6 +157,7 @@ function makeConversation() {
156
157
  },
157
158
  abortController: null,
158
159
  currentRequestId: undefined,
160
+ modeSessions: mockUnownedModeSessions(),
159
161
  queue: { length: 0 },
160
162
  setTrustContext: () => {},
161
163
  replayActivityState: () => {},
@@ -113,6 +113,7 @@ mock.module("../contacts/guardian-delivery-reader.js", () => ({
113
113
  import type { AuthContext } from "../runtime/auth/types.js";
114
114
  import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
115
115
  import { callHandler } from "./helpers/call-route-handler.js";
116
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
116
117
 
117
118
  const _testAuthContext: AuthContext = {
118
119
  subject: "actor:self:test-guardian",
@@ -185,6 +186,7 @@ describe("handleSendMessage guardian reply interception", () => {
185
186
  setHostAppControlProxy: () => {},
186
187
  restoreBrowserProxyAvailability: () => {},
187
188
  addPreactivatedSkillId: () => {},
189
+ modeSessions: mockUnownedModeSessions(),
188
190
  } as unknown as import("../daemon/conversation.js").Conversation;
189
191
 
190
192
  const req = new Request("http://localhost/v1/messages", {
@@ -275,6 +277,7 @@ describe("handleSendMessage guardian reply interception", () => {
275
277
  setHostAppControlProxy: () => {},
276
278
  restoreBrowserProxyAvailability: () => {},
277
279
  addPreactivatedSkillId: () => {},
280
+ modeSessions: mockUnownedModeSessions(),
278
281
  } as unknown as import("../daemon/conversation.js").Conversation;
279
282
 
280
283
  const req = new Request("http://localhost/v1/messages", {
@@ -353,6 +356,7 @@ describe("handleSendMessage guardian reply interception", () => {
353
356
  setHostAppControlProxy: () => {},
354
357
  restoreBrowserProxyAvailability: () => {},
355
358
  addPreactivatedSkillId: () => {},
359
+ modeSessions: mockUnownedModeSessions(),
356
360
  } as unknown as import("../daemon/conversation.js").Conversation;
357
361
 
358
362
  const req = new Request("http://localhost/v1/messages", {
@@ -442,6 +446,7 @@ describe("handleSendMessage guardian reply interception", () => {
442
446
  setHostAppControlProxy: () => {},
443
447
  restoreBrowserProxyAvailability: () => {},
444
448
  addPreactivatedSkillId: () => {},
449
+ modeSessions: mockUnownedModeSessions(),
445
450
  } as unknown as import("../daemon/conversation.js").Conversation;
446
451
 
447
452
  const req = new Request("http://localhost/v1/messages", {
@@ -529,6 +534,7 @@ describe("handleSendMessage guardian reply interception", () => {
529
534
  setHostAppControlProxy: () => {},
530
535
  restoreBrowserProxyAvailability: () => {},
531
536
  addPreactivatedSkillId: () => {},
537
+ modeSessions: mockUnownedModeSessions(),
532
538
  } as unknown as import("../daemon/conversation.js").Conversation;
533
539
 
534
540
  const req = new Request("http://localhost/v1/messages", {
@@ -612,6 +618,7 @@ describe("handleSendMessage guardian reply interception", () => {
612
618
  setHostAppControlProxy: () => {},
613
619
  restoreBrowserProxyAvailability: () => {},
614
620
  addPreactivatedSkillId: () => {},
621
+ modeSessions: mockUnownedModeSessions(),
615
622
  } as unknown as import("../daemon/conversation.js").Conversation;
616
623
 
617
624
  const req = new Request("http://localhost/v1/messages", {
@@ -689,6 +696,7 @@ describe("handleSendMessage guardian reply interception", () => {
689
696
  setHostAppControlProxy: () => {},
690
697
  restoreBrowserProxyAvailability: () => {},
691
698
  addPreactivatedSkillId: () => {},
699
+ modeSessions: mockUnownedModeSessions(),
692
700
  } as unknown as import("../daemon/conversation.js").Conversation;
693
701
 
694
702
  const req = new Request("http://localhost/v1/messages", {
@@ -768,6 +776,7 @@ describe("handleSendMessage guardian reply interception", () => {
768
776
  setHostAppControlProxy: () => {},
769
777
  restoreBrowserProxyAvailability: () => {},
770
778
  addPreactivatedSkillId: () => {},
779
+ modeSessions: mockUnownedModeSessions(),
771
780
  } as unknown as import("../daemon/conversation.js").Conversation;
772
781
 
773
782
  const req = new Request("http://localhost/v1/messages", {
@@ -848,6 +857,7 @@ describe("handleSendMessage guardian reply interception", () => {
848
857
  setHostAppControlProxy: () => {},
849
858
  restoreBrowserProxyAvailability: () => {},
850
859
  addPreactivatedSkillId: () => {},
860
+ modeSessions: mockUnownedModeSessions(),
851
861
  } as unknown as import("../daemon/conversation.js").Conversation;
852
862
 
853
863
  const req = new Request("http://localhost/v1/messages", {
@@ -126,6 +126,7 @@ import {
126
126
  import * as pendingInteractions from "../runtime/pending-interactions.js";
127
127
  import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
128
128
  import { callHandler } from "./helpers/call-route-handler.js";
129
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
129
130
 
130
131
  const CONV_ID = "conv-hidden-queue";
131
132
 
@@ -159,6 +160,7 @@ function makeConversationWithPendingConfirmation(
159
160
  },
160
161
  },
161
162
  currentRequestId: undefined,
163
+ modeSessions: mockUnownedModeSessions(),
162
164
  queue: {
163
165
  length: 0,
164
166
  promoteToHead: (requestId: string) => ({ requestId }),
@@ -166,6 +166,7 @@ import { CONVERSATION_BUSY_MESSAGE } from "../daemon/conversation-messaging.js";
166
166
  import type { AuthContext } from "../runtime/auth/types.js";
167
167
  import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
168
168
  import { callHandler } from "./helpers/call-route-handler.js";
169
+ import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
169
170
 
170
171
  const _testAuthContext: AuthContext = {
171
172
  subject: "actor:self:test-guardian",
@@ -233,6 +234,7 @@ function makeConversation() {
233
234
  messages,
234
235
  abortController: null,
235
236
  currentRequestId: undefined,
237
+ modeSessions: mockUnownedModeSessions(),
236
238
  queue: { length: 0 },
237
239
  setTrustContext: () => {},
238
240
  replayActivityState: () => {},
@@ -673,6 +673,35 @@ describe("injectChannelCapabilityContext", () => {
673
673
  const text = (result.content[0] as { type: "text"; text: string }).text;
674
674
  expect(text).not.toContain("Do NOT use markdown tables");
675
675
  });
676
+
677
+ test("injects email send CLI constraint for email channel", () => {
678
+ const caps: ChannelCapabilities = {
679
+ channel: "email",
680
+ dashboardCapable: false,
681
+ supportsDynamicUi: false,
682
+ supportsVoiceInput: false,
683
+ };
684
+
685
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
686
+ const text = (result.content[0] as { type: "text"; text: string }).text;
687
+ expect(text).toContain("Conversation text is not emailed");
688
+ expect(text).toContain("assistant email send");
689
+ expect(text).toContain("--reply-to");
690
+ });
691
+
692
+ test("does NOT inject email send CLI constraint for non-email channels", () => {
693
+ const caps: ChannelCapabilities = {
694
+ channel: "telegram",
695
+ dashboardCapable: false,
696
+ supportsDynamicUi: false,
697
+ supportsVoiceInput: false,
698
+ };
699
+
700
+ const result = injectChannelCapabilityContext(baseUserMessage, caps);
701
+ const text = (result.content[0] as { type: "text"; text: string }).text;
702
+ expect(text).not.toContain("Conversation text is not emailed");
703
+ expect(text).not.toContain("assistant email send");
704
+ });
676
705
  });
677
706
 
678
707
  // ---------------------------------------------------------------------------