@vellumai/assistant 0.6.5 → 0.6.6
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/AGENTS.md +9 -1
- package/ARCHITECTURE.md +15 -17
- package/Dockerfile +6 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
- package/docs/architecture/integrations.md +32 -39
- package/docs/architecture/memory.md +25 -30
- package/docs/architecture/security.md +7 -6
- package/docs/browser-use-architecture-phase2.md +63 -20
- package/docs/plugins.md +761 -0
- package/examples/plugins/echo/README.md +132 -0
- package/examples/plugins/echo/package.json +17 -0
- package/examples/plugins/echo/register.ts +187 -0
- package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
- package/openapi.yaml +212 -68
- package/package.json +1 -1
- package/src/__tests__/app-compiler.test.ts +57 -0
- package/src/__tests__/approval-cascade.test.ts +7 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
- package/src/__tests__/avatar-generator.test.ts +4 -2
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/catalog-cache.test.ts +69 -0
- package/src/__tests__/checker.test.ts +459 -171
- package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
- package/src/__tests__/compaction-events.test.ts +501 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
- package/src/__tests__/config-model-image-provider.test.ts +110 -0
- package/src/__tests__/config-schema.test.ts +22 -9
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
- package/src/__tests__/contacts-tools.test.ts +26 -0
- package/src/__tests__/context-overflow-policy.test.ts +7 -7
- package/src/__tests__/context-window-manager.test.ts +355 -4
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
- package/src/__tests__/conversation-agent-loop.test.ts +30 -141
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
- package/src/__tests__/conversation-pairing.test.ts +174 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
- package/src/__tests__/conversation-process-callsite.test.ts +3 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
- package/src/__tests__/conversation-queue.test.ts +29 -14
- package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
- package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
- package/src/__tests__/conversation-seed-composer.test.ts +2 -2
- package/src/__tests__/conversation-slash-queue.test.ts +7 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
- package/src/__tests__/conversation-speed-override.test.ts +6 -1
- package/src/__tests__/conversation-title-service.test.ts +116 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
- package/src/__tests__/conversation-usage.test.ts +1 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
- package/src/__tests__/credential-health-service.test.ts +78 -9
- package/src/__tests__/credential-security-invariants.test.ts +2 -2
- package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
- package/src/__tests__/empty-response-pipeline.test.ts +305 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
- package/src/__tests__/first-greeting.test.ts +247 -5
- package/src/__tests__/headless-browser-mode.test.ts +57 -0
- package/src/__tests__/history-repair-pipeline.test.ts +399 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
- package/src/__tests__/host-proxy-interface.test.ts +36 -2
- package/src/__tests__/image-credentials.test.ts +137 -0
- package/src/__tests__/image-service-dispatcher.test.ts +186 -0
- package/src/__tests__/injector-chain.test.ts +526 -0
- package/src/__tests__/intent-routing.test.ts +0 -26
- package/src/__tests__/llm-call-pipeline.test.ts +285 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/media-generate-image.test.ts +119 -13
- package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
- package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +5 -68
- package/src/__tests__/model-intents.test.ts +4 -2
- package/src/__tests__/notification-broadcaster.test.ts +3 -3
- package/src/__tests__/notification-decision-strategy.test.ts +0 -11
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
- package/src/__tests__/oauth-apps-routes.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +14 -12
- package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
- package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
- package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
- package/src/__tests__/oauth-providers-routes.test.ts +3 -2
- package/src/__tests__/oauth-store.test.ts +41 -76
- package/src/__tests__/onboarding-template-contract.test.ts +16 -64
- package/src/__tests__/openai-image-service.test.ts +368 -0
- package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
- package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
- package/src/__tests__/persistence-pipeline.test.ts +377 -0
- package/src/__tests__/pipeline-runner.test.ts +565 -0
- package/src/__tests__/platform.test.ts +5 -2
- package/src/__tests__/plugin-bootstrap.test.ts +483 -0
- package/src/__tests__/plugin-registry.test.ts +273 -0
- package/src/__tests__/plugin-route-contribution.test.ts +288 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
- package/src/__tests__/plugin-types.test.ts +320 -0
- package/src/__tests__/pricing.test.ts +44 -12
- package/src/__tests__/proxy-approval-callback.test.ts +69 -8
- package/src/__tests__/reaction-persistence.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +0 -2
- package/src/__tests__/schedule-routes.test.ts +131 -1
- package/src/__tests__/scheduler-recurrence.test.ts +14 -70
- package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
- package/src/__tests__/secret-detection-handler.test.ts +0 -10
- package/src/__tests__/shell-identity.test.ts +0 -134
- package/src/__tests__/suggestion-routes.test.ts +103 -4
- package/src/__tests__/task-memory-cleanup.test.ts +1 -0
- package/src/__tests__/task-scheduler.test.ts +3 -15
- package/src/__tests__/test-preload.ts +11 -0
- package/src/__tests__/title-generate-pipeline.test.ts +224 -0
- package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
- package/src/__tests__/tool-error-pipeline.test.ts +244 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
- package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
- package/src/__tests__/tool-executor.test.ts +141 -0
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -110
- package/src/__tests__/user-plugin-loader.test.ts +191 -0
- package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
- package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
- package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
- package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
- package/src/__tests__/workspace-policy.test.ts +21 -3
- package/src/agent/loop.ts +340 -102
- package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
- package/src/approvals/guardian-request-resolvers.ts +80 -0
- package/src/backup/__tests__/backup-worker.test.ts +2 -13
- package/src/backup/backup-worker.ts +3 -15
- package/src/bundler/app-compiler.ts +84 -1
- package/src/calls/call-state.ts +2 -2
- package/src/channels/__tests__/types.test.ts +3 -3
- package/src/channels/types.ts +6 -4
- package/src/cli/__tests__/notifications.test.ts +87 -211
- package/src/cli/commands/__tests__/backup.test.ts +1 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
- package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
- package/src/cli/commands/backup.ts +2 -2
- package/src/cli/commands/clients.ts +138 -0
- package/src/cli/commands/completions.ts +2 -9
- package/src/cli/commands/conversations.ts +55 -7
- package/src/cli/commands/image-generation.ts +33 -34
- package/src/cli/commands/notifications.ts +68 -103
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +2 -2
- package/src/cli/commands/oauth/providers.ts +176 -8
- package/src/cli/commands/oauth/status.ts +46 -36
- package/src/cli/commands/skills.ts +3 -4
- package/src/cli/program.ts +25 -29
- package/src/config/__tests__/backup-schema.test.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -3
- package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
- package/src/config/bundled-tool-registry.ts +0 -15
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +19 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/conversations.ts +16 -0
- package/src/config/schemas/llm.ts +2 -3
- package/src/config/schemas/security.ts +6 -6
- package/src/config/schemas/tts.ts +11 -0
- package/src/config/skill-state.ts +6 -2
- package/src/config/skills.ts +94 -5
- package/src/context/__tests__/compact-prompt.test.ts +27 -9
- package/src/context/prompts/compact.md +26 -12
- package/src/context/tool-result-truncation.ts +3 -63
- package/src/context/window-manager.ts +190 -16
- package/src/credential-health/credential-health-service.ts +19 -6
- package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
- package/src/daemon/config-watcher.ts +0 -2
- package/src/daemon/context-overflow-policy.ts +4 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
- package/src/daemon/conversation-agent-loop.ts +984 -683
- package/src/daemon/conversation-history.ts +10 -19
- package/src/daemon/conversation-lifecycle.ts +37 -19
- package/src/daemon/conversation-notifiers.ts +2 -110
- package/src/daemon/conversation-process.ts +14 -7
- package/src/daemon/conversation-runtime-assembly.ts +532 -411
- package/src/daemon/conversation-tool-setup.ts +41 -4
- package/src/daemon/conversation.ts +80 -35
- package/src/daemon/external-plugins-bootstrap.ts +478 -0
- package/src/daemon/first-greeting.ts +191 -14
- package/src/daemon/handlers/config-model.ts +11 -0
- package/src/daemon/handlers/skills.ts +5 -1
- package/src/daemon/lifecycle.ts +33 -68
- package/src/daemon/message-types/computer-use.ts +2 -34
- package/src/daemon/message-types/conversations.ts +49 -0
- package/src/daemon/message-types/messages.ts +12 -0
- package/src/daemon/server.ts +5 -3
- package/src/daemon/shutdown-handlers.ts +2 -12
- package/src/daemon/tool-side-effects.ts +14 -56
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
- package/src/heartbeat/heartbeat-service.ts +24 -1
- package/src/home/__tests__/feed-population-integration.test.ts +312 -0
- package/src/home/emit-feed-event.ts +7 -0
- package/src/home/feed-types.ts +41 -2
- package/src/home/rewrite-command-preview.ts +66 -0
- package/src/ipc/__tests__/socket-path.test.ts +11 -50
- package/src/ipc/cli-client.ts +1 -1
- package/src/ipc/cli-server.ts +3 -3
- package/src/ipc/gateway-client.ts +4 -1
- package/src/ipc/routes/browser-context.ts +2 -0
- package/src/ipc/routes/browser.ts +1 -0
- package/src/ipc/routes/get-contact.ts +16 -0
- package/src/ipc/routes/index.ts +14 -0
- package/src/ipc/routes/list-clients.ts +31 -0
- package/src/ipc/routes/merge-contacts.ts +17 -0
- package/src/ipc/routes/notification.ts +133 -0
- package/src/ipc/routes/rename-conversation.ts +59 -0
- package/src/ipc/routes/search-contacts.ts +19 -0
- package/src/ipc/routes/upsert-contact.ts +25 -0
- package/src/ipc/socket-path.ts +14 -38
- package/src/media/app-icon-generator.ts +23 -46
- package/src/media/avatar-router.ts +26 -41
- package/src/media/gemini-image-service.ts +8 -41
- package/src/media/image-credentials.ts +73 -0
- package/src/media/image-service.ts +85 -0
- package/src/media/openai-image-service.ts +131 -0
- package/src/media/types.ts +46 -0
- package/src/memory/conversation-crud.ts +48 -18
- package/src/memory/conversation-queries.ts +57 -4
- package/src/memory/conversation-title-service.ts +25 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-gemini.test.ts +41 -2
- package/src/memory/embedding-gemini.ts +6 -1
- package/src/memory/graph/bootstrap.test.ts +282 -0
- package/src/memory/graph/bootstrap.ts +8 -5
- package/src/memory/graph/extraction.ts +10 -2
- package/src/memory/graph/graph-search.test.ts +1 -0
- package/src/memory/graph/inspect.ts +2 -2
- package/src/memory/graph/retriever.ts +10 -3
- package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
- package/src/memory/migrations/149-oauth-tables.ts +1 -0
- package/src/memory/migrations/223-schedule-script-column.ts +11 -0
- package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
- package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/pkb/pkb-index.test.ts +1 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +65 -4
- package/src/memory/pkb/pkb-search.ts +40 -18
- package/src/memory/qdrant-client.test.ts +60 -0
- package/src/memory/qdrant-client.ts +25 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/oauth.ts +4 -1
- package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
- package/src/messaging/providers/slack/render-transcript.ts +58 -0
- package/src/notifications/conversation-pairing.ts +78 -19
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +1 -2
- package/src/oauth/AGENTS.md +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
- package/src/oauth/connect-orchestrator.ts +8 -34
- package/src/oauth/connect-types.ts +6 -10
- package/src/oauth/manual-token-connection.ts +23 -0
- package/src/oauth/oauth-store.ts +30 -14
- package/src/oauth/provider-serializer.ts +6 -1
- package/src/oauth/seed-providers.ts +56 -108
- package/src/outbound-proxy/http-forwarder.ts +9 -0
- package/src/permissions/approval-policy.test.ts +293 -18
- package/src/permissions/approval-policy.ts +110 -58
- package/src/permissions/arg-parser.test.ts +161 -0
- package/src/permissions/arg-parser.ts +141 -0
- package/src/permissions/bash-risk-classifier.test.ts +414 -2
- package/src/permissions/bash-risk-classifier.ts +303 -60
- package/src/permissions/checker.ts +157 -29
- package/src/permissions/command-registry.test.ts +239 -0
- package/src/permissions/command-registry.ts +234 -54
- package/src/permissions/defaults.ts +5 -4
- package/src/permissions/gateway-threshold-reader.ts +196 -0
- package/src/permissions/prompter.ts +4 -0
- package/src/permissions/risk-types.ts +61 -4
- package/src/permissions/schedule-risk-classifier.test.ts +129 -0
- package/src/permissions/schedule-risk-classifier.ts +85 -0
- package/src/permissions/shell-identity.ts +2 -42
- package/src/permissions/types.ts +2 -0
- package/src/permissions/workspace-policy.ts +8 -3
- package/src/plugins/defaults/circuit-breaker.ts +146 -0
- package/src/plugins/defaults/compaction.ts +145 -0
- package/src/plugins/defaults/empty-response.ts +126 -0
- package/src/plugins/defaults/history-repair.ts +85 -0
- package/src/plugins/defaults/index.ts +116 -0
- package/src/plugins/defaults/injectors.ts +491 -0
- package/src/plugins/defaults/llm-call.ts +82 -0
- package/src/plugins/defaults/memory-retrieval.ts +226 -0
- package/src/plugins/defaults/overflow-reduce.ts +181 -0
- package/src/plugins/defaults/persistence.ts +129 -0
- package/src/plugins/defaults/title-generate.ts +95 -0
- package/src/plugins/defaults/token-estimate.ts +104 -0
- package/src/plugins/defaults/tool-error.ts +126 -0
- package/src/plugins/defaults/tool-execute.ts +89 -0
- package/src/plugins/defaults/tool-result-truncate.ts +88 -0
- package/src/plugins/pipeline.ts +316 -0
- package/src/plugins/plugin-skill-contributions.ts +292 -0
- package/src/plugins/registry.ts +241 -0
- package/src/plugins/types.ts +1134 -0
- package/src/plugins/user-loader.ts +177 -0
- package/src/prompts/templates/BOOTSTRAP.md +27 -77
- package/src/providers/model-catalog.ts +52 -29
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
- package/src/providers/speech-to-text/xai-realtime.ts +39 -14
- package/src/runtime/AGENTS.md +25 -16
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
- package/src/runtime/__tests__/client-registry.test.ts +293 -0
- package/src/runtime/client-registry.ts +261 -0
- package/src/runtime/http-server.ts +77 -8
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +1 -22
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
- package/src/runtime/routes/approval-routes.ts +17 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
- package/src/runtime/routes/conversation-routes.ts +223 -116
- package/src/runtime/routes/inbound-message-handler.ts +88 -13
- package/src/runtime/routes/memory-item-routes.test.ts +1 -0
- package/src/runtime/routes/migration-routes.ts +0 -3
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
- package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
- package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
- package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
- package/src/runtime/routes/playground/deps.ts +56 -0
- package/src/runtime/routes/playground/force-compact.ts +73 -0
- package/src/runtime/routes/playground/guard.ts +37 -0
- package/src/runtime/routes/playground/index.ts +28 -0
- package/src/runtime/routes/playground/inject-failures.ts +159 -0
- package/src/runtime/routes/playground/reset-circuit.ts +115 -0
- package/src/runtime/routes/playground/seed-conversation.ts +139 -0
- package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
- package/src/runtime/routes/playground/state.ts +78 -0
- package/src/runtime/routes/schedule-routes.ts +89 -8
- package/src/runtime/skill-route-registry.ts +75 -15
- package/src/schedule/run-script.ts +68 -0
- package/src/schedule/schedule-store.ts +7 -1
- package/src/schedule/scheduler.ts +48 -8
- package/src/skills/catalog-cache.ts +12 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
- package/src/tools/browser/browser-execution.ts +88 -19
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
- package/src/tools/browser/cdp-client/factory.ts +15 -4
- package/src/tools/executor.ts +126 -74
- package/src/tools/network/script-proxy/session-manager.ts +37 -1
- package/src/tools/permission-checker.ts +98 -49
- package/src/tools/policy-context.ts +4 -0
- package/src/tools/registry.ts +140 -3
- package/src/tools/schedule/create.ts +23 -8
- package/src/tools/schedule/update.ts +3 -1
- package/src/tools/secret-detection-handler.ts +0 -51
- package/src/tools/system/avatar-generator.ts +6 -2
- package/src/tools/types.ts +28 -2
- package/src/util/platform.ts +7 -2
- package/src/util/pricing.ts +26 -3
- package/src/workspace/migrations/006-services-config.ts +2 -4
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
- package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
- package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/tsconfig.json +1 -1
- package/hook-templates/debug-prompt-logger/hook.json +0 -7
- package/hook-templates/debug-prompt-logger/run.sh +0 -66
- package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
- package/src/__tests__/context-overflow-approval.test.ts +0 -156
- package/src/__tests__/hooks-blocking.test.ts +0 -178
- package/src/__tests__/hooks-cli.test.ts +0 -182
- package/src/__tests__/hooks-config.test.ts +0 -108
- package/src/__tests__/hooks-discovery.test.ts +0 -211
- package/src/__tests__/hooks-integration.test.ts +0 -196
- package/src/__tests__/hooks-manager.test.ts +0 -226
- package/src/__tests__/hooks-runner.test.ts +0 -175
- package/src/__tests__/hooks-settings.test.ts +0 -160
- package/src/__tests__/hooks-templates.test.ts +0 -169
- package/src/__tests__/hooks-ts-runner.test.ts +0 -170
- package/src/__tests__/hooks-watch.test.ts +0 -112
- package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
- package/src/__tests__/oauth-scope-policy.test.ts +0 -180
- package/src/__tests__/send-notification-tool.test.ts +0 -83
- package/src/cli/commands/shotgun.ts +0 -266
- package/src/config/bundled-skills/conversations/SKILL.md +0 -20
- package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
- package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
- package/src/config/bundled-skills/notifications/SKILL.md +0 -40
- package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
- package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
- package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
- package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
- package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
- package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
- package/src/daemon/context-overflow-approval.ts +0 -52
- package/src/daemon/watch-handler.ts +0 -399
- package/src/hooks/cli.ts +0 -253
- package/src/hooks/config.ts +0 -100
- package/src/hooks/discovery.ts +0 -135
- package/src/hooks/manager.ts +0 -179
- package/src/hooks/runner.ts +0 -117
- package/src/hooks/templates.ts +0 -77
- package/src/hooks/types.ts +0 -75
- package/src/oauth/scope-policy.ts +0 -89
- package/src/runtime/gateway-internal-client.ts +0 -94
- package/src/runtime/routes/watch-routes.ts +0 -156
- package/src/signals/shotgun.ts +0 -203
- package/src/tools/watch/screen-watch.ts +0 -144
- package/src/tools/watch/watch-state.ts +0 -142
|
@@ -53,6 +53,17 @@ const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
|
53
53
|
*/
|
|
54
54
|
const DEFAULT_INACTIVITY_TIMEOUT_MS = 30_000;
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Default interval (ms) for emitting Deepgram `KeepAlive` control frames
|
|
58
|
+
* during silent stretches. Deepgram's server-side timeout closes the
|
|
59
|
+
* socket if no real audio content arrives for ~10s; raw silence PCM does
|
|
60
|
+
* not reset that timer, only an explicit `{"type":"KeepAlive"}` message
|
|
61
|
+
* does. Sending one every 5s keeps the socket alive through arbitrary
|
|
62
|
+
* pauses (think: 1:1 voice mode while the user is thinking) without any
|
|
63
|
+
* meaningful bandwidth cost.
|
|
64
|
+
*/
|
|
65
|
+
const DEFAULT_KEEPALIVE_INTERVAL_MS = 5_000;
|
|
66
|
+
|
|
56
67
|
/**
|
|
57
68
|
* Maximum WebSocket bufferedAmount (bytes) before sendAudio applies
|
|
58
69
|
* backpressure by dropping frames. This prevents unbounded memory growth
|
|
@@ -87,6 +98,13 @@ export interface DeepgramRealtimeOptions {
|
|
|
87
98
|
connectTimeoutMs?: number;
|
|
88
99
|
/** Inactivity timeout in milliseconds. Default: 30_000. */
|
|
89
100
|
inactivityTimeoutMs?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Interval (ms) between Deepgram `KeepAlive` control frames sent during
|
|
103
|
+
* silent stretches. Default: 5_000. Set to 0 to disable (not recommended
|
|
104
|
+
* outside tests — the server-side socket will close after ~10s of
|
|
105
|
+
* silence).
|
|
106
|
+
*/
|
|
107
|
+
keepaliveIntervalMs?: number;
|
|
90
108
|
/** Audio sample rate in Hz (default: 16000). Passed through from the client WebSocket connection. */
|
|
91
109
|
sampleRate?: number;
|
|
92
110
|
/**
|
|
@@ -222,6 +240,7 @@ export class DeepgramRealtimeTranscriber implements StreamingTranscriber {
|
|
|
222
240
|
private readonly baseUrl: string;
|
|
223
241
|
private readonly connectTimeoutMs: number;
|
|
224
242
|
private readonly inactivityTimeoutMs: number;
|
|
243
|
+
private readonly keepaliveIntervalMs: number;
|
|
225
244
|
private readonly sampleRate: number;
|
|
226
245
|
/**
|
|
227
246
|
* Whether speaker diarization is requested. Forwarded to the Deepgram
|
|
@@ -248,6 +267,13 @@ export class DeepgramRealtimeTranscriber implements StreamingTranscriber {
|
|
|
248
267
|
/** Close grace timer handle. */
|
|
249
268
|
private closeGraceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
250
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Periodic keepalive timer. Fires every {@link keepaliveIntervalMs} while
|
|
272
|
+
* the socket is open and emits a Deepgram `KeepAlive` control frame so
|
|
273
|
+
* silent stretches do not trip Deepgram's server-side inactivity close.
|
|
274
|
+
*/
|
|
275
|
+
private keepaliveTimer: ReturnType<typeof setInterval> | null = null;
|
|
276
|
+
|
|
251
277
|
constructor(apiKey: string, options: DeepgramRealtimeOptions = {}) {
|
|
252
278
|
this.apiKey = apiKey;
|
|
253
279
|
this.model = options.model ?? DEFAULT_MODEL;
|
|
@@ -260,6 +286,8 @@ export class DeepgramRealtimeTranscriber implements StreamingTranscriber {
|
|
|
260
286
|
options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
261
287
|
this.inactivityTimeoutMs =
|
|
262
288
|
options.inactivityTimeoutMs ?? DEFAULT_INACTIVITY_TIMEOUT_MS;
|
|
289
|
+
this.keepaliveIntervalMs =
|
|
290
|
+
options.keepaliveIntervalMs ?? DEFAULT_KEEPALIVE_INTERVAL_MS;
|
|
263
291
|
this.sampleRate = options.sampleRate ?? 16_000;
|
|
264
292
|
this.diarize = options.diarize ?? false;
|
|
265
293
|
}
|
|
@@ -329,6 +357,7 @@ export class DeepgramRealtimeTranscriber implements StreamingTranscriber {
|
|
|
329
357
|
// the active session lifetime.
|
|
330
358
|
this.attachSessionHandlers(ws);
|
|
331
359
|
this.resetInactivityTimer();
|
|
360
|
+
this.startKeepaliveTimer();
|
|
332
361
|
|
|
333
362
|
log.info("Deepgram realtime session opened");
|
|
334
363
|
}
|
|
@@ -644,6 +673,34 @@ export class DeepgramRealtimeTranscriber implements StreamingTranscriber {
|
|
|
644
673
|
clearTimeout(this.closeGraceTimer);
|
|
645
674
|
this.closeGraceTimer = null;
|
|
646
675
|
}
|
|
676
|
+
if (this.keepaliveTimer !== null) {
|
|
677
|
+
clearInterval(this.keepaliveTimer);
|
|
678
|
+
this.keepaliveTimer = null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Start the periodic keepalive timer. Sends a Deepgram `KeepAlive`
|
|
684
|
+
* control frame every {@link keepaliveIntervalMs}; this is the only
|
|
685
|
+
* thing that resets Deepgram's server-side inactivity timer when the
|
|
686
|
+
* stream is carrying silence (raw silence PCM frames do not count).
|
|
687
|
+
*
|
|
688
|
+
* Skipped when {@link keepaliveIntervalMs} is 0 (test override) or the
|
|
689
|
+
* adapter is already closed/stopping.
|
|
690
|
+
*/
|
|
691
|
+
private startKeepaliveTimer(): void {
|
|
692
|
+
if (this.closed || this.stopping) return;
|
|
693
|
+
if (this.keepaliveIntervalMs <= 0) return;
|
|
694
|
+
this.keepaliveTimer = setInterval(() => {
|
|
695
|
+
if (this.closed || this.stopping) return;
|
|
696
|
+
const ws = this.ws;
|
|
697
|
+
if (!ws || ws.readyState !== WS_OPEN) return;
|
|
698
|
+
try {
|
|
699
|
+
ws.send(JSON.stringify({ type: "KeepAlive" }));
|
|
700
|
+
} catch (err) {
|
|
701
|
+
log.warn({ err }, "Deepgram KeepAlive send failed");
|
|
702
|
+
}
|
|
703
|
+
}, this.keepaliveIntervalMs);
|
|
647
704
|
}
|
|
648
705
|
|
|
649
706
|
/**
|
|
@@ -497,14 +497,79 @@ describe("XAIRealtimeTranscriber", () => {
|
|
|
497
497
|
const transcriber = new XAIRealtimeTranscriber(TEST_API_KEY, {
|
|
498
498
|
connectTimeoutMs: 1_000,
|
|
499
499
|
});
|
|
500
|
-
const { onEvent } = createEventCollector();
|
|
500
|
+
const { events, onEvent } = createEventCollector();
|
|
501
501
|
|
|
502
|
-
// First attempt: transport-level error before open.
|
|
502
|
+
// First attempt: transport-level error before open. Real WebSocket
|
|
503
|
+
// implementations commonly chain `error` → `close` on the abandoned
|
|
504
|
+
// socket, so simulate both to catch the regression where the old
|
|
505
|
+
// socket's stray close event corrupts `this.closed` and silently
|
|
506
|
+
// breaks the retry.
|
|
507
|
+
const firstSocket = mockWs;
|
|
503
508
|
const firstAttempt = transcriber.start(onEvent);
|
|
504
|
-
|
|
509
|
+
firstSocket.simulateError(new Error("ECONNREFUSED"));
|
|
510
|
+
firstSocket.simulateClose(1006, "abnormal closure");
|
|
505
511
|
await expect(firstAttempt).rejects.toThrow(/xAI realtime connect error/);
|
|
506
512
|
|
|
507
|
-
//
|
|
513
|
+
// The stray close on the abandoned socket must NOT have emitted a
|
|
514
|
+
// `closed` event or marked the transcriber closed — otherwise the
|
|
515
|
+
// retry below would silently no-op on sendAudio.
|
|
516
|
+
expect(events.filter((e) => e.type === "closed")).toHaveLength(0);
|
|
517
|
+
|
|
518
|
+
// Second attempt — instance must be reusable AND fully functional.
|
|
519
|
+
mockWs = new MockWebSocket();
|
|
520
|
+
(globalThis as Record<string, unknown>).WebSocket = class {
|
|
521
|
+
constructor(
|
|
522
|
+
_url: string,
|
|
523
|
+
_options?: { headers?: Record<string, string> },
|
|
524
|
+
) {
|
|
525
|
+
return mockWs;
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const secondAttempt = transcriber.start(onEvent);
|
|
530
|
+
mockWs.simulateOpen();
|
|
531
|
+
mockWs.simulateMessage(CREATED_FRAME);
|
|
532
|
+
await expect(secondAttempt).resolves.toBeUndefined();
|
|
533
|
+
|
|
534
|
+
// Confirm the retry session is actually live — sendAudio must reach
|
|
535
|
+
// the new socket (proves `this.closed` wasn't sticky-corrupted).
|
|
536
|
+
transcriber.sendAudio(Buffer.from([0x01, 0x02, 0x03]), "audio/pcm");
|
|
537
|
+
expect(mockWs.sentData).toHaveLength(1);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// ─────────────────────────────────────────────────────────────────
|
|
541
|
+
// Regression: a close event on the abandoned socket fired AFTER a
|
|
542
|
+
// handshake-phase rejection must not corrupt `this.closed`. Real
|
|
543
|
+
// WebSocket impls emit `close` asynchronously after `ws.close()` is
|
|
544
|
+
// called, and commonly chain `error` → `close`; if the handshake
|
|
545
|
+
// listeners aren't detached before `forceClose()`, the stray event
|
|
546
|
+
// routes through `handleProviderClose` and flips `this.closed`,
|
|
547
|
+
// silently breaking subsequent retries.
|
|
548
|
+
// ─────────────────────────────────────────────────────────────────
|
|
549
|
+
|
|
550
|
+
test("stray close on abandoned socket after handshake rejection does not break retry", async () => {
|
|
551
|
+
const transcriber = new XAIRealtimeTranscriber(TEST_API_KEY, {
|
|
552
|
+
connectTimeoutMs: 1_000,
|
|
553
|
+
});
|
|
554
|
+
const { events, onEvent } = createEventCollector();
|
|
555
|
+
|
|
556
|
+
// First attempt: close rejects the handshake.
|
|
557
|
+
const firstSocket = mockWs;
|
|
558
|
+
const firstAttempt = transcriber.start(onEvent);
|
|
559
|
+
firstSocket.simulateClose(4001, "unauthorized");
|
|
560
|
+
await expect(firstAttempt).rejects.toThrow(
|
|
561
|
+
/xAI WebSocket closed before handshake/,
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
// Simulate a second close event arriving on the abandoned socket
|
|
565
|
+
// (as `forceClose()` → `ws.close()` would trigger in a real impl).
|
|
566
|
+
firstSocket.simulateClose(1006, "abnormal closure");
|
|
567
|
+
|
|
568
|
+
// The stray event must be a no-op: no `closed` event emitted, and
|
|
569
|
+
// no internal state corruption.
|
|
570
|
+
expect(events.filter((e) => e.type === "closed")).toHaveLength(0);
|
|
571
|
+
|
|
572
|
+
// Retry and confirm the session is fully functional.
|
|
508
573
|
mockWs = new MockWebSocket();
|
|
509
574
|
(globalThis as Record<string, unknown>).WebSocket = class {
|
|
510
575
|
constructor(
|
|
@@ -519,6 +584,9 @@ describe("XAIRealtimeTranscriber", () => {
|
|
|
519
584
|
mockWs.simulateOpen();
|
|
520
585
|
mockWs.simulateMessage(CREATED_FRAME);
|
|
521
586
|
await expect(secondAttempt).resolves.toBeUndefined();
|
|
587
|
+
|
|
588
|
+
transcriber.sendAudio(Buffer.from([0x01, 0x02, 0x03]), "audio/pcm");
|
|
589
|
+
expect(mockWs.sentData).toHaveLength(1);
|
|
522
590
|
});
|
|
523
591
|
|
|
524
592
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -255,13 +255,17 @@ export class XAIRealtimeTranscriber implements StreamingTranscriber {
|
|
|
255
255
|
await new Promise<void>((resolve, reject) => {
|
|
256
256
|
let settled = false;
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
258
|
+
// Listener references, captured so we can detach them from the
|
|
259
|
+
// abandoned socket on every reject/timeout path. Without this,
|
|
260
|
+
// `forceClose()` → `ws.close()` triggers an asynchronous `close`
|
|
261
|
+
// event (and real WebSocket impls commonly chain `error` → `close`)
|
|
262
|
+
// on the old socket. With the listeners still attached, that stray
|
|
263
|
+
// event routes through the `settled === true` branch into
|
|
264
|
+
// `handleProviderClose`, which calls `emitClosedAndCleanup()` and
|
|
265
|
+
// sets `this.closed = true`. A subsequent `start()` then resolves
|
|
266
|
+
// but `sendAudio` / `stop` / timers all no-op because `this.closed`
|
|
267
|
+
// is sticky — retry is silently dead. Detaching the handlers
|
|
268
|
+
// before `forceClose()` closes that window.
|
|
265
269
|
const settleResolve = () => {
|
|
266
270
|
if (settled) return;
|
|
267
271
|
settled = true;
|
|
@@ -273,6 +277,9 @@ export class XAIRealtimeTranscriber implements StreamingTranscriber {
|
|
|
273
277
|
if (settled) return;
|
|
274
278
|
settled = true;
|
|
275
279
|
clearTimeout(handshakeTimer);
|
|
280
|
+
// Detach listeners BEFORE `forceClose()` so stray close/error
|
|
281
|
+
// events on the abandoned socket can't flip `this.closed`.
|
|
282
|
+
detachHandshakeListeners();
|
|
276
283
|
// Null out this.ws (via forceClose) so the instance can be
|
|
277
284
|
// reused for a retry. Without this, a subsequent start() call
|
|
278
285
|
// would throw "start() called twice" even though no session
|
|
@@ -288,9 +295,8 @@ export class XAIRealtimeTranscriber implements StreamingTranscriber {
|
|
|
288
295
|
const onOpen = () => {
|
|
289
296
|
ws.removeEventListener("open", onOpen);
|
|
290
297
|
};
|
|
291
|
-
ws.addEventListener("open", onOpen);
|
|
292
298
|
|
|
293
|
-
|
|
299
|
+
const onMessage = (ev: { data: unknown }) => {
|
|
294
300
|
if (!settled) {
|
|
295
301
|
if (tryParseHandshakeFrame(ev.data)?.type === "transcript.created") {
|
|
296
302
|
settleResolve();
|
|
@@ -303,9 +309,9 @@ export class XAIRealtimeTranscriber implements StreamingTranscriber {
|
|
|
303
309
|
return;
|
|
304
310
|
}
|
|
305
311
|
this.handleProviderMessage(ev.data);
|
|
306
|
-
}
|
|
312
|
+
};
|
|
307
313
|
|
|
308
|
-
|
|
314
|
+
const onClose = (ev: { code: number; reason: string }) => {
|
|
309
315
|
if (!settled) {
|
|
310
316
|
// 401 / 403 on connect arrive as WebSocket close codes 4001 /
|
|
311
317
|
// 4003 in most runtimes (or 1008 policy-violation in others).
|
|
@@ -320,9 +326,9 @@ export class XAIRealtimeTranscriber implements StreamingTranscriber {
|
|
|
320
326
|
return;
|
|
321
327
|
}
|
|
322
328
|
this.handleProviderClose(ev.code, ev.reason);
|
|
323
|
-
}
|
|
329
|
+
};
|
|
324
330
|
|
|
325
|
-
|
|
331
|
+
const onError = (ev: unknown) => {
|
|
326
332
|
if (!settled) {
|
|
327
333
|
const msg =
|
|
328
334
|
ev instanceof Error
|
|
@@ -334,7 +340,26 @@ export class XAIRealtimeTranscriber implements StreamingTranscriber {
|
|
|
334
340
|
return;
|
|
335
341
|
}
|
|
336
342
|
this.handleProviderError(ev);
|
|
337
|
-
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const detachHandshakeListeners = () => {
|
|
346
|
+
ws.removeEventListener("message", onMessage);
|
|
347
|
+
ws.removeEventListener("close", onClose);
|
|
348
|
+
ws.removeEventListener("error", onError);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const handshakeTimer = setTimeout(() => {
|
|
352
|
+
if (settled) return;
|
|
353
|
+
settled = true;
|
|
354
|
+
detachHandshakeListeners();
|
|
355
|
+
this.forceClose();
|
|
356
|
+
reject(new Error("xAI realtime connect timeout"));
|
|
357
|
+
}, this.connectTimeoutMs);
|
|
358
|
+
|
|
359
|
+
ws.addEventListener("open", onOpen);
|
|
360
|
+
ws.addEventListener("message", onMessage);
|
|
361
|
+
ws.addEventListener("close", onClose);
|
|
362
|
+
ws.addEventListener("error", onError);
|
|
338
363
|
});
|
|
339
364
|
|
|
340
365
|
this.resetInactivityTimer();
|
package/src/runtime/AGENTS.md
CHANGED
|
@@ -65,7 +65,7 @@ Host browser allows the assistant to proxy CDP (Chrome DevTools Protocol) JSON-R
|
|
|
65
65
|
|
|
66
66
|
The `chrome-extension` interface in `INTERFACE_IDS` is a non-interactive transport that supports only the `host_browser` capability — it does NOT support `host_bash`, `host_file`, or `host_cu`. This is encoded in `supportsHostProxy(id, capability)`: passing a capability argument returns `true` for `chrome-extension` only when the capability is `host_browser`; the no-arg form returns `false` for `chrome-extension` (so legacy desktop-only call sites that assume full-desktop proxy availability continue to gate correctly).
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
For the chrome-extension interface, `host_browser_request` frames do NOT travel through `assistantEventHub`. Instead they are routed through the `ChromeExtensionRegistry` singleton (`runtime/chrome-extension-registry.ts`), which tracks active chrome-extension WebSocket connections keyed by `(guardianId, clientInstanceId)`. The registry is populated on WebSocket `open` and drained on `close` inside `http-server.ts`'s `/v1/browser-relay` handlers — see the `wsType === "browser-relay"` branches. For macOS, `host_browser_request` frames travel through `assistantEventHub` (SSE) by default; when the guardian also has an active extension connection, the registry-routed WebSocket sender takes precedence.
|
|
69
69
|
|
|
70
70
|
A single guardian may have multiple parallel extension installs connected at once (two Chrome profiles, two desktops sharing a sync identity). Each install generates a stable `clientInstanceId` on first run, persists it in `chrome.storage.local`, and sends it on every WebSocket handshake as a query param (`clientInstanceId=...`) or header (`x-client-instance-id`). The registry keys inner entries by that id so sibling installs don't evict each other on register/unregister. The default `send(guardianId, msg)` path routes to whichever instance has the most recent activity (`lastActiveAt`); `sendToInstance(guardianId, clientInstanceId, msg)` pins a specific install. Older extension builds that omit the id get a connection-scoped `legacy:<connectionId>` fallback key so they degrade gracefully to single-instance semantics.
|
|
71
71
|
|
|
@@ -79,11 +79,20 @@ See `docs/browser-use-architecture-phase2.md` for the full wire diagram and comp
|
|
|
79
79
|
|
|
80
80
|
On macOS-originated turns, the CDP factory (`tools/browser/cdp-client/factory.ts`) evaluates three browser backends in strict priority order. Each candidate is tried lazily; if the first command fails with a transport-level error, the factory falls over to the next candidate. CDP protocol errors (the browser understood the command but rejected it) do NOT trigger failover.
|
|
81
81
|
|
|
82
|
-
| Priority | Backend
|
|
83
|
-
| -------- |
|
|
84
|
-
| 1 | **Extension**
|
|
85
|
-
| 2 | **cdp-inspect**
|
|
86
|
-
| 3 | **Local**
|
|
82
|
+
| Priority | Backend | Condition | Transport |
|
|
83
|
+
| -------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
84
|
+
| 1 | **Extension / host proxy** | `hostBrowserProxy` present AND `isAvailable()` returns `true`. On macOS, the proxy is always provisioned (SSE sender when no extension is present, registry-routed when extension is connected) | WS via `ChromeExtensionRegistry` (extension present) or SSE via `assistantEventHub` (macOS host proxy) |
|
|
85
|
+
| 2 | **cdp-inspect** | (a) `hostBrowser.cdpInspect.enabled` is `true` in config, OR (b) `transportInterface === "macos"` AND `desktopAuto.enabled` is `true` (default) AND the cooldown from a prior failure is not active | Direct CDP WebSocket to `localhost:9222` |
|
|
86
|
+
| 3 | **Local** | Always present as the final fallback | In-process Playwright CDP via `browserManager` |
|
|
87
|
+
|
|
88
|
+
**Transport selection for the extension/host-proxy backend:**
|
|
89
|
+
|
|
90
|
+
The "extension" backend label is a misnomer inherited from the original Phase 2 design where only the Chrome Extension provided host-browser access. In the current architecture, two transports can power this backend:
|
|
91
|
+
|
|
92
|
+
- **Extension WebSocket**: When the `ChromeExtensionRegistry` has an active entry for the guardian, `resolveHostBrowserSender()` returns a registry-routed sender. `host_browser_request` frames travel over the `/v1/browser-relay` WebSocket to the Chrome extension, which executes CDP commands via `chrome.debugger`.
|
|
93
|
+
- **macOS SSE bridge**: When the macOS desktop client is connected but no extension is present, `resolveHostBrowserSender()` returns the `onEvent` SSE hub sender. `host_browser_request` frames travel through `assistantEventHub` to the desktop client's SSE connection. The desktop client executes CDP commands against the local Chrome and POSTs results back to `/v1/host-browser-result`.
|
|
94
|
+
|
|
95
|
+
Both transports use the same `HostBrowserProxy` → `ExtensionCdpClient` pipeline. The `browser_status` output distinguishes the transport via the `details.transport` field: `"extension-ws"` or `"macos-sse"`.
|
|
87
96
|
|
|
88
97
|
**Fallback criteria for cdp-inspect (desktop-auto):**
|
|
89
98
|
|
|
@@ -98,24 +107,24 @@ On macOS-originated turns, the CDP factory (`tools/browser/cdp-client/factory.ts
|
|
|
98
107
|
|
|
99
108
|
All CDP-backed browser tools (`browser_navigate`, `browser_snapshot`, `browser_screenshot`, `browser_click`, `browser_type`, `browser_hover`, `browser_scroll`, `browser_press_key`, `browser_select_option`, `browser_wait_for`, `browser_extract`, `browser_fill_credential`, `browser_attach`, `browser_detach`, `browser_close`, `browser_status`) accept an optional `browser_mode` input parameter that overrides the automatic backend selection for that invocation.
|
|
100
109
|
|
|
101
|
-
| Value | Behavior
|
|
102
|
-
| ---------------- |
|
|
103
|
-
| `auto` (default) | Existing priority-ordered fallback: extension -> cdp-inspect -> local
|
|
104
|
-
| `extension` | Pin to
|
|
105
|
-
| `cdp-inspect` | Pin to CDP inspect/debugger backend. Fails if endpoint unreachable.
|
|
106
|
-
| `local` | Pin to local Playwright-managed browser. No fallback.
|
|
107
|
-
| `cdp-debugger` | Alias for `cdp-inspect`.
|
|
108
|
-
| `playwright` | Alias for `local`.
|
|
110
|
+
| Value | Behavior |
|
|
111
|
+
| ---------------- | ---------------------------------------------------------------------------- |
|
|
112
|
+
| `auto` (default) | Existing priority-ordered fallback: extension -> cdp-inspect -> local |
|
|
113
|
+
| `extension` | Pin to extension/host-proxy backend. Fails immediately if proxy unavailable. |
|
|
114
|
+
| `cdp-inspect` | Pin to CDP inspect/debugger backend. Fails if endpoint unreachable. |
|
|
115
|
+
| `local` | Pin to local Playwright-managed browser. No fallback. |
|
|
116
|
+
| `cdp-debugger` | Alias for `cdp-inspect`. |
|
|
117
|
+
| `playwright` | Alias for `local`. |
|
|
109
118
|
|
|
110
119
|
**Strict pinned-mode semantics**: When `browser_mode` is set to a specific backend (not `auto`), the factory builds exactly one candidate and disables failover. If the pinned backend is unavailable, the tool returns a detailed error including:
|
|
111
120
|
|
|
112
121
|
- The requested mode
|
|
113
122
|
- An ordered list of attempted backends with exact failure reasons
|
|
114
|
-
- A remediation checklist tailored by backend
|
|
123
|
+
- A remediation checklist tailored by backend, failure code, and transport (e.g. for macOS SSE: "Verify the Vellum desktop app is running"; for extension: "Ensure Chrome is running with the extension paired")
|
|
115
124
|
|
|
116
125
|
**Auto-mode fallback logging**: In auto mode, fallback transitions are logged at `warn` level with structured metadata including the full candidate sequence and per-candidate failure reasons. This ensures fallback events are always observable in production logs.
|
|
117
126
|
|
|
118
|
-
**Test coverage:** Regression tests for `browser_mode` wiring live in `__tests__/headless-browser-mode.test.ts`. E2E regression tests for backend precedence live in `__tests__/host-browser-e2e-cloud.test.ts` (extension path) and `__tests__/conversation-routes-disk-view.test.ts` (macOS fallback path). Unit tests for pinned candidate construction and failover live in `tools/browser/cdp-client/__tests__/factory.test.ts`.
|
|
127
|
+
**Test coverage:** Regression tests for `browser_mode` wiring live in `__tests__/headless-browser-mode.test.ts`. E2E regression tests for backend precedence live in `__tests__/host-browser-e2e-cloud.test.ts` (extension path and macOS SSE bridge path) and `__tests__/conversation-routes-disk-view.test.ts` (macOS fallback path). Unit tests for pinned candidate construction and failover live in `tools/browser/cdp-client/__tests__/factory.test.ts`. Browser status tests covering macOS host-browser diagnostics live in `tools/browser/__tests__/browser-status.test.ts`.
|
|
119
128
|
|
|
120
129
|
### Channel approvals (Telegram, Slack)
|
|
121
130
|
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
verifyHostBrowserCapability,
|
|
27
27
|
} from "../capability-tokens.js";
|
|
28
28
|
import {
|
|
29
|
-
|
|
29
|
+
getAllowedExtensionOrigins,
|
|
30
30
|
handleBrowserExtensionPair,
|
|
31
31
|
NATIVE_HOST_MARKER_HEADER,
|
|
32
32
|
NATIVE_HOST_MARKER_VALUE,
|
|
@@ -55,10 +55,10 @@ const lanPeerServer = mockServer("192.168.1.10");
|
|
|
55
55
|
const publicPeerServer = mockServer("203.0.113.50");
|
|
56
56
|
|
|
57
57
|
const ALLOWED_ORIGIN = (() => {
|
|
58
|
-
const first = Array.from(
|
|
58
|
+
const first = Array.from(getAllowedExtensionOrigins())[0];
|
|
59
59
|
if (!first) {
|
|
60
60
|
throw new Error(
|
|
61
|
-
"
|
|
61
|
+
"getAllowedExtensionOrigins() must contain at least one extension origin for tests",
|
|
62
62
|
);
|
|
63
63
|
}
|
|
64
64
|
return first;
|