@vellumai/assistant 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docker-entrypoint.sh +12 -2
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/checker.test.ts +104 -170
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +21 -6
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/log-export-workspace.test.ts +190 -0
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +74 -55
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -3
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +11 -5
- package/src/__tests__/test-preload.ts +14 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +62 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -35
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +55 -0
- package/src/cli/__tests__/run-assistant-command.ts +34 -7
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +16 -2
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +10 -3
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +12 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +46 -7
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +16 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -16
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +6 -2
- package/src/daemon/context-overflow-approval.ts +5 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +74 -19
- package/src/daemon/conversation-attachments.ts +40 -1
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +66 -3
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +159 -20
- package/src/daemon/conversation-surfaces.ts +78 -12
- package/src/daemon/conversation-tool-setup.ts +74 -11
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +227 -11
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -139
- package/src/daemon/handlers/shared.ts +65 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +86 -12
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +59 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -6
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/settings.ts +12 -0
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +112 -35
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +14 -0
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +38 -10
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +65 -5
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +177 -18
- package/src/memory/graph/capability-seed.ts +3 -5
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +5 -5
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +127 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -96
- package/src/prompts/templates/SOUL.md +11 -11
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +24 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +308 -28
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +60 -25
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -100
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -1
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +24 -21
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -3
- package/src/util/platform.ts +14 -19
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/029-seed-pkb.ts +1 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E smoke test for the cloud-hosted `host_browser_request` round-trip.
|
|
3
|
+
*
|
|
4
|
+
* Boots the runtime HTTP server in-process, opens a mock chrome-extension
|
|
5
|
+
* WebSocket against `/v1/browser-relay`, and drives
|
|
6
|
+
* `HostBrowserProxy.request()` end-to-end:
|
|
7
|
+
*
|
|
8
|
+
* proxy.request()
|
|
9
|
+
* → sendToClient (routed via ChromeExtensionRegistry by guardianId)
|
|
10
|
+
* → mock extension WebSocket receives host_browser_request
|
|
11
|
+
* → mock CDP handler (Browser.getVersion fake)
|
|
12
|
+
* → POST /v1/host-browser-result
|
|
13
|
+
* → handleHostBrowserResult → conversation.resolveHostBrowser
|
|
14
|
+
* → proxy.resolve() → request() resolves
|
|
15
|
+
*
|
|
16
|
+
* Covers:
|
|
17
|
+
* - Happy path: Browser.getVersion round-trips and returns the fake
|
|
18
|
+
* product string.
|
|
19
|
+
* - Abort: an aborted AbortSignal resolves with "Aborted" and the mock
|
|
20
|
+
* extension receives a host_browser_cancel frame.
|
|
21
|
+
* - Timeout: if the mock extension receives the frame but never
|
|
22
|
+
* POSTs a result, the proxy's setTimeout path fires and surfaces
|
|
23
|
+
* a "timed out waiting for client response" error.
|
|
24
|
+
*
|
|
25
|
+
* The test runs entirely in Bun + loopback WebSocket/fetch — no real
|
|
26
|
+
* Chrome required.
|
|
27
|
+
*/
|
|
28
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
29
|
+
|
|
30
|
+
// ── Module mocks (must be declared before the real imports below) ────
|
|
31
|
+
|
|
32
|
+
mock.module("../util/logger.js", () => ({
|
|
33
|
+
getLogger: () =>
|
|
34
|
+
new Proxy({} as Record<string, unknown>, {
|
|
35
|
+
get: () => () => {},
|
|
36
|
+
}),
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
mock.module("../config/loader.js", () => ({
|
|
40
|
+
getConfig: () => ({
|
|
41
|
+
ui: {},
|
|
42
|
+
model: "test",
|
|
43
|
+
provider: "test",
|
|
44
|
+
memory: { enabled: false },
|
|
45
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
46
|
+
secretDetection: { enabled: false },
|
|
47
|
+
contextWindow: { maxInputTokens: 200000 },
|
|
48
|
+
services: {
|
|
49
|
+
inference: {
|
|
50
|
+
mode: "your-own",
|
|
51
|
+
provider: "anthropic",
|
|
52
|
+
model: "claude-opus-4-6",
|
|
53
|
+
},
|
|
54
|
+
"image-generation": {
|
|
55
|
+
mode: "your-own",
|
|
56
|
+
provider: "gemini",
|
|
57
|
+
model: "gemini-3.1-flash-image-preview",
|
|
58
|
+
},
|
|
59
|
+
"web-search": { mode: "your-own", provider: "inference-provider-native" },
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
// ── Real imports (after mocks) ──────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
import type { Conversation } from "../daemon/conversation.js";
|
|
67
|
+
import { HostBrowserProxy } from "../daemon/host-browser-proxy.js";
|
|
68
|
+
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
69
|
+
import { getDb, initializeDb } from "../memory/db.js";
|
|
70
|
+
import { mintToken } from "../runtime/auth/token-service.js";
|
|
71
|
+
import {
|
|
72
|
+
__resetChromeExtensionRegistryForTests,
|
|
73
|
+
getChromeExtensionRegistry,
|
|
74
|
+
} from "../runtime/chrome-extension-registry.js";
|
|
75
|
+
import { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
76
|
+
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
77
|
+
|
|
78
|
+
initializeDb();
|
|
79
|
+
|
|
80
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Wrap a HostBrowserProxy in a sendToClient that:
|
|
84
|
+
* 1. Routes host_browser_request/host_browser_cancel via the Chrome
|
|
85
|
+
* extension registry for the given guardianId.
|
|
86
|
+
* 2. Registers a pending interaction for each request so the
|
|
87
|
+
* `/v1/host-browser-result` HTTP route can find the stub
|
|
88
|
+
* conversation and call `resolveHostBrowser` on it.
|
|
89
|
+
*
|
|
90
|
+
* Returns the proxy and its stub conversation. In production this
|
|
91
|
+
* wiring lives in `conversation-routes.ts` `makeHubPublisher`; the test
|
|
92
|
+
* reproduces the minimum surface needed for the round-trip.
|
|
93
|
+
*/
|
|
94
|
+
function createBoundProxy(
|
|
95
|
+
guardianId: string,
|
|
96
|
+
conversationId: string,
|
|
97
|
+
): { proxy: HostBrowserProxy; conversation: Conversation } {
|
|
98
|
+
// The stub Conversation's `resolveHostBrowser` routes straight back
|
|
99
|
+
// to the real proxy. Declare the proxy reference first so the stub
|
|
100
|
+
// can close over it before the proxy itself is constructed below.
|
|
101
|
+
let proxyRef: HostBrowserProxy | null = null;
|
|
102
|
+
const conversation = {
|
|
103
|
+
resolveHostBrowser(
|
|
104
|
+
requestId: string,
|
|
105
|
+
response: { content: string; isError: boolean },
|
|
106
|
+
) {
|
|
107
|
+
proxyRef?.resolve(requestId, response);
|
|
108
|
+
},
|
|
109
|
+
} as unknown as Conversation;
|
|
110
|
+
|
|
111
|
+
const sendToClient = (msg: ServerMessage) => {
|
|
112
|
+
// Register pending interactions for host_browser_request envelopes
|
|
113
|
+
// so the /v1/host-browser-result route can look them up.
|
|
114
|
+
if ((msg as { type: string }).type === "host_browser_request") {
|
|
115
|
+
const requestId = (msg as { requestId: string }).requestId;
|
|
116
|
+
pendingInteractions.register(requestId, {
|
|
117
|
+
conversation,
|
|
118
|
+
conversationId,
|
|
119
|
+
kind: "host_browser",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const ok = getChromeExtensionRegistry().send(guardianId, msg);
|
|
123
|
+
if (!ok) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`chrome-extension host_browser send failed: no active connection for guardian ${guardianId}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const proxy = new HostBrowserProxy(sendToClient);
|
|
131
|
+
proxyRef = proxy;
|
|
132
|
+
return { proxy, conversation };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Mint an actor-bound JWT for the given guardianId. The WebSocket
|
|
137
|
+
* upgrade handler parses `sub=actor:<assistantId>:<actorPrincipalId>`
|
|
138
|
+
* and treats `actorPrincipalId` as the guardianId.
|
|
139
|
+
*/
|
|
140
|
+
function mintActorToken(guardianId: string): string {
|
|
141
|
+
return mintToken({
|
|
142
|
+
aud: "vellum-daemon",
|
|
143
|
+
sub: `actor:self:${guardianId}`,
|
|
144
|
+
scope_profile: "actor_client_v1",
|
|
145
|
+
policy_epoch: 1,
|
|
146
|
+
ttlSeconds: 3600,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── Tests ───────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
describe("host_browser cloud-hosted e2e round-trip", () => {
|
|
153
|
+
let server: RuntimeHttpServer;
|
|
154
|
+
let port: number;
|
|
155
|
+
let runtimeBaseUrl: string;
|
|
156
|
+
|
|
157
|
+
beforeEach(async () => {
|
|
158
|
+
// Each test gets a clean DB and a fresh registry so connection
|
|
159
|
+
// state doesn't leak between cases.
|
|
160
|
+
const db = getDb();
|
|
161
|
+
db.run("DELETE FROM contact_channels");
|
|
162
|
+
db.run("DELETE FROM contacts");
|
|
163
|
+
pendingInteractions.clear();
|
|
164
|
+
__resetChromeExtensionRegistryForTests();
|
|
165
|
+
|
|
166
|
+
port = 19800 + Math.floor(Math.random() * 200);
|
|
167
|
+
runtimeBaseUrl = `http://127.0.0.1:${port}`;
|
|
168
|
+
server = new RuntimeHttpServer({ port });
|
|
169
|
+
await server.start();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
afterEach(async () => {
|
|
173
|
+
await server?.stop();
|
|
174
|
+
pendingInteractions.clear();
|
|
175
|
+
__resetChromeExtensionRegistryForTests();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("happy path: Browser.getVersion round-trips through the mock extension", async () => {
|
|
179
|
+
const guardianId = `test-guardian-${crypto.randomUUID()}`;
|
|
180
|
+
const token = mintActorToken(guardianId);
|
|
181
|
+
|
|
182
|
+
// Dynamic import keeps the module cache warm across tests but avoids
|
|
183
|
+
// binding the fixture at file-load time (where the mocks might not
|
|
184
|
+
// yet have applied for a freshly forked test worker).
|
|
185
|
+
const { createMockChromeExtension } =
|
|
186
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
187
|
+
const mockExt = createMockChromeExtension({
|
|
188
|
+
runtimeBaseUrl,
|
|
189
|
+
token,
|
|
190
|
+
});
|
|
191
|
+
await mockExt.start();
|
|
192
|
+
await mockExt.waitForConnection();
|
|
193
|
+
|
|
194
|
+
// Give the open handler a tick to register the connection in the
|
|
195
|
+
// ChromeExtensionRegistry (Bun's WebSocket open callback fires
|
|
196
|
+
// asynchronously after the upgrade handler returns).
|
|
197
|
+
await waitForRegistryEntry(guardianId);
|
|
198
|
+
|
|
199
|
+
const { proxy } = createBoundProxy(guardianId, "conv-happy");
|
|
200
|
+
|
|
201
|
+
const result = await proxy.request(
|
|
202
|
+
{ cdpMethod: "Browser.getVersion" },
|
|
203
|
+
"conv-happy",
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
expect(result.isError).toBe(false);
|
|
207
|
+
expect(result.content).toContain("Chrome/MockTest");
|
|
208
|
+
|
|
209
|
+
const received = mockExt.receivedRequests();
|
|
210
|
+
expect(received).toHaveLength(1);
|
|
211
|
+
expect(received[0].cdpMethod).toBe("Browser.getVersion");
|
|
212
|
+
expect(typeof received[0].requestId).toBe("string");
|
|
213
|
+
expect(received[0].conversationId).toBe("conv-happy");
|
|
214
|
+
|
|
215
|
+
proxy.dispose();
|
|
216
|
+
await mockExt.stop();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("happy path (WS result transport): Browser.getVersion round-trips when the extension returns the result over the same WS", async () => {
|
|
220
|
+
const guardianId = `test-guardian-${crypto.randomUUID()}`;
|
|
221
|
+
const token = mintActorToken(guardianId);
|
|
222
|
+
|
|
223
|
+
const { createMockChromeExtension } =
|
|
224
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
225
|
+
// Same fixture as the HTTP happy path, but configured to return
|
|
226
|
+
// results over the /v1/browser-relay WebSocket instead of POSTing
|
|
227
|
+
// /v1/host-browser-result. This exercises the runtime WS
|
|
228
|
+
// `message` handler's host_browser_result dispatch path added in
|
|
229
|
+
// PR2 of the browser-use remediation plan.
|
|
230
|
+
const mockExt = createMockChromeExtension({
|
|
231
|
+
runtimeBaseUrl,
|
|
232
|
+
token,
|
|
233
|
+
resultTransport: "ws",
|
|
234
|
+
});
|
|
235
|
+
await mockExt.start();
|
|
236
|
+
await mockExt.waitForConnection();
|
|
237
|
+
await waitForRegistryEntry(guardianId);
|
|
238
|
+
|
|
239
|
+
const { proxy } = createBoundProxy(guardianId, "conv-happy-ws");
|
|
240
|
+
|
|
241
|
+
const result = await proxy.request(
|
|
242
|
+
{ cdpMethod: "Browser.getVersion" },
|
|
243
|
+
"conv-happy-ws",
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expect(result.isError).toBe(false);
|
|
247
|
+
expect(result.content).toContain("Chrome/MockTest");
|
|
248
|
+
|
|
249
|
+
const received = mockExt.receivedRequests();
|
|
250
|
+
expect(received).toHaveLength(1);
|
|
251
|
+
expect(received[0].cdpMethod).toBe("Browser.getVersion");
|
|
252
|
+
expect(received[0].conversationId).toBe("conv-happy-ws");
|
|
253
|
+
|
|
254
|
+
// The pending interaction must be fully consumed — if the WS
|
|
255
|
+
// handler silently no-op'd, the entry would still be registered
|
|
256
|
+
// after the proxy resolves.
|
|
257
|
+
expect(pendingInteractions.get(received[0].requestId)).toBeUndefined();
|
|
258
|
+
|
|
259
|
+
proxy.dispose();
|
|
260
|
+
await mockExt.stop();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("abort: AbortSignal resolves to 'Aborted' and extension receives host_browser_cancel", async () => {
|
|
264
|
+
const guardianId = `test-guardian-${crypto.randomUUID()}`;
|
|
265
|
+
const token = mintActorToken(guardianId);
|
|
266
|
+
|
|
267
|
+
const { createMockChromeExtension } =
|
|
268
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
269
|
+
const mockExt = createMockChromeExtension({
|
|
270
|
+
runtimeBaseUrl,
|
|
271
|
+
token,
|
|
272
|
+
// Hang forever so we can abort mid-flight without a race against
|
|
273
|
+
// the default handler's immediate response.
|
|
274
|
+
cdpHandler: () => new Promise(() => {}),
|
|
275
|
+
});
|
|
276
|
+
await mockExt.start();
|
|
277
|
+
await mockExt.waitForConnection();
|
|
278
|
+
await waitForRegistryEntry(guardianId);
|
|
279
|
+
|
|
280
|
+
const { proxy } = createBoundProxy(guardianId, "conv-abort");
|
|
281
|
+
|
|
282
|
+
const controller = new AbortController();
|
|
283
|
+
const resultPromise = proxy.request(
|
|
284
|
+
{ cdpMethod: "Browser.getVersion" },
|
|
285
|
+
"conv-abort",
|
|
286
|
+
controller.signal,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Wait for the mock extension to observe the request, then abort so
|
|
290
|
+
// the cancel envelope has somewhere to land.
|
|
291
|
+
await waitFor(() => mockExt.receivedRequests().length === 1);
|
|
292
|
+
|
|
293
|
+
controller.abort();
|
|
294
|
+
const result = await resultPromise;
|
|
295
|
+
|
|
296
|
+
expect(result.content).toBe("Aborted");
|
|
297
|
+
expect(result.isError).toBe(true);
|
|
298
|
+
|
|
299
|
+
// The cancel frame is dispatched synchronously from the abort
|
|
300
|
+
// listener, but the WebSocket delivers it asynchronously — give it a
|
|
301
|
+
// few turns to arrive before asserting.
|
|
302
|
+
await waitFor(() => mockExt.receivedCancels().length === 1);
|
|
303
|
+
const cancels = mockExt.receivedCancels();
|
|
304
|
+
expect(cancels).toHaveLength(1);
|
|
305
|
+
expect(cancels[0].requestId).toBe(mockExt.receivedRequests()[0].requestId);
|
|
306
|
+
|
|
307
|
+
proxy.dispose();
|
|
308
|
+
await mockExt.stop();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("abort: late /v1/host-browser-result POST after cancel is ignored (no ghost completion)", async () => {
|
|
312
|
+
// Regression for PR6 of the browser-use remediation plan. The
|
|
313
|
+
// daemon-side proxy must treat a late result POST — arriving
|
|
314
|
+
// after the caller has already been resolved with "Aborted" —
|
|
315
|
+
// as a benign race, not a noisy false-positive timeout. It must
|
|
316
|
+
// also NOT resolve the caller a second time.
|
|
317
|
+
//
|
|
318
|
+
// We exercise this from the daemon's perspective by:
|
|
319
|
+
// 1. Starting a request with an AbortSignal.
|
|
320
|
+
// 2. Aborting the signal so the proxy resolves with "Aborted".
|
|
321
|
+
// 3. Manually POSTing a host_browser_result for the same
|
|
322
|
+
// requestId straight to /v1/host-browser-result (bypassing
|
|
323
|
+
// the compliant dispatcher's cancel-suppression).
|
|
324
|
+
// 4. Verifying the POST is accepted by the runtime (i.e. the
|
|
325
|
+
// HTTP layer doesn't explode) and the caller's promise
|
|
326
|
+
// never fulfils twice.
|
|
327
|
+
const guardianId = `test-guardian-${crypto.randomUUID()}`;
|
|
328
|
+
const token = mintActorToken(guardianId);
|
|
329
|
+
|
|
330
|
+
const { createMockChromeExtension } =
|
|
331
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
332
|
+
const mockExt = createMockChromeExtension({
|
|
333
|
+
runtimeBaseUrl,
|
|
334
|
+
token,
|
|
335
|
+
// Hang forever — same gating trick as the plain abort test,
|
|
336
|
+
// so we can cancel before the handler returns anything.
|
|
337
|
+
cdpHandler: () => new Promise(() => {}),
|
|
338
|
+
});
|
|
339
|
+
await mockExt.start();
|
|
340
|
+
await mockExt.waitForConnection();
|
|
341
|
+
await waitForRegistryEntry(guardianId);
|
|
342
|
+
|
|
343
|
+
const { proxy } = createBoundProxy(guardianId, "conv-abort-late");
|
|
344
|
+
|
|
345
|
+
let resolveCount = 0;
|
|
346
|
+
const controller = new AbortController();
|
|
347
|
+
const resultPromise = proxy
|
|
348
|
+
.request(
|
|
349
|
+
{ cdpMethod: "Browser.getVersion" },
|
|
350
|
+
"conv-abort-late",
|
|
351
|
+
controller.signal,
|
|
352
|
+
)
|
|
353
|
+
.then((r) => {
|
|
354
|
+
resolveCount += 1;
|
|
355
|
+
return r;
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
await waitFor(() => mockExt.receivedRequests().length === 1);
|
|
359
|
+
const pendingRequestId = mockExt.receivedRequests()[0].requestId;
|
|
360
|
+
|
|
361
|
+
controller.abort();
|
|
362
|
+
const result = await resultPromise;
|
|
363
|
+
expect(result.content).toBe("Aborted");
|
|
364
|
+
expect(result.isError).toBe(true);
|
|
365
|
+
expect(resolveCount).toBe(1);
|
|
366
|
+
|
|
367
|
+
// Now manually submit a late result for the same requestId —
|
|
368
|
+
// simulating a non-compliant client that failed to honour the
|
|
369
|
+
// cancel envelope. The runtime must accept the POST without
|
|
370
|
+
// error and the proxy must NOT resolve the caller a second time.
|
|
371
|
+
const lateResp = await fetch(`${runtimeBaseUrl}/v1/host-browser-result`, {
|
|
372
|
+
method: "POST",
|
|
373
|
+
headers: {
|
|
374
|
+
"Content-Type": "application/json",
|
|
375
|
+
Authorization: `Bearer ${token}`,
|
|
376
|
+
},
|
|
377
|
+
body: JSON.stringify({
|
|
378
|
+
requestId: pendingRequestId,
|
|
379
|
+
content: JSON.stringify({ product: "Chrome/LateResult" }),
|
|
380
|
+
isError: false,
|
|
381
|
+
}),
|
|
382
|
+
});
|
|
383
|
+
await lateResp.body?.cancel();
|
|
384
|
+
|
|
385
|
+
// Give the runtime a few turns to process the POST and hit its
|
|
386
|
+
// "no pending request" debug branch. If the proxy resolved a
|
|
387
|
+
// second time, `resolveCount` would be 2 here.
|
|
388
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
389
|
+
expect(resolveCount).toBe(1);
|
|
390
|
+
|
|
391
|
+
proxy.dispose();
|
|
392
|
+
await mockExt.stop();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("timeout: proxy.request resolves with timeout error when client never responds", async () => {
|
|
396
|
+
const guardianId = `test-guardian-${crypto.randomUUID()}`;
|
|
397
|
+
const token = mintActorToken(guardianId);
|
|
398
|
+
|
|
399
|
+
const { createMockChromeExtension } =
|
|
400
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
401
|
+
// CDP handler that never resolves — the request frame reaches the
|
|
402
|
+
// mock extension successfully, but no result is ever POSTed back.
|
|
403
|
+
// This exercises the proxy's `setTimeout` path (as opposed to a
|
|
404
|
+
// synchronous send failure, which is a separate code path).
|
|
405
|
+
const mockExt = createMockChromeExtension({
|
|
406
|
+
runtimeBaseUrl,
|
|
407
|
+
token,
|
|
408
|
+
cdpHandler: () => new Promise(() => {}),
|
|
409
|
+
});
|
|
410
|
+
await mockExt.start();
|
|
411
|
+
await mockExt.waitForConnection();
|
|
412
|
+
await waitForRegistryEntry(guardianId);
|
|
413
|
+
|
|
414
|
+
const { proxy } = createBoundProxy(guardianId, "conv-timeout");
|
|
415
|
+
|
|
416
|
+
// 50ms timeout — short enough to keep the test fast, long enough
|
|
417
|
+
// for the request frame to make the WS round-trip to the mock
|
|
418
|
+
// extension before the timer fires.
|
|
419
|
+
const result = await proxy.request(
|
|
420
|
+
{ cdpMethod: "Browser.getVersion", timeout_seconds: 0.05 },
|
|
421
|
+
"conv-timeout",
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
expect(result.isError).toBe(true);
|
|
425
|
+
expect(result.content).toContain("timed out");
|
|
426
|
+
|
|
427
|
+
// Sanity check: the frame actually reached the mock extension (so
|
|
428
|
+
// we know we're exercising the proxy's timer, not a send failure).
|
|
429
|
+
expect(mockExt.receivedRequests()).toHaveLength(1);
|
|
430
|
+
expect(mockExt.receivedRequests()[0].cdpMethod).toBe("Browser.getVersion");
|
|
431
|
+
|
|
432
|
+
proxy.dispose();
|
|
433
|
+
await mockExt.stop();
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// ── Local wait helpers ──────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
async function waitFor(
|
|
440
|
+
predicate: () => boolean,
|
|
441
|
+
timeoutMs = 2000,
|
|
442
|
+
): Promise<void> {
|
|
443
|
+
const deadline = Date.now() + timeoutMs;
|
|
444
|
+
while (!predicate()) {
|
|
445
|
+
if (Date.now() > deadline) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
`waitFor: predicate did not become true within ${timeoutMs}ms`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function waitForRegistryEntry(
|
|
455
|
+
guardianId: string,
|
|
456
|
+
timeoutMs = 2000,
|
|
457
|
+
): Promise<void> {
|
|
458
|
+
await waitFor(
|
|
459
|
+
() => getChromeExtensionRegistry().get(guardianId) !== undefined,
|
|
460
|
+
timeoutMs,
|
|
461
|
+
);
|
|
462
|
+
}
|