@vellumai/assistant 0.8.4 → 0.8.5

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 (438) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/browser-use-architecture-phase2.md +1 -1
  3. package/knip.json +2 -1
  4. package/openapi.yaml +809 -11
  5. package/package.json +1 -1
  6. package/src/__tests__/anthropic-provider.test.ts +34 -37
  7. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  8. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  9. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
  11. package/src/__tests__/btw-routes.test.ts +2 -3
  12. package/src/__tests__/call-controller.test.ts +0 -1
  13. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  14. package/src/__tests__/channel-guardian.test.ts +3 -3
  15. package/src/__tests__/checker.test.ts +6 -15
  16. package/src/__tests__/compaction-events.test.ts +1 -0
  17. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  18. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  19. package/src/__tests__/computer-use-tools.test.ts +2 -4
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  21. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  22. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
  24. package/src/__tests__/conversation-agent-loop.test.ts +163 -122
  25. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  26. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  27. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  29. package/src/__tests__/conversation-error.test.ts +31 -0
  30. package/src/__tests__/conversation-fork-crud.test.ts +178 -15
  31. package/src/__tests__/conversation-lifecycle.test.ts +52 -11
  32. package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
  33. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  34. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  36. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  37. package/src/__tests__/conversation-store.test.ts +1 -1
  38. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  40. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  42. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  43. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  44. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  45. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  46. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  47. package/src/__tests__/email-html-renderer.test.ts +12 -0
  48. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  49. package/src/__tests__/gemini-provider.test.ts +78 -0
  50. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  51. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  52. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  53. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  54. package/src/__tests__/heartbeat-service.test.ts +4 -0
  55. package/src/__tests__/host-shell-tool.test.ts +1 -1
  56. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  57. package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
  58. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  59. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  60. package/src/__tests__/llm-resolver.test.ts +77 -9
  61. package/src/__tests__/llm-usage-store.test.ts +66 -0
  62. package/src/__tests__/logger.test.ts +89 -0
  63. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  64. package/src/__tests__/media-generate-image.test.ts +31 -0
  65. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  66. package/src/__tests__/model-intents.test.ts +2 -4
  67. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  68. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  69. package/src/__tests__/openai-provider.test.ts +46 -0
  70. package/src/__tests__/openai-responses-provider.test.ts +114 -12
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
  72. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  73. package/src/__tests__/platform.test.ts +2 -2
  74. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  75. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  76. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  77. package/src/__tests__/plugin-types.test.ts +3 -2
  78. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  79. package/src/__tests__/pricing.test.ts +12 -0
  80. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  81. package/src/__tests__/registry.test.ts +2 -8
  82. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  83. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  84. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  85. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  87. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  88. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  89. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  90. package/src/__tests__/suggestion-routes.test.ts +1 -0
  91. package/src/__tests__/sync-message-contract.test.ts +59 -0
  92. package/src/__tests__/system-prompt.test.ts +145 -131
  93. package/src/__tests__/terminal-tools.test.ts +1 -1
  94. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  95. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  96. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  98. package/src/__tests__/tool-executor.test.ts +9 -62
  99. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  100. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  101. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  102. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  103. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  104. package/src/__tests__/usage-routes.test.ts +3 -0
  105. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  106. package/src/__tests__/workspace-git-service.test.ts +6 -5
  107. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  108. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  109. package/src/acp/prepare-agent-env.ts +78 -0
  110. package/src/acp/session-manager.ts +1 -1
  111. package/src/agent/loop.ts +8 -0
  112. package/src/api/README.md +5 -0
  113. package/src/api/index.ts +4 -0
  114. package/src/api/package.json +10 -0
  115. package/src/background-wake/background-wake-routes.test.ts +233 -0
  116. package/src/background-wake/runtime-registry.ts +24 -0
  117. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  118. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  119. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  120. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  121. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  122. package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
  123. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  124. package/src/cli/commands/browser.ts +247 -0
  125. package/src/cli/commands/domain.ts +91 -41
  126. package/src/cli/commands/inference.ts +93 -40
  127. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  128. package/src/cli/commands/memory-v2.ts +176 -1
  129. package/src/cli/commands/memory-v3-render.ts +344 -0
  130. package/src/cli/commands/memory-v3.ts +316 -0
  131. package/src/cli/program.ts +2 -0
  132. package/src/config/assistant-feature-flags.ts +21 -9
  133. package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
  134. package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
  135. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  136. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  137. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  138. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  139. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  140. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  141. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  142. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  143. package/src/config/bundled-tool-registry.ts +2 -0
  144. package/src/config/call-site-defaults.ts +7 -6
  145. package/src/config/feature-flag-registry.json +16 -0
  146. package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
  147. package/src/config/schemas/call-site-catalog.ts +21 -7
  148. package/src/config/schemas/llm.ts +12 -1
  149. package/src/config/schemas/memory-v2.ts +246 -0
  150. package/src/config/schemas/memory.ts +2 -1
  151. package/src/context/compactor.ts +52 -0
  152. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  153. package/src/conversations/message-consolidation.ts +404 -0
  154. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  155. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  156. package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
  157. package/src/daemon/conversation-agent-loop.ts +126 -76
  158. package/src/daemon/conversation-error.ts +31 -1
  159. package/src/daemon/conversation-lifecycle.ts +27 -22
  160. package/src/daemon/conversation-runtime-assembly.ts +10 -9
  161. package/src/daemon/conversation-tool-setup.ts +63 -3
  162. package/src/daemon/conversation-usage.ts +2 -0
  163. package/src/daemon/conversation.ts +14 -29
  164. package/src/daemon/disk-pressure-guard.ts +14 -2
  165. package/src/daemon/handlers/config-model.test.ts +1 -0
  166. package/src/daemon/handlers/conversations.ts +11 -3
  167. package/src/daemon/host-browser-proxy.ts +5 -5
  168. package/src/daemon/host-cu-proxy.ts +4 -4
  169. package/src/daemon/host-file-proxy.ts +4 -4
  170. package/src/daemon/host-proxy-base.ts +4 -4
  171. package/src/daemon/host-transfer-proxy.ts +10 -10
  172. package/src/daemon/lifecycle.ts +23 -20
  173. package/src/daemon/meet-manifest-loader.ts +1 -7
  174. package/src/daemon/message-types/conversations.ts +6 -9
  175. package/src/daemon/message-types/home.ts +1 -13
  176. package/src/daemon/message-types/messages.ts +6 -14
  177. package/src/daemon/message-types/sync.ts +14 -0
  178. package/src/daemon/shutdown-handlers.ts +24 -5
  179. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  180. package/src/daemon/tool-setup-types.ts +13 -0
  181. package/src/events/relationship-state-updated.ts +25 -0
  182. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  183. package/src/home/home-greeting.ts +0 -9
  184. package/src/home/suggested-prompts.ts +0 -9
  185. package/src/ipc/gateway-flag-listener.ts +123 -0
  186. package/src/ipc/skill-routes/registries.ts +8 -12
  187. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  188. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  189. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  190. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  191. package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
  192. package/src/memory/auto-analysis-enqueue.ts +5 -1
  193. package/src/memory/conversation-crud.ts +71 -70
  194. package/src/memory/conversation-starters-cadence.ts +3 -1
  195. package/src/memory/conversation-title-service.ts +19 -3
  196. package/src/memory/db-async-query.ts +214 -0
  197. package/src/memory/db-init.ts +10 -0
  198. package/src/memory/db-maintenance.ts +30 -21
  199. package/src/memory/graph/bootstrap.ts +8 -1
  200. package/src/memory/graph/capability-seed.ts +7 -3
  201. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  202. package/src/memory/graph/extraction.ts +1 -5
  203. package/src/memory/graph/graph-search.ts +7 -1
  204. package/src/memory/indexer.ts +28 -18
  205. package/src/memory/job-handlers/cleanup.ts +76 -18
  206. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  207. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  208. package/src/memory/jobs-store.ts +14 -0
  209. package/src/memory/jobs-worker.ts +55 -22
  210. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  211. package/src/memory/llm-request-log-source-local.ts +7 -0
  212. package/src/memory/llm-request-log-source.ts +9 -2
  213. package/src/memory/llm-request-log-store.ts +43 -1
  214. package/src/memory/llm-usage-store.ts +24 -0
  215. package/src/memory/memory-retrospective-enqueue.ts +8 -1
  216. package/src/memory/memory-retrospective-job.ts +5 -0
  217. package/src/memory/memory-v2-activation-log-store.ts +15 -6
  218. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  219. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  220. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  221. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  222. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  223. package/src/memory/migrations/index.ts +17 -0
  224. package/src/memory/migrations/registry.ts +33 -0
  225. package/src/memory/schema/conversations.ts +1 -1
  226. package/src/memory/schema/infrastructure.ts +21 -0
  227. package/src/memory/tool-usage-store.ts +36 -8
  228. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  229. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  230. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  231. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  232. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  233. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  234. package/src/memory/v2/__tests__/injection.test.ts +127 -98
  235. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  236. package/src/memory/v2/__tests__/router.test.ts +171 -3
  237. package/src/memory/v2/harness/compare.ts +57 -0
  238. package/src/memory/v2/harness/metrics.ts +124 -0
  239. package/src/memory/v2/harness/oracle.ts +145 -0
  240. package/src/memory/v2/harness/replay-input.ts +224 -0
  241. package/src/memory/v2/harness/retriever.ts +74 -0
  242. package/src/memory/v2/harness/router-retriever.ts +43 -0
  243. package/src/memory/v2/harness/runner.ts +106 -0
  244. package/src/memory/v2/harness/trace.ts +58 -0
  245. package/src/memory/v2/injection.ts +21 -15
  246. package/src/memory/v2/prompts/router.ts +26 -1
  247. package/src/memory/v2/qdrant.ts +14 -2
  248. package/src/memory/v2/router.ts +171 -18
  249. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  250. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  251. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  252. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  253. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  254. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  255. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  256. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  257. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  258. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  259. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  260. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  261. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  262. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  263. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  264. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  265. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  266. package/src/memory/v3/auto-edges.ts +223 -0
  267. package/src/memory/v3/coactivation-store.ts +124 -0
  268. package/src/memory/v3/consolidation-job.ts +323 -0
  269. package/src/memory/v3/edge-learning-job.ts +160 -0
  270. package/src/memory/v3/edges.ts +249 -0
  271. package/src/memory/v3/filter.ts +281 -0
  272. package/src/memory/v3/gate.ts +334 -0
  273. package/src/memory/v3/index-composition.ts +113 -0
  274. package/src/memory/v3/llm-capture.ts +46 -0
  275. package/src/memory/v3/loop.ts +382 -0
  276. package/src/memory/v3/maintenance.ts +144 -0
  277. package/src/memory/v3/prompt-context.ts +33 -0
  278. package/src/memory/v3/prompts/consolidation.ts +458 -0
  279. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  280. package/src/memory/v3/retriever.ts +33 -0
  281. package/src/memory/v3/scouts.ts +420 -0
  282. package/src/memory/v3/shadow-middleware.ts +305 -0
  283. package/src/memory/v3/traversal.ts +206 -0
  284. package/src/memory/v3/tree-index.ts +237 -0
  285. package/src/memory/v3/tree-store.ts +394 -0
  286. package/src/memory/v3/tree-walk.ts +351 -0
  287. package/src/memory/v3/types.ts +65 -0
  288. package/src/memory/v3/validate.ts +300 -0
  289. package/src/notifications/adapters/macos.ts +18 -1
  290. package/src/notifications/adapters/platform.ts +1 -1
  291. package/src/notifications/decision-engine.ts +1 -4
  292. package/src/notifications/emit-signal.ts +29 -49
  293. package/src/permissions/prompter.ts +3 -3
  294. package/src/permissions/question-prompter.ts +5 -2
  295. package/src/permissions/secret-prompter.ts +2 -2
  296. package/src/plugin-api/index.ts +4 -0
  297. package/src/plugin-api/types.ts +7 -33
  298. package/src/plugins/defaults/index.ts +6 -0
  299. package/src/plugins/defaults/injectors.ts +18 -11
  300. package/src/plugins/external-plugin-loader.ts +5 -68
  301. package/src/plugins/types.ts +11 -16
  302. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  303. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  304. package/src/prompts/persona-resolver.ts +36 -21
  305. package/src/prompts/sections.ts +39 -7
  306. package/src/prompts/system-prompt.ts +50 -185
  307. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  308. package/src/prompts/templates/system-sections.ts +230 -8
  309. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  310. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  311. package/src/providers/anthropic/client.ts +32 -66
  312. package/src/providers/call-site-routing.ts +14 -2
  313. package/src/providers/connection-model-compat.ts +38 -0
  314. package/src/providers/connection-resolution.ts +16 -2
  315. package/src/providers/gemini/client.ts +49 -6
  316. package/src/providers/inference/adapter-factory.ts +3 -0
  317. package/src/providers/minimax/client.ts +106 -0
  318. package/src/providers/model-catalog.ts +43 -0
  319. package/src/providers/model-intents.ts +1 -1
  320. package/src/providers/openai/chat-completions-provider.ts +6 -3
  321. package/src/providers/openai/codex-models.ts +18 -0
  322. package/src/providers/openai/responses-provider.ts +78 -21
  323. package/src/providers/provider-send-message.ts +7 -1
  324. package/src/providers/retry.ts +34 -3
  325. package/src/providers/thinking-config.ts +26 -1
  326. package/src/providers/usage-tracking.ts +2 -0
  327. package/src/runtime/AGENTS.md +2 -2
  328. package/src/runtime/agent-wake.ts +1 -0
  329. package/src/runtime/assistant-event-hub.ts +76 -6
  330. package/src/runtime/auth/route-policy.ts +36 -0
  331. package/src/runtime/btw-sidechain.ts +0 -6
  332. package/src/runtime/http-types.ts +0 -2
  333. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  334. package/src/runtime/pending-interactions.ts +0 -1
  335. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
  336. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
  337. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  338. package/src/runtime/routes/acp-routes.test.ts +255 -6
  339. package/src/runtime/routes/acp-routes.ts +8 -1
  340. package/src/runtime/routes/avatar-routes.ts +10 -10
  341. package/src/runtime/routes/background-wake-routes.ts +188 -0
  342. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  343. package/src/runtime/routes/btw-routes.ts +0 -6
  344. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  345. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  346. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  347. package/src/runtime/routes/conversation-query-routes.ts +142 -36
  348. package/src/runtime/routes/conversation-routes.ts +252 -410
  349. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  350. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  351. package/src/runtime/routes/domain-routes.ts +60 -10
  352. package/src/runtime/routes/email-routes.ts +5 -2
  353. package/src/runtime/routes/events-routes.ts +54 -10
  354. package/src/runtime/routes/group-routes.ts +24 -8
  355. package/src/runtime/routes/host-browser-routes.ts +10 -2
  356. package/src/runtime/routes/host-cu-routes.ts +2 -2
  357. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  358. package/src/runtime/routes/index.ts +8 -0
  359. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  360. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  361. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  362. package/src/runtime/routes/memory-item-routes.ts +8 -3
  363. package/src/runtime/routes/memory-v2-routes.ts +215 -5
  364. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  365. package/src/runtime/routes/migration-routes.ts +21 -24
  366. package/src/runtime/routes/plugins-routes.ts +337 -0
  367. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  368. package/src/runtime/routes/secret-routes.ts +25 -5
  369. package/src/runtime/routes/settings-routes.ts +12 -11
  370. package/src/runtime/routes/slack-channel-routes.ts +5 -4
  371. package/src/runtime/routes/workspace-routes.ts +25 -10
  372. package/src/runtime/sync/resource-sync-events.ts +106 -38
  373. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  374. package/src/runtime/sync/sync-publisher.ts +2 -1
  375. package/src/runtime/verification-outbound-actions.ts +73 -1
  376. package/src/telemetry/types.ts +12 -0
  377. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  378. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  379. package/src/tools/acp/spawn.test.ts +119 -0
  380. package/src/tools/acp/spawn.ts +15 -2
  381. package/src/tools/apps/definitions.ts +2 -8
  382. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  383. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  384. package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
  385. package/src/tools/browser/browser-execution.ts +16 -3
  386. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  387. package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
  388. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
  389. package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
  390. package/src/tools/browser/cdp-client/factory.ts +100 -17
  391. package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
  392. package/src/tools/browser/cdp-client/types.ts +65 -0
  393. package/src/tools/browser/pinned-tabs.ts +96 -40
  394. package/src/tools/computer-use/definitions.ts +22 -78
  395. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  396. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  397. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  398. package/src/tools/credentials/vault.ts +3 -9
  399. package/src/tools/document/document-tool.ts +59 -0
  400. package/src/tools/execution-target.ts +21 -23
  401. package/src/tools/executor.ts +6 -1
  402. package/src/tools/filesystem/edit.ts +3 -9
  403. package/src/tools/filesystem/list.ts +3 -9
  404. package/src/tools/filesystem/read.ts +3 -9
  405. package/src/tools/filesystem/write.ts +3 -9
  406. package/src/tools/host-filesystem/edit.ts +3 -9
  407. package/src/tools/host-filesystem/read.ts +3 -9
  408. package/src/tools/host-filesystem/transfer.ts +3 -9
  409. package/src/tools/host-filesystem/write.ts +3 -9
  410. package/src/tools/host-terminal/host-shell.ts +3 -9
  411. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  412. package/src/tools/memory/register.test.ts +1 -1
  413. package/src/tools/memory/register.ts +4 -9
  414. package/src/tools/network/web-fetch.ts +3 -9
  415. package/src/tools/network/web-search.ts +25 -32
  416. package/src/tools/registry.ts +7 -23
  417. package/src/tools/schema-transforms.ts +1 -1
  418. package/src/tools/skills/execute.ts +3 -9
  419. package/src/tools/skills/load.ts +3 -9
  420. package/src/tools/skills/skill-tool-factory.ts +1 -8
  421. package/src/tools/subagent/notify-parent.ts +3 -9
  422. package/src/tools/system/request-permission.ts +3 -9
  423. package/src/tools/terminal/shell.ts +3 -9
  424. package/src/tools/tool-defaults.ts +94 -0
  425. package/src/tools/types.ts +27 -98
  426. package/src/tools/ui-surface/definitions.ts +6 -22
  427. package/src/usage/pricing.ts +23 -0
  428. package/src/usage/types.ts +12 -0
  429. package/src/util/logger.ts +16 -7
  430. package/src/util/platform.ts +7 -2
  431. package/src/util/sqlite3-runtime.ts +65 -0
  432. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  433. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  434. package/src/workspace/migrations/registry.ts +2 -0
  435. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  436. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  437. package/src/daemon/query-complexity-router.ts +0 -75
  438. package/src/prompts/cache-boundary.ts +0 -8
