@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.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docker-entrypoint.sh +12 -2
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/checker.test.ts +104 -170
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +21 -6
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/log-export-workspace.test.ts +190 -0
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +74 -55
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -3
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +11 -5
- package/src/__tests__/test-preload.ts +14 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +62 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -35
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +55 -0
- package/src/cli/__tests__/run-assistant-command.ts +34 -7
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +16 -2
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +10 -3
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +12 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +46 -7
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +16 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -16
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +6 -2
- package/src/daemon/context-overflow-approval.ts +5 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +74 -19
- package/src/daemon/conversation-attachments.ts +40 -1
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +66 -3
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +159 -20
- package/src/daemon/conversation-surfaces.ts +78 -12
- package/src/daemon/conversation-tool-setup.ts +74 -11
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +227 -11
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -139
- package/src/daemon/handlers/shared.ts +65 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +86 -12
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +59 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -6
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/settings.ts +12 -0
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +112 -35
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +14 -0
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +38 -10
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +65 -5
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +177 -18
- package/src/memory/graph/capability-seed.ts +3 -5
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +5 -5
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +127 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -96
- package/src/prompts/templates/SOUL.md +11 -11
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +24 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +308 -28
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +60 -25
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -100
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -1
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +24 -21
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -3
- package/src/util/platform.ts +14 -19
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/029-seed-pkb.ts +1 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- 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 (
|
|
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 (
|
|
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[
|
|
75
|
+
manualConnectionStore[provider] = "active";
|
|
76
76
|
} else {
|
|
77
|
-
delete manualConnectionStore[
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
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((
|
|
116
|
+
mockDisconnectOAuthProvider = mock((provider: string) =>
|
|
108
117
|
Promise.resolve(
|
|
109
|
-
mockConnections.has(
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
1336
|
+
tokenExchangeUrl: "https://oauth.example.com/token",
|
|
1337
|
+
refreshUrl: null,
|
|
1328
1338
|
});
|
|
1329
1339
|
mockApps.set(appId, {
|
|
1330
1340
|
id: appId,
|
|
1331
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
151
|
+
provider: string,
|
|
152
152
|
): Promise<"disconnected" | "not-found" | "error"> => {
|
|
153
|
-
disconnectOAuthProviderCalls.push(
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
+
});
|