@vellumai/assistant 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docker-entrypoint.sh +12 -2
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/checker.test.ts +104 -170
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +21 -6
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/log-export-workspace.test.ts +190 -0
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +74 -55
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -3
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +11 -5
- package/src/__tests__/test-preload.ts +14 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +62 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -35
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +55 -0
- package/src/cli/__tests__/run-assistant-command.ts +34 -7
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +16 -2
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +10 -3
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +12 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +46 -7
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +16 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -16
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +6 -2
- package/src/daemon/context-overflow-approval.ts +5 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +74 -19
- package/src/daemon/conversation-attachments.ts +40 -1
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +66 -3
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +159 -20
- package/src/daemon/conversation-surfaces.ts +78 -12
- package/src/daemon/conversation-tool-setup.ts +74 -11
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +227 -11
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -139
- package/src/daemon/handlers/shared.ts +65 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +86 -12
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +59 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -6
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/settings.ts +12 -0
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +112 -35
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +14 -0
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +38 -10
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +65 -5
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +177 -18
- package/src/memory/graph/capability-seed.ts +3 -5
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +5 -5
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +127 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -96
- package/src/prompts/templates/SOUL.md +11 -11
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +24 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +308 -28
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +60 -25
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -100
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -1
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +24 -21
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -3
- package/src/util/platform.ts +14 -19
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/029-seed-pkb.ts +1 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import { getLogger } from "../../../util/logger.js";
|
|
2
|
+
import {
|
|
3
|
+
type DevToolsTarget,
|
|
4
|
+
type DevToolsVersionInfo,
|
|
5
|
+
listDevToolsTargets,
|
|
6
|
+
pickDefaultTarget,
|
|
7
|
+
probeDevToolsJsonVersion,
|
|
8
|
+
} from "./cdp-inspect/discovery.js";
|
|
9
|
+
import {
|
|
10
|
+
type CdpWsTransport,
|
|
11
|
+
CdpWsTransportError,
|
|
12
|
+
connectCdpWsTransport,
|
|
13
|
+
} from "./cdp-inspect/ws-transport.js";
|
|
14
|
+
import { CdpError } from "./errors.js";
|
|
15
|
+
import type { CdpClientKind, ScopedCdpClient } from "./types.js";
|
|
16
|
+
|
|
17
|
+
const log = getLogger("cdp-inspect-client");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default timeout (ms) for each discovery HTTP probe. Kept short so a
|
|
21
|
+
* user who has no chrome running on the configured port fails fast
|
|
22
|
+
* instead of blocking the entire tool invocation. The ws-transport
|
|
23
|
+
* has its own, separate connect timeout.
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_DISCOVERY_TIMEOUT_MS = 2_000;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Subset of DevTools endpoint config the CdpInspectClient needs. The
|
|
29
|
+
* higher-level factory is responsible for feeding these values
|
|
30
|
+
* from the user's settings. Everything else — connect timeouts, ws
|
|
31
|
+
* retries, abort plumbing — is controlled locally here so we don't
|
|
32
|
+
* leak transport knobs into tool call sites.
|
|
33
|
+
*/
|
|
34
|
+
export interface CdpInspectClientOptions {
|
|
35
|
+
/** Loopback host — enforced by discovery helpers before any I/O. */
|
|
36
|
+
host: string;
|
|
37
|
+
/** Port the user's Chrome is listening on for DevTools HTTP. */
|
|
38
|
+
port: number;
|
|
39
|
+
/** Optional per-attach discovery probe timeout. */
|
|
40
|
+
discoveryTimeoutMs?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Optional per-attach ws connect timeout. Forwarded verbatim to
|
|
43
|
+
* {@link connectCdpWsTransport}.
|
|
44
|
+
*/
|
|
45
|
+
wsConnectTimeoutMs?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Test seam: override the discovery / transport helpers so unit
|
|
48
|
+
* tests don't need a real Chrome or a Bun.serve-backed fake peer.
|
|
49
|
+
* The factory does not use this path.
|
|
50
|
+
*/
|
|
51
|
+
helpers?: CdpInspectHelpers;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Override shape used by tests. Each field defaults to the real
|
|
56
|
+
* implementation imported at the top of this module when omitted.
|
|
57
|
+
*/
|
|
58
|
+
export interface CdpInspectHelpers {
|
|
59
|
+
probeDevToolsJsonVersion?: typeof probeDevToolsJsonVersion;
|
|
60
|
+
listDevToolsTargets?: typeof listDevToolsTargets;
|
|
61
|
+
pickDefaultTarget?: typeof pickDefaultTarget;
|
|
62
|
+
connectCdpWsTransport?: typeof connectCdpWsTransport;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface AttachedSession {
|
|
66
|
+
transport: CdpWsTransport;
|
|
67
|
+
sessionId: string;
|
|
68
|
+
target: DevToolsTarget;
|
|
69
|
+
version: DevToolsVersionInfo;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* In-flight attach handle. Wraps the shared attach promise with a
|
|
74
|
+
* dedicated {@link AbortController} and a ref-count of live callers
|
|
75
|
+
* waiting on the attach. When every caller has raced its own signal
|
|
76
|
+
* and given up, the ref-count drops to zero and the shared controller
|
|
77
|
+
* aborts so the underlying discovery / ws / `Target.attachToTarget`
|
|
78
|
+
* work stops promptly instead of wedging the socket.
|
|
79
|
+
*/
|
|
80
|
+
interface PendingAttach {
|
|
81
|
+
/** Shared attach promise — resolved exactly once per attach attempt. */
|
|
82
|
+
promise: Promise<AttachedSession>;
|
|
83
|
+
/** Controller wired through probe / list / connect / attach. */
|
|
84
|
+
controller: AbortController;
|
|
85
|
+
/** Number of live callers still awaiting this attach. */
|
|
86
|
+
waiters: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* CdpClient backed by the DevTools JSON protocol over a raw
|
|
91
|
+
* WebSocket (the `cdp-inspect` transport). Composes the discovery
|
|
92
|
+
* helpers (`probeDevToolsJsonVersion` + `listDevToolsTargets` +
|
|
93
|
+
* `pickDefaultTarget`) with the shared `connectCdpWsTransport` to
|
|
94
|
+
* reach an already-running Chrome instance the user has launched
|
|
95
|
+
* with `--remote-debugging-port`.
|
|
96
|
+
*
|
|
97
|
+
* Lifetime mirrors {@link import("./local-cdp-client.js").LocalCdpClient}:
|
|
98
|
+
*
|
|
99
|
+
* - Lazy one-time attach: the first `send()` performs version probe
|
|
100
|
+
* + target discovery + ws connect + `Target.attachToTarget`, then
|
|
101
|
+
* caches the session for every subsequent call.
|
|
102
|
+
* - Concurrent callers share a single in-flight attach promise so
|
|
103
|
+
* `Target.attachToTarget` runs exactly once per client instance.
|
|
104
|
+
* - Each `send(..., signal)` caller can race its own AbortSignal
|
|
105
|
+
* against the shared attach and cut through promptly. When every
|
|
106
|
+
* concurrent caller has aborted, the shared attach work is also
|
|
107
|
+
* cancelled so we don't leak discovery fetches or a half-open ws.
|
|
108
|
+
* - If the attach promise rejects, the cached promise is cleared so
|
|
109
|
+
* the next `send()` retries from scratch instead of replaying the
|
|
110
|
+
* same failure forever.
|
|
111
|
+
* - `dispose()` is idempotent and tears down the ws transport if an
|
|
112
|
+
* attach ever resolved.
|
|
113
|
+
*/
|
|
114
|
+
export class CdpInspectClient implements ScopedCdpClient {
|
|
115
|
+
readonly kind: CdpClientKind = "cdp-inspect";
|
|
116
|
+
|
|
117
|
+
private pending: PendingAttach | null = null;
|
|
118
|
+
private session: AttachedSession | null = null;
|
|
119
|
+
private disposed = false;
|
|
120
|
+
private readonly helpers: Required<CdpInspectHelpers>;
|
|
121
|
+
|
|
122
|
+
constructor(
|
|
123
|
+
public readonly conversationId: string,
|
|
124
|
+
private readonly options: CdpInspectClientOptions,
|
|
125
|
+
) {
|
|
126
|
+
this.helpers = {
|
|
127
|
+
probeDevToolsJsonVersion:
|
|
128
|
+
options.helpers?.probeDevToolsJsonVersion ?? probeDevToolsJsonVersion,
|
|
129
|
+
listDevToolsTargets:
|
|
130
|
+
options.helpers?.listDevToolsTargets ?? listDevToolsTargets,
|
|
131
|
+
pickDefaultTarget:
|
|
132
|
+
options.helpers?.pickDefaultTarget ?? pickDefaultTarget,
|
|
133
|
+
connectCdpWsTransport:
|
|
134
|
+
options.helpers?.connectCdpWsTransport ?? connectCdpWsTransport,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Lazily attach (and cache) a CDP session against the configured
|
|
140
|
+
* DevTools endpoint. Each caller races its own `signal` against the
|
|
141
|
+
* shared attach so an individual abort always wins promptly; when
|
|
142
|
+
* every waiter has aborted, the shared attach work is cancelled
|
|
143
|
+
* too via the pending attach's internal controller. See class-level
|
|
144
|
+
* docs for the resilience contract — in particular, transient
|
|
145
|
+
* attach failures must NOT poison the cached promise for subsequent
|
|
146
|
+
* calls.
|
|
147
|
+
*/
|
|
148
|
+
private async ensureSession(signal?: AbortSignal): Promise<AttachedSession> {
|
|
149
|
+
if (this.disposed) {
|
|
150
|
+
throw new CdpError("disposed", "CdpInspectClient already disposed");
|
|
151
|
+
}
|
|
152
|
+
if (this.session) return this.session;
|
|
153
|
+
|
|
154
|
+
const pending = this.pending ?? this.startAttach();
|
|
155
|
+
pending.waiters += 1;
|
|
156
|
+
|
|
157
|
+
// `onAbort` fires exactly once if this caller's signal wins the
|
|
158
|
+
// race. It (a) drops this caller's waiter slot and (b) aborts
|
|
159
|
+
// the shared controller iff no other caller is still listening,
|
|
160
|
+
// so the underlying discovery / ws / attach work is cancelled
|
|
161
|
+
// promptly instead of leaking into the background.
|
|
162
|
+
let released = false;
|
|
163
|
+
const onAbort = () => {
|
|
164
|
+
if (released) return;
|
|
165
|
+
released = true;
|
|
166
|
+
pending.waiters -= 1;
|
|
167
|
+
if (pending.waiters <= 0 && this.pending === pending) {
|
|
168
|
+
// Clear the cached pending attach synchronously BEFORE
|
|
169
|
+
// firing the shared controller's abort. Otherwise a new
|
|
170
|
+
// `send()` that enters `ensureSession` between this abort
|
|
171
|
+
// and the async `.catch()` handler in `startAttach()` would
|
|
172
|
+
// reuse this already-aborted attach and immediately fail
|
|
173
|
+
// with an `aborted` error even though the new caller never
|
|
174
|
+
// aborted its own signal.
|
|
175
|
+
this.pending = null;
|
|
176
|
+
try {
|
|
177
|
+
pending.controller.abort();
|
|
178
|
+
} catch {
|
|
179
|
+
// best effort
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
return await raceAbort(pending.promise, signal, onAbort);
|
|
186
|
+
} finally {
|
|
187
|
+
// Inner attach resolved or rejected before this caller's
|
|
188
|
+
// signal fired — drop the waiter slot without touching the
|
|
189
|
+
// shared controller (it's already settled). If `onAbort` ran,
|
|
190
|
+
// `released` is already true and this is a no-op.
|
|
191
|
+
if (!released) {
|
|
192
|
+
released = true;
|
|
193
|
+
pending.waiters -= 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Kick off a fresh shared attach. The returned {@link PendingAttach}
|
|
200
|
+
* is cached on `this.pending` until it either resolves (in which
|
|
201
|
+
* case the session is stashed on `this.session`) or rejects (in
|
|
202
|
+
* which case the cache is cleared so the next caller retries from
|
|
203
|
+
* scratch).
|
|
204
|
+
*/
|
|
205
|
+
private startAttach(): PendingAttach {
|
|
206
|
+
const controller = new AbortController();
|
|
207
|
+
const pending: PendingAttach = {
|
|
208
|
+
controller,
|
|
209
|
+
waiters: 0,
|
|
210
|
+
promise: this.attach(controller.signal),
|
|
211
|
+
};
|
|
212
|
+
this.pending = pending;
|
|
213
|
+
|
|
214
|
+
pending.promise
|
|
215
|
+
.then((session) => {
|
|
216
|
+
// Another concurrent attach may have won the race (e.g. after
|
|
217
|
+
// a dispose + retry). Only cache the result if we're still
|
|
218
|
+
// the current attempt.
|
|
219
|
+
if (this.pending === pending) {
|
|
220
|
+
this.pending = null;
|
|
221
|
+
if (this.disposed) {
|
|
222
|
+
// Dispose landed before we could publish the session —
|
|
223
|
+
// tear down the transport immediately so we don't leak.
|
|
224
|
+
try {
|
|
225
|
+
session.transport.dispose();
|
|
226
|
+
} catch {
|
|
227
|
+
// best effort
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
this.session = session;
|
|
232
|
+
} else {
|
|
233
|
+
// A stale attempt — drop the transport so we don't leak.
|
|
234
|
+
try {
|
|
235
|
+
session.transport.dispose();
|
|
236
|
+
} catch {
|
|
237
|
+
// best effort
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
.catch(() => {
|
|
242
|
+
// Clear the cached pending attach on rejection so the next
|
|
243
|
+
// call retries from scratch instead of replaying the same
|
|
244
|
+
// failure forever. Only clear if we're still the current
|
|
245
|
+
// attempt — a concurrent dispose may have already nulled it.
|
|
246
|
+
if (this.pending === pending) {
|
|
247
|
+
this.pending = null;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return pending;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Perform the actual discovery + ws-connect + attach sequence. All
|
|
256
|
+
* underlying errors are rethrown unchanged so the `send()` wrapper
|
|
257
|
+
* can map them to stable `CdpError` codes without double-wrapping
|
|
258
|
+
* the already-typed discovery / ws-transport errors.
|
|
259
|
+
*
|
|
260
|
+
* The `signal` here is the shared, internal {@link PendingAttach}
|
|
261
|
+
* signal — NOT the per-caller signal. It is aborted when the last
|
|
262
|
+
* caller interested in this attach has given up, or when `dispose()`
|
|
263
|
+
* races an in-flight attach.
|
|
264
|
+
*/
|
|
265
|
+
private async attach(signal: AbortSignal): Promise<AttachedSession> {
|
|
266
|
+
const discoveryTimeoutMs =
|
|
267
|
+
this.options.discoveryTimeoutMs ?? DEFAULT_DISCOVERY_TIMEOUT_MS;
|
|
268
|
+
const { host, port } = this.options;
|
|
269
|
+
|
|
270
|
+
const version = await this.helpers.probeDevToolsJsonVersion({
|
|
271
|
+
host,
|
|
272
|
+
port,
|
|
273
|
+
timeoutMs: discoveryTimeoutMs,
|
|
274
|
+
signal,
|
|
275
|
+
});
|
|
276
|
+
if (signal.aborted) {
|
|
277
|
+
throw new CdpError("aborted", "CdpInspectClient attach aborted");
|
|
278
|
+
}
|
|
279
|
+
const targets = await this.helpers.listDevToolsTargets({
|
|
280
|
+
host,
|
|
281
|
+
port,
|
|
282
|
+
timeoutMs: discoveryTimeoutMs,
|
|
283
|
+
signal,
|
|
284
|
+
});
|
|
285
|
+
if (signal.aborted) {
|
|
286
|
+
throw new CdpError("aborted", "CdpInspectClient attach aborted");
|
|
287
|
+
}
|
|
288
|
+
const target = this.helpers.pickDefaultTarget(targets);
|
|
289
|
+
|
|
290
|
+
// Prefer the browser-level ws URL from the version probe because
|
|
291
|
+
// it lets us multiplex multiple attached targets through a single
|
|
292
|
+
// transport. Fall back to the target-specific URL if (for some
|
|
293
|
+
// reason) the version probe omitted it.
|
|
294
|
+
const wsUrl = version.webSocketDebuggerUrl || target.webSocketDebuggerUrl;
|
|
295
|
+
const transport = await this.helpers.connectCdpWsTransport(wsUrl, {
|
|
296
|
+
connectTimeoutMs: this.options.wsConnectTimeoutMs,
|
|
297
|
+
signal,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// If dispose() landed while connect was in flight, tear down the
|
|
301
|
+
// transport we just opened and surface a "disposed" CdpError to
|
|
302
|
+
// the caller so we don't leak a half-attached session.
|
|
303
|
+
if (this.disposed) {
|
|
304
|
+
try {
|
|
305
|
+
transport.dispose();
|
|
306
|
+
} catch {
|
|
307
|
+
// best effort
|
|
308
|
+
}
|
|
309
|
+
throw new CdpError("disposed", "CdpInspectClient disposed during attach");
|
|
310
|
+
}
|
|
311
|
+
if (signal.aborted) {
|
|
312
|
+
try {
|
|
313
|
+
transport.dispose();
|
|
314
|
+
} catch {
|
|
315
|
+
// best effort
|
|
316
|
+
}
|
|
317
|
+
throw new CdpError(
|
|
318
|
+
"aborted",
|
|
319
|
+
"CdpInspectClient attach aborted after ws connect",
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let attachResult: unknown;
|
|
324
|
+
try {
|
|
325
|
+
attachResult = await transport.send<unknown>(
|
|
326
|
+
"Target.attachToTarget",
|
|
327
|
+
{
|
|
328
|
+
targetId: target.id,
|
|
329
|
+
flatten: true,
|
|
330
|
+
},
|
|
331
|
+
{ signal },
|
|
332
|
+
);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
// Attach failed — drop the transport we just opened so we don't
|
|
335
|
+
// leak the socket on retry.
|
|
336
|
+
try {
|
|
337
|
+
transport.dispose();
|
|
338
|
+
} catch {
|
|
339
|
+
// best effort
|
|
340
|
+
}
|
|
341
|
+
throw err;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const sessionId = extractSessionId(attachResult);
|
|
345
|
+
if (!sessionId) {
|
|
346
|
+
try {
|
|
347
|
+
transport.dispose();
|
|
348
|
+
} catch {
|
|
349
|
+
// best effort
|
|
350
|
+
}
|
|
351
|
+
throw new CdpError(
|
|
352
|
+
"cdp_error",
|
|
353
|
+
"Target.attachToTarget did not return a sessionId",
|
|
354
|
+
{ cdpMethod: "Target.attachToTarget" },
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
log.debug(
|
|
359
|
+
{
|
|
360
|
+
conversationId: this.conversationId,
|
|
361
|
+
targetId: target.id,
|
|
362
|
+
sessionId,
|
|
363
|
+
},
|
|
364
|
+
"Attached CdpInspectClient session",
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
return { transport, sessionId, target, version };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async send<T = unknown>(
|
|
371
|
+
method: string,
|
|
372
|
+
params?: Record<string, unknown>,
|
|
373
|
+
signal?: AbortSignal,
|
|
374
|
+
): Promise<T> {
|
|
375
|
+
if (this.disposed) {
|
|
376
|
+
throw new CdpError("disposed", "CdpInspectClient already disposed", {
|
|
377
|
+
cdpMethod: method,
|
|
378
|
+
cdpParams: params,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
if (signal?.aborted) {
|
|
382
|
+
throw new CdpError("aborted", "Aborted before send", {
|
|
383
|
+
cdpMethod: method,
|
|
384
|
+
cdpParams: params,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let attached: AttachedSession;
|
|
389
|
+
try {
|
|
390
|
+
attached = await this.ensureSession(signal);
|
|
391
|
+
} catch (err) {
|
|
392
|
+
if (signal?.aborted) {
|
|
393
|
+
throw new CdpError("aborted", "Aborted during send", {
|
|
394
|
+
cdpMethod: method,
|
|
395
|
+
cdpParams: params,
|
|
396
|
+
underlying: err,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
// If a concurrent dispose() aborted the shared attach under us,
|
|
400
|
+
// surface a stable "disposed" error instead of the incidental
|
|
401
|
+
// discovery / transport rejection that the aborted work threw.
|
|
402
|
+
if (this.disposed) {
|
|
403
|
+
throw new CdpError("disposed", "CdpInspectClient already disposed", {
|
|
404
|
+
cdpMethod: method,
|
|
405
|
+
cdpParams: params,
|
|
406
|
+
underlying: err,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
throw mapEnsureSessionError(err, method, params);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// A late dispose may have landed while ensureSession was in
|
|
413
|
+
// flight — surface a "disposed" error instead of sending into a
|
|
414
|
+
// torn-down transport.
|
|
415
|
+
if (this.disposed) {
|
|
416
|
+
throw new CdpError("disposed", "CdpInspectClient already disposed", {
|
|
417
|
+
cdpMethod: method,
|
|
418
|
+
cdpParams: params,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
return (await attached.transport.send<T>(method, params, {
|
|
424
|
+
sessionId: attached.sessionId,
|
|
425
|
+
signal,
|
|
426
|
+
})) as T;
|
|
427
|
+
} catch (err) {
|
|
428
|
+
if (signal?.aborted) {
|
|
429
|
+
throw new CdpError("aborted", "Aborted during send", {
|
|
430
|
+
cdpMethod: method,
|
|
431
|
+
cdpParams: params,
|
|
432
|
+
underlying: err,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
if (err instanceof CdpWsTransportError) {
|
|
436
|
+
if (err.code === "aborted") {
|
|
437
|
+
throw new CdpError("aborted", err.message, {
|
|
438
|
+
cdpMethod: method,
|
|
439
|
+
cdpParams: params,
|
|
440
|
+
underlying: err,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (err.code === "cdp_error") {
|
|
444
|
+
throw new CdpError("cdp_error", err.cdpMessage ?? err.message, {
|
|
445
|
+
cdpMethod: method,
|
|
446
|
+
cdpParams: params,
|
|
447
|
+
underlying: err,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
// closed / timeout / transport_error all map onto
|
|
451
|
+
// transport_error in the shared CdpClient taxonomy.
|
|
452
|
+
throw new CdpError("transport_error", err.message, {
|
|
453
|
+
cdpMethod: method,
|
|
454
|
+
cdpParams: params,
|
|
455
|
+
underlying: err,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
if (err instanceof CdpError) {
|
|
459
|
+
throw err;
|
|
460
|
+
}
|
|
461
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
462
|
+
throw new CdpError("cdp_error", msg, {
|
|
463
|
+
cdpMethod: method,
|
|
464
|
+
cdpParams: params,
|
|
465
|
+
underlying: err,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
dispose(): void {
|
|
471
|
+
if (this.disposed) return;
|
|
472
|
+
this.disposed = true;
|
|
473
|
+
|
|
474
|
+
// Cancel any in-flight attach so discovery / ws / Target.attach
|
|
475
|
+
// stop promptly. The `.then()` handler in startAttach() will
|
|
476
|
+
// tear down any transport that managed to open before dispose
|
|
477
|
+
// landed.
|
|
478
|
+
const pending = this.pending;
|
|
479
|
+
this.pending = null;
|
|
480
|
+
if (pending) {
|
|
481
|
+
try {
|
|
482
|
+
pending.controller.abort();
|
|
483
|
+
} catch {
|
|
484
|
+
// best effort
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const session = this.session;
|
|
489
|
+
this.session = null;
|
|
490
|
+
if (session) {
|
|
491
|
+
try {
|
|
492
|
+
session.transport.dispose();
|
|
493
|
+
} catch (err) {
|
|
494
|
+
log.debug(
|
|
495
|
+
{ err },
|
|
496
|
+
"CdpInspectClient: transport.dispose threw (ignored)",
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Classify an `ensureSession()` rejection into a stable CdpError
|
|
505
|
+
* code. Discovery + ws-transport failures become `transport_error`,
|
|
506
|
+
* while CDP-level errors returned by `Target.attachToTarget` become
|
|
507
|
+
* `cdp_error`. Already-typed CdpErrors (e.g. a missing-sessionId
|
|
508
|
+
* attach response or a concurrent dispose) are rewritten so that
|
|
509
|
+
* the internal `cdpMethod` (`"Target.attachToTarget"`) is replaced
|
|
510
|
+
* with the caller's method, while preserving the underlying error
|
|
511
|
+
* shape.
|
|
512
|
+
*/
|
|
513
|
+
function mapEnsureSessionError(
|
|
514
|
+
err: unknown,
|
|
515
|
+
method: string,
|
|
516
|
+
params?: Record<string, unknown>,
|
|
517
|
+
): CdpError {
|
|
518
|
+
if (err instanceof CdpError) {
|
|
519
|
+
// Rewrite cdpMethod to the caller's method so attach-stage
|
|
520
|
+
// metadata (e.g. "Target.attachToTarget") doesn't leak into the
|
|
521
|
+
// caller-visible error. Preserve code, message, and the original
|
|
522
|
+
// underlying error so logging / upstream mapping can still
|
|
523
|
+
// introspect the real cause.
|
|
524
|
+
return new CdpError(err.code, err.message, {
|
|
525
|
+
cdpMethod: method,
|
|
526
|
+
cdpParams: params,
|
|
527
|
+
underlying: err.underlying ?? err,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
if (err instanceof CdpWsTransportError) {
|
|
531
|
+
if (err.code === "cdp_error") {
|
|
532
|
+
return new CdpError("cdp_error", err.cdpMessage ?? err.message, {
|
|
533
|
+
cdpMethod: method,
|
|
534
|
+
cdpParams: params,
|
|
535
|
+
underlying: err,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
return new CdpError("transport_error", err.message, {
|
|
539
|
+
cdpMethod: method,
|
|
540
|
+
cdpParams: params,
|
|
541
|
+
underlying: err,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
// DevToolsDiscoveryError (and any other non-CDP rejection) is
|
|
545
|
+
// treated as a transport-level failure.
|
|
546
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
547
|
+
return new CdpError("transport_error", msg, {
|
|
548
|
+
cdpMethod: method,
|
|
549
|
+
cdpParams: params,
|
|
550
|
+
underlying: err,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Race a long-running shared promise against a per-caller
|
|
556
|
+
* {@link AbortSignal}. When the signal fires first, the returned
|
|
557
|
+
* promise rejects with a synthetic `abort` error and the optional
|
|
558
|
+
* `onAbort` hook is invoked exactly once so callers can decrement
|
|
559
|
+
* ref-counts, release locks, etc. The underlying `inner` promise is
|
|
560
|
+
* intentionally NOT cancelled here — shared in-flight work is
|
|
561
|
+
* cancelled separately via the owning {@link PendingAttach}
|
|
562
|
+
* controller once every waiter has given up.
|
|
563
|
+
*/
|
|
564
|
+
function raceAbort<T>(
|
|
565
|
+
inner: Promise<T>,
|
|
566
|
+
signal: AbortSignal | undefined,
|
|
567
|
+
onAbort: () => void,
|
|
568
|
+
): Promise<T> {
|
|
569
|
+
if (!signal) return inner;
|
|
570
|
+
if (signal.aborted) {
|
|
571
|
+
try {
|
|
572
|
+
onAbort();
|
|
573
|
+
} catch {
|
|
574
|
+
// best effort
|
|
575
|
+
}
|
|
576
|
+
return Promise.reject(new Error("aborted"));
|
|
577
|
+
}
|
|
578
|
+
return new Promise<T>((resolve, reject) => {
|
|
579
|
+
let settled = false;
|
|
580
|
+
const handleAbort = () => {
|
|
581
|
+
if (settled) return;
|
|
582
|
+
settled = true;
|
|
583
|
+
try {
|
|
584
|
+
onAbort();
|
|
585
|
+
} catch {
|
|
586
|
+
// best effort
|
|
587
|
+
}
|
|
588
|
+
reject(new Error("aborted"));
|
|
589
|
+
};
|
|
590
|
+
signal.addEventListener("abort", handleAbort, { once: true });
|
|
591
|
+
inner.then(
|
|
592
|
+
(value) => {
|
|
593
|
+
if (settled) return;
|
|
594
|
+
settled = true;
|
|
595
|
+
signal.removeEventListener("abort", handleAbort);
|
|
596
|
+
resolve(value);
|
|
597
|
+
},
|
|
598
|
+
(err) => {
|
|
599
|
+
if (settled) return;
|
|
600
|
+
settled = true;
|
|
601
|
+
signal.removeEventListener("abort", handleAbort);
|
|
602
|
+
reject(err);
|
|
603
|
+
},
|
|
604
|
+
);
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Pull the `sessionId` field out of a `Target.attachToTarget` CDP
|
|
610
|
+
* result. CDP returns an object shaped `{ sessionId: string }`; we
|
|
611
|
+
* guard defensively against malformed replies so a broken Chrome
|
|
612
|
+
* fork cannot silently send us into an un-typed send loop.
|
|
613
|
+
*/
|
|
614
|
+
function extractSessionId(result: unknown): string | null {
|
|
615
|
+
if (!result || typeof result !== "object") return null;
|
|
616
|
+
const record = result as Record<string, unknown>;
|
|
617
|
+
const sessionId = record.sessionId;
|
|
618
|
+
if (typeof sessionId === "string" && sessionId.length > 0) {
|
|
619
|
+
return sessionId;
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Factory for a fresh {@link CdpInspectClient} bound to a
|
|
626
|
+
* conversation. Keeping the constructor + factory split lets the
|
|
627
|
+
* cdp-client factory wires this up alongside local / extension
|
|
628
|
+
* without exposing the class directly to callers.
|
|
629
|
+
*/
|
|
630
|
+
export function createCdpInspectClient(
|
|
631
|
+
conversationId: string,
|
|
632
|
+
options: CdpInspectClientOptions,
|
|
633
|
+
): CdpInspectClient {
|
|
634
|
+
return new CdpInspectClient(conversationId, options);
|
|
635
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type CdpErrorCode =
|
|
2
|
+
| "cdp_error" // JSON-RPC error returned by CDP
|
|
3
|
+
| "transport_error" // underlying transport failed (socket closed, timeout)
|
|
4
|
+
| "aborted" // caller-provided AbortSignal fired
|
|
5
|
+
| "disposed"; // client.dispose() already called
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Single error type thrown by all CdpClient implementations. Carries
|
|
9
|
+
* the offending CDP method + params for logging and a stable code so
|
|
10
|
+
* callers can branch without string-sniffing.
|
|
11
|
+
*/
|
|
12
|
+
export class CdpError extends Error {
|
|
13
|
+
readonly code: CdpErrorCode;
|
|
14
|
+
readonly cdpMethod?: string;
|
|
15
|
+
readonly cdpParams?: Record<string, unknown>;
|
|
16
|
+
readonly underlying?: unknown;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
code: CdpErrorCode,
|
|
20
|
+
message: string,
|
|
21
|
+
details?: {
|
|
22
|
+
cdpMethod?: string;
|
|
23
|
+
cdpParams?: Record<string, unknown>;
|
|
24
|
+
underlying?: unknown;
|
|
25
|
+
},
|
|
26
|
+
) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "CdpError";
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.cdpMethod = details?.cdpMethod;
|
|
31
|
+
this.cdpParams = details?.cdpParams;
|
|
32
|
+
this.underlying = details?.underlying;
|
|
33
|
+
}
|
|
34
|
+
}
|