@vellumai/assistant 0.6.3 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +298 -39
- package/Dockerfile +14 -3
- package/README.md +3 -4
- package/bun.lock +13 -16
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +121 -0
- package/knip.json +20 -3
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +5 -4
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +1094 -72
- package/package.json +9 -8
- package/scripts/generate-openapi.ts +50 -12
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +235 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +434 -12
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- 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 +12 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +52 -159
- package/src/__tests__/btw-routes.test.ts +54 -1
- package/src/__tests__/call-controller.test.ts +582 -22
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- 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__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +576 -502
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +83 -0
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +1458 -198
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +45 -10
- package/src/__tests__/contact-store-user-file.test.ts +511 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +618 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +32 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +62 -17
- package/src/__tests__/conversation-agent-loop.test.ts +510 -84
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +165 -9
- package/src/__tests__/conversation-error.test.ts +102 -1
- package/src/__tests__/conversation-history-web-search.test.ts +17 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +42 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +32 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +32 -16
- package/src/__tests__/conversation-queue.test.ts +932 -76
- package/src/__tests__/conversation-routes-disk-view.test.ts +299 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2790 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -143
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +120 -34
- package/src/__tests__/conversation-slash-unknown.test.ts +32 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +45 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +46 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -3
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +495 -3
- package/src/__tests__/credentials-cli.test.ts +32 -16
- package/src/__tests__/cross-provider-web-search.test.ts +230 -35
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -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__/edit-propagation.test.ts +280 -0
- 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__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +101 -15
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- 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 -3
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +44 -1
- 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 +166 -32
- 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__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +609 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- 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__/messaging-skill-split.test.ts +3 -34
- 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-from-url.test.ts +684 -0
- 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 +10 -84
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- 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 +95 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +257 -9
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +183 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1501 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +5 -1
- 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 +224 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- 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 +424 -6
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +34 -2
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +80 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -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 +19 -30
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +126 -53
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +26 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -49
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +10 -6
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +88 -113
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +442 -103
- 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-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- 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 +5 -22
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- 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-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -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-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -11
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +251 -19
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- 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/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -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/guardian-question-copy.ts +2 -2
- 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 +9 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -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 +28 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +130 -5
- 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/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +90 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +308 -16
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- 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 -10
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -2
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +53 -8
- package/src/cli.ts +0 -37
- 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/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- 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/services/reduce.ts +1 -1
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +28 -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/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 +0 -167
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +39 -10
- package/src/config/feature-flag-registry.json +63 -15
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +220 -22
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +65 -51
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +32 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -30
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +49 -14
- package/src/config/schemas/heartbeat.ts +27 -10
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +53 -4
- package/src/config/schemas/stt.ts +60 -0
- package/src/config/schemas/tts.ts +283 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +6 -2
- 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/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +272 -35
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +23 -2
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +96 -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/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +99 -6
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +85 -12
- package/src/daemon/conversation-agent-loop.ts +563 -104
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +46 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +85 -11
- package/src/daemon/conversation-messaging.ts +110 -7
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +591 -23
- package/src/daemon/conversation-queue-manager.ts +27 -0
- package/src/daemon/conversation-runtime-assembly.ts +769 -28
- package/src/daemon/conversation-slash.ts +38 -2
- package/src/daemon/conversation-surfaces.ts +483 -5
- package/src/daemon/conversation-tool-setup.ts +35 -5
- package/src/daemon/conversation-usage.ts +8 -5
- package/src/daemon/conversation.ts +193 -47
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +13 -3
- package/src/daemon/handlers/shared.ts +51 -1
- package/src/daemon/handlers/skills.ts +323 -79
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +185 -26
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +48 -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 +23 -1
- 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/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +463 -10
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +156 -22
- 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 +222 -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 +442 -0
- package/src/home/assistant-feed-authoring.ts +128 -0
- package/src/home/emit-feed-event.ts +162 -0
- package/src/home/feed-scheduler.ts +263 -0
- package/src/home/feed-types.ts +235 -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 +413 -0
- package/src/home/suggested-prompts.ts +101 -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__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +152 -0
- package/src/ipc/cli-server.ts +252 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +21 -0
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -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 +233 -0
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/admin.ts +18 -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 +74 -0
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +199 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +65 -1
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +8 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +104 -29
- package/src/memory/graph/extraction.test.ts +295 -2
- package/src/memory/graph/extraction.ts +181 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +257 -66
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -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 +39 -30
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +106 -5
- package/src/memory/jobs-worker.ts +26 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- 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/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- 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/slack-thread-store.ts +37 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +79 -6
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +155 -38
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +6 -12
- package/src/notifications/preference-extractor.ts +2 -6
- 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 +10 -0
- package/src/oauth/platform-connection.test.ts +145 -0
- package/src/oauth/platform-connection.ts +62 -31
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +218 -699
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +71 -75
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +164 -65
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -13
- package/src/platform/client.test.ts +10 -0
- package/src/platform/client.ts +19 -1
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +127 -3
- package/src/prompts/system-prompt.ts +78 -38
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +5 -3
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +335 -70
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +74 -3
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +502 -28
- package/src/providers/model-intents.ts +8 -8
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +530 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +579 -0
- package/src/providers/openrouter/client.ts +168 -4
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +21 -10
- package/src/providers/retry.ts +185 -39
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +883 -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 +323 -0
- package/src/providers/speech-to-text/resolve.ts +393 -6
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +102 -3
- package/src/runtime/AGENTS.md +45 -3
- package/src/runtime/__tests__/agent-wake.test.ts +872 -0
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -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 +553 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +34 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +15 -3
- 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/channel-reply-delivery.ts +106 -2
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +447 -11
- package/src/runtime/http-types.ts +29 -3
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- 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/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -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 +187 -8
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- 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 +618 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +247 -0
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -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/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/avatar-routes.ts +20 -4
- 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 +9 -10
- 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 +133 -0
- package/src/runtime/routes/conversation-routes.ts +487 -160
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +452 -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-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- 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 +36 -6
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +325 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +722 -91
- package/src/runtime/routes/settings-routes.ts +26 -7
- 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/trust-rules-routes.ts +30 -14
- 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.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +11 -3
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +426 -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 +340 -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 +71 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +58 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +122 -37
- package/src/security/secure-keys.ts +32 -10
- package/src/security/token-manager.ts +35 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/catalog-install.ts +31 -3
- 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-cache-store.ts +97 -0
- 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 +468 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +228 -0
- package/src/stt/stt-stream-session.ts +506 -0
- package/src/stt/types.ts +334 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +79 -27
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +166 -0
- package/src/tools/browser/browser-execution.ts +1208 -41
- 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 +205 -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/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +41 -7
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +25 -12
- package/src/tools/network/web-search.ts +20 -2
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +0 -9
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +6 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +11 -2
- package/src/tools/terminal/shell.ts +16 -4
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +29 -3
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +1061 -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 +219 -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 +44 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +199 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/types/tar-stream.d.ts +66 -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/json.ts +17 -0
- package/src/util/platform.ts +56 -12
- package/src/util/pricing.ts +78 -5
- 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 +24 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/git-service.ts +23 -4
- 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/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +32 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- 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/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -250
- package/src/__tests__/update-bulletin-format.test.ts +0 -122
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -277
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -63
- package/src/config/bundled-skills/browser/TOOLS.json +0 -393
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -32
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -175
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -558
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -149
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -220
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -251
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -107
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- 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/UPDATES.md +0 -38
- package/src/prompts/templates/USER.md +0 -13
- package/src/prompts/update-bulletin-format.ts +0 -68
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -128
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { join } from "node:path";
|
|
3
9
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
4
10
|
|
|
@@ -51,8 +57,14 @@ import { invalidateConfigCache, loadConfig } from "../config/loader.js";
|
|
|
51
57
|
import {
|
|
52
58
|
AssistantConfigSchema,
|
|
53
59
|
DEFAULT_ELEVENLABS_VOICE_ID,
|
|
60
|
+
SttServiceSchema,
|
|
61
|
+
TtsServiceSchema,
|
|
62
|
+
VALID_TTS_SERVICE_PROVIDERS,
|
|
54
63
|
} from "../config/schema.js";
|
|
64
|
+
import type { AssistantConfig } from "../config/types.js";
|
|
55
65
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
66
|
+
import { listCatalogProviderIds } from "../tts/provider-catalog.js";
|
|
67
|
+
import { resolveTtsConfig } from "../tts/tts-config-resolver.js";
|
|
56
68
|
|
|
57
69
|
// ---------------------------------------------------------------------------
|
|
58
70
|
// Helpers
|
|
@@ -69,9 +81,11 @@ function writeConfig(obj: unknown): void {
|
|
|
69
81
|
describe("AssistantConfigSchema", () => {
|
|
70
82
|
test("parses empty object with full defaults", () => {
|
|
71
83
|
const result = AssistantConfigSchema.parse({});
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
// services.inference now carries only `mode`; provider/model live under
|
|
85
|
+
// llm.default.{provider,model} (see PR 19 of unify-llm-callsites).
|
|
74
86
|
expect(result.services.inference.mode).toBe("your-own");
|
|
87
|
+
expect(result.llm.default.provider).toBe("anthropic");
|
|
88
|
+
expect(result.llm.default.model).toBe("claude-opus-4-7");
|
|
75
89
|
expect(result.services["image-generation"].provider).toBe("gemini");
|
|
76
90
|
expect(result.services["image-generation"].model).toBe(
|
|
77
91
|
"gemini-3.1-flash-image-preview",
|
|
@@ -81,12 +95,12 @@ describe("AssistantConfigSchema", () => {
|
|
|
81
95
|
"inference-provider-native",
|
|
82
96
|
);
|
|
83
97
|
expect(result.services["web-search"].mode).toBe("your-own");
|
|
84
|
-
expect(result.maxTokens).toBe(64000);
|
|
85
|
-
expect(result.thinking).toEqual({
|
|
98
|
+
expect(result.llm.default.maxTokens).toBe(64000);
|
|
99
|
+
expect(result.llm.default.thinking).toEqual({
|
|
86
100
|
enabled: true,
|
|
87
101
|
streamThinking: true,
|
|
88
102
|
});
|
|
89
|
-
expect(result.contextWindow).toEqual({
|
|
103
|
+
expect(result.llm.default.contextWindow).toEqual({
|
|
90
104
|
enabled: true,
|
|
91
105
|
maxInputTokens: 200000,
|
|
92
106
|
targetBudgetRatio: 0.3,
|
|
@@ -122,11 +136,13 @@ describe("AssistantConfigSchema", () => {
|
|
|
122
136
|
|
|
123
137
|
test("accepts valid complete config", () => {
|
|
124
138
|
const input = {
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
llm: {
|
|
140
|
+
default: {
|
|
141
|
+
provider: "openai" as const,
|
|
142
|
+
model: "gpt-4",
|
|
143
|
+
maxTokens: 4096,
|
|
144
|
+
},
|
|
127
145
|
},
|
|
128
|
-
maxTokens: 4096,
|
|
129
|
-
thinking: { enabled: true },
|
|
130
146
|
timeouts: {
|
|
131
147
|
shellDefaultTimeoutSec: 30,
|
|
132
148
|
shellMaxTimeoutSec: 300,
|
|
@@ -142,13 +158,186 @@ describe("AssistantConfigSchema", () => {
|
|
|
142
158
|
auditLog: { retentionDays: 30 },
|
|
143
159
|
};
|
|
144
160
|
const result = AssistantConfigSchema.parse(input);
|
|
145
|
-
expect(result.
|
|
146
|
-
expect(result.
|
|
147
|
-
expect(result.maxTokens).toBe(4096);
|
|
148
|
-
expect(result.thinking.enabled).toBe(true);
|
|
161
|
+
expect(result.llm.default.provider).toBe("openai");
|
|
162
|
+
expect(result.llm.default.model).toBe("gpt-4");
|
|
163
|
+
expect(result.llm.default.maxTokens).toBe(4096);
|
|
164
|
+
expect(result.llm.default.thinking.enabled).toBe(true);
|
|
149
165
|
expect(result.secretDetection.action).toBe("block");
|
|
150
166
|
});
|
|
151
167
|
|
|
168
|
+
test("applies llm defaults when llm key is omitted", () => {
|
|
169
|
+
const result = AssistantConfigSchema.parse({});
|
|
170
|
+
expect(result.llm).toBeDefined();
|
|
171
|
+
expect(result.llm.default).toEqual({
|
|
172
|
+
provider: "anthropic",
|
|
173
|
+
model: "claude-opus-4-7",
|
|
174
|
+
maxTokens: 64000,
|
|
175
|
+
effort: "max",
|
|
176
|
+
speed: "standard",
|
|
177
|
+
temperature: null,
|
|
178
|
+
thinking: { enabled: true, streamThinking: true },
|
|
179
|
+
contextWindow: {
|
|
180
|
+
enabled: true,
|
|
181
|
+
maxInputTokens: 200000,
|
|
182
|
+
targetBudgetRatio: 0.3,
|
|
183
|
+
compactThreshold: 0.8,
|
|
184
|
+
summaryBudgetRatio: 0.05,
|
|
185
|
+
overflowRecovery: {
|
|
186
|
+
enabled: true,
|
|
187
|
+
safetyMarginRatio: 0.05,
|
|
188
|
+
maxAttempts: 3,
|
|
189
|
+
interactiveLatestTurnCompression: "summarize",
|
|
190
|
+
nonInteractiveLatestTurnCompression: "truncate",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
openrouter: { only: [] },
|
|
194
|
+
});
|
|
195
|
+
expect(result.llm.profiles).toEqual({});
|
|
196
|
+
expect(result.llm.callSites).toEqual({});
|
|
197
|
+
expect(result.llm.pricingOverrides).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("accepts an explicit llm block with profiles and call sites", () => {
|
|
201
|
+
const input = {
|
|
202
|
+
llm: {
|
|
203
|
+
default: {
|
|
204
|
+
provider: "anthropic" as const,
|
|
205
|
+
model: "claude-opus-4-7",
|
|
206
|
+
maxTokens: 32000,
|
|
207
|
+
effort: "high" as const,
|
|
208
|
+
speed: "fast" as const,
|
|
209
|
+
temperature: null,
|
|
210
|
+
thinking: { enabled: true, streamThinking: false },
|
|
211
|
+
contextWindow: {
|
|
212
|
+
enabled: true,
|
|
213
|
+
maxInputTokens: 200000,
|
|
214
|
+
targetBudgetRatio: 0.3,
|
|
215
|
+
compactThreshold: 0.8,
|
|
216
|
+
summaryBudgetRatio: 0.05,
|
|
217
|
+
overflowRecovery: {
|
|
218
|
+
enabled: true,
|
|
219
|
+
safetyMarginRatio: 0.05,
|
|
220
|
+
maxAttempts: 3,
|
|
221
|
+
interactiveLatestTurnCompression: "summarize" as const,
|
|
222
|
+
nonInteractiveLatestTurnCompression: "truncate" as const,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
profiles: {
|
|
227
|
+
fast: { speed: "fast" as const, effort: "low" as const },
|
|
228
|
+
},
|
|
229
|
+
callSites: {
|
|
230
|
+
mainAgent: { profile: "fast" },
|
|
231
|
+
commitMessage: { maxTokens: 256 },
|
|
232
|
+
},
|
|
233
|
+
pricingOverrides: [],
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
const result = AssistantConfigSchema.parse(input);
|
|
237
|
+
expect(result.llm.default.model).toBe("claude-opus-4-7");
|
|
238
|
+
expect(result.llm.default.speed).toBe("fast");
|
|
239
|
+
expect(result.llm.profiles?.fast).toEqual({
|
|
240
|
+
speed: "fast",
|
|
241
|
+
effort: "low",
|
|
242
|
+
});
|
|
243
|
+
expect(result.llm.callSites?.mainAgent).toEqual({ profile: "fast" });
|
|
244
|
+
expect(result.llm.callSites?.commitMessage).toEqual({ maxTokens: 256 });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("rejects an llm.callSites entry that references an undefined profile", () => {
|
|
248
|
+
const input = {
|
|
249
|
+
llm: {
|
|
250
|
+
default: {
|
|
251
|
+
provider: "anthropic" as const,
|
|
252
|
+
model: "claude-opus-4-6",
|
|
253
|
+
maxTokens: 64000,
|
|
254
|
+
effort: "max" as const,
|
|
255
|
+
speed: "standard" as const,
|
|
256
|
+
temperature: null,
|
|
257
|
+
thinking: { enabled: true, streamThinking: true },
|
|
258
|
+
contextWindow: {
|
|
259
|
+
enabled: true,
|
|
260
|
+
maxInputTokens: 200000,
|
|
261
|
+
targetBudgetRatio: 0.3,
|
|
262
|
+
compactThreshold: 0.8,
|
|
263
|
+
summaryBudgetRatio: 0.05,
|
|
264
|
+
overflowRecovery: {
|
|
265
|
+
enabled: true,
|
|
266
|
+
safetyMarginRatio: 0.05,
|
|
267
|
+
maxAttempts: 3,
|
|
268
|
+
interactiveLatestTurnCompression: "summarize" as const,
|
|
269
|
+
nonInteractiveLatestTurnCompression: "truncate" as const,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
callSites: {
|
|
274
|
+
mainAgent: { profile: "missing-profile" },
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
expect(() => AssistantConfigSchema.parse(input)).toThrow(/missing-profile/);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("legacy top-level inference keys are ignored after PR 19 cleanup", () => {
|
|
282
|
+
// The legacy keys (top-level maxTokens, effort, speed, thinking,
|
|
283
|
+
// contextWindow, services.inference.{provider,model}) were removed in PR
|
|
284
|
+
// 19. Configs that still carry them parse cleanly because Zod strips
|
|
285
|
+
// unknown fields, and migration 039 erases them from the on-disk file
|
|
286
|
+
// entirely.
|
|
287
|
+
const input = {
|
|
288
|
+
services: {
|
|
289
|
+
inference: { provider: "openai", model: "gpt-4" },
|
|
290
|
+
},
|
|
291
|
+
maxTokens: 8000,
|
|
292
|
+
effort: "medium",
|
|
293
|
+
speed: "fast",
|
|
294
|
+
thinking: { enabled: false, streamThinking: false },
|
|
295
|
+
};
|
|
296
|
+
const result = AssistantConfigSchema.parse(input);
|
|
297
|
+
expect((result as Record<string, unknown>).maxTokens).toBeUndefined();
|
|
298
|
+
expect((result as Record<string, unknown>).effort).toBeUndefined();
|
|
299
|
+
expect((result as Record<string, unknown>).speed).toBeUndefined();
|
|
300
|
+
expect((result as Record<string, unknown>).thinking).toBeUndefined();
|
|
301
|
+
expect(
|
|
302
|
+
(result.services.inference as Record<string, unknown>).provider,
|
|
303
|
+
).toBeUndefined();
|
|
304
|
+
expect(
|
|
305
|
+
(result.services.inference as Record<string, unknown>).model,
|
|
306
|
+
).toBeUndefined();
|
|
307
|
+
expect(result.llm.default.provider).toBe("anthropic");
|
|
308
|
+
expect(result.llm.default.model).toBe("claude-opus-4-7");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("partial llm config (empty `llm: {}`) doesn't trigger full config reset", () => {
|
|
312
|
+
// Regression guard: previously LLMConfigBase had no schema-level defaults,
|
|
313
|
+
// so any `llm: {}` block would fail validation and the loader's recovery
|
|
314
|
+
// path would fall through to `cloneDefaultConfig()`, discarding unrelated
|
|
315
|
+
// valid settings (like a custom `llm.default.maxTokens`). With leaf-level
|
|
316
|
+
// defaults, `llm: {}` parses cleanly and the user's other settings are
|
|
317
|
+
// preserved.
|
|
318
|
+
const result = AssistantConfigSchema.parse({
|
|
319
|
+
llm: { default: { maxTokens: 32000 } },
|
|
320
|
+
});
|
|
321
|
+
expect(result.llm.default.maxTokens).toBe(32000);
|
|
322
|
+
expect(result.llm.default.provider).toBe("anthropic");
|
|
323
|
+
expect(result.llm.default.model).toBe("claude-opus-4-7");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("llm.default with one missing field still parses (defaults applied)", () => {
|
|
327
|
+
// A user can override a single field of `llm.default` without specifying
|
|
328
|
+
// the rest — schema-level defaults fill in everything that wasn't set.
|
|
329
|
+
const result = AssistantConfigSchema.parse({
|
|
330
|
+
llm: { default: { model: "claude-haiku-4-5" } },
|
|
331
|
+
});
|
|
332
|
+
expect(result.llm.default.model).toBe("claude-haiku-4-5");
|
|
333
|
+
expect(result.llm.default.provider).toBe("anthropic");
|
|
334
|
+
expect(result.llm.default.maxTokens).toBe(64000);
|
|
335
|
+
expect(result.llm.default.thinking).toEqual({
|
|
336
|
+
enabled: true,
|
|
337
|
+
streamThinking: true,
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
152
341
|
test("applies rollout defaults for dynamic budget", () => {
|
|
153
342
|
const result = AssistantConfigSchema.parse({});
|
|
154
343
|
expect(result.memory.retrieval.dynamicBudget).toEqual({
|
|
@@ -166,7 +355,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
166
355
|
enqueueIntervalMs: 6 * 60 * 60 * 1000,
|
|
167
356
|
supersededItemRetentionMs: 30 * 24 * 60 * 60 * 1000,
|
|
168
357
|
conversationRetentionDays: 0,
|
|
169
|
-
llmRequestLogRetentionMs: 1 *
|
|
358
|
+
llmRequestLogRetentionMs: 1 * 60 * 60 * 1000,
|
|
170
359
|
});
|
|
171
360
|
});
|
|
172
361
|
|
|
@@ -177,15 +366,74 @@ describe("AssistantConfigSchema", () => {
|
|
|
177
366
|
expect(result.success).toBe(false);
|
|
178
367
|
});
|
|
179
368
|
|
|
369
|
+
test("accepts memory.cleanup.llmRequestLogRetentionMs at the 365-day boundary", () => {
|
|
370
|
+
const max = 365 * 24 * 60 * 60 * 1000;
|
|
371
|
+
const result = AssistantConfigSchema.safeParse({
|
|
372
|
+
memory: { cleanup: { llmRequestLogRetentionMs: max } },
|
|
373
|
+
});
|
|
374
|
+
expect(result.success).toBe(true);
|
|
375
|
+
if (result.success) {
|
|
376
|
+
expect(result.data.memory.cleanup.llmRequestLogRetentionMs).toBe(max);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("rejects memory.cleanup.llmRequestLogRetentionMs above 365 days", () => {
|
|
381
|
+
// This must match the gateway's MAX_LLM_REQUEST_LOG_RETENTION_MS. Without
|
|
382
|
+
// the Zod .max(), a manually edited config.json with a large value would
|
|
383
|
+
// be silently accepted by the daemon and then truncated by the macOS
|
|
384
|
+
// picker on the next PATCH — a quiet data-loss bug.
|
|
385
|
+
const overMax = 365 * 24 * 60 * 60 * 1000 + 1;
|
|
386
|
+
const result = AssistantConfigSchema.safeParse({
|
|
387
|
+
memory: { cleanup: { llmRequestLogRetentionMs: overMax } },
|
|
388
|
+
});
|
|
389
|
+
expect(result.success).toBe(false);
|
|
390
|
+
if (!result.success) {
|
|
391
|
+
expect(
|
|
392
|
+
result.error.issues.some((i) =>
|
|
393
|
+
i.path.includes("llmRequestLogRetentionMs"),
|
|
394
|
+
),
|
|
395
|
+
).toBe(true);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("rejects negative memory.cleanup.llmRequestLogRetentionMs", () => {
|
|
400
|
+
const result = AssistantConfigSchema.safeParse({
|
|
401
|
+
memory: { cleanup: { llmRequestLogRetentionMs: -1 } },
|
|
402
|
+
});
|
|
403
|
+
expect(result.success).toBe(false);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("accepts null memory.cleanup.llmRequestLogRetentionMs (keep forever)", () => {
|
|
407
|
+
const result = AssistantConfigSchema.safeParse({
|
|
408
|
+
memory: { cleanup: { llmRequestLogRetentionMs: null } },
|
|
409
|
+
});
|
|
410
|
+
expect(result.success).toBe(true);
|
|
411
|
+
if (result.success) {
|
|
412
|
+
expect(result.data.memory.cleanup.llmRequestLogRetentionMs).toBeNull();
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("accepts memory.cleanup.llmRequestLogRetentionMs: 0 (prune immediately)", () => {
|
|
417
|
+
const result = AssistantConfigSchema.safeParse({
|
|
418
|
+
memory: { cleanup: { llmRequestLogRetentionMs: 0 } },
|
|
419
|
+
});
|
|
420
|
+
expect(result.success).toBe(true);
|
|
421
|
+
if (result.success) {
|
|
422
|
+
expect(result.data.memory.cleanup.llmRequestLogRetentionMs).toBe(0);
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
180
426
|
test("rejects invalid provider", () => {
|
|
181
427
|
const result = AssistantConfigSchema.safeParse({
|
|
182
|
-
|
|
428
|
+
llm: { default: { provider: "invalid" } },
|
|
183
429
|
});
|
|
184
430
|
expect(result.success).toBe(false);
|
|
185
431
|
});
|
|
186
432
|
|
|
187
|
-
test("rejects negative maxTokens", () => {
|
|
188
|
-
const result = AssistantConfigSchema.safeParse({
|
|
433
|
+
test("rejects negative llm.default.maxTokens", () => {
|
|
434
|
+
const result = AssistantConfigSchema.safeParse({
|
|
435
|
+
llm: { default: { maxTokens: -100 } },
|
|
436
|
+
});
|
|
189
437
|
expect(result.success).toBe(false);
|
|
190
438
|
if (!result.success) {
|
|
191
439
|
expect(
|
|
@@ -194,8 +442,10 @@ describe("AssistantConfigSchema", () => {
|
|
|
194
442
|
}
|
|
195
443
|
});
|
|
196
444
|
|
|
197
|
-
test("rejects non-integer maxTokens", () => {
|
|
198
|
-
const result = AssistantConfigSchema.safeParse({
|
|
445
|
+
test("rejects non-integer llm.default.maxTokens", () => {
|
|
446
|
+
const result = AssistantConfigSchema.safeParse({
|
|
447
|
+
llm: { default: { maxTokens: 3.14 } },
|
|
448
|
+
});
|
|
199
449
|
expect(result.success).toBe(false);
|
|
200
450
|
if (!result.success) {
|
|
201
451
|
expect(
|
|
@@ -204,9 +454,9 @@ describe("AssistantConfigSchema", () => {
|
|
|
204
454
|
}
|
|
205
455
|
});
|
|
206
456
|
|
|
207
|
-
test("rejects string maxTokens", () => {
|
|
457
|
+
test("rejects string llm.default.maxTokens", () => {
|
|
208
458
|
const result = AssistantConfigSchema.safeParse({
|
|
209
|
-
maxTokens: "not-a-number",
|
|
459
|
+
llm: { default: { maxTokens: "not-a-number" } },
|
|
210
460
|
});
|
|
211
461
|
expect(result.success).toBe(false);
|
|
212
462
|
if (!result.success) {
|
|
@@ -232,7 +482,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
232
482
|
|
|
233
483
|
test("rejects invalid thinking config", () => {
|
|
234
484
|
const result = AssistantConfigSchema.safeParse({
|
|
235
|
-
thinking: { enabled: "yes" },
|
|
485
|
+
llm: { default: { thinking: { enabled: "yes" } } },
|
|
236
486
|
});
|
|
237
487
|
expect(result.success).toBe(false);
|
|
238
488
|
if (!result.success) {
|
|
@@ -242,16 +492,21 @@ describe("AssistantConfigSchema", () => {
|
|
|
242
492
|
|
|
243
493
|
test("rejects contextWindow targetBudgetRatio >= compactThreshold", () => {
|
|
244
494
|
const result = AssistantConfigSchema.safeParse({
|
|
245
|
-
|
|
495
|
+
llm: {
|
|
496
|
+
default: {
|
|
497
|
+
contextWindow: { targetBudgetRatio: 0.8, compactThreshold: 0.8 },
|
|
498
|
+
},
|
|
499
|
+
},
|
|
246
500
|
});
|
|
247
501
|
expect(result.success).toBe(false);
|
|
248
502
|
if (!result.success) {
|
|
249
503
|
expect(
|
|
250
504
|
result.error.issues.some(
|
|
251
505
|
(issue) =>
|
|
252
|
-
issue.path.join(".") ===
|
|
506
|
+
issue.path.join(".") ===
|
|
507
|
+
"llm.default.contextWindow.targetBudgetRatio" &&
|
|
253
508
|
issue.message.includes(
|
|
254
|
-
"must be less than contextWindow.compactThreshold",
|
|
509
|
+
"must be less than llm.default.contextWindow.compactThreshold",
|
|
255
510
|
),
|
|
256
511
|
),
|
|
257
512
|
).toBe(true);
|
|
@@ -261,7 +516,11 @@ describe("AssistantConfigSchema", () => {
|
|
|
261
516
|
test("rejects overflowRecovery safetyMarginRatio out of (0,1) range", () => {
|
|
262
517
|
for (const bad of [0, 1, -0.1, 1.5]) {
|
|
263
518
|
const result = AssistantConfigSchema.safeParse({
|
|
264
|
-
|
|
519
|
+
llm: {
|
|
520
|
+
default: {
|
|
521
|
+
contextWindow: { overflowRecovery: { safetyMarginRatio: bad } },
|
|
522
|
+
},
|
|
523
|
+
},
|
|
265
524
|
});
|
|
266
525
|
expect(result.success).toBe(false);
|
|
267
526
|
if (!result.success) {
|
|
@@ -276,8 +535,12 @@ describe("AssistantConfigSchema", () => {
|
|
|
276
535
|
|
|
277
536
|
test("rejects invalid overflowRecovery interactiveLatestTurnCompression", () => {
|
|
278
537
|
const result = AssistantConfigSchema.safeParse({
|
|
279
|
-
|
|
280
|
-
|
|
538
|
+
llm: {
|
|
539
|
+
default: {
|
|
540
|
+
contextWindow: {
|
|
541
|
+
overflowRecovery: { interactiveLatestTurnCompression: "explode" },
|
|
542
|
+
},
|
|
543
|
+
},
|
|
281
544
|
},
|
|
282
545
|
});
|
|
283
546
|
expect(result.success).toBe(false);
|
|
@@ -292,8 +555,12 @@ describe("AssistantConfigSchema", () => {
|
|
|
292
555
|
|
|
293
556
|
test("rejects invalid overflowRecovery nonInteractiveLatestTurnCompression", () => {
|
|
294
557
|
const result = AssistantConfigSchema.safeParse({
|
|
295
|
-
|
|
296
|
-
|
|
558
|
+
llm: {
|
|
559
|
+
default: {
|
|
560
|
+
contextWindow: {
|
|
561
|
+
overflowRecovery: { nonInteractiveLatestTurnCompression: "nope" },
|
|
562
|
+
},
|
|
563
|
+
},
|
|
297
564
|
},
|
|
298
565
|
});
|
|
299
566
|
expect(result.success).toBe(false);
|
|
@@ -364,7 +631,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
364
631
|
"ollama",
|
|
365
632
|
] as const) {
|
|
366
633
|
const result = AssistantConfigSchema.safeParse({
|
|
367
|
-
|
|
634
|
+
llm: { default: { provider } },
|
|
368
635
|
});
|
|
369
636
|
expect(result.success).toBe(true);
|
|
370
637
|
}
|
|
@@ -381,13 +648,19 @@ describe("AssistantConfigSchema", () => {
|
|
|
381
648
|
|
|
382
649
|
test("provides helpful error messages", () => {
|
|
383
650
|
const result = AssistantConfigSchema.safeParse({
|
|
384
|
-
maxTokens: -1,
|
|
651
|
+
llm: { default: { maxTokens: -1 } },
|
|
385
652
|
secretDetection: { action: "explode" },
|
|
386
653
|
});
|
|
387
654
|
expect(result.success).toBe(false);
|
|
388
655
|
if (!result.success) {
|
|
389
656
|
const messages = result.error.issues.map((i) => i.message);
|
|
390
|
-
|
|
657
|
+
// The llm.default.maxTokens validation rejects -1 with a "Too small"
|
|
658
|
+
// / "expected number to be >0" message from Zod's default issue text.
|
|
659
|
+
expect(
|
|
660
|
+
messages.some(
|
|
661
|
+
(m) => m.includes("positive") || /expected number to be >0/i.test(m),
|
|
662
|
+
),
|
|
663
|
+
).toBe(true);
|
|
391
664
|
expect(
|
|
392
665
|
messages.some(
|
|
393
666
|
(m) =>
|
|
@@ -402,6 +675,11 @@ describe("AssistantConfigSchema", () => {
|
|
|
402
675
|
expect(result.permissions).toEqual({
|
|
403
676
|
mode: "workspace",
|
|
404
677
|
hostAccess: false,
|
|
678
|
+
autoApproveUpTo: {
|
|
679
|
+
conversation: "low",
|
|
680
|
+
background: "medium",
|
|
681
|
+
headless: "none",
|
|
682
|
+
},
|
|
405
683
|
});
|
|
406
684
|
});
|
|
407
685
|
|
|
@@ -438,6 +716,119 @@ describe("AssistantConfigSchema", () => {
|
|
|
438
716
|
}
|
|
439
717
|
});
|
|
440
718
|
|
|
719
|
+
test("defaults autoApproveUpTo to per-context object when not specified", () => {
|
|
720
|
+
const result = AssistantConfigSchema.parse({
|
|
721
|
+
permissions: { mode: "workspace" },
|
|
722
|
+
});
|
|
723
|
+
expect(result.permissions.autoApproveUpTo).toEqual({
|
|
724
|
+
conversation: "low",
|
|
725
|
+
background: "medium",
|
|
726
|
+
headless: "none",
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
test("accepts autoApproveUpTo none", () => {
|
|
731
|
+
const result = AssistantConfigSchema.parse({
|
|
732
|
+
permissions: { autoApproveUpTo: "none" },
|
|
733
|
+
});
|
|
734
|
+
expect(result.permissions.autoApproveUpTo).toBe("none");
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("accepts autoApproveUpTo low", () => {
|
|
738
|
+
const result = AssistantConfigSchema.parse({
|
|
739
|
+
permissions: { autoApproveUpTo: "low" },
|
|
740
|
+
});
|
|
741
|
+
expect(result.permissions.autoApproveUpTo).toBe("low");
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test("accepts autoApproveUpTo medium", () => {
|
|
745
|
+
const result = AssistantConfigSchema.parse({
|
|
746
|
+
permissions: { autoApproveUpTo: "medium" },
|
|
747
|
+
});
|
|
748
|
+
expect(result.permissions.autoApproveUpTo).toBe("medium");
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
test("rejects autoApproveUpTo high", () => {
|
|
752
|
+
const result = AssistantConfigSchema.safeParse({
|
|
753
|
+
permissions: { autoApproveUpTo: "high" },
|
|
754
|
+
});
|
|
755
|
+
expect(result.success).toBe(false);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test("rejects invalid autoApproveUpTo string", () => {
|
|
759
|
+
const result = AssistantConfigSchema.safeParse({
|
|
760
|
+
permissions: { autoApproveUpTo: "everything" },
|
|
761
|
+
});
|
|
762
|
+
expect(result.success).toBe(false);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
test("autoApproveUpTo round-trips through JSON serialization", () => {
|
|
766
|
+
const original = AssistantConfigSchema.parse({
|
|
767
|
+
permissions: { autoApproveUpTo: "medium" },
|
|
768
|
+
});
|
|
769
|
+
const json = JSON.stringify(original);
|
|
770
|
+
const parsed = AssistantConfigSchema.parse(JSON.parse(json));
|
|
771
|
+
expect(parsed.permissions.autoApproveUpTo).toBe("medium");
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("accepts autoApproveUpTo as per-context object", () => {
|
|
775
|
+
const result = AssistantConfigSchema.parse({
|
|
776
|
+
permissions: {
|
|
777
|
+
autoApproveUpTo: {
|
|
778
|
+
conversation: "low",
|
|
779
|
+
background: "medium",
|
|
780
|
+
headless: "none",
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
expect(result.permissions.autoApproveUpTo).toEqual({
|
|
785
|
+
conversation: "low",
|
|
786
|
+
background: "medium",
|
|
787
|
+
headless: "none",
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
test("per-context object applies defaults for omitted keys", () => {
|
|
792
|
+
const result = AssistantConfigSchema.parse({
|
|
793
|
+
permissions: {
|
|
794
|
+
autoApproveUpTo: {},
|
|
795
|
+
},
|
|
796
|
+
});
|
|
797
|
+
expect(result.permissions.autoApproveUpTo).toEqual({
|
|
798
|
+
conversation: "low",
|
|
799
|
+
background: "medium",
|
|
800
|
+
headless: "none",
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
test("per-context object rejects invalid enum values", () => {
|
|
805
|
+
const result = AssistantConfigSchema.safeParse({
|
|
806
|
+
permissions: {
|
|
807
|
+
autoApproveUpTo: { conversation: "high" },
|
|
808
|
+
},
|
|
809
|
+
});
|
|
810
|
+
expect(result.success).toBe(false);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
test("per-context object round-trips through JSON serialization", () => {
|
|
814
|
+
const original = AssistantConfigSchema.parse({
|
|
815
|
+
permissions: {
|
|
816
|
+
autoApproveUpTo: {
|
|
817
|
+
conversation: "none",
|
|
818
|
+
background: "low",
|
|
819
|
+
headless: "medium",
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
});
|
|
823
|
+
const json = JSON.stringify(original);
|
|
824
|
+
const parsed = AssistantConfigSchema.parse(JSON.parse(json));
|
|
825
|
+
expect(parsed.permissions.autoApproveUpTo).toEqual({
|
|
826
|
+
conversation: "none",
|
|
827
|
+
background: "low",
|
|
828
|
+
headless: "medium",
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
|
|
441
832
|
test("applies workspaceGit defaults including interactiveGitTimeoutMs", () => {
|
|
442
833
|
const result = AssistantConfigSchema.parse({});
|
|
443
834
|
expect(result.workspaceGit).toEqual({
|
|
@@ -451,11 +842,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
451
842
|
enrichmentMaxRetries: 2,
|
|
452
843
|
commitMessageLLM: {
|
|
453
844
|
enabled: false,
|
|
454
|
-
useConfiguredProvider: true,
|
|
455
|
-
providerFastModelOverrides: {},
|
|
456
845
|
timeoutMs: 600,
|
|
457
|
-
maxTokens: 120,
|
|
458
|
-
temperature: 0.2,
|
|
459
846
|
maxFilesInPrompt: 30,
|
|
460
847
|
maxDiffBytes: 12000,
|
|
461
848
|
minRemainingTurnBudgetMs: 1000,
|
|
@@ -509,11 +896,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
509
896
|
const result = AssistantConfigSchema.parse({});
|
|
510
897
|
const llm = result.workspaceGit.commitMessageLLM;
|
|
511
898
|
expect(llm.enabled).toBe(false);
|
|
512
|
-
expect(llm.useConfiguredProvider).toBe(true);
|
|
513
|
-
expect(llm.providerFastModelOverrides).toEqual({});
|
|
514
899
|
expect(llm.timeoutMs).toBe(600);
|
|
515
|
-
expect(llm.maxTokens).toBe(120);
|
|
516
|
-
expect(llm.temperature).toBe(0.2);
|
|
517
900
|
expect(llm.maxFilesInPrompt).toBe(30);
|
|
518
901
|
expect(llm.maxDiffBytes).toBe(12000);
|
|
519
902
|
expect(llm.minRemainingTurnBudgetMs).toBe(1000);
|
|
@@ -526,13 +909,6 @@ describe("AssistantConfigSchema", () => {
|
|
|
526
909
|
expect(result.success).toBe(false);
|
|
527
910
|
});
|
|
528
911
|
|
|
529
|
-
test("rejects commitMessageLLM.temperature > 2", () => {
|
|
530
|
-
const result = AssistantConfigSchema.safeParse({
|
|
531
|
-
workspaceGit: { commitMessageLLM: { temperature: 2.5 } },
|
|
532
|
-
});
|
|
533
|
-
expect(result.success).toBe(false);
|
|
534
|
-
});
|
|
535
|
-
|
|
536
912
|
test("breaker settings have correct defaults", () => {
|
|
537
913
|
const result = AssistantConfigSchema.parse({});
|
|
538
914
|
const breaker = result.workspaceGit.commitMessageLLM.breaker;
|
|
@@ -547,14 +923,12 @@ describe("AssistantConfigSchema", () => {
|
|
|
547
923
|
commitMessageLLM: {
|
|
548
924
|
enabled: true,
|
|
549
925
|
timeoutMs: 1000,
|
|
550
|
-
temperature: 0.5,
|
|
551
926
|
breaker: { openAfterFailures: 5 },
|
|
552
927
|
},
|
|
553
928
|
},
|
|
554
929
|
});
|
|
555
930
|
expect(result.workspaceGit.commitMessageLLM.enabled).toBe(true);
|
|
556
931
|
expect(result.workspaceGit.commitMessageLLM.timeoutMs).toBe(1000);
|
|
557
|
-
expect(result.workspaceGit.commitMessageLLM.temperature).toBe(0.5);
|
|
558
932
|
expect(result.workspaceGit.commitMessageLLM.breaker.openAfterFailures).toBe(
|
|
559
933
|
5,
|
|
560
934
|
);
|
|
@@ -564,18 +938,18 @@ describe("AssistantConfigSchema", () => {
|
|
|
564
938
|
);
|
|
565
939
|
});
|
|
566
940
|
|
|
567
|
-
test("
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const result = AssistantConfigSchema.safeParse({
|
|
576
|
-
workspaceGit: { commitMessageLLM: { maxTokens: 3.5 } },
|
|
941
|
+
test("ignores legacy commitMessageLLM.{maxTokens,temperature} keys", () => {
|
|
942
|
+
// PR 19 removed maxTokens/temperature from the schema; Zod silently
|
|
943
|
+
// strips them on parse. Migration 039 erases them from disk so they
|
|
944
|
+
// don't accumulate over time.
|
|
945
|
+
const result = AssistantConfigSchema.parse({
|
|
946
|
+
workspaceGit: {
|
|
947
|
+
commitMessageLLM: { maxTokens: 200, temperature: 0.5 },
|
|
948
|
+
},
|
|
577
949
|
});
|
|
578
|
-
|
|
950
|
+
const cm = result.workspaceGit.commitMessageLLM as Record<string, unknown>;
|
|
951
|
+
expect(cm.maxTokens).toBeUndefined();
|
|
952
|
+
expect(cm.temperature).toBeUndefined();
|
|
579
953
|
});
|
|
580
954
|
|
|
581
955
|
// ── Calls config ────────────────────────────────────────────────────
|
|
@@ -602,8 +976,6 @@ describe("AssistantConfigSchema", () => {
|
|
|
602
976
|
},
|
|
603
977
|
voice: {
|
|
604
978
|
language: "en-US",
|
|
605
|
-
transcriptionProvider: "Deepgram",
|
|
606
|
-
ttsProvider: "elevenlabs",
|
|
607
979
|
hints: [],
|
|
608
980
|
interruptSensitivity: "low",
|
|
609
981
|
},
|
|
@@ -711,29 +1083,8 @@ describe("AssistantConfigSchema", () => {
|
|
|
711
1083
|
test("config without calls.voice parses correctly and produces defaults", () => {
|
|
712
1084
|
const result = AssistantConfigSchema.parse({});
|
|
713
1085
|
expect(result.calls.voice.language).toBe("en-US");
|
|
714
|
-
expect(result.calls.voice.
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
test("elevenlabs tuning params have correct defaults", () => {
|
|
718
|
-
const result = AssistantConfigSchema.parse({});
|
|
719
|
-
expect(result.elevenlabs.voiceModelId).toBe("");
|
|
720
|
-
expect(result.elevenlabs.speed).toBe(1.0);
|
|
721
|
-
expect(result.elevenlabs.stability).toBe(0.5);
|
|
722
|
-
expect(result.elevenlabs.similarityBoost).toBe(0.75);
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
test("rejects elevenlabs.speed below 0.7", () => {
|
|
726
|
-
const result = AssistantConfigSchema.safeParse({
|
|
727
|
-
elevenlabs: { speed: 0.5 },
|
|
728
|
-
});
|
|
729
|
-
expect(result.success).toBe(false);
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
test("rejects elevenlabs.speed above 1.2", () => {
|
|
733
|
-
const result = AssistantConfigSchema.safeParse({
|
|
734
|
-
elevenlabs: { speed: 1.5 },
|
|
735
|
-
});
|
|
736
|
-
expect(result.success).toBe(false);
|
|
1086
|
+
expect(result.calls.voice.hints).toEqual([]);
|
|
1087
|
+
expect(result.calls.voice.interruptSensitivity).toBe("low");
|
|
737
1088
|
});
|
|
738
1089
|
|
|
739
1090
|
test("accepts valid calls.voice overrides", () => {
|
|
@@ -741,51 +1092,29 @@ describe("AssistantConfigSchema", () => {
|
|
|
741
1092
|
calls: {
|
|
742
1093
|
voice: {
|
|
743
1094
|
language: "es-ES",
|
|
744
|
-
transcriptionProvider: "Google",
|
|
745
1095
|
},
|
|
746
1096
|
},
|
|
747
|
-
elevenlabs: {
|
|
748
|
-
stability: 0.8,
|
|
749
|
-
},
|
|
750
1097
|
});
|
|
751
1098
|
expect(result.calls.voice.language).toBe("es-ES");
|
|
752
|
-
expect(result.calls.voice.transcriptionProvider).toBe("Google");
|
|
753
|
-
expect(result.elevenlabs.stability).toBe(0.8);
|
|
754
|
-
// Defaults preserved for unset fields
|
|
755
|
-
expect(result.elevenlabs.voiceModelId).toBe("");
|
|
756
|
-
expect(result.elevenlabs.similarityBoost).toBe(0.75);
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
test("rejects invalid calls.voice.transcriptionProvider", () => {
|
|
760
|
-
const result = AssistantConfigSchema.safeParse({
|
|
761
|
-
calls: { voice: { transcriptionProvider: "AWS" } },
|
|
762
|
-
});
|
|
763
|
-
expect(result.success).toBe(false);
|
|
764
|
-
if (!result.success) {
|
|
765
|
-
const msgs = result.error.issues.map((i) => i.message);
|
|
766
|
-
expect(
|
|
767
|
-
msgs.some((m) => m.includes("calls.voice.transcriptionProvider")),
|
|
768
|
-
).toBe(true);
|
|
769
|
-
}
|
|
770
1099
|
});
|
|
771
1100
|
|
|
772
|
-
test("
|
|
773
|
-
|
|
774
|
-
|
|
1101
|
+
test("transcriptionProvider is no longer part of the voice config schema", () => {
|
|
1102
|
+
// Zod strips unrecognized keys by default — the legacy field is silently ignored.
|
|
1103
|
+
const result = AssistantConfigSchema.parse({
|
|
1104
|
+
calls: { voice: { transcriptionProvider: "Google" } },
|
|
775
1105
|
});
|
|
776
|
-
expect(
|
|
1106
|
+
expect(
|
|
1107
|
+
(result.calls.voice as Record<string, unknown>).transcriptionProvider,
|
|
1108
|
+
).toBeUndefined();
|
|
777
1109
|
});
|
|
778
1110
|
|
|
779
|
-
test("
|
|
1111
|
+
test("legacy calls.model key is stripped after PR 19 cleanup", () => {
|
|
1112
|
+
// calls.model moved to llm.callSites.callAgent.model in PR 4 and the
|
|
1113
|
+
// legacy field was removed in PR 19. Zod silently strips unknown keys.
|
|
780
1114
|
const result = AssistantConfigSchema.parse({
|
|
781
1115
|
calls: { model: "claude-haiku-4-5-20251001" },
|
|
782
1116
|
});
|
|
783
|
-
expect(result.calls.model).
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
test("calls.model is undefined by default", () => {
|
|
787
|
-
const result = AssistantConfigSchema.parse({});
|
|
788
|
-
expect(result.calls.model).toBeUndefined();
|
|
1117
|
+
expect((result.calls as Record<string, unknown>).model).toBeUndefined();
|
|
789
1118
|
});
|
|
790
1119
|
|
|
791
1120
|
// ── Caller identity config ────────────────────────────────────────
|
|
@@ -850,6 +1179,10 @@ describe("AssistantConfigSchema", () => {
|
|
|
850
1179
|
host: "localhost",
|
|
851
1180
|
port: 9222,
|
|
852
1181
|
probeTimeoutMs: 500,
|
|
1182
|
+
desktopAuto: {
|
|
1183
|
+
enabled: true,
|
|
1184
|
+
cooldownMs: 30_000,
|
|
1185
|
+
},
|
|
853
1186
|
},
|
|
854
1187
|
});
|
|
855
1188
|
});
|
|
@@ -958,68 +1291,481 @@ describe("AssistantConfigSchema", () => {
|
|
|
958
1291
|
});
|
|
959
1292
|
expect(result.success).toBe(false);
|
|
960
1293
|
});
|
|
961
|
-
});
|
|
962
1294
|
|
|
963
|
-
//
|
|
964
|
-
// Tests: Voice quality profile resolver
|
|
965
|
-
// ---------------------------------------------------------------------------
|
|
1295
|
+
// ── services.tts config ──────────────────────────────────────────────
|
|
966
1296
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
expect(
|
|
972
|
-
|
|
1297
|
+
test("applies services.tts defaults when not specified", () => {
|
|
1298
|
+
const result = AssistantConfigSchema.parse({});
|
|
1299
|
+
expect(result.services.tts.mode).toBe("your-own");
|
|
1300
|
+
expect(result.services.tts.provider).toBe("elevenlabs");
|
|
1301
|
+
expect(result.services.tts.providers.elevenlabs.voiceId).toBe(
|
|
1302
|
+
DEFAULT_ELEVENLABS_VOICE_ID,
|
|
1303
|
+
);
|
|
1304
|
+
expect(result.services.tts.providers.elevenlabs.speed).toBe(1.0);
|
|
1305
|
+
expect(result.services.tts.providers.elevenlabs.stability).toBe(0.5);
|
|
1306
|
+
expect(result.services.tts.providers.elevenlabs.similarityBoost).toBe(0.75);
|
|
1307
|
+
expect(
|
|
1308
|
+
result.services.tts.providers.elevenlabs.conversationTimeoutSeconds,
|
|
1309
|
+
).toBe(30);
|
|
1310
|
+
expect(result.services.tts.providers["fish-audio"].referenceId).toBe("");
|
|
1311
|
+
expect(result.services.tts.providers["fish-audio"].chunkLength).toBe(200);
|
|
1312
|
+
expect(result.services.tts.providers["fish-audio"].format).toBe("mp3");
|
|
1313
|
+
expect(result.services.tts.providers["fish-audio"].speed).toBe(1.0);
|
|
1314
|
+
expect(result.services.tts.providers.deepgram.model).toBe(
|
|
1315
|
+
"aura-asteria-en",
|
|
1316
|
+
);
|
|
1317
|
+
expect(result.services.tts.providers.deepgram.format).toBe("mp3");
|
|
973
1318
|
});
|
|
974
1319
|
|
|
975
|
-
test("
|
|
976
|
-
const
|
|
977
|
-
|
|
1320
|
+
test("accepts valid services.tts provider override", () => {
|
|
1321
|
+
const result = AssistantConfigSchema.parse({
|
|
1322
|
+
services: { tts: { provider: "fish-audio" } },
|
|
978
1323
|
});
|
|
979
|
-
|
|
980
|
-
expect(
|
|
981
|
-
expect(profile.voice).toBe("test-voice-id");
|
|
1324
|
+
expect(result.services.tts.provider).toBe("fish-audio");
|
|
1325
|
+
expect(result.services.tts.mode).toBe("your-own");
|
|
982
1326
|
});
|
|
983
1327
|
|
|
984
|
-
test("
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
1328
|
+
test("accepts deepgram as services.tts.provider", () => {
|
|
1329
|
+
const result = AssistantConfigSchema.parse({
|
|
1330
|
+
services: { tts: { provider: "deepgram" } },
|
|
1331
|
+
});
|
|
1332
|
+
expect(result.services.tts.provider).toBe("deepgram");
|
|
1333
|
+
expect(result.services.tts.mode).toBe("your-own");
|
|
988
1334
|
});
|
|
989
1335
|
|
|
990
|
-
test("
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1336
|
+
test("accepts valid services.tts.providers.elevenlabs overrides", () => {
|
|
1337
|
+
const result = AssistantConfigSchema.parse({
|
|
1338
|
+
services: {
|
|
1339
|
+
tts: {
|
|
1340
|
+
providers: {
|
|
1341
|
+
elevenlabs: { voiceId: "custom-voice", speed: 0.8 },
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
998
1344
|
},
|
|
999
1345
|
});
|
|
1000
|
-
|
|
1001
|
-
|
|
1346
|
+
expect(result.services.tts.providers.elevenlabs.voiceId).toBe(
|
|
1347
|
+
"custom-voice",
|
|
1348
|
+
);
|
|
1349
|
+
expect(result.services.tts.providers.elevenlabs.speed).toBe(0.8);
|
|
1350
|
+
// Unset fields preserve defaults
|
|
1351
|
+
expect(result.services.tts.providers.elevenlabs.stability).toBe(0.5);
|
|
1002
1352
|
});
|
|
1003
|
-
});
|
|
1004
|
-
|
|
1005
|
-
// ---------------------------------------------------------------------------
|
|
1006
|
-
// Tests: buildElevenLabsVoiceSpec
|
|
1007
|
-
// ---------------------------------------------------------------------------
|
|
1008
1353
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1354
|
+
test("accepts valid services.tts.providers.fish-audio overrides", () => {
|
|
1355
|
+
const result = AssistantConfigSchema.parse({
|
|
1356
|
+
services: {
|
|
1357
|
+
tts: {
|
|
1358
|
+
providers: {
|
|
1359
|
+
"fish-audio": { referenceId: "my-voice", format: "wav" },
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1362
|
+
},
|
|
1017
1363
|
});
|
|
1018
|
-
expect(
|
|
1364
|
+
expect(result.services.tts.providers["fish-audio"].referenceId).toBe(
|
|
1365
|
+
"my-voice",
|
|
1366
|
+
);
|
|
1367
|
+
expect(result.services.tts.providers["fish-audio"].format).toBe("wav");
|
|
1368
|
+
// Defaults preserved
|
|
1369
|
+
expect(result.services.tts.providers["fish-audio"].chunkLength).toBe(200);
|
|
1019
1370
|
});
|
|
1020
1371
|
|
|
1021
|
-
test("
|
|
1022
|
-
const
|
|
1372
|
+
test("accepts valid services.tts.providers.deepgram overrides", () => {
|
|
1373
|
+
const result = AssistantConfigSchema.parse({
|
|
1374
|
+
services: {
|
|
1375
|
+
tts: {
|
|
1376
|
+
providers: {
|
|
1377
|
+
deepgram: { model: "aura-luna-en", format: "opus" },
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
});
|
|
1382
|
+
expect(result.services.tts.providers.deepgram.model).toBe("aura-luna-en");
|
|
1383
|
+
expect(result.services.tts.providers.deepgram.format).toBe("opus");
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
test("rejects services.tts.mode = managed", () => {
|
|
1387
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1388
|
+
services: { tts: { mode: "managed" } },
|
|
1389
|
+
});
|
|
1390
|
+
expect(result.success).toBe(false);
|
|
1391
|
+
if (!result.success) {
|
|
1392
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
1393
|
+
expect(
|
|
1394
|
+
msgs.some((m) => m.includes("your-own") || m.includes("managed")),
|
|
1395
|
+
).toBe(true);
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
// ── hostBrowser.cdpInspect.desktopAuto config ───────────────────────
|
|
1400
|
+
|
|
1401
|
+
test("applies hostBrowser.cdpInspect.desktopAuto defaults", () => {
|
|
1402
|
+
const result = AssistantConfigSchema.parse({});
|
|
1403
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto).toEqual({
|
|
1404
|
+
enabled: true,
|
|
1405
|
+
cooldownMs: 30_000,
|
|
1406
|
+
});
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
test("accepts hostBrowser.cdpInspect.desktopAuto overrides", () => {
|
|
1410
|
+
const result = AssistantConfigSchema.parse({
|
|
1411
|
+
hostBrowser: {
|
|
1412
|
+
cdpInspect: {
|
|
1413
|
+
desktopAuto: { enabled: false, cooldownMs: 10_000 },
|
|
1414
|
+
},
|
|
1415
|
+
},
|
|
1416
|
+
});
|
|
1417
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto.enabled).toBe(false);
|
|
1418
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto.cooldownMs).toBe(10_000);
|
|
1419
|
+
});
|
|
1420
|
+
|
|
1421
|
+
test("accepts hostBrowser.cdpInspect.desktopAuto.cooldownMs of 0 (disable cooldown)", () => {
|
|
1422
|
+
const result = AssistantConfigSchema.parse({
|
|
1423
|
+
hostBrowser: {
|
|
1424
|
+
cdpInspect: { desktopAuto: { cooldownMs: 0 } },
|
|
1425
|
+
},
|
|
1426
|
+
});
|
|
1427
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto.cooldownMs).toBe(0);
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
test("rejects hostBrowser.cdpInspect.desktopAuto.cooldownMs below 0", () => {
|
|
1431
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1432
|
+
hostBrowser: {
|
|
1433
|
+
cdpInspect: { desktopAuto: { cooldownMs: -1 } },
|
|
1434
|
+
},
|
|
1435
|
+
});
|
|
1436
|
+
expect(result.success).toBe(false);
|
|
1437
|
+
if (!result.success) {
|
|
1438
|
+
expect(
|
|
1439
|
+
result.error.issues.some((issue) =>
|
|
1440
|
+
issue.path.join(".").includes("cooldownMs"),
|
|
1441
|
+
),
|
|
1442
|
+
).toBe(true);
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
test("rejects invalid services.tts.provider", () => {
|
|
1447
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1448
|
+
services: { tts: { provider: "aws-polly" } },
|
|
1449
|
+
});
|
|
1450
|
+
expect(result.success).toBe(false);
|
|
1451
|
+
if (!result.success) {
|
|
1452
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
1453
|
+
expect(msgs.some((m) => m.includes("services.tts.provider"))).toBe(true);
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
test("services.tts.mode only accepts your-own as literal", () => {
|
|
1458
|
+
// Explicit your-own should work
|
|
1459
|
+
const valid = TtsServiceSchema.safeParse({ mode: "your-own" });
|
|
1460
|
+
expect(valid.success).toBe(true);
|
|
1461
|
+
|
|
1462
|
+
// managed should be rejected
|
|
1463
|
+
const invalid = TtsServiceSchema.safeParse({ mode: "managed" });
|
|
1464
|
+
expect(invalid.success).toBe(false);
|
|
1465
|
+
|
|
1466
|
+
// Any other string should be rejected
|
|
1467
|
+
const invalid2 = TtsServiceSchema.safeParse({ mode: "self-hosted" });
|
|
1468
|
+
expect(invalid2.success).toBe(false);
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
// ── services.stt config ──────────────────────────────────────────────
|
|
1472
|
+
|
|
1473
|
+
test("rejects services.stt without explicit provider", () => {
|
|
1474
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1475
|
+
services: { stt: { mode: "your-own" } },
|
|
1476
|
+
});
|
|
1477
|
+
expect(result.success).toBe(false);
|
|
1478
|
+
if (!result.success) {
|
|
1479
|
+
expect(
|
|
1480
|
+
result.error.issues.some((i) => i.path.join(".").includes("provider")),
|
|
1481
|
+
).toBe(true);
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
test("applies services.stt structural defaults when provider is explicit", () => {
|
|
1486
|
+
const result = AssistantConfigSchema.parse({
|
|
1487
|
+
services: { stt: { provider: "openai-whisper" } },
|
|
1488
|
+
});
|
|
1489
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1490
|
+
expect(result.services.stt.provider).toBe("openai-whisper");
|
|
1491
|
+
// providers defaults to empty sparse map
|
|
1492
|
+
expect(result.services.stt.providers).toEqual({});
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
test("accepts valid services.stt provider override", () => {
|
|
1496
|
+
const result = AssistantConfigSchema.parse({
|
|
1497
|
+
services: { stt: { provider: "openai-whisper" } },
|
|
1498
|
+
});
|
|
1499
|
+
expect(result.services.stt.provider).toBe("openai-whisper");
|
|
1500
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
test("accepts valid services.stt.providers.openai-whisper overrides", () => {
|
|
1504
|
+
const result = AssistantConfigSchema.parse({
|
|
1505
|
+
services: {
|
|
1506
|
+
stt: {
|
|
1507
|
+
provider: "openai-whisper",
|
|
1508
|
+
providers: {
|
|
1509
|
+
"openai-whisper": {},
|
|
1510
|
+
},
|
|
1511
|
+
},
|
|
1512
|
+
},
|
|
1513
|
+
});
|
|
1514
|
+
expect(result.services.stt.providers["openai-whisper"]).toEqual({});
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
test("parses when providers map is empty (sparse default)", () => {
|
|
1518
|
+
const result = AssistantConfigSchema.parse({
|
|
1519
|
+
services: { stt: { provider: "deepgram", providers: {} } },
|
|
1520
|
+
});
|
|
1521
|
+
expect(result.services.stt.providers).toEqual({});
|
|
1522
|
+
expect(result.services.stt.provider).toBe("deepgram");
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
test("parses when unknown future provider blobs exist under providers", () => {
|
|
1526
|
+
const result = AssistantConfigSchema.parse({
|
|
1527
|
+
services: {
|
|
1528
|
+
stt: {
|
|
1529
|
+
provider: "openai-whisper",
|
|
1530
|
+
providers: {
|
|
1531
|
+
"openai-whisper": {},
|
|
1532
|
+
"future-provider": { model: "next-gen", lang: "en" },
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1535
|
+
},
|
|
1536
|
+
});
|
|
1537
|
+
expect(result.services.stt.providers["openai-whisper"]).toEqual({});
|
|
1538
|
+
expect(result.services.stt.providers["future-provider"]).toEqual({
|
|
1539
|
+
model: "next-gen",
|
|
1540
|
+
lang: "en",
|
|
1541
|
+
});
|
|
1542
|
+
});
|
|
1543
|
+
|
|
1544
|
+
test("rejects services.stt.mode = managed", () => {
|
|
1545
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1546
|
+
services: { stt: { mode: "managed" } },
|
|
1547
|
+
});
|
|
1548
|
+
expect(result.success).toBe(false);
|
|
1549
|
+
if (!result.success) {
|
|
1550
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
1551
|
+
expect(
|
|
1552
|
+
msgs.some((m) => m.includes("your-own") || m.includes("managed")),
|
|
1553
|
+
).toBe(true);
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
test("rejects invalid services.stt.provider", () => {
|
|
1558
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1559
|
+
services: { stt: { provider: "azure-speech" } },
|
|
1560
|
+
});
|
|
1561
|
+
expect(result.success).toBe(false);
|
|
1562
|
+
if (!result.success) {
|
|
1563
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
1564
|
+
expect(msgs.some((m) => m.includes("services.stt.provider"))).toBe(true);
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
test("accepts deepgram as services.stt.provider", () => {
|
|
1569
|
+
const result = AssistantConfigSchema.parse({
|
|
1570
|
+
services: { stt: { provider: "deepgram" } },
|
|
1571
|
+
});
|
|
1572
|
+
expect(result.services.stt.provider).toBe("deepgram");
|
|
1573
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
test("accepts google-gemini as services.stt.provider", () => {
|
|
1577
|
+
const result = AssistantConfigSchema.parse({
|
|
1578
|
+
services: { stt: { provider: "google-gemini" } },
|
|
1579
|
+
});
|
|
1580
|
+
expect(result.services.stt.provider).toBe("google-gemini");
|
|
1581
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
test("applies services.stt structural defaults when google-gemini provider is explicit", () => {
|
|
1585
|
+
const result = AssistantConfigSchema.parse({
|
|
1586
|
+
services: { stt: { provider: "google-gemini" } },
|
|
1587
|
+
});
|
|
1588
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1589
|
+
expect(result.services.stt.provider).toBe("google-gemini");
|
|
1590
|
+
expect(result.services.stt.providers).toEqual({});
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
test("accepts valid services.stt.providers.deepgram overrides", () => {
|
|
1594
|
+
const result = AssistantConfigSchema.parse({
|
|
1595
|
+
services: {
|
|
1596
|
+
stt: {
|
|
1597
|
+
provider: "deepgram",
|
|
1598
|
+
providers: {
|
|
1599
|
+
deepgram: {},
|
|
1600
|
+
},
|
|
1601
|
+
},
|
|
1602
|
+
},
|
|
1603
|
+
});
|
|
1604
|
+
expect(result.services.stt.providers.deepgram).toEqual({});
|
|
1605
|
+
});
|
|
1606
|
+
|
|
1607
|
+
test("existing configs with explicit per-provider objects continue to parse", () => {
|
|
1608
|
+
// Configs with explicit per-provider objects must continue to
|
|
1609
|
+
// parse and round-trip successfully.
|
|
1610
|
+
const result = AssistantConfigSchema.parse({
|
|
1611
|
+
services: {
|
|
1612
|
+
stt: {
|
|
1613
|
+
provider: "openai-whisper",
|
|
1614
|
+
providers: {
|
|
1615
|
+
"openai-whisper": {},
|
|
1616
|
+
deepgram: {},
|
|
1617
|
+
},
|
|
1618
|
+
},
|
|
1619
|
+
},
|
|
1620
|
+
});
|
|
1621
|
+
expect(result.services.stt.providers["openai-whisper"]).toEqual({});
|
|
1622
|
+
expect(result.services.stt.providers.deepgram).toEqual({});
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
test("services.stt.provider is required (no implicit default)", () => {
|
|
1626
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1627
|
+
services: { stt: {} },
|
|
1628
|
+
});
|
|
1629
|
+
expect(result.success).toBe(false);
|
|
1630
|
+
});
|
|
1631
|
+
|
|
1632
|
+
test("services.stt.mode only accepts your-own as literal", () => {
|
|
1633
|
+
// Explicit your-own should work
|
|
1634
|
+
const valid = SttServiceSchema.safeParse({
|
|
1635
|
+
mode: "your-own",
|
|
1636
|
+
provider: "openai-whisper",
|
|
1637
|
+
});
|
|
1638
|
+
expect(valid.success).toBe(true);
|
|
1639
|
+
|
|
1640
|
+
// managed should be rejected
|
|
1641
|
+
const invalid = SttServiceSchema.safeParse({
|
|
1642
|
+
mode: "managed",
|
|
1643
|
+
provider: "openai-whisper",
|
|
1644
|
+
});
|
|
1645
|
+
expect(invalid.success).toBe(false);
|
|
1646
|
+
|
|
1647
|
+
// Any other string should be rejected
|
|
1648
|
+
const invalid2 = SttServiceSchema.safeParse({
|
|
1649
|
+
mode: "self-hosted",
|
|
1650
|
+
provider: "openai-whisper",
|
|
1651
|
+
});
|
|
1652
|
+
expect(invalid2.success).toBe(false);
|
|
1653
|
+
});
|
|
1654
|
+
|
|
1655
|
+
test("rejects hostBrowser.cdpInspect.desktopAuto.cooldownMs above 300000", () => {
|
|
1656
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1657
|
+
hostBrowser: {
|
|
1658
|
+
cdpInspect: { desktopAuto: { cooldownMs: 500_000 } },
|
|
1659
|
+
},
|
|
1660
|
+
});
|
|
1661
|
+
expect(result.success).toBe(false);
|
|
1662
|
+
if (!result.success) {
|
|
1663
|
+
expect(
|
|
1664
|
+
result.error.issues.some((issue) =>
|
|
1665
|
+
issue.path.join(".").includes("cooldownMs"),
|
|
1666
|
+
),
|
|
1667
|
+
).toBe(true);
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
test("rejects non-integer hostBrowser.cdpInspect.desktopAuto.cooldownMs", () => {
|
|
1672
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1673
|
+
hostBrowser: {
|
|
1674
|
+
cdpInspect: { desktopAuto: { cooldownMs: 5000.5 } },
|
|
1675
|
+
},
|
|
1676
|
+
});
|
|
1677
|
+
expect(result.success).toBe(false);
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
test("rejects non-boolean hostBrowser.cdpInspect.desktopAuto.enabled", () => {
|
|
1681
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1682
|
+
hostBrowser: {
|
|
1683
|
+
cdpInspect: { desktopAuto: { enabled: "yes" } },
|
|
1684
|
+
},
|
|
1685
|
+
});
|
|
1686
|
+
expect(result.success).toBe(false);
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
test("desktopAuto defaults preserved when only cdpInspect.enabled is set", () => {
|
|
1690
|
+
const result = AssistantConfigSchema.parse({
|
|
1691
|
+
hostBrowser: { cdpInspect: { enabled: true } },
|
|
1692
|
+
});
|
|
1693
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto).toEqual({
|
|
1694
|
+
enabled: true,
|
|
1695
|
+
cooldownMs: 30_000,
|
|
1696
|
+
});
|
|
1697
|
+
});
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
// ---------------------------------------------------------------------------
|
|
1701
|
+
// Tests: Voice quality profile resolver
|
|
1702
|
+
// ---------------------------------------------------------------------------
|
|
1703
|
+
|
|
1704
|
+
describe("resolveVoiceQualityProfile", () => {
|
|
1705
|
+
test("always returns ElevenLabs ttsProvider", () => {
|
|
1706
|
+
const config = AssistantConfigSchema.parse({});
|
|
1707
|
+
const profile = resolveVoiceQualityProfile(config);
|
|
1708
|
+
expect(profile.ttsProvider).toBe("ElevenLabs");
|
|
1709
|
+
});
|
|
1710
|
+
|
|
1711
|
+
test("uses services.tts.providers.elevenlabs.voiceId for voice", () => {
|
|
1712
|
+
const config = AssistantConfigSchema.parse({
|
|
1713
|
+
services: {
|
|
1714
|
+
tts: {
|
|
1715
|
+
providers: { elevenlabs: { voiceId: "test-voice-id" } },
|
|
1716
|
+
},
|
|
1717
|
+
},
|
|
1718
|
+
});
|
|
1719
|
+
const profile = resolveVoiceQualityProfile(config);
|
|
1720
|
+
expect(profile.ttsProvider).toBe("ElevenLabs");
|
|
1721
|
+
expect(profile.voice).toBe("test-voice-id");
|
|
1722
|
+
});
|
|
1723
|
+
|
|
1724
|
+
test("defaults to Amelia voice ID when elevenlabs.voiceId is not set", () => {
|
|
1725
|
+
const config = AssistantConfigSchema.parse({});
|
|
1726
|
+
const profile = resolveVoiceQualityProfile(config);
|
|
1727
|
+
expect(profile.voice).toBe(DEFAULT_ELEVENLABS_VOICE_ID);
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
test("applies voice tuning params from services.tts.providers.elevenlabs config", () => {
|
|
1731
|
+
const config = AssistantConfigSchema.parse({
|
|
1732
|
+
services: {
|
|
1733
|
+
tts: {
|
|
1734
|
+
providers: {
|
|
1735
|
+
elevenlabs: {
|
|
1736
|
+
voiceId: "abc123",
|
|
1737
|
+
voiceModelId: "turbo_v2_5",
|
|
1738
|
+
speed: 0.9,
|
|
1739
|
+
stability: 0.8,
|
|
1740
|
+
similarityBoost: 0.9,
|
|
1741
|
+
},
|
|
1742
|
+
},
|
|
1743
|
+
},
|
|
1744
|
+
},
|
|
1745
|
+
});
|
|
1746
|
+
const profile = resolveVoiceQualityProfile(config);
|
|
1747
|
+
expect(profile.voice).toBe("abc123-turbo_v2_5-0.9_0.8_0.9");
|
|
1748
|
+
});
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
// ---------------------------------------------------------------------------
|
|
1752
|
+
// Tests: buildElevenLabsVoiceSpec
|
|
1753
|
+
// ---------------------------------------------------------------------------
|
|
1754
|
+
|
|
1755
|
+
describe("buildElevenLabsVoiceSpec", () => {
|
|
1756
|
+
test("produces Twilio-compliant voice string: voiceId-model-speed_stability_similarity", () => {
|
|
1757
|
+
const spec = buildElevenLabsVoiceSpec({
|
|
1758
|
+
voiceId: "abc123",
|
|
1759
|
+
voiceModelId: "turbo_v2_5",
|
|
1760
|
+
speed: 1.0,
|
|
1761
|
+
stability: 0.5,
|
|
1762
|
+
similarityBoost: 0.75,
|
|
1763
|
+
});
|
|
1764
|
+
expect(spec).toBe("abc123-turbo_v2_5-1_0.5_0.75");
|
|
1765
|
+
});
|
|
1766
|
+
|
|
1767
|
+
test("returns empty string when voiceId is empty", () => {
|
|
1768
|
+
const spec = buildElevenLabsVoiceSpec({
|
|
1023
1769
|
voiceId: "",
|
|
1024
1770
|
voiceModelId: "turbo_v2_5",
|
|
1025
1771
|
speed: 1.0,
|
|
@@ -1042,13 +1788,424 @@ describe("buildElevenLabsVoiceSpec", () => {
|
|
|
1042
1788
|
|
|
1043
1789
|
test("default config uses a bare voiceId when no model override is set", () => {
|
|
1044
1790
|
const config = AssistantConfigSchema.parse({
|
|
1045
|
-
|
|
1791
|
+
services: {
|
|
1792
|
+
tts: {
|
|
1793
|
+
providers: { elevenlabs: { voiceId: "test" } },
|
|
1794
|
+
},
|
|
1795
|
+
},
|
|
1046
1796
|
});
|
|
1047
|
-
const spec = buildElevenLabsVoiceSpec(
|
|
1797
|
+
const spec = buildElevenLabsVoiceSpec(
|
|
1798
|
+
config.services.tts.providers.elevenlabs,
|
|
1799
|
+
);
|
|
1048
1800
|
expect(spec).toBe("test");
|
|
1049
1801
|
});
|
|
1050
1802
|
});
|
|
1051
1803
|
|
|
1804
|
+
// ---------------------------------------------------------------------------
|
|
1805
|
+
// Tests: TTS config resolver
|
|
1806
|
+
// ---------------------------------------------------------------------------
|
|
1807
|
+
|
|
1808
|
+
describe("resolveTtsConfig", () => {
|
|
1809
|
+
test("returns default provider and config from empty config", () => {
|
|
1810
|
+
const config = AssistantConfigSchema.parse({});
|
|
1811
|
+
const resolved = resolveTtsConfig(config);
|
|
1812
|
+
expect(resolved.provider).toBe("elevenlabs");
|
|
1813
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1814
|
+
voiceId: DEFAULT_ELEVENLABS_VOICE_ID,
|
|
1815
|
+
speed: 1.0,
|
|
1816
|
+
stability: 0.5,
|
|
1817
|
+
similarityBoost: 0.75,
|
|
1818
|
+
});
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1821
|
+
test("uses canonical services.tts.provider when set", () => {
|
|
1822
|
+
const config = AssistantConfigSchema.parse({
|
|
1823
|
+
services: { tts: { provider: "fish-audio" } },
|
|
1824
|
+
});
|
|
1825
|
+
const resolved = resolveTtsConfig(config);
|
|
1826
|
+
expect(resolved.provider).toBe("fish-audio");
|
|
1827
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1828
|
+
referenceId: "",
|
|
1829
|
+
chunkLength: 200,
|
|
1830
|
+
format: "mp3",
|
|
1831
|
+
speed: 1.0,
|
|
1832
|
+
});
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1835
|
+
test("returns canonical elevenlabs config from services.tts.providers", () => {
|
|
1836
|
+
const config = AssistantConfigSchema.parse({
|
|
1837
|
+
services: {
|
|
1838
|
+
tts: {
|
|
1839
|
+
provider: "elevenlabs",
|
|
1840
|
+
providers: {
|
|
1841
|
+
elevenlabs: { voiceId: "canonical-voice", stability: 0.9 },
|
|
1842
|
+
},
|
|
1843
|
+
},
|
|
1844
|
+
},
|
|
1845
|
+
});
|
|
1846
|
+
const resolved = resolveTtsConfig(config);
|
|
1847
|
+
expect(resolved.provider).toBe("elevenlabs");
|
|
1848
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1849
|
+
voiceId: "canonical-voice",
|
|
1850
|
+
stability: 0.9,
|
|
1851
|
+
});
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
test("uses canonical elevenlabs config exclusively (no legacy fallback)", () => {
|
|
1855
|
+
const config = AssistantConfigSchema.parse({
|
|
1856
|
+
services: {
|
|
1857
|
+
tts: {
|
|
1858
|
+
providers: {
|
|
1859
|
+
elevenlabs: { voiceId: "canonical-voice", speed: 0.9 },
|
|
1860
|
+
},
|
|
1861
|
+
},
|
|
1862
|
+
},
|
|
1863
|
+
});
|
|
1864
|
+
const resolved = resolveTtsConfig(config);
|
|
1865
|
+
expect(resolved.provider).toBe("elevenlabs");
|
|
1866
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1867
|
+
voiceId: "canonical-voice",
|
|
1868
|
+
speed: 0.9,
|
|
1869
|
+
});
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
test("uses canonical fish-audio config exclusively (no legacy fallback)", () => {
|
|
1873
|
+
const config = AssistantConfigSchema.parse({
|
|
1874
|
+
services: {
|
|
1875
|
+
tts: {
|
|
1876
|
+
provider: "fish-audio",
|
|
1877
|
+
providers: {
|
|
1878
|
+
"fish-audio": { referenceId: "canonical-ref", format: "wav" },
|
|
1879
|
+
},
|
|
1880
|
+
},
|
|
1881
|
+
},
|
|
1882
|
+
});
|
|
1883
|
+
const resolved = resolveTtsConfig(config);
|
|
1884
|
+
expect(resolved.provider).toBe("fish-audio");
|
|
1885
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1886
|
+
referenceId: "canonical-ref",
|
|
1887
|
+
format: "wav",
|
|
1888
|
+
});
|
|
1889
|
+
});
|
|
1890
|
+
|
|
1891
|
+
test("returns empty config for unknown provider", () => {
|
|
1892
|
+
// Force an unknown provider via type assertion for coverage.
|
|
1893
|
+
// structuredClone prevents mutation from leaking into Zod's shared
|
|
1894
|
+
// default objects (Zod 4 stores defaults by reference).
|
|
1895
|
+
const config = structuredClone(
|
|
1896
|
+
AssistantConfigSchema.parse({}),
|
|
1897
|
+
) as AssistantConfig;
|
|
1898
|
+
(config.services.tts as { provider: string }).provider = "aws-polly";
|
|
1899
|
+
const resolved = resolveTtsConfig(config);
|
|
1900
|
+
expect(resolved.provider).toBe("aws-polly");
|
|
1901
|
+
expect(resolved.providerConfig).toEqual({});
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
test("unknown provider resolution is deterministic across repeated calls", () => {
|
|
1905
|
+
const config = structuredClone(
|
|
1906
|
+
AssistantConfigSchema.parse({}),
|
|
1907
|
+
) as AssistantConfig;
|
|
1908
|
+
(config.services.tts as { provider: string }).provider = "nonexistent";
|
|
1909
|
+
const first = resolveTtsConfig(config);
|
|
1910
|
+
const second = resolveTtsConfig(config);
|
|
1911
|
+
expect(first).toEqual(second);
|
|
1912
|
+
expect(first.providerConfig).toEqual({});
|
|
1913
|
+
});
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
// ---------------------------------------------------------------------------
|
|
1917
|
+
// Tests: TTS provider catalog integration
|
|
1918
|
+
// ---------------------------------------------------------------------------
|
|
1919
|
+
|
|
1920
|
+
describe("TTS provider catalog integration", () => {
|
|
1921
|
+
test("VALID_TTS_SERVICE_PROVIDERS matches catalog provider IDs", () => {
|
|
1922
|
+
const catalogIds = listCatalogProviderIds();
|
|
1923
|
+
expect([...VALID_TTS_SERVICE_PROVIDERS]).toEqual(catalogIds);
|
|
1924
|
+
});
|
|
1925
|
+
|
|
1926
|
+
test("schema accepts all catalog provider IDs as services.tts.provider", () => {
|
|
1927
|
+
for (const providerId of listCatalogProviderIds()) {
|
|
1928
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1929
|
+
services: { tts: { provider: providerId } },
|
|
1930
|
+
});
|
|
1931
|
+
expect(result.success).toBe(true);
|
|
1932
|
+
if (result.success) {
|
|
1933
|
+
expect(result.data.services.tts.provider).toBe(providerId);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
test("TtsProvidersSchema has a key for every catalog provider", () => {
|
|
1939
|
+
const parsed = AssistantConfigSchema.parse({});
|
|
1940
|
+
const providerKeys = Object.keys(parsed.services.tts.providers);
|
|
1941
|
+
for (const providerId of listCatalogProviderIds()) {
|
|
1942
|
+
expect(providerKeys).toContain(providerId);
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
|
|
1946
|
+
test("resolveTtsConfig returns correct defaults for each catalog provider", () => {
|
|
1947
|
+
for (const providerId of listCatalogProviderIds()) {
|
|
1948
|
+
const config = AssistantConfigSchema.parse({
|
|
1949
|
+
services: { tts: { provider: providerId } },
|
|
1950
|
+
});
|
|
1951
|
+
const resolved = resolveTtsConfig(config);
|
|
1952
|
+
expect(resolved.provider).toBe(providerId);
|
|
1953
|
+
// Every catalog provider should resolve to a non-empty config object
|
|
1954
|
+
expect(Object.keys(resolved.providerConfig).length).toBeGreaterThan(0);
|
|
1955
|
+
}
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1958
|
+
test("resolveTtsConfig returns overridden values for elevenlabs", () => {
|
|
1959
|
+
const config = AssistantConfigSchema.parse({
|
|
1960
|
+
services: {
|
|
1961
|
+
tts: {
|
|
1962
|
+
provider: "elevenlabs",
|
|
1963
|
+
providers: {
|
|
1964
|
+
elevenlabs: { voiceId: "override-voice", speed: 0.7 },
|
|
1965
|
+
},
|
|
1966
|
+
},
|
|
1967
|
+
},
|
|
1968
|
+
});
|
|
1969
|
+
const resolved = resolveTtsConfig(config);
|
|
1970
|
+
expect(resolved.provider).toBe("elevenlabs");
|
|
1971
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1972
|
+
voiceId: "override-voice",
|
|
1973
|
+
speed: 0.7,
|
|
1974
|
+
// Defaults still present for unset fields
|
|
1975
|
+
stability: 0.5,
|
|
1976
|
+
similarityBoost: 0.75,
|
|
1977
|
+
});
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
test("resolveTtsConfig returns overridden values for fish-audio", () => {
|
|
1981
|
+
const config = AssistantConfigSchema.parse({
|
|
1982
|
+
services: {
|
|
1983
|
+
tts: {
|
|
1984
|
+
provider: "fish-audio",
|
|
1985
|
+
providers: {
|
|
1986
|
+
"fish-audio": {
|
|
1987
|
+
referenceId: "override-ref",
|
|
1988
|
+
format: "opus",
|
|
1989
|
+
speed: 1.5,
|
|
1990
|
+
},
|
|
1991
|
+
},
|
|
1992
|
+
},
|
|
1993
|
+
},
|
|
1994
|
+
});
|
|
1995
|
+
const resolved = resolveTtsConfig(config);
|
|
1996
|
+
expect(resolved.provider).toBe("fish-audio");
|
|
1997
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1998
|
+
referenceId: "override-ref",
|
|
1999
|
+
format: "opus",
|
|
2000
|
+
speed: 1.5,
|
|
2001
|
+
// Defaults for unset fields
|
|
2002
|
+
chunkLength: 200,
|
|
2003
|
+
});
|
|
2004
|
+
});
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
// ---------------------------------------------------------------------------
|
|
2008
|
+
// Tests: TTS migration 032
|
|
2009
|
+
// ---------------------------------------------------------------------------
|
|
2010
|
+
|
|
2011
|
+
describe("032-tts-provider-unification migration", () => {
|
|
2012
|
+
const migrationDir = join(WORKSPACE_DIR, "_mig032");
|
|
2013
|
+
|
|
2014
|
+
beforeEach(() => {
|
|
2015
|
+
if (existsSync(migrationDir)) {
|
|
2016
|
+
rmSync(migrationDir, { recursive: true, force: true });
|
|
2017
|
+
}
|
|
2018
|
+
mkdirSync(migrationDir, { recursive: true });
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
afterEach(() => {
|
|
2022
|
+
if (existsSync(migrationDir)) {
|
|
2023
|
+
rmSync(migrationDir, { recursive: true, force: true });
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
function writeMigConfig(obj: unknown): void {
|
|
2028
|
+
writeFileSync(
|
|
2029
|
+
join(migrationDir, "config.json"),
|
|
2030
|
+
JSON.stringify(obj, null, 2),
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
function readMigConfig(): Record<string, unknown> {
|
|
2035
|
+
return JSON.parse(
|
|
2036
|
+
readFileSync(join(migrationDir, "config.json"), "utf-8"),
|
|
2037
|
+
) as Record<string, unknown>;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
test("backfills provider from calls.voice.ttsProvider", async () => {
|
|
2041
|
+
writeMigConfig({
|
|
2042
|
+
calls: { voice: { ttsProvider: "fish-audio" } },
|
|
2043
|
+
});
|
|
2044
|
+
const { ttsProviderUnificationMigration } =
|
|
2045
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2046
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2047
|
+
const result = readMigConfig();
|
|
2048
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
2049
|
+
string,
|
|
2050
|
+
unknown
|
|
2051
|
+
>;
|
|
2052
|
+
expect(tts.provider).toBe("fish-audio");
|
|
2053
|
+
expect(tts.mode).toBe("your-own");
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
test("backfills elevenlabs provider config from legacy keys", async () => {
|
|
2057
|
+
writeMigConfig({
|
|
2058
|
+
calls: { voice: { ttsProvider: "elevenlabs" } },
|
|
2059
|
+
elevenlabs: { voiceId: "my-voice", speed: 0.8 },
|
|
2060
|
+
});
|
|
2061
|
+
const { ttsProviderUnificationMigration } =
|
|
2062
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2063
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2064
|
+
const result = readMigConfig();
|
|
2065
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
2066
|
+
string,
|
|
2067
|
+
unknown
|
|
2068
|
+
>;
|
|
2069
|
+
const providers = tts.providers as Record<string, Record<string, unknown>>;
|
|
2070
|
+
expect(providers.elevenlabs.voiceId).toBe("my-voice");
|
|
2071
|
+
expect(providers.elevenlabs.speed).toBe(0.8);
|
|
2072
|
+
});
|
|
2073
|
+
|
|
2074
|
+
test("backfills fish-audio provider config from legacy keys", async () => {
|
|
2075
|
+
writeMigConfig({
|
|
2076
|
+
calls: { voice: { ttsProvider: "fish-audio" } },
|
|
2077
|
+
fishAudio: { referenceId: "my-ref", format: "wav" },
|
|
2078
|
+
});
|
|
2079
|
+
const { ttsProviderUnificationMigration } =
|
|
2080
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2081
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2082
|
+
const result = readMigConfig();
|
|
2083
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
2084
|
+
string,
|
|
2085
|
+
unknown
|
|
2086
|
+
>;
|
|
2087
|
+
const providers = tts.providers as Record<string, Record<string, unknown>>;
|
|
2088
|
+
expect(providers["fish-audio"].referenceId).toBe("my-ref");
|
|
2089
|
+
expect(providers["fish-audio"].format).toBe("wav");
|
|
2090
|
+
});
|
|
2091
|
+
|
|
2092
|
+
test("removes legacy fields after migration", async () => {
|
|
2093
|
+
writeMigConfig({
|
|
2094
|
+
calls: { voice: { ttsProvider: "elevenlabs", language: "en-US" } },
|
|
2095
|
+
elevenlabs: { voiceId: "my-voice" },
|
|
2096
|
+
});
|
|
2097
|
+
const { ttsProviderUnificationMigration } =
|
|
2098
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2099
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2100
|
+
const result = readMigConfig();
|
|
2101
|
+
// Legacy keys removed
|
|
2102
|
+
expect(
|
|
2103
|
+
(
|
|
2104
|
+
(result.calls as Record<string, unknown>).voice as Record<
|
|
2105
|
+
string,
|
|
2106
|
+
unknown
|
|
2107
|
+
>
|
|
2108
|
+
).ttsProvider,
|
|
2109
|
+
).toBeUndefined();
|
|
2110
|
+
expect(result.elevenlabs).toBeUndefined();
|
|
2111
|
+
// Other voice fields preserved
|
|
2112
|
+
expect(
|
|
2113
|
+
(
|
|
2114
|
+
(result.calls as Record<string, unknown>).voice as Record<
|
|
2115
|
+
string,
|
|
2116
|
+
unknown
|
|
2117
|
+
>
|
|
2118
|
+
).language,
|
|
2119
|
+
).toBe("en-US");
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
test("is idempotent — repeated runs produce no changes", async () => {
|
|
2123
|
+
writeMigConfig({
|
|
2124
|
+
calls: { voice: { ttsProvider: "fish-audio" } },
|
|
2125
|
+
fishAudio: { referenceId: "my-ref" },
|
|
2126
|
+
});
|
|
2127
|
+
const { ttsProviderUnificationMigration } =
|
|
2128
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2129
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2130
|
+
const afterFirst = readMigConfig();
|
|
2131
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2132
|
+
const afterSecond = readMigConfig();
|
|
2133
|
+
expect(afterSecond).toEqual(afterFirst);
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
test("does not overwrite existing services.tts.provider", async () => {
|
|
2137
|
+
writeMigConfig({
|
|
2138
|
+
services: { tts: { provider: "elevenlabs" } },
|
|
2139
|
+
calls: { voice: { ttsProvider: "fish-audio" } },
|
|
2140
|
+
});
|
|
2141
|
+
const { ttsProviderUnificationMigration } =
|
|
2142
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2143
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2144
|
+
const result = readMigConfig();
|
|
2145
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
2146
|
+
string,
|
|
2147
|
+
unknown
|
|
2148
|
+
>;
|
|
2149
|
+
// Should keep the existing canonical value, not the legacy one
|
|
2150
|
+
expect(tts.provider).toBe("elevenlabs");
|
|
2151
|
+
});
|
|
2152
|
+
|
|
2153
|
+
test("does not overwrite existing canonical provider config keys", async () => {
|
|
2154
|
+
writeMigConfig({
|
|
2155
|
+
services: {
|
|
2156
|
+
tts: {
|
|
2157
|
+
providers: {
|
|
2158
|
+
elevenlabs: { voiceId: "canonical-voice" },
|
|
2159
|
+
},
|
|
2160
|
+
},
|
|
2161
|
+
},
|
|
2162
|
+
elevenlabs: { voiceId: "legacy-voice", speed: 0.8 },
|
|
2163
|
+
});
|
|
2164
|
+
const { ttsProviderUnificationMigration } =
|
|
2165
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2166
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2167
|
+
const result = readMigConfig();
|
|
2168
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
2169
|
+
string,
|
|
2170
|
+
unknown
|
|
2171
|
+
>;
|
|
2172
|
+
const providers = tts.providers as Record<string, Record<string, unknown>>;
|
|
2173
|
+
// Canonical voiceId preserved, legacy speed backfilled
|
|
2174
|
+
expect(providers.elevenlabs.voiceId).toBe("canonical-voice");
|
|
2175
|
+
expect(providers.elevenlabs.speed).toBe(0.8);
|
|
2176
|
+
// Legacy top-level key removed
|
|
2177
|
+
expect(result.elevenlabs).toBeUndefined();
|
|
2178
|
+
});
|
|
2179
|
+
|
|
2180
|
+
test("skips config without any legacy TTS fields", async () => {
|
|
2181
|
+
writeMigConfig({ maxTokens: 4096 });
|
|
2182
|
+
const { ttsProviderUnificationMigration } =
|
|
2183
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2184
|
+
const before = readMigConfig();
|
|
2185
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
2186
|
+
const after = readMigConfig();
|
|
2187
|
+
// Should remain unchanged (no services.tts added)
|
|
2188
|
+
expect(after).toEqual(before);
|
|
2189
|
+
});
|
|
2190
|
+
|
|
2191
|
+
test("down removes services.tts from config", async () => {
|
|
2192
|
+
writeMigConfig({
|
|
2193
|
+
services: {
|
|
2194
|
+
inference: { provider: "anthropic" },
|
|
2195
|
+
tts: { provider: "elevenlabs", mode: "your-own" },
|
|
2196
|
+
},
|
|
2197
|
+
});
|
|
2198
|
+
const { ttsProviderUnificationMigration } =
|
|
2199
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
2200
|
+
await ttsProviderUnificationMigration.down(migrationDir);
|
|
2201
|
+
const result = readMigConfig();
|
|
2202
|
+
const services = result.services as Record<string, unknown>;
|
|
2203
|
+
expect(services.tts).toBeUndefined();
|
|
2204
|
+
// Other services keys preserved
|
|
2205
|
+
expect(services.inference).toBeDefined();
|
|
2206
|
+
});
|
|
2207
|
+
});
|
|
2208
|
+
|
|
1052
2209
|
// ---------------------------------------------------------------------------
|
|
1053
2210
|
// Tests: loader integration (config file -> loadConfig with fallback)
|
|
1054
2211
|
// ---------------------------------------------------------------------------
|
|
@@ -1083,28 +2240,27 @@ describe("loadConfig with schema validation", () => {
|
|
|
1083
2240
|
// intermittently trigger unhandled ENOENT in CI if the directory is removed.
|
|
1084
2241
|
test("loads valid config", () => {
|
|
1085
2242
|
writeConfig({
|
|
1086
|
-
|
|
1087
|
-
|
|
2243
|
+
llm: {
|
|
2244
|
+
default: { provider: "openai", model: "gpt-4", maxTokens: 4096 },
|
|
1088
2245
|
},
|
|
1089
|
-
maxTokens: 4096,
|
|
1090
2246
|
});
|
|
1091
2247
|
const config = loadConfig();
|
|
1092
|
-
expect(config.
|
|
1093
|
-
expect(config.
|
|
1094
|
-
expect(config.maxTokens).toBe(4096);
|
|
2248
|
+
expect(config.llm.default.provider).toBe("openai");
|
|
2249
|
+
expect(config.llm.default.model).toBe("gpt-4");
|
|
2250
|
+
expect(config.llm.default.maxTokens).toBe(4096);
|
|
1095
2251
|
});
|
|
1096
2252
|
|
|
1097
2253
|
test("applies defaults for missing fields", () => {
|
|
1098
2254
|
writeConfig({});
|
|
1099
2255
|
const config = loadConfig();
|
|
1100
|
-
expect(config.
|
|
1101
|
-
expect(config.
|
|
1102
|
-
expect(config.maxTokens).toBe(64000);
|
|
1103
|
-
expect(config.thinking).toEqual({
|
|
2256
|
+
expect(config.llm.default.provider).toBe("anthropic");
|
|
2257
|
+
expect(config.llm.default.model).toBe("claude-opus-4-7");
|
|
2258
|
+
expect(config.llm.default.maxTokens).toBe(64000);
|
|
2259
|
+
expect(config.llm.default.thinking).toEqual({
|
|
1104
2260
|
enabled: true,
|
|
1105
2261
|
streamThinking: true,
|
|
1106
2262
|
});
|
|
1107
|
-
expect(config.contextWindow).toEqual({
|
|
2263
|
+
expect(config.llm.default.contextWindow).toEqual({
|
|
1108
2264
|
enabled: true,
|
|
1109
2265
|
maxInputTokens: 200000,
|
|
1110
2266
|
targetBudgetRatio: 0.3,
|
|
@@ -1122,16 +2278,16 @@ describe("loadConfig with schema validation", () => {
|
|
|
1122
2278
|
|
|
1123
2279
|
test("falls back to default for invalid provider", () => {
|
|
1124
2280
|
writeConfig({
|
|
1125
|
-
|
|
2281
|
+
llm: { default: { provider: "invalid-provider" } },
|
|
1126
2282
|
});
|
|
1127
2283
|
const config = loadConfig();
|
|
1128
|
-
expect(config.
|
|
2284
|
+
expect(config.llm.default.provider).toBe("anthropic");
|
|
1129
2285
|
});
|
|
1130
2286
|
|
|
1131
2287
|
test("falls back to default for invalid maxTokens", () => {
|
|
1132
|
-
writeConfig({ maxTokens: -100 });
|
|
2288
|
+
writeConfig({ llm: { default: { maxTokens: -100 } } });
|
|
1133
2289
|
const config = loadConfig();
|
|
1134
|
-
expect(config.maxTokens).toBe(64000);
|
|
2290
|
+
expect(config.llm.default.maxTokens).toBe(64000);
|
|
1135
2291
|
});
|
|
1136
2292
|
|
|
1137
2293
|
test("falls back to defaults for invalid nested values", () => {
|
|
@@ -1146,23 +2302,26 @@ describe("loadConfig with schema validation", () => {
|
|
|
1146
2302
|
|
|
1147
2303
|
test("preserves valid fields when other fields are invalid", () => {
|
|
1148
2304
|
writeConfig({
|
|
1149
|
-
|
|
1150
|
-
|
|
2305
|
+
llm: {
|
|
2306
|
+
default: {
|
|
2307
|
+
provider: "openai",
|
|
2308
|
+
model: "gpt-4",
|
|
2309
|
+
maxTokens: -1,
|
|
2310
|
+
thinking: { enabled: true },
|
|
2311
|
+
},
|
|
1151
2312
|
},
|
|
1152
|
-
maxTokens: -1,
|
|
1153
|
-
thinking: { enabled: true },
|
|
1154
2313
|
});
|
|
1155
2314
|
const config = loadConfig();
|
|
1156
|
-
expect(config.
|
|
1157
|
-
expect(config.
|
|
1158
|
-
expect(config.thinking.enabled).toBe(true);
|
|
1159
|
-
expect(config.maxTokens).toBe(64000);
|
|
2315
|
+
expect(config.llm.default.provider).toBe("openai");
|
|
2316
|
+
expect(config.llm.default.model).toBe("gpt-4");
|
|
2317
|
+
expect(config.llm.default.thinking.enabled).toBe(true);
|
|
2318
|
+
expect(config.llm.default.maxTokens).toBe(64000);
|
|
1160
2319
|
});
|
|
1161
2320
|
|
|
1162
2321
|
test("handles no config file", () => {
|
|
1163
2322
|
const config = loadConfig();
|
|
1164
|
-
expect(config.
|
|
1165
|
-
expect(config.maxTokens).toBe(64000);
|
|
2323
|
+
expect(config.llm.default.provider).toBe("anthropic");
|
|
2324
|
+
expect(config.llm.default.maxTokens).toBe(64000);
|
|
1166
2325
|
});
|
|
1167
2326
|
|
|
1168
2327
|
test("partial nested objects get defaults for missing fields", () => {
|
|
@@ -1183,11 +2342,15 @@ describe("loadConfig with schema validation", () => {
|
|
|
1183
2342
|
|
|
1184
2343
|
test("falls back for invalid contextWindow relationship", () => {
|
|
1185
2344
|
writeConfig({
|
|
1186
|
-
|
|
2345
|
+
llm: {
|
|
2346
|
+
default: {
|
|
2347
|
+
contextWindow: { targetBudgetRatio: 0.8, compactThreshold: 0.8 },
|
|
2348
|
+
},
|
|
2349
|
+
},
|
|
1187
2350
|
});
|
|
1188
2351
|
const config = loadConfig();
|
|
1189
|
-
expect(config.contextWindow.targetBudgetRatio).toBe(0.3);
|
|
1190
|
-
expect(config.contextWindow.compactThreshold).toBe(0.8);
|
|
2352
|
+
expect(config.llm.default.contextWindow.targetBudgetRatio).toBe(0.3);
|
|
2353
|
+
expect(config.llm.default.contextWindow.compactThreshold).toBe(0.8);
|
|
1191
2354
|
});
|
|
1192
2355
|
|
|
1193
2356
|
test("falls back for invalid rateLimit values", () => {
|
|
@@ -1210,6 +2373,11 @@ describe("loadConfig with schema validation", () => {
|
|
|
1210
2373
|
expect(config.permissions).toEqual({
|
|
1211
2374
|
mode: "workspace",
|
|
1212
2375
|
hostAccess: false,
|
|
2376
|
+
autoApproveUpTo: {
|
|
2377
|
+
conversation: "low",
|
|
2378
|
+
background: "medium",
|
|
2379
|
+
headless: "none",
|
|
2380
|
+
},
|
|
1213
2381
|
});
|
|
1214
2382
|
});
|
|
1215
2383
|
|
|
@@ -1244,6 +2412,93 @@ describe("loadConfig with schema validation", () => {
|
|
|
1244
2412
|
expect(config.calls.provider).toBe("twilio");
|
|
1245
2413
|
});
|
|
1246
2414
|
|
|
2415
|
+
test("recovers from partial filing.activeHours without wiping unrelated fields", () => {
|
|
2416
|
+
// Only activeHoursStart is set. The superRefine must emit the issue so
|
|
2417
|
+
// the loader's delete-and-retry can strip the set field; otherwise the
|
|
2418
|
+
// mismatch persists and the config falls back to full defaults (which
|
|
2419
|
+
// would reset llm.default.maxTokens below to 64000).
|
|
2420
|
+
writeConfig({
|
|
2421
|
+
llm: { default: { maxTokens: 4096 } },
|
|
2422
|
+
filing: { activeHoursStart: 8 },
|
|
2423
|
+
});
|
|
2424
|
+
const config = loadConfig();
|
|
2425
|
+
expect(config.llm.default.maxTokens).toBe(4096);
|
|
2426
|
+
expect(config.filing.activeHoursStart).toBeNull();
|
|
2427
|
+
expect(config.filing.activeHoursEnd).toBeNull();
|
|
2428
|
+
});
|
|
2429
|
+
|
|
2430
|
+
test("recovers from partial heartbeat.activeHours without wiping unrelated fields", () => {
|
|
2431
|
+
// activeHoursStart is explicitly nulled while activeHoursEnd defaults to
|
|
2432
|
+
// 22 — a mismatch. Dual-emit strips both sides; both defaults restore
|
|
2433
|
+
// (8, 22). llm.default.maxTokens is unaffected.
|
|
2434
|
+
writeConfig({
|
|
2435
|
+
llm: { default: { maxTokens: 4096 } },
|
|
2436
|
+
heartbeat: { activeHoursStart: null },
|
|
2437
|
+
});
|
|
2438
|
+
const config = loadConfig();
|
|
2439
|
+
expect(config.llm.default.maxTokens).toBe(4096);
|
|
2440
|
+
expect(config.heartbeat.activeHoursStart).toBe(8);
|
|
2441
|
+
expect(config.heartbeat.activeHoursEnd).toBe(22);
|
|
2442
|
+
});
|
|
2443
|
+
|
|
2444
|
+
test("recovers from heartbeat.activeHours null-mismatch where explicit value equals opposite default", () => {
|
|
2445
|
+
// { start: null, end: 8 } — single-emit on the null side would strip
|
|
2446
|
+
// start, the default 8 would restore it, and the equal-hours check would
|
|
2447
|
+
// fire, cascading to a full defaults reset that wipes llm.default.maxTokens.
|
|
2448
|
+
// Dual-emit strips both sides in one pass.
|
|
2449
|
+
writeConfig({
|
|
2450
|
+
llm: { default: { maxTokens: 4096 } },
|
|
2451
|
+
heartbeat: { activeHoursStart: null, activeHoursEnd: 8 },
|
|
2452
|
+
});
|
|
2453
|
+
const config = loadConfig();
|
|
2454
|
+
expect(config.llm.default.maxTokens).toBe(4096);
|
|
2455
|
+
expect(config.heartbeat.activeHoursStart).toBe(8);
|
|
2456
|
+
expect(config.heartbeat.activeHoursEnd).toBe(22);
|
|
2457
|
+
});
|
|
2458
|
+
|
|
2459
|
+
test("recovers from heartbeat.activeHours null-mismatch on the end side", () => {
|
|
2460
|
+
// { start: 22, end: null } — same cascade class as above, mirrored.
|
|
2461
|
+
writeConfig({
|
|
2462
|
+
llm: { default: { maxTokens: 4096 } },
|
|
2463
|
+
heartbeat: { activeHoursStart: 22, activeHoursEnd: null },
|
|
2464
|
+
});
|
|
2465
|
+
const config = loadConfig();
|
|
2466
|
+
expect(config.llm.default.maxTokens).toBe(4096);
|
|
2467
|
+
expect(config.heartbeat.activeHoursStart).toBe(8);
|
|
2468
|
+
expect(config.heartbeat.activeHoursEnd).toBe(22);
|
|
2469
|
+
});
|
|
2470
|
+
|
|
2471
|
+
test("recovers from equal heartbeat.activeHours without wiping unrelated fields", () => {
|
|
2472
|
+
// { start: 22, end: 22 } — both equal to the default for end. Single-emit
|
|
2473
|
+
// on one path would strip one side, the default would recreate the
|
|
2474
|
+
// equal-hours mismatch, and the loader would fall back to full defaults,
|
|
2475
|
+
// wiping llm.default.maxTokens. Dual-emit strips both sides at once.
|
|
2476
|
+
writeConfig({
|
|
2477
|
+
llm: { default: { maxTokens: 4096 } },
|
|
2478
|
+
heartbeat: { activeHoursStart: 22, activeHoursEnd: 22 },
|
|
2479
|
+
});
|
|
2480
|
+
const config = loadConfig();
|
|
2481
|
+
expect(config.llm.default.maxTokens).toBe(4096);
|
|
2482
|
+
expect(config.heartbeat.activeHoursStart).toBe(8);
|
|
2483
|
+
expect(config.heartbeat.activeHoursEnd).toBe(22);
|
|
2484
|
+
});
|
|
2485
|
+
|
|
2486
|
+
test("recovers from equal filing.activeHours without wiping unrelated fields", () => {
|
|
2487
|
+
// activeHoursStart === activeHoursEnd is invalid (empty window). Filing's
|
|
2488
|
+
// defaults are null/null, so single-emit on one path would strip one side
|
|
2489
|
+
// and the null default would recreate a mismatch — cascading to a full
|
|
2490
|
+
// defaults reset that wipes llm.default.maxTokens. Dual-emit strips both
|
|
2491
|
+
// sides so both defaults restore to null.
|
|
2492
|
+
writeConfig({
|
|
2493
|
+
llm: { default: { maxTokens: 1234 } },
|
|
2494
|
+
filing: { activeHoursStart: 5, activeHoursEnd: 5 },
|
|
2495
|
+
});
|
|
2496
|
+
const config = loadConfig();
|
|
2497
|
+
expect(config.llm.default.maxTokens).toBe(1234);
|
|
2498
|
+
expect(config.filing.activeHoursStart).toBeNull();
|
|
2499
|
+
expect(config.filing.activeHoursEnd).toBeNull();
|
|
2500
|
+
});
|
|
2501
|
+
|
|
1247
2502
|
test("applies calls defaults when not specified", () => {
|
|
1248
2503
|
writeConfig({});
|
|
1249
2504
|
const config = loadConfig();
|
|
@@ -1253,8 +2508,13 @@ describe("loadConfig with schema validation", () => {
|
|
|
1253
2508
|
expect(config.calls.disclosure.enabled).toBe(true);
|
|
1254
2509
|
expect(config.calls.safety.denyCategories).toEqual([]);
|
|
1255
2510
|
expect(config.calls.voice.language).toBe("en-US");
|
|
1256
|
-
expect(
|
|
1257
|
-
|
|
2511
|
+
expect(
|
|
2512
|
+
(config.calls.voice as Record<string, unknown>).transcriptionProvider,
|
|
2513
|
+
).toBeUndefined();
|
|
2514
|
+
expect(
|
|
2515
|
+
(config.calls.voice as Record<string, unknown>).ttsProvider,
|
|
2516
|
+
).toBeUndefined();
|
|
2517
|
+
expect((config.calls as Record<string, unknown>).model).toBeUndefined();
|
|
1258
2518
|
expect(config.calls.callerIdentity).toEqual({
|
|
1259
2519
|
allowPerCallOverride: true,
|
|
1260
2520
|
});
|