@vellumai/assistant 0.6.1 → 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 (463) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docker-entrypoint.sh +12 -2
  4. package/docs/architecture/memory.md +1 -1
  5. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  7. package/openapi.yaml +184 -69
  8. package/package.json +41 -41
  9. package/scripts/generate-openapi.ts +1 -2
  10. package/src/__tests__/acp-session.test.ts +43 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +1 -0
  13. package/src/__tests__/app-source-watcher.test.ts +37 -11
  14. package/src/__tests__/approval-routes-http.test.ts +178 -1
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  17. package/src/__tests__/browser-manager.test.ts +40 -27
  18. package/src/__tests__/catalog-files.test.ts +862 -0
  19. package/src/__tests__/channel-approvals.test.ts +53 -0
  20. package/src/__tests__/checker.test.ts +104 -170
  21. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  22. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  23. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  24. package/src/__tests__/config-schema.test.ts +125 -48
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  26. package/src/__tests__/context-overflow-approval.test.ts +21 -6
  27. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  28. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  29. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  30. package/src/__tests__/conversation-attachments.test.ts +80 -4
  31. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  32. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  33. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  34. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  35. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  36. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  37. package/src/__tests__/conversation-queue.test.ts +45 -2
  38. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  39. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  40. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  42. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  43. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  44. package/src/__tests__/conversation-store.test.ts +195 -0
  45. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  46. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
  47. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  48. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  49. package/src/__tests__/credential-vault.test.ts +152 -13
  50. package/src/__tests__/credentials-cli.test.ts +2 -2
  51. package/src/__tests__/date-context.test.ts +4 -4
  52. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  53. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  54. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  55. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  56. package/src/__tests__/gemini-provider.test.ts +2 -2
  57. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  58. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  59. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  60. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  61. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  62. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  63. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  64. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  65. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  66. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  67. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  68. package/src/__tests__/host-browser-routes.test.ts +198 -0
  69. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  70. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  71. package/src/__tests__/host-file-proxy.test.ts +185 -1
  72. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  73. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  74. package/src/__tests__/host-shell-tool.test.ts +1 -11
  75. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  76. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  77. package/src/__tests__/inline-command-runner.test.ts +7 -5
  78. package/src/__tests__/integration-status.test.ts +6 -7
  79. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  80. package/src/__tests__/log-export-workspace.test.ts +190 -0
  81. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  82. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  83. package/src/__tests__/mcp-health-check.test.ts +10 -3
  84. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  85. package/src/__tests__/migration-export-http.test.ts +61 -2
  86. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  87. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  88. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  89. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  90. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  91. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  92. package/src/__tests__/oauth-cli.test.ts +707 -60
  93. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  94. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  95. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  96. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  97. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  98. package/src/__tests__/oauth-store.test.ts +1386 -182
  99. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  100. package/src/__tests__/onboarding-template-contract.test.ts +74 -55
  101. package/src/__tests__/openai-provider.test.ts +2 -2
  102. package/src/__tests__/outlook-categories.test.ts +1 -1
  103. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  104. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  105. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  106. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  107. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  108. package/src/__tests__/outlook-trash.test.ts +1 -1
  109. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  110. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  111. package/src/__tests__/permission-mode.test.ts +28 -56
  112. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  113. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  114. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  115. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  116. package/src/__tests__/require-fresh-approval.test.ts +40 -3
  117. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  118. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  119. package/src/__tests__/schedule-routes.test.ts +162 -0
  120. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  121. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  123. package/src/__tests__/set-permission-mode.test.ts +13 -250
  124. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  125. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  126. package/src/__tests__/slack-channel-config.test.ts +12 -15
  127. package/src/__tests__/subagent-detail.test.ts +44 -2
  128. package/src/__tests__/subagent-disposal.test.ts +1 -0
  129. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  130. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  131. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  132. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  134. package/src/__tests__/subagent-tools.test.ts +1 -0
  135. package/src/__tests__/subagent-types.test.ts +1 -0
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  137. package/src/__tests__/system-prompt.test.ts +72 -1
  138. package/src/__tests__/task-scheduler.test.ts +32 -6
  139. package/src/__tests__/telegram-config.test.ts +10 -13
  140. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  141. package/src/__tests__/terminal-tools.test.ts +11 -5
  142. package/src/__tests__/test-preload.ts +14 -0
  143. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  144. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  145. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  146. package/src/__tests__/tool-executor.test.ts +0 -1
  147. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  148. package/src/__tests__/top-level-renderer.test.ts +73 -1
  149. package/src/__tests__/transport-hints-queue.test.ts +62 -0
  150. package/src/__tests__/trust-store.test.ts +4 -4
  151. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  152. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  153. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  154. package/src/__tests__/workspace-policy.test.ts +2 -7
  155. package/src/acp/client-handler.ts +30 -4
  156. package/src/agent/loop.ts +12 -35
  157. package/src/approvals/guardian-request-resolvers.ts +21 -15
  158. package/src/browser-session/__tests__/manager.test.ts +297 -0
  159. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  160. package/src/browser-session/backends/extension.ts +26 -0
  161. package/src/browser-session/backends/local.ts +24 -0
  162. package/src/browser-session/events.ts +164 -0
  163. package/src/browser-session/index.ts +27 -0
  164. package/src/browser-session/manager.ts +159 -0
  165. package/src/browser-session/types.ts +28 -0
  166. package/src/channels/__tests__/types.test.ts +134 -0
  167. package/src/channels/types.ts +55 -0
  168. package/src/cli/__tests__/run-assistant-command.ts +34 -7
  169. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  170. package/src/cli/commands/browser-relay.ts +339 -409
  171. package/src/cli/commands/credentials.ts +3 -3
  172. package/src/cli/commands/default-action.ts +68 -1
  173. package/src/cli/commands/email.ts +18 -13
  174. package/src/cli/commands/mcp.ts +16 -4
  175. package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
  176. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  177. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  178. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  179. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  180. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  181. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  182. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  183. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  184. package/src/cli/commands/oauth/apps.ts +7 -4
  185. package/src/cli/commands/oauth/connect.ts +16 -2
  186. package/src/cli/commands/oauth/disconnect.ts +1 -1
  187. package/src/cli/commands/oauth/providers.ts +200 -36
  188. package/src/cli/commands/oauth/shared.ts +5 -5
  189. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  190. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  191. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  192. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/platform/index.ts +107 -10
  194. package/src/cli/commands/usage.ts +10 -9
  195. package/src/cli/lib/daemon-credential-client.ts +4 -0
  196. package/src/cli/program.ts +10 -3
  197. package/src/config/assistant-feature-flags.ts +59 -55
  198. package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
  199. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  200. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  201. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  202. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  203. package/src/config/bundled-skills/document/SKILL.md +4 -0
  204. package/src/config/bundled-skills/gmail/SKILL.md +12 -7
  205. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  206. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  207. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  208. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  209. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  210. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  211. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  212. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  213. package/src/config/env-registry.ts +14 -0
  214. package/src/config/env.ts +21 -0
  215. package/src/config/feature-flag-registry.json +46 -7
  216. package/src/config/loader.ts +56 -1
  217. package/src/config/sanitize-for-transfer.ts +47 -0
  218. package/src/config/schema.ts +46 -5
  219. package/src/config/schemas/host-browser.ts +66 -0
  220. package/src/config/schemas/memory-lifecycle.ts +1 -1
  221. package/src/config/schemas/memory-retrieval.ts +103 -0
  222. package/src/config/schemas/security.ts +0 -6
  223. package/src/config/schemas/services.ts +16 -0
  224. package/src/config/types.ts +0 -1
  225. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  226. package/src/context/window-manager.ts +19 -1
  227. package/src/credential-execution/approval-bridge.ts +49 -16
  228. package/src/credential-execution/managed-catalog.ts +3 -7
  229. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  230. package/src/daemon/app-source-watcher.ts +35 -0
  231. package/src/daemon/config-watcher.ts +6 -2
  232. package/src/daemon/context-overflow-approval.ts +5 -1
  233. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  234. package/src/daemon/conversation-agent-loop.ts +74 -19
  235. package/src/daemon/conversation-attachments.ts +40 -1
  236. package/src/daemon/conversation-messaging.ts +3 -0
  237. package/src/daemon/conversation-process.ts +66 -3
  238. package/src/daemon/conversation-queue-manager.ts +8 -0
  239. package/src/daemon/conversation-runtime-assembly.ts +159 -20
  240. package/src/daemon/conversation-surfaces.ts +78 -12
  241. package/src/daemon/conversation-tool-setup.ts +74 -11
  242. package/src/daemon/conversation-workspace.ts +12 -0
  243. package/src/daemon/conversation.ts +227 -11
  244. package/src/daemon/date-context.ts +10 -10
  245. package/src/daemon/first-greeting.ts +3 -2
  246. package/src/daemon/handlers/conversations.ts +9 -139
  247. package/src/daemon/handlers/shared.ts +65 -0
  248. package/src/daemon/handlers/skills.ts +232 -37
  249. package/src/daemon/host-bash-proxy.ts +48 -13
  250. package/src/daemon/host-browser-proxy.ts +191 -0
  251. package/src/daemon/host-cu-proxy.ts +36 -11
  252. package/src/daemon/host-file-proxy.ts +57 -9
  253. package/src/daemon/lifecycle.ts +86 -12
  254. package/src/daemon/message-protocol.ts +7 -0
  255. package/src/daemon/message-types/conversations.ts +59 -13
  256. package/src/daemon/message-types/host-browser.ts +100 -0
  257. package/src/daemon/message-types/messages.ts +5 -6
  258. package/src/daemon/message-types/notifications.ts +12 -0
  259. package/src/daemon/message-types/settings.ts +12 -0
  260. package/src/daemon/message-types/skills.ts +10 -0
  261. package/src/daemon/message-types/subagents.ts +2 -0
  262. package/src/daemon/server.ts +112 -35
  263. package/src/daemon/tool-side-effects.ts +6 -0
  264. package/src/daemon/transport-hints.ts +14 -0
  265. package/src/inbound/platform-callback-registration.ts +18 -17
  266. package/src/index.ts +1 -1
  267. package/src/mcp/client.ts +59 -24
  268. package/src/memory/app-store.ts +31 -1
  269. package/src/memory/conversation-crud.ts +38 -10
  270. package/src/memory/conversation-directories.ts +39 -0
  271. package/src/memory/conversation-group-migration.ts +65 -5
  272. package/src/memory/conversation-starters-cadence.ts +76 -0
  273. package/src/memory/conversation-title-service.ts +5 -2
  274. package/src/memory/db-init.ts +12 -0
  275. package/src/memory/embedding-backend.test.ts +75 -0
  276. package/src/memory/embedding-backend.ts +131 -5
  277. package/src/memory/embedding-gemini.test.ts +54 -0
  278. package/src/memory/embedding-gemini.ts +20 -9
  279. package/src/memory/embedding-local.ts +177 -18
  280. package/src/memory/graph/capability-seed.ts +3 -5
  281. package/src/memory/graph/consolidation.ts +10 -23
  282. package/src/memory/graph/extraction-job.ts +15 -0
  283. package/src/memory/graph/retriever.ts +40 -22
  284. package/src/memory/graph/store.test.ts +7 -3
  285. package/src/memory/graph/store.ts +47 -12
  286. package/src/memory/group-crud.ts +25 -9
  287. package/src/memory/llm-usage-store.ts +45 -4
  288. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  289. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  290. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  291. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  292. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  293. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  294. package/src/memory/migrations/index.ts +6 -0
  295. package/src/memory/migrations/registry.ts +8 -0
  296. package/src/memory/schema/conversations.ts +1 -0
  297. package/src/memory/schema/oauth.ts +18 -13
  298. package/src/messaging/provider.ts +1 -1
  299. package/src/notifications/broadcaster.ts +6 -0
  300. package/src/notifications/conversation-pairing.ts +12 -4
  301. package/src/notifications/emit-signal.ts +14 -0
  302. package/src/notifications/signal.ts +11 -0
  303. package/src/oauth/AGENTS.md +76 -0
  304. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  305. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  306. package/src/oauth/byo-connection.test.ts +8 -8
  307. package/src/oauth/byo-connection.ts +7 -7
  308. package/src/oauth/connect-orchestrator.ts +23 -21
  309. package/src/oauth/connect-types.ts +3 -3
  310. package/src/oauth/connection-resolver.test.ts +17 -4
  311. package/src/oauth/connection-resolver.ts +16 -16
  312. package/src/oauth/connection.ts +1 -1
  313. package/src/oauth/manual-token-connection.ts +13 -13
  314. package/src/oauth/oauth-store.ts +214 -100
  315. package/src/oauth/platform-connection.test.ts +5 -5
  316. package/src/oauth/platform-connection.ts +4 -4
  317. package/src/oauth/provider-serializer.ts +31 -5
  318. package/src/oauth/revoke.ts +76 -0
  319. package/src/oauth/seed-providers.ts +127 -87
  320. package/src/oauth/token-persistence.ts +1 -1
  321. package/src/permissions/checker.ts +3 -3
  322. package/src/permissions/defaults.ts +7 -8
  323. package/src/permissions/permission-mode.ts +4 -11
  324. package/src/permissions/prompter.ts +13 -3
  325. package/src/permissions/v2-consent-policy.ts +87 -0
  326. package/src/platform/client.ts +1 -1
  327. package/src/prompts/system-prompt.ts +18 -21
  328. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  329. package/src/prompts/templates/BOOTSTRAP.md +59 -96
  330. package/src/prompts/templates/SOUL.md +11 -11
  331. package/src/providers/anthropic/client.ts +1 -0
  332. package/src/providers/types.ts +1 -1
  333. package/src/runtime/AGENTS.md +23 -0
  334. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  335. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  336. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  337. package/src/runtime/assistant-event-hub.ts +24 -2
  338. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  339. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  341. package/src/runtime/auth/middleware.ts +98 -0
  342. package/src/runtime/auth/route-policy.ts +6 -7
  343. package/src/runtime/auth/token-service.ts +8 -0
  344. package/src/runtime/capability-tokens.ts +414 -0
  345. package/src/runtime/channel-approvals.ts +18 -5
  346. package/src/runtime/chrome-extension-registry.ts +332 -0
  347. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  348. package/src/runtime/guardian-decision-types.ts +7 -0
  349. package/src/runtime/http-server.ts +425 -70
  350. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  351. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  352. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  353. package/src/runtime/migrations/migration-transport.ts +6 -0
  354. package/src/runtime/migrations/migration-wizard.ts +22 -2
  355. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  356. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  357. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  358. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  359. package/src/runtime/pending-interactions.ts +29 -13
  360. package/src/runtime/routes/approval-routes.ts +90 -16
  361. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  362. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  363. package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
  364. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  365. package/src/runtime/routes/conversation-routes.ts +308 -28
  366. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  367. package/src/runtime/routes/group-routes.ts +22 -8
  368. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  369. package/src/runtime/routes/host-browser-routes.ts +279 -0
  370. package/src/runtime/routes/host-file-routes.ts +9 -1
  371. package/src/runtime/routes/identity-routes.ts +259 -16
  372. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  373. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  374. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  375. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  376. package/src/runtime/routes/log-export-routes.ts +60 -25
  377. package/src/runtime/routes/memory-item-routes.ts +1 -7
  378. package/src/runtime/routes/migration-routes.ts +87 -2
  379. package/src/runtime/routes/oauth-apps.ts +15 -17
  380. package/src/runtime/routes/oauth-providers.ts +4 -0
  381. package/src/runtime/routes/schedule-routes.ts +24 -11
  382. package/src/runtime/routes/settings-routes.ts +9 -97
  383. package/src/runtime/routes/skills-routes.ts +52 -2
  384. package/src/runtime/routes/subagents-routes.ts +14 -10
  385. package/src/runtime/routes/usage-routes.ts +8 -7
  386. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  387. package/src/runtime/routes/workspace-routes.ts +8 -1
  388. package/src/runtime/routes/workspace-utils.ts +2 -0
  389. package/src/schedule/scheduler.ts +7 -5
  390. package/src/security/ces-credential-client.ts +20 -0
  391. package/src/security/ces-rpc-credential-backend.ts +17 -0
  392. package/src/security/credential-backend.ts +5 -0
  393. package/src/security/oauth2.ts +42 -25
  394. package/src/security/secure-keys.ts +118 -25
  395. package/src/security/token-manager.ts +23 -10
  396. package/src/skills/catalog-files.ts +492 -0
  397. package/src/skills/inline-command-runner.ts +12 -14
  398. package/src/subagent/manager.ts +131 -26
  399. package/src/subagent/types.ts +19 -0
  400. package/src/tools/apps/executors.ts +11 -2
  401. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  402. package/src/tools/browser/auth-detector.ts +43 -12
  403. package/src/tools/browser/browser-execution.ts +645 -340
  404. package/src/tools/browser/browser-manager.ts +36 -12
  405. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  406. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  407. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  408. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  409. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  410. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  411. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  412. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  413. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  414. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  415. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  416. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  417. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  418. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  419. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  420. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  421. package/src/tools/browser/cdp-client/errors.ts +34 -0
  422. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  423. package/src/tools/browser/cdp-client/factory.ts +204 -0
  424. package/src/tools/browser/cdp-client/index.ts +14 -0
  425. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  426. package/src/tools/browser/cdp-client/types.ts +52 -0
  427. package/src/tools/filesystem/edit.ts +1 -1
  428. package/src/tools/filesystem/list.ts +1 -1
  429. package/src/tools/filesystem/read.ts +1 -1
  430. package/src/tools/filesystem/write.ts +2 -1
  431. package/src/tools/host-filesystem/edit.ts +1 -1
  432. package/src/tools/host-filesystem/read.ts +12 -15
  433. package/src/tools/host-filesystem/write.ts +1 -1
  434. package/src/tools/host-terminal/host-shell.ts +21 -16
  435. package/src/tools/permission-checker.ts +77 -100
  436. package/src/tools/registry.ts +0 -2
  437. package/src/tools/secret-detection-handler.ts +34 -1
  438. package/src/tools/shared/filesystem/image-read.ts +61 -40
  439. package/src/tools/skills/sandbox-runner.ts +3 -6
  440. package/src/tools/subagent/spawn.ts +47 -3
  441. package/src/tools/subagent/status.ts +2 -0
  442. package/src/tools/system/register.ts +2 -16
  443. package/src/tools/terminal/safe-env.ts +7 -0
  444. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  445. package/src/tools/terminal/sandbox.ts +4 -1
  446. package/src/tools/terminal/shell.ts +24 -21
  447. package/src/tools/tool-approval-handler.ts +48 -2
  448. package/src/tools/types.ts +2 -3
  449. package/src/util/platform.ts +14 -19
  450. package/src/watcher/provider-types.ts +1 -1
  451. package/src/workspace/migrations/029-seed-pkb.ts +1 -0
  452. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  453. package/src/workspace/migrations/registry.ts +2 -0
  454. package/src/workspace/top-level-renderer.ts +19 -1
  455. package/src/__tests__/chrome-cdp.test.ts +0 -419
  456. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  457. package/src/__tests__/permission-mode-store.test.ts +0 -277
  458. package/src/browser-extension-relay/protocol.ts +0 -63
  459. package/src/browser-extension-relay/server.ts +0 -203
  460. package/src/config/schemas/sandbox.ts +0 -14
  461. package/src/permissions/permission-mode-store.ts +0 -180
  462. package/src/tools/browser/chrome-cdp.ts +0 -239
  463. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -60,11 +60,11 @@ let slackChannelConfigCalls: Array<{
60
60
  }> = [];
