@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
@@ -1,13 +1,32 @@
1
1
  /**
2
2
  * HTTP route handlers for Twilio voice webhooks.
3
3
  *
4
- * - handleVoiceWebhook: initial voice webhook; returns TwiML to connect ConversationRelay
4
+ * - handleVoiceWebhook: initial voice webhook; returns TwiML to connect
5
+ * ConversationRelay (Twilio-native STT) or Stream (custom media-stream STT)
5
6
  * - handleStatusCallback: call status updates (ringing, in-progress, completed, etc.)
6
7
  * - handleConnectAction: called when the ConversationRelay connection ends
8
+ *
9
+ * ## STT routing
10
+ *
11
+ * TwiML generation is driven by `services.stt.provider` via
12
+ * {@link resolveTelephonySttRouting}. The resolver returns a discriminated
13
+ * strategy that determines which TwiML path to use:
14
+ *
15
+ * - **`conversation-relay-native`** (deepgram, google-gemini) — emits
16
+ * `<Connect><ConversationRelay>` with Twilio-native `transcriptionProvider`
17
+ * and `speechModel` attributes.
18
+ *
19
+ * - **`media-stream-custom`** (openai-whisper) — emits
20
+ * `<Connect><Stream>` so the daemon receives raw audio and transcribes
21
+ * server-side.
7
22
  */
8
23
 
9
24
  import { loadConfig } from "../config/loader.js";
10
- import { getTwilioRelayUrl } from "../inbound/public-ingress-urls.js";
25
+ import {
26
+ getTwilioMediaStreamUrl,
27
+ getTwilioRelayUrl,
28
+ } from "../inbound/public-ingress-urls.js";
29
+ import { getProviderEntry } from "../providers/speech-to-text/provider-catalog.js";
11
30
  import { mintEdgeRelayToken } from "../runtime/auth/token-service.js";
12
31
  import { getLogger } from "../util/logger.js";
13
32
  import { persistCallCompletionMessage } from "./call-conversation-messages.js";
@@ -26,12 +45,33 @@ import {
26
45
  releaseCallbackClaim,
27
46
  updateCallSession,
28
47
  } from "./call-store.js";
48
+ import { routeSetup } from "./relay-setup-router.js";
29
49
  import { resolveCallHints } from "./stt-hints.js";
50
+ import { resolveTelephonySttRouting } from "./telephony-stt-routing.js";
30
51
  import type { CallStatus } from "./types.js";
31
52
  import { resolveVoiceQualityProfile } from "./voice-quality.js";
32
53
 
33
54
  const log = getLogger("twilio-routes");
34
55
 
56
+ // ── Speech config type ───────────────────────────────────────────────
57
+
58
+ /**
59
+ * Twilio ConversationRelay speech-to-text attributes.
60
+ *
61
+ * All values are pre-formatted strings ready for direct insertion into
62
+ * TwiML XML attribute values (XML escaping is the caller's responsibility).
63
+ */
64
+ export interface TwilioRelaySpeechConfig {
65
+ /** STT provider name (e.g. "Deepgram", "Google"). */
66
+ transcriptionProvider: string;
67
+ /** ASR model identifier, or undefined when the provider default should be used. */
68
+ speechModel: string | undefined;
69
+ /** Comma-separated vocabulary hints for the STT provider, or undefined when no hints are available. */
70
+ hints: string | undefined;
71
+ /** How aggressively the provider detects the start of caller speech. */
72
+ interruptSensitivity: string;
73
+ }
74
+
35
75
  // ── Helpers ──────────────────────────────────────────────────────────
36
76
 
37
77
  function escapeXml(str: string): string {
@@ -49,15 +89,12 @@ export function generateTwiML(
49
89
  welcomeGreeting: string | null,
50
90
  profile: {
51
91
  language: string;
52
- transcriptionProvider: string;
53
- speechModel?: string;
54
92
  ttsProvider: string;
55
93
  voice: string;
56
- interruptSensitivity: string;
57
94
  },
95
+ speechConfig: TwilioRelaySpeechConfig,
58
96
  relayToken?: string,
59
97
  customParameters?: Record<string, string>,
60
- hints?: string,
61
98
  ): string {
62
99
  const greetingAttr =
63
100
  welcomeGreeting && welcomeGreeting.trim().length > 0
@@ -95,16 +132,76 @@ export function generateTwiML(
95
132
  ${greetingAttr}
96
133
  voice="${escapeXml(profile.voice)}"
97
134
  language="${escapeXml(profile.language)}"
98
- transcriptionProvider="${escapeXml(profile.transcriptionProvider)}"${profile.speechModel ? `\n speechModel="${escapeXml(profile.speechModel)}"` : ""}
135
+ transcriptionProvider="${escapeXml(speechConfig.transcriptionProvider)}"${speechConfig.speechModel ? `\n speechModel="${escapeXml(speechConfig.speechModel)}"` : ""}
99
136
  ttsProvider="${escapeXml(profile.ttsProvider)}"
100
137
  interruptible="true"
101
138
  dtmfDetection="true"
102
- interruptSensitivity="${escapeXml(profile.interruptSensitivity)}"${hints ? `\n hints="${escapeXml(hints)}"` : ""}
139
+ interruptSensitivity="${escapeXml(speechConfig.interruptSensitivity)}"${speechConfig.hints ? `\n hints="${escapeXml(speechConfig.hints)}"` : ""}
103
140
  ${relayClose}
104
141
  </Connect>
105
142
  </Response>`;
106
143
  }
107
144
 
145
+ /**
146
+ * Generate `<Connect><Stream>` TwiML for the media-stream STT path.
147
+ *
148
+ * Used when the telephony STT routing resolver selects `media-stream-custom`
149
+ * (e.g. OpenAI Whisper). Twilio opens a WebSocket to `streamUrl` and sends
150
+ * raw audio frames; the daemon transcribes server-side.
151
+ *
152
+ * `callSessionId` and `token` are encoded as **path segments** on the
153
+ * WebSocket URL so the gateway can validate and route the upgrade request
154
+ * before any Twilio `start` frame arrives. Twilio Media Streams does not
155
+ * reliably preserve URL query parameters across the WebSocket upgrade, so
156
+ * path-based encoding is the primary transport for handshake metadata.
157
+ *
158
+ * Both values are also propagated as `<Parameter>` children so Twilio
159
+ * includes them in the `start` event's `customParameters` object for
160
+ * downstream observability.
161
+ */
162
+ export function generateStreamTwiML(
163
+ callSessionId: string,
164
+ streamUrl: string,
165
+ relayToken?: string,
166
+ customParameters?: Record<string, string>,
167
+ ): string {
168
+ // Build the WebSocket URL with callSessionId and token as path segments.
169
+ // Twilio Media Streams does not reliably preserve query parameters
170
+ // across the WebSocket upgrade, so path-based encoding is the primary
171
+ // transport. The gateway extracts metadata from path segments first,
172
+ // falling back to query parameters for legacy compatibility.
173
+ let fullStreamUrl = streamUrl.replace(/\/+$/, "");
174
+ fullStreamUrl += `/${encodeURIComponent(callSessionId)}`;
175
+ if (relayToken) {
176
+ fullStreamUrl += `/${encodeURIComponent(relayToken)}`;
177
+ }
178
+
179
+ // Build <Parameter> elements for the Twilio start event payload.
180
+ // Spread customParameters first so callSessionId and token cannot be
181
+ // overridden by caller-supplied values.
182
+ const allParams: Record<string, string> = {
183
+ ...customParameters,
184
+ callSessionId,
185
+ };
186
+
187
+ if (relayToken) {
188
+ allParams.token = relayToken;
189
+ }
190
+
191
+ let parameterElements = "";
192
+ for (const [key, value] of Object.entries(allParams)) {
193
+ parameterElements += `\n <Parameter name="${escapeXml(key)}" value="${escapeXml(value)}" />`;
194
+ }
195
+
196
+ return `<?xml version="1.0" encoding="UTF-8"?>
197
+ <Response>
198
+ <Connect>
199
+ <Stream url="${escapeXml(fullStreamUrl)}">${parameterElements}
200
+ </Stream>
201
+ </Connect>
202
+ </Response>`;
203
+ }
204
+
108
205
  export function buildWelcomeGreeting(
109
206
  task: string | null,
110
207
  configuredGreeting?: string,
@@ -233,8 +330,17 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
233
330
 
234
331
  /**
235
332
  * Shared TwiML generation for both inbound and outbound voice webhooks.
236
- * Resolves voice quality profile, relay URL, and welcome greeting,
237
- * then returns a Response with the generated TwiML.
333
+ *
334
+ * Resolves the telephony STT routing strategy from `services.stt.provider`
335
+ * and branches:
336
+ *
337
+ * - **`conversation-relay-native`** — emits `<Connect><ConversationRelay>`
338
+ * TwiML with Twilio-native STT attributes (`transcriptionProvider`,
339
+ * `speechModel`). Used for deepgram and google-gemini.
340
+ *
341
+ * - **`media-stream-custom`** — emits `<Connect><Stream>` TwiML so the
342
+ * daemon receives raw audio for server-side transcription. Used for
343
+ * openai-whisper.
238
344
  *
239
345
  * When `verificationSessionId` is provided, it is included as a
240
346
  * `<Parameter>` in the TwiML for observability and compatibility with
@@ -253,18 +359,137 @@ function buildVoiceWebhookTwiml(
253
359
  } | null,
254
360
  verificationSessionId?: string | null,
255
361
  ): Response {
256
- const profile = resolveVoiceQualityProfile(loadConfig());
257
-
258
- const hints = resolveCallHints(sessionContext, profile.hints);
362
+ const cfg = loadConfig();
363
+ const profile = resolveVoiceQualityProfile(cfg);
259
364
 
260
365
  log.info(
261
366
  { callSessionId, ttsProvider: profile.ttsProvider, voice: profile.voice },
262
367
  "Voice quality profile resolved",
263
368
  );
264
369
 
265
- const relayUrl = getTwilioRelayUrl(loadConfig());
266
- const welcomeGreeting = buildWelcomeGreeting(sessionContext?.task ?? null);
370
+ // Resolve telephony STT strategy from services.stt.provider.
371
+ // The routing resolver handles speech-model defaults internally per provider.
372
+ const routingResult = resolveTelephonySttRouting();
373
+
374
+ // Derive Deepgram fallback values from the provider catalog so they stay
375
+ // in sync with the single source of truth. Hardcoded strings are kept only
376
+ // as a final safety net in case the catalog entry is missing.
377
+ const dgEntry = getProviderEntry("deepgram");
378
+ const fallbackProvider =
379
+ dgEntry?.telephonyRouting.twilioNativeMapping?.provider ?? "Deepgram";
380
+ const fallbackModel =
381
+ dgEntry?.telephonyRouting.twilioNativeMapping?.defaultSpeechModel ??
382
+ "nova-3";
383
+
384
+ if (routingResult.status === "unknown-provider") {
385
+ log.error(
386
+ {
387
+ callSessionId,
388
+ providerId: routingResult.providerId,
389
+ reason: routingResult.reason,
390
+ },
391
+ "Telephony STT routing failed — unknown provider; falling back to ConversationRelay with Deepgram",
392
+ );
393
+ // Graceful degradation: fall back to Deepgram ConversationRelay so
394
+ // calls don't fail entirely on a misconfigured provider.
395
+ return buildConversationRelayResponse(
396
+ callSessionId,
397
+ cfg,
398
+ profile,
399
+ sessionContext,
400
+ verificationSessionId,
401
+ { transcriptionProvider: fallbackProvider, speechModel: fallbackModel },
402
+ );
403
+ }
404
+
405
+ const { strategy } = routingResult;
406
+
407
+ if (strategy.strategy === "conversation-relay-native") {
408
+ return buildConversationRelayResponse(
409
+ callSessionId,
410
+ cfg,
411
+ profile,
412
+ sessionContext,
413
+ verificationSessionId,
414
+ {
415
+ transcriptionProvider: strategy.transcriptionProvider,
416
+ speechModel: strategy.speechModel,
417
+ },
418
+ );
419
+ }
420
+
421
+ // media-stream-custom path — preflight check to reject interactive setup
422
+ // flows that the media-stream transport cannot support. The media-stream
423
+ // server has a defensive fallback for these cases, but catching them here
424
+ // avoids bootstrapping a WebSocket session that will immediately fail.
425
+ const session = getCallSession(callSessionId);
426
+ const from = sessionContext?.fromNumber ?? "";
427
+ const to = sessionContext?.toNumber ?? "";
267
428
 
429
+ const { outcome } = routeSetup({
430
+ callSessionId,
431
+ session: session ?? null,
432
+ from,
433
+ to,
434
+ });
435
+
436
+ // The media-stream transport supports normal_call and deny (which speaks
437
+ // a message and tears down). All other outcomes require interactive
438
+ // sub-flows (DTMF entry, name capture, guardian wait) that media-stream
439
+ // cannot perform. Reject these deterministically before stream bootstrap.
440
+ if (outcome.action !== "normal_call" && outcome.action !== "deny") {
441
+ log.warn(
442
+ {
443
+ callSessionId,
444
+ setupAction: outcome.action,
445
+ strategy: "media-stream-custom",
446
+ },
447
+ "Media-stream preflight rejected unsupported interactive setup flow — falling back to ConversationRelay with Deepgram",
448
+ );
449
+ // Fall back to ConversationRelay so the interactive flow can proceed
450
+ // through the relay server which supports it natively.
451
+ return buildConversationRelayResponse(
452
+ callSessionId,
453
+ cfg,
454
+ profile,
455
+ sessionContext,
456
+ verificationSessionId,
457
+ { transcriptionProvider: fallbackProvider, speechModel: fallbackModel },
458
+ );
459
+ }
460
+
461
+ return buildMediaStreamResponse(callSessionId, cfg, verificationSessionId);
462
+ }
463
+
464
+ /**
465
+ * Build a ConversationRelay TwiML response for Twilio-native STT providers.
466
+ */
467
+ function buildConversationRelayResponse(
468
+ callSessionId: string,
469
+ cfg: ReturnType<typeof loadConfig>,
470
+ profile: ReturnType<typeof resolveVoiceQualityProfile>,
471
+ sessionContext: {
472
+ task: string | null;
473
+ toNumber: string;
474
+ fromNumber: string;
475
+ direction: "inbound" | "outbound";
476
+ inviteFriendName: string | null;
477
+ inviteGuardianName: string | null;
478
+ } | null,
479
+ verificationSessionId: string | null | undefined,
480
+ sttAttrs: { transcriptionProvider: string; speechModel: string | undefined },
481
+ ): Response {
482
+ const rawHints = resolveCallHints(sessionContext, profile.hints);
483
+
484
+ const speechConfig: TwilioRelaySpeechConfig = {
485
+ transcriptionProvider: sttAttrs.transcriptionProvider,
486
+ speechModel: sttAttrs.speechModel,
487
+ hints: rawHints && rawHints.length > 0 ? rawHints : undefined,
488
+ interruptSensitivity: profile.interruptSensitivity,
489
+ };
490
+
491
+ const relayUrl = getTwilioRelayUrl(cfg);
492
+ const welcomeGreeting = buildWelcomeGreeting(sessionContext?.task ?? null);
268
493
  const relayToken = mintEdgeRelayToken();
269
494
 
270
495
  // Propagate verificationSessionId as a TwiML <Parameter> for
@@ -278,12 +503,51 @@ function buildVoiceWebhookTwiml(
278
503
  relayUrl,
279
504
  welcomeGreeting,
280
505
  profile,
506
+ speechConfig,
281
507
  relayToken,
282
508
  customParameters,
283
- hints || undefined,
284
509
  );
285
510
 
286
- log.info({ callSessionId }, "Returning ConversationRelay TwiML");
511
+ log.info(
512
+ {
513
+ callSessionId,
514
+ strategy: "conversation-relay-native",
515
+ transcriptionProvider: sttAttrs.transcriptionProvider,
516
+ },
517
+ "Returning ConversationRelay TwiML",
518
+ );
519
+
520
+ return new Response(twiml, {
521
+ status: 200,
522
+ headers: { "Content-Type": "text/xml" },
523
+ });
524
+ }
525
+
526
+ /**
527
+ * Build a Stream TwiML response for custom media-stream STT providers.
528
+ */
529
+ function buildMediaStreamResponse(
530
+ callSessionId: string,
531
+ cfg: ReturnType<typeof loadConfig>,
532
+ verificationSessionId: string | null | undefined,
533
+ ): Response {
534
+ const streamUrl = getTwilioMediaStreamUrl(cfg);
535
+ const relayToken = mintEdgeRelayToken();
536
+
537
+ const customParameters: Record<string, string> | undefined =
538
+ verificationSessionId ? { verificationSessionId } : undefined;
539
+
540
+ const twiml = generateStreamTwiML(
541
+ callSessionId,
542
+ streamUrl,
543
+ relayToken,
544
+ customParameters,
545
+ );
546
+
547
+ log.info(
548
+ { callSessionId, strategy: "media-stream-custom" },
549
+ "Returning Stream TwiML",
550
+ );
287
551
 
288
552
  return new Response(twiml, {
289
553
  status: 200,
@@ -1,9 +1,14 @@
1
1
  import { loadConfig } from "../config/loader.js";
2
+ import { DEFAULT_ELEVENLABS_VOICE_ID } from "../config/schemas/elevenlabs.js";
3
+ import { getTtsProvider } from "../tts/provider-registry.js";
4
+ import { resolveTtsConfig } from "../tts/tts-config-resolver.js";
5
+ import {
6
+ getNativeTwilioVoiceSpec,
7
+ resolveCallStrategy,
8
+ } from "./tts-call-strategy.js";
2
9
 
3
10
  export interface VoiceQualityProfile {
4
11
  language: string;
5
- transcriptionProvider: string;
6
- speechModel?: string;
7
12
  ttsProvider: string;
8
13
  voice: string;
9
14
  interruptSensitivity: string;
@@ -45,49 +50,87 @@ export function buildElevenLabsVoiceSpec(config: {
45
50
  /**
46
51
  * Resolve the effective voice quality profile from config.
47
52
  *
48
- * Supports ElevenLabs (default) and Fish Audio TTS providers.
49
- * When Fish Audio is selected, `ttsProvider` is set to `"Google"` as a
50
- * placeholder ConversationRelay requires a valid provider in TwiML, but
51
- * actual audio is delivered via `play` messages from the call-controller.
52
- * The voice string is left empty since it is unused in that mode.
53
+ * Uses the explicit call strategy from the TTS provider catalog to
54
+ * determine the call path. The catalog's `callMode` field
55
+ * (`"native-twilio"` vs `"synthesized-play"`) drives the decision
56
+ * rather than inferring behavior from runtime `supportsStreaming`
57
+ * capability.
53
58
  *
54
- * For ElevenLabs, the voice ID comes from the shared `elevenlabs.voiceId`
55
- * config (defaults to Amelia ZF6FPAbjXT4488VcRRnw).
59
+ * For **synthesized-play** providers (e.g. Fish Audio),
60
+ * ConversationRelay needs a valid TTS provider in TwiML, so we set
61
+ * `ttsProvider` to `"Google"` as a placeholder and leave `voice` empty
62
+ * since actual audio is delivered via `play` messages.
63
+ *
64
+ * For **native-twilio** providers (e.g. ElevenLabs), `ttsProvider` and
65
+ * `voice` are populated via the provider's registered
66
+ * {@link NativeTwilioVoiceSpec} builder so Twilio handles TTS natively.
67
+ * New native providers plug in by registering a voice-spec builder --
68
+ * no edits to this module required.
69
+ *
70
+ * NOTE: STT provider and speech model are intentionally NOT part of this
71
+ * profile. STT resolution is handled once in the voice webhook route
72
+ * (`twilio-routes.ts`) via `resolveTelephonySttRouting()` to maintain a
73
+ * single point of ownership.
56
74
  */
57
75
  export function resolveVoiceQualityProfile(
58
76
  config?: ReturnType<typeof loadConfig>,
59
77
  ): VoiceQualityProfile {
60
78
  const cfg = config ?? loadConfig();
61
79
  const voice = cfg.calls.voice;
62
- const configuredTts = voice.ttsProvider ?? "elevenlabs";
63
- const fishAudio = configuredTts === "fish-audio";
64
- const isGoogle = voice.transcriptionProvider === "Google";
65
- // Treat the legacy Deepgram default ("nova-3") as unset when provider is
66
- // Google — upgraded workspaces may still have it persisted from prior defaults.
67
- const effectiveSpeechModel =
68
- voice.speechModel == null ||
69
- (voice.speechModel === "nova-3" && isGoogle)
70
- ? isGoogle
71
- ? undefined
72
- : "nova-3"
73
- : voice.speechModel;
80
+
81
+ // Resolve the call strategy from catalog metadata.
82
+ // Falls back to native ElevenLabs when config/catalog is unavailable.
83
+ const strategy = resolveCallStrategy(cfg);
84
+
85
+ // Before committing to the catalog-derived strategy, verify the
86
+ // runtime provider is actually registered. If the provider registry
87
+ // hasn't been initialised (early startup, test mocks), fall back to
88
+ // native mode so this function and resolveCallTtsProvider agree on
89
+ // the same degraded-mode path.
90
+ let runtimeAvailable = false;
91
+ try {
92
+ const resolved = resolveTtsConfig(cfg);
93
+ getTtsProvider(resolved.provider);
94
+ runtimeAvailable = true;
95
+ } catch {
96
+ // Provider not registered — will fall through to native path below.
97
+ }
98
+
99
+ let ttsProvider: string;
100
+ let voiceSpec: string;
101
+
102
+ if (runtimeAvailable && strategy.callMode === "synthesized-play") {
103
+ // Synthesized providers stream audio via `play` messages.
104
+ // Twilio still needs a valid ttsProvider in TwiML, so use a
105
+ // placeholder and leave voice empty.
106
+ ttsProvider = "Google";
107
+ voiceSpec = "";
108
+ } else {
109
+ // Native providers: delegate voice-spec building to the
110
+ // provider's registered builder.
111
+ try {
112
+ const spec = getNativeTwilioVoiceSpec(strategy.providerId);
113
+ ttsProvider = spec.twilioProviderName;
114
+
115
+ const resolved = resolveTtsConfig(cfg);
116
+ voiceSpec = spec.buildVoiceSpec(resolved.providerConfig);
117
+ } catch {
118
+ // Voice-spec builder not registered or config unavailable --
119
+ // fall back to ElevenLabs using the config's elevenlabs block.
120
+ ttsProvider = "ElevenLabs";
121
+ voiceSpec = buildElevenLabsVoiceSpec(
122
+ cfg.services?.tts?.providers?.elevenlabs ?? {
123
+ voiceId: DEFAULT_ELEVENLABS_VOICE_ID,
124
+ },
125
+ );
126
+ }
127
+ }
128
+
74
129
  return {
75
130
  language: voice.language,
76
- transcriptionProvider: voice.transcriptionProvider,
77
- speechModel: effectiveSpeechModel,
78
- ttsProvider: fishAudio ? "Google" : "ElevenLabs",
79
- voice: fishAudio ? "" : buildElevenLabsVoiceSpec(cfg.elevenlabs),
131
+ ttsProvider,
132
+ voice: voiceSpec,
80
133
  interruptSensitivity: voice.interruptSensitivity ?? "low",
81
134
  hints: voice.hints ?? [],
82
135
  };
83
136
  }
84
-
85
- /**
86
- * Check whether Fish Audio TTS is configured for phone calls.
87
- */
88
- export function isFishAudioTts(
89
- config?: ReturnType<typeof loadConfig>,
90
- ): boolean {
91
- const cfg = config ?? loadConfig();
92
- return cfg.calls.voice?.ttsProvider === "fish-audio";
93
- }
@@ -21,6 +21,7 @@ import { buildAssistantEvent } from "../runtime/assistant-event.js";
21
21
  import { assistantEventHub } from "../runtime/assistant-event-hub.js";
22
22
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
23
23
  import { computeToolApprovalDigest } from "../security/tool-approval-digest.js";
24
+ import { createAbortReason } from "../util/abort-reasons.js";
24
25
  import { getLogger } from "../util/logger.js";
25
26
  import {
26
27
  CALL_OPENING_MARKER,
@@ -539,7 +540,13 @@ export async function startVoiceTurn(
539
540
 
540
541
  const abortFn = () => {
541
542
  if (conversation.currentRequestId === requestId) {
542
- conversation.abort();
543
+ conversation.abort(
544
+ createAbortReason(
545
+ "voice_session_aborted",
546
+ "voice-session-bridge.abortFn",
547
+ conversation.conversationId,
548
+ ),
549
+ );
543
550
  }
544
551
  };
545
552
 
@@ -145,6 +145,22 @@ export function supportsHostProxy(
145
145
  return false;
146
146
  }
147
147
 
148
+ /**
149
+ * Whether the interface can service host_browser frames via the
150
+ * ChromeExtensionRegistry (WebSocket) when an extension connection exists.
151
+ *
152
+ * Returns `true` for interfaces where the daemon should set
153
+ * `hostBrowserSenderOverride` and provision a `HostBrowserProxy` when the
154
+ * guardian has an active extension connection — currently `chrome-extension`
155
+ * and `macos`.
156
+ *
157
+ * Use this instead of hard-coding interface checks so new desktop interfaces
158
+ * only need to be added in one place.
159
+ */
160
+ export function canServiceRegistryBrowser(id: InterfaceId): boolean {
161
+ return id === "chrome-extension" || id === "macos";
162
+ }
163
+
148
164
  export interface TurnInterfaceContext {
149
165
  userMessageInterface: InterfaceId;
150
166
  assistantMessageInterface: InterfaceId;
@@ -25,10 +25,20 @@ export async function runAssistantCommandFull(
25
25
 
26
26
  const stdoutChunks: string[] = [];
27
27
  const originalWrite = process.stdout.write;
28
- process.stdout.write = ((chunk: string | Uint8Array) => {
28
+ // Override must invoke the callback (when provided) so that `Writable` streams
29
+ // piped into `process.stdout` (e.g. pino's CLI destination) can drain.
30
+ // Without this, only the first write lands and subsequent writes hang in
31
+ // backpressure. The second arg can be either an encoding string or the callback.
32
+ process.stdout.write = ((
33
+ chunk: string | Uint8Array,
34
+ encoding?: unknown,
35
+ cb?: (err?: Error | null) => void,
36
+ ) => {
29
37
  stdoutChunks.push(
30
38
  typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk),
31
39
  );
40
+ const callback = typeof encoding === "function" ? encoding : cb;
41
+ if (typeof callback === "function") callback();
32
42
  return true;
33
43
  }) as typeof process.stdout.write;
34
44