@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,580 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the raw CDP JSON-RPC WebSocket transport.
|
|
3
|
+
*
|
|
4
|
+
* These tests stand up a fake CDP peer on a random loopback port
|
|
5
|
+
* with `Bun.serve` so we can exercise every failure mode without
|
|
6
|
+
* any real Chrome install. The fake peer consumes the JSON-RPC
|
|
7
|
+
* request frame verbatim, then returns whatever envelope the test
|
|
8
|
+
* scenario wants (success, CDP error, delayed, etc.).
|
|
9
|
+
*
|
|
10
|
+
* Every test is responsible for stopping the fake server in a
|
|
11
|
+
* `try/finally` (or via `afterEach`) to avoid port leaks across the
|
|
12
|
+
* suite.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, test } from "bun:test";
|
|
16
|
+
|
|
17
|
+
import type { ServerWebSocket } from "bun";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
type CdpWsTransport,
|
|
21
|
+
CdpWsTransportError,
|
|
22
|
+
connectCdpWsTransport,
|
|
23
|
+
} from "../ws-transport.js";
|
|
24
|
+
|
|
25
|
+
interface WsFrame {
|
|
26
|
+
id?: number;
|
|
27
|
+
method?: string;
|
|
28
|
+
params?: Record<string, unknown>;
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface FakeWsServer {
|
|
33
|
+
url: string;
|
|
34
|
+
stop(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Start a fake websocket server whose inbound-message handler is
|
|
39
|
+
* controlled by the caller. The caller gets the raw parsed CDP
|
|
40
|
+
* request frame plus the underlying `ServerWebSocket` so it can
|
|
41
|
+
* respond, delay, close, or do nothing at all.
|
|
42
|
+
*/
|
|
43
|
+
function startFakeWsServer(options: {
|
|
44
|
+
onMessage?: (
|
|
45
|
+
ws: ServerWebSocket<undefined>,
|
|
46
|
+
frame: WsFrame,
|
|
47
|
+
raw: string,
|
|
48
|
+
) => void;
|
|
49
|
+
onOpen?: (ws: ServerWebSocket<undefined>) => void;
|
|
50
|
+
}): FakeWsServer {
|
|
51
|
+
const server = Bun.serve({
|
|
52
|
+
port: 0,
|
|
53
|
+
hostname: "127.0.0.1",
|
|
54
|
+
fetch(req, srv) {
|
|
55
|
+
if (srv.upgrade(req)) return;
|
|
56
|
+
return new Response("expected ws", { status: 400 });
|
|
57
|
+
},
|
|
58
|
+
websocket: {
|
|
59
|
+
open(ws) {
|
|
60
|
+
options.onOpen?.(ws);
|
|
61
|
+
},
|
|
62
|
+
message(ws, message) {
|
|
63
|
+
const raw =
|
|
64
|
+
typeof message === "string"
|
|
65
|
+
? message
|
|
66
|
+
: new TextDecoder().decode(message);
|
|
67
|
+
let parsed: WsFrame;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(raw) as WsFrame;
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
options.onMessage?.(ws, parsed, raw);
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const url = `ws://127.0.0.1:${server.port}`;
|
|
78
|
+
return {
|
|
79
|
+
url,
|
|
80
|
+
async stop() {
|
|
81
|
+
// Fire-and-forget stop. `await server.stop(true)` can hang
|
|
82
|
+
// in bun 1.3.x when a ws client has already closed, and
|
|
83
|
+
// `await server.stop()` waits for in-flight connections to
|
|
84
|
+
// drain, which can also hang in pathological tests. We don't
|
|
85
|
+
// need to block on shutdown for correctness — the process
|
|
86
|
+
// exits after the suite, and every test allocates a fresh
|
|
87
|
+
// random port.
|
|
88
|
+
server.stop();
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Start a fake HTTP (non-ws) server that refuses upgrade requests
|
|
95
|
+
* with `400`. Used to verify that `connectCdpWsTransport` rejects
|
|
96
|
+
* (with either `timeout` or `transport_error`) when the peer does
|
|
97
|
+
* not speak websockets.
|
|
98
|
+
*/
|
|
99
|
+
function startNonWsServer(): FakeWsServer {
|
|
100
|
+
const server = Bun.serve({
|
|
101
|
+
port: 0,
|
|
102
|
+
hostname: "127.0.0.1",
|
|
103
|
+
fetch() {
|
|
104
|
+
return new Response("no websocket here", { status: 400 });
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const url = `ws://127.0.0.1:${server.port}`;
|
|
108
|
+
return {
|
|
109
|
+
url,
|
|
110
|
+
async stop() {
|
|
111
|
+
server.stop();
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function withTransport<T>(
|
|
117
|
+
server: FakeWsServer,
|
|
118
|
+
fn: (transport: CdpWsTransport) => Promise<T>,
|
|
119
|
+
connectOpts?: { connectTimeoutMs?: number },
|
|
120
|
+
): Promise<T> {
|
|
121
|
+
const transport = await connectCdpWsTransport(server.url, connectOpts);
|
|
122
|
+
try {
|
|
123
|
+
return await fn(transport);
|
|
124
|
+
} finally {
|
|
125
|
+
transport.dispose();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
describe("CdpWsTransportError", () => {
|
|
130
|
+
test("subclasses Error with code and metadata", () => {
|
|
131
|
+
const err = new CdpWsTransportError("cdp_error", "boom", {
|
|
132
|
+
cdpMethod: "Page.navigate",
|
|
133
|
+
cdpCode: -32000,
|
|
134
|
+
cdpMessage: "boom",
|
|
135
|
+
cdpData: { foo: "bar" },
|
|
136
|
+
});
|
|
137
|
+
expect(err).toBeInstanceOf(Error);
|
|
138
|
+
expect(err).toBeInstanceOf(CdpWsTransportError);
|
|
139
|
+
expect(err.name).toBe("CdpWsTransportError");
|
|
140
|
+
expect(err.code).toBe("cdp_error");
|
|
141
|
+
expect(err.cdpMethod).toBe("Page.navigate");
|
|
142
|
+
expect(err.cdpCode).toBe(-32000);
|
|
143
|
+
expect(err.cdpMessage).toBe("boom");
|
|
144
|
+
expect(err.cdpData).toEqual({ foo: "bar" });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("supports all documented codes", () => {
|
|
148
|
+
const codes = [
|
|
149
|
+
"closed",
|
|
150
|
+
"aborted",
|
|
151
|
+
"timeout",
|
|
152
|
+
"transport_error",
|
|
153
|
+
"cdp_error",
|
|
154
|
+
] as const;
|
|
155
|
+
for (const code of codes) {
|
|
156
|
+
const err = new CdpWsTransportError(code);
|
|
157
|
+
expect(err.code).toBe(code);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("connectCdpWsTransport", () => {
|
|
163
|
+
test("send+receive success — Browser.getVersion style", async () => {
|
|
164
|
+
const server = startFakeWsServer({
|
|
165
|
+
onMessage(ws, frame) {
|
|
166
|
+
if (frame.method === "Browser.getVersion") {
|
|
167
|
+
ws.send(
|
|
168
|
+
JSON.stringify({
|
|
169
|
+
id: frame.id,
|
|
170
|
+
result: {
|
|
171
|
+
protocolVersion: "1.3",
|
|
172
|
+
product: "Chrome/123.0.0.0",
|
|
173
|
+
revision: "@deadbeef",
|
|
174
|
+
userAgent: "test",
|
|
175
|
+
jsVersion: "1.0",
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
try {
|
|
183
|
+
await withTransport(server, async (transport) => {
|
|
184
|
+
const result = await transport.send<{ product: string }>(
|
|
185
|
+
"Browser.getVersion",
|
|
186
|
+
);
|
|
187
|
+
expect(result.product).toBe("Chrome/123.0.0.0");
|
|
188
|
+
});
|
|
189
|
+
} finally {
|
|
190
|
+
await server.stop();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("rejects with cdp_error when the peer returns a JSON-RPC error", async () => {
|
|
195
|
+
const server = startFakeWsServer({
|
|
196
|
+
onMessage(ws, frame) {
|
|
197
|
+
ws.send(
|
|
198
|
+
JSON.stringify({
|
|
199
|
+
id: frame.id,
|
|
200
|
+
error: {
|
|
201
|
+
code: -32601,
|
|
202
|
+
message: "Method not found",
|
|
203
|
+
data: { method: frame.method },
|
|
204
|
+
},
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
await withTransport(server, async (transport) => {
|
|
211
|
+
await expect(transport.send("Bogus.method")).rejects.toMatchObject({
|
|
212
|
+
name: "CdpWsTransportError",
|
|
213
|
+
code: "cdp_error",
|
|
214
|
+
cdpMethod: "Bogus.method",
|
|
215
|
+
cdpCode: -32601,
|
|
216
|
+
cdpMessage: "Method not found",
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
} finally {
|
|
220
|
+
await server.stop();
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("handles concurrent out-of-order responses", async () => {
|
|
225
|
+
const pendingFrames: WsFrame[] = [];
|
|
226
|
+
const server = startFakeWsServer({
|
|
227
|
+
onMessage(ws, frame) {
|
|
228
|
+
pendingFrames.push(frame);
|
|
229
|
+
// Respond in reverse order after all three arrive.
|
|
230
|
+
if (pendingFrames.length === 3) {
|
|
231
|
+
const [f1, f2, f3] = pendingFrames;
|
|
232
|
+
ws.send(JSON.stringify({ id: f3!.id, result: { n: 3 } }));
|
|
233
|
+
ws.send(JSON.stringify({ id: f1!.id, result: { n: 1 } }));
|
|
234
|
+
ws.send(JSON.stringify({ id: f2!.id, result: { n: 2 } }));
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
try {
|
|
239
|
+
await withTransport(server, async (transport) => {
|
|
240
|
+
const p1 = transport.send<{ n: number }>("A.one");
|
|
241
|
+
const p2 = transport.send<{ n: number }>("B.two");
|
|
242
|
+
const p3 = transport.send<{ n: number }>("C.three");
|
|
243
|
+
const results = await Promise.all([p1, p2, p3]);
|
|
244
|
+
expect(results).toEqual([{ n: 1 }, { n: 2 }, { n: 3 }]);
|
|
245
|
+
});
|
|
246
|
+
} finally {
|
|
247
|
+
await server.stop();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("fans out events (frames with no id) to listeners", async () => {
|
|
252
|
+
const server = startFakeWsServer({
|
|
253
|
+
onOpen(ws) {
|
|
254
|
+
// Push an unsolicited event shortly after open.
|
|
255
|
+
setTimeout(() => {
|
|
256
|
+
ws.send(
|
|
257
|
+
JSON.stringify({
|
|
258
|
+
method: "Target.targetCreated",
|
|
259
|
+
params: { targetId: "abc" },
|
|
260
|
+
sessionId: "S1",
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
}, 5);
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
try {
|
|
267
|
+
await withTransport(server, async (transport) => {
|
|
268
|
+
const received: Array<{
|
|
269
|
+
method: string;
|
|
270
|
+
params?: unknown;
|
|
271
|
+
sessionId?: string;
|
|
272
|
+
}> = [];
|
|
273
|
+
transport.addEventListener((ev) => {
|
|
274
|
+
received.push(ev);
|
|
275
|
+
});
|
|
276
|
+
// Wait for the event to arrive.
|
|
277
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
278
|
+
expect(received).toEqual([
|
|
279
|
+
{
|
|
280
|
+
method: "Target.targetCreated",
|
|
281
|
+
params: { targetId: "abc" },
|
|
282
|
+
sessionId: "S1",
|
|
283
|
+
},
|
|
284
|
+
]);
|
|
285
|
+
});
|
|
286
|
+
} finally {
|
|
287
|
+
await server.stop();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("does not resolve any pending request for a no-id event", async () => {
|
|
292
|
+
let gotRequest = false;
|
|
293
|
+
const server = startFakeWsServer({
|
|
294
|
+
onOpen(ws) {
|
|
295
|
+
// Emit a no-id event before any send() happens.
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
ws.send(
|
|
298
|
+
JSON.stringify({
|
|
299
|
+
method: "Target.attachedToTarget",
|
|
300
|
+
params: {},
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
}, 5);
|
|
304
|
+
},
|
|
305
|
+
onMessage(ws, frame) {
|
|
306
|
+
gotRequest = true;
|
|
307
|
+
ws.send(JSON.stringify({ id: frame.id, result: { ok: true } }));
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
try {
|
|
311
|
+
await withTransport(server, async (transport) => {
|
|
312
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
313
|
+
// Pending-request map must still be empty here — otherwise
|
|
314
|
+
// a subsequent send would deadlock on a stale entry.
|
|
315
|
+
const result = await transport.send<{ ok: boolean }>("Test.ping");
|
|
316
|
+
expect(result.ok).toBe(true);
|
|
317
|
+
expect(gotRequest).toBe(true);
|
|
318
|
+
});
|
|
319
|
+
} finally {
|
|
320
|
+
await server.stop();
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("forwards sessionId on the wire when opts.sessionId is provided", async () => {
|
|
325
|
+
let seen: WsFrame | null = null;
|
|
326
|
+
const server = startFakeWsServer({
|
|
327
|
+
onMessage(ws, frame) {
|
|
328
|
+
seen = frame;
|
|
329
|
+
ws.send(JSON.stringify({ id: frame.id, result: {} }));
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
try {
|
|
333
|
+
await withTransport(server, async (transport) => {
|
|
334
|
+
await transport.send("Runtime.enable", undefined, {
|
|
335
|
+
sessionId: "sess-42",
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
expect(seen).not.toBeNull();
|
|
339
|
+
// seen is WsFrame | null; narrow via unknown for the
|
|
340
|
+
// assertions below (TS narrows `seen` to `null` inside the
|
|
341
|
+
// closure, which is why a direct cast trips noImplicitAny).
|
|
342
|
+
const frame = seen as unknown as WsFrame;
|
|
343
|
+
expect(frame.sessionId).toBe("sess-42");
|
|
344
|
+
expect(frame.method).toBe("Runtime.enable");
|
|
345
|
+
} finally {
|
|
346
|
+
await server.stop();
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("abort mid-request rejects with aborted and drops late response", async () => {
|
|
351
|
+
let deferredFrameId: number | undefined;
|
|
352
|
+
let serverWs: ServerWebSocket<undefined> | null = null;
|
|
353
|
+
const server = startFakeWsServer({
|
|
354
|
+
onMessage(ws, frame) {
|
|
355
|
+
// Hold the response indefinitely so the abort can race.
|
|
356
|
+
deferredFrameId = frame.id;
|
|
357
|
+
serverWs = ws;
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
try {
|
|
361
|
+
const transport = await connectCdpWsTransport(server.url);
|
|
362
|
+
try {
|
|
363
|
+
const controller = new AbortController();
|
|
364
|
+
const sendPromise = transport.send(
|
|
365
|
+
"Slow.call",
|
|
366
|
+
{},
|
|
367
|
+
{
|
|
368
|
+
signal: controller.signal,
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
// Let the request land on the server.
|
|
372
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
373
|
+
controller.abort();
|
|
374
|
+
await expect(sendPromise).rejects.toMatchObject({
|
|
375
|
+
name: "CdpWsTransportError",
|
|
376
|
+
code: "aborted",
|
|
377
|
+
cdpMethod: "Slow.call",
|
|
378
|
+
});
|
|
379
|
+
// Now deliver a response for the dropped id. The transport
|
|
380
|
+
// should silently ignore it — a subsequent send should
|
|
381
|
+
// still work and MUST NOT deadlock.
|
|
382
|
+
if (deferredFrameId !== undefined && serverWs) {
|
|
383
|
+
(serverWs as ServerWebSocket<undefined>).send(
|
|
384
|
+
JSON.stringify({ id: deferredFrameId, result: { late: true } }),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
// Follow-up request must still function.
|
|
388
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
389
|
+
} finally {
|
|
390
|
+
transport.dispose();
|
|
391
|
+
}
|
|
392
|
+
} finally {
|
|
393
|
+
await server.stop();
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("server close mid-request rejects pending with closed", async () => {
|
|
398
|
+
const server = startFakeWsServer({
|
|
399
|
+
onMessage(ws) {
|
|
400
|
+
// Close the socket without responding.
|
|
401
|
+
ws.close();
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
try {
|
|
405
|
+
const transport = await connectCdpWsTransport(server.url);
|
|
406
|
+
try {
|
|
407
|
+
let caught: unknown;
|
|
408
|
+
try {
|
|
409
|
+
await transport.send("Some.method");
|
|
410
|
+
} catch (err) {
|
|
411
|
+
caught = err;
|
|
412
|
+
}
|
|
413
|
+
expect(caught).toBeInstanceOf(CdpWsTransportError);
|
|
414
|
+
expect((caught as CdpWsTransportError).code).toBe("closed");
|
|
415
|
+
|
|
416
|
+
// After close, subsequent sends also reject with closed.
|
|
417
|
+
let caught2: unknown;
|
|
418
|
+
try {
|
|
419
|
+
await transport.send("Another.method");
|
|
420
|
+
} catch (err) {
|
|
421
|
+
caught2 = err;
|
|
422
|
+
}
|
|
423
|
+
expect(caught2).toBeInstanceOf(CdpWsTransportError);
|
|
424
|
+
expect((caught2 as CdpWsTransportError).code).toBe("closed");
|
|
425
|
+
} finally {
|
|
426
|
+
transport.dispose();
|
|
427
|
+
}
|
|
428
|
+
} finally {
|
|
429
|
+
await server.stop();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("connect rejects with transport_error when peer refuses upgrade", async () => {
|
|
434
|
+
const server = startNonWsServer();
|
|
435
|
+
try {
|
|
436
|
+
await expect(
|
|
437
|
+
connectCdpWsTransport(server.url, { connectTimeoutMs: 500 }),
|
|
438
|
+
).rejects.toMatchObject({
|
|
439
|
+
name: "CdpWsTransportError",
|
|
440
|
+
});
|
|
441
|
+
// Code can be either timeout or transport_error depending on
|
|
442
|
+
// how the runtime surfaces an HTTP 400 upgrade failure — both
|
|
443
|
+
// are acceptable per the transport contract.
|
|
444
|
+
try {
|
|
445
|
+
await connectCdpWsTransport(server.url, { connectTimeoutMs: 200 });
|
|
446
|
+
} catch (err) {
|
|
447
|
+
expect(err).toBeInstanceOf(CdpWsTransportError);
|
|
448
|
+
const code = (err as CdpWsTransportError).code;
|
|
449
|
+
expect(["timeout", "transport_error"]).toContain(code);
|
|
450
|
+
}
|
|
451
|
+
} finally {
|
|
452
|
+
await server.stop();
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("connect rejects with timeout when peer never accepts", async () => {
|
|
457
|
+
// A server that hangs forever (never responds to the upgrade).
|
|
458
|
+
// We simulate this by pointing at an unroutable port guarded by
|
|
459
|
+
// a very short connect timeout; expect either timeout or
|
|
460
|
+
// transport_error since runtimes vary in how they surface a
|
|
461
|
+
// refused connection.
|
|
462
|
+
let err: unknown;
|
|
463
|
+
try {
|
|
464
|
+
await connectCdpWsTransport("ws://127.0.0.1:1", {
|
|
465
|
+
connectTimeoutMs: 100,
|
|
466
|
+
});
|
|
467
|
+
} catch (caught) {
|
|
468
|
+
err = caught;
|
|
469
|
+
}
|
|
470
|
+
expect(err).toBeInstanceOf(CdpWsTransportError);
|
|
471
|
+
const code = (err as CdpWsTransportError).code;
|
|
472
|
+
expect(["timeout", "transport_error"]).toContain(code);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("dispose is idempotent", async () => {
|
|
476
|
+
const server = startFakeWsServer({});
|
|
477
|
+
try {
|
|
478
|
+
const transport = await connectCdpWsTransport(server.url);
|
|
479
|
+
transport.dispose();
|
|
480
|
+
expect(() => transport.dispose()).not.toThrow();
|
|
481
|
+
expect(() => transport.dispose()).not.toThrow();
|
|
482
|
+
// send after dispose rejects with closed.
|
|
483
|
+
await expect(transport.send("X")).rejects.toMatchObject({
|
|
484
|
+
code: "closed",
|
|
485
|
+
});
|
|
486
|
+
} finally {
|
|
487
|
+
await server.stop();
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("dispose rejects any in-flight pending requests with closed", async () => {
|
|
492
|
+
const server = startFakeWsServer({
|
|
493
|
+
onMessage() {
|
|
494
|
+
// Never respond — we want the request to stay pending until
|
|
495
|
+
// dispose fires.
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
try {
|
|
499
|
+
const transport = await connectCdpWsTransport(server.url);
|
|
500
|
+
const p = transport.send("Never.responds");
|
|
501
|
+
// Give the request time to reach the server.
|
|
502
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
503
|
+
transport.dispose();
|
|
504
|
+
await expect(p).rejects.toMatchObject({
|
|
505
|
+
name: "CdpWsTransportError",
|
|
506
|
+
code: "closed",
|
|
507
|
+
});
|
|
508
|
+
} finally {
|
|
509
|
+
await server.stop();
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("addEventListener returns an unsubscribe function", async () => {
|
|
514
|
+
const server = startFakeWsServer({
|
|
515
|
+
onOpen(ws) {
|
|
516
|
+
setTimeout(() => {
|
|
517
|
+
ws.send(JSON.stringify({ method: "Ev.first", params: {} }));
|
|
518
|
+
setTimeout(() => {
|
|
519
|
+
ws.send(JSON.stringify({ method: "Ev.second", params: {} }));
|
|
520
|
+
}, 10);
|
|
521
|
+
}, 5);
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
try {
|
|
525
|
+
await withTransport(server, async (transport) => {
|
|
526
|
+
const received: string[] = [];
|
|
527
|
+
const unsub = transport.addEventListener((ev) => {
|
|
528
|
+
received.push(ev.method);
|
|
529
|
+
if (ev.method === "Ev.first") unsub();
|
|
530
|
+
});
|
|
531
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
532
|
+
expect(received).toEqual(["Ev.first"]);
|
|
533
|
+
});
|
|
534
|
+
} finally {
|
|
535
|
+
await server.stop();
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test("send after dispose rejects immediately with closed", async () => {
|
|
540
|
+
const server = startFakeWsServer({});
|
|
541
|
+
try {
|
|
542
|
+
const transport = await connectCdpWsTransport(server.url);
|
|
543
|
+
transport.dispose();
|
|
544
|
+
await expect(transport.send("Page.enable")).rejects.toMatchObject({
|
|
545
|
+
name: "CdpWsTransportError",
|
|
546
|
+
code: "closed",
|
|
547
|
+
cdpMethod: "Page.enable",
|
|
548
|
+
});
|
|
549
|
+
} finally {
|
|
550
|
+
await server.stop();
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test("listener errors are swallowed and do not affect correlation", async () => {
|
|
555
|
+
const server = startFakeWsServer({
|
|
556
|
+
onOpen(ws) {
|
|
557
|
+
setTimeout(() => {
|
|
558
|
+
ws.send(JSON.stringify({ method: "Boom.event", params: {} }));
|
|
559
|
+
}, 5);
|
|
560
|
+
},
|
|
561
|
+
onMessage(ws, frame) {
|
|
562
|
+
ws.send(JSON.stringify({ id: frame.id, result: { ok: true } }));
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
try {
|
|
566
|
+
await withTransport(server, async (transport) => {
|
|
567
|
+
transport.addEventListener(() => {
|
|
568
|
+
throw new Error("listener crash");
|
|
569
|
+
});
|
|
570
|
+
// Wait for the event to arrive and be swallowed.
|
|
571
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
572
|
+
// Subsequent requests must still work.
|
|
573
|
+
const result = await transport.send<{ ok: boolean }>("Page.enable");
|
|
574
|
+
expect(result.ok).toBe(true);
|
|
575
|
+
});
|
|
576
|
+
} finally {
|
|
577
|
+
await server.stop();
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
});
|