@vellumai/assistant 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- 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__/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__/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 +16 -1
- 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 +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -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 -1
- 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__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- 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__/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 +75 -57
- 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__/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 -1
- 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-tools.test.ts +9 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- 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 +14 -29
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -6
- 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 +53 -3
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- 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 +44 -44
- 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 +6 -3
- 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/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 +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- 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 +1 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- 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 +44 -5
- 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 +8 -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 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +58 -24
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-process.ts +48 -1
- package/src/daemon/conversation-runtime-assembly.ts +118 -36
- package/src/daemon/conversation-surfaces.ts +37 -36
- package/src/daemon/conversation-tool-setup.ts +74 -8
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +226 -8
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -140
- package/src/daemon/handlers/shared.ts +58 -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 +65 -11
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +55 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -5
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +92 -12
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +23 -0
- 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 +176 -17
- 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/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/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 +3 -3
- 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 +126 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- 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 +2 -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/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 +2 -1
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +301 -27
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- 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-routes.ts +42 -22
- 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/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 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- 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/shell.ts +21 -16
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -0
- package/src/util/platform.ts +14 -19
- 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,103 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
4
|
+
import type { ToolContext } from "../tools/types.js";
|
|
5
|
+
|
|
6
|
+
const hostAccessByConversation = new Map<string, boolean>();
|
|
7
|
+
|
|
8
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
9
|
+
getConversationHostAccess: (conversationId: string) =>
|
|
10
|
+
hostAccessByConversation.get(conversationId) ?? false,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
function makeContext(overrides?: Partial<ToolContext>): ToolContext {
|
|
14
|
+
return {
|
|
15
|
+
workingDir: "/tmp/test",
|
|
16
|
+
conversationId: "test-conv",
|
|
17
|
+
trustClass: "guardian",
|
|
18
|
+
isInteractive: true,
|
|
19
|
+
...overrides,
|
|
20
|
+
} as ToolContext;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
_setOverridesForTesting({});
|
|
25
|
+
hostAccessByConversation.clear();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
_setOverridesForTesting({});
|
|
30
|
+
hostAccessByConversation.clear();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("v2-consent-policy", () => {
|
|
34
|
+
test("returns legacy when the flag is disabled", async () => {
|
|
35
|
+
const { evaluateV2ConsentDisposition } =
|
|
36
|
+
await import("../permissions/v2-consent-policy.js");
|
|
37
|
+
|
|
38
|
+
expect(evaluateV2ConsentDisposition("host_bash", {}, makeContext())).toBe(
|
|
39
|
+
"legacy",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("auto-allows non-host tools when the flag is enabled", async () => {
|
|
44
|
+
_setOverridesForTesting({ "permission-controls-v2": true });
|
|
45
|
+
const { evaluateV2ConsentDisposition } =
|
|
46
|
+
await import("../permissions/v2-consent-policy.js");
|
|
47
|
+
|
|
48
|
+
expect(evaluateV2ConsentDisposition("bash", {}, makeContext())).toBe(
|
|
49
|
+
"auto_allow",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("uses conversation-scoped host access when the flag is enabled", async () => {
|
|
54
|
+
_setOverridesForTesting({ "permission-controls-v2": true });
|
|
55
|
+
hostAccessByConversation.set("allowed-conv", true);
|
|
56
|
+
hostAccessByConversation.set("blocked-conv", false);
|
|
57
|
+
const { evaluateV2ConsentDisposition } =
|
|
58
|
+
await import("../permissions/v2-consent-policy.js");
|
|
59
|
+
|
|
60
|
+
expect(
|
|
61
|
+
evaluateV2ConsentDisposition(
|
|
62
|
+
"host_bash",
|
|
63
|
+
{},
|
|
64
|
+
makeContext({ conversationId: "allowed-conv" }),
|
|
65
|
+
),
|
|
66
|
+
).toBe("auto_allow");
|
|
67
|
+
expect(
|
|
68
|
+
evaluateV2ConsentDisposition(
|
|
69
|
+
"host_bash",
|
|
70
|
+
{},
|
|
71
|
+
makeContext({ conversationId: "blocked-conv" }),
|
|
72
|
+
),
|
|
73
|
+
).toBe("prompt_host_access");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("host-access prompts are identified by the stripped-down prompt shape", async () => {
|
|
77
|
+
_setOverridesForTesting({ "permission-controls-v2": true });
|
|
78
|
+
const {
|
|
79
|
+
CONVERSATION_HOST_ACCESS_PROMPT,
|
|
80
|
+
isConversationHostAccessEnablePrompt,
|
|
81
|
+
} = await import("../permissions/v2-consent-policy.js");
|
|
82
|
+
|
|
83
|
+
expect(
|
|
84
|
+
isConversationHostAccessEnablePrompt({
|
|
85
|
+
toolName: "host_bash",
|
|
86
|
+
...CONVERSATION_HOST_ACCESS_PROMPT,
|
|
87
|
+
}),
|
|
88
|
+
).toBe(true);
|
|
89
|
+
expect(
|
|
90
|
+
isConversationHostAccessEnablePrompt({
|
|
91
|
+
toolName: "host_bash",
|
|
92
|
+
...CONVERSATION_HOST_ACCESS_PROMPT,
|
|
93
|
+
allowlistOptions: [
|
|
94
|
+
{
|
|
95
|
+
label: "Specific command",
|
|
96
|
+
description: "legacy rule",
|
|
97
|
+
pattern: "bash:*",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
}),
|
|
101
|
+
).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
|
|
33
33
|
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
34
34
|
import type { UserDecision } from "../permissions/types.js";
|
|
35
|
+
import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
|
|
35
36
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
36
37
|
import { getLogger } from "../util/logger.js";
|
|
37
38
|
|
|
@@ -184,6 +185,15 @@ export class VellumAcpClientHandler implements Client {
|
|
|
184
185
|
kind: opt.kind,
|
|
185
186
|
}));
|
|
186
187
|
|
|
188
|
+
if (isPermissionControlsV2Enabled()) {
|
|
189
|
+
const allowOptionId = findAllowOptionId(options);
|
|
190
|
+
return {
|
|
191
|
+
outcome: allowOptionId
|
|
192
|
+
? { outcome: "selected", optionId: allowOptionId }
|
|
193
|
+
: { outcome: "cancelled" },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
187
197
|
// Send the confirmation_request first — this triggers makeEventSender
|
|
188
198
|
// which registers a normal "confirmation" entry in pendingInteractions.
|
|
189
199
|
this.sendToVellum({
|
|
@@ -422,15 +432,31 @@ function mapDecisionToOptionId(
|
|
|
422
432
|
const alwaysDeny = options.find((o) => o.kind === "reject_always");
|
|
423
433
|
if (alwaysDeny) return alwaysDeny.optionId;
|
|
424
434
|
}
|
|
425
|
-
const denyOpt =
|
|
426
|
-
|
|
427
|
-
options.find((o) => o.kind === "reject_always");
|
|
428
|
-
if (denyOpt) return denyOpt.optionId;
|
|
435
|
+
const denyOpt = findRejectOptionId(options);
|
|
436
|
+
if (denyOpt) return denyOpt;
|
|
429
437
|
|
|
430
438
|
// Fallback: return first option
|
|
431
439
|
return options[0]?.optionId ?? "deny";
|
|
432
440
|
}
|
|
433
441
|
|
|
442
|
+
function findRejectOptionId(
|
|
443
|
+
options: Array<{ optionId: string; kind: string }>,
|
|
444
|
+
): string | undefined {
|
|
445
|
+
return (
|
|
446
|
+
options.find((o) => o.kind === "reject_once")?.optionId ??
|
|
447
|
+
options.find((o) => o.kind === "reject_always")?.optionId
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function findAllowOptionId(
|
|
452
|
+
options: Array<{ optionId: string; kind: string }>,
|
|
453
|
+
): string | undefined {
|
|
454
|
+
return (
|
|
455
|
+
options.find((o) => o.kind === "allow_once")?.optionId ??
|
|
456
|
+
options.find((o) => o.kind === "allow_always")?.optionId
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
434
460
|
/**
|
|
435
461
|
* Extracts text from a ContentBlock.
|
|
436
462
|
*/
|
package/src/agent/loop.ts
CHANGED
|
@@ -82,7 +82,12 @@ export type AgentEvent =
|
|
|
82
82
|
toolUseId: string;
|
|
83
83
|
input: Record<string, unknown>;
|
|
84
84
|
}
|
|
85
|
-
| {
|
|
85
|
+
| {
|
|
86
|
+
type: "server_tool_complete";
|
|
87
|
+
toolUseId: string;
|
|
88
|
+
isError: boolean;
|
|
89
|
+
content?: unknown[];
|
|
90
|
+
}
|
|
86
91
|
| { type: "error"; error: Error }
|
|
87
92
|
| {
|
|
88
93
|
type: "usage";
|
|
@@ -98,7 +103,7 @@ export type AgentEvent =
|
|
|
98
103
|
};
|
|
99
104
|
|
|
100
105
|
const DEFAULT_CONFIG: AgentLoopConfig = {
|
|
101
|
-
maxTokens:
|
|
106
|
+
maxTokens: 64000,
|
|
102
107
|
effort: "high",
|
|
103
108
|
minTurnIntervalMs: 150,
|
|
104
109
|
};
|
|
@@ -344,6 +349,7 @@ export class AgentLoop {
|
|
|
344
349
|
type: "server_tool_complete",
|
|
345
350
|
toolUseId: event.toolUseId,
|
|
346
351
|
isError: event.isError,
|
|
352
|
+
...(event.content ? { content: event.content } : {}),
|
|
347
353
|
});
|
|
348
354
|
}
|
|
349
355
|
},
|
|
@@ -709,10 +715,10 @@ export function compactAxTreeHistory(messages: Message[]): Message[] {
|
|
|
709
715
|
* once by the LLM on the turn it was captured, then replaced with a text
|
|
710
716
|
* placeholder on subsequent turns.
|
|
711
717
|
*
|
|
712
|
-
* We
|
|
713
|
-
* message) because
|
|
714
|
-
*
|
|
715
|
-
*
|
|
718
|
+
* We target the last user message with tool_results (not just the last user
|
|
719
|
+
* message) because a plain-text user message may follow the tool-result
|
|
720
|
+
* turn. Using the last user message unconditionally would leave the most
|
|
721
|
+
* recent tool screenshots unprotected from stripping.
|
|
716
722
|
*/
|
|
717
723
|
function stripOldImageBlocks(history: Message[]): Message[] {
|
|
718
724
|
// Find the last user message that contains tool_result blocks.
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "../notifications/signal.js";
|
|
27
27
|
import { addRule } from "../permissions/trust-store.js";
|
|
28
28
|
import type { UserDecision } from "../permissions/types.js";
|
|
29
|
+
import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
|
|
29
30
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
30
31
|
import { mintDaemonDeliveryToken } from "../runtime/auth/token-service.js";
|
|
31
32
|
import type { ApprovalAction } from "../runtime/channel-approval-types.js";
|
|
@@ -192,8 +193,9 @@ const pendingInteractionResolver: GuardianRequestResolver = {
|
|
|
192
193
|
// Handle approve_always: persist a trust rule when the confirmation
|
|
193
194
|
// explicitly allows persistence and provides explicit options.
|
|
194
195
|
if (
|
|
195
|
-
|
|
196
|
-
decision.action === "
|
|
196
|
+
!isPermissionControlsV2Enabled() &&
|
|
197
|
+
(decision.action === "approve_always" ||
|
|
198
|
+
decision.action === "approve_once")
|
|
197
199
|
) {
|
|
198
200
|
const details = interaction.confirmationDetails;
|
|
199
201
|
if (
|
|
@@ -237,19 +239,23 @@ const pendingInteractionResolver: GuardianRequestResolver = {
|
|
|
237
239
|
// temporal modes (approve_10m, approve_conversation) reach the session with
|
|
238
240
|
// the correct UserDecision instead of collapsing to plain "allow".
|
|
239
241
|
let userDecision: UserDecision;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
242
|
+
if (isPermissionControlsV2Enabled()) {
|
|
243
|
+
userDecision = decision.action === "reject" ? "deny" : "allow";
|
|
244
|
+
} else {
|
|
245
|
+
switch (decision.action) {
|
|
246
|
+
case "reject":
|
|
247
|
+
userDecision = "deny";
|
|
248
|
+
break;
|
|
249
|
+
case "approve_10m":
|
|
250
|
+
userDecision = "allow_10m";
|
|
251
|
+
break;
|
|
252
|
+
case "approve_conversation":
|
|
253
|
+
userDecision = "allow_conversation";
|
|
254
|
+
break;
|
|
255
|
+
default:
|
|
256
|
+
userDecision = "allow";
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
253
259
|
}
|
|
254
260
|
resolved.conversation!.handleConfirmationResponse(
|
|
255
261
|
request.id,
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type BrowserBackend,
|
|
5
|
+
BrowserSessionManager,
|
|
6
|
+
type CdpCommand,
|
|
7
|
+
type CdpResult,
|
|
8
|
+
createExtensionBackend,
|
|
9
|
+
createLocalBackend,
|
|
10
|
+
} from "../index.js";
|
|
11
|
+
|
|
12
|
+
interface MockBackendState {
|
|
13
|
+
available: boolean;
|
|
14
|
+
disposed: boolean;
|
|
15
|
+
lastCommand?: CdpCommand;
|
|
16
|
+
lastSignal?: AbortSignal;
|
|
17
|
+
sendImpl?: (command: CdpCommand, signal?: AbortSignal) => Promise<CdpResult>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createMockExtensionBackend(state: MockBackendState): BrowserBackend {
|
|
21
|
+
return createExtensionBackend({
|
|
22
|
+
isAvailable: () => state.available,
|
|
23
|
+
sendCdp: async (command, signal) => {
|
|
24
|
+
state.lastCommand = command;
|
|
25
|
+
state.lastSignal = signal;
|
|
26
|
+
if (state.sendImpl) return state.sendImpl(command, signal);
|
|
27
|
+
return { result: { ok: true } };
|
|
28
|
+
},
|
|
29
|
+
dispose: () => {
|
|
30
|
+
state.disposed = true;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createMockLocalBackend(state: MockBackendState): BrowserBackend {
|
|
36
|
+
return createLocalBackend({
|
|
37
|
+
isAvailable: () => state.available,
|
|
38
|
+
sendCdp: async (command, signal) => {
|
|
39
|
+
state.lastCommand = command;
|
|
40
|
+
state.lastSignal = signal;
|
|
41
|
+
if (state.sendImpl) return state.sendImpl(command, signal);
|
|
42
|
+
return { result: { ok: true, kind: "local" } };
|
|
43
|
+
},
|
|
44
|
+
dispose: () => {
|
|
45
|
+
state.disposed = true;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("BrowserSessionManager", () => {
|
|
51
|
+
test("selectBackend throws when no backend is available", () => {
|
|
52
|
+
const state: MockBackendState = { available: false, disposed: false };
|
|
53
|
+
const manager = new BrowserSessionManager({
|
|
54
|
+
backends: [createMockExtensionBackend(state)],
|
|
55
|
+
});
|
|
56
|
+
expect(() => manager.selectBackend()).toThrow(
|
|
57
|
+
"No available browser backend",
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("selectBackend returns the extension backend when available", () => {
|
|
62
|
+
const state: MockBackendState = { available: true, disposed: false };
|
|
63
|
+
const backend = createMockExtensionBackend(state);
|
|
64
|
+
const manager = new BrowserSessionManager({ backends: [backend] });
|
|
65
|
+
const selected = manager.selectBackend();
|
|
66
|
+
expect(selected.kind).toBe("extension");
|
|
67
|
+
expect(selected).toBe(backend);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("createSession returns a session with a new uuid stored in the map", () => {
|
|
71
|
+
const state: MockBackendState = { available: true, disposed: false };
|
|
72
|
+
const manager = new BrowserSessionManager({
|
|
73
|
+
backends: [createMockExtensionBackend(state)],
|
|
74
|
+
});
|
|
75
|
+
const session = manager.createSession();
|
|
76
|
+
expect(session.id).toBeTruthy();
|
|
77
|
+
expect(session.backendKind).toBe("extension");
|
|
78
|
+
// Lookup round-trips.
|
|
79
|
+
expect(manager.getSession(session.id)).toEqual(session);
|
|
80
|
+
// Two sessions get unique ids.
|
|
81
|
+
const another = manager.createSession();
|
|
82
|
+
expect(another.id).not.toBe(session.id);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("send delegates to backend.send and returns the CDP result", async () => {
|
|
86
|
+
const expectedResult: CdpResult = { result: { value: 42 } };
|
|
87
|
+
const state: MockBackendState = {
|
|
88
|
+
available: true,
|
|
89
|
+
disposed: false,
|
|
90
|
+
sendImpl: async () => expectedResult,
|
|
91
|
+
};
|
|
92
|
+
const manager = new BrowserSessionManager({
|
|
93
|
+
backends: [createMockExtensionBackend(state)],
|
|
94
|
+
});
|
|
95
|
+
const result = await manager.send(undefined, {
|
|
96
|
+
method: "Browser.getVersion",
|
|
97
|
+
params: { foo: "bar" },
|
|
98
|
+
});
|
|
99
|
+
expect(result).toEqual(expectedResult);
|
|
100
|
+
expect(state.lastCommand).toEqual({
|
|
101
|
+
method: "Browser.getVersion",
|
|
102
|
+
params: { foo: "bar" },
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("send with an aborted signal propagates the abort", async () => {
|
|
107
|
+
const state: MockBackendState = {
|
|
108
|
+
available: true,
|
|
109
|
+
disposed: false,
|
|
110
|
+
sendImpl: async (_command, signal) => {
|
|
111
|
+
if (signal?.aborted) {
|
|
112
|
+
throw new Error("aborted");
|
|
113
|
+
}
|
|
114
|
+
return { result: { ok: true } };
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
const manager = new BrowserSessionManager({
|
|
118
|
+
backends: [createMockExtensionBackend(state)],
|
|
119
|
+
});
|
|
120
|
+
const controller = new AbortController();
|
|
121
|
+
controller.abort();
|
|
122
|
+
await expect(
|
|
123
|
+
manager.send(
|
|
124
|
+
undefined,
|
|
125
|
+
{ method: "Browser.getVersion" },
|
|
126
|
+
controller.signal,
|
|
127
|
+
),
|
|
128
|
+
).rejects.toThrow("aborted");
|
|
129
|
+
expect(state.lastSignal).toBe(controller.signal);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("disposeAll calls backend.dispose and clears the session map", () => {
|
|
133
|
+
const state: MockBackendState = { available: true, disposed: false };
|
|
134
|
+
const manager = new BrowserSessionManager({
|
|
135
|
+
backends: [createMockExtensionBackend(state)],
|
|
136
|
+
});
|
|
137
|
+
const session = manager.createSession();
|
|
138
|
+
expect(manager.getSession(session.id)).toBeDefined();
|
|
139
|
+
manager.disposeAll();
|
|
140
|
+
expect(state.disposed).toBe(true);
|
|
141
|
+
expect(manager.getSession(session.id)).toBeUndefined();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("send with a known sessionId routes through the matching backend", async () => {
|
|
145
|
+
const expectedResult: CdpResult = { result: { routed: true } };
|
|
146
|
+
const state: MockBackendState = {
|
|
147
|
+
available: true,
|
|
148
|
+
disposed: false,
|
|
149
|
+
sendImpl: async () => expectedResult,
|
|
150
|
+
};
|
|
151
|
+
const manager = new BrowserSessionManager({
|
|
152
|
+
backends: [createMockExtensionBackend(state)],
|
|
153
|
+
});
|
|
154
|
+
const session = manager.createSession();
|
|
155
|
+
const result = await manager.send(session.id, {
|
|
156
|
+
method: "Browser.getVersion",
|
|
157
|
+
});
|
|
158
|
+
expect(result).toEqual(expectedResult);
|
|
159
|
+
expect(state.lastCommand).toEqual({ method: "Browser.getVersion" });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("send with an unknown sessionId throws", async () => {
|
|
163
|
+
const state: MockBackendState = { available: true, disposed: false };
|
|
164
|
+
const manager = new BrowserSessionManager({
|
|
165
|
+
backends: [createMockExtensionBackend(state)],
|
|
166
|
+
});
|
|
167
|
+
await expect(
|
|
168
|
+
manager.send("does-not-exist", { method: "Browser.getVersion" }),
|
|
169
|
+
).rejects.toThrow("Unknown browser session: does-not-exist");
|
|
170
|
+
// The mock backend should not have received the command.
|
|
171
|
+
expect(state.lastCommand).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("send with a sessionId of a disposed session throws", async () => {
|
|
175
|
+
const state: MockBackendState = { available: true, disposed: false };
|
|
176
|
+
const manager = new BrowserSessionManager({
|
|
177
|
+
backends: [createMockExtensionBackend(state)],
|
|
178
|
+
});
|
|
179
|
+
const session = manager.createSession();
|
|
180
|
+
manager.disposeSession(session.id);
|
|
181
|
+
await expect(
|
|
182
|
+
manager.send(session.id, { method: "Browser.getVersion" }),
|
|
183
|
+
).rejects.toThrow(`Unknown browser session: ${session.id}`);
|
|
184
|
+
expect(state.lastCommand).toBeUndefined();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("selectBackend returns a local backend when it is the only registration", () => {
|
|
188
|
+
const state: MockBackendState = { available: true, disposed: false };
|
|
189
|
+
const backend = createMockLocalBackend(state);
|
|
190
|
+
const manager = new BrowserSessionManager({ backends: [backend] });
|
|
191
|
+
const selected = manager.selectBackend();
|
|
192
|
+
expect(selected.kind).toBe("local");
|
|
193
|
+
expect(selected).toBe(backend);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("createSession tags sessions with the local backend kind", () => {
|
|
197
|
+
const state: MockBackendState = { available: true, disposed: false };
|
|
198
|
+
const manager = new BrowserSessionManager({
|
|
199
|
+
backends: [createMockLocalBackend(state)],
|
|
200
|
+
});
|
|
201
|
+
const session = manager.createSession();
|
|
202
|
+
expect(session.backendKind).toBe("local");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("send routes through local backend when its session is used", async () => {
|
|
206
|
+
const state: MockBackendState = { available: true, disposed: false };
|
|
207
|
+
const manager = new BrowserSessionManager({
|
|
208
|
+
backends: [createMockLocalBackend(state)],
|
|
209
|
+
});
|
|
210
|
+
const session = manager.createSession();
|
|
211
|
+
const result = await manager.send(session.id, {
|
|
212
|
+
method: "Runtime.evaluate",
|
|
213
|
+
params: { expression: "1+1" },
|
|
214
|
+
});
|
|
215
|
+
expect(result).toEqual({ result: { ok: true, kind: "local" } });
|
|
216
|
+
expect(state.lastCommand).toEqual({
|
|
217
|
+
method: "Runtime.evaluate",
|
|
218
|
+
params: { expression: "1+1" },
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("selectBackend falls back to the first available backend when earlier ones are unavailable", () => {
|
|
223
|
+
const extState: MockBackendState = { available: false, disposed: false };
|
|
224
|
+
const localState: MockBackendState = { available: true, disposed: false };
|
|
225
|
+
const ext = createMockExtensionBackend(extState);
|
|
226
|
+
const local = createMockLocalBackend(localState);
|
|
227
|
+
const manager = new BrowserSessionManager({ backends: [ext, local] });
|
|
228
|
+
const selected = manager.selectBackend();
|
|
229
|
+
expect(selected.kind).toBe("local");
|
|
230
|
+
expect(selected).toBe(local);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("selectBackend prefers the first available backend", () => {
|
|
234
|
+
const extState: MockBackendState = { available: true, disposed: false };
|
|
235
|
+
const localState: MockBackendState = { available: true, disposed: false };
|
|
236
|
+
const ext = createMockExtensionBackend(extState);
|
|
237
|
+
const local = createMockLocalBackend(localState);
|
|
238
|
+
const manager = new BrowserSessionManager({ backends: [ext, local] });
|
|
239
|
+
const selected = manager.selectBackend();
|
|
240
|
+
expect(selected.kind).toBe("extension");
|
|
241
|
+
expect(selected).toBe(ext);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("send routes to the backend matching the session's backendKind when multiple backends are registered", async () => {
|
|
245
|
+
const extState: MockBackendState = {
|
|
246
|
+
available: true,
|
|
247
|
+
disposed: false,
|
|
248
|
+
sendImpl: async () => ({ result: { from: "extension" } }),
|
|
249
|
+
};
|
|
250
|
+
const localState: MockBackendState = {
|
|
251
|
+
available: true,
|
|
252
|
+
disposed: false,
|
|
253
|
+
sendImpl: async () => ({ result: { from: "local" } }),
|
|
254
|
+
};
|
|
255
|
+
const ext = createMockExtensionBackend(extState);
|
|
256
|
+
const local = createMockLocalBackend(localState);
|
|
257
|
+
const manager = new BrowserSessionManager({ backends: [ext, local] });
|
|
258
|
+
|
|
259
|
+
// createSession picks the first available backend (extension), but we
|
|
260
|
+
// can also force the local one by passing a backendKind via direct
|
|
261
|
+
// session construction. To keep this test self-contained we verify the
|
|
262
|
+
// default path and then mark the extension unavailable to force the
|
|
263
|
+
// local backend for a new session.
|
|
264
|
+
const extSession = manager.createSession();
|
|
265
|
+
expect(extSession.backendKind).toBe("extension");
|
|
266
|
+
const extResult = await manager.send(extSession.id, {
|
|
267
|
+
method: "Browser.getVersion",
|
|
268
|
+
});
|
|
269
|
+
expect(extResult).toEqual({ result: { from: "extension" } });
|
|
270
|
+
expect(extState.lastCommand).toEqual({ method: "Browser.getVersion" });
|
|
271
|
+
expect(localState.lastCommand).toBeUndefined();
|
|
272
|
+
|
|
273
|
+
extState.available = false;
|
|
274
|
+
const localSession = manager.createSession();
|
|
275
|
+
expect(localSession.backendKind).toBe("local");
|
|
276
|
+
const localResult = await manager.send(localSession.id, {
|
|
277
|
+
method: "Runtime.evaluate",
|
|
278
|
+
});
|
|
279
|
+
expect(localResult).toEqual({ result: { from: "local" } });
|
|
280
|
+
expect(localState.lastCommand).toEqual({ method: "Runtime.evaluate" });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("disposeAll disposes every registered backend", () => {
|
|
284
|
+
const extState: MockBackendState = { available: true, disposed: false };
|
|
285
|
+
const localState: MockBackendState = { available: true, disposed: false };
|
|
286
|
+
const manager = new BrowserSessionManager({
|
|
287
|
+
backends: [
|
|
288
|
+
createMockExtensionBackend(extState),
|
|
289
|
+
createMockLocalBackend(localState),
|
|
290
|
+
],
|
|
291
|
+
});
|
|
292
|
+
manager.createSession();
|
|
293
|
+
manager.disposeAll();
|
|
294
|
+
expect(extState.disposed).toBe(true);
|
|
295
|
+
expect(localState.disposed).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { BrowserBackend, CdpCommand, CdpResult } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* cdp-inspect backend for BrowserSessionManager. Wraps a
|
|
5
|
+
* caller-provided `sendCdp` transport that talks to an already-running
|
|
6
|
+
* Chrome via DevTools JSON discovery + a raw WebSocket transport
|
|
7
|
+
* (see `assistant/src/tools/browser/cdp-client/cdp-inspect-client.ts`).
|
|
8
|
+
*
|
|
9
|
+
* The factory in
|
|
10
|
+
* `assistant/src/tools/browser/cdp-client/factory.ts` constructs
|
|
11
|
+
* one per tool invocation, paralleling the existing extension
|
|
12
|
+
* and local backend wiring.
|
|
13
|
+
*/
|
|
14
|
+
export interface CdpInspectBackendDeps {
|
|
15
|
+
/** Sends a CDP command to the user's Chrome via cdp-inspect and returns the CDP result. */
|
|
16
|
+
sendCdp(command: CdpCommand, signal?: AbortSignal): Promise<CdpResult>;
|
|
17
|
+
isAvailable(): boolean;
|
|
18
|
+
dispose(): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createCdpInspectBackend(
|
|
22
|
+
deps: CdpInspectBackendDeps,
|
|
23
|
+
): BrowserBackend {
|
|
24
|
+
return {
|
|
25
|
+
kind: "cdp-inspect",
|
|
26
|
+
isAvailable: deps.isAvailable,
|
|
27
|
+
send: deps.sendCdp,
|
|
28
|
+
dispose: deps.dispose,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BrowserBackend, CdpCommand, CdpResult } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extension backend for BrowserSessionManager. Wraps a caller-provided
|
|
5
|
+
* `sendCdp` transport that routes CDP commands through the daemon's
|
|
6
|
+
* HostBrowserProxy to an attached chrome extension. The factory in
|
|
7
|
+
* `assistant/src/tools/browser/cdp-client/factory.ts` constructs one
|
|
8
|
+
* per tool invocation using the conversation's `hostBrowserProxy`.
|
|
9
|
+
*/
|
|
10
|
+
export interface ExtensionBackendDeps {
|
|
11
|
+
/** Sends a CDP command to an attached chrome extension and returns the CDP result. */
|
|
12
|
+
sendCdp(command: CdpCommand, signal?: AbortSignal): Promise<CdpResult>;
|
|
13
|
+
isAvailable(): boolean;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createExtensionBackend(
|
|
18
|
+
deps: ExtensionBackendDeps,
|
|
19
|
+
): BrowserBackend {
|
|
20
|
+
return {
|
|
21
|
+
kind: "extension",
|
|
22
|
+
isAvailable: deps.isAvailable,
|
|
23
|
+
send: deps.sendCdp,
|
|
24
|
+
dispose: deps.dispose,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BrowserBackend, CdpCommand, CdpResult } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local backend for BrowserSessionManager. Wraps a caller-provided
|
|
5
|
+
* `sendCdp` transport that drives a Playwright CDPSession against the
|
|
6
|
+
* sacrificial-profile Chromium managed by `browserManager`. The factory
|
|
7
|
+
* in `assistant/src/tools/browser/cdp-client/factory.ts` constructs one
|
|
8
|
+
* per tool invocation using the per-conversation LocalCdpClient.
|
|
9
|
+
*/
|
|
10
|
+
export interface LocalBackendDeps {
|
|
11
|
+
/** Sends a CDP command to a Playwright CDPSession and returns the CDP result. */
|
|
12
|
+
sendCdp(command: CdpCommand, signal?: AbortSignal): Promise<CdpResult>;
|
|
13
|
+
isAvailable(): boolean;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createLocalBackend(deps: LocalBackendDeps): BrowserBackend {
|
|
18
|
+
return {
|
|
19
|
+
kind: "local",
|
|
20
|
+
isAvailable: deps.isAvailable,
|
|
21
|
+
send: deps.sendCdp,
|
|
22
|
+
dispose: deps.dispose,
|
|
23
|
+
};
|
|
24
|
+
}
|