@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
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for HostFileProxy Phase 2 targetClientId behaviour.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Explicit targetClientId validation (valid, unknown, incapable)
|
|
6
|
+
* - Auto-resolve when exactly one host_file-capable client is connected
|
|
7
|
+
* - Untargeted broadcast when multiple capable clients are connected
|
|
8
|
+
* - targetClientId propagated into cancel messages (abort + dispose)
|
|
9
|
+
* - Timeout message includes clientId when resolvedTargetClientId is set
|
|
10
|
+
*/
|
|
11
|
+
import { afterEach, describe, expect, jest, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
const sentMessages: unknown[] = [];
|
|
14
|
+
const sentMessageOptions: unknown[] = [];
|
|
15
|
+
const resolvedInteractionIds: string[] = [];
|
|
16
|
+
let mockHasClient = false;
|
|
17
|
+
let mockCapableClients: Array<{ clientId: string; capabilities: string[] }> = [];
|
|
18
|
+
let mockClientRegistry: Map<string, { clientId: string; capabilities: string[] }> = new Map();
|
|
19
|
+
|
|
20
|
+
mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
21
|
+
broadcastMessage: (msg: unknown, _conversationId?: string, options?: unknown) => {
|
|
22
|
+
sentMessages.push(msg);
|
|
23
|
+
sentMessageOptions.push(options);
|
|
24
|
+
},
|
|
25
|
+
assistantEventHub: {
|
|
26
|
+
getMostRecentClientByCapability: (cap: string) =>
|
|
27
|
+
cap === "host_file" && mockHasClient ? { id: "mock-client" } : null,
|
|
28
|
+
listClientsByCapability: (_cap: string) => mockCapableClients,
|
|
29
|
+
getClientById: (clientId: string) => mockClientRegistry.get(clientId),
|
|
30
|
+
},
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const pendingInteractionMap = new Map<string, Record<string, unknown>>();
|
|
34
|
+
mock.module("../runtime/pending-interactions.js", () => ({
|
|
35
|
+
register: (requestId: string, interaction: Record<string, unknown>) => {
|
|
36
|
+
pendingInteractionMap.set(requestId, interaction);
|
|
37
|
+
},
|
|
38
|
+
resolve: (requestId: string) => {
|
|
39
|
+
const interaction = pendingInteractionMap.get(requestId);
|
|
40
|
+
pendingInteractionMap.delete(requestId);
|
|
41
|
+
resolvedInteractionIds.push(requestId);
|
|
42
|
+
return interaction;
|
|
43
|
+
},
|
|
44
|
+
get: (requestId: string) => pendingInteractionMap.get(requestId),
|
|
45
|
+
getByKind: (_kind: string) => Array.from(pendingInteractionMap.entries())
|
|
46
|
+
.filter(([, v]) => v.kind === _kind)
|
|
47
|
+
.map(([requestId, v]) => ({ requestId, ...v })),
|
|
48
|
+
getByConversation: () => [],
|
|
49
|
+
removeByConversation: () => {},
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
const { HostFileProxy } = await import("../daemon/host-file-proxy.js");
|
|
53
|
+
|
|
54
|
+
describe("HostFileProxy — targetClientId (Phase 2)", () => {
|
|
55
|
+
let proxy: InstanceType<typeof HostFileProxy>;
|
|
56
|
+
|
|
57
|
+
function setup() {
|
|
58
|
+
sentMessages.length = 0;
|
|
59
|
+
sentMessageOptions.length = 0;
|
|
60
|
+
resolvedInteractionIds.length = 0;
|
|
61
|
+
pendingInteractionMap.clear();
|
|
62
|
+
mockHasClient = false;
|
|
63
|
+
mockCapableClients = [];
|
|
64
|
+
mockClientRegistry = new Map();
|
|
65
|
+
proxy = new (HostFileProxy as any)();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function setupSingleClient(clientId = "client-1") {
|
|
69
|
+
const entry = { clientId, capabilities: ["host_file"] };
|
|
70
|
+
mockCapableClients = [entry];
|
|
71
|
+
mockClientRegistry.set(clientId, entry);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function setupMultipleClients(clientIds: string[]) {
|
|
75
|
+
mockCapableClients = clientIds.map((id) => ({
|
|
76
|
+
clientId: id,
|
|
77
|
+
capabilities: ["host_file"],
|
|
78
|
+
}));
|
|
79
|
+
for (const entry of mockCapableClients) {
|
|
80
|
+
mockClientRegistry.set(entry.clientId, entry);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
proxy?.dispose();
|
|
86
|
+
HostFileProxy.reset();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// ── Explicit targetClientId — valid ──────────────────────────────────
|
|
90
|
+
|
|
91
|
+
describe("explicit targetClientId — valid client with host_file", () => {
|
|
92
|
+
test("resolves to that client and broadcasts with targetClientId option", async () => {
|
|
93
|
+
setup();
|
|
94
|
+
setupSingleClient("client-mac");
|
|
95
|
+
// Also add a second client so explicit targeting is meaningful
|
|
96
|
+
const entry2 = { clientId: "client-linux", capabilities: ["host_file"] };
|
|
97
|
+
mockCapableClients.push(entry2);
|
|
98
|
+
mockClientRegistry.set("client-linux", entry2);
|
|
99
|
+
|
|
100
|
+
const resultPromise = proxy.request(
|
|
101
|
+
{
|
|
102
|
+
operation: "read",
|
|
103
|
+
path: "/home/user/notes.txt",
|
|
104
|
+
targetClientId: "client-mac",
|
|
105
|
+
},
|
|
106
|
+
"session-1",
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(sentMessages).toHaveLength(1);
|
|
110
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
111
|
+
expect(sent.type).toBe("host_file_request");
|
|
112
|
+
expect(sent.targetClientId).toBe("client-mac");
|
|
113
|
+
|
|
114
|
+
const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
|
|
115
|
+
expect(opts?.targetClientId).toBe("client-mac");
|
|
116
|
+
|
|
117
|
+
const requestId = sent.requestId as string;
|
|
118
|
+
proxy.resolve(requestId, { content: "file contents", isError: false });
|
|
119
|
+
|
|
120
|
+
const result = await resultPromise;
|
|
121
|
+
expect(result.isError).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// ── Explicit targetClientId — unknown client ─────────────────────────
|
|
126
|
+
|
|
127
|
+
describe("explicit targetClientId — unknown client", () => {
|
|
128
|
+
test("returns error result immediately without broadcasting", async () => {
|
|
129
|
+
setup();
|
|
130
|
+
setupSingleClient("client-mac");
|
|
131
|
+
|
|
132
|
+
const result = await proxy.request(
|
|
133
|
+
{
|
|
134
|
+
operation: "read",
|
|
135
|
+
path: "/tmp/file.txt",
|
|
136
|
+
targetClientId: "client-unknown",
|
|
137
|
+
},
|
|
138
|
+
"session-1",
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(result.isError).toBe(true);
|
|
142
|
+
expect(result.content).toContain("client-unknown");
|
|
143
|
+
expect(result.content).toContain("assistant clients list --capability host_file");
|
|
144
|
+
// No pending entry should have been created
|
|
145
|
+
expect(sentMessages).toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("does not create a pending entry for unknown client", async () => {
|
|
149
|
+
setup();
|
|
150
|
+
|
|
151
|
+
const result = await proxy.request(
|
|
152
|
+
{
|
|
153
|
+
operation: "write",
|
|
154
|
+
path: "/tmp/out.txt",
|
|
155
|
+
content: "data",
|
|
156
|
+
targetClientId: "client-ghost",
|
|
157
|
+
},
|
|
158
|
+
"session-1",
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(result.isError).toBe(true);
|
|
162
|
+
expect(sentMessages).toHaveLength(0);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ── Explicit targetClientId — incapable client ───────────────────────
|
|
167
|
+
|
|
168
|
+
describe("explicit targetClientId — connected but lacks host_file", () => {
|
|
169
|
+
test("returns error result immediately without broadcasting", async () => {
|
|
170
|
+
setup();
|
|
171
|
+
// Register a client that exists but does not have host_file
|
|
172
|
+
mockClientRegistry.set("client-no-file", {
|
|
173
|
+
clientId: "client-no-file",
|
|
174
|
+
capabilities: ["host_bash"],
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const result = await proxy.request(
|
|
178
|
+
{
|
|
179
|
+
operation: "read",
|
|
180
|
+
path: "/tmp/test.txt",
|
|
181
|
+
targetClientId: "client-no-file",
|
|
182
|
+
},
|
|
183
|
+
"session-1",
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
expect(result.isError).toBe(true);
|
|
187
|
+
expect(result.content).toContain("client-no-file");
|
|
188
|
+
expect(result.content).toContain("does not support host_file");
|
|
189
|
+
expect(sentMessages).toHaveLength(0);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ── Auto-resolve single capable client ───────────────────────────────
|
|
194
|
+
|
|
195
|
+
describe("auto-resolve single capable client", () => {
|
|
196
|
+
test("resolves target when exactly one host_file-capable client is connected", async () => {
|
|
197
|
+
setup();
|
|
198
|
+
setupSingleClient("client-solo");
|
|
199
|
+
|
|
200
|
+
const resultPromise = proxy.request(
|
|
201
|
+
{ operation: "read", path: "/tmp/file.txt" },
|
|
202
|
+
"session-1",
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
expect(sentMessages).toHaveLength(1);
|
|
206
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
207
|
+
expect(sent.targetClientId).toBe("client-solo");
|
|
208
|
+
|
|
209
|
+
const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
|
|
210
|
+
expect(opts?.targetClientId).toBe("client-solo");
|
|
211
|
+
|
|
212
|
+
const requestId = sent.requestId as string;
|
|
213
|
+
proxy.resolve(requestId, { content: "ok", isError: false });
|
|
214
|
+
|
|
215
|
+
const result = await resultPromise;
|
|
216
|
+
expect(result.isError).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ── No target — multiple capable clients ─────────────────────────────
|
|
221
|
+
|
|
222
|
+
describe("no explicit target — multiple capable clients", () => {
|
|
223
|
+
test("broadcasts without targetClientId (untargeted)", async () => {
|
|
224
|
+
setup();
|
|
225
|
+
setupMultipleClients(["client-1", "client-2"]);
|
|
226
|
+
|
|
227
|
+
const resultPromise = proxy.request(
|
|
228
|
+
{ operation: "read", path: "/tmp/file.txt" },
|
|
229
|
+
"session-1",
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(sentMessages).toHaveLength(1);
|
|
233
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
234
|
+
expect(sent.type).toBe("host_file_request");
|
|
235
|
+
expect(sent.targetClientId).toBeUndefined();
|
|
236
|
+
|
|
237
|
+
const opts = sentMessageOptions[0] as Record<string, unknown> | undefined;
|
|
238
|
+
expect(opts?.targetClientId).toBeUndefined();
|
|
239
|
+
|
|
240
|
+
const requestId = sent.requestId as string;
|
|
241
|
+
proxy.resolve(requestId, { content: "ok", isError: false });
|
|
242
|
+
|
|
243
|
+
const result = await resultPromise;
|
|
244
|
+
expect(result.isError).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ── targetClientId in cancel (abort signal) ──────────────────────────
|
|
249
|
+
|
|
250
|
+
describe("targetClientId in cancel — abort signal", () => {
|
|
251
|
+
test("cancel broadcast includes targetClientId when request was targeted", async () => {
|
|
252
|
+
setup();
|
|
253
|
+
setupSingleClient("client-abc");
|
|
254
|
+
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
const resultPromise = proxy.request(
|
|
257
|
+
{ operation: "read", path: "/tmp/file.txt" },
|
|
258
|
+
"session-1",
|
|
259
|
+
controller.signal,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
263
|
+
const requestId = sent.requestId as string;
|
|
264
|
+
expect(sent.targetClientId).toBe("client-abc");
|
|
265
|
+
|
|
266
|
+
controller.abort();
|
|
267
|
+
await resultPromise;
|
|
268
|
+
|
|
269
|
+
// Second message is the cancel
|
|
270
|
+
expect(sentMessages).toHaveLength(2);
|
|
271
|
+
const cancelMsg = sentMessages[1] as Record<string, unknown>;
|
|
272
|
+
expect(cancelMsg.type).toBe("host_file_cancel");
|
|
273
|
+
expect(cancelMsg.requestId).toBe(requestId);
|
|
274
|
+
expect(cancelMsg.targetClientId).toBe("client-abc");
|
|
275
|
+
|
|
276
|
+
const cancelOpts = sentMessageOptions[1] as Record<string, unknown> | undefined;
|
|
277
|
+
expect(cancelOpts?.targetClientId).toBe("client-abc");
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// ── targetClientId in cancel (dispose) ───────────────────────────────
|
|
282
|
+
|
|
283
|
+
describe("targetClientId in cancel — dispose", () => {
|
|
284
|
+
test("dispose cancel broadcast includes targetClientId for targeted request", () => {
|
|
285
|
+
setup();
|
|
286
|
+
setupSingleClient("client-xyz");
|
|
287
|
+
|
|
288
|
+
const p = proxy.request(
|
|
289
|
+
{ operation: "read", path: "/tmp/file.txt" },
|
|
290
|
+
"session-1",
|
|
291
|
+
);
|
|
292
|
+
p.catch(() => {}); // expected rejection on dispose
|
|
293
|
+
|
|
294
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
295
|
+
expect(sent.targetClientId).toBe("client-xyz");
|
|
296
|
+
const requestId = sent.requestId as string;
|
|
297
|
+
|
|
298
|
+
proxy.dispose();
|
|
299
|
+
|
|
300
|
+
const cancelMessages = sentMessages
|
|
301
|
+
.slice(1)
|
|
302
|
+
.filter(
|
|
303
|
+
(m) => (m as Record<string, unknown>).type === "host_file_cancel",
|
|
304
|
+
) as Array<Record<string, unknown>>;
|
|
305
|
+
expect(cancelMessages).toHaveLength(1);
|
|
306
|
+
expect(cancelMessages[0].requestId).toBe(requestId);
|
|
307
|
+
expect(cancelMessages[0].targetClientId).toBe("client-xyz");
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// ── Timeout message includes clientId ────────────────────────────────
|
|
312
|
+
|
|
313
|
+
describe("timeout message includes clientId", () => {
|
|
314
|
+
test("timeout resolve message mentions resolvedTargetClientId", async () => {
|
|
315
|
+
setup();
|
|
316
|
+
setupSingleClient("client-mac");
|
|
317
|
+
|
|
318
|
+
jest.useFakeTimers();
|
|
319
|
+
try {
|
|
320
|
+
const resultPromise = proxy.request(
|
|
321
|
+
{ operation: "read", path: "/tmp/slow.txt" },
|
|
322
|
+
"session-1",
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const sent = sentMessages[0] as Record<string, unknown>;
|
|
326
|
+
expect(sent.targetClientId).toBe("client-mac");
|
|
327
|
+
|
|
328
|
+
// Advance past the 30s internal timeout
|
|
329
|
+
jest.advanceTimersByTime(31 * 1000);
|
|
330
|
+
|
|
331
|
+
const result = await resultPromise;
|
|
332
|
+
expect(result.isError).toBe(true);
|
|
333
|
+
expect(result.content).toContain("client-mac");
|
|
334
|
+
} finally {
|
|
335
|
+
jest.useRealTimers();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
});
|
|
@@ -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", () => ({
|
|
@@ -9,20 +8,16 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
|
9
8
|
assistantEventHub: {
|
|
10
9
|
getMostRecentClientByCapability: (cap: string) =>
|
|
11
10
|
cap === "host_file" && mockHasClient ? { id: "mock-client" } : null,
|
|
11
|
+
listClientsByCapability: (cap: string) =>
|
|
12
|
+
cap === "host_file" && mockHasClient
|
|
13
|
+
? [{ clientId: "mock-client", capabilities: ["host_file"] }]
|
|
14
|
+
: [],
|
|
15
|
+
getClientById: (_id: string) => undefined,
|
|
12
16
|
},
|
|
13
17
|
}));
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
resolvedInteractionIds.push(requestId);
|
|
18
|
-
return undefined;
|
|
19
|
-
},
|
|
20
|
-
get: () => undefined,
|
|
21
|
-
getByKind: () => [],
|
|
22
|
-
getByConversation: () => [],
|
|
23
|
-
removeByConversation: () => {},
|
|
24
|
-
}));
|
|
25
|
-
|
|
19
|
+
// Use the REAL pending-interactions module — the proxy self-registers here.
|
|
20
|
+
const pendingInteractions = await import("../runtime/pending-interactions.js");
|
|
26
21
|
const { HostFileProxy } = await import("../daemon/host-file-proxy.js");
|
|
27
22
|
|
|
28
23
|
// Minimal PNG header
|
|
@@ -36,14 +31,15 @@ describe("HostFileProxy", () => {
|
|
|
36
31
|
|
|
37
32
|
function setup() {
|
|
38
33
|
sentMessages.length = 0;
|
|
39
|
-
resolvedInteractionIds.length = 0;
|
|
40
34
|
mockHasClient = false;
|
|
35
|
+
pendingInteractions.clear();
|
|
41
36
|
proxy = new (HostFileProxy as any)();
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
afterEach(() => {
|
|
45
40
|
proxy?.dispose();
|
|
46
41
|
HostFileProxy.reset();
|
|
42
|
+
pendingInteractions.clear();
|
|
47
43
|
});
|
|
48
44
|
|
|
49
45
|
describe("request/resolve lifecycle (happy path)", () => {
|
|
@@ -68,7 +64,7 @@ describe("HostFileProxy", () => {
|
|
|
68
64
|
expect(typeof sent.requestId).toBe("string");
|
|
69
65
|
|
|
70
66
|
const requestId = sent.requestId as string;
|
|
71
|
-
expect(
|
|
67
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
72
68
|
|
|
73
69
|
// Simulate client response
|
|
74
70
|
proxy.resolve(requestId, {
|
|
@@ -79,7 +75,7 @@ describe("HostFileProxy", () => {
|
|
|
79
75
|
const result = await resultPromise;
|
|
80
76
|
expect(result.content).toBe("file contents here");
|
|
81
77
|
expect(result.isError).toBe(false);
|
|
82
|
-
expect(
|
|
78
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
83
79
|
});
|
|
84
80
|
|
|
85
81
|
test("resolves error responses correctly", async () => {
|
|
@@ -208,7 +204,7 @@ describe("HostFileProxy", () => {
|
|
|
208
204
|
|
|
209
205
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
210
206
|
const requestId = sent.requestId as string;
|
|
211
|
-
expect(
|
|
207
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
212
208
|
|
|
213
209
|
// Resolve to avoid test hanging (actual 30s timeout too long for test)
|
|
214
210
|
proxy.resolve(requestId, {
|
|
@@ -236,14 +232,14 @@ describe("HostFileProxy", () => {
|
|
|
236
232
|
|
|
237
233
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
238
234
|
const requestId = sent.requestId as string;
|
|
239
|
-
expect(
|
|
235
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
240
236
|
|
|
241
237
|
controller.abort();
|
|
242
238
|
|
|
243
239
|
const result = await resultPromise;
|
|
244
240
|
expect(result.content).toBe("Aborted");
|
|
245
241
|
expect(result.isError).toBe(true);
|
|
246
|
-
expect(
|
|
242
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
247
243
|
});
|
|
248
244
|
|
|
249
245
|
test("sends host_file_cancel to client on abort", async () => {
|
|
@@ -321,11 +317,11 @@ describe("HostFileProxy", () => {
|
|
|
321
317
|
|
|
322
318
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
323
319
|
const requestId = sent.requestId as string;
|
|
324
|
-
expect(
|
|
320
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
325
321
|
|
|
326
322
|
proxy.dispose();
|
|
327
323
|
|
|
328
|
-
expect(
|
|
324
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
329
325
|
expect(resultPromise).rejects.toThrow("Host file proxy disposed");
|
|
330
326
|
});
|
|
331
327
|
|
|
@@ -389,7 +385,7 @@ describe("HostFileProxy", () => {
|
|
|
389
385
|
isError: false,
|
|
390
386
|
});
|
|
391
387
|
|
|
392
|
-
expect(
|
|
388
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
393
389
|
});
|
|
394
390
|
});
|
|
395
391
|
|
|
@@ -457,7 +453,10 @@ describe("HostFileProxy", () => {
|
|
|
457
453
|
|
|
458
454
|
const requestId = (sentMessages[0] as Record<string, unknown>)
|
|
459
455
|
.requestId as string;
|
|
460
|
-
proxy.resolve(requestId, {
|
|
456
|
+
proxy.resolve(requestId, {
|
|
457
|
+
content: "file contents",
|
|
458
|
+
isError: false,
|
|
459
|
+
});
|
|
461
460
|
await resultPromise;
|
|
462
461
|
|
|
463
462
|
// Listener is detached after normal completion.
|
|
@@ -488,7 +487,7 @@ describe("HostFileProxy", () => {
|
|
|
488
487
|
|
|
489
488
|
const requestId = (sentMessages[0] as Record<string, unknown>)
|
|
490
489
|
.requestId as string;
|
|
491
|
-
expect(
|
|
490
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
492
491
|
|
|
493
492
|
// Advance past the 30s internal timeout.
|
|
494
493
|
jest.advanceTimersByTime(31 * 1000);
|
|
@@ -496,7 +495,7 @@ describe("HostFileProxy", () => {
|
|
|
496
495
|
const result = await resultPromise;
|
|
497
496
|
expect(result.isError).toBe(true);
|
|
498
497
|
expect(result.content).toContain("Host file proxy timed out");
|
|
499
|
-
expect(
|
|
498
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
500
499
|
|
|
501
500
|
// Listener is detached after the timer fires.
|
|
502
501
|
expect(spy.removeCalls).toEqual(["abort"]);
|
|
@@ -510,8 +509,8 @@ describe("HostFileProxy", () => {
|
|
|
510
509
|
});
|
|
511
510
|
});
|
|
512
511
|
|
|
513
|
-
describe("pendingInteractions
|
|
514
|
-
test("
|
|
512
|
+
describe("pendingInteractions cleanup", () => {
|
|
513
|
+
test("cleans up on abort", async () => {
|
|
515
514
|
setup();
|
|
516
515
|
|
|
517
516
|
const controller = new AbortController();
|
|
@@ -526,29 +525,23 @@ describe("HostFileProxy", () => {
|
|
|
526
525
|
|
|
527
526
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
528
527
|
const requestId = sent.requestId as string;
|
|
528
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
529
529
|
|
|
530
530
|
controller.abort();
|
|
531
531
|
await resultPromise;
|
|
532
532
|
|
|
533
|
-
expect(
|
|
533
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
534
534
|
});
|
|
535
535
|
|
|
536
|
-
test("
|
|
536
|
+
test("cleans up for each pending request on dispose", () => {
|
|
537
537
|
setup();
|
|
538
538
|
|
|
539
|
-
// Create two pending requests and catch rejections from dispose
|
|
540
539
|
const p1 = proxy.request(
|
|
541
|
-
{
|
|
542
|
-
operation: "read",
|
|
543
|
-
path: "/tmp/a.txt",
|
|
544
|
-
},
|
|
540
|
+
{ operation: "read", path: "/tmp/a.txt" },
|
|
545
541
|
"session-1",
|
|
546
542
|
);
|
|
547
543
|
const p2 = proxy.request(
|
|
548
|
-
{
|
|
549
|
-
operation: "read",
|
|
550
|
-
path: "/tmp/b.txt",
|
|
551
|
-
},
|
|
544
|
+
{ operation: "read", path: "/tmp/b.txt" },
|
|
552
545
|
"session-1",
|
|
553
546
|
);
|
|
554
547
|
p1.catch(() => {}); // Expected rejection on dispose
|
|
@@ -558,15 +551,16 @@ describe("HostFileProxy", () => {
|
|
|
558
551
|
(m) => m.requestId as string,
|
|
559
552
|
);
|
|
560
553
|
expect(ids).toHaveLength(2);
|
|
554
|
+
expect(pendingInteractions.get(ids[0])).toBeDefined();
|
|
555
|
+
expect(pendingInteractions.get(ids[1])).toBeDefined();
|
|
561
556
|
|
|
562
557
|
proxy.dispose();
|
|
563
558
|
|
|
564
|
-
expect(
|
|
565
|
-
expect(
|
|
566
|
-
expect(resolvedInteractionIds).toContain(ids[1]);
|
|
559
|
+
expect(pendingInteractions.get(ids[0])).toBeUndefined();
|
|
560
|
+
expect(pendingInteractions.get(ids[1])).toBeUndefined();
|
|
567
561
|
});
|
|
568
562
|
|
|
569
|
-
test("
|
|
563
|
+
test("cleans up on normal client-initiated resolveResult", async () => {
|
|
570
564
|
setup();
|
|
571
565
|
|
|
572
566
|
const resultPromise = proxy.request(
|
|
@@ -579,15 +573,15 @@ describe("HostFileProxy", () => {
|
|
|
579
573
|
|
|
580
574
|
const sent = sentMessages[0] as Record<string, unknown>;
|
|
581
575
|
const requestId = sent.requestId as string;
|
|
576
|
+
expect(pendingInteractions.get(requestId)).toBeDefined();
|
|
582
577
|
|
|
583
|
-
// Normal resolve from client — should NOT trigger pendingInteractions.resolve
|
|
584
578
|
proxy.resolve(requestId, {
|
|
585
579
|
content: "file contents",
|
|
586
580
|
isError: false,
|
|
587
581
|
});
|
|
588
582
|
|
|
589
583
|
await resultPromise;
|
|
590
|
-
expect(
|
|
584
|
+
expect(pendingInteractions.get(requestId)).toBeUndefined();
|
|
591
585
|
});
|
|
592
586
|
});
|
|
593
587
|
});
|
|
@@ -304,4 +304,21 @@ describe("host_file_read image support", () => {
|
|
|
304
304
|
expect(result.content).toContain("2 second line");
|
|
305
305
|
expect((result as any).contentBlocks).toBeUndefined();
|
|
306
306
|
});
|
|
307
|
+
|
|
308
|
+
test("passes target_client_id to HostFileProxy.instance.request", async () => {
|
|
309
|
+
const capturedInputs: HostFileInput[] = [];
|
|
310
|
+
mockFileProxyAvailable = true;
|
|
311
|
+
mockFileProxyRequestFn = async (input) => {
|
|
312
|
+
capturedInputs.push(input);
|
|
313
|
+
return { content: "proxied", isError: false };
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
await hostFileReadTool.execute(
|
|
317
|
+
{ path: "/host/notes.txt", target_client_id: "client-x" },
|
|
318
|
+
makeContext(),
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
expect(capturedInputs).toHaveLength(1);
|
|
322
|
+
expect(capturedInputs[0].targetClientId).toBe("client-x");
|
|
323
|
+
});
|
|
307
324
|
});
|