@vellumai/assistant 0.8.4 → 0.8.5
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/ARCHITECTURE.md +2 -2
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/openapi.yaml +809 -11
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +34 -37
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
- package/src/__tests__/audit-log-rotation.test.ts +70 -16
- package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +2 -3
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
- package/src/__tests__/computer-use-tools.test.ts +2 -4
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
- package/src/__tests__/conversation-agent-loop.test.ts +163 -122
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-fork-crud.test.ts +178 -15
- package/src/__tests__/conversation-lifecycle.test.ts +52 -11
- package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/gateway-flag-listener.test.ts +237 -0
- package/src/__tests__/gemini-provider.test.ts +78 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -5
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +77 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/model-intents.test.ts +2 -4
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -1
- package/src/__tests__/openai-provider.test.ts +46 -0
- package/src/__tests__/openai-responses-provider.test.ts +114 -12
- package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -2
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/registry.test.ts +2 -8
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
- package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +145 -131
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
- package/src/__tests__/tool-executor.test.ts +9 -62
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +6 -5
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +1 -1
- package/src/agent/loop.ts +8 -0
- package/src/api/README.md +5 -0
- package/src/api/index.ts +4 -0
- package/src/api/package.json +10 -0
- package/src/background-wake/background-wake-routes.test.ts +233 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +176 -1
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/call-site-defaults.ts +7 -6
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
- package/src/config/schemas/call-site-catalog.ts +21 -7
- package/src/config/schemas/llm.ts +12 -1
- package/src/config/schemas/memory-v2.ts +246 -0
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +52 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
- package/src/daemon/conversation-agent-loop.ts +126 -76
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +27 -22
- package/src/daemon/conversation-runtime-assembly.ts +10 -9
- package/src/daemon/conversation-tool-setup.ts +63 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +14 -29
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +11 -3
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +4 -4
- package/src/daemon/host-file-proxy.ts +4 -4
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +10 -10
- package/src/daemon/lifecycle.ts +23 -20
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-types/conversations.ts +6 -9
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +6 -14
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +52 -0
- package/src/daemon/tool-setup-types.ts +13 -0
- package/src/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/home-greeting.ts +0 -9
- package/src/home/suggested-prompts.ts +0 -9
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/registries.ts +8 -12
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +71 -70
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-init.ts +10 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/graph/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +55 -22
- package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
- package/src/memory/llm-request-log-source-local.ts +7 -0
- package/src/memory/llm-request-log-source.ts +9 -2
- package/src/memory/llm-request-log-store.ts +43 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-enqueue.ts +8 -1
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/memory-v2-activation-log-store.ts +15 -6
- package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/schema/conversations.ts +1 -1
- package/src/memory/schema/infrastructure.ts +21 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
- package/src/memory/v2/__tests__/injection.test.ts +127 -98
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +171 -3
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +124 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +224 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +106 -0
- package/src/memory/v2/harness/trace.ts +58 -0
- package/src/memory/v2/injection.ts +21 -15
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +171 -18
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +563 -0
- package/src/memory/v3/__tests__/filter.test.ts +512 -0
- package/src/memory/v3/__tests__/gate.test.ts +574 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
- package/src/memory/v3/__tests__/loop.test.ts +530 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +440 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +469 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
- package/src/memory/v3/__tests__/validate.test.ts +245 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +249 -0
- package/src/memory/v3/filter.ts +281 -0
- package/src/memory/v3/gate.ts +334 -0
- package/src/memory/v3/index-composition.ts +113 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +382 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +420 -0
- package/src/memory/v3/shadow-middleware.ts +305 -0
- package/src/memory/v3/traversal.ts +206 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +351 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +300 -0
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/emit-signal.ts +29 -49
- package/src/permissions/prompter.ts +3 -3
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +2 -2
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +18 -11
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +11 -16
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +39 -7
- package/src/prompts/system-prompt.ts +50 -185
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +230 -8
- package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +32 -66
- package/src/providers/call-site-routing.ts +14 -2
- package/src/providers/connection-model-compat.ts +38 -0
- package/src/providers/connection-resolution.ts +16 -2
- package/src/providers/gemini/client.ts +49 -6
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +43 -0
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +6 -3
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +78 -21
- package/src/providers/provider-send-message.ts +7 -1
- package/src/providers/retry.ts +34 -3
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/agent-wake.ts +1 -0
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +36 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +0 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +188 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +0 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +12 -4
- package/src/runtime/routes/conversation-management-routes.ts +77 -20
- package/src/runtime/routes/conversation-query-routes.ts +142 -36
- package/src/runtime/routes/conversation-routes.ts +252 -410
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +24 -8
- package/src/runtime/routes/host-browser-routes.ts +10 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +215 -5
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +5 -4
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/verification-outbound-actions.ts +73 -1
- package/src/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +2 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
- package/src/tools/browser/browser-execution.ts +16 -3
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
- package/src/tools/browser/cdp-client/factory.ts +100 -17
- package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
- package/src/tools/browser/cdp-client/types.ts +65 -0
- package/src/tools/browser/pinned-tabs.ts +96 -40
- package/src/tools/computer-use/definitions.ts +22 -78
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- package/src/tools/document/document-tool.ts +59 -0
- package/src/tools/execution-target.ts +21 -23
- package/src/tools/executor.ts +6 -1
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -8
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -9
- package/src/tools/network/web-fetch.ts +3 -9
- package/src/tools/network/web-search.ts +25 -32
- package/src/tools/registry.ts +7 -23
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -8
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +27 -98
- package/src/tools/ui-surface/definitions.ts +6 -22
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -2
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- package/src/daemon/query-complexity-router.ts +0 -75
- package/src/prompts/cache-boundary.ts +0 -8
|
@@ -2,13 +2,12 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
import { QuestionPrompter } from "../../permissions/question-prompter.js";
|
|
4
4
|
import { RiskLevel } from "../../permissions/types.js";
|
|
5
|
-
import type { ToolDefinition } from "../../providers/types.js";
|
|
6
5
|
import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
|
|
7
6
|
import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
8
7
|
|
|
9
8
|
// ── Input schema ────────────────────────────────────────────────────
|
|
10
9
|
// Runtime validation lives in Zod; the wire-level definition surfaced
|
|
11
|
-
// to the LLM is the hand-written JSON Schema in
|
|
10
|
+
// to the LLM is the hand-written JSON Schema in `input_schema` below.
|
|
12
11
|
// (The codebase does not currently use zod-to-json-schema for tool defs,
|
|
13
12
|
// so the two are kept in sync manually.)
|
|
14
13
|
|
|
@@ -109,53 +108,37 @@ const DESCRIPTION = [
|
|
|
109
108
|
"context shown beneath the label.",
|
|
110
109
|
].join("\n");
|
|
111
110
|
|
|
111
|
+
// Shared option-schema fragment used by both the batched `questions[]`
|
|
112
|
+
// shape and the legacy flat `options` field.
|
|
113
|
+
const OPTION_ITEMS_SCHEMA = {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
id: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description:
|
|
119
|
+
"Stable identifier for this option (returned verbatim in the response).",
|
|
120
|
+
},
|
|
121
|
+
label: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "Short human-readable label.",
|
|
124
|
+
},
|
|
125
|
+
description: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "Optional one-line context shown beneath the label.",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
required: ["id", "label"],
|
|
131
|
+
} as const;
|
|
132
|
+
|
|
112
133
|
// ── Tool ────────────────────────────────────────────────────────────
|
|
113
134
|
|
|
114
135
|
export class AskQuestionTool implements Tool {
|
|
115
136
|
name = "ask_question";
|
|
116
137
|
description = DESCRIPTION;
|
|
117
138
|
category = "interaction";
|
|
139
|
+
executionTarget = "sandbox" as const;
|
|
118
140
|
defaultRiskLevel = RiskLevel.Low;
|
|
119
|
-
|
|
120
|
-
// Override hook for tests: lets a test replace the prompter factory
|
|
121
|
-
// without monkey-patching the module. Default factory wires the real
|
|
122
|
-
// broadcastMessage so the question reaches every connected client.
|
|
123
|
-
private prompterFactory: () => Pick<QuestionPrompter, "prompt">;
|
|
124
|
-
|
|
125
|
-
constructor(
|
|
126
|
-
prompterFactory: () => Pick<QuestionPrompter, "prompt"> = () =>
|
|
127
|
-
new QuestionPrompter({ broadcastMessage }),
|
|
128
|
-
) {
|
|
129
|
-
this.prompterFactory = prompterFactory;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
getDefinition(): ToolDefinition {
|
|
133
|
-
// Shared option-schema fragment used by both the batched `questions[]`
|
|
134
|
-
// shape and the legacy flat `options` field.
|
|
135
|
-
const optionItemsSchema = {
|
|
136
|
-
type: "object",
|
|
137
|
-
properties: {
|
|
138
|
-
id: {
|
|
139
|
-
type: "string",
|
|
140
|
-
description:
|
|
141
|
-
"Stable identifier for this option (returned verbatim in the response).",
|
|
142
|
-
},
|
|
143
|
-
label: {
|
|
144
|
-
type: "string",
|
|
145
|
-
description: "Short human-readable label.",
|
|
146
|
-
},
|
|
147
|
-
description: {
|
|
148
|
-
type: "string",
|
|
149
|
-
description: "Optional one-line context shown beneath the label.",
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
required: ["id", "label"],
|
|
153
|
-
} as const;
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
name: this.name,
|
|
157
|
-
description: this.description,
|
|
158
|
-
input_schema: {
|
|
141
|
+
input_schema = {
|
|
159
142
|
type: "object",
|
|
160
143
|
properties: {
|
|
161
144
|
// ── Recommended shape ─────────────────────────────────────
|
|
@@ -182,7 +165,7 @@ export class AskQuestionTool implements Tool {
|
|
|
182
165
|
maxItems: 4,
|
|
183
166
|
description:
|
|
184
167
|
"2–4 structured options. The UI always appends a free-text fallback slot, so do not include a 'something else' option here.",
|
|
185
|
-
items:
|
|
168
|
+
items: OPTION_ITEMS_SCHEMA,
|
|
186
169
|
},
|
|
187
170
|
freeTextPlaceholder: {
|
|
188
171
|
type: "string",
|
|
@@ -212,7 +195,7 @@ export class AskQuestionTool implements Tool {
|
|
|
212
195
|
maxItems: 4,
|
|
213
196
|
description:
|
|
214
197
|
"Legacy: 2–4 structured options. Prefer `questions[].options`. The UI always appends a free-text fallback slot, so do not include a 'something else' option here.",
|
|
215
|
-
items:
|
|
198
|
+
items: OPTION_ITEMS_SCHEMA,
|
|
216
199
|
},
|
|
217
200
|
freeTextPlaceholder: {
|
|
218
201
|
type: "string",
|
|
@@ -222,8 +205,18 @@ export class AskQuestionTool implements Tool {
|
|
|
222
205
|
},
|
|
223
206
|
// No top-level `required` — caller must supply either `questions`
|
|
224
207
|
// or the legacy flat trio (`question` + `options`). Enforced in Zod.
|
|
225
|
-
},
|
|
226
208
|
};
|
|
209
|
+
|
|
210
|
+
// Override hook for tests: lets a test replace the prompter factory
|
|
211
|
+
// without monkey-patching the module. Default factory wires the real
|
|
212
|
+
// broadcastMessage so the question reaches every connected client.
|
|
213
|
+
private prompterFactory: () => Pick<QuestionPrompter, "prompt">;
|
|
214
|
+
|
|
215
|
+
constructor(
|
|
216
|
+
prompterFactory: () => Pick<QuestionPrompter, "prompt"> = () =>
|
|
217
|
+
new QuestionPrompter({ broadcastMessage }),
|
|
218
|
+
) {
|
|
219
|
+
this.prompterFactory = prompterFactory;
|
|
227
220
|
}
|
|
228
221
|
|
|
229
222
|
async execute(
|
|
@@ -77,4 +77,74 @@ describe("pinned-tabs", () => {
|
|
|
77
77
|
setPinnedTab("conv-a", "");
|
|
78
78
|
expect(getPinnedTab("conv-a")).toBeUndefined();
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
describe("cross-client isolation (#31361)", () => {
|
|
82
|
+
test("same tabId on different clients are stored independently", () => {
|
|
83
|
+
setPinnedTab("conv-a", "99", "clientA");
|
|
84
|
+
setPinnedTab("conv-a", "99", "clientB");
|
|
85
|
+
expect(getPinnedTab("conv-a", "clientA")).toBe("99");
|
|
86
|
+
expect(getPinnedTab("conv-a", "clientB")).toBe("99");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("clearPinnedTabByTabId with clientId only clears that client", () => {
|
|
90
|
+
setPinnedTab("conv-a", "99", "clientA");
|
|
91
|
+
setPinnedTab("conv-b", "99", "clientB");
|
|
92
|
+
|
|
93
|
+
const cleared = clearPinnedTabByTabId("99", "clientA");
|
|
94
|
+
|
|
95
|
+
expect(cleared).toBe(1);
|
|
96
|
+
expect(getPinnedTab("conv-a", "clientA")).toBeUndefined();
|
|
97
|
+
expect(getPinnedTab("conv-b", "clientB")).toBe("99"); // clientB's pin survives
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("getPinnedTab with matching clientId returns correct tabId", () => {
|
|
101
|
+
setPinnedTab("conv-x", "42", "clientA");
|
|
102
|
+
setPinnedTab("conv-x", "77", "clientB");
|
|
103
|
+
|
|
104
|
+
expect(getPinnedTab("conv-x", "clientA")).toBe("42");
|
|
105
|
+
expect(getPinnedTab("conv-x", "clientB")).toBe("77");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("clearPinnedTabByTabId without clientId clears all matching entries (backward compat)", () => {
|
|
109
|
+
setPinnedTab("conv-a", "99", "clientA");
|
|
110
|
+
setPinnedTab("conv-b", "99", "clientB");
|
|
111
|
+
|
|
112
|
+
const cleared = clearPinnedTabByTabId("99");
|
|
113
|
+
|
|
114
|
+
expect(cleared).toBe(2);
|
|
115
|
+
expect(getPinnedTab("conv-a", "clientA")).toBeUndefined();
|
|
116
|
+
expect(getPinnedTab("conv-b", "clientB")).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("clearPinnedTab with clientId only removes that client slot", () => {
|
|
120
|
+
setPinnedTab("conv-a", "42", "clientA");
|
|
121
|
+
setPinnedTab("conv-a", "77", "clientB");
|
|
122
|
+
|
|
123
|
+
clearPinnedTab("conv-a", "clientA");
|
|
124
|
+
|
|
125
|
+
expect(getPinnedTab("conv-a", "clientA")).toBeUndefined();
|
|
126
|
+
expect(getPinnedTab("conv-a", "clientB")).toBe("77");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("clearPinnedTab without clientId removes all slots", () => {
|
|
130
|
+
setPinnedTab("conv-a", "42", "clientA");
|
|
131
|
+
setPinnedTab("conv-a", "77", "clientB");
|
|
132
|
+
|
|
133
|
+
clearPinnedTab("conv-a");
|
|
134
|
+
|
|
135
|
+
expect(getPinnedTab("conv-a", "clientA")).toBeUndefined();
|
|
136
|
+
expect(getPinnedTab("conv-a", "clientB")).toBeUndefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("getPinnedTab with unknown clientId falls back to __default__ slot", () => {
|
|
140
|
+
setPinnedTab("conv-a", "42"); // stored in __default__ slot
|
|
141
|
+
expect(getPinnedTab("conv-a", "clientA")).toBe("42");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("getPinnedTab without clientId returns first entry", () => {
|
|
145
|
+
setPinnedTab("conv-a", "42", "clientA");
|
|
146
|
+
const result = getPinnedTab("conv-a");
|
|
147
|
+
expect(result).toBe("42");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
80
150
|
});
|
|
@@ -481,6 +481,15 @@ function wrapWithKindMemo(
|
|
|
481
481
|
setCdpSessionId(cdpSessionId: string): void {
|
|
482
482
|
inner.setCdpSessionId?.(cdpSessionId);
|
|
483
483
|
},
|
|
484
|
+
listTabs() {
|
|
485
|
+
return inner.listTabs();
|
|
486
|
+
},
|
|
487
|
+
selectTab(tabId: number) {
|
|
488
|
+
return inner.selectTab(tabId);
|
|
489
|
+
},
|
|
490
|
+
closeTab(tabId: number) {
|
|
491
|
+
return inner.closeTab(tabId);
|
|
492
|
+
},
|
|
484
493
|
};
|
|
485
494
|
}
|
|
486
495
|
|
|
@@ -644,7 +653,7 @@ export async function executeBrowserNavigate(
|
|
|
644
653
|
const newTab = input.new_tab === true;
|
|
645
654
|
if (newTab && cdp.kind === "extension") {
|
|
646
655
|
try {
|
|
647
|
-
const result = await cdp.send<{ tabId?: number | string }>(
|
|
656
|
+
const result = await cdp.send<{ tabId?: number | string; clientId?: string }>(
|
|
648
657
|
"Vellum.createTab",
|
|
649
658
|
{},
|
|
650
659
|
context.signal,
|
|
@@ -655,6 +664,10 @@ export async function executeBrowserNavigate(
|
|
|
655
664
|
: typeof result?.tabId === "string"
|
|
656
665
|
? result.tabId
|
|
657
666
|
: undefined;
|
|
667
|
+
const clientId =
|
|
668
|
+
typeof result?.clientId === "string" && result.clientId.length > 0
|
|
669
|
+
? result.clientId
|
|
670
|
+
: undefined;
|
|
658
671
|
if (!tabId) {
|
|
659
672
|
// Malformed createTab response (no tabId). We're nominally falling
|
|
660
673
|
// back to active-tab routing — but the live `cdp` instance was
|
|
@@ -676,9 +689,9 @@ export async function executeBrowserNavigate(
|
|
|
676
689
|
);
|
|
677
690
|
} else {
|
|
678
691
|
cdp.setCdpSessionId?.(tabId);
|
|
679
|
-
setPinnedTab(context.conversationId, tabId);
|
|
692
|
+
setPinnedTab(context.conversationId, tabId, clientId);
|
|
680
693
|
log.debug(
|
|
681
|
-
{ conversationId: context.conversationId, tabId },
|
|
694
|
+
{ conversationId: context.conversationId, tabId, clientId },
|
|
682
695
|
"Opened new tab via --new-tab; pinned subsequent ops to it",
|
|
683
696
|
);
|
|
684
697
|
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that listTabs, selectTab, and closeTab are properly forwarded through
|
|
3
|
+
* the real buildChainedClient wrapper.
|
|
4
|
+
*
|
|
5
|
+
* Per the test-flat-mocks-trap rule, these tests use the REAL buildChainedClient
|
|
6
|
+
* function with a mock candidate — NOT a flat fake that has the methods defined
|
|
7
|
+
* directly on the scoped client.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import type { BrowserBackend, CdpCommand, CdpResult } from "../../../../browser-session/types.js";
|
|
13
|
+
import { CdpError } from "../errors.js";
|
|
14
|
+
import type { BackendCandidate, CdpClient, TabInfo } from "../types.js";
|
|
15
|
+
|
|
16
|
+
// Import buildChainedClient directly. Since this test file does not mock
|
|
17
|
+
// any of factory.ts's imports, we import directly.
|
|
18
|
+
const { buildChainedClient } = await import("../factory.js");
|
|
19
|
+
|
|
20
|
+
// ── Helpers ───────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a BrowserBackend that delegates CDP sends through a CdpClient.
|
|
24
|
+
* This is the real wiring that factory.ts uses — the backend wraps the
|
|
25
|
+
* client's send method via dispatchThroughClient.
|
|
26
|
+
*/
|
|
27
|
+
function makeBackendFromClient(client: CdpClient, kind: "extension" | "local" | "cdp-inspect"): BrowserBackend {
|
|
28
|
+
return {
|
|
29
|
+
kind,
|
|
30
|
+
isAvailable: () => true,
|
|
31
|
+
send: async (command: CdpCommand, signal?: AbortSignal): Promise<CdpResult> => {
|
|
32
|
+
try {
|
|
33
|
+
const result = await client.send(command.method, command.params, signal);
|
|
34
|
+
return { result };
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err instanceof CdpError) {
|
|
37
|
+
return {
|
|
38
|
+
error: { code: -1, message: err.message, data: err },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
dispose: () => client.dispose(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface FakeClientWithTabMethods extends CdpClient {
|
|
49
|
+
kind: "extension";
|
|
50
|
+
conversationId: string;
|
|
51
|
+
listTabsMock: ReturnType<typeof mock>;
|
|
52
|
+
selectTabMock: ReturnType<typeof mock>;
|
|
53
|
+
closeTabMock: ReturnType<typeof mock>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function makeFakeExtensionClientWithTabMethods(
|
|
57
|
+
conversationId: string,
|
|
58
|
+
): FakeClientWithTabMethods {
|
|
59
|
+
const listTabsMock = mock(async (): Promise<TabInfo[]> => [
|
|
60
|
+
{
|
|
61
|
+
tabId: 1,
|
|
62
|
+
windowId: 10,
|
|
63
|
+
url: "https://example.com",
|
|
64
|
+
title: "Example",
|
|
65
|
+
active: true,
|
|
66
|
+
pinned: false,
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
const selectTabMock = mock(async (_tabId: number) => ({
|
|
70
|
+
tabId: 42,
|
|
71
|
+
windowId: 10,
|
|
72
|
+
url: "https://example.com",
|
|
73
|
+
title: "Example",
|
|
74
|
+
clientId: "clientA",
|
|
75
|
+
}));
|
|
76
|
+
const closeTabMock = mock(async (_tabId: number) => ({
|
|
77
|
+
closed: true as const,
|
|
78
|
+
tabId: 99,
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
kind: "extension",
|
|
83
|
+
conversationId,
|
|
84
|
+
listTabsMock,
|
|
85
|
+
selectTabMock,
|
|
86
|
+
closeTabMock,
|
|
87
|
+
send: mock(async () => ({ ok: true })) as unknown as CdpClient["send"],
|
|
88
|
+
dispose: mock(() => {}),
|
|
89
|
+
listTabs: listTabsMock,
|
|
90
|
+
selectTab: selectTabMock,
|
|
91
|
+
closeTab: closeTabMock,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Build a BackendCandidate that wraps a fake extension client with tab methods.
|
|
97
|
+
*/
|
|
98
|
+
function makeCandidateFromClient(
|
|
99
|
+
conversationId: string,
|
|
100
|
+
fakeClient: CdpClient,
|
|
101
|
+
kind: "extension" | "local" | "cdp-inspect",
|
|
102
|
+
): BackendCandidate {
|
|
103
|
+
return {
|
|
104
|
+
kind,
|
|
105
|
+
reason: `test ${kind} client`,
|
|
106
|
+
create() {
|
|
107
|
+
return {
|
|
108
|
+
client: fakeClient,
|
|
109
|
+
backend: makeBackendFromClient(fakeClient, kind),
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Helpers for fresh-client (pre-sticky) tests ───────────────────────
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* A fake extension client whose `send` method returns Vellum.* pseudo-responses
|
|
119
|
+
* so that fresh-client tab calls can succeed without a prior `send("Runtime.*")`.
|
|
120
|
+
* This simulates how the real ExtensionCdpClient handles Vellum.listTabs etc.
|
|
121
|
+
* through the dispatcher before chrome.debugger.sendCommand.
|
|
122
|
+
*/
|
|
123
|
+
function makeFakeExtensionClientForFreshPath(conversationId: string) {
|
|
124
|
+
return {
|
|
125
|
+
kind: "extension" as const,
|
|
126
|
+
conversationId,
|
|
127
|
+
send: mock(
|
|
128
|
+
async (method: string, params?: Record<string, unknown>): Promise<unknown> => {
|
|
129
|
+
if (method === "Vellum.listTabs") {
|
|
130
|
+
return {
|
|
131
|
+
tabs: [
|
|
132
|
+
{
|
|
133
|
+
tabId: 1,
|
|
134
|
+
windowId: 10,
|
|
135
|
+
url: "https://example.com",
|
|
136
|
+
title: "Example",
|
|
137
|
+
active: true,
|
|
138
|
+
pinned: false,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (method === "Vellum.selectTab") {
|
|
144
|
+
return {
|
|
145
|
+
tabId: params?.tabId,
|
|
146
|
+
windowId: 10,
|
|
147
|
+
url: "https://example.com",
|
|
148
|
+
title: "Example",
|
|
149
|
+
clientId: "clientA",
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (method === "Vellum.closeTab") {
|
|
153
|
+
return { closed: true, tabId: params?.tabId };
|
|
154
|
+
}
|
|
155
|
+
return {};
|
|
156
|
+
},
|
|
157
|
+
) as unknown as CdpClient["send"],
|
|
158
|
+
dispose: mock(() => {}),
|
|
159
|
+
// These must be present so active?.client.listTabs etc. check passes
|
|
160
|
+
listTabs: mock(async (): Promise<TabInfo[]> => []),
|
|
161
|
+
selectTab: mock(async (_tabId: number) => ({
|
|
162
|
+
tabId: _tabId,
|
|
163
|
+
windowId: 10,
|
|
164
|
+
url: "https://example.com",
|
|
165
|
+
title: "Example",
|
|
166
|
+
clientId: "clientA",
|
|
167
|
+
})),
|
|
168
|
+
closeTab: mock(async (_tabId: number) => ({
|
|
169
|
+
closed: true as const,
|
|
170
|
+
tabId: _tabId,
|
|
171
|
+
})),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Tests ─────────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
describe("buildChainedClient — tab management methods", () => {
|
|
178
|
+
test("listTabs is forwarded to the underlying client after backend becomes sticky", async () => {
|
|
179
|
+
const conversationId = "conv-tabs-list";
|
|
180
|
+
const fakeClient = makeFakeExtensionClientWithTabMethods(conversationId);
|
|
181
|
+
const candidate = makeCandidateFromClient(conversationId, fakeClient, "extension");
|
|
182
|
+
|
|
183
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
184
|
+
|
|
185
|
+
// Establish sticky by sending a command first
|
|
186
|
+
await scoped.send("Runtime.evaluate", {});
|
|
187
|
+
|
|
188
|
+
const tabs = await scoped.listTabs();
|
|
189
|
+
expect(tabs).toHaveLength(1);
|
|
190
|
+
expect(tabs[0].tabId).toBe(1);
|
|
191
|
+
expect(tabs[0].active).toBe(true);
|
|
192
|
+
expect(fakeClient.listTabsMock).toHaveBeenCalledTimes(1);
|
|
193
|
+
|
|
194
|
+
scoped.dispose();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("selectTab is forwarded to the underlying client", async () => {
|
|
198
|
+
const conversationId = "conv-tabs-select";
|
|
199
|
+
const fakeClient = makeFakeExtensionClientWithTabMethods(conversationId);
|
|
200
|
+
const candidate = makeCandidateFromClient(conversationId, fakeClient, "extension");
|
|
201
|
+
|
|
202
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
203
|
+
|
|
204
|
+
// Establish sticky
|
|
205
|
+
await scoped.send("Runtime.evaluate", {});
|
|
206
|
+
|
|
207
|
+
const result = await scoped.selectTab(42);
|
|
208
|
+
expect(result.tabId).toBe(42);
|
|
209
|
+
expect(result.clientId).toBe("clientA");
|
|
210
|
+
expect(fakeClient.selectTabMock).toHaveBeenCalledWith(42);
|
|
211
|
+
|
|
212
|
+
scoped.dispose();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("closeTab is forwarded to the underlying client", async () => {
|
|
216
|
+
const conversationId = "conv-tabs-close";
|
|
217
|
+
const fakeClient = makeFakeExtensionClientWithTabMethods(conversationId);
|
|
218
|
+
const candidate = makeCandidateFromClient(conversationId, fakeClient, "extension");
|
|
219
|
+
|
|
220
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
221
|
+
|
|
222
|
+
// Establish sticky
|
|
223
|
+
await scoped.send("Runtime.evaluate", {});
|
|
224
|
+
|
|
225
|
+
const result = await scoped.closeTab(99);
|
|
226
|
+
expect(result.closed).toBe(true);
|
|
227
|
+
expect(result.tabId).toBe(99);
|
|
228
|
+
expect(fakeClient.closeTabMock).toHaveBeenCalledWith(99);
|
|
229
|
+
|
|
230
|
+
scoped.dispose();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("listTabs throws transport_error when backend does not support it", async () => {
|
|
234
|
+
const conversationId = "conv-tabs-no-list";
|
|
235
|
+
// A local client without listTabs/selectTab/closeTab
|
|
236
|
+
const noTabsClient: CdpClient & {
|
|
237
|
+
kind: "local";
|
|
238
|
+
conversationId: string;
|
|
239
|
+
} = {
|
|
240
|
+
kind: "local",
|
|
241
|
+
conversationId,
|
|
242
|
+
send: mock(async () => ({})) as unknown as CdpClient["send"],
|
|
243
|
+
dispose: mock(() => {}),
|
|
244
|
+
};
|
|
245
|
+
const candidate = makeCandidateFromClient(
|
|
246
|
+
conversationId,
|
|
247
|
+
noTabsClient,
|
|
248
|
+
"local",
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
252
|
+
|
|
253
|
+
// Establish sticky
|
|
254
|
+
await scoped.send("Runtime.evaluate", {});
|
|
255
|
+
|
|
256
|
+
let caught: unknown;
|
|
257
|
+
try {
|
|
258
|
+
await scoped.listTabs();
|
|
259
|
+
} catch (err) {
|
|
260
|
+
caught = err;
|
|
261
|
+
}
|
|
262
|
+
expect(caught).toBeInstanceOf(CdpError);
|
|
263
|
+
expect((caught as CdpError).code).toBe("transport_error");
|
|
264
|
+
|
|
265
|
+
scoped.dispose();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("selectTab throws transport_error when backend does not support it", async () => {
|
|
269
|
+
const conversationId = "conv-tabs-no-select";
|
|
270
|
+
const noTabsClient: CdpClient & {
|
|
271
|
+
kind: "local";
|
|
272
|
+
conversationId: string;
|
|
273
|
+
} = {
|
|
274
|
+
kind: "local",
|
|
275
|
+
conversationId,
|
|
276
|
+
send: mock(async () => ({})) as unknown as CdpClient["send"],
|
|
277
|
+
dispose: mock(() => {}),
|
|
278
|
+
};
|
|
279
|
+
const candidate = makeCandidateFromClient(
|
|
280
|
+
conversationId,
|
|
281
|
+
noTabsClient,
|
|
282
|
+
"local",
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
286
|
+
|
|
287
|
+
// Establish sticky
|
|
288
|
+
await scoped.send("Runtime.evaluate", {});
|
|
289
|
+
|
|
290
|
+
let caught: unknown;
|
|
291
|
+
try {
|
|
292
|
+
await scoped.selectTab(42);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
caught = err;
|
|
295
|
+
}
|
|
296
|
+
expect(caught).toBeInstanceOf(CdpError);
|
|
297
|
+
expect((caught as CdpError).code).toBe("transport_error");
|
|
298
|
+
|
|
299
|
+
scoped.dispose();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("closeTab throws transport_error when backend does not support it", async () => {
|
|
303
|
+
const conversationId = "conv-tabs-no-close";
|
|
304
|
+
const noTabsClient: CdpClient & {
|
|
305
|
+
kind: "local";
|
|
306
|
+
conversationId: string;
|
|
307
|
+
} = {
|
|
308
|
+
kind: "local",
|
|
309
|
+
conversationId,
|
|
310
|
+
send: mock(async () => ({})) as unknown as CdpClient["send"],
|
|
311
|
+
dispose: mock(() => {}),
|
|
312
|
+
};
|
|
313
|
+
const candidate = makeCandidateFromClient(
|
|
314
|
+
conversationId,
|
|
315
|
+
noTabsClient,
|
|
316
|
+
"local",
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
320
|
+
|
|
321
|
+
// Establish sticky
|
|
322
|
+
await scoped.send("Runtime.evaluate", {});
|
|
323
|
+
|
|
324
|
+
let caught: unknown;
|
|
325
|
+
try {
|
|
326
|
+
await scoped.closeTab(77);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
caught = err;
|
|
329
|
+
}
|
|
330
|
+
expect(caught).toBeInstanceOf(CdpError);
|
|
331
|
+
expect((caught as CdpError).code).toBe("transport_error");
|
|
332
|
+
|
|
333
|
+
scoped.dispose();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("buildChainedClient — fresh-client tab calls (no prior send)", () => {
|
|
338
|
+
test("listTabs on a fresh client triggers failover walk and returns tabs", async () => {
|
|
339
|
+
const conversationId = "conv-fresh-list";
|
|
340
|
+
const fakeClient = makeFakeExtensionClientForFreshPath(conversationId);
|
|
341
|
+
const candidate = makeCandidateFromClient(conversationId, fakeClient, "extension");
|
|
342
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
343
|
+
|
|
344
|
+
// Call listTabs WITHOUT establishing sticky — this is the production scenario
|
|
345
|
+
const tabs = await scoped.listTabs();
|
|
346
|
+
expect(tabs).toHaveLength(1);
|
|
347
|
+
expect(tabs[0].tabId).toBe(1);
|
|
348
|
+
expect(tabs[0].active).toBe(true);
|
|
349
|
+
|
|
350
|
+
scoped.dispose();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("selectTab on a fresh client triggers failover walk and returns tab info", async () => {
|
|
354
|
+
const conversationId = "conv-fresh-select";
|
|
355
|
+
const fakeClient = makeFakeExtensionClientForFreshPath(conversationId);
|
|
356
|
+
const candidate = makeCandidateFromClient(conversationId, fakeClient, "extension");
|
|
357
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
358
|
+
|
|
359
|
+
const result = await scoped.selectTab(42);
|
|
360
|
+
expect(result.tabId).toBe(42);
|
|
361
|
+
expect(result.clientId).toBe("clientA");
|
|
362
|
+
|
|
363
|
+
scoped.dispose();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("closeTab on a fresh client triggers failover walk and returns closed status", async () => {
|
|
367
|
+
const conversationId = "conv-fresh-close";
|
|
368
|
+
const fakeClient = makeFakeExtensionClientForFreshPath(conversationId);
|
|
369
|
+
const candidate = makeCandidateFromClient(conversationId, fakeClient, "extension");
|
|
370
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
371
|
+
|
|
372
|
+
const result = await scoped.closeTab(99);
|
|
373
|
+
expect(result.closed).toBe(true);
|
|
374
|
+
expect(result.tabId).toBe(99);
|
|
375
|
+
|
|
376
|
+
scoped.dispose();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("listTabs on fresh non-extension client throws transport_error after sticky established", async () => {
|
|
380
|
+
const conversationId = "conv-fresh-no-list";
|
|
381
|
+
const noTabsClient: CdpClient & { kind: "local"; conversationId: string } = {
|
|
382
|
+
kind: "local",
|
|
383
|
+
conversationId,
|
|
384
|
+
send: mock(async () => ({})) as unknown as CdpClient["send"],
|
|
385
|
+
dispose: mock(() => {}),
|
|
386
|
+
};
|
|
387
|
+
const candidate = makeCandidateFromClient(conversationId, noTabsClient, "local");
|
|
388
|
+
const scoped = buildChainedClient(conversationId, [candidate]);
|
|
389
|
+
|
|
390
|
+
// No prior send — fresh client, non-extension backend
|
|
391
|
+
let caught: unknown;
|
|
392
|
+
try {
|
|
393
|
+
await scoped.listTabs();
|
|
394
|
+
} catch (err) {
|
|
395
|
+
caught = err;
|
|
396
|
+
}
|
|
397
|
+
expect(caught).toBeInstanceOf(CdpError);
|
|
398
|
+
expect((caught as CdpError).code).toBe("transport_error");
|
|
399
|
+
|
|
400
|
+
scoped.dispose();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
@@ -89,6 +89,9 @@ describe("cdp-client re-exports", () => {
|
|
|
89
89
|
},
|
|
90
90
|
dispose: () => {},
|
|
91
91
|
setCdpSessionId: () => {},
|
|
92
|
+
listTabs: async () => [],
|
|
93
|
+
selectTab: async (_tabId: number) => ({ tabId: _tabId }),
|
|
94
|
+
closeTab: async (_tabId: number) => ({ closed: true as const, tabId: _tabId }),
|
|
92
95
|
};
|
|
93
96
|
expect(scoped.kind).toBe(kind);
|
|
94
97
|
expect(scoped.conversationId).toBe("conv-123");
|
|
@@ -710,6 +710,18 @@ export class CdpInspectClient implements ScopedCdpClient {
|
|
|
710
710
|
// no-op
|
|
711
711
|
}
|
|
712
712
|
|
|
713
|
+
async listTabs(): Promise<never> {
|
|
714
|
+
throw new CdpError("transport_error", "listTabs is not supported by the cdp-inspect backend (extension backend required)");
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
async selectTab(_tabId: number): Promise<never> {
|
|
718
|
+
throw new CdpError("transport_error", "selectTab is not supported by the cdp-inspect backend (extension backend required)");
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async closeTab(_tabId: number): Promise<never> {
|
|
722
|
+
throw new CdpError("transport_error", "closeTab is not supported by the cdp-inspect backend (extension backend required)");
|
|
723
|
+
}
|
|
724
|
+
|
|
713
725
|
dispose(): void {
|
|
714
726
|
if (this.disposed) return;
|
|
715
727
|
this.disposed = true;
|