@lawpath-tech/openclaw 2026.2.21-19 → 2026.2.21-21

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 (183) hide show
  1. package/dist/{agents-C9ij96eI.js → agents-C83RLKX2.js} +4 -4
  2. package/dist/{agents.config-PFAfbBID.js → agents.config-BVjazvnK.js} +1 -1
  3. package/dist/{agents.config-CBvMKGLh.js → agents.config-dnXbFv5H.js} +1 -1
  4. package/dist/{audio-preflight-omzaYFGD.js → audio-preflight-CLqoZYgI.js} +4 -4
  5. package/dist/{audio-preflight-CODznzqu.js → audio-preflight-Da7vejCH.js} +4 -4
  6. package/dist/{auth-choice-DH2T1E9c.js → auth-choice-BJNaxS-L.js} +1 -1
  7. package/dist/{auth-choice-CLiY8PdF.js → auth-choice-Hb6530su.js} +1 -1
  8. package/dist/{banner-CqQBBjKH.js → banner-dFAx4eE_.js} +1 -1
  9. package/dist/build-info.json +3 -3
  10. package/dist/bundled/boot-md/handler.js +6 -6
  11. package/dist/bundled/session-memory/handler.js +6 -6
  12. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  13. package/dist/{channel-options-e7Z9Smf2.js → channel-options--M8F-DHl.js} +1 -1
  14. package/dist/{channel-options-CLS27y1A.js → channel-options-D0Qjq83D.js} +1 -1
  15. package/dist/{channel-web-DHMbpU2p.js → channel-web-D8hSmcPF.js} +1 -1
  16. package/dist/{channels-cli-BDHLWbaU.js → channels-cli-6uwOEBM-.js} +4 -4
  17. package/dist/{channels-cli-CDwQerVi.js → channels-cli-BtZHPcBX.js} +4 -4
  18. package/dist/{chrome-BAv9fEiv.js → chrome-Bqbv_ZSj.js} +7 -7
  19. package/dist/{chrome-lRUqqgSD.js → chrome-CZuniMYN.js} +7 -7
  20. package/dist/{cli-BGJFLB0I.js → cli-CIImVBCM.js} +1 -1
  21. package/dist/{cli-C1nypCKP.js → cli-CijjdR37.js} +1 -1
  22. package/dist/{command-registry-pGuORBuB.js → command-registry-BtDC5Ll-.js} +9 -9
  23. package/dist/{completion-cli-Bf-Ojq_a.js → completion-cli-Bw5nZmTm.js} +2 -2
  24. package/dist/{completion-cli-C79-bpAx.js → completion-cli-CI6LWHg3.js} +1 -1
  25. package/dist/{config-cli-DLdH6zIF.js → config-cli-CgjgjWri.js} +1 -1
  26. package/dist/{config-cli-BcNHb4_E.js → config-cli-Dc0TQ1yE.js} +1 -1
  27. package/dist/{configure-BSt9S0yk.js → configure-CGqsSqXB.js} +3 -3
  28. package/dist/{configure-h1d7nrgR.js → configure-GUYUjx8A.js} +3 -3
  29. package/dist/{deliver-B2d2N8OJ.js → deliver-0ThKlzQo.js} +1 -1
  30. package/dist/{deliver-CLJRPnfx.js → deliver-CKH_FhS0.js} +1 -1
  31. package/dist/{doctor-completion-BB1f594d.js → doctor-completion-DgPc1rDZ.js} +1 -1
  32. package/dist/{doctor-completion-deY10x7w.js → doctor-completion-DipoVPSi.js} +1 -1
  33. package/dist/entry.js +1 -1
  34. package/dist/extensionAPI.js +6 -6
  35. package/dist/{gateway-cli-IziQis2t.js → gateway-cli-Ds3oN72Y.js} +8 -8
  36. package/dist/{gateway-cli-B-XwzYp2.js → gateway-cli-DuMINk2B.js} +8 -8
  37. package/dist/{health-DMrTgW8V.js → health-DQjqH1zU.js} +1 -1
  38. package/dist/{health-UmuJyu1Q.js → health-Dg0bAIKS.js} +1 -1
  39. package/dist/{hooks-cli-BparoMpt.js → hooks-cli-CEtCwdza.js} +2 -2
  40. package/dist/{hooks-cli-B6WCYLyG.js → hooks-cli-Cac2Ru8r.js} +2 -2
  41. package/dist/{image--DDZnw-F.js → image-88q3KE-C.js} +1 -1
  42. package/dist/{image-Ci28h-op.js → image-CxPjVob-.js} +1 -1
  43. package/dist/index.js +6 -6
  44. package/dist/llm-slug-generator.js +6 -6
  45. package/dist/{models-nhERae29.js → models-DnhANpnd.js} +2 -2
  46. package/dist/{models-cli-bfKNgoHD.js → models-cli-03noBf8W.js} +3 -3
  47. package/dist/{models-cli-BnbeEOCU.js → models-cli-CaP_rt4y.js} +2 -2
  48. package/dist/{onboard-D_VxXTXD.js → onboard-BMc-yUv5.js} +2 -2
  49. package/dist/{onboard-C3pJ0NU-.js → onboard-BZg2c27h.js} +2 -2
  50. package/dist/{onboard-channels-CyeyvIfw.js → onboard-channels-Lt2gG1e2.js} +1 -1
  51. package/dist/{onboard-channels-YvwszRaQ.js → onboard-channels-O6KvizE3.js} +1 -1
  52. package/dist/{onboarding-0zwx3Yv7.js → onboarding-CTgBlanm.js} +3 -3
  53. package/dist/{onboarding-D0_Q5m2W.js → onboarding-G28sv5Ms.js} +3 -3
  54. package/dist/{onboarding.finalize-CYb0_Q0z.js → onboarding.finalize-Cpt9Ymcn.js} +5 -5
  55. package/dist/{onboarding.finalize-CVeI23_g.js → onboarding.finalize-D2sylHDB.js} +6 -6
  56. package/dist/{pi-embedded-D7qD6fo-.js → pi-embedded-Dz24QZz9.js} +96 -17
  57. package/dist/{pi-embedded-CwQsXrVT.js → pi-embedded-ZvazjIyF.js} +96 -17
  58. package/dist/{pi-embedded-helpers-Cd0S0WfR.js → pi-embedded-helpers-B0Kht0I2.js} +4 -4
  59. package/dist/{pi-embedded-helpers-Ll4Lztu1.js → pi-embedded-helpers-CSE0v99A.js} +4 -4
  60. package/dist/{plugin-registry-83ThmVHf.js → plugin-registry-1pF4_jNl.js} +1 -1
  61. package/dist/{plugin-registry-D57lc9ob.js → plugin-registry-CHOacv01.js} +1 -1
  62. package/dist/plugin-sdk/{accounts-DUBJHEgi.js → accounts-BcQo4OUu.js} +1 -1
  63. package/dist/plugin-sdk/{accounts-wSu5JyDM.js → accounts-DEsAwvaZ.js} +1 -1
  64. package/dist/plugin-sdk/{accounts-Ciwy7Yxd.js → accounts-fnLsjAOi.js} +3 -3
  65. package/dist/plugin-sdk/{active-listener-Bnau4oiu.js → active-listener-CeaKSm29.js} +1 -1
  66. package/dist/plugin-sdk/{agent-scope-cT7IxdHY.js → agent-scope-D5PlT0Z0.js} +2 -2
  67. package/dist/plugin-sdk/{api-key-rotation-ElOMCP9r.js → api-key-rotation-B3O1qn0m.js} +1 -1
  68. package/dist/plugin-sdk/{audio-preflight-DtozH3sJ.js → audio-preflight-CYSy3uuZ.js} +24 -24
  69. package/dist/plugin-sdk/{bindings-CKqdifyU.js → bindings-C0XEX0yK.js} +2 -2
  70. package/dist/plugin-sdk/{channel-activity-CCnr-9bS.js → channel-activity-WgE8Z4Bb.js} +1 -1
  71. package/dist/plugin-sdk/{channel-web-XYtgc8aq.js → channel-web-Nw-bUgtV.js} +22 -22
  72. package/dist/plugin-sdk/{chrome-BO7SUTgY.js → chrome-C1xnOQsc.js} +3 -3
  73. package/dist/plugin-sdk/{chunk-DyzO7IVo.js → chunk-CQk9vawA.js} +1 -1
  74. package/dist/plugin-sdk/{command-format-DcBXOX9Z.js → command-format-DHXa0Clc.js} +1 -1
  75. package/dist/plugin-sdk/{commands-registry-BsSKPwgj.js → commands-registry-yTZdwtLj.js} +4 -4
  76. package/dist/plugin-sdk/{config-9OZuRn0C.js → config--kW4R0el.js} +9 -9
  77. package/dist/plugin-sdk/{deliver-BO_wt0wd.js → deliver-C2gjA_UH.js} +10 -10
  78. package/dist/plugin-sdk/{diagnostic-DKyVS3IK.js → diagnostic-Up1aMyxZ.js} +1 -1
  79. package/dist/plugin-sdk/{image-C31B_R8P.js → image-Bcc99tVA.js} +4 -4
  80. package/dist/plugin-sdk/{image-ops-B8aJIly2.js → image-ops-C6RLbtSJ.js} +1 -1
  81. package/dist/plugin-sdk/index.js +53 -53
  82. package/dist/plugin-sdk/{ir-CD5VqKGg.js → ir-Cwi1KVG5.js} +4 -4
  83. package/dist/plugin-sdk/{local-roots-D4PCK2jj.js → local-roots-6BSl8AoA.js} +3 -3
  84. package/dist/plugin-sdk/{login-CW17h3xU.js → login-Cau5eVVj.js} +7 -7
  85. package/dist/plugin-sdk/{login-qr-CMdk06CS.js → login-qr-CLWtfA9m.js} +9 -9
  86. package/dist/plugin-sdk/{manager-CZPPxI6R.js → manager-C3Eb_8Hz.js} +8 -8
  87. package/dist/plugin-sdk/{manifest-registry-sgHvjA6E.js → manifest-registry-DvEm2HHf.js} +1 -1
  88. package/dist/plugin-sdk/{markdown-tables-Bo-r4msS.js → markdown-tables-cJqvQe9j.js} +1 -1
  89. package/dist/plugin-sdk/{message-channel-DmB4bGFh.js → message-channel-DY9wVB2A.js} +1 -1
  90. package/dist/plugin-sdk/{model-selection-CXooYLb2.js → model-selection-BjVTBZiY.js} +4 -4
  91. package/dist/plugin-sdk/{outbound-dAcQHyyr.js → outbound-Co1QSkdS.js} +7 -7
  92. package/dist/plugin-sdk/{outbound-attachment-CCxMtk8m.js → outbound-attachment-CsmRyZPI.js} +2 -2
  93. package/dist/plugin-sdk/{pi-auth-json-C-euHc3x.js → pi-auth-json-DfEOhoTK.js} +5 -5
  94. package/dist/plugin-sdk/{pi-embedded-helpers-DU0nLxRU.js → pi-embedded-helpers-D3EACnFn.js} +17 -17
  95. package/dist/plugin-sdk/plugins/runtime/types.d.ts +2 -0
  96. package/dist/plugin-sdk/{plugins-DIzw7Ncy.js → plugins-Dyz98ZJX.js} +4 -4
  97. package/dist/plugin-sdk/{pw-ai-CYmDAwXD.js → pw-ai-smoXiGqZ.js} +8 -8
  98. package/dist/plugin-sdk/{qmd-manager-DQiT4WTr.js → qmd-manager-CUhJ7WG2.js} +4 -4
  99. package/dist/plugin-sdk/{registry-O1yza0Vh.js → registry-DL-33c9X.js} +2 -2
  100. package/dist/plugin-sdk/{replies-Bnu7HnvW.js → replies-Bkaifx0j.js} +3 -3
  101. package/dist/plugin-sdk/{reply-BSOpvBX4.js → reply-Cb54ddUA.js} +158 -79
  102. package/dist/plugin-sdk/{reply-prefix-BF0omhZE.js → reply-prefix-5vdbga0R.js} +1 -1
  103. package/dist/plugin-sdk/{resolve-outbound-target-DzZIT1V1.js → resolve-outbound-target-CP7QDN14.js} +2 -2
  104. package/dist/plugin-sdk/{resolve-route-B63ztF-V.js → resolve-route-eopifBrM.js} +3 -3
  105. package/dist/plugin-sdk/{retry-JFNwfUew.js → retry-5P3d0rYw.js} +1 -1
  106. package/dist/plugin-sdk/{runner-DztRiWhh.js → runner-DqwPn56n.js} +9 -9
  107. package/dist/plugin-sdk/{send-DEV4iiFj.js → send-C2NQjVF9.js} +10 -10
  108. package/dist/plugin-sdk/{send-D-spKwjf.js → send-D_X6p9H0.js} +10 -10
  109. package/dist/plugin-sdk/{send-CK3DCPv-.js → send-DnFB-v5n.js} +6 -6
  110. package/dist/plugin-sdk/{send-D1C2sJG1.js → send-PPtjKj-0.js} +7 -7
  111. package/dist/plugin-sdk/{send-DrYvH25B.js → send-_uj_NuFI.js} +6 -6
  112. package/dist/plugin-sdk/{session-DRJl5tKC.js → session-CZngPTYk.js} +4 -4
  113. package/dist/plugin-sdk/{skill-commands-Dv5AeOZQ.js → skill-commands-Cr54qPdy.js} +5 -5
  114. package/dist/plugin-sdk/{skills-5hEda4_Y.js → skills-Dcu4o-9l.js} +7 -7
  115. package/dist/plugin-sdk/{sqlite-DUu0dwic.js → sqlite-ulpcowfN.js} +1 -1
  116. package/dist/plugin-sdk/{store-DoRXD0QB.js → store-EUYCRBvs.js} +2 -2
  117. package/dist/plugin-sdk/{subsystem-C4Rh0kdL.js → subsystem-C9WnrKN8.js} +1 -1
  118. package/dist/plugin-sdk/{tables-C_81Ve2l.js → tables-CMdQECje.js} +1 -1
  119. package/dist/plugin-sdk/{target-errors-C638yp2-.js → target-errors-CrR2lAF7.js} +2 -2
  120. package/dist/plugin-sdk/{thinking-DexKPSsI.js → thinking-C7zYA2s9.js} +5 -5
  121. package/dist/plugin-sdk/{tokens-K9ITCatc.js → tokens-2475WU2Z.js} +1 -1
  122. package/dist/plugin-sdk/{tool-images-BvjD_HnR.js → tool-images-DCvC8yL7.js} +2 -2
  123. package/dist/plugin-sdk/{tool-loop-detection-BBRtoox8.js → tool-loop-detection-D0ADW0h2.js} +2 -2
  124. package/dist/plugin-sdk/tts/tts-core.d.ts +15 -0
  125. package/dist/plugin-sdk/tts/tts.d.ts +16 -0
  126. package/dist/plugin-sdk/web-BVkjyY4A.js +65 -0
  127. package/dist/plugin-sdk/{whatsapp-actions-D8i2vCxS.js → whatsapp-actions-DC4RRNqv.js} +21 -21
  128. package/dist/{plugins-cli-BymsOcmR.js → plugins-cli-CflNTqmB.js} +2 -2
  129. package/dist/{plugins-cli-DOWA2F0z.js → plugins-cli-DW0TLzrq.js} +2 -2
  130. package/dist/{program-T05XIetU.js → program-DUkjf6lq.js} +7 -7
  131. package/dist/{program-context-CDmctW1L.js → program-context-C0-LzXoV.js} +17 -17
  132. package/dist/{prompt-select-styled-cR4-U64b.js → prompt-select-styled-CQUE1vow.js} +4 -4
  133. package/dist/{prompt-select-styled-BbzQhWF7.js → prompt-select-styled-CWNxdPrL.js} +4 -4
  134. package/dist/{provider-auth-helpers-BbnDoytN.js → provider-auth-helpers-D39L7fZC.js} +1 -1
  135. package/dist/{provider-auth-helpers-C4RNIRyy.js → provider-auth-helpers-bYUBBkmL.js} +1 -1
  136. package/dist/{push-apns-Dli4pG4y.js → push-apns-DGIqfAg5.js} +1 -1
  137. package/dist/{push-apns-DUeNpqyF.js → push-apns-Qfohz-Hs.js} +1 -1
  138. package/dist/{pw-ai-KrN0mqVH.js → pw-ai-D-_aGzdQ.js} +1 -1
  139. package/dist/{pw-ai-C-kqYO4L.js → pw-ai-S3cpSYOy.js} +1 -1
  140. package/dist/{register.agent-aY_6enDc.js → register.agent-BEM0_4uG.js} +5 -5
  141. package/dist/{register.agent-DNAtU5WP.js → register.agent-WQgVmACk.js} +6 -6
  142. package/dist/{register.configure-CwlwOMZ3.js → register.configure-CCpFmFPt.js} +6 -6
  143. package/dist/{register.configure-C_n9kIWJ.js → register.configure-CdW3aECy.js} +6 -6
  144. package/dist/{register.maintenance-DC40nnCm.js → register.maintenance-B8RZLSOe.js} +8 -8
  145. package/dist/{register.maintenance-Dddndcoo.js → register.maintenance-CM3QEdFz.js} +7 -7
  146. package/dist/{register.message-DiR6desB.js → register.message-BRLYHuVS.js} +2 -2
  147. package/dist/{register.message-CpEf0b15.js → register.message-XYm9NyDT.js} +2 -2
  148. package/dist/{register.onboard-bOeA39xj.js → register.onboard-BzAJbRpP.js} +4 -4
  149. package/dist/{register.onboard-VzVmqpEA.js → register.onboard-EwGHFUsd.js} +4 -4
  150. package/dist/{register.setup-CNLQzxl8.js → register.setup-33shhZje.js} +4 -4
  151. package/dist/{register.setup-D6iO5Xqa.js → register.setup-DpdZyiMv.js} +4 -4
  152. package/dist/{register.status-health-sessions-CoiaeduR.js → register.status-health-sessions-C8-iqepo.js} +3 -3
  153. package/dist/{register.status-health-sessions-BtJeTZnN.js → register.status-health-sessions-CK4f2nj_.js} +3 -3
  154. package/dist/{register.subclis-CFTYYdAQ.js → register.subclis-Mn68G5tp.js} +9 -9
  155. package/dist/{reply-Bv-RvRzs.js → reply-DuVUTFfT.js} +82 -3
  156. package/dist/{run-main-EzFOCEdp.js → run-main-CNB3qjRM.js} +14 -14
  157. package/dist/{runner-ChBxge-W.js → runner-D1eXJZ8T.js} +1 -1
  158. package/dist/{runner-Dq-qfrq7.js → runner-a43IsYad.js} +1 -1
  159. package/dist/{server-node-events-B9TqPvCI.js → server-node-events-B3o3P600.js} +2 -2
  160. package/dist/{server-node-events-syk21TN6.js → server-node-events-BPFwUGbS.js} +2 -2
  161. package/dist/{session-dirs-BAcQuXpY.js → session-dirs-Cw2efkey.js} +1 -1
  162. package/dist/{session-dirs-t0YpmrIL.js → session-dirs-DsU4kvIK.js} +1 -1
  163. package/dist/{status-keCWmejk.js → status-BSJIuIlN.js} +1 -1
  164. package/dist/{status-05251w21.js → status-BhHpKHQP.js} +2 -2
  165. package/dist/{status-BqDiXxwL.js → status-C2PvVLaW.js} +1 -1
  166. package/dist/{status-B8UVm7P3.js → status-CLKmcFCC.js} +2 -2
  167. package/dist/{subagent-registry-Lm4ps45z.js → subagent-registry-Da0a_Vka.js} +82 -3
  168. package/dist/{update-cli-BTF6TXGD.js → update-cli-BW3q5tFm.js} +7 -7
  169. package/dist/{update-cli-aW6jVJIJ.js → update-cli-D4EUMrX6.js} +8 -8
  170. package/dist/{web-BYZfljIx.js → web-BTbIFuWM.js} +2 -2
  171. package/dist/{web-BjxYv-hA.js → web-C7TQSVU0.js} +1 -1
  172. package/dist/{web-DPHo2cWX.js → web-CVSol55V.js} +6 -6
  173. package/dist/{web-Cs0IP-ZO.js → web-Y49Dumye.js} +6 -6
  174. package/extensions/voice-call/package.json +2 -0
  175. package/extensions/voice-call/src/config.ts +16 -3
  176. package/extensions/voice-call/src/providers/realtime-session.ts +210 -0
  177. package/extensions/voice-call/src/providers/twilio.ts +31 -13
  178. package/extensions/voice-call/src/response-generator.ts +3 -3
  179. package/extensions/voice-call/src/telephony-audio.ts +54 -0
  180. package/extensions/voice-call/src/telephony-tts.ts +40 -2
  181. package/extensions/voice-call/src/webhook.ts +174 -8
  182. package/package.json +1 -1
  183. package/dist/plugin-sdk/web-DiLYLsml.js +0 -65
