@vellumai/assistant 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docs/architecture/memory.md +1 -1
  4. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  5. package/openapi.yaml +184 -69
  6. package/package.json +41 -41
  7. package/scripts/generate-openapi.ts +1 -2
  8. package/src/__tests__/acp-session.test.ts +43 -0
  9. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  10. package/src/__tests__/app-executors.test.ts +1 -0
  11. package/src/__tests__/app-source-watcher.test.ts +37 -11
  12. package/src/__tests__/approval-routes-http.test.ts +178 -1
  13. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  14. package/src/__tests__/browser-manager.test.ts +40 -27
  15. package/src/__tests__/catalog-files.test.ts +862 -0
  16. package/src/__tests__/channel-approvals.test.ts +53 -0
  17. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +125 -48
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  21. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  22. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  24. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  25. package/src/__tests__/conversation-attachments.test.ts +80 -4
  26. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  27. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  28. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  29. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  30. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  31. package/src/__tests__/conversation-queue.test.ts +45 -2
  32. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  33. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  35. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  36. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  37. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  38. package/src/__tests__/conversation-store.test.ts +195 -0
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  40. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  41. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  42. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  43. package/src/__tests__/credential-vault.test.ts +152 -13
  44. package/src/__tests__/credentials-cli.test.ts +2 -2
  45. package/src/__tests__/date-context.test.ts +4 -4
  46. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  47. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  48. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  49. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  50. package/src/__tests__/gemini-provider.test.ts +2 -2
  51. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  52. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  53. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  54. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  55. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  56. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  57. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  58. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  59. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  60. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  61. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  62. package/src/__tests__/host-browser-routes.test.ts +198 -0
  63. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  64. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  65. package/src/__tests__/host-file-proxy.test.ts +185 -1
  66. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  67. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  68. package/src/__tests__/host-shell-tool.test.ts +1 -11
  69. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  70. package/src/__tests__/integration-status.test.ts +6 -7
  71. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  72. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  73. package/src/__tests__/mcp-health-check.test.ts +10 -3
  74. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  75. package/src/__tests__/migration-export-http.test.ts +61 -2
  76. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  77. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  78. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  80. package/src/__tests__/oauth-cli.test.ts +707 -60
  81. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  82. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  83. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  84. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  85. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  86. package/src/__tests__/oauth-store.test.ts +1386 -182
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  88. package/src/__tests__/onboarding-template-contract.test.ts +75 -57
  89. package/src/__tests__/openai-provider.test.ts +2 -2
  90. package/src/__tests__/outlook-categories.test.ts +1 -1
  91. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  92. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  93. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  94. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  95. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  96. package/src/__tests__/outlook-trash.test.ts +1 -1
  97. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  98. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  99. package/src/__tests__/permission-mode.test.ts +28 -56
  100. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  101. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  102. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  103. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  104. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  105. package/src/__tests__/schedule-routes.test.ts +162 -0
  106. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  107. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  108. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  109. package/src/__tests__/set-permission-mode.test.ts +13 -250
  110. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  111. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  112. package/src/__tests__/slack-channel-config.test.ts +12 -15
  113. package/src/__tests__/subagent-detail.test.ts +44 -2
  114. package/src/__tests__/subagent-disposal.test.ts +1 -0
  115. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  116. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  117. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  118. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  119. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  120. package/src/__tests__/subagent-tools.test.ts +1 -0
  121. package/src/__tests__/subagent-types.test.ts +1 -0
  122. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  123. package/src/__tests__/system-prompt.test.ts +72 -1
  124. package/src/__tests__/task-scheduler.test.ts +32 -6
  125. package/src/__tests__/telegram-config.test.ts +10 -13
  126. package/src/__tests__/terminal-tools.test.ts +9 -0
  127. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  128. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  129. package/src/__tests__/top-level-renderer.test.ts +73 -1
  130. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  131. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  132. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  133. package/src/acp/client-handler.ts +30 -4
  134. package/src/agent/loop.ts +12 -6
  135. package/src/approvals/guardian-request-resolvers.ts +21 -15
  136. package/src/browser-session/__tests__/manager.test.ts +297 -0
  137. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  138. package/src/browser-session/backends/extension.ts +26 -0
  139. package/src/browser-session/backends/local.ts +24 -0
  140. package/src/browser-session/events.ts +164 -0
  141. package/src/browser-session/index.ts +27 -0
  142. package/src/browser-session/manager.ts +159 -0
  143. package/src/browser-session/types.ts +28 -0
  144. package/src/channels/__tests__/types.test.ts +134 -0
  145. package/src/channels/types.ts +53 -3
  146. package/src/cli/commands/browser-relay.ts +339 -409
  147. package/src/cli/commands/credentials.ts +3 -3
  148. package/src/cli/commands/email.ts +18 -13
  149. package/src/cli/commands/mcp.ts +16 -4
  150. package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
  151. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  152. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  153. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  154. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  155. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  156. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  157. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  158. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  159. package/src/cli/commands/oauth/apps.ts +7 -4
  160. package/src/cli/commands/oauth/connect.ts +6 -3
  161. package/src/cli/commands/oauth/disconnect.ts +1 -1
  162. package/src/cli/commands/oauth/providers.ts +200 -36
  163. package/src/cli/commands/oauth/shared.ts +5 -5
  164. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  165. package/src/cli/commands/platform/index.ts +107 -10
  166. package/src/cli/commands/usage.ts +10 -9
  167. package/src/cli/lib/daemon-credential-client.ts +4 -0
  168. package/src/cli/program.ts +1 -1
  169. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  170. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  171. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  172. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  173. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  174. package/src/config/bundled-skills/document/SKILL.md +4 -0
  175. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  176. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  177. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  178. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  179. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  180. package/src/config/env-registry.ts +14 -0
  181. package/src/config/env.ts +21 -0
  182. package/src/config/feature-flag-registry.json +44 -5
  183. package/src/config/loader.ts +56 -1
  184. package/src/config/sanitize-for-transfer.ts +47 -0
  185. package/src/config/schema.ts +46 -5
  186. package/src/config/schemas/host-browser.ts +66 -0
  187. package/src/config/schemas/memory-lifecycle.ts +1 -1
  188. package/src/config/schemas/memory-retrieval.ts +103 -0
  189. package/src/config/schemas/security.ts +0 -6
  190. package/src/config/schemas/services.ts +8 -0
  191. package/src/config/types.ts +0 -1
  192. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  193. package/src/context/window-manager.ts +19 -1
  194. package/src/credential-execution/approval-bridge.ts +49 -15
  195. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  196. package/src/daemon/app-source-watcher.ts +35 -0
  197. package/src/daemon/context-overflow-approval.ts +5 -0
  198. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  199. package/src/daemon/conversation-agent-loop.ts +58 -24
  200. package/src/daemon/conversation-attachments.ts +40 -0
  201. package/src/daemon/conversation-process.ts +48 -1
  202. package/src/daemon/conversation-runtime-assembly.ts +118 -36
  203. package/src/daemon/conversation-surfaces.ts +37 -36
  204. package/src/daemon/conversation-tool-setup.ts +74 -8
  205. package/src/daemon/conversation-workspace.ts +12 -0
  206. package/src/daemon/conversation.ts +226 -8
  207. package/src/daemon/date-context.ts +10 -10
  208. package/src/daemon/first-greeting.ts +3 -2
  209. package/src/daemon/handlers/conversations.ts +9 -140
  210. package/src/daemon/handlers/shared.ts +58 -0
  211. package/src/daemon/handlers/skills.ts +232 -37
  212. package/src/daemon/host-bash-proxy.ts +48 -13
  213. package/src/daemon/host-browser-proxy.ts +191 -0
  214. package/src/daemon/host-cu-proxy.ts +36 -11
  215. package/src/daemon/host-file-proxy.ts +57 -9
  216. package/src/daemon/lifecycle.ts +65 -11
  217. package/src/daemon/message-protocol.ts +7 -0
  218. package/src/daemon/message-types/conversations.ts +55 -13
  219. package/src/daemon/message-types/host-browser.ts +100 -0
  220. package/src/daemon/message-types/messages.ts +5 -5
  221. package/src/daemon/message-types/skills.ts +10 -0
  222. package/src/daemon/message-types/subagents.ts +2 -0
  223. package/src/daemon/server.ts +92 -12
  224. package/src/daemon/tool-side-effects.ts +6 -0
  225. package/src/daemon/transport-hints.ts +5 -24
  226. package/src/inbound/platform-callback-registration.ts +18 -17
  227. package/src/mcp/client.ts +59 -24
  228. package/src/memory/app-store.ts +31 -1
  229. package/src/memory/conversation-crud.ts +23 -0
  230. package/src/memory/conversation-starters-cadence.ts +76 -0
  231. package/src/memory/conversation-title-service.ts +5 -2
  232. package/src/memory/db-init.ts +12 -0
  233. package/src/memory/embedding-backend.test.ts +75 -0
  234. package/src/memory/embedding-backend.ts +131 -5
  235. package/src/memory/embedding-gemini.test.ts +54 -0
  236. package/src/memory/embedding-gemini.ts +20 -9
  237. package/src/memory/embedding-local.ts +176 -17
  238. package/src/memory/graph/consolidation.ts +10 -23
  239. package/src/memory/graph/extraction-job.ts +15 -0
  240. package/src/memory/graph/retriever.ts +40 -22
  241. package/src/memory/graph/store.test.ts +7 -3
  242. package/src/memory/graph/store.ts +47 -12
  243. package/src/memory/llm-usage-store.ts +45 -4
  244. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  245. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  246. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  247. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  248. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  249. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  250. package/src/memory/migrations/index.ts +6 -0
  251. package/src/memory/migrations/registry.ts +8 -0
  252. package/src/memory/schema/conversations.ts +1 -0
  253. package/src/memory/schema/oauth.ts +18 -13
  254. package/src/oauth/AGENTS.md +76 -0
  255. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  256. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  257. package/src/oauth/byo-connection.test.ts +8 -8
  258. package/src/oauth/byo-connection.ts +7 -7
  259. package/src/oauth/connect-orchestrator.ts +23 -21
  260. package/src/oauth/connect-types.ts +3 -3
  261. package/src/oauth/connection-resolver.test.ts +17 -4
  262. package/src/oauth/connection-resolver.ts +16 -16
  263. package/src/oauth/connection.ts +1 -1
  264. package/src/oauth/manual-token-connection.ts +13 -13
  265. package/src/oauth/oauth-store.ts +214 -100
  266. package/src/oauth/platform-connection.test.ts +3 -3
  267. package/src/oauth/platform-connection.ts +4 -4
  268. package/src/oauth/provider-serializer.ts +31 -5
  269. package/src/oauth/revoke.ts +76 -0
  270. package/src/oauth/seed-providers.ts +126 -87
  271. package/src/oauth/token-persistence.ts +1 -1
  272. package/src/permissions/permission-mode.ts +4 -11
  273. package/src/permissions/prompter.ts +13 -1
  274. package/src/permissions/v2-consent-policy.ts +87 -0
  275. package/src/prompts/system-prompt.ts +18 -21
  276. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  277. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  278. package/src/providers/anthropic/client.ts +1 -0
  279. package/src/providers/types.ts +1 -1
  280. package/src/runtime/AGENTS.md +23 -0
  281. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  282. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  283. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  284. package/src/runtime/assistant-event-hub.ts +2 -2
  285. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  286. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  287. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  288. package/src/runtime/auth/middleware.ts +98 -0
  289. package/src/runtime/auth/route-policy.ts +6 -7
  290. package/src/runtime/capability-tokens.ts +414 -0
  291. package/src/runtime/channel-approvals.ts +18 -5
  292. package/src/runtime/chrome-extension-registry.ts +332 -0
  293. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  294. package/src/runtime/guardian-decision-types.ts +7 -0
  295. package/src/runtime/http-server.ts +425 -70
  296. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  297. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  298. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  299. package/src/runtime/migrations/migration-transport.ts +6 -0
  300. package/src/runtime/migrations/migration-wizard.ts +22 -2
  301. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  302. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  303. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  304. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  305. package/src/runtime/pending-interactions.ts +29 -13
  306. package/src/runtime/routes/approval-routes.ts +90 -16
  307. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  308. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  309. package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
  310. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  311. package/src/runtime/routes/conversation-routes.ts +301 -27
  312. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  313. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  314. package/src/runtime/routes/host-browser-routes.ts +279 -0
  315. package/src/runtime/routes/host-file-routes.ts +9 -1
  316. package/src/runtime/routes/identity-routes.ts +259 -16
  317. package/src/runtime/routes/log-export-routes.ts +42 -22
  318. package/src/runtime/routes/memory-item-routes.ts +1 -7
  319. package/src/runtime/routes/migration-routes.ts +87 -2
  320. package/src/runtime/routes/oauth-apps.ts +15 -17
  321. package/src/runtime/routes/oauth-providers.ts +4 -0
  322. package/src/runtime/routes/schedule-routes.ts +24 -11
  323. package/src/runtime/routes/settings-routes.ts +9 -97
  324. package/src/runtime/routes/skills-routes.ts +52 -2
  325. package/src/runtime/routes/subagents-routes.ts +14 -10
  326. package/src/runtime/routes/usage-routes.ts +8 -7
  327. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  328. package/src/runtime/routes/workspace-routes.ts +8 -1
  329. package/src/runtime/routes/workspace-utils.ts +2 -0
  330. package/src/schedule/scheduler.ts +7 -5
  331. package/src/security/ces-credential-client.ts +20 -0
  332. package/src/security/ces-rpc-credential-backend.ts +17 -0
  333. package/src/security/credential-backend.ts +5 -0
  334. package/src/security/oauth2.ts +42 -25
  335. package/src/security/secure-keys.ts +118 -25
  336. package/src/security/token-manager.ts +23 -10
  337. package/src/skills/catalog-files.ts +492 -0
  338. package/src/subagent/manager.ts +131 -26
  339. package/src/subagent/types.ts +19 -0
  340. package/src/tools/apps/executors.ts +11 -2
  341. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  342. package/src/tools/browser/auth-detector.ts +43 -12
  343. package/src/tools/browser/browser-execution.ts +645 -340
  344. package/src/tools/browser/browser-manager.ts +36 -12
  345. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  346. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  347. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  348. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  349. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  350. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  351. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  352. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  353. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  354. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  355. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  356. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  357. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  358. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  359. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  360. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  361. package/src/tools/browser/cdp-client/errors.ts +34 -0
  362. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  363. package/src/tools/browser/cdp-client/factory.ts +204 -0
  364. package/src/tools/browser/cdp-client/index.ts +14 -0
  365. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  366. package/src/tools/browser/cdp-client/types.ts +52 -0
  367. package/src/tools/filesystem/edit.ts +1 -1
  368. package/src/tools/filesystem/list.ts +1 -1
  369. package/src/tools/filesystem/read.ts +1 -1
  370. package/src/tools/filesystem/write.ts +2 -1
  371. package/src/tools/host-filesystem/edit.ts +1 -1
  372. package/src/tools/host-filesystem/read.ts +12 -15
  373. package/src/tools/host-filesystem/write.ts +1 -1
  374. package/src/tools/host-terminal/host-shell.ts +21 -16
  375. package/src/tools/permission-checker.ts +77 -82
  376. package/src/tools/registry.ts +0 -2
  377. package/src/tools/secret-detection-handler.ts +34 -0
  378. package/src/tools/shared/filesystem/image-read.ts +61 -40
  379. package/src/tools/subagent/spawn.ts +47 -3
  380. package/src/tools/subagent/status.ts +2 -0
  381. package/src/tools/system/register.ts +2 -16
  382. package/src/tools/terminal/safe-env.ts +7 -0
  383. package/src/tools/terminal/shell.ts +21 -16
  384. package/src/tools/tool-approval-handler.ts +48 -2
  385. package/src/tools/types.ts +2 -0
  386. package/src/util/platform.ts +14 -19
  387. package/src/workspace/top-level-renderer.ts +19 -1
  388. package/src/__tests__/chrome-cdp.test.ts +0 -419
  389. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  390. package/src/__tests__/permission-mode-store.test.ts +0 -277
  391. package/src/browser-extension-relay/protocol.ts +0 -63
  392. package/src/browser-extension-relay/server.ts +0 -203
  393. package/src/config/schemas/sandbox.ts +0 -14
  394. package/src/permissions/permission-mode-store.ts +0 -180
  395. package/src/tools/browser/chrome-cdp.ts +0 -239
  396. package/src/tools/system/set-permission-mode.ts +0 -103
