@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
|
@@ -9,11 +9,64 @@ mock.module("../util/logger.js", () => ({
|
|
|
9
9
|
}),
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// ── Fake CdpClient ───────────────────────────────────────────────────
|
|
13
|
+
//
|
|
14
|
+
// Programmable send handler + call log shared across tests. Each test
|
|
15
|
+
// resets these in `beforeEach` via `resetCdp()`. The mocked
|
|
16
|
+
// `getCdpClient` mirrors the real factory's routing decision (local
|
|
17
|
+
// vs extension is driven by `context.hostBrowserProxy`) so individual
|
|
18
|
+
// tests can exercise either transport without process-wide coupling.
|
|
19
|
+
//
|
|
20
|
+
// Note: bun's `mock.module` is process-global, but `scripts/test.sh`
|
|
21
|
+
// runs each test file in its own bun process so this mock only
|
|
22
|
+
// affects this file's tests.
|
|
23
|
+
|
|
24
|
+
let cdpSendCalls: Array<{ method: string; params?: unknown }> = [];
|
|
25
|
+
let cdpSendHandler: (
|
|
26
|
+
method: string,
|
|
27
|
+
params?: Record<string, unknown>,
|
|
28
|
+
) => unknown = () => ({});
|
|
29
|
+
let cdpDisposed = false;
|
|
30
|
+
|
|
31
|
+
function makeFakeCdp(kind: "local" | "extension", conversationId: string) {
|
|
32
|
+
return {
|
|
33
|
+
kind,
|
|
34
|
+
conversationId,
|
|
35
|
+
async send<T>(
|
|
36
|
+
method: string,
|
|
37
|
+
params?: Record<string, unknown>,
|
|
38
|
+
): Promise<T> {
|
|
39
|
+
cdpSendCalls.push({ method, params });
|
|
40
|
+
const value = cdpSendHandler(method, params);
|
|
41
|
+
return (await value) as T;
|
|
42
|
+
},
|
|
43
|
+
dispose() {
|
|
44
|
+
cdpDisposed = true;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
mock.module("../tools/browser/cdp-client/factory.js", () => ({
|
|
50
|
+
getCdpClient: (context: {
|
|
51
|
+
hostBrowserProxy?: unknown;
|
|
52
|
+
conversationId: string;
|
|
53
|
+
}) =>
|
|
54
|
+
makeFakeCdp(
|
|
55
|
+
context.hostBrowserProxy ? "extension" : "local",
|
|
56
|
+
context.conversationId,
|
|
57
|
+
),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// ── Minimal browserManager stub ──────────────────────────────────────
|
|
61
|
+
//
|
|
62
|
+
// The local path still installs a Playwright route handler via
|
|
63
|
+
// browserManager.getOrCreateSessionPage() → page.route(...). We keep
|
|
64
|
+
// a tiny stub so the happy path doesn't blow up when the route handler
|
|
65
|
+
// is installed/uninstalled; the route logic itself is only exercised
|
|
66
|
+
// by the SSRF redirect test below.
|
|
67
|
+
|
|
13
68
|
let mockPage: {
|
|
14
|
-
|
|
15
|
-
title: ReturnType<typeof mock>;
|
|
16
|
-
url: ReturnType<typeof mock>;
|
|
69
|
+
url: () => string;
|
|
17
70
|
route: ReturnType<typeof mock>;
|
|
18
71
|
unroute: ReturnType<typeof mock>;
|
|
19
72
|
close: () => Promise<void>;
|
|
@@ -21,20 +74,31 @@ let mockPage: {
|
|
|
21
74
|
};
|
|
22
75
|
|
|
23
76
|
let getOrCreateSessionPageMock: ReturnType<typeof mock>;
|
|
77
|
+
let clearSnapshotBackendNodeMapMock: ReturnType<typeof mock>;
|
|
78
|
+
let positionWindowSidebarMock: ReturnType<typeof mock>;
|
|
24
79
|
|
|
25
80
|
mock.module("../tools/browser/browser-manager.js", () => {
|
|
26
81
|
getOrCreateSessionPageMock = mock(async () => mockPage);
|
|
82
|
+
clearSnapshotBackendNodeMapMock = mock(() => {});
|
|
83
|
+
positionWindowSidebarMock = mock(async () => {});
|
|
27
84
|
return {
|
|
28
85
|
browserManager: {
|
|
29
86
|
getOrCreateSessionPage: getOrCreateSessionPageMock,
|
|
30
|
-
|
|
87
|
+
clearSnapshotBackendNodeMap: clearSnapshotBackendNodeMapMock,
|
|
31
88
|
supportsRouteInterception: true,
|
|
32
89
|
isInteractive: () => false,
|
|
33
|
-
positionWindowSidebar:
|
|
90
|
+
positionWindowSidebar: positionWindowSidebarMock,
|
|
34
91
|
},
|
|
35
92
|
};
|
|
36
93
|
});
|
|
37
94
|
|
|
95
|
+
mock.module("../tools/browser/browser-screencast.js", () => ({
|
|
96
|
+
ensureScreencast: async () => {},
|
|
97
|
+
getSender: () => null,
|
|
98
|
+
stopAllScreencasts: async () => {},
|
|
99
|
+
stopBrowserScreencast: async () => {},
|
|
100
|
+
}));
|
|
101
|
+
|
|
38
102
|
// Default url-safety: allow everything
|
|
39
103
|
let parseUrlResult: URL | null = null;
|
|
40
104
|
let isPrivateResult = false;
|
|
@@ -59,12 +123,7 @@ const ctx: ToolContext = {
|
|
|
59
123
|
|
|
60
124
|
function resetMockPage() {
|
|
61
125
|
mockPage = {
|
|
62
|
-
|
|
63
|
-
status: () => 200,
|
|
64
|
-
url: () => "https://example.com/",
|
|
65
|
-
})),
|
|
66
|
-
title: mock(async () => "Example"),
|
|
67
|
-
url: mock(() => "https://example.com/"),
|
|
126
|
+
url: () => "https://example.com/",
|
|
68
127
|
route: mock(async () => {}),
|
|
69
128
|
unroute: mock(async () => {}),
|
|
70
129
|
close: async () => {},
|
|
@@ -72,20 +131,87 @@ function resetMockPage() {
|
|
|
72
131
|
};
|
|
73
132
|
}
|
|
74
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Default CDP handler. Returns values in the CDP response shape
|
|
136
|
+
* (`{ result: { value } }`) for `Runtime.evaluate` calls and resolves
|
|
137
|
+
* with `{}` for other methods.
|
|
138
|
+
*
|
|
139
|
+
* navigateAndWait now reads the pre-nav URL, then polls readyState +
|
|
140
|
+
* href in a single combined evaluate. The default flow:
|
|
141
|
+
*
|
|
142
|
+
* 1. Pre-nav `document.location.href` → "about:blank" (the baseline
|
|
143
|
+
* used by navigateAndWait's commit detection).
|
|
144
|
+
* 2. `Page.navigate`
|
|
145
|
+
* 3. Combined poll `({ readyState, href })` → `{ readyState:
|
|
146
|
+
* "complete", href: "https://example.com/page" }`. Because the
|
|
147
|
+
* href changed from the pre-nav value, commit detection fires
|
|
148
|
+
* on the first poll.
|
|
149
|
+
* 4. `document.title` → "Example".
|
|
150
|
+
*/
|
|
151
|
+
function defaultCdpHandler(
|
|
152
|
+
method: string,
|
|
153
|
+
params?: Record<string, unknown>,
|
|
154
|
+
): unknown {
|
|
155
|
+
if (method === "Page.navigate") return { frameId: "f1" };
|
|
156
|
+
if (method === "Runtime.evaluate") {
|
|
157
|
+
const expression = String(params?.["expression"] ?? "");
|
|
158
|
+
if (expression === "document.location.href") {
|
|
159
|
+
return { result: { value: "about:blank" } };
|
|
160
|
+
}
|
|
161
|
+
if (expression === "document.title") {
|
|
162
|
+
return { result: { value: "Example" } };
|
|
163
|
+
}
|
|
164
|
+
// Combined readyState + href polling expression from
|
|
165
|
+
// navigateAndWait. The commit-detection logic requires a
|
|
166
|
+
// different href from the pre-nav baseline so we return the
|
|
167
|
+
// requested page URL here.
|
|
168
|
+
if (
|
|
169
|
+
expression.includes("readyState") &&
|
|
170
|
+
expression.includes("document.location.href")
|
|
171
|
+
) {
|
|
172
|
+
return {
|
|
173
|
+
result: {
|
|
174
|
+
value: {
|
|
175
|
+
readyState: "complete",
|
|
176
|
+
href: "https://example.com/page",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// DOM_DETECT / CAPTCHA_DETECT / DISMISS_MODALS IIFEs fall through
|
|
182
|
+
// to a generic "no challenge" result. The auth-detector IIFE
|
|
183
|
+
// expects `{result: {value: null | {...}}}` shape.
|
|
184
|
+
return { result: { value: null } };
|
|
185
|
+
}
|
|
186
|
+
return {};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function resetCdp() {
|
|
190
|
+
cdpSendCalls = [];
|
|
191
|
+
cdpDisposed = false;
|
|
192
|
+
cdpSendHandler = defaultCdpHandler;
|
|
193
|
+
}
|
|
194
|
+
|
|
75
195
|
describe("executeBrowserNavigate", () => {
|
|
76
196
|
beforeEach(() => {
|
|
77
197
|
parseUrlResult = null;
|
|
78
198
|
isPrivateResult = false;
|
|
79
199
|
resolveResult = {};
|
|
80
200
|
resetMockPage();
|
|
201
|
+
resetCdp();
|
|
81
202
|
});
|
|
82
203
|
|
|
83
204
|
// ── Input validation ───────────────────────────────────────────
|
|
205
|
+
//
|
|
206
|
+
// These run entirely within the upfront validation block and do
|
|
207
|
+
// not touch CDP. The tests intentionally do not assert anything
|
|
208
|
+
// about the CdpClient — the factory should never be called.
|
|
84
209
|
|
|
85
210
|
test("rejects missing or invalid url", async () => {
|
|
86
211
|
const result = await executeBrowserNavigate({}, ctx);
|
|
87
212
|
expect(result.isError).toBe(true);
|
|
88
213
|
expect(result.content).toContain("url is required");
|
|
214
|
+
expect(cdpSendCalls).toEqual([]);
|
|
89
215
|
});
|
|
90
216
|
|
|
91
217
|
test("rejects non-http(s) protocols", async () => {
|
|
@@ -96,6 +222,7 @@ describe("executeBrowserNavigate", () => {
|
|
|
96
222
|
);
|
|
97
223
|
expect(result.isError).toBe(true);
|
|
98
224
|
expect(result.content).toContain("http or https");
|
|
225
|
+
expect(cdpSendCalls).toEqual([]);
|
|
99
226
|
});
|
|
100
227
|
|
|
101
228
|
// ── Private network blocking ───────────────────────────────────
|
|
@@ -110,6 +237,7 @@ describe("executeBrowserNavigate", () => {
|
|
|
110
237
|
expect(result.isError).toBe(true);
|
|
111
238
|
expect(result.content).toContain("Refusing to navigate");
|
|
112
239
|
expect(result.content).toContain("localhost");
|
|
240
|
+
expect(cdpSendCalls).toEqual([]);
|
|
113
241
|
});
|
|
114
242
|
|
|
115
243
|
test("allows private hosts with allow_private_network=true", async () => {
|
|
@@ -120,7 +248,7 @@ describe("executeBrowserNavigate", () => {
|
|
|
120
248
|
ctx,
|
|
121
249
|
);
|
|
122
250
|
expect(result.isError).toBe(false);
|
|
123
|
-
expect(result.content).toContain("Status:
|
|
251
|
+
expect(result.content).toContain("Status: unknown");
|
|
124
252
|
});
|
|
125
253
|
|
|
126
254
|
test("blocks DNS-resolved private addresses by default", async () => {
|
|
@@ -133,6 +261,7 @@ describe("executeBrowserNavigate", () => {
|
|
|
133
261
|
);
|
|
134
262
|
expect(result.isError).toBe(true);
|
|
135
263
|
expect(result.content).toContain("10.0.0.1");
|
|
264
|
+
expect(cdpSendCalls).toEqual([]);
|
|
136
265
|
});
|
|
137
266
|
|
|
138
267
|
test("skips DNS check with allow_private_network=true", async () => {
|
|
@@ -144,12 +273,12 @@ describe("executeBrowserNavigate", () => {
|
|
|
144
273
|
ctx,
|
|
145
274
|
);
|
|
146
275
|
expect(result.isError).toBe(false);
|
|
147
|
-
expect(result.content).toContain("Status:
|
|
276
|
+
expect(result.content).toContain("Status: unknown");
|
|
148
277
|
});
|
|
149
278
|
|
|
150
|
-
// ──
|
|
279
|
+
// ── Happy path (CDP navigate) ──────────────────────────────────
|
|
151
280
|
|
|
152
|
-
test("
|
|
281
|
+
test("calls Page.navigate with the requested URL and returns URL+title", async () => {
|
|
153
282
|
parseUrlResult = new URL("https://example.com/page");
|
|
154
283
|
const result = await executeBrowserNavigate(
|
|
155
284
|
{ url: "https://example.com/page" },
|
|
@@ -158,13 +287,64 @@ describe("executeBrowserNavigate", () => {
|
|
|
158
287
|
expect(result.isError).toBe(false);
|
|
159
288
|
expect(result.content).toContain("Requested URL:");
|
|
160
289
|
expect(result.content).toContain("Final URL:");
|
|
161
|
-
expect(result.content).toContain("Status:
|
|
290
|
+
expect(result.content).toContain("Status: unknown");
|
|
162
291
|
expect(result.content).toContain("Title: Example");
|
|
292
|
+
|
|
293
|
+
// Page.navigate was called with the expected URL
|
|
294
|
+
const navigateCall = cdpSendCalls.find((c) => c.method === "Page.navigate");
|
|
295
|
+
expect(navigateCall).toBeDefined();
|
|
296
|
+
expect(navigateCall!.params).toEqual({ url: "https://example.com/page" });
|
|
297
|
+
|
|
298
|
+
// navigateAndWait polls readyState+href in a single combined
|
|
299
|
+
// evaluate and also reads `document.location.href` pre-nav; the
|
|
300
|
+
// caller separately reads `document.title` after the nav.
|
|
301
|
+
const evaluateCalls = cdpSendCalls.filter(
|
|
302
|
+
(c) => c.method === "Runtime.evaluate",
|
|
303
|
+
);
|
|
304
|
+
const expressions = evaluateCalls.map(
|
|
305
|
+
(c) => (c.params as Record<string, unknown>)["expression"] as string,
|
|
306
|
+
);
|
|
307
|
+
expect(expressions.some((e) => e.includes("readyState"))).toBe(true);
|
|
308
|
+
expect(expressions).toContain("document.location.href");
|
|
309
|
+
expect(expressions).toContain("document.title");
|
|
310
|
+
|
|
311
|
+
// The CdpClient was disposed in the finally block.
|
|
312
|
+
expect(cdpDisposed).toBe(true);
|
|
163
313
|
});
|
|
164
314
|
|
|
165
315
|
test("notes redirect when final URL differs", async () => {
|
|
166
316
|
parseUrlResult = new URL("https://example.com/old");
|
|
167
|
-
|
|
317
|
+
// Pre-nav URL is about:blank so commit detection fires on the
|
|
318
|
+
// first poll. The combined poll returns a different href than
|
|
319
|
+
// the requested URL — that's what triggers the "redirected" note.
|
|
320
|
+
cdpSendHandler = (method, params) => {
|
|
321
|
+
if (method === "Page.navigate") return { frameId: "f1" };
|
|
322
|
+
if (method === "Runtime.evaluate") {
|
|
323
|
+
const expression = String(params?.["expression"] ?? "");
|
|
324
|
+
if (expression === "document.location.href") {
|
|
325
|
+
return { result: { value: "about:blank" } };
|
|
326
|
+
}
|
|
327
|
+
if (expression === "document.title") {
|
|
328
|
+
return { result: { value: "New" } };
|
|
329
|
+
}
|
|
330
|
+
if (
|
|
331
|
+
expression.includes("readyState") &&
|
|
332
|
+
expression.includes("document.location.href")
|
|
333
|
+
) {
|
|
334
|
+
return {
|
|
335
|
+
result: {
|
|
336
|
+
value: {
|
|
337
|
+
readyState: "complete",
|
|
338
|
+
href: "https://example.com/new",
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return { result: { value: null } };
|
|
344
|
+
}
|
|
345
|
+
return {};
|
|
346
|
+
};
|
|
347
|
+
|
|
168
348
|
const result = await executeBrowserNavigate(
|
|
169
349
|
{ url: "https://example.com/old" },
|
|
170
350
|
ctx,
|
|
@@ -173,24 +353,95 @@ describe("executeBrowserNavigate", () => {
|
|
|
173
353
|
expect(result.content).toContain("redirected");
|
|
174
354
|
});
|
|
175
355
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
356
|
+
// ── Timeout / readyState stays "loading" ───────────────────────
|
|
357
|
+
|
|
358
|
+
test("reports a timeout note when document.readyState never completes", async () => {
|
|
359
|
+
parseUrlResult = new URL("https://example.com/slow");
|
|
360
|
+
cdpSendHandler = (method, params) => {
|
|
361
|
+
if (method === "Page.navigate") return { frameId: "f1" };
|
|
362
|
+
if (method === "Runtime.evaluate") {
|
|
363
|
+
const expression = String(params?.["expression"] ?? "");
|
|
364
|
+
if (expression === "document.location.href") {
|
|
365
|
+
// Pre-nav URL read. Returning the same URL as the target
|
|
366
|
+
// would trigger the same-URL reload fallback; using
|
|
367
|
+
// about:blank keeps the cross-URL commit-detection path
|
|
368
|
+
// active so the polling loop actually exercises readyState.
|
|
369
|
+
return { result: { value: "about:blank" } };
|
|
370
|
+
}
|
|
371
|
+
if (expression === "document.title") {
|
|
372
|
+
return { result: { value: "Loading" } };
|
|
373
|
+
}
|
|
374
|
+
if (
|
|
375
|
+
expression.includes("readyState") &&
|
|
376
|
+
expression.includes("document.location.href")
|
|
377
|
+
) {
|
|
378
|
+
// Stuck in "loading" — forces navigateAndWait to exhaust
|
|
379
|
+
// its timeout budget (or get aborted by the test's signal).
|
|
380
|
+
return {
|
|
381
|
+
result: {
|
|
382
|
+
value: {
|
|
383
|
+
readyState: "loading",
|
|
384
|
+
href: "https://example.com/slow",
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return { result: { value: null } };
|
|
390
|
+
}
|
|
391
|
+
return {};
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// Use a short deadline for the test — the NAVIGATE_TIMEOUT_MS
|
|
395
|
+
// const is 15s which is too slow for a unit test. We bound this
|
|
396
|
+
// by aborting after ~200ms so the helper surfaces a CdpError
|
|
397
|
+
// with code "aborted" rather than waiting the full 15s.
|
|
398
|
+
//
|
|
399
|
+
// The in-function `navigationTimedOut` branch is NOT the path
|
|
400
|
+
// exercised here (aborts throw instead of returning timedOut).
|
|
401
|
+
// The happy-path timeout is simulated by the other timeout
|
|
402
|
+
// behavior test below.
|
|
403
|
+
const ctrl = new AbortController();
|
|
404
|
+
const timer = setTimeout(() => ctrl.abort(), 200);
|
|
179
405
|
const result = await executeBrowserNavigate(
|
|
180
|
-
{ url: "https://example.com" },
|
|
181
|
-
ctx,
|
|
406
|
+
{ url: "https://example.com/slow" },
|
|
407
|
+
{ ...ctx, signal: ctrl.signal },
|
|
182
408
|
);
|
|
183
|
-
|
|
184
|
-
expect(result.
|
|
409
|
+
clearTimeout(timer);
|
|
410
|
+
expect(result.isError).toBe(true);
|
|
411
|
+
expect(result.content).toContain("Navigation failed");
|
|
412
|
+
expect(cdpDisposed).toBe(true);
|
|
185
413
|
});
|
|
186
414
|
|
|
187
|
-
// ──
|
|
415
|
+
// ── Pre-aborted signal ─────────────────────────────────────────
|
|
188
416
|
|
|
189
|
-
test("
|
|
417
|
+
test("returns early-abort error when signal is already aborted", async () => {
|
|
418
|
+
parseUrlResult = new URL("https://example.com/page");
|
|
419
|
+
const ctrl = new AbortController();
|
|
420
|
+
ctrl.abort();
|
|
421
|
+
const result = await executeBrowserNavigate(
|
|
422
|
+
{ url: "https://example.com/page" },
|
|
423
|
+
{ ...ctx, signal: ctrl.signal },
|
|
424
|
+
);
|
|
425
|
+
expect(result.isError).toBe(true);
|
|
426
|
+
expect(result.content).toContain("operation was cancelled");
|
|
427
|
+
// Pre-abort short-circuits before any CDP call is made.
|
|
428
|
+
expect(cdpSendCalls).toEqual([]);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// ── Navigation errors ──────────────────────────────────────────
|
|
432
|
+
|
|
433
|
+
test("catches navigation errors from Page.navigate", async () => {
|
|
190
434
|
parseUrlResult = new URL("https://example.com");
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
435
|
+
cdpSendHandler = (method) => {
|
|
436
|
+
if (method === "Page.navigate") {
|
|
437
|
+
throw new Error("net::ERR_CONNECTION_REFUSED");
|
|
438
|
+
}
|
|
439
|
+
if (method === "Runtime.evaluate") {
|
|
440
|
+
return { result: { value: "https://example.com/" } };
|
|
441
|
+
}
|
|
442
|
+
return {};
|
|
443
|
+
};
|
|
444
|
+
|
|
194
445
|
const result = await executeBrowserNavigate(
|
|
195
446
|
{ url: "https://example.com" },
|
|
196
447
|
ctx,
|
|
@@ -198,13 +449,62 @@ describe("executeBrowserNavigate", () => {
|
|
|
198
449
|
expect(result.isError).toBe(true);
|
|
199
450
|
expect(result.content).toContain("Navigation failed");
|
|
200
451
|
expect(result.content).toContain("ERR_CONNECTION_REFUSED");
|
|
452
|
+
expect(cdpDisposed).toBe(true);
|
|
201
453
|
});
|
|
202
454
|
|
|
203
|
-
test("
|
|
455
|
+
test("surfaces Page.navigate errorText as a navigation failure", async () => {
|
|
456
|
+
// CDP signals DNS / connection errors via the response's
|
|
457
|
+
// `errorText` field rather than throwing. Without this, the
|
|
458
|
+
// navigate helper would poll readyState on the OLD page (which is
|
|
459
|
+
// "complete") and report success with the stale URL — leaking
|
|
460
|
+
// potentially sensitive content the agent never asked for.
|
|
461
|
+
parseUrlResult = new URL("https://nope.invalid");
|
|
462
|
+
cdpSendHandler = (method, params) => {
|
|
463
|
+
if (method === "Page.navigate") {
|
|
464
|
+
return { frameId: "f1", errorText: "net::ERR_NAME_NOT_RESOLVED" };
|
|
465
|
+
}
|
|
466
|
+
if (method === "Runtime.evaluate") {
|
|
467
|
+
const expression = String(params?.["expression"] ?? "");
|
|
468
|
+
if (expression === "document.location.href") {
|
|
469
|
+
return { result: { value: "https://example.com/old" } };
|
|
470
|
+
}
|
|
471
|
+
return { result: { value: null } };
|
|
472
|
+
}
|
|
473
|
+
return {};
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const result = await executeBrowserNavigate(
|
|
477
|
+
{ url: "https://nope.invalid" },
|
|
478
|
+
ctx,
|
|
479
|
+
);
|
|
480
|
+
expect(result.isError).toBe(true);
|
|
481
|
+
expect(result.content).toContain("Navigation failed");
|
|
482
|
+
expect(result.content).toContain("ERR_NAME_NOT_RESOLVED");
|
|
483
|
+
expect(cdpDisposed).toBe(true);
|
|
484
|
+
|
|
485
|
+
// Should NOT have polled readyState — navigate failed before the
|
|
486
|
+
// wait loop ran. navigateAndWait combines readyState + href into a
|
|
487
|
+
// single evaluate, so we look for any expression containing both.
|
|
488
|
+
const readyStateCalls = cdpSendCalls.filter((c) => {
|
|
489
|
+
if (c.method !== "Runtime.evaluate") return false;
|
|
490
|
+
const expr = (c.params as { expression?: string } | undefined)
|
|
491
|
+
?.expression;
|
|
492
|
+
return (
|
|
493
|
+
typeof expr === "string" &&
|
|
494
|
+
expr.includes("readyState") &&
|
|
495
|
+
expr.includes("document.location.href")
|
|
496
|
+
);
|
|
497
|
+
});
|
|
498
|
+
expect(readyStateCalls).toHaveLength(0);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// ── SSRF route interception (local path only) ─────────────────
|
|
502
|
+
|
|
503
|
+
test("returns security message when route handler blocks a redirect", async () => {
|
|
204
504
|
parseUrlResult = new URL("https://public.example.com");
|
|
205
505
|
isPrivateResult = false;
|
|
206
506
|
|
|
207
|
-
// Capture the route handler
|
|
507
|
+
// Capture the installed route handler.
|
|
208
508
|
let capturedHandler:
|
|
209
509
|
| ((route: unknown, request: unknown) => Promise<void>)
|
|
210
510
|
| null = null;
|
|
@@ -217,21 +517,33 @@ describe("executeBrowserNavigate", () => {
|
|
|
217
517
|
},
|
|
218
518
|
);
|
|
219
519
|
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
520
|
+
// When Page.navigate is called, simulate a private redirect by
|
|
521
|
+
// invoking the captured route handler, then throw to mirror how
|
|
522
|
+
// the Playwright route interceptor signals blockage to the caller.
|
|
523
|
+
cdpSendHandler = (method) => {
|
|
524
|
+
if (method === "Page.navigate") {
|
|
525
|
+
if (capturedHandler) {
|
|
526
|
+
const origPrivate = isPrivateResult;
|
|
527
|
+
isPrivateResult = true;
|
|
528
|
+
const mockRoute = {
|
|
529
|
+
abort: mock(async () => {}),
|
|
530
|
+
continue: mock(async () => {}),
|
|
531
|
+
};
|
|
532
|
+
const mockRequest = { url: () => "http://169.254.169.254/metadata" };
|
|
533
|
+
// Invoke the captured handler. Intentionally fire-and-forget
|
|
534
|
+
// because Page.navigate is synchronous from the test's
|
|
535
|
+
// perspective — the handler only mutates `blockedUrl` in the
|
|
536
|
+
// closed-over scope.
|
|
537
|
+
void capturedHandler(mockRoute, mockRequest);
|
|
538
|
+
isPrivateResult = origPrivate;
|
|
539
|
+
}
|
|
540
|
+
throw new Error("net::ERR_BLOCKED_BY_CLIENT");
|
|
232
541
|
}
|
|
233
|
-
|
|
234
|
-
|
|
542
|
+
if (method === "Runtime.evaluate") {
|
|
543
|
+
return { result: { value: "https://public.example.com/" } };
|
|
544
|
+
}
|
|
545
|
+
return {};
|
|
546
|
+
};
|
|
235
547
|
|
|
236
548
|
const result = await executeBrowserNavigate(
|
|
237
549
|
{ url: "https://public.example.com" },
|
|
@@ -240,7 +552,37 @@ describe("executeBrowserNavigate", () => {
|
|
|
240
552
|
expect(result.isError).toBe(true);
|
|
241
553
|
expect(result.content).toContain("Navigation blocked");
|
|
242
554
|
expect(result.content).toContain("allow_private_network=true");
|
|
243
|
-
// Should NOT contain the raw
|
|
555
|
+
// Should NOT contain the raw underlying error
|
|
244
556
|
expect(result.content).not.toContain("ERR_BLOCKED_BY_CLIENT");
|
|
557
|
+
expect(cdpDisposed).toBe(true);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// ── Extension path (no browserManager / route interception) ───
|
|
561
|
+
|
|
562
|
+
test("extension path skips getOrCreateSessionPage and route interception", async () => {
|
|
563
|
+
parseUrlResult = new URL("https://example.com/page");
|
|
564
|
+
// Supplying a non-null hostBrowserProxy on the context routes the
|
|
565
|
+
// mocked getCdpClient to the extension path (it mirrors the real
|
|
566
|
+
// factory's routing logic).
|
|
567
|
+
const extensionCtx: ToolContext = {
|
|
568
|
+
...ctx,
|
|
569
|
+
hostBrowserProxy: {} as unknown as ToolContext["hostBrowserProxy"],
|
|
570
|
+
};
|
|
571
|
+
// Reset page call trackers to verify they are not touched.
|
|
572
|
+
const routeCallsBefore = mockPage.route.mock.calls.length;
|
|
573
|
+
const unrouteCallsBefore = mockPage.unroute.mock.calls.length;
|
|
574
|
+
|
|
575
|
+
const result = await executeBrowserNavigate(
|
|
576
|
+
{ url: "https://example.com/page" },
|
|
577
|
+
extensionCtx,
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
expect(result.isError).toBe(false);
|
|
581
|
+
// Extension path never installs or removes a Playwright route.
|
|
582
|
+
expect(mockPage.route.mock.calls.length).toBe(routeCallsBefore);
|
|
583
|
+
expect(mockPage.unroute.mock.calls.length).toBe(unrouteCallsBefore);
|
|
584
|
+
// Page.navigate still goes through the CdpClient.
|
|
585
|
+
expect(cdpSendCalls.some((c) => c.method === "Page.navigate")).toBe(true);
|
|
586
|
+
expect(cdpDisposed).toBe(true);
|
|
245
587
|
});
|
|
246
588
|
});
|