@vellumai/assistant 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +5 -13
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/openapi.yaml +982 -72
- package/package.json +4 -6
- package/scripts/generate-openapi.ts +0 -1
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +11 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-schema.test.ts +1013 -66
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +98 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +901 -60
- package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/credential-vault-unit.test.ts +379 -3
- package/src/__tests__/credentials-cli.test.ts +40 -16
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/headless-browser-interactions.test.ts +43 -0
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +176 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +5 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +112 -26
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +18 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +43 -3
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +255 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/gmail/SKILL.md +53 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +2 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +34 -10
- package/src/config/feature-flag-registry.json +46 -14
- package/src/config/loader.ts +26 -12
- package/src/config/schema.ts +35 -10
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/services.ts +44 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +45 -12
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +101 -24
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +581 -19
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +11 -1
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +94 -4
- package/src/daemon/conversation-tool-setup.ts +25 -0
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation.ts +86 -28
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +4 -1
- package/src/daemon/handlers/shared.ts +22 -0
- package/src/daemon/handlers/skills.ts +321 -77
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +122 -25
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +34 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +14 -0
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +347 -2
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +12 -3
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +1 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +99 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/db-init.ts +6 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +27 -18
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +9 -0
- package/src/oauth/platform-connection.test.ts +98 -0
- package/src/oauth/platform-connection.ts +52 -31
- package/src/oauth/seed-providers.ts +7 -0
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +59 -18
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -61
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +9 -0
- package/src/runtime/AGENTS.md +43 -1
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +30 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +10 -10
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/http-server.ts +395 -10
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +34 -0
- package/src/runtime/pending-interactions.ts +0 -11
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
- package/src/runtime/routes/conversation-management-routes.ts +115 -0
- package/src/runtime/routes/conversation-routes.ts +367 -146
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +40 -5
- package/src/runtime/routes/settings-routes.ts +22 -5
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +30 -2
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +50 -0
- package/src/security/oauth2.ts +26 -4
- package/src/security/secure-keys.ts +25 -2
- package/src/security/token-manager.ts +8 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +38 -14
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/browser-execution.ts +1163 -23
- package/src/tools/browser/browser-manager.ts +45 -0
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/terminal/safe-env.ts +10 -2
- package/src/tools/terminal/shell.ts +15 -4
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +17 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +54 -10
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
|
@@ -39,6 +39,40 @@ mock.module("../config/loader.js", () => {
|
|
|
39
39
|
},
|
|
40
40
|
memory: { enabled: false },
|
|
41
41
|
notifications: { decisionModelIntent: "latency-optimized" },
|
|
42
|
+
services: {
|
|
43
|
+
tts: {
|
|
44
|
+
mode: "your-own" as const,
|
|
45
|
+
provider: "elevenlabs",
|
|
46
|
+
providers: {
|
|
47
|
+
elevenlabs: {
|
|
48
|
+
voiceId: "ZF6FPAbjXT4488VcRRnw",
|
|
49
|
+
voiceModelId: "",
|
|
50
|
+
speed: 1.0,
|
|
51
|
+
stability: 0.5,
|
|
52
|
+
similarityBoost: 0.75,
|
|
53
|
+
conversationTimeoutSeconds: 30,
|
|
54
|
+
},
|
|
55
|
+
"fish-audio": {
|
|
56
|
+
referenceId: "",
|
|
57
|
+
chunkLength: 200,
|
|
58
|
+
format: "mp3",
|
|
59
|
+
latency: "normal",
|
|
60
|
+
speed: 1.0,
|
|
61
|
+
},
|
|
62
|
+
deepgram: {
|
|
63
|
+
model: "aura-2-theia-en",
|
|
64
|
+
format: "mp3",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
elevenlabs: {
|
|
70
|
+
voiceId: "ZF6FPAbjXT4488VcRRnw",
|
|
71
|
+
},
|
|
72
|
+
fishAudio: {
|
|
73
|
+
referenceId: "",
|
|
74
|
+
format: "mp3",
|
|
75
|
+
},
|
|
42
76
|
};
|
|
43
77
|
return {
|
|
44
78
|
getConfig: () => config,
|
|
@@ -54,6 +88,17 @@ mock.module("../config/loader.js", () => {
|
|
|
54
88
|
};
|
|
55
89
|
});
|
|
56
90
|
|
|
91
|
+
// ── Credential mock (prevents real key lookups) ──────────────────────
|
|
92
|
+
|
|
93
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
94
|
+
getSecureKeyAsync: async () => null,
|
|
95
|
+
getSecureKey: () => null,
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
mock.module("../security/credential-key.js", () => ({
|
|
99
|
+
credentialKey: (...args: string[]) => args.join("/"),
|
|
100
|
+
}));
|
|
101
|
+
|
|
57
102
|
// ── Call constants mock ──────────────────────────────────────────────
|
|
58
103
|
|
|
59
104
|
let mockConsultationTimeoutMs = 90_000;
|
|
@@ -128,6 +173,63 @@ mock.module("../calls/voice-session-bridge.js", () => {
|
|
|
128
173
|
};
|
|
129
174
|
});
|
|
130
175
|
|
|
176
|
+
// ── TTS provider registry setup ──────────────────────────────────────
|
|
177
|
+
// Register test providers so call-controller can resolve the TTS provider
|
|
178
|
+
// abstraction. ElevenLabs is the default native provider (no streaming),
|
|
179
|
+
// while Fish Audio is a synthesized provider (streaming).
|
|
180
|
+
|
|
181
|
+
import {
|
|
182
|
+
_resetTtsProviderRegistry,
|
|
183
|
+
registerTtsProvider,
|
|
184
|
+
} from "../tts/provider-registry.js";
|
|
185
|
+
import type { TtsProvider } from "../tts/types.js";
|
|
186
|
+
|
|
187
|
+
function registerTestTtsProviders(): void {
|
|
188
|
+
_resetTtsProviderRegistry();
|
|
189
|
+
|
|
190
|
+
const elevenlabs: TtsProvider = {
|
|
191
|
+
id: "elevenlabs",
|
|
192
|
+
capabilities: { supportsStreaming: false, supportedFormats: ["mp3"] },
|
|
193
|
+
async synthesize() {
|
|
194
|
+
return { audio: Buffer.from(""), contentType: "audio/mpeg" };
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
registerTtsProvider(elevenlabs);
|
|
198
|
+
|
|
199
|
+
const fishAudio: TtsProvider = {
|
|
200
|
+
id: "fish-audio",
|
|
201
|
+
capabilities: {
|
|
202
|
+
supportsStreaming: true,
|
|
203
|
+
supportedFormats: ["mp3", "wav", "opus"],
|
|
204
|
+
},
|
|
205
|
+
async synthesize() {
|
|
206
|
+
return { audio: Buffer.from(""), contentType: "audio/mpeg" };
|
|
207
|
+
},
|
|
208
|
+
async synthesizeStream(_req, _onChunk) {
|
|
209
|
+
return { audio: Buffer.from(""), contentType: "audio/mpeg" };
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
registerTtsProvider(fishAudio);
|
|
213
|
+
|
|
214
|
+
const deepgram: TtsProvider = {
|
|
215
|
+
id: "deepgram",
|
|
216
|
+
capabilities: {
|
|
217
|
+
supportsStreaming: false,
|
|
218
|
+
supportedFormats: ["mp3", "wav", "opus"],
|
|
219
|
+
},
|
|
220
|
+
async synthesize() {
|
|
221
|
+
return {
|
|
222
|
+
audio: Buffer.from("fake-deepgram-audio"),
|
|
223
|
+
contentType: "audio/mpeg",
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
registerTtsProvider(deepgram);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Register providers immediately so they're available for all tests
|
|
231
|
+
registerTestTtsProviders();
|
|
232
|
+
|
|
131
233
|
// ── Import source modules after all mocks are registered ────────────
|
|
132
234
|
|
|
133
235
|
import { CallController } from "../calls/call-controller.js";
|
|
@@ -139,7 +241,9 @@ import {
|
|
|
139
241
|
getPendingQuestion,
|
|
140
242
|
updateCallSession,
|
|
141
243
|
} from "../calls/call-store.js";
|
|
142
|
-
import type {
|
|
244
|
+
import type { CallTransport } from "../calls/call-transport.js";
|
|
245
|
+
import { resolveCallTtsProvider } from "../calls/resolve-call-tts-provider.js";
|
|
246
|
+
import { loadConfig } from "../config/loader.js";
|
|
143
247
|
import {
|
|
144
248
|
getCanonicalGuardianRequest,
|
|
145
249
|
getPendingCanonicalRequestByCallSessionId,
|
|
@@ -154,18 +258,20 @@ afterAll(() => {
|
|
|
154
258
|
resetDb();
|
|
155
259
|
});
|
|
156
260
|
|
|
157
|
-
// ──
|
|
261
|
+
// ── CallTransport mock factory ───────────────────────────────────────
|
|
158
262
|
|
|
159
|
-
interface
|
|
263
|
+
interface MockTransport extends CallTransport {
|
|
160
264
|
sentTokens: Array<{ token: string; last: boolean }>;
|
|
265
|
+
sentPlayUrls: string[];
|
|
161
266
|
endCalled: boolean;
|
|
162
267
|
endReason: string | undefined;
|
|
163
268
|
mockConnectionState: string;
|
|
164
269
|
}
|
|
165
270
|
|
|
166
|
-
function
|
|
271
|
+
function createMockTransport(): MockTransport {
|
|
167
272
|
const state = {
|
|
168
273
|
sentTokens: [] as Array<{ token: string; last: boolean }>,
|
|
274
|
+
sentPlayUrls: [] as string[],
|
|
169
275
|
_endCalled: false,
|
|
170
276
|
_endReason: undefined as string | undefined,
|
|
171
277
|
_connectionState: "connected",
|
|
@@ -175,6 +281,9 @@ function createMockRelay(): MockRelay {
|
|
|
175
281
|
get sentTokens() {
|
|
176
282
|
return state.sentTokens;
|
|
177
283
|
},
|
|
284
|
+
get sentPlayUrls() {
|
|
285
|
+
return state.sentPlayUrls;
|
|
286
|
+
},
|
|
178
287
|
get endCalled() {
|
|
179
288
|
return state._endCalled;
|
|
180
289
|
},
|
|
@@ -190,6 +299,9 @@ function createMockRelay(): MockRelay {
|
|
|
190
299
|
sendTextToken(token: string, last: boolean) {
|
|
191
300
|
state.sentTokens.push({ token, last });
|
|
192
301
|
},
|
|
302
|
+
sendPlayUrl(url: string) {
|
|
303
|
+
state.sentPlayUrls.push(url);
|
|
304
|
+
},
|
|
193
305
|
endSession(reason?: string) {
|
|
194
306
|
state._endCalled = true;
|
|
195
307
|
state._endReason = reason;
|
|
@@ -197,7 +309,7 @@ function createMockRelay(): MockRelay {
|
|
|
197
309
|
getConnectionState() {
|
|
198
310
|
return state._connectionState;
|
|
199
311
|
},
|
|
200
|
-
} as
|
|
312
|
+
} as MockTransport;
|
|
201
313
|
}
|
|
202
314
|
|
|
203
315
|
// ── Helpers ─────────────────────────────────────────────────────────
|
|
@@ -254,7 +366,7 @@ async function pollUntil(
|
|
|
254
366
|
}
|
|
255
367
|
|
|
256
368
|
/**
|
|
257
|
-
* Create a call session and a controller wired to a mock
|
|
369
|
+
* Create a call session and a controller wired to a mock transport.
|
|
258
370
|
*/
|
|
259
371
|
function setupController(
|
|
260
372
|
task?: string,
|
|
@@ -272,17 +384,12 @@ function setupController(
|
|
|
272
384
|
task,
|
|
273
385
|
});
|
|
274
386
|
updateCallSession(session.id, { status: "in_progress" });
|
|
275
|
-
const
|
|
276
|
-
const controller = new CallController(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
assistantId: opts?.assistantId,
|
|
282
|
-
trustContext: opts?.trustContext,
|
|
283
|
-
},
|
|
284
|
-
);
|
|
285
|
-
return { session, relay, controller };
|
|
387
|
+
const transport = createMockTransport();
|
|
388
|
+
const controller = new CallController(session.id, transport, task ?? null, {
|
|
389
|
+
assistantId: opts?.assistantId,
|
|
390
|
+
trustContext: opts?.trustContext,
|
|
391
|
+
});
|
|
392
|
+
return { session, relay: transport, controller };
|
|
286
393
|
}
|
|
287
394
|
|
|
288
395
|
function getLatestAssistantText(conversationId: string): string | null {
|
|
@@ -325,14 +432,14 @@ function setupControllerWithOrigin(task?: string) {
|
|
|
325
432
|
status: "in_progress",
|
|
326
433
|
startedAt: Date.now() - 30_000,
|
|
327
434
|
});
|
|
328
|
-
const
|
|
435
|
+
const transport = createMockTransport();
|
|
329
436
|
const controller = new CallController(
|
|
330
437
|
session.id,
|
|
331
|
-
|
|
438
|
+
transport,
|
|
332
439
|
task ?? null,
|
|
333
440
|
{},
|
|
334
441
|
);
|
|
335
|
-
return { session, relay, controller };
|
|
442
|
+
return { session, relay: transport, controller };
|
|
336
443
|
}
|
|
337
444
|
|
|
338
445
|
describe("call-controller", () => {
|
|
@@ -345,6 +452,12 @@ describe("call-controller", () => {
|
|
|
345
452
|
// Reset consultation timeout to the default (long) value
|
|
346
453
|
mockConsultationTimeoutMs = 90_000;
|
|
347
454
|
mockSilenceTimeoutMs = 30_000;
|
|
455
|
+
// Reset TTS config to defaults so per-test mutations don't leak.
|
|
456
|
+
const cfg = loadConfig();
|
|
457
|
+
cfg.services.tts.provider = "elevenlabs";
|
|
458
|
+
cfg.services.tts.providers["fish-audio"].referenceId = "";
|
|
459
|
+
// Reset TTS provider registry to ensure clean state
|
|
460
|
+
registerTestTtsProviders();
|
|
348
461
|
});
|
|
349
462
|
|
|
350
463
|
// ── handleCallerUtterance ─────────────────────────────────────────
|
|
@@ -2291,4 +2404,452 @@ describe("call-controller", () => {
|
|
|
2291
2404
|
|
|
2292
2405
|
controller.destroy();
|
|
2293
2406
|
});
|
|
2407
|
+
|
|
2408
|
+
// ── TTS provider abstraction: native-token path ─────────────────────
|
|
2409
|
+
|
|
2410
|
+
test("native provider (ElevenLabs): streams text tokens directly to relay", async () => {
|
|
2411
|
+
// Default config uses ElevenLabs (native, no streaming) — the text
|
|
2412
|
+
// tokens should flow directly through sendTextToken to the relay.
|
|
2413
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2414
|
+
createMockVoiceTurn(["Hello", ", how", " are you?"]),
|
|
2415
|
+
);
|
|
2416
|
+
const { relay, controller } = setupController();
|
|
2417
|
+
|
|
2418
|
+
await controller.handleCallerUtterance("Hi");
|
|
2419
|
+
|
|
2420
|
+
// Verify text tokens were sent directly (not empty — real text content)
|
|
2421
|
+
const nonEmptyTokens = relay.sentTokens.filter((t) => t.token.length > 0);
|
|
2422
|
+
expect(nonEmptyTokens.length).toBeGreaterThan(0);
|
|
2423
|
+
// At least one token should contain actual text content
|
|
2424
|
+
const allText = nonEmptyTokens.map((t) => t.token).join("");
|
|
2425
|
+
expect(allText).toContain("Hello");
|
|
2426
|
+
expect(allText).toContain("how");
|
|
2427
|
+
expect(allText).toContain("are you");
|
|
2428
|
+
|
|
2429
|
+
// The final token should signal end of turn
|
|
2430
|
+
const lastToken = relay.sentTokens[relay.sentTokens.length - 1];
|
|
2431
|
+
expect(lastToken.last).toBe(true);
|
|
2432
|
+
|
|
2433
|
+
controller.destroy();
|
|
2434
|
+
});
|
|
2435
|
+
|
|
2436
|
+
test("native provider (ElevenLabs): strips control markers from streamed text tokens", async () => {
|
|
2437
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2438
|
+
createMockVoiceTurn([
|
|
2439
|
+
"I will check on that. ",
|
|
2440
|
+
"[ASK_GUARDIAN: Is 3pm ok?]",
|
|
2441
|
+
]),
|
|
2442
|
+
);
|
|
2443
|
+
const { relay, controller } = setupController();
|
|
2444
|
+
|
|
2445
|
+
await controller.handleCallerUtterance("Book an appointment");
|
|
2446
|
+
|
|
2447
|
+
const allText = relay.sentTokens.map((t) => t.token).join("");
|
|
2448
|
+
expect(allText).toContain("I will check on that.");
|
|
2449
|
+
expect(allText).not.toContain("[ASK_GUARDIAN:");
|
|
2450
|
+
|
|
2451
|
+
controller.destroy();
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2454
|
+
test("native provider (ElevenLabs): END_CALL marker handled correctly with text tokens", async () => {
|
|
2455
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2456
|
+
createMockVoiceTurn(["Thanks for calling! ", "[END_CALL]"]),
|
|
2457
|
+
);
|
|
2458
|
+
const { session, relay, controller } = setupController();
|
|
2459
|
+
|
|
2460
|
+
await controller.handleCallerUtterance("Goodbye");
|
|
2461
|
+
|
|
2462
|
+
expect(relay.endCalled).toBe(true);
|
|
2463
|
+
const updatedSession = getCallSession(session.id);
|
|
2464
|
+
expect(updatedSession!.status).toBe("completed");
|
|
2465
|
+
|
|
2466
|
+
const allText = relay.sentTokens.map((t) => t.token).join("");
|
|
2467
|
+
expect(allText).not.toContain("[END_CALL]");
|
|
2468
|
+
expect(allText).toContain("Thanks for calling!");
|
|
2469
|
+
|
|
2470
|
+
controller.destroy();
|
|
2471
|
+
});
|
|
2472
|
+
|
|
2473
|
+
test("synthesized provider: if synthesis fails before first chunk, falls back to text-token speech without sending play URL", async () => {
|
|
2474
|
+
const cfg = loadConfig();
|
|
2475
|
+
cfg.services.tts.provider = "fish-audio";
|
|
2476
|
+
cfg.services.tts.providers["fish-audio"].referenceId = "fish-ref-123";
|
|
2477
|
+
|
|
2478
|
+
_resetTtsProviderRegistry();
|
|
2479
|
+
const elevenlabs: TtsProvider = {
|
|
2480
|
+
id: "elevenlabs",
|
|
2481
|
+
capabilities: { supportsStreaming: false, supportedFormats: ["mp3"] },
|
|
2482
|
+
async synthesize() {
|
|
2483
|
+
return { audio: Buffer.from(""), contentType: "audio/mpeg" };
|
|
2484
|
+
},
|
|
2485
|
+
};
|
|
2486
|
+
registerTtsProvider(elevenlabs);
|
|
2487
|
+
|
|
2488
|
+
const fishAudioFailing: TtsProvider = {
|
|
2489
|
+
id: "fish-audio",
|
|
2490
|
+
capabilities: {
|
|
2491
|
+
supportsStreaming: true,
|
|
2492
|
+
supportedFormats: ["mp3", "wav", "opus"],
|
|
2493
|
+
},
|
|
2494
|
+
async synthesize() {
|
|
2495
|
+
throw new Error("fish-audio synth failure");
|
|
2496
|
+
},
|
|
2497
|
+
async synthesizeStream() {
|
|
2498
|
+
throw new Error("fish-audio stream failure");
|
|
2499
|
+
},
|
|
2500
|
+
};
|
|
2501
|
+
registerTtsProvider(fishAudioFailing);
|
|
2502
|
+
|
|
2503
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2504
|
+
createMockVoiceTurn(["Hello from synthesized path"]),
|
|
2505
|
+
);
|
|
2506
|
+
const { relay, controller } = setupController();
|
|
2507
|
+
|
|
2508
|
+
await controller.handleCallerUtterance("Hi");
|
|
2509
|
+
|
|
2510
|
+
// No play URL should be emitted when synthesis fails before first chunk.
|
|
2511
|
+
expect(relay.sentPlayUrls.length).toBe(0);
|
|
2512
|
+
|
|
2513
|
+
// Fallback token speech should still reach the caller.
|
|
2514
|
+
const fallbackText = relay.sentTokens.map((t) => t.token).join("");
|
|
2515
|
+
expect(fallbackText).toContain("Hello from synthesized path");
|
|
2516
|
+
|
|
2517
|
+
const lastToken = relay.sentTokens[relay.sentTokens.length - 1];
|
|
2518
|
+
expect(lastToken.last).toBe(true);
|
|
2519
|
+
|
|
2520
|
+
controller.destroy();
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2523
|
+
test("Deepgram selected path resolves useSynthesizedPath to true", () => {
|
|
2524
|
+
const cfg = loadConfig();
|
|
2525
|
+
cfg.services.tts.provider = "deepgram";
|
|
2526
|
+
|
|
2527
|
+
const result = resolveCallTtsProvider();
|
|
2528
|
+
expect(result.provider).not.toBeNull();
|
|
2529
|
+
expect(result.provider!.id).toBe("deepgram");
|
|
2530
|
+
expect(result.useSynthesizedPath).toBe(true);
|
|
2531
|
+
});
|
|
2532
|
+
|
|
2533
|
+
test("Deepgram synthesis failure does NOT fall back to native token TTS", async () => {
|
|
2534
|
+
const cfg = loadConfig();
|
|
2535
|
+
cfg.services.tts.provider = "deepgram";
|
|
2536
|
+
|
|
2537
|
+
_resetTtsProviderRegistry();
|
|
2538
|
+
const elevenlabs: TtsProvider = {
|
|
2539
|
+
id: "elevenlabs",
|
|
2540
|
+
capabilities: { supportsStreaming: false, supportedFormats: ["mp3"] },
|
|
2541
|
+
async synthesize() {
|
|
2542
|
+
return { audio: Buffer.from(""), contentType: "audio/mpeg" };
|
|
2543
|
+
},
|
|
2544
|
+
};
|
|
2545
|
+
registerTtsProvider(elevenlabs);
|
|
2546
|
+
|
|
2547
|
+
const deepgramFailing: TtsProvider = {
|
|
2548
|
+
id: "deepgram",
|
|
2549
|
+
capabilities: {
|
|
2550
|
+
supportsStreaming: false,
|
|
2551
|
+
supportedFormats: ["mp3", "wav", "opus"],
|
|
2552
|
+
},
|
|
2553
|
+
async synthesize() {
|
|
2554
|
+
const err = new Error("Deepgram TTS returned 503: service unavailable");
|
|
2555
|
+
(err as Error & { code?: string }).code = "DEEPGRAM_TTS_HTTP_ERROR";
|
|
2556
|
+
throw err;
|
|
2557
|
+
},
|
|
2558
|
+
};
|
|
2559
|
+
registerTtsProvider(deepgramFailing);
|
|
2560
|
+
|
|
2561
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2562
|
+
createMockVoiceTurn(["Hello from deepgram path"]),
|
|
2563
|
+
);
|
|
2564
|
+
const { relay, controller } = setupController();
|
|
2565
|
+
|
|
2566
|
+
await controller.handleCallerUtterance("Hi");
|
|
2567
|
+
|
|
2568
|
+
// No play URL should be emitted when synthesis fails.
|
|
2569
|
+
expect(relay.sentPlayUrls.length).toBe(0);
|
|
2570
|
+
|
|
2571
|
+
// Deepgram should NOT have fallen back to sending the LLM text as
|
|
2572
|
+
// native token TTS. Instead, the outer error handler should have
|
|
2573
|
+
// produced a recovery message ("Could you repeat that?").
|
|
2574
|
+
const allTokenText = relay.sentTokens.map((t) => t.token).join("");
|
|
2575
|
+
expect(allTokenText).not.toContain("Hello from deepgram path");
|
|
2576
|
+
expect(allTokenText).toContain("technical issue");
|
|
2577
|
+
|
|
2578
|
+
controller.destroy();
|
|
2579
|
+
});
|
|
2580
|
+
|
|
2581
|
+
test("Fish Audio synthesis failure still falls back to native token TTS (unchanged behavior)", async () => {
|
|
2582
|
+
const cfg = loadConfig();
|
|
2583
|
+
cfg.services.tts.provider = "fish-audio";
|
|
2584
|
+
cfg.services.tts.providers["fish-audio"].referenceId = "fish-ref-abc";
|
|
2585
|
+
|
|
2586
|
+
_resetTtsProviderRegistry();
|
|
2587
|
+
const elevenlabs: TtsProvider = {
|
|
2588
|
+
id: "elevenlabs",
|
|
2589
|
+
capabilities: { supportsStreaming: false, supportedFormats: ["mp3"] },
|
|
2590
|
+
async synthesize() {
|
|
2591
|
+
return { audio: Buffer.from(""), contentType: "audio/mpeg" };
|
|
2592
|
+
},
|
|
2593
|
+
};
|
|
2594
|
+
registerTtsProvider(elevenlabs);
|
|
2595
|
+
|
|
2596
|
+
const fishAudioFailing: TtsProvider = {
|
|
2597
|
+
id: "fish-audio",
|
|
2598
|
+
capabilities: {
|
|
2599
|
+
supportsStreaming: true,
|
|
2600
|
+
supportedFormats: ["mp3", "wav", "opus"],
|
|
2601
|
+
},
|
|
2602
|
+
async synthesize() {
|
|
2603
|
+
throw new Error("fish-audio synth failure");
|
|
2604
|
+
},
|
|
2605
|
+
async synthesizeStream() {
|
|
2606
|
+
throw new Error("fish-audio stream failure");
|
|
2607
|
+
},
|
|
2608
|
+
};
|
|
2609
|
+
registerTtsProvider(fishAudioFailing);
|
|
2610
|
+
|
|
2611
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2612
|
+
createMockVoiceTurn(["Hello from fish path"]),
|
|
2613
|
+
);
|
|
2614
|
+
const { relay, controller } = setupController();
|
|
2615
|
+
|
|
2616
|
+
await controller.handleCallerUtterance("Hi");
|
|
2617
|
+
|
|
2618
|
+
// Fish Audio fallback: the LLM text should reach the caller
|
|
2619
|
+
// via native token TTS despite synthesis failure.
|
|
2620
|
+
const allTokenText = relay.sentTokens.map((t) => t.token).join("");
|
|
2621
|
+
expect(allTokenText).toContain("Hello from fish path");
|
|
2622
|
+
|
|
2623
|
+
controller.destroy();
|
|
2624
|
+
});
|
|
2625
|
+
|
|
2626
|
+
// ── TTS provider abstraction: interruption behavior ─────────────────
|
|
2627
|
+
|
|
2628
|
+
test("handleInterrupt: cancels synthesis abort controller for native provider path", async () => {
|
|
2629
|
+
// Using the default native provider (ElevenLabs) — no synthesis abort
|
|
2630
|
+
// controller should be active, but interrupt should still work cleanly.
|
|
2631
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2632
|
+
async (opts: {
|
|
2633
|
+
signal?: AbortSignal;
|
|
2634
|
+
onTextDelta: (t: string) => void;
|
|
2635
|
+
onComplete: () => void;
|
|
2636
|
+
}) => {
|
|
2637
|
+
return new Promise((resolve) => {
|
|
2638
|
+
const timeout = setTimeout(() => {
|
|
2639
|
+
opts.onTextDelta("This should be interrupted");
|
|
2640
|
+
opts.onComplete();
|
|
2641
|
+
resolve({ turnId: "run-1", abort: () => {} });
|
|
2642
|
+
}, 1000);
|
|
2643
|
+
|
|
2644
|
+
opts.signal?.addEventListener(
|
|
2645
|
+
"abort",
|
|
2646
|
+
() => {
|
|
2647
|
+
clearTimeout(timeout);
|
|
2648
|
+
opts.onComplete();
|
|
2649
|
+
resolve({ turnId: "run-1", abort: () => {} });
|
|
2650
|
+
},
|
|
2651
|
+
{ once: true },
|
|
2652
|
+
);
|
|
2653
|
+
});
|
|
2654
|
+
},
|
|
2655
|
+
);
|
|
2656
|
+
|
|
2657
|
+
const { relay, controller } = setupController();
|
|
2658
|
+
const turnPromise = controller.handleCallerUtterance("Start speaking");
|
|
2659
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
2660
|
+
controller.handleInterrupt();
|
|
2661
|
+
await turnPromise;
|
|
2662
|
+
|
|
2663
|
+
// Should have sent an end-of-turn marker
|
|
2664
|
+
const endTurnMarkers = relay.sentTokens.filter(
|
|
2665
|
+
(t) => t.token === "" && t.last === true,
|
|
2666
|
+
);
|
|
2667
|
+
expect(endTurnMarkers.length).toBeGreaterThan(0);
|
|
2668
|
+
expect(controller.getState()).toBe("idle");
|
|
2669
|
+
|
|
2670
|
+
controller.destroy();
|
|
2671
|
+
});
|
|
2672
|
+
|
|
2673
|
+
// ── Shared TTS provider resolution ──────────────────────────────────
|
|
2674
|
+
|
|
2675
|
+
describe("resolveCallTtsProvider (shared helper)", () => {
|
|
2676
|
+
test("returns native path with elevenlabs (non-streaming provider)", () => {
|
|
2677
|
+
// Default config has provider: "elevenlabs" which is registered as
|
|
2678
|
+
// non-streaming in registerTestTtsProviders()
|
|
2679
|
+
const result = resolveCallTtsProvider();
|
|
2680
|
+
expect(result.provider).not.toBeNull();
|
|
2681
|
+
expect(result.provider!.id).toBe("elevenlabs");
|
|
2682
|
+
expect(result.useSynthesizedPath).toBe(false);
|
|
2683
|
+
expect(result.audioFormat).toBe("mp3");
|
|
2684
|
+
});
|
|
2685
|
+
|
|
2686
|
+
test("returns fallback when provider registry is empty", () => {
|
|
2687
|
+
_resetTtsProviderRegistry();
|
|
2688
|
+
const result = resolveCallTtsProvider();
|
|
2689
|
+
expect(result.provider).toBeNull();
|
|
2690
|
+
expect(result.useSynthesizedPath).toBe(false);
|
|
2691
|
+
expect(result.audioFormat).toBe("mp3");
|
|
2692
|
+
});
|
|
2693
|
+
|
|
2694
|
+
test("degrades fish-audio synthesized path when referenceId is missing", () => {
|
|
2695
|
+
const cfg = loadConfig();
|
|
2696
|
+
cfg.services.tts.provider = "fish-audio";
|
|
2697
|
+
cfg.services.tts.providers["fish-audio"].referenceId = "";
|
|
2698
|
+
|
|
2699
|
+
const result = resolveCallTtsProvider();
|
|
2700
|
+
expect(result.provider).toBeNull();
|
|
2701
|
+
expect(result.useSynthesizedPath).toBe(false);
|
|
2702
|
+
expect(result.audioFormat).toBe("mp3");
|
|
2703
|
+
});
|
|
2704
|
+
|
|
2705
|
+
test("call controller LLM path uses shared resolution (native provider sends text tokens)", async () => {
|
|
2706
|
+
// With the default elevenlabs provider (non-streaming), the call
|
|
2707
|
+
// controller should send text tokens directly to the relay (native path).
|
|
2708
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2709
|
+
createMockVoiceTurn(["Hello", " caller"]),
|
|
2710
|
+
);
|
|
2711
|
+
const { relay, controller } = setupController();
|
|
2712
|
+
|
|
2713
|
+
await controller.handleCallerUtterance("Hi");
|
|
2714
|
+
|
|
2715
|
+
// Native path: text tokens should be sent, no play URLs
|
|
2716
|
+
const nonEmptyTokens = relay.sentTokens.filter((t) => t.token.length > 0);
|
|
2717
|
+
expect(nonEmptyTokens.length).toBeGreaterThan(0);
|
|
2718
|
+
expect(relay.sentPlayUrls.length).toBe(0);
|
|
2719
|
+
|
|
2720
|
+
controller.destroy();
|
|
2721
|
+
});
|
|
2722
|
+
|
|
2723
|
+
test("returns synthesized path with deepgram provider", () => {
|
|
2724
|
+
const cfg = loadConfig();
|
|
2725
|
+
cfg.services.tts.provider = "deepgram";
|
|
2726
|
+
|
|
2727
|
+
const result = resolveCallTtsProvider();
|
|
2728
|
+
expect(result.provider).not.toBeNull();
|
|
2729
|
+
expect(result.provider!.id).toBe("deepgram");
|
|
2730
|
+
expect(result.useSynthesizedPath).toBe(true);
|
|
2731
|
+
expect(result.audioFormat).toBe("mp3");
|
|
2732
|
+
});
|
|
2733
|
+
|
|
2734
|
+
test("Deepgram does not apply fish-audio referenceId gate", () => {
|
|
2735
|
+
// Deepgram has no referenceId requirement. Verify the fish-audio
|
|
2736
|
+
// config gate does not apply to deepgram resolution.
|
|
2737
|
+
const cfg = loadConfig();
|
|
2738
|
+
cfg.services.tts.provider = "deepgram";
|
|
2739
|
+
// fish-audio referenceId left empty — should not affect deepgram.
|
|
2740
|
+
cfg.services.tts.providers["fish-audio"].referenceId = "";
|
|
2741
|
+
|
|
2742
|
+
const result = resolveCallTtsProvider();
|
|
2743
|
+
expect(result.provider).not.toBeNull();
|
|
2744
|
+
expect(result.provider!.id).toBe("deepgram");
|
|
2745
|
+
expect(result.useSynthesizedPath).toBe(true);
|
|
2746
|
+
});
|
|
2747
|
+
});
|
|
2748
|
+
|
|
2749
|
+
// ── handleBargeIn ───────────────────────────────────────────────────
|
|
2750
|
+
|
|
2751
|
+
describe("handleBargeIn", () => {
|
|
2752
|
+
test("handleBargeIn returns false and does not abort when controller is idle", () => {
|
|
2753
|
+
const { relay, controller } = setupController();
|
|
2754
|
+
|
|
2755
|
+
// Controller starts idle after construction
|
|
2756
|
+
expect(controller.getState()).toBe("idle");
|
|
2757
|
+
const result = controller.handleBargeIn();
|
|
2758
|
+
|
|
2759
|
+
expect(result).toBe(false);
|
|
2760
|
+
// No end-of-turn token should have been sent (no interruption)
|
|
2761
|
+
const endTokens = relay.sentTokens.filter(
|
|
2762
|
+
(t) => t.last === true && t.token === "",
|
|
2763
|
+
);
|
|
2764
|
+
expect(endTokens.length).toBe(0);
|
|
2765
|
+
|
|
2766
|
+
controller.destroy();
|
|
2767
|
+
});
|
|
2768
|
+
|
|
2769
|
+
test("handleBargeIn returns false when controller is processing", async () => {
|
|
2770
|
+
// Use a slow turn that never completes so we can observe
|
|
2771
|
+
// the processing state.
|
|
2772
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2773
|
+
async (opts: {
|
|
2774
|
+
onTextDelta: (t: string) => void;
|
|
2775
|
+
onComplete: () => void;
|
|
2776
|
+
signal?: AbortSignal;
|
|
2777
|
+
}) => {
|
|
2778
|
+
// Don't call onComplete — keep in processing/speaking
|
|
2779
|
+
return { turnId: "run-slow", abort: () => opts.onComplete() };
|
|
2780
|
+
},
|
|
2781
|
+
);
|
|
2782
|
+
|
|
2783
|
+
const { relay, controller } = setupController();
|
|
2784
|
+
// Kick off a turn (moves to speaking state)
|
|
2785
|
+
const turnPromise = controller.handleCallerUtterance("Hello");
|
|
2786
|
+
|
|
2787
|
+
// Wait for microtasks to settle
|
|
2788
|
+
for (let i = 0; i < 5; i++) await Promise.resolve();
|
|
2789
|
+
|
|
2790
|
+
// The controller transitions to "speaking" once runTurnInner starts.
|
|
2791
|
+
// Before any onTextDelta, a barge-in should be accepted if speaking.
|
|
2792
|
+
// But if no text has been emitted yet, the state is "speaking" per
|
|
2793
|
+
// the implementation (state is set to speaking at the start of
|
|
2794
|
+
// runTurnInner). So handleBargeIn should accept. Let's verify the
|
|
2795
|
+
// state and behavior.
|
|
2796
|
+
const bargeResult = controller.handleBargeIn();
|
|
2797
|
+
|
|
2798
|
+
// Regardless of the specific state, if accepted the transport
|
|
2799
|
+
// should see an interrupt token.
|
|
2800
|
+
if (bargeResult) {
|
|
2801
|
+
const endTokens = relay.sentTokens.filter(
|
|
2802
|
+
(t) => t.last === true && t.token === "",
|
|
2803
|
+
);
|
|
2804
|
+
expect(endTokens.length).toBeGreaterThan(0);
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
// Cleanup: abort the pending turn
|
|
2808
|
+
controller.destroy();
|
|
2809
|
+
await turnPromise.catch(() => {});
|
|
2810
|
+
});
|
|
2811
|
+
|
|
2812
|
+
test("handleBargeIn returns true and interrupts when controller is speaking", async () => {
|
|
2813
|
+
// Create a turn that holds the speaking state long enough to test barge-in
|
|
2814
|
+
let resolveComplete: () => void;
|
|
2815
|
+
const completePromise = new Promise<void>((r) => {
|
|
2816
|
+
resolveComplete = r;
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
mockStartVoiceTurn.mockImplementation(
|
|
2820
|
+
async (opts: {
|
|
2821
|
+
onTextDelta: (t: string) => void;
|
|
2822
|
+
onComplete: () => void;
|
|
2823
|
+
signal?: AbortSignal;
|
|
2824
|
+
}) => {
|
|
2825
|
+
opts.onTextDelta("Hello");
|
|
2826
|
+
opts.onTextDelta(" there");
|
|
2827
|
+
// Don't complete yet — wait for external signal
|
|
2828
|
+
opts.signal?.addEventListener("abort", () => {
|
|
2829
|
+
resolveComplete!();
|
|
2830
|
+
});
|
|
2831
|
+
await completePromise;
|
|
2832
|
+
opts.onComplete();
|
|
2833
|
+
return { turnId: "run-barge", abort: () => resolveComplete!() };
|
|
2834
|
+
},
|
|
2835
|
+
);
|
|
2836
|
+
|
|
2837
|
+
const { controller } = setupController();
|
|
2838
|
+
const turnPromise = controller.handleCallerUtterance("Hi");
|
|
2839
|
+
|
|
2840
|
+
// Let microtasks settle so onTextDelta runs
|
|
2841
|
+
for (let i = 0; i < 10; i++) await Promise.resolve();
|
|
2842
|
+
|
|
2843
|
+
expect(controller.getState()).toBe("speaking");
|
|
2844
|
+
|
|
2845
|
+
const result = controller.handleBargeIn();
|
|
2846
|
+
expect(result).toBe(true);
|
|
2847
|
+
|
|
2848
|
+
// After barge-in, controller should be idle
|
|
2849
|
+
expect(controller.getState()).toBe("idle");
|
|
2850
|
+
|
|
2851
|
+
controller.destroy();
|
|
2852
|
+
await turnPromise.catch(() => {});
|
|
2853
|
+
});
|
|
2854
|
+
});
|
|
2294
2855
|
});
|