@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,443 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Embedding anisotropy correction (Mu & Viswanath "all-but-the-top")
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
//
|
|
5
|
+
// Modern transformer-based embedding models (Gemini's `gemini-embedding-2`
|
|
6
|
+
// being the most pronounced offender) produce vectors that occupy a narrow
|
|
7
|
+
// cone of the embedding space rather than spreading over the unit sphere.
|
|
8
|
+
// The downstream effect is that cosine similarities cluster in a compressed
|
|
9
|
+
// range — typically 0.4–0.7 for Gemini — which (a) makes absolute thresholds
|
|
10
|
+
// meaningless and (b) lets a few dominant directions drown out semantic
|
|
11
|
+
// signal.
|
|
12
|
+
//
|
|
13
|
+
// The fix: compute the corpus mean and its top-k principal components, then
|
|
14
|
+
// post-process every embedding via
|
|
15
|
+
//
|
|
16
|
+
// vec' = vec - mean
|
|
17
|
+
// for each pc_i: vec' = vec' - (vec' · pc_i) pc_i
|
|
18
|
+
// vec' = vec' / ||vec'||
|
|
19
|
+
//
|
|
20
|
+
// k = 1 is the safest starting point and reliably restores spread without
|
|
21
|
+
// risking semantic signal — see Mu & Viswanath 2018.
|
|
22
|
+
//
|
|
23
|
+
// ── Storage ────────────────────────────────────────────────────────────────
|
|
24
|
+
//
|
|
25
|
+
// Calibrations are persisted as JSON under
|
|
26
|
+
// `<workspace>/data/anisotropy/<provider>-<model>-<dim>.json` so each
|
|
27
|
+
// (provider, model, dim) tuple has its own. A loaded calibration is cached
|
|
28
|
+
// in-process; `clearAnisotropyCacheForTests` resets the module cache.
|
|
29
|
+
//
|
|
30
|
+
// ── Sphere-vs-raw inputs ───────────────────────────────────────────────────
|
|
31
|
+
//
|
|
32
|
+
// Qdrant pre-normalises vectors at insert time when the collection uses the
|
|
33
|
+
// Cosine distance, so the data we scroll for fitting lives on the unit
|
|
34
|
+
// sphere. Gemini's API, by contrast, returns raw (un-normalised) vectors at
|
|
35
|
+
// query time. To keep the fit and apply paths consistent we L2-normalise the
|
|
36
|
+
// input before applying the correction, regardless of source.
|
|
37
|
+
|
|
38
|
+
import { existsSync } from "node:fs";
|
|
39
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
40
|
+
import { join } from "node:path";
|
|
41
|
+
|
|
42
|
+
import { getDataDir } from "../util/platform.js";
|
|
43
|
+
import type { EmbeddingProviderName } from "./embedding-types.js";
|
|
44
|
+
|
|
45
|
+
/** Persisted anisotropy fit for a single (provider, model, dim) tuple. */
|
|
46
|
+
export interface AnisotropyCalibration {
|
|
47
|
+
provider: EmbeddingProviderName;
|
|
48
|
+
model: string;
|
|
49
|
+
/** Dimensionality of the embedding vectors this calibration applies to. */
|
|
50
|
+
dim: number;
|
|
51
|
+
/** Per-dimension mean across the fit sample. Length === `dim`. */
|
|
52
|
+
mean: number[];
|
|
53
|
+
/**
|
|
54
|
+
* Top-k principal components to project out. Stored as an array of
|
|
55
|
+
* unit-length d-vectors, one per component, in descending eigenvalue order.
|
|
56
|
+
* `components.length` is the operator-chosen `k` (typically 1, possibly 2-3).
|
|
57
|
+
*/
|
|
58
|
+
components: number[][];
|
|
59
|
+
/**
|
|
60
|
+
* Per-component variance (eigenvalues): `‖X v_i‖² / (N - 1)`. Same length
|
|
61
|
+
* as `components`. Useful to validate the fit (PC1 should explain a clear
|
|
62
|
+
* majority of variance for a truly anisotropic embedder).
|
|
63
|
+
*/
|
|
64
|
+
componentVariance: number[];
|
|
65
|
+
/**
|
|
66
|
+
* Total variance across all directions: `Σ_i ‖x_i - mean‖² / (N - 1)`.
|
|
67
|
+
* Combine with `componentVariance` to compute the explained-variance ratio
|
|
68
|
+
* per PC — i.e. how much of the corpus variance each PC accounts for.
|
|
69
|
+
*/
|
|
70
|
+
totalVariance: number;
|
|
71
|
+
/** Number of vectors used to compute this fit. */
|
|
72
|
+
sampleCount: number;
|
|
73
|
+
/** Wall-clock millis when the fit was computed (Date.now()). */
|
|
74
|
+
fitAt: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fit a calibration: corpus mean + top-k principal components via power
|
|
81
|
+
* iteration with Gram-Schmidt deflation.
|
|
82
|
+
*
|
|
83
|
+
* `vectors` is treated as a row-major data matrix (each entry is one sample).
|
|
84
|
+
* `k` is the number of leading principal components to extract (≥ 1).
|
|
85
|
+
*
|
|
86
|
+
* Returns the calibration without persisting it. Use `saveCalibration` to
|
|
87
|
+
* write it under `<workspace>/data/anisotropy/`.
|
|
88
|
+
*
|
|
89
|
+
* Throws when `vectors` is empty, `k` is non-positive, or the row dimensions
|
|
90
|
+
* disagree — these are caller bugs, not transient failures.
|
|
91
|
+
*/
|
|
92
|
+
export function fitAnisotropyCalibration(
|
|
93
|
+
vectors: readonly (readonly number[])[],
|
|
94
|
+
k: number,
|
|
95
|
+
meta: { provider: EmbeddingProviderName; model: string },
|
|
96
|
+
): AnisotropyCalibration {
|
|
97
|
+
if (vectors.length === 0) {
|
|
98
|
+
throw new Error("fitAnisotropyCalibration: no vectors supplied");
|
|
99
|
+
}
|
|
100
|
+
if (k < 1 || !Number.isInteger(k)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`fitAnisotropyCalibration: k must be a positive integer, got ${k}`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const dim = vectors[0].length;
|
|
106
|
+
if (dim === 0) {
|
|
107
|
+
throw new Error("fitAnisotropyCalibration: vectors are zero-dimensional");
|
|
108
|
+
}
|
|
109
|
+
for (let i = 1; i < vectors.length; i++) {
|
|
110
|
+
if (vectors[i].length !== dim) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`fitAnisotropyCalibration: vector ${i} has dim ${vectors[i].length}, expected ${dim}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (k > dim) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`fitAnisotropyCalibration: requested k=${k} exceeds embedding dim=${dim}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const n = vectors.length;
|
|
123
|
+
// Flatten into a contiguous Float64Array for cache locality during the
|
|
124
|
+
// O(k · iter · n · d) inner loop. Centre each row in place against the
|
|
125
|
+
// running mean once it's computed.
|
|
126
|
+
const data = new Float64Array(n * dim);
|
|
127
|
+
for (let i = 0; i < n; i++) {
|
|
128
|
+
const row = vectors[i];
|
|
129
|
+
for (let j = 0; j < dim; j++) {
|
|
130
|
+
data[i * dim + j] = row[j];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const mean = new Float64Array(dim);
|
|
135
|
+
for (let i = 0; i < n; i++) {
|
|
136
|
+
const base = i * dim;
|
|
137
|
+
for (let j = 0; j < dim; j++) {
|
|
138
|
+
mean[j] += data[base + j];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (let j = 0; j < dim; j++) {
|
|
142
|
+
mean[j] /= n;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Centre rows in place. After this `data` represents X = X_raw - mean.
|
|
146
|
+
for (let i = 0; i < n; i++) {
|
|
147
|
+
const base = i * dim;
|
|
148
|
+
for (let j = 0; j < dim; j++) {
|
|
149
|
+
data[base + j] -= mean[j];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Total variance: tr(X^T X) / (n - 1) = Σ_i ‖x_i‖² / (n - 1).
|
|
154
|
+
// Use n-1 for sample variance; falls back to 1 when n === 1 to avoid div0.
|
|
155
|
+
const denom = Math.max(1, n - 1);
|
|
156
|
+
let totalVariance = 0;
|
|
157
|
+
for (let i = 0; i < n * dim; i++) {
|
|
158
|
+
totalVariance += data[i] * data[i];
|
|
159
|
+
}
|
|
160
|
+
totalVariance /= denom;
|
|
161
|
+
|
|
162
|
+
const components: Float64Array[] = [];
|
|
163
|
+
const componentVariance: number[] = [];
|
|
164
|
+
for (let pcIdx = 0; pcIdx < k; pcIdx++) {
|
|
165
|
+
const v = powerIteration(data, n, dim, components);
|
|
166
|
+
// Eigenvalue λ = ‖X v‖² / (n - 1).
|
|
167
|
+
const Xv = matmulXv(data, n, dim, v);
|
|
168
|
+
let xvSq = 0;
|
|
169
|
+
for (let i = 0; i < n; i++) xvSq += Xv[i] * Xv[i];
|
|
170
|
+
components.push(v);
|
|
171
|
+
componentVariance.push(xvSq / denom);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
provider: meta.provider,
|
|
176
|
+
model: meta.model,
|
|
177
|
+
dim,
|
|
178
|
+
mean: Array.from(mean),
|
|
179
|
+
components: components.map((c) => Array.from(c)),
|
|
180
|
+
componentVariance,
|
|
181
|
+
totalVariance,
|
|
182
|
+
sampleCount: n,
|
|
183
|
+
fitAt: Date.now(),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Apply the all-but-the-top correction to a single embedding vector.
|
|
189
|
+
*
|
|
190
|
+
* The input is L2-normalised first so callers don't have to think about
|
|
191
|
+
* whether the source already lives on the unit sphere (Qdrant pre-normalises
|
|
192
|
+
* stored vectors under cosine distance; Gemini's API does not). The result
|
|
193
|
+
* is L2-normalised again so cosine similarity continues to behave like a
|
|
194
|
+
* dot product.
|
|
195
|
+
*
|
|
196
|
+
* Returns a fresh `number[]`; never mutates `vec` or the calibration.
|
|
197
|
+
*/
|
|
198
|
+
export function applyAnisotropyCorrection(
|
|
199
|
+
vec: readonly number[],
|
|
200
|
+
calib: AnisotropyCalibration,
|
|
201
|
+
): number[] {
|
|
202
|
+
if (vec.length !== calib.dim) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`applyAnisotropyCorrection: vec dim ${vec.length} != calibration dim ${calib.dim}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const out = new Float64Array(calib.dim);
|
|
209
|
+
for (let j = 0; j < calib.dim; j++) out[j] = vec[j];
|
|
210
|
+
l2NormalizeInPlace(out);
|
|
211
|
+
|
|
212
|
+
for (let j = 0; j < calib.dim; j++) {
|
|
213
|
+
out[j] -= calib.mean[j];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const pc of calib.components) {
|
|
217
|
+
let proj = 0;
|
|
218
|
+
for (let j = 0; j < calib.dim; j++) proj += out[j] * pc[j];
|
|
219
|
+
for (let j = 0; j < calib.dim; j++) out[j] -= proj * pc[j];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
l2NormalizeInPlace(out);
|
|
223
|
+
return Array.from(out);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Compute the explained-variance ratio for each component. The list is
|
|
228
|
+
* monotonically non-increasing because power iteration with deflation pulls
|
|
229
|
+
* the largest eigenvalue first.
|
|
230
|
+
*/
|
|
231
|
+
export function explainedVarianceRatio(calib: AnisotropyCalibration): number[] {
|
|
232
|
+
if (calib.totalVariance === 0) {
|
|
233
|
+
return calib.componentVariance.map(() => 0);
|
|
234
|
+
}
|
|
235
|
+
return calib.componentVariance.map((v) => v / calib.totalVariance);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Persistence ──────────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
const cache = new Map<string, AnisotropyCalibration | null>();
|
|
241
|
+
|
|
242
|
+
function calibrationKey(
|
|
243
|
+
provider: EmbeddingProviderName,
|
|
244
|
+
model: string,
|
|
245
|
+
dim: number,
|
|
246
|
+
): string {
|
|
247
|
+
return `${provider}:${model}:${dim}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function calibrationPath(
|
|
251
|
+
provider: EmbeddingProviderName,
|
|
252
|
+
model: string,
|
|
253
|
+
dim: number,
|
|
254
|
+
): string {
|
|
255
|
+
// Models can contain slashes (`gemini-embedding-2`, `text-embedding-3-large`,
|
|
256
|
+
// `BAAI/bge-base-en-v1.5`). Replace anything that's not filename-safe with
|
|
257
|
+
// `_` so the on-disk name is portable across platforms.
|
|
258
|
+
const safeModel = model.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
259
|
+
return join(
|
|
260
|
+
getDataDir(),
|
|
261
|
+
"anisotropy",
|
|
262
|
+
`${provider}-${safeModel}-${dim}.json`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Convenience: load the calibration and apply it to a vector in one call.
|
|
268
|
+
* Returns the input untouched when no calibration has been persisted for the
|
|
269
|
+
* (provider, model, dim) tuple. The intended call site is right at the
|
|
270
|
+
* boundary between the embedding backend and consumers that store/query
|
|
271
|
+
* vectors against Qdrant — write paths apply this before upsert, read paths
|
|
272
|
+
* apply it before search.
|
|
273
|
+
*/
|
|
274
|
+
export async function applyCorrectionIfCalibrated(
|
|
275
|
+
vec: number[],
|
|
276
|
+
provider: EmbeddingProviderName,
|
|
277
|
+
model: string,
|
|
278
|
+
): Promise<number[]> {
|
|
279
|
+
const calib = await loadCalibration(provider, model, vec.length);
|
|
280
|
+
if (!calib) return vec;
|
|
281
|
+
return applyAnisotropyCorrection(vec, calib);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Load the calibration for a (provider, model, dim) tuple. Returns `null`
|
|
286
|
+
* when no fit has been persisted yet — callers should treat this as
|
|
287
|
+
* "anisotropy correction is off for this embedder" and pass through raw
|
|
288
|
+
* vectors. Module-level cached so subsequent calls hit memory.
|
|
289
|
+
*/
|
|
290
|
+
export async function loadCalibration(
|
|
291
|
+
provider: EmbeddingProviderName,
|
|
292
|
+
model: string,
|
|
293
|
+
dim: number,
|
|
294
|
+
): Promise<AnisotropyCalibration | null> {
|
|
295
|
+
const key = calibrationKey(provider, model, dim);
|
|
296
|
+
if (cache.has(key)) return cache.get(key) ?? null;
|
|
297
|
+
|
|
298
|
+
const path = calibrationPath(provider, model, dim);
|
|
299
|
+
if (!existsSync(path)) {
|
|
300
|
+
cache.set(key, null);
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const raw = await readFile(path, "utf8");
|
|
305
|
+
const parsed = JSON.parse(raw) as AnisotropyCalibration;
|
|
306
|
+
cache.set(key, parsed);
|
|
307
|
+
return parsed;
|
|
308
|
+
} catch {
|
|
309
|
+
// A corrupt file is treated the same as a missing one: pass-through.
|
|
310
|
+
// The fit path will overwrite with a valid file on the next run.
|
|
311
|
+
cache.set(key, null);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Persist a calibration to disk and refresh the in-process cache so the
|
|
318
|
+
* next `loadCalibration` returns the new fit without a file read.
|
|
319
|
+
*/
|
|
320
|
+
export async function saveCalibration(
|
|
321
|
+
calib: AnisotropyCalibration,
|
|
322
|
+
): Promise<string> {
|
|
323
|
+
const path = calibrationPath(calib.provider, calib.model, calib.dim);
|
|
324
|
+
await mkdir(join(getDataDir(), "anisotropy"), { recursive: true });
|
|
325
|
+
await writeFile(path, JSON.stringify(calib), "utf8");
|
|
326
|
+
cache.set(calibrationKey(calib.provider, calib.model, calib.dim), calib);
|
|
327
|
+
return path;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** @internal Test-only: drop the in-process calibration cache. */
|
|
331
|
+
export function clearAnisotropyCacheForTests(): void {
|
|
332
|
+
cache.clear();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ── Power iteration internals ────────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
const POWER_ITERATION_MAX = 200;
|
|
338
|
+
const POWER_ITERATION_TOL = 1e-7;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Find the dominant eigenvector of X^T X (with previously-found components
|
|
342
|
+
* deflated out) via power iteration. Operates on `X v` and `X^T u` separately
|
|
343
|
+
* so we never materialise the d×d covariance matrix — for d=3072 that would
|
|
344
|
+
* be ~75 MB and cripple memory locality. Returns a unit-length d-vector.
|
|
345
|
+
*/
|
|
346
|
+
function powerIteration(
|
|
347
|
+
data: Float64Array,
|
|
348
|
+
n: number,
|
|
349
|
+
dim: number,
|
|
350
|
+
deflate: readonly Float64Array[],
|
|
351
|
+
): Float64Array {
|
|
352
|
+
// Deterministic init: a fixed unit vector. Power iteration converges from
|
|
353
|
+
// any non-orthogonal start, and a deterministic seed keeps fit results
|
|
354
|
+
// reproducible across runs (helpful for debugging and tests).
|
|
355
|
+
const v = new Float64Array(dim);
|
|
356
|
+
v[0] = 1;
|
|
357
|
+
// Project off any previously-found components from the init too, so we
|
|
358
|
+
// don't waste iterations re-deflating the same direction every step.
|
|
359
|
+
deflateInPlace(v, deflate);
|
|
360
|
+
l2NormalizeInPlace(v);
|
|
361
|
+
|
|
362
|
+
let prevDot = 0;
|
|
363
|
+
for (let iter = 0; iter < POWER_ITERATION_MAX; iter++) {
|
|
364
|
+
const Xv = matmulXv(data, n, dim, v);
|
|
365
|
+
const next = matmulXTu(data, n, dim, Xv);
|
|
366
|
+
deflateInPlace(next, deflate);
|
|
367
|
+
const norm = l2NormalizeInPlace(next);
|
|
368
|
+
if (norm === 0) {
|
|
369
|
+
// The remaining variance lives entirely in the deflated subspace —
|
|
370
|
+
// every direction we can pick is orthogonal to the data. Returning the
|
|
371
|
+
// current best estimate keeps the spectrum monotonic instead of
|
|
372
|
+
// emitting NaN downstream.
|
|
373
|
+
return v;
|
|
374
|
+
}
|
|
375
|
+
let dot = 0;
|
|
376
|
+
for (let j = 0; j < dim; j++) dot += v[j] * next[j];
|
|
377
|
+
// |dot| approaches 1 as power iteration converges (sign can flip across
|
|
378
|
+
// iterations, so absolute value).
|
|
379
|
+
if (Math.abs(Math.abs(dot) - Math.abs(prevDot)) < POWER_ITERATION_TOL) {
|
|
380
|
+
return next;
|
|
381
|
+
}
|
|
382
|
+
prevDot = dot;
|
|
383
|
+
for (let j = 0; j < dim; j++) v[j] = next[j];
|
|
384
|
+
}
|
|
385
|
+
return v;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** y = X v, where X is n×d row-major. Returns a fresh Float64Array of length n. */
|
|
389
|
+
function matmulXv(
|
|
390
|
+
data: Float64Array,
|
|
391
|
+
n: number,
|
|
392
|
+
dim: number,
|
|
393
|
+
v: Float64Array,
|
|
394
|
+
): Float64Array {
|
|
395
|
+
const out = new Float64Array(n);
|
|
396
|
+
for (let i = 0; i < n; i++) {
|
|
397
|
+
const base = i * dim;
|
|
398
|
+
let acc = 0;
|
|
399
|
+
for (let j = 0; j < dim; j++) acc += data[base + j] * v[j];
|
|
400
|
+
out[i] = acc;
|
|
401
|
+
}
|
|
402
|
+
return out;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** y = X^T u, where X is n×d row-major. Returns a fresh Float64Array of length d. */
|
|
406
|
+
function matmulXTu(
|
|
407
|
+
data: Float64Array,
|
|
408
|
+
n: number,
|
|
409
|
+
dim: number,
|
|
410
|
+
u: Float64Array,
|
|
411
|
+
): Float64Array {
|
|
412
|
+
const out = new Float64Array(dim);
|
|
413
|
+
for (let i = 0; i < n; i++) {
|
|
414
|
+
const base = i * dim;
|
|
415
|
+
const ui = u[i];
|
|
416
|
+
for (let j = 0; j < dim; j++) out[j] += data[base + j] * ui;
|
|
417
|
+
}
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Subtract every previously-found component from `v` (Gram-Schmidt). */
|
|
422
|
+
function deflateInPlace(
|
|
423
|
+
v: Float64Array,
|
|
424
|
+
deflate: readonly Float64Array[],
|
|
425
|
+
): void {
|
|
426
|
+
for (const pc of deflate) {
|
|
427
|
+
let proj = 0;
|
|
428
|
+
const dim = v.length;
|
|
429
|
+
for (let j = 0; j < dim; j++) proj += v[j] * pc[j];
|
|
430
|
+
for (let j = 0; j < dim; j++) v[j] -= proj * pc[j];
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** L2-normalise `v` in place. Returns the original norm so callers can detect zero vectors. */
|
|
435
|
+
function l2NormalizeInPlace(v: Float64Array): number {
|
|
436
|
+
let norm = 0;
|
|
437
|
+
for (let j = 0; j < v.length; j++) norm += v[j] * v[j];
|
|
438
|
+
norm = Math.sqrt(norm);
|
|
439
|
+
if (norm === 0) return 0;
|
|
440
|
+
const inv = 1 / norm;
|
|
441
|
+
for (let j = 0; j < v.length; j++) v[j] *= inv;
|
|
442
|
+
return norm;
|
|
443
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants extracted from auto-analysis-guard.ts to break the
|
|
3
|
+
* conversation-crud ↔ auto-analysis-guard cycle.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Sentinel value for the `source` column of auto-analysis conversations.
|
|
8
|
+
* Used both when creating them and when querying "all except auto-analysis."
|
|
9
|
+
*/
|
|
10
|
+
export const AUTO_ANALYSIS_SOURCE = "auto-analysis";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Dedicated `group_id` value for auto-analysis rolling conversations.
|
|
14
|
+
* Placed in the `system:background` group alongside heartbeat and filing
|
|
15
|
+
* conversations, rendered as a "Reflections" sub-group in the sidebar.
|
|
16
|
+
*/
|
|
17
|
+
export const AUTO_ANALYSIS_GROUP_ID = "system:background";
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
+
import { AUTO_ANALYSIS_SOURCE } from "./auto-analysis-constants.js";
|
|
1
2
|
import { getConversationSource } from "./conversation-crud.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* service auto-branch) imports this constant rather than hardcoding the
|
|
7
|
-
* string.
|
|
8
|
-
*/
|
|
9
|
-
export const AUTO_ANALYSIS_SOURCE = "auto-analysis";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Dedicated `group_id` value for auto-analysis rolling conversations.
|
|
13
|
-
* Placed in the `system:background` group alongside heartbeat and filing
|
|
14
|
-
* conversations, rendered as a "Reflections" sub-group in the sidebar.
|
|
15
|
-
*/
|
|
16
|
-
export const AUTO_ANALYSIS_GROUP_ID = "system:background";
|
|
3
|
+
export {
|
|
4
|
+
AUTO_ANALYSIS_GROUP_ID,
|
|
5
|
+
AUTO_ANALYSIS_SOURCE,
|
|
6
|
+
} from "./auto-analysis-constants.js";
|
|
17
7
|
|
|
18
8
|
/**
|
|
19
9
|
* Returns true if the conversation's `source` column is `"auto-analysis"`,
|
|
@@ -74,7 +74,7 @@ export interface CanonicalGuardianRequest {
|
|
|
74
74
|
updatedAt: number;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
interface CanonicalGuardianDelivery {
|
|
78
78
|
id: string;
|
|
79
79
|
requestId: string;
|
|
80
80
|
destinationChannel: string;
|
|
@@ -189,7 +189,7 @@ function rowToDelivery(
|
|
|
189
189
|
// Canonical Guardian Requests
|
|
190
190
|
// ---------------------------------------------------------------------------
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
interface CreateCanonicalGuardianRequestParams {
|
|
193
193
|
id?: string;
|
|
194
194
|
kind: string;
|
|
195
195
|
sourceType: string;
|
|
@@ -315,7 +315,7 @@ export function getCanonicalGuardianRequestByCode(
|
|
|
315
315
|
return row ? rowToRequest(row) : null;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
|
|
318
|
+
interface ListCanonicalGuardianRequestsFilters {
|
|
319
319
|
status?: CanonicalRequestStatus;
|
|
320
320
|
guardianExternalUserId?: string;
|
|
321
321
|
guardianPrincipalId?: string;
|
|
@@ -394,7 +394,7 @@ export function listCanonicalGuardianRequests(
|
|
|
394
394
|
.map(rowToRequest);
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
-
|
|
397
|
+
interface UpdateCanonicalGuardianRequestParams {
|
|
398
398
|
status?: CanonicalRequestStatus;
|
|
399
399
|
answerText?: string;
|
|
400
400
|
decidedByExternalUserId?: string;
|
|
@@ -430,7 +430,7 @@ export function updateCanonicalGuardianRequest(
|
|
|
430
430
|
return getCanonicalGuardianRequest(id);
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
|
|
433
|
+
interface ResolveDecision {
|
|
434
434
|
status: CanonicalRequestStatus;
|
|
435
435
|
answerText?: string;
|
|
436
436
|
decidedByExternalUserId?: string;
|
|
@@ -533,7 +533,7 @@ export function expireAllPendingCanonicalRequests(): number {
|
|
|
533
533
|
// Canonical Guardian Deliveries
|
|
534
534
|
// ---------------------------------------------------------------------------
|
|
535
535
|
|
|
536
|
-
|
|
536
|
+
interface CreateCanonicalGuardianDeliveryParams {
|
|
537
537
|
id?: string;
|
|
538
538
|
requestId: string;
|
|
539
539
|
destinationChannel: string;
|
|
@@ -755,7 +755,7 @@ export function isRequestInConversationScope(
|
|
|
755
755
|
);
|
|
756
756
|
}
|
|
757
757
|
|
|
758
|
-
|
|
758
|
+
interface UpdateCanonicalGuardianDeliveryParams {
|
|
759
759
|
status?: string;
|
|
760
760
|
destinationMessageId?: string;
|
|
761
761
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for workspace-evidence secret redaction in the agentic recall
|
|
3
|
+
* flow (ATL-320).
|
|
4
|
+
*
|
|
5
|
+
* `redactWorkspaceEvidence` scrubs secrets from workspace-sourced evidence
|
|
6
|
+
* excerpts before they are serialised into the prompt that is sent to the
|
|
7
|
+
* external recall LLM provider. Memory/PKB/conversation evidence is left
|
|
8
|
+
* untouched — those sources contain intentionally stored user content.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { redactWorkspaceEvidence } from "../agent-runner.js";
|
|
14
|
+
import type { RecallEvidence } from "../types.js";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
function makeEvidence(
|
|
21
|
+
overrides: Partial<RecallEvidence> & Pick<RecallEvidence, "source">,
|
|
22
|
+
): RecallEvidence {
|
|
23
|
+
return {
|
|
24
|
+
id: "ev-1",
|
|
25
|
+
title: "test-file.ts",
|
|
26
|
+
locator: "workspace://test-file.ts",
|
|
27
|
+
excerpt: "some content",
|
|
28
|
+
score: 1,
|
|
29
|
+
...overrides,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Tests
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
// Anthropic key pattern requires sk-ant- + 80+ chars; use a realistic length.
|
|
38
|
+
const ANTHROPIC_KEY = "sk-ant-api03-" + "A1b2C3d4E5f6G7h8I9j0".repeat(5);
|
|
39
|
+
// Generic secret assignment — caught by the Generic Secret Assignment pattern.
|
|
40
|
+
// Using a generic form avoids GitHub push-protection false-positives on Stripe
|
|
41
|
+
// or other vendor-prefix patterns while still exercising the redaction path.
|
|
42
|
+
const GENERIC_SECRET_EXCERPT = `api_key="a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"`;
|
|
43
|
+
|
|
44
|
+
describe("redactWorkspaceEvidence", () => {
|
|
45
|
+
test("redacts known-prefix secrets (Anthropic key) from workspace excerpts", () => {
|
|
46
|
+
const [result] = redactWorkspaceEvidence([
|
|
47
|
+
makeEvidence({
|
|
48
|
+
source: "workspace",
|
|
49
|
+
excerpt: `The API key is ${ANTHROPIC_KEY}`,
|
|
50
|
+
}),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
expect(result.excerpt).not.toContain(ANTHROPIC_KEY);
|
|
54
|
+
expect(result.excerpt).toContain("<redacted");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("redacts generic secret assignments from workspace excerpts", () => {
|
|
58
|
+
const [result] = redactWorkspaceEvidence([
|
|
59
|
+
makeEvidence({ source: "workspace", excerpt: GENERIC_SECRET_EXCERPT }),
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
expect(result.excerpt).not.toBe(GENERIC_SECRET_EXCERPT);
|
|
63
|
+
expect(result.excerpt).toContain("<redacted");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("does NOT modify non-secret workspace excerpts", () => {
|
|
67
|
+
const safeContent = "This is a normal comment explaining the architecture.";
|
|
68
|
+
const original = makeEvidence({ source: "workspace", excerpt: safeContent });
|
|
69
|
+
const [result] = redactWorkspaceEvidence([original]);
|
|
70
|
+
|
|
71
|
+
expect(result.excerpt).toBe(safeContent);
|
|
72
|
+
// No copy made when nothing changed — same reference
|
|
73
|
+
expect(result).toBe(original);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("does NOT redact non-workspace sources", () => {
|
|
77
|
+
// Memory/PKB/conversation evidence is intentionally stored user content —
|
|
78
|
+
// redacting it would break recall for things the user deliberately noted.
|
|
79
|
+
const secretLike = "token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0In0.abc";
|
|
80
|
+
|
|
81
|
+
for (const source of ["memory", "pkb", "conversations"] as const) {
|
|
82
|
+
const original = makeEvidence({ source, excerpt: secretLike });
|
|
83
|
+
const [result] = redactWorkspaceEvidence([original]);
|
|
84
|
+
|
|
85
|
+
expect(result.excerpt).toBe(secretLike);
|
|
86
|
+
expect(result).toBe(original); // Same reference, untouched
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("redacts multiple secrets across multiple workspace evidence items", () => {
|
|
91
|
+
const results = redactWorkspaceEvidence([
|
|
92
|
+
makeEvidence({ id: "ev-1", source: "workspace", excerpt: `key=${ANTHROPIC_KEY}` }),
|
|
93
|
+
makeEvidence({ id: "ev-2", source: "workspace", excerpt: GENERIC_SECRET_EXCERPT }),
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
expect(results[0].excerpt).not.toContain(ANTHROPIC_KEY);
|
|
97
|
+
expect(results[1].excerpt).not.toBe(GENERIC_SECRET_EXCERPT);
|
|
98
|
+
expect(results[0].excerpt).toContain("<redacted");
|
|
99
|
+
expect(results[1].excerpt).toContain("<redacted");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("does not mutate the original evidence objects", () => {
|
|
103
|
+
const secret = ANTHROPIC_KEY;
|
|
104
|
+
const original = makeEvidence({ source: "workspace", excerpt: `key=${secret}` });
|
|
105
|
+
const originalExcerpt = original.excerpt;
|
|
106
|
+
|
|
107
|
+
redactWorkspaceEvidence([original]);
|
|
108
|
+
|
|
109
|
+
expect(original.excerpt).toBe(originalExcerpt); // Original unchanged
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("handles mixed sources in one batch correctly", () => {
|
|
113
|
+
const secret = ANTHROPIC_KEY;
|
|
114
|
+
const wsItem = makeEvidence({ id: "ev-ws", source: "workspace", excerpt: `key=${secret}` });
|
|
115
|
+
const memItem = makeEvidence({ id: "ev-mem", source: "memory", excerpt: `key=${secret}` });
|
|
116
|
+
|
|
117
|
+
const [wsResult, memResult] = redactWorkspaceEvidence([wsItem, memItem]);
|
|
118
|
+
|
|
119
|
+
expect(wsResult.excerpt).not.toContain(secret);
|
|
120
|
+
expect(memResult.excerpt).toContain(secret); // Memory untouched
|
|
121
|
+
});
|
|
122
|
+
});
|