@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
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { AssistantConfig } from "../config/types.js";
|
|
4
|
+
import {
|
|
5
|
+
clearEmbeddingBackendCache,
|
|
6
|
+
resetLocalEmbeddingFailureState,
|
|
7
|
+
selectEmbeddingBackend,
|
|
8
|
+
} from "./embedding-backend.js";
|
|
9
|
+
|
|
10
|
+
const LOCAL_CONFIG = {
|
|
11
|
+
memory: {
|
|
12
|
+
embeddings: {
|
|
13
|
+
provider: "local",
|
|
14
|
+
localModel: "BAAI/bge-small-en-v1.5",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
} as unknown as AssistantConfig;
|
|
18
|
+
|
|
19
|
+
describe("embedding backend cache invalidation", () => {
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
clearEmbeddingBackendCache();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("clearEmbeddingBackendCache disposes cached backends before clearing", async () => {
|
|
25
|
+
const firstSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
|
|
26
|
+
expect(firstSelection.backend).not.toBeNull();
|
|
27
|
+
|
|
28
|
+
const dispose = mock();
|
|
29
|
+
(firstSelection.backend as { dispose?: () => void }).dispose = dispose;
|
|
30
|
+
|
|
31
|
+
clearEmbeddingBackendCache();
|
|
32
|
+
|
|
33
|
+
expect(dispose).toHaveBeenCalledTimes(1);
|
|
34
|
+
|
|
35
|
+
const secondSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
|
|
36
|
+
expect(secondSelection.backend).not.toBe(firstSelection.backend);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("resetLocalEmbeddingFailureState preserves live cached backends", async () => {
|
|
40
|
+
const firstSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
|
|
41
|
+
expect(firstSelection.backend).not.toBeNull();
|
|
42
|
+
|
|
43
|
+
const dispose = mock();
|
|
44
|
+
(firstSelection.backend as { dispose?: () => void }).dispose = dispose;
|
|
45
|
+
|
|
46
|
+
resetLocalEmbeddingFailureState();
|
|
47
|
+
|
|
48
|
+
expect(dispose).not.toHaveBeenCalled();
|
|
49
|
+
|
|
50
|
+
const secondSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
|
|
51
|
+
expect(secondSelection.backend).toBe(firstSelection.backend);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("resetLocalEmbeddingFailureState clears poisoned local backend retry state", async () => {
|
|
55
|
+
const firstSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
|
|
56
|
+
expect(firstSelection.backend).not.toBeNull();
|
|
57
|
+
|
|
58
|
+
const poisonedInitPromise = Promise.reject(new Error("poisoned"));
|
|
59
|
+
poisonedInitPromise.catch(() => {});
|
|
60
|
+
|
|
61
|
+
const backend = firstSelection.backend as unknown as {
|
|
62
|
+
delegate: unknown;
|
|
63
|
+
initPromise: Promise<unknown> | null;
|
|
64
|
+
};
|
|
65
|
+
backend.delegate = null;
|
|
66
|
+
backend.initPromise = poisonedInitPromise;
|
|
67
|
+
|
|
68
|
+
resetLocalEmbeddingFailureState();
|
|
69
|
+
|
|
70
|
+
expect(backend.initPromise).toBeNull();
|
|
71
|
+
|
|
72
|
+
const secondSelection = await selectEmbeddingBackend(LOCAL_CONFIG);
|
|
73
|
+
expect(secondSelection.backend).toBe(firstSelection.backend);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
3
4
|
import { getOllamaBaseUrlEnv } from "../config/env.js";
|
|
4
5
|
import type { AssistantConfig } from "../config/types.js";
|
|
6
|
+
import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
|
|
7
|
+
import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
|
|
5
8
|
import { getProviderKeyAsync } from "../security/secure-keys.js";
|
|
6
9
|
import { getLogger } from "../util/logger.js";
|
|
7
10
|
import { GeminiEmbeddingBackend } from "./embedding-gemini.js";
|
|
@@ -67,6 +70,16 @@ class LazyLocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
dispose(): void {
|
|
74
|
+
this.delegate?.dispose?.();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
resetForRetry(): void {
|
|
78
|
+
if (!this.delegate) {
|
|
79
|
+
this.initPromise = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
private async getDelegate(): Promise<EmbeddingBackend> {
|
|
71
84
|
if (this.delegate) return this.delegate;
|
|
72
85
|
if (!this.initPromise) {
|
|
@@ -176,12 +189,32 @@ function putInVectorCache(
|
|
|
176
189
|
|
|
177
190
|
/** Clear cached embedding backends and the in-memory vector cache. */
|
|
178
191
|
export function clearEmbeddingBackendCache(): void {
|
|
192
|
+
for (const backend of new Set(backendCache.values())) {
|
|
193
|
+
try {
|
|
194
|
+
backend.dispose?.();
|
|
195
|
+
} catch (err) {
|
|
196
|
+
log.warn(
|
|
197
|
+
{ err, provider: backend.provider, model: backend.model },
|
|
198
|
+
"Failed to dispose embedding backend during cache clear",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
179
202
|
backendCache.clear();
|
|
180
203
|
vectorCache.clear();
|
|
181
204
|
vectorCacheBytes = 0;
|
|
182
205
|
localBackendBroken = false;
|
|
183
206
|
}
|
|
184
207
|
|
|
208
|
+
/** Reset the sticky local-backend failure flag without evicting live backends. */
|
|
209
|
+
export function resetLocalEmbeddingFailureState(): void {
|
|
210
|
+
localBackendBroken = false;
|
|
211
|
+
for (const backend of new Set(backendCache.values())) {
|
|
212
|
+
if (backend instanceof LazyLocalEmbeddingBackend) {
|
|
213
|
+
backend.resetForRetry();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
185
218
|
function cacheKey(provider: string, model: string, extras?: string[]): string {
|
|
186
219
|
if (extras && extras.length > 0) {
|
|
187
220
|
return `${provider}:${model}:${extras.join(":")}`;
|
|
@@ -243,6 +276,7 @@ export interface EmbeddingBackend {
|
|
|
243
276
|
inputs: EmbeddingInput[],
|
|
244
277
|
options?: EmbeddingRequestOptions,
|
|
245
278
|
): Promise<number[][]>;
|
|
279
|
+
dispose?(): void;
|
|
246
280
|
}
|
|
247
281
|
|
|
248
282
|
export interface EmbeddingBackendSelection {
|
|
@@ -280,6 +314,44 @@ export async function selectEmbeddingBackend(
|
|
|
280
314
|
};
|
|
281
315
|
}
|
|
282
316
|
|
|
317
|
+
// When the managed-gemini-embeddings-enabled flag is on AND managed proxy
|
|
318
|
+
// prerequisites are satisfied, insert managed-proxy Gemini at the front of
|
|
319
|
+
// the auto chain so platform assistants use Vellum-managed Gemini embeddings.
|
|
320
|
+
if (
|
|
321
|
+
(requested === "auto" || requested === "gemini") &&
|
|
322
|
+
isAssistantFeatureFlagEnabled("managed-gemini-embeddings-enabled", config)
|
|
323
|
+
) {
|
|
324
|
+
const proxyCtx = await resolveManagedProxyContext();
|
|
325
|
+
if (proxyCtx.enabled) {
|
|
326
|
+
const meta = MANAGED_PROVIDER_META["gemini"];
|
|
327
|
+
if (meta?.managed && meta.proxyPath) {
|
|
328
|
+
const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
|
|
329
|
+
const managedModel = config.memory.embeddings.geminiModel;
|
|
330
|
+
const managedDimensions =
|
|
331
|
+
config.memory.embeddings.geminiDimensions ?? 3072;
|
|
332
|
+
const extras = geminiCacheExtras(config);
|
|
333
|
+
return {
|
|
334
|
+
backend: getCachedOrCreate(
|
|
335
|
+
"gemini",
|
|
336
|
+
managedModel,
|
|
337
|
+
() =>
|
|
338
|
+
new GeminiEmbeddingBackend(
|
|
339
|
+
proxyCtx.assistantApiKey,
|
|
340
|
+
managedModel,
|
|
341
|
+
{
|
|
342
|
+
taskType: config.memory.embeddings.geminiTaskType,
|
|
343
|
+
dimensions: managedDimensions,
|
|
344
|
+
managedBaseUrl,
|
|
345
|
+
},
|
|
346
|
+
),
|
|
347
|
+
[...extras, "managed"],
|
|
348
|
+
),
|
|
349
|
+
reason: null,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
283
355
|
// Auto order: local → openai → gemini → ollama
|
|
284
356
|
const order: EmbeddingProviderName[] =
|
|
285
357
|
requested === "auto"
|
|
@@ -329,11 +401,18 @@ export async function selectEmbeddingBackend(
|
|
|
329
401
|
case "gemini": {
|
|
330
402
|
const geminiKey = await getProviderKeyAsync("gemini");
|
|
331
403
|
if (!geminiKey) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
404
|
+
// Check managed cache variant first so a warm managed backend
|
|
405
|
+
// survives transient proxy-context blips, then non-managed.
|
|
406
|
+
const cached =
|
|
407
|
+
getCached("gemini", config.memory.embeddings.geminiModel, [
|
|
408
|
+
...geminiCacheExtras(config),
|
|
409
|
+
"managed",
|
|
410
|
+
]) ??
|
|
411
|
+
getCached(
|
|
412
|
+
"gemini",
|
|
413
|
+
config.memory.embeddings.geminiModel,
|
|
414
|
+
geminiCacheExtras(config),
|
|
415
|
+
);
|
|
337
416
|
if (cached) return { backend: cached, reason: null };
|
|
338
417
|
continue;
|
|
339
418
|
}
|
|
@@ -614,6 +693,53 @@ async function selectFallbackBackends(
|
|
|
614
693
|
geminiCacheExtras(config),
|
|
615
694
|
),
|
|
616
695
|
);
|
|
696
|
+
} else if (
|
|
697
|
+
isAssistantFeatureFlagEnabled(
|
|
698
|
+
"managed-gemini-embeddings-enabled",
|
|
699
|
+
config,
|
|
700
|
+
)
|
|
701
|
+
) {
|
|
702
|
+
// Try managed proxy Gemini as fallback when no direct key exists.
|
|
703
|
+
const proxyCtx = await resolveManagedProxyContext();
|
|
704
|
+
const meta = MANAGED_PROVIDER_META["gemini"];
|
|
705
|
+
if (proxyCtx.enabled && meta?.managed && meta.proxyPath) {
|
|
706
|
+
const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
|
|
707
|
+
const managedModel = config.memory.embeddings.geminiModel;
|
|
708
|
+
const managedDimensions =
|
|
709
|
+
config.memory.embeddings.geminiDimensions ?? 3072;
|
|
710
|
+
const extras = geminiCacheExtras(config);
|
|
711
|
+
backends.push(
|
|
712
|
+
getCachedOrCreate(
|
|
713
|
+
"gemini",
|
|
714
|
+
managedModel,
|
|
715
|
+
() =>
|
|
716
|
+
new GeminiEmbeddingBackend(
|
|
717
|
+
proxyCtx.assistantApiKey,
|
|
718
|
+
managedModel,
|
|
719
|
+
{
|
|
720
|
+
taskType: config.memory.embeddings.geminiTaskType,
|
|
721
|
+
dimensions: managedDimensions,
|
|
722
|
+
managedBaseUrl,
|
|
723
|
+
},
|
|
724
|
+
),
|
|
725
|
+
[...extras, "managed"],
|
|
726
|
+
),
|
|
727
|
+
);
|
|
728
|
+
} else {
|
|
729
|
+
// Check managed cache variant first, then non-managed, so a warm
|
|
730
|
+
// managed backend survives transient proxy-context blips.
|
|
731
|
+
const cached =
|
|
732
|
+
getCached("gemini", config.memory.embeddings.geminiModel, [
|
|
733
|
+
...geminiCacheExtras(config),
|
|
734
|
+
"managed",
|
|
735
|
+
]) ??
|
|
736
|
+
getCached(
|
|
737
|
+
"gemini",
|
|
738
|
+
config.memory.embeddings.geminiModel,
|
|
739
|
+
geminiCacheExtras(config),
|
|
740
|
+
);
|
|
741
|
+
if (cached) backends.push(cached);
|
|
742
|
+
}
|
|
617
743
|
} else {
|
|
618
744
|
// Preserve cached backend on transient credential-store failures.
|
|
619
745
|
const cached = getCached(
|
|
@@ -253,4 +253,58 @@ describe("GeminiEmbeddingBackend", () => {
|
|
|
253
253
|
expect(result[1]).toEqual([0.2, 0.4]);
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
|
+
|
|
257
|
+
describe("managed proxy transport", () => {
|
|
258
|
+
test("routes through managed proxy base URL when managedBaseUrl is set", async () => {
|
|
259
|
+
const backend = new GeminiEmbeddingBackend(
|
|
260
|
+
"ast-managed-key",
|
|
261
|
+
"gemini-embedding-2-preview",
|
|
262
|
+
{
|
|
263
|
+
managedBaseUrl:
|
|
264
|
+
"https://platform.example.com/v1/runtime-proxy/gemini",
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
|
+
await backend.embed(["hello"]);
|
|
268
|
+
|
|
269
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
270
|
+
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
271
|
+
expect(url).toBe(
|
|
272
|
+
"https://platform.example.com/v1/runtime-proxy/gemini/v1beta/models/gemini-embedding-2-preview:embedContent",
|
|
273
|
+
);
|
|
274
|
+
// Should NOT have key= query param
|
|
275
|
+
expect(url).not.toContain("key=");
|
|
276
|
+
// Should have Bearer auth header
|
|
277
|
+
const headers = init.headers as Record<string, string>;
|
|
278
|
+
expect(headers["Authorization"]).toBe("Bearer ast-managed-key");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("uses direct Google API URL when managedBaseUrl is not set", async () => {
|
|
282
|
+
const backend = new GeminiEmbeddingBackend("direct-key", "test-model");
|
|
283
|
+
await backend.embed(["hello"]);
|
|
284
|
+
|
|
285
|
+
const [url, init] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
286
|
+
expect(url).toContain("generativelanguage.googleapis.com");
|
|
287
|
+
expect(url).toContain("key=direct-key");
|
|
288
|
+
// Should NOT have Authorization header
|
|
289
|
+
const headers = init.headers as Record<string, string>;
|
|
290
|
+
expect(headers["Authorization"]).toBeUndefined();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("includes outputDimensionality with managed proxy", async () => {
|
|
294
|
+
const backend = new GeminiEmbeddingBackend(
|
|
295
|
+
"ast-managed-key",
|
|
296
|
+
"gemini-embedding-2-preview",
|
|
297
|
+
{
|
|
298
|
+
managedBaseUrl:
|
|
299
|
+
"https://platform.example.com/v1/runtime-proxy/gemini",
|
|
300
|
+
dimensions: 3072,
|
|
301
|
+
},
|
|
302
|
+
);
|
|
303
|
+
await backend.embed(["hello"]);
|
|
304
|
+
|
|
305
|
+
const [, init] = mockFetch.mock.calls[0] as [string, RequestInit];
|
|
306
|
+
const body = JSON.parse(init.body as string);
|
|
307
|
+
expect(body.outputDimensionality).toBe(3072);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
256
310
|
});
|
|
@@ -15,22 +15,27 @@ interface GeminiEmbedResponse {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export interface GeminiEmbeddingOptions {
|
|
19
|
+
taskType?: EmbeddingTaskType;
|
|
20
|
+
dimensions?: number;
|
|
21
|
+
/** When set, routes requests through the managed proxy at this base URL. */
|
|
22
|
+
managedBaseUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
export class GeminiEmbeddingBackend implements EmbeddingBackend {
|
|
19
26
|
readonly provider = "gemini" as const;
|
|
20
27
|
readonly model: string;
|
|
21
28
|
private readonly apiKey: string;
|
|
22
29
|
private readonly taskType?: EmbeddingTaskType;
|
|
23
30
|
private readonly dimensions?: number;
|
|
31
|
+
private readonly managedBaseUrl?: string;
|
|
24
32
|
|
|
25
|
-
constructor(
|
|
26
|
-
apiKey: string,
|
|
27
|
-
model: string,
|
|
28
|
-
options?: { taskType?: EmbeddingTaskType; dimensions?: number },
|
|
29
|
-
) {
|
|
33
|
+
constructor(apiKey: string, model: string, options?: GeminiEmbeddingOptions) {
|
|
30
34
|
this.apiKey = apiKey;
|
|
31
35
|
this.model = model;
|
|
32
36
|
this.taskType = options?.taskType;
|
|
33
37
|
this.dimensions = options?.dimensions;
|
|
38
|
+
this.managedBaseUrl = options?.managedBaseUrl;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
async embed(
|
|
@@ -59,12 +64,18 @@ export class GeminiEmbeddingBackend implements EmbeddingBackend {
|
|
|
59
64
|
if (this.taskType) body.taskType = this.taskType;
|
|
60
65
|
if (this.dimensions) body.outputDimensionality = this.dimensions;
|
|
61
66
|
|
|
62
|
-
const url =
|
|
63
|
-
this.model
|
|
64
|
-
|
|
67
|
+
const url = this.managedBaseUrl
|
|
68
|
+
? `${this.managedBaseUrl}/v1beta/models/${encodeURIComponent(this.model)}:embedContent`
|
|
69
|
+
: `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(this.model)}:embedContent?key=${encodeURIComponent(this.apiKey)}`;
|
|
70
|
+
const headers: Record<string, string> = {
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
};
|
|
73
|
+
if (this.managedBaseUrl) {
|
|
74
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
75
|
+
}
|
|
65
76
|
const response = await fetch(url, {
|
|
66
77
|
method: "POST",
|
|
67
|
-
headers
|
|
78
|
+
headers,
|
|
68
79
|
body: JSON.stringify(body),
|
|
69
80
|
signal: options?.signal,
|
|
70
81
|
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
rmSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { join } from "node:path";
|
|
3
9
|
|
|
4
10
|
import { getIsContainerized } from "../config/env-registry.js";
|
|
@@ -83,6 +89,8 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
83
89
|
}
|
|
84
90
|
>();
|
|
85
91
|
private stdoutReaderActive = false;
|
|
92
|
+
private activeEmbeds = 0;
|
|
93
|
+
private disposeRequested = false;
|
|
86
94
|
|
|
87
95
|
private readonly initGuard = new PromiseGuard<void>();
|
|
88
96
|
|
|
@@ -94,6 +102,9 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
94
102
|
inputs: EmbeddingInput[],
|
|
95
103
|
options?: EmbeddingRequestOptions,
|
|
96
104
|
): Promise<number[][]> {
|
|
105
|
+
if (this.disposeRequested) {
|
|
106
|
+
throw new Error("Local embedding backend is shutting down");
|
|
107
|
+
}
|
|
97
108
|
if (inputs.length === 0) return [];
|
|
98
109
|
|
|
99
110
|
const texts = inputs.map((i) => {
|
|
@@ -106,24 +117,30 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
106
117
|
if (options?.signal?.aborted)
|
|
107
118
|
throw new DOMException("Aborted", "AbortError");
|
|
108
119
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
this.activeEmbeds++;
|
|
121
|
+
try {
|
|
122
|
+
await this.ensureInitialized();
|
|
123
|
+
|
|
124
|
+
const results: number[][] = [];
|
|
125
|
+
const batchSize = 32;
|
|
126
|
+
for (let i = 0; i < texts.length; i += batchSize) {
|
|
127
|
+
if (options?.signal?.aborted)
|
|
128
|
+
throw new DOMException("Aborted", "AbortError");
|
|
129
|
+
const batch = texts.slice(i, i + batchSize);
|
|
130
|
+
const response = await this.sendRequest(batch);
|
|
131
|
+
if (response.error) {
|
|
132
|
+
throw new Error(`Embedding worker error: ${response.error}`);
|
|
133
|
+
}
|
|
134
|
+
if (!response.vectors) {
|
|
135
|
+
throw new Error("Embedding worker returned no vectors");
|
|
136
|
+
}
|
|
137
|
+
results.push(...response.vectors);
|
|
123
138
|
}
|
|
124
|
-
results
|
|
139
|
+
return results;
|
|
140
|
+
} finally {
|
|
141
|
+
this.activeEmbeds--;
|
|
142
|
+
this.disposeIfIdle();
|
|
125
143
|
}
|
|
126
|
-
return results;
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
private sendRequest(texts: string[]): Promise<WorkerResponse> {
|
|
@@ -148,6 +165,11 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
148
165
|
await this.initGuard.run(() => this.initialize());
|
|
149
166
|
}
|
|
150
167
|
|
|
168
|
+
dispose(): void {
|
|
169
|
+
this.disposeRequested = true;
|
|
170
|
+
this.disposeIfIdle();
|
|
171
|
+
}
|
|
172
|
+
|
|
151
173
|
private async initialize(): Promise<void> {
|
|
152
174
|
log.info({ model: this.model }, "Initializing local embedding backend");
|
|
153
175
|
|
|
@@ -199,13 +221,19 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
199
221
|
const embeddingModelsDir = getEmbeddingModelsDir();
|
|
200
222
|
const modelCacheDir = `${embeddingModelsDir}/model-cache`;
|
|
201
223
|
|
|
224
|
+
// Singleton guard: an orphaned embed worker from a previous daemon
|
|
225
|
+
// (e.g. one that crashed without cleanup) may still be running and
|
|
226
|
+
// holding the workspace's PID file. Detect and reclaim it before
|
|
227
|
+
// spawning so we never leave duplicate workers eating CPU/memory.
|
|
228
|
+
this.reclaimStaleWorker(workerPath);
|
|
229
|
+
|
|
202
230
|
log.info(
|
|
203
231
|
{ bunPath, workerPath, model: this.model },
|
|
204
232
|
"Spawning embedding worker process",
|
|
205
233
|
);
|
|
206
234
|
|
|
207
235
|
const proc = Bun.spawn({
|
|
208
|
-
cmd: [bunPath, workerPath, this.model, modelCacheDir],
|
|
236
|
+
cmd: [bunPath, "--smol", workerPath, this.model, modelCacheDir],
|
|
209
237
|
stdin: "pipe",
|
|
210
238
|
stdout: "pipe",
|
|
211
239
|
stderr: "pipe",
|
|
@@ -255,6 +283,8 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
255
283
|
{ pid: proc.pid, model: this.model },
|
|
256
284
|
"Embedding worker process started",
|
|
257
285
|
);
|
|
286
|
+
|
|
287
|
+
this.disposeIfIdle();
|
|
258
288
|
}
|
|
259
289
|
|
|
260
290
|
private drainStderr(stderr: ReadableStream<Uint8Array>): void {
|
|
@@ -355,6 +385,7 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
355
385
|
if (pending) {
|
|
356
386
|
this.pendingRequests.delete(msg.id);
|
|
357
387
|
pending.resolve(msg);
|
|
388
|
+
this.disposeIfIdle();
|
|
358
389
|
}
|
|
359
390
|
}
|
|
360
391
|
}
|
|
@@ -425,4 +456,132 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
|
|
|
425
456
|
// Best-effort
|
|
426
457
|
}
|
|
427
458
|
}
|
|
459
|
+
|
|
460
|
+
/** Read the PID from the on-disk PID file, or null if missing/invalid. */
|
|
461
|
+
private readPidFile(): number | null {
|
|
462
|
+
const path = this.getPidFilePath();
|
|
463
|
+
if (!existsSync(path)) return null;
|
|
464
|
+
try {
|
|
465
|
+
const pid = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
466
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
467
|
+
} catch {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Verify a PID belongs to this workspace's embed worker before sending
|
|
474
|
+
* signals — defends against PID reuse killing an unrelated process if the
|
|
475
|
+
* original worker exited and the OS recycled the PID.
|
|
476
|
+
*
|
|
477
|
+
* Matching `embed-worker` alone would also match a sibling assistant
|
|
478
|
+
* instance's worker (different VELLUM_WORKSPACE_DIR), so we match against
|
|
479
|
+
* the absolute worker script path, which lives under THIS workspace's
|
|
480
|
+
* embedding-models directory and is therefore unique per instance.
|
|
481
|
+
*/
|
|
482
|
+
private isOurEmbedWorker(pid: number, workerPath: string): boolean {
|
|
483
|
+
try {
|
|
484
|
+
// `-ww` disables column-width truncation. Without it, macOS `ps` clips
|
|
485
|
+
// the command field to the terminal width, which can cut off the
|
|
486
|
+
// workerPath argument and cause this check to spuriously return false
|
|
487
|
+
// for genuine orphans. Same flag is used by daemon-control.ts:123 for
|
|
488
|
+
// exactly this reason.
|
|
489
|
+
const result = Bun.spawnSync({
|
|
490
|
+
cmd: ["ps", "-ww", "-p", String(pid), "-o", "command="],
|
|
491
|
+
stdout: "pipe",
|
|
492
|
+
stderr: "ignore",
|
|
493
|
+
});
|
|
494
|
+
if (result.exitCode !== 0) return false;
|
|
495
|
+
const cmd = new TextDecoder().decode(result.stdout).trim();
|
|
496
|
+
if (!cmd) return false;
|
|
497
|
+
return cmd.includes(workerPath);
|
|
498
|
+
} catch {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* If a previous embed worker is still running for this workspace (orphaned
|
|
505
|
+
* by a crashed daemon, for example), terminate it before spawning a new one
|
|
506
|
+
* so we never end up with duplicate workers competing for the same workspace.
|
|
507
|
+
*
|
|
508
|
+
* Stale PID files (process no longer exists) are silently cleaned up.
|
|
509
|
+
* PIDs that have been recycled to unrelated processes — including embed
|
|
510
|
+
* workers belonging to *other* assistant instances — are left untouched.
|
|
511
|
+
*/
|
|
512
|
+
private reclaimStaleWorker(workerPath: string): void {
|
|
513
|
+
const pid = this.readPidFile();
|
|
514
|
+
if (pid == null) return;
|
|
515
|
+
|
|
516
|
+
// Never signal ourselves — should not happen since the worker is a child
|
|
517
|
+
// process, but guard against logic bugs that would deadlock the daemon.
|
|
518
|
+
if (pid === process.pid) {
|
|
519
|
+
this.removePidFile();
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
let isAlive = false;
|
|
524
|
+
try {
|
|
525
|
+
// Signal 0 just probes for liveness without delivering a signal.
|
|
526
|
+
process.kill(pid, 0);
|
|
527
|
+
isAlive = true;
|
|
528
|
+
} catch {
|
|
529
|
+
// ESRCH — no such process. PID file is stale.
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (!isAlive) {
|
|
533
|
+
log.info(
|
|
534
|
+
{ pid, model: this.model },
|
|
535
|
+
"Removing stale embed worker PID file (process no longer exists)",
|
|
536
|
+
);
|
|
537
|
+
this.removePidFile();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (!this.isOurEmbedWorker(pid, workerPath)) {
|
|
542
|
+
// PID points to something that isn't this workspace's embed worker —
|
|
543
|
+
// either an unrelated process (PID reuse after the original worker
|
|
544
|
+
// exited) or another assistant instance's worker. Either way, don't
|
|
545
|
+
// signal it; just drop the stale file so the new worker can claim it.
|
|
546
|
+
log.warn(
|
|
547
|
+
{ pid, model: this.model },
|
|
548
|
+
"PID file points to a process that is not this workspace's embed worker; clearing without killing",
|
|
549
|
+
);
|
|
550
|
+
this.removePidFile();
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
log.warn(
|
|
555
|
+
{ pid, model: this.model },
|
|
556
|
+
"Found orphaned embed worker from a previous daemon, terminating it",
|
|
557
|
+
);
|
|
558
|
+
try {
|
|
559
|
+
process.kill(pid, "SIGTERM");
|
|
560
|
+
} catch {
|
|
561
|
+
// Race: it exited between the liveness check and the kill — fine.
|
|
562
|
+
}
|
|
563
|
+
this.removePidFile();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private disposeIfIdle(): void {
|
|
567
|
+
if (!this.disposeRequested) return;
|
|
568
|
+
if (this.activeEmbeds > 0) return;
|
|
569
|
+
if (this.pendingRequests.size > 0) return;
|
|
570
|
+
if (this.readyResolve || this.readyReject) return;
|
|
571
|
+
|
|
572
|
+
const proc = this.workerProc;
|
|
573
|
+
this.workerProc = null;
|
|
574
|
+
this.stdoutReaderActive = false;
|
|
575
|
+
this.stdoutBuffer = "";
|
|
576
|
+
this.initGuard.reset();
|
|
577
|
+
this.removePidFile();
|
|
578
|
+
|
|
579
|
+
if (!proc) return;
|
|
580
|
+
|
|
581
|
+
try {
|
|
582
|
+
proc.kill();
|
|
583
|
+
} catch {
|
|
584
|
+
// Worker may already be exiting
|
|
585
|
+
}
|
|
586
|
+
}
|
|
428
587
|
}
|
|
@@ -127,9 +127,7 @@ export function seedSkillGraphNodes(): void {
|
|
|
127
127
|
// skip catalog-based pruning to avoid incorrectly marking valid
|
|
128
128
|
// uninstalled catalog nodes as gone. But still prune locally disabled
|
|
129
129
|
// skills so stale capability nodes don't linger after cold start.
|
|
130
|
-
log.info(
|
|
131
|
-
"Catalog cache is cold — pruning only locally disabled skills",
|
|
132
|
-
);
|
|
130
|
+
log.info("Catalog cache is cold — pruning only locally disabled skills");
|
|
133
131
|
const disabled = resolved.filter((r) => r.state !== "enabled");
|
|
134
132
|
for (const { summary } of disabled) {
|
|
135
133
|
deleteSkillCapabilityNode(summary.id);
|
|
@@ -158,9 +156,9 @@ export function seedSkillGraphNodes(): void {
|
|
|
158
156
|
* Seed graph nodes for all CLI commands.
|
|
159
157
|
* Prunes stale nodes whose commands are no longer registered.
|
|
160
158
|
*/
|
|
161
|
-
export function seedCliGraphNodes(): void {
|
|
159
|
+
export async function seedCliGraphNodes(): Promise<void> {
|
|
162
160
|
try {
|
|
163
|
-
const program = buildCliProgram();
|
|
161
|
+
const program = await buildCliProgram();
|
|
164
162
|
|
|
165
163
|
const seenKeys = new Set<string>();
|
|
166
164
|
for (const cmd of program.commands) {
|