@vellumai/assistant 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +75 -57
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -6
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +53 -3
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +44 -5
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +8 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +58 -24
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-process.ts +48 -1
- package/src/daemon/conversation-runtime-assembly.ts +118 -36
- package/src/daemon/conversation-surfaces.ts +37 -36
- package/src/daemon/conversation-tool-setup.ts +74 -8
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +226 -8
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -140
- package/src/daemon/handlers/shared.ts +58 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +65 -11
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +55 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -5
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +92 -12
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +23 -0
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +3 -3
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +126 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +301 -27
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/shell.ts +21 -16
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -0
- package/src/util/platform.ts +14 -19
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { HostBrowserProxy } from "../../../daemon/host-browser-proxy.js";
|
|
2
|
+
import { getLogger } from "../../../util/logger.js";
|
|
3
|
+
import { CdpError } from "./errors.js";
|
|
4
|
+
import type { CdpClientKind, ScopedCdpClient } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const log = getLogger("extension-cdp-client");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* CdpClient backed by HostBrowserProxy. Each `send` becomes a
|
|
10
|
+
* host_browser_request / host_browser_result round-trip over the
|
|
11
|
+
* chrome-extension WebSocket.
|
|
12
|
+
*
|
|
13
|
+
* Unlike LocalCdpClient, this implementation cannot deliver
|
|
14
|
+
* CDP events (subscribing to "Page.lifecycleEvent" etc.) because
|
|
15
|
+
* HostBrowserProxy is request/reply only. Helpers that need
|
|
16
|
+
* event subscription (waitForLifecycleEvent) must fall back to
|
|
17
|
+
* polling via Runtime.evaluate — see cdp-dom-helpers.ts#navigateAndWait.
|
|
18
|
+
*/
|
|
19
|
+
export class ExtensionCdpClient implements ScopedCdpClient {
|
|
20
|
+
readonly kind: CdpClientKind = "extension";
|
|
21
|
+
private disposed = false;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private readonly proxy: HostBrowserProxy,
|
|
25
|
+
public readonly conversationId: string,
|
|
26
|
+
private readonly cdpSessionId?: string,
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
async send<T = unknown>(
|
|
30
|
+
method: string,
|
|
31
|
+
params?: Record<string, unknown>,
|
|
32
|
+
signal?: AbortSignal,
|
|
33
|
+
): Promise<T> {
|
|
34
|
+
if (this.disposed) {
|
|
35
|
+
throw new CdpError("disposed", "ExtensionCdpClient already disposed", {
|
|
36
|
+
cdpMethod: method,
|
|
37
|
+
cdpParams: params,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (signal?.aborted) {
|
|
41
|
+
throw new CdpError("aborted", "Aborted before send", {
|
|
42
|
+
cdpMethod: method,
|
|
43
|
+
cdpParams: params,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let result;
|
|
48
|
+
try {
|
|
49
|
+
result = await this.proxy.request(
|
|
50
|
+
{
|
|
51
|
+
cdpMethod: method,
|
|
52
|
+
cdpParams: params,
|
|
53
|
+
cdpSessionId: this.cdpSessionId,
|
|
54
|
+
},
|
|
55
|
+
this.conversationId,
|
|
56
|
+
signal,
|
|
57
|
+
);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new CdpError(
|
|
60
|
+
"transport_error",
|
|
61
|
+
err instanceof Error ? err.message : String(err),
|
|
62
|
+
{
|
|
63
|
+
cdpMethod: method,
|
|
64
|
+
cdpParams: params,
|
|
65
|
+
underlying: err,
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (signal?.aborted || result.content === "Aborted") {
|
|
71
|
+
throw new CdpError("aborted", "CDP call aborted", {
|
|
72
|
+
cdpMethod: method,
|
|
73
|
+
cdpParams: params,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let parsed: unknown;
|
|
78
|
+
try {
|
|
79
|
+
parsed = JSON.parse(result.content);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
throw new CdpError(
|
|
82
|
+
"transport_error",
|
|
83
|
+
`Non-JSON content from host_browser_result: ${result.content.slice(0, 200)}`,
|
|
84
|
+
{
|
|
85
|
+
cdpMethod: method,
|
|
86
|
+
cdpParams: params,
|
|
87
|
+
underlying: err,
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (result.isError) {
|
|
93
|
+
const msg =
|
|
94
|
+
(typeof parsed === "object" &&
|
|
95
|
+
parsed !== null &&
|
|
96
|
+
"message" in parsed &&
|
|
97
|
+
typeof (parsed as { message: unknown }).message === "string" &&
|
|
98
|
+
(parsed as { message: string }).message) ||
|
|
99
|
+
`CDP error for ${method}`;
|
|
100
|
+
log.debug({ method, params, parsed }, "ExtensionCdpClient: CDP error");
|
|
101
|
+
throw new CdpError("cdp_error", msg, {
|
|
102
|
+
cdpMethod: method,
|
|
103
|
+
cdpParams: params,
|
|
104
|
+
underlying: parsed,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return parsed as T;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
dispose(): void {
|
|
112
|
+
this.disposed = true;
|
|
113
|
+
// HostBrowserProxy is owned by the conversation — do NOT dispose
|
|
114
|
+
// it here. In-flight requests will be cancelled by the AbortSignal
|
|
115
|
+
// the tool passes in, or by conversation teardown.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createExtensionCdpClient(
|
|
120
|
+
proxy: HostBrowserProxy,
|
|
121
|
+
conversationId: string,
|
|
122
|
+
cdpSessionId?: string,
|
|
123
|
+
): ExtensionCdpClient {
|
|
124
|
+
return new ExtensionCdpClient(proxy, conversationId, cdpSessionId);
|
|
125
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BrowserBackend,
|
|
3
|
+
BrowserSessionManager,
|
|
4
|
+
type CdpCommand,
|
|
5
|
+
type CdpResult,
|
|
6
|
+
createCdpInspectBackend,
|
|
7
|
+
createExtensionBackend,
|
|
8
|
+
createLocalBackend,
|
|
9
|
+
} from "../../../browser-session/index.js";
|
|
10
|
+
import { getConfig } from "../../../config/loader.js";
|
|
11
|
+
import type { ToolContext } from "../../types.js";
|
|
12
|
+
import { createCdpInspectClient } from "./cdp-inspect-client.js";
|
|
13
|
+
import { CdpError } from "./errors.js";
|
|
14
|
+
import { createExtensionCdpClient } from "./extension-cdp-client.js";
|
|
15
|
+
import { createLocalCdpClient } from "./local-cdp-client.js";
|
|
16
|
+
import type { CdpClient, CdpClientKind, ScopedCdpClient } from "./types.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Select the appropriate CdpClient implementation for a tool
|
|
20
|
+
* invocation based on the ToolContext and config. Three backends are
|
|
21
|
+
* considered in priority order:
|
|
22
|
+
*
|
|
23
|
+
* 1. **Extension** — When `context.hostBrowserProxy` is set (macOS
|
|
24
|
+
* desktop / cloud-hosted with a chrome-extension bound to the
|
|
25
|
+
* conversation), register an extension backend so CDP commands
|
|
26
|
+
* ride the host_browser_request / host_browser_result round-trip.
|
|
27
|
+
* 2. **cdp-inspect** — When the extension is absent and
|
|
28
|
+
* `hostBrowser.cdpInspect.enabled` is `true` in config, construct
|
|
29
|
+
* a `CdpInspectClient` that attaches to an already-running Chrome
|
|
30
|
+
* via the DevTools JSON protocol (`--remote-debugging-port`).
|
|
31
|
+
* 3. **Local** — Default. Drives Playwright's CDPSession
|
|
32
|
+
* against the sacrificial-profile browser managed by
|
|
33
|
+
* browserManager.
|
|
34
|
+
*
|
|
35
|
+
* All three paths go through a per-invocation `BrowserSessionManager`
|
|
36
|
+
* so the manager is the single choke point for CDP routing, session
|
|
37
|
+
* lifetime, and (future) session invalidation handling. The returned
|
|
38
|
+
* client is `kind`-tagged so tools can branch on transport — e.g.
|
|
39
|
+
* browser_navigate skips Playwright-specific screencast and handoff
|
|
40
|
+
* hooks when `kind === "extension"`.
|
|
41
|
+
*
|
|
42
|
+
* IMPORTANT: the returned client is per-invocation. Tools MUST call
|
|
43
|
+
* `dispose()` in a finally block. Dispose tears down the manager's
|
|
44
|
+
* session and the underlying CDP client. Disposing an extension-backed
|
|
45
|
+
* client does NOT dispose the underlying HostBrowserProxy — that is
|
|
46
|
+
* owned by the conversation.
|
|
47
|
+
*/
|
|
48
|
+
export function getCdpClient(context: ToolContext): ScopedCdpClient {
|
|
49
|
+
const { conversationId, hostBrowserProxy } = context;
|
|
50
|
+
|
|
51
|
+
// 1. Extension backend — preferred when a chrome-extension is bound.
|
|
52
|
+
if (hostBrowserProxy) {
|
|
53
|
+
const client = createExtensionCdpClient(hostBrowserProxy, conversationId);
|
|
54
|
+
const backend = createExtensionBackend({
|
|
55
|
+
isAvailable: () => true,
|
|
56
|
+
sendCdp: (command, signal) =>
|
|
57
|
+
dispatchThroughClient(client, command, signal),
|
|
58
|
+
dispose: () => client.dispose(),
|
|
59
|
+
});
|
|
60
|
+
return buildManagedClient("extension", conversationId, backend);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 2. cdp-inspect backend — opt-in via config when the extension is absent.
|
|
64
|
+
const cdpInspectConfig = getConfig().hostBrowser.cdpInspect;
|
|
65
|
+
if (cdpInspectConfig.enabled) {
|
|
66
|
+
const client = createCdpInspectClient(conversationId, {
|
|
67
|
+
host: cdpInspectConfig.host,
|
|
68
|
+
port: cdpInspectConfig.port,
|
|
69
|
+
discoveryTimeoutMs: cdpInspectConfig.probeTimeoutMs,
|
|
70
|
+
});
|
|
71
|
+
const backend = createCdpInspectBackend({
|
|
72
|
+
isAvailable: () => true,
|
|
73
|
+
sendCdp: (command, signal) =>
|
|
74
|
+
dispatchThroughClient(client, command, signal),
|
|
75
|
+
dispose: () => client.dispose(),
|
|
76
|
+
});
|
|
77
|
+
return buildManagedClient("cdp-inspect", conversationId, backend);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. Local backend — default (Playwright-backed Chromium).
|
|
81
|
+
const client = createLocalCdpClient(conversationId);
|
|
82
|
+
const backend = createLocalBackend({
|
|
83
|
+
isAvailable: () => true,
|
|
84
|
+
sendCdp: (command, signal) =>
|
|
85
|
+
dispatchThroughClient(client, command, signal),
|
|
86
|
+
dispose: () => client.dispose(),
|
|
87
|
+
});
|
|
88
|
+
return buildManagedClient("local", conversationId, backend);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build a ScopedCdpClient whose `send()` routes through a
|
|
93
|
+
* BrowserSessionManager seeded with a single backend + session. This
|
|
94
|
+
* lets tool call sites remain backend-agnostic while giving the
|
|
95
|
+
* manager a seam for future session-invalidation and multi-target
|
|
96
|
+
* routing work.
|
|
97
|
+
*/
|
|
98
|
+
function buildManagedClient(
|
|
99
|
+
kind: CdpClientKind,
|
|
100
|
+
conversationId: string,
|
|
101
|
+
backend: BrowserBackend,
|
|
102
|
+
): ScopedCdpClient {
|
|
103
|
+
const manager = new BrowserSessionManager({ backends: [backend] });
|
|
104
|
+
const session = manager.createSession();
|
|
105
|
+
let disposed = false;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
kind,
|
|
109
|
+
conversationId,
|
|
110
|
+
async send<T = unknown>(
|
|
111
|
+
method: string,
|
|
112
|
+
params?: Record<string, unknown>,
|
|
113
|
+
signal?: AbortSignal,
|
|
114
|
+
): Promise<T> {
|
|
115
|
+
if (disposed) {
|
|
116
|
+
const clientName =
|
|
117
|
+
kind === "extension"
|
|
118
|
+
? "ExtensionCdpClient"
|
|
119
|
+
: kind === "cdp-inspect"
|
|
120
|
+
? "CdpInspectClient"
|
|
121
|
+
: "LocalCdpClient";
|
|
122
|
+
throw new CdpError("disposed", `${clientName} already disposed`, {
|
|
123
|
+
cdpMethod: method,
|
|
124
|
+
cdpParams: params,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const command: CdpCommand = { method, params };
|
|
128
|
+
const envelope = await manager.send(session.id, command, signal);
|
|
129
|
+
return unwrapResult<T>(envelope, method, params);
|
|
130
|
+
},
|
|
131
|
+
dispose(): void {
|
|
132
|
+
if (disposed) return;
|
|
133
|
+
disposed = true;
|
|
134
|
+
// disposeAll() tears down the per-invocation backend (which in
|
|
135
|
+
// turn disposes the underlying CdpClient) and clears the single
|
|
136
|
+
// session we created in buildManagedClient.
|
|
137
|
+
manager.disposeAll();
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Adapter that makes an existing `CdpClient` look like a
|
|
144
|
+
* `BrowserBackend.send`. Converts thrown CdpErrors back into a
|
|
145
|
+
* `CdpResult` envelope with an `error` payload so the manager does
|
|
146
|
+
* not need to know about our thrown-error convention, then the
|
|
147
|
+
* envelope is unwrapped again on the way out of the managed client.
|
|
148
|
+
*
|
|
149
|
+
* The per-command `command.sessionId` (populated by the manager from
|
|
150
|
+
* a session's opaque `targetId`) is intentionally not forwarded to
|
|
151
|
+
* the underlying CdpClient today — both LocalCdpClient and
|
|
152
|
+
* ExtensionCdpClient take their CDP sessionId at construction time
|
|
153
|
+
* and tools run one client per invocation. The seam is preserved so
|
|
154
|
+
* a future multi-target backend can read it off the CdpCommand.
|
|
155
|
+
*/
|
|
156
|
+
async function dispatchThroughClient(
|
|
157
|
+
client: CdpClient,
|
|
158
|
+
command: CdpCommand,
|
|
159
|
+
signal: AbortSignal | undefined,
|
|
160
|
+
): Promise<CdpResult> {
|
|
161
|
+
try {
|
|
162
|
+
const result = await client.send(command.method, command.params, signal);
|
|
163
|
+
return { result };
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (err instanceof CdpError) {
|
|
166
|
+
// Preserve the original CdpError so unwrapResult can re-throw it
|
|
167
|
+
// verbatim. CdpResult's error channel is opaque to the manager,
|
|
168
|
+
// so stashing the instance under `data` is safe.
|
|
169
|
+
return {
|
|
170
|
+
error: {
|
|
171
|
+
code: -1,
|
|
172
|
+
message: err.message,
|
|
173
|
+
data: err,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Unwrap a CdpResult envelope into the raw CDP result `T` or throw
|
|
183
|
+
* the underlying CdpError. If the envelope carries an error but the
|
|
184
|
+
* `data` is not a CdpError (e.g. a future backend surfaces a JSON-RPC
|
|
185
|
+
* error envelope directly), synthesize a transport_error CdpError so
|
|
186
|
+
* call sites keep their uniform error handling.
|
|
187
|
+
*/
|
|
188
|
+
function unwrapResult<T>(
|
|
189
|
+
envelope: CdpResult,
|
|
190
|
+
method: string,
|
|
191
|
+
params?: Record<string, unknown>,
|
|
192
|
+
): T {
|
|
193
|
+
if (envelope.error) {
|
|
194
|
+
if (envelope.error.data instanceof CdpError) {
|
|
195
|
+
throw envelope.error.data;
|
|
196
|
+
}
|
|
197
|
+
throw new CdpError("cdp_error", envelope.error.message, {
|
|
198
|
+
cdpMethod: method,
|
|
199
|
+
cdpParams: params,
|
|
200
|
+
underlying: envelope.error,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return envelope.result as T;
|
|
204
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
CdpInspectClient,
|
|
3
|
+
type CdpInspectClientOptions,
|
|
4
|
+
type CdpInspectHelpers,
|
|
5
|
+
createCdpInspectClient,
|
|
6
|
+
} from "./cdp-inspect-client.js";
|
|
7
|
+
export { CdpError, type CdpErrorCode } from "./errors.js";
|
|
8
|
+
export {
|
|
9
|
+
createExtensionCdpClient,
|
|
10
|
+
ExtensionCdpClient,
|
|
11
|
+
} from "./extension-cdp-client.js";
|
|
12
|
+
export { getCdpClient } from "./factory.js";
|
|
13
|
+
export { createLocalCdpClient, LocalCdpClient } from "./local-cdp-client.js";
|
|
14
|
+
export type { CdpClient, CdpClientKind, ScopedCdpClient } from "./types.js";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { getLogger } from "../../../util/logger.js";
|
|
2
|
+
import { browserManager } from "../browser-manager.js";
|
|
3
|
+
import { CdpError } from "./errors.js";
|
|
4
|
+
import type { CdpClientKind, ScopedCdpClient } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const log = getLogger("local-cdp-client");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal shape of the Playwright CDPSession we depend on. Avoids a
|
|
10
|
+
* direct Playwright type import so the CDP client stays buildable
|
|
11
|
+
* even when Playwright types are not present (dev builds, CI jobs
|
|
12
|
+
* that skip the playwright install).
|
|
13
|
+
*/
|
|
14
|
+
interface PlaywrightCdpSession {
|
|
15
|
+
send(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
16
|
+
detach(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Minimal shape of the Playwright Page we use for CDP session
|
|
21
|
+
* creation. Intentionally narrow so we never have to import the
|
|
22
|
+
* Playwright types directly from this module.
|
|
23
|
+
*/
|
|
24
|
+
interface RawPlaywrightPage {
|
|
25
|
+
context(): {
|
|
26
|
+
newCDPSession(page: unknown): Promise<PlaywrightCdpSession>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Playwright-backed implementation of {@link ScopedCdpClient}. Used
|
|
32
|
+
* for CLI conversations, headless cloud conversations, unit tests,
|
|
33
|
+
* and any desktop conversation that does not have a `hostBrowserProxy`
|
|
34
|
+
* configured.
|
|
35
|
+
*
|
|
36
|
+
* LocalCdpClient owns only the per-conversation CDP session; the
|
|
37
|
+
* underlying Chromium is still launched and torn down by
|
|
38
|
+
* `browserManager.ensureContext()` / `browserManager.shutdown()`.
|
|
39
|
+
*/
|
|
40
|
+
export class LocalCdpClient implements ScopedCdpClient {
|
|
41
|
+
readonly kind: CdpClientKind = "local";
|
|
42
|
+
|
|
43
|
+
private sessionPromise: Promise<PlaywrightCdpSession> | null = null;
|
|
44
|
+
private disposed = false;
|
|
45
|
+
|
|
46
|
+
constructor(public readonly conversationId: string) {}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Lazily create (and cache) a Playwright CDP session for this
|
|
50
|
+
* conversation. Concurrent callers share the same in-flight promise
|
|
51
|
+
* so `newCDPSession` is only called once per LocalCdpClient
|
|
52
|
+
* instance.
|
|
53
|
+
*
|
|
54
|
+
* If the underlying browser launch / `newCDPSession` rejects (e.g.
|
|
55
|
+
* a transient Chromium spawn failure), the cached promise is
|
|
56
|
+
* cleared so the next `ensureSession()` call retries from scratch
|
|
57
|
+
* instead of replaying the same rejection forever.
|
|
58
|
+
*/
|
|
59
|
+
private async ensureSession(): Promise<PlaywrightCdpSession> {
|
|
60
|
+
if (this.disposed) {
|
|
61
|
+
throw new CdpError("disposed", "LocalCdpClient already disposed");
|
|
62
|
+
}
|
|
63
|
+
if (this.sessionPromise) return this.sessionPromise;
|
|
64
|
+
const created = this.createSession();
|
|
65
|
+
this.sessionPromise = created;
|
|
66
|
+
// Clear the cached promise on rejection so the next call retries
|
|
67
|
+
// from scratch instead of replaying the same failure forever.
|
|
68
|
+
// Only clear if `created` is still the cached promise — a
|
|
69
|
+
// concurrent dispose may have already nulled it.
|
|
70
|
+
created.catch(() => {
|
|
71
|
+
if (this.sessionPromise === created) {
|
|
72
|
+
this.sessionPromise = null;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return created;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private async createSession(): Promise<PlaywrightCdpSession> {
|
|
79
|
+
const page = await browserManager.getOrCreateSessionPage(
|
|
80
|
+
this.conversationId,
|
|
81
|
+
);
|
|
82
|
+
const rawPage = page as unknown as RawPlaywrightPage;
|
|
83
|
+
const session = await rawPage.context().newCDPSession(rawPage);
|
|
84
|
+
log.debug(
|
|
85
|
+
{ conversationId: this.conversationId },
|
|
86
|
+
"Created Playwright CDP session for LocalCdpClient",
|
|
87
|
+
);
|
|
88
|
+
return session;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async send<T = unknown>(
|
|
92
|
+
method: string,
|
|
93
|
+
params?: Record<string, unknown>,
|
|
94
|
+
signal?: AbortSignal,
|
|
95
|
+
): Promise<T> {
|
|
96
|
+
if (this.disposed) {
|
|
97
|
+
throw new CdpError("disposed", "LocalCdpClient already disposed", {
|
|
98
|
+
cdpMethod: method,
|
|
99
|
+
cdpParams: params,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (signal?.aborted) {
|
|
103
|
+
throw new CdpError("aborted", "Aborted before send", {
|
|
104
|
+
cdpMethod: method,
|
|
105
|
+
cdpParams: params,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
let session: PlaywrightCdpSession;
|
|
109
|
+
try {
|
|
110
|
+
session = await this.ensureSession();
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (signal?.aborted) {
|
|
113
|
+
throw new CdpError("aborted", "Aborted during send", {
|
|
114
|
+
cdpMethod: method,
|
|
115
|
+
cdpParams: params,
|
|
116
|
+
underlying: err,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// Re-throw existing CdpError instances unchanged so we don't
|
|
120
|
+
// double-wrap (e.g. a "disposed" error raised by a concurrent
|
|
121
|
+
// dispose landing during ensureSession()).
|
|
122
|
+
if (err instanceof CdpError) {
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
// ensureSession failures (browser launch errors, Chromium
|
|
126
|
+
// spawn failures, etc) are surfaced as transport_error so
|
|
127
|
+
// callers can distinguish them from CDP protocol errors raised
|
|
128
|
+
// by session.send below.
|
|
129
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
130
|
+
throw new CdpError("transport_error", msg, {
|
|
131
|
+
cdpMethod: method,
|
|
132
|
+
cdpParams: params,
|
|
133
|
+
underlying: err,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const result = (await session.send(method, params)) as T;
|
|
138
|
+
return result;
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (signal?.aborted) {
|
|
141
|
+
throw new CdpError("aborted", "Aborted during send", {
|
|
142
|
+
cdpMethod: method,
|
|
143
|
+
cdpParams: params,
|
|
144
|
+
underlying: err,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (err instanceof CdpError) {
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
151
|
+
throw new CdpError("cdp_error", msg, {
|
|
152
|
+
cdpMethod: method,
|
|
153
|
+
cdpParams: params,
|
|
154
|
+
underlying: err,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
dispose(): void {
|
|
160
|
+
if (this.disposed) return;
|
|
161
|
+
this.disposed = true;
|
|
162
|
+
const pending = this.sessionPromise;
|
|
163
|
+
this.sessionPromise = null;
|
|
164
|
+
if (!pending) return;
|
|
165
|
+
pending
|
|
166
|
+
.then(async (session) => {
|
|
167
|
+
try {
|
|
168
|
+
await session.detach();
|
|
169
|
+
} catch (err) {
|
|
170
|
+
log.debug({ err }, "LocalCdpClient: session.detach threw (ignored)");
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
.catch(() => {
|
|
174
|
+
// Session never resolved — nothing to detach.
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Factory for a fresh {@link LocalCdpClient} bound to a conversation.
|
|
181
|
+
* Keeping the constructor + factory split lets the cdp-client factory
|
|
182
|
+
* branch between local and extension transports without
|
|
183
|
+
* exposing the class directly to callers.
|
|
184
|
+
*/
|
|
185
|
+
export function createLocalCdpClient(conversationId: string): LocalCdpClient {
|
|
186
|
+
return new LocalCdpClient(conversationId);
|
|
187
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal typed surface over Chrome DevTools Protocol. Implemented by
|
|
3
|
+
* LocalCdpClient (Playwright-backed, same-process Chromium),
|
|
4
|
+
* ExtensionCdpClient (routes through HostBrowserProxy to the user's
|
|
5
|
+
* Chrome via chrome.debugger), and CdpInspectClient (connects to a
|
|
6
|
+
* remote browser over a raw CDP WebSocket URL). Tools call
|
|
7
|
+
* `send(method, params)` with a CDP method name and return the raw
|
|
8
|
+
* CDP result object; errors are thrown as {@link CdpError}.
|
|
9
|
+
*/
|
|
10
|
+
export interface CdpClient {
|
|
11
|
+
/**
|
|
12
|
+
* Send a CDP command and await the result. `method` must be a
|
|
13
|
+
* well-known CDP method name (e.g. "Page.navigate",
|
|
14
|
+
* "Runtime.evaluate", "Accessibility.getFullAXTree"). `params` is
|
|
15
|
+
* forwarded verbatim.
|
|
16
|
+
*
|
|
17
|
+
* On success, returns the raw `result` object from the CDP response
|
|
18
|
+
* as `T`. On JSON-RPC error or transport failure, throws a
|
|
19
|
+
* {@link CdpError}. Abort propagates via `signal`; aborted calls
|
|
20
|
+
* throw an {@link CdpError} with `code === "aborted"`.
|
|
21
|
+
*/
|
|
22
|
+
send<T = unknown>(
|
|
23
|
+
method: string,
|
|
24
|
+
params?: Record<string, unknown>,
|
|
25
|
+
signal?: AbortSignal,
|
|
26
|
+
): Promise<T>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Release any backend-side resources (CDP sessions, in-flight
|
|
30
|
+
* requests, listeners). Idempotent. Calling `send` after `dispose`
|
|
31
|
+
* is allowed but should surface as an error.
|
|
32
|
+
*/
|
|
33
|
+
dispose(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Backend kind exposed by a concrete CdpClient. Used by tools that
|
|
38
|
+
* want to branch on the transport (e.g. browser_navigate should skip
|
|
39
|
+
* the sacrificial-profile screencast setup when running against the
|
|
40
|
+
* user's own Chrome via the extension).
|
|
41
|
+
*/
|
|
42
|
+
export type CdpClientKind = "local" | "extension" | "cdp-inspect";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Concrete CdpClient instance returned by the factory. Carries the
|
|
46
|
+
* backend `kind` for transport-aware branches in tool code.
|
|
47
|
+
*/
|
|
48
|
+
export interface ScopedCdpClient extends CdpClient {
|
|
49
|
+
readonly kind: CdpClientKind;
|
|
50
|
+
/** Stable conversation id this client is bound to. */
|
|
51
|
+
readonly conversationId: string;
|
|
52
|
+
}
|
|
@@ -9,7 +9,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
|
9
9
|
class FileEditTool implements Tool {
|
|
10
10
|
name = "file_edit";
|
|
11
11
|
description =
|
|
12
|
-
"Replace an exact string in a file with a new string. Use this for surgical edits instead of rewriting entire files.
|
|
12
|
+
"Replace an exact string in a file on your own machine with a new string. Use this for surgical edits instead of rewriting entire files. Use host_file_edit for files on your guardian's device instead.";
|
|
13
13
|
category = "filesystem";
|
|
14
14
|
defaultRiskLevel = RiskLevel.Low;
|
|
15
15
|
|
|
@@ -8,7 +8,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
|
8
8
|
class FileListTool implements Tool {
|
|
9
9
|
name = "file_list";
|
|
10
10
|
description =
|
|
11
|
-
"List the contents of a directory. Returns file and subdirectory names with type indicators and sizes.";
|
|
11
|
+
"List the contents of a directory on your own machine. Returns file and subdirectory names with type indicators and sizes.";
|
|
12
12
|
category = "filesystem";
|
|
13
13
|
defaultRiskLevel = RiskLevel.Low;
|
|
14
14
|
|
|
@@ -14,7 +14,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
|
14
14
|
class FileReadTool implements Tool {
|
|
15
15
|
name = "file_read";
|
|
16
16
|
description =
|
|
17
|
-
"Read the contents of a file. For image files (JPEG, PNG, GIF, WebP), returns the image for visual analysis.
|
|
17
|
+
"Read the contents of a file on your own machine. For image files (JPEG, PNG, GIF, WebP), returns the image for visual analysis. Use host_file_read for files on your guardian's device instead.";
|
|
18
18
|
category = "filesystem";
|
|
19
19
|
defaultRiskLevel = RiskLevel.Low;
|
|
20
20
|
|
|
@@ -8,7 +8,8 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
|
8
8
|
|
|
9
9
|
class FileWriteTool implements Tool {
|
|
10
10
|
name = "file_write";
|
|
11
|
-
description =
|
|
11
|
+
description =
|
|
12
|
+
"Write content to a file on your own machine, creating it if it does not exist. Use host_file_write for files on your guardian's device instead.";
|
|
12
13
|
category = "filesystem";
|
|
13
14
|
defaultRiskLevel = RiskLevel.Low;
|
|
14
15
|
|
|
@@ -8,7 +8,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
|
8
8
|
class HostFileEditTool implements Tool {
|
|
9
9
|
name = "host_file_edit";
|
|
10
10
|
description =
|
|
11
|
-
"Replace exact text in a
|
|
11
|
+
"Replace exact text in a file on your guardian's device with new text. For files on your own machine, use file_edit instead.";
|
|
12
12
|
category = "host-filesystem";
|
|
13
13
|
defaultRiskLevel = RiskLevel.Medium;
|
|
14
14
|
|
|
@@ -13,7 +13,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
|
13
13
|
class HostFileReadTool implements Tool {
|
|
14
14
|
name = "host_file_read";
|
|
15
15
|
description =
|
|
16
|
-
"Read the contents of a file on
|
|
16
|
+
"Read the contents of a file on your guardian's device, including images (JPEG, PNG, GIF, WebP). For files on your own machine, use file_read instead.";
|
|
17
17
|
category = "host-filesystem";
|
|
18
18
|
defaultRiskLevel = RiskLevel.Medium;
|
|
19
19
|
|
|
@@ -54,21 +54,9 @@ class HostFileReadTool implements Tool {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Image files must be handled locally — the host-file proxy protocol
|
|
58
|
-
// only carries {content, isError} and cannot transport contentBlocks
|
|
59
|
-
// (base64 image data). Check for image extensions before the proxy
|
|
60
|
-
// short-circuit so image reads work in managed/macOS+iOS sessions.
|
|
61
|
-
const ext = extname(rawPath).toLowerCase();
|
|
62
|
-
if (IMAGE_EXTENSIONS.has(ext)) {
|
|
63
|
-
const pathCheck = hostPolicy(rawPath);
|
|
64
|
-
if (!pathCheck.ok) {
|
|
65
|
-
return { content: `Error: ${pathCheck.error}`, isError: true };
|
|
66
|
-
}
|
|
67
|
-
return readImageFile(pathCheck.resolved);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
57
|
// Proxy to connected client for execution on the user's machine
|
|
71
|
-
// when a capable client is available (managed/cloud-hosted mode)
|
|
58
|
+
// when a capable client is available (managed/cloud-hosted mode),
|
|
59
|
+
// including image reads that need the host filesystem view.
|
|
72
60
|
if (context.hostFileProxy?.isAvailable()) {
|
|
73
61
|
return context.hostFileProxy.request(
|
|
74
62
|
{
|
|
@@ -82,6 +70,15 @@ class HostFileReadTool implements Tool {
|
|
|
82
70
|
);
|
|
83
71
|
}
|
|
84
72
|
|
|
73
|
+
const ext = extname(rawPath).toLowerCase();
|
|
74
|
+
if (IMAGE_EXTENSIONS.has(ext)) {
|
|
75
|
+
const pathCheck = hostPolicy(rawPath);
|
|
76
|
+
if (!pathCheck.ok) {
|
|
77
|
+
return { content: `Error: ${pathCheck.error}`, isError: true };
|
|
78
|
+
}
|
|
79
|
+
return readImageFile(pathCheck.resolved);
|
|
80
|
+
}
|
|
81
|
+
|
|
85
82
|
const ops = new FileSystemOps(hostPolicy);
|
|
86
83
|
|
|
87
84
|
const result = ops.readFileSafe({
|