@vellumai/assistant 0.6.3 → 0.6.4
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 +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +5 -13
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/openapi.yaml +982 -72
- package/package.json +4 -6
- package/scripts/generate-openapi.ts +0 -1
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +11 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-schema.test.ts +1013 -66
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +98 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +901 -60
- package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/credential-vault-unit.test.ts +379 -3
- package/src/__tests__/credentials-cli.test.ts +40 -16
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/headless-browser-interactions.test.ts +43 -0
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +176 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +5 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +112 -26
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +18 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +43 -3
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +255 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/gmail/SKILL.md +53 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +2 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +34 -10
- package/src/config/feature-flag-registry.json +46 -14
- package/src/config/loader.ts +26 -12
- package/src/config/schema.ts +35 -10
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/services.ts +44 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +45 -12
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +101 -24
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +581 -19
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +11 -1
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +94 -4
- package/src/daemon/conversation-tool-setup.ts +25 -0
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation.ts +86 -28
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +4 -1
- package/src/daemon/handlers/shared.ts +22 -0
- package/src/daemon/handlers/skills.ts +321 -77
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +122 -25
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +34 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +14 -0
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +347 -2
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +12 -3
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +1 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +99 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/db-init.ts +6 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +27 -18
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +9 -0
- package/src/oauth/platform-connection.test.ts +98 -0
- package/src/oauth/platform-connection.ts +52 -31
- package/src/oauth/seed-providers.ts +7 -0
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +59 -18
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -61
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +9 -0
- package/src/runtime/AGENTS.md +43 -1
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +30 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +10 -10
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/http-server.ts +395 -10
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +34 -0
- package/src/runtime/pending-interactions.ts +0 -11
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
- package/src/runtime/routes/conversation-management-routes.ts +115 -0
- package/src/runtime/routes/conversation-routes.ts +367 -146
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +40 -5
- package/src/runtime/routes/settings-routes.ts +22 -5
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +30 -2
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +50 -0
- package/src/security/oauth2.ts +26 -4
- package/src/security/secure-keys.ts +25 -2
- package/src/security/token-manager.ts +8 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +38 -14
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/browser-execution.ts +1163 -23
- package/src/tools/browser/browser-manager.ts +45 -0
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/terminal/safe-env.ts +10 -2
- package/src/tools/terminal/shell.ts +15 -4
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +17 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +54 -10
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
|
@@ -72,8 +72,12 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
72
72
|
generateScopeOptions: () => [{ label: "/tmp", scope: "/tmp" }],
|
|
73
73
|
}));
|
|
74
74
|
|
|
75
|
+
// Mock every export so downstream test files that dynamically import modules
|
|
76
|
+
// with a static `from "../memory/tool-usage-store.js"` still see all symbols.
|
|
75
77
|
mock.module("../memory/tool-usage-store.js", () => ({
|
|
76
78
|
recordToolInvocation: () => {},
|
|
79
|
+
getRecentInvocations: () => [],
|
|
80
|
+
rotateToolInvocations: () => 0,
|
|
77
81
|
}));
|
|
78
82
|
|
|
79
83
|
mock.module("../tools/registry.js", () => ({
|
|
@@ -621,11 +625,11 @@ describe("ToolExecutor lifecycle events", () => {
|
|
|
621
625
|
expect(events.map((e) => e.type)).toEqual(["start", "executed"]);
|
|
622
626
|
});
|
|
623
627
|
|
|
624
|
-
test("file_edit to
|
|
625
|
-
// Security invariant: editing
|
|
626
|
-
// prompt, even when a trust rule would auto-allow.
|
|
628
|
+
test("file_edit to guardian persona emits permission_prompt under forcePromptSideEffects", async () => {
|
|
629
|
+
// Security invariant: editing the guardian persona file in a private
|
|
630
|
+
// thread must always prompt, even when a trust rule would auto-allow.
|
|
627
631
|
checkerDecision = "allow";
|
|
628
|
-
checkerReason = "Matched trust rule: file_edit:*/
|
|
632
|
+
checkerReason = "Matched trust rule: file_edit:*/users/*.md";
|
|
629
633
|
checkerRisk = "low";
|
|
630
634
|
promptDecision = "allow";
|
|
631
635
|
|
|
@@ -635,7 +639,7 @@ describe("ToolExecutor lifecycle events", () => {
|
|
|
635
639
|
const result = await executor.execute(
|
|
636
640
|
"file_edit",
|
|
637
641
|
{
|
|
638
|
-
path: "/Users/sidd/.vellum/workspace/
|
|
642
|
+
path: "/Users/sidd/.vellum/workspace/users/sidd.md",
|
|
639
643
|
old_string: "old",
|
|
640
644
|
new_string: "new",
|
|
641
645
|
},
|
|
@@ -92,8 +92,12 @@ mock.module("../permissions/trust-store.js", () => ({
|
|
|
92
92
|
}));
|
|
93
93
|
|
|
94
94
|
// ── Tool usage store ──
|
|
95
|
+
// Mock every export so downstream test files that dynamically import modules
|
|
96
|
+
// with a static `from "../memory/tool-usage-store.js"` still see all symbols.
|
|
95
97
|
mock.module("../memory/tool-usage-store.js", () => ({
|
|
96
98
|
recordToolInvocation: () => {},
|
|
99
|
+
getRecentInvocations: () => [],
|
|
100
|
+
rotateToolInvocations: () => 0,
|
|
97
101
|
}));
|
|
98
102
|
|
|
99
103
|
// ── Path policy ──
|
|
@@ -129,8 +129,12 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
129
129
|
scopeOptionsOverride ?? [{ label: "/tmp", scope: "/tmp" }],
|
|
130
130
|
}));
|
|
131
131
|
|
|
132
|
+
// Mock every export so downstream test files that dynamically import modules
|
|
133
|
+
// with a static `from "../memory/tool-usage-store.js"` still see all symbols.
|
|
132
134
|
mock.module("../memory/tool-usage-store.js", () => ({
|
|
133
135
|
recordToolInvocation: () => {},
|
|
136
|
+
getRecentInvocations: () => [],
|
|
137
|
+
rotateToolInvocations: () => 0,
|
|
134
138
|
}));
|
|
135
139
|
|
|
136
140
|
mock.module("../tools/registry.js", () => ({
|
|
@@ -1148,6 +1152,8 @@ describe("isSideEffectTool", () => {
|
|
|
1148
1152
|
"browser_type",
|
|
1149
1153
|
"browser_press_key",
|
|
1150
1154
|
"browser_close",
|
|
1155
|
+
"browser_attach",
|
|
1156
|
+
"browser_detach",
|
|
1151
1157
|
"browser_fill_credential",
|
|
1152
1158
|
"document_create",
|
|
1153
1159
|
"document_update",
|
|
@@ -1220,12 +1226,13 @@ describe("isSideEffectTool", () => {
|
|
|
1220
1226
|
});
|
|
1221
1227
|
});
|
|
1222
1228
|
|
|
1223
|
-
// Baseline: allow rules can auto-allow file_edit for
|
|
1224
|
-
// The mock check() delegates to
|
|
1225
|
-
//
|
|
1226
|
-
// a blanket
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
+
// Baseline: allow rules can auto-allow file_edit for the guardian persona
|
|
1230
|
+
// today (no forced prompting). The mock check() delegates to
|
|
1231
|
+
// findHighestPriorityRule (via spy) so a regression in trust-rule matching
|
|
1232
|
+
// would cause this test to fail instead of being masked by a blanket
|
|
1233
|
+
// mock-allow.
|
|
1234
|
+
describe("ToolExecutor baseline: allow rule auto-allows file_edit guardian persona", () => {
|
|
1235
|
+
const guardianPersonaPath = "/Users/sidd/.vellum/workspace/users/sidd.md";
|
|
1229
1236
|
let ruleSpy: ReturnType<typeof spyOn> | undefined;
|
|
1230
1237
|
|
|
1231
1238
|
beforeEach(() => {
|
|
@@ -1239,18 +1246,19 @@ describe("ToolExecutor baseline: allow rule auto-allows file_edit USER.md", () =
|
|
|
1239
1246
|
addRuleSpy = undefined;
|
|
1240
1247
|
}
|
|
1241
1248
|
|
|
1242
|
-
// Simulate a trust rule that allows file_edit on
|
|
1243
|
-
// findHighestPriorityRule. This mirrors the
|
|
1244
|
-
// the trust-store creates for
|
|
1249
|
+
// Simulate a trust rule that allows file_edit on the guardian's per-user
|
|
1250
|
+
// persona file by stubbing findHighestPriorityRule. This mirrors the
|
|
1251
|
+
// default allow rules that the trust-store creates for the guardian
|
|
1252
|
+
// persona file (see permissions/defaults.ts).
|
|
1245
1253
|
ruleSpy = spyOn(trustStore, "findHighestPriorityRule").mockImplementation(
|
|
1246
1254
|
(tool: string, commands: string[], _scope: string) => {
|
|
1247
1255
|
if (tool !== "file_edit") return null;
|
|
1248
1256
|
for (const cmd of commands) {
|
|
1249
|
-
if (cmd === `file_edit:${
|
|
1257
|
+
if (cmd === `file_edit:${guardianPersonaPath}`) {
|
|
1250
1258
|
return {
|
|
1251
|
-
id: "default:allow-file_edit-
|
|
1259
|
+
id: "default:allow-file_edit-guardian-persona",
|
|
1252
1260
|
tool: "file_edit",
|
|
1253
|
-
pattern: `file_edit:${
|
|
1261
|
+
pattern: `file_edit:${guardianPersonaPath}`,
|
|
1254
1262
|
scope: "everywhere",
|
|
1255
1263
|
decision: "allow" as const,
|
|
1256
1264
|
priority: 100,
|
|
@@ -1294,11 +1302,11 @@ describe("ToolExecutor baseline: allow rule auto-allows file_edit USER.md", () =
|
|
|
1294
1302
|
}
|
|
1295
1303
|
});
|
|
1296
1304
|
|
|
1297
|
-
test("file_edit to
|
|
1305
|
+
test("file_edit to guardian persona is auto-allowed via trust rule", async () => {
|
|
1298
1306
|
const executor = new ToolExecutor(makePrompter());
|
|
1299
1307
|
const result = await executor.execute(
|
|
1300
1308
|
"file_edit",
|
|
1301
|
-
{ path:
|
|
1309
|
+
{ path: guardianPersonaPath, content: "hello" },
|
|
1302
1310
|
makeContext(),
|
|
1303
1311
|
);
|
|
1304
1312
|
expect(result.isError).toBe(false);
|
|
@@ -1310,7 +1318,7 @@ describe("ToolExecutor baseline: allow rule auto-allows file_edit USER.md", () =
|
|
|
1310
1318
|
expect(ruleSpy).toHaveBeenCalled();
|
|
1311
1319
|
});
|
|
1312
1320
|
|
|
1313
|
-
test("file_edit to a non-
|
|
1321
|
+
test("file_edit to a non-guardian-persona path is NOT auto-allowed without a matching rule", async () => {
|
|
1314
1322
|
let promptCalled = false;
|
|
1315
1323
|
const trackingPrompter = {
|
|
1316
1324
|
prompt: async () => {
|
|
@@ -1530,22 +1538,23 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1530
1538
|
expect(promptCount).toBe(1);
|
|
1531
1539
|
});
|
|
1532
1540
|
|
|
1533
|
-
// ──
|
|
1541
|
+
// ── Guardian persona security invariant (PR 31) ──────────
|
|
1534
1542
|
|
|
1535
|
-
test("file_edit to
|
|
1536
|
-
// This is a key security invariant:
|
|
1537
|
-
// memory. In a private conversation
|
|
1538
|
-
// must always require explicit
|
|
1543
|
+
test("file_edit to guardian persona forces prompt in private conversation even with matching trust rule", async () => {
|
|
1544
|
+
// This is a key security invariant: the guardian persona file contains
|
|
1545
|
+
// the user's persistent memory. In a private conversation
|
|
1546
|
+
// (forcePromptSideEffects=true), edits to it must always require explicit
|
|
1547
|
+
// approval, even when a trust rule matches.
|
|
1539
1548
|
checkResultOverride = {
|
|
1540
1549
|
decision: "allow",
|
|
1541
|
-
reason: "Matched trust rule: file_edit:*/
|
|
1550
|
+
reason: "Matched trust rule: file_edit:*/users/*.md",
|
|
1542
1551
|
};
|
|
1543
1552
|
|
|
1544
1553
|
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1545
1554
|
const result = await executor.execute(
|
|
1546
1555
|
"file_edit",
|
|
1547
1556
|
{
|
|
1548
|
-
path: "/Users/sidd/.vellum/workspace/
|
|
1557
|
+
path: "/Users/sidd/.vellum/workspace/users/sidd.md",
|
|
1549
1558
|
old_string: "old pref",
|
|
1550
1559
|
new_string: "new pref",
|
|
1551
1560
|
},
|
|
@@ -1557,14 +1566,14 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
|
|
|
1557
1566
|
expect(promptCalled).toBe(true);
|
|
1558
1567
|
});
|
|
1559
1568
|
|
|
1560
|
-
test("host_file_edit to
|
|
1569
|
+
test("host_file_edit to guardian persona forces prompt in private conversation", async () => {
|
|
1561
1570
|
checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
|
|
1562
1571
|
|
|
1563
1572
|
const executor = new ToolExecutor(makeTrackingPrompter());
|
|
1564
1573
|
const result = await executor.execute(
|
|
1565
1574
|
"host_file_edit",
|
|
1566
1575
|
{
|
|
1567
|
-
path: "/Users/sidd/.vellum/workspace/
|
|
1576
|
+
path: "/Users/sidd/.vellum/workspace/users/sidd.md",
|
|
1568
1577
|
old_string: "x",
|
|
1569
1578
|
new_string: "y",
|
|
1570
1579
|
},
|
|
@@ -11,6 +11,20 @@ import {
|
|
|
11
11
|
} from "../context/tool-result-truncation.js";
|
|
12
12
|
import type { ContentBlock, ToolResultContent } from "../providers/types.js";
|
|
13
13
|
|
|
14
|
+
function hasOrphanedSurrogate(str: string): boolean {
|
|
15
|
+
for (let i = 0; i < str.length; i++) {
|
|
16
|
+
const code = str.charCodeAt(i);
|
|
17
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
18
|
+
const next = i + 1 < str.length ? str.charCodeAt(i + 1) : 0;
|
|
19
|
+
if (next < 0xdc00 || next > 0xdfff) return true;
|
|
20
|
+
i++;
|
|
21
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
// ---------------------------------------------------------------------------
|
|
15
29
|
// Helpers
|
|
16
30
|
// ---------------------------------------------------------------------------
|
|
@@ -100,6 +114,28 @@ describe("truncateToolResultText", () => {
|
|
|
100
114
|
expect(result).toBe(text);
|
|
101
115
|
expect(result).not.toContain(TRUNCATION_SUFFIX);
|
|
102
116
|
});
|
|
117
|
+
|
|
118
|
+
test("does not orphan a UTF-16 surrogate pair at the cut boundary", () => {
|
|
119
|
+
// Regression for the "no low surrogate in string" Anthropic 400 error.
|
|
120
|
+
// Build a string where the cut point lands inside a surrogate pair:
|
|
121
|
+
// 4999 padding chars, then an emoji (2 code units), then enough filler
|
|
122
|
+
// to push the cut inside the pair.
|
|
123
|
+
const EMOJI = "\uD83C\uDF89";
|
|
124
|
+
// maxChars = 5_000, so cutPoint = 5_000 - TRUNCATION_SUFFIX.length.
|
|
125
|
+
// Put the emoji so its high surrogate lands exactly at cutPoint - 1.
|
|
126
|
+
const maxChars = 5_000;
|
|
127
|
+
const cutPoint = maxChars - TRUNCATION_SUFFIX.length;
|
|
128
|
+
// Fill up to cutPoint - 1 with "a"s, then place the emoji so the high
|
|
129
|
+
// surrogate is the character at cutPoint - 1 and the low is at cutPoint.
|
|
130
|
+
const prefix = "a".repeat(cutPoint - 1);
|
|
131
|
+
const text = prefix + EMOJI + "b".repeat(100);
|
|
132
|
+
// Use a long filler with no newlines so lastIndexOf("\n", cutPoint) === -1
|
|
133
|
+
// and the function falls back to cutPoint itself.
|
|
134
|
+
const result = truncateToolResultText(text, maxChars);
|
|
135
|
+
expect(hasOrphanedSurrogate(result)).toBe(false);
|
|
136
|
+
// JSON.stringify must not throw on the result.
|
|
137
|
+
expect(() => JSON.stringify(result)).not.toThrow();
|
|
138
|
+
});
|
|
103
139
|
});
|
|
104
140
|
|
|
105
141
|
// ---------------------------------------------------------------------------
|
|
@@ -740,8 +740,10 @@ describe("Trust Store", () => {
|
|
|
740
740
|
].sort();
|
|
741
741
|
expect(defaultTools).toEqual([
|
|
742
742
|
"bash",
|
|
743
|
+
"browser_attach",
|
|
743
744
|
"browser_click",
|
|
744
745
|
"browser_close",
|
|
746
|
+
"browser_detach",
|
|
745
747
|
"browser_extract",
|
|
746
748
|
"browser_fill_credential",
|
|
747
749
|
"browser_hover",
|
|
@@ -751,6 +753,7 @@ describe("Trust Store", () => {
|
|
|
751
753
|
"browser_scroll",
|
|
752
754
|
"browser_select_option",
|
|
753
755
|
"browser_snapshot",
|
|
756
|
+
"browser_status",
|
|
754
757
|
"browser_type",
|
|
755
758
|
"browser_wait_for",
|
|
756
759
|
"browser_wait_for_download",
|
|
@@ -1078,13 +1081,15 @@ describe("Trust Store", () => {
|
|
|
1078
1081
|
|
|
1079
1082
|
// ── default allow: browser tools ────────────────────────────
|
|
1080
1083
|
|
|
1081
|
-
test("all
|
|
1084
|
+
test("all browser tools have default allow rules", () => {
|
|
1082
1085
|
const templates = getDefaultRuleTemplates();
|
|
1083
1086
|
const browserTools = [
|
|
1084
1087
|
"browser_navigate",
|
|
1085
1088
|
"browser_snapshot",
|
|
1086
1089
|
"browser_screenshot",
|
|
1087
1090
|
"browser_close",
|
|
1091
|
+
"browser_attach",
|
|
1092
|
+
"browser_detach",
|
|
1088
1093
|
"browser_click",
|
|
1089
1094
|
"browser_type",
|
|
1090
1095
|
"browser_press_key",
|
|
@@ -1095,6 +1100,7 @@ describe("Trust Store", () => {
|
|
|
1095
1100
|
"browser_extract",
|
|
1096
1101
|
"browser_wait_for_download",
|
|
1097
1102
|
"browser_fill_credential",
|
|
1103
|
+
"browser_status",
|
|
1098
1104
|
];
|
|
1099
1105
|
|
|
1100
1106
|
for (const tool of browserTools) {
|
|
@@ -117,7 +117,7 @@ mock.module("../prompts/user-reference.js", () => ({
|
|
|
117
117
|
...realUserReference,
|
|
118
118
|
resolveUserReference: () => "my human",
|
|
119
119
|
resolveGuardianName: (guardianDisplayName?: string | null): string => {
|
|
120
|
-
// Mirror the real implementation:
|
|
120
|
+
// Mirror the real implementation: guardian persona name (from users/<slug>.md) > guardianDisplayName > default
|
|
121
121
|
const userRef = "my human"; // In tests, resolveUserReference() returns this
|
|
122
122
|
if (userRef !== "my human") return userRef;
|
|
123
123
|
if (guardianDisplayName && guardianDisplayName.trim().length > 0) {
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, test } from "bun:test";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getCatalogProvider,
|
|
7
|
+
listCatalogProviderIds,
|
|
8
|
+
} from "../tts/provider-catalog.js";
|
|
9
|
+
import type { TtsProviderId } from "../tts/types.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parity guard: daemon TTS provider catalog vs client TTS catalog JSON.
|
|
13
|
+
*
|
|
14
|
+
* The daemon maintains its canonical provider catalog in
|
|
15
|
+
* `assistant/src/tts/provider-catalog.ts`.
|
|
16
|
+
* The client-facing metadata lives in `meta/tts-provider-catalog.json` and is
|
|
17
|
+
* bundled into native clients at build time.
|
|
18
|
+
*
|
|
19
|
+
* These tests enforce that both catalogs stay in sync on the fields they
|
|
20
|
+
* share: provider IDs, ordering, and credential metadata needed for client
|
|
21
|
+
* key handling. CI will fail when they drift, forcing the developer to update
|
|
22
|
+
* whichever side fell behind.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Helpers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/** Resolve repo root (tests run from assistant/) */
|
|
30
|
+
function getRepoRoot(): string {
|
|
31
|
+
return join(process.cwd(), "..");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface ClientCatalogCredentialsGuide {
|
|
35
|
+
description: string;
|
|
36
|
+
url: string;
|
|
37
|
+
linkLabel: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ClientCatalogEntry {
|
|
41
|
+
id: string;
|
|
42
|
+
displayName: string;
|
|
43
|
+
subtitle: string;
|
|
44
|
+
setupMode: string;
|
|
45
|
+
setupHint: string;
|
|
46
|
+
credentialMode: "api-key" | "credential";
|
|
47
|
+
/** Present when credentialMode is "api-key". */
|
|
48
|
+
apiKeyProviderName?: string;
|
|
49
|
+
/** Present when credentialMode is "credential". */
|
|
50
|
+
credentialNamespace?: string;
|
|
51
|
+
credentialsGuide: ClientCatalogCredentialsGuide;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ClientCatalog {
|
|
55
|
+
version: number;
|
|
56
|
+
providers: ClientCatalogEntry[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function loadClientCatalog(): ClientCatalog {
|
|
60
|
+
const catalogPath = join(getRepoRoot(), "meta", "tts-provider-catalog.json");
|
|
61
|
+
const raw = readFileSync(catalogPath, "utf-8");
|
|
62
|
+
return JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Derive the expected credentialMode and associated metadata from a daemon
|
|
67
|
+
* catalog entry's secret requirements.
|
|
68
|
+
*
|
|
69
|
+
* Convention:
|
|
70
|
+
* - If the first secretRequirement.credentialStoreKey starts with "credential/",
|
|
71
|
+
* the client uses credentialMode "credential" and credentialNamespace is the
|
|
72
|
+
* second path segment.
|
|
73
|
+
* - Otherwise, the client uses credentialMode "api-key" and apiKeyProviderName
|
|
74
|
+
* is the bare key.
|
|
75
|
+
*/
|
|
76
|
+
function deriveCredentialMetadata(daemonEntry: {
|
|
77
|
+
secretRequirements: readonly { readonly credentialStoreKey: string }[];
|
|
78
|
+
}): {
|
|
79
|
+
credentialMode: "api-key" | "credential";
|
|
80
|
+
apiKeyProviderName?: string;
|
|
81
|
+
credentialNamespace?: string;
|
|
82
|
+
} {
|
|
83
|
+
const key = daemonEntry.secretRequirements[0]?.credentialStoreKey ?? "";
|
|
84
|
+
if (key.startsWith("credential/")) {
|
|
85
|
+
const parts = key.split("/");
|
|
86
|
+
return { credentialMode: "credential", credentialNamespace: parts[1] };
|
|
87
|
+
}
|
|
88
|
+
return { credentialMode: "api-key", apiKeyProviderName: key };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Tests
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
describe("TTS catalog parity: daemon vs client", () => {
|
|
96
|
+
// -----------------------------------------------------------------------
|
|
97
|
+
// Provider ID parity
|
|
98
|
+
// -----------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
test("client catalog provider IDs match daemon catalog provider IDs", () => {
|
|
101
|
+
const daemonIds = listCatalogProviderIds();
|
|
102
|
+
const clientCatalog = loadClientCatalog();
|
|
103
|
+
const clientIds = clientCatalog.providers.map((p) => p.id);
|
|
104
|
+
|
|
105
|
+
// Every daemon provider ID must appear in the client catalog
|
|
106
|
+
const missingInClient = daemonIds.filter((id) => !clientIds.includes(id));
|
|
107
|
+
if (missingInClient.length > 0) {
|
|
108
|
+
const message = [
|
|
109
|
+
"Daemon catalog has provider IDs not present in meta/tts-provider-catalog.json.",
|
|
110
|
+
"",
|
|
111
|
+
"Missing in client catalog:",
|
|
112
|
+
...missingInClient.map((id) => ` - ${id}`),
|
|
113
|
+
"",
|
|
114
|
+
"Add entries for these providers to meta/tts-provider-catalog.json.",
|
|
115
|
+
].join("\n");
|
|
116
|
+
expect(missingInClient, message).toEqual([]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Every client catalog provider ID must appear in the daemon catalog
|
|
120
|
+
const missingInDaemon = clientIds.filter(
|
|
121
|
+
(id) => !daemonIds.includes(id as never),
|
|
122
|
+
);
|
|
123
|
+
if (missingInDaemon.length > 0) {
|
|
124
|
+
const message = [
|
|
125
|
+
"Client catalog (meta/tts-provider-catalog.json) has provider IDs not present in daemon catalog.",
|
|
126
|
+
"",
|
|
127
|
+
"Missing in daemon catalog:",
|
|
128
|
+
...missingInDaemon.map((id) => ` - ${id}`),
|
|
129
|
+
"",
|
|
130
|
+
"Add entries for these providers to assistant/src/tts/provider-catalog.ts.",
|
|
131
|
+
].join("\n");
|
|
132
|
+
expect(missingInDaemon, message).toEqual([]);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("daemon and client catalog list providers in the same order", () => {
|
|
137
|
+
const daemonIds = listCatalogProviderIds();
|
|
138
|
+
const clientCatalog = loadClientCatalog();
|
|
139
|
+
const clientIds = clientCatalog.providers.map((p) => p.id);
|
|
140
|
+
|
|
141
|
+
expect(clientIds).toEqual([...daemonIds]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// -----------------------------------------------------------------------
|
|
145
|
+
// Credential metadata parity
|
|
146
|
+
// -----------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
test("each client catalog entry credentialMode matches its daemon counterpart", () => {
|
|
149
|
+
const clientCatalog = loadClientCatalog();
|
|
150
|
+
const violations: string[] = [];
|
|
151
|
+
|
|
152
|
+
for (const clientEntry of clientCatalog.providers) {
|
|
153
|
+
try {
|
|
154
|
+
const daemonEntry = getCatalogProvider(clientEntry.id as TtsProviderId);
|
|
155
|
+
const expected = deriveCredentialMetadata(daemonEntry);
|
|
156
|
+
if (clientEntry.credentialMode !== expected.credentialMode) {
|
|
157
|
+
violations.push(
|
|
158
|
+
`Provider "${clientEntry.id}": client credentialMode="${clientEntry.credentialMode}" ` +
|
|
159
|
+
`!= expected "${expected.credentialMode}" (derived from daemon credentialStoreKey)`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Unknown ID — covered by the provider ID parity test above.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (violations.length > 0) {
|
|
168
|
+
const message = [
|
|
169
|
+
"Credential mode mismatch between daemon and client TTS catalogs.",
|
|
170
|
+
"",
|
|
171
|
+
"Violations:",
|
|
172
|
+
...violations.map((v) => ` - ${v}`),
|
|
173
|
+
"",
|
|
174
|
+
"Update meta/tts-provider-catalog.json or assistant/src/tts/provider-catalog.ts to match.",
|
|
175
|
+
].join("\n");
|
|
176
|
+
expect(violations, message).toEqual([]);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("api-key providers: client apiKeyProviderName matches daemon credentialStoreKey", () => {
|
|
181
|
+
const clientCatalog = loadClientCatalog();
|
|
182
|
+
const violations: string[] = [];
|
|
183
|
+
|
|
184
|
+
for (const clientEntry of clientCatalog.providers) {
|
|
185
|
+
if (clientEntry.credentialMode !== "api-key") continue;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const daemonEntry = getCatalogProvider(clientEntry.id as TtsProviderId);
|
|
189
|
+
const expected = deriveCredentialMetadata(daemonEntry);
|
|
190
|
+
if (clientEntry.apiKeyProviderName !== expected.apiKeyProviderName) {
|
|
191
|
+
violations.push(
|
|
192
|
+
`Provider "${clientEntry.id}": client apiKeyProviderName="${clientEntry.apiKeyProviderName}" ` +
|
|
193
|
+
`!= expected "${expected.apiKeyProviderName}" (derived from daemon credentialStoreKey)`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
// Unknown ID — covered by the provider ID parity test above.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (violations.length > 0) {
|
|
202
|
+
const message = [
|
|
203
|
+
"apiKeyProviderName mismatch between daemon and client TTS catalogs.",
|
|
204
|
+
"",
|
|
205
|
+
"Violations:",
|
|
206
|
+
...violations.map((v) => ` - ${v}`),
|
|
207
|
+
"",
|
|
208
|
+
"Update meta/tts-provider-catalog.json or assistant/src/tts/provider-catalog.ts to match.",
|
|
209
|
+
].join("\n");
|
|
210
|
+
expect(violations, message).toEqual([]);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("credential providers: client credentialNamespace matches daemon credentialStoreKey namespace", () => {
|
|
215
|
+
const clientCatalog = loadClientCatalog();
|
|
216
|
+
const violations: string[] = [];
|
|
217
|
+
|
|
218
|
+
for (const clientEntry of clientCatalog.providers) {
|
|
219
|
+
if (clientEntry.credentialMode !== "credential") continue;
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const daemonEntry = getCatalogProvider(clientEntry.id as TtsProviderId);
|
|
223
|
+
const expected = deriveCredentialMetadata(daemonEntry);
|
|
224
|
+
if (clientEntry.credentialNamespace !== expected.credentialNamespace) {
|
|
225
|
+
violations.push(
|
|
226
|
+
`Provider "${clientEntry.id}": client credentialNamespace="${clientEntry.credentialNamespace}" ` +
|
|
227
|
+
`!= expected "${expected.credentialNamespace}" (derived from daemon credentialStoreKey)`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// Unknown ID — covered by the provider ID parity test above.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (violations.length > 0) {
|
|
236
|
+
const message = [
|
|
237
|
+
"credentialNamespace mismatch between daemon and client TTS catalogs.",
|
|
238
|
+
"",
|
|
239
|
+
"Violations:",
|
|
240
|
+
...violations.map((v) => ` - ${v}`),
|
|
241
|
+
"",
|
|
242
|
+
"Update meta/tts-provider-catalog.json or assistant/src/tts/provider-catalog.ts to match.",
|
|
243
|
+
].join("\n");
|
|
244
|
+
expect(violations, message).toEqual([]);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// -----------------------------------------------------------------------
|
|
249
|
+
// Display name parity
|
|
250
|
+
// -----------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
test("each client catalog entry displayName matches its daemon counterpart", () => {
|
|
253
|
+
const clientCatalog = loadClientCatalog();
|
|
254
|
+
const violations: string[] = [];
|
|
255
|
+
|
|
256
|
+
for (const clientEntry of clientCatalog.providers) {
|
|
257
|
+
try {
|
|
258
|
+
const daemonEntry = getCatalogProvider(clientEntry.id as TtsProviderId);
|
|
259
|
+
if (clientEntry.displayName !== daemonEntry.displayName) {
|
|
260
|
+
violations.push(
|
|
261
|
+
`Provider "${clientEntry.id}": client displayName="${clientEntry.displayName}" ` +
|
|
262
|
+
`!= daemon displayName="${daemonEntry.displayName}"`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
} catch {
|
|
266
|
+
// Unknown ID — covered by the provider ID parity test above.
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (violations.length > 0) {
|
|
271
|
+
const message = [
|
|
272
|
+
"Display name mismatch between daemon and client TTS catalogs.",
|
|
273
|
+
"",
|
|
274
|
+
"Violations:",
|
|
275
|
+
...violations.map((v) => ` - ${v}`),
|
|
276
|
+
"",
|
|
277
|
+
"Update meta/tts-provider-catalog.json or assistant/src/tts/provider-catalog.ts to match.",
|
|
278
|
+
].join("\n");
|
|
279
|
+
expect(violations, message).toEqual([]);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// -----------------------------------------------------------------------
|
|
284
|
+
// Structural sanity
|
|
285
|
+
// -----------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
test("client catalog JSON has a version field", () => {
|
|
288
|
+
const clientCatalog = loadClientCatalog();
|
|
289
|
+
expect(typeof clientCatalog.version).toBe("number");
|
|
290
|
+
expect(clientCatalog.version).toBeGreaterThanOrEqual(1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("client catalog has at least one provider", () => {
|
|
294
|
+
const clientCatalog = loadClientCatalog();
|
|
295
|
+
expect(clientCatalog.providers.length).toBeGreaterThan(0);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("every client catalog entry has required fields", () => {
|
|
299
|
+
const clientCatalog = loadClientCatalog();
|
|
300
|
+
const violations: string[] = [];
|
|
301
|
+
|
|
302
|
+
for (const entry of clientCatalog.providers) {
|
|
303
|
+
if (!entry.id || typeof entry.id !== "string") {
|
|
304
|
+
violations.push(`Entry missing or invalid 'id'`);
|
|
305
|
+
}
|
|
306
|
+
if (!entry.displayName || typeof entry.displayName !== "string") {
|
|
307
|
+
violations.push(`${entry.id}: missing or invalid 'displayName'`);
|
|
308
|
+
}
|
|
309
|
+
if (!entry.setupMode || typeof entry.setupMode !== "string") {
|
|
310
|
+
violations.push(`${entry.id}: missing or invalid 'setupMode'`);
|
|
311
|
+
}
|
|
312
|
+
if (
|
|
313
|
+
!entry.credentialMode ||
|
|
314
|
+
!["api-key", "credential"].includes(entry.credentialMode)
|
|
315
|
+
) {
|
|
316
|
+
violations.push(
|
|
317
|
+
`${entry.id}: missing or invalid 'credentialMode' (expected "api-key" or "credential")`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
if (entry.credentialMode === "api-key" && !entry.apiKeyProviderName) {
|
|
321
|
+
violations.push(
|
|
322
|
+
`${entry.id}: credentialMode is "api-key" but apiKeyProviderName is missing`,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
if (entry.credentialMode === "credential" && !entry.credentialNamespace) {
|
|
326
|
+
violations.push(
|
|
327
|
+
`${entry.id}: credentialMode is "credential" but credentialNamespace is missing`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (!entry.credentialsGuide || !entry.credentialsGuide.url) {
|
|
331
|
+
violations.push(`${entry.id}: missing or invalid 'credentialsGuide'`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (violations.length > 0) {
|
|
336
|
+
const message = [
|
|
337
|
+
"Client catalog entries have missing or invalid required fields.",
|
|
338
|
+
"",
|
|
339
|
+
"Violations:",
|
|
340
|
+
...violations.map((v) => ` - ${v}`),
|
|
341
|
+
].join("\n");
|
|
342
|
+
expect(violations, message).toEqual([]);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
});
|