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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/ARCHITECTURE.md +43 -29
  2. package/Dockerfile +63 -0
  3. package/docs/architecture/security.md +10 -5
  4. package/docs/architecture/turn-actor.md +2 -0
  5. package/docs/desktop-browser-cli.md +45 -4
  6. package/docs/live-voice-task-outcomes.md +100 -0
  7. package/docs/mode-sessions.md +183 -0
  8. package/docs/voice-input-diagnostics.md +58 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
  16. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
  17. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
  18. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
  19. package/node_modules/@vellumai/service-contracts/package.json +2 -1
  20. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
  22. package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
  23. package/openapi.yaml +665 -14
  24. package/package.json +1 -1
  25. package/scripts/smoke-desktop-browser-cli.ts +10 -26
  26. package/scripts/smoke-desktop-window-theme.ts +2 -3
  27. package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
  28. package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
  29. package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
  30. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  31. package/src/__tests__/answered-question-persistence.test.ts +2 -0
  32. package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
  33. package/src/__tests__/approval-message-composer.test.ts +1 -118
  34. package/src/__tests__/approval-routes-http.test.ts +3 -0
  35. package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
  36. package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
  37. package/src/__tests__/assistant-event-targeting.test.ts +186 -0
  38. package/src/__tests__/assistant-stream-state.test.ts +29 -9
  39. package/src/__tests__/browser-fill-credential.test.ts +11 -22
  40. package/src/__tests__/btw-routes.test.ts +4 -1
  41. package/src/__tests__/build-persisted-content.test.ts +1 -1
  42. package/src/__tests__/call-conversation-messages.test.ts +5 -0
  43. package/src/__tests__/call-recovery.test.ts +7 -2
  44. package/src/__tests__/call-routes-http.test.ts +12 -0
  45. package/src/__tests__/call-store.test.ts +7 -2
  46. package/src/__tests__/channel-retry-sweep.test.ts +9 -0
  47. package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
  48. package/src/__tests__/checker.test.ts +11 -3
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
  50. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
  51. package/src/__tests__/computer-use-tools.test.ts +1 -233
  52. package/src/__tests__/config-model-image-provider.test.ts +39 -0
  53. package/src/__tests__/config-schema.test.ts +16 -0
  54. package/src/__tests__/config-watcher.test.ts +24 -0
  55. package/src/__tests__/context-search-agent-runner.test.ts +260 -1
  56. package/src/__tests__/context-search-fanout.test.ts +1 -1
  57. package/src/__tests__/context-search-types.test.ts +7 -7
  58. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
  59. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
  60. package/src/__tests__/conversation-agent-loop.test.ts +546 -16
  61. package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
  62. package/src/__tests__/conversation-evictor.test.ts +62 -37
  63. package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
  64. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
  65. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  66. package/src/__tests__/conversation-retry-route.test.ts +22 -0
  67. package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
  68. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
  69. package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
  70. package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
  73. package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
  74. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
  75. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
  76. package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
  77. package/src/__tests__/conversation-undo.test.ts +61 -0
  78. package/src/__tests__/credential-execution-client.test.ts +6 -6
  79. package/src/__tests__/credential-prompt-route.test.ts +35 -3
  80. package/src/__tests__/cu-unified-flow.test.ts +82 -1
  81. package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
  82. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
  83. package/src/__tests__/dm-backfill.test.ts +91 -1
  84. package/src/__tests__/document-create-route.test.ts +88 -0
  85. package/src/__tests__/document-sync-tags.test.ts +27 -0
  86. package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
  87. package/src/__tests__/events-tail-route.test.ts +25 -0
  88. package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
  89. package/src/__tests__/guardian-action-sweep.test.ts +16 -0
  90. package/src/__tests__/guardian-dispatch.test.ts +13 -0
  91. package/src/__tests__/headless-browser-interactions.test.ts +64 -20
  92. package/src/__tests__/heartbeat-service.test.ts +2 -59
  93. package/src/__tests__/helpers/mock-conversation.ts +21 -0
  94. package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
  95. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
  96. package/src/__tests__/host-cu-proxy.test.ts +46 -2
  97. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  98. package/src/__tests__/image-credentials.test.ts +49 -0
  99. package/src/__tests__/image-service-dispatcher.test.ts +45 -0
  100. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  101. package/src/__tests__/interrupt-on-send.test.ts +57 -43
  102. package/src/__tests__/interrupt-turn-note.test.ts +5 -6
  103. package/src/__tests__/llm-catalog-parity.test.ts +2 -2
  104. package/src/__tests__/media-generate-image.test.ts +49 -1
  105. package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
  106. package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
  107. package/src/__tests__/mode-session-history.test.ts +245 -0
  108. package/src/__tests__/mode-session-recovery.test.ts +222 -0
  109. package/src/__tests__/mode-session-wire.test.ts +141 -0
  110. package/src/__tests__/notification-guardian-path.test.ts +7 -0
  111. package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
  112. package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  115. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  116. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +5 -0
  118. package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
  119. package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
  120. package/src/__tests__/process-message-display-content.test.ts +56 -0
  121. package/src/__tests__/reaction-persistence.test.ts +5 -0
  122. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
  123. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
  124. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
  125. package/src/__tests__/secret-ingress-http.test.ts +2 -0
  126. package/src/__tests__/secure-keys.test.ts +43 -1
  127. package/src/__tests__/send-endpoint-busy.test.ts +163 -1
  128. package/src/__tests__/server-history-render.test.ts +91 -1
  129. package/src/__tests__/settings-routes.test.ts +50 -1
  130. package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
  132. package/src/__tests__/subagent-manager-notify.test.ts +1 -2
  133. package/src/__tests__/subagent-notify-parent.test.ts +146 -39
  134. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
  135. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
  136. package/src/__tests__/thread-backfill.test.ts +53 -0
  137. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  138. package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
  139. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  140. package/src/__tests__/twilio-routes.test.ts +1 -0
  141. package/src/__tests__/web-search-backend-failure.test.ts +1 -1
  142. package/src/activation/progress-store.test.ts +12 -7
  143. package/src/agent/loop.ts +99 -41
  144. package/src/api/events/assistant-turn-start.ts +3 -0
  145. package/src/api/events/generation-handoff.ts +2 -0
  146. package/src/api/events/message-complete.ts +2 -0
  147. package/src/api/events/notification-conversation-created.ts +14 -16
  148. package/src/api/events/notification-intent.ts +4 -2
  149. package/src/api/events/tool-result.ts +85 -0
  150. package/src/api/events/tool-use-start.ts +3 -0
  151. package/src/api/events/user-message-echo.ts +3 -0
  152. package/src/api/index.ts +31 -6
  153. package/src/api/mode-session.test.ts +131 -0
  154. package/src/api/mode-session.ts +148 -0
  155. package/src/api/remember-tool.ts +15 -0
  156. package/src/api/responses/conversation-message.ts +6 -0
  157. package/src/approvals/AGENTS.md +4 -2
  158. package/src/browser/__tests__/operations.test.ts +29 -0
  159. package/src/browser/operation-meta.ts +2 -1
  160. package/src/browser/operations.ts +29 -0
  161. package/src/browser/virtual-desktop-target.ts +2 -8
  162. package/src/bundler/bundle-signer.ts +1 -3
  163. package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
  164. package/src/calls/__tests__/call-controller.test.ts +94 -0
  165. package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
  166. package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
  167. package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
  168. package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
  169. package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
  170. package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
  171. package/src/calls/barge-in-guard.ts +5 -0
  172. package/src/calls/call-controller.ts +196 -1
  173. package/src/calls/call-domain.ts +4 -0
  174. package/src/calls/call-store.ts +87 -2
  175. package/src/calls/guardian-action-sweep.ts +22 -8
  176. package/src/calls/media-stream-audio-transcode.ts +1 -3
  177. package/src/calls/typesafe-noul.ts +156 -0
  178. package/src/calls/voice-control-protocol.ts +6 -0
  179. package/src/calls/voice-escalation-judge.ts +145 -0
  180. package/src/calls/voice-escalation-target.ts +10 -0
  181. package/src/calls/voice-leg-coordinator.ts +121 -3
  182. package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
  183. package/src/calls/voice-session-bridge.ts +256 -112
  184. package/src/calls/voice-triage-escalate.ts +7 -0
  185. package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
  186. package/src/cli/commands/__tests__/notifications.test.ts +35 -10
  187. package/src/cli/commands/browser.help.ts +4 -5
  188. package/src/cli/commands/browser.ts +0 -1
  189. package/src/cli/commands/clients.help.ts +46 -0
  190. package/src/cli/commands/clients.ts +116 -0
  191. package/src/cli/commands/credentials.help.ts +6 -1
  192. package/src/cli/commands/notifications.help.ts +16 -2
  193. package/src/cli/commands/notifications.ts +30 -8
  194. package/src/cli/commands/roadmap.help.ts +18 -21
  195. package/src/cli/commands/roadmap.ts +4 -21
  196. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
  197. package/src/cli/lib/bundled-marketplace.json +84 -34
  198. package/src/cli/lib/plugin-marketplace.ts +8 -0
  199. package/src/cli/lib/publish-plugin.ts +1 -2
  200. package/src/config/__tests__/default-provider.test.ts +3 -1
  201. package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
  202. package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
  203. package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
  204. package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
  205. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
  206. package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
  207. package/src/config/call-site-defaults.ts +9 -0
  208. package/src/config/feature-flag-registry.json +16 -0
  209. package/src/config/profile-text-generation.test.ts +2 -2
  210. package/src/config/schemas/__tests__/tools.test.ts +31 -0
  211. package/src/config/schemas/call-site-catalog.ts +14 -0
  212. package/src/config/schemas/channels.ts +0 -18
  213. package/src/config/schemas/llm.ts +3 -1
  214. package/src/config/schemas/mcp.ts +2 -1
  215. package/src/config/schemas/memory-retrospective.ts +11 -2
  216. package/src/config/schemas/services.ts +15 -6
  217. package/src/config/schemas/tools.ts +10 -0
  218. package/src/config/session-groups-gate.ts +21 -0
  219. package/src/config/skills.ts +2 -6
  220. package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
  221. package/src/conversations/message-consolidation.ts +102 -5
  222. package/src/credential-execution/ces-connect.test.ts +69 -0
  223. package/src/credential-execution/ces-connect.ts +115 -0
  224. package/src/credential-execution/ces-runtime.ts +55 -93
  225. package/src/credential-execution/executable-discovery.ts +1 -11
  226. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
  227. package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
  228. package/src/daemon/browser-mode-session.test.ts +402 -0
  229. package/src/daemon/browser-mode-session.ts +186 -0
  230. package/src/daemon/computer-use-mode-session.test.ts +210 -0
  231. package/src/daemon/computer-use-mode-session.ts +105 -0
  232. package/src/daemon/config-watcher.ts +10 -0
  233. package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
  234. package/src/daemon/conversation-agent-loop.ts +219 -25
  235. package/src/daemon/conversation-evictor.ts +5 -24
  236. package/src/daemon/conversation-history.ts +10 -0
  237. package/src/daemon/conversation-interrupt-repair.ts +4 -43
  238. package/src/daemon/conversation-interrupt.ts +7 -11
  239. package/src/daemon/conversation-messaging.ts +58 -5
  240. package/src/daemon/conversation-mode-session.test.ts +1667 -0
  241. package/src/daemon/conversation-mode-session.ts +1008 -0
  242. package/src/daemon/conversation-process.ts +5 -0
  243. package/src/daemon/conversation-runtime-assembly.ts +10 -20
  244. package/src/daemon/conversation-skill-tools.ts +1 -0
  245. package/src/daemon/conversation-store.ts +4 -4
  246. package/src/daemon/conversation-surface-snapshots.ts +135 -0
  247. package/src/daemon/conversation-surfaces.ts +113 -5
  248. package/src/daemon/conversation-tool-setup.ts +8 -3
  249. package/src/daemon/conversation.ts +143 -41
  250. package/src/daemon/handlers/config-model.ts +7 -5
  251. package/src/daemon/handlers/shared.ts +55 -16
  252. package/src/daemon/host-cu-proxy.ts +51 -22
  253. package/src/daemon/lifecycle.ts +30 -13
  254. package/src/daemon/message-protocol.ts +0 -1
  255. package/src/daemon/message-provenance.ts +28 -4
  256. package/src/daemon/mode-session-startup-recovery.ts +23 -0
  257. package/src/daemon/mode-session-tracking.ts +54 -0
  258. package/src/daemon/process-message.ts +13 -4
  259. package/src/daemon/virtual-desktop-context.ts +21 -0
  260. package/src/desktop/__tests__/fake-desktop.ts +1 -0
  261. package/src/desktop/desktop-accessibility-script.ts +88 -0
  262. package/src/desktop/desktop-accessibility.test.ts +160 -0
  263. package/src/desktop/desktop-accessibility.ts +169 -0
  264. package/src/desktop/desktop-automation-lease.test.ts +76 -68
  265. package/src/desktop/desktop-automation-lease.ts +41 -50
  266. package/src/desktop/desktop-browser-endpoint.ts +4 -0
  267. package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
  268. package/src/desktop/desktop-computer-use.test.ts +449 -0
  269. package/src/desktop/desktop-computer-use.ts +502 -0
  270. package/src/desktop/desktop-dependencies.ts +47 -293
  271. package/src/desktop/desktop-display.ts +2 -2
  272. package/src/desktop/desktop-panel-config.test.ts +13 -3
  273. package/src/desktop/desktop-panel-config.ts +109 -15
  274. package/src/desktop/desktop-session-manager.test.ts +69 -21
  275. package/src/desktop/desktop-session-manager.ts +76 -22
  276. package/src/desktop/desktop-stream-bridge.test.ts +11 -122
  277. package/src/desktop/desktop-stream-bridge.ts +6 -8
  278. package/src/desktop/desktop-window-theme.ts +5 -25
  279. package/src/desktop/virtual-desktop-feature.ts +52 -2
  280. package/src/desktop/virtual-desktop-platform.test.ts +5 -9
  281. package/src/documents/document-store.ts +23 -0
  282. package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
  283. package/src/heartbeat/heartbeat-service.ts +5 -33
  284. package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
  285. package/src/ipc/assistant-server.ts +2 -0
  286. package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
  287. package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
  288. package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
  289. package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
  290. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
  291. package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
  292. package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
  293. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
  294. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
  295. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
  296. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
  297. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
  298. package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
  299. package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
  300. package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
  301. package/src/live-voice/__tests__/session-controls.test.ts +23 -0
  302. package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
  303. package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
  304. package/src/live-voice/camera-mode-session.test.ts +265 -0
  305. package/src/live-voice/camera-mode-session.ts +277 -0
  306. package/src/live-voice/continuation-judge.ts +123 -0
  307. package/src/live-voice/live-activity-reporter.ts +115 -6
  308. package/src/live-voice/live-voice-manager.ts +36 -1
  309. package/src/live-voice/live-voice-photo.ts +88 -7
  310. package/src/live-voice/live-voice-session-manager.ts +15 -0
  311. package/src/live-voice/live-voice-session.ts +1443 -747
  312. package/src/live-voice/live-voice-tts.ts +22 -4
  313. package/src/live-voice/microphone-idle-gate.ts +56 -0
  314. package/src/live-voice/protocol.ts +171 -4
  315. package/src/live-voice/session-controls.ts +11 -3
  316. package/src/live-voice/task-outcomes.ts +131 -0
  317. package/src/live-voice/voice-input-diagnostics.ts +117 -0
  318. package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
  319. package/src/mcp/__tests__/tool-caps.test.ts +28 -1
  320. package/src/mcp/manager.ts +3 -0
  321. package/src/mcp/tool-caps.ts +12 -4
  322. package/src/media/__tests__/image-models.test.ts +58 -0
  323. package/src/media/image-credentials.ts +5 -0
  324. package/src/media/image-models.ts +52 -0
  325. package/src/media/image-service.ts +8 -0
  326. package/src/media/openrouter-image-service.test.ts +133 -0
  327. package/src/media/openrouter-image-service.ts +153 -0
  328. package/src/media/types.ts +1 -1
  329. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
  330. package/src/messaging/providers/discord/send.ts +4 -17
  331. package/src/messaging/providers/discord/transport.ts +2 -2
  332. package/src/messaging/providers/outlook/client.ts +16 -22
  333. package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
  334. package/src/messaging/providers/telegram-bot/send.ts +10 -7
  335. package/src/messaging/providers/telegram-bot/transport.ts +3 -10
  336. package/src/messaging/providers/whatsapp/send.test.ts +169 -4
  337. package/src/monitoring/__tests__/control.test.ts +17 -5
  338. package/src/monitoring/control.ts +15 -5
  339. package/src/notifications/AGENTS.md +1 -1
  340. package/src/notifications/README.md +83 -99
  341. package/src/notifications/__tests__/broadcaster.test.ts +1 -1
  342. package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
  343. package/src/notifications/__tests__/copy-composer.test.ts +1 -1
  344. package/src/notifications/__tests__/decision-engine.test.ts +79 -2
  345. package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
  346. package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
  347. package/src/notifications/__tests__/notification-utils.test.ts +18 -0
  348. package/src/notifications/adapters/macos.ts +37 -46
  349. package/src/notifications/broadcaster.ts +5 -6
  350. package/src/notifications/channel-allowlist.ts +44 -0
  351. package/src/notifications/copy-composer.ts +0 -23
  352. package/src/notifications/decision-engine.ts +24 -14
  353. package/src/notifications/deterministic-checks.ts +1 -1
  354. package/src/notifications/emit-signal.ts +123 -37
  355. package/src/notifications/home-feed-side-effect.ts +9 -7
  356. package/src/notifications/notification-utils.ts +19 -0
  357. package/src/notifications/signal.ts +4 -19
  358. package/src/notifications/types.ts +15 -0
  359. package/src/oauth/connection-resolver.ts +1 -2
  360. package/src/oauth/oauth-store.ts +1 -3
  361. package/src/oauth/provider-serializer.ts +7 -0
  362. package/src/oauth/seed-providers.ts +30 -0
  363. package/src/onboarding/onboarding-events-store.ts +99 -12
  364. package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
  365. package/src/persistence/client-connection-events-store.ts +377 -0
  366. package/src/persistence/conversation-crud.ts +55 -6
  367. package/src/persistence/conversation-mode-sessions.test.ts +424 -0
  368. package/src/persistence/conversation-mode-sessions.ts +460 -0
  369. package/src/persistence/conversation-queries.ts +6 -19
  370. package/src/persistence/llm-usage-store.ts +3 -9
  371. package/src/persistence/message-metadata.ts +25 -0
  372. package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
  373. package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
  374. package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
  375. package/src/persistence/schema/conversations.ts +67 -0
  376. package/src/persistence/schema/infrastructure.ts +29 -0
  377. package/src/persistence/steps.ts +17 -0
  378. package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
  379. package/src/plugin-api/effective-context-window.ts +33 -0
  380. package/src/plugin-api/index.ts +24 -5
  381. package/src/plugin-api/plugin-name-env.ts +125 -0
  382. package/src/plugin-api/resolve-credential.ts +79 -20
  383. package/src/plugin-api/vision-support.test.ts +22 -0
  384. package/src/plugin-api/vision-support.ts +17 -5
  385. package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
  386. package/src/plugins/active-plugin-env.ts +51 -0
  387. package/src/plugins/defaults/index.ts +24 -0
  388. package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
  389. package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
  390. package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
  391. package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
  392. package/src/plugins/defaults/memory/context-search/format.ts +51 -7
  393. package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
  394. package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
  395. package/src/plugins/defaults/memory/context-search/search.ts +8 -14
  396. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
  397. package/src/plugins/defaults/memory/context-search/types.ts +1 -11
  398. package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
  399. package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
  400. package/src/plugins/defaults/memory/graph/tools.ts +35 -64
  401. package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
  402. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
  403. package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
  404. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
  405. package/src/plugins/defaults/memory/tools.test.ts +124 -9
  406. package/src/plugins/defaults/memory/tools.ts +38 -11
  407. package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
  408. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
  409. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
  410. package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
  411. package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
  412. package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
  413. package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
  414. package/src/plugins/defaults/memory/worker-control.ts +15 -5
  415. package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
  416. package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
  417. package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
  418. package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
  419. package/src/plugins/defaults/task-progress/package.json +14 -0
  420. package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
  421. package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
  422. package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
  423. package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
  424. package/src/providers/anthropic/client.ts +10 -1
  425. package/src/providers/anthropic-gateway-shared.ts +6 -0
  426. package/src/providers/fetch-provider-catalog.ts +11 -0
  427. package/src/providers/gemini/client.ts +25 -8
  428. package/src/providers/inference/adapter-factory.ts +1 -1
  429. package/src/providers/jev/client.test.ts +10 -0
  430. package/src/providers/jev/client.ts +20 -4
  431. package/src/providers/model-catalog.ts +4 -3
  432. package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
  433. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
  434. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
  435. package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
  436. package/src/providers/openai/chat-completions-provider.ts +100 -29
  437. package/src/providers/openai/google-thought-signature.ts +0 -6
  438. package/src/providers/openai/responses-provider.ts +23 -1
  439. package/src/providers/ratelimit.ts +1 -3
  440. package/src/providers/search-provider-catalog.ts +24 -0
  441. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
  442. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
  443. package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
  444. package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
  445. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
  446. package/src/providers/types.ts +5 -0
  447. package/src/routes/control.ts +7 -5
  448. package/src/runtime/AGENTS.md +4 -2
  449. package/src/runtime/actor-trust-resolver.ts +3 -3
  450. package/src/runtime/agent-wake.ts +1 -1
  451. package/src/runtime/approval-message-composer.ts +3 -69
  452. package/src/runtime/assistant-event-hub.ts +73 -75
  453. package/src/runtime/assistant-event-publish-options.ts +7 -0
  454. package/src/runtime/assistant-event-targeting.ts +79 -0
  455. package/src/runtime/assistant-stream-state.ts +13 -100
  456. package/src/runtime/background-job-runner.ts +5 -5
  457. package/src/runtime/channel-retry-sweep.ts +1 -0
  458. package/src/runtime/guardian-action-message-composer.ts +8 -161
  459. package/src/runtime/guardian-reply-router.ts +28 -32
  460. package/src/runtime/http-types.ts +6 -0
  461. package/src/runtime/message-composer-types.ts +3 -61
  462. package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
  463. package/src/runtime/migrations/vbundle-builder.ts +24 -1
  464. package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
  465. package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
  466. package/src/runtime/migrations/vbundle-importer.ts +4 -1
  467. package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
  468. package/src/runtime/migrations/vbundle-validator.ts +1 -0
  469. package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
  470. package/src/runtime/pending-interactions.ts +29 -0
  471. package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
  472. package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
  473. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
  474. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
  475. package/src/runtime/routes/browser-context.ts +31 -11
  476. package/src/runtime/routes/browser-routes.ts +43 -4
  477. package/src/runtime/routes/btw-routes.ts +9 -4
  478. package/src/runtime/routes/client-routes.ts +108 -0
  479. package/src/runtime/routes/conversation-management-routes.ts +4 -1
  480. package/src/runtime/routes/conversation-routes.ts +113 -27
  481. package/src/runtime/routes/credential-prompt-routes.ts +4 -3
  482. package/src/runtime/routes/credential-routes.ts +1 -2
  483. package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
  484. package/src/runtime/routes/desktop-setup-routes.ts +4 -6
  485. package/src/runtime/routes/documents-routes.ts +51 -0
  486. package/src/runtime/routes/email-routes.ts +3 -7
  487. package/src/runtime/routes/events-routes.ts +75 -54
  488. package/src/runtime/routes/identity-routes.ts +1 -5
  489. package/src/runtime/routes/image-generation-routes.ts +8 -14
  490. package/src/runtime/routes/inbound-message-handler.ts +35 -38
  491. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
  492. package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
  493. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
  494. package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
  495. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
  496. package/src/runtime/routes/llm-context-normalization.ts +1 -7
  497. package/src/runtime/routes/migration-routes.ts +73 -2
  498. package/src/runtime/routes/notification-routes.ts +10 -0
  499. package/src/runtime/routes/oauth-providers.ts +7 -0
  500. package/src/runtime/routes/plugins-routes.ts +6 -0
  501. package/src/runtime/routes/roadmap-routes.ts +47 -8
  502. package/src/runtime/routes/secret-routes.ts +1 -1
  503. package/src/runtime/routes/settings-routes.ts +10 -4
  504. package/src/runtime/routes/watcher-routes.ts +1 -3
  505. package/src/runtime/trust-class.test.ts +7 -6
  506. package/src/runtime/trust-class.ts +9 -17
  507. package/src/schedule/__tests__/worker-control.test.ts +36 -5
  508. package/src/schedule/worker-control.ts +15 -5
  509. package/src/security/secure-keys.ts +53 -103
  510. package/src/skills/clawhub.ts +3 -8
  511. package/src/skills/managed-store.ts +1 -5
  512. package/src/stt/types.ts +4 -0
  513. package/src/subagent/notify.ts +54 -34
  514. package/src/subagent/parent-notification.ts +6 -0
  515. package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
  516. package/src/telemetry/phone-call-funnel.ts +144 -0
  517. package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
  518. package/src/tools/ask-question/ask-question-tool.ts +6 -4
  519. package/src/tools/browser/browser-execution.ts +22 -59
  520. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
  521. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
  522. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
  523. package/src/tools/client-os.ts +15 -0
  524. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
  525. package/src/tools/computer-use/target.ts +42 -0
  526. package/src/tools/document/document-tool.ts +13 -18
  527. package/src/tools/execution-target.ts +14 -16
  528. package/src/tools/host-terminal/host-shell.ts +4 -1
  529. package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
  530. package/src/tools/network/__tests__/web-search.test.ts +203 -0
  531. package/src/tools/network/web-fetch.ts +274 -7
  532. package/src/tools/network/web-search.ts +468 -6
  533. package/src/tools/permission-checker.ts +3 -3
  534. package/src/tools/policy-context.ts +3 -1
  535. package/src/tools/skills/load.ts +2 -1
  536. package/src/tools/skills/sandbox-runner.ts +11 -2
  537. package/src/tools/skills/skill-script-runner.ts +7 -0
  538. package/src/tools/skills/skill-tool-factory.ts +15 -1
  539. package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
  540. package/src/tools/terminal/safe-env.ts +4 -0
  541. package/src/tools/terminal/shell.ts +2 -0
  542. package/src/tools/tool-approval-handler.ts +5 -1
  543. package/src/tools/tool-types.ts +3 -3
  544. package/src/tools/types.ts +8 -2
  545. package/src/util/__tests__/worker-process-command.test.ts +5 -4
  546. package/src/util/abort-reasons.ts +18 -26
  547. package/src/util/canonicalize-identity.test.ts +21 -0
  548. package/src/util/canonicalize-identity.ts +16 -0
  549. package/src/util/errors.ts +13 -3
  550. package/src/util/provider-error-patterns.ts +3 -0
  551. package/src/util/worker-process.ts +77 -35
  552. package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
  553. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
  554. package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
  555. package/src/workspace/migrations/registry.ts +2 -0
  556. package/src/__tests__/computer-use-window-schema.test.ts +0 -24
  557. package/src/api/events/heartbeat-alert.ts +0 -20
  558. package/src/daemon/message-types/web-activity.ts +0 -65
  559. package/src/desktop/desktop-dependencies.test.ts +0 -131
  560. package/src/tools/computer-use/definitions.ts +0 -607
