@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,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end WebSocket dispatch test for the PR10 envelopes:
|
|
3
|
+
*
|
|
4
|
+
* - `host_browser_event` — the extension forwards every
|
|
5
|
+
* `chrome.debugger.onEvent` firing to the runtime over the
|
|
6
|
+
* browser-relay WebSocket. This test asserts that the runtime's
|
|
7
|
+
* inbound frame handler fans the event out to subscribers of the
|
|
8
|
+
* module-level browser-session event bus with the method + params
|
|
9
|
+
* + cdpSessionId preserved.
|
|
10
|
+
*
|
|
11
|
+
* - `host_browser_session_invalidated` — the extension forwards a
|
|
12
|
+
* detach notification over the same socket. This test asserts
|
|
13
|
+
* that the runtime-side `BrowserSessionManager` evicts any stale
|
|
14
|
+
* session whose `targetId` matches the invalidated envelope and
|
|
15
|
+
* that the next CDP command against that session throws, forcing
|
|
16
|
+
* the owning tool to create a fresh session (which in production
|
|
17
|
+
* triggers a reattach on the extension's dispatcher).
|
|
18
|
+
*
|
|
19
|
+
* Unlike the unit test in `host-browser-event-routes.test.ts`, this
|
|
20
|
+
* file stands up the full `RuntimeHttpServer` so the WS upgrade,
|
|
21
|
+
* frame parse, dispatch switch, and resolver helpers all run through
|
|
22
|
+
* their production code paths. The capability-token transport is
|
|
23
|
+
* used so the test does not depend on a valid guardian-bound JWT.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
27
|
+
|
|
28
|
+
// ── Module mocks (must be declared before the real imports below) ───
|
|
29
|
+
|
|
30
|
+
mock.module("../util/logger.js", () => ({
|
|
31
|
+
getLogger: () =>
|
|
32
|
+
new Proxy({} as Record<string, unknown>, {
|
|
33
|
+
get: () => () => {},
|
|
34
|
+
}),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
mock.module("../config/loader.js", () => ({
|
|
38
|
+
getConfig: () => ({
|
|
39
|
+
ui: {},
|
|
40
|
+
model: "test",
|
|
41
|
+
provider: "test",
|
|
42
|
+
memory: { enabled: false },
|
|
43
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
44
|
+
secretDetection: { enabled: false },
|
|
45
|
+
contextWindow: { maxInputTokens: 200000 },
|
|
46
|
+
services: {
|
|
47
|
+
inference: {
|
|
48
|
+
mode: "your-own",
|
|
49
|
+
provider: "anthropic",
|
|
50
|
+
model: "claude-opus-4-6",
|
|
51
|
+
},
|
|
52
|
+
"image-generation": {
|
|
53
|
+
mode: "your-own",
|
|
54
|
+
provider: "gemini",
|
|
55
|
+
model: "gemini-3.1-flash-image-preview",
|
|
56
|
+
},
|
|
57
|
+
"web-search": { mode: "your-own", provider: "inference-provider-native" },
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
// ── Real imports (after mocks) ──────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
import {
|
|
65
|
+
__resetBrowserSessionEventsForTests,
|
|
66
|
+
BrowserSessionManager,
|
|
67
|
+
createExtensionBackend,
|
|
68
|
+
type ForwardedCdpEvent,
|
|
69
|
+
onCdpEvent,
|
|
70
|
+
} from "../browser-session/index.js";
|
|
71
|
+
import { getDb, initializeDb } from "../memory/db.js";
|
|
72
|
+
import { mintHostBrowserCapability } from "../runtime/capability-tokens.js";
|
|
73
|
+
import {
|
|
74
|
+
__resetChromeExtensionRegistryForTests,
|
|
75
|
+
getChromeExtensionRegistry,
|
|
76
|
+
} from "../runtime/chrome-extension-registry.js";
|
|
77
|
+
import { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
78
|
+
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
79
|
+
|
|
80
|
+
initializeDb();
|
|
81
|
+
|
|
82
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
async function waitFor(
|
|
85
|
+
predicate: () => boolean,
|
|
86
|
+
timeoutMs = 2000,
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
const deadline = Date.now() + timeoutMs;
|
|
89
|
+
while (!predicate()) {
|
|
90
|
+
if (Date.now() > deadline) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`waitFor: predicate did not become true within ${timeoutMs}ms`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function waitForRegistryEntry(
|
|
100
|
+
guardianId: string,
|
|
101
|
+
timeoutMs = 2000,
|
|
102
|
+
): Promise<void> {
|
|
103
|
+
await waitFor(
|
|
104
|
+
() => getChromeExtensionRegistry().get(guardianId) !== undefined,
|
|
105
|
+
timeoutMs,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Tests ───────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
describe("host_browser WS event + invalidation e2e", () => {
|
|
112
|
+
let server: RuntimeHttpServer;
|
|
113
|
+
let port: number;
|
|
114
|
+
let runtimeBaseUrl: string;
|
|
115
|
+
|
|
116
|
+
beforeEach(async () => {
|
|
117
|
+
const db = getDb();
|
|
118
|
+
db.run("DELETE FROM contact_channels");
|
|
119
|
+
db.run("DELETE FROM contacts");
|
|
120
|
+
pendingInteractions.clear();
|
|
121
|
+
__resetChromeExtensionRegistryForTests();
|
|
122
|
+
__resetBrowserSessionEventsForTests();
|
|
123
|
+
|
|
124
|
+
// Pick a non-colliding port in the same band as the other
|
|
125
|
+
// host-browser e2e tests but offset so parallel runs don't
|
|
126
|
+
// step on one another.
|
|
127
|
+
port = 19900 + Math.floor(Math.random() * 200);
|
|
128
|
+
runtimeBaseUrl = `http://127.0.0.1:${port}`;
|
|
129
|
+
server = new RuntimeHttpServer({ port });
|
|
130
|
+
await server.start();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
afterEach(async () => {
|
|
134
|
+
await server?.stop();
|
|
135
|
+
pendingInteractions.clear();
|
|
136
|
+
__resetChromeExtensionRegistryForTests();
|
|
137
|
+
__resetBrowserSessionEventsForTests();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("host_browser_event frame fans out to browser-session event bus subscribers", async () => {
|
|
141
|
+
const guardianId = `guardian-${crypto.randomUUID()}`;
|
|
142
|
+
const { token } = mintHostBrowserCapability(guardianId);
|
|
143
|
+
|
|
144
|
+
const { createMockChromeExtension } =
|
|
145
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
146
|
+
const mockExt = createMockChromeExtension({
|
|
147
|
+
runtimeBaseUrl,
|
|
148
|
+
token,
|
|
149
|
+
resultTransport: "ws",
|
|
150
|
+
});
|
|
151
|
+
await mockExt.start();
|
|
152
|
+
await mockExt.waitForConnection();
|
|
153
|
+
await waitForRegistryEntry(guardianId);
|
|
154
|
+
|
|
155
|
+
// Subscribe BEFORE sending the frame so we're guaranteed to see
|
|
156
|
+
// the fanout. The subscription is module-level so it survives
|
|
157
|
+
// across the WS round-trip naturally.
|
|
158
|
+
const observed: ForwardedCdpEvent[] = [];
|
|
159
|
+
const unsubscribe = onCdpEvent((event) => observed.push(event));
|
|
160
|
+
|
|
161
|
+
mockExt.sendHostBrowserEvent({
|
|
162
|
+
method: "Page.frameNavigated",
|
|
163
|
+
params: { frame: { id: "frame-1", url: "https://example.com" } },
|
|
164
|
+
cdpSessionId: "target-abc",
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// The WS dispatch hop is asynchronous — poll until the event
|
|
168
|
+
// lands or the test times out.
|
|
169
|
+
await waitFor(() => observed.length === 1);
|
|
170
|
+
|
|
171
|
+
expect(observed[0].method).toBe("Page.frameNavigated");
|
|
172
|
+
expect(observed[0].params).toEqual({
|
|
173
|
+
frame: { id: "frame-1", url: "https://example.com" },
|
|
174
|
+
});
|
|
175
|
+
expect(observed[0].cdpSessionId).toBe("target-abc");
|
|
176
|
+
|
|
177
|
+
unsubscribe();
|
|
178
|
+
await mockExt.stop();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("host_browser_event frames with no params are still routed", async () => {
|
|
182
|
+
const guardianId = `guardian-${crypto.randomUUID()}`;
|
|
183
|
+
const { token } = mintHostBrowserCapability(guardianId);
|
|
184
|
+
|
|
185
|
+
const { createMockChromeExtension } =
|
|
186
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
187
|
+
const mockExt = createMockChromeExtension({
|
|
188
|
+
runtimeBaseUrl,
|
|
189
|
+
token,
|
|
190
|
+
resultTransport: "ws",
|
|
191
|
+
});
|
|
192
|
+
await mockExt.start();
|
|
193
|
+
await mockExt.waitForConnection();
|
|
194
|
+
await waitForRegistryEntry(guardianId);
|
|
195
|
+
|
|
196
|
+
const observed: ForwardedCdpEvent[] = [];
|
|
197
|
+
const unsubscribe = onCdpEvent((event) => observed.push(event));
|
|
198
|
+
|
|
199
|
+
mockExt.sendHostBrowserEvent({ method: "Target.targetDestroyed" });
|
|
200
|
+
|
|
201
|
+
await waitFor(() => observed.length === 1);
|
|
202
|
+
expect(observed[0].method).toBe("Target.targetDestroyed");
|
|
203
|
+
expect(observed[0].params).toBeUndefined();
|
|
204
|
+
expect(observed[0].cdpSessionId).toBeUndefined();
|
|
205
|
+
|
|
206
|
+
unsubscribe();
|
|
207
|
+
await mockExt.stop();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("host_browser_session_invalidated frame evicts stale sessions and the next command forces reattach", async () => {
|
|
211
|
+
const guardianId = `guardian-${crypto.randomUUID()}`;
|
|
212
|
+
const { token } = mintHostBrowserCapability(guardianId);
|
|
213
|
+
|
|
214
|
+
const { createMockChromeExtension } =
|
|
215
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
216
|
+
const mockExt = createMockChromeExtension({
|
|
217
|
+
runtimeBaseUrl,
|
|
218
|
+
token,
|
|
219
|
+
resultTransport: "ws",
|
|
220
|
+
});
|
|
221
|
+
await mockExt.start();
|
|
222
|
+
await mockExt.waitForConnection();
|
|
223
|
+
await waitForRegistryEntry(guardianId);
|
|
224
|
+
|
|
225
|
+
// Stand up a BrowserSessionManager that mirrors what a tool
|
|
226
|
+
// invocation would build. The backend counts dispatch attempts
|
|
227
|
+
// so we can assert the first post-invalidation send never
|
|
228
|
+
// reached the backend while the second (after reattach) did.
|
|
229
|
+
const sent: Array<{ method: string }> = [];
|
|
230
|
+
const backend = createExtensionBackend({
|
|
231
|
+
isAvailable: () => true,
|
|
232
|
+
sendCdp: async (command) => {
|
|
233
|
+
sent.push({ method: command.method });
|
|
234
|
+
return { result: { ok: true } };
|
|
235
|
+
},
|
|
236
|
+
dispose: () => {},
|
|
237
|
+
});
|
|
238
|
+
const manager = new BrowserSessionManager({ backends: [backend] });
|
|
239
|
+
const session = manager.createSession();
|
|
240
|
+
session.targetId = "tab-77";
|
|
241
|
+
|
|
242
|
+
// Fire the invalidation envelope from the extension side.
|
|
243
|
+
mockExt.sendSessionInvalidated({
|
|
244
|
+
targetId: "tab-77",
|
|
245
|
+
reason: "target_closed",
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Wait until the WS dispatch hop lands — `isTargetInvalidated`
|
|
249
|
+
// peeks at the registry without consuming the entry, so we can
|
|
250
|
+
// poll safely.
|
|
251
|
+
const { isTargetInvalidated } =
|
|
252
|
+
await import("../browser-session/events.js");
|
|
253
|
+
await waitFor(() => isTargetInvalidated("tab-77"));
|
|
254
|
+
|
|
255
|
+
// The next send against the invalidated session MUST throw —
|
|
256
|
+
// the manager consumes the invalidation flag, evicts the
|
|
257
|
+
// session, and rejects the command so the caller can create a
|
|
258
|
+
// fresh session (which triggers a reattach on the extension
|
|
259
|
+
// side).
|
|
260
|
+
await expect(
|
|
261
|
+
manager.send(session.id, { method: "Page.navigate" }),
|
|
262
|
+
).rejects.toThrow(/invalidated/);
|
|
263
|
+
|
|
264
|
+
// Sanity: the backend never saw the doomed command.
|
|
265
|
+
expect(sent).toHaveLength(0);
|
|
266
|
+
|
|
267
|
+
// The evicted session is gone — sending again throws
|
|
268
|
+
// "Unknown browser session", which is the signal a tool uses
|
|
269
|
+
// to rebuild a fresh session.
|
|
270
|
+
await expect(
|
|
271
|
+
manager.send(session.id, { method: "Page.navigate" }),
|
|
272
|
+
).rejects.toThrow(/Unknown browser session/);
|
|
273
|
+
|
|
274
|
+
// Creating a fresh session proves the reattach path works:
|
|
275
|
+
// the caller bounces through `createSession` and a subsequent
|
|
276
|
+
// send dispatches normally through the backend.
|
|
277
|
+
const fresh = manager.createSession();
|
|
278
|
+
const result = await manager.send(fresh.id, {
|
|
279
|
+
method: "Page.navigate",
|
|
280
|
+
});
|
|
281
|
+
expect(result.result).toEqual({ ok: true });
|
|
282
|
+
expect(sent).toEqual([{ method: "Page.navigate" }]);
|
|
283
|
+
|
|
284
|
+
await mockExt.stop();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("malformed host_browser_event frames are dropped without tearing down the socket", async () => {
|
|
288
|
+
const guardianId = `guardian-${crypto.randomUUID()}`;
|
|
289
|
+
const { token } = mintHostBrowserCapability(guardianId);
|
|
290
|
+
|
|
291
|
+
const { createMockChromeExtension } =
|
|
292
|
+
await import("./fixtures/mock-chrome-extension.js");
|
|
293
|
+
const mockExt = createMockChromeExtension({
|
|
294
|
+
runtimeBaseUrl,
|
|
295
|
+
token,
|
|
296
|
+
resultTransport: "ws",
|
|
297
|
+
});
|
|
298
|
+
await mockExt.start();
|
|
299
|
+
await mockExt.waitForConnection();
|
|
300
|
+
await waitForRegistryEntry(guardianId);
|
|
301
|
+
|
|
302
|
+
const observed: ForwardedCdpEvent[] = [];
|
|
303
|
+
const unsubscribe = onCdpEvent((event) => observed.push(event));
|
|
304
|
+
|
|
305
|
+
// Send a frame with no method — the resolver must reject it
|
|
306
|
+
// and the WS dispatcher must swallow the rejection.
|
|
307
|
+
mockExt.sendHostBrowserEvent({ method: "" });
|
|
308
|
+
|
|
309
|
+
// Follow up with a valid frame and assert that ONLY the valid
|
|
310
|
+
// frame was published — proving the socket survived the bad
|
|
311
|
+
// frame and the dispatcher kept processing subsequent messages.
|
|
312
|
+
mockExt.sendHostBrowserEvent({ method: "Page.loadEventFired" });
|
|
313
|
+
|
|
314
|
+
await waitFor(() => observed.length === 1);
|
|
315
|
+
expect(observed[0].method).toBe("Page.loadEventFired");
|
|
316
|
+
|
|
317
|
+
unsubscribe();
|
|
318
|
+
await mockExt.stop();
|
|
319
|
+
});
|
|
320
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterEach, describe, expect, jest, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import { HostCuProxy } from "../daemon/host-cu-proxy.js";
|
|
4
4
|
|
|
@@ -776,6 +776,176 @@ describe("HostCuProxy", () => {
|
|
|
776
776
|
});
|
|
777
777
|
});
|
|
778
778
|
|
|
779
|
+
// -------------------------------------------------------------------------
|
|
780
|
+
// abort listener lifecycle
|
|
781
|
+
// -------------------------------------------------------------------------
|
|
782
|
+
|
|
783
|
+
describe("abort listener lifecycle", () => {
|
|
784
|
+
// Helper that wraps an AbortSignal to observe add/removeEventListener
|
|
785
|
+
// invocations without tripping over tsc's strict overload matching on
|
|
786
|
+
// AbortSignal itself.
|
|
787
|
+
type Spied = {
|
|
788
|
+
signal: AbortSignal;
|
|
789
|
+
addCalls: string[];
|
|
790
|
+
removeCalls: string[];
|
|
791
|
+
};
|
|
792
|
+
function spySignal(source: AbortSignal): Spied {
|
|
793
|
+
const addCalls: string[] = [];
|
|
794
|
+
const removeCalls: string[] = [];
|
|
795
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
796
|
+
const s = source as any;
|
|
797
|
+
const origAdd = source.addEventListener.bind(source);
|
|
798
|
+
const origRemove = source.removeEventListener.bind(source);
|
|
799
|
+
s.addEventListener = (
|
|
800
|
+
type: string,
|
|
801
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
802
|
+
...rest: any[]
|
|
803
|
+
) => {
|
|
804
|
+
addCalls.push(type);
|
|
805
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
806
|
+
return (origAdd as any)(type, ...rest);
|
|
807
|
+
};
|
|
808
|
+
s.removeEventListener = (
|
|
809
|
+
type: string,
|
|
810
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
811
|
+
...rest: any[]
|
|
812
|
+
) => {
|
|
813
|
+
removeCalls.push(type);
|
|
814
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
815
|
+
return (origRemove as any)(type, ...rest);
|
|
816
|
+
};
|
|
817
|
+
return { signal: source, addCalls, removeCalls };
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
test("removes abort listener from signal after resolve completes", async () => {
|
|
821
|
+
setup();
|
|
822
|
+
const controller = new AbortController();
|
|
823
|
+
const spy = spySignal(controller.signal);
|
|
824
|
+
|
|
825
|
+
const resultPromise = proxy.request(
|
|
826
|
+
"computer_use_click",
|
|
827
|
+
{ element_id: 1 },
|
|
828
|
+
"session-1",
|
|
829
|
+
1,
|
|
830
|
+
undefined,
|
|
831
|
+
spy.signal,
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
expect(spy.addCalls).toEqual(["abort"]);
|
|
835
|
+
expect(spy.removeCalls).toEqual([]);
|
|
836
|
+
|
|
837
|
+
const requestId = (sentMessages[0] as Record<string, unknown>)
|
|
838
|
+
.requestId as string;
|
|
839
|
+
proxy.resolve(requestId, { axTree: "Button [1]" });
|
|
840
|
+
await resultPromise;
|
|
841
|
+
|
|
842
|
+
// Listener is detached after normal completion.
|
|
843
|
+
expect(spy.removeCalls).toEqual(["abort"]);
|
|
844
|
+
|
|
845
|
+
// Subsequent aborts are harmless no-ops (no side effects on the proxy).
|
|
846
|
+
controller.abort();
|
|
847
|
+
// No additional emitted envelopes from the late abort.
|
|
848
|
+
expect(sentMessages).toHaveLength(1);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test("removes abort listener from signal on timer timeout", async () => {
|
|
852
|
+
setup();
|
|
853
|
+
|
|
854
|
+
jest.useFakeTimers();
|
|
855
|
+
try {
|
|
856
|
+
const controller = new AbortController();
|
|
857
|
+
const spy = spySignal(controller.signal);
|
|
858
|
+
|
|
859
|
+
const resultPromise = proxy.request(
|
|
860
|
+
"computer_use_click",
|
|
861
|
+
{ element_id: 1 },
|
|
862
|
+
"session-1",
|
|
863
|
+
1,
|
|
864
|
+
undefined,
|
|
865
|
+
spy.signal,
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
expect(spy.addCalls).toEqual(["abort"]);
|
|
869
|
+
expect(spy.removeCalls).toEqual([]);
|
|
870
|
+
|
|
871
|
+
const requestId = (sentMessages[0] as Record<string, unknown>)
|
|
872
|
+
.requestId as string;
|
|
873
|
+
expect(proxy.hasPendingRequest(requestId)).toBe(true);
|
|
874
|
+
|
|
875
|
+
// Advance past the 60s internal timeout.
|
|
876
|
+
jest.advanceTimersByTime(61 * 1000);
|
|
877
|
+
|
|
878
|
+
const result = await resultPromise;
|
|
879
|
+
expect(result.isError).toBe(true);
|
|
880
|
+
expect(result.content).toContain("Host CU proxy timed out");
|
|
881
|
+
expect(proxy.hasPendingRequest(requestId)).toBe(false);
|
|
882
|
+
|
|
883
|
+
// Listener is detached after the timer fires.
|
|
884
|
+
expect(spy.removeCalls).toEqual(["abort"]);
|
|
885
|
+
|
|
886
|
+
// Subsequent aborts should be harmless — no cancel emitted.
|
|
887
|
+
controller.abort();
|
|
888
|
+
expect(sentMessages).toHaveLength(1);
|
|
889
|
+
} finally {
|
|
890
|
+
jest.useRealTimers();
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
// -------------------------------------------------------------------------
|
|
896
|
+
// sender throws synchronously
|
|
897
|
+
// -------------------------------------------------------------------------
|
|
898
|
+
|
|
899
|
+
describe("sender throws synchronously", () => {
|
|
900
|
+
test("rejects the promise, clears pending state and timer, invokes onInternalResolve", async () => {
|
|
901
|
+
sentMessages = [];
|
|
902
|
+
resolvedRequestIds = [];
|
|
903
|
+
const throwingSend = () => {
|
|
904
|
+
throw new Error("transport down");
|
|
905
|
+
};
|
|
906
|
+
proxy = new HostCuProxy(throwingSend as never, (requestId: string) =>
|
|
907
|
+
resolvedRequestIds.push(requestId),
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
// request() synchronously calls sendToClient inside the Promise
|
|
911
|
+
// executor. A throw there surfaces as a rejected promise.
|
|
912
|
+
const resultPromise = proxy.request(
|
|
913
|
+
"computer_use_click",
|
|
914
|
+
{ element_id: 1 },
|
|
915
|
+
"session-1",
|
|
916
|
+
1,
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
await expect(resultPromise).rejects.toThrow("transport down");
|
|
920
|
+
|
|
921
|
+
// The internal resolve should fire exactly once as part of cleanup.
|
|
922
|
+
expect(resolvedRequestIds).toHaveLength(1);
|
|
923
|
+
|
|
924
|
+
// Issue a new request on a fresh (non-throwing) sender and verify
|
|
925
|
+
// the proxy is still functional — no stale timers or bookkeeping
|
|
926
|
+
// from the failed request.
|
|
927
|
+
sentMessages = [];
|
|
928
|
+
proxy.updateSender(
|
|
929
|
+
((msg: unknown) => sentMessages.push(msg)) as never,
|
|
930
|
+
true,
|
|
931
|
+
);
|
|
932
|
+
const okPromise = proxy.request(
|
|
933
|
+
"computer_use_click",
|
|
934
|
+
{ element_id: 2 },
|
|
935
|
+
"session-1",
|
|
936
|
+
2,
|
|
937
|
+
);
|
|
938
|
+
expect(sentMessages).toHaveLength(1);
|
|
939
|
+
const okRequestId = (sentMessages[0] as Record<string, unknown>)
|
|
940
|
+
.requestId as string;
|
|
941
|
+
expect(proxy.hasPendingRequest(okRequestId)).toBe(true);
|
|
942
|
+
proxy.resolve(okRequestId, { axTree: "Button [2]" });
|
|
943
|
+
const okResult = await okPromise;
|
|
944
|
+
expect(okResult.isError).toBe(false);
|
|
945
|
+
expect(okResult.content).toContain("Button [2]");
|
|
946
|
+
});
|
|
947
|
+
});
|
|
948
|
+
|
|
779
949
|
// -------------------------------------------------------------------------
|
|
780
950
|
// onInternalResolve callback
|
|
781
951
|
// -------------------------------------------------------------------------
|