@vellumai/assistant 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (895) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +41 -49
  4. package/bunfig.toml +3 -0
  5. package/docs/architecture/memory.md +1 -1
  6. package/docs/backup-troubleshooting.md +52 -0
  7. package/docs/browser-use-architecture-phase2.md +174 -0
  8. package/docs/stt-provider-onboarding.md +120 -0
  9. package/knip.json +12 -2
  10. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  11. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  12. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  13. package/openapi.yaml +1111 -86
  14. package/package.json +40 -42
  15. package/scripts/generate-openapi.ts +0 -2
  16. package/scripts/test.sh +73 -18
  17. package/src/__tests__/acp-session.test.ts +43 -0
  18. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  19. package/src/__tests__/agent-loop.test.ts +123 -0
  20. package/src/__tests__/anthropic-provider.test.ts +263 -10
  21. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  22. package/src/__tests__/app-executors.test.ts +1 -0
  23. package/src/__tests__/app-source-watcher.test.ts +37 -11
  24. package/src/__tests__/approval-routes-http.test.ts +178 -1
  25. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  26. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  27. package/src/__tests__/browser-fill-credential.test.ts +240 -94
  28. package/src/__tests__/browser-manager.test.ts +40 -27
  29. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  30. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  31. package/src/__tests__/btw-routes.test.ts +7 -0
  32. package/src/__tests__/call-controller.test.ts +581 -20
  33. package/src/__tests__/catalog-files.test.ts +1000 -0
  34. package/src/__tests__/channel-approvals.test.ts +53 -0
  35. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  36. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  37. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  38. package/src/__tests__/checker.test.ts +157 -10
  39. package/src/__tests__/clawhub-files.test.ts +347 -0
  40. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  41. package/src/__tests__/config-analysis.test.ts +100 -0
  42. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  43. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  44. package/src/__tests__/config-schema.test.ts +1248 -224
  45. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  46. package/src/__tests__/config-watcher.test.ts +43 -8
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  48. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  49. package/src/__tests__/contacts-write.test.ts +197 -0
  50. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  51. package/src/__tests__/context-window-manager.test.ts +88 -0
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
  54. package/src/__tests__/conversation-agent-loop.test.ts +99 -3
  55. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  56. package/src/__tests__/conversation-attachments.test.ts +80 -4
  57. package/src/__tests__/conversation-confirmation-signals.test.ts +290 -0
  58. package/src/__tests__/conversation-error.test.ts +70 -0
  59. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  60. package/src/__tests__/conversation-history-web-search.test.ts +12 -4
  61. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  62. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  63. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  64. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  65. package/src/__tests__/conversation-list-source.test.ts +145 -0
  66. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  67. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  68. package/src/__tests__/conversation-queue.test.ts +946 -62
  69. package/src/__tests__/conversation-routes-disk-view.test.ts +275 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +324 -46
  73. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  74. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  76. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  77. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  78. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  79. package/src/__tests__/conversation-store.test.ts +195 -0
  80. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  81. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  82. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  83. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  84. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  85. package/src/__tests__/credential-health-service.test.ts +352 -0
  86. package/src/__tests__/credential-security-invariants.test.ts +6 -3
  87. package/src/__tests__/credential-vault-unit.test.ts +383 -7
  88. package/src/__tests__/credential-vault.test.ts +152 -13
  89. package/src/__tests__/credentials-cli.test.ts +42 -18
  90. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  91. package/src/__tests__/date-context.test.ts +4 -4
  92. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  93. package/src/__tests__/device-id.test.ts +112 -0
  94. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  95. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  96. package/src/__tests__/email-html-renderer.test.ts +71 -0
  97. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  98. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  99. package/src/__tests__/emit-event-signal.test.ts +71 -0
  100. package/src/__tests__/extension-id-sync-guard.test.ts +222 -0
  101. package/src/__tests__/fixtures/mock-chrome-extension.ts +386 -0
  102. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -0
  104. package/src/__tests__/gemini-provider.test.ts +66 -2
  105. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  106. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  107. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  108. package/src/__tests__/gmail-preferences.test.ts +117 -0
  109. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  110. package/src/__tests__/headless-browser-interactions.test.ts +738 -359
  111. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  112. package/src/__tests__/headless-browser-navigate.test.ts +528 -49
  113. package/src/__tests__/headless-browser-read-tools.test.ts +274 -100
  114. package/src/__tests__/headless-browser-snapshot.test.ts +250 -77
  115. package/src/__tests__/heartbeat-service.test.ts +70 -17
  116. package/src/__tests__/home-state-routes.test.ts +162 -0
  117. package/src/__tests__/host-bash-proxy.test.ts +145 -1
  118. package/src/__tests__/host-browser-e2e-cloud.test.ts +596 -0
  119. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  120. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  121. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  122. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  123. package/src/__tests__/host-browser-routes.test.ts +198 -0
  124. package/src/__tests__/host-browser-ws-events-e2e.test.ts +423 -0
  125. package/src/__tests__/host-cu-proxy.test.ts +166 -1
  126. package/src/__tests__/host-file-proxy.test.ts +185 -1
  127. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  128. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  129. package/src/__tests__/host-shell-tool.test.ts +1 -11
  130. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  131. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  132. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  133. package/src/__tests__/integration-status.test.ts +6 -7
  134. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  135. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  136. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  137. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  138. package/src/__tests__/llm-usage-store.test.ts +363 -0
  139. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  140. package/src/__tests__/mcp-health-check.test.ts +10 -3
  141. package/src/__tests__/media-stream-output.test.ts +555 -0
  142. package/src/__tests__/media-stream-parser.test.ts +374 -0
  143. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  144. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  145. package/src/__tests__/media-turn-detector.test.ts +440 -0
  146. package/src/__tests__/message-queue.test.ts +125 -0
  147. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  148. package/src/__tests__/migration-export-http.test.ts +67 -8
  149. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  150. package/src/__tests__/migration-import-commit-http.test.ts +109 -7
  151. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  152. package/src/__tests__/migration-validate-http.test.ts +3 -3
  153. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  154. package/src/__tests__/model-intents.test.ts +2 -2
  155. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  156. package/src/__tests__/oauth-apps-routes.test.ts +18 -12
  157. package/src/__tests__/oauth-cli.test.ts +709 -60
  158. package/src/__tests__/oauth-connect-orchestrator.test.ts +118 -24
  159. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  160. package/src/__tests__/oauth-provider-serializer.test.ts +147 -10
  161. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  162. package/src/__tests__/oauth-providers-routes.test.ts +52 -14
  163. package/src/__tests__/oauth-store.test.ts +1465 -176
  164. package/src/__tests__/oauth2-gateway-transport.test.ts +460 -26
  165. package/src/__tests__/onboarding-template-contract.test.ts +81 -70
  166. package/src/__tests__/openai-provider.test.ts +178 -2
  167. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  168. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  169. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  170. package/src/__tests__/outlook-categories.test.ts +1 -1
  171. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  172. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  173. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  174. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  175. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  176. package/src/__tests__/outlook-trash.test.ts +1 -1
  177. package/src/__tests__/outlook-unsubscribe.test.ts +32 -3
  178. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  179. package/src/__tests__/permission-mode.test.ts +28 -56
  180. package/src/__tests__/persona-resolver.test.ts +251 -0
  181. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  182. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  183. package/src/__tests__/platform.test.ts +92 -1
  184. package/src/__tests__/post-turn-tool-result-truncation.test.ts +343 -0
  185. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  186. package/src/__tests__/pricing.test.ts +174 -0
  187. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  188. package/src/__tests__/qdrant-manager.test.ts +29 -8
  189. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  190. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  191. package/src/__tests__/relay-server.test.ts +423 -5
  192. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  193. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  194. package/src/__tests__/schedule-routes.test.ts +162 -0
  195. package/src/__tests__/search-skills-unified.test.ts +118 -0
  196. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  197. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  198. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  199. package/src/__tests__/secure-keys.test.ts +107 -0
  200. package/src/__tests__/send-endpoint-busy.test.ts +8 -1
  201. package/src/__tests__/sequence-store.test.ts +1 -1
  202. package/src/__tests__/server-history-render.test.ts +49 -0
  203. package/src/__tests__/set-permission-mode.test.ts +13 -250
  204. package/src/__tests__/settings-routes.test.ts +201 -0
  205. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  206. package/src/__tests__/skills-file-content-endpoint.test.ts +801 -0
  207. package/src/__tests__/skills-files-catalog-fallback.test.ts +738 -0
  208. package/src/__tests__/skills.test.ts +5 -2
  209. package/src/__tests__/skillssh-files.test.ts +446 -0
  210. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  211. package/src/__tests__/slack-channel-config.test.ts +576 -16
  212. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  213. package/src/__tests__/stt-stream-session.test.ts +535 -0
  214. package/src/__tests__/subagent-detail.test.ts +44 -2
  215. package/src/__tests__/subagent-disposal.test.ts +1 -0
  216. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  217. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  218. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  219. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  220. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  221. package/src/__tests__/subagent-tools.test.ts +1 -0
  222. package/src/__tests__/subagent-types.test.ts +1 -0
  223. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  224. package/src/__tests__/system-prompt.test.ts +184 -27
  225. package/src/__tests__/task-scheduler.test.ts +32 -6
  226. package/src/__tests__/telegram-config.test.ts +10 -13
  227. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  228. package/src/__tests__/terminal-tools.test.ts +25 -5
  229. package/src/__tests__/test-preload.ts +18 -0
  230. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  231. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  232. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  233. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  234. package/src/__tests__/tool-executor.test.ts +33 -24
  235. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  236. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  237. package/src/__tests__/top-level-renderer.test.ts +73 -1
  238. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  239. package/src/__tests__/trust-store.test.ts +7 -1
  240. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  241. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  242. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  243. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  244. package/src/__tests__/twilio-routes.test.ts +376 -0
  245. package/src/__tests__/unicode.test.ts +293 -0
  246. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  247. package/src/__tests__/update-bulletin.test.ts +206 -5
  248. package/src/__tests__/usage-routes.test.ts +25 -4
  249. package/src/__tests__/user-reference.test.ts +46 -61
  250. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  251. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  252. package/src/__tests__/voice-config-update.test.ts +403 -0
  253. package/src/__tests__/voice-quality.test.ts +434 -19
  254. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  255. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  256. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  257. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  258. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  259. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  260. package/src/__tests__/workspace-policy.test.ts +2 -0
  261. package/src/acp/client-handler.ts +30 -4
  262. package/src/agent/image-optimize.ts +24 -12
  263. package/src/agent/loop.ts +55 -9
  264. package/src/approvals/guardian-request-resolvers.ts +21 -15
  265. package/src/backup/__tests__/backup-key.test.ts +152 -0
  266. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  267. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  268. package/src/backup/__tests__/local-writer.test.ts +218 -0
  269. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  270. package/src/backup/__tests__/paths.test.ts +300 -0
  271. package/src/backup/__tests__/restore.test.ts +498 -0
  272. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  273. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  274. package/src/backup/backup-key.ts +137 -0
  275. package/src/backup/backup-worker.ts +459 -0
  276. package/src/backup/list-snapshots.ts +147 -0
  277. package/src/backup/local-writer.ts +133 -0
  278. package/src/backup/offsite-writer.ts +222 -0
  279. package/src/backup/paths.ts +226 -0
  280. package/src/backup/restore.ts +322 -0
  281. package/src/backup/snapshot-lock.ts +431 -0
  282. package/src/backup/stream-crypt.ts +263 -0
  283. package/src/browser-session/__tests__/manager.test.ts +297 -0
  284. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  285. package/src/browser-session/backends/extension.ts +26 -0
  286. package/src/browser-session/backends/local.ts +24 -0
  287. package/src/browser-session/events.ts +164 -0
  288. package/src/browser-session/index.ts +27 -0
  289. package/src/browser-session/manager.ts +159 -0
  290. package/src/browser-session/types.ts +28 -0
  291. package/src/bundler/package-resolver.ts +4 -0
  292. package/src/calls/audio-store.ts +11 -5
  293. package/src/calls/call-controller.ts +226 -71
  294. package/src/calls/call-domain.ts +9 -0
  295. package/src/calls/call-speech-output.ts +190 -0
  296. package/src/calls/call-transport.ts +77 -0
  297. package/src/calls/media-stream-audio-transcode.ts +173 -0
  298. package/src/calls/media-stream-output.ts +660 -0
  299. package/src/calls/media-stream-parser.ts +300 -0
  300. package/src/calls/media-stream-protocol.ts +166 -0
  301. package/src/calls/media-stream-server.ts +592 -0
  302. package/src/calls/media-stream-stt-session.ts +460 -0
  303. package/src/calls/media-turn-detector.ts +230 -0
  304. package/src/calls/relay-server.ts +90 -75
  305. package/src/calls/resolve-call-tts-provider.ts +136 -0
  306. package/src/calls/telephony-stt-routing.ts +145 -0
  307. package/src/calls/tts-call-strategy.ts +161 -0
  308. package/src/calls/tts-text-sanitizer.ts +32 -16
  309. package/src/calls/twilio-routes.ts +281 -17
  310. package/src/calls/voice-quality.ts +78 -35
  311. package/src/calls/voice-session-bridge.ts +8 -1
  312. package/src/channels/__tests__/types.test.ts +134 -0
  313. package/src/channels/types.ts +69 -3
  314. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  315. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  316. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  317. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  318. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  319. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  320. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  321. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  322. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  323. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  324. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  325. package/src/cli/commands/backup.ts +993 -0
  326. package/src/cli/commands/conversations.ts +77 -0
  327. package/src/cli/commands/credentials.ts +3 -4
  328. package/src/cli/commands/domain.ts +210 -0
  329. package/src/cli/commands/email.ts +273 -16
  330. package/src/cli/commands/mcp.ts +16 -4
  331. package/src/cli/commands/oauth/__tests__/connect.test.ts +56 -44
  332. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  333. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  334. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  335. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +32 -33
  336. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +330 -0
  337. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +117 -12
  338. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  339. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  340. package/src/cli/commands/oauth/apps.ts +7 -4
  341. package/src/cli/commands/oauth/connect.ts +6 -3
  342. package/src/cli/commands/oauth/disconnect.ts +1 -1
  343. package/src/cli/commands/oauth/mode.ts +12 -3
  344. package/src/cli/commands/oauth/providers.ts +215 -36
  345. package/src/cli/commands/oauth/shared.ts +7 -6
  346. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +254 -0
  347. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  348. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  349. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  350. package/src/cli/commands/platform/index.ts +107 -10
  351. package/src/cli/commands/usage.ts +10 -9
  352. package/src/cli/lib/daemon-credential-client.ts +4 -0
  353. package/src/cli/program.ts +30 -4
  354. package/src/config/__tests__/backup-schema.test.ts +134 -0
  355. package/src/config/assistant-feature-flags.ts +61 -62
  356. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  357. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +141 -0
  358. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  359. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  360. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  361. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  362. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  363. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  364. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  365. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  366. package/src/config/bundled-skills/contacts/SKILL.md +5 -2
  367. package/src/config/bundled-skills/document/SKILL.md +4 -0
  368. package/src/config/bundled-skills/gmail/SKILL.md +54 -8
  369. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  370. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  371. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  372. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  373. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  374. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  375. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  376. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  377. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  378. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  379. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  380. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  381. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  382. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  383. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  384. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  385. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  386. package/src/config/bundled-skills/outlook/SKILL.md +9 -2
  387. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  388. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  389. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  390. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  391. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  392. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  393. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  394. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  395. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  396. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  397. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  398. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  399. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  400. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  401. package/src/config/bundled-tool-registry.ts +8 -0
  402. package/src/config/env-registry.ts +38 -0
  403. package/src/config/env.ts +49 -4
  404. package/src/config/feature-flag-registry.json +85 -14
  405. package/src/config/loader.ts +82 -13
  406. package/src/config/sanitize-for-transfer.ts +47 -0
  407. package/src/config/schema.ts +81 -15
  408. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  409. package/src/config/schemas/analysis.ts +51 -0
  410. package/src/config/schemas/backup.ts +72 -0
  411. package/src/config/schemas/calls.ts +1 -26
  412. package/src/config/schemas/elevenlabs.ts +0 -59
  413. package/src/config/schemas/filing.ts +47 -7
  414. package/src/config/schemas/heartbeat.ts +27 -5
  415. package/src/config/schemas/host-browser.ts +112 -0
  416. package/src/config/schemas/inference.ts +1 -1
  417. package/src/config/schemas/memory-lifecycle.ts +14 -2
  418. package/src/config/schemas/memory-retrieval.ts +103 -0
  419. package/src/config/schemas/security.ts +0 -6
  420. package/src/config/schemas/services.ts +52 -0
  421. package/src/config/schemas/stt.ts +59 -0
  422. package/src/config/schemas/tts.ts +230 -0
  423. package/src/config/schemas/updates.ts +14 -0
  424. package/src/config/skills.ts +4 -0
  425. package/src/config/types.ts +4 -1
  426. package/src/contacts/contact-store.ts +56 -11
  427. package/src/contacts/contacts-write.ts +38 -1
  428. package/src/context/post-turn-tool-result-truncation.ts +177 -0
  429. package/src/context/tool-result-truncation.ts +2 -1
  430. package/src/context/window-manager.ts +61 -10
  431. package/src/credential-execution/approval-bridge.ts +49 -15
  432. package/src/credential-execution/executable-discovery.ts +12 -2
  433. package/src/credential-execution/process-manager.ts +33 -2
  434. package/src/credential-health/credential-health-service.ts +366 -0
  435. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  436. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  437. package/src/daemon/__tests__/conversation-tool-setup.test.ts +195 -0
  438. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  439. package/src/daemon/app-source-watcher.ts +35 -0
  440. package/src/daemon/config-watcher.ts +99 -5
  441. package/src/daemon/context-overflow-approval.ts +5 -0
  442. package/src/daemon/conversation-agent-loop-handlers.ts +23 -2
  443. package/src/daemon/conversation-agent-loop.ts +153 -42
  444. package/src/daemon/conversation-attachments.ts +40 -0
  445. package/src/daemon/conversation-error.ts +11 -0
  446. package/src/daemon/conversation-history.ts +40 -6
  447. package/src/daemon/conversation-launch.ts +220 -0
  448. package/src/daemon/conversation-lifecycle.ts +59 -9
  449. package/src/daemon/conversation-messaging.ts +37 -3
  450. package/src/daemon/conversation-notifiers.ts +5 -0
  451. package/src/daemon/conversation-process.ts +622 -13
  452. package/src/daemon/conversation-queue-manager.ts +24 -0
  453. package/src/daemon/conversation-runtime-assembly.ts +128 -36
  454. package/src/daemon/conversation-slash.ts +36 -0
  455. package/src/daemon/conversation-surfaces.ts +131 -40
  456. package/src/daemon/conversation-tool-setup.ts +99 -8
  457. package/src/daemon/conversation-usage.ts +7 -4
  458. package/src/daemon/conversation-workspace.ts +12 -0
  459. package/src/daemon/conversation.ts +292 -16
  460. package/src/daemon/date-context.ts +10 -10
  461. package/src/daemon/first-greeting.ts +3 -2
  462. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  463. package/src/daemon/handlers/conversations.ts +13 -141
  464. package/src/daemon/handlers/shared.ts +80 -0
  465. package/src/daemon/handlers/skills.ts +483 -44
  466. package/src/daemon/host-bash-proxy.ts +48 -13
  467. package/src/daemon/host-browser-proxy.ts +192 -0
  468. package/src/daemon/host-cu-proxy.ts +36 -11
  469. package/src/daemon/host-file-proxy.ts +57 -9
  470. package/src/daemon/lifecycle.ts +179 -28
  471. package/src/daemon/message-protocol.ts +13 -0
  472. package/src/daemon/message-types/conversations.ts +89 -14
  473. package/src/daemon/message-types/home.ts +40 -0
  474. package/src/daemon/message-types/host-browser.ts +100 -0
  475. package/src/daemon/message-types/meet.ts +143 -0
  476. package/src/daemon/message-types/messages.ts +19 -5
  477. package/src/daemon/message-types/schedules.ts +34 -2
  478. package/src/daemon/message-types/skills.ts +26 -0
  479. package/src/daemon/message-types/subagents.ts +2 -0
  480. package/src/daemon/message-types/surfaces.ts +2 -0
  481. package/src/daemon/server.ts +439 -14
  482. package/src/daemon/shutdown-handlers.ts +32 -4
  483. package/src/daemon/shutdown-registry.ts +40 -0
  484. package/src/daemon/tool-side-effects.ts +15 -0
  485. package/src/daemon/transport-hints.ts +5 -24
  486. package/src/email/html-renderer.ts +76 -0
  487. package/src/heartbeat/heartbeat-service.ts +93 -7
  488. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  489. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  490. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  491. package/src/home/__tests__/feed-types.test.ts +275 -0
  492. package/src/home/__tests__/feed-writer.test.ts +688 -0
  493. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  494. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  495. package/src/home/__tests__/progress-formula.test.ts +213 -0
  496. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  497. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  498. package/src/home/assistant-feed-authoring.ts +124 -0
  499. package/src/home/emit-feed-event.ts +158 -0
  500. package/src/home/feed-scheduler.ts +247 -0
  501. package/src/home/feed-types.ts +181 -0
  502. package/src/home/feed-writer.ts +469 -0
  503. package/src/home/platform-gmail-digest.ts +163 -0
  504. package/src/home/progress-formula.ts +86 -0
  505. package/src/home/relationship-state-writer.ts +824 -0
  506. package/src/home/relationship-state.ts +143 -0
  507. package/src/home/rollup-producer.ts +384 -0
  508. package/src/hooks/runner.ts +7 -0
  509. package/src/inbound/platform-callback-registration.ts +30 -20
  510. package/src/inbound/public-ingress-urls.ts +12 -0
  511. package/src/instrument.ts +1 -1
  512. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  513. package/src/ipc/cli-client.ts +151 -0
  514. package/src/ipc/cli-server.ts +234 -0
  515. package/src/ipc/gateway-client.ts +180 -0
  516. package/src/ipc/routes/index.ts +5 -0
  517. package/src/ipc/routes/wake-conversation.ts +19 -0
  518. package/src/mcp/client.ts +59 -24
  519. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  520. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  521. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  522. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  523. package/src/memory/app-store.ts +31 -1
  524. package/src/memory/attachments-store.ts +70 -0
  525. package/src/memory/auto-analysis-enqueue.ts +127 -0
  526. package/src/memory/auto-analysis-guard.ts +27 -0
  527. package/src/memory/cleanup-schedule-state.ts +37 -0
  528. package/src/memory/conversation-analyze-job.ts +73 -0
  529. package/src/memory/conversation-crud.ts +122 -0
  530. package/src/memory/conversation-disk-view.ts +7 -0
  531. package/src/memory/conversation-group-migration.ts +34 -2
  532. package/src/memory/conversation-queries.ts +6 -5
  533. package/src/memory/conversation-starters-cadence.ts +76 -0
  534. package/src/memory/conversation-title-service.ts +5 -2
  535. package/src/memory/db-init.ts +18 -0
  536. package/src/memory/db-maintenance.ts +108 -0
  537. package/src/memory/db.ts +1 -0
  538. package/src/memory/embedding-backend.test.ts +75 -0
  539. package/src/memory/embedding-backend.ts +131 -5
  540. package/src/memory/embedding-gemini.test.ts +54 -0
  541. package/src/memory/embedding-gemini.ts +20 -9
  542. package/src/memory/embedding-local.ts +176 -17
  543. package/src/memory/graph/consolidation.ts +10 -23
  544. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  545. package/src/memory/graph/extraction-job.ts +15 -0
  546. package/src/memory/graph/extraction.test.ts +23 -0
  547. package/src/memory/graph/extraction.ts +8 -0
  548. package/src/memory/graph/retriever.ts +67 -40
  549. package/src/memory/graph/scoring.test.ts +186 -0
  550. package/src/memory/graph/scoring.ts +31 -1
  551. package/src/memory/graph/store.test.ts +7 -3
  552. package/src/memory/graph/store.ts +47 -12
  553. package/src/memory/graph/tools.ts +1 -1
  554. package/src/memory/group-crud.ts +6 -1
  555. package/src/memory/indexer.ts +95 -16
  556. package/src/memory/job-handlers/cleanup.ts +11 -8
  557. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  558. package/src/memory/jobs-store.ts +64 -4
  559. package/src/memory/jobs-worker.ts +22 -9
  560. package/src/memory/llm-usage-store.ts +137 -60
  561. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  562. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  563. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  564. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  565. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  566. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  567. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  568. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  569. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  570. package/src/memory/migrations/index.ts +12 -0
  571. package/src/memory/migrations/registry.ts +16 -0
  572. package/src/memory/qdrant-manager.ts +43 -16
  573. package/src/memory/schema/conversations.ts +3 -0
  574. package/src/memory/schema/oauth.ts +21 -13
  575. package/src/memory/usage-buckets.ts +396 -0
  576. package/src/messaging/providers/gmail/client.ts +57 -6
  577. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  578. package/src/messaging/providers/slack/adapter.ts +143 -38
  579. package/src/messaging/providers/slack/client.ts +16 -0
  580. package/src/messaging/providers/slack/types.ts +4 -0
  581. package/src/notifications/decision-engine.ts +3 -3
  582. package/src/notifications/signal.ts +5 -0
  583. package/src/oauth/AGENTS.md +76 -0
  584. package/src/oauth/__tests__/identity-verifier.test.ts +25 -19
  585. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  586. package/src/oauth/byo-connection.test.ts +26 -9
  587. package/src/oauth/byo-connection.ts +10 -8
  588. package/src/oauth/connect-orchestrator.ts +25 -21
  589. package/src/oauth/connect-types.ts +3 -3
  590. package/src/oauth/connection-resolver.test.ts +17 -4
  591. package/src/oauth/connection-resolver.ts +22 -18
  592. package/src/oauth/connection.ts +3 -1
  593. package/src/oauth/manual-token-connection.ts +13 -13
  594. package/src/oauth/oauth-store.ts +223 -100
  595. package/src/oauth/platform-connection.test.ts +101 -3
  596. package/src/oauth/platform-connection.ts +56 -35
  597. package/src/oauth/provider-serializer.ts +31 -5
  598. package/src/oauth/revoke.ts +76 -0
  599. package/src/oauth/seed-providers.ts +133 -87
  600. package/src/oauth/token-persistence.ts +1 -1
  601. package/src/permissions/checker.ts +16 -6
  602. package/src/permissions/defaults.ts +49 -1
  603. package/src/permissions/permission-mode.ts +4 -11
  604. package/src/permissions/prompter.ts +13 -1
  605. package/src/permissions/trust-store.ts +3 -3
  606. package/src/permissions/v2-consent-policy.ts +87 -0
  607. package/src/permissions/workspace-policy.ts +3 -0
  608. package/src/platform/client.test.ts +10 -0
  609. package/src/platform/sync-identity.ts +129 -0
  610. package/src/prompts/persona-resolver.ts +126 -2
  611. package/src/prompts/system-prompt.ts +76 -38
  612. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  613. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  614. package/src/prompts/templates/SOUL.md +3 -1
  615. package/src/prompts/templates/UPDATES.md +12 -0
  616. package/src/prompts/templates/channels/slack.md +20 -0
  617. package/src/prompts/update-bulletin-format.ts +26 -9
  618. package/src/prompts/update-bulletin.ts +34 -23
  619. package/src/prompts/user-reference.ts +20 -17
  620. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  621. package/src/providers/anthropic/client.ts +157 -60
  622. package/src/providers/fireworks/client.ts +2 -2
  623. package/src/providers/gemini/client.ts +9 -1
  624. package/src/providers/model-catalog.ts +6 -0
  625. package/src/providers/model-intents.ts +4 -4
  626. package/src/providers/ollama/client.ts +2 -2
  627. package/src/providers/openai/chat-completions-provider.ts +474 -0
  628. package/src/providers/openai/client.ts +25 -440
  629. package/src/providers/openai/responses-provider.ts +502 -0
  630. package/src/providers/openrouter/client.ts +101 -4
  631. package/src/providers/provider-secret-catalog.ts +139 -0
  632. package/src/providers/registry.ts +2 -2
  633. package/src/providers/retry.ts +14 -3
  634. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  635. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  636. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  637. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  638. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  639. package/src/providers/speech-to-text/deepgram.ts +115 -0
  640. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  641. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  642. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  643. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  644. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  645. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  646. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  647. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  648. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  649. package/src/providers/speech-to-text/resolve.ts +386 -6
  650. package/src/providers/types.ts +10 -1
  651. package/src/runtime/AGENTS.md +65 -0
  652. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  653. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  654. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  655. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  656. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  657. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  658. package/src/runtime/agent-wake.ts +512 -0
  659. package/src/runtime/assistant-event-hub.ts +2 -2
  660. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  661. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  662. package/src/runtime/auth/__tests__/route-policy.test.ts +48 -0
  663. package/src/runtime/auth/middleware.ts +98 -0
  664. package/src/runtime/auth/route-policy.ts +33 -9
  665. package/src/runtime/auth/token-service.ts +56 -1
  666. package/src/runtime/btw-sidechain.ts +2 -0
  667. package/src/runtime/capability-tokens.ts +414 -0
  668. package/src/runtime/channel-approvals.ts +18 -5
  669. package/src/runtime/channel-invite-transport.ts +1 -1
  670. package/src/runtime/channel-invite-transports/email.ts +14 -6
  671. package/src/runtime/channel-readiness-service.ts +12 -22
  672. package/src/runtime/chrome-extension-registry.ts +368 -0
  673. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  674. package/src/runtime/guardian-decision-types.ts +7 -0
  675. package/src/runtime/http-server.ts +815 -75
  676. package/src/runtime/http-types.ts +6 -2
  677. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  678. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  679. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +198 -0
  680. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  681. package/src/runtime/migrations/migration-transport.ts +7 -0
  682. package/src/runtime/migrations/migration-wizard.ts +23 -2
  683. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  684. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  685. package/src/runtime/migrations/vbundle-import-analyzer.ts +96 -1
  686. package/src/runtime/migrations/vbundle-importer.ts +89 -5
  687. package/src/runtime/pending-interactions.ts +18 -13
  688. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  689. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  690. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  691. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  692. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  693. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  694. package/src/runtime/routes/app-management-routes.ts +12 -18
  695. package/src/runtime/routes/approval-routes.ts +90 -16
  696. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  697. package/src/runtime/routes/attachment-routes.ts +216 -17
  698. package/src/runtime/routes/backup-routes.ts +519 -0
  699. package/src/runtime/routes/browser-extension-pair-routes.ts +556 -0
  700. package/src/runtime/routes/btw-routes.ts +8 -6
  701. package/src/runtime/routes/contact-routes.test.ts +298 -0
  702. package/src/runtime/routes/contact-routes.ts +132 -5
  703. package/src/runtime/routes/conversation-analysis-routes.ts +22 -141
  704. package/src/runtime/routes/conversation-management-routes.ts +223 -0
  705. package/src/runtime/routes/conversation-routes.ts +598 -103
  706. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  707. package/src/runtime/routes/filing-routes.ts +93 -0
  708. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  709. package/src/runtime/routes/home-feed-routes.ts +334 -0
  710. package/src/runtime/routes/home-state-routes.ts +138 -0
  711. package/src/runtime/routes/host-browser-routes.ts +268 -0
  712. package/src/runtime/routes/host-file-routes.ts +9 -1
  713. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  714. package/src/runtime/routes/identity-routes.ts +262 -33
  715. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  716. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  717. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  718. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  719. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  720. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  721. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  722. package/src/runtime/routes/log-export-routes.ts +42 -22
  723. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  724. package/src/runtime/routes/memory-item-routes.ts +1 -7
  725. package/src/runtime/routes/migration-routes.ts +122 -2
  726. package/src/runtime/routes/oauth-apps.ts +15 -17
  727. package/src/runtime/routes/oauth-providers.ts +4 -0
  728. package/src/runtime/routes/schedule-routes.ts +24 -11
  729. package/src/runtime/routes/settings-routes.ts +31 -102
  730. package/src/runtime/routes/skills-routes.ts +128 -9
  731. package/src/runtime/routes/stt-routes.ts +233 -0
  732. package/src/runtime/routes/subagents-routes.ts +14 -10
  733. package/src/runtime/routes/surface-action-routes.ts +41 -2
  734. package/src/runtime/routes/tts-routes.ts +108 -24
  735. package/src/runtime/routes/usage-routes.ts +38 -9
  736. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  737. package/src/runtime/routes/user-routes.ts +13 -1
  738. package/src/runtime/routes/work-items-routes.ts +8 -1
  739. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  740. package/src/runtime/routes/workspace-routes.ts +8 -1
  741. package/src/runtime/routes/workspace-utils.ts +2 -0
  742. package/src/runtime/runtime-mode.ts +33 -0
  743. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  744. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  745. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  746. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  747. package/src/runtime/services/analyze-conversation.ts +344 -0
  748. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  749. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  750. package/src/runtime/skill-route-registry.ts +49 -0
  751. package/src/runtime/slack-block-formatting.ts +437 -10
  752. package/src/schedule/scheduler.ts +57 -5
  753. package/src/security/ces-credential-client.ts +20 -0
  754. package/src/security/ces-rpc-credential-backend.ts +17 -0
  755. package/src/security/credential-backend.ts +5 -0
  756. package/src/security/oauth2.ts +68 -29
  757. package/src/security/secure-keys.ts +143 -27
  758. package/src/security/token-manager.ts +31 -10
  759. package/src/sequence/engine.ts +23 -0
  760. package/src/sequence/types.ts +1 -1
  761. package/src/skills/catalog-files.ts +554 -0
  762. package/src/skills/category-inference.ts +122 -0
  763. package/src/skills/clawhub-files.ts +213 -0
  764. package/src/skills/clawhub.ts +84 -23
  765. package/src/skills/skill-file-provider.ts +40 -0
  766. package/src/skills/skillssh-files.ts +395 -0
  767. package/src/skills/skillssh-registry.ts +4 -4
  768. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  769. package/src/stt/__tests__/types.test.ts +89 -0
  770. package/src/stt/daemon-batch-transcriber.ts +195 -0
  771. package/src/stt/stt-stream-session.ts +499 -0
  772. package/src/stt/types.ts +330 -0
  773. package/src/stt/wav-encoder.test.ts +373 -0
  774. package/src/stt/wav-encoder.ts +175 -0
  775. package/src/subagent/manager.ts +169 -40
  776. package/src/subagent/types.ts +19 -0
  777. package/src/tools/apps/executors.ts +11 -2
  778. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  779. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  780. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  781. package/src/tools/browser/auth-detector.ts +43 -12
  782. package/src/tools/browser/browser-execution.ts +1787 -342
  783. package/src/tools/browser/browser-manager.ts +81 -12
  784. package/src/tools/browser/browser-mode-constants.ts +12 -0
  785. package/src/tools/browser/browser-mode.ts +92 -0
  786. package/src/tools/browser/browser-status-constants.ts +33 -0
  787. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  788. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  789. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1263 -0
  790. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +359 -0
  791. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1993 -0
  792. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  793. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  794. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  795. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  796. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  797. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  798. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +1007 -0
  799. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  800. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +744 -0
  801. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  802. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +868 -0
  803. package/src/tools/browser/cdp-client/errors.ts +49 -0
  804. package/src/tools/browser/cdp-client/extension-cdp-client.ts +148 -0
  805. package/src/tools/browser/cdp-client/factory.ts +914 -0
  806. package/src/tools/browser/cdp-client/index.ts +28 -0
  807. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  808. package/src/tools/browser/cdp-client/types.ts +120 -0
  809. package/src/tools/credentials/vault.ts +35 -6
  810. package/src/tools/filesystem/edit.ts +1 -1
  811. package/src/tools/filesystem/list.ts +1 -1
  812. package/src/tools/filesystem/read.ts +1 -1
  813. package/src/tools/filesystem/write.ts +2 -1
  814. package/src/tools/host-filesystem/edit.ts +1 -1
  815. package/src/tools/host-filesystem/read.ts +12 -15
  816. package/src/tools/host-filesystem/write.ts +1 -1
  817. package/src/tools/host-terminal/host-shell.ts +21 -16
  818. package/src/tools/network/web-fetch.ts +5 -2
  819. package/src/tools/network/web-search.ts +5 -2
  820. package/src/tools/permission-checker.ts +77 -82
  821. package/src/tools/registry.ts +0 -2
  822. package/src/tools/secret-detection-handler.ts +34 -0
  823. package/src/tools/shared/filesystem/image-read.ts +61 -40
  824. package/src/tools/shared/shell-output.ts +3 -1
  825. package/src/tools/side-effects.ts +2 -0
  826. package/src/tools/skills/sandbox-runner.ts +3 -2
  827. package/src/tools/subagent/spawn.ts +47 -3
  828. package/src/tools/subagent/status.ts +2 -0
  829. package/src/tools/system/register.ts +2 -16
  830. package/src/tools/terminal/safe-env.ts +15 -0
  831. package/src/tools/terminal/shell.ts +36 -20
  832. package/src/tools/tool-approval-handler.ts +48 -2
  833. package/src/tools/tool-manifest.ts +21 -0
  834. package/src/tools/types.ts +19 -0
  835. package/src/tools/ui-surface/definitions.ts +6 -1
  836. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  837. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  838. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  839. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  840. package/src/tts/provider-catalog.ts +201 -0
  841. package/src/tts/provider-registry.ts +73 -0
  842. package/src/tts/providers/deepgram-provider.ts +219 -0
  843. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  844. package/src/tts/providers/fish-audio-provider.ts +183 -0
  845. package/src/tts/providers/index.ts +42 -0
  846. package/src/tts/providers/register-builtins.ts +130 -0
  847. package/src/tts/synthesize-text.ts +110 -0
  848. package/src/tts/tts-config-resolver.ts +78 -0
  849. package/src/tts/types.ts +153 -0
  850. package/src/types/onboarding-context.ts +7 -0
  851. package/src/util/abort-reasons.ts +58 -0
  852. package/src/util/device-id.ts +32 -16
  853. package/src/util/errors.ts +9 -1
  854. package/src/util/platform.ts +63 -24
  855. package/src/util/pricing.ts +66 -3
  856. package/src/util/spawn.ts +1 -1
  857. package/src/util/truncate.ts +4 -2
  858. package/src/util/unicode.ts +201 -0
  859. package/src/version.ts +19 -24
  860. package/src/watcher/engine.ts +23 -0
  861. package/src/watcher/watcher-store.ts +31 -0
  862. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  863. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  864. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  865. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  866. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  867. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  868. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  869. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  870. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  871. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  872. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  873. package/src/workspace/migrations/registry.ts +16 -0
  874. package/src/workspace/top-level-renderer.ts +31 -1
  875. package/src/workspace/turn-commit.ts +31 -0
  876. package/src/__tests__/chrome-cdp.test.ts +0 -419
  877. package/src/__tests__/email-cli.test.ts +0 -297
  878. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  879. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  880. package/src/__tests__/permission-mode-store.test.ts +0 -277
  881. package/src/browser-extension-relay/protocol.ts +0 -63
  882. package/src/browser-extension-relay/server.ts +0 -203
  883. package/src/cli/commands/browser-relay.ts +0 -536
  884. package/src/config/schemas/sandbox.ts +0 -14
  885. package/src/email/guardrails.ts +0 -221
  886. package/src/email/provider.ts +0 -117
  887. package/src/email/providers/agentmail.ts +0 -361
  888. package/src/email/providers/index.ts +0 -65
  889. package/src/email/service.ts +0 -384
  890. package/src/email/types.ts +0 -126
  891. package/src/permissions/permission-mode-store.ts +0 -180
  892. package/src/prompts/templates/USER.md +0 -13
  893. package/src/providers/speech-to-text/types.ts +0 -17
  894. package/src/tools/browser/chrome-cdp.ts +0 -239
  895. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -23,6 +23,8 @@ import { revokeScopedApprovalGrantsForContext } from "../memory/scoped-approval-
