@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,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Head-of-line repro for the per-lane scheduler in `runMemoryJobsOnce`.
|
|
3
|
+
*
|
|
4
|
+
* Before this fix, all non-embed jobs ran through a single bounded worker
|
|
5
|
+
* pool, so a long-running `graph_consolidate` LLM call would pin every slot
|
|
6
|
+
* and starve fast-lane jobs (e.g. `embed_concept_page` consolidation pages)
|
|
7
|
+
* for the duration of that call.
|
|
8
|
+
*
|
|
9
|
+
* The new scheduler runs slow / fast / embed lanes in parallel pools, each
|
|
10
|
+
* with its own concurrency budget. This test enqueues a wave of slow LLM
|
|
11
|
+
* jobs alongside fast jobs and asserts that every fast job completes before
|
|
12
|
+
* any slow job's promise resolves — proving the lanes are truly independent.
|
|
13
|
+
*/
|
|
14
|
+
import { beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
|
+
|
|
16
|
+
import { eq } from "drizzle-orm";
|
|
17
|
+
|
|
18
|
+
import { DEFAULT_CONFIG } from "../config/defaults.js";
|
|
19
|
+
import type { AssistantConfig } from "../config/types.js";
|
|
20
|
+
|
|
21
|
+
// ── Mocks (must precede imports of tested module) ──────────────────
|
|
22
|
+
|
|
23
|
+
mock.module("../util/logger.js", () => ({
|
|
24
|
+
getLogger: () =>
|
|
25
|
+
new Proxy({} as Record<string, unknown>, {
|
|
26
|
+
get: () => () => {},
|
|
27
|
+
}),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
// Per-lane caps: 1 slow slot (so only 1 of the 5 enqueued slow jobs runs in
|
|
31
|
+
// this tick) and a generous fast cap so every fast job both gets claimed
|
|
32
|
+
// and gets a slot in the lane pool. The OLD shared-pool scheduler — which
|
|
33
|
+
// claimed jobs without lane awareness and ran them through a single
|
|
34
|
+
// workerConcurrency-sized pool — would pin its slots on the first claimed
|
|
35
|
+
// slow jobs and force the fast jobs to queue behind a 200ms LLM call.
|
|
36
|
+
const TEST_CONFIG: AssistantConfig = {
|
|
37
|
+
...DEFAULT_CONFIG,
|
|
38
|
+
memory: {
|
|
39
|
+
...DEFAULT_CONFIG.memory,
|
|
40
|
+
enabled: true,
|
|
41
|
+
jobs: {
|
|
42
|
+
...DEFAULT_CONFIG.memory.jobs,
|
|
43
|
+
slowLlmConcurrency: 1,
|
|
44
|
+
fastConcurrency: 5,
|
|
45
|
+
embedConcurrency: 1,
|
|
46
|
+
workerConcurrency: 2,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
mock.module("../config/loader.js", () => ({
|
|
52
|
+
getConfig: () => TEST_CONFIG,
|
|
53
|
+
loadConfig: () => TEST_CONFIG,
|
|
54
|
+
invalidateConfigCache: () => {},
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
// ── Track timestamps so we can assert ordering ─────────────────────
|
|
58
|
+
|
|
59
|
+
type CompletionRecord = {
|
|
60
|
+
type: string;
|
|
61
|
+
conversationId: string;
|
|
62
|
+
completedAt: number;
|
|
63
|
+
};
|
|
64
|
+
const completions: CompletionRecord[] = [];
|
|
65
|
+
|
|
66
|
+
// Slow-lane handler: blocks for SLOW_DELAY_MS. The test asserts every fast
|
|
67
|
+
// job completes before any slow job — a single 200ms window is plenty.
|
|
68
|
+
const SLOW_DELAY_MS = 200;
|
|
69
|
+
|
|
70
|
+
mock.module("../memory/graph/consolidation.js", () => ({
|
|
71
|
+
runConsolidation: async (
|
|
72
|
+
scopeId: string,
|
|
73
|
+
): Promise<{
|
|
74
|
+
totalUpdated: number;
|
|
75
|
+
totalDeleted: number;
|
|
76
|
+
totalMergeEdges: number;
|
|
77
|
+
}> => {
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, SLOW_DELAY_MS));
|
|
79
|
+
completions.push({
|
|
80
|
+
type: "graph_consolidate",
|
|
81
|
+
conversationId: scopeId,
|
|
82
|
+
completedAt: Date.now(),
|
|
83
|
+
});
|
|
84
|
+
return { totalUpdated: 0, totalDeleted: 0, totalMergeEdges: 0 };
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
// Fast-lane handler: resolves on the next microtask. The test fires this
|
|
89
|
+
// many times in parallel; nothing should block.
|
|
90
|
+
mock.module("../memory/jobs/embed-concept-page.js", () => ({
|
|
91
|
+
embedConceptPageJob: async (job: {
|
|
92
|
+
payload: { slug?: string };
|
|
93
|
+
}): Promise<void> => {
|
|
94
|
+
completions.push({
|
|
95
|
+
type: "embed_concept_page",
|
|
96
|
+
conversationId: job.payload.slug ?? "",
|
|
97
|
+
completedAt: Date.now(),
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
// Stub remaining heavy boundaries that we never exercise but that get pulled
|
|
103
|
+
// in transitively through jobs-worker's eager imports. These aren't strictly
|
|
104
|
+
// required if the host machine can resolve them, but mocking them keeps the
|
|
105
|
+
// test hermetic and fast under `bun test`.
|
|
106
|
+
mock.module("../memory/db-maintenance.js", () => ({
|
|
107
|
+
maybeRunDbMaintenance: () => {},
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
import { getDb } from "../memory/db-connection.js";
|
|
111
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
112
|
+
import { enqueueMemoryJob } from "../memory/jobs-store.js";
|
|
113
|
+
import { runMemoryJobsOnce } from "../memory/jobs-worker.js";
|
|
114
|
+
import { _resetQdrantBreaker } from "../memory/qdrant-circuit-breaker.js";
|
|
115
|
+
import { memoryJobs } from "../memory/schema.js";
|
|
116
|
+
|
|
117
|
+
describe("memory jobs worker lane scheduling", () => {
|
|
118
|
+
beforeAll(() => {
|
|
119
|
+
initializeDb();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
const db = getDb();
|
|
124
|
+
db.run("DELETE FROM memory_jobs");
|
|
125
|
+
completions.length = 0;
|
|
126
|
+
_resetQdrantBreaker();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("fast lane completes before slow lane releases its slot", async () => {
|
|
130
|
+
// 5 slow `graph_consolidate` jobs across distinct scopes (so they would
|
|
131
|
+
// serialize behind a single shared pool) plus 5 fast `embed_concept_page`
|
|
132
|
+
// jobs across distinct slugs.
|
|
133
|
+
for (let i = 0; i < 5; i++) {
|
|
134
|
+
enqueueMemoryJob("graph_consolidate", { scopeId: `slow-${i}` });
|
|
135
|
+
}
|
|
136
|
+
for (let i = 0; i < 5; i++) {
|
|
137
|
+
enqueueMemoryJob("embed_concept_page", { slug: `fast-${i}` });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await runMemoryJobsOnce();
|
|
141
|
+
|
|
142
|
+
const fastDone = completions.filter((c) => c.type === "embed_concept_page");
|
|
143
|
+
const slowDone = completions.filter((c) => c.type === "graph_consolidate");
|
|
144
|
+
|
|
145
|
+
// Slow lane is capped at 1 in this test, so only 1 slow job ran in this
|
|
146
|
+
// tick. Fast lane has cap 2, but with 5 fast jobs it runs all 5 because
|
|
147
|
+
// each handler resolves on the next microtask.
|
|
148
|
+
expect(fastDone).toHaveLength(5);
|
|
149
|
+
expect(slowDone).toHaveLength(1);
|
|
150
|
+
|
|
151
|
+
// Head-of-line guarantee: every fast completion timestamp must precede
|
|
152
|
+
// the (single) slow completion timestamp. Under the old shared pool with
|
|
153
|
+
// workerConcurrency=2 and 1 slow slot occupied, this still held only if
|
|
154
|
+
// a fast slot freed up first — but with 2 slow jobs in flight (the old
|
|
155
|
+
// claim path would have claimed multiple slow jobs into the shared pool)
|
|
156
|
+
// both slots would be pinned for SLOW_DELAY_MS and fast work would queue
|
|
157
|
+
// behind. With the lane scheduler, fast work has its own pool.
|
|
158
|
+
const earliestSlow = Math.min(...slowDone.map((c) => c.completedAt));
|
|
159
|
+
for (const fast of fastDone) {
|
|
160
|
+
expect(fast.completedAt).toBeLessThan(earliestSlow);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Sanity: exactly 1 of the 5 enqueued slow jobs reached `completed` (the
|
|
164
|
+
// slow lane's per-tick budget). The other 4 are still pending and will be
|
|
165
|
+
// picked up on subsequent ticks. (FIFO ordering inside the slow lane is
|
|
166
|
+
// covered separately in jobs-store-qdrant-breaker.test.ts.)
|
|
167
|
+
//
|
|
168
|
+
// Note: a sixth `graph_consolidate` row may also appear here — the
|
|
169
|
+
// `maybeEnqueueGraphMaintenanceJobs` tail of `runMemoryJobsOnce` enqueues
|
|
170
|
+
// its own maintenance job whose checkpoint is missing in this fresh DB.
|
|
171
|
+
// That row is irrelevant; we only care about the completed-vs-pending
|
|
172
|
+
// counts of jobs we explicitly seeded.
|
|
173
|
+
const completedSlow = countSlowByStatus("completed");
|
|
174
|
+
expect(completedSlow).toBe(1);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
function countSlowByStatus(
|
|
179
|
+
status: "pending" | "running" | "completed",
|
|
180
|
+
): number {
|
|
181
|
+
const db = getDb();
|
|
182
|
+
return db
|
|
183
|
+
.select()
|
|
184
|
+
.from(memoryJobs)
|
|
185
|
+
.where(eq(memoryJobs.type, "graph_consolidate"))
|
|
186
|
+
.all()
|
|
187
|
+
.filter((row) => row.status === status).length;
|
|
188
|
+
}
|
|
@@ -256,6 +256,8 @@ function createValidVBundle(
|
|
|
256
256
|
bundle_id: string;
|
|
257
257
|
origin_mode: "managed" | "self-hosted-remote" | "self-hosted-local";
|
|
258
258
|
secrets_redacted: boolean;
|
|
259
|
+
min_runtime_version: string;
|
|
260
|
+
max_runtime_version: string | null;
|
|
259
261
|
}>,
|
|
260
262
|
): Uint8Array {
|
|
261
263
|
const dbData = new Uint8Array([0x53, 0x51, 0x4c, 0x69, 0x74, 0x65]);
|
|
@@ -296,8 +298,11 @@ function createValidVBundle(
|
|
|
296
298
|
mode: overrides?.origin_mode ?? "self-hosted-local",
|
|
297
299
|
},
|
|
298
300
|
compatibility: {
|
|
299
|
-
min_runtime_version: "0.0.0-test",
|
|
300
|
-
max_runtime_version:
|
|
301
|
+
min_runtime_version: overrides?.min_runtime_version ?? "0.0.0-test",
|
|
302
|
+
max_runtime_version:
|
|
303
|
+
overrides?.max_runtime_version === undefined
|
|
304
|
+
? null
|
|
305
|
+
: overrides.max_runtime_version,
|
|
301
306
|
},
|
|
302
307
|
contents,
|
|
303
308
|
checksum: "",
|
|
@@ -652,6 +657,107 @@ describe("handleMigrationImport — validation failures", () => {
|
|
|
652
657
|
});
|
|
653
658
|
});
|
|
654
659
|
|
|
660
|
+
// ---------------------------------------------------------------------------
|
|
661
|
+
// HTTP handler tests: version_incompatible (Codex P2 regression)
|
|
662
|
+
//
|
|
663
|
+
// Pin: a bundle whose min_runtime_version exceeds APP_VERSION must surface as
|
|
664
|
+
// a 4xx user-actionable response (422 Unprocessable Entity), not a 500
|
|
665
|
+
// InternalError. Body mirrors the platform's PR #5470 response shape so
|
|
666
|
+
// clients can render the same UX regardless of which gate (platform or
|
|
667
|
+
// runtime) rejected the bundle.
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
|
|
670
|
+
describe("handleMigrationImport — version_incompatible", () => {
|
|
671
|
+
test("incompatible bundle returns 422 with structured body, not 500", async () => {
|
|
672
|
+
const vbundle = createValidVBundle(undefined, {
|
|
673
|
+
min_runtime_version: "99.0.0",
|
|
674
|
+
});
|
|
675
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
676
|
+
method: "POST",
|
|
677
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
678
|
+
body: toArrayBuffer(vbundle),
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const res = await callHandler(handleMigrationImport, req);
|
|
682
|
+
const body = (await res.json()) as {
|
|
683
|
+
error: {
|
|
684
|
+
code: string;
|
|
685
|
+
message: string;
|
|
686
|
+
details?: {
|
|
687
|
+
reason: string;
|
|
688
|
+
bundle_compat: {
|
|
689
|
+
min_runtime_version: string;
|
|
690
|
+
max_runtime_version: string | null;
|
|
691
|
+
};
|
|
692
|
+
runtime_version: string;
|
|
693
|
+
};
|
|
694
|
+
};
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
expect(res.status).toBe(422);
|
|
698
|
+
expect(body.error.code).toBe("UNPROCESSABLE_ENTITY");
|
|
699
|
+
expect(body.error.message).toContain("99.0.0");
|
|
700
|
+
expect(body.error.details).toBeDefined();
|
|
701
|
+
expect(body.error.details!.reason).toBe("version_incompatible");
|
|
702
|
+
expect(body.error.details!.bundle_compat.min_runtime_version).toBe(
|
|
703
|
+
"99.0.0",
|
|
704
|
+
);
|
|
705
|
+
expect(body.error.details!.bundle_compat.max_runtime_version).toBeNull();
|
|
706
|
+
expect(typeof body.error.details!.runtime_version).toBe("string");
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
test("incompatible bundle does not modify disk", async () => {
|
|
710
|
+
const originalDb = new Uint8Array(readFileSync(testDbPath));
|
|
711
|
+
const originalConfig = readFileSync(testConfigPath, "utf8");
|
|
712
|
+
|
|
713
|
+
const vbundle = createValidVBundle(undefined, {
|
|
714
|
+
min_runtime_version: "99.0.0",
|
|
715
|
+
});
|
|
716
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
717
|
+
method: "POST",
|
|
718
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
719
|
+
body: toArrayBuffer(vbundle),
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
await callHandler(handleMigrationImport, req);
|
|
723
|
+
|
|
724
|
+
const currentDb = new Uint8Array(readFileSync(testDbPath));
|
|
725
|
+
const currentConfig = readFileSync(testConfigPath, "utf8");
|
|
726
|
+
|
|
727
|
+
expect(currentDb).toEqual(originalDb);
|
|
728
|
+
expect(currentConfig).toBe(originalConfig);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// Regression: the route handler pre-checks runtime-version compat using
|
|
732
|
+
// `validation.manifest.compatibility` before calling `resetDb()` and
|
|
733
|
+
// `commitImport()`. If a future refactor reorders the close to come
|
|
734
|
+
// before the gate, an incompatible bundle would still 422 (commitImport
|
|
735
|
+
// has its own defense-in-depth gate) but it would unnecessarily close
|
|
736
|
+
// and reopen the live SQLite singleton on every rejected import.
|
|
737
|
+
//
|
|
738
|
+
// We can't easily spy on `resetDb` here without mocking `db-connection.js`
|
|
739
|
+
// module-wide (which other tests in this file rely on for real). The
|
|
740
|
+
// semantic regression — disk unchanged + 422 — is covered by the two
|
|
741
|
+
// tests above. This test is a marker so future readers know the pre-
|
|
742
|
+
// check intent; if it ever starts failing, recheck `handleMigrationImport`
|
|
743
|
+
// ordering against the comment at line 861-862 ("Validate the bundle
|
|
744
|
+
// before closing the DB to avoid an unnecessary close/reopen cycle when
|
|
745
|
+
// the bundle is invalid").
|
|
746
|
+
test("incompatible bundle short-circuits before resetDb (intent marker)", async () => {
|
|
747
|
+
const vbundle = createValidVBundle(undefined, {
|
|
748
|
+
min_runtime_version: "99.0.0",
|
|
749
|
+
});
|
|
750
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
751
|
+
method: "POST",
|
|
752
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
753
|
+
body: toArrayBuffer(vbundle),
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const res = await callHandler(handleMigrationImport, req);
|
|
757
|
+
expect(res.status).toBe(422);
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
|
|
655
761
|
// ---------------------------------------------------------------------------
|
|
656
762
|
// commitImport unit tests
|
|
657
763
|
// ---------------------------------------------------------------------------
|
|
@@ -38,6 +38,7 @@ import { mock } from "bun:test";
|
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
39
|
|
|
40
40
|
/** IPC result the fake gateway will return (keyed by method name). */
|
|
41
|
+
|
|
41
42
|
let ipcResults: Record<string, unknown> = {};
|
|
42
43
|
|
|
43
44
|
/** Whether the fake ipcCall should simulate a connection error. */
|
|
@@ -380,12 +380,10 @@ mock.module("../config/loader.js", () => ({
|
|
|
380
380
|
getConfig: () => mockGetConfig(),
|
|
381
381
|
getConfigReadOnly: () => mockGetConfig(),
|
|
382
382
|
loadConfig: () => mockGetConfig(),
|
|
383
|
-
saveConfig: () => {},
|
|
384
383
|
invalidateConfigCache: () => {},
|
|
385
384
|
loadRawConfig: () => ({}),
|
|
386
385
|
saveRawConfig: () => {},
|
|
387
386
|
applyNestedDefaults: (c: unknown) => c,
|
|
388
|
-
deepMergeMissing: (a: unknown) => a,
|
|
389
387
|
deepMergeOverwrite: (a: unknown) => a,
|
|
390
388
|
mergeDefaultWorkspaceConfig: () => {},
|
|
391
389
|
getNestedValue: () => undefined,
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies that known-pattern secrets are redacted before being written to
|
|
3
|
+
* durable conversation storage while the live model history (pendingToolResults,
|
|
4
|
+
* raw message content) is left untouched.
|
|
5
|
+
*
|
|
6
|
+
* Touch points under test:
|
|
7
|
+
* - Tool result content blocks persisted by handleMessageComplete
|
|
8
|
+
* - Assistant message text blocks persisted by handleMessageComplete
|
|
9
|
+
*/
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
// ── Shared mock plumbing (must precede module-under-test imports) ──────────
|
|
13
|
+
|
|
14
|
+
mock.module("../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
mock.module("../config/loader.js", () => ({
|
|
22
|
+
getConfig: () => ({
|
|
23
|
+
skills: {
|
|
24
|
+
entries: {},
|
|
25
|
+
load: { extraDirs: [], watch: true, watchDebounceMs: 250 },
|
|
26
|
+
install: { nodeManager: "npm" },
|
|
27
|
+
allowBundled: null,
|
|
28
|
+
remoteProviders: {
|
|
29
|
+
skillssh: { enabled: true },
|
|
30
|
+
clawhub: { enabled: true },
|
|
31
|
+
},
|
|
32
|
+
remotePolicy: {
|
|
33
|
+
blockSuspicious: true,
|
|
34
|
+
blockMalware: true,
|
|
35
|
+
maxSkillsShRisk: "medium",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
loadConfig: () => ({}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
interface AddMessageCall {
|
|
43
|
+
conversationId: string;
|
|
44
|
+
role: string;
|
|
45
|
+
content: string;
|
|
46
|
+
metadata?: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
const addMessageCalls: AddMessageCall[] = [];
|
|
49
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
50
|
+
addMessage: (
|
|
51
|
+
conversationId: string,
|
|
52
|
+
role: string,
|
|
53
|
+
content: string,
|
|
54
|
+
metadata?: Record<string, unknown>,
|
|
55
|
+
) => {
|
|
56
|
+
addMessageCalls.push({ conversationId, role, content, metadata });
|
|
57
|
+
return { id: `mock-msg-${addMessageCalls.length}` };
|
|
58
|
+
},
|
|
59
|
+
getConversation: () => null,
|
|
60
|
+
getMessageById: () => null,
|
|
61
|
+
updateMessageContent: () => {},
|
|
62
|
+
provenanceFromTrustContext: () => ({}),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
66
|
+
recordRequestLog: () => {},
|
|
67
|
+
backfillMessageIdOnLogs: () => {},
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
mock.module("../memory/memory-recall-log-store.js", () => ({
|
|
71
|
+
backfillMemoryRecallLogMessageId: () => {},
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
mock.module("../memory/conversation-disk-view.js", () => ({
|
|
75
|
+
syncMessageToDisk: () => {},
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// ── Imports (after mocks) ──────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
import type { AgentEvent } from "../agent/loop.js";
|
|
81
|
+
import type {
|
|
82
|
+
EventHandlerDeps,
|
|
83
|
+
EventHandlerState,
|
|
84
|
+
} from "../daemon/conversation-agent-loop-handlers.js";
|
|
85
|
+
import {
|
|
86
|
+
createEventHandlerState,
|
|
87
|
+
handleMessageComplete,
|
|
88
|
+
} from "../daemon/conversation-agent-loop-handlers.js";
|
|
89
|
+
|
|
90
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
const CONV = "conv-redact-test";
|
|
93
|
+
|
|
94
|
+
function makeDeps(): EventHandlerDeps {
|
|
95
|
+
return {
|
|
96
|
+
ctx: {
|
|
97
|
+
conversationId: CONV,
|
|
98
|
+
provider: { name: "anthropic" },
|
|
99
|
+
traceEmitter: { emit: () => {} },
|
|
100
|
+
currentTurnSurfaces: [],
|
|
101
|
+
trustContext: {
|
|
102
|
+
sourceChannel: "vellum",
|
|
103
|
+
trustClass: "guardian",
|
|
104
|
+
},
|
|
105
|
+
} as unknown as EventHandlerDeps["ctx"],
|
|
106
|
+
onEvent: () => {},
|
|
107
|
+
reqId: "test-req",
|
|
108
|
+
isFirstMessage: false,
|
|
109
|
+
shouldGenerateTitle: false,
|
|
110
|
+
rlog: new Proxy({} as Record<string, unknown>, {
|
|
111
|
+
get: () => () => {},
|
|
112
|
+
}) as unknown as EventHandlerDeps["rlog"],
|
|
113
|
+
turnChannelContext: {
|
|
114
|
+
userMessageChannel: "vellum",
|
|
115
|
+
assistantMessageChannel: "vellum",
|
|
116
|
+
} as EventHandlerDeps["turnChannelContext"],
|
|
117
|
+
turnInterfaceContext: {
|
|
118
|
+
userMessageInterface: "macos",
|
|
119
|
+
assistantMessageInterface: "macos",
|
|
120
|
+
} as EventHandlerDeps["turnInterfaceContext"],
|
|
121
|
+
} as EventHandlerDeps;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function makeMessageCompleteEvent(
|
|
125
|
+
text: string,
|
|
126
|
+
): Extract<AgentEvent, { type: "message_complete" }> {
|
|
127
|
+
return {
|
|
128
|
+
type: "message_complete",
|
|
129
|
+
message: { role: "assistant", content: [{ type: "text", text }] },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function lastPersisted(role: "assistant" | "user"): AddMessageCall {
|
|
134
|
+
for (let i = addMessageCalls.length - 1; i >= 0; i--) {
|
|
135
|
+
if (addMessageCalls[i].role === role) return addMessageCalls[i];
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`No ${role} message was persisted`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── Tests ──────────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
describe("persistence-layer secret redaction", () => {
|
|
143
|
+
let state: EventHandlerState;
|
|
144
|
+
|
|
145
|
+
beforeEach(() => {
|
|
146
|
+
addMessageCalls.length = 0;
|
|
147
|
+
state = createEventHandlerState();
|
|
148
|
+
state.turnStartedAt = 1_700_000_000_000;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
afterEach(() => {
|
|
152
|
+
addMessageCalls.length = 0;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ── Tool result content ──────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
test("redacts Anthropic API key in tool result content before persistence", async () => {
|
|
158
|
+
// Pattern requires 80+ chars after "sk-ant-"
|
|
159
|
+
const secret =
|
|
160
|
+
"sk-ant-api03-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
|
|
161
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
162
|
+
state.pendingToolResults.set("tool-use-1", {
|
|
163
|
+
content: `Here is the key: ${secret}`,
|
|
164
|
+
isError: false,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
|
|
168
|
+
|
|
169
|
+
const persisted = lastPersisted("user");
|
|
170
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
171
|
+
type: string;
|
|
172
|
+
content: string;
|
|
173
|
+
}>;
|
|
174
|
+
expect(blocks[0].content).not.toContain("sk-ant-api03-");
|
|
175
|
+
expect(blocks[0].content).toContain("<redacted");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("redacts GitHub PAT in tool result content before persistence", async () => {
|
|
179
|
+
// Pattern requires 36+ chars after "ghp_"
|
|
180
|
+
const secret = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn";
|
|
181
|
+
state.pendingToolResults.set("tool-use-2", {
|
|
182
|
+
content: `token=${secret}`,
|
|
183
|
+
isError: false,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
|
|
187
|
+
|
|
188
|
+
const persisted = lastPersisted("user");
|
|
189
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
190
|
+
type: string;
|
|
191
|
+
content: string;
|
|
192
|
+
}>;
|
|
193
|
+
expect(blocks[0].content).not.toContain("ghp_");
|
|
194
|
+
expect(blocks[0].content).toContain("<redacted");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("does not redact non-secret content (UUID, hex hash) in tool result", async () => {
|
|
198
|
+
const safe = "id=550e8400-e29b-41d4-a716-446655440000 sha=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2";
|
|
199
|
+
state.pendingToolResults.set("tool-use-3", {
|
|
200
|
+
content: safe,
|
|
201
|
+
isError: false,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
|
|
205
|
+
|
|
206
|
+
const persisted = lastPersisted("user");
|
|
207
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
208
|
+
type: string;
|
|
209
|
+
content: string;
|
|
210
|
+
}>;
|
|
211
|
+
expect(blocks[0].content).toBe(safe);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("live model state (pendingToolResults) is not modified by persistence redaction", async () => {
|
|
215
|
+
const secret =
|
|
216
|
+
"sk-ant-api03-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
|
|
217
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
218
|
+
const originalContent = `key=${secret}`;
|
|
219
|
+
state.pendingToolResults.set("tool-use-4", {
|
|
220
|
+
content: originalContent,
|
|
221
|
+
isError: false,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Capture the content before handleMessageComplete clears pendingToolResults
|
|
225
|
+
const contentSnapshot = state.pendingToolResults.get("tool-use-4")!.content;
|
|
226
|
+
|
|
227
|
+
await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent("done"));
|
|
228
|
+
|
|
229
|
+
// The snapshot taken from live state before the call must be unmodified
|
|
230
|
+
expect(contentSnapshot).toBe(originalContent);
|
|
231
|
+
expect(contentSnapshot).toContain("sk-ant-api03-");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// ── Assistant message text ───────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
test("redacts known-pattern secret quoted in assistant text before persistence", async () => {
|
|
237
|
+
const secret =
|
|
238
|
+
"sk-ant-api03-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
|
|
239
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
240
|
+
const text = `Your API key is \`${secret}\`. Keep it safe.`;
|
|
241
|
+
|
|
242
|
+
await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
|
|
243
|
+
|
|
244
|
+
const persisted = lastPersisted("assistant");
|
|
245
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
246
|
+
type: string;
|
|
247
|
+
text?: string;
|
|
248
|
+
}>;
|
|
249
|
+
const textBlock = blocks.find((b) => b.type === "text");
|
|
250
|
+
expect(textBlock?.text).not.toContain("sk-ant-api03-");
|
|
251
|
+
expect(textBlock?.text).toContain("<redacted");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("redacts OpenAI Project Key quoted in assistant text before persistence", async () => {
|
|
255
|
+
// Pattern requires 40+ chars after "sk-proj-"
|
|
256
|
+
const secret = "sk-proj-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst";
|
|
257
|
+
const text = `I found this key in the config: ${secret}`;
|
|
258
|
+
|
|
259
|
+
await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
|
|
260
|
+
|
|
261
|
+
const persisted = lastPersisted("assistant");
|
|
262
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
263
|
+
type: string;
|
|
264
|
+
text?: string;
|
|
265
|
+
}>;
|
|
266
|
+
const textBlock = blocks.find((b) => b.type === "text");
|
|
267
|
+
expect(textBlock?.text).not.toContain("sk-proj-");
|
|
268
|
+
expect(textBlock?.text).toContain("<redacted");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("does not redact non-secret text in assistant message", async () => {
|
|
272
|
+
const safe = "Here is the file list: index.ts, util.ts, main.ts";
|
|
273
|
+
|
|
274
|
+
await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(safe));
|
|
275
|
+
|
|
276
|
+
const persisted = lastPersisted("assistant");
|
|
277
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
278
|
+
type: string;
|
|
279
|
+
text?: string;
|
|
280
|
+
}>;
|
|
281
|
+
const textBlock = blocks.find((b) => b.type === "text");
|
|
282
|
+
expect(textBlock?.text).toBe(safe);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("does not redact random-looking strings that lack known prefixes", async () => {
|
|
286
|
+
// High-entropy but no known credential prefix — should NOT be redacted
|
|
287
|
+
const text = "checksum: 8f14e45fceea167a5a36dedd4bea2543";
|
|
288
|
+
|
|
289
|
+
await handleMessageComplete(state, makeDeps(), makeMessageCompleteEvent(text));
|
|
290
|
+
|
|
291
|
+
const persisted = lastPersisted("assistant");
|
|
292
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
293
|
+
type: string;
|
|
294
|
+
text?: string;
|
|
295
|
+
}>;
|
|
296
|
+
const textBlock = blocks.find((b) => b.type === "text");
|
|
297
|
+
expect(textBlock?.text).toBe(text);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
@@ -67,7 +67,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
67
67
|
getConfig: () => mockConfig,
|
|
68
68
|
loadConfig: () => mockConfig,
|
|
69
69
|
invalidateConfigCache: () => {},
|
|
70
|
-
saveConfig: () => {},
|
|
71
70
|
loadRawConfig: () => ({}),
|
|
72
71
|
saveRawConfig: () => {},
|
|
73
72
|
getNestedValue: () => undefined,
|
|
@@ -245,13 +244,11 @@ describe("platform-hosted bash auto-approval", () => {
|
|
|
245
244
|
expect(promptCalled).toBe(true);
|
|
246
245
|
});
|
|
247
246
|
|
|
248
|
-
test("bash NOT auto-approved for non-guardian actors via platform path (
|
|
247
|
+
test("bash NOT auto-approved for non-guardian actors via platform path (trusted_contact requires grant)", async () => {
|
|
249
248
|
checkResultOverride = { decision: "prompt", reason: "Needs approval" };
|
|
250
249
|
|
|
251
|
-
const platformAutoApproveCalled = false;
|
|
252
250
|
const trackingPrompter = {
|
|
253
251
|
prompt: async () => {
|
|
254
|
-
// If this is called, we know the platform auto-approve did NOT fire
|
|
255
252
|
return { decision: "allow" as const };
|
|
256
253
|
},
|
|
257
254
|
resolveConfirmation: () => {},
|
|
@@ -270,11 +267,10 @@ describe("platform-hosted bash auto-approval", () => {
|
|
|
270
267
|
}),
|
|
271
268
|
);
|
|
272
269
|
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
expect(result.isError).toBe(
|
|
277
|
-
void platformAutoApproveCalled; // suppress unused warning
|
|
270
|
+
// trusted_contact now requires a guardian-scoped grant for side-effect
|
|
271
|
+
// tools. Without a grant, the pre-execution gate denies the invocation
|
|
272
|
+
// before the permission checker or prompter is reached.
|
|
273
|
+
expect(result.isError).toBe(true);
|
|
278
274
|
});
|
|
279
275
|
|
|
280
276
|
test("bash NOT auto-approved when requireFreshApproval is set", async () => {
|