@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
|
@@ -9,6 +9,55 @@ mock.module("../util/logger.js", () => ({
|
|
|
9
9
|
}),
|
|
10
10
|
}));
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Fake CDP session used by the screenshot/extract/wait_for tests in
|
|
14
|
+
* this file. The tests configure `sendHandler` before invoking a
|
|
15
|
+
* tool; each `session.send(method, params)` call is recorded in
|
|
16
|
+
* `sendCalls` and routed to the handler. The handler returns either
|
|
17
|
+
* a CDP response object (e.g. `{ result: { value: ... } }` for
|
|
18
|
+
* `Runtime.evaluate`, or `{ data }` for `Page.captureScreenshot`) or
|
|
19
|
+
* an `Error` to simulate a CDP failure.
|
|
20
|
+
*
|
|
21
|
+
* The fake session is exposed via `mockPage.context().newCDPSession(page)`
|
|
22
|
+
* which is what the real `LocalCdpClient` calls internally. Going
|
|
23
|
+
* through the real `LocalCdpClient` (instead of mocking the factory
|
|
24
|
+
* or the cdp-client submodules) avoids polluting the global module
|
|
25
|
+
* cache that the `factory.test.ts` and LocalCdpClient/
|
|
26
|
+
* ExtensionCdpClient unit tests rely on.
|
|
27
|
+
*/
|
|
28
|
+
interface SendCall {
|
|
29
|
+
method: string;
|
|
30
|
+
params: Record<string, unknown> | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let sendCalls: SendCall[];
|
|
34
|
+
let sendHandler: (
|
|
35
|
+
method: string,
|
|
36
|
+
params: Record<string, unknown> | undefined,
|
|
37
|
+
) => unknown;
|
|
38
|
+
|
|
39
|
+
function resetCdpMock() {
|
|
40
|
+
sendCalls = [];
|
|
41
|
+
sendHandler = () => ({});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const fakeCdpSession = {
|
|
45
|
+
send: async (method: string, params?: Record<string, unknown>) => {
|
|
46
|
+
sendCalls.push({ method, params });
|
|
47
|
+
const value = sendHandler(method, params);
|
|
48
|
+
if (value instanceof Error) throw value;
|
|
49
|
+
return value;
|
|
50
|
+
},
|
|
51
|
+
// Provided so `LocalCdpClient.dispose()` can call `session.detach()`
|
|
52
|
+
// without throwing. The tests don't assert on detach calls.
|
|
53
|
+
detach: async () => {},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// The mock page is served by `browserManager.getOrCreateSessionPage`
|
|
57
|
+
// and is consumed indirectly: LocalCdpClient calls
|
|
58
|
+
// `page.context().newCDPSession(page)` to obtain a CDP session and
|
|
59
|
+
// then dispatches raw CDP methods against it. The page's
|
|
60
|
+
// `context().newCDPSession` is wired to return `fakeCdpSession` above.
|
|
12
61
|
let mockPage: {
|
|
13
62
|
click: ReturnType<typeof mock>;
|
|
14
63
|
fill: ReturnType<typeof mock>;
|
|
@@ -22,25 +71,17 @@ let mockPage: {
|
|
|
22
71
|
waitForSelector: ReturnType<typeof mock>;
|
|
23
72
|
waitForFunction: ReturnType<typeof mock>;
|
|
24
73
|
keyboard: { press: ReturnType<typeof mock> };
|
|
74
|
+
context: () => {
|
|
75
|
+
newCDPSession: (page: unknown) => Promise<typeof fakeCdpSession>;
|
|
76
|
+
};
|
|
25
77
|
};
|
|
26
78
|
|
|
27
|
-
let snapshotMaps: Map<string, Map<string, string>>;
|
|
28
|
-
|
|
29
79
|
mock.module("../tools/browser/browser-manager.js", () => {
|
|
30
|
-
snapshotMaps = new Map();
|
|
31
80
|
return {
|
|
32
81
|
browserManager: {
|
|
33
82
|
getOrCreateSessionPage: async () => mockPage,
|
|
34
83
|
closeSessionPage: async () => {},
|
|
35
84
|
closeAllPages: async () => {},
|
|
36
|
-
storeSnapshotMap: (conversationId: string, map: Map<string, string>) => {
|
|
37
|
-
snapshotMaps.set(conversationId, map);
|
|
38
|
-
},
|
|
39
|
-
resolveSnapshotSelector: (conversationId: string, elementId: string) => {
|
|
40
|
-
const map = snapshotMaps.get(conversationId);
|
|
41
|
-
if (!map) return null;
|
|
42
|
-
return map.get(elementId) ?? null;
|
|
43
|
-
},
|
|
44
85
|
},
|
|
45
86
|
};
|
|
46
87
|
});
|
|
@@ -55,8 +96,9 @@ mock.module("../tools/network/url-safety.js", () => ({
|
|
|
55
96
|
|
|
56
97
|
import {
|
|
57
98
|
executeBrowserExtract,
|
|
58
|
-
|
|
99
|
+
executeBrowserScreenshot,
|
|
59
100
|
executeBrowserWaitFor,
|
|
101
|
+
EXTRACT_LINKS_EXPRESSION,
|
|
60
102
|
} from "../tools/browser/browser-execution.js";
|
|
61
103
|
import type { ToolContext } from "../tools/types.js";
|
|
62
104
|
|
|
@@ -83,72 +125,76 @@ function resetMockPage() {
|
|
|
83
125
|
waitForSelector: mock(async () => null),
|
|
84
126
|
waitForFunction: mock(async () => null),
|
|
85
127
|
keyboard: { press: mock(async () => {}) },
|
|
128
|
+
// `LocalCdpClient.ensureSession()` calls `page.context().newCDPSession(
|
|
129
|
+
// page)` to create a Playwright CDPSession. For these tests we
|
|
130
|
+
// return the in-file `fakeCdpSession` which records every send()
|
|
131
|
+
// into `sendCalls` and lets each test set `sendHandler` to shape
|
|
132
|
+
// the responses.
|
|
133
|
+
context: () => ({
|
|
134
|
+
newCDPSession: async (_page: unknown) => fakeCdpSession,
|
|
135
|
+
}),
|
|
86
136
|
};
|
|
87
137
|
}
|
|
88
138
|
|
|
89
|
-
//
|
|
139
|
+
// executeBrowserPressKey tests live in
|
|
140
|
+
// `headless-browser-interactions.test.ts` alongside the other
|
|
141
|
+
// CDP-migrated interaction tools.
|
|
90
142
|
|
|
91
|
-
|
|
143
|
+
// ── browser_screenshot ───────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
describe("executeBrowserScreenshot", () => {
|
|
92
146
|
beforeEach(() => {
|
|
93
147
|
resetMockPage();
|
|
94
|
-
|
|
148
|
+
resetCdpMock();
|
|
95
149
|
});
|
|
96
150
|
|
|
97
|
-
test("
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
});
|
|
151
|
+
test("captures viewport screenshot via Page.captureScreenshot", async () => {
|
|
152
|
+
// Base64 for "abc" = "YWJj"
|
|
153
|
+
sendHandler = () => ({ data: "YWJj" });
|
|
154
|
+
|
|
155
|
+
const result = await executeBrowserScreenshot({}, ctx);
|
|
103
156
|
|
|
104
|
-
test("presses key on targeted element via element_id", async () => {
|
|
105
|
-
snapshotMaps.set(
|
|
106
|
-
"test-conversation",
|
|
107
|
-
new Map([["e1", '[data-vellum-eid="e1"]']]),
|
|
108
|
-
);
|
|
109
|
-
const result = await executeBrowserPressKey(
|
|
110
|
-
{ key: "Tab", element_id: "e1" },
|
|
111
|
-
ctx,
|
|
112
|
-
);
|
|
113
157
|
expect(result.isError).toBe(false);
|
|
114
|
-
expect(result.content).toContain(
|
|
115
|
-
expect(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
158
|
+
expect(result.content).toContain("Screenshot captured");
|
|
159
|
+
expect(result.content).toContain("viewport");
|
|
160
|
+
expect(result.contentBlocks).toHaveLength(1);
|
|
161
|
+
const block = result.contentBlocks![0]!;
|
|
162
|
+
|
|
163
|
+
const source = (block as any).source;
|
|
164
|
+
expect(source.media_type).toBe("image/jpeg");
|
|
165
|
+
expect(source.data).toBe("YWJj");
|
|
166
|
+
// Page.captureScreenshot was called with jpeg quality 80
|
|
167
|
+
expect(sendCalls).toHaveLength(1);
|
|
168
|
+
expect(sendCalls[0]!.method).toBe("Page.captureScreenshot");
|
|
169
|
+
expect(sendCalls[0]!.params).toEqual({
|
|
170
|
+
format: "jpeg",
|
|
171
|
+
quality: 80,
|
|
172
|
+
captureBeyondViewport: false,
|
|
173
|
+
});
|
|
119
174
|
});
|
|
120
175
|
|
|
121
|
-
test("
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
176
|
+
test("captures full-page screenshot when full_page is true", async () => {
|
|
177
|
+
sendHandler = () => ({ data: "YWJj" });
|
|
178
|
+
|
|
179
|
+
const result = await executeBrowserScreenshot({ full_page: true }, ctx);
|
|
180
|
+
|
|
126
181
|
expect(result.isError).toBe(false);
|
|
127
|
-
expect(
|
|
182
|
+
expect(result.content).toContain("full page");
|
|
183
|
+
expect(sendCalls[0]!.params).toEqual({
|
|
184
|
+
format: "jpeg",
|
|
185
|
+
quality: 80,
|
|
186
|
+
captureBeyondViewport: true,
|
|
187
|
+
});
|
|
128
188
|
});
|
|
129
189
|
|
|
130
|
-
test("
|
|
131
|
-
|
|
132
|
-
expect(result.isError).toBe(true);
|
|
133
|
-
expect(result.content).toContain("key is required");
|
|
134
|
-
});
|
|
190
|
+
test("surfaces CDP failure as an error result", async () => {
|
|
191
|
+
sendHandler = () => new Error("CDP crashed");
|
|
135
192
|
|
|
136
|
-
|
|
137
|
-
const result = await executeBrowserPressKey(
|
|
138
|
-
{ key: "Enter", element_id: "e99" },
|
|
139
|
-
ctx,
|
|
140
|
-
);
|
|
141
|
-
expect(result.isError).toBe(true);
|
|
142
|
-
expect(result.content).toContain('element_id "e99" not found');
|
|
143
|
-
});
|
|
193
|
+
const result = await executeBrowserScreenshot({}, ctx);
|
|
144
194
|
|
|
145
|
-
test("handles press error", async () => {
|
|
146
|
-
mockPage.keyboard.press = mock(async () => {
|
|
147
|
-
throw new Error("key not recognized");
|
|
148
|
-
});
|
|
149
|
-
const result = await executeBrowserPressKey({ key: "InvalidKey" }, ctx);
|
|
150
195
|
expect(result.isError).toBe(true);
|
|
151
|
-
expect(result.content).toContain("
|
|
196
|
+
expect(result.content).toContain("Screenshot failed");
|
|
197
|
+
expect(result.content).toContain("CDP crashed");
|
|
152
198
|
});
|
|
153
199
|
});
|
|
154
200
|
|
|
@@ -157,28 +203,67 @@ describe("executeBrowserPressKey", () => {
|
|
|
157
203
|
describe("executeBrowserWaitFor", () => {
|
|
158
204
|
beforeEach(() => {
|
|
159
205
|
resetMockPage();
|
|
206
|
+
resetCdpMock();
|
|
160
207
|
});
|
|
161
208
|
|
|
162
|
-
test("waits for selector", async () => {
|
|
209
|
+
test("waits for selector (CDP polling)", async () => {
|
|
210
|
+
// waitForSelector polls via Runtime.evaluate until the element
|
|
211
|
+
// exists, then calls querySelectorBackendNodeId (which triggers
|
|
212
|
+
// DOM.getDocument / DOM.querySelector / DOM.describeNode).
|
|
213
|
+
sendHandler = (method, _params) => {
|
|
214
|
+
if (method === "Runtime.evaluate") {
|
|
215
|
+
return { result: { value: true } };
|
|
216
|
+
}
|
|
217
|
+
if (method === "DOM.getDocument") return { root: { nodeId: 1 } };
|
|
218
|
+
if (method === "DOM.querySelector") return { nodeId: 42 };
|
|
219
|
+
if (method === "DOM.describeNode") {
|
|
220
|
+
return { node: { backendNodeId: 100 } };
|
|
221
|
+
}
|
|
222
|
+
return {};
|
|
223
|
+
};
|
|
224
|
+
|
|
163
225
|
const result = await executeBrowserWaitFor({ selector: "#loaded" }, ctx);
|
|
226
|
+
|
|
164
227
|
expect(result.isError).toBe(false);
|
|
165
228
|
expect(result.content).toContain('Element matching "#loaded" appeared');
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
229
|
+
// First call is Runtime.evaluate checking existence
|
|
230
|
+
const evaluateCall = sendCalls.find((c) => c.method === "Runtime.evaluate");
|
|
231
|
+
expect(evaluateCall).toBeDefined();
|
|
232
|
+
expect((evaluateCall!.params as { expression: string }).expression).toBe(
|
|
233
|
+
'document.querySelector("#loaded") !== null',
|
|
234
|
+
);
|
|
169
235
|
});
|
|
170
236
|
|
|
171
|
-
test("waits for text", async () => {
|
|
237
|
+
test("waits for text (CDP polling)", async () => {
|
|
238
|
+
sendHandler = (method) => {
|
|
239
|
+
if (method === "Runtime.evaluate") {
|
|
240
|
+
return { result: { value: true } };
|
|
241
|
+
}
|
|
242
|
+
return {};
|
|
243
|
+
};
|
|
244
|
+
|
|
172
245
|
const result = await executeBrowserWaitFor({ text: "Success" }, ctx);
|
|
246
|
+
|
|
173
247
|
expect(result.isError).toBe(false);
|
|
174
248
|
expect(result.content).toContain('Text "Success" appeared');
|
|
175
|
-
|
|
249
|
+
const evaluateCall = sendCalls.find((c) => c.method === "Runtime.evaluate");
|
|
250
|
+
expect(evaluateCall).toBeDefined();
|
|
251
|
+
expect(
|
|
252
|
+
(evaluateCall!.params as { expression: string }).expression,
|
|
253
|
+
).toContain('"Success"');
|
|
254
|
+
expect(
|
|
255
|
+
(evaluateCall!.params as { expression: string }).expression,
|
|
256
|
+
).toContain(".includes(");
|
|
176
257
|
});
|
|
177
258
|
|
|
178
|
-
test("waits for duration", async () => {
|
|
259
|
+
test("waits for duration (no CDP client acquired)", async () => {
|
|
179
260
|
const result = await executeBrowserWaitFor({ duration: 10 }, ctx);
|
|
261
|
+
|
|
180
262
|
expect(result.isError).toBe(false);
|
|
181
263
|
expect(result.content).toContain("Waited 10ms");
|
|
264
|
+
// Duration mode is transport-agnostic and must not allocate a
|
|
265
|
+
// CdpClient — so neither send nor dispose should have fired.
|
|
266
|
+
expect(sendCalls).toHaveLength(0);
|
|
182
267
|
});
|
|
183
268
|
|
|
184
269
|
test("errors when no mode specified", async () => {
|
|
@@ -187,6 +272,8 @@ describe("executeBrowserWaitFor", () => {
|
|
|
187
272
|
expect(result.content).toContain(
|
|
188
273
|
"Exactly one of selector, text, or duration",
|
|
189
274
|
);
|
|
275
|
+
// Validation rejects before any CDP work is attempted.
|
|
276
|
+
expect(sendCalls).toHaveLength(0);
|
|
190
277
|
});
|
|
191
278
|
|
|
192
279
|
test("errors when multiple modes specified", async () => {
|
|
@@ -196,17 +283,7 @@ describe("executeBrowserWaitFor", () => {
|
|
|
196
283
|
);
|
|
197
284
|
expect(result.isError).toBe(true);
|
|
198
285
|
expect(result.content).toContain("exactly one");
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
test("respects custom timeout", async () => {
|
|
202
|
-
const result = await executeBrowserWaitFor(
|
|
203
|
-
{ selector: "#el", timeout: 5000 },
|
|
204
|
-
ctx,
|
|
205
|
-
);
|
|
206
|
-
expect(result.isError).toBe(false);
|
|
207
|
-
expect(mockPage.waitForSelector).toHaveBeenCalledWith("#el", {
|
|
208
|
-
timeout: 5000,
|
|
209
|
-
});
|
|
286
|
+
expect(sendCalls).toHaveLength(0);
|
|
210
287
|
});
|
|
211
288
|
|
|
212
289
|
test("caps duration at MAX_WAIT_MS", async () => {
|
|
@@ -217,14 +294,16 @@ describe("executeBrowserWaitFor", () => {
|
|
|
217
294
|
expect(result.content).toContain("Waited 50ms");
|
|
218
295
|
});
|
|
219
296
|
|
|
220
|
-
test("
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
297
|
+
test("surfaces CDP transport failure as a wait error", async () => {
|
|
298
|
+
sendHandler = () => new Error("CDP transport failed");
|
|
299
|
+
|
|
300
|
+
const result = await executeBrowserWaitFor(
|
|
301
|
+
{ selector: "#missing", timeout: 100 },
|
|
302
|
+
ctx,
|
|
303
|
+
);
|
|
304
|
+
|
|
225
305
|
expect(result.isError).toBe(true);
|
|
226
306
|
expect(result.content).toContain("Wait failed");
|
|
227
|
-
expect(result.content).toContain("Timeout");
|
|
228
307
|
});
|
|
229
308
|
});
|
|
230
309
|
|
|
@@ -233,60 +312,144 @@ describe("executeBrowserWaitFor", () => {
|
|
|
233
312
|
describe("executeBrowserExtract", () => {
|
|
234
313
|
beforeEach(() => {
|
|
235
314
|
resetMockPage();
|
|
315
|
+
resetCdpMock();
|
|
236
316
|
});
|
|
237
317
|
|
|
238
|
-
test("extracts page text content", async () => {
|
|
239
|
-
|
|
318
|
+
test("extracts page text content via CDP", async () => {
|
|
319
|
+
sendHandler = (method, params) => {
|
|
320
|
+
if (method !== "Runtime.evaluate") return {};
|
|
321
|
+
const expression = (params as { expression: string }).expression;
|
|
322
|
+
if (expression === "document.location.href") {
|
|
323
|
+
return { result: { value: "https://example.com/" } };
|
|
324
|
+
}
|
|
325
|
+
if (expression === "document.title") {
|
|
326
|
+
return { result: { value: "Test Page" } };
|
|
327
|
+
}
|
|
328
|
+
if (expression === "document.body?.innerText ?? ''") {
|
|
329
|
+
return { result: { value: "Hello World" } };
|
|
330
|
+
}
|
|
331
|
+
return { result: { value: null } };
|
|
332
|
+
};
|
|
333
|
+
|
|
240
334
|
const result = await executeBrowserExtract({}, ctx);
|
|
335
|
+
|
|
241
336
|
expect(result.isError).toBe(false);
|
|
242
337
|
expect(result.content).toContain("URL: https://example.com/");
|
|
243
338
|
expect(result.content).toContain("Title: Test Page");
|
|
244
339
|
expect(result.content).toContain("Hello World");
|
|
340
|
+
// URL, title, body innerText = 3 Runtime.evaluate calls
|
|
341
|
+
const evaluateCalls = sendCalls.filter(
|
|
342
|
+
(c) => c.method === "Runtime.evaluate",
|
|
343
|
+
);
|
|
344
|
+
expect(evaluateCalls).toHaveLength(3);
|
|
245
345
|
});
|
|
246
346
|
|
|
247
347
|
test("shows (empty page) for empty content", async () => {
|
|
248
|
-
|
|
348
|
+
sendHandler = (method, params) => {
|
|
349
|
+
if (method !== "Runtime.evaluate") return {};
|
|
350
|
+
const expression = (params as { expression: string }).expression;
|
|
351
|
+
if (expression === "document.location.href") {
|
|
352
|
+
return { result: { value: "https://example.com/" } };
|
|
353
|
+
}
|
|
354
|
+
if (expression === "document.title") {
|
|
355
|
+
return { result: { value: "Test Page" } };
|
|
356
|
+
}
|
|
357
|
+
return { result: { value: "" } };
|
|
358
|
+
};
|
|
359
|
+
|
|
249
360
|
const result = await executeBrowserExtract({}, ctx);
|
|
250
361
|
expect(result.content).toContain("(empty page)");
|
|
251
362
|
});
|
|
252
363
|
|
|
253
364
|
test("truncates long content", async () => {
|
|
254
365
|
const longText = "x".repeat(60_000);
|
|
255
|
-
|
|
366
|
+
sendHandler = (method, params) => {
|
|
367
|
+
if (method !== "Runtime.evaluate") return {};
|
|
368
|
+
const expression = (params as { expression: string }).expression;
|
|
369
|
+
if (expression === "document.location.href") {
|
|
370
|
+
return { result: { value: "https://example.com/" } };
|
|
371
|
+
}
|
|
372
|
+
if (expression === "document.title") {
|
|
373
|
+
return { result: { value: "Test Page" } };
|
|
374
|
+
}
|
|
375
|
+
return { result: { value: longText } };
|
|
376
|
+
};
|
|
377
|
+
|
|
256
378
|
const result = await executeBrowserExtract({}, ctx);
|
|
257
379
|
expect(result.content).toContain("... (truncated)");
|
|
258
380
|
// Content should be capped
|
|
259
381
|
expect(result.content.length).toBeLessThan(60_000);
|
|
260
382
|
});
|
|
261
383
|
|
|
262
|
-
test("includes links when requested", async () => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
384
|
+
test("includes links when requested using EXTRACT_LINKS_EXPRESSION", async () => {
|
|
385
|
+
sendHandler = (method, params) => {
|
|
386
|
+
if (method !== "Runtime.evaluate") return {};
|
|
387
|
+
const expression = (params as { expression: string }).expression;
|
|
388
|
+
if (expression === "document.location.href") {
|
|
389
|
+
return { result: { value: "https://example.com/" } };
|
|
390
|
+
}
|
|
391
|
+
if (expression === "document.title") {
|
|
392
|
+
return { result: { value: "Test Page" } };
|
|
393
|
+
}
|
|
394
|
+
if (expression === "document.body?.innerText ?? ''") {
|
|
395
|
+
return { result: { value: "Page text" } };
|
|
396
|
+
}
|
|
397
|
+
if (expression === EXTRACT_LINKS_EXPRESSION) {
|
|
398
|
+
return {
|
|
399
|
+
result: {
|
|
400
|
+
value: [
|
|
401
|
+
{ text: "About", href: "https://example.com/about" },
|
|
402
|
+
{ text: "Contact", href: "https://example.com/contact" },
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return { result: { value: null } };
|
|
408
|
+
};
|
|
272
409
|
|
|
273
410
|
const result = await executeBrowserExtract({ include_links: true }, ctx);
|
|
274
411
|
expect(result.isError).toBe(false);
|
|
275
412
|
expect(result.content).toContain("Links:");
|
|
276
413
|
expect(result.content).toContain("[About](https://example.com/about)");
|
|
277
414
|
expect(result.content).toContain("[Contact](https://example.com/contact)");
|
|
415
|
+
// EXTRACT_LINKS_EXPRESSION was actually used (assert the expression appears in a call)
|
|
416
|
+
const linksCall = sendCalls.find(
|
|
417
|
+
(c) =>
|
|
418
|
+
c.method === "Runtime.evaluate" &&
|
|
419
|
+
(c.params as { expression: string }).expression ===
|
|
420
|
+
EXTRACT_LINKS_EXPRESSION,
|
|
421
|
+
);
|
|
422
|
+
expect(linksCall).toBeDefined();
|
|
278
423
|
});
|
|
279
424
|
|
|
280
425
|
test("does not include links by default", async () => {
|
|
281
|
-
|
|
426
|
+
sendHandler = (method, params) => {
|
|
427
|
+
if (method !== "Runtime.evaluate") return {};
|
|
428
|
+
const expression = (params as { expression: string }).expression;
|
|
429
|
+
if (expression === "document.location.href") {
|
|
430
|
+
return { result: { value: "https://example.com/" } };
|
|
431
|
+
}
|
|
432
|
+
if (expression === "document.title") {
|
|
433
|
+
return { result: { value: "Test Page" } };
|
|
434
|
+
}
|
|
435
|
+
return { result: { value: "Page text" } };
|
|
436
|
+
};
|
|
437
|
+
|
|
282
438
|
const result = await executeBrowserExtract({}, ctx);
|
|
283
439
|
expect(result.content).not.toContain("Links:");
|
|
440
|
+
// EXTRACT_LINKS_EXPRESSION should NOT have been evaluated
|
|
441
|
+
const linksCall = sendCalls.find(
|
|
442
|
+
(c) =>
|
|
443
|
+
c.method === "Runtime.evaluate" &&
|
|
444
|
+
(c.params as { expression: string }).expression ===
|
|
445
|
+
EXTRACT_LINKS_EXPRESSION,
|
|
446
|
+
);
|
|
447
|
+
expect(linksCall).toBeUndefined();
|
|
284
448
|
});
|
|
285
449
|
|
|
286
|
-
test("
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
});
|
|
450
|
+
test("surfaces CDP failure as an extract error", async () => {
|
|
451
|
+
sendHandler = () => new Error("page crashed");
|
|
452
|
+
|
|
290
453
|
const result = await executeBrowserExtract({}, ctx);
|
|
291
454
|
expect(result.isError).toBe(true);
|
|
292
455
|
expect(result.content).toContain("Extract failed");
|