@@ -21,7 +21,10 @@ import {
21
21
  isValidConversationStarterText,
22
22
  } from "../../memory/conversation-starter-validation.js";
23
23
  import { getDb } from "../../memory/db-connection.js";
24
- import { enqueueMemoryJob } from "../../memory/jobs-store.js";
24
+ import {
25
+ enqueueMemoryJob,
26
+ isMemoryEnabled,
27
+ } from "../../memory/jobs-store.js";
25
28
  import { conversationStarters, memoryJobs } from "../../memory/schema.js";
26
29
  import { NotFoundError } from "./errors.js";
27
30
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -203,7 +206,7 @@ function handleListConversationStarters({
203
206
  checkpointAhead ||
204
207
  (invalidItemCount > 0 && totalActive > 0 && !withinCooldown);
205
208
 
206
- if (shouldRefresh && !hasActiveJob) {
209
+ if (shouldRefresh && !hasActiveJob && isMemoryEnabled()) {
207
210
  enqueueMemoryJob("generate_conversation_starters", { scopeId });
208
211
  hasActiveJob = true;
209
212
  }
@@ -225,7 +228,7 @@ function handleListConversationStarters({
225
228
 
226
229
  const existing = hasActiveConversationStarterJob(db, scopeId);
227
230
 
228
- if (!existing) {
231
+ if (!existing && isMemoryEnabled()) {
229
232
  enqueueMemoryJob("generate_conversation_starters", { scopeId });
230
233
  }
231
234
 
@@ -11,7 +11,7 @@ import type { RouteDefinition } from "./types.js";
11
11
 
12
12
  const DiskPressureStatusSchema = z.object({
13
13
  enabled: z.boolean(),
14
- state: z.enum(["disabled", "ok", "critical", "unknown"]),
14
+ state: z.enum(["disabled", "ok", "warning", "critical", "unknown"]),
15
15
  locked: z.boolean(),
16
16
  acknowledged: z.boolean(),
17
17
  overrideActive: z.boolean(),
@@ -35,7 +35,10 @@ async function requireClient(): Promise<VellumPlatformClient> {
35
35
  // ── Handlers ──────────────────────────────────────────────────────────
36
36
 
37
37
  async function handleDomainRegister({ body = {} }: RouteHandlerArgs) {
38
- const { subdomain } = body as { subdomain?: string };
38
+ const { subdomain, email_username } = body as {
39
+ subdomain?: string;
40
+ email_username?: string;
41
+ };
39
42
  const client = await requireClient();
40
43
  const apexDomain = getApexDomain();
41
44
 
@@ -43,6 +46,9 @@ async function handleDomainRegister({ body = {} }: RouteHandlerArgs) {
43
46
  if (subdomain) {
44
47
  reqBody.subdomain = subdomain;
45
48
  }
49
+ if (email_username) {
50
+ reqBody.email_username = email_username;
51
+ }
46
52
 
47
53
  const response = await client.fetch(
48
54
  `/v1/assistants/${client.platformAssistantId}/domains/`,
@@ -58,12 +64,18 @@ async function handleDomainRegister({ body = {} }: RouteHandlerArgs) {
58
64
  string,
59
65
  unknown
60
66
  >;
67
+ const firstFieldError = ["subdomain", "email_username"].reduce<
68
+ string | undefined
69
+ >(
70
+ (found, field) =>
71
+ found ??
72
+ (Array.isArray(respBody[field])
73
+ ? (respBody[field][0] as string)
74
+ : undefined),
75
+ undefined,
76
+ );
61
77
  const detail =
62
- respBody.detail ??
63
- (Array.isArray(respBody.subdomain)
64
- ? respBody.subdomain[0]
65
- : undefined) ??
66
- `HTTP ${response.status}`;
78
+ respBody.detail ?? firstFieldError ?? `HTTP ${response.status}`;
67
79
  throw new BadRequestError(String(detail));
68
80
  }
69
81
 
@@ -71,10 +83,9 @@ async function handleDomainRegister({ body = {} }: RouteHandlerArgs) {
71
83
  id: string;
72
84
  subdomain?: string;
73
85
  domain?: string;
74
- status?: string;
75
- verified?: boolean;
76
86
  created_at?: string;
77
87
  created?: string;
88
+ email_error?: { detail: string; code: string };
78
89
  };
79
90
 
80
91
  // Persist the subdomain to config so getAssistantDomain() can use it
@@ -117,8 +128,6 @@ async function handleDomainStatus(_args: RouteHandlerArgs) {
117
128
  id: string;
118
129
  subdomain?: string;
119
130
  domain?: string;
120
- status?: string;
121
- verified?: boolean;
122
131
  created_at?: string;
123
132
  created?: string;
124
133
  }[];
@@ -145,6 +154,39 @@ async function handleDomainStatus(_args: RouteHandlerArgs) {
145
154
  return data;
146
155
  }
147
156
 
157
+ async function handleDomainVerificationStatus({
158
+ body = {},
159
+ }: RouteHandlerArgs) {
160
+ const { domain_id } = body as { domain_id?: string };
161
+ if (!domain_id) {
162
+ throw new BadRequestError("domain_id is required");
163
+ }
164
+ const client = await requireClient();
165
+
166
+ const response = await client.fetch(
167
+ `/v1/assistants/${client.platformAssistantId}/domains/${domain_id}/verification-status/`,
168
+ );
169
+
170
+ if (!response.ok) {
171
+ const respBody = (await response.json().catch(() => ({}))) as Record<
172
+ string,
173
+ unknown
174
+ >;
175
+ const detail = respBody.detail ?? `HTTP ${response.status}`;
176
+ throw new RouteError(
177
+ String(detail),
178
+ "VERIFICATION_STATUS_FAILED",
179
+ response.status,
180
+ );
181
+ }
182
+
183
+ return (await response.json()) as {
184
+ domain: string;
185
+ status: string;
186
+ message: string;
187
+ };
188
+ }
189
+
148
190
  // ── Route definitions ─────────────────────────────────────────────────
149
191
 
150
192
  export const ROUTES: RouteDefinition[] = [
@@ -164,4 +206,12 @@ export const ROUTES: RouteDefinition[] = [
164
206
  summary: "Show domain registration and health",
165
207
  tags: ["domain"],
166
208
  },
209
+ {
210
+ operationId: "domain_verification_status",
211
+ endpoint: "domain/verification-status",
212
+ method: "POST",
213
+ handler: handleDomainVerificationStatus,
214
+ summary: "Get live DNS verification status for a domain",
215
+ tags: ["domain"],
216
+ },
167
217
  ];
@@ -277,13 +277,16 @@ async function handleEmailSend({ body = {} }: RouteHandlerArgs) {
277
277
 
278
278
  const fromAddress = addresses[0].address;
279
279
 
280
+ // LLMs often produce literal "\n" escape sequences instead of real newlines.
281
+ const normalizedText = text?.replace(/\\n/g, "\n") ?? "";
282
+
280
283
  // Auto-generate HTML from text if not provided
281
- const resolvedHtml = html ?? markdownToEmailHtml(text);
284
+ const resolvedHtml = html ?? markdownToEmailHtml(normalizedText);
282
285
 
283
286
  const payload: Record<string, unknown> = {
284
287
  to,
285
288
  from_address: fromAddress,
286
- text,
289
+ text: normalizedText,
287
290
  };
288
291
  if (subject) payload.subject = subject;
289
292
  if (resolvedHtml) payload.html = resolvedHtml;
@@ -26,6 +26,7 @@ import { z } from "zod";
26
26
  import type { HostProxyCapability } from "../../channels/types.js";
27
27
  import { parseInterfaceId, supportsHostProxy } from "../../channels/types.js";
28
28
  import { emitContactChange } from "../../contacts/contact-events.js";
29
+ import { getConversation } from "../../memory/conversation-crud.js";
29
30
  import { getOrCreateConversation } from "../../memory/conversation-key-store.js";
30
31
  import { getLogger } from "../../util/logger.js";
31
32
  import { formatSseFrame, formatSseHeartbeat } from "../assistant-event.js";
@@ -39,7 +40,11 @@ import {
39
40
  assistantEventHub,
40
41
  } from "../assistant-event-hub.js";
41
42
  import { resolveActorPrincipalIdForLocalGuardian } from "../local-actor-identity.js";
42
- import { BadRequestError, ServiceUnavailableError } from "./errors.js";
43
+ import {
44
+ BadRequestError,
45
+ NotFoundError,
46
+ ServiceUnavailableError,
47
+ } from "./errors.js";
43
48
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
44
49
 
45
50
  const log = getLogger("events-routes");
@@ -220,9 +225,17 @@ const defaultSseShedReporter: SseShedReporter = (reason, inst) => {
220
225
  * Stream assistant events as Server-Sent Events.
221
226
  *
222
227
  * Query params:
223
- * conversationKey -- optional; when provided, scopes the stream to one
224
- * conversation. When omitted, the stream delivers events
225
- * from ALL conversations for this assistant.
228
+ * conversationId -- optional; assistant-minted internal conversation id.
229
+ * When provided, the stream is scoped to that one
230
+ * conversation; the daemon 404s if no such conversation
231
+ * exists (clients must obtain the id from a prior
232
+ * response).
233
+ * conversationKey -- optional; external key (non-vellum channels) or the
234
+ * web idempotency key. Resolved via the conversation
235
+ * keys table; materializes a row on first use.
236
+ * Ignored when `conversationId` is also provided.
237
+ * When both are omitted, the stream delivers events from ALL
238
+ * conversations for this assistant.
226
239
  *
227
240
  * Headers (optional):
228
241
  * X-Vellum-Client-Id -- stable per-install UUID identifying this client.
@@ -248,8 +261,18 @@ export function handleSubscribeAssistantEvents(
248
261
  ): ReadableStream<Uint8Array> {
249
262
  const { queryParams, headers, abortSignal } = args;
250
263
 
251
- const conversationKey = queryParams?.conversationKey;
252
- if ("conversationKey" in (queryParams ?? {}) && !conversationKey?.trim()) {
264
+ const rawConversationId = queryParams?.conversationId;
265
+ const rawConversationKey = queryParams?.conversationKey;
266
+ if (
267
+ "conversationId" in (queryParams ?? {}) &&
268
+ !rawConversationId?.trim()
269
+ ) {
270
+ throw new BadRequestError("conversationId must not be empty");
271
+ }
272
+ if (
273
+ "conversationKey" in (queryParams ?? {}) &&
274
+ !rawConversationKey?.trim()
275
+ ) {
253
276
  throw new BadRequestError("conversationKey must not be empty");
254
277
  }
255
278
 
@@ -293,10 +316,25 @@ export function handleSubscribeAssistantEvents(
293
316
  "host_browser",
294
317
  ];
295
318
 
319
+ // Resolve the scope. `conversationId` (when supplied) is the
320
+ // assistant-minted internal id — looked up directly; 404 if absent.
321
+ // Otherwise fall through to `conversationKey`, which is treated as an
322
+ // external key and resolved via the conversation_keys table
323
+ // (materialized on first use, preserving the existing subscribe-time
324
+ // create behavior for the web idempotency flow).
296
325
  const filter: AssistantEventFilter = {};
297
- if (conversationKey) {
298
- const mapping = getOrCreateConversation(conversationKey);
326
+ let scopeConversationKey: string | null = null;
327
+ if (rawConversationId) {
328
+ const existing = getConversation(rawConversationId);
329
+ if (!existing) {
330
+ throw new NotFoundError(`Conversation ${rawConversationId} not found`);
331
+ }
332
+ filter.conversationId = existing.id;
333
+ scopeConversationKey = existing.id;
334
+ } else if (rawConversationKey) {
335
+ const mapping = getOrCreateConversation(rawConversationKey);
299
336
  filter.conversationId = mapping.conversationId;
337
+ scopeConversationKey = rawConversationKey;
300
338
  }
301
339
 
302
340
  const encoder = new TextEncoder();
@@ -316,7 +354,7 @@ export function handleSubscribeAssistantEvents(
316
354
  heartbeatsSent: 0,
317
355
  clientId,
318
356
  interfaceId,
319
- conversationKey: conversationKey ?? null,
357
+ conversationKey: scopeConversationKey,
320
358
  };
321
359
 
322
360
  ensureEventLoopDelayMonitorStarted();
@@ -470,9 +508,15 @@ export const ROUTES: RouteDefinition[] = [
470
508
  description: "Stream assistant events as Server-Sent Events (SSE).",
471
509
  tags: ["events"],
472
510
  queryParams: [
511
+ {
512
+ name: "conversationId",
513
+ description:
514
+ "Scope to a single conversation by its assistant-minted internal id. 404s if no such conversation exists.",
515
+ },
473
516
  {
474
517
  name: "conversationKey",
475
- description: "Scope to a single conversation",
518
+ description:
519
+ "Scope to a single conversation by an external key (non-vellum channels) or the web idempotency key. Materializes a row on first use. Ignored when conversationId is also provided.",
476
520
  },
477
521
  ],
478
522
  responseHeaders: {
@@ -41,14 +41,17 @@ function handleListGroups() {
41
41
  return { groups: groups.map(serializeGroup) };
42
42
  }
43
43
 
44
- function handleCreateGroup({ body = {} }: RouteHandlerArgs) {
44
+ function handleCreateGroup({ body = {}, headers }: RouteHandlerArgs) {
45
45
  const name = body.name;
46
46
  if (!name || typeof name !== "string") {
47
47
  throw new BadRequestError("Missing or invalid name");
48
48
  }
49
49
  try {
50
50
  const group = createGroup(name);
51
- publishConversationListChanged("created");
51
+ publishConversationListChanged(
52
+ "created",
53
+ headers?.["x-vellum-client-id"]?.trim() || undefined,
54
+ );
52
55
  return serializeGroup(group);
53
56
  } catch (err) {
54
57
  if (
@@ -63,7 +66,11 @@ function handleCreateGroup({ body = {} }: RouteHandlerArgs) {
63
66
  }
64
67
  }
65
68
 
66
- function handleUpdateGroup({ pathParams = {}, body = {} }: RouteHandlerArgs) {
69
+ function handleUpdateGroup({
70
+ pathParams = {},
71
+ body = {},
72
+ headers,
73
+ }: RouteHandlerArgs) {
67
74
  const groupId = pathParams.groupId;
68
75
  const existing = getGroup(groupId);
69
76
  if (!existing) {
@@ -92,11 +99,14 @@ function handleUpdateGroup({ pathParams = {}, body = {} }: RouteHandlerArgs) {
92
99
  if (!updated) {
93
100
  throw new NotFoundError("Group not found");
94
101
  }
95
- publishConversationListChanged("reordered");
102
+ publishConversationListChanged(
103
+ "reordered",
104
+ headers?.["x-vellum-client-id"]?.trim() || undefined,
105
+ );
96
106
  return serializeGroup(updated);
97
107
  }
98
108
 
99
- function handleDeleteGroup({ pathParams = {} }: RouteHandlerArgs) {
109
+ function handleDeleteGroup({ pathParams = {}, headers }: RouteHandlerArgs) {
100
110
  const groupId = pathParams.groupId;
101
111
  const existing = getGroup(groupId);
102
112
  if (!existing) {
@@ -106,10 +116,13 @@ function handleDeleteGroup({ pathParams = {} }: RouteHandlerArgs) {
106
116
  throw new ForbiddenError("System groups cannot be deleted");
107
117
  }
108
118
  deleteGroup(groupId);
109
- publishConversationListChanged("reordered");
119
+ publishConversationListChanged(
120
+ "reordered",
121
+ headers?.["x-vellum-client-id"]?.trim() || undefined,
122
+ );
110
123
  }
111
124
 
112
- function handleReorderGroups({ body = {} }: RouteHandlerArgs) {
125
+ function handleReorderGroups({ body = {}, headers }: RouteHandlerArgs) {
113
126
  const updates = body.updates as
114
127
  | Array<{ groupId: string; sortPosition: number }>
115
128
  | undefined;
@@ -135,7 +148,10 @@ function handleReorderGroups({ body = {} }: RouteHandlerArgs) {
135
148
  }
136
149
  }
137
150
  reorderGroups(updates);
138
- publishConversationListChanged("reordered");
151
+ publishConversationListChanged(
152
+ "reordered",
153
+ headers?.["x-vellum-client-id"]?.trim() || undefined,
154
+ );
139
155
  return { ok: true };
140
156
  }
141
157
 
@@ -223,8 +223,9 @@ export type HostBrowserSessionInvalidatedResolution =
223
223
  export function resolveHostBrowserSessionInvalidated(frame: {
224
224
  targetId?: unknown;
225
225
  reason?: unknown;
226
+ clientId?: unknown;
226
227
  }): HostBrowserSessionInvalidatedResolution {
227
- const { targetId, reason } = frame;
228
+ const { targetId, reason, clientId } = frame;
228
229
 
229
230
  if (targetId !== undefined && typeof targetId !== "string") {
230
231
  return {
@@ -235,6 +236,9 @@ export function resolveHostBrowserSessionInvalidated(frame: {
235
236
  };
236
237
  }
237
238
 
239
+ const resolvedClientId =
240
+ typeof clientId === "string" && clientId.length > 0 ? clientId : undefined;
241
+
238
242
  if (typeof targetId === "string" && targetId.length > 0) {
239
243
  markTargetInvalidated(
240
244
  targetId,
@@ -245,7 +249,7 @@ export function resolveHostBrowserSessionInvalidated(frame: {
245
249
  // Without this, a user closing the pinned tab manually would see
246
250
  // their next browser command fail with a `cdp_session_not_found`
247
251
  // until the daemon process restarts.
248
- clearPinnedTabByTabId(targetId);
252
+ clearPinnedTabByTabId(targetId, resolvedClientId);
249
253
  }
250
254
 
251
255
  return { ok: true };
@@ -387,6 +391,10 @@ export const ROUTES: RouteDefinition[] = [
387
391
  .optional()
388
392
  .describe("CDP target that was detached"),
389
393
  reason: z.string().optional().describe("Detach reason"),
394
+ clientId: z
395
+ .string()
396
+ .optional()
397
+ .describe("Extension client ID that reported the invalidation"),
390
398
  }),
391
399
  responseBody: z.object({
392
400
  accepted: z.boolean(),
@@ -107,12 +107,12 @@ function handleHostCuResult({ body, headers }: RouteHandlerArgs) {
107
107
 
108
108
  const conversation = findConversation(peeked.conversationId);
109
109
  if (!conversation) {
110
- pendingInteractions.resolve(requestId);
110
+ pendingInteractions.resolve(requestId, "cancelled");
111
111
  throw new NotFoundError("Conversation not found for host CU result");
112
112
  }
113
113
 
114
114
  if (!conversation.hostCuProxy) {
115
- pendingInteractions.resolve(requestId);
115
+ pendingInteractions.resolve(requestId, "cancelled");
116
116
  throw new NotFoundError("No host CU proxy for conversation");
117
117
  }
118
118
 
@@ -342,6 +342,49 @@ export async function enforceIngressAcl(
342
342
  }
343
343
  }
344
344
 
345
+ // Email: initiate a verification challenge via the guardian notification
346
+ // pipeline. Unlike Slack, we cannot DM the requester directly — the
347
+ // verification code is delivered to the guardian, who decides whether
348
+ // to share it with the email sender out-of-band.
349
+ if (sourceChannel === "email" && (canonicalSenderId ?? rawSenderId)) {
350
+ const emailVerifyResult = initiateEmailVerificationChallenge({
351
+ sourceChannel,
352
+ senderUserId: (canonicalSenderId ?? rawSenderId)!,
353
+ });
354
+
355
+ if (emailVerifyResult.initiated) {
356
+ try {
357
+ notifyGuardianOfAccessRequest({
358
+ canonicalAssistantId,
359
+ sourceChannel,
360
+ conversationExternalId,
361
+ actorExternalId: canonicalSenderId ?? rawSenderId,
362
+ actorDisplayName,
363
+ actorUsername,
364
+ messagePreview: truncate(
365
+ trimmedContent,
366
+ MESSAGE_PREVIEW_MAX_LENGTH,
367
+ ),
368
+ });
369
+ } catch (err) {
370
+ log.error(
371
+ { err, sourceChannel, conversationExternalId },
372
+ "Failed to notify guardian of access request (email verification)",
373
+ );
374
+ }
375
+
376
+ return {
377
+ resolvedMember: null,
378
+ earlyResponse: ({
379
+ accepted: true,
380
+ denied: true,
381
+ reason: "verification_challenge_sent",
382
+ verificationSessionId: emailVerifyResult.sessionId,
383
+ }),
384
+ };
385
+ }
386
+ }
387
+
345
388
  // Notify the guardian about the access request so they can approve/deny.
346
389
  // Uses the shared helper which handles guardian binding lookup,
347
390
  // deduplication, canonical request creation, and notification emission.
@@ -1048,10 +1091,10 @@ async function handleInviteCodeIntercept(params: {
1048
1091
  }
1049
1092
 
1050
1093
  // ---------------------------------------------------------------------------
1051
- // Slack verification challenge
1094
+ // Channel verification challenges
1052
1095
  // ---------------------------------------------------------------------------
1053
1096
 
1054
- interface SlackVerificationResult {
1097
+ interface VerificationChallengeResult {
1055
1098
  initiated: boolean;
1056
1099
  sessionId?: string;
1057
1100
  }
@@ -1066,7 +1109,7 @@ interface SlackVerificationResult {
1066
1109
  function initiateSlackVerificationChallenge(params: {
1067
1110
  sourceChannel: ChannelId;
1068
1111
  senderUserId: string;
1069
- }): SlackVerificationResult {
1112
+ }): VerificationChallengeResult {
1070
1113
  const { sourceChannel, senderUserId } = params;
1071
1114
 
1072
1115
  // Skip if there is already a pending challenge or active session for
@@ -1121,3 +1164,53 @@ function initiateSlackVerificationChallenge(params: {
1121
1164
  return { initiated: false };
1122
1165
  }
1123
1166
  }
1167
+
1168
+ // ---------------------------------------------------------------------------
1169
+ // Email verification challenge
1170
+ // ---------------------------------------------------------------------------
1171
+
1172
+ function initiateEmailVerificationChallenge(params: {
1173
+ sourceChannel: ChannelId;
1174
+ senderUserId: string;
1175
+ }): VerificationChallengeResult {
1176
+ const { sourceChannel, senderUserId } = params;
1177
+
1178
+ const existingChallenge = getPendingSession(sourceChannel);
1179
+ const existingSession = findActiveSession(sourceChannel);
1180
+ const senderHasPending =
1181
+ (existingChallenge &&
1182
+ existingChallenge.expectedExternalUserId === senderUserId) ||
1183
+ (existingSession &&
1184
+ existingSession.expectedExternalUserId === senderUserId);
1185
+ if (senderHasPending) {
1186
+ log.debug(
1187
+ { sourceChannel, senderUserId },
1188
+ "Email verification: skipping — existing challenge/session for this sender",
1189
+ );
1190
+ return { initiated: false };
1191
+ }
1192
+
1193
+ try {
1194
+ const session = createOutboundSession({
1195
+ channel: sourceChannel,
1196
+ expectedExternalUserId: senderUserId,
1197
+ expectedChatId: senderUserId,
1198
+ identityBindingStatus: "bound",
1199
+ destinationAddress: senderUserId,
1200
+ verificationPurpose: "trusted_contact",
1201
+ });
1202
+
1203
+ log.info(
1204
+ { sourceChannel, senderUserId, sessionId: session.sessionId },
1205
+ "Email verification challenge initiated for unknown contact",
1206
+ );
1207
+
1208
+ return { initiated: true, sessionId: session.sessionId };
1209
+ } catch (err) {
1210
+ log.error(
1211
+ { err, sourceChannel, senderUserId },
1212
+ "Failed to initiate email verification challenge",
1213
+ );
1214
+ return { initiated: false };
1215
+ }
1216
+ }
@@ -18,10 +18,12 @@ import { ROUTES as AUDIT_ROUTES } from "./audit-routes.js";
18
18
  import { ROUTES as AUTH_ROUTES } from "./auth-routes.js";
19
19
  import { ROUTES as AVATAR_ROUTES } from "./avatar-routes.js";
20
20
  import { ROUTES as BACKGROUND_TOOL_ROUTES } from "./background-tool-routes.js";
21
+ import { ROUTES as BACKGROUND_WAKE_ROUTES } from "./background-wake-routes.js";
21
22
  import { ROUTES as BACKUP_ROUTES } from "./backup-routes.js";
22
23
  import { ROUTES as BOOKMARK_ROUTES } from "./bookmark-routes.js";
23
24
  import { ROUTES as BRAIN_GRAPH_ROUTES } from "./brain-graph-routes.js";
24
25
  import { ROUTES as BROWSER_ROUTES } from "./browser-routes.js";
26
+ import { ROUTES as BROWSER_TABS_ROUTES } from "./browser-tabs-routes.js";
25
27
  import { ROUTES as BTW_ROUTES } from "./btw-routes.js";
26
28
  import { ROUTES as CACHE_ROUTES } from "./cache-routes.js";
27
29
  import { ROUTES as CALL_ROUTES } from "./call-routes.js";
@@ -88,6 +90,7 @@ import { ROUTES as LOG_EXPORT_ROUTES } from "./log-export-routes.js";
88
90
  import { ROUTES as MCP_AUTH_ROUTES } from "./mcp-auth-routes.js";
89
91
  import { ROUTES as MEMORY_ITEM_ROUTES } from "./memory-item-routes.js";
90
92
  import { ROUTES as MEMORY_V2_ROUTES } from "./memory-v2-routes.js";
93
+ import { ROUTES as MEMORY_V3_ROUTES } from "./memory-v3-routes.js";
91
94
  import { ROUTES as MIGRATION_ROLLBACK_ROUTES } from "./migration-rollback-routes.js";
92
95
  import { ROUTES as MIGRATION_ROUTES } from "./migration-routes.js";
93
96
  import { ROUTES as NOTIFICATION_ROUTES } from "./notification-routes.js";
@@ -98,6 +101,7 @@ import { ROUTES as OAUTH_LIFECYCLE_ROUTES } from "./oauth-lifecycle-routes.js";
98
101
  import { ROUTES as OAUTH_PROVIDERS_ROUTES } from "./oauth-providers.js";
99
102
  import { ROUTES as PLATFORM_ROUTES } from "./platform-routes.js";
100
103
  import { ROUTES as PLAYGROUND_ROUTES } from "./playground/index.js";
104
+ import { ROUTES as PLUGINS_ROUTES } from "./plugins-routes.js";
101
105
  import { ROUTES as PROFILER_ROUTES } from "./profiler-routes.js";
102
106
  import { ROUTES as PS_ROUTES } from "./ps-routes.js";
103
107
  import { ROUTES as PUBLISH_ROUTES } from "./publish-routes.js";
@@ -145,6 +149,7 @@ export const ROUTES: RouteDefinition[] = [
145
149
  ...AUDIT_ROUTES,
146
150
  ...AUTH_ROUTES,
147
151
  ...AVATAR_ROUTES,
152
+ ...BACKGROUND_WAKE_ROUTES,
148
153
  ...BACKGROUND_TOOL_ROUTES,
149
154
  ...BACKUP_ROUTES,
150
155
  ...BOOKMARK_ROUTES,
@@ -156,6 +161,7 @@ export const ROUTES: RouteDefinition[] = [
156
161
  ...CHANNEL_AVAILABILITY_ROUTES,
157
162
  ...CHANNEL_READINESS_ROUTES,
158
163
  ...BROWSER_ROUTES,
164
+ ...BROWSER_TABS_ROUTES,
159
165
  ...BTW_ROUTES,
160
166
  ...BRAIN_GRAPH_ROUTES,
161
167
  ...CLIENT_ROUTES,
@@ -211,6 +217,7 @@ export const ROUTES: RouteDefinition[] = [
211
217
  ...LLM_CALL_SITES_ROUTES,
212
218
  ...MEMORY_ITEM_ROUTES,
213
219
  ...MEMORY_V2_ROUTES,
220
+ ...MEMORY_V3_ROUTES,
214
221
  ...MIGRATION_ROLLBACK_ROUTES,
215
222
  ...MIGRATION_ROUTES,
216
223
  ...NOTIFICATION_ROUTES,
@@ -220,6 +227,7 @@ export const ROUTES: RouteDefinition[] = [
220
227
  ...OAUTH_PROVIDERS_ROUTES,
221
228
  ...PLATFORM_ROUTES,
222
229
  ...PLAYGROUND_ROUTES,
230
+ ...PLUGINS_ROUTES,
223
231
  ...PROFILER_ROUTES,
224
232
  ...PS_ROUTES,
225
233
  ...PUBLISH_ROUTES,
@@ -67,11 +67,13 @@ export async function setInferenceProfileSession({
67
67
  profile,
68
68
  ttlSeconds,
69
69
  sessionId: callerSessionId,
70
+ originClientId,
70
71
  }: {
71
72
  conversationId: string;
72
73
  profile: string | null;
73
74
  ttlSeconds?: number | null;
74
75
  sessionId?: string;
76
+ originClientId?: string;
75
77
  }): Promise<InferenceProfileSessionResult> {
76
78
  const resolvedId = resolveConversationId(conversationId) ?? conversationId;
77
79
  const conversation = getConversation(resolvedId);
@@ -116,12 +118,15 @@ export async function setInferenceProfileSession({
116
118
  };
117
119
  }
118
120
  setConversationInferenceProfileSession(resolvedId, null, null, null);
119
- publishConversationInferenceProfileChanged({
120
- conversationId: resolvedId,
121
- profile: null,
122
- sessionId: null,
123
- expiresAt: null,
124
- });
121
+ publishConversationInferenceProfileChanged(
122
+ {
123
+ conversationId: resolvedId,
124
+ profile: null,
125
+ sessionId: null,
126
+ expiresAt: null,
127
+ },
128
+ originClientId,
129
+ );
125
130
  return {
126
131
  conversationId: resolvedId,
127
132
  profile: null,
@@ -184,12 +189,15 @@ export async function setInferenceProfileSession({
184
189
  newExpiresAt ?? null,
185
190
  );
186
191
 
187
- publishConversationInferenceProfileChanged({
188
- conversationId: resolvedId,
189
- profile,
190
- sessionId: newSessionId ?? null,
191
- expiresAt: newExpiresAt ?? null,
192
- });
192
+ publishConversationInferenceProfileChanged(
193
+ {
194
+ conversationId: resolvedId,
195
+ profile,
196
+ sessionId: newSessionId ?? null,
197
+ expiresAt: newExpiresAt ?? null,
198
+ },
199
+ originClientId,
200
+ );
193
201
 
194
202
  return {
195
203
  conversationId: resolvedId,
@@ -218,6 +226,7 @@ export async function setInferenceProfileSession({
218
226
  */
219
227
  export async function closeInferenceProfileSession(
220
228
  conversationId: string,
229
+ originClientId?: string,
221
230
  ): Promise<{
222
231
  conversationId: string;
223
232
  closed: { profile: string | null; sessionId: string | null } | null;
@@ -241,6 +250,7 @@ export async function closeInferenceProfileSession(
241
250
  const result = await setInferenceProfileSession({
242
251
  conversationId: resolvedId,
243
252
  profile: null,
253
+ originClientId,
244
254
  });
245
255
  return {
246
256
  conversationId: result.conversationId,