package/src/mcp/client.ts CHANGED
@@ -35,6 +35,12 @@ export class McpClient {
35
35
  | null = null;
36
36
  private connected = false;
37
37
  private oauthProvider: McpOAuthProvider | null = null;
38
+ private _lastError: Error | null = null;
39
+
40
+ /** The last connection error, if any. Null when connected or not yet attempted. */
41
+ get lastError(): Error | null {
42
+ return this._lastError;
43
+ }
38
44
 
39
45
  get isConnected(): boolean {
40
46
  return this.connected;
@@ -46,6 +52,16 @@ export class McpClient {
46
52
  name: "vellum-assistant",
47
53
  version: "1.0.0",
48
54
  });
55
+
56
+ // Prevent SDK-internal transport errors (e.g. SSE reconnection auth
57
+ // failures) from surfacing as unhandled rejections that crash the daemon
58
+ // via the global unhandledRejection → shutdown handler.
59
+ this.client.onerror = (error) => {
60
+ log.warn(
61
+ { serverId: this.serverId, err: error },
62
+ "MCP SDK transport error (non-fatal)",
63
+ );
64
+ };
49
65
  }
50
66
 
51
67
  async connect(transportConfig: McpTransport): Promise<void> {
@@ -102,34 +118,24 @@ export class McpClient {
102
118
  }
103
119
  this.transport = null;
104
120
 
105
- if (isHttpTransport) {
106
- const isAuthError =
107
- err instanceof UnauthorizedError ||
108
- (err instanceof Error &&
109
- /\b(401|403|unauthorized|forbidden)\b/i.test(err.message)) ||
110
- (err != null &&
111
- typeof err === "object" &&
112
- "code" in err &&
113
- (err.code === 401 || err.code === 403));
114
-
115
- if (isAuthError) {
116
- // Auth-related — user can run `assistant mcp auth <name>` to authenticate.
117
- log.info(
118
- { serverId: this.serverId, err },
119
- "MCP server requires authentication",
120
- );
121
- return;
122
- }
123
-
124
- // Non-auth error (DNS, TLS, timeout, etc.) — log and re-throw
125
- log.error(
121
+ if (isHttpTransport && isAuthRelatedError(err)) {
122
+ // Auth-related — user can run `assistant mcp auth <name>` to authenticate.
123
+ log.info(
126
124
  { serverId: this.serverId, err },
127
- "MCP server connection failed",
125
+ "MCP server requires authentication",
128
126
  );
129
- throw err;
127
+ return;
130
128
  }
131
129
 
132
- throw err;
130
+ // Non-auth error (DNS, TLS, timeout, etc.) — log but never propagate
131
+ // an MCP connection failure to the caller. The daemon must keep
132
+ // running even when individual MCP servers are unreachable.
133
+ this._lastError = err instanceof Error ? err : new Error(String(err));
134
+ log.error(
135
+ { serverId: this.serverId, err },
136
+ "MCP server connection failed",
137
+ );
138
+ return;
133
139
  }
134
140
 
135
141
  this.connected = true;
@@ -258,3 +264,32 @@ export class McpClient {
258
264
  }
259
265
  }
260
266
  }
