@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.
Files changed (667) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +5 -13
  4. package/docs/backup-troubleshooting.md +52 -0
  5. package/docs/browser-use-architecture-phase2.md +174 -0
  6. package/docs/stt-provider-onboarding.md +120 -0
  7. package/knip.json +12 -2
  8. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  9. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  10. package/openapi.yaml +982 -72
  11. package/package.json +4 -6
  12. package/scripts/generate-openapi.ts +0 -1
  13. package/scripts/test.sh +73 -18
  14. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  15. package/src/__tests__/agent-loop.test.ts +123 -0
  16. package/src/__tests__/anthropic-provider.test.ts +263 -10
  17. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  18. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  19. package/src/__tests__/browser-fill-credential.test.ts +11 -0
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  22. package/src/__tests__/btw-routes.test.ts +7 -0
  23. package/src/__tests__/call-controller.test.ts +581 -20
  24. package/src/__tests__/catalog-files.test.ts +138 -0
  25. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  26. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  27. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  28. package/src/__tests__/checker.test.ts +157 -10
  29. package/src/__tests__/clawhub-files.test.ts +347 -0
  30. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  31. package/src/__tests__/config-analysis.test.ts +100 -0
  32. package/src/__tests__/config-schema.test.ts +1013 -66
  33. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  34. package/src/__tests__/config-watcher.test.ts +43 -8
  35. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  36. package/src/__tests__/contacts-write.test.ts +197 -0
  37. package/src/__tests__/context-window-manager.test.ts +88 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +98 -2
  41. package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
  42. package/src/__tests__/conversation-error.test.ts +70 -0
  43. package/src/__tests__/conversation-history-web-search.test.ts +11 -4
  44. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  45. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  46. package/src/__tests__/conversation-list-source.test.ts +145 -0
  47. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  49. package/src/__tests__/conversation-queue.test.ts +901 -60
  50. package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
  52. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  53. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  59. package/src/__tests__/credential-health-service.test.ts +352 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  61. package/src/__tests__/credential-vault-unit.test.ts +379 -3
  62. package/src/__tests__/credentials-cli.test.ts +40 -16
  63. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  65. package/src/__tests__/device-id.test.ts +112 -0
  66. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  67. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  68. package/src/__tests__/email-html-renderer.test.ts +71 -0
  69. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  70. package/src/__tests__/emit-event-signal.test.ts +71 -0
  71. package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
  72. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  73. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  74. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  75. package/src/__tests__/gemini-provider.test.ts +64 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  77. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  78. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  79. package/src/__tests__/gmail-preferences.test.ts +117 -0
  80. package/src/__tests__/headless-browser-interactions.test.ts +43 -0
  81. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  82. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  83. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  84. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  85. package/src/__tests__/heartbeat-service.test.ts +70 -17
  86. package/src/__tests__/home-state-routes.test.ts +162 -0
  87. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  88. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  89. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  90. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  91. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  92. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  93. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  94. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  95. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  96. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  97. package/src/__tests__/llm-usage-store.test.ts +363 -0
  98. package/src/__tests__/media-stream-output.test.ts +555 -0
  99. package/src/__tests__/media-stream-parser.test.ts +374 -0
  100. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  101. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  102. package/src/__tests__/media-turn-detector.test.ts +440 -0
  103. package/src/__tests__/message-queue.test.ts +125 -0
  104. package/src/__tests__/migration-export-http.test.ts +6 -6
  105. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  106. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  107. package/src/__tests__/migration-validate-http.test.ts +3 -3
  108. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  109. package/src/__tests__/model-intents.test.ts +2 -2
  110. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  111. package/src/__tests__/oauth-cli.test.ts +2 -0
  112. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  115. package/src/__tests__/oauth-store.test.ts +85 -0
  116. package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
  117. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  118. package/src/__tests__/openai-provider.test.ts +176 -0
  119. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  120. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  121. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  122. package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
  123. package/src/__tests__/persona-resolver.test.ts +251 -0
  124. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  125. package/src/__tests__/platform.test.ts +92 -1
  126. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  127. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  128. package/src/__tests__/pricing.test.ts +174 -0
  129. package/src/__tests__/qdrant-manager.test.ts +29 -8
  130. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  131. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  132. package/src/__tests__/relay-server.test.ts +423 -5
  133. package/src/__tests__/search-skills-unified.test.ts +118 -0
  134. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  135. package/src/__tests__/secure-keys.test.ts +107 -0
  136. package/src/__tests__/send-endpoint-busy.test.ts +5 -1
  137. package/src/__tests__/sequence-store.test.ts +1 -1
  138. package/src/__tests__/server-history-render.test.ts +49 -0
  139. package/src/__tests__/settings-routes.test.ts +201 -0
  140. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  141. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  142. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  143. package/src/__tests__/skills.test.ts +5 -2
  144. package/src/__tests__/skillssh-files.test.ts +446 -0
  145. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  146. package/src/__tests__/slack-channel-config.test.ts +564 -1
  147. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  148. package/src/__tests__/stt-stream-session.test.ts +535 -0
  149. package/src/__tests__/system-prompt.test.ts +112 -26
  150. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  151. package/src/__tests__/terminal-tools.test.ts +18 -7
  152. package/src/__tests__/test-preload.ts +18 -0
  153. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  155. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  156. package/src/__tests__/tool-executor.test.ts +33 -24
  157. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  158. package/src/__tests__/trust-store.test.ts +7 -1
  159. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  160. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  161. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  162. package/src/__tests__/twilio-routes.test.ts +376 -0
  163. package/src/__tests__/unicode.test.ts +293 -0
  164. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  165. package/src/__tests__/update-bulletin.test.ts +206 -5
  166. package/src/__tests__/usage-routes.test.ts +25 -4
  167. package/src/__tests__/user-reference.test.ts +46 -61
  168. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  169. package/src/__tests__/voice-config-update.test.ts +403 -0
  170. package/src/__tests__/voice-quality.test.ts +434 -19
  171. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  172. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  173. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  174. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  175. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  176. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  177. package/src/__tests__/workspace-policy.test.ts +2 -0
  178. package/src/agent/image-optimize.ts +24 -12
  179. package/src/agent/loop.ts +43 -3
  180. package/src/backup/__tests__/backup-key.test.ts +152 -0
  181. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  182. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  183. package/src/backup/__tests__/local-writer.test.ts +218 -0
  184. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  185. package/src/backup/__tests__/paths.test.ts +300 -0
  186. package/src/backup/__tests__/restore.test.ts +498 -0
  187. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  188. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  189. package/src/backup/backup-key.ts +137 -0
  190. package/src/backup/backup-worker.ts +459 -0
  191. package/src/backup/list-snapshots.ts +147 -0
  192. package/src/backup/local-writer.ts +133 -0
  193. package/src/backup/offsite-writer.ts +222 -0
  194. package/src/backup/paths.ts +226 -0
  195. package/src/backup/restore.ts +322 -0
  196. package/src/backup/snapshot-lock.ts +431 -0
  197. package/src/backup/stream-crypt.ts +263 -0
  198. package/src/bundler/package-resolver.ts +4 -0
  199. package/src/calls/audio-store.ts +11 -5
  200. package/src/calls/call-controller.ts +226 -71
  201. package/src/calls/call-domain.ts +9 -0
  202. package/src/calls/call-speech-output.ts +190 -0
  203. package/src/calls/call-transport.ts +77 -0
  204. package/src/calls/media-stream-audio-transcode.ts +173 -0
  205. package/src/calls/media-stream-output.ts +660 -0
  206. package/src/calls/media-stream-parser.ts +300 -0
  207. package/src/calls/media-stream-protocol.ts +166 -0
  208. package/src/calls/media-stream-server.ts +592 -0
  209. package/src/calls/media-stream-stt-session.ts +460 -0
  210. package/src/calls/media-turn-detector.ts +230 -0
  211. package/src/calls/relay-server.ts +90 -75
  212. package/src/calls/resolve-call-tts-provider.ts +136 -0
  213. package/src/calls/telephony-stt-routing.ts +145 -0
  214. package/src/calls/tts-call-strategy.ts +161 -0
  215. package/src/calls/tts-text-sanitizer.ts +32 -16
  216. package/src/calls/twilio-routes.ts +281 -17
  217. package/src/calls/voice-quality.ts +78 -35
  218. package/src/calls/voice-session-bridge.ts +8 -1
  219. package/src/channels/types.ts +16 -0
  220. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  221. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  222. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  223. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  224. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  225. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  226. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  227. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  228. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  229. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  230. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  231. package/src/cli/commands/backup.ts +993 -0
  232. package/src/cli/commands/conversations.ts +77 -0
  233. package/src/cli/commands/credentials.ts +0 -1
  234. package/src/cli/commands/domain.ts +210 -0
  235. package/src/cli/commands/email.ts +255 -3
  236. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  237. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  238. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  239. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  240. package/src/cli/commands/oauth/mode.ts +12 -3
  241. package/src/cli/commands/oauth/providers.ts +15 -0
  242. package/src/cli/commands/oauth/shared.ts +2 -1
  243. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
  244. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  245. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  246. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  247. package/src/cli/program.ts +30 -4
  248. package/src/config/__tests__/backup-schema.test.ts +134 -0
  249. package/src/config/assistant-feature-flags.ts +61 -62
  250. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  251. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  252. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  253. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  254. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  255. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  256. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  258. package/src/config/bundled-skills/gmail/SKILL.md +53 -7
  259. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  260. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  261. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  262. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  263. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  264. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  265. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  266. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  267. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  268. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  269. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  270. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  271. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  272. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  273. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  274. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  275. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  276. package/src/config/bundled-skills/outlook/SKILL.md +2 -2
  277. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  278. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  279. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  280. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  281. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  282. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  283. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  284. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  285. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  286. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  287. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  288. package/src/config/bundled-tool-registry.ts +8 -0
  289. package/src/config/env-registry.ts +24 -0
  290. package/src/config/env.ts +34 -10
  291. package/src/config/feature-flag-registry.json +46 -14
  292. package/src/config/loader.ts +26 -12
  293. package/src/config/schema.ts +35 -10
  294. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  295. package/src/config/schemas/analysis.ts +51 -0
  296. package/src/config/schemas/backup.ts +72 -0
  297. package/src/config/schemas/calls.ts +1 -26
  298. package/src/config/schemas/elevenlabs.ts +0 -59
  299. package/src/config/schemas/filing.ts +47 -7
  300. package/src/config/schemas/heartbeat.ts +27 -5
  301. package/src/config/schemas/host-browser.ts +47 -1
  302. package/src/config/schemas/inference.ts +1 -1
  303. package/src/config/schemas/memory-lifecycle.ts +14 -2
  304. package/src/config/schemas/services.ts +44 -0
  305. package/src/config/schemas/stt.ts +59 -0
  306. package/src/config/schemas/tts.ts +230 -0
  307. package/src/config/schemas/updates.ts +14 -0
  308. package/src/config/skills.ts +4 -0
  309. package/src/config/types.ts +4 -0
  310. package/src/contacts/contact-store.ts +56 -11
  311. package/src/contacts/contacts-write.ts +38 -1
  312. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  313. package/src/context/tool-result-truncation.ts +2 -1
  314. package/src/context/window-manager.ts +45 -12
  315. package/src/credential-execution/executable-discovery.ts +12 -2
  316. package/src/credential-execution/process-manager.ts +33 -2
  317. package/src/credential-health/credential-health-service.ts +366 -0
  318. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  319. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  320. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  321. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  322. package/src/daemon/config-watcher.ts +99 -5
  323. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  324. package/src/daemon/conversation-agent-loop.ts +101 -24
  325. package/src/daemon/conversation-error.ts +11 -0
  326. package/src/daemon/conversation-history.ts +40 -6
  327. package/src/daemon/conversation-launch.ts +220 -0
  328. package/src/daemon/conversation-lifecycle.ts +59 -9
  329. package/src/daemon/conversation-messaging.ts +37 -3
  330. package/src/daemon/conversation-notifiers.ts +5 -0
  331. package/src/daemon/conversation-process.ts +581 -19
  332. package/src/daemon/conversation-queue-manager.ts +24 -0
  333. package/src/daemon/conversation-runtime-assembly.ts +11 -1
  334. package/src/daemon/conversation-slash.ts +36 -0
  335. package/src/daemon/conversation-surfaces.ts +94 -4
  336. package/src/daemon/conversation-tool-setup.ts +25 -0
  337. package/src/daemon/conversation-usage.ts +7 -4
  338. package/src/daemon/conversation.ts +86 -28
  339. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  340. package/src/daemon/handlers/conversations.ts +4 -1
  341. package/src/daemon/handlers/shared.ts +22 -0
  342. package/src/daemon/handlers/skills.ts +321 -77
  343. package/src/daemon/host-browser-proxy.ts +2 -1
  344. package/src/daemon/lifecycle.ts +122 -25
  345. package/src/daemon/message-protocol.ts +6 -0
  346. package/src/daemon/message-types/conversations.ts +34 -1
  347. package/src/daemon/message-types/home.ts +40 -0
  348. package/src/daemon/message-types/meet.ts +143 -0
  349. package/src/daemon/message-types/messages.ts +14 -0
  350. package/src/daemon/message-types/schedules.ts +34 -2
  351. package/src/daemon/message-types/skills.ts +16 -0
  352. package/src/daemon/message-types/surfaces.ts +2 -0
  353. package/src/daemon/server.ts +347 -2
  354. package/src/daemon/shutdown-handlers.ts +32 -4
  355. package/src/daemon/shutdown-registry.ts +40 -0
  356. package/src/daemon/tool-side-effects.ts +9 -0
  357. package/src/email/html-renderer.ts +76 -0
  358. package/src/heartbeat/heartbeat-service.ts +93 -7
  359. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  360. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  361. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  362. package/src/home/__tests__/feed-types.test.ts +275 -0
  363. package/src/home/__tests__/feed-writer.test.ts +688 -0
  364. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  365. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  366. package/src/home/__tests__/progress-formula.test.ts +213 -0
  367. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  368. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  369. package/src/home/assistant-feed-authoring.ts +124 -0
  370. package/src/home/emit-feed-event.ts +158 -0
  371. package/src/home/feed-scheduler.ts +247 -0
  372. package/src/home/feed-types.ts +181 -0
  373. package/src/home/feed-writer.ts +469 -0
  374. package/src/home/platform-gmail-digest.ts +163 -0
  375. package/src/home/progress-formula.ts +86 -0
  376. package/src/home/relationship-state-writer.ts +824 -0
  377. package/src/home/relationship-state.ts +143 -0
  378. package/src/home/rollup-producer.ts +384 -0
  379. package/src/hooks/runner.ts +7 -0
  380. package/src/inbound/platform-callback-registration.ts +12 -3
  381. package/src/inbound/public-ingress-urls.ts +12 -0
  382. package/src/instrument.ts +1 -1
  383. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  384. package/src/ipc/cli-client.ts +151 -0
  385. package/src/ipc/cli-server.ts +234 -0
  386. package/src/ipc/gateway-client.ts +180 -0
  387. package/src/ipc/routes/index.ts +5 -0
  388. package/src/ipc/routes/wake-conversation.ts +19 -0
  389. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  390. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  391. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  392. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  393. package/src/memory/app-store.ts +1 -1
  394. package/src/memory/attachments-store.ts +70 -0
  395. package/src/memory/auto-analysis-enqueue.ts +127 -0
  396. package/src/memory/auto-analysis-guard.ts +27 -0
  397. package/src/memory/cleanup-schedule-state.ts +37 -0
  398. package/src/memory/conversation-analyze-job.ts +73 -0
  399. package/src/memory/conversation-crud.ts +99 -0
  400. package/src/memory/conversation-disk-view.ts +7 -0
  401. package/src/memory/conversation-group-migration.ts +34 -2
  402. package/src/memory/conversation-queries.ts +6 -5
  403. package/src/memory/db-init.ts +6 -0
  404. package/src/memory/db-maintenance.ts +108 -0
  405. package/src/memory/db.ts +1 -0
  406. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  407. package/src/memory/graph/extraction.test.ts +23 -0
  408. package/src/memory/graph/extraction.ts +8 -0
  409. package/src/memory/graph/retriever.ts +27 -18
  410. package/src/memory/graph/scoring.test.ts +186 -0
  411. package/src/memory/graph/scoring.ts +31 -1
  412. package/src/memory/graph/tools.ts +1 -1
  413. package/src/memory/group-crud.ts +6 -1
  414. package/src/memory/indexer.ts +95 -16
  415. package/src/memory/job-handlers/cleanup.ts +11 -8
  416. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  417. package/src/memory/jobs-store.ts +64 -4
  418. package/src/memory/jobs-worker.ts +22 -9
  419. package/src/memory/llm-usage-store.ts +92 -56
  420. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  421. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  422. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  423. package/src/memory/migrations/index.ts +6 -0
  424. package/src/memory/migrations/registry.ts +8 -0
  425. package/src/memory/qdrant-manager.ts +43 -16
  426. package/src/memory/schema/conversations.ts +2 -0
  427. package/src/memory/schema/oauth.ts +3 -0
  428. package/src/memory/usage-buckets.ts +396 -0
  429. package/src/messaging/providers/gmail/client.ts +57 -6
  430. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  431. package/src/messaging/providers/slack/adapter.ts +143 -38
  432. package/src/messaging/providers/slack/client.ts +16 -0
  433. package/src/messaging/providers/slack/types.ts +4 -0
  434. package/src/notifications/decision-engine.ts +3 -3
  435. package/src/notifications/signal.ts +5 -0
  436. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  437. package/src/oauth/byo-connection.test.ts +18 -1
  438. package/src/oauth/byo-connection.ts +3 -1
  439. package/src/oauth/connect-orchestrator.ts +2 -0
  440. package/src/oauth/connection-resolver.ts +6 -2
  441. package/src/oauth/connection.ts +2 -0
  442. package/src/oauth/oauth-store.ts +9 -0
  443. package/src/oauth/platform-connection.test.ts +98 -0
  444. package/src/oauth/platform-connection.ts +52 -31
  445. package/src/oauth/seed-providers.ts +7 -0
  446. package/src/permissions/checker.ts +16 -6
  447. package/src/permissions/defaults.ts +49 -1
  448. package/src/permissions/trust-store.ts +3 -3
  449. package/src/permissions/workspace-policy.ts +3 -0
  450. package/src/platform/client.test.ts +10 -0
  451. package/src/platform/sync-identity.ts +129 -0
  452. package/src/prompts/persona-resolver.ts +126 -2
  453. package/src/prompts/system-prompt.ts +59 -18
  454. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  455. package/src/prompts/templates/SOUL.md +3 -1
  456. package/src/prompts/templates/UPDATES.md +12 -0
  457. package/src/prompts/templates/channels/slack.md +20 -0
  458. package/src/prompts/update-bulletin-format.ts +26 -9
  459. package/src/prompts/update-bulletin.ts +34 -23
  460. package/src/prompts/user-reference.ts +20 -17
  461. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  462. package/src/providers/anthropic/client.ts +157 -61
  463. package/src/providers/fireworks/client.ts +2 -2
  464. package/src/providers/gemini/client.ts +9 -1
  465. package/src/providers/model-catalog.ts +6 -0
  466. package/src/providers/model-intents.ts +4 -4
  467. package/src/providers/ollama/client.ts +2 -2
  468. package/src/providers/openai/chat-completions-provider.ts +474 -0
  469. package/src/providers/openai/client.ts +25 -440
  470. package/src/providers/openai/responses-provider.ts +502 -0
  471. package/src/providers/openrouter/client.ts +101 -4
  472. package/src/providers/provider-secret-catalog.ts +139 -0
  473. package/src/providers/registry.ts +2 -2
  474. package/src/providers/retry.ts +14 -3
  475. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  476. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  477. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  478. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  479. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  480. package/src/providers/speech-to-text/deepgram.ts +115 -0
  481. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  482. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  483. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  484. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  485. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  486. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  487. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  488. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  489. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  490. package/src/providers/speech-to-text/resolve.ts +386 -6
  491. package/src/providers/types.ts +9 -0
  492. package/src/runtime/AGENTS.md +43 -1
  493. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  494. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  495. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  496. package/src/runtime/agent-wake.ts +512 -0
  497. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  498. package/src/runtime/auth/route-policy.ts +30 -5
  499. package/src/runtime/auth/token-service.ts +56 -1
  500. package/src/runtime/btw-sidechain.ts +2 -0
  501. package/src/runtime/capability-tokens.ts +10 -10
  502. package/src/runtime/channel-invite-transport.ts +1 -1
  503. package/src/runtime/channel-invite-transports/email.ts +14 -6
  504. package/src/runtime/channel-readiness-service.ts +12 -22
  505. package/src/runtime/chrome-extension-registry.ts +38 -2
  506. package/src/runtime/http-server.ts +395 -10
  507. package/src/runtime/http-types.ts +6 -2
  508. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  509. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  510. package/src/runtime/migrations/migration-transport.ts +1 -0
  511. package/src/runtime/migrations/migration-wizard.ts +1 -0
  512. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  513. package/src/runtime/migrations/vbundle-importer.ts +34 -0
  514. package/src/runtime/pending-interactions.ts +0 -11
  515. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  516. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  517. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  518. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  519. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  520. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  521. package/src/runtime/routes/app-management-routes.ts +12 -18
  522. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  523. package/src/runtime/routes/attachment-routes.ts +216 -17
  524. package/src/runtime/routes/backup-routes.ts +519 -0
  525. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  526. package/src/runtime/routes/btw-routes.ts +8 -6
  527. package/src/runtime/routes/contact-routes.test.ts +298 -0
  528. package/src/runtime/routes/contact-routes.ts +132 -5
  529. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  530. package/src/runtime/routes/conversation-management-routes.ts +115 -0
  531. package/src/runtime/routes/conversation-routes.ts +367 -146
  532. package/src/runtime/routes/filing-routes.ts +93 -0
  533. package/src/runtime/routes/home-feed-routes.ts +334 -0
  534. package/src/runtime/routes/home-state-routes.ts +138 -0
  535. package/src/runtime/routes/host-browser-routes.ts +3 -14
  536. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  537. package/src/runtime/routes/identity-routes.ts +3 -17
  538. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  539. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  540. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  541. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  542. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  543. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  544. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  545. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  546. package/src/runtime/routes/migration-routes.ts +40 -5
  547. package/src/runtime/routes/settings-routes.ts +22 -5
  548. package/src/runtime/routes/skills-routes.ts +76 -7
  549. package/src/runtime/routes/stt-routes.ts +233 -0
  550. package/src/runtime/routes/surface-action-routes.ts +41 -2
  551. package/src/runtime/routes/tts-routes.ts +108 -24
  552. package/src/runtime/routes/usage-routes.ts +30 -2
  553. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  554. package/src/runtime/routes/user-routes.ts +13 -1
  555. package/src/runtime/routes/work-items-routes.ts +8 -1
  556. package/src/runtime/runtime-mode.ts +33 -0
  557. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  558. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  559. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  560. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  561. package/src/runtime/services/analyze-conversation.ts +344 -0
  562. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  563. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  564. package/src/runtime/skill-route-registry.ts +49 -0
  565. package/src/runtime/slack-block-formatting.ts +437 -10
  566. package/src/schedule/scheduler.ts +50 -0
  567. package/src/security/oauth2.ts +26 -4
  568. package/src/security/secure-keys.ts +25 -2
  569. package/src/security/token-manager.ts +8 -0
  570. package/src/sequence/engine.ts +23 -0
  571. package/src/sequence/types.ts +1 -1
  572. package/src/skills/catalog-files.ts +64 -2
  573. package/src/skills/category-inference.ts +122 -0
  574. package/src/skills/clawhub-files.ts +213 -0
  575. package/src/skills/clawhub.ts +84 -23
  576. package/src/skills/skill-file-provider.ts +40 -0
  577. package/src/skills/skillssh-files.ts +395 -0
  578. package/src/skills/skillssh-registry.ts +4 -4
  579. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  580. package/src/stt/__tests__/types.test.ts +89 -0
  581. package/src/stt/daemon-batch-transcriber.ts +195 -0
  582. package/src/stt/stt-stream-session.ts +499 -0
  583. package/src/stt/types.ts +330 -0
  584. package/src/stt/wav-encoder.test.ts +373 -0
  585. package/src/stt/wav-encoder.ts +175 -0
  586. package/src/subagent/manager.ts +38 -14
  587. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  588. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  589. package/src/tools/browser/browser-execution.ts +1163 -23
  590. package/src/tools/browser/browser-manager.ts +45 -0
  591. package/src/tools/browser/browser-mode-constants.ts +12 -0
  592. package/src/tools/browser/browser-mode.ts +92 -0
  593. package/src/tools/browser/browser-status-constants.ts +33 -0
  594. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  595. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  596. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  597. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  598. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
  599. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  600. package/src/tools/browser/cdp-client/errors.ts +15 -0
  601. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  602. package/src/tools/browser/cdp-client/factory.ts +797 -87
  603. package/src/tools/browser/cdp-client/index.ts +16 -2
  604. package/src/tools/browser/cdp-client/types.ts +68 -0
  605. package/src/tools/credentials/vault.ts +35 -6
  606. package/src/tools/network/web-fetch.ts +5 -2
  607. package/src/tools/network/web-search.ts +5 -2
  608. package/src/tools/shared/shell-output.ts +3 -1
  609. package/src/tools/side-effects.ts +2 -0
  610. package/src/tools/skills/sandbox-runner.ts +3 -2
  611. package/src/tools/terminal/safe-env.ts +10 -2
  612. package/src/tools/terminal/shell.ts +15 -4
  613. package/src/tools/tool-manifest.ts +21 -0
  614. package/src/tools/types.ts +17 -0
  615. package/src/tools/ui-surface/definitions.ts +6 -1
  616. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  617. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  618. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  619. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  620. package/src/tts/provider-catalog.ts +201 -0
  621. package/src/tts/provider-registry.ts +73 -0
  622. package/src/tts/providers/deepgram-provider.ts +219 -0
  623. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  624. package/src/tts/providers/fish-audio-provider.ts +183 -0
  625. package/src/tts/providers/index.ts +42 -0
  626. package/src/tts/providers/register-builtins.ts +130 -0
  627. package/src/tts/synthesize-text.ts +110 -0
  628. package/src/tts/tts-config-resolver.ts +78 -0
  629. package/src/tts/types.ts +153 -0
  630. package/src/types/onboarding-context.ts +7 -0
  631. package/src/util/abort-reasons.ts +58 -0
  632. package/src/util/device-id.ts +32 -16
  633. package/src/util/errors.ts +9 -1
  634. package/src/util/platform.ts +54 -10
  635. package/src/util/pricing.ts +66 -3
  636. package/src/util/spawn.ts +1 -1
  637. package/src/util/truncate.ts +4 -2
  638. package/src/util/unicode.ts +201 -0
  639. package/src/version.ts +19 -24
  640. package/src/watcher/engine.ts +23 -0
  641. package/src/watcher/watcher-store.ts +31 -0
  642. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  643. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  644. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  645. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  646. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  647. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  648. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  649. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  650. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  651. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  652. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  653. package/src/workspace/migrations/registry.ts +16 -0
  654. package/src/workspace/top-level-renderer.ts +13 -1
  655. package/src/workspace/turn-commit.ts +31 -0
  656. package/src/__tests__/email-cli.test.ts +0 -297
  657. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  658. package/src/cli/commands/browser-relay.ts +0 -466
  659. package/src/email/guardrails.ts +0 -221
  660. package/src/email/provider.ts +0 -117
  661. package/src/email/providers/agentmail.ts +0 -361
  662. package/src/email/providers/index.ts +0 -65
  663. package/src/email/service.ts +0 -384
  664. package/src/email/types.ts +0 -126
  665. package/src/prompts/templates/USER.md +0 -13
  666. package/src/providers/speech-to-text/types.ts +0 -17
  667. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