@@ -8,13 +8,13 @@ import { c as resolveIdentityNamePrefix, l as resolveMessagePrefix, t as createR
8
8
  import "./github-copilot-token-ttqQRqMA.js";
9
9
  import { r as formatCliCommand } from "./env-DulyEJtM.js";
10
10
  import "./tokens-DnMmat9I.js";
11
- import { C as shouldComputeCommandAuthorized, S as hasControlCommand, T as normalizeMentionText, _ as enqueueSystemEvent, a as resolveMentionGating, b as formatInboundEnvelope, c as recordPendingHistoryEntryIfEnabled, f as createDedupeCache, g as formatDurationPrecise, h as parseActivationCommand, i as buildPairingReply, l as shouldAckReactionForWhatsApp, m as normalizeGroupActivation, n as computeBackoff, o as DEFAULT_GROUP_HISTORY_LIMIT, p as getReplyFromConfig, r as sleepWithAbort, s as buildHistoryContextFromEntries, u as dispatchReplyWithBufferedBlockDispatcher, v as createInboundDebouncer, w as buildMentionRegexes, x as resolveEnvelopeFormatOptions, y as resolveInboundDebounceMs } from "./pi-embedded-CwQsXrVT.js";
11
+ import { C as shouldComputeCommandAuthorized, S as hasControlCommand, T as normalizeMentionText, _ as enqueueSystemEvent, a as resolveMentionGating, b as formatInboundEnvelope, c as recordPendingHistoryEntryIfEnabled, f as createDedupeCache, g as formatDurationPrecise, h as parseActivationCommand, i as buildPairingReply, l as shouldAckReactionForWhatsApp, m as normalizeGroupActivation, n as computeBackoff, o as DEFAULT_GROUP_HISTORY_LIMIT, p as getReplyFromConfig, r as sleepWithAbort, s as buildHistoryContextFromEntries, u as dispatchReplyWithBufferedBlockDispatcher, v as createInboundDebouncer, w as buildMentionRegexes, x as resolveEnvelopeFormatOptions, y as resolveInboundDebounceMs } from "./pi-embedded-ZvazjIyF.js";
12
12
  import "./plugins-B90TBXkV.js";
13
13
  import { i as logWebSelfId, l as readWebSelfId, n as WA_WEB_AUTH_DIR, p as webAuthExists, r as getWebAuthAgeMs, s as pickWebChannel, t as resolveWhatsAppAccount } from "./accounts-Cxza2UXO.js";
14
14
  import "./bindings-q3EHMdG1.js";
15
15
  import "./send-Chks8Sx_.js";
16
16
  import "./send-D4cCHXzJ.js";
17
- import "./deliver-B2d2N8OJ.js";
17
+ import "./deliver-0ThKlzQo.js";
18
18
  import "./diagnostic-DzmNBZt3.js";
19
19
  import "./diagnostic-session-state-C1vRJs5w.js";
20
20
  import "./accounts-sr0G5UOe.js";
@@ -22,10 +22,10 @@ import { G as formatLocationText, J as readChannelAllowFromStore, K as toLocatio
22
22
  import "./image-ops-BfqdVNxh.js";
23
23
  import "./pi-model-discovery-j5tVLINv.js";
24
24
  import "./message-channel-DNOtJsL1.js";
25
- import { Ct as resolveGroupSessionKey, H as loadSessionStore, K as updateLastRoute, U as readSessionUpdatedAt, W as recordSessionMetaFromInbound } from "./pi-embedded-helpers-Cd0S0WfR.js";
25
+ import { Ct as resolveGroupSessionKey, H as loadSessionStore, K as updateLastRoute, U as readSessionUpdatedAt, W as recordSessionMetaFromInbound } from "./pi-embedded-helpers-B0Kht0I2.js";
26
26
  import { n as loadConfig } from "./config-KL7847B1.js";
27
27
  import "./manifest-registry-sze5OW-s.js";
28
- import "./chrome-lRUqqgSD.js";
28
+ import "./chrome-CZuniMYN.js";
29
29
  import "./skills-DGNBryo1.js";
30
30
  import "./redact-1NGYV_8p.js";
31
31
  import "./errors-CPfngF0S.js";
@@ -35,7 +35,7 @@ import { h as resolveChannelGroupRequireMention, m as resolveChannelGroupPolicy
35
35
  import "./accounts-DAZL5b5o.js";
36
36
  import { s as resolveStorePath } from "./paths-5iQF9bSz.js";
37
37
  import "./tool-images-TQiVqsOc.js";
38
- import "./image--DDZnw-F.js";
38
+ import "./image-88q3KE-C.js";
39
39
  import "./manager-CsXJuzK4.js";
40
40
  import "./gemini-auth-BF6ZCNOV.js";
41
41
  import "./sqlite-CmTvWxxq.js";
@@ -49,7 +49,7 @@ import "./render-B1VqYyvo.js";
49
49
  import "./commands-registry-DEXQtgBS.js";
50
50
  import "./skill-commands-CGvIAIiQ.js";
51
51
  import { t as finalizeInboundContext } from "./inbound-context-QOi5vzUt.js";
52
- import { p as registerUnhandledRejectionHandler } from "./runner-Dq-qfrq7.js";
52
+ import { p as registerUnhandledRejectionHandler } from "./runner-a43IsYad.js";
53
53
  import "./fetch-BMa0enEg.js";
54
54
  import { n as recordChannelActivity } from "./channel-activity-7fb2OxTV.js";
55
55
  import { t as convertMarkdownTables } from "./tables-Ds9lRo34.js";
@@ -5,7 +5,7 @@ import "./agent-scope-DSUeV7-g.js";
5
5
  import { r as defaultRuntime, t as createSubsystemLogger } from "./subsystem-BF9jT_Nw.js";
6
6
  import "./workspace-DYx5Dxtw.js";
7
7
  import "./tokens-B-c56SkM.js";
8
- import { C as resolveEnvelopeFormatOptions, D as normalizeMentionText, E as buildMentionRegexes, S as formatInboundEnvelope, T as shouldComputeCommandAuthorized, _ as parseActivationCommand, b as createInboundDebouncer, c as DEFAULT_GROUP_HISTORY_LIMIT, d as shouldAckReactionForWhatsApp, f as dispatchReplyWithBufferedBlockDispatcher, g as normalizeGroupActivation, h as getReplyFromConfig, l as buildHistoryContextFromEntries, m as createDedupeCache, n as computeBackoff, o as buildPairingReply, r as sleepWithAbort, s as resolveMentionGating, u as recordPendingHistoryEntryIfEnabled, v as formatDurationPrecise, w as hasControlCommand, x as resolveInboundDebounceMs, y as enqueueSystemEvent } from "./pi-embedded-D7qD6fo-.js";
8
+ import { C as resolveEnvelopeFormatOptions, D as normalizeMentionText, E as buildMentionRegexes, S as formatInboundEnvelope, T as shouldComputeCommandAuthorized, _ as parseActivationCommand, b as createInboundDebouncer, c as DEFAULT_GROUP_HISTORY_LIMIT, d as shouldAckReactionForWhatsApp, f as dispatchReplyWithBufferedBlockDispatcher, g as normalizeGroupActivation, h as getReplyFromConfig, l as buildHistoryContextFromEntries, m as createDedupeCache, n as computeBackoff, o as buildPairingReply, r as sleepWithAbort, s as resolveMentionGating, u as recordPendingHistoryEntryIfEnabled, v as formatDurationPrecise, w as hasControlCommand, x as resolveInboundDebounceMs, y as enqueueSystemEvent } from "./pi-embedded-Dz24QZz9.js";
9
9
  import "./plugins-DIrNa311.js";
10
10
  import { i as logWebSelfId, l as readWebSelfId, n as WA_WEB_AUTH_DIR, p as webAuthExists, r as getWebAuthAgeMs, s as pickWebChannel, t as resolveWhatsAppAccount } from "./accounts-C8m4ADkw.js";
11
11
  import "./boolean-B8-BqKGQ.js";
@@ -13,7 +13,7 @@ import { t as formatCliCommand } from "./command-format-BOHIfoCX.js";
13
13
  import "./bindings-BKEEBdsb.js";
14
14
  import "./send-Byt_wJDK.js";
15
15
  import "./send-BEFzSOoD.js";
16
- import "./deliver-CLJRPnfx.js";
16
+ import "./deliver-CKH_FhS0.js";
17
17
  import "./diagnostic-DriDPU6M.js";
18
18
  import "./diagnostic-session-state-Bxo4UHOL.js";
19
19
  import "./accounts-CQT5u65w.js";
@@ -23,10 +23,10 @@ import "./model-auth-vtNH8C4P.js";
23
23
  import "./github-copilot-token-Dgb9dAHW.js";
24
24
  import "./pi-model-discovery-DaNAekda.js";
25
25
  import "./message-channel-CHYSjmbr.js";
26
- import { Ct as resolveGroupSessionKey, G as updateLastRoute, H as loadSessionStore, U as readSessionUpdatedAt, W as recordSessionMetaFromInbound } from "./pi-embedded-helpers-Ll4Lztu1.js";
26
+ import { Ct as resolveGroupSessionKey, G as updateLastRoute, H as loadSessionStore, U as readSessionUpdatedAt, W as recordSessionMetaFromInbound } from "./pi-embedded-helpers-CSE0v99A.js";
27
27
  import { n as loadConfig } from "./config-Du_-inxH.js";
28
28
  import "./manifest-registry-Dhk4A5vb.js";
29
- import "./chrome-BAv9fEiv.js";
29
+ import "./chrome-Bqbv_ZSj.js";
30
30
  import "./frontmatter-CdkBcBAo.js";
31
31
  import "./skills-DSZuhFx9.js";
32
32
  import "./redact-jSxx6Ep2.js";
@@ -37,7 +37,7 @@ import { h as resolveChannelGroupRequireMention, m as resolveChannelGroupPolicy
37
37
  import "./accounts-DFrH9IVU.js";
38
38
  import { s as resolveStorePath } from "./paths-BEAbheM8.js";
39
39
  import "./tool-images-C1rdDr34.js";
40
- import "./image-Ci28h-op.js";
40
+ import "./image-CxPjVob-.js";
41
41
  import { c as resolveIdentityNamePrefix, l as resolveMessagePrefix, t as createReplyPrefixOptions } from "./reply-prefix-DZrS3Kf7.js";
42
42
  import "./manager-D7SRrHxT.js";
43
43
  import "./gemini-auth-A2JaRMHH.js";
@@ -52,7 +52,7 @@ import "./render-CDCvpfhh.js";
52
52
  import "./commands-registry-BzlB3qtZ.js";
53
53
  import "./skill-commands-CqyVDpti.js";
54
54
  import { t as finalizeInboundContext } from "./inbound-context-BlHX0H3E.js";
55
- import { p as registerUnhandledRejectionHandler } from "./runner-ChBxge-W.js";
55
+ import { p as registerUnhandledRejectionHandler } from "./runner-D1eXJZ8T.js";
56
56
  import "./fetch-DtI0mtzx.js";
57
57
  import { n as recordChannelActivity } from "./channel-activity-BgsTqLau.js";
58
58
  import { t as convertMarkdownTables } from "./tables-C3clT_iA.js";
@@ -4,6 +4,8 @@
4
4
  "description": "OpenClaw voice-call plugin",
5
5
  "type": "module",
6
6
  "dependencies": {
7
+ "@openai/agents": "^0.8.1",
8
+ "@openai/agents-extensions": "^0.8.1",
7
9
  "@sinclair/typebox": "0.34.48",
8
10
  "ws": "^8.19.0",
9
11
  "zod": "^4.3.6"
@@ -213,8 +213,8 @@ export const VoiceCallStreamingConfigSchema = z
213
213
  openaiApiKey: z.string().min(1).optional(),
214
214
  /** OpenAI transcription model (default: gpt-4o-transcribe) */
215
215
  sttModel: z.string().min(1).default("gpt-4o-transcribe"),
216
- /** VAD silence duration in ms before considering speech ended */
217
- silenceDurationMs: z.number().int().positive().default(800),
216
+ /** VAD silence duration in ms before considering speech ended (lower = snappier turn detection) */
217
+ silenceDurationMs: z.number().int().positive().default(500),
218
218
  /** VAD threshold 0-1 (higher = less sensitive) */
219
219
  vadThreshold: z.number().min(0).max(1).default(0.5),
220
220
  /** WebSocket path for media stream connections */
@@ -225,7 +225,7 @@ export const VoiceCallStreamingConfigSchema = z
225
225
  enabled: false,
226
226
  sttProvider: "openai-realtime",
227
227
  sttModel: "gpt-4o-transcribe",
228
- silenceDurationMs: 800,
228
+ silenceDurationMs: 500,
229
229
  vadThreshold: 0.5,
230
230
  streamPath: "/voice/stream",
231
231
  });
@@ -331,6 +331,19 @@ export const VoiceCallConfigSchema = z
331
331
 
332
332
  /** Timeout for response generation in ms (default 30s) */
333
333
  responseTimeoutMs: z.number().int().positive().default(30000),
334
+
335
+ /**
336
+ * Response mode:
337
+ * - "embedded": Use the embedded Pi agent (STT → LLM → TTS chain). Full tool support but higher latency.
338
+ * - "realtime": Use OpenAI Realtime API for native speech-to-speech. Much lower latency (~200-500ms).
339
+ */
340
+ responseMode: z.enum(["embedded", "realtime"]).default("embedded"),
341
+
342
+ /** Model for realtime speech-to-speech mode (only used when responseMode is "realtime") */
343
+ realtimeModel: z.string().default("gpt-4o-realtime-preview"),
344
+
345
+ /** Voice for realtime mode (only used when responseMode is "realtime") */
346
+ realtimeVoice: z.string().default("verse"),
334
347
  })
335
348
  .strict();
336
349
 
@@ -0,0 +1,210 @@
1
+ /**
2
+ * OpenAI Realtime API session provider for voice calls.
3
+ *
4
+ * Replaces the STT → LLM → TTS chain with native speech-to-speech via the
5
+ * OpenAI Agents SDK RealtimeSession + TwilioRealtimeTransportLayer.
6
+ * Achieves ~200-500ms response latency vs 2-14s with the embedded agent.
7
+ */
8
+
9
+ import { execFile } from "node:child_process";
10
+ import { promisify } from "node:util";
11
+ import type { WebSocket as NodeWebSocket } from "ws";
12
+ import { RealtimeAgent, RealtimeSession, tool, backgroundResult } from "@openai/agents/realtime";
13
+ import { TwilioRealtimeTransportLayer } from "@openai/agents-extensions";
14
+ import { z } from "zod";
15
+ import type { VoiceCallConfig } from "../config.js";
16
+
17
+ const execFileAsync = promisify(execFile);
18
+
19
+ const SCRIPT_TIMEOUT_MS = 15_000;
20
+
21
+ async function runScript(command: string, args: string[]): Promise<string> {
22
+ try {
23
+ const { stdout, stderr } = await execFileAsync(command, args, {
24
+ timeout: SCRIPT_TIMEOUT_MS,
25
+ maxBuffer: 1024 * 512,
26
+ env: process.env,
27
+ });
28
+ return stdout.trim() || stderr.trim() || "(no output)";
29
+ } catch (err) {
30
+ const msg = err instanceof Error ? err.message : String(err);
31
+ return `Error: ${msg}`;
32
+ }
33
+ }
34
+
35
+ function buildRealtimeTools() {
36
+ const stripeGross = tool({
37
+ name: "stripe_gross_mtd",
38
+ description:
39
+ "Get Stripe gross revenue month-to-date. Returns current MTD figures, daily average, and target tracking.",
40
+ parameters: z.object({}),
41
+ execute: async () => {
42
+ return backgroundResult(
43
+ await runScript("node", ["/root/clawd/skills/stripe/scripts/stripe.js", "gross-mtd"]),
44
+ );
45
+ },
46
+ });
47
+
48
+ const stripeDailySummary = tool({
49
+ name: "stripe_daily_summary",
50
+ description:
51
+ "Get Stripe daily summary for a specific date (charges, refunds, net). Defaults to yesterday if no date provided.",
52
+ parameters: z.object({
53
+ date: z.string().optional().describe("Date in YYYY-MM-DD format. Defaults to yesterday."),
54
+ }),
55
+ execute: async ({ date }) => {
56
+ const args = ["/root/clawd/skills/stripe/scripts/stripe.js", "daily-summary"];
57
+ if (date) args.push("--date", date);
58
+ return backgroundResult(await runScript("node", args));
59
+ },
60
+ });
61
+
62
+ const webSearch = tool({
63
+ name: "web_search",
64
+ description:
65
+ "Search the web for current information. Use for news, current events, factual lookups.",
66
+ parameters: z.object({
67
+ query: z.string().describe("Search query"),
68
+ }),
69
+ execute: async ({ query }) => {
70
+ return backgroundResult(
71
+ await runScript("node", [
72
+ "-e",
73
+ `import('undici').then(({fetch})=>fetch('https://api.search.brave.com/res/v1/web/search?q='+encodeURIComponent(${JSON.stringify(query)}),{headers:{'X-Subscription-Token':process.env.BRAVE_SEARCH_API_KEY||''}}).then(r=>r.json()).then(d=>{const results=(d.web?.results||[]).slice(0,5).map(r=>r.title+': '+r.description).join('\\n');console.log(results||'No results')})).catch(e=>console.error(e.message))`,
74
+ ]),
75
+ );
76
+ },
77
+ });
78
+
79
+ const memoryRecall = tool({
80
+ name: "memory_recall",
81
+ description:
82
+ "Recall stored memories and context. Use to check preferences, past decisions, or remembered facts.",
83
+ parameters: z.object({
84
+ query: z.string().describe("What to recall — topic, person name, or context"),
85
+ }),
86
+ execute: async ({ query }) => {
87
+ return backgroundResult(
88
+ await runScript("node", [
89
+ "/root/clawd/skills/memory/scripts/memory.js",
90
+ "recall",
91
+ "UCH9TS4TB",
92
+ query,
93
+ ]),
94
+ );
95
+ },
96
+ });
97
+
98
+ const uptimeCheck = tool({
99
+ name: "uptime_check",
100
+ description: "Check current uptime status of monitored services via UptimeRobot.",
101
+ parameters: z.object({}),
102
+ execute: async () => {
103
+ return backgroundResult(
104
+ await runScript("node", ["/root/clawd/skills/uptime-robot/scripts/uptime.js"]),
105
+ );
106
+ },
107
+ });
108
+
109
+ const shellExec = tool({
110
+ name: "run_command",
111
+ description:
112
+ "Execute a shell command on the server. Use for quick checks like date, uptime, disk usage, git status, etc.",
113
+ parameters: z.object({
114
+ command: z.string().describe("Shell command to run"),
115
+ }),
116
+ execute: async ({ command }) => {
117
+ return backgroundResult(await runScript("bash", ["-c", command]));
118
+ },
119
+ });
120
+
121
+ return [stripeGross, stripeDailySummary, webSearch, memoryRecall, uptimeCheck, shellExec];
122
+ }
123
+
124
+ export type RealtimeCallSession = {
125
+ session: RealtimeSession;
126
+ close: () => void;
127
+ };
128
+
129
+ export function createRealtimeCallSession(params: {
130
+ twilioWebSocket: WebSocket | NodeWebSocket;
131
+ voiceConfig: VoiceCallConfig;
132
+ callId: string;
133
+ from: string;
134
+ }): RealtimeCallSession {
135
+ const { twilioWebSocket, voiceConfig, callId, from } = params;
136
+
137
+ const systemPrompt =
138
+ voiceConfig.responseSystemPrompt ??
139
+ `You are Clawpath — Lawpath's AI intern — on a PHONE CALL. You're Australian, warm, and casual. You naturally say things like "no worries", "reckon", "too easy", "beauty". You're a proud Rabbitohs fan.
140
+
141
+ VOICE RULES (critical):
142
+ - MAX 2 short sentences per reply. This is spoken aloud, not text.
143
+ - NEVER use lists, bullet points, markdown, asterisks, or numbered items.
144
+ - NEVER read out URLs or long technical details.
145
+ - Use tools when the caller asks you to look something up, check data, or do something. Say "gimme a sec" first so they know you're working on it.
146
+ - If the caller says bye, goodbye, or wants to end the call, say a brief farewell and include [END_CALL] at the end of your response.
147
+ - Match the caller's energy — quick question gets a quick answer, yarn gets a yarn.`;
148
+
149
+ const agent = new RealtimeAgent({
150
+ name: "Clawpath",
151
+ instructions: `${systemPrompt}\n\nThe caller's phone number is ${from}. Call ID: ${callId}.`,
152
+ tools: buildRealtimeTools(),
153
+ });
154
+
155
+ const voice = voiceConfig.realtimeVoice || "verse";
156
+
157
+ const transport = new TwilioRealtimeTransportLayer({
158
+ twilioWebSocket,
159
+ });
160
+
161
+ const model = voiceConfig.realtimeModel || "gpt-4o-realtime-preview";
162
+
163
+ const session = new RealtimeSession(agent, {
164
+ transport,
165
+ model,
166
+ config: {
167
+ audio: {
168
+ output: {
169
+ voice,
170
+ },
171
+ },
172
+ },
173
+ tracingDisabled: true,
174
+ });
175
+
176
+ session.on("error", (error: unknown) => {
177
+ console.error(`[voice-call] [realtime] Session error for call ${callId}:`, error);
178
+ });
179
+
180
+ session.on("agent_updated" as any, (newAgent: unknown) => {
181
+ console.log(`[voice-call] [realtime] Agent updated for call ${callId}:`, newAgent);
182
+ });
183
+
184
+ const apiKey = voiceConfig.streaming?.openaiApiKey || process.env.OPENAI_API_KEY;
185
+ if (!apiKey) {
186
+ throw new Error("OpenAI API key required for realtime voice mode");
187
+ }
188
+
189
+ session
190
+ .connect({ apiKey })
191
+ .then(() => {
192
+ console.log(
193
+ `[voice-call] [realtime] Connected for call ${callId} (model=${model}, voice=${voice})`,
194
+ );
195
+ })
196
+ .catch((err) => {
197
+ console.error(`[voice-call] [realtime] Connection failed for call ${callId}:`, err);
198
+ });
199
+
200
+ return {
201
+ session,
202
+ close: () => {
203
+ try {
204
+ session.close();
205
+ } catch {
206
+ // best effort
207
+ }
208
+ },
209
+ };
210
+ }
@@ -565,30 +565,48 @@ export class TwilioProvider implements VoiceCallProvider {
565
565
  throw new Error("TTS provider and media stream handler required");
566
566
  }
567
567
 
568
- // Stream audio in 20ms chunks (160 bytes at 8kHz mu-law)
569
- const CHUNK_SIZE = 160;
568
+ const CHUNK_SIZE = 160; // 20ms at 8kHz mu-law
570
569
  const CHUNK_DELAY_MS = 20;
571
-
572
570
  const handler = this.mediaStreamHandler;
573
571
  const ttsProvider = this.ttsProvider;
572
+
574
573
  await handler.queueTts(streamSid, async (signal) => {
575
- // Generate audio with core TTS (returns mu-law at 8kHz)
574
+ // Try streaming TTS first (sends audio as chunks arrive from API)
575
+ if (ttsProvider.synthesizeForTelephonyStream) {
576
+ try {
577
+ let framesEmitted = 0;
578
+ const stream = ttsProvider.synthesizeForTelephonyStream(text);
579
+ for await (const muLawChunk of stream) {
580
+ if (signal.aborted) break;
581
+ for (const chunk of chunkAudio(muLawChunk, CHUNK_SIZE)) {
582
+ if (signal.aborted) break;
583
+ handler.sendAudio(streamSid, chunk);
584
+ framesEmitted++;
585
+ await new Promise((resolve) => setTimeout(resolve, CHUNK_DELAY_MS));
586
+ }
587
+ }
588
+ if (!signal.aborted && framesEmitted > 0) {
589
+ handler.sendMark(streamSid, `tts-${Date.now()}`);
590
+ }
591
+ return;
592
+ } catch (err) {
593
+ console.warn(
594
+ `[voice-call] Streaming TTS failed, falling back to buffered:`,
595
+ err instanceof Error ? err.message : err,
596
+ );
597
+ }
598
+ }
599
+
600
+ // Fallback: buffered TTS (full audio generated before sending)
576
601
  const muLawAudio = await ttsProvider.synthesizeForTelephony(text);
577
602
  for (const chunk of chunkAudio(muLawAudio, CHUNK_SIZE)) {
578
- if (signal.aborted) {
579
- break;
580
- }
603
+ if (signal.aborted) break;
581
604
  handler.sendAudio(streamSid, chunk);
582
-
583
- // Pace the audio to match real-time playback
584
605
  await new Promise((resolve) => setTimeout(resolve, CHUNK_DELAY_MS));
585
- if (signal.aborted) {
586
- break;
587
- }
606
+ if (signal.aborted) break;
588
607
  }
589
608
 
590
609
  if (!signal.aborted) {
591
- // Send a mark to track when audio finishes
592
610
  handler.sendMark(streamSid, `tts-${Date.now()}`);
593
611
  }
594
612
  });
@@ -59,9 +59,9 @@ export async function generateVoiceResponse(
59
59
  }
60
60
  const cfg = coreConfig;
61
61
 
62
- // Build voice-specific session key based on phone number
63
- const normalizedPhone = from.replace(/\D/g, "");
64
- const sessionKey = `voice:${normalizedPhone}`;
62
+ // Build voice-specific session key per call (not per phone number)
63
+ // so conversation history doesn't leak between separate calls
64
+ const sessionKey = `voice:${callId}`;
65
65
  const agentId = "main";
66
66
 
67
67
  // Resolve paths
@@ -67,6 +67,60 @@ export function chunkAudio(audio: Buffer, chunkSize = 160): Generator<Buffer, vo
67
67
  })();