267
+
268
+ /**
269
+ * Returns true when `err` looks like an authentication / authorization failure
270
+ * from the MCP SDK or the remote server. Used to distinguish "needs auth"
271
+ * from genuine transport failures so we can log guidance instead of crashing.
272
+ */
273
+ function isAuthRelatedError(err: unknown): boolean {
274
+ if (err instanceof UnauthorizedError) return true;
275
+
276
+ if (
277
+ err instanceof Error &&
278
+ /\b(401|403|unauthorized|forbidden|authorizationCode is required|prepareTokenRequest)\b/i.test(
279
+ err.message,
280
+ )
281
+ ) {
282
+ return true;
283
+ }
284
+
285
+ if (
286
+ err != null &&
287
+ typeof err === "object" &&
288
+ "code" in err &&
289
+ (err.code === 401 || err.code === 403)
290
+ ) {
291
+ return true;
292
+ }
293
+
294
+ return false;
295
+ }
@@ -64,6 +64,23 @@ export function isMultifileApp(app: AppDefinition): boolean {
64
64
  return app.formatVersion === 2;
65
65
  }
66
66
 
67
+ /**
68
+ * Resolve the effective HTML for an app. For single-file apps this is
69
+ * `htmlDefinition` (the root index.html). For multifile apps it reads the
70
+ * compiled `dist/index.html` and inlines JS/CSS assets so the result is a
71
+ * self-contained HTML string suitable for `loadHTMLString`.
72
+ */
73
+ export function resolveEffectiveAppHtml(app: AppDefinition): string {
74
+ if (!isMultifileApp(app)) return app.htmlDefinition;
75
+
76
+ const appDir = getAppDirPath(app.id);
77
+ const distIndex = join(appDir, "dist", "index.html");
78
+ if (existsSync(distIndex)) {
79
+ return inlineDistAssets(appDir, readFileSync(distIndex, "utf-8"));
80
+ }
81
+ return app.htmlDefinition;
82
+ }
83
+
67
84
  /**
68
85
  * Inline dist assets (main.js, main.css) into the compiled HTML so it can be
69
86
  * delivered as a self-contained string via loadHTMLString/SSE without needing
@@ -75,7 +92,10 @@ export function inlineDistAssets(appDir: string, html: string): string {
75
92
  // Inline main.js
76
93
  const jsPath = join(distDir, "main.js");
77
94
  if (existsSync(jsPath)) {
78
- const js = readFileSync(jsPath, "utf-8").replace(/<\/script>/g, "<\\/script>");
95
+ const js = readFileSync(jsPath, "utf-8").replace(
96
+ /<\/script>/g,
97
+ "<\\/script>",
98
+ );
79
99
  html = html.replace(
80
100
  /<script\s+type="module"\s+src="main\.js"\s*><\/script>/,
81
101
  () => `<script type="module">${js}</script>`,
@@ -818,6 +838,16 @@ export function listAppFiles(appId: string): string[] {
818
838
  return results.sort();
819
839
  }
820
840
 
841
+ /**
842
+ * Check whether a file exists in the app directory.
843
+ * Path is validated to prevent traversal.
844
+ */
845
+ export function appFileExists(appId: string, path: string): boolean {
846
+ validateId(appId);
847
+ const resolved = validateFilePath(appId, path);
848
+ return existsSync(resolved);
849
+ }
850
+
821
851
  /**
822
852
  * Read a file from the app directory.
823
853
  * Path is validated to prevent traversal.
@@ -170,6 +170,7 @@ export interface ConversationRow {
170
170
  originInterface: string | null;
171
171
  forkParentConversationId: string | null;
172
172
  forkParentMessageId: string | null;
173
+ hostAccess: number;
173
174
  isAutoTitle: number;
174
175
  scheduleJobId: string | null;
175
176
  lastMessageAt: number | null;
@@ -196,6 +197,7 @@ export const parseConversation = createRowMapper<
196
197
  originInterface: "originInterface",
197
198
  forkParentConversationId: "forkParentConversationId",
198
199
  forkParentMessageId: "forkParentMessageId",
200
+ hostAccess: "hostAccess",
199
201
  isAutoTitle: "isAutoTitle",
200
202
  scheduleJobId: "scheduleJobId",
201
203
  lastMessageAt: "lastMessageAt",
@@ -245,6 +247,7 @@ export function createConversation(
245
247
  source?: string;
246
248
  scheduleJobId?: string;
247
249
  groupId?: string;
250
+ hostAccess?: boolean;
248
251
  },
249
252
  ) {
250
253
  const db = getDb();
@@ -276,6 +279,7 @@ export function createConversation(
276
279
  contextSummary: null as string | null,
277
280
  contextCompactedMessageCount: 0,
278
281
  contextCompactedAt: null as number | null,
282
+ hostAccess: opts.hostAccess ? 1 : 0,
279
283
  conversationType,
280
284
  source,
281
285
  memoryScopeId,
@@ -388,6 +392,11 @@ export function getConversationMemoryScopeId(conversationId: string): string {
388
392
  return conv?.memoryScopeId ?? "default";
389
393
  }
390
394
 
395
+ export function getConversationHostAccess(conversationId: string): boolean {
396
+ const conv = getConversation(conversationId);
397
+ return conv?.hostAccess === 1;
398
+ }
399
+
391
400
  /**
392
401
  * Fetch group_id for a conversation via raw SQL. group_id is NOT in the
393
402
  * Drizzle schema (raw-query-only pattern), so ConversationRow doesn't
@@ -1123,6 +1132,20 @@ export function updateConversationContextWindow(
1123
1132
  .run();
1124
1133
  }
1125
1134
 
1135
+ export function updateConversationHostAccess(
1136
+ id: string,
1137
+ hostAccess: boolean,
1138
+ ): void {
1139
+ const db = getDb();
1140
+ db.update(conversations)
1141
+ .set({
1142
+ hostAccess: hostAccess ? 1 : 0,
1143
+ updatedAt: Date.now(),
1144
+ })
1145
+ .where(eq(conversations.id, id))
1146
+ .run();
1147
+ }
1148
+
1126
1149
  /**
1127
1150
  * Delete all conversations, messages, and related data (tool invocations,
1128
1151
  * memory segments, etc.) from the daemon database.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Cadence logic for conversation starters generation.
3
+ *
4
+ * Decides whether a new generation job should be enqueued based on how many
5
+ * active memory items have accumulated since the last generation.
6
+ */
7
+
8
+ import { and, eq, inArray, sql } from "drizzle-orm";
9
+
10
+ import { getLogger } from "../util/logger.js";
11
+ import { getDb } from "./db.js";
12
+ import { enqueueMemoryJob } from "./jobs-store.js";
13
+ import { rawGet } from "./raw-query.js";
14
+ import { memoryCheckpoints, memoryJobs } from "./schema.js";
15
+
16
+ const log = getLogger("conversation-starters-cadence");
17
+
18
+ /**
19
+ * Check whether enough new memory items have accumulated to justify
20
+ * generating a fresh batch of conversation starters.
21
+ */
22
+ export function maybeEnqueueConversationStartersJob(scopeId: string): void {
23
+ const db = getDb();
24
+
25
+ // Count total active memory items
26
+ const countRow = rawGet<{ c: number }>(
27
+ `SELECT COUNT(*) AS c FROM memory_graph_nodes WHERE fidelity != 'gone' AND scope_id = ?`,
28
+ scopeId,
29
+ );
30
+ const totalActive = countRow?.c ?? 0;
31
+ if (totalActive === 0) return;
32
+
33
+ // Read checkpoint: item count at last generation (scoped so each scope tracks independently)
34
+ const checkpointKey = `conversation_starters:item_count_at_last_gen:${scopeId}`;
35
+ const checkpoint = db
36
+ .select({ value: memoryCheckpoints.value })
37
+ .from(memoryCheckpoints)
38
+ .where(eq(memoryCheckpoints.key, checkpointKey))
39
+ .get();
40
+ const parsedLastCount = checkpoint ? parseInt(checkpoint.value, 10) : 0;
41
+ const lastCount = Number.isFinite(parsedLastCount) ? parsedLastCount : 0;
42
+
43
+ // Cadence formula
44
+ let threshold: number;
45
+ if (totalActive <= 10) {
46
+ threshold = 1;
47
+ } else if (totalActive <= 50) {
48
+ threshold = 5;
49
+ } else {
50
+ threshold = 10;
51
+ }
52
+
53
+ const checkpointAhead = totalActive < lastCount;
54
+ const delta = Math.max(0, totalActive - lastCount);
55
+ if (!checkpointAhead && delta < threshold) return;
56
+
57
+ // Dedup: don't enqueue if a pending/running job for this scope already exists
58
+ const existing = db
59
+ .select({ id: memoryJobs.id })
60
+ .from(memoryJobs)
61
+ .where(
62
+ and(
63
+ eq(memoryJobs.type, "generate_conversation_starters"),
64
+ inArray(memoryJobs.status, ["pending", "running"]),
65
+ sql`json_extract(${memoryJobs.payload}, '$.scopeId') = ${scopeId}`,
66
+ ),
67
+ )
68
+ .get();
69
+ if (existing) return;
70
+
71
+ enqueueMemoryJob("generate_conversation_starters", { scopeId });
72
+ log.info(
73
+ { totalActive, lastCount, delta, threshold, scopeId, checkpointAhead },
74
+ "Enqueued conversation starters generation job",
75
+ );
76
+ }
@@ -284,10 +284,13 @@ export function queueRegenerateConversationTitle(
284
284
  */
285
285
  function buildTitleSystemPrompt(): string {
286
286
  return [
287
- "You generate short conversation titles. Output ONLY the title text — no explanation, no quotes, no markdown, no preamble.",
287
+ "You generate ultra-concise conversation titles. Output ONLY the title text — no explanation, no quotes, no markdown, no preamble.",
288
288
  "",
289
289
  "Rules:",
290
- "- Summarize the TOPIC the user is asking about",
290
+ "- 2–6 words. Titles longer than 6 words are unacceptable — ruthlessly compress",
291
+ "- Summarize the TOPIC, not the request or instructions",
292
+ "- Noun phrases are ideal (e.g. 'Auth Middleware Rewrite', 'Docker Volume Mounts')",
293
+ "- Do NOT echo back what the user asked you to do",
291
294
  "- Do NOT respond to the conversation content",
292
295
  "- Do NOT assess feasibility or comment on capabilities",
293
296
  ].join("\n");
@@ -58,6 +58,7 @@ import {
58
58
  migrateContactsRolePrincipal,
59
59
  migrateContactsUserFileColumn,
60
60
  migrateConversationForkLineage,
61
+ migrateConversationHostAccess,
61
62
  migrateConversationsLastMessageAt,
62
63
  migrateConversationsThreadTypeIndex,
63
64
  migrateCreateConversationGraphMemoryState,
@@ -110,9 +111,14 @@ import {
110
111
  migrateOAuthProvidersBehaviorColumns,
111
112
  migrateOAuthProvidersDisplayMetadata,
112
113
  migrateOAuthProvidersFeatureFlag,
114
+ migrateOAuthProvidersLogoUrl,
113
115
  migrateOAuthProvidersManagedServiceConfigKey,
114
116
  migrateOAuthProvidersPingConfig,
115
117
  migrateOAuthProvidersPingUrl,
118
+ migrateOAuthProvidersRefreshUrl,
119
+ migrateOAuthProvidersRevoke,
120
+ migrateOAuthProvidersScopeSeparator,
121
+ migrateOAuthProvidersTokenAuthMethodDefault,
116
122
  migrateReminderRoutingIntent,
117
123
  migrateRemindersToSchedules,
118
124
  migrateRenameConversationTypeColumn,
@@ -352,6 +358,12 @@ export function initializeDb(): void {
352
358
  migrateScheduleReuseConversation,
353
359
  migrateMemoryRecallLogsQueryContext,
354
360
  migrateLlmRequestLogsCreatedAtIndex,
361
+ migrateOAuthProvidersScopeSeparator,
362
+ migrateOAuthProvidersRefreshUrl,
363
+ migrateOAuthProvidersRevoke,
364
+ migrateOAuthProvidersTokenAuthMethodDefault,
365
+ migrateConversationHostAccess,
366
+ migrateOAuthProvidersLogoUrl,
355
367
  ];
356
368
 
357
369
  // Run each migration step, catching and logging individual failures so one
@@ -0,0 +1,75 @@
1
+ import { afterEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { AssistantConfig } from "../config/types.js";
4
+ import {
5
+ clearEmbeddingBackendCache,
6
+ resetLocalEmbeddingFailureState,
7
+ selectEmbeddingBackend,
8
+ } from "./embedding-backend.js";
9
+
10
+ const LOCAL_CONFIG = {
11
+ memory: {
12
+ embeddings: {
13
+ provider: "local",
14
+ localModel: "BAAI/bge-small-en-v1.5",
15
+ },
16
+ },
17
+ } as unknown as AssistantConfig;
18
+
19
+ describe("embedding backend cache invalidation", () => {
20
+ afterEach(() => {
21
+ clearEmbeddingBackendCache();
22
+ });
23
+
24
+ test("clearEmbeddingBackendCache disposes cached backends before clearing", async () => {
25
+ const firstSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
26
+ expect(firstSelection.backend).not.toBeNull();
27
+
28
+ const dispose = mock();
29
+ (firstSelection.backend as { dispose?: () => void }).dispose = dispose;
30
+
31
+ clearEmbeddingBackendCache();
32
+
33
+ expect(dispose).toHaveBeenCalledTimes(1);
34
+
35
+ const secondSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
36
+ expect(secondSelection.backend).not.toBe(firstSelection.backend);
37
+ });
38
+
39
+ test("resetLocalEmbeddingFailureState preserves live cached backends", async () => {
40
+ const firstSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
41
+ expect(firstSelection.backend).not.toBeNull();
42
+
43
+ const dispose = mock();
44
+ (firstSelection.backend as { dispose?: () => void }).dispose = dispose;
45
+
46
+ resetLocalEmbeddingFailureState();
47
+
48
+ expect(dispose).not.toHaveBeenCalled();
49
+
50
+ const secondSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
51
+ expect(secondSelection.backend).toBe(firstSelection.backend);
52
+ });
53
+
54
+ test("resetLocalEmbeddingFailureState clears poisoned local backend retry state", async () => {
55
+ const firstSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
56
+ expect(firstSelection.backend).not.toBeNull();
57
+
58
+ const poisonedInitPromise = Promise.reject(new Error("poisoned"));
59
+ poisonedInitPromise.catch(() => {});
60
+
61
+ const backend = firstSelection.backend as unknown as {
62
+ delegate: unknown;
63
+ initPromise: Promise<unknown> | null;
64
+ };
65
+ backend.delegate = null;
66
+ backend.initPromise = poisonedInitPromise;
67
+
68
+ resetLocalEmbeddingFailureState();
69
+
70
+ expect(backend.initPromise).toBeNull();
71
+
72
+ const secondSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
73
+ expect(secondSelection.backend).toBe(firstSelection.backend);
74
+ });
75
+ });
@@ -1,7 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
 
3
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
3
4
  import { getOllamaBaseUrlEnv } from "../config/env.js";
4
5
  import type { AssistantConfig } from "../config/types.js";
6
+ import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
7
+ import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
5
8
  import { getProviderKeyAsync } from "../security/secure-keys.js";
6
9
  import { getLogger } from "../util/logger.js";
7
10
  import { GeminiEmbeddingBackend } from "./embedding-gemini.js";
@@ -67,6 +70,16 @@ class LazyLocalEmbeddingBackend implements EmbeddingBackend {
67
70
  }
68
71
  }
69
72
 
73
+ dispose(): void {
74
+ this.delegate?.dispose?.();
75
+ }
76
+
77
+ resetForRetry(): void {
78
+ if (!this.delegate) {
79
+ this.initPromise = null;
80
+ }
81
+ }
82
+
70
83
  private async getDelegate(): Promise<EmbeddingBackend> {
71
84
  if (this.delegate) return this.delegate;
72
85
  if (!this.initPromise) {
@@ -176,12 +189,32 @@ function putInVectorCache(
176
189
 
177
190
  /** Clear cached embedding backends and the in-memory vector cache. */
178
191
  export function clearEmbeddingBackendCache(): void {
192
+ for (const backend of new Set(backendCache.values())) {
193
+ try {
194
+ backend.dispose?.();
195
+ } catch (err) {
196
+ log.warn(
197
+ { err, provider: backend.provider, model: backend.model },
198
+ "Failed to dispose embedding backend during cache clear",
199
+ );
200
+ }
201
+ }
179
202
  backendCache.clear();
180
203
  vectorCache.clear();
181
204
  vectorCacheBytes = 0;
182
205
  localBackendBroken = false;
183
206
  }
184
207
 
208
+ /** Reset the sticky local-backend failure flag without evicting live backends. */
209
+ export function resetLocalEmbeddingFailureState(): void {
210
+ localBackendBroken = false;
211
+ for (const backend of new Set(backendCache.values())) {
212
+ if (backend instanceof LazyLocalEmbeddingBackend) {
213
+ backend.resetForRetry();
214
+ }
215
+ }
216
+ }
217
+
185
218
  function cacheKey(provider: string, model: string, extras?: string[]): string {
186
219
  if (extras && extras.length > 0) {
187
220
  return `${provider}:${model}:${extras.join(":")}`;
@@ -243,6 +276,7 @@ export interface EmbeddingBackend {
243
276
  inputs: EmbeddingInput[],
244
277
  options?: EmbeddingRequestOptions,
245
278
  ): Promise<number[][]>;
279
+ dispose?(): void;
246
280
  }
247
281
 
248
282
  export interface EmbeddingBackendSelection {
@@ -280,6 +314,44 @@ export async function selectEmbeddingBackend(
280
314
  };
281
315
  }
282
316
 
317
+ // When the managed-gemini-embeddings-enabled flag is on AND managed proxy
318
+ // prerequisites are satisfied, insert managed-proxy Gemini at the front of
319
+ // the auto chain so platform assistants use Vellum-managed Gemini embeddings.
320
+ if (
321
+ (requested === "auto" || requested === "gemini") &&
322
+ isAssistantFeatureFlagEnabled("managed-gemini-embeddings-enabled", config)
323
+ ) {
324
+ const proxyCtx = await resolveManagedProxyContext();
325
+ if (proxyCtx.enabled) {
326
+ const meta = MANAGED_PROVIDER_META["gemini"];
327
+ if (meta?.managed && meta.proxyPath) {
328
+ const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
329
+ const managedModel = config.memory.embeddings.geminiModel;
330
+ const managedDimensions =
331
+ config.memory.embeddings.geminiDimensions ?? 3072;
332
+ const extras = geminiCacheExtras(config);
333
+ return {
334
+ backend: getCachedOrCreate(
335
+ "gemini",
336
+ managedModel,
337
+ () =>
338
+ new GeminiEmbeddingBackend(
339
+ proxyCtx.assistantApiKey,
340
+ managedModel,
341
+ {
342
+ taskType: config.memory.embeddings.geminiTaskType,
343
+ dimensions: managedDimensions,
344
+ managedBaseUrl,
345
+ },
346
+ ),
347
+ [...extras, "managed"],
348
+ ),
349
+ reason: null,
350
+ };
351
+ }
352
+ }
353
+ }
354
+
283
355
  // Auto order: local → openai → gemini → ollama
284
356
  const order: EmbeddingProviderName[] =
285
357
  requested === "auto"
@@ -329,11 +401,18 @@ export async function selectEmbeddingBackend(
329
401
  case "gemini": {
330
402
  const geminiKey = await getProviderKeyAsync("gemini");
331
403
  if (!geminiKey) {
332
- const cached = getCached(
333
- "gemini",
334
- config.memory.embeddings.geminiModel,
335
- geminiCacheExtras(config),
336
- );
404
+ // Check managed cache variant first so a warm managed backend
405
+ // survives transient proxy-context blips, then non-managed.
406
+ const cached =
407
+ getCached("gemini", config.memory.embeddings.geminiModel, [
408
+ ...geminiCacheExtras(config),
409
+ "managed",
410
+ ]) ??
411
+ getCached(
412
+ "gemini",
413
+ config.memory.embeddings.geminiModel,
414
+ geminiCacheExtras(config),
415
+ );
337
416
  if (cached) return { backend: cached, reason: null };
338
417
  continue;
339
418
  }
@@ -614,6 +693,53 @@ async function selectFallbackBackends(
614
693
  geminiCacheExtras(config),
615
694
  ),
616
695
  );
696
+ } else if (
697
+ isAssistantFeatureFlagEnabled(
698
+ "managed-gemini-embeddings-enabled",
699
+ config,
700
+ )
701
+ ) {
702
+ // Try managed proxy Gemini as fallback when no direct key exists.
703
+ const proxyCtx = await resolveManagedProxyContext();
704
+ const meta = MANAGED_PROVIDER_META["gemini"];
705
+ if (proxyCtx.enabled && meta?.managed && meta.proxyPath) {
706
+ const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
707
+ const managedModel = config.memory.embeddings.geminiModel;
708
+ const managedDimensions =
709
+ config.memory.embeddings.geminiDimensions ?? 3072;
710
+ const extras = geminiCacheExtras(config);
711
+ backends.push(
712
+ getCachedOrCreate(
713
+ "gemini",
714
+ managedModel,
715
+ () =>
716
+ new GeminiEmbeddingBackend(
717
+ proxyCtx.assistantApiKey,
718
+ managedModel,
719
+ {
720
+ taskType: config.memory.embeddings.geminiTaskType,
721
+ dimensions: managedDimensions,
722
+ managedBaseUrl,
723
+ },
724
+ ),
725
+ [...extras, "managed"],
726
+ ),
727
+ );
728
+ } else {
729
+ // Check managed cache variant first, then non-managed, so a warm
730
+ // managed backend survives transient proxy-context blips.
731
+ const cached =
732
+ getCached("gemini", config.memory.embeddings.geminiModel, [
733
+ ...geminiCacheExtras(config),
734
+ "managed",
735
+ ]) ??
736
+ getCached(
737
+ "gemini",
738
+ config.memory.embeddings.geminiModel,
739
+ geminiCacheExtras(config),
740
+ );
741
+ if (cached) backends.push(cached);
742
+ }
617
743
  } else {
618
744
  // Preserve cached backend on transient credential-store failures.
619
745
  const cached = getCached(
@@ -253,4 +253,58 @@ describe("GeminiEmbeddingBackend", () => {
253
253
  expect(result[1]).toEqual([0.2, 0.4]);
254
254
  });
255
255
  });
256
+
257
+ describe("managed proxy transport", () => {
258
+ test("routes through managed proxy base URL when managedBaseUrl is set", async () => {
259
+ const backend = new GeminiEmbeddingBackend(
260
+ "ast-managed-key",
261
+ "gemini-embedding-2-preview",
262
+ {
263
+ managedBaseUrl:
264
+ "https://platform.example.com/v1/runtime-proxy/gemini",
265
+ },
266
+ );
267
+ await backend.embed(["hello"]);
268
+
269
+ expect(mockFetch).toHaveBeenCalledTimes(1);
270
+ const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit];
271
+ expect(url).toBe(
272
+ "https://platform.example.com/v1/runtime-proxy/gemini/v1beta/models/gemini-embedding-2-preview:embedContent",
273
+ );
274
+ // Should NOT have key= query param
275
+ expect(url).not.toContain("key=");
276
+ // Should have Bearer auth header
277
+ const headers = init.headers as Record<string, string>;
278
+ expect(headers["Authorization"]).toBe("Bearer ast-managed-key");
279
+ });
280
+
281
+ test("uses direct Google API URL when managedBaseUrl is not set", async () => {
282
+ const backend = new GeminiEmbeddingBackend("direct-key", "test-model");
283
+ await backend.embed(["hello"]);
284
+
285
+ const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit];
286
+ expect(url).toContain("generativelanguage.googleapis.com");
287
+ expect(url).toContain("key=direct-key");
288
+ // Should NOT have Authorization header
289
+ const headers = init.headers as Record<string, string>;
290
+ expect(headers["Authorization"]).toBeUndefined();
291
+ });
292
+
293
+ test("includes outputDimensionality with managed proxy", async () => {
294
+ const backend = new GeminiEmbeddingBackend(
295
+ "ast-managed-key",
296
+ "gemini-embedding-2-preview",
297
+ {
298
+ managedBaseUrl:
299
+ "https://platform.example.com/v1/runtime-proxy/gemini",
300
+ dimensions: 3072,
301
+ },
302
+ );
303
+ await backend.embed(["hello"]);
304
+
305
+ const [, init] = mockFetch.mock.calls[0] as [string, RequestInit];
306
+ const body = JSON.parse(init.body as string);
307
+ expect(body.outputDimensionality).toBe(3072);
308
+ });
309
+ });
256
310
  });