@vellumai/assistant 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +41 -49
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +1111 -86
- package/package.json +40 -42
- package/scripts/generate-openapi.ts +0 -2
- package/scripts/test.sh +73 -18
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- 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 +240 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +1000 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +1248 -224
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
- package/src/__tests__/conversation-agent-loop.test.ts +99 -3
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +290 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +12 -4
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +946 -62
- package/src/__tests__/conversation-routes-disk-view.test.ts +275 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +324 -46
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +6 -3
- package/src/__tests__/credential-vault-unit.test.ts +383 -7
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +42 -18
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +222 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +386 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +2 -0
- package/src/__tests__/gemini-provider.test.ts +66 -2
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +738 -359
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +528 -49
- package/src/__tests__/headless-browser-read-tools.test.ts +274 -100
- package/src/__tests__/headless-browser-snapshot.test.ts +250 -77
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +145 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +596 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +423 -0
- package/src/__tests__/host-cu-proxy.test.ts +166 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +67 -8
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +109 -7
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +18 -12
- package/src/__tests__/oauth-cli.test.ts +709 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +118 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +147 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +52 -14
- package/src/__tests__/oauth-store.test.ts +1465 -176
- package/src/__tests__/oauth2-gateway-transport.test.ts +460 -26
- package/src/__tests__/onboarding-template-contract.test.ts +81 -70
- package/src/__tests__/openai-provider.test.ts +178 -2
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +32 -3
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +343 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +8 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +801 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +738 -0
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +576 -16
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +184 -27
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +25 -5
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +55 -9
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- 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-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +69 -3
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +3 -4
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +273 -16
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +56 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +32 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +330 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +117 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +215 -36
- package/src/cli/commands/oauth/shared.ts +7 -6
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +254 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +141 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +5 -2
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +54 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +9 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +38 -0
- package/src/config/env.ts +49 -4
- package/src/config/feature-flag-registry.json +85 -14
- package/src/config/loader.ts +82 -13
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +81 -15
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +112 -0
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +52 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -1
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +177 -0
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +61 -10
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +195 -0
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +23 -2
- package/src/daemon/conversation-agent-loop.ts +153 -42
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +622 -13
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +128 -36
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +131 -40
- package/src/daemon/conversation-tool-setup.ts +99 -8
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +292 -16
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +13 -141
- package/src/daemon/handlers/shared.ts +80 -0
- package/src/daemon/handlers/skills.ts +483 -44
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +192 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +179 -28
- package/src/daemon/message-protocol.ts +13 -0
- package/src/daemon/message-types/conversations.ts +89 -14
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +19 -5
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +26 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +439 -14
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +30 -20
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/mcp/client.ts +59 -24
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +31 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +122 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +18 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +67 -40
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +137 -60
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +12 -0
- package/src/memory/migrations/registry.ts +16 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/oauth.ts +21 -13
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +25 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +26 -9
- package/src/oauth/byo-connection.ts +10 -8
- package/src/oauth/connect-orchestrator.ts +25 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +22 -18
- package/src/oauth/connection.ts +3 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +223 -100
- package/src/oauth/platform-connection.test.ts +101 -3
- package/src/oauth/platform-connection.ts +56 -35
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +133 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +76 -38
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -60
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +10 -1
- package/src/runtime/AGENTS.md +65 -0
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +48 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +33 -9
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +368 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +815 -75
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +198 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +7 -0
- package/src/runtime/migrations/migration-wizard.ts +23 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +96 -1
- package/src/runtime/migrations/vbundle-importer.ts +89 -5
- package/src/runtime/pending-interactions.ts +18 -13
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +556 -0
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -141
- package/src/runtime/routes/conversation-management-routes.ts +223 -0
- package/src/runtime/routes/conversation-routes.ts +598 -103
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +268 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +262 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +122 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +31 -102
- package/src/runtime/routes/skills-routes.ts +128 -9
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +38 -9
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +57 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +68 -29
- package/src/security/secure-keys.ts +143 -27
- package/src/security/token-manager.ts +31 -10
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +554 -0
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +169 -40
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +1787 -342
- package/src/tools/browser/browser-manager.ts +81 -12
- 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__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1263 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +359 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1993 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +1007 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +744 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +868 -0
- package/src/tools/browser/cdp-client/errors.ts +49 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +148 -0
- package/src/tools/browser/cdp-client/factory.ts +914 -0
- package/src/tools/browser/cdp-client/index.ts +28 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +120 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +15 -0
- package/src/tools/terminal/shell.ts +36 -20
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +19 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +63 -24
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +31 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/cli/commands/browser-relay.ts +0 -536
- package/src/config/schemas/sandbox.ts +0 -14
- 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/permissions/permission-mode-store.ts +0 -180
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -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
|
|
@@ -81,7 +93,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
81
93
|
"inference-provider-native",
|
|
82
94
|
);
|
|
83
95
|
expect(result.services["web-search"].mode).toBe("your-own");
|
|
84
|
-
expect(result.maxTokens).toBe(
|
|
96
|
+
expect(result.maxTokens).toBe(64000);
|
|
85
97
|
expect(result.thinking).toEqual({
|
|
86
98
|
enabled: true,
|
|
87
99
|
streamThinking: true,
|
|
@@ -107,9 +119,6 @@ describe("AssistantConfigSchema", () => {
|
|
|
107
119
|
toolExecutionTimeoutSec: 120,
|
|
108
120
|
providerStreamTimeoutSec: 1800,
|
|
109
121
|
});
|
|
110
|
-
expect(result.sandbox).toEqual({
|
|
111
|
-
enabled: false,
|
|
112
|
-
});
|
|
113
122
|
expect(result.rateLimit).toEqual({
|
|
114
123
|
maxRequestsPerMinute: 0,
|
|
115
124
|
});
|
|
@@ -169,7 +178,7 @@ describe("AssistantConfigSchema", () => {
|
|
|
169
178
|
enqueueIntervalMs: 6 * 60 * 60 * 1000,
|
|
170
179
|
supersededItemRetentionMs: 30 * 24 * 60 * 60 * 1000,
|
|
171
180
|
conversationRetentionDays: 0,
|
|
172
|
-
llmRequestLogRetentionMs:
|
|
181
|
+
llmRequestLogRetentionMs: 1 * 60 * 60 * 1000,
|
|
173
182
|
});
|
|
174
183
|
});
|
|
175
184
|
|
|
@@ -180,6 +189,63 @@ describe("AssistantConfigSchema", () => {
|
|
|
180
189
|
expect(result.success).toBe(false);
|
|
181
190
|
});
|
|
182
191
|
|
|
192
|
+
test("accepts memory.cleanup.llmRequestLogRetentionMs at the 365-day boundary", () => {
|
|
193
|
+
const max = 365 * 24 * 60 * 60 * 1000;
|
|
194
|
+
const result = AssistantConfigSchema.safeParse({
|
|
195
|
+
memory: { cleanup: { llmRequestLogRetentionMs: max } },
|
|
196
|
+
});
|
|
197
|
+
expect(result.success).toBe(true);
|
|
198
|
+
if (result.success) {
|
|
199
|
+
expect(result.data.memory.cleanup.llmRequestLogRetentionMs).toBe(max);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("rejects memory.cleanup.llmRequestLogRetentionMs above 365 days", () => {
|
|
204
|
+
// This must match the gateway's MAX_LLM_REQUEST_LOG_RETENTION_MS. Without
|
|
205
|
+
// the Zod .max(), a manually edited config.json with a large value would
|
|
206
|
+
// be silently accepted by the daemon and then truncated by the macOS
|
|
207
|
+
// picker on the next PATCH — a quiet data-loss bug.
|
|
208
|
+
const overMax = 365 * 24 * 60 * 60 * 1000 + 1;
|
|
209
|
+
const result = AssistantConfigSchema.safeParse({
|
|
210
|
+
memory: { cleanup: { llmRequestLogRetentionMs: overMax } },
|
|
211
|
+
});
|
|
212
|
+
expect(result.success).toBe(false);
|
|
213
|
+
if (!result.success) {
|
|
214
|
+
expect(
|
|
215
|
+
result.error.issues.some((i) =>
|
|
216
|
+
i.path.includes("llmRequestLogRetentionMs"),
|
|
217
|
+
),
|
|
218
|
+
).toBe(true);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("rejects negative memory.cleanup.llmRequestLogRetentionMs", () => {
|
|
223
|
+
const result = AssistantConfigSchema.safeParse({
|
|
224
|
+
memory: { cleanup: { llmRequestLogRetentionMs: -1 } },
|
|
225
|
+
});
|
|
226
|
+
expect(result.success).toBe(false);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("accepts null memory.cleanup.llmRequestLogRetentionMs (keep forever)", () => {
|
|
230
|
+
const result = AssistantConfigSchema.safeParse({
|
|
231
|
+
memory: { cleanup: { llmRequestLogRetentionMs: null } },
|
|
232
|
+
});
|
|
233
|
+
expect(result.success).toBe(true);
|
|
234
|
+
if (result.success) {
|
|
235
|
+
expect(result.data.memory.cleanup.llmRequestLogRetentionMs).toBeNull();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("accepts memory.cleanup.llmRequestLogRetentionMs: 0 (prune immediately)", () => {
|
|
240
|
+
const result = AssistantConfigSchema.safeParse({
|
|
241
|
+
memory: { cleanup: { llmRequestLogRetentionMs: 0 } },
|
|
242
|
+
});
|
|
243
|
+
expect(result.success).toBe(true);
|
|
244
|
+
if (result.success) {
|
|
245
|
+
expect(result.data.memory.cleanup.llmRequestLogRetentionMs).toBe(0);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
183
249
|
test("rejects invalid provider", () => {
|
|
184
250
|
const result = AssistantConfigSchema.safeParse({
|
|
185
251
|
services: { inference: { provider: "invalid" } },
|
|
@@ -400,29 +466,10 @@ describe("AssistantConfigSchema", () => {
|
|
|
400
466
|
}
|
|
401
467
|
});
|
|
402
468
|
|
|
403
|
-
test("sandbox with only enabled still parses", () => {
|
|
404
|
-
const result = AssistantConfigSchema.parse({ sandbox: { enabled: false } });
|
|
405
|
-
expect(result.sandbox.enabled).toBe(false);
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
test("rejects unknown sandbox fields", () => {
|
|
409
|
-
const result = AssistantConfigSchema.safeParse({
|
|
410
|
-
sandbox: { backend: "docker" },
|
|
411
|
-
});
|
|
412
|
-
// Unknown keys are stripped by Zod passthrough/strip, so parse should still succeed
|
|
413
|
-
// but the unknown field should not appear in the output
|
|
414
|
-
if (result.success) {
|
|
415
|
-
expect(
|
|
416
|
-
(result.data.sandbox as Record<string, unknown>)["backend"],
|
|
417
|
-
).toBeUndefined();
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
|
|
421
469
|
test("defaults permissions.mode to workspace", () => {
|
|
422
470
|
const result = AssistantConfigSchema.parse({});
|
|
423
471
|
expect(result.permissions).toEqual({
|
|
424
472
|
mode: "workspace",
|
|
425
|
-
askBeforeActing: true,
|
|
426
473
|
hostAccess: false,
|
|
427
474
|
});
|
|
428
475
|
});
|
|
@@ -624,8 +671,6 @@ describe("AssistantConfigSchema", () => {
|
|
|
624
671
|
},
|
|
625
672
|
voice: {
|
|
626
673
|
language: "en-US",
|
|
627
|
-
transcriptionProvider: "Deepgram",
|
|
628
|
-
ttsProvider: "elevenlabs",
|
|
629
674
|
hints: [],
|
|
630
675
|
interruptSensitivity: "low",
|
|
631
676
|
},
|
|
@@ -733,29 +778,8 @@ describe("AssistantConfigSchema", () => {
|
|
|
733
778
|
test("config without calls.voice parses correctly and produces defaults", () => {
|
|
734
779
|
const result = AssistantConfigSchema.parse({});
|
|
735
780
|
expect(result.calls.voice.language).toBe("en-US");
|
|
736
|
-
expect(result.calls.voice.
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
test("elevenlabs tuning params have correct defaults", () => {
|
|
740
|
-
const result = AssistantConfigSchema.parse({});
|
|
741
|
-
expect(result.elevenlabs.voiceModelId).toBe("");
|
|
742
|
-
expect(result.elevenlabs.speed).toBe(1.0);
|
|
743
|
-
expect(result.elevenlabs.stability).toBe(0.5);
|
|
744
|
-
expect(result.elevenlabs.similarityBoost).toBe(0.75);
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
test("rejects elevenlabs.speed below 0.7", () => {
|
|
748
|
-
const result = AssistantConfigSchema.safeParse({
|
|
749
|
-
elevenlabs: { speed: 0.5 },
|
|
750
|
-
});
|
|
751
|
-
expect(result.success).toBe(false);
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
test("rejects elevenlabs.speed above 1.2", () => {
|
|
755
|
-
const result = AssistantConfigSchema.safeParse({
|
|
756
|
-
elevenlabs: { speed: 1.5 },
|
|
757
|
-
});
|
|
758
|
-
expect(result.success).toBe(false);
|
|
781
|
+
expect(result.calls.voice.hints).toEqual([]);
|
|
782
|
+
expect(result.calls.voice.interruptSensitivity).toBe("low");
|
|
759
783
|
});
|
|
760
784
|
|
|
761
785
|
test("accepts valid calls.voice overrides", () => {
|
|
@@ -763,39 +787,20 @@ describe("AssistantConfigSchema", () => {
|
|
|
763
787
|
calls: {
|
|
764
788
|
voice: {
|
|
765
789
|
language: "es-ES",
|
|
766
|
-
transcriptionProvider: "Google",
|
|
767
790
|
},
|
|
768
791
|
},
|
|
769
|
-
elevenlabs: {
|
|
770
|
-
stability: 0.8,
|
|
771
|
-
},
|
|
772
792
|
});
|
|
773
793
|
expect(result.calls.voice.language).toBe("es-ES");
|
|
774
|
-
expect(result.calls.voice.transcriptionProvider).toBe("Google");
|
|
775
|
-
expect(result.elevenlabs.stability).toBe(0.8);
|
|
776
|
-
// Defaults preserved for unset fields
|
|
777
|
-
expect(result.elevenlabs.voiceModelId).toBe("");
|
|
778
|
-
expect(result.elevenlabs.similarityBoost).toBe(0.75);
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
test("rejects invalid calls.voice.transcriptionProvider", () => {
|
|
782
|
-
const result = AssistantConfigSchema.safeParse({
|
|
783
|
-
calls: { voice: { transcriptionProvider: "AWS" } },
|
|
784
|
-
});
|
|
785
|
-
expect(result.success).toBe(false);
|
|
786
|
-
if (!result.success) {
|
|
787
|
-
const msgs = result.error.issues.map((i) => i.message);
|
|
788
|
-
expect(
|
|
789
|
-
msgs.some((m) => m.includes("calls.voice.transcriptionProvider")),
|
|
790
|
-
).toBe(true);
|
|
791
|
-
}
|
|
792
794
|
});
|
|
793
795
|
|
|
794
|
-
test("
|
|
795
|
-
|
|
796
|
-
|
|
796
|
+
test("transcriptionProvider is no longer part of the voice config schema", () => {
|
|
797
|
+
// Zod strips unrecognized keys by default — the legacy field is silently ignored.
|
|
798
|
+
const result = AssistantConfigSchema.parse({
|
|
799
|
+
calls: { voice: { transcriptionProvider: "Google" } },
|
|
797
800
|
});
|
|
798
|
-
expect(
|
|
801
|
+
expect(
|
|
802
|
+
(result.calls.voice as Record<string, unknown>).transcriptionProvider,
|
|
803
|
+
).toBeUndefined();
|
|
799
804
|
});
|
|
800
805
|
|
|
801
806
|
test("accepts optional calls.model", () => {
|
|
@@ -861,169 +866,1116 @@ describe("AssistantConfigSchema", () => {
|
|
|
861
866
|
});
|
|
862
867
|
expect(result.calls.callerIdentity.allowPerCallOverride).toBe(true);
|
|
863
868
|
});
|
|
864
|
-
});
|
|
865
869
|
|
|
866
|
-
//
|
|
867
|
-
// Tests: Voice quality profile resolver
|
|
868
|
-
// ---------------------------------------------------------------------------
|
|
870
|
+
// ── hostBrowser.cdpInspect config ─────────────────────────────────
|
|
869
871
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
872
|
+
test("applies hostBrowser.cdpInspect defaults", () => {
|
|
873
|
+
const result = AssistantConfigSchema.parse({});
|
|
874
|
+
expect(result.hostBrowser).toEqual({
|
|
875
|
+
cdpInspect: {
|
|
876
|
+
enabled: false,
|
|
877
|
+
host: "localhost",
|
|
878
|
+
port: 9222,
|
|
879
|
+
probeTimeoutMs: 500,
|
|
880
|
+
desktopAuto: {
|
|
881
|
+
enabled: true,
|
|
882
|
+
cooldownMs: 30_000,
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
});
|
|
876
886
|
});
|
|
877
887
|
|
|
878
|
-
test("
|
|
879
|
-
const
|
|
880
|
-
|
|
888
|
+
test("accepts hostBrowser.cdpInspect enabled with custom host/port", () => {
|
|
889
|
+
const result = AssistantConfigSchema.parse({
|
|
890
|
+
hostBrowser: {
|
|
891
|
+
cdpInspect: {
|
|
892
|
+
enabled: true,
|
|
893
|
+
host: "127.0.0.1",
|
|
894
|
+
port: 9333,
|
|
895
|
+
},
|
|
896
|
+
},
|
|
881
897
|
});
|
|
882
|
-
|
|
883
|
-
expect(
|
|
884
|
-
expect(
|
|
898
|
+
expect(result.hostBrowser.cdpInspect.enabled).toBe(true);
|
|
899
|
+
expect(result.hostBrowser.cdpInspect.host).toBe("127.0.0.1");
|
|
900
|
+
expect(result.hostBrowser.cdpInspect.port).toBe(9333);
|
|
901
|
+
// Unset field should still receive its default.
|
|
902
|
+
expect(result.hostBrowser.cdpInspect.probeTimeoutMs).toBe(500);
|
|
885
903
|
});
|
|
886
904
|
|
|
887
|
-
test("
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
905
|
+
test("accepts hostBrowser.cdpInspect custom probeTimeoutMs", () => {
|
|
906
|
+
const result = AssistantConfigSchema.parse({
|
|
907
|
+
hostBrowser: { cdpInspect: { probeTimeoutMs: 1000 } },
|
|
908
|
+
});
|
|
909
|
+
expect(result.hostBrowser.cdpInspect.probeTimeoutMs).toBe(1000);
|
|
891
910
|
});
|
|
892
911
|
|
|
893
|
-
test("
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
voiceId: "abc123",
|
|
897
|
-
voiceModelId: "turbo_v2_5",
|
|
898
|
-
speed: 0.9,
|
|
899
|
-
stability: 0.8,
|
|
900
|
-
similarityBoost: 0.9,
|
|
901
|
-
},
|
|
912
|
+
test("rejects hostBrowser.cdpInspect.port below 1", () => {
|
|
913
|
+
const result = AssistantConfigSchema.safeParse({
|
|
914
|
+
hostBrowser: { cdpInspect: { port: 0 } },
|
|
902
915
|
});
|
|
903
|
-
|
|
904
|
-
|
|
916
|
+
expect(result.success).toBe(false);
|
|
917
|
+
if (!result.success) {
|
|
918
|
+
expect(
|
|
919
|
+
result.error.issues.some((issue) =>
|
|
920
|
+
issue.path.join(".").includes("hostBrowser.cdpInspect.port"),
|
|
921
|
+
),
|
|
922
|
+
).toBe(true);
|
|
923
|
+
}
|
|
905
924
|
});
|
|
906
|
-
});
|
|
907
925
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
926
|
+
test("rejects hostBrowser.cdpInspect.port above 65535", () => {
|
|
927
|
+
const result = AssistantConfigSchema.safeParse({
|
|
928
|
+
hostBrowser: { cdpInspect: { port: 70000 } },
|
|
929
|
+
});
|
|
930
|
+
expect(result.success).toBe(false);
|
|
931
|
+
if (!result.success) {
|
|
932
|
+
expect(
|
|
933
|
+
result.error.issues.some((issue) =>
|
|
934
|
+
issue.path.join(".").includes("hostBrowser.cdpInspect.port"),
|
|
935
|
+
),
|
|
936
|
+
).toBe(true);
|
|
937
|
+
}
|
|
938
|
+
});
|
|
911
939
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
voiceId: "abc123",
|
|
916
|
-
voiceModelId: "turbo_v2_5",
|
|
917
|
-
speed: 1.0,
|
|
918
|
-
stability: 0.5,
|
|
919
|
-
similarityBoost: 0.75,
|
|
940
|
+
test("rejects non-integer hostBrowser.cdpInspect.port", () => {
|
|
941
|
+
const result = AssistantConfigSchema.safeParse({
|
|
942
|
+
hostBrowser: { cdpInspect: { port: 9222.5 } },
|
|
920
943
|
});
|
|
921
|
-
expect(
|
|
944
|
+
expect(result.success).toBe(false);
|
|
922
945
|
});
|
|
923
946
|
|
|
924
|
-
test("
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
voiceModelId: "turbo_v2_5",
|
|
928
|
-
speed: 1.0,
|
|
929
|
-
stability: 0.5,
|
|
930
|
-
similarityBoost: 0.75,
|
|
947
|
+
test("rejects hostBrowser.cdpInspect.probeTimeoutMs below 50", () => {
|
|
948
|
+
const result = AssistantConfigSchema.safeParse({
|
|
949
|
+
hostBrowser: { cdpInspect: { probeTimeoutMs: 10 } },
|
|
931
950
|
});
|
|
932
|
-
expect(
|
|
951
|
+
expect(result.success).toBe(false);
|
|
952
|
+
if (!result.success) {
|
|
953
|
+
expect(
|
|
954
|
+
result.error.issues.some((issue) =>
|
|
955
|
+
issue.path
|
|
956
|
+
.join(".")
|
|
957
|
+
.includes("hostBrowser.cdpInspect.probeTimeoutMs"),
|
|
958
|
+
),
|
|
959
|
+
).toBe(true);
|
|
960
|
+
}
|
|
933
961
|
});
|
|
934
962
|
|
|
935
|
-
test("
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
voiceModelId: "eleven_multilingual_v2",
|
|
939
|
-
speed: 0.9,
|
|
940
|
-
stability: 0.8,
|
|
941
|
-
similarityBoost: 0.9,
|
|
963
|
+
test("rejects hostBrowser.cdpInspect.probeTimeoutMs above 5000", () => {
|
|
964
|
+
const result = AssistantConfigSchema.safeParse({
|
|
965
|
+
hostBrowser: { cdpInspect: { probeTimeoutMs: 10000 } },
|
|
942
966
|
});
|
|
943
|
-
expect(
|
|
967
|
+
expect(result.success).toBe(false);
|
|
968
|
+
if (!result.success) {
|
|
969
|
+
expect(
|
|
970
|
+
result.error.issues.some((issue) =>
|
|
971
|
+
issue.path
|
|
972
|
+
.join(".")
|
|
973
|
+
.includes("hostBrowser.cdpInspect.probeTimeoutMs"),
|
|
974
|
+
),
|
|
975
|
+
).toBe(true);
|
|
976
|
+
}
|
|
944
977
|
});
|
|
945
978
|
|
|
946
|
-
test("
|
|
947
|
-
const
|
|
948
|
-
|
|
979
|
+
test("rejects non-integer hostBrowser.cdpInspect.probeTimeoutMs", () => {
|
|
980
|
+
const result = AssistantConfigSchema.safeParse({
|
|
981
|
+
hostBrowser: { cdpInspect: { probeTimeoutMs: 500.5 } },
|
|
949
982
|
});
|
|
950
|
-
|
|
951
|
-
expect(spec).toBe("test");
|
|
983
|
+
expect(result.success).toBe(false);
|
|
952
984
|
});
|
|
953
|
-
});
|
|
954
985
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
986
|
+
test("rejects non-boolean hostBrowser.cdpInspect.enabled", () => {
|
|
987
|
+
const result = AssistantConfigSchema.safeParse({
|
|
988
|
+
hostBrowser: { cdpInspect: { enabled: "yes" } },
|
|
989
|
+
});
|
|
990
|
+
expect(result.success).toBe(false);
|
|
991
|
+
});
|
|
958
992
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
993
|
+
// ── services.tts config ──────────────────────────────────────────────
|
|
994
|
+
|
|
995
|
+
test("applies services.tts defaults when not specified", () => {
|
|
996
|
+
const result = AssistantConfigSchema.parse({});
|
|
997
|
+
expect(result.services.tts.mode).toBe("your-own");
|
|
998
|
+
expect(result.services.tts.provider).toBe("elevenlabs");
|
|
999
|
+
expect(result.services.tts.providers.elevenlabs.voiceId).toBe(
|
|
1000
|
+
DEFAULT_ELEVENLABS_VOICE_ID,
|
|
1001
|
+
);
|
|
1002
|
+
expect(result.services.tts.providers.elevenlabs.speed).toBe(1.0);
|
|
1003
|
+
expect(result.services.tts.providers.elevenlabs.stability).toBe(0.5);
|
|
1004
|
+
expect(result.services.tts.providers.elevenlabs.similarityBoost).toBe(0.75);
|
|
1005
|
+
expect(
|
|
1006
|
+
result.services.tts.providers.elevenlabs.conversationTimeoutSeconds,
|
|
1007
|
+
).toBe(30);
|
|
1008
|
+
expect(result.services.tts.providers["fish-audio"].referenceId).toBe("");
|
|
1009
|
+
expect(result.services.tts.providers["fish-audio"].chunkLength).toBe(200);
|
|
1010
|
+
expect(result.services.tts.providers["fish-audio"].format).toBe("mp3");
|
|
1011
|
+
expect(result.services.tts.providers["fish-audio"].speed).toBe(1.0);
|
|
1012
|
+
expect(result.services.tts.providers.deepgram.model).toBe(
|
|
1013
|
+
"aura-asteria-en",
|
|
1014
|
+
);
|
|
1015
|
+
expect(result.services.tts.providers.deepgram.format).toBe("mp3");
|
|
977
1016
|
});
|
|
978
1017
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1018
|
+
test("accepts valid services.tts provider override", () => {
|
|
1019
|
+
const result = AssistantConfigSchema.parse({
|
|
1020
|
+
services: { tts: { provider: "fish-audio" } },
|
|
1021
|
+
});
|
|
1022
|
+
expect(result.services.tts.provider).toBe("fish-audio");
|
|
1023
|
+
expect(result.services.tts.mode).toBe("your-own");
|
|
982
1024
|
});
|
|
983
1025
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1026
|
+
test("accepts deepgram as services.tts.provider", () => {
|
|
1027
|
+
const result = AssistantConfigSchema.parse({
|
|
1028
|
+
services: { tts: { provider: "deepgram" } },
|
|
1029
|
+
});
|
|
1030
|
+
expect(result.services.tts.provider).toBe("deepgram");
|
|
1031
|
+
expect(result.services.tts.mode).toBe("your-own");
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
test("accepts valid services.tts.providers.elevenlabs overrides", () => {
|
|
1035
|
+
const result = AssistantConfigSchema.parse({
|
|
989
1036
|
services: {
|
|
990
|
-
|
|
1037
|
+
tts: {
|
|
1038
|
+
providers: {
|
|
1039
|
+
elevenlabs: { voiceId: "custom-voice", speed: 0.8 },
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
991
1042
|
},
|
|
992
|
-
maxTokens: 4096,
|
|
993
1043
|
});
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
expect(
|
|
1044
|
+
expect(result.services.tts.providers.elevenlabs.voiceId).toBe(
|
|
1045
|
+
"custom-voice",
|
|
1046
|
+
);
|
|
1047
|
+
expect(result.services.tts.providers.elevenlabs.speed).toBe(0.8);
|
|
1048
|
+
// Unset fields preserve defaults
|
|
1049
|
+
expect(result.services.tts.providers.elevenlabs.stability).toBe(0.5);
|
|
998
1050
|
});
|
|
999
1051
|
|
|
1000
|
-
test("
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
streamThinking: true,
|
|
1009
|
-
});
|
|
1010
|
-
expect(config.contextWindow).toEqual({
|
|
1011
|
-
enabled: true,
|
|
1012
|
-
maxInputTokens: 200000,
|
|
1013
|
-
targetBudgetRatio: 0.3,
|
|
1014
|
-
compactThreshold: 0.8,
|
|
1015
|
-
summaryBudgetRatio: 0.05,
|
|
1016
|
-
overflowRecovery: {
|
|
1017
|
-
enabled: true,
|
|
1018
|
-
safetyMarginRatio: 0.05,
|
|
1019
|
-
maxAttempts: 3,
|
|
1020
|
-
interactiveLatestTurnCompression: "summarize",
|
|
1021
|
-
nonInteractiveLatestTurnCompression: "truncate",
|
|
1052
|
+
test("accepts valid services.tts.providers.fish-audio overrides", () => {
|
|
1053
|
+
const result = AssistantConfigSchema.parse({
|
|
1054
|
+
services: {
|
|
1055
|
+
tts: {
|
|
1056
|
+
providers: {
|
|
1057
|
+
"fish-audio": { referenceId: "my-voice", format: "wav" },
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1022
1060
|
},
|
|
1023
1061
|
});
|
|
1062
|
+
expect(result.services.tts.providers["fish-audio"].referenceId).toBe(
|
|
1063
|
+
"my-voice",
|
|
1064
|
+
);
|
|
1065
|
+
expect(result.services.tts.providers["fish-audio"].format).toBe("wav");
|
|
1066
|
+
// Defaults preserved
|
|
1067
|
+
expect(result.services.tts.providers["fish-audio"].chunkLength).toBe(200);
|
|
1024
1068
|
});
|
|
1025
1069
|
|
|
1026
|
-
test("
|
|
1070
|
+
test("accepts valid services.tts.providers.deepgram overrides", () => {
|
|
1071
|
+
const result = AssistantConfigSchema.parse({
|
|
1072
|
+
services: {
|
|
1073
|
+
tts: {
|
|
1074
|
+
providers: {
|
|
1075
|
+
deepgram: { model: "aura-luna-en", format: "opus" },
|
|
1076
|
+
},
|
|
1077
|
+
},
|
|
1078
|
+
},
|
|
1079
|
+
});
|
|
1080
|
+
expect(result.services.tts.providers.deepgram.model).toBe("aura-luna-en");
|
|
1081
|
+
expect(result.services.tts.providers.deepgram.format).toBe("opus");
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
test("rejects services.tts.mode = managed", () => {
|
|
1085
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1086
|
+
services: { tts: { mode: "managed" } },
|
|
1087
|
+
});
|
|
1088
|
+
expect(result.success).toBe(false);
|
|
1089
|
+
if (!result.success) {
|
|
1090
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
1091
|
+
expect(
|
|
1092
|
+
msgs.some((m) => m.includes("your-own") || m.includes("managed")),
|
|
1093
|
+
).toBe(true);
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// ── hostBrowser.cdpInspect.desktopAuto config ───────────────────────
|
|
1098
|
+
|
|
1099
|
+
test("applies hostBrowser.cdpInspect.desktopAuto defaults", () => {
|
|
1100
|
+
const result = AssistantConfigSchema.parse({});
|
|
1101
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto).toEqual({
|
|
1102
|
+
enabled: true,
|
|
1103
|
+
cooldownMs: 30_000,
|
|
1104
|
+
});
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
test("accepts hostBrowser.cdpInspect.desktopAuto overrides", () => {
|
|
1108
|
+
const result = AssistantConfigSchema.parse({
|
|
1109
|
+
hostBrowser: {
|
|
1110
|
+
cdpInspect: {
|
|
1111
|
+
desktopAuto: { enabled: false, cooldownMs: 10_000 },
|
|
1112
|
+
},
|
|
1113
|
+
},
|
|
1114
|
+
});
|
|
1115
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto.enabled).toBe(false);
|
|
1116
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto.cooldownMs).toBe(10_000);
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
test("accepts hostBrowser.cdpInspect.desktopAuto.cooldownMs of 0 (disable cooldown)", () => {
|
|
1120
|
+
const result = AssistantConfigSchema.parse({
|
|
1121
|
+
hostBrowser: {
|
|
1122
|
+
cdpInspect: { desktopAuto: { cooldownMs: 0 } },
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto.cooldownMs).toBe(0);
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
test("rejects hostBrowser.cdpInspect.desktopAuto.cooldownMs below 0", () => {
|
|
1129
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1130
|
+
hostBrowser: {
|
|
1131
|
+
cdpInspect: { desktopAuto: { cooldownMs: -1 } },
|
|
1132
|
+
},
|
|
1133
|
+
});
|
|
1134
|
+
expect(result.success).toBe(false);
|
|
1135
|
+
if (!result.success) {
|
|
1136
|
+
expect(
|
|
1137
|
+
result.error.issues.some((issue) =>
|
|
1138
|
+
issue.path.join(".").includes("cooldownMs"),
|
|
1139
|
+
),
|
|
1140
|
+
).toBe(true);
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
test("rejects invalid services.tts.provider", () => {
|
|
1145
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1146
|
+
services: { tts: { provider: "aws-polly" } },
|
|
1147
|
+
});
|
|
1148
|
+
expect(result.success).toBe(false);
|
|
1149
|
+
if (!result.success) {
|
|
1150
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
1151
|
+
expect(msgs.some((m) => m.includes("services.tts.provider"))).toBe(true);
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
test("services.tts.mode only accepts your-own as literal", () => {
|
|
1156
|
+
// Explicit your-own should work
|
|
1157
|
+
const valid = TtsServiceSchema.safeParse({ mode: "your-own" });
|
|
1158
|
+
expect(valid.success).toBe(true);
|
|
1159
|
+
|
|
1160
|
+
// managed should be rejected
|
|
1161
|
+
const invalid = TtsServiceSchema.safeParse({ mode: "managed" });
|
|
1162
|
+
expect(invalid.success).toBe(false);
|
|
1163
|
+
|
|
1164
|
+
// Any other string should be rejected
|
|
1165
|
+
const invalid2 = TtsServiceSchema.safeParse({ mode: "self-hosted" });
|
|
1166
|
+
expect(invalid2.success).toBe(false);
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// ── services.stt config ──────────────────────────────────────────────
|
|
1170
|
+
|
|
1171
|
+
test("rejects services.stt without explicit provider", () => {
|
|
1172
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1173
|
+
services: { stt: { mode: "your-own" } },
|
|
1174
|
+
});
|
|
1175
|
+
expect(result.success).toBe(false);
|
|
1176
|
+
if (!result.success) {
|
|
1177
|
+
expect(
|
|
1178
|
+
result.error.issues.some((i) => i.path.join(".").includes("provider")),
|
|
1179
|
+
).toBe(true);
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
test("applies services.stt structural defaults when provider is explicit", () => {
|
|
1184
|
+
const result = AssistantConfigSchema.parse({
|
|
1185
|
+
services: { stt: { provider: "openai-whisper" } },
|
|
1186
|
+
});
|
|
1187
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1188
|
+
expect(result.services.stt.provider).toBe("openai-whisper");
|
|
1189
|
+
// providers defaults to empty sparse map
|
|
1190
|
+
expect(result.services.stt.providers).toEqual({});
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
test("accepts valid services.stt provider override", () => {
|
|
1194
|
+
const result = AssistantConfigSchema.parse({
|
|
1195
|
+
services: { stt: { provider: "openai-whisper" } },
|
|
1196
|
+
});
|
|
1197
|
+
expect(result.services.stt.provider).toBe("openai-whisper");
|
|
1198
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1201
|
+
test("accepts valid services.stt.providers.openai-whisper overrides", () => {
|
|
1202
|
+
const result = AssistantConfigSchema.parse({
|
|
1203
|
+
services: {
|
|
1204
|
+
stt: {
|
|
1205
|
+
provider: "openai-whisper",
|
|
1206
|
+
providers: {
|
|
1207
|
+
"openai-whisper": {},
|
|
1208
|
+
},
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1212
|
+
expect(result.services.stt.providers["openai-whisper"]).toEqual({});
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
test("parses when providers map is empty (sparse default)", () => {
|
|
1216
|
+
const result = AssistantConfigSchema.parse({
|
|
1217
|
+
services: { stt: { provider: "deepgram", providers: {} } },
|
|
1218
|
+
});
|
|
1219
|
+
expect(result.services.stt.providers).toEqual({});
|
|
1220
|
+
expect(result.services.stt.provider).toBe("deepgram");
|
|
1221
|
+
});
|
|
1222
|
+
|
|
1223
|
+
test("parses when unknown future provider blobs exist under providers", () => {
|
|
1224
|
+
const result = AssistantConfigSchema.parse({
|
|
1225
|
+
services: {
|
|
1226
|
+
stt: {
|
|
1227
|
+
provider: "openai-whisper",
|
|
1228
|
+
providers: {
|
|
1229
|
+
"openai-whisper": {},
|
|
1230
|
+
"future-provider": { model: "next-gen", lang: "en" },
|
|
1231
|
+
},
|
|
1232
|
+
},
|
|
1233
|
+
},
|
|
1234
|
+
});
|
|
1235
|
+
expect(result.services.stt.providers["openai-whisper"]).toEqual({});
|
|
1236
|
+
expect(result.services.stt.providers["future-provider"]).toEqual({
|
|
1237
|
+
model: "next-gen",
|
|
1238
|
+
lang: "en",
|
|
1239
|
+
});
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
test("rejects services.stt.mode = managed", () => {
|
|
1243
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1244
|
+
services: { stt: { mode: "managed" } },
|
|
1245
|
+
});
|
|
1246
|
+
expect(result.success).toBe(false);
|
|
1247
|
+
if (!result.success) {
|
|
1248
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
1249
|
+
expect(
|
|
1250
|
+
msgs.some((m) => m.includes("your-own") || m.includes("managed")),
|
|
1251
|
+
).toBe(true);
|
|
1252
|
+
}
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
test("rejects invalid services.stt.provider", () => {
|
|
1256
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1257
|
+
services: { stt: { provider: "azure-speech" } },
|
|
1258
|
+
});
|
|
1259
|
+
expect(result.success).toBe(false);
|
|
1260
|
+
if (!result.success) {
|
|
1261
|
+
const msgs = result.error.issues.map((i) => i.message);
|
|
1262
|
+
expect(msgs.some((m) => m.includes("services.stt.provider"))).toBe(true);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
test("accepts deepgram as services.stt.provider", () => {
|
|
1267
|
+
const result = AssistantConfigSchema.parse({
|
|
1268
|
+
services: { stt: { provider: "deepgram" } },
|
|
1269
|
+
});
|
|
1270
|
+
expect(result.services.stt.provider).toBe("deepgram");
|
|
1271
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
test("accepts google-gemini as services.stt.provider", () => {
|
|
1275
|
+
const result = AssistantConfigSchema.parse({
|
|
1276
|
+
services: { stt: { provider: "google-gemini" } },
|
|
1277
|
+
});
|
|
1278
|
+
expect(result.services.stt.provider).toBe("google-gemini");
|
|
1279
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
test("applies services.stt structural defaults when google-gemini provider is explicit", () => {
|
|
1283
|
+
const result = AssistantConfigSchema.parse({
|
|
1284
|
+
services: { stt: { provider: "google-gemini" } },
|
|
1285
|
+
});
|
|
1286
|
+
expect(result.services.stt.mode).toBe("your-own");
|
|
1287
|
+
expect(result.services.stt.provider).toBe("google-gemini");
|
|
1288
|
+
expect(result.services.stt.providers).toEqual({});
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
test("accepts valid services.stt.providers.deepgram overrides", () => {
|
|
1292
|
+
const result = AssistantConfigSchema.parse({
|
|
1293
|
+
services: {
|
|
1294
|
+
stt: {
|
|
1295
|
+
provider: "deepgram",
|
|
1296
|
+
providers: {
|
|
1297
|
+
deepgram: {},
|
|
1298
|
+
},
|
|
1299
|
+
},
|
|
1300
|
+
},
|
|
1301
|
+
});
|
|
1302
|
+
expect(result.services.stt.providers.deepgram).toEqual({});
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
test("existing configs with explicit per-provider objects continue to parse", () => {
|
|
1306
|
+
// Configs with explicit per-provider objects must continue to
|
|
1307
|
+
// parse and round-trip successfully.
|
|
1308
|
+
const result = AssistantConfigSchema.parse({
|
|
1309
|
+
services: {
|
|
1310
|
+
stt: {
|
|
1311
|
+
provider: "openai-whisper",
|
|
1312
|
+
providers: {
|
|
1313
|
+
"openai-whisper": {},
|
|
1314
|
+
deepgram: {},
|
|
1315
|
+
},
|
|
1316
|
+
},
|
|
1317
|
+
},
|
|
1318
|
+
});
|
|
1319
|
+
expect(result.services.stt.providers["openai-whisper"]).toEqual({});
|
|
1320
|
+
expect(result.services.stt.providers.deepgram).toEqual({});
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
test("services.stt.provider is required (no implicit default)", () => {
|
|
1324
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1325
|
+
services: { stt: {} },
|
|
1326
|
+
});
|
|
1327
|
+
expect(result.success).toBe(false);
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
test("services.stt.mode only accepts your-own as literal", () => {
|
|
1331
|
+
// Explicit your-own should work
|
|
1332
|
+
const valid = SttServiceSchema.safeParse({
|
|
1333
|
+
mode: "your-own",
|
|
1334
|
+
provider: "openai-whisper",
|
|
1335
|
+
});
|
|
1336
|
+
expect(valid.success).toBe(true);
|
|
1337
|
+
|
|
1338
|
+
// managed should be rejected
|
|
1339
|
+
const invalid = SttServiceSchema.safeParse({
|
|
1340
|
+
mode: "managed",
|
|
1341
|
+
provider: "openai-whisper",
|
|
1342
|
+
});
|
|
1343
|
+
expect(invalid.success).toBe(false);
|
|
1344
|
+
|
|
1345
|
+
// Any other string should be rejected
|
|
1346
|
+
const invalid2 = SttServiceSchema.safeParse({
|
|
1347
|
+
mode: "self-hosted",
|
|
1348
|
+
provider: "openai-whisper",
|
|
1349
|
+
});
|
|
1350
|
+
expect(invalid2.success).toBe(false);
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
test("rejects hostBrowser.cdpInspect.desktopAuto.cooldownMs above 300000", () => {
|
|
1354
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1355
|
+
hostBrowser: {
|
|
1356
|
+
cdpInspect: { desktopAuto: { cooldownMs: 500_000 } },
|
|
1357
|
+
},
|
|
1358
|
+
});
|
|
1359
|
+
expect(result.success).toBe(false);
|
|
1360
|
+
if (!result.success) {
|
|
1361
|
+
expect(
|
|
1362
|
+
result.error.issues.some((issue) =>
|
|
1363
|
+
issue.path.join(".").includes("cooldownMs"),
|
|
1364
|
+
),
|
|
1365
|
+
).toBe(true);
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
test("rejects non-integer hostBrowser.cdpInspect.desktopAuto.cooldownMs", () => {
|
|
1370
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1371
|
+
hostBrowser: {
|
|
1372
|
+
cdpInspect: { desktopAuto: { cooldownMs: 5000.5 } },
|
|
1373
|
+
},
|
|
1374
|
+
});
|
|
1375
|
+
expect(result.success).toBe(false);
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
test("rejects non-boolean hostBrowser.cdpInspect.desktopAuto.enabled", () => {
|
|
1379
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1380
|
+
hostBrowser: {
|
|
1381
|
+
cdpInspect: { desktopAuto: { enabled: "yes" } },
|
|
1382
|
+
},
|
|
1383
|
+
});
|
|
1384
|
+
expect(result.success).toBe(false);
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
test("desktopAuto defaults preserved when only cdpInspect.enabled is set", () => {
|
|
1388
|
+
const result = AssistantConfigSchema.parse({
|
|
1389
|
+
hostBrowser: { cdpInspect: { enabled: true } },
|
|
1390
|
+
});
|
|
1391
|
+
expect(result.hostBrowser.cdpInspect.desktopAuto).toEqual({
|
|
1392
|
+
enabled: true,
|
|
1393
|
+
cooldownMs: 30_000,
|
|
1394
|
+
});
|
|
1395
|
+
});
|
|
1396
|
+
});
|
|
1397
|
+
|
|
1398
|
+
// ---------------------------------------------------------------------------
|
|
1399
|
+
// Tests: Voice quality profile resolver
|
|
1400
|
+
// ---------------------------------------------------------------------------
|
|
1401
|
+
|
|
1402
|
+
describe("resolveVoiceQualityProfile", () => {
|
|
1403
|
+
test("always returns ElevenLabs ttsProvider", () => {
|
|
1404
|
+
const config = AssistantConfigSchema.parse({});
|
|
1405
|
+
const profile = resolveVoiceQualityProfile(config);
|
|
1406
|
+
expect(profile.ttsProvider).toBe("ElevenLabs");
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
test("uses services.tts.providers.elevenlabs.voiceId for voice", () => {
|
|
1410
|
+
const config = AssistantConfigSchema.parse({
|
|
1411
|
+
services: {
|
|
1412
|
+
tts: {
|
|
1413
|
+
providers: { elevenlabs: { voiceId: "test-voice-id" } },
|
|
1414
|
+
},
|
|
1415
|
+
},
|
|
1416
|
+
});
|
|
1417
|
+
const profile = resolveVoiceQualityProfile(config);
|
|
1418
|
+
expect(profile.ttsProvider).toBe("ElevenLabs");
|
|
1419
|
+
expect(profile.voice).toBe("test-voice-id");
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
test("defaults to Amelia voice ID when elevenlabs.voiceId is not set", () => {
|
|
1423
|
+
const config = AssistantConfigSchema.parse({});
|
|
1424
|
+
const profile = resolveVoiceQualityProfile(config);
|
|
1425
|
+
expect(profile.voice).toBe(DEFAULT_ELEVENLABS_VOICE_ID);
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
test("applies voice tuning params from services.tts.providers.elevenlabs config", () => {
|
|
1429
|
+
const config = AssistantConfigSchema.parse({
|
|
1430
|
+
services: {
|
|
1431
|
+
tts: {
|
|
1432
|
+
providers: {
|
|
1433
|
+
elevenlabs: {
|
|
1434
|
+
voiceId: "abc123",
|
|
1435
|
+
voiceModelId: "turbo_v2_5",
|
|
1436
|
+
speed: 0.9,
|
|
1437
|
+
stability: 0.8,
|
|
1438
|
+
similarityBoost: 0.9,
|
|
1439
|
+
},
|
|
1440
|
+
},
|
|
1441
|
+
},
|
|
1442
|
+
},
|
|
1443
|
+
});
|
|
1444
|
+
const profile = resolveVoiceQualityProfile(config);
|
|
1445
|
+
expect(profile.voice).toBe("abc123-turbo_v2_5-0.9_0.8_0.9");
|
|
1446
|
+
});
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
// ---------------------------------------------------------------------------
|
|
1450
|
+
// Tests: buildElevenLabsVoiceSpec
|
|
1451
|
+
// ---------------------------------------------------------------------------
|
|
1452
|
+
|
|
1453
|
+
describe("buildElevenLabsVoiceSpec", () => {
|
|
1454
|
+
test("produces Twilio-compliant voice string: voiceId-model-speed_stability_similarity", () => {
|
|
1455
|
+
const spec = buildElevenLabsVoiceSpec({
|
|
1456
|
+
voiceId: "abc123",
|
|
1457
|
+
voiceModelId: "turbo_v2_5",
|
|
1458
|
+
speed: 1.0,
|
|
1459
|
+
stability: 0.5,
|
|
1460
|
+
similarityBoost: 0.75,
|
|
1461
|
+
});
|
|
1462
|
+
expect(spec).toBe("abc123-turbo_v2_5-1_0.5_0.75");
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
test("returns empty string when voiceId is empty", () => {
|
|
1466
|
+
const spec = buildElevenLabsVoiceSpec({
|
|
1467
|
+
voiceId: "",
|
|
1468
|
+
voiceModelId: "turbo_v2_5",
|
|
1469
|
+
speed: 1.0,
|
|
1470
|
+
stability: 0.5,
|
|
1471
|
+
similarityBoost: 0.75,
|
|
1472
|
+
});
|
|
1473
|
+
expect(spec).toBe("");
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
test("formats custom parameters correctly", () => {
|
|
1477
|
+
const spec = buildElevenLabsVoiceSpec({
|
|
1478
|
+
voiceId: "myVoice",
|
|
1479
|
+
voiceModelId: "eleven_multilingual_v2",
|
|
1480
|
+
speed: 0.9,
|
|
1481
|
+
stability: 0.8,
|
|
1482
|
+
similarityBoost: 0.9,
|
|
1483
|
+
});
|
|
1484
|
+
expect(spec).toBe("myVoice-eleven_multilingual_v2-0.9_0.8_0.9");
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
test("default config uses a bare voiceId when no model override is set", () => {
|
|
1488
|
+
const config = AssistantConfigSchema.parse({
|
|
1489
|
+
services: {
|
|
1490
|
+
tts: {
|
|
1491
|
+
providers: { elevenlabs: { voiceId: "test" } },
|
|
1492
|
+
},
|
|
1493
|
+
},
|
|
1494
|
+
});
|
|
1495
|
+
const spec = buildElevenLabsVoiceSpec(
|
|
1496
|
+
config.services.tts.providers.elevenlabs,
|
|
1497
|
+
);
|
|
1498
|
+
expect(spec).toBe("test");
|
|
1499
|
+
});
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
// ---------------------------------------------------------------------------
|
|
1503
|
+
// Tests: TTS config resolver
|
|
1504
|
+
// ---------------------------------------------------------------------------
|
|
1505
|
+
|
|
1506
|
+
describe("resolveTtsConfig", () => {
|
|
1507
|
+
test("returns default provider and config from empty config", () => {
|
|
1508
|
+
const config = AssistantConfigSchema.parse({});
|
|
1509
|
+
const resolved = resolveTtsConfig(config);
|
|
1510
|
+
expect(resolved.provider).toBe("elevenlabs");
|
|
1511
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1512
|
+
voiceId: DEFAULT_ELEVENLABS_VOICE_ID,
|
|
1513
|
+
speed: 1.0,
|
|
1514
|
+
stability: 0.5,
|
|
1515
|
+
similarityBoost: 0.75,
|
|
1516
|
+
});
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
test("uses canonical services.tts.provider when set", () => {
|
|
1520
|
+
const config = AssistantConfigSchema.parse({
|
|
1521
|
+
services: { tts: { provider: "fish-audio" } },
|
|
1522
|
+
});
|
|
1523
|
+
const resolved = resolveTtsConfig(config);
|
|
1524
|
+
expect(resolved.provider).toBe("fish-audio");
|
|
1525
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1526
|
+
referenceId: "",
|
|
1527
|
+
chunkLength: 200,
|
|
1528
|
+
format: "mp3",
|
|
1529
|
+
speed: 1.0,
|
|
1530
|
+
});
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
test("returns canonical elevenlabs config from services.tts.providers", () => {
|
|
1534
|
+
const config = AssistantConfigSchema.parse({
|
|
1535
|
+
services: {
|
|
1536
|
+
tts: {
|
|
1537
|
+
provider: "elevenlabs",
|
|
1538
|
+
providers: {
|
|
1539
|
+
elevenlabs: { voiceId: "canonical-voice", stability: 0.9 },
|
|
1540
|
+
},
|
|
1541
|
+
},
|
|
1542
|
+
},
|
|
1543
|
+
});
|
|
1544
|
+
const resolved = resolveTtsConfig(config);
|
|
1545
|
+
expect(resolved.provider).toBe("elevenlabs");
|
|
1546
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1547
|
+
voiceId: "canonical-voice",
|
|
1548
|
+
stability: 0.9,
|
|
1549
|
+
});
|
|
1550
|
+
});
|
|
1551
|
+
|
|
1552
|
+
test("uses canonical elevenlabs config exclusively (no legacy fallback)", () => {
|
|
1553
|
+
const config = AssistantConfigSchema.parse({
|
|
1554
|
+
services: {
|
|
1555
|
+
tts: {
|
|
1556
|
+
providers: {
|
|
1557
|
+
elevenlabs: { voiceId: "canonical-voice", speed: 0.9 },
|
|
1558
|
+
},
|
|
1559
|
+
},
|
|
1560
|
+
},
|
|
1561
|
+
});
|
|
1562
|
+
const resolved = resolveTtsConfig(config);
|
|
1563
|
+
expect(resolved.provider).toBe("elevenlabs");
|
|
1564
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1565
|
+
voiceId: "canonical-voice",
|
|
1566
|
+
speed: 0.9,
|
|
1567
|
+
});
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
test("uses canonical fish-audio config exclusively (no legacy fallback)", () => {
|
|
1571
|
+
const config = AssistantConfigSchema.parse({
|
|
1572
|
+
services: {
|
|
1573
|
+
tts: {
|
|
1574
|
+
provider: "fish-audio",
|
|
1575
|
+
providers: {
|
|
1576
|
+
"fish-audio": { referenceId: "canonical-ref", format: "wav" },
|
|
1577
|
+
},
|
|
1578
|
+
},
|
|
1579
|
+
},
|
|
1580
|
+
});
|
|
1581
|
+
const resolved = resolveTtsConfig(config);
|
|
1582
|
+
expect(resolved.provider).toBe("fish-audio");
|
|
1583
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1584
|
+
referenceId: "canonical-ref",
|
|
1585
|
+
format: "wav",
|
|
1586
|
+
});
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
test("returns empty config for unknown provider", () => {
|
|
1590
|
+
// Force an unknown provider via type assertion for coverage.
|
|
1591
|
+
// structuredClone prevents mutation from leaking into Zod's shared
|
|
1592
|
+
// default objects (Zod 4 stores defaults by reference).
|
|
1593
|
+
const config = structuredClone(
|
|
1594
|
+
AssistantConfigSchema.parse({}),
|
|
1595
|
+
) as AssistantConfig;
|
|
1596
|
+
(config.services.tts as { provider: string }).provider = "aws-polly";
|
|
1597
|
+
const resolved = resolveTtsConfig(config);
|
|
1598
|
+
expect(resolved.provider).toBe("aws-polly");
|
|
1599
|
+
expect(resolved.providerConfig).toEqual({});
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
test("unknown provider resolution is deterministic across repeated calls", () => {
|
|
1603
|
+
const config = structuredClone(
|
|
1604
|
+
AssistantConfigSchema.parse({}),
|
|
1605
|
+
) as AssistantConfig;
|
|
1606
|
+
(config.services.tts as { provider: string }).provider = "nonexistent";
|
|
1607
|
+
const first = resolveTtsConfig(config);
|
|
1608
|
+
const second = resolveTtsConfig(config);
|
|
1609
|
+
expect(first).toEqual(second);
|
|
1610
|
+
expect(first.providerConfig).toEqual({});
|
|
1611
|
+
});
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1614
|
+
// ---------------------------------------------------------------------------
|
|
1615
|
+
// Tests: TTS provider catalog integration
|
|
1616
|
+
// ---------------------------------------------------------------------------
|
|
1617
|
+
|
|
1618
|
+
describe("TTS provider catalog integration", () => {
|
|
1619
|
+
test("VALID_TTS_SERVICE_PROVIDERS matches catalog provider IDs", () => {
|
|
1620
|
+
const catalogIds = listCatalogProviderIds();
|
|
1621
|
+
expect([...VALID_TTS_SERVICE_PROVIDERS]).toEqual(catalogIds);
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
test("schema accepts all catalog provider IDs as services.tts.provider", () => {
|
|
1625
|
+
for (const providerId of listCatalogProviderIds()) {
|
|
1626
|
+
const result = AssistantConfigSchema.safeParse({
|
|
1627
|
+
services: { tts: { provider: providerId } },
|
|
1628
|
+
});
|
|
1629
|
+
expect(result.success).toBe(true);
|
|
1630
|
+
if (result.success) {
|
|
1631
|
+
expect(result.data.services.tts.provider).toBe(providerId);
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
|
|
1636
|
+
test("TtsProvidersSchema has a key for every catalog provider", () => {
|
|
1637
|
+
const parsed = AssistantConfigSchema.parse({});
|
|
1638
|
+
const providerKeys = Object.keys(parsed.services.tts.providers);
|
|
1639
|
+
for (const providerId of listCatalogProviderIds()) {
|
|
1640
|
+
expect(providerKeys).toContain(providerId);
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
test("resolveTtsConfig returns correct defaults for each catalog provider", () => {
|
|
1645
|
+
for (const providerId of listCatalogProviderIds()) {
|
|
1646
|
+
const config = AssistantConfigSchema.parse({
|
|
1647
|
+
services: { tts: { provider: providerId } },
|
|
1648
|
+
});
|
|
1649
|
+
const resolved = resolveTtsConfig(config);
|
|
1650
|
+
expect(resolved.provider).toBe(providerId);
|
|
1651
|
+
// Every catalog provider should resolve to a non-empty config object
|
|
1652
|
+
expect(Object.keys(resolved.providerConfig).length).toBeGreaterThan(0);
|
|
1653
|
+
}
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
test("resolveTtsConfig returns overridden values for elevenlabs", () => {
|
|
1657
|
+
const config = AssistantConfigSchema.parse({
|
|
1658
|
+
services: {
|
|
1659
|
+
tts: {
|
|
1660
|
+
provider: "elevenlabs",
|
|
1661
|
+
providers: {
|
|
1662
|
+
elevenlabs: { voiceId: "override-voice", speed: 0.7 },
|
|
1663
|
+
},
|
|
1664
|
+
},
|
|
1665
|
+
},
|
|
1666
|
+
});
|
|
1667
|
+
const resolved = resolveTtsConfig(config);
|
|
1668
|
+
expect(resolved.provider).toBe("elevenlabs");
|
|
1669
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1670
|
+
voiceId: "override-voice",
|
|
1671
|
+
speed: 0.7,
|
|
1672
|
+
// Defaults still present for unset fields
|
|
1673
|
+
stability: 0.5,
|
|
1674
|
+
similarityBoost: 0.75,
|
|
1675
|
+
});
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
test("resolveTtsConfig returns overridden values for fish-audio", () => {
|
|
1679
|
+
const config = AssistantConfigSchema.parse({
|
|
1680
|
+
services: {
|
|
1681
|
+
tts: {
|
|
1682
|
+
provider: "fish-audio",
|
|
1683
|
+
providers: {
|
|
1684
|
+
"fish-audio": {
|
|
1685
|
+
referenceId: "override-ref",
|
|
1686
|
+
format: "opus",
|
|
1687
|
+
speed: 1.5,
|
|
1688
|
+
},
|
|
1689
|
+
},
|
|
1690
|
+
},
|
|
1691
|
+
},
|
|
1692
|
+
});
|
|
1693
|
+
const resolved = resolveTtsConfig(config);
|
|
1694
|
+
expect(resolved.provider).toBe("fish-audio");
|
|
1695
|
+
expect(resolved.providerConfig).toMatchObject({
|
|
1696
|
+
referenceId: "override-ref",
|
|
1697
|
+
format: "opus",
|
|
1698
|
+
speed: 1.5,
|
|
1699
|
+
// Defaults for unset fields
|
|
1700
|
+
chunkLength: 200,
|
|
1701
|
+
});
|
|
1702
|
+
});
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
// ---------------------------------------------------------------------------
|
|
1706
|
+
// Tests: TTS migration 032
|
|
1707
|
+
// ---------------------------------------------------------------------------
|
|
1708
|
+
|
|
1709
|
+
describe("032-tts-provider-unification migration", () => {
|
|
1710
|
+
const migrationDir = join(WORKSPACE_DIR, "_mig032");
|
|
1711
|
+
|
|
1712
|
+
beforeEach(() => {
|
|
1713
|
+
if (existsSync(migrationDir)) {
|
|
1714
|
+
rmSync(migrationDir, { recursive: true, force: true });
|
|
1715
|
+
}
|
|
1716
|
+
mkdirSync(migrationDir, { recursive: true });
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
afterEach(() => {
|
|
1720
|
+
if (existsSync(migrationDir)) {
|
|
1721
|
+
rmSync(migrationDir, { recursive: true, force: true });
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
function writeMigConfig(obj: unknown): void {
|
|
1726
|
+
writeFileSync(
|
|
1727
|
+
join(migrationDir, "config.json"),
|
|
1728
|
+
JSON.stringify(obj, null, 2),
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
function readMigConfig(): Record<string, unknown> {
|
|
1733
|
+
return JSON.parse(
|
|
1734
|
+
readFileSync(join(migrationDir, "config.json"), "utf-8"),
|
|
1735
|
+
) as Record<string, unknown>;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
test("backfills provider from calls.voice.ttsProvider", async () => {
|
|
1739
|
+
writeMigConfig({
|
|
1740
|
+
calls: { voice: { ttsProvider: "fish-audio" } },
|
|
1741
|
+
});
|
|
1742
|
+
const { ttsProviderUnificationMigration } =
|
|
1743
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1744
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1745
|
+
const result = readMigConfig();
|
|
1746
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
1747
|
+
string,
|
|
1748
|
+
unknown
|
|
1749
|
+
>;
|
|
1750
|
+
expect(tts.provider).toBe("fish-audio");
|
|
1751
|
+
expect(tts.mode).toBe("your-own");
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
test("backfills elevenlabs provider config from legacy keys", async () => {
|
|
1755
|
+
writeMigConfig({
|
|
1756
|
+
calls: { voice: { ttsProvider: "elevenlabs" } },
|
|
1757
|
+
elevenlabs: { voiceId: "my-voice", speed: 0.8 },
|
|
1758
|
+
});
|
|
1759
|
+
const { ttsProviderUnificationMigration } =
|
|
1760
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1761
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1762
|
+
const result = readMigConfig();
|
|
1763
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
1764
|
+
string,
|
|
1765
|
+
unknown
|
|
1766
|
+
>;
|
|
1767
|
+
const providers = tts.providers as Record<string, Record<string, unknown>>;
|
|
1768
|
+
expect(providers.elevenlabs.voiceId).toBe("my-voice");
|
|
1769
|
+
expect(providers.elevenlabs.speed).toBe(0.8);
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
test("backfills fish-audio provider config from legacy keys", async () => {
|
|
1773
|
+
writeMigConfig({
|
|
1774
|
+
calls: { voice: { ttsProvider: "fish-audio" } },
|
|
1775
|
+
fishAudio: { referenceId: "my-ref", format: "wav" },
|
|
1776
|
+
});
|
|
1777
|
+
const { ttsProviderUnificationMigration } =
|
|
1778
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1779
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1780
|
+
const result = readMigConfig();
|
|
1781
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
1782
|
+
string,
|
|
1783
|
+
unknown
|
|
1784
|
+
>;
|
|
1785
|
+
const providers = tts.providers as Record<string, Record<string, unknown>>;
|
|
1786
|
+
expect(providers["fish-audio"].referenceId).toBe("my-ref");
|
|
1787
|
+
expect(providers["fish-audio"].format).toBe("wav");
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
test("removes legacy fields after migration", async () => {
|
|
1791
|
+
writeMigConfig({
|
|
1792
|
+
calls: { voice: { ttsProvider: "elevenlabs", language: "en-US" } },
|
|
1793
|
+
elevenlabs: { voiceId: "my-voice" },
|
|
1794
|
+
});
|
|
1795
|
+
const { ttsProviderUnificationMigration } =
|
|
1796
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1797
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1798
|
+
const result = readMigConfig();
|
|
1799
|
+
// Legacy keys removed
|
|
1800
|
+
expect(
|
|
1801
|
+
(
|
|
1802
|
+
(result.calls as Record<string, unknown>).voice as Record<
|
|
1803
|
+
string,
|
|
1804
|
+
unknown
|
|
1805
|
+
>
|
|
1806
|
+
).ttsProvider,
|
|
1807
|
+
).toBeUndefined();
|
|
1808
|
+
expect(result.elevenlabs).toBeUndefined();
|
|
1809
|
+
// Other voice fields preserved
|
|
1810
|
+
expect(
|
|
1811
|
+
(
|
|
1812
|
+
(result.calls as Record<string, unknown>).voice as Record<
|
|
1813
|
+
string,
|
|
1814
|
+
unknown
|
|
1815
|
+
>
|
|
1816
|
+
).language,
|
|
1817
|
+
).toBe("en-US");
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
test("is idempotent — repeated runs produce no changes", async () => {
|
|
1821
|
+
writeMigConfig({
|
|
1822
|
+
calls: { voice: { ttsProvider: "fish-audio" } },
|
|
1823
|
+
fishAudio: { referenceId: "my-ref" },
|
|
1824
|
+
});
|
|
1825
|
+
const { ttsProviderUnificationMigration } =
|
|
1826
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1827
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1828
|
+
const afterFirst = readMigConfig();
|
|
1829
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1830
|
+
const afterSecond = readMigConfig();
|
|
1831
|
+
expect(afterSecond).toEqual(afterFirst);
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
test("does not overwrite existing services.tts.provider", async () => {
|
|
1835
|
+
writeMigConfig({
|
|
1836
|
+
services: { tts: { provider: "elevenlabs" } },
|
|
1837
|
+
calls: { voice: { ttsProvider: "fish-audio" } },
|
|
1838
|
+
});
|
|
1839
|
+
const { ttsProviderUnificationMigration } =
|
|
1840
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1841
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1842
|
+
const result = readMigConfig();
|
|
1843
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
1844
|
+
string,
|
|
1845
|
+
unknown
|
|
1846
|
+
>;
|
|
1847
|
+
// Should keep the existing canonical value, not the legacy one
|
|
1848
|
+
expect(tts.provider).toBe("elevenlabs");
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
test("does not overwrite existing canonical provider config keys", async () => {
|
|
1852
|
+
writeMigConfig({
|
|
1853
|
+
services: {
|
|
1854
|
+
tts: {
|
|
1855
|
+
providers: {
|
|
1856
|
+
elevenlabs: { voiceId: "canonical-voice" },
|
|
1857
|
+
},
|
|
1858
|
+
},
|
|
1859
|
+
},
|
|
1860
|
+
elevenlabs: { voiceId: "legacy-voice", speed: 0.8 },
|
|
1861
|
+
});
|
|
1862
|
+
const { ttsProviderUnificationMigration } =
|
|
1863
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1864
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1865
|
+
const result = readMigConfig();
|
|
1866
|
+
const tts = (result.services as Record<string, unknown>).tts as Record<
|
|
1867
|
+
string,
|
|
1868
|
+
unknown
|
|
1869
|
+
>;
|
|
1870
|
+
const providers = tts.providers as Record<string, Record<string, unknown>>;
|
|
1871
|
+
// Canonical voiceId preserved, legacy speed backfilled
|
|
1872
|
+
expect(providers.elevenlabs.voiceId).toBe("canonical-voice");
|
|
1873
|
+
expect(providers.elevenlabs.speed).toBe(0.8);
|
|
1874
|
+
// Legacy top-level key removed
|
|
1875
|
+
expect(result.elevenlabs).toBeUndefined();
|
|
1876
|
+
});
|
|
1877
|
+
|
|
1878
|
+
test("skips config without any legacy TTS fields", async () => {
|
|
1879
|
+
writeMigConfig({ maxTokens: 4096 });
|
|
1880
|
+
const { ttsProviderUnificationMigration } =
|
|
1881
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1882
|
+
const before = readMigConfig();
|
|
1883
|
+
await ttsProviderUnificationMigration.run(migrationDir);
|
|
1884
|
+
const after = readMigConfig();
|
|
1885
|
+
// Should remain unchanged (no services.tts added)
|
|
1886
|
+
expect(after).toEqual(before);
|
|
1887
|
+
});
|
|
1888
|
+
|
|
1889
|
+
test("down removes services.tts from config", async () => {
|
|
1890
|
+
writeMigConfig({
|
|
1891
|
+
services: {
|
|
1892
|
+
inference: { provider: "anthropic" },
|
|
1893
|
+
tts: { provider: "elevenlabs", mode: "your-own" },
|
|
1894
|
+
},
|
|
1895
|
+
});
|
|
1896
|
+
const { ttsProviderUnificationMigration } =
|
|
1897
|
+
await import("../workspace/migrations/032-tts-provider-unification.js");
|
|
1898
|
+
await ttsProviderUnificationMigration.down(migrationDir);
|
|
1899
|
+
const result = readMigConfig();
|
|
1900
|
+
const services = result.services as Record<string, unknown>;
|
|
1901
|
+
expect(services.tts).toBeUndefined();
|
|
1902
|
+
// Other services keys preserved
|
|
1903
|
+
expect(services.inference).toBeDefined();
|
|
1904
|
+
});
|
|
1905
|
+
});
|
|
1906
|
+
|
|
1907
|
+
// ---------------------------------------------------------------------------
|
|
1908
|
+
// Tests: loader integration (config file -> loadConfig with fallback)
|
|
1909
|
+
// ---------------------------------------------------------------------------
|
|
1910
|
+
|
|
1911
|
+
describe("loadConfig with schema validation", () => {
|
|
1912
|
+
beforeEach(() => {
|
|
1913
|
+
// Keep WORKSPACE_DIR and logs in place to avoid racing async logger stream init.
|
|
1914
|
+
ensureTestDir();
|
|
1915
|
+
const resetPaths = [
|
|
1916
|
+
CONFIG_PATH,
|
|
1917
|
+
join(WORKSPACE_DIR, "keys.enc"),
|
|
1918
|
+
join(WORKSPACE_DIR, "data"),
|
|
1919
|
+
join(WORKSPACE_DIR, "data", "memory"),
|
|
1920
|
+
];
|
|
1921
|
+
for (const path of resetPaths) {
|
|
1922
|
+
if (existsSync(path)) {
|
|
1923
|
+
rmSync(path, { recursive: true, force: true });
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
ensureTestDir();
|
|
1927
|
+
_setStorePath(join(WORKSPACE_DIR, "keys.enc"));
|
|
1928
|
+
invalidateConfigCache();
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
afterEach(() => {
|
|
1932
|
+
_setStorePath(null);
|
|
1933
|
+
invalidateConfigCache();
|
|
1934
|
+
});
|
|
1935
|
+
|
|
1936
|
+
// Intentionally do not remove WORKSPACE_DIR in afterAll.
|
|
1937
|
+
// A late async logger flush may still target logs under this path and can
|
|
1938
|
+
// intermittently trigger unhandled ENOENT in CI if the directory is removed.
|
|
1939
|
+
test("loads valid config", () => {
|
|
1940
|
+
writeConfig({
|
|
1941
|
+
services: {
|
|
1942
|
+
inference: { provider: "openai", model: "gpt-4" },
|
|
1943
|
+
},
|
|
1944
|
+
maxTokens: 4096,
|
|
1945
|
+
});
|
|
1946
|
+
const config = loadConfig();
|
|
1947
|
+
expect(config.services.inference.provider).toBe("openai");
|
|
1948
|
+
expect(config.services.inference.model).toBe("gpt-4");
|
|
1949
|
+
expect(config.maxTokens).toBe(4096);
|
|
1950
|
+
});
|
|
1951
|
+
|
|
1952
|
+
test("applies defaults for missing fields", () => {
|
|
1953
|
+
writeConfig({});
|
|
1954
|
+
const config = loadConfig();
|
|
1955
|
+
expect(config.services.inference.provider).toBe("anthropic");
|
|
1956
|
+
expect(config.services.inference.model).toBe("claude-opus-4-6");
|
|
1957
|
+
expect(config.maxTokens).toBe(64000);
|
|
1958
|
+
expect(config.thinking).toEqual({
|
|
1959
|
+
enabled: true,
|
|
1960
|
+
streamThinking: true,
|
|
1961
|
+
});
|
|
1962
|
+
expect(config.contextWindow).toEqual({
|
|
1963
|
+
enabled: true,
|
|
1964
|
+
maxInputTokens: 200000,
|
|
1965
|
+
targetBudgetRatio: 0.3,
|
|
1966
|
+
compactThreshold: 0.8,
|
|
1967
|
+
summaryBudgetRatio: 0.05,
|
|
1968
|
+
overflowRecovery: {
|
|
1969
|
+
enabled: true,
|
|
1970
|
+
safetyMarginRatio: 0.05,
|
|
1971
|
+
maxAttempts: 3,
|
|
1972
|
+
interactiveLatestTurnCompression: "summarize",
|
|
1973
|
+
nonInteractiveLatestTurnCompression: "truncate",
|
|
1974
|
+
},
|
|
1975
|
+
});
|
|
1976
|
+
});
|
|
1977
|
+
|
|
1978
|
+
test("falls back to default for invalid provider", () => {
|
|
1027
1979
|
writeConfig({
|
|
1028
1980
|
services: { inference: { provider: "invalid-provider" } },
|
|
1029
1981
|
});
|
|
@@ -1034,7 +1986,7 @@ describe("loadConfig with schema validation", () => {
|
|
|
1034
1986
|
test("falls back to default for invalid maxTokens", () => {
|
|
1035
1987
|
writeConfig({ maxTokens: -100 });
|
|
1036
1988
|
const config = loadConfig();
|
|
1037
|
-
expect(config.maxTokens).toBe(
|
|
1989
|
+
expect(config.maxTokens).toBe(64000);
|
|
1038
1990
|
});
|
|
1039
1991
|
|
|
1040
1992
|
test("falls back to defaults for invalid nested values", () => {
|
|
@@ -1059,13 +2011,13 @@ describe("loadConfig with schema validation", () => {
|
|
|
1059
2011
|
expect(config.services.inference.provider).toBe("openai");
|
|
1060
2012
|
expect(config.services.inference.model).toBe("gpt-4");
|
|
1061
2013
|
expect(config.thinking.enabled).toBe(true);
|
|
1062
|
-
expect(config.maxTokens).toBe(
|
|
2014
|
+
expect(config.maxTokens).toBe(64000);
|
|
1063
2015
|
});
|
|
1064
2016
|
|
|
1065
2017
|
test("handles no config file", () => {
|
|
1066
2018
|
const config = loadConfig();
|
|
1067
2019
|
expect(config.services.inference.provider).toBe("anthropic");
|
|
1068
|
-
expect(config.maxTokens).toBe(
|
|
2020
|
+
expect(config.maxTokens).toBe(64000);
|
|
1069
2021
|
});
|
|
1070
2022
|
|
|
1071
2023
|
test("partial nested objects get defaults for missing fields", () => {
|
|
@@ -1084,25 +2036,6 @@ describe("loadConfig with schema validation", () => {
|
|
|
1084
2036
|
expect(config.secretDetection.action).toBe("redact");
|
|
1085
2037
|
});
|
|
1086
2038
|
|
|
1087
|
-
test("falls back for invalid sandbox.enabled", () => {
|
|
1088
|
-
writeConfig({ sandbox: { enabled: "yes" } });
|
|
1089
|
-
const config = loadConfig();
|
|
1090
|
-
expect(config.sandbox.enabled).toBe(false);
|
|
1091
|
-
});
|
|
1092
|
-
|
|
1093
|
-
test("loads sandbox with only enabled field", () => {
|
|
1094
|
-
writeConfig({ sandbox: { enabled: false } });
|
|
1095
|
-
const config = loadConfig();
|
|
1096
|
-
expect(config.sandbox.enabled).toBe(false);
|
|
1097
|
-
});
|
|
1098
|
-
|
|
1099
|
-
test("strips unknown sandbox fields", () => {
|
|
1100
|
-
writeConfig({ sandbox: { enabled: true, backend: "docker" } });
|
|
1101
|
-
const config = loadConfig();
|
|
1102
|
-
expect(config.sandbox.enabled).toBe(true);
|
|
1103
|
-
expect("backend" in config.sandbox).toBe(false);
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
2039
|
test("falls back for invalid contextWindow relationship", () => {
|
|
1107
2040
|
writeConfig({
|
|
1108
2041
|
contextWindow: { targetBudgetRatio: 0.8, compactThreshold: 0.8 },
|
|
@@ -1131,7 +2064,6 @@ describe("loadConfig with schema validation", () => {
|
|
|
1131
2064
|
const config = loadConfig();
|
|
1132
2065
|
expect(config.permissions).toEqual({
|
|
1133
2066
|
mode: "workspace",
|
|
1134
|
-
askBeforeActing: true,
|
|
1135
2067
|
hostAccess: false,
|
|
1136
2068
|
});
|
|
1137
2069
|
});
|
|
@@ -1167,6 +2099,93 @@ describe("loadConfig with schema validation", () => {
|
|
|
1167
2099
|
expect(config.calls.provider).toBe("twilio");
|
|
1168
2100
|
});
|
|
1169
2101
|
|
|
2102
|
+
test("recovers from partial filing.activeHours without wiping unrelated fields", () => {
|
|
2103
|
+
// Only activeHoursStart is set. The superRefine must emit the issue so
|
|
2104
|
+
// the loader's delete-and-retry can strip the set field; otherwise the
|
|
2105
|
+
// mismatch persists and the config falls back to full defaults (which
|
|
2106
|
+
// would reset maxTokens below to 64000).
|
|
2107
|
+
writeConfig({
|
|
2108
|
+
maxTokens: 4096,
|
|
2109
|
+
filing: { activeHoursStart: 8 },
|
|
2110
|
+
});
|
|
2111
|
+
const config = loadConfig();
|
|
2112
|
+
expect(config.maxTokens).toBe(4096);
|
|
2113
|
+
expect(config.filing.activeHoursStart).toBeNull();
|
|
2114
|
+
expect(config.filing.activeHoursEnd).toBeNull();
|
|
2115
|
+
});
|
|
2116
|
+
|
|
2117
|
+
test("recovers from partial heartbeat.activeHours without wiping unrelated fields", () => {
|
|
2118
|
+
// activeHoursStart is explicitly nulled while activeHoursEnd defaults to
|
|
2119
|
+
// 22 — a mismatch. Dual-emit strips both sides; both defaults restore
|
|
2120
|
+
// (8, 22). maxTokens is unaffected.
|
|
2121
|
+
writeConfig({
|
|
2122
|
+
maxTokens: 4096,
|
|
2123
|
+
heartbeat: { activeHoursStart: null },
|
|
2124
|
+
});
|
|
2125
|
+
const config = loadConfig();
|
|
2126
|
+
expect(config.maxTokens).toBe(4096);
|
|
2127
|
+
expect(config.heartbeat.activeHoursStart).toBe(8);
|
|
2128
|
+
expect(config.heartbeat.activeHoursEnd).toBe(22);
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
test("recovers from heartbeat.activeHours null-mismatch where explicit value equals opposite default", () => {
|
|
2132
|
+
// { start: null, end: 8 } — single-emit on the null side would strip
|
|
2133
|
+
// start, the default 8 would restore it, and the equal-hours check would
|
|
2134
|
+
// fire, cascading to a full defaults reset that wipes maxTokens.
|
|
2135
|
+
// Dual-emit strips both sides in one pass.
|
|
2136
|
+
writeConfig({
|
|
2137
|
+
maxTokens: 4096,
|
|
2138
|
+
heartbeat: { activeHoursStart: null, activeHoursEnd: 8 },
|
|
2139
|
+
});
|
|
2140
|
+
const config = loadConfig();
|
|
2141
|
+
expect(config.maxTokens).toBe(4096);
|
|
2142
|
+
expect(config.heartbeat.activeHoursStart).toBe(8);
|
|
2143
|
+
expect(config.heartbeat.activeHoursEnd).toBe(22);
|
|
2144
|
+
});
|
|
2145
|
+
|
|
2146
|
+
test("recovers from heartbeat.activeHours null-mismatch on the end side", () => {
|
|
2147
|
+
// { start: 22, end: null } — same cascade class as above, mirrored.
|
|
2148
|
+
writeConfig({
|
|
2149
|
+
maxTokens: 4096,
|
|
2150
|
+
heartbeat: { activeHoursStart: 22, activeHoursEnd: null },
|
|
2151
|
+
});
|
|
2152
|
+
const config = loadConfig();
|
|
2153
|
+
expect(config.maxTokens).toBe(4096);
|
|
2154
|
+
expect(config.heartbeat.activeHoursStart).toBe(8);
|
|
2155
|
+
expect(config.heartbeat.activeHoursEnd).toBe(22);
|
|
2156
|
+
});
|
|
2157
|
+
|
|
2158
|
+
test("recovers from equal heartbeat.activeHours without wiping unrelated fields", () => {
|
|
2159
|
+
// { start: 22, end: 22 } — both equal to the default for end. Single-emit
|
|
2160
|
+
// on one path would strip one side, the default would recreate the
|
|
2161
|
+
// equal-hours mismatch, and the loader would fall back to full defaults,
|
|
2162
|
+
// wiping maxTokens. Dual-emit strips both sides at once.
|
|
2163
|
+
writeConfig({
|
|
2164
|
+
maxTokens: 4096,
|
|
2165
|
+
heartbeat: { activeHoursStart: 22, activeHoursEnd: 22 },
|
|
2166
|
+
});
|
|
2167
|
+
const config = loadConfig();
|
|
2168
|
+
expect(config.maxTokens).toBe(4096);
|
|
2169
|
+
expect(config.heartbeat.activeHoursStart).toBe(8);
|
|
2170
|
+
expect(config.heartbeat.activeHoursEnd).toBe(22);
|
|
2171
|
+
});
|
|
2172
|
+
|
|
2173
|
+
test("recovers from equal filing.activeHours without wiping unrelated fields", () => {
|
|
2174
|
+
// activeHoursStart === activeHoursEnd is invalid (empty window). Filing's
|
|
2175
|
+
// defaults are null/null, so single-emit on one path would strip one side
|
|
2176
|
+
// and the null default would recreate a mismatch — cascading to a full
|
|
2177
|
+
// defaults reset that wipes maxTokens. Dual-emit strips both sides so
|
|
2178
|
+
// both defaults restore to null.
|
|
2179
|
+
writeConfig({
|
|
2180
|
+
maxTokens: 1234,
|
|
2181
|
+
filing: { activeHoursStart: 5, activeHoursEnd: 5 },
|
|
2182
|
+
});
|
|
2183
|
+
const config = loadConfig();
|
|
2184
|
+
expect(config.maxTokens).toBe(1234);
|
|
2185
|
+
expect(config.filing.activeHoursStart).toBeNull();
|
|
2186
|
+
expect(config.filing.activeHoursEnd).toBeNull();
|
|
2187
|
+
});
|
|
2188
|
+
|
|
1170
2189
|
test("applies calls defaults when not specified", () => {
|
|
1171
2190
|
writeConfig({});
|
|
1172
2191
|
const config = loadConfig();
|
|
@@ -1176,7 +2195,12 @@ describe("loadConfig with schema validation", () => {
|
|
|
1176
2195
|
expect(config.calls.disclosure.enabled).toBe(true);
|
|
1177
2196
|
expect(config.calls.safety.denyCategories).toEqual([]);
|
|
1178
2197
|
expect(config.calls.voice.language).toBe("en-US");
|
|
1179
|
-
expect(
|
|
2198
|
+
expect(
|
|
2199
|
+
(config.calls.voice as Record<string, unknown>).transcriptionProvider,
|
|
2200
|
+
).toBeUndefined();
|
|
2201
|
+
expect(
|
|
2202
|
+
(config.calls.voice as Record<string, unknown>).ttsProvider,
|
|
2203
|
+
).toBeUndefined();
|
|
1180
2204
|
expect(config.calls.model).toBeUndefined();
|
|
1181
2205
|
expect(config.calls.callerIdentity).toEqual({
|
|
1182
2206
|
allowPerCallOverride: true,
|