23
23
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
24
24
  import { mintDaemonDeliveryToken } from "../runtime/auth/token-service.js";
25
25
  import { computeToolApprovalDigest } from "../security/tool-approval-digest.js";
26
+ import { getCatalogProvider } from "../tts/provider-catalog.js";
27
+ import type { TtsProvider, TtsProviderId } from "../tts/types.js";
26
28
  import { getLogger } from "../util/logger.js";
27
29
  import { createStreamingEntry } from "./audio-store.js";
28
30
  import {
@@ -44,11 +46,11 @@ import {
44
46
  recordCallEvent,
45
47
  updateCallSession,
46
48
  } from "./call-store.js";
49
+ import type { CallTransport } from "./call-transport.js";
47
50
  import { finalizeCall } from "./finalize-call.js";
48
- import { synthesizeWithFishAudio } from "./fish-audio-client.js";
49
51
  import { sendGuardianExpiryNotices } from "./guardian-action-sweep.js";
50
52
  import { dispatchGuardianQuestion } from "./guardian-dispatch.js";
51
- import type { RelayConnection } from "./relay-server.js";
53
+ import { resolveCallTtsProvider } from "./resolve-call-tts-provider.js";
52
54
  import type { PromptSpeakerContext } from "./speaker-identification.js";
53
55
  import { sanitizeForTts } from "./tts-text-sanitizer.js";
54
56
  import {
@@ -61,7 +63,6 @@ import {
61
63
  extractBalancedJson,
62
64
  stripInternalSpeechMarkers,
63
65
  } from "./voice-control-protocol.js";
64
- import { isFishAudioTts } from "./voice-quality.js";
65
66
  import {
66
67
  startVoiceTurn,
67
68
  type VoiceTurnHandle,
@@ -87,7 +88,7 @@ interface PendingGuardianInput {
87
88
 
88
89
  export class CallController {
89
90
  private callSessionId: string;
90
- private relay: RelayConnection;
91
+ private transport: CallTransport;
91
92
  private state: ControllerState = "idle";
92
93
  private abortController: AbortController = new AbortController();
93
94
  private currentTurnHandle: VoiceTurnHandle | null = null;
@@ -139,12 +140,12 @@ export class CallController {
139
140
  * without blocking the caller.
140
141
  */
141
142
  private guardianUnavailableForCall = false;
142
- /** Active Fish Audio session — tracked so interrupt handling can close it. */
143
- private activeFishAbort: AbortController | null = null;
143
+ /** Active synthesized-TTS session — tracked so interrupt handling can close it. */
144
+ private activeSynthesisAbort: AbortController | null = null;
144
145
 
145
146
  constructor(
146
147
  callSessionId: string,
147
- relay: RelayConnection,
148
+ transport: CallTransport,
148
149
  task: string | null,
149
150
  opts?: {
150
151
  broadcast?: (msg: ServerMessage) => void;
@@ -153,7 +154,7 @@ export class CallController {
153
154
  },
154
155
  ) {
155
156
  this.callSessionId = callSessionId;
156
- this.relay = relay;
157
+ this.transport = transport;
157
158
  this.task = task;
158
159
  this.isInbound = !task;
159
160
  this.broadcast = opts?.broadcast;
@@ -344,22 +345,58 @@ export class CallController {
344
345
  await this.runTurn(`[USER_INSTRUCTION: ${instructionText}]`);
345
346
  }
346
347
 
348
+ /**
349
+ * Handle a barge-in attempt from inbound caller audio.
350
+ *
351
+ * Only interrupts the in-flight turn when the assistant is actively
352
+ * speaking. When the controller is idle or still processing (no TTS
353
+ * output yet), the barge-in is ignored — this prevents false
354
+ * interruption on initial inbound media frames that arrive before
355
+ * the assistant has had a chance to produce its first response.
356
+ *
357
+ * @returns `true` if the barge-in was accepted (assistant was speaking),
358
+ * `false` if it was ignored (assistant idle or processing).
359
+ */
360
+ handleBargeIn(): boolean {
361
+ if (this.state !== "speaking") {
362
+ log.debug(
363
+ {
364
+ callSessionId: this.callSessionId,
365
+ state: this.state,
366
+ },
367
+ "Barge-in ignored — assistant not speaking",
368
+ );
369
+ return false;
370
+ }
371
+
372
+ log.info(
373
+ { callSessionId: this.callSessionId },
374
+ "Barge-in accepted — interrupting assistant speech",
375
+ );
376
+ this.handleInterrupt();
377
+ return true;
378
+ }
379
+
347
380
  /**
348
381
  * Handle caller interrupting the assistant's speech.
382
+ *
383
+ * This is the hard interrupt path used for explicit teardown and
384
+ * internal abort scenarios. For barge-in from inbound audio, prefer
385
+ * {@link handleBargeIn} which gates on the speaking state.
349
386
  */
350
387
  handleInterrupt(): void {
351
388
  const wasSpeaking = this.state === "speaking";
352
389
  this.abortCurrentTurn();
353
390
  this.llmRunVersion++;
354
- // Cancel in-flight Fish Audio synthesis on barge-in
355
- if (this.activeFishAbort) {
356
- this.activeFishAbort.abort();
357
- this.activeFishAbort = null;
391
+ // Cancel in-flight synthesized TTS on barge-in
392
+ if (this.activeSynthesisAbort) {
393
+ this.activeSynthesisAbort.abort();
394
+ this.activeSynthesisAbort = null;
358
395
  }
359
396
  // Explicitly terminate the in-progress TTS turn so the relay can
360
397
  // immediately hand control back to the caller after barge-in.
361
398
  if (wasSpeaking) {
362
- this.relay.sendTextToken("", true);
399
+ this.transport.sendTextToken("", true);
363
400
  }
364
401
  this.state = "idle";
365
402
  // Restart silence detection so a barge-in that never yields a
@@ -386,9 +423,9 @@ export class CallController {
386
423
  this.pendingInstructions = [];
387
424
  this.llmRunVersion++;
388
425
  this.abortCurrentTurn();
389
- if (this.activeFishAbort) {
390
- this.activeFishAbort.abort();
391
- this.activeFishAbort = null;
426
+ if (this.activeSynthesisAbort) {
427
+ this.activeSynthesisAbort.abort();
428
+ this.activeSynthesisAbort = null;
392
429
  }
393
430
  this.currentTurnPromise = null;
394
431
  unregisterCallController(this.callSessionId);
@@ -516,7 +553,7 @@ export class CallController {
516
553
  return;
517
554
  }
518
555
  log.error({ err, callSessionId: this.callSessionId }, "Voice turn error");
519
- this.relay.sendTextToken(
556
+ this.transport.sendTextToken(
520
557
  "I'm sorry, I encountered a technical issue. Could you repeat that?",
521
558
  true,
522
559
  );
@@ -536,11 +573,19 @@ export class CallController {
536
573
  runVersion: number,
537
574
  runSignal: AbortSignal,
538
575
  ): Promise<string> {
539
- // Fish Audio TTS routing: when configured, buffer text by sentence
540
- // boundaries and synthesize via Fish Audio instead of streaming text
541
- // tokens for ElevenLabs TTS.
542
- const config = loadConfig();
543
- const useFishAudio = isFishAudioTts(config);
576
+ // Resolve the active TTS provider through the global abstraction.
577
+ // The catalog's callMode determines the call path: synthesized-play
578
+ // providers buffer text, synthesize via provider API, and stream
579
+ // audio chunks to Twilio via play-URL. Native-twilio providers
580
+ // stream text tokens to the relay for Twilio's built-in TTS.
581
+ //
582
+ // When the transport requires WAV (media-stream), request WAV so
583
+ // the audio store entry and any downstream fetch/transcode receives
584
+ // PCM that audioBufferToFrames can convert to mu-law.
585
+ const { provider, useSynthesizedPath, audioFormat } =
586
+ resolveCallTtsProvider({
587
+ preferWav: this.transport.requiresWavAudio,
588
+ });
544
589
 
545
590
  // Buffer incoming tokens so we can strip control markers ([ASK_GUARDIAN:...], [END_CALL])
546
591
  // before they reach TTS. We hold text whenever an unmatched '[' appears, since it
@@ -548,18 +593,18 @@ export class CallController {
548
593
  let ttsBuffer = "";
549
594
  let fullResponseText = "";
550
595
 
551
- // When using Fish Audio, we accumulate all text and synthesize
596
+ // When using the synthesized path, we accumulate all text and synthesize
552
597
  // the complete response at the end of the turn (better prosody).
553
- let fishAudioTextBuffer = "";
598
+ let synthesizedTextBuffer = "";
554
599
 
555
600
  /** Emit a chunk of safe text to the appropriate TTS backend. */
556
601
  const emitSafeChunk = (safeText: string): void => {
557
602
  const cleaned = sanitizeForTts(safeText);
558
603
  if (cleaned.length === 0) return;
559
- if (useFishAudio) {
560
- fishAudioTextBuffer += cleaned;
604
+ if (useSynthesizedPath) {
605
+ synthesizedTextBuffer += cleaned;
561
606
  } else {
562
- this.relay.sendTextToken(cleaned, false);
607
+ this.transport.sendTextToken(cleaned, false);
563
608
  }
564
609
  };
565
610
 
@@ -670,48 +715,29 @@ export class CallController {
670
715
  emitSafeChunk(ttsBuffer);
671
716
  }
672
717
 
673
- // When using Fish Audio, synthesize the complete response text in a
674
- // single REST API call. The full text gives Fish Audio better context
675
- // for prosody and intonation. Audio streams back via chunked transfer
676
- // encoding and is forwarded to Twilio as it arrives.
677
- const sanitizedFishText = sanitizeForTts(fishAudioTextBuffer.trim());
678
- if (useFishAudio && sanitizedFishText.length > 0) {
718
+ // Synthesized-play path: when the active provider supports streaming,
719
+ // synthesize the complete response text via the provider's streaming
720
+ // API. The full text gives the provider better context for prosody
721
+ // and intonation. Audio streams back via chunked transfer encoding
722
+ // and is forwarded to Twilio as it arrives.
723
+ const sanitizedSynthText = sanitizeForTts(synthesizedTextBuffer.trim());
724
+ if (useSynthesizedPath && provider && sanitizedSynthText.length > 0) {
679
725
  if (!this.isCurrentRun(runVersion)) return fullResponseText;
680
- let handle: ReturnType<typeof createStreamingEntry> | null = null;
681
- try {
682
- const format = config.fishAudio.format ?? "mp3";
683
- handle = createStreamingEntry(format as "mp3" | "wav" | "opus");
684
- const baseUrl = getPublicBaseUrl(config);
685
- const url = `${baseUrl}/v1/audio/${handle.audioId}`;
686
- this.relay.sendPlayUrl(url);
687
- const abortController = new AbortController();
688
- this.activeFishAbort = abortController;
689
- await synthesizeWithFishAudio(
690
- sanitizedFishText,
691
- config.fishAudio,
692
- {
693
- onChunk: (chunk) => handle!.push(chunk),
694
- signal: abortController.signal,
695
- },
696
- );
697
- } catch (err) {
698
- if (err instanceof DOMException && err.name === "AbortError") {
699
- log.debug("Fish Audio synthesis aborted (barge-in)");
700
- } else {
701
- log.error({ err }, "Fish Audio synthesis failed — skipping");
702
- }
703
- } finally {
704
- this.activeFishAbort = null;
705
- handle?.finalize();
706
- }
726
+ await this.synthesizeAndStreamAudio(
727
+ provider,
728
+ sanitizedSynthText,
729
+ runVersion,
730
+ audioFormat,
731
+ );
707
732
  }
708
733
 
709
734
  // Signal end of this turn's speech. An empty token with `last: true`
710
735
  // tells ConversationRelay to start listening — it does NOT trigger TTS
711
- // synthesis. This is required even when Fish Audio handled all audio
712
- // playback, because ConversationRelay still needs the end-of-turn signal
713
- // to transition from "assistant speaking" to "caller speaking" state.
714
- this.relay.sendTextToken("", true);
736
+ // synthesis. This is required even when a synthesized provider handled
737
+ // all audio playback, because ConversationRelay still needs the
738
+ // end-of-turn signal to transition from "assistant speaking" to
739
+ // "caller speaking" state.
740
+ this.transport.sendTextToken("", true);
715
741
 
716
742
  // Mark the greeting's first response as awaiting ack
717
743
  if (this.lastSentWasOpener && fullResponseText.length > 0) {
@@ -722,6 +748,133 @@ export class CallController {
722
748
  return fullResponseText;
723
749
  }
724
750
 
751
+ /**
752
+ * Synthesize text via a streaming TTS provider and forward audio chunks
753
+ * to Twilio through the audio store / play-URL mechanism.
754
+ */
755
+ private async synthesizeAndStreamAudio(
756
+ provider: TtsProvider,
757
+ text: string,
758
+ _runVersion: number,
759
+ format: "mp3" | "wav" | "opus" = "mp3",
760
+ ): Promise<void> {
761
+ let handle: ReturnType<typeof createStreamingEntry> | null = null;
762
+ let playUrlSent = false;
763
+ try {
764
+ // When format is WAV (media-stream transport), request raw PCM from
765
+ // the provider so the audio bytes match the store's content-type.
766
+ // Without this, providers like Fish Audio still return mp3 and the
767
+ // downstream mu-law transcoder fails on the format mismatch.
768
+ const outputFormat = format === "wav" ? ("pcm" as const) : undefined;
769
+
770
+ // Use "pcm" as the store format when requesting PCM output so the
771
+ // audio store entry's content-type (audio/pcm) matches the raw PCM
772
+ // bytes providers return. Without this, the store says "audio/wav"
773
+ // but the bytes have no RIFF header, causing audioBufferToFrames to
774
+ // fall through to the wrong decode path.
775
+ const storeFormat = outputFormat ? "pcm" : format;
776
+ handle = createStreamingEntry(storeFormat);
777
+ const config = loadConfig();
778
+ const baseUrl = getPublicBaseUrl(config);
779
+ const url = `${baseUrl}/v1/audio/${handle.audioId}`;
780
+ const sendPlayUrlOnce = (): void => {
781
+ if (playUrlSent) return;
782
+ this.transport.sendPlayUrl(url);
783
+ playUrlSent = true;
784
+ };
785
+
786
+ const abortController = new AbortController();
787
+ this.activeSynthesisAbort = abortController;
788
+
789
+ if (provider.synthesizeStream) {
790
+ let streamedChunk = false;
791
+ await provider.synthesizeStream(
792
+ {
793
+ text,
794
+ useCase: "phone-call",
795
+ outputFormat,
796
+ signal: abortController.signal,
797
+ },
798
+ (chunk) => {
799
+ if (chunk.byteLength === 0) return;
800
+ if (!streamedChunk) {
801
+ sendPlayUrlOnce();
802
+ streamedChunk = true;
803
+ }
804
+ handle!.push(chunk);
805
+ },
806
+ );
807
+
808
+ // Some provider adapters may return a buffer without invoking
809
+ // onChunk. If that happens, do not leave a dangling unspeakable
810
+ // turn; degrade to native token TTS below by treating it as no-audio.
811
+ if (!streamedChunk) {
812
+ throw new Error("Streaming TTS returned no audio chunks");
813
+ }
814
+ } else {
815
+ // Fallback: buffer-oriented synthesis for providers that don't
816
+ // implement streaming (shouldn't normally reach here since
817
+ // useSynthesizedPath is gated on catalog callMode).
818
+ const result = await provider.synthesize({
819
+ text,
820
+ useCase: "phone-call",
821
+ outputFormat,
822
+ signal: abortController.signal,
823
+ });
824
+ if (result.audio.byteLength === 0) {
825
+ throw new Error("Buffer TTS returned an empty audio payload");
826
+ }
827
+ sendPlayUrlOnce();
828
+ handle.push(result.audio);
829
+ }
830
+ } catch (err) {
831
+ if (err instanceof DOMException && err.name === "AbortError") {
832
+ log.debug(
833
+ { provider: provider.id },
834
+ "TTS synthesis aborted (barge-in)",
835
+ );
836
+ } else {
837
+ // Extract error class and code for diagnosable log entries.
838
+ const errName = err instanceof Error ? err.name : String(err);
839
+ const errCode =
840
+ err instanceof Error && "code" in err
841
+ ? (err as Error & { code?: string }).code
842
+ : undefined;
843
+
844
+ // `allowNativeFallback` controls whether the LLM's original
845
+ // response text should be sent via native Twilio token-based
846
+ // TTS when synthesis fails. When false (e.g. Deepgram), the
847
+ // error is re-thrown so the outer catch handler sends a
848
+ // generic recovery message via native TTS instead — the
849
+ // caller still hears *something*, but not the LLM's text
850
+ // rendered in a mismatched voice.
851
+ const catalogEntry = getCatalogProvider(provider.id as TtsProviderId);
852
+ if (!catalogEntry.allowNativeFallback) {
853
+ log.error(
854
+ { err, provider: provider.id, errName, errCode },
855
+ "TTS synthesis failed — native fallback disabled for this provider",
856
+ );
857
+ throw err;
858
+ }
859
+
860
+ log.error(
861
+ { err, provider: provider.id, errName, errCode },
862
+ "TTS synthesis failed — falling back to native token TTS",
863
+ );
864
+ // If synthesis fails before any audio has started, degrade to
865
+ // token-based speech on ConversationRelay so the caller still
866
+ // hears a response instead of silence. This fallback is only
867
+ // used for providers whose catalog entry allows native fallback.
868
+ if (!playUrlSent && !this.transport.requiresWavAudio) {
869
+ this.transport.sendTextToken(text, false);
870
+ }
871
+ }
872
+ } finally {
873
+ this.activeSynthesisAbort = null;
874
+ handle?.finalize();
875
+ }
876
+ }
877
+
725
878
  /**
726
879
  * Handle post-turn marker detection and dispatch: guardian consultation
727
880
  * (ASK_GUARDIAN_APPROVAL / ASK_GUARDIAN), call finalization (END_CALL),
@@ -734,7 +887,9 @@ export class CallController {
734
887
  recordCallEvent(this.callSessionId, "assistant_spoke", {
735
888
  text: responseText,
736
889
  });
737
- const spokenText = sanitizeForTts(stripInternalSpeechMarkers(responseText)).trim();
890
+ const spokenText = sanitizeForTts(
891
+ stripInternalSpeechMarkers(responseText),
892
+ ).trim();
738
893
  if (spokenText.length > 0) {
739
894
  const session = getCallSession(this.callSessionId);
740
895
  if (session) {
@@ -952,7 +1107,7 @@ export class CallController {
952
1107
  currentSession.status !== "cancelled"
953
1108
  : false;
954
1109
 
955
- this.relay.endSession("Call completed");
1110
+ this.transport.endSession("Call completed");
956
1111
  updateCallSession(this.callSessionId, {
957
1112
  status: "completed",
958
1113
  endedAt: Date.now(),
@@ -1200,7 +1355,7 @@ export class CallController {
1200
1355
  { callSessionId: this.callSessionId },
1201
1356
  "Call duration warning",
1202
1357
  );
1203
- this.relay.sendTextToken(
1358
+ this.transport.sendTextToken(
1204
1359
  "Just to let you know, we're running low on time for this call.",
1205
1360
  true,
1206
1361
  );
@@ -1212,7 +1367,7 @@ export class CallController {
1212
1367
  { callSessionId: this.callSessionId },
1213
1368
  "Call duration limit reached",
1214
1369
  );
1215
- this.relay.sendTextToken(
1370
+ this.transport.sendTextToken(
1216
1371
  "I'm sorry, but we've reached the maximum time for this call. Thank you for your time. Goodbye!",
1217
1372
  true,
1218
1373
  );
@@ -1225,7 +1380,7 @@ export class CallController {
1225
1380
  currentSession.status !== "cancelled"
1226
1381
  : false;
1227
1382
 
1228
- this.relay.endSession("Maximum call duration reached");
1383
+ this.transport.endSession("Maximum call duration reached");
1229
1384
  updateCallSession(this.callSessionId, {
1230
1385
  status: "completed",
1231
1386
  endedAt: Date.now(),
@@ -1274,7 +1429,7 @@ export class CallController {
1274
1429
  // inbound access-request wait (relay state).
1275
1430
  if (
1276
1431
  this.pendingGuardianInput ||
1277
- this.relay.getConnectionState() === "awaiting_guardian_decision"
1432
+ this.transport.getConnectionState() === "awaiting_guardian_decision"
1278
1433
  ) {
1279
1434
  log.debug(
1280
1435
  { callSessionId: this.callSessionId },
@@ -1286,7 +1441,7 @@ export class CallController {
1286
1441
  { callSessionId: this.callSessionId },
1287
1442
  "Silence timeout triggered",
1288
1443
  );
1289
- this.relay.sendTextToken("Are you still there?", true);
1444
+ this.transport.sendTextToken("Are you still there?", true);
1290
1445
  }, getSilenceTimeoutMs());
1291
1446
  }
1292
1447
  }
@@ -38,6 +38,7 @@ import {
38
38
  getPendingQuestion,
39
39
  updateCallSession,
40
40
  } from "./call-store.js";
41
+ import { activeMediaStreamSessions } from "./media-stream-server.js";
41
42
  import { activeRelayConnections } from "./relay-server.js";
42
43
  import { getTwilioConfig } from "./twilio-config.js";
43
44
  import { TwilioConversationRelayProvider } from "./twilio-provider.js";
@@ -679,6 +680,14 @@ export async function cancelCall(
679
680
  activeRelayConnections.delete(callSessionId);
680
681
  }
681
682
 
683
+ // End the media-stream session if active
684
+ const mediaStreamSession = activeMediaStreamSessions.get(callSessionId);
685
+ if (mediaStreamSession) {
686
+ mediaStreamSession.getOutput().endSession(reason);
687
+ mediaStreamSession.destroy();
688
+ activeMediaStreamSessions.delete(callSessionId);
689
+ }
690
+
682
691
  // Clean up controller
683
692
  const controller = getCallController(callSessionId);
684
693
  if (controller) {
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Provider-aware speech output for deterministic call prompts.
3
+ *
4
+ * Deterministic call prompts (verification codes, guardian wait updates,
5
+ * timeout copy, failure copy, etc.) are routed through the same provider
6
+ * abstraction used by the call controller so that configured synthesized
7
+ * providers (e.g. Fish Audio) are respected for all spoken output.
8
+ *
9
+ * Two output paths (determined by the catalog's `callMode`):
10
+ * - **Native**: `callMode: "native-twilio"` — text is sent via
11
+ * `sendTextToken()` for Twilio's built-in TTS engine.
12
+ * - **Synthesized**: `callMode: "synthesized-play"` — text is synthesized
13
+ * via the provider API, streamed through the audio store, and played
14
+ * via `sendPlayUrl()`.
15
+ */
16
+
17
+ import { loadConfig } from "../config/loader.js";
18
+ import { getPublicBaseUrl } from "../inbound/public-ingress-urls.js";
19
+ import { getCatalogProvider } from "../tts/provider-catalog.js";
20
+ import type { TtsProvider, TtsProviderId } from "../tts/types.js";
21
+ import { getLogger } from "../util/logger.js";
22
+ import { createStreamingEntry } from "./audio-store.js";
23
+ import type { CallTransport } from "./call-transport.js";
24
+ import { resolveCallTtsProvider } from "./resolve-call-tts-provider.js";
25
+
26
+ const log = getLogger("call-speech-output");
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Public API
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /**
33
+ * Speak a deterministic text prompt through the active TTS provider.
34
+ *
35
+ * For native providers this is equivalent to `relay.sendTextToken(text, true)`
36
+ * and resolves immediately (synchronous send).
37
+ *
38
+ * For synthesized providers this synthesizes audio via the provider API,
39
+ * sends the play URL to the relay, and resolves once synthesis is complete.
40
+ * Callers in disconnect/teardown flows should `await` the returned promise
41
+ * before starting teardown timers so that the play URL is delivered to
42
+ * Twilio before the session ends. Interactive mid-call callers can
43
+ * fire-and-forget with `void speakSystemPrompt(...)`.
44
+ */
45
+ export function speakSystemPrompt(
46
+ relay: CallTransport,
47
+ text: string,
48
+ ): Promise<void> {
49
+ // When the transport requires WAV (media-stream), request WAV so
50
+ // the audio store entry contains PCM that audioBufferToFrames can
51
+ // transcode to mu-law. Without this, compressed formats (mp3, opus)
52
+ // are fetched by processFetchUrlItem and produce garbled audio.
53
+ const { provider, useSynthesizedPath, audioFormat } = resolveCallTtsProvider({
54
+ preferWav: relay.requiresWavAudio,
55
+ });
56
+
57
+ if (!useSynthesizedPath || !provider) {
58
+ // Native path — send text for Twilio's built-in TTS.
59
+ relay.sendTextToken(text, true);
60
+ return Promise.resolve();
61
+ }
62
+
63
+ // Synthesized path — synthesize audio and send play URL.
64
+ return synthesizeAndPlay(relay, provider, text, audioFormat);
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Internal helpers
69
+ // ---------------------------------------------------------------------------
70
+
71
+ /**
72
+ * Synthesize text via a streaming TTS provider and send the play URL
73
+ * to the relay.
74
+ *
75
+ * On synthesis failure the behavior depends on the provider:
76
+ * - Providers with a native Twilio TTS fallback (e.g. Fish Audio) fall
77
+ * back to `sendTextToken(text)` so the caller still hears the message.
78
+ * - Providers without a native fallback (e.g. Deepgram) log the error
79
+ * and send only an empty end-of-turn signal — the caller hears nothing
80
+ * but the relay transitions back to listening state.
81
+ */
82
+ async function synthesizeAndPlay(
83
+ relay: CallTransport,
84
+ provider: TtsProvider,
85
+ text: string,
86
+ format: "mp3" | "wav" | "opus",
87
+ ): Promise<void> {
88
+ let handle: ReturnType<typeof createStreamingEntry> | null = null;
89
+ let playUrlSent = false;
90
+ try {
91
+ // When format is WAV (media-stream transport), request raw PCM from
92
+ // the provider so the audio bytes match the store's content-type.
93
+ // Without this, providers like Fish Audio still return mp3 and the
94
+ // downstream mu-law transcoder fails on the format mismatch.
95
+ const outputFormat = format === "wav" ? ("pcm" as const) : undefined;
96
+
97
+ // Use "pcm" as the store format when requesting PCM output so the
98
+ // audio store entry's content-type (audio/pcm) matches the raw PCM
99
+ // bytes providers return. Without this, the store says "audio/wav"
100
+ // but the bytes have no RIFF header, causing audioBufferToFrames to
101
+ // fall through to the wrong decode path.
102
+ const storeFormat = outputFormat ? "pcm" : format;
103
+ handle = createStreamingEntry(storeFormat);
104
+ const config = loadConfig();
105
+ const baseUrl = getPublicBaseUrl(config);
106
+ const url = `${baseUrl}/v1/audio/${handle.audioId}`;
107
+ const sendPlayUrlOnce = (): void => {
108
+ if (playUrlSent) return;
109
+ relay.sendPlayUrl(url);
110
+ playUrlSent = true;
111
+ };
112
+
113
+ if (provider.synthesizeStream) {
114
+ let streamedChunk = false;
115
+ await provider.synthesizeStream(
116
+ { text, useCase: "phone-call", outputFormat },
117
+ (chunk) => {
118
+ if (chunk.byteLength === 0) return;
119
+ if (!streamedChunk) {
120
+ sendPlayUrlOnce();
121
+ streamedChunk = true;
122
+ }
123
+ handle!.push(chunk);
124
+ },
125
+ );
126
+ if (!streamedChunk) {
127
+ throw new Error("Streaming TTS returned no audio chunks");
128
+ }
129
+ } else {
130
+ const result = await provider.synthesize({
131
+ text,
132
+ useCase: "phone-call",
133
+ outputFormat,
134
+ });
135
+ if (result.audio.byteLength === 0) {
136
+ throw new Error("Buffer TTS returned an empty audio payload");
137
+ }
138
+ sendPlayUrlOnce();
139
+ handle.push(result.audio);
140
+ }
141
+
142
+ // Signal end of this turn's speech. An empty token with `last: true`
143
+ // tells ConversationRelay to start listening — it does NOT trigger TTS
144
+ // synthesis. This is required even when a synthesized provider handled
145
+ // all audio playback, because ConversationRelay still needs the
146
+ // end-of-turn signal to transition from "assistant speaking" to
147
+ // "caller speaking" state.
148
+ relay.sendTextToken("", true);
149
+ } catch (err) {
150
+ // Extract error class and code for diagnosable log entries.
151
+ const errName = err instanceof Error ? err.name : String(err);
152
+ const errCode =
153
+ err instanceof Error && "code" in err
154
+ ? (err as Error & { code?: string }).code
155
+ : undefined;
156
+
157
+ // `allowNativeFallback` controls whether the system prompt text
158
+ // should be sent via native Twilio token-based TTS when synthesis
159
+ // fails. When false (e.g. Deepgram), the design choice is to
160
+ // send only an end-of-turn signal — the caller hears nothing for
161
+ // this prompt — rather than degrading to a mismatched voice.
162
+ // Callers use fire-and-forget (`void speakSystemPrompt(...)`) so
163
+ // throwing here would produce an unhandled promise rejection.
164
+ const catalogEntry = getCatalogProvider(provider.id as TtsProviderId);
165
+ if (!catalogEntry.allowNativeFallback) {
166
+ log.error(
167
+ { err, provider: provider.id, errName, errCode },
168
+ "System prompt TTS synthesis failed — native fallback disabled for this provider",
169
+ );
170
+ // Send the end-of-turn signal so ConversationRelay transitions from
171
+ // "assistant speaking" to "caller speaking" state. Without this, the
172
+ // relay hangs waiting for the prompt to complete and the caller
173
+ // cannot interact.
174
+ relay.sendTextToken("", true);
175
+ return;
176
+ }
177
+
178
+ log.error(
179
+ { err, provider: provider.id, errName, errCode },
180
+ "System prompt TTS synthesis failed — falling back to native TTS",
181
+ );
182
+ // Fallback: send text via native TTS so the caller still hears the message.
183
+ // sendTextToken with last:true includes the end-of-turn signal inherently.
184
+ // This fallback is only used for providers whose catalog entry allows
185
+ // native fallback.
186
+ relay.sendTextToken(text, true);
187
+ } finally {
188
+ handle?.finalize();
189
+ }
190
+ }