@vellumai/assistant 0.7.1 → 0.7.2
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 +32 -49
- package/Dockerfile +1 -0
- package/README.md +1 -2
- package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
- package/bun.lock +26 -26
- package/docs/architecture/security.md +20 -0
- package/docs/plugins.md +7 -9
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
- package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
- package/node_modules/@vellumai/twilio-client/package.json +18 -0
- package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
- package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
- package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
- package/openapi.yaml +565 -12
- package/package.json +6 -3
- package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
- package/src/__tests__/app-bundler.test.ts +170 -1
- package/src/__tests__/app-control-flow.test.ts +374 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
- package/src/__tests__/app-executors.test.ts +30 -43
- package/src/__tests__/approval-routes-http.test.ts +23 -6
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
- package/src/__tests__/assistant-event-hub.test.ts +109 -2
- package/src/__tests__/assistant-event.test.ts +10 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
- package/src/__tests__/background-shell-host-bash.test.ts +14 -15
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
- package/src/__tests__/btw-routes.test.ts +13 -4
- package/src/__tests__/call-controller.test.ts +49 -1
- package/src/__tests__/call-domain.test.ts +0 -2
- package/src/__tests__/call-routes-http.test.ts +0 -2
- package/src/__tests__/channel-readiness-service.test.ts +59 -1
- package/src/__tests__/checker.test.ts +3 -4
- package/src/__tests__/config-loader-backfill.test.ts +90 -155
- package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-set-platform-guard.test.ts +48 -4
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-lifecycle.test.ts +36 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-slash-commands.test.ts +0 -4
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
- package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
- package/src/__tests__/credentials-cli.test.ts +5 -12
- package/src/__tests__/cu-unified-flow.test.ts +185 -23
- package/src/__tests__/daemon-credential-client.test.ts +101 -19
- package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
- package/src/__tests__/heartbeat-service.test.ts +718 -1
- package/src/__tests__/helpers/call-route-handler.ts +7 -1
- package/src/__tests__/host-app-control-proxy.test.ts +602 -0
- package/src/__tests__/host-app-control-routes.test.ts +263 -0
- package/src/__tests__/host-bash-proxy.test.ts +246 -47
- package/src/__tests__/host-bash-routes.test.ts +294 -0
- package/src/__tests__/host-browser-proxy.test.ts +24 -22
- package/src/__tests__/host-browser-routes.test.ts +39 -13
- package/src/__tests__/host-cu-proxy.test.ts +41 -52
- package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
- package/src/__tests__/host-file-edit-tool.test.ts +47 -1
- package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
- package/src/__tests__/host-file-proxy.test.ts +37 -43
- package/src/__tests__/host-file-read-tool.test.ts +17 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
- package/src/__tests__/host-file-write-tool.test.ts +42 -1
- package/src/__tests__/host-proxy-base.test.ts +312 -0
- package/src/__tests__/host-shell-tool.test.ts +22 -4
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
- package/src/__tests__/host-transfer-proxy.test.ts +121 -22
- package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +29 -0
- package/src/__tests__/identity-routes.test.ts +103 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
- package/src/__tests__/inline-command-runner.test.ts +0 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
- package/src/__tests__/integration-status.test.ts +85 -5
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/mcp-auth-routes.test.ts +197 -0
- package/src/__tests__/mcp-cli.test.ts +338 -2
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
- package/src/__tests__/migration-import-commit-http.test.ts +108 -2
- package/src/__tests__/mock-gateway-ipc.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +0 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
- package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +97 -0
- package/src/__tests__/require-fresh-approval.test.ts +0 -1
- package/src/__tests__/retry-backoff.test.ts +87 -0
- package/src/__tests__/runtime-events-sse.test.ts +10 -6
- package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
- package/src/__tests__/schedule-retry.test.ts +715 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-feature-flags.test.ts +43 -41
- package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
- package/src/__tests__/skill-load-inline-command.test.ts +0 -51
- package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +9 -14
- package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-config.test.ts +0 -1
- package/src/__tests__/test-preload.ts +8 -0
- package/src/__tests__/tool-approval-handler.test.ts +3 -4
- package/src/__tests__/tool-audit-listener.test.ts +48 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +3 -16
- package/src/__tests__/twilio-routes.test.ts +3 -5
- package/src/__tests__/twilio-validation.test.ts +93 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
- package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
- package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
- package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
- package/src/backup/__tests__/paths.test.ts +0 -22
- package/src/backup/__tests__/restore.test.ts +51 -151
- package/src/backup/paths.ts +2 -18
- package/src/backup/restore.ts +107 -231
- package/src/bundler/app-bundler.ts +51 -3
- package/src/calls/relay-server.ts +4 -44
- package/src/calls/twilio-config.ts +2 -17
- package/src/calls/twilio-rest.ts +33 -105
- package/src/calls/twilio-routes.ts +11 -12
- package/src/channels/types.ts +8 -7
- package/src/cli/commands/__tests__/backup.test.ts +6 -277
- package/src/cli/commands/__tests__/gateway.test.ts +288 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
- package/src/cli/commands/backup.ts +6 -331
- package/src/cli/commands/clients.ts +36 -37
- package/src/cli/commands/contacts.ts +73 -0
- package/src/cli/commands/conversations.ts +2 -5
- package/src/cli/commands/credentials.ts +15 -7
- package/src/cli/commands/domain.ts +66 -15
- package/src/cli/commands/gateway.ts +183 -0
- package/src/cli/commands/keys.ts +9 -6
- package/src/cli/commands/mcp.ts +116 -156
- package/src/cli/commands/memory-v2.ts +296 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
- package/src/cli/commands/platform/disconnect.ts +5 -4
- package/src/cli/commands/platform/index.ts +0 -18
- package/src/cli/lib/daemon-credential-client.ts +110 -28
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +67 -10
- package/src/config/bundled-skills/acp/SKILL.md +6 -0
- package/src/config/bundled-skills/acp/TOOLS.json +1 -22
- package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
- package/src/config/bundled-skills/app-control/SKILL.md +75 -0
- package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
- package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
- package/src/config/bundled-skills/document/TOOLS.json +0 -8
- package/src/config/bundled-skills/followups/TOOLS.json +0 -12
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
- package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
- package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
- package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
- package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
- package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
- package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
- package/src/config/bundled-skills/settings/SKILL.md +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +0 -12
- package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
- package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
- package/src/config/bundled-skills/subagent/SKILL.md +6 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
- package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
- package/src/config/bundled-tool-registry.ts +21 -0
- package/src/config/env-registry.ts +0 -2
- package/src/config/env.ts +19 -12
- package/src/config/feature-flag-registry.json +21 -133
- package/src/config/loader.ts +73 -99
- package/src/config/sanitize-for-transfer.ts +2 -0
- package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
- package/src/config/schemas/calls.ts +0 -9
- package/src/config/schemas/heartbeat.ts +63 -0
- package/src/config/schemas/ingress.ts +10 -6
- package/src/config/schemas/llm.ts +5 -10
- package/src/config/schemas/memory-lifecycle.ts +77 -24
- package/src/config/schemas/memory-v2.ts +48 -4
- package/src/config/schemas/platform.ts +6 -0
- package/src/config/schemas/services.ts +1 -15
- package/src/config/schemas/skills.ts +0 -6
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/contacts/contact-store.ts +0 -30
- package/src/contacts/contacts-write.ts +0 -27
- package/src/context/window-manager.ts +1 -2
- package/src/credential-execution/feature-gates.ts +10 -10
- package/src/credential-execution/process-manager.ts +12 -41
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
- package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
- package/src/daemon/config-watcher.ts +4 -3
- package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
- package/src/daemon/conversation-agent-loop.ts +32 -28
- package/src/daemon/conversation-lifecycle.ts +8 -1
- package/src/daemon/conversation-process.ts +16 -11
- package/src/daemon/conversation-runtime-assembly.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +125 -4
- package/src/daemon/conversation-tool-setup.ts +16 -55
- package/src/daemon/conversation.ts +21 -2
- package/src/daemon/doordash-steps.ts +1 -1
- package/src/daemon/handlers/shared.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +293 -0
- package/src/daemon/host-bash-proxy.ts +84 -74
- package/src/daemon/host-browser-proxy.ts +67 -82
- package/src/daemon/host-cu-proxy.ts +81 -86
- package/src/daemon/host-file-proxy.ts +93 -69
- package/src/daemon/host-proxy-base.ts +294 -0
- package/src/daemon/host-proxy-preactivation.ts +82 -0
- package/src/daemon/host-transfer-proxy.ts +247 -129
- package/src/daemon/lifecycle.ts +115 -117
- package/src/daemon/message-protocol.ts +3 -8
- package/src/daemon/message-types/contacts.ts +23 -1
- package/src/daemon/message-types/conversations.ts +11 -8
- package/src/daemon/message-types/host-app-control.ts +150 -0
- package/src/daemon/message-types/host-bash.ts +4 -0
- package/src/daemon/message-types/host-cu.ts +2 -0
- package/src/daemon/message-types/host-file.ts +4 -0
- package/src/daemon/message-types/host-transfer.ts +3 -0
- package/src/daemon/message-types/schedules.ts +8 -3
- package/src/daemon/message-types/skills.ts +2 -2
- package/src/daemon/process-message.ts +18 -1
- package/src/daemon/shutdown-handlers.ts +0 -3
- package/src/daemon/tool-setup-types.ts +51 -0
- package/src/daemon/tool-side-effects.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -1
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
- package/src/heartbeat/heartbeat-run-store.ts +236 -0
- package/src/heartbeat/heartbeat-service.ts +280 -49
- package/src/home/__tests__/post-connect-feed.test.ts +99 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
- package/src/home/__tests__/suggested-prompts.test.ts +89 -0
- package/src/home/post-connect-feed.ts +68 -0
- package/src/home/relationship-state-writer.ts +17 -92
- package/src/home/suggested-prompts.ts +46 -10
- package/src/inbound/public-ingress-urls.ts +32 -34
- package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
- package/src/ipc/assistant-server.ts +14 -1
- package/src/ipc/cli-client.ts +32 -1
- package/src/live-voice/live-voice-metrics.ts +10 -10
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
- package/src/mcp/mcp-auth-orchestrator.ts +213 -0
- package/src/mcp/mcp-auth-state.ts +133 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
- package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
- package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
- package/src/memory/anisotropy.test.ts +247 -0
- package/src/memory/anisotropy.ts +443 -0
- package/src/memory/auto-analysis-constants.ts +17 -0
- package/src/memory/auto-analysis-guard.ts +5 -15
- package/src/memory/canonical-guardian-store.ts +7 -7
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
- package/src/memory/context-search/agent-protocol.ts +6 -6
- package/src/memory/context-search/agent-runner.ts +32 -7
- package/src/memory/context-search/sources/memory-v2.ts +17 -5
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-key-store.ts +2 -15
- package/src/memory/db-init.ts +4 -0
- package/src/memory/embedding-backend.ts +9 -21
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
- package/src/memory/graph/conversation-graph-memory.ts +1 -24
- package/src/memory/graph/graph-search.ts +8 -0
- package/src/memory/graph/retriever.ts +28 -0
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
- package/src/memory/jobs/embed-concept-page.ts +28 -2
- package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
- package/src/memory/jobs-store.ts +66 -22
- package/src/memory/jobs-worker.ts +112 -63
- package/src/memory/memory-v2-activation-log-store.ts +1 -1
- package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
- package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/pkb/pkb-search.ts +7 -0
- package/src/memory/qdrant-client.ts +50 -20
- package/src/memory/schema/infrastructure.ts +15 -0
- package/src/memory/search/semantic.ts +7 -0
- package/src/memory/sparse-tokenize.ts +49 -0
- package/src/memory/v2/__tests__/activation.test.ts +77 -95
- package/src/memory/v2/__tests__/injection.test.ts +43 -21
- package/src/memory/v2/__tests__/sim.test.ts +166 -6
- package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
- package/src/memory/v2/__tests__/static-context.test.ts +0 -1
- package/src/memory/v2/activation.ts +69 -88
- package/src/memory/v2/consolidation-job.ts +3 -5
- package/src/memory/v2/constants.ts +7 -0
- package/src/memory/v2/injection.ts +86 -53
- package/src/memory/v2/prompts/consolidation.ts +312 -91
- package/src/memory/v2/qdrant.ts +99 -1
- package/src/memory/v2/sim.ts +126 -16
- package/src/memory/v2/skill-qdrant.ts +12 -3
- package/src/memory/v2/skill-store.ts +16 -1
- package/src/memory/v2/sparse-bm25.ts +245 -0
- package/src/memory/v2/static-context.ts +6 -5
- package/src/messaging/providers/gmail/types.ts +0 -49
- package/src/messaging/providers/slack/adapter.ts +1 -31
- package/src/messaging/providers/slack/types.ts +0 -32
- package/src/notifications/README.md +10 -10
- package/src/notifications/broadcaster.ts +1 -1
- package/src/notifications/guardian-question-mode.ts +5 -5
- package/src/oauth/connect-orchestrator.ts +4 -0
- package/src/oauth/credential-token-resolver.ts +1 -3
- package/src/oauth/manual-token-connection.ts +0 -4
- package/src/outbound-proxy/index.ts +1 -37
- package/src/outbound-proxy/logging.ts +1 -1
- package/src/outbound-proxy/policy.ts +6 -5
- package/src/outbound-proxy/router.ts +2 -1
- package/src/permissions/approval-policy.test.ts +6 -275
- package/src/permissions/approval-policy.ts +0 -51
- package/src/permissions/checker.test.ts +0 -1
- package/src/permissions/checker.ts +3 -17
- package/src/permissions/gateway-threshold-reader.ts +2 -0
- package/src/permissions/prompter.ts +34 -1
- package/src/permissions/secret-prompter.ts +6 -2
- package/src/prompts/bootstrap-cleanup.ts +27 -0
- package/src/prompts/system-prompt.ts +3 -18
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/providers/speech-to-text/provider-catalog.ts +7 -8
- package/src/runtime/assistant-event-hub.ts +118 -96
- package/src/runtime/assistant-event.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
- package/src/runtime/auth/middleware.ts +0 -96
- package/src/runtime/auth/route-policy.ts +19 -0
- package/src/runtime/btw-sidechain.ts +2 -3
- package/src/runtime/channel-invite-transport.ts +2 -48
- package/src/runtime/channel-invite-transports/email.ts +1 -1
- package/src/runtime/channel-invite-transports/slack.ts +1 -1
- package/src/runtime/channel-invite-transports/telegram.ts +1 -1
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
- package/src/runtime/channel-invite-types.ts +54 -0
- package/src/runtime/channel-readiness-service.ts +32 -13
- package/src/runtime/http-server.ts +3 -329
- package/src/runtime/http-types.ts +0 -5
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
- package/src/runtime/migrations/migration-transport.ts +7 -7
- package/src/runtime/migrations/vbundle-builder.ts +327 -60
- package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
- package/src/runtime/migrations/vbundle-importer.ts +245 -68
- package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
- package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
- package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
- package/src/runtime/migrations/vbundle-validator.ts +114 -0
- package/src/runtime/pending-interactions.ts +35 -9
- package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
- package/src/runtime/routes/approval-interception-types.ts +13 -0
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
- package/src/runtime/routes/backup-routes.ts +15 -38
- package/src/runtime/routes/btw-routes.ts +14 -37
- package/src/runtime/routes/client-routes.ts +1 -0
- package/src/runtime/routes/contact-prompt-routes.ts +183 -0
- package/src/runtime/routes/conversation-query-routes.ts +36 -1
- package/src/runtime/routes/conversation-routes.ts +30 -13
- package/src/runtime/routes/document-pdf-renderer.ts +165 -0
- package/src/runtime/routes/documents-routes.ts +30 -0
- package/src/runtime/routes/errors.ts +19 -4
- package/src/runtime/routes/events-routes.ts +12 -6
- package/src/runtime/routes/gateway-log-routes.ts +79 -0
- package/src/runtime/routes/guardian-approval-interception.ts +2 -8
- package/src/runtime/routes/heartbeat-routes.ts +103 -38
- package/src/runtime/routes/host-app-control-routes.ts +134 -0
- package/src/runtime/routes/host-bash-routes.ts +36 -6
- package/src/runtime/routes/host-browser-routes.ts +108 -13
- package/src/runtime/routes/host-cu-routes.ts +44 -14
- package/src/runtime/routes/host-file-routes.ts +33 -10
- package/src/runtime/routes/host-transfer-routes.ts +64 -24
- package/src/runtime/routes/http-adapter.ts +1 -0
- package/src/runtime/routes/identity-intro-cache.ts +30 -0
- package/src/runtime/routes/identity-routes.ts +15 -43
- package/src/runtime/routes/inbound-message-handler.ts +1 -9
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/mcp-auth-routes.ts +132 -0
- package/src/runtime/routes/memory-item-routes.ts +10 -12
- package/src/runtime/routes/memory-v2-routes.ts +441 -1
- package/src/runtime/routes/migration-routes.ts +96 -0
- package/src/runtime/routes/schedule-routes.ts +7 -0
- package/src/runtime/verification-templates.ts +4 -7
- package/src/schedule/integration-status.ts +66 -2
- package/src/schedule/recurrence-engine.ts +4 -1
- package/src/schedule/retry-backoff.ts +18 -0
- package/src/schedule/retry-policy.ts +82 -0
- package/src/schedule/schedule-recovery.ts +64 -0
- package/src/schedule/schedule-store.ts +106 -2
- package/src/schedule/scheduler-types.ts +25 -0
- package/src/schedule/scheduler.ts +63 -38
- package/src/security/oauth-callback-registry.ts +8 -0
- package/src/sequence/analytics.ts +5 -5
- package/src/sequence/engine.ts +1 -1
- package/src/skills/catalog-files.ts +2 -8
- package/src/skills/include-graph.ts +5 -5
- package/src/skills/remote-skill-policy.ts +5 -5
- package/src/skills/skill-file-provider.ts +1 -1
- package/src/skills/skill-file-types.ts +13 -0
- package/src/skills/skillssh-audit-types.ts +28 -0
- package/src/skills/skillssh-registry.ts +8 -21
- package/src/telemetry/types.ts +2 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
- package/src/tools/apps/executors.ts +56 -69
- package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
- package/src/tools/browser/browser-execution.ts +2 -2
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
- package/src/tools/browser/cdp-client/factory.ts +23 -24
- package/src/tools/browser/cdp-client/index.ts +1 -14
- package/src/tools/computer-use/definitions.ts +42 -20
- package/src/tools/executor.ts +2 -0
- package/src/tools/host-filesystem/edit.ts +26 -0
- package/src/tools/host-filesystem/read.ts +26 -0
- package/src/tools/host-filesystem/transfer.ts +31 -1
- package/src/tools/host-filesystem/write.ts +26 -0
- package/src/tools/host-terminal/host-shell.ts +58 -0
- package/src/tools/schedule/create.ts +6 -0
- package/src/tools/schedule/list.ts +2 -0
- package/src/tools/schedule/update.ts +10 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
- package/src/tools/shared/filesystem/path-policy.ts +25 -1
- package/src/tools/skills/load.ts +0 -32
- package/src/tools/tool-approval-handler.ts +1 -5
- package/src/tools/types.ts +4 -0
- package/src/usage/pricing.ts +1 -1
- package/src/workspace/hatched-date.ts +86 -0
- package/src/workspace/migrations/003-seed-device-id.ts +1 -1
- package/src/workspace/migrations/006-services-config.ts +8 -5
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
- package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
- package/src/workspace/migrations/utils.ts +21 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
- package/src/__tests__/twilio-rest.test.ts +0 -34
- package/src/backup/__tests__/backup-key.test.ts +0 -152
- package/src/backup/__tests__/backup-worker.test.ts +0 -782
- package/src/backup/__tests__/offsite-writer.test.ts +0 -641
- package/src/backup/__tests__/stream-crypt.test.ts +0 -228
- package/src/backup/backup-key.ts +0 -137
- package/src/backup/backup-worker.ts +0 -472
- package/src/backup/offsite-writer.ts +0 -222
- package/src/backup/stream-crypt.ts +0 -263
- package/src/daemon/message-types/pairing.ts +0 -58
- package/src/outbound-proxy/config.ts +0 -20
- package/src/outbound-proxy/health.ts +0 -18
- package/src/outbound-proxy/types.ts +0 -150
- package/src/runtime/capability-tokens.ts +0 -190
- package/src/signals/mcp-reload.ts +0 -18
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { afterEach, describe, expect, jest, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
const sentMessages: unknown[] = [];
|
|
4
|
-
const resolvedInteractionIds: string[] = [];
|
|
5
4
|
let mockHasClient = false;
|
|
6
5
|
|
|
7
6
|
mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
@@ -12,17 +11,8 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
|
12
11
|
},
|
|
13
12
|
}));
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
resolvedInteractionIds.push(requestId);
|
|
18
|
-
return undefined;
|
|
19
|
-
},
|
|
20
|
-
get: () => undefined,
|
|
21
|
-
getByKind: () => [],
|
|
22
|
-
getByConversation: () => [],
|
|
23
|
-
removeByConversation: () => {},
|
|
24
|
-
}));
|
|
25
|
-
|
|
14
|
+
// Use the REAL pending-interactions module — the proxy self-registers here.
|
|
15
|
+
const pendingInteractions = await import("../runtime/pending-interactions.js");
|
|
26
16
|
const { HostCuProxy } = await import("../daemon/host-cu-proxy.js");
|
|
27
17
|
|
|
28
18
|
describe("HostCuProxy", () => {
|
|
@@ -30,13 +20,14 @@ describe("HostCuProxy", () => {
|
|
|
30
20
|
|
|
31
21
|
function setup(maxSteps?: number) {
|
|
32
22
|
sentMessages.length = 0;
|
|
33
|
-
resolvedInteractionIds.length = 0;
|
|
34
23
|
mockHasClient = false;
|
|
24
|
+
pendingInteractions.clear();
|
|
35
25
|
proxy = new HostCuProxy(maxSteps);
|
|
36
26
|
}
|
|
37
27
|
|
|
38
28
|
afterEach(() => {
|
|
39
29
|
proxy?.dispose();
|
|
30
|
+
pendingInteractions.clear();
|
|
40
31
|
});
|
|
41
32
|
|
|
42
33
|
// -------------------------------------------------------------------------
|
|
@@ -66,9 +57,9 @@ describe("HostCuProxy", () => {
|
|
|
66
57
|
expect(typeof sent.requestId).toBe("string");
|
|
67
58
|
|
|
68
59
|
const requestId = sent.requestId as string;
|
|
69
|
-
expect(
|
|
60
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
70
61
|
|
|
71
|
-
proxy.
|
|
62
|
+
proxy.processObservation(requestId, {
|
|
72
63
|
axTree: "Button [1]\nLabel [2]",
|
|
73
64
|
executionResult: "Clicked element 42",
|
|
74
65
|
});
|
|
@@ -78,7 +69,7 @@ describe("HostCuProxy", () => {
|
|
|
78
69
|
expect(result.content).toContain("<ax-tree>");
|
|
79
70
|
expect(result.content).toContain("CURRENT SCREEN STATE:");
|
|
80
71
|
expect(result.isError).toBe(false);
|
|
81
|
-
expect(
|
|
72
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
82
73
|
});
|
|
83
74
|
|
|
84
75
|
test("formats error observation correctly", async () => {
|
|
@@ -94,7 +85,7 @@ describe("HostCuProxy", () => {
|
|
|
94
85
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
95
86
|
const requestId = sent.requestId as string;
|
|
96
87
|
|
|
97
|
-
proxy.
|
|
88
|
+
proxy.processObservation(requestId, {
|
|
98
89
|
executionError: "Element not found",
|
|
99
90
|
axTree: "Window [1]",
|
|
100
91
|
});
|
|
@@ -118,7 +109,7 @@ describe("HostCuProxy", () => {
|
|
|
118
109
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
119
110
|
const requestId = sent.requestId as string;
|
|
120
111
|
|
|
121
|
-
proxy.
|
|
112
|
+
proxy.processObservation(requestId, {
|
|
122
113
|
axTree: "Button [1]",
|
|
123
114
|
screenshot: "base64data",
|
|
124
115
|
screenshotWidthPx: 1920,
|
|
@@ -142,7 +133,7 @@ describe("HostCuProxy", () => {
|
|
|
142
133
|
test("resolves with unknown requestId is silently ignored", () => {
|
|
143
134
|
setup();
|
|
144
135
|
// Should not throw
|
|
145
|
-
proxy.
|
|
136
|
+
proxy.processObservation("unknown-id", { axTree: "something" });
|
|
146
137
|
});
|
|
147
138
|
});
|
|
148
139
|
|
|
@@ -165,10 +156,10 @@ describe("HostCuProxy", () => {
|
|
|
165
156
|
|
|
166
157
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
167
158
|
const requestId = sent.requestId as string;
|
|
168
|
-
expect(
|
|
159
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
169
160
|
|
|
170
161
|
// Resolve to avoid test hanging
|
|
171
|
-
proxy.
|
|
162
|
+
proxy.processObservation(requestId, { axTree: "resolved" });
|
|
172
163
|
await resultPromise;
|
|
173
164
|
});
|
|
174
165
|
});
|
|
@@ -193,14 +184,14 @@ describe("HostCuProxy", () => {
|
|
|
193
184
|
|
|
194
185
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
195
186
|
const requestId = sent.requestId as string;
|
|
196
|
-
expect(
|
|
187
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
197
188
|
|
|
198
189
|
controller.abort();
|
|
199
190
|
|
|
200
191
|
const result = await resultPromise;
|
|
201
192
|
expect(result.content).toContain("Aborted");
|
|
202
193
|
expect(result.isError).toBe(true);
|
|
203
|
-
expect(
|
|
194
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
204
195
|
});
|
|
205
196
|
|
|
206
197
|
test("sends host_cu_cancel to client on abort", async () => {
|
|
@@ -296,7 +287,7 @@ describe("HostCuProxy", () => {
|
|
|
296
287
|
expect(sentMessages).toHaveLength(1); // Message was sent
|
|
297
288
|
|
|
298
289
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
299
|
-
proxy.
|
|
290
|
+
proxy.processObservation(sent.requestId as string, { axTree: "screen" });
|
|
300
291
|
|
|
301
292
|
const result = await resultPromise;
|
|
302
293
|
expect(result.isError).toBe(false);
|
|
@@ -370,7 +361,7 @@ describe("HostCuProxy", () => {
|
|
|
370
361
|
);
|
|
371
362
|
proxy.recordAction("computer_use_click", { element_id: 1 });
|
|
372
363
|
const sent1 = sentMessages[0] as Record<string, unknown>;
|
|
373
|
-
proxy.
|
|
364
|
+
proxy.processObservation(sent1.requestId as string, {
|
|
374
365
|
axTree: "Button [1]",
|
|
375
366
|
});
|
|
376
367
|
await p1;
|
|
@@ -384,7 +375,7 @@ describe("HostCuProxy", () => {
|
|
|
384
375
|
);
|
|
385
376
|
proxy.recordAction("computer_use_click", { element_id: 1 });
|
|
386
377
|
const sent2 = sentMessages[1] as Record<string, unknown>;
|
|
387
|
-
proxy.
|
|
378
|
+
proxy.processObservation(sent2.requestId as string, {
|
|
388
379
|
axTree: "Button [1]",
|
|
389
380
|
// No axDiff — screen unchanged
|
|
390
381
|
});
|
|
@@ -402,7 +393,7 @@ describe("HostCuProxy", () => {
|
|
|
402
393
|
);
|
|
403
394
|
proxy.recordAction("computer_use_click", { element_id: 1 });
|
|
404
395
|
const sent3 = sentMessages[2] as Record<string, unknown>;
|
|
405
|
-
proxy.
|
|
396
|
+
proxy.processObservation(sent3.requestId as string, {
|
|
406
397
|
axTree: "Button [1]",
|
|
407
398
|
});
|
|
408
399
|
const result3 = await p3;
|
|
@@ -424,7 +415,7 @@ describe("HostCuProxy", () => {
|
|
|
424
415
|
1,
|
|
425
416
|
);
|
|
426
417
|
const sent1 = sentMessages[0] as Record<string, unknown>;
|
|
427
|
-
proxy.
|
|
418
|
+
proxy.processObservation(sent1.requestId as string, {
|
|
428
419
|
axTree: "Button [1]",
|
|
429
420
|
// No axDiff on first observation — this is normal, not unchanged
|
|
430
421
|
});
|
|
@@ -444,7 +435,7 @@ describe("HostCuProxy", () => {
|
|
|
444
435
|
);
|
|
445
436
|
proxy.recordAction("computer_use_click", { element_id: 1 });
|
|
446
437
|
const sent1 = sentMessages[0] as Record<string, unknown>;
|
|
447
|
-
proxy.
|
|
438
|
+
proxy.processObservation(sent1.requestId as string, {
|
|
448
439
|
axTree: "Button [1]",
|
|
449
440
|
});
|
|
450
441
|
await p1;
|
|
@@ -458,7 +449,7 @@ describe("HostCuProxy", () => {
|
|
|
458
449
|
);
|
|
459
450
|
proxy.recordAction("computer_use_wait", { duration_ms: 2000 });
|
|
460
451
|
const sent2 = sentMessages[1] as Record<string, unknown>;
|
|
461
|
-
proxy.
|
|
452
|
+
proxy.processObservation(sent2.requestId as string, {
|
|
462
453
|
axTree: "Button [1]",
|
|
463
454
|
// No axDiff — screen unchanged, but that's expected after wait
|
|
464
455
|
});
|
|
@@ -478,7 +469,7 @@ describe("HostCuProxy", () => {
|
|
|
478
469
|
);
|
|
479
470
|
proxy.recordAction("computer_use_click", { element_id: 1 });
|
|
480
471
|
const sent1 = sentMessages[0] as Record<string, unknown>;
|
|
481
|
-
proxy.
|
|
472
|
+
proxy.processObservation(sent1.requestId as string, {
|
|
482
473
|
axTree: "Button [1]",
|
|
483
474
|
});
|
|
484
475
|
await p1;
|
|
@@ -492,7 +483,7 @@ describe("HostCuProxy", () => {
|
|
|
492
483
|
);
|
|
493
484
|
proxy.recordAction("computer_use_click", { element_id: 1 });
|
|
494
485
|
const sent2 = sentMessages[1] as Record<string, unknown>;
|
|
495
|
-
proxy.
|
|
486
|
+
proxy.processObservation(sent2.requestId as string, {
|
|
496
487
|
axTree: "Button [1]",
|
|
497
488
|
});
|
|
498
489
|
await p2;
|
|
@@ -507,7 +498,7 @@ describe("HostCuProxy", () => {
|
|
|
507
498
|
);
|
|
508
499
|
proxy.recordAction("computer_use_click", { element_id: 2 });
|
|
509
500
|
const sent3 = sentMessages[2] as Record<string, unknown>;
|
|
510
|
-
proxy.
|
|
501
|
+
proxy.processObservation(sent3.requestId as string, {
|
|
511
502
|
axTree: "TextField [1]",
|
|
512
503
|
axDiff: "+ TextField [1]\n- Button [1]",
|
|
513
504
|
});
|
|
@@ -719,11 +710,11 @@ describe("HostCuProxy", () => {
|
|
|
719
710
|
|
|
720
711
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
721
712
|
const requestId = sent.requestId as string;
|
|
722
|
-
expect(
|
|
713
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
723
714
|
|
|
724
715
|
proxy.dispose();
|
|
725
716
|
|
|
726
|
-
expect(
|
|
717
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
727
718
|
await expect(resultPromise).rejects.toThrow("Host CU proxy disposed");
|
|
728
719
|
});
|
|
729
720
|
|
|
@@ -786,9 +777,9 @@ describe("HostCuProxy", () => {
|
|
|
786
777
|
expect(result.content).toContain("Aborted");
|
|
787
778
|
|
|
788
779
|
// Late resolve should be silently ignored (no throw, no double-resolve)
|
|
789
|
-
proxy.
|
|
780
|
+
proxy.processObservation(requestId, { axTree: "late response" });
|
|
790
781
|
|
|
791
|
-
expect(
|
|
782
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
792
783
|
});
|
|
793
784
|
});
|
|
794
785
|
|
|
@@ -811,17 +802,11 @@ describe("HostCuProxy", () => {
|
|
|
811
802
|
const s = source as any;
|
|
812
803
|
const origAdd = source.addEventListener.bind(source);
|
|
813
804
|
const origRemove = source.removeEventListener.bind(source);
|
|
814
|
-
s.addEventListener = (
|
|
815
|
-
type: string,
|
|
816
|
-
...rest: any[]
|
|
817
|
-
) => {
|
|
805
|
+
s.addEventListener = (type: string, ...rest: any[]) => {
|
|
818
806
|
addCalls.push(type);
|
|
819
807
|
return (origAdd as any)(type, ...rest);
|
|
820
808
|
};
|
|
821
|
-
s.removeEventListener = (
|
|
822
|
-
type: string,
|
|
823
|
-
...rest: any[]
|
|
824
|
-
) => {
|
|
809
|
+
s.removeEventListener = (type: string, ...rest: any[]) => {
|
|
825
810
|
removeCalls.push(type);
|
|
826
811
|
return (origRemove as any)(type, ...rest);
|
|
827
812
|
};
|
|
@@ -847,7 +832,7 @@ describe("HostCuProxy", () => {
|
|
|
847
832
|
|
|
848
833
|
const requestId = (sentMessages[0] as Record<string, unknown>)
|
|
849
834
|
.requestId as string;
|
|
850
|
-
proxy.
|
|
835
|
+
proxy.processObservation(requestId, { axTree: "Button [1]" });
|
|
851
836
|
await resultPromise;
|
|
852
837
|
|
|
853
838
|
// Listener is detached after normal completion.
|
|
@@ -881,7 +866,7 @@ describe("HostCuProxy", () => {
|
|
|
881
866
|
|
|
882
867
|
const requestId = (sentMessages[0] as Record<string, unknown>)
|
|
883
868
|
.requestId as string;
|
|
884
|
-
expect(
|
|
869
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
885
870
|
|
|
886
871
|
// Advance past the 60s internal timeout.
|
|
887
872
|
jest.advanceTimersByTime(61 * 1000);
|
|
@@ -889,7 +874,7 @@ describe("HostCuProxy", () => {
|
|
|
889
874
|
const result = await resultPromise;
|
|
890
875
|
expect(result.isError).toBe(true);
|
|
891
876
|
expect(result.content).toContain("Host CU proxy timed out");
|
|
892
|
-
expect(
|
|
877
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
893
878
|
|
|
894
879
|
// Listener is detached after the timer fires.
|
|
895
880
|
expect(spy.removeCalls).toEqual(["abort"]);
|
|
@@ -927,7 +912,7 @@ describe("HostCuProxy", () => {
|
|
|
927
912
|
controller.abort();
|
|
928
913
|
|
|
929
914
|
await resultPromise;
|
|
930
|
-
expect(
|
|
915
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
931
916
|
});
|
|
932
917
|
|
|
933
918
|
test("fires on dispose", async () => {
|
|
@@ -948,7 +933,7 @@ describe("HostCuProxy", () => {
|
|
|
948
933
|
// dispose rejects pending requests — catch to avoid unhandled rejection
|
|
949
934
|
await resultPromise.catch(() => {});
|
|
950
935
|
|
|
951
|
-
expect(
|
|
936
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
952
937
|
});
|
|
953
938
|
|
|
954
939
|
test("does not fire on normal client-initiated resolve", async () => {
|
|
@@ -964,10 +949,10 @@ describe("HostCuProxy", () => {
|
|
|
964
949
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
965
950
|
const requestId = sent.requestId as string;
|
|
966
951
|
|
|
967
|
-
proxy.
|
|
952
|
+
proxy.processObservation(requestId, { axTree: "Button [1]" });
|
|
968
953
|
|
|
969
954
|
await resultPromise;
|
|
970
|
-
expect(
|
|
955
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
971
956
|
});
|
|
972
957
|
});
|
|
973
958
|
|
|
@@ -988,4 +973,8 @@ describe("HostCuProxy", () => {
|
|
|
988
973
|
expect(proxy.isAvailable()).toBe(true);
|
|
989
974
|
});
|
|
990
975
|
});
|
|
976
|
+
|
|
977
|
+
// targetClientId validation lives at the surfaceProxyResolver layer (so an
|
|
978
|
+
// invalid ID does not burn a step or pollute action history before being
|
|
979
|
+
// rejected). See cu-unified-flow.test.ts for those tests.
|
|
991
980
|
});
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the host-cu-result route 403 guard introduced in Phase 2.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* 1. Targeted + correct x-vellum-client-id header → 200 accepted
|
|
6
|
+
* 2. Targeted + missing header → 400 BadRequestError
|
|
7
|
+
* 3. Targeted + wrong header → 403 ForbiddenError, interaction NOT consumed
|
|
8
|
+
* 4. Untargeted (no targetClientId, no header) → 200 accepted (regression)
|
|
9
|
+
*
|
|
10
|
+
* Resolution goes through conversation.hostCuProxy?.resolve(...). The
|
|
11
|
+
* conversation store is mocked to return a controlled conversation object.
|
|
12
|
+
*
|
|
13
|
+
* Note: host-cu-routes.ts has a deep import chain (conversation-store →
|
|
14
|
+
* conversation.ts → ces-client → service-contracts) that requires mocking
|
|
15
|
+
* before the module loads. We use dynamic imports to ensure all mocks are
|
|
16
|
+
* registered before the route module is evaluated.
|
|
17
|
+
*/
|
|
18
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
// ── Module mocks ─────────────────────────────────────────────────────────────
|
|
21
|
+
// Must be registered before the host-cu-routes module is loaded.
|
|
22
|
+
|
|
23
|
+
mock.module("../config/env.js", () => ({
|
|
24
|
+
isHttpAuthDisabled: () => true,
|
|
25
|
+
hasUngatedHttpAuthDisabled: () => false,
|
|
26
|
+
getPlatformBaseUrl: () => "https://platform.example.com",
|
|
27
|
+
getGatewayInternalBaseUrl: () => "http://localhost:8080",
|
|
28
|
+
getIngressPublicBaseUrl: () => undefined,
|
|
29
|
+
setIngressPublicBaseUrl: () => {},
|
|
30
|
+
getRuntimeHttpPort: () => 3000,
|
|
31
|
+
getRuntimeHttpHost: () => "0.0.0.0",
|
|
32
|
+
getSentryDsn: () => "",
|
|
33
|
+
getQdrantUrlEnv: () => undefined,
|
|
34
|
+
getQdrantHttpPortEnv: () => undefined,
|
|
35
|
+
getQdrantReadyzTimeoutMs: () => undefined,
|
|
36
|
+
getOllamaBaseUrlEnv: () => undefined,
|
|
37
|
+
setPlatformBaseUrl: () => {},
|
|
38
|
+
getAssistantDomain: () => "example.com",
|
|
39
|
+
setPlatformAssistantId: () => {},
|
|
40
|
+
getPlatformAssistantId: () => "test-assistant-id",
|
|
41
|
+
setPlatformOrganizationId: () => {},
|
|
42
|
+
getPlatformOrganizationId: () => "test-org-id",
|
|
43
|
+
setPlatformUserId: () => {},
|
|
44
|
+
getPlatformUserId: () => "test-user-id",
|
|
45
|
+
getPlatformInternalApiKey: () => "test-api-key",
|
|
46
|
+
validateEnv: () => {},
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
import type { PendingInteraction } from "../runtime/pending-interactions.js";
|
|
50
|
+
|
|
51
|
+
const pendingStore = new Map<string, PendingInteraction>();
|
|
52
|
+
const resolvedIds: string[] = [];
|
|
53
|
+
|
|
54
|
+
mock.module("../runtime/pending-interactions.js", () => ({
|
|
55
|
+
get: (requestId: string) => pendingStore.get(requestId),
|
|
56
|
+
resolve: (requestId: string) => {
|
|
57
|
+
const entry = pendingStore.get(requestId);
|
|
58
|
+
if (entry) {
|
|
59
|
+
pendingStore.delete(requestId);
|
|
60
|
+
resolvedIds.push(requestId);
|
|
61
|
+
}
|
|
62
|
+
return entry;
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
interface CuResolveCall {
|
|
67
|
+
requestId: string;
|
|
68
|
+
payload: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const cuResolveSpy: CuResolveCall[] = [];
|
|
72
|
+
|
|
73
|
+
// Controlled conversation map: conversationId → conversation object
|
|
74
|
+
const conversationStore = new Map<string, { hostCuProxy?: { processObservation: (...args: unknown[]) => void } }>();
|
|
75
|
+
|
|
76
|
+
mock.module("../daemon/conversation-store.js", () => ({
|
|
77
|
+
findConversation: (conversationId: string) => conversationStore.get(conversationId),
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
// ── Real imports (after mocks) ──────────────────────────────────────────────
|
|
81
|
+
// Use dynamic import to ensure the mocks above are applied before loading.
|
|
82
|
+
|
|
83
|
+
import {
|
|
84
|
+
BadRequestError,
|
|
85
|
+
ForbiddenError,
|
|
86
|
+
} from "../runtime/routes/errors.js";
|
|
87
|
+
|
|
88
|
+
const { ROUTES } = await import("../runtime/routes/host-cu-routes.js");
|
|
89
|
+
|
|
90
|
+
afterAll(() => {
|
|
91
|
+
mock.restore();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const handleHostCuResult = ROUTES.find(
|
|
95
|
+
(r: { endpoint: string }) => r.endpoint === "host-cu-result",
|
|
96
|
+
)!.handler;
|
|
97
|
+
|
|
98
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
function registerPending(
|
|
101
|
+
requestId: string,
|
|
102
|
+
overrides: Partial<PendingInteraction> = {},
|
|
103
|
+
): void {
|
|
104
|
+
const entry: PendingInteraction = {
|
|
105
|
+
conversationId: "conv-cu-1",
|
|
106
|
+
kind: "host_cu",
|
|
107
|
+
...overrides,
|
|
108
|
+
};
|
|
109
|
+
pendingStore.set(requestId, entry);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function registerConversation(conversationId = "conv-cu-1"): void {
|
|
113
|
+
conversationStore.set(conversationId, {
|
|
114
|
+
hostCuProxy: {
|
|
115
|
+
processObservation(requestId: unknown, payload: unknown) {
|
|
116
|
+
// Simulate what the real processObservation does: consume the pending interaction
|
|
117
|
+
pendingStore.delete(requestId as string);
|
|
118
|
+
resolvedIds.push(requestId as string);
|
|
119
|
+
cuResolveSpy.push({
|
|
120
|
+
requestId: requestId as string,
|
|
121
|
+
payload: payload as Record<string, unknown>,
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function cuBody(requestId: string): Record<string, unknown> {
|
|
129
|
+
return {
|
|
130
|
+
requestId,
|
|
131
|
+
axTree: "Button [1]",
|
|
132
|
+
executionResult: "Clicked",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Tests ─────────────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
describe("handleHostCuResult — Phase 2 targetClientId guard", () => {
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
pendingStore.clear();
|
|
141
|
+
conversationStore.clear();
|
|
142
|
+
resolvedIds.length = 0;
|
|
143
|
+
cuResolveSpy.length = 0;
|
|
144
|
+
// Default: register a conversation with a hostCuProxy
|
|
145
|
+
registerConversation("conv-cu-1");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ── 1. Targeted + correct header → 200 ────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
describe("targeted + correct x-vellum-client-id header", () => {
|
|
151
|
+
test("returns { accepted: true } and resolves the interaction", async () => {
|
|
152
|
+
const requestId = "req-cu-targeted-match";
|
|
153
|
+
registerPending(requestId, { targetClientId: "client-A" });
|
|
154
|
+
|
|
155
|
+
const result = await handleHostCuResult({
|
|
156
|
+
body: cuBody(requestId),
|
|
157
|
+
headers: { "x-vellum-client-id": "client-A" },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
expect(result).toEqual({ accepted: true });
|
|
161
|
+
expect(cuResolveSpy).toHaveLength(1);
|
|
162
|
+
expect(cuResolveSpy[0].requestId).toBe(requestId);
|
|
163
|
+
expect(resolvedIds).toContain(requestId);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("trims whitespace from header before comparing", async () => {
|
|
167
|
+
const requestId = "req-cu-targeted-trim";
|
|
168
|
+
registerPending(requestId, { targetClientId: "client-A" });
|
|
169
|
+
|
|
170
|
+
const result = await handleHostCuResult({
|
|
171
|
+
body: cuBody(requestId),
|
|
172
|
+
headers: { "x-vellum-client-id": " client-A " },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(result).toEqual({ accepted: true });
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ── 2. Targeted + missing header → 400 ────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
describe("targeted + missing x-vellum-client-id header", () => {
|
|
182
|
+
test("throws BadRequestError (400) when header is absent", () => {
|
|
183
|
+
const requestId = "req-cu-targeted-no-header";
|
|
184
|
+
registerPending(requestId, { targetClientId: "client-A" });
|
|
185
|
+
|
|
186
|
+
expect(() =>
|
|
187
|
+
handleHostCuResult({ body: cuBody(requestId) }),
|
|
188
|
+
).toThrow(BadRequestError);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("throws BadRequestError (400) when header is empty string", () => {
|
|
192
|
+
const requestId = "req-cu-targeted-empty-header";
|
|
193
|
+
registerPending(requestId, { targetClientId: "client-A" });
|
|
194
|
+
|
|
195
|
+
expect(() =>
|
|
196
|
+
handleHostCuResult({
|
|
197
|
+
body: cuBody(requestId),
|
|
198
|
+
headers: { "x-vellum-client-id": " " },
|
|
199
|
+
}),
|
|
200
|
+
).toThrow(BadRequestError);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("interaction is NOT resolved on 400 (still pending)", () => {
|
|
204
|
+
const requestId = "req-cu-targeted-no-header-stays";
|
|
205
|
+
registerPending(requestId, { targetClientId: "client-A" });
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
handleHostCuResult({ body: cuBody(requestId) });
|
|
209
|
+
} catch {
|
|
210
|
+
// expected
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
expect(resolvedIds).not.toContain(requestId);
|
|
214
|
+
expect(pendingStore.has(requestId)).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ── 3. Targeted + wrong header → 403 ──────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
describe("targeted + wrong x-vellum-client-id header", () => {
|
|
221
|
+
test("throws ForbiddenError (403) when client ID does not match", () => {
|
|
222
|
+
const requestId = "req-cu-targeted-mismatch";
|
|
223
|
+
registerPending(requestId, { targetClientId: "client-A" });
|
|
224
|
+
|
|
225
|
+
expect(() =>
|
|
226
|
+
handleHostCuResult({
|
|
227
|
+
body: cuBody(requestId),
|
|
228
|
+
headers: { "x-vellum-client-id": "client-B" },
|
|
229
|
+
}),
|
|
230
|
+
).toThrow(ForbiddenError);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("ForbiddenError message names both submitting and expected client", () => {
|
|
234
|
+
const requestId = "req-cu-targeted-mismatch-msg";
|
|
235
|
+
registerPending(requestId, { targetClientId: "client-A" });
|
|
236
|
+
|
|
237
|
+
let caught: unknown;
|
|
238
|
+
try {
|
|
239
|
+
handleHostCuResult({
|
|
240
|
+
body: cuBody(requestId),
|
|
241
|
+
headers: { "x-vellum-client-id": "client-B" },
|
|
242
|
+
});
|
|
243
|
+
} catch (e) {
|
|
244
|
+
caught = e;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
expect(caught).toBeInstanceOf(ForbiddenError);
|
|
248
|
+
const msg = (caught as ForbiddenError).message;
|
|
249
|
+
expect(msg).toContain("client-B");
|
|
250
|
+
expect(msg).toContain("client-A");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("interaction is NOT consumed on 403 (pendingInteractions.get still returns it)", () => {
|
|
254
|
+
const requestId = "req-cu-targeted-mismatch-stays";
|
|
255
|
+
registerPending(requestId, { targetClientId: "client-A" });
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
handleHostCuResult({
|
|
259
|
+
body: cuBody(requestId),
|
|
260
|
+
headers: { "x-vellum-client-id": "client-B" },
|
|
261
|
+
});
|
|
262
|
+
} catch {
|
|
263
|
+
// expected
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
expect(resolvedIds).not.toContain(requestId);
|
|
267
|
+
expect(pendingStore.has(requestId)).toBe(true);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// ── 4. Untargeted — regression ────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
describe("untargeted request (no targetClientId)", () => {
|
|
274
|
+
test("accepts when no header is provided", async () => {
|
|
275
|
+
const requestId = "req-cu-untargeted-no-header";
|
|
276
|
+
registerPending(requestId);
|
|
277
|
+
|
|
278
|
+
const result = await handleHostCuResult({
|
|
279
|
+
body: cuBody(requestId),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(result).toEqual({ accepted: true });
|
|
283
|
+
expect(cuResolveSpy).toHaveLength(1);
|
|
284
|
+
expect(resolvedIds).toContain(requestId);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("accepts when header is present (header ignored for untargeted)", async () => {
|
|
288
|
+
const requestId = "req-cu-untargeted-with-header";
|
|
289
|
+
registerPending(requestId);
|
|
290
|
+
|
|
291
|
+
const result = await handleHostCuResult({
|
|
292
|
+
body: cuBody(requestId),
|
|
293
|
+
headers: { "x-vellum-client-id": "client-whatever" },
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(result).toEqual({ accepted: true });
|
|
297
|
+
expect(cuResolveSpy).toHaveLength(1);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
});
|
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { afterEach, describe, expect, test } from "bun:test";
|
|
4
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
import type { HostFileInput } from "../daemon/host-file-proxy.js";
|
|
7
|
+
import type { ToolExecutionResult } from "../tools/types.js";
|
|
8
|
+
|
|
9
|
+
// Mock HostFileProxy singleton so proxy delegation tests can control it.
|
|
10
|
+
let mockFileProxyAvailable = false;
|
|
11
|
+
let mockFileProxyRequestFn: (
|
|
12
|
+
input: HostFileInput,
|
|
13
|
+
conversationId: string,
|
|
14
|
+
signal?: AbortSignal,
|
|
15
|
+
) => Promise<ToolExecutionResult> = () => Promise.resolve({ content: "", isError: false });
|
|
16
|
+
|
|
17
|
+
mock.module("../daemon/host-file-proxy.js", () => ({
|
|
18
|
+
HostFileProxy: {
|
|
19
|
+
get instance() {
|
|
20
|
+
return {
|
|
21
|
+
isAvailable: () => mockFileProxyAvailable,
|
|
22
|
+
request: mockFileProxyRequestFn,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
5
27
|
|
|
6
28
|
import { hostFileEditTool } from "../tools/host-filesystem/edit.js";
|
|
7
29
|
import type { ToolContext } from "../tools/types.js";
|
|
@@ -20,6 +42,8 @@ afterEach(() => {
|
|
|
20
42
|
for (const dir of testDirs.splice(0)) {
|
|
21
43
|
rmSync(dir, { recursive: true, force: true });
|
|
22
44
|
}
|
|
45
|
+
mockFileProxyAvailable = false;
|
|
46
|
+
mockFileProxyRequestFn = () => Promise.resolve({ content: "", isError: false });
|
|
23
47
|
});
|
|
24
48
|
|
|
25
49
|
describe("host_file_edit tool", () => {
|
|
@@ -268,4 +292,26 @@ describe("host_file_edit tool", () => {
|
|
|
268
292
|
result.content.includes("Successfully edited"),
|
|
269
293
|
).toBe(true);
|
|
270
294
|
});
|
|
295
|
+
|
|
296
|
+
test("passes target_client_id to HostFileProxy.instance.request", async () => {
|
|
297
|
+
const capturedInputs: HostFileInput[] = [];
|
|
298
|
+
mockFileProxyAvailable = true;
|
|
299
|
+
mockFileProxyRequestFn = async (input) => {
|
|
300
|
+
capturedInputs.push(input);
|
|
301
|
+
return { content: "proxied edit", isError: false };
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
await hostFileEditTool.execute(
|
|
305
|
+
{
|
|
306
|
+
path: "/host/file.txt",
|
|
307
|
+
old_string: "old",
|
|
308
|
+
new_string: "new",
|
|
309
|
+
target_client_id: "client-x",
|
|
310
|
+
},
|
|
311
|
+
makeContext(),
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
expect(capturedInputs).toHaveLength(1);
|
|
315
|
+
expect(capturedInputs[0].targetClientId).toBe("client-x");
|
|
316
|
+
});
|
|
271
317
|
});
|