@@ -43,6 +43,7 @@ import {
43
43
  } from "./call-constants.js";
44
44
  import { CallController } from "./call-controller.js";
45
45
  import { addPointerMessage, formatDuration } from "./call-pointer-messages.js";
46
+ import { speakSystemPrompt } from "./call-speech-output.js";
46
47
  import { fireCallTranscriptNotifier } from "./call-state.js";
47
48
  import { isTerminalState } from "./call-state-machine.js";
48
49
  import {
@@ -50,6 +51,7 @@ import {
50
51
  recordCallEvent,
51
52
  updateCallSession,
52
53
  } from "./call-store.js";
54
+ import { ConversationRelayTransport } from "./call-transport.js";
53
55
  import { finalizeCall } from "./finalize-call.js";
54
56
  import {
55
57
  classifyWaitUtterance,
@@ -570,9 +572,10 @@ export class RelayConnection {
570
572
  resolved.actorTrust,
571
573
  resolved.otherPartyNumber,
572
574
  );
575
+ const transport = new ConversationRelayTransport(this);
573
576
  const controller = new CallController(
574
577
  this.callSessionId,
575
- this,
578
+ transport,
576
579
  session?.task ?? null,
577
580
  {
578
581
  broadcast: globalBroadcast,
@@ -594,7 +597,7 @@ export class RelayConnection {
594
597
  await this.startVerification(session, outcome.verificationConfig);
595
598
  return;
596
599
  case "deny":
597
- this.denyInboundCall(msg.from, resolved, outcome);
600
+ await this.denyInboundCall(msg.from, resolved, outcome);
598
601
  return;
599
602
  case "invite_redemption":
600
603
  this.startInviteRedemption(
@@ -690,24 +693,24 @@ export class RelayConnection {
690
693
  }
691
694
 
692
695
  /** Deny an inbound call with a TTS message and schedule disconnect. */
693
- private denyInboundCall(
696
+ private async denyInboundCall(
694
697
  from: string,
695
698
  resolved: import("./relay-setup-router.js").SetupResolved,
696
699
  outcome: { message: string; logReason: string },
697
- ): void {
700
+ ): Promise<void> {
698
701
  recordCallEvent(this.callSessionId, "inbound_acl_denied", {
699
702
  from,
700
703
  trustClass: resolved.actorTrust.trustClass,
701
704
  channelId: resolved.actorTrust.memberRecord?.channel.id,
702
705
  memberPolicy: resolved.actorTrust.memberRecord?.channel.policy,
703
706
  });
704
- this.sendTextToken(outcome.message, true);
705
707
  this.connectionState = "disconnecting";
706
708
  updateCallSession(this.callSessionId, {
707
709
  status: "failed",
708
710
  endedAt: Date.now(),
709
711
  lastError: outcome.logReason,
710
712
  });
713
+ await speakSystemPrompt(this, outcome.message);
711
714
  setTimeout(() => {
712
715
  this.endSession(outcome.logReason);
713
716
  }, getTtsPlaybackDelayMs());
@@ -740,9 +743,9 @@ export class RelayConnection {
740
743
 
741
744
  // Send a TTS prompt with the code spoken digit by digit
742
745
  const spokenCode = code.split("").join(". ");
743
- this.sendTextToken(
746
+ void speakSystemPrompt(
747
+ this,
744
748
  `Please enter the verification code: ${spokenCode}.`,
745
- true,
746
749
  );
747
750
 
748
751
  // Post the verification code to the initiating conversation so the
@@ -861,7 +864,7 @@ export class RelayConnection {
861
864
  handoffText = `Great! ${guardianLabel} said I can speak with you. How can I help?`;
862
865
  }
863
866
 
864
- this.sendTextToken(handoffText, true);
867
+ void speakSystemPrompt(this, handoffText);
865
868
 
866
869
  recordCallEvent(this.callSessionId, "assistant_spoke", {
867
870
  text: handoffText,
@@ -904,9 +907,9 @@ export class RelayConnection {
904
907
  maxAttempts: this.verificationMaxAttempts,
905
908
  });
906
909
 
907
- this.sendTextToken(
910
+ void speakSystemPrompt(
911
+ this,
908
912
  "Welcome. Please enter your six-digit verification code using your keypad, or speak the digits now.",
909
- true,
910
913
  );
911
914
 
912
915
  log.info(
@@ -946,7 +949,7 @@ export class RelayConnection {
946
949
  GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_CALL_INTRO,
947
950
  { codeDigits: this.verificationCodeLength },
948
951
  );
949
- this.sendTextToken(introText, true);
952
+ void speakSystemPrompt(this, introText);
950
953
 
951
954
  log.info(
952
955
  {
@@ -963,7 +966,9 @@ export class RelayConnection {
963
966
  * Delegates to the extracted attemptVerificationCode() and
964
967
  * interprets the structured result to drive side-effects.
965
968
  */
966
- private handleVerificationCodeResult(enteredCode: string): void {
969
+ private async handleVerificationCodeResult(
970
+ enteredCode: string,
971
+ ): Promise<void> {
967
972
  if (!this.verificationAssistantId || !this.verificationFromNumber) {
968
973
  return;
969
974
  }
@@ -1102,8 +1107,6 @@ export class RelayConnection {
1102
1107
  "Guardian voice verification failed — max attempts reached",
1103
1108
  );
1104
1109
 
1105
- this.sendTextToken(result.ttsMessage, true);
1106
-
1107
1110
  updateCallSession(this.callSessionId, {
1108
1111
  status: "failed",
1109
1112
  endedAt: Date.now(),
@@ -1135,6 +1138,7 @@ export class RelayConnection {
1135
1138
  }
1136
1139
  }
1137
1140
 
1141
+ await speakSystemPrompt(this, result.ttsMessage);
1138
1142
  setTimeout(() => {
1139
1143
  this.endSession("Verification failed — challenge rejected");
1140
1144
  }, getTtsPlaybackDelayMs());
@@ -1151,7 +1155,7 @@ export class RelayConnection {
1151
1155
  },
1152
1156
  "Guardian voice verification attempt failed — retrying",
1153
1157
  );
1154
- this.sendTextToken(result.ttsMessage, true);
1158
+ void speakSystemPrompt(this, result.ttsMessage);
1155
1159
  }
1156
1160
  }
1157
1161
 
@@ -1196,7 +1200,7 @@ export class RelayConnection {
1196
1200
  } else {
1197
1201
  promptText = `Welcome ${displayFriend}. Please enter the 6-digit code that ${displayGuardian} provided you to verify your identity.`;
1198
1202
  }
1199
- this.sendTextToken(promptText, true);
1203
+ void speakSystemPrompt(this, promptText);
1200
1204
 
1201
1205
  log.info(
1202
1206
  { callSessionId: this.callSessionId, assistantId },
@@ -1221,7 +1225,7 @@ export class RelayConnection {
1221
1225
  ? `Hi, this is ${assistantName}, ${guardianLabel}'s assistant. Sorry, I don't recognize this number. I'll let ${guardianLabel} know you called and see if I have permission to speak with you. Can I get your name?`
1222
1226
  : `Hi, this is ${guardianLabel}'s assistant. Sorry, I don't recognize this number. I'll let ${guardianLabel} know you called and see if I have permission to speak with you. Can I get your name?`;
1223
1227
 
1224
- this.sendTextToken(greeting, true);
1228
+ void speakSystemPrompt(this, greeting);
1225
1229
 
1226
1230
  // Start a timeout so silent callers don't keep the call open indefinitely.
1227
1231
  // Uses a 30-second window — enough time to speak a name but short enough
@@ -1229,7 +1233,7 @@ export class RelayConnection {
1229
1233
  const NAME_CAPTURE_TIMEOUT_MS = 30_000;
1230
1234
  this.nameCaptureTimeoutTimer = setTimeout(() => {
1231
1235
  if (this.connectionState !== "awaiting_name") return;
1232
- this.handleNameCaptureTimeout();
1236
+ void this.handleNameCaptureTimeout();
1233
1237
  }, NAME_CAPTURE_TIMEOUT_MS);
1234
1238
 
1235
1239
  log.info(
@@ -1307,7 +1311,7 @@ export class RelayConnection {
1307
1311
  { callSessionId: this.callSessionId },
1308
1312
  "Access request ID is null after notification attempt — failing closed",
1309
1313
  );
1310
- this.handleAccessRequestTimeout();
1314
+ void this.handleAccessRequestTimeout();
1311
1315
  return;
1312
1316
  }
1313
1317
 
@@ -1327,9 +1331,9 @@ export class RelayConnection {
1327
1331
  const pollIntervalMs = getAccessRequestPollIntervalMs();
1328
1332
 
1329
1333
  const guardianLabel = this.resolveGuardianLabel();
1330
- this.sendTextToken(
1334
+ void speakSystemPrompt(
1335
+ this,
1331
1336
  `Thank you. I've let ${guardianLabel} know. Please hold while I check if I have permission to speak with you.`,
1332
- true,
1333
1337
  );
1334
1338
 
1335
1339
  updateCallSession(this.callSessionId, { status: "waiting_on_user" });
@@ -1363,7 +1367,7 @@ export class RelayConnection {
1363
1367
  if (request.status === "approved") {
1364
1368
  this.handleAccessRequestApproved();
1365
1369
  } else if (request.status === "denied") {
1366
- this.handleAccessRequestDenied();
1370
+ void this.handleAccessRequestDenied();
1367
1371
  }
1368
1372
  // 'pending' continues polling; 'expired'/'cancelled' handled by timeout
1369
1373
  }, pollIntervalMs);
@@ -1377,7 +1381,7 @@ export class RelayConnection {
1377
1381
  "Access request in-call wait timed out",
1378
1382
  );
1379
1383
 
1380
- this.handleAccessRequestTimeout();
1384
+ void this.handleAccessRequestTimeout();
1381
1385
  }, timeoutMs);
1382
1386
 
1383
1387
  log.info(
@@ -1450,7 +1454,7 @@ export class RelayConnection {
1450
1454
  /**
1451
1455
  * Handle a denied access request: deliver deterministic copy and hang up.
1452
1456
  */
1453
- private handleAccessRequestDenied(): void {
1457
+ private async handleAccessRequestDenied(): Promise<void> {
1454
1458
  this.clearAccessRequestWait();
1455
1459
 
1456
1460
  const guardianLabel = this.resolveGuardianLabel();
@@ -1460,11 +1464,6 @@ export class RelayConnection {
1460
1464
  requestId: this.accessRequestId,
1461
1465
  });
1462
1466
 
1463
- this.sendTextToken(
1464
- `Sorry, ${guardianLabel} says I'm not allowed to speak with you. Goodbye.`,
1465
- true,
1466
- );
1467
-
1468
1467
  this.connectionState = "disconnecting";
1469
1468
 
1470
1469
  updateCallSession(this.callSessionId, {
@@ -1478,6 +1477,10 @@ export class RelayConnection {
1478
1477
  "Access request denied — ending call",
1479
1478
  );
1480
1479
 
1480
+ await speakSystemPrompt(
1481
+ this,
1482
+ `Sorry, ${guardianLabel} says I'm not allowed to speak with you. Goodbye.`,
1483
+ );
1481
1484
  setTimeout(() => {
1482
1485
  this.endSession("Access request denied");
1483
1486
  }, getTtsPlaybackDelayMs());
@@ -1486,7 +1489,7 @@ export class RelayConnection {
1486
1489
  /**
1487
1490
  * Handle an access request timeout: deliver deterministic copy and hang up.
1488
1491
  */
1489
- private handleAccessRequestTimeout(): void {
1492
+ private async handleAccessRequestTimeout(): Promise<void> {
1490
1493
  // Emit callback handoff notification before clearing wait state
1491
1494
  this.emitAccessRequestCallbackHandoffForReason("timeout");
1492
1495
 
@@ -1503,10 +1506,6 @@ export class RelayConnection {
1503
1506
  const callbackNote = this.callbackOptIn
1504
1507
  ? ` I've noted that you'd like a callback — I'll pass that along to ${guardianLabel}.`
1505
1508
  : "";
1506
- this.sendTextToken(
1507
- `Sorry, I can't get ahold of ${guardianLabel} right now. I'll let them know you called.${callbackNote}`,
1508
- true,
1509
- );
1510
1509
 
1511
1510
  this.connectionState = "disconnecting";
1512
1511
 
@@ -1521,6 +1520,10 @@ export class RelayConnection {
1521
1520
  "Access request timed out — ending call",
1522
1521
  );
1523
1522
 
1523
+ await speakSystemPrompt(
1524
+ this,
1525
+ `Sorry, I can't get ahold of ${guardianLabel} right now. I'll let them know you called.${callbackNote}`,
1526
+ );
1524
1527
  setTimeout(() => {
1525
1528
  this.endSession("Access request timed out");
1526
1529
  }, getTtsPlaybackDelayMs());
@@ -1546,7 +1549,7 @@ export class RelayConnection {
1546
1549
  * Handle a name capture timeout: the caller never provided their name
1547
1550
  * within the allotted window. Deliver deterministic copy and hang up.
1548
1551
  */
1549
- private handleNameCaptureTimeout(): void {
1552
+ private async handleNameCaptureTimeout(): Promise<void> {
1550
1553
  if (this.nameCaptureTimeoutTimer) {
1551
1554
  clearTimeout(this.nameCaptureTimeoutTimer);
1552
1555
  this.nameCaptureTimeoutTimer = null;
@@ -1556,11 +1559,6 @@ export class RelayConnection {
1556
1559
  from: this.accessRequestFromNumber,
1557
1560
  });
1558
1561
 
1559
- this.sendTextToken(
1560
- "Sorry, I didn't catch your name. Please try calling back. Goodbye.",
1561
- true,
1562
- );
1563
-
1564
1562
  this.connectionState = "disconnecting";
1565
1563
 
1566
1564
  updateCallSession(this.callSessionId, {
@@ -1574,6 +1572,10 @@ export class RelayConnection {
1574
1572
  "Name capture timed out — ending call",
1575
1573
  );
1576
1574
 
1575
+ await speakSystemPrompt(
1576
+ this,
1577
+ "Sorry, I didn't catch your name. Please try calling back. Goodbye.",
1578
+ );
1577
1579
  setTimeout(() => {
1578
1580
  this.endSession("Name capture timed out");
1579
1581
  }, getTtsPlaybackDelayMs());
@@ -1584,7 +1586,9 @@ export class RelayConnection {
1584
1586
  * Delegates to the extracted attemptInviteCodeRedemption() and
1585
1587
  * interprets the structured result to drive side-effects.
1586
1588
  */
1587
- private handleInviteCodeRedemptionResult(enteredCode: string): void {
1589
+ private async handleInviteCodeRedemptionResult(
1590
+ enteredCode: string,
1591
+ ): Promise<void> {
1588
1592
  if (!this.inviteRedemptionAssistantId || !this.inviteRedemptionFromNumber) {
1589
1593
  return;
1590
1594
  }
@@ -1634,8 +1638,6 @@ export class RelayConnection {
1634
1638
  "Voice invite redemption failed — invalid or expired code",
1635
1639
  );
1636
1640
 
1637
- this.sendTextToken(result.ttsMessage, true);
1638
-
1639
1641
  this.connectionState = "disconnecting";
1640
1642
 
1641
1643
  updateCallSession(this.callSessionId, {
@@ -1649,6 +1651,7 @@ export class RelayConnection {
1649
1651
  finalizeCall(this.callSessionId, failSession.conversationId);
1650
1652
  }
1651
1653
 
1654
+ await speakSystemPrompt(this, result.ttsMessage);
1652
1655
  setTimeout(() => {
1653
1656
  this.endSession("Invite redemption failed");
1654
1657
  }, getTtsPlaybackDelayMs());
@@ -1659,8 +1662,9 @@ export class RelayConnection {
1659
1662
 
1660
1663
  /**
1661
1664
  * Resolve a human-readable guardian label for voice wait copy.
1662
- * Delegates to the shared resolveGuardianName() which checks USER.md
1663
- * first, then falls back to Contact.displayName, then DEFAULT_USER_REFERENCE.
1665
+ * Delegates to the shared resolveGuardianName() which checks the
1666
+ * guardian's per-user persona file (users/<slug>.md) first, then falls
1667
+ * back to Contact.displayName, then DEFAULT_USER_REFERENCE.
1664
1668
  */
1665
1669
  private resolveGuardianLabel(): string {
1666
1670
  // Look up the guardian contact for a displayName fallback
@@ -1691,7 +1695,7 @@ export class RelayConnection {
1691
1695
  callSessionId: this.callSessionId,
1692
1696
  consumeSequence: () => this.heartbeatSequence++,
1693
1697
  resolveGuardianLabel: () => this.resolveGuardianLabel(),
1694
- sendTextToken: (text, last) => this.sendTextToken(text, last),
1698
+ sendTextToken: (text, _last) => void speakSystemPrompt(this, text),
1695
1699
  scheduleNext: () => this.scheduleNextHeartbeat(),
1696
1700
  });
1697
1701
  }
@@ -1737,9 +1741,9 @@ export class RelayConnection {
1737
1741
  clearTimeout(this.accessRequestHeartbeatTimer);
1738
1742
  this.accessRequestHeartbeatTimer = null;
1739
1743
  }
1740
- this.sendTextToken(
1744
+ void speakSystemPrompt(
1745
+ this,
1741
1746
  `Noted, I'll make sure ${guardianLabel} knows you'd like a callback. For now, I'll keep trying to reach them.`,
1742
- true,
1743
1747
  );
1744
1748
  this.scheduleNextHeartbeat();
1745
1749
  return;
@@ -1756,9 +1760,9 @@ export class RelayConnection {
1756
1760
  clearTimeout(this.accessRequestHeartbeatTimer);
1757
1761
  this.accessRequestHeartbeatTimer = null;
1758
1762
  }
1759
- this.sendTextToken(
1763
+ void speakSystemPrompt(
1764
+ this,
1760
1765
  `No problem, I'll keep holding. Still waiting on ${guardianLabel}.`,
1761
- true,
1762
1766
  );
1763
1767
  this.scheduleNextHeartbeat();
1764
1768
  return;
@@ -1793,15 +1797,15 @@ export class RelayConnection {
1793
1797
  "voice_guardian_wait_callback_offer_sent",
1794
1798
  {},
1795
1799
  );
1796
- this.sendTextToken(
1800
+ void speakSystemPrompt(
1801
+ this,
1797
1802
  `I understand this is taking a while. I can have ${guardianLabel} call you back once I hear from them. Would you like that, or would you prefer to keep holding?`,
1798
- true,
1799
1803
  );
1800
1804
  } else {
1801
1805
  // Already offered callback — just reassure
1802
- this.sendTextToken(
1806
+ void speakSystemPrompt(
1807
+ this,
1803
1808
  `I hear you, I'm sorry for the wait. Still trying to reach ${guardianLabel}.`,
1804
- true,
1805
1809
  );
1806
1810
  }
1807
1811
  this.scheduleNextHeartbeat();
@@ -1814,9 +1818,9 @@ export class RelayConnection {
1814
1818
  clearTimeout(this.accessRequestHeartbeatTimer);
1815
1819
  this.accessRequestHeartbeatTimer = null;
1816
1820
  }
1817
- this.sendTextToken(
1821
+ void speakSystemPrompt(
1822
+ this,
1818
1823
  `Yes, I'm still here. Still waiting to hear back from ${guardianLabel}.`,
1819
- true,
1820
1824
  );
1821
1825
  this.scheduleNextHeartbeat();
1822
1826
  break;
@@ -1827,9 +1831,9 @@ export class RelayConnection {
1827
1831
  clearTimeout(this.accessRequestHeartbeatTimer);
1828
1832
  this.accessRequestHeartbeatTimer = null;
1829
1833
  }
1830
- this.sendTextToken(
1834
+ void speakSystemPrompt(
1835
+ this,
1831
1836
  `Thanks for that. I'm still waiting on ${guardianLabel}. I'll let you know as soon as I hear back.`,
1832
- true,
1833
1837
  );
1834
1838
  this.scheduleNextHeartbeat();
1835
1839
  break;
@@ -1888,11 +1892,11 @@ export class RelayConnection {
1888
1892
  );
1889
1893
  if (spokenDigits.length >= this.verificationCodeLength) {
1890
1894
  const enteredCode = spokenDigits.slice(0, this.verificationCodeLength);
1891
- this.handleVerificationCodeResult(enteredCode);
1895
+ void this.handleVerificationCodeResult(enteredCode);
1892
1896
  } else if (spokenDigits.length > 0) {
1893
- this.sendTextToken(
1897
+ void speakSystemPrompt(
1898
+ this,
1894
1899
  `I heard ${spokenDigits.length} digits. Please enter all ${this.verificationCodeLength} digits of your code.`,
1895
- true,
1896
1900
  );
1897
1901
  }
1898
1902
  return;
@@ -1918,11 +1922,11 @@ export class RelayConnection {
1918
1922
  0,
1919
1923
  this.inviteRedemptionCodeLength,
1920
1924
  );
1921
- this.handleInviteCodeRedemptionResult(enteredCode);
1925
+ void this.handleInviteCodeRedemptionResult(enteredCode);
1922
1926
  } else if (spokenDigits.length > 0) {
1923
- this.sendTextToken(
1927
+ void speakSystemPrompt(
1928
+ this,
1924
1929
  `I heard ${spokenDigits.length} digits. Please enter all ${this.inviteRedemptionCodeLength} digits of your code.`,
1925
- true,
1926
1930
  );
1927
1931
  }
1928
1932
  return;
@@ -2014,7 +2018,7 @@ export class RelayConnection {
2014
2018
  );
2015
2019
  }
2016
2020
  }
2017
- this.sendTextToken("I'm still setting up. Please hold.", true);
2021
+ void speakSystemPrompt(this, "I'm still setting up. Please hold.");
2018
2022
  }
2019
2023
  }
2020
2024
 
@@ -2073,7 +2077,7 @@ export class RelayConnection {
2073
2077
  this.verificationCodeLength,
2074
2078
  );
2075
2079
  this.dtmfBuffer = "";
2076
- this.handleVerificationCodeResult(enteredCode);
2080
+ void this.handleVerificationCodeResult(enteredCode);
2077
2081
  }
2078
2082
  return;
2079
2083
  }
@@ -2092,7 +2096,7 @@ export class RelayConnection {
2092
2096
  this.inviteRedemptionCodeLength,
2093
2097
  );
2094
2098
  this.dtmfBuffer = "";
2095
- this.handleInviteCodeRedemptionResult(enteredCode);
2099
+ void this.handleInviteCodeRedemptionResult(enteredCode);
2096
2100
  }
2097
2101
  return;
2098
2102
  }
@@ -2155,8 +2159,6 @@ export class RelayConnection {
2155
2159
  "Callee verification failed — max attempts reached",
2156
2160
  );
2157
2161
 
2158
- this.sendTextToken("Verification failed. Goodbye.", true);
2159
-
2160
2162
  // Mark failed immediately so a relay close during the goodbye TTS
2161
2163
  // window cannot race this into a terminal "completed" status.
2162
2164
  updateCallSession(this.callSessionId, {
@@ -2188,10 +2190,23 @@ export class RelayConnection {
2188
2190
  }
2189
2191
  }
2190
2192
 
2191
- // End the call with failed status after TTS plays
2192
- setTimeout(() => {
2193
- this.endSession("Verification failed");
2194
- }, getTtsPlaybackDelayMs());
2193
+ // Wait for synthesis to complete before starting teardown timer
2194
+ // so the caller hears the goodbye message.
2195
+ void speakSystemPrompt(this, "Verification failed. Goodbye.")
2196
+ .then(() => {
2197
+ setTimeout(() => {
2198
+ this.endSession("Verification failed");
2199
+ }, getTtsPlaybackDelayMs());
2200
+ })
2201
+ .catch((err) => {
2202
+ log.error(
2203
+ { err, callSessionId: this.callSessionId },
2204
+ "System prompt TTS failed during verification teardown",
2205
+ );
2206
+ setTimeout(() => {
2207
+ this.endSession("Verification failed");
2208
+ }, getTtsPlaybackDelayMs());
2209
+ });
2195
2210
  } else {
2196
2211
  // Allow another attempt
2197
2212
  log.info(
@@ -2202,9 +2217,9 @@ export class RelayConnection {
2202
2217
  },
2203
2218
  "Callee verification attempt failed — retrying",
2204
2219
  );
2205
- this.sendTextToken(
2220
+ void speakSystemPrompt(
2221
+ this,
2206
2222
  "That code was incorrect. Please try again.",
2207
- true,
2208
2223
  );
2209
2224
  }
2210
2225
  }
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Shared call TTS provider resolution.
3
+ *
4
+ * Both the call controller (LLM turn speech) and deterministic system
5
+ * prompts (verification codes, guardian wait updates, timeout copy) use
6
+ * this helper so that provider selection, format fallback, and the
7
+ * native-vs-synthesized strategy decision stay in one implementation.
8
+ */
9
+
10
+ import { loadConfig } from "../config/loader.js";
11
+ import { getTtsProvider } from "../tts/provider-registry.js";
12
+ import { resolveTtsConfig } from "../tts/tts-config-resolver.js";
13
+ import type { TtsProvider } from "../tts/types.js";
14
+ import { getLogger } from "../util/logger.js";
15
+ import { resolveCallStrategy } from "./tts-call-strategy.js";
16
+
17
+ const log = getLogger("resolve-call-tts-provider");
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Public types
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export interface ResolvedCallTts {
24
+ /** The resolved TTS provider, or null when config/registry is unavailable. */
25
+ provider: TtsProvider | null;
26
+
27
+ /**
28
+ * True when the catalog's `callMode` is `"synthesized-play"` -- audio
29
+ * is synthesized via the provider API and streamed through the audio
30
+ * store. False when `callMode` is `"native-twilio"` -- text tokens are
31
+ * sent directly to the relay for Twilio's built-in TTS engine.
32
+ */
33
+ useSynthesizedPath: boolean;
34
+
35
+ /** Audio format to use for synthesized audio. */
36
+ audioFormat: "mp3" | "wav" | "opus";
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Options
41
+ // ---------------------------------------------------------------------------
42
+
43
+ export interface ResolveCallTtsOptions {
44
+ /**
45
+ * When true, force `audioFormat` to `"wav"` regardless of the
46
+ * provider's configured format. The media-stream transport sets this
47
+ * because its {@link audioBufferToFrames} can only correctly
48
+ * transcode WAV (PCM) to mu-law -- compressed formats (mp3, opus)
49
+ * are sent as raw bytes and produce garbled audio.
50
+ */
51
+ preferWav?: boolean;
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Public API
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /**
59
+ * Resolve the active TTS provider via the global provider abstraction.
60
+ *
61
+ * The native-vs-synthesized decision is driven by the catalog's
62
+ * `callMode` field via {@link resolveCallStrategy} -- the same single
63
+ * decision path used by `voice-quality.ts`. Providers with
64
+ * `callMode: "synthesized-play"` have their audio streamed through the
65
+ * audio store and played via `sendPlayUrl`. Providers with
66
+ * `callMode: "native-twilio"` stream text tokens directly to the relay
67
+ * for Twilio's built-in TTS.
68
+ *
69
+ * Falls back to the native path with `mp3` format when the config is
70
+ * missing a `services.tts` block or the provider is not registered
71
+ * (e.g. unit tests or early startup).
72
+ */
73
+ export function resolveCallTtsProvider(
74
+ options?: ResolveCallTtsOptions,
75
+ ): ResolvedCallTts {
76
+ try {
77
+ const config = loadConfig();
78
+ const resolved = resolveTtsConfig(config);
79
+ const provider = getTtsProvider(resolved.provider);
80
+
81
+ // Use the catalog's callMode to decide the call path -- the same
82
+ // decision path used by voice-quality.ts via resolveCallStrategy().
83
+ const strategy = resolveCallStrategy(config);
84
+ const useSynthesizedPath = strategy.callMode === "synthesized-play";
85
+
86
+ // For synthesized providers, preflight provider-specific config
87
+ // invariants that would otherwise fail only at first synthesis call.
88
+ // If required config is missing, degrade to the native token path
89
+ // (Twilio TTS) rather than letting the call stay silent.
90
+ //
91
+ // Fish Audio requires a reference ID when no per-request voiceId is
92
+ // supplied (which is the telephony default).
93
+ if (useSynthesizedPath && resolved.provider === "fish-audio") {
94
+ const referenceId = (resolved.providerConfig as { referenceId?: string })
95
+ .referenceId;
96
+ if (!referenceId?.trim()) {
97
+ log.warn(
98
+ { provider: resolved.provider },
99
+ "Synthesized call TTS disabled: fish-audio.referenceId is not configured; falling back to native token path",
100
+ );
101
+ return {
102
+ provider: null,
103
+ useSynthesizedPath: false,
104
+ audioFormat: "mp3",
105
+ };
106
+ }
107
+ }
108
+
109
+ // Read the user-configured audio format from the resolved provider
110
+ // config so the streaming store entry's content-type matches the
111
+ // actual audio bytes the provider produces.
112
+ //
113
+ // When preferWav is set (media-stream transport), force WAV so
114
+ // audioBufferToFrames receives PCM it can transcode to mu-law.
115
+ const audioFormat: "mp3" | "wav" | "opus" = options?.preferWav
116
+ ? "wav"
117
+ : (() => {
118
+ const configuredFormat = (
119
+ resolved.providerConfig as { format?: string }
120
+ ).format;
121
+ return (
122
+ configuredFormat &&
123
+ ["mp3", "wav", "opus"].includes(configuredFormat)
124
+ ? configuredFormat
125
+ : "mp3"
126
+ ) as "mp3" | "wav" | "opus";
127
+ })();
128
+
129
+ return { provider, useSynthesizedPath, audioFormat };
130
+ } catch {
131
+ // Config missing `services.tts` block or provider not registered
132
+ // (e.g. unit tests or early startup) -- fall back to the native
133
+ // path where the provider object is not used.
134
+ return { provider: null, useSynthesizedPath: false, audioFormat: "mp3" };
135
+ }
136
+ }