@@ -0,0 +1,460 @@
1
+ import { and, asc, eq, inArray, sql } from "drizzle-orm";
2
+
3
+ import {
4
+ type ModeSessionMode,
5
+ modeSessionRowStartsDisplay,
6
+ type ModeSessionStatus,
7
+ type ModeSessionSummary,
8
+ ModeSessionSummarySchema,
9
+ } from "../api/mode-session.js";
10
+ import { type DrizzleDb, getDb } from "./db-connection.js";
11
+ import {
12
+ readMessageSentAt,
13
+ readModeSessionMetadata,
14
+ } from "./message-metadata.js";
15
+ import { conversationModeSessions, messages } from "./schema/index.js";
16
+
17
+ export type ModeSessionWriteFailureReason =
18
+ | "already_exists"
19
+ | "not_found"
20
+ | "stale_revision"
21
+ | "terminal";
22
+
23
+ export type ModeSessionWriteResult =
24
+ | { ok: true; session: ModeSessionSummary }
25
+ | {
26
+ ok: false;
27
+ reason: ModeSessionWriteFailureReason;
28
+ session?: ModeSessionSummary;
29
+ };
30
+
31
+ export interface ModeSessionStoreOptions {
32
+ db?: DrizzleDb;
33
+ }
34
+
35
+ type ModeSessionReader = Pick<DrizzleDb, "select">;
36
+
37
+ interface ModeSessionMutationOptions extends ModeSessionStoreOptions {
38
+ acceptTerminal?: (
39
+ current: typeof conversationModeSessions.$inferSelect,
40
+ ) => boolean;
41
+ }
42
+
43
+ export interface BeginModeSessionInput {
44
+ id: string;
45
+ conversationId: string;
46
+ mode: ModeSessionMode;
47
+ sourceStartedAt: number;
48
+ }
49
+
50
+ export interface RevisionedModeSessionInput {
51
+ id: string;
52
+ conversationId: string;
53
+ expectedRevision: number;
54
+ }
55
+
56
+ export interface UpdateModeSessionActivityInput extends RevisionedModeSessionInput {
57
+ lastActivityAt: number;
58
+ lastOwnedMessageId?: string | null;
59
+ }
60
+
61
+ export interface UpdateModeSessionBoundariesInput extends RevisionedModeSessionInput {
62
+ firstIncluded: { at: number; messageId: string } | null;
63
+ lastActivityAt: number;
64
+ lastOwnedMessageId: string | null;
65
+ }
66
+
67
+ interface FinalizeModeSessionInputBase extends RevisionedModeSessionInput {
68
+ endReason: string;
69
+ lastActivityAt?: number;
70
+ lastOwnedMessageId?: string | null;
71
+ }
72
+
73
+ export type FinalizeModeSessionInput = FinalizeModeSessionInputBase &
74
+ (
75
+ | { status: Extract<ModeSessionStatus, "completed">; endedAt: number }
76
+ | {
77
+ status: Extract<ModeSessionStatus, "interrupted">;
78
+ endedAt: number | null;
79
+ }
80
+ );
81
+
82
+ function toSummary(
83
+ row: typeof conversationModeSessions.$inferSelect,
84
+ ): ModeSessionSummary {
85
+ return ModeSessionSummarySchema.parse(row);
86
+ }
87
+
88
+ function getSession(
89
+ database: ModeSessionReader,
90
+ conversationId: string,
91
+ id: string,
92
+ ): typeof conversationModeSessions.$inferSelect | undefined {
93
+ return database
94
+ .select()
95
+ .from(conversationModeSessions)
96
+ .where(
97
+ and(
98
+ eq(conversationModeSessions.conversationId, conversationId),
99
+ eq(conversationModeSessions.id, id),
100
+ ),
101
+ )
102
+ .get();
103
+ }
104
+
105
+ function classifyRejectedWrite(
106
+ current: typeof conversationModeSessions.$inferSelect | undefined,
107
+ expectedRevision: number,
108
+ ): ModeSessionWriteResult {
109
+ if (!current) {
110
+ return { ok: false, reason: "not_found" };
111
+ }
112
+ const session = toSummary(current);
113
+ if (current.status !== "active") {
114
+ return { ok: false, reason: "terminal", session };
115
+ }
116
+ if (current.revision !== expectedRevision) {
117
+ return { ok: false, reason: "stale_revision", session };
118
+ }
119
+ throw new Error("Mode session write was rejected without a state conflict");
120
+ }
121
+
122
+ function mutateActiveSession(
123
+ input: RevisionedModeSessionInput,
124
+ buildValues: (
125
+ current: typeof conversationModeSessions.$inferSelect,
126
+ ) => Partial<typeof conversationModeSessions.$inferInsert>,
127
+ options?: ModeSessionMutationOptions,
128
+ ): ModeSessionWriteResult {
129
+ const database = options?.db ?? getDb();
130
+ return database.transaction(
131
+ (tx) => {
132
+ const current = getSession(tx, input.conversationId, input.id);
133
+ if (
134
+ !current ||
135
+ current.status !== "active" ||
136
+ current.revision !== input.expectedRevision
137
+ ) {
138
+ if (
139
+ current &&
140
+ current.status !== "active" &&
141
+ options?.acceptTerminal?.(current)
142
+ ) {
143
+ return { ok: true, session: toSummary(current) };
144
+ }
145
+ return classifyRejectedWrite(current, input.expectedRevision);
146
+ }
147
+
148
+ tx.update(conversationModeSessions)
149
+ .set({
150
+ ...buildValues(current),
151
+ revision: sql`${conversationModeSessions.revision} + 1`,
152
+ })
153
+ .where(
154
+ and(
155
+ eq(conversationModeSessions.conversationId, input.conversationId),
156
+ eq(conversationModeSessions.id, input.id),
157
+ eq(conversationModeSessions.status, "active"),
158
+ eq(conversationModeSessions.revision, input.expectedRevision),
159
+ ),
160
+ )
161
+ .run();
162
+
163
+ const updated = getSession(tx, input.conversationId, input.id);
164
+ if (!updated || updated.revision !== input.expectedRevision + 1) {
165
+ return classifyRejectedWrite(updated, input.expectedRevision);
166
+ }
167
+ return { ok: true, session: toSummary(updated) };
168
+ },
169
+ { behavior: "immediate" },
170
+ );
171
+ }
172
+
173
+ export function beginConversationModeSession(
174
+ input: BeginModeSessionInput,
175
+ options?: ModeSessionStoreOptions,
176
+ ): ModeSessionWriteResult {
177
+ const database = options?.db ?? getDb();
178
+ return database.transaction(
179
+ (tx) => {
180
+ const existing = tx
181
+ .select()
182
+ .from(conversationModeSessions)
183
+ .where(eq(conversationModeSessions.id, input.id))
184
+ .get();
185
+ if (existing) {
186
+ return {
187
+ ok: false,
188
+ reason: "already_exists",
189
+ ...(existing.conversationId === input.conversationId
190
+ ? { session: toSummary(existing) }
191
+ : {}),
192
+ };
193
+ }
194
+
195
+ tx.insert(conversationModeSessions)
196
+ .values({
197
+ ...input,
198
+ status: "active",
199
+ firstIncludedAt: null,
200
+ firstIncludedMessageId: null,
201
+ lastActivityAt: input.sourceStartedAt,
202
+ lastOwnedMessageId: null,
203
+ endedAt: null,
204
+ endReason: null,
205
+ revision: 1,
206
+ })
207
+ .run();
208
+ const created = getSession(tx, input.conversationId, input.id);
209
+ if (!created) {
210
+ throw new Error("Mode session insert did not create a readable row");
211
+ }
212
+ return { ok: true, session: toSummary(created) };
213
+ },
214
+ { behavior: "immediate" },
215
+ );
216
+ }
217
+
218
+ export function updateConversationModeSessionActivity(
219
+ input: UpdateModeSessionActivityInput,
220
+ options?: ModeSessionStoreOptions,
221
+ ): ModeSessionWriteResult {
222
+ return mutateActiveSession(
223
+ input,
224
+ (current) => ({
225
+ lastActivityAt: Math.max(current.lastActivityAt, input.lastActivityAt),
226
+ ...(input.lastOwnedMessageId !== undefined
227
+ ? { lastOwnedMessageId: input.lastOwnedMessageId }
228
+ : {}),
229
+ }),
230
+ options,
231
+ );
232
+ }
233
+
234
+ export function updateConversationModeSessionBoundaries(
235
+ input: UpdateModeSessionBoundariesInput,
236
+ options?: ModeSessionStoreOptions,
237
+ ): ModeSessionWriteResult {
238
+ return mutateActiveSession(
239
+ input,
240
+ (current) => ({
241
+ firstIncludedAt: input.firstIncluded?.at ?? null,
242
+ firstIncludedMessageId: input.firstIncluded?.messageId ?? null,
243
+ lastActivityAt: Math.max(current.lastActivityAt, input.lastActivityAt),
244
+ lastOwnedMessageId: input.lastOwnedMessageId,
245
+ }),
246
+ options,
247
+ );
248
+ }
249
+
250
+ export function advanceConversationModeSessionRevision(
251
+ input: RevisionedModeSessionInput,
252
+ options?: ModeSessionStoreOptions,
253
+ ): ModeSessionWriteResult {
254
+ return mutateActiveSession(input, () => ({}), options);
255
+ }
256
+
257
+ export function finalizeConversationModeSession(
258
+ input: FinalizeModeSessionInput,
259
+ options?: ModeSessionStoreOptions,
260
+ ): ModeSessionWriteResult {
261
+ return mutateActiveSession(
262
+ input,
263
+ (current) => {
264
+ const lastActivityAt = Math.max(
265
+ current.lastActivityAt,
266
+ input.lastActivityAt ?? current.lastActivityAt,
267
+ );
268
+ if (input.endedAt !== null && input.endedAt < lastActivityAt) {
269
+ throw new Error("Mode session end cannot precede its last activity");
270
+ }
271
+ return {
272
+ status: input.status,
273
+ endedAt: input.endedAt,
274
+ endReason: input.endReason,
275
+ lastActivityAt,
276
+ ...(input.lastOwnedMessageId !== undefined
277
+ ? { lastOwnedMessageId: input.lastOwnedMessageId }
278
+ : {}),
279
+ };
280
+ },
281
+ {
282
+ ...options,
283
+ acceptTerminal: (current) =>
284
+ current.status === input.status &&
285
+ current.endedAt === input.endedAt &&
286
+ current.endReason === input.endReason &&
287
+ (input.lastActivityAt === undefined ||
288
+ input.lastActivityAt <= current.lastActivityAt) &&
289
+ (input.lastOwnedMessageId === undefined ||
290
+ input.lastOwnedMessageId === current.lastOwnedMessageId),
291
+ },
292
+ );
293
+ }
294
+
295
+ export function getConversationModeSession(
296
+ conversationId: string,
297
+ id: string,
298
+ options?: ModeSessionStoreOptions,
299
+ ): ModeSessionSummary | null {
300
+ const row = getSession(options?.db ?? getDb(), conversationId, id);
301
+ return row ? toSummary(row) : null;
302
+ }
303
+
304
+ export function listConversationModeSessionsByIds(
305
+ conversationId: string,
306
+ ids: readonly string[],
307
+ options?: ModeSessionStoreOptions,
308
+ ): ModeSessionSummary[] {
309
+ if (ids.length === 0) {
310
+ return [];
311
+ }
312
+ const rows = (options?.db ?? getDb())
313
+ .select()
314
+ .from(conversationModeSessions)
315
+ .where(
316
+ and(
317
+ eq(conversationModeSessions.conversationId, conversationId),
318
+ inArray(conversationModeSessions.id, [...new Set(ids)]),
319
+ ),
320
+ )
321
+ .all();
322
+ return rows.map(toSummary);
323
+ }
324
+
325
+ export function recoverActiveConversationModeSessions(
326
+ options?: ModeSessionStoreOptions,
327
+ ): number {
328
+ const result = (options?.db ?? getDb())
329
+ .update(conversationModeSessions)
330
+ .set({
331
+ status: "interrupted",
332
+ endedAt: null,
333
+ endReason: "assistant_restarted",
334
+ revision: sql`${conversationModeSessions.revision} + 1`,
335
+ })
336
+ .where(eq(conversationModeSessions.status, "active"))
337
+ .run() as unknown as { changes: number };
338
+ return result.changes;
339
+ }
340
+
341
+ /** Recompute replaceable row boundaries after destructive transcript edits. */
342
+ export function repairConversationModeSessionBoundaries(
343
+ conversationId: string,
344
+ options?: ModeSessionStoreOptions,
345
+ ): number {
346
+ const database = options?.db ?? getDb();
347
+ return database.transaction(
348
+ (tx) => {
349
+ const sessions = tx
350
+ .select()
351
+ .from(conversationModeSessions)
352
+ .where(eq(conversationModeSessions.conversationId, conversationId))
353
+ .all();
354
+ if (sessions.length === 0) {
355
+ return 0;
356
+ }
357
+ const sessionsById = new Map(
358
+ sessions.map((session) => [session.id, session]),
359
+ );
360
+ const boundaries = new Map<
361
+ string,
362
+ {
363
+ firstAt: number | null;
364
+ firstMessageId: string | null;
365
+ lastAt: number;
366
+ lastMessageId: string;
367
+ }
368
+ >();
369
+ for (const message of tx
370
+ .select({
371
+ id: messages.id,
372
+ role: messages.role,
373
+ createdAt: messages.createdAt,
374
+ metadata: messages.metadata,
375
+ })
376
+ .from(messages)
377
+ .where(eq(messages.conversationId, conversationId))
378
+ .orderBy(asc(messages.createdAt), asc(messages.id))
379
+ .all()) {
380
+ const owner = readModeSessionMetadata(message.metadata);
381
+ if (!owner) {
382
+ continue;
383
+ }
384
+ const session = sessionsById.get(owner.id);
385
+ if (!session || session.mode !== owner.mode) {
386
+ continue;
387
+ }
388
+ const at = readMessageSentAt(message.metadata) ?? message.createdAt;
389
+ const startsDisplayBoundary = modeSessionRowStartsDisplay(
390
+ session.mode,
391
+ message.role === "assistant",
392
+ );
393
+ const current = boundaries.get(owner.id);
394
+ if (!current) {
395
+ boundaries.set(owner.id, {
396
+ firstAt: startsDisplayBoundary ? at : null,
397
+ firstMessageId: startsDisplayBoundary ? message.id : null,
398
+ lastAt: at,
399
+ lastMessageId: message.id,
400
+ });
401
+ } else {
402
+ if (
403
+ startsDisplayBoundary &&
404
+ (current.firstAt === null || at < current.firstAt)
405
+ ) {
406
+ current.firstAt = at;
407
+ current.firstMessageId = message.id;
408
+ }
409
+ current.lastAt = Math.max(current.lastAt, at);
410
+ current.lastMessageId = message.id;
411
+ }
412
+ }
413
+
414
+ let repaired = 0;
415
+ for (const session of sessions) {
416
+ const boundary = boundaries.get(session.id);
417
+ const firstIncludedAt = boundary?.firstAt ?? null;
418
+ const firstIncludedMessageId = boundary?.firstMessageId ?? null;
419
+ const lastOwnedMessageId = boundary?.lastMessageId ?? null;
420
+ const observedLastActivityAt =
421
+ boundary?.lastAt ?? session.lastActivityAt;
422
+ const lastActivityAt =
423
+ session.endedAt === null
424
+ ? Math.max(session.lastActivityAt, observedLastActivityAt)
425
+ : Math.min(
426
+ session.endedAt,
427
+ Math.max(session.lastActivityAt, observedLastActivityAt),
428
+ );
429
+ if (
430
+ session.firstIncludedAt === firstIncludedAt &&
431
+ session.firstIncludedMessageId === firstIncludedMessageId &&
432
+ session.lastOwnedMessageId === lastOwnedMessageId &&
433
+ session.lastActivityAt === lastActivityAt
434
+ ) {
435
+ continue;
436
+ }
437
+ const result = tx
438
+ .update(conversationModeSessions)
439
+ .set({
440
+ firstIncludedAt,
441
+ firstIncludedMessageId,
442
+ lastOwnedMessageId,
443
+ lastActivityAt,
444
+ revision: sql`${conversationModeSessions.revision} + 1`,
445
+ })
446
+ .where(
447
+ and(
448
+ eq(conversationModeSessions.conversationId, conversationId),
449
+ eq(conversationModeSessions.id, session.id),
450
+ eq(conversationModeSessions.revision, session.revision),
451
+ ),
452
+ )
453
+ .run() as unknown as { changes: number };
454
+ repaired += result.changes;
455
+ }
456
+ return repaired;
457
+ },
458
+ { behavior: "immediate" },
459
+ );
460
+ }
@@ -955,19 +955,6 @@ function likeContainsPattern(query: string): string {
955
955
  .replace(/_/g, "\\_")}%`;
956
956
  }
957
957
 
958
- /**
959
- * Whether the sparse Qdrant `messages_lexical` index — the only source of
960
- * message-content matches — is a safe read source. Content matching is
961
- * unavailable (title matches only) until the one-time upgrade backfill has
962
- * fully drained: a partially populated collection would silently miss older
963
- * content (an empty result — not a throw). Indexing itself is unconditional
964
- * host infrastructure, so completion is the only gate; the recall read site
965
- * applies the same one via the shared {@link isLexicalBackfillComplete}.
966
- */
967
- function isMessageContentSearchAvailable(): boolean {
968
- return isLexicalBackfillComplete();
969
- }
970
-
971
958
  /**
972
959
  * Full-text search across message content.
973
960
  *
@@ -976,9 +963,9 @@ function isMessageContentSearchAvailable(): boolean {
976
963
  * merged with a `LIKE` match on conversation titles; matching conversations
977
964
  * return with their relevant messages, ordered by most recently updated.
978
965
  *
979
- * Content matching is index-only — there is no `messages.content` scan
966
+ * Content matching is index-only: there is no `messages.content` scan
980
967
  * fallback and no other content source. Only the title arm can match while
981
- * the index is not a safe read source ({@link isMessageContentSearchAvailable}),
968
+ * the index is not a safe read source ({@link isLexicalBackfillComplete}),
982
969
  * for a query that tokenizes to nothing under the shared tokenizer (non-ASCII
983
970
  * or single-char input like "你", "é", "C++"), or when the Qdrant lexical
984
971
  * lookup fails (logged). An unindexed or unreachable index yields fewer
@@ -1017,7 +1004,7 @@ export async function searchConversations(
1017
1004
  const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
1018
1005
 
1019
1006
  const hasTokens = hasLexicalTokens(trimmed);
1020
- const contentSearchAvailable = isMessageContentSearchAvailable();
1007
+ const contentSearchAvailable = isLexicalBackfillComplete();
1021
1008
 
1022
1009
  // LIKE pattern for title matching (message-content indexes don't cover titles).
1023
1010
  const titlePattern = likeContainsPattern(query);
@@ -1453,9 +1440,9 @@ function buildExcerptFromText(
1453
1440
  text.length,
1454
1441
  match.index + match.length + EXCERPT_WINDOW,
1455
1442
  );
1456
- const excerpt =
1443
+ return (
1457
1444
  (start > 0 ? "\u2026" : "") +
1458
1445
  text.slice(start, end).replace(/\s+/g, " ").trim() +
1459
- (end < text.length ? "\u2026" : "");
1460
- return excerpt;
1446
+ (end < text.length ? "\u2026" : "")
1447
+ );
1461
1448
  }
@@ -515,19 +515,13 @@ interface GroupRow {
515
515
 
516
516
  type UsageQueryParam = ScheduleAttributionSqlParam;
517
517
 
518
- function normalizeUsageAggregationFilter(
519
- filter?: UsageAggregationFilter,
520
- ): UsageAggregationFilter {
521
- return normalizeScheduleAttributionFilter(filter);
522
- }
523
-
524
518
  function buildUsageAggregationWhere(
525
519
  range: UsageTimeRange,
526
520
  filter?: UsageAggregationFilter,
527
521
  eventAlias?: string,
528
522
  now: number = Date.now(),
529
523
  ): { sql: string; params: UsageQueryParam[] } {
530
- const normalized = normalizeUsageAggregationFilter(filter);
524
+ const normalized = normalizeScheduleAttributionFilter(filter);
531
525
  const eventTable = eventAlias ?? "llm_usage_events";
532
526
  const createdAt = `${eventTable}.created_at`;
533
527
  const clauses = [`${createdAt} >= ? AND ${createdAt} <= ?`];
@@ -852,7 +846,7 @@ export function getUsageGroupBreakdown(
852
846
  // Runtime allowlist — defense-in-depth against SQL injection via type assertions.
853
847
  assertGroupByDimension(groupBy);
854
848
 
855
- const normalizedFilter = normalizeUsageAggregationFilter(filter);
849
+ const normalizedFilter = normalizeScheduleAttributionFilter(filter);
856
850
 
857
851
  // Conversation grouping requires a JOIN with conversations to resolve titles.
858
852
  if (groupBy === "conversation") {
@@ -984,7 +978,7 @@ export function getUsageGroupedSeries(
984
978
  throw new Error("Grouped usage series does not support conversation");
985
979
  }
986
980
 
987
- const normalizedFilter = normalizeUsageAggregationFilter(filter);
981
+ const normalizedFilter = normalizeScheduleAttributionFilter(filter);
988
982
  let rows: UsageGroupedBucketRow[];
989
983
 
990
984
  if (groupBy === "schedule") {
@@ -1,3 +1,4 @@
1
+ import { type ModeSession, parseModeSession } from "../api/mode-session.js";
1
2
  import { safeParseRecord } from "../util/json.js";
2
3
 
3
4
  /**
@@ -18,3 +19,27 @@ export function mergeMessageMetadata(
18
19
  ...updates,
19
20
  });
20
21
  }
22
+
23
+ export function readModeSessionMetadata(
24
+ metadata: string | null | undefined,
25
+ ): ModeSession | undefined {
26
+ if (!metadata) {
27
+ return undefined;
28
+ }
29
+ return parseModeSession(safeParseRecord(metadata).modeSession);
30
+ }
31
+
32
+ export function readMessageSentAt(
33
+ metadata: string | null | undefined,
34
+ ): number | undefined {
35
+ if (!metadata) {
36
+ return undefined;
37
+ }
38
+ const sentAt = safeParseRecord(metadata).sentAt;
39
+ return typeof sentAt === "number" &&
40
+ Number.isInteger(sentAt) &&
41
+ sentAt >= 0 &&
42
+ Number.isFinite(new Date(sentAt).getTime())
43
+ ? sentAt
44
+ : undefined;
45
+ }
@@ -0,0 +1,41 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ const TABLE = "conversation_mode_sessions";
5
+
6
+ export function migrateCreateConversationModeSessions(
7
+ database: DrizzleDb,
8
+ ): void {
9
+ const sqlite = getSqliteFrom(database);
10
+ sqlite.exec(/* sql */ `
11
+ CREATE TABLE IF NOT EXISTS ${TABLE} (
12
+ id TEXT PRIMARY KEY,
13
+ conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
14
+ mode TEXT NOT NULL CHECK (mode IN ('computer_use', 'browser', 'live_vision', 'ambient')),
15
+ status TEXT NOT NULL CHECK (status IN ('active', 'completed', 'interrupted')),
16
+ source_started_at INTEGER NOT NULL,
17
+ first_included_at INTEGER,
18
+ first_included_message_id TEXT,
19
+ last_activity_at INTEGER NOT NULL,
20
+ last_owned_message_id TEXT,
21
+ ended_at INTEGER,
22
+ end_reason TEXT,
23
+ revision INTEGER NOT NULL DEFAULT 1 CHECK (revision > 0),
24
+ CHECK ((first_included_at IS NULL) = (first_included_message_id IS NULL)),
25
+ CHECK (
26
+ (status = 'active' AND ended_at IS NULL AND end_reason IS NULL)
27
+ OR (status = 'completed' AND ended_at IS NOT NULL AND end_reason IS NOT NULL AND length(end_reason) > 0)
28
+ OR (status = 'interrupted' AND end_reason IS NOT NULL AND length(end_reason) > 0)
29
+ ),
30
+ CHECK (ended_at IS NULL OR ended_at >= last_activity_at),
31
+ CHECK (last_activity_at >= source_started_at),
32
+ CHECK (first_included_at IS NULL OR first_included_at <= last_activity_at)
33
+ );
34
+ CREATE INDEX IF NOT EXISTS idx_conversation_mode_sessions_conversation_status
35
+ ON ${TABLE} (conversation_id, status);
36
+ `);
37
+ }
38
+
39
+ export function downCreateConversationModeSessions(database: DrizzleDb): void {
40
+ getSqliteFrom(database).exec(`DROP TABLE IF EXISTS ${TABLE}`);
41
+ }
@@ -0,0 +1,40 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ const TABLE = "client_connection_events";
5
+
6
+ /**
7
+ * Create `client_connection_events`: the durable hub subscribe/dispose
8
+ * ledger used by `assistant clients history`.
9
+ *
10
+ * One row per client-subscriber lifecycle event. Reconnects shorter than
11
+ * the flap window are coalesced at read time, not at write time, so the
12
+ * raw event stream stays a complete audit.
13
+ *
14
+ * Idempotent via IF NOT EXISTS.
15
+ */
16
+ export function migrateCreateClientConnectionEvents(database: DrizzleDb): void {
17
+ const sqlite = getSqliteFrom(database);
18
+ sqlite.exec(
19
+ `CREATE TABLE IF NOT EXISTS ${TABLE} (
20
+ id TEXT PRIMARY KEY,
21
+ client_id TEXT NOT NULL,
22
+ interface_id TEXT NOT NULL,
23
+ connection_id TEXT NOT NULL,
24
+ reason TEXT NOT NULL,
25
+ occurred_at INTEGER NOT NULL,
26
+ actor_principal_id TEXT,
27
+ client_version TEXT,
28
+ sse_watchdog INTEGER,
29
+ machine_name TEXT
30
+ )`,
31
+ );
32
+ sqlite.exec(
33
+ `CREATE INDEX IF NOT EXISTS idx_client_connection_events_client_occurred
34
+ ON ${TABLE} (client_id, occurred_at)`,
35
+ );
36
+ sqlite.exec(
37
+ `CREATE INDEX IF NOT EXISTS idx_client_connection_events_occurred
38
+ ON ${TABLE} (occurred_at)`,
39
+ );
40
+ }