61
61
 
62
62
  mock.module("../oauth/manual-token-connection.js", () => ({
63
- syncManualTokenConnection: async (providerKey: string) => {
63
+ syncManualTokenConnection: async (provider: string) => {
64
64
  const { credentialKey } = await import("../security/credential-key.js");
65
65
  const { getSecureKeyAsync } = await import("../security/secure-keys.js");
66
66
 
67
- if (providerKey === "slack_channel") {
67
+ if (provider === "slack_channel") {
68
68
  const hasBotToken = !!(await getSecureKeyAsync(
69
69
  credentialKey("slack_channel", "bot_token"),
70
70
  ));
@@ -72,9 +72,9 @@ mock.module("../oauth/manual-token-connection.js", () => ({
72
72
  credentialKey("slack_channel", "app_token"),
73
73
  ));
74
74
  if (hasBotToken && hasAppToken) {
75
- manualConnectionStore[providerKey] = "active";
75
+ manualConnectionStore[provider] = "active";
76
76
  } else {
77
- delete manualConnectionStore[providerKey];
77
+ delete manualConnectionStore[provider];
78
78
  }
79
79
  }
80
80
  },
@@ -50,7 +50,15 @@ mock.module("../tools/registry.js", () => ({
50
50
  // ---------------------------------------------------------------------------
51
51
 
52
52
  let mockRefreshOAuth2Token: ReturnType<
53
- typeof mock<() => Promise<{ accessToken: string; expiresIn: number }>>
53
+ typeof mock<
54
+ (
55
+ tokenExchangeUrl: string,
56
+ clientId: string,
57
+ refreshToken: string,
58
+ clientSecret?: string,
59
+ tokenEndpointAuthMethod?: string,
60
+ ) => Promise<{ accessToken: string; expiresIn: number }>
61
+ >
54
62
  >;
55
63
 
56
64
  mock.module("../security/oauth2.js", () => {
@@ -74,7 +82,7 @@ const mockConnections = new Map<
74
82
  string,
75
83
  {
76
84
  id: string;
77
- providerKey: string;
85
+ provider: string;
78
86
  oauthAppId: string;
79
87
  expiresAt: number | null;
80
88
  }
@@ -83,7 +91,7 @@ const mockApps = new Map<
83
91
  string,
84
92
  {
85
93
  id: string;
86
- providerKey: string;
94
+ provider: string;
87
95
  clientId: string;
88
96
  clientSecretCredentialPath: string;
89
97
  }
@@ -92,21 +100,22 @@ const mockProviders = new Map<
92
100
  string,
93
101
  {
94
102
  key: string;
95
- tokenUrl: string;
103
+ tokenExchangeUrl: string;
104
+ refreshUrl?: string | null;
96
105
  tokenEndpointAuthMethod?: string;
97
106
  }
98
107
  >();
99
108
 
100
109
  let mockDisconnectOAuthProvider: ReturnType<
101
110
  typeof mock<
102
- (providerKey: string) => Promise<"disconnected" | "not-found" | "error">
111
+ (provider: string) => Promise<"disconnected" | "not-found" | "error">
103
112
  >
104
113
  >;
105
114
 
106
115
  mock.module("../oauth/oauth-store.js", () => {
107
- mockDisconnectOAuthProvider = mock((providerKey: string) =>
116
+ mockDisconnectOAuthProvider = mock((provider: string) =>
108
117
  Promise.resolve(
109
- mockConnections.has(providerKey)
118
+ mockConnections.has(provider)
110
119
  ? ("disconnected" as const)
111
120
  : ("not-found" as const),
112
121
  ),
@@ -746,7 +755,7 @@ describe("credential_store tool", () => {
746
755
  // Simulate an active OAuth connection for this service
747
756
  mockConnections.set("google", {
748
757
  id: "conn-gmail",
749
- providerKey: "google",
758
+ provider: "google",
750
759
  oauthAppId: "app-gmail",
751
760
  expiresAt: Date.now() + 3600_000,
752
761
  });
@@ -1303,7 +1312,7 @@ describe("withValidToken refresh deduplication", () => {
1303
1312
  * Helper: set up a service with an access token, refresh token, and
1304
1313
  * mock DB data so that token refresh can proceed through doRefresh().
1305
1314
  *
1306
- * OAuth-specific fields (tokenUrl, clientId, expiresAt) are now stored
1315
+ * OAuth-specific fields (tokenExchangeUrl, clientId, expiresAt) are now stored
1307
1316
  * in the SQLite oauth-store. The mock maps simulate the DB layer.
1308
1317
  */
1309
1318
  async function setupService(
@@ -1324,17 +1333,18 @@ describe("withValidToken refresh deduplication", () => {
1324
1333
  );
1325
1334
  mockProviders.set(service, {
1326
1335
  key: service,
1327
- tokenUrl: "https://oauth.example.com/token",
1336
+ tokenExchangeUrl: "https://oauth.example.com/token",
1337
+ refreshUrl: null,
1328
1338
  });
1329
1339
  mockApps.set(appId, {
1330
1340
  id: appId,
1331
- providerKey: service,
1341
+ provider: service,
1332
1342
  clientId: "test-client-id",
1333
1343
  clientSecretCredentialPath: `oauth_app/${appId}/client_secret`,
1334
1344
  });
1335
1345
  mockConnections.set(service, {
1336
1346
  id: connId,
1337
- providerKey: service,
1347
+ provider: service,
1338
1348
  oauthAppId: appId,
1339
1349
  expiresAt: opts?.expired
1340
1350
  ? Date.now() - 60_000 // expired 1 minute ago
@@ -1428,7 +1438,7 @@ describe("withValidToken refresh deduplication", () => {
1428
1438
  let refreshCallCount = 0;
1429
1439
  mockRefreshOAuth2Token.mockImplementation(() => {
1430
1440
  refreshCallCount++;
1431
- // Both services use the same tokenUrl in this test, so we track by
1441
+ // Both services use the same tokenExchangeUrl in this test, so we track by
1432
1442
  // call order to return the correct deferred promise.
1433
1443
  if (refreshCallCount === 1) return gmailPromise;
1434
1444
  return slackPromise;
@@ -1527,4 +1537,133 @@ describe("withValidToken refresh deduplication", () => {
1527
1537
  // Only one actual refresh attempt
1528
1538
  expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
1529
1539
  });
1540
+
1541
+ // -----------------------------------------------------------------------
1542
+ // refreshUrl resolution — provider.refreshUrl with fallback to tokenExchangeUrl
1543
+ // -----------------------------------------------------------------------
1544
+ describe("refreshUrl resolution", () => {
1545
+ test("uses provider.refreshUrl when set", async () => {
1546
+ await setupService("google");
1547
+ mockProviders.get("google")!.refreshUrl =
1548
+ "https://refresh.example.com/token";
1549
+
1550
+ mockRefreshOAuth2Token.mockImplementation(() =>
1551
+ Promise.resolve({
1552
+ accessToken: "new-token-from-refresh-url",
1553
+ expiresIn: 3600,
1554
+ }),
1555
+ );
1556
+
1557
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
1558
+
1559
+ const callback = async (token: string) => {
1560
+ if (token === "old-access-token") throw err401;
1561
+ return `result-with-${token}`;
1562
+ };
1563
+
1564
+ const result = await withValidToken("google", callback);
1565
+
1566
+ expect(result).toBe("result-with-new-token-from-refresh-url");
1567
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
1568
+ // Assert the refresh endpoint passed in is provider.refreshUrl, not
1569
+ // the tokenExchangeUrl fallback.
1570
+ expect(mockRefreshOAuth2Token.mock.calls[0]?.[0]).toBe(
1571
+ "https://refresh.example.com/token",
1572
+ );
1573
+ });
1574
+
1575
+ test("falls back to provider.tokenExchangeUrl when refreshUrl is null", async () => {
1576
+ // setupService sets refreshUrl: null by default — this exercises the
1577
+ // fallback path explicitly.
1578
+ await setupService("google");
1579
+ expect(mockProviders.get("google")!.refreshUrl).toBeNull();
1580
+
1581
+ mockRefreshOAuth2Token.mockImplementation(() =>
1582
+ Promise.resolve({
1583
+ accessToken: "new-token-from-token-exchange-url",
1584
+ expiresIn: 3600,
1585
+ }),
1586
+ );
1587
+
1588
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
1589
+
1590
+ const callback = async (token: string) => {
1591
+ if (token === "old-access-token") throw err401;
1592
+ return `result-with-${token}`;
1593
+ };
1594
+
1595
+ const result = await withValidToken("google", callback);
1596
+
1597
+ expect(result).toBe("result-with-new-token-from-token-exchange-url");
1598
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
1599
+ // Assert the refresh endpoint falls back to tokenExchangeUrl.
1600
+ expect(mockRefreshOAuth2Token.mock.calls[0]?.[0]).toBe(
1601
+ "https://oauth.example.com/token",
1602
+ );
1603
+ });
1604
+
1605
+ test("falls back to provider.tokenExchangeUrl when refreshUrl is undefined", async () => {
1606
+ await setupService("google");
1607
+ // Delete the refreshUrl field entirely so the property is `undefined`
1608
+ // rather than `null`. Both representations of "not set" must produce
1609
+ // the fallback behavior.
1610
+ delete mockProviders.get("google")!.refreshUrl;
1611
+ expect(mockProviders.get("google")!.refreshUrl).toBeUndefined();
1612
+
1613
+ mockRefreshOAuth2Token.mockImplementation(() =>
1614
+ Promise.resolve({
1615
+ accessToken: "new-token-from-token-exchange-url",
1616
+ expiresIn: 3600,
1617
+ }),
1618
+ );
1619
+
1620
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
1621
+
1622
+ const callback = async (token: string) => {
1623
+ if (token === "old-access-token") throw err401;
1624
+ return `result-with-${token}`;
1625
+ };
1626
+
1627
+ const result = await withValidToken("google", callback);
1628
+
1629
+ expect(result).toBe("result-with-new-token-from-token-exchange-url");
1630
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
1631
+ // Assert the refresh endpoint falls back to tokenExchangeUrl.
1632
+ expect(mockRefreshOAuth2Token.mock.calls[0]?.[0]).toBe(
1633
+ "https://oauth.example.com/token",
1634
+ );
1635
+ });
1636
+
1637
+ test("falls back to provider.tokenExchangeUrl when refreshUrl is empty string", async () => {
1638
+ // Platform's Python `oauth_app.refresh_url or oauth_app.token_exchange_url`
1639
+ // treats an empty string as unset. We use `||` (not `??`) so empty
1640
+ // strings follow the same fallback path and never resolve to an empty
1641
+ // endpoint.
1642
+ await setupService("google");
1643
+ mockProviders.get("google")!.refreshUrl = "";
1644
+
1645
+ mockRefreshOAuth2Token.mockImplementation(() =>
1646
+ Promise.resolve({
1647
+ accessToken: "new-token-from-token-exchange-url",
1648
+ expiresIn: 3600,
1649
+ }),
1650
+ );
1651
+
1652
+ const err401 = Object.assign(new Error("Unauthorized"), { status: 401 });
1653
+
1654
+ const callback = async (token: string) => {
1655
+ if (token === "old-access-token") throw err401;
1656
+ return `result-with-${token}`;
1657
+ };
1658
+
1659
+ const result = await withValidToken("google", callback);
1660
+
1661
+ expect(result).toBe("result-with-new-token-from-token-exchange-url");
1662
+ expect(mockRefreshOAuth2Token).toHaveBeenCalledTimes(1);
1663
+ // Assert the refresh endpoint falls back to tokenExchangeUrl — NOT "".
1664
+ expect(mockRefreshOAuth2Token.mock.calls[0]?.[0]).toBe(
1665
+ "https://oauth.example.com/token",
1666
+ );
1667
+ });
1668
+ });
1530
1669
  });
@@ -148,9 +148,9 @@ let disconnectOAuthProviderResult: "disconnected" | "not-found" | "error" =
148
148
 
149
149
  mock.module("../oauth/oauth-store.js", () => ({
150
150
  disconnectOAuthProvider: async (
151
- providerKey: string,
151
+ provider: string,
152
152
  ): Promise<"disconnected" | "not-found" | "error"> => {
153
- disconnectOAuthProviderCalls.push(providerKey);
153
+ disconnectOAuthProviderCalls.push(provider);
154
154
  return disconnectOAuthProviderResult;
155
155
  },
156
156
  getConnectionByProvider: (): undefined => undefined,
@@ -115,7 +115,7 @@ describe("formatTurnTimestamp", () => {
115
115
  timeZone: "America/Chicago",
116
116
  });
117
117
  expect(result).toBe(
118
- "2026-04-02 (Thu) 01:52:33 -05:00 (America/Chicago)",
118
+ "2026-04-02 (Thursday) 01:52:33 -05:00 (America/Chicago)",
119
119
  );
120
120
  });
121
121
 
@@ -124,7 +124,7 @@ describe("formatTurnTimestamp", () => {
124
124
  nowMs: THU_APR_02_0652,
125
125
  hostTimeZone: "UTC",
126
126
  });
127
- expect(result).toBe("2026-04-02 (Thu) 06:52:33 +00:00 (UTC)");
127
+ expect(result).toBe("2026-04-02 (Thursday) 06:52:33 +00:00 (UTC)");
128
128
  });
129
129
 
130
130
  test("handles user timezone override", () => {
@@ -133,7 +133,7 @@ describe("formatTurnTimestamp", () => {
133
133
  hostTimeZone: "UTC",
134
134
  userTimeZone: "Asia/Tokyo",
135
135
  });
136
- expect(result).toBe("2026-04-02 (Thu) 15:52:33 +09:00 (Asia/Tokyo)");
136
+ expect(result).toBe("2026-04-02 (Thursday) 15:52:33 +09:00 (Asia/Tokyo)");
137
137
  });
138
138
 
139
139
  test("handles DST correctly", () => {
@@ -144,7 +144,7 @@ describe("formatTurnTimestamp", () => {
144
144
  timeZone: "America/New_York",
145
145
  });
146
146
  expect(result).toBe(
147
- "2026-07-01 (Wed) 08:00:30 -04:00 (America/New_York)",
147
+ "2026-07-01 (Wednesday) 08:00:30 -04:00 (America/New_York)",
148
148
  );
149
149
  });
150
150
 
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Tests for managed proxy Gemini embedding backend selection.
3
+ *
4
+ * Verifies that selectEmbeddingBackend correctly routes through the
5
+ * managed proxy when the feature flag is enabled and managed proxy
6
+ * prerequisites are satisfied.
7
+ */
8
+
9
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
10
+
11
+ import { credentialKey } from "../security/credential-key.js";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Mocks — must be before importing the module under test
15
+ // ---------------------------------------------------------------------------
16
+
17
+ // Suppress logger output
18
+ mock.module("../util/logger.js", () => ({
19
+ getLogger: () =>
20
+ new Proxy({} as Record<string, unknown>, {
21
+ get: () => () => {},
22
+ }),
23
+ }));
24
+
25
+ // Mutable state for managed proxy context
26
+ let mockPlatformBaseUrl = "";
27
+ let mockAssistantApiKey: string | null = null;
28
+ let mockProviderKeys: Record<string, string | null> = {};
29
+
30
+ mock.module("../config/env.js", () => ({
31
+ getPlatformBaseUrl: () => mockPlatformBaseUrl,
32
+ getOllamaBaseUrlEnv: () => "",
33
+ }));
34
+
35
+ mock.module("../security/secure-keys.js", () => ({
36
+ getSecureKeyAsync: async (key: string) => {
37
+ if (key === credentialKey("vellum", "assistant_api_key")) {
38
+ return mockAssistantApiKey;
39
+ }
40
+ // Provider keys are looked up by plain name
41
+ return mockProviderKeys[key] ?? null;
42
+ },
43
+ }));
44
+
45
+ // Feature flag mock
46
+ const mockFeatureFlags: Record<string, boolean> = {};
47
+
48
+ mock.module("../config/assistant-feature-flags.js", () => ({
49
+ isAssistantFeatureFlagEnabled: (key: string, _config: unknown) => {
50
+ return mockFeatureFlags[key] ?? false;
51
+ },
52
+ }));
53
+
54
+ import type { AssistantConfig } from "../config/types.js";
55
+ import {
56
+ clearEmbeddingBackendCache,
57
+ selectEmbeddingBackend,
58
+ } from "../memory/embedding-backend.js";
59
+ import { GeminiEmbeddingBackend } from "../memory/embedding-gemini.js";
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Helpers
63
+ // ---------------------------------------------------------------------------
64
+
65
+ const PLATFORM_BASE = "https://platform.example.com";
66
+ const MANAGED_API_KEY = "ast-managed-key-123";
67
+
68
+ function enableManagedProxy() {
69
+ mockPlatformBaseUrl = PLATFORM_BASE;
70
+ mockAssistantApiKey = MANAGED_API_KEY;
71
+ }
72
+
73
+ function disableManagedProxy() {
74
+ mockPlatformBaseUrl = "";
75
+ mockAssistantApiKey = null;
76
+ }
77
+
78
+ function enableFlag() {
79
+ mockFeatureFlags["managed-gemini-embeddings-enabled"] = true;
80
+ }
81
+
82
+ function disableFlag() {
83
+ mockFeatureFlags["managed-gemini-embeddings-enabled"] = false;
84
+ }
85
+
86
+ function makeConfig(
87
+ overrides: {
88
+ provider?: string;
89
+ geminiModel?: string;
90
+ geminiDimensions?: number;
91
+ } = {},
92
+ ): AssistantConfig {
93
+ return {
94
+ memory: {
95
+ embeddings: {
96
+ provider: overrides.provider ?? "auto",
97
+ localModel: "Xenova/bge-small-en-v1.5",
98
+ openaiModel: "text-embedding-3-small",
99
+ geminiModel: overrides.geminiModel ?? "gemini-embedding-2-preview",
100
+ geminiDimensions: overrides.geminiDimensions,
101
+ ollamaModel: "nomic-embed-text",
102
+ },
103
+ qdrant: {
104
+ vectorSize: 384,
105
+ },
106
+ },
107
+ services: {
108
+ inference: { provider: "anthropic" },
109
+ },
110
+ } as unknown as AssistantConfig;
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Tests
115
+ // ---------------------------------------------------------------------------
116
+
117
+ beforeEach(() => {
118
+ disableManagedProxy();
119
+ disableFlag();
120
+ mockProviderKeys = {};
121
+ clearEmbeddingBackendCache();
122
+ });
123
+
124
+ afterEach(() => {
125
+ clearEmbeddingBackendCache();
126
+ });
127
+
128
+ describe("managed proxy Gemini embedding selection", () => {
129
+ test("selects managed proxy Gemini when flag enabled and proxy context available", async () => {
130
+ enableManagedProxy();
131
+ enableFlag();
132
+ const config = makeConfig();
133
+
134
+ const { backend, reason } = await selectEmbeddingBackend(config);
135
+
136
+ expect(backend).not.toBeNull();
137
+ expect(backend).toBeInstanceOf(GeminiEmbeddingBackend);
138
+ expect(backend!.provider).toBe("gemini");
139
+ expect(reason).toBeNull();
140
+ });
141
+
142
+ test("managed proxy backend uses default 3072 dimensions when geminiDimensions not set", async () => {
143
+ enableManagedProxy();
144
+ enableFlag();
145
+ const config = makeConfig();
146
+
147
+ const { backend } = await selectEmbeddingBackend(config);
148
+
149
+ expect(backend).toBeInstanceOf(GeminiEmbeddingBackend);
150
+ // Access the private dimensions field to verify default
151
+ const dimensions = (backend as unknown as { dimensions: number })
152
+ .dimensions;
153
+ expect(dimensions).toBe(3072);
154
+ });
155
+
156
+ test("managed proxy backend uses explicit geminiDimensions when set", async () => {
157
+ enableManagedProxy();
158
+ enableFlag();
159
+ const config = makeConfig({ geminiDimensions: 768 });
160
+
161
+ const { backend } = await selectEmbeddingBackend(config);
162
+
163
+ expect(backend).toBeInstanceOf(GeminiEmbeddingBackend);
164
+ const dimensions = (backend as unknown as { dimensions: number })
165
+ .dimensions;
166
+ expect(dimensions).toBe(768);
167
+ });
168
+
169
+ test("managed proxy backend uses managedBaseUrl (not direct Google API)", async () => {
170
+ enableManagedProxy();
171
+ enableFlag();
172
+ const config = makeConfig();
173
+
174
+ const { backend } = await selectEmbeddingBackend(config);
175
+
176
+ expect(backend).toBeInstanceOf(GeminiEmbeddingBackend);
177
+ const managedBaseUrl = (backend as unknown as { managedBaseUrl: string })
178
+ .managedBaseUrl;
179
+ expect(managedBaseUrl).toBe(`${PLATFORM_BASE}/v1/runtime-proxy/gemini`);
180
+ });
181
+
182
+ test("falls back to local when flag is disabled (no managed proxy)", async () => {
183
+ enableManagedProxy();
184
+ disableFlag();
185
+ const config = makeConfig();
186
+
187
+ const { backend } = await selectEmbeddingBackend(config);
188
+
189
+ // With auto and no provider keys, falls through to local
190
+ expect(backend).not.toBeNull();
191
+ expect(backend!.provider).toBe("local");
192
+ });
193
+
194
+ test("falls back to local when managed proxy context unavailable", async () => {
195
+ disableManagedProxy();
196
+ enableFlag();
197
+ const config = makeConfig();
198
+
199
+ const { backend } = await selectEmbeddingBackend(config);
200
+
201
+ expect(backend).not.toBeNull();
202
+ expect(backend!.provider).toBe("local");
203
+ });
204
+
205
+ test("selects managed proxy when provider is explicitly gemini", async () => {
206
+ enableManagedProxy();
207
+ enableFlag();
208
+ const config = makeConfig({ provider: "gemini" });
209
+
210
+ const { backend } = await selectEmbeddingBackend(config);
211
+
212
+ expect(backend).toBeInstanceOf(GeminiEmbeddingBackend);
213
+ const managedBaseUrl = (backend as unknown as { managedBaseUrl: string })
214
+ .managedBaseUrl;
215
+ expect(managedBaseUrl).toContain("/v1/runtime-proxy/gemini");
216
+ });
217
+
218
+ test("does not use managed proxy when provider is explicitly local", async () => {
219
+ enableManagedProxy();
220
+ enableFlag();
221
+ const config = makeConfig({ provider: "local" });
222
+
223
+ const { backend } = await selectEmbeddingBackend(config);
224
+
225
+ expect(backend).not.toBeNull();
226
+ expect(backend!.provider).toBe("local");
227
+ });
228
+
229
+ test("does not use managed proxy when provider is explicitly openai", async () => {
230
+ enableManagedProxy();
231
+ enableFlag();
232
+ mockProviderKeys["openai"] = "user-openai-key";
233
+ const config = makeConfig({ provider: "openai" });
234
+
235
+ const { backend } = await selectEmbeddingBackend(config);
236
+
237
+ expect(backend).not.toBeNull();
238
+ expect(backend!.provider).toBe("openai");
239
+ });
240
+
241
+ test("direct Gemini key still works when flag is off", async () => {
242
+ disableManagedProxy();
243
+ disableFlag();
244
+ mockProviderKeys["gemini"] = "user-gemini-key";
245
+ const config = makeConfig({ provider: "gemini" });
246
+
247
+ const { backend } = await selectEmbeddingBackend(config);
248
+
249
+ expect(backend).toBeInstanceOf(GeminiEmbeddingBackend);
250
+ expect(backend!.provider).toBe("gemini");
251
+ // Should NOT use managed proxy
252
+ const managedBaseUrl = (backend as unknown as { managedBaseUrl?: string })
253
+ .managedBaseUrl;
254
+ expect(managedBaseUrl).toBeUndefined();
255
+ });
256
+ });