68
68
  }
69
69
 
70
+ /**
71
+ * Stateful incremental PCM-to-mulaw converter for streaming.
72
+ * Handles odd-byte chunk boundaries between calls.
73
+ */
74
+ export type PcmToMulawStreamState = {
75
+ oddByte: number | null;
76
+ srcPosCarry: number;
77
+ };
78
+
79
+ export function createPcmToMulawStreamState(): PcmToMulawStreamState {
80
+ return { oddByte: null, srcPosCarry: 0 };
81
+ }
82
+
83
+ export function convertPcmChunkToMulaw8k(
84
+ chunk: Buffer,
85
+ inputSampleRate: number,
86
+ state: PcmToMulawStreamState,
87
+ ): Buffer {
88
+ let data = chunk;
89
+ if (state.oddByte !== null) {
90
+ data = Buffer.concat([Buffer.from([state.oddByte]), chunk]);
91
+ state.oddByte = null;
92
+ }
93
+ if (data.length % 2 !== 0) {
94
+ state.oddByte = data[data.length - 1]!;
95
+ data = data.subarray(0, data.length - 1);
96
+ }
97
+
98
+ if (data.length === 0) return Buffer.alloc(0);
99
+
100
+ const inputSamples = data.length / 2;
101
+ const ratio = inputSampleRate / TELEPHONY_SAMPLE_RATE;
102
+ const outputSamples: number[] = [];
103
+
104
+ let srcPos = state.srcPosCarry;
105
+ while (srcPos < inputSamples) {
106
+ const srcIndex = Math.floor(srcPos);
107
+ const frac = srcPos - srcIndex;
108
+ const s0 = data.readInt16LE(srcIndex * 2);
109
+ const s1Index = Math.min(srcIndex + 1, inputSamples - 1);
110
+ const s1 = data.readInt16LE(s1Index * 2);
111
+ const sample = Math.round(s0 + frac * (s1 - s0));
112
+ outputSamples.push(linearToMulaw(clamp16(sample)));
113
+ srcPos += ratio;
114
+ }
115
+ state.srcPosCarry = srcPos - inputSamples;
116
+
117
+ const output = Buffer.alloc(outputSamples.length);
118
+ for (let i = 0; i < outputSamples.length; i++) {
119
+ output[i] = outputSamples[i]!;
120
+ }
121
+ return output;
122
+ }
123
+
70
124
  function linearToMulaw(sample: number): number {
71
125
  const BIAS = 132;
72
126
  const CLIP = 32635;
@@ -1,6 +1,11 @@
1
+ import type { Readable } from "node:stream";
1
2
  import type { VoiceCallTtsConfig } from "./config.js";
2
3
  import type { CoreConfig } from "./core-bridge.js";
3
- import { convertPcmToMulaw8k } from "./telephony-audio.js";
4
+ import {
5
+ convertPcmToMulaw8k,
6
+ convertPcmChunkToMulaw8k,
7
+ createPcmToMulawStreamState,
8
+ } from "./telephony-audio.js";
4
9
 
5
10
  export type TelephonyTtsRuntime = {
6
11
  textToSpeechTelephony: (params: {
@@ -14,10 +19,18 @@ export type TelephonyTtsRuntime = {
14
19
  provider?: string;
15
20
  error?: string;
16
21
  }>;
22
+ textToSpeechTelephonyStream?: (params: { text: string; cfg: CoreConfig }) => Promise<{
23
+ success: boolean;
24
+ stream?: ReadableStream<Uint8Array>;
25
+ sampleRate?: number;
26
+ cleanup?: () => void;
27
+ error?: string;
28
+ }>;
17
29
  };
18
30
 
19
31
  export type TelephonyTtsProvider = {
20
32
  synthesizeForTelephony: (text: string) => Promise<Buffer>;
33
+ synthesizeForTelephonyStream?: (text: string) => AsyncGenerator<Buffer, void, unknown>;
21
34
  };
22
35
 
23
36
  const BLOCKED_MERGE_KEYS = new Set(["__proto__", "prototype", "constructor"]);
@@ -30,7 +43,7 @@ export function createTelephonyTtsProvider(params: {
30
43
  const { coreConfig, ttsOverride, runtime } = params;
31
44
  const mergedConfig = applyTtsOverride(coreConfig, ttsOverride);
32
45
 
33
- return {
46
+ const provider: TelephonyTtsProvider = {
34
47
  synthesizeForTelephony: async (text: string) => {
35
48
  const result = await runtime.textToSpeechTelephony({
36
49
  text,
@@ -44,6 +57,31 @@ export function createTelephonyTtsProvider(params: {
44
57
  return convertPcmToMulaw8k(result.audioBuffer, result.sampleRate);
45
58
  },
46
59
  };
60
+
61
+ if (runtime.textToSpeechTelephonyStream) {
62
+ const streamFn = runtime.textToSpeechTelephonyStream;
63
+ provider.synthesizeForTelephonyStream = async function* (text: string) {
64
+ const result = await streamFn({ text, cfg: mergedConfig });
65
+ if (!result.success || !result.stream || !result.sampleRate) {
66
+ throw new Error(result.error ?? "TTS streaming failed");
67
+ }
68
+ const state = createPcmToMulawStreamState();
69
+ const reader = result.stream.getReader();
70
+ try {
71
+ while (true) {
72
+ const { done, value } = await reader.read();
73
+ if (done) break;
74
+ const mulaw = convertPcmChunkToMulaw8k(Buffer.from(value), result.sampleRate, state);
75
+ if (mulaw.length > 0) yield mulaw;
76
+ }
77
+ } finally {
78
+ reader.releaseLock();
79
+ result.cleanup?.();
80
+ }
81
+ };
82
+ }
83
+
84
+ return provider;
47
85
  }
48
86
 
49
87
  function applyTtsOverride(coreConfig: CoreConfig, override?: VoiceCallTtsConfig): CoreConfig {