@openclaw/discord 2026.9.5 → 2026.9.6

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 (421) hide show
  1. package/dist/.setup/{activities-api-DBwRt5ES.mjs → activities-api-COU4-0nR.mjs} +4 -4
  2. package/dist/.setup/{approval-handler.runtime-Du3n5ypX.mjs → approval-handler.runtime-CbBktBb4.mjs} +5 -5
  3. package/dist/.setup/{approval-message-updates-BN7JtScm.mjs → approval-message-updates-jf9zMaIC.mjs} +1 -1
  4. package/dist/.setup/{audit-C8uaKoa9.mjs → audit-vKbh-LFR.mjs} +3 -3
  5. package/dist/.setup/{channel-ChKdlBgH.mjs → channel-Dv4e3Jpc.mjs} +19 -16
  6. package/dist/.setup/{channel-actions.runtime-CJmiFfps.mjs → channel-actions.runtime-leZuw01r.mjs} +13 -7
  7. package/dist/.setup/{channel.setup-BlC3-Kzz.mjs → channel.setup-0NX6FFwi.mjs} +1 -1
  8. package/dist/.setup/{components-MqMWUWnE.mjs → components-yBEb75bB.mjs} +1 -2
  9. package/dist/.setup/{conversation-binding-route-_rz33QHO.mjs → conversation-binding-route-B7qImK_b.mjs} +6 -3
  10. package/dist/.setup/{doctor-DxcdkXzz.mjs → doctor-D1R-uFIu.mjs} +2 -2
  11. package/dist/.setup/{handle-action.guild-admin-DAFyh_yV.mjs → handle-action.guild-admin-DcFa8NwA.mjs} +3 -3
  12. package/dist/.setup/{message-handler-DnUrDpaU.mjs → message-handler-X6ejmbtY.mjs} +6 -5
  13. package/dist/.setup/{message-handler.preflight-CtqaF_fb.mjs → message-handler.preflight-DE_POxFc.mjs} +14 -117
  14. package/dist/.setup/message-handler.preflight-pluralkit-8Xji34AF.mjs +143 -0
  15. package/dist/.setup/{message-handler.process-Dz1xvuQU.mjs → message-handler.process-CCmIVnxy.mjs} +387 -317
  16. package/dist/.setup/{outbound-adapter-C8v79z9Z.mjs → outbound-adapter-ClIgmu4F.mjs} +5 -5
  17. package/dist/.setup/{outbound-session-route-CqWbuMOm.mjs → outbound-session-route-VulUE0yV.mjs} +7 -6
  18. package/dist/.setup/{preflight-audio-DrnqFA8U.mjs → preflight-audio-DHGIHEpv.mjs} +2 -1
  19. package/dist/.setup/{provider-72zIvN-i.mjs → provider-iOf73HW-.mjs} +425 -286
  20. package/dist/.setup/{provider-session.runtime-BW7QrF5C.mjs → provider-session.runtime-DrGAYnZv.mjs} +3 -3
  21. package/dist/.setup/provider.runtime-BmjW_pIw.mjs +2 -0
  22. package/dist/.setup/{realtime-session.runtime-DTccTgTM.mjs → realtime-session.runtime-Dz8uVutq.mjs} +338 -297
  23. package/dist/.setup/receive-recovery-DHst8WRx.mjs +517 -0
  24. package/dist/.setup/{retry-DBfcEgmO.mjs → retry-BEYkDy0P.mjs} +78 -13
  25. package/dist/.setup/{runtime-SchNfUsQ.mjs → runtime-B8lJfyrA.mjs} +30 -11
  26. package/dist/.setup/{runtime-BVoa7hhx.mjs → runtime-DisDbixd.mjs} +1 -1
  27. package/dist/.setup/{send-CKDczUNL.mjs → send-CIBzvXjS.mjs} +23 -9
  28. package/dist/.setup/{send.components-DWQjXhPV.mjs → send.components-ChY21qr-.mjs} +4 -4
  29. package/dist/.setup/{send.outbound-D0hQ7s50.mjs → send.outbound-QTyuFupn.mjs} +15 -16
  30. package/dist/.setup/{send.shared-Zwx6BxQ5.mjs → send.shared-VNvWfX2T.mjs} +2 -2
  31. package/dist/.setup/setup-plugin-api-Dvr41dyx.mjs +3 -0
  32. package/dist/.setup/{shared-CaA3Vv4n.mjs → shared-DHPDaMSx.mjs} +1 -1
  33. package/dist/.setup/{subagent-hooks-Bired9q6.mjs → subagent-hooks-CjW7r8Pe.mjs} +1 -1
  34. package/dist/.setup/{target-resolver-B6MrxbYv.mjs → target-resolver-BXpy6VT8.mjs} +1 -1
  35. package/dist/.setup/targets-CvemhN3i.mjs +3 -0
  36. package/dist/.setup/{thread-bindings-CK5272V3.mjs → thread-bindings-CEh2dhqP.mjs} +3 -3
  37. package/dist/.setup/{thread-bindings.manager-eexkKyrG.mjs → thread-bindings.manager-ve-i6oqt.mjs} +94 -45
  38. package/dist/.setup/{transcripts-source-CuWYgayu.mjs → transcripts-source-DVegW0WI.mjs} +61 -7
  39. package/dist/.setup/{voice-runtime-DHVQ2mFz.mjs → voice-runtime-elTtjEIG.mjs} +553 -901
  40. package/dist/action-runtime-api.js +1 -1
  41. package/dist/activities-api.js +1 -1
  42. package/dist/api.js +10 -10
  43. package/dist/channel-plugin-api.js +1 -1
  44. package/dist/index.js +1 -1
  45. package/dist/runtime-api.js +12 -12
  46. package/dist/setup-entry.js +1 -1
  47. package/dist/setup-plugin-api.js +1 -1
  48. package/dist/src/voice/audio-worker.runtime.js +1065 -0
  49. package/dist/subagent-hooks-api.js +1 -1
  50. package/dist/transcripts-source-api.js +1 -1
  51. package/node_modules/@napi-rs/wasm-runtime/dist/emnapi-plugins.cjs +8 -4
  52. package/node_modules/@napi-rs/wasm-runtime/dist/emnapi-plugins.js +8 -4
  53. package/node_modules/@napi-rs/wasm-runtime/dist/fs-proxy.cjs +1 -1
  54. package/node_modules/@napi-rs/wasm-runtime/dist/fs.js +770 -695
  55. package/node_modules/@napi-rs/wasm-runtime/fs-proxy.js +3 -2
  56. package/node_modules/@napi-rs/wasm-runtime/package.json +1 -1
  57. package/node_modules/@tybys/wasm-util/LICENSE +21 -0
  58. package/node_modules/@tybys/wasm-util/package.json +1 -1
  59. package/node_modules/@types/node/README.md +1 -1
  60. package/node_modules/@types/node/package.json +2 -2
  61. package/node_modules/@types/node/web-globals/streams.d.ts +4 -0
  62. package/node_modules/zod/README.md +2 -2
  63. package/node_modules/zod/compile.cjs +19 -0
  64. package/node_modules/zod/index.cjs +19 -0
  65. package/node_modules/zod/locales/index.cjs +19 -0
  66. package/node_modules/zod/mini/index.cjs +19 -0
  67. package/node_modules/zod/package.json +8 -7
  68. package/node_modules/zod/src/v3/helpers/errorUtil.ts +1 -1
  69. package/node_modules/zod/src/v4/classic/external.ts +2 -0
  70. package/node_modules/zod/src/v4/classic/from-json-schema.ts +248 -25
  71. package/node_modules/zod/src/v4/classic/schemas.ts +243 -168
  72. package/node_modules/zod/src/v4/classic/tests/assignability.test.ts +60 -9
  73. package/node_modules/zod/src/v4/classic/tests/bigint.test.ts +6 -0
  74. package/node_modules/zod/src/v4/classic/tests/continuability.test.ts +2 -2
  75. package/node_modules/zod/src/v4/classic/tests/cyclic-data.test.ts +265 -0
  76. package/node_modules/zod/src/v4/classic/tests/discriminated-unions.test.ts +112 -6
  77. package/node_modules/zod/src/v4/classic/tests/enum.test.ts +9 -0
  78. package/node_modules/zod/src/v4/classic/tests/error.test.ts +130 -1
  79. package/node_modules/zod/src/v4/classic/tests/from-json-schema.test.ts +157 -0
  80. package/node_modules/zod/src/v4/classic/tests/iban.test.ts +81 -0
  81. package/node_modules/zod/src/v4/classic/tests/instance-footprint.test.ts +40 -2
  82. package/node_modules/zod/src/v4/classic/tests/instanceof.test.ts +27 -7
  83. package/node_modules/zod/src/v4/classic/tests/issue-schema.test.ts +88 -56
  84. package/node_modules/zod/src/v4/classic/tests/number.test.ts +39 -0
  85. package/node_modules/zod/src/v4/classic/tests/object.test.ts +56 -0
  86. package/node_modules/zod/src/v4/classic/tests/optin-ladder.test.ts +3 -2
  87. package/node_modules/zod/src/v4/classic/tests/prefault.test.ts +70 -0
  88. package/node_modules/zod/src/v4/classic/tests/properties.test.ts +141 -0
  89. package/node_modules/zod/src/v4/classic/tests/recursive-types.test.ts +46 -1
  90. package/node_modules/zod/src/v4/classic/tests/standard-schema.test.ts +19 -0
  91. package/node_modules/zod/src/v4/classic/tests/string.test.ts +95 -1
  92. package/node_modules/zod/src/v4/classic/tests/template-literal.test.ts +33 -1
  93. package/node_modules/zod/src/v4/classic/tests/to-json-schema.test.ts +234 -38
  94. package/node_modules/zod/src/v4/classic/tests/validate.test.ts +569 -0
  95. package/node_modules/zod/src/v4/core/api.ts +44 -28
  96. package/node_modules/zod/src/v4/core/checks.ts +62 -102
  97. package/node_modules/zod/src/v4/core/compile.ts +218 -93
  98. package/node_modules/zod/src/v4/core/core.ts +1 -3
  99. package/node_modules/zod/src/v4/core/doc.ts +6 -2
  100. package/node_modules/zod/src/v4/core/errors.ts +3 -6
  101. package/node_modules/zod/src/v4/core/json-schema-generator.ts +2 -2
  102. package/node_modules/zod/src/v4/core/json-schema-processors.ts +185 -81
  103. package/node_modules/zod/src/v4/core/memoizer.ts +75 -28
  104. package/node_modules/zod/src/v4/core/parse.ts +37 -16
  105. package/node_modules/zod/src/v4/core/regexes.ts +14 -4
  106. package/node_modules/zod/src/v4/core/schemas.ts +296 -132
  107. package/node_modules/zod/src/v4/core/tests/cached.test.ts +54 -0
  108. package/node_modules/zod/src/v4/core/tests/compile-differential.test.ts +35 -0
  109. package/node_modules/zod/src/v4/core/tests/compile-generative.test.ts +445 -0
  110. package/node_modules/zod/src/v4/core/tests/compile.test.ts +399 -2
  111. package/node_modules/zod/src/v4/core/tests/email-regex.test.ts +140 -0
  112. package/node_modules/zod/src/v4/core/tests/locales/tg.test.ts +85 -0
  113. package/node_modules/zod/src/v4/core/tests/polyfill-collision.test.ts +46 -0
  114. package/node_modules/zod/src/v4/core/tests/url-no-canparse.test.ts +118 -16
  115. package/node_modules/zod/src/v4/core/to-json-schema.ts +9 -7
  116. package/node_modules/zod/src/v4/core/util.ts +218 -147
  117. package/node_modules/zod/src/v4/core/versions.ts +2 -2
  118. package/node_modules/zod/src/v4/locales/ar.ts +2 -0
  119. package/node_modules/zod/src/v4/locales/az.ts +2 -0
  120. package/node_modules/zod/src/v4/locales/be.ts +2 -0
  121. package/node_modules/zod/src/v4/locales/bg.ts +2 -0
  122. package/node_modules/zod/src/v4/locales/bn.ts +2 -0
  123. package/node_modules/zod/src/v4/locales/ca.ts +2 -0
  124. package/node_modules/zod/src/v4/locales/ckb.ts +2 -0
  125. package/node_modules/zod/src/v4/locales/cs.ts +2 -0
  126. package/node_modules/zod/src/v4/locales/da.ts +2 -0
  127. package/node_modules/zod/src/v4/locales/de.ts +2 -0
  128. package/node_modules/zod/src/v4/locales/el.ts +2 -0
  129. package/node_modules/zod/src/v4/locales/en.ts +2 -0
  130. package/node_modules/zod/src/v4/locales/eo.ts +2 -0
  131. package/node_modules/zod/src/v4/locales/es.ts +2 -0
  132. package/node_modules/zod/src/v4/locales/fa.ts +2 -0
  133. package/node_modules/zod/src/v4/locales/fi.ts +2 -0
  134. package/node_modules/zod/src/v4/locales/fr-CA.ts +2 -0
  135. package/node_modules/zod/src/v4/locales/fr.ts +2 -0
  136. package/node_modules/zod/src/v4/locales/gu.ts +2 -0
  137. package/node_modules/zod/src/v4/locales/he.ts +2 -0
  138. package/node_modules/zod/src/v4/locales/hi.ts +2 -0
  139. package/node_modules/zod/src/v4/locales/hr.ts +2 -0
  140. package/node_modules/zod/src/v4/locales/hu.ts +2 -0
  141. package/node_modules/zod/src/v4/locales/hy.ts +2 -0
  142. package/node_modules/zod/src/v4/locales/id.ts +2 -0
  143. package/node_modules/zod/src/v4/locales/index.ts +1 -0
  144. package/node_modules/zod/src/v4/locales/is.ts +2 -0
  145. package/node_modules/zod/src/v4/locales/it.ts +2 -0
  146. package/node_modules/zod/src/v4/locales/ja.ts +2 -0
  147. package/node_modules/zod/src/v4/locales/ka.ts +2 -0
  148. package/node_modules/zod/src/v4/locales/km.ts +2 -0
  149. package/node_modules/zod/src/v4/locales/kn.ts +2 -0
  150. package/node_modules/zod/src/v4/locales/ko.ts +2 -0
  151. package/node_modules/zod/src/v4/locales/lt.ts +2 -0
  152. package/node_modules/zod/src/v4/locales/mk.ts +2 -0
  153. package/node_modules/zod/src/v4/locales/ms.ts +2 -0
  154. package/node_modules/zod/src/v4/locales/ne.ts +2 -0
  155. package/node_modules/zod/src/v4/locales/nl.ts +2 -0
  156. package/node_modules/zod/src/v4/locales/nn.ts +2 -0
  157. package/node_modules/zod/src/v4/locales/no.ts +2 -0
  158. package/node_modules/zod/src/v4/locales/ota.ts +2 -0
  159. package/node_modules/zod/src/v4/locales/pl.ts +2 -0
  160. package/node_modules/zod/src/v4/locales/ps.ts +2 -0
  161. package/node_modules/zod/src/v4/locales/pt-BR.ts +2 -0
  162. package/node_modules/zod/src/v4/locales/pt.ts +2 -0
  163. package/node_modules/zod/src/v4/locales/ro.ts +2 -0
  164. package/node_modules/zod/src/v4/locales/ru.ts +2 -0
  165. package/node_modules/zod/src/v4/locales/sk.ts +2 -0
  166. package/node_modules/zod/src/v4/locales/sl.ts +2 -0
  167. package/node_modules/zod/src/v4/locales/sv.ts +2 -0
  168. package/node_modules/zod/src/v4/locales/ta.ts +2 -0
  169. package/node_modules/zod/src/v4/locales/tg.ts +135 -0
  170. package/node_modules/zod/src/v4/locales/th.ts +2 -0
  171. package/node_modules/zod/src/v4/locales/tk.ts +2 -0
  172. package/node_modules/zod/src/v4/locales/tr.ts +2 -0
  173. package/node_modules/zod/src/v4/locales/uk.ts +2 -0
  174. package/node_modules/zod/src/v4/locales/ur.ts +2 -0
  175. package/node_modules/zod/src/v4/locales/uz.ts +2 -0
  176. package/node_modules/zod/src/v4/locales/vi.ts +2 -0
  177. package/node_modules/zod/src/v4/locales/yo.ts +2 -0
  178. package/node_modules/zod/src/v4/locales/zh-CN.ts +2 -0
  179. package/node_modules/zod/src/v4/locales/zh-TW.ts +2 -0
  180. package/node_modules/zod/src/v4/mini/external.ts +2 -0
  181. package/node_modules/zod/src/v4/mini/schemas.ts +32 -7
  182. package/node_modules/zod/src/v4/mini/tests/checks.test.ts +1 -1
  183. package/node_modules/zod/src/v4/mini/tests/computed.test.ts +11 -17
  184. package/node_modules/zod/src/v4/mini/tests/index.test.ts +86 -0
  185. package/node_modules/zod/src/v4/mini/tests/recursive-types.test.ts +16 -0
  186. package/node_modules/zod/src/v4/mini/tests/string.test.ts +23 -0
  187. package/node_modules/zod/v3/ZodError.cjs +19 -0
  188. package/node_modules/zod/v3/errors.cjs +19 -0
  189. package/node_modules/zod/v3/external.cjs +19 -0
  190. package/node_modules/zod/v3/helpers/enumUtil.cjs +19 -0
  191. package/node_modules/zod/v3/helpers/errorUtil.cjs +20 -1
  192. package/node_modules/zod/v3/helpers/errorUtil.js +1 -1
  193. package/node_modules/zod/v3/helpers/parseUtil.cjs +19 -0
  194. package/node_modules/zod/v3/helpers/partialUtil.cjs +19 -0
  195. package/node_modules/zod/v3/helpers/typeAliases.cjs +19 -0
  196. package/node_modules/zod/v3/helpers/util.cjs +19 -0
  197. package/node_modules/zod/v3/index.cjs +19 -0
  198. package/node_modules/zod/v3/standard-schema.cjs +19 -0
  199. package/node_modules/zod/v3/types.cjs +19 -0
  200. package/node_modules/zod/v4/classic/checks.cjs +19 -0
  201. package/node_modules/zod/v4/classic/coerce.cjs +19 -0
  202. package/node_modules/zod/v4/classic/compat.cjs +19 -0
  203. package/node_modules/zod/v4/classic/deep-partial.cjs +19 -0
  204. package/node_modules/zod/v4/classic/errors.cjs +19 -0
  205. package/node_modules/zod/v4/classic/external.cjs +22 -1
  206. package/node_modules/zod/v4/classic/external.d.cts +1 -1
  207. package/node_modules/zod/v4/classic/external.d.ts +1 -1
  208. package/node_modules/zod/v4/classic/external.js +1 -1
  209. package/node_modules/zod/v4/classic/from-json-schema.cjs +264 -24
  210. package/node_modules/zod/v4/classic/from-json-schema.js +245 -24
  211. package/node_modules/zod/v4/classic/in-out.cjs +19 -0
  212. package/node_modules/zod/v4/classic/index.cjs +19 -0
  213. package/node_modules/zod/v4/classic/iso.cjs +19 -0
  214. package/node_modules/zod/v4/classic/parse.cjs +19 -0
  215. package/node_modules/zod/v4/classic/schemas.cjs +100 -35
  216. package/node_modules/zod/v4/classic/schemas.d.cts +15 -1
  217. package/node_modules/zod/v4/classic/schemas.d.ts +15 -1
  218. package/node_modules/zod/v4/classic/schemas.js +77 -33
  219. package/node_modules/zod/v4/core/api.cjs +45 -22
  220. package/node_modules/zod/v4/core/api.d.cts +12 -6
  221. package/node_modules/zod/v4/core/api.d.ts +12 -6
  222. package/node_modules/zod/v4/core/api.js +25 -22
  223. package/node_modules/zod/v4/core/checks.cjs +51 -97
  224. package/node_modules/zod/v4/core/checks.d.cts +17 -1
  225. package/node_modules/zod/v4/core/checks.d.ts +17 -1
  226. package/node_modules/zod/v4/core/checks.js +31 -96
  227. package/node_modules/zod/v4/core/compile.cjs +173 -78
  228. package/node_modules/zod/v4/core/compile.d.cts +18 -4
  229. package/node_modules/zod/v4/core/compile.d.ts +18 -4
  230. package/node_modules/zod/v4/core/compile.js +154 -79
  231. package/node_modules/zod/v4/core/core.cjs +20 -1
  232. package/node_modules/zod/v4/core/core.js +1 -1
  233. package/node_modules/zod/v4/core/doc.cjs +26 -2
  234. package/node_modules/zod/v4/core/doc.js +7 -2
  235. package/node_modules/zod/v4/core/errors.cjs +21 -5
  236. package/node_modules/zod/v4/core/errors.js +2 -5
  237. package/node_modules/zod/v4/core/index.cjs +19 -0
  238. package/node_modules/zod/v4/core/json-schema-generator.cjs +20 -1
  239. package/node_modules/zod/v4/core/json-schema-generator.js +2 -2
  240. package/node_modules/zod/v4/core/json-schema-processors.cjs +188 -72
  241. package/node_modules/zod/v4/core/json-schema-processors.d.cts +16 -0
  242. package/node_modules/zod/v4/core/json-schema-processors.d.ts +16 -0
  243. package/node_modules/zod/v4/core/json-schema-processors.js +170 -74
  244. package/node_modules/zod/v4/core/json-schema.cjs +19 -0
  245. package/node_modules/zod/v4/core/memoizer.cjs +111 -23
  246. package/node_modules/zod/v4/core/memoizer.js +69 -23
  247. package/node_modules/zod/v4/core/parse.cjs +56 -16
  248. package/node_modules/zod/v4/core/parse.js +37 -16
  249. package/node_modules/zod/v4/core/regexes.cjs +31 -6
  250. package/node_modules/zod/v4/core/regexes.d.cts +3 -0
  251. package/node_modules/zod/v4/core/regexes.d.ts +3 -0
  252. package/node_modules/zod/v4/core/regexes.js +10 -4
  253. package/node_modules/zod/v4/core/registries.cjs +19 -0
  254. package/node_modules/zod/v4/core/schemas.cjs +302 -114
  255. package/node_modules/zod/v4/core/schemas.d.cts +28 -8
  256. package/node_modules/zod/v4/core/schemas.d.ts +28 -8
  257. package/node_modules/zod/v4/core/schemas.js +279 -113
  258. package/node_modules/zod/v4/core/standard-schema.cjs +19 -0
  259. package/node_modules/zod/v4/core/to-json-schema.cjs +28 -7
  260. package/node_modules/zod/v4/core/to-json-schema.d.cts +4 -3
  261. package/node_modules/zod/v4/core/to-json-schema.d.ts +4 -3
  262. package/node_modules/zod/v4/core/to-json-schema.js +7 -6
  263. package/node_modules/zod/v4/core/util.cjs +177 -143
  264. package/node_modules/zod/v4/core/util.d.cts +25 -4
  265. package/node_modules/zod/v4/core/util.d.ts +25 -4
  266. package/node_modules/zod/v4/core/util.js +156 -143
  267. package/node_modules/zod/v4/core/versions.cjs +21 -2
  268. package/node_modules/zod/v4/core/versions.d.cts +1 -1
  269. package/node_modules/zod/v4/core/versions.d.ts +1 -1
  270. package/node_modules/zod/v4/core/versions.js +2 -2
  271. package/node_modules/zod/v4/core/visit.cjs +19 -0
  272. package/node_modules/zod/v4/index.cjs +19 -0
  273. package/node_modules/zod/v4/locales/ar.cjs +2 -0
  274. package/node_modules/zod/v4/locales/ar.js +2 -0
  275. package/node_modules/zod/v4/locales/az.cjs +2 -0
  276. package/node_modules/zod/v4/locales/az.js +2 -0
  277. package/node_modules/zod/v4/locales/be.cjs +2 -0
  278. package/node_modules/zod/v4/locales/be.js +2 -0
  279. package/node_modules/zod/v4/locales/bg.cjs +2 -0
  280. package/node_modules/zod/v4/locales/bg.js +2 -0
  281. package/node_modules/zod/v4/locales/bn.cjs +2 -0
  282. package/node_modules/zod/v4/locales/bn.js +2 -0
  283. package/node_modules/zod/v4/locales/ca.cjs +2 -0
  284. package/node_modules/zod/v4/locales/ca.js +2 -0
  285. package/node_modules/zod/v4/locales/ckb.cjs +2 -0
  286. package/node_modules/zod/v4/locales/ckb.js +2 -0
  287. package/node_modules/zod/v4/locales/cs.cjs +2 -0
  288. package/node_modules/zod/v4/locales/cs.js +2 -0
  289. package/node_modules/zod/v4/locales/da.cjs +2 -0
  290. package/node_modules/zod/v4/locales/da.js +2 -0
  291. package/node_modules/zod/v4/locales/de.cjs +2 -0
  292. package/node_modules/zod/v4/locales/de.js +2 -0
  293. package/node_modules/zod/v4/locales/el.cjs +2 -0
  294. package/node_modules/zod/v4/locales/el.js +2 -0
  295. package/node_modules/zod/v4/locales/en.cjs +2 -0
  296. package/node_modules/zod/v4/locales/en.js +2 -0
  297. package/node_modules/zod/v4/locales/eo.cjs +2 -0
  298. package/node_modules/zod/v4/locales/eo.js +2 -0
  299. package/node_modules/zod/v4/locales/es.cjs +2 -0
  300. package/node_modules/zod/v4/locales/es.js +2 -0
  301. package/node_modules/zod/v4/locales/fa.cjs +2 -0
  302. package/node_modules/zod/v4/locales/fa.js +2 -0
  303. package/node_modules/zod/v4/locales/fi.cjs +2 -0
  304. package/node_modules/zod/v4/locales/fi.js +2 -0
  305. package/node_modules/zod/v4/locales/fr-CA.cjs +2 -0
  306. package/node_modules/zod/v4/locales/fr-CA.js +2 -0
  307. package/node_modules/zod/v4/locales/fr.cjs +2 -0
  308. package/node_modules/zod/v4/locales/fr.js +2 -0
  309. package/node_modules/zod/v4/locales/gu.cjs +2 -0
  310. package/node_modules/zod/v4/locales/gu.js +2 -0
  311. package/node_modules/zod/v4/locales/he.cjs +2 -0
  312. package/node_modules/zod/v4/locales/he.js +2 -0
  313. package/node_modules/zod/v4/locales/hi.cjs +2 -0
  314. package/node_modules/zod/v4/locales/hi.js +2 -0
  315. package/node_modules/zod/v4/locales/hr.cjs +2 -0
  316. package/node_modules/zod/v4/locales/hr.js +2 -0
  317. package/node_modules/zod/v4/locales/hu.cjs +2 -0
  318. package/node_modules/zod/v4/locales/hu.js +2 -0
  319. package/node_modules/zod/v4/locales/hy.cjs +2 -0
  320. package/node_modules/zod/v4/locales/hy.js +2 -0
  321. package/node_modules/zod/v4/locales/id.cjs +2 -0
  322. package/node_modules/zod/v4/locales/id.js +2 -0
  323. package/node_modules/zod/v4/locales/index.cjs +22 -1
  324. package/node_modules/zod/v4/locales/index.d.cts +1 -0
  325. package/node_modules/zod/v4/locales/index.d.ts +1 -0
  326. package/node_modules/zod/v4/locales/index.js +1 -0
  327. package/node_modules/zod/v4/locales/is.cjs +2 -0
  328. package/node_modules/zod/v4/locales/is.js +2 -0
  329. package/node_modules/zod/v4/locales/it.cjs +2 -0
  330. package/node_modules/zod/v4/locales/it.js +2 -0
  331. package/node_modules/zod/v4/locales/ja.cjs +2 -0
  332. package/node_modules/zod/v4/locales/ja.js +2 -0
  333. package/node_modules/zod/v4/locales/ka.cjs +2 -0
  334. package/node_modules/zod/v4/locales/ka.js +2 -0
  335. package/node_modules/zod/v4/locales/km.cjs +2 -0
  336. package/node_modules/zod/v4/locales/km.js +2 -0
  337. package/node_modules/zod/v4/locales/kn.cjs +2 -0
  338. package/node_modules/zod/v4/locales/kn.js +2 -0
  339. package/node_modules/zod/v4/locales/ko.cjs +2 -0
  340. package/node_modules/zod/v4/locales/ko.js +2 -0
  341. package/node_modules/zod/v4/locales/lt.cjs +2 -0
  342. package/node_modules/zod/v4/locales/lt.js +2 -0
  343. package/node_modules/zod/v4/locales/mk.cjs +2 -0
  344. package/node_modules/zod/v4/locales/mk.js +2 -0
  345. package/node_modules/zod/v4/locales/ms.cjs +2 -0
  346. package/node_modules/zod/v4/locales/ms.js +2 -0
  347. package/node_modules/zod/v4/locales/ne.cjs +2 -0
  348. package/node_modules/zod/v4/locales/ne.js +2 -0
  349. package/node_modules/zod/v4/locales/nl.cjs +2 -0
  350. package/node_modules/zod/v4/locales/nl.js +2 -0
  351. package/node_modules/zod/v4/locales/nn.cjs +2 -0
  352. package/node_modules/zod/v4/locales/nn.js +2 -0
  353. package/node_modules/zod/v4/locales/no.cjs +2 -0
  354. package/node_modules/zod/v4/locales/no.js +2 -0
  355. package/node_modules/zod/v4/locales/ota.cjs +2 -0
  356. package/node_modules/zod/v4/locales/ota.js +2 -0
  357. package/node_modules/zod/v4/locales/pl.cjs +2 -0
  358. package/node_modules/zod/v4/locales/pl.js +2 -0
  359. package/node_modules/zod/v4/locales/ps.cjs +2 -0
  360. package/node_modules/zod/v4/locales/ps.js +2 -0
  361. package/node_modules/zod/v4/locales/pt-BR.cjs +2 -0
  362. package/node_modules/zod/v4/locales/pt-BR.js +2 -0
  363. package/node_modules/zod/v4/locales/pt.cjs +2 -0
  364. package/node_modules/zod/v4/locales/pt.js +2 -0
  365. package/node_modules/zod/v4/locales/ro.cjs +2 -0
  366. package/node_modules/zod/v4/locales/ro.js +2 -0
  367. package/node_modules/zod/v4/locales/ru.cjs +2 -0
  368. package/node_modules/zod/v4/locales/ru.js +2 -0
  369. package/node_modules/zod/v4/locales/sk.cjs +2 -0
  370. package/node_modules/zod/v4/locales/sk.js +2 -0
  371. package/node_modules/zod/v4/locales/sl.cjs +2 -0
  372. package/node_modules/zod/v4/locales/sl.js +2 -0
  373. package/node_modules/zod/v4/locales/sv.cjs +2 -0
  374. package/node_modules/zod/v4/locales/sv.js +2 -0
  375. package/node_modules/zod/v4/locales/ta.cjs +2 -0
  376. package/node_modules/zod/v4/locales/ta.js +2 -0
  377. package/node_modules/zod/v4/locales/tg.cjs +144 -0
  378. package/node_modules/zod/v4/locales/tg.d.cts +5 -0
  379. package/node_modules/zod/v4/locales/tg.d.ts +4 -0
  380. package/node_modules/zod/v4/locales/tg.js +117 -0
  381. package/node_modules/zod/v4/locales/th.cjs +2 -0
  382. package/node_modules/zod/v4/locales/th.js +2 -0
  383. package/node_modules/zod/v4/locales/tk.cjs +2 -0
  384. package/node_modules/zod/v4/locales/tk.js +2 -0
  385. package/node_modules/zod/v4/locales/tr.cjs +2 -0
  386. package/node_modules/zod/v4/locales/tr.js +2 -0
  387. package/node_modules/zod/v4/locales/uk.cjs +2 -0
  388. package/node_modules/zod/v4/locales/uk.js +2 -0
  389. package/node_modules/zod/v4/locales/ur.cjs +2 -0
  390. package/node_modules/zod/v4/locales/ur.js +2 -0
  391. package/node_modules/zod/v4/locales/uz.cjs +2 -0
  392. package/node_modules/zod/v4/locales/uz.js +2 -0
  393. package/node_modules/zod/v4/locales/vi.cjs +2 -0
  394. package/node_modules/zod/v4/locales/vi.js +2 -0
  395. package/node_modules/zod/v4/locales/yo.cjs +2 -0
  396. package/node_modules/zod/v4/locales/yo.js +2 -0
  397. package/node_modules/zod/v4/locales/zh-CN.cjs +2 -0
  398. package/node_modules/zod/v4/locales/zh-CN.js +2 -0
  399. package/node_modules/zod/v4/locales/zh-TW.cjs +2 -0
  400. package/node_modules/zod/v4/locales/zh-TW.js +2 -0
  401. package/node_modules/zod/v4/mini/checks.cjs +19 -0
  402. package/node_modules/zod/v4/mini/coerce.cjs +19 -0
  403. package/node_modules/zod/v4/mini/deep-partial.cjs +19 -0
  404. package/node_modules/zod/v4/mini/external.cjs +22 -1
  405. package/node_modules/zod/v4/mini/external.d.cts +1 -1
  406. package/node_modules/zod/v4/mini/external.d.ts +1 -1
  407. package/node_modules/zod/v4/mini/external.js +1 -1
  408. package/node_modules/zod/v4/mini/in-out.cjs +19 -0
  409. package/node_modules/zod/v4/mini/index.cjs +19 -0
  410. package/node_modules/zod/v4/mini/iso.cjs +19 -0
  411. package/node_modules/zod/v4/mini/parse.cjs +19 -0
  412. package/node_modules/zod/v4/mini/schemas.cjs +44 -8
  413. package/node_modules/zod/v4/mini/schemas.d.cts +6 -1
  414. package/node_modules/zod/v4/mini/schemas.d.ts +6 -1
  415. package/node_modules/zod/v4/mini/schemas.js +21 -6
  416. package/node_modules/zod/v4-mini/index.cjs +19 -0
  417. package/package.json +8 -5
  418. package/dist/.setup/message-handler.history-CJOsN9w5.mjs +0 -28
  419. package/dist/.setup/provider.runtime-DuhmKdDU.mjs +0 -2
  420. package/dist/.setup/setup-plugin-api-CINIEBLa.mjs +0 -3
  421. package/dist/.setup/targets-D1mAB5yO.mjs +0 -3
@@ -1,135 +1,31 @@
1
1
  import { Mt as getGuildVoiceState, Ut as __exportAll, l as ReadyListener, p as VoiceStateUpdateListener, t as discord_exports, u as ResumedListener, y as isUnknownDiscordVoiceStateError } from "./discord-BXpHW-cu.mjs";
2
2
  import { h as resolveDiscordOwnerAccess, o as normalizeDiscordSlug, x as formatDiscordUserTag } from "./channel-type-DKnjV1XW.mjs";
3
3
  import { t as getDiscordRuntime } from "./runtime-DgnVQ7zW.mjs";
4
- import { y as parseDiscordTarget } from "./retry-DBfcEgmO.mjs";
5
- import { o as formatMention } from "./send.outbound-D0hQ7s50.mjs";
6
- import { c as resolveDiscordVoiceAccess, l as resolveDiscordVoiceAccessTarget, n as resolveDiscordTranscriptsCapture, o as resolveDiscordVoiceEnabled, s as authorizeDiscordVoiceIngress } from "./transcripts-source-CuWYgayu.mjs";
7
- import { m as buildDiscordGroupSystemPrompt } from "./provider-72zIvN-i.mjs";
8
- import { createRequire } from "node:module";
4
+ import { y as parseDiscordTarget } from "./retry-BEYkDy0P.mjs";
5
+ import { o as formatMention } from "./send.outbound-QTyuFupn.mjs";
6
+ import { c as authorizeDiscordVoiceIngress, l as resolveDiscordVoiceAccess, r as resolveDiscordTranscriptsCapture, s as resolveDiscordVoiceEnabled, t as bindDiscordCaptureReceipts, u as resolveDiscordVoiceAccessTarget } from "./transcripts-source-DVegW0WI.mjs";
7
+ import { a as finishVoiceDecryptRecovery, c as resetVoiceReceiveRecoveryState, m as writeVoiceWavFile, n as analyzeVoiceReceiveError, o as noteVoiceDecryptFailure, r as createVoiceReceiveRecoveryState, t as DECRYPT_FAILURE_WINDOW_MS, y as restoreDiscordAudioError } from "./receive-recovery-DHst8WRx.mjs";
8
+ import { m as buildDiscordGroupSystemPrompt } from "./provider-iOf73HW-.mjs";
9
9
  import { randomUUID } from "node:crypto";
10
10
  import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
11
11
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
12
12
  import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
13
- import { formatErrorMessage as formatErrorMessage$1, readErrorName } from "openclaw/plugin-sdk/error-runtime";
13
+ import { formatErrorMessage as formatErrorMessage$1, readErrorName, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
14
14
  import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
15
15
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
16
- import { createSubsystemLogger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
16
+ import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
17
17
  import { escapeRegExp, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
18
18
  import { stripInlineDirectiveTagsForDisplay } from "openclaw/plugin-sdk/text-chunking";
19
- import { resolveFfmpegBin, unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
19
+ import { unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
20
+ import { Worker } from "node:worker_threads";
21
+ import { PassThrough, Readable } from "node:stream";
20
22
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
21
- import { resolvePreferredOpenClawTmpDir, tempWorkspace } from "openclaw/plugin-sdk/temp-path";
22
- import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
23
23
  import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
24
- import { controlRealtimeVoiceAgentRun, createStreamingPcmResampler, parseRealtimeVoiceAgentControlToolArgs, shouldAutoControlRealtimeVoiceAgentText } from "openclaw/plugin-sdk/realtime-voice";
25
- import { spawn } from "node:child_process";
26
- import { Duplex, PassThrough, Readable } from "node:stream";
27
- import { StringDecoder } from "node:string_decoder";
28
- import { Application, OpusError, createDecoder, createEncoder } from "libopus-wasm";
24
+ import { resolveAgentDir } from "openclaw/plugin-sdk/agent-runtime";
25
+ import { EventEmitter } from "node:events";
26
+ import { controlRealtimeVoiceAgentRun, parseRealtimeVoiceAgentControlToolArgs, shouldAutoControlRealtimeVoiceAgentText } from "openclaw/plugin-sdk/realtime-voice";
29
27
  import { resolveRealtimeBootstrapContextInstructions } from "openclaw/plugin-sdk/realtime-bootstrap-context";
30
- //#region extensions/discord/src/voice/sdk-runtime.ts
31
- let cachedDiscordVoiceSdk = null;
32
- function loadDiscordVoiceSdk() {
33
- if (cachedDiscordVoiceSdk) return cachedDiscordVoiceSdk;
34
- cachedDiscordVoiceSdk = createRequire(import.meta.url)("@discordjs/voice");
35
- return cachedDiscordVoiceSdk;
36
- }
37
- //#endregion
38
- //#region extensions/discord/src/voice/realtime-player.ts
39
- const DISCORD_REALTIME_PLAYBACK_IDLE_MS = 2e3;
40
- /** One physical player serves every speaker lane in the room. */
41
- var DiscordRealtimePlayer = class {
42
- constructor(player) {
43
- this.player = player;
44
- this.queue = [];
45
- this.lanes = /* @__PURE__ */ new Set();
46
- this.changing = false;
47
- this.closed = false;
48
- this.onIdle = () => {
49
- const request = this.current;
50
- this.current = void 0;
51
- if (request) this.transition(() => request.onIdle());
52
- };
53
- player.on(loadDiscordVoiceSdk().AudioPlayerStatus.Idle, this.onIdle);
54
- }
55
- registerLane(lane) {
56
- this.lanes.add(lane);
57
- return () => this.lanes.delete(lane);
58
- }
59
- enqueue(request) {
60
- if (this.closed || this.current === request) return;
61
- if (!this.queue.includes(request)) this.queue.push(request);
62
- this.drain();
63
- }
64
- isRetiring(request) {
65
- if (this.current !== request) return false;
66
- const state = this.player.state;
67
- return state.status !== loadDiscordVoiceSdk().AudioPlayerStatus.Idle && state.resource.silenceRemaining >= 0;
68
- }
69
- cancel(request) {
70
- this.queue = this.queue.filter((queued) => queued !== request);
71
- if (this.current !== request) {
72
- this.drain();
73
- return;
74
- }
75
- this.current = void 0;
76
- this.transition(() => this.player.stop(true));
77
- }
78
- handleBargeIn(reason = "barge-in") {
79
- const current = this.current;
80
- if (current) return current.onBargeIn(reason);
81
- let interrupted = false;
82
- const activeLanes = Array.from(this.lanes).filter((lane) => lane.hasOutput());
83
- for (const lane of activeLanes) interrupted = lane.onBargeIn(reason) || interrupted;
84
- return interrupted;
85
- }
86
- isActive() {
87
- return this.current !== void 0 || Array.from(this.lanes).some((lane) => lane.hasOutput());
88
- }
89
- cancelForControl() {
90
- this.transition(() => {
91
- this.queue = [];
92
- for (const lane of this.lanes) lane.cancelForControl();
93
- });
94
- }
95
- close() {
96
- this.closed = true;
97
- this.queue = [];
98
- this.lanes.clear();
99
- this.current = void 0;
100
- this.player.off(loadDiscordVoiceSdk().AudioPlayerStatus.Idle, this.onIdle);
101
- this.player.stop(true);
102
- }
103
- /** Prevent retiring one lane from granting playback to a sibling that is also retiring. */
104
- transition(action) {
105
- const wasChanging = this.changing;
106
- this.changing = true;
107
- try {
108
- action();
109
- } finally {
110
- this.changing = wasChanging;
111
- this.drain();
112
- }
113
- }
114
- drain() {
115
- if (this.closed || this.changing || this.current) return;
116
- const next = this.queue[0];
117
- if (!next?.isReady()) return;
118
- this.queue.shift();
119
- this.current = next;
120
- this.transition(() => {
121
- try {
122
- this.player.play(next.createResource());
123
- next.onStart();
124
- } catch (error) {
125
- this.current = void 0;
126
- this.player.stop(true);
127
- next.onError(error);
128
- }
129
- });
130
- }
131
- };
132
- //#endregion
28
+ import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "openclaw/plugin-sdk/process-runtime";
133
29
  //#region extensions/discord/src/voice/log-preview.ts
134
30
  const DISCORD_VOICE_LOG_PREVIEW_CHARS = 500;
135
31
  function formatVoiceLogPreview(text) {
@@ -156,7 +52,7 @@ function formatRealtimeInterruptionLog(event) {
156
52
  }
157
53
  }
158
54
  function formatRealtimeLifecycleLog(event) {
159
- if (!event.type.startsWith("session.")) return;
55
+ if (!event.type.startsWith("session.") || event.type.startsWith("session.output_audio") || event.type.endsWith(".delta") || event.type.endsWith(".append")) return;
160
56
  const detail = event.detail ? ` ${event.detail}` : "";
161
57
  return `discord voice: realtime lifecycle ${event.direction}:${event.type}${detail}`;
162
58
  }
@@ -165,7 +61,7 @@ function shouldLogRealtimeVerboseEvent(event) {
165
61
  }
166
62
  //#endregion
167
63
  //#region extensions/discord/src/voice/agent-control.ts
168
- const logger$9 = createSubsystemLogger("discord/voice");
64
+ const logger$10 = createSubsystemLogger("discord/voice");
169
65
  async function controlDiscordVoiceAgentRun(params) {
170
66
  const context = params.resolveContext ? await params.resolveContext() : params.context;
171
67
  const assertCurrent = () => {
@@ -214,7 +110,7 @@ async function maybeControlDiscordVoiceAgentRun(params) {
214
110
  };
215
111
  }
216
112
  function logDiscordVoiceAgentControlResult(entry, result) {
217
- logger$9.info(`discord voice: realtime active-run control handled mode=${result.mode} ok=${result.ok} active=${result.active} reason=${result.reason ?? "none"} voiceSession=${entry.voiceSessionKey} supervisorSession=${entry.route.sessionKey} agent=${entry.route.agentId}`);
113
+ logger$10.info(`discord voice: realtime active-run control handled mode=${result.mode} ok=${result.ok} active=${result.active} reason=${result.reason ?? "none"} voiceSession=${entry.voiceSessionKey} supervisorSession=${entry.route.sessionKey} agent=${entry.route.agentId}`);
218
114
  }
219
115
  async function handleDiscordVoiceAgentControlToolCall(params) {
220
116
  let result;
@@ -255,368 +151,11 @@ function formatVoiceIngressPrompt(transcript, speakerLabel) {
255
151
  return [DISCORD_VOICE_SPOKEN_OUTPUT_CONTRACT, voiceInput].join("\n\n");
256
152
  }
257
153
  //#endregion
258
- //#region extensions/discord/src/voice/audio.ts
259
- const SAMPLE_RATE = 48e3;
260
- const CHANNELS = 2;
261
- const BIT_DEPTH = 16;
262
- const FFMPEG_ERROR_OUTPUT_BYTES = 8192;
263
- const DISCORD_OPUS_FRAME_SIZE = 960;
264
- const DISCORD_OPUS_MAX_DECODE_FRAME_SIZE = SAMPLE_RATE * 120 / 1e3;
265
- const DISCORD_OPUS_ENCODE_BATCH_FRAMES = 8;
266
- const DISCORD_OPUS_FRAME_BYTES = DISCORD_OPUS_FRAME_SIZE * CHANNELS * (BIT_DEPTH / 8);
267
- const FFMPEG_PCM_ARGUMENTS = [
268
- "-analyzeduration",
269
- "0",
270
- "-loglevel",
271
- "error",
272
- "-vn",
273
- "-sn",
274
- "-dn",
275
- "-f",
276
- "s16le",
277
- "-ar",
278
- String(SAMPLE_RATE),
279
- "-ac",
280
- String(CHANNELS)
281
- ];
282
- let warnedOpusMissing = false;
283
- function buildWavBuffer(chunks) {
284
- const pcmBytes = chunks.reduce((total, chunk) => total + chunk.length, 0);
285
- const blockAlign = 4;
286
- const byteRate = SAMPLE_RATE * blockAlign;
287
- const wav = Buffer.allocUnsafe(44 + pcmBytes);
288
- const header = wav.subarray(0, 44);
289
- header.write("RIFF", 0);
290
- header.writeUInt32LE(36 + pcmBytes, 4);
291
- header.write("WAVE", 8);
292
- header.write("fmt ", 12);
293
- header.writeUInt32LE(16, 16);
294
- header.writeUInt16LE(1, 20);
295
- header.writeUInt16LE(CHANNELS, 22);
296
- header.writeUInt32LE(SAMPLE_RATE, 24);
297
- header.writeUInt32LE(byteRate, 28);
298
- header.writeUInt16LE(blockAlign, 32);
299
- header.writeUInt16LE(BIT_DEPTH, 34);
300
- header.write("data", 36);
301
- header.writeUInt32LE(pcmBytes, 40);
302
- let offset = 44;
303
- for (const chunk of chunks) offset += chunk.copy(wav, offset);
304
- return wav;
305
- }
306
- function createDiscordOpusEncodeStream() {
307
- return new DiscordOpusEncodeStream();
308
- }
309
- function createDiscordOpusPlaybackStream(input) {
310
- const inputSource = typeof input === "string" ? input : "pipe:0";
311
- const ffmpeg = spawn(resolveFfmpegBin(), [
312
- "-i",
313
- inputSource,
314
- ...FFMPEG_PCM_ARGUMENTS,
315
- "pipe:1"
316
- ], {
317
- stdio: [
318
- "pipe",
319
- "pipe",
320
- "pipe"
321
- ],
322
- windowsHide: true
323
- });
324
- const opusStream = createDiscordOpusEncodeStream();
325
- const stderr = Buffer.alloc(FFMPEG_ERROR_OUTPUT_BYTES);
326
- let stderrBytes = 0;
327
- let ffmpegClosed = false;
328
- const killFfmpeg = (signal = "SIGTERM") => {
329
- if (!ffmpegClosed && !ffmpeg.killed) ffmpeg.kill(signal);
330
- };
331
- ffmpeg.stderr.on("data", (chunk) => {
332
- if (stderrBytes < FFMPEG_ERROR_OUTPUT_BYTES) stderrBytes += chunk.copy(stderr, stderrBytes, 0, FFMPEG_ERROR_OUTPUT_BYTES - stderrBytes);
333
- });
334
- ffmpeg.once("error", (err) => {
335
- opusStream.destroy(err);
336
- });
337
- ffmpeg.once("close", (code, signal) => {
338
- ffmpegClosed = true;
339
- if (code && code !== 0) {
340
- const stderrText = new StringDecoder("utf8").write(stderr.subarray(0, stderrBytes)).trim();
341
- const suffix = stderrText ? `: ${stderrText}` : "";
342
- opusStream.destroy(/* @__PURE__ */ new Error(`ffmpeg exited with code ${code}${suffix}`));
343
- return;
344
- }
345
- if (signal) opusStream.destroy(/* @__PURE__ */ new Error(`ffmpeg exited with signal ${signal}`));
346
- });
347
- for (const readable of [ffmpeg.stdout, ffmpeg.stderr]) readable.on("error", (err) => {
348
- killFfmpeg("SIGKILL");
349
- opusStream.destroy(err);
350
- });
351
- ffmpeg.stdin.on("error", (err) => {
352
- if (err.code !== "EPIPE") opusStream.destroy(err);
353
- });
354
- ffmpeg.stdout.pipe(opusStream);
355
- opusStream.once("close", () => {
356
- if (!opusStream.readableEnded) killFfmpeg();
357
- });
358
- if (typeof input !== "string") {
359
- input.on("error", (err) => {
360
- ffmpeg.stdin.destroy(err);
361
- opusStream.destroy(err);
362
- });
363
- input.pipe(ffmpeg.stdin);
364
- } else ffmpeg.stdin.end();
365
- return opusStream;
366
- }
367
- var DiscordOpusEncodeStream = class extends Duplex {
368
- #partialFrame = Buffer.alloc(DISCORD_OPUS_FRAME_BYTES);
369
- #partialBytes = 0;
370
- #pending;
371
- #scheduled;
372
- #readBlocked = false;
373
- #partialFlushRequested = false;
374
- #encoder;
375
- #packetPcmBytes = /* @__PURE__ */ new WeakMap();
376
- constructor() {
377
- super({ readableObjectMode: true });
378
- }
379
- _construct(done) {
380
- createEncoder({
381
- application: Application.Audio,
382
- channels: CHANNELS,
383
- sampleRate: SAMPLE_RATE
384
- }).then((encoder) => {
385
- this.#encoder = encoder;
386
- done();
387
- }, (err) => done(err instanceof Error ? err : new Error(formatErrorMessage(err))));
388
- }
389
- _write(chunk, _encoding, done) {
390
- this.#pending = {
391
- chunk,
392
- offset: 0,
393
- done
394
- };
395
- this.#schedule();
396
- }
397
- _read() {
398
- this.#readBlocked = false;
399
- this.#schedule();
400
- }
401
- #schedule() {
402
- if (this.destroyed || this.#scheduled || this.#readBlocked || !this.#pending) return;
403
- this.#scheduled = setImmediate(() => {
404
- this.#scheduled = void 0;
405
- this.#encodeBatch();
406
- });
407
- }
408
- #encodeBatch() {
409
- const pending = this.#pending;
410
- if (!pending || this.destroyed) return;
411
- try {
412
- for (let count = 0; count < DISCORD_OPUS_ENCODE_BATCH_FRAMES; count += 1) {
413
- const remainingBytes = pending.chunk.length - pending.offset;
414
- if (this.#partialBytes > 0 || remainingBytes < DISCORD_OPUS_FRAME_BYTES) {
415
- const copied = pending.chunk.copy(this.#partialFrame, this.#partialBytes, pending.offset, pending.offset + DISCORD_OPUS_FRAME_BYTES - this.#partialBytes);
416
- pending.offset += copied;
417
- this.#partialBytes += copied;
418
- if (this.#partialBytes < DISCORD_OPUS_FRAME_BYTES) {
419
- this.#pending = void 0;
420
- pending.done();
421
- this.#flushRequestedPartialFrame();
422
- return;
423
- }
424
- this.#partialBytes = 0;
425
- this.#readBlocked = !this.#encodeFrame(this.#partialFrame);
426
- } else {
427
- const frame = pending.chunk.subarray(pending.offset, pending.offset + DISCORD_OPUS_FRAME_BYTES);
428
- pending.offset += DISCORD_OPUS_FRAME_BYTES;
429
- this.#readBlocked = !this.#encodeFrame(frame);
430
- }
431
- if (this.destroyed || this.#readBlocked) return;
432
- }
433
- this.#schedule();
434
- } catch (err) {
435
- this.#pending = void 0;
436
- pending.done(err instanceof Error ? err : new Error(formatErrorMessage(err)));
437
- }
438
- }
439
- _final(done) {
440
- try {
441
- this.flushPartialFrame();
442
- this.push(null);
443
- done();
444
- } catch (err) {
445
- done(err instanceof Error ? err : new Error(formatErrorMessage(err)));
446
- }
447
- }
448
- flushPartialFrameWhenReady() {
449
- this.#partialFlushRequested = true;
450
- if (this.#partialBytes > 0) this.#flushRequestedPartialFrame();
451
- }
452
- #flushRequestedPartialFrame() {
453
- if (!this.#partialFlushRequested || this.#pending || this.writableLength > 0) return;
454
- this.#partialFlushRequested = false;
455
- try {
456
- this.flushPartialFrame();
457
- } catch (error) {
458
- this.destroy(error instanceof Error ? error : new Error(formatErrorMessage(error)));
459
- }
460
- }
461
- flushPartialFrame() {
462
- if (this.destroyed || this.#pending || this.#partialBytes === 0) return false;
463
- const pcmBytes = this.#partialBytes;
464
- this.#partialFrame.fill(0, pcmBytes);
465
- this.#partialBytes = 0;
466
- this.#readBlocked = !this.#encodeFrame(this.#partialFrame, pcmBytes);
467
- return true;
468
- }
469
- takePcmBytes(packet) {
470
- const bytes = this.#packetPcmBytes.get(packet) ?? 0;
471
- this.#packetPcmBytes.delete(packet);
472
- return bytes;
473
- }
474
- _destroy(err, done) {
475
- this.#encoder?.free();
476
- clearImmediate(this.#scheduled);
477
- this.#scheduled = void 0;
478
- this.#partialFlushRequested = false;
479
- const pending = this.#pending;
480
- this.#pending = void 0;
481
- pending?.done(err ?? /* @__PURE__ */ new Error("Discord Opus encoder was destroyed"));
482
- this.#partialBytes = 0;
483
- this.#partialFrame = Buffer.alloc(0);
484
- done(err);
485
- }
486
- #encodeFrame(frame, pcmBytes = frame.length) {
487
- const packet = Buffer.from(this.#encoder.encode(frame, { frameSize: DISCORD_OPUS_FRAME_SIZE }));
488
- this.#packetPcmBytes.set(packet, pcmBytes);
489
- return this.push(packet);
490
- }
491
- };
492
- function pcmInt16ToBuffer(pcm) {
493
- return Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength);
494
- }
495
- async function decodeOpusStreamChunks(stream, params) {
496
- try {
497
- for await (const { pcm, packet } of decodeOpusFrames(stream, params)) await params.onChunk(pcm, packet);
498
- } catch (err) {
499
- params.onError?.(err);
500
- }
501
- }
502
- async function* decodeOpusFrames(stream, params) {
503
- let decoder;
504
- try {
505
- decoder = await createDecoder({
506
- channels: CHANNELS,
507
- sampleRate: SAMPLE_RATE
508
- });
509
- } catch (err) {
510
- params.onError?.(err);
511
- if (!warnedOpusMissing) {
512
- warnedOpusMissing = true;
513
- params.onWarn(`discord voice: no usable opus decoder available (libopus-wasm: ${formatErrorMessage(err)}); cannot decode voice audio`);
514
- }
515
- return;
516
- }
517
- params.onVerbose("opus decoder: libopus-wasm");
518
- try {
519
- for await (const chunk of stream) {
520
- if (!chunk || !(chunk instanceof Buffer) || chunk.length === 0) continue;
521
- const decoded = decoder.decode(chunk, { maxFrameSize: DISCORD_OPUS_MAX_DECODE_FRAME_SIZE });
522
- if (decoded.length > 0) yield {
523
- pcm: pcmInt16ToBuffer(decoded),
524
- packet: chunk
525
- };
526
- }
527
- } catch (err) {
528
- params.onError?.(err);
529
- if (shouldLogVerbose()) logVerbose(`discord voice: opus decode failed: ${formatErrorMessage(err)}`);
530
- } finally {
531
- decoder.free();
532
- }
533
- }
534
- function createDiscordPcmToRealtimeConverter() {
535
- const resampler = createStreamingPcmResampler(SAMPLE_RATE, 24e3);
536
- let trailingFrame = Buffer.alloc(0);
537
- return {
538
- process(pcm) {
539
- const input = trailingFrame.length > 0 ? Buffer.concat([trailingFrame, pcm]) : pcm;
540
- const completeBytes = input.length - input.length % 4;
541
- trailingFrame = Buffer.from(input.subarray(completeBytes));
542
- const mono = Buffer.alloc(completeBytes / 2);
543
- for (let offset = 0; offset < completeBytes; offset += 4) mono.writeInt16LE(Math.round((input.readInt16LE(offset) + input.readInt16LE(offset + 2)) / 2), offset / 2);
544
- return resampler.process(mono);
545
- },
546
- flush() {
547
- trailingFrame = Buffer.alloc(0);
548
- return resampler.flush();
549
- }
550
- };
551
- }
552
- function duplicateMonoChannels(mono) {
553
- const stereo = Buffer.alloc(mono.length * 2);
554
- for (let offset = 0; offset < mono.length; offset += 2) {
555
- const sample = mono.readInt16LE(offset);
556
- stereo.writeInt16LE(sample, offset * 2);
557
- stereo.writeInt16LE(sample, offset * 2 + 2);
558
- }
559
- return stereo;
560
- }
561
- function createRealtimePcmToDiscordConverter() {
562
- let resampler = createStreamingPcmResampler(24e3, SAMPLE_RATE);
563
- let history = Buffer.alloc(0);
564
- let trailingByte = Buffer.alloc(0);
565
- let replayBytes = 0;
566
- let flushed = false;
567
- const takeOutput = (pcm) => {
568
- const skippedBytes = Math.min(replayBytes, pcm.length);
569
- replayBytes -= skippedBytes;
570
- return duplicateMonoChannels(pcm.subarray(skippedBytes));
571
- };
572
- return {
573
- process(pcm) {
574
- const input = trailingByte.length > 0 ? Buffer.concat([trailingByte, pcm]) : pcm;
575
- const completeBytes = input.length - input.length % 2;
576
- const completePcm = input.subarray(0, completeBytes);
577
- trailingByte = Buffer.from(input.subarray(completeBytes));
578
- history = Buffer.concat([history, completePcm.subarray(-64)]).subarray(-64);
579
- return takeOutput(resampler.process(completePcm));
580
- },
581
- drain() {
582
- if (flushed) return Buffer.alloc(0);
583
- const output = takeOutput(resampler.flush());
584
- resampler = createStreamingPcmResampler(24e3, SAMPLE_RATE);
585
- replayBytes = history.length * 2 - resampler.process(history).length;
586
- return output;
587
- },
588
- flush() {
589
- flushed = true;
590
- trailingByte = Buffer.alloc(0);
591
- history = Buffer.alloc(0);
592
- return takeOutput(resampler.flush());
593
- }
594
- };
595
- }
596
- async function writeVoiceWavFile(chunks) {
597
- const wav = buildWavBuffer(chunks);
598
- const workspace = await tempWorkspace({
599
- rootDir: resolvePreferredOpenClawTmpDir(),
600
- prefix: "discord-voice-"
601
- });
602
- try {
603
- return {
604
- path: await workspace.write("segment.wav", wav),
605
- durationSeconds: (wav.length - 44) / (BIT_DEPTH / 8 * CHANNELS * SAMPLE_RATE),
606
- cleanup: () => workspace[Symbol.asyncDispose]()
607
- };
608
- } catch (error) {
609
- await workspace.cleanup();
610
- throw error;
611
- }
612
- }
613
- //#endregion
614
154
  //#region extensions/discord/src/voice/session.ts
615
155
  const MIN_SEGMENT_SECONDS = .35;
616
156
  const CAPTURE_FINALIZE_GRACE_MS = 2e3;
617
157
  const VOICE_CONNECT_READY_TIMEOUT_MS = 3e4;
618
158
  const VOICE_RECONNECT_GRACE_MS = 15e3;
619
- const PLAYBACK_READY_TIMEOUT_MS = 6e4;
620
159
  function resolveVoiceTimeoutMs(value, fallbackMs) {
621
160
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return fallbackMs;
622
161
  return Math.floor(value);
@@ -823,7 +362,7 @@ var DiscordRealtimeRecording = class {
823
362
  //#endregion
824
363
  //#region extensions/discord/src/voice/ingress.ts
825
364
  const DISCORD_VOICE_MESSAGE_PROVIDER = "discord-voice";
826
- const logger$8 = createSubsystemLogger("discord/voice");
365
+ const logger$9 = createSubsystemLogger("discord/voice");
827
366
  function summarizeAgentTurnPayloads(payloads) {
828
367
  let textPayloads = 0;
829
368
  let nonEmptyTextPayloads = 0;
@@ -889,12 +428,12 @@ async function runDiscordVoiceAgentTurn(params) {
889
428
  if (!context || params.entry.captureOnly || params.entry.sessionLifecycle.status !== "active" || context.isCurrent?.() === false) return null;
890
429
  params.signal?.throwIfAborted();
891
430
  const voiceModel = normalizeOptionalString(params.discordConfig.voice?.model);
892
- const runId = params.voiceSelection && context.senderIsOwner ? randomUUID() : void 0;
431
+ const runId = params.voiceSelection ? randomUUID() : void 0;
893
432
  const unbind = runId ? params.voiceSelection?.bindRun({
894
433
  runId,
895
434
  assertCurrent: () => {
896
435
  params.signal?.throwIfAborted();
897
- if (params.entry.sessionLifecycle.status !== "active" || context.isCurrent?.() === false || !context.senderIsOwner) throw new Error("Discord voice speaker no longer owns this call");
436
+ if (params.entry.sessionLifecycle.status !== "active" || context.isCurrent?.() === false) throw new Error("Discord voice access is no longer valid for this call");
898
437
  }
899
438
  }) : void 0;
900
439
  let result;
@@ -920,7 +459,7 @@ async function runDiscordVoiceAgentTurn(params) {
920
459
  }
921
460
  const payloads = result.payloads ?? [];
922
461
  const text = payloads.map((payload) => payload.text).filter((entry) => typeof entry === "string" && entry.trim()).join("\n").trim();
923
- if (!text) logger$8.info(`discord voice: agent turn produced no speakable payloads guild=${params.entry.guildId} channel=${params.entry.channelId} voiceSession=${params.entry.voiceSessionKey} supervisorSession=${params.entry.route.sessionKey} agent=${params.entry.route.agentId} user=${params.userId} ${summarizeAgentTurnPayloads(payloads)}`);
462
+ if (!text) logger$9.info(`discord voice: agent turn produced no speakable payloads guild=${params.entry.guildId} channel=${params.entry.channelId} voiceSession=${params.entry.voiceSessionKey} supervisorSession=${params.entry.route.sessionKey} agent=${params.entry.route.agentId} user=${params.userId} ${summarizeAgentTurnPayloads(payloads)}`);
924
463
  return {
925
464
  context,
926
465
  text
@@ -935,10 +474,10 @@ async function resolveDiscordVoiceRealtimeBootstrapContext(params) {
935
474
  agentId: params.entry.route.agentId,
936
475
  sessionKey: params.entry.route.sessionKey,
937
476
  files,
938
- warn: (message) => logger$8.warn(`discord voice: realtime bootstrap context: ${message}`)
477
+ warn: (message) => logger$9.warn(`discord voice: realtime bootstrap context: ${message}`)
939
478
  });
940
479
  } catch (error) {
941
- logger$8.warn(`discord voice: realtime bootstrap context unavailable: ${error instanceof Error ? error.message : String(error)}`);
480
+ logger$9.warn(`discord voice: realtime bootstrap context unavailable: ${error instanceof Error ? error.message : String(error)}`);
942
481
  return;
943
482
  }
944
483
  }
@@ -1125,7 +664,7 @@ async function resolveDiscordVoiceIngressContextWithParticipants(params) {
1125
664
  }
1126
665
  //#endregion
1127
666
  //#region extensions/discord/src/voice/membership.ts
1128
- const logger$7 = createSubsystemLogger("discord/voice");
667
+ const logger$8 = createSubsystemLogger("discord/voice");
1129
668
  const MAX_INFERRED_PARTICIPANTS = 256;
1130
669
  var DiscordVoiceMembershipTracker = class {
1131
670
  constructor(client, speakerContext, accountId) {
@@ -1158,7 +697,7 @@ var DiscordVoiceMembershipTracker = class {
1158
697
  };
1159
698
  this.states.set(entry, state);
1160
699
  const initialLines = formatDiscordVoiceParticipantStateLines(roster);
1161
- if (this.publish(entry, this.initialRosterEvent(entry, initialLines))) logger$7.info(`discord voice: participant roster event queued guild=${entry.guildId} channel=${entry.channelId} participants=${roster.totalCount} supervisorSession=${entry.route.sessionKey}`);
700
+ if (this.publish(entry, this.initialRosterEvent(entry, initialLines))) logger$8.info(`discord voice: participant roster event queued guild=${entry.guildId} channel=${entry.channelId} participants=${roster.totalCount} supervisorSession=${entry.route.sessionKey}`);
1162
701
  const activationRevision = state.revision;
1163
702
  (async () => {
1164
703
  const lines = await resolveDiscordVoiceParticipantLines({
@@ -1169,7 +708,7 @@ var DiscordVoiceMembershipTracker = class {
1169
708
  if (lines.join("\n") === initialLines.join("\n")) return;
1170
709
  if (!state.active || state.revision !== activationRevision || entry.sessionLifecycle.status === "stopped") return;
1171
710
  if (!this.publish(entry, this.initialRosterEvent(entry, lines))) return;
1172
- logger$7.info(`discord voice: enriched participant roster event queued guild=${entry.guildId} channel=${entry.channelId} participants=${roster.totalCount} supervisorSession=${entry.route.sessionKey}`);
711
+ logger$8.info(`discord voice: enriched participant roster event queued guild=${entry.guildId} channel=${entry.channelId} participants=${roster.totalCount} supervisorSession=${entry.route.sessionKey}`);
1173
712
  })().catch((err) => {
1174
713
  this.logFailure(entry, err);
1175
714
  });
@@ -1185,7 +724,7 @@ var DiscordVoiceMembershipTracker = class {
1185
724
  `The agent left guild_id=${JSON.stringify(entry.guildId)} channel_id=${JSON.stringify(entry.channelId)}.`,
1186
725
  "Any prior roster or membership updates for this voice session are no longer live. Do not respond to this event on its own."
1187
726
  ].join("\n"))) return;
1188
- logger$7.info(`discord voice: participant session-ended event queued guild=${entry.guildId} channel=${entry.channelId} supervisorSession=${entry.route.sessionKey}`);
727
+ logger$8.info(`discord voice: participant session-ended event queued guild=${entry.guildId} channel=${entry.channelId} supervisorSession=${entry.route.sessionKey}`);
1189
728
  }
1190
729
  countHumanParticipants(entry, botUserId) {
1191
730
  const state = this.states.get(entry);
@@ -1221,7 +760,7 @@ var DiscordVoiceMembershipTracker = class {
1221
760
  ...rosterLines.length > 0 ? rosterLines : ["- none"],
1222
761
  "This roster snapshot supersedes prior voice membership context. Do not respond to this event on its own."
1223
762
  ].join("\n"))) return;
1224
- logger$7.info(`discord voice: inferred participant-present event queued guild=${entry.guildId} channel=${entry.channelId} user=${normalizedUserId} supervisorSession=${entry.route.sessionKey}`);
763
+ logger$8.info(`discord voice: inferred participant-present event queued guild=${entry.guildId} channel=${entry.channelId} user=${normalizedUserId} supervisorSession=${entry.route.sessionKey}`);
1225
764
  }
1226
765
  track(entry, data, previousVoiceState) {
1227
766
  if (!entry) return;
@@ -1252,7 +791,7 @@ var DiscordVoiceMembershipTracker = class {
1252
791
  ...rosterLines.length > 0 ? rosterLines : ["- none"],
1253
792
  "This roster snapshot supersedes prior voice membership context. Do not respond to this event on its own."
1254
793
  ].join("\n"))) return;
1255
- logger$7.info(`discord voice: participant ${isPresent ? "joined" : "left"} event queued guild=${entry.guildId} channel=${entry.channelId} user=${userId} supervisorSession=${entry.route.sessionKey}`);
794
+ logger$8.info(`discord voice: participant ${isPresent ? "joined" : "left"} event queued guild=${entry.guildId} channel=${entry.channelId} user=${userId} supervisorSession=${entry.route.sessionKey}`);
1256
795
  }
1257
796
  publish(entry, text) {
1258
797
  try {
@@ -1263,7 +802,7 @@ var DiscordVoiceMembershipTracker = class {
1263
802
  }
1264
803
  }
1265
804
  logFailure(entry, err) {
1266
- logger$7.warn(`discord voice: participant notification failed guild=${entry.guildId} channel=${entry.channelId}: ${formatErrorMessage(err)}`);
805
+ logger$8.warn(`discord voice: participant notification failed guild=${entry.guildId} channel=${entry.channelId}: ${formatErrorMessage(err)}`);
1267
806
  }
1268
807
  roster(entry, botUserId, additionalUserIds) {
1269
808
  return collectDiscordVoiceParticipants({
@@ -1380,103 +919,8 @@ var DiscordVoiceSpeakerContextResolver = class {
1380
919
  }
1381
920
  };
1382
921
  //#endregion
1383
- //#region extensions/discord/src/voice/receive-recovery.ts
1384
- const DECRYPT_FAILURE_WINDOW_MS = 3e4;
1385
- const DECRYPT_FAILURE_RECONNECT_THRESHOLD = 3;
1386
- const DECRYPT_FAILURE_MARKER = "DecryptionFailed(";
1387
- const DAVE_PASSTHROUGH_DISABLED_MARKER = "UnencryptedWhenPassthroughDisabled";
1388
- const WASM_MEMORY_ACCESS_MARKER = "memory access out of bounds";
1389
- const OPUS_INVALID_PACKET_CODE = -4;
1390
- function createVoiceReceiveRecoveryState() {
1391
- return {
1392
- decryptFailureCount: 0,
1393
- lastDecryptFailureAt: 0,
1394
- decryptRecoveryInFlight: false
1395
- };
1396
- }
1397
- function isAbortLikeReceiveError(err) {
1398
- if (!err || typeof err !== "object") return false;
1399
- const name = "name" in err && typeof err.name === "string" ? err.name : "";
1400
- const message = "message" in err && typeof err.message === "string" ? err.message : "";
1401
- return name === "AbortError" || message === "Premature close" || message.includes("The operation was aborted") || message.includes("aborted");
1402
- }
1403
- function isOpusDecodeInvalidPacketError(err) {
1404
- if (!err || typeof err !== "object") return false;
1405
- const maybeOpusError = err;
1406
- const isDecodeOperation = maybeOpusError.operation === "decode" || maybeOpusError.operation === "decodeFloat";
1407
- const isInvalidPacket = maybeOpusError.code === OPUS_INVALID_PACKET_CODE || maybeOpusError.codeName === "InvalidPacket";
1408
- return isDecodeOperation && isInvalidPacket && (err instanceof OpusError || maybeOpusError.name === "OpusError");
1409
- }
1410
- function analyzeVoiceReceiveError(err) {
1411
- const message = formatErrorMessage(err);
1412
- const normalizedMessage = message.toLowerCase();
1413
- const shouldAttemptPassthrough = message.includes(DAVE_PASSTHROUGH_DISABLED_MARKER);
1414
- const isWasmMemoryAccessFailure = normalizedMessage.includes(WASM_MEMORY_ACCESS_MARKER);
1415
- return {
1416
- message,
1417
- isAbortLike: isAbortLikeReceiveError(err),
1418
- isDecodeCorruption: isOpusDecodeInvalidPacketError(err),
1419
- shouldAttemptPassthrough,
1420
- countsAsDecryptFailure: message.includes(DECRYPT_FAILURE_MARKER) || shouldAttemptPassthrough || isWasmMemoryAccessFailure
1421
- };
1422
- }
1423
- function noteVoiceDecryptFailure(state, now = Date.now()) {
1424
- if (now - state.lastDecryptFailureAt > 3e4) state.decryptFailureCount = 0;
1425
- state.lastDecryptFailureAt = now;
1426
- state.decryptFailureCount += 1;
1427
- const firstFailure = state.decryptFailureCount === 1;
1428
- if (state.decryptFailureCount < DECRYPT_FAILURE_RECONNECT_THRESHOLD || state.decryptRecoveryInFlight) return {
1429
- firstFailure,
1430
- shouldRecover: false
1431
- };
1432
- state.decryptRecoveryInFlight = true;
1433
- resetVoiceReceiveRecoveryState(state);
1434
- return {
1435
- firstFailure,
1436
- shouldRecover: true
1437
- };
1438
- }
1439
- function resetVoiceReceiveRecoveryState(state) {
1440
- state.decryptFailureCount = 0;
1441
- state.lastDecryptFailureAt = 0;
1442
- }
1443
- function finishVoiceDecryptRecovery(state) {
1444
- state.decryptRecoveryInFlight = false;
1445
- }
1446
- function isDaveReinitializing(session) {
1447
- return session.reinitializing === true;
1448
- }
1449
- function recoverDaveZeroTransition(params) {
1450
- const { target, sdk, onWarn } = params;
1451
- const networkingState = target.connection.state.networking?.state;
1452
- const daveSession = networkingState?.dave;
1453
- if (target.connection.state.status !== sdk.VoiceConnectionStatus.Ready || networkingState?.code !== sdk.NetworkingStatusCode.Ready || daveSession?.lastTransitionId !== 0 || daveSession.reinitializing !== false || typeof daveSession.recoverFromInvalidTransition !== "function") return "not-attempted";
1454
- try {
1455
- daveSession.recoverFromInvalidTransition(0);
1456
- return "recovered";
1457
- } catch (err) {
1458
- onWarn(`discord voice: failed to recover DAVE transition 0 guild=${target.guildId} channel=${target.channelId}: ${formatErrorMessage(err)}`);
1459
- return isDaveReinitializing(daveSession) ? "failed" : "not-attempted";
1460
- }
1461
- }
1462
- function enableDaveReceivePassthrough(params) {
1463
- const { target, sdk, reason, expirySeconds, onVerbose, onWarn } = params;
1464
- const networkingState = target.connection.state.networking?.state;
1465
- if (target.connection.state.status !== sdk.VoiceConnectionStatus.Ready || !networkingState || networkingState.code !== sdk.NetworkingStatusCode.Ready && networkingState.code !== sdk.NetworkingStatusCode.Resuming) return false;
1466
- const daveSession = networkingState.dave?.session;
1467
- if (!daveSession) return false;
1468
- try {
1469
- daveSession.setPassthroughMode(true, expirySeconds);
1470
- onVerbose(`enabled DAVE receive passthrough: guild ${target.guildId} channel ${target.channelId} expiry=${expirySeconds}s reason=${reason}`);
1471
- return true;
1472
- } catch (err) {
1473
- onWarn(`discord voice: failed to enable DAVE passthrough guild=${target.guildId} channel=${target.channelId} reason=${reason}: ${formatErrorMessage(err)}`);
1474
- return false;
1475
- }
1476
- }
1477
- //#endregion
1478
922
  //#region extensions/discord/src/voice/voice-following.ts
1479
- const logger$6 = createSubsystemLogger("discord/voice");
923
+ const logger$7 = createSubsystemLogger("discord/voice");
1480
924
  const FOLLOW_USERS_RECONCILE_INTERVAL_MS = 1e4;
1481
925
  const FOLLOW_USERS_RECONCILE_MAX_GUILDS_PER_RUN = 4;
1482
926
  const FOLLOW_USERS_RECONCILE_MAX_REST_LOOKUPS_PER_RUN = 32;
@@ -1511,14 +955,11 @@ function resolveFollowUsersEnabled(voiceConfig) {
1511
955
  }
1512
956
  function logFollowUserReconcileVerbose(reason, message) {
1513
957
  if (reason === "interval") {
1514
- logger$6.trace(`discord voice: ${message}`);
958
+ logger$7.trace(`discord voice: ${message}`);
1515
959
  return;
1516
960
  }
1517
961
  logVoiceVerbose(message);
1518
962
  }
1519
- function resolveVoiceConnectionGroup$1(accountId) {
1520
- return `openclaw:${accountId}`;
1521
- }
1522
963
  var DiscordVoiceFollowing = class {
1523
964
  constructor(params) {
1524
965
  this.params = params;
@@ -1548,7 +989,7 @@ var DiscordVoiceFollowing = class {
1548
989
  channelId
1549
990
  })) {
1550
991
  if (existing && existing.channelId !== channelId) {
1551
- logger$6.warn(`discord voice: bot moved to allowed channel guild=${guildId} from=${existing.channelId} to=${channelId}; rebuilding voice session`);
992
+ logger$7.warn(`discord voice: bot moved to allowed channel guild=${guildId} from=${existing.channelId} to=${channelId}; rebuilding voice session`);
1552
993
  await this.params.join({
1553
994
  guildId,
1554
995
  channelId
@@ -1556,20 +997,12 @@ var DiscordVoiceFollowing = class {
1556
997
  }
1557
998
  return;
1558
999
  }
1559
- logger$6.warn(`discord voice: bot moved to non-allowed channel guild=${guildId} channel=${channelId}; leaving`);
1000
+ logger$7.warn(`discord voice: bot moved to non-allowed channel guild=${guildId} channel=${channelId}; leaving`);
1560
1001
  if (existing) await this.params.leave({ guildId });
1561
- else {
1562
- const voiceSdk = loadDiscordVoiceSdk();
1563
- const connection = voiceSdk.getVoiceConnection(guildId, resolveVoiceConnectionGroup$1(this.params.accountId));
1564
- if (connection) this.params.destroyVoiceConnection({
1565
- connection,
1566
- voiceSdk,
1567
- reason: `non-allowed voice state guild ${guildId} channel ${channelId}`
1568
- });
1569
- }
1002
+ else await this.params.stopTransport(guildId);
1570
1003
  const target = this.resolveVoiceResidencyTarget(guildId);
1571
1004
  if (target) {
1572
- logger$6.warn(`discord voice: rejoining allowed voice channel guild=${guildId} channel=${target.channelId}`);
1005
+ logger$7.warn(`discord voice: rejoining allowed voice channel guild=${guildId} channel=${target.channelId}`);
1573
1006
  await this.params.join(target);
1574
1007
  }
1575
1008
  }
@@ -1602,7 +1035,7 @@ var DiscordVoiceFollowing = class {
1602
1035
  channelId
1603
1036
  })) {
1604
1037
  this.followedUserChannels.delete(followKey);
1605
- logger$6.warn(`discord voice: followed user joined non-allowed channel guild=${guildId} user=${userId} channel=${channelId}; ignoring`);
1038
+ logger$7.warn(`discord voice: followed user joined non-allowed channel guild=${guildId} user=${userId} channel=${channelId}; ignoring`);
1606
1039
  if (existing && wasFollowedVoiceSession && !this.hasFollowedUserInChannel(existing)) await this.handoffToAnotherFollowedUserOrLeave({
1607
1040
  guildId,
1608
1041
  userId,
@@ -1622,12 +1055,12 @@ var DiscordVoiceFollowing = class {
1622
1055
  const recoveryAttemptAt = this.params.getRecoveryAttempt(guildId);
1623
1056
  if (!existing && previousFollowedChannelId === channelId && recoveryAttemptAt !== void 0) {
1624
1057
  if (Date.now() - recoveryAttemptAt < 3e4) {
1625
- logger$6.warn(`discord voice: automatic follow suppressed during DAVE recovery cooldown guild=${guildId} channel=${channelId}; retry /vc join after the voice gateway recovers`);
1058
+ logger$7.warn(`discord voice: automatic follow suppressed during DAVE recovery cooldown guild=${guildId} channel=${channelId}; retry /vc join after the voice gateway recovers`);
1626
1059
  return;
1627
1060
  }
1628
1061
  this.params.deleteRecoveryAttempt(guildId);
1629
1062
  }
1630
- logger$6.info(`discord voice: following user guild=${guildId} user=${userId} channel=${channelId}`);
1063
+ logger$7.info(`discord voice: following user guild=${guildId} user=${userId} channel=${channelId}`);
1631
1064
  const result = await this.params.join({
1632
1065
  guildId,
1633
1066
  channelId
@@ -1636,7 +1069,7 @@ var DiscordVoiceFollowing = class {
1636
1069
  if (!result.ok) {
1637
1070
  if (this.params.getSession(guildId)?.channelId === channelId) this.followedVoiceGuilds.add(guildId);
1638
1071
  else this.followedUserChannels.delete(followKey);
1639
- logger$6.warn(`discord voice: failed to follow user guild=${guildId} user=${userId} channel=${channelId}: ${result.message}`);
1072
+ logger$7.warn(`discord voice: failed to follow user guild=${guildId} user=${userId} channel=${channelId}: ${result.message}`);
1640
1073
  return;
1641
1074
  }
1642
1075
  this.followedVoiceGuilds.add(guildId);
@@ -1672,7 +1105,7 @@ var DiscordVoiceFollowing = class {
1672
1105
  if (this.followUsersReconcileTimer) return;
1673
1106
  this.followUsersReconcileTimer = setInterval(() => {
1674
1107
  this.reconcileFollowedUsers("interval").catch((err) => {
1675
- logger$6.warn(`discord voice: follow user reconciliation failed: ${formatErrorMessage(err)}`);
1108
+ logger$7.warn(`discord voice: follow user reconciliation failed: ${formatErrorMessage(err)}`);
1676
1109
  });
1677
1110
  }, FOLLOW_USERS_RECONCILE_INTERVAL_MS);
1678
1111
  this.followUsersReconcileTimer.unref?.();
@@ -1698,7 +1131,7 @@ var DiscordVoiceFollowing = class {
1698
1131
  for (const userId of plan.userIds) {
1699
1132
  const voiceState = await getGuildVoiceState(this.params.client.rest, plan.guildId, userId).catch((err) => {
1700
1133
  if (!isUnknownDiscordVoiceStateError(err)) {
1701
- logger$6.warn(`follow-user reconcile skipped (transient voice-state error) guild=${plan.guildId} user=${userId} trigger=${reason}: ${formatErrorMessage(err)}`);
1134
+ logger$7.warn(`follow-user reconcile skipped (transient voice-state error) guild=${plan.guildId} user=${userId} trigger=${reason}: ${formatErrorMessage(err)}`);
1702
1135
  return "transient-error";
1703
1136
  }
1704
1137
  logFollowUserReconcileVerbose(reason, `follow user reconcile reason=${reason}: no voice state guild ${plan.guildId} user ${userId}: ${formatErrorMessage(err)}`);
@@ -1798,18 +1231,18 @@ var DiscordVoiceFollowing = class {
1798
1231
  async handoffToAnotherFollowedUserOrLeave(params) {
1799
1232
  const target = this.resolveFollowedUserHandoffTarget(params.guildId, params.existing.channelId);
1800
1233
  if (target) {
1801
- logger$6.info(`discord voice: followed user ${params.reason} guild=${params.guildId} user=${params.userId}; moving to remaining followed user channel=${target.channelId}`);
1234
+ logger$7.info(`discord voice: followed user ${params.reason} guild=${params.guildId} user=${params.userId}; moving to remaining followed user channel=${target.channelId}`);
1802
1235
  const result = await this.params.join(target, { preserveFollowState: true });
1803
1236
  if (result.ok) this.followedVoiceGuilds.add(params.guildId);
1804
1237
  else {
1805
- logger$6.warn(`discord voice: failed to hand off followed user session guild=${params.guildId} channel=${target.channelId}: ${result.message}`);
1238
+ logger$7.warn(`discord voice: failed to hand off followed user session guild=${params.guildId} channel=${target.channelId}: ${result.message}`);
1806
1239
  this.followedVoiceGuilds.delete(params.guildId);
1807
1240
  this.deleteFollowedUserChannelsForGuild(params.guildId);
1808
1241
  await this.params.leave({ guildId: params.guildId });
1809
1242
  }
1810
1243
  return;
1811
1244
  }
1812
- logger$6.info(`discord voice: followed user ${params.reason} guild=${params.guildId} user=${params.userId}; leaving channel=${params.existing.channelId}`);
1245
+ logger$7.info(`discord voice: followed user ${params.reason} guild=${params.guildId} user=${params.userId}; leaving channel=${params.existing.channelId}`);
1813
1246
  await this.params.leave({ guildId: params.guildId });
1814
1247
  }
1815
1248
  async disconnectStaleFollowedBotVoiceState(params) {
@@ -1819,7 +1252,7 @@ var DiscordVoiceFollowing = class {
1819
1252
  const existing = this.params.getSession(guildId);
1820
1253
  if (existing) {
1821
1254
  if (this.followedVoiceGuilds.has(guildId)) {
1822
- logger$6.info(`discord voice: follow reconcile leaving local session guild=${guildId} channel=${existing.channelId} reason=${reason}`);
1255
+ logger$7.info(`discord voice: follow reconcile leaving local session guild=${guildId} channel=${existing.channelId} reason=${reason}`);
1823
1256
  await this.params.leave({ guildId });
1824
1257
  }
1825
1258
  return;
@@ -1828,7 +1261,7 @@ var DiscordVoiceFollowing = class {
1828
1261
  if (!botUserId) return;
1829
1262
  const botVoiceState = await getGuildVoiceState(this.params.client.rest, guildId, botUserId).catch((err) => {
1830
1263
  if (!isUnknownDiscordVoiceStateError(err)) {
1831
- logger$6.warn(`discord voice: follow reconcile skipped transient bot voice state error guild=${guildId} reason=${reason}: ${formatErrorMessage(err)}`);
1264
+ logger$7.warn(`discord voice: follow reconcile skipped transient bot voice state error guild=${guildId} reason=${reason}: ${formatErrorMessage(err)}`);
1832
1265
  return "transient-error";
1833
1266
  }
1834
1267
  logFollowUserReconcileVerbose(reason, `follow user reconcile reason=${reason}: no bot voice state guild ${guildId}: ${formatErrorMessage(err)}`);
@@ -1838,10 +1271,10 @@ var DiscordVoiceFollowing = class {
1838
1271
  if (!botChannelId) return;
1839
1272
  const gateway = this.params.client.getPlugin("voice")?.getGateway(guildId);
1840
1273
  if (!gateway) {
1841
- logger$6.warn(`discord voice: follow reconcile cannot disconnect stale bot voice state guild=${guildId} channel=${botChannelId}; gateway unavailable`);
1274
+ logger$7.warn(`discord voice: follow reconcile cannot disconnect stale bot voice state guild=${guildId} channel=${botChannelId}; gateway unavailable`);
1842
1275
  return;
1843
1276
  }
1844
- logger$6.info(`discord voice: follow reconcile disconnecting stale bot voice state guild=${guildId} channel=${botChannelId} reason=${reason}`);
1277
+ logger$7.info(`discord voice: follow reconcile disconnecting stale bot voice state guild=${guildId} channel=${botChannelId} reason=${reason}`);
1845
1278
  gateway.updateVoiceState({
1846
1279
  guild_id: guildId,
1847
1280
  channel_id: null,
@@ -1868,7 +1301,8 @@ function stopVoiceCaptureState(state) {
1868
1301
  state.clear();
1869
1302
  for (const capture of captures) {
1870
1303
  clearVoiceCaptureFinalizeTimer(capture);
1871
- capture.stream?.destroy();
1304
+ if (capture.stopInput) capture.stopInput();
1305
+ else capture.stream?.destroy();
1872
1306
  }
1873
1307
  }
1874
1308
  function clearVoiceCaptureFinalizeTimer(capture) {
@@ -1992,13 +1426,13 @@ async function synthesizeVoiceReplyAudio(params) {
1992
1426
  }
1993
1427
  //#endregion
1994
1428
  //#region extensions/discord/src/voice/segment.ts
1995
- const logger$5 = createSubsystemLogger("discord/voice");
1429
+ const logger$6 = createSubsystemLogger("discord/voice");
1996
1430
  async function processDiscordVoiceSegment(params) {
1997
1431
  const { entry, wavPath, userId, durationSeconds } = params;
1998
1432
  const conversationCurrent = () => !entry.captureOnly && entry.sessionLifecycle.status === "active" && params.isConversationCurrent();
1999
1433
  logVoiceVerbose(`segment processing (${durationSeconds.toFixed(2)}s): guild ${entry.guildId} channel ${entry.channelId}`);
2000
1434
  const ingress = params.resolveIngressContext().catch((error) => {
2001
- logger$5.warn(`discord voice: conversation authorization failed: ${formatErrorMessage$1(error)}`);
1435
+ logger$6.warn(`discord voice: conversation authorization failed: ${formatErrorMessage$1(error)}`);
2002
1436
  return null;
2003
1437
  });
2004
1438
  const conversationAuthorized = ingress.then((context) => Boolean(context) && conversationCurrent());
@@ -2072,14 +1506,14 @@ async function respondToDiscordVoiceTranscript(params) {
2072
1506
  text: transcript
2073
1507
  }).catch((error) => {
2074
1508
  if (readErrorName(error) === "AbortError") {
2075
- logger$5.warn(`discord voice: active-run control cancelled: ${formatErrorMessage$1(error)}`);
1509
+ logger$6.warn(`discord voice: active-run control cancelled: ${formatErrorMessage$1(error)}`);
2076
1510
  return null;
2077
1511
  }
2078
- logger$5.warn(`discord voice: active-run control failed; falling back to normal segment handling: ${formatErrorMessage$1(error)}`);
1512
+ logger$6.warn(`discord voice: active-run control failed; falling back to normal segment handling: ${formatErrorMessage$1(error)}`);
2079
1513
  });
2080
1514
  if (control === null || !conversationCurrent()) return;
2081
1515
  if (control?.handled) {
2082
- logger$5.info(`discord voice: active-run control handled mode=${control.result.mode} ok=${control.result.ok} active=${control.result.active} reason=${control.result.reason ?? "none"} session=${entry.route.sessionKey}`);
1516
+ logger$6.info(`discord voice: active-run control handled mode=${control.result.mode} ok=${control.result.ok} active=${control.result.active} reason=${control.result.reason ?? "none"} session=${entry.route.sessionKey}`);
2083
1517
  replyText = control.speakText ?? "";
2084
1518
  } else {
2085
1519
  const prompt = formatVoiceIngressPrompt(transcript, ingress.speakerLabel);
@@ -2119,13 +1553,13 @@ async function respondToDiscordVoiceTranscript(params) {
2119
1553
  return;
2120
1554
  }
2121
1555
  if (voiceReplyAudio.status === "failed") {
2122
- logger$5.warn(`discord voice: TTS failed: ${voiceReplyAudio.error ?? "unknown error"}`);
1556
+ logger$6.warn(`discord voice: TTS failed: ${voiceReplyAudio.error ?? "unknown error"}`);
2123
1557
  return;
2124
1558
  }
2125
1559
  const streamFailure = voiceReplyAudio.mode === "file" ? voiceReplyAudio.streamFailure : void 0;
2126
1560
  if (streamFailure && !entry.ttsStreamFallbackWarned) {
2127
1561
  entry.ttsStreamFallbackWarned = true;
2128
- logger$5.warn(`discord voice: streaming TTS failed provider=${streamFailure.provider} reasonCode=${streamFailure.reasonCode}; using file fallback`);
1562
+ logger$6.warn(`discord voice: streaming TTS failed provider=${streamFailure.provider} reasonCode=${streamFailure.reasonCode}; using file fallback`);
2129
1563
  }
2130
1564
  logVoiceVerbose(`tts ok (${voiceReplyAudio.speakText.length} chars): guild ${entry.guildId} channel ${entry.channelId}`);
2131
1565
  const releaseAudio = voiceReplyAudio.mode === "stream" ? voiceReplyAudio.release : () => unlinkIfExists(voiceReplyAudio.audioPath);
@@ -2134,32 +1568,22 @@ async function respondToDiscordVoiceTranscript(params) {
2134
1568
  return;
2135
1569
  }
2136
1570
  params.enqueuePlayback(entry, async () => {
2137
- const voiceSdk = loadDiscordVoiceSdk();
2138
- const playbackLifecycle = new AbortController();
2139
- let playbackStarted = false;
2140
- const cancelStoppedPlayback = () => (!playbackStarted || entry.sessionLifecycle.status === "stopped") && playbackLifecycle.abort();
2141
1571
  try {
2142
1572
  if (entry.sessionLifecycle.status === "stopped") return;
2143
- entry.player.on(voiceSdk.AudioPlayerStatus.Idle, cancelStoppedPlayback);
2144
1573
  const input = voiceReplyAudio.mode === "stream" ? Readable.fromWeb(voiceReplyAudio.audioStream) : voiceReplyAudio.audioPath;
2145
1574
  logVoiceVerbose(`playback start: guild ${entry.guildId} channel ${entry.channelId} ${voiceReplyAudio.mode}`);
2146
- const resource = voiceSdk.createAudioResource(createDiscordOpusPlaybackStream(input), { inputType: voiceSdk.StreamType.Opus });
2147
- entry.player.play(resource);
2148
- await voiceSdk.entersState(entry.player, voiceSdk.AudioPlayerStatus.Playing, AbortSignal.any([AbortSignal.timeout(PLAYBACK_READY_TIMEOUT_MS), playbackLifecycle.signal]));
2149
- playbackStarted = true;
2150
- await voiceSdk.entersState(entry.player, voiceSdk.AudioPlayerStatus.Idle, playbackLifecycle.signal);
2151
- logVoiceVerbose(`playback done: guild ${entry.guildId} channel ${entry.channelId}`);
1575
+ await entry.audio.play(input);
1576
+ logVoiceVerbose("playback done: guild " + entry.guildId + " channel " + entry.channelId);
2152
1577
  } catch (error) {
2153
1578
  if (entry.sessionLifecycle.status !== "stopped") throw error;
2154
1579
  } finally {
2155
- entry.player.off(voiceSdk.AudioPlayerStatus.Idle, cancelStoppedPlayback);
2156
1580
  await releaseAudio?.();
2157
1581
  }
2158
1582
  });
2159
1583
  }
2160
1584
  //#endregion
2161
1585
  //#region extensions/discord/src/voice/voice-recording.ts
2162
- const logger$4 = createSubsystemLogger("discord/voice");
1586
+ const logger$5 = createSubsystemLogger("discord/voice");
2163
1587
  const PCM_BYTES_PER_MILLISECOND = 192;
2164
1588
  const MAX_PENDING_RECORDING_JOBS = 128;
2165
1589
  const MAX_PENDING_RECORDING_BYTES = 67108864;
@@ -2180,6 +1604,7 @@ var DiscordVoiceRecording = class {
2180
1604
  this.chunks = [];
2181
1605
  this.bytes = 0;
2182
1606
  this.chunked = false;
1607
+ this.recordingEpoch = 0n;
2183
1608
  this.startedAt = 0;
2184
1609
  this.completion = Promise.resolve();
2185
1610
  const budget = params.entry.audioInputBudget;
@@ -2190,9 +1615,10 @@ var DiscordVoiceRecording = class {
2190
1615
  this.params.onExcluded();
2191
1616
  return;
2192
1617
  }
2193
- this.chunked ||= receipt.capture !== void 0;
2194
- if (receipt.capture !== this.capture) await this.flush();
1618
+ this.chunked ||= receipt.recordingEpoch !== 0n;
1619
+ if (receipt.capture !== this.capture || receipt.recordingEpoch !== this.recordingEpoch) await this.flush();
2195
1620
  this.capture = receipt.capture;
1621
+ this.recordingEpoch = receipt.recordingEpoch;
2196
1622
  if (!this.capture?.isCurrent() && !this.params.canConverse()) {
2197
1623
  this.params.onExcluded();
2198
1624
  return;
@@ -2201,7 +1627,7 @@ var DiscordVoiceRecording = class {
2201
1627
  this.chunks = [];
2202
1628
  this.bytes = 0;
2203
1629
  this.overflow = /* @__PURE__ */ new Error("Discord voice audio exceeds the transcription limit; speak a shorter segment.");
2204
- logger$4.warn(`discord voice: ${this.overflow.message}`);
1630
+ logger$5.warn(`discord voice: ${this.overflow.message}`);
2205
1631
  throw this.overflow;
2206
1632
  }
2207
1633
  for (let offset = 0; offset < pcm.length;) {
@@ -2247,7 +1673,7 @@ var DiscordVoiceRecording = class {
2247
1673
  try {
2248
1674
  await wav.cleanup();
2249
1675
  } catch (error) {
2250
- logger$4.warn(`discord voice: recording cleanup failed: ${formatErrorMessage(error)}`);
1676
+ logger$5.warn(`discord voice: recording cleanup failed: ${formatErrorMessage(error)}`);
2251
1677
  } finally {
2252
1678
  releaseBudget();
2253
1679
  }
@@ -2276,7 +1702,7 @@ var DiscordVoiceRecording = class {
2276
1702
  });
2277
1703
  } catch (error) {
2278
1704
  this.params.onExcluded();
2279
- logger$4.warn(`discord voice: recording failed: ${formatErrorMessage(error)}`);
1705
+ logger$5.warn(`discord voice: recording failed: ${formatErrorMessage(error)}`);
2280
1706
  } finally {
2281
1707
  await cleanup();
2282
1708
  }
@@ -2288,7 +1714,7 @@ var DiscordVoiceRecording = class {
2288
1714
  };
2289
1715
  //#endregion
2290
1716
  //#region extensions/discord/src/voice/voice-receive.ts
2291
- const logger$3 = createSubsystemLogger("discord/voice");
1717
+ const logger$4 = createSubsystemLogger("discord/voice");
2292
1718
  const MAX_PENDING_OPUS_PACKETS = 1e3;
2293
1719
  const MAX_PENDING_OPUS_BYTES = 1048576;
2294
1720
  var DiscordVoiceReceive = class {
@@ -2296,15 +1722,12 @@ var DiscordVoiceReceive = class {
2296
1722
  this.params = params;
2297
1723
  this.daveRecoveryAttempts = /* @__PURE__ */ new Map();
2298
1724
  }
2299
- scheduleCaptureFinalize(entry, userId, reason) {
2300
- const graceMs = resolveVoiceTimeoutMs(this.params.discordConfig.voice?.captureSilenceGraceMs, CAPTURE_FINALIZE_GRACE_MS);
1725
+ scheduleCaptureFinalize(entry, userId, _reason) {
1726
+ if (entry.capture.get(userId)?.stream) return;
2301
1727
  scheduleVoiceCaptureFinalize({
2302
1728
  state: entry.capture,
2303
1729
  userId,
2304
- delayMs: graceMs,
2305
- onFinalize: () => {
2306
- logVoiceVerbose(`capture finalize: guild ${entry.guildId} channel ${entry.channelId} user ${userId} reason=${reason} grace=${graceMs}ms`);
2307
- }
1730
+ delayMs: resolveVoiceTimeoutMs(this.params.discordConfig.voice?.captureSilenceGraceMs, CAPTURE_FINALIZE_GRACE_MS)
2308
1731
  });
2309
1732
  }
2310
1733
  async handleSpeakingStart(entry, userId, origin = "native") {
@@ -2320,7 +1743,7 @@ var DiscordVoiceReceive = class {
2320
1743
  }
2321
1744
  const capture = entry.transcripts;
2322
1745
  const realtime = entry.realtimeLifecycle.status === "active" ? entry.realtimeLifecycle.instance : void 0;
2323
- const playing = entry.player.state.status === loadDiscordVoiceSdk().AudioPlayerStatus.Playing;
1746
+ const playing = entry.audio.playerStatus === "playing";
2324
1747
  const conversationAllowed = origin === "native" && !entry.captureOnly && !(playing && !realtime?.canReceiveDuringPlayback());
2325
1748
  if (!capture && !conversationAllowed) {
2326
1749
  logVoiceVerbose(`capture ignored: guild ${entry.guildId} channel ${entry.channelId} user ${userId} reason=${playing ? "protected playback" : "inactive capture"}`);
@@ -2344,11 +1767,12 @@ var DiscordVoiceReceive = class {
2344
1767
  await this.receiveSpeaker(entry, userId, reservation, conversationAllowed, realtimeIngress);
2345
1768
  } finally {
2346
1769
  const stream = reservation.stream;
2347
- if (finishVoiceCapture(entry.capture, userId, reservation) && stream && !stream.destroyed) stream.destroy();
1770
+ if (!stream) finishVoiceCapture(entry.capture, userId, reservation);
1771
+ else if (!stream.destroyed) stream.destroy();
2348
1772
  }
2349
1773
  }
2350
1774
  captureCurrentSpeakers(entry) {
2351
- for (const userId of entry.connection.receiver.speaking.users.keys()) this.handleSpeakingStart(entry, userId, "scan").catch((error) => logger$3.warn(`discord voice: capture failed: ${formatErrorMessage(error)}`));
1775
+ for (const userId of entry.audio.speakingUsers) this.handleSpeakingStart(entry, userId, "scan").catch((error) => logger$4.warn(`discord voice: capture failed: ${formatErrorMessage(error)}`));
2352
1776
  }
2353
1777
  responseContext(entry, userId) {
2354
1778
  return {
@@ -2366,48 +1790,75 @@ var DiscordVoiceReceive = class {
2366
1790
  return guild && typeof guild.name === "string" && guild.name.trim() ? guild.name : void 0;
2367
1791
  },
2368
1792
  enqueuePlayback: (playbackEntry, task) => {
2369
- playbackEntry.playbackQueue = playbackEntry.playbackQueue.then(task).catch((err) => logger$3.warn(`discord voice: playback failed: ${formatErrorMessage(err)}`));
1793
+ playbackEntry.playbackQueue = playbackEntry.playbackQueue.then(task).catch((err) => logger$4.warn(`discord voice: playback failed: ${formatErrorMessage(err)}`));
2370
1794
  }
2371
1795
  };
2372
1796
  }
2373
1797
  async receiveSpeaker(entry, userId, reservation, conversationAllowed, admittedIngress) {
2374
- const voiceSdk = loadDiscordVoiceSdk();
2375
1798
  const realtime = entry.realtimeLifecycle.status === "active" ? entry.realtimeLifecycle.instance : void 0;
2376
- const protectedPlayback = () => entry.player.state.status === voiceSdk.AudioPlayerStatus.Playing && !realtime?.canReceiveDuringPlayback();
1799
+ const protectedPlayback = () => entry.audio.playerStatus === "playing" && !realtime?.canReceiveDuringPlayback();
2377
1800
  this.enableDaveReceivePassthrough(entry, `speaker ${userId} start`, 15);
2378
- const stream = reservation.stream = entry.connection.receiver.subscribe(userId, { end: { behavior: voiceSdk.EndBehaviorType.Manual } });
2379
1801
  if (!entry.audioInputBudget.enabled && !realtime) {
2380
- logger$3.warn("discord voice: capture skipped: audio understanding is disabled; enable tools.media.audio.enabled to transcribe voice.");
1802
+ logger$4.warn("discord voice: capture skipped: audio understanding is disabled; enable tools.media.audio.enabled to transcribe voice.");
2381
1803
  return;
2382
1804
  }
1805
+ const captureReceipts = this.params.bindCaptureReceipts(entry);
1806
+ let stream;
1807
+ try {
1808
+ stream = entry.audio.subscribe(userId, captureReceipts.state);
1809
+ } catch (error) {
1810
+ captureReceipts.close();
1811
+ throw error;
1812
+ }
1813
+ reservation.stream = stream;
1814
+ reservation.stopInput = () => stream.stopInput();
1815
+ clearVoiceCaptureFinalizeTimer(reservation);
1816
+ const finalizeReservation = () => finishVoiceCapture(entry.capture, userId, reservation);
1817
+ if (stream.physicalFinalized) finalizeReservation();
1818
+ else stream.once("finalized", finalizeReservation);
2383
1819
  const input = new PassThrough({ objectMode: true });
2384
1820
  const receipts = /* @__PURE__ */ new WeakMap();
2385
1821
  let failed = false;
2386
1822
  let pendingPackets = 0;
2387
1823
  let pendingBytes = 0;
2388
- const acceptPacket = (packet) => {
2389
- if (failed || !packet.length || !this.params.isEntryCurrent(entry) || entry.capture.get(userId) !== reservation) return;
2390
- const capture = entry.transcripts;
2391
- if (!capture && !conversationAllowed) return;
1824
+ let resetReceiveRecovery = false;
1825
+ const acceptPacket = (frame) => {
1826
+ const packet = Buffer.from(frame.packet);
1827
+ if (failed || !packet.length || stream.destroyed) {
1828
+ stream.acknowledge(frame);
1829
+ return;
1830
+ }
1831
+ const capture = captureReceipts.resolve(frame.recordingEpoch);
1832
+ if (!capture && (!conversationAllowed || !this.params.isEntryCurrent(entry))) {
1833
+ stream.acknowledge(frame);
1834
+ return;
1835
+ }
2392
1836
  if (pendingPackets >= MAX_PENDING_OPUS_PACKETS || packet.length > MAX_PENDING_OPUS_BYTES - pendingBytes) {
2393
1837
  onError(/* @__PURE__ */ new Error("Discord voice receive backlog exceeded; try speaking again."));
2394
- finishVoiceCapture(entry.capture, userId, reservation);
2395
1838
  input.destroy();
2396
1839
  stream.destroy();
2397
1840
  return;
2398
1841
  }
1842
+ if (!resetReceiveRecovery && frame.pcm.byteLength > 0 && this.params.isEntryCurrent(entry)) {
1843
+ resetReceiveRecovery = true;
1844
+ this.resetDecryptFailureState(entry);
1845
+ }
2399
1846
  pendingPackets += 1;
2400
1847
  pendingBytes += packet.length;
2401
1848
  const receivedPacket = Buffer.from(packet);
2402
1849
  receipts.set(receivedPacket, {
2403
1850
  capture,
2404
- startedAt: Date.now()
1851
+ startedAt: frame.receivedAt,
1852
+ recordingEpoch: frame.recordingEpoch
1853
+ });
1854
+ input.write({
1855
+ pcm: Buffer.from(frame.pcm),
1856
+ packet: receivedPacket,
1857
+ frame
2405
1858
  });
2406
- input.write(receivedPacket);
2407
1859
  };
2408
1860
  const endInput = () => input.end();
2409
1861
  let aborted = false;
2410
- let resetReceiveRecovery = false;
2411
1862
  const onError = (error) => {
2412
1863
  const analysis = analyzeVoiceReceiveError(error);
2413
1864
  if (analysis.isAbortLike && !analysis.countsAsDecryptFailure) {
@@ -2432,7 +1883,7 @@ var DiscordVoiceReceive = class {
2432
1883
  isCurrent: () => this.params.isEntryCurrent(entry),
2433
1884
  canAdmit: () => !protectedPlayback(),
2434
1885
  createTurn: realtime ? (context) => realtime.beginSpeakerTurn(context, userId, realtimeRecording) : void 0,
2435
- warn: (message) => logger$3.warn(message)
1886
+ warn: (message) => logger$4.warn(message)
2436
1887
  }) : void 0;
2437
1888
  const recording = new DiscordVoiceRecording({
2438
1889
  entry,
@@ -2458,39 +1909,40 @@ var DiscordVoiceReceive = class {
2458
1909
  let conversationCompletion;
2459
1910
  try {
2460
1911
  if (!conversation && !entry.transcripts?.isCurrent()) return;
2461
- await decodeOpusStreamChunks(input, {
2462
- onChunk: async (pcm, packet) => {
2463
- const receipt = receipts.get(packet);
2464
- if (!receipt || failed) return;
2465
- pendingPackets -= 1;
2466
- pendingBytes -= packet.length;
2467
- receipts.delete(packet);
2468
- if (!resetReceiveRecovery && pcm.length > 0) {
2469
- resetReceiveRecovery = true;
2470
- this.resetDecryptFailureState(entry);
1912
+ const processFrame = async (pcm, packet) => {
1913
+ const receipt = receipts.get(packet);
1914
+ if (!receipt || failed) return;
1915
+ pendingPackets -= 1;
1916
+ pendingBytes -= packet.length;
1917
+ receipts.delete(packet);
1918
+ if (!receipt.capture && conversation && !conversation.ingress) {
1919
+ if (!await waitForVoiceCaptureAdmission({
1920
+ capture: reservation,
1921
+ conversationAuthorized: conversation.ready.then(() => conversation.ingress !== null),
1922
+ isRecordingCurrent: () => entry.transcripts?.isCurrent() === true
1923
+ })) {
1924
+ stream.destroy();
1925
+ return;
2471
1926
  }
2472
- if (!receipt.capture && conversation && !conversation.ingress) {
2473
- if (!await waitForVoiceCaptureAdmission({
2474
- capture: reservation,
2475
- conversationAuthorized: conversation.ready.then(() => conversation.ingress !== null),
2476
- isRecordingCurrent: () => entry.transcripts?.isCurrent() === true
2477
- })) {
2478
- finishVoiceCapture(entry.capture, userId, reservation);
2479
- stream.destroy();
2480
- return;
2481
- }
1927
+ }
1928
+ if (failed) return;
1929
+ realtimeRecording?.noteReceipt(receipt);
1930
+ conversation?.sendAudio(pcm, receipt);
1931
+ await recording.append(pcm, receipt);
1932
+ };
1933
+ try {
1934
+ for await (const decoded of input) {
1935
+ const frame = decoded;
1936
+ try {
1937
+ await processFrame(frame.pcm, frame.packet);
1938
+ } finally {
1939
+ stream.acknowledge(frame.frame);
2482
1940
  }
2483
- if (failed) return;
2484
- realtimeRecording?.noteReceipt(receipt);
2485
- conversation?.sendAudio(pcm, receipt);
2486
- await recording.append(pcm, receipt);
2487
- },
2488
- onError,
2489
- onVerbose: logVoiceVerbose,
2490
- onWarn: (message) => logger$3.warn(message)
2491
- });
1941
+ }
1942
+ } catch (error) {
1943
+ onError(error);
1944
+ }
2492
1945
  await recording.finish();
2493
- finishVoiceCapture(entry.capture, userId, reservation);
2494
1946
  stream.destroy();
2495
1947
  if (conversation) {
2496
1948
  if (realtime) conversationCompletion = entry.conversations.finishAudio(conversation);
@@ -2507,13 +1959,14 @@ var DiscordVoiceReceive = class {
2507
1959
  ingress: currentIngress,
2508
1960
  transcript
2509
1961
  });
2510
- }).catch((error) => logger$3.warn(`discord voice: processing failed: ${formatErrorMessage(error)}`));
1962
+ }).catch((error) => logger$4.warn(`discord voice: processing failed: ${formatErrorMessage(error)}`));
2511
1963
  }
2512
1964
  }
2513
1965
  } finally {
2514
1966
  realtimeRecording?.sealBatch();
2515
- if (conversationCompletion) conversationCompletion.catch((error) => logger$3.warn(`discord voice: conversation failed: ${formatErrorMessage(error)}`));
1967
+ if (conversationCompletion) conversationCompletion.catch((error) => logger$4.warn(`discord voice: conversation failed: ${formatErrorMessage(error)}`));
2516
1968
  else if (conversation) entry.conversations.release(conversation);
1969
+ captureReceipts.close();
2517
1970
  stream.off("data", acceptPacket);
2518
1971
  stream.off("end", endInput);
2519
1972
  stream.off("close", endInput);
@@ -2531,46 +1984,19 @@ var DiscordVoiceReceive = class {
2531
1984
  logVoiceVerbose(`receive decode skipped: ${analysis.message}`);
2532
1985
  return;
2533
1986
  }
2534
- logger$3.warn(`discord voice: receive error: ${analysis.message}`);
2535
- if (analysis.shouldAttemptPassthrough) {
2536
- if (this.params.isEntryCurrent(entry)) {
2537
- if (recoverDaveZeroTransition({
2538
- target: entry,
2539
- sdk: loadDiscordVoiceSdk(),
2540
- onWarn: (message) => logger$3.warn(message)
2541
- }) === "failed") {
2542
- this.startDecryptRecovery(entry, true);
2543
- return;
2544
- }
2545
- }
2546
- this.enableDaveReceivePassthrough(entry, "receive decrypt error", 15);
1987
+ logger$4.warn(`discord voice: receive error: ${analysis.message}`);
1988
+ if (err instanceof Error && "daveRecoveryFailed" in err && err.daveRecoveryFailed === true) {
1989
+ this.startDecryptRecovery(entry, true);
1990
+ return;
2547
1991
  }
2548
1992
  if (!analysis.countsAsDecryptFailure) return;
2549
1993
  const decryptFailure = noteVoiceDecryptFailure(entry.receiveRecovery);
2550
- if (decryptFailure.firstFailure) logger$3.warn("discord voice: DAVE decrypt failures detected; voice receive may be unstable (upstream: discordjs/discord.js#11419)");
1994
+ if (decryptFailure.firstFailure) logger$4.warn("discord voice: DAVE decrypt failures detected; voice receive may be unstable (upstream: discordjs/discord.js#11419)");
2551
1995
  if (!decryptFailure.shouldRecover) return;
2552
1996
  this.startDecryptRecovery(entry);
2553
1997
  }
2554
1998
  enableDaveReceivePassthrough(entry, reason, expirySeconds) {
2555
- const voiceSdk = loadDiscordVoiceSdk();
2556
- return enableDaveReceivePassthrough({
2557
- target: {
2558
- guildId: entry.guildId,
2559
- channelId: entry.channelId,
2560
- connection: entry.connection
2561
- },
2562
- sdk: {
2563
- VoiceConnectionStatus: { Ready: voiceSdk.VoiceConnectionStatus.Ready },
2564
- NetworkingStatusCode: {
2565
- Ready: voiceSdk.NetworkingStatusCode.Ready,
2566
- Resuming: voiceSdk.NetworkingStatusCode.Resuming
2567
- }
2568
- },
2569
- reason,
2570
- expirySeconds,
2571
- onVerbose: logVoiceVerbose,
2572
- onWarn: (message) => logger$3.warn(message)
2573
- });
1999
+ entry.audio.enablePassthrough(reason, expirySeconds);
2574
2000
  }
2575
2001
  async resolveDiscordVoiceIngressContext(entry, userId) {
2576
2002
  return await resolveDiscordVoiceIngressContextWithParticipants({
@@ -2591,7 +2017,7 @@ var DiscordVoiceReceive = class {
2591
2017
  const currentContext = await this.resolveDiscordVoiceIngressContext(entry, userId);
2592
2018
  params.signal?.throwIfAborted();
2593
2019
  if (!this.params.isEntryCurrent(entry) || !params.isCurrent() || !currentContext || currentContext.isCurrent?.() === false || currentContext.senderIsOwner !== context.senderIsOwner) throw new DOMException("Discord voice speaker authorization changed before delegation", "AbortError");
2594
- logger$3.info(`discord voice: agent turn start guild=${entry.guildId} channel=${entry.channelId} voiceSession=${entry.voiceSessionKey} supervisorSession=${entry.route.sessionKey} agent=${entry.route.agentId} user=${userId} speaker=${context.speakerLabel} owner=${context.senderIsOwner} model=${this.params.discordConfig.voice?.model ?? "route-default"} message=${formatVoiceLogPreview(message)}`);
2020
+ logger$4.info(`discord voice: agent turn start guild=${entry.guildId} channel=${entry.channelId} voiceSession=${entry.voiceSessionKey} supervisorSession=${entry.route.sessionKey} agent=${entry.route.agentId} user=${userId} speaker=${context.speakerLabel} owner=${context.senderIsOwner} model=${this.params.discordConfig.voice?.model ?? "route-default"} message=${formatVoiceLogPreview(message)}`);
2595
2021
  const turn = await runDiscordVoiceAgentTurn({
2596
2022
  entry,
2597
2023
  accountId: this.params.accountId,
@@ -2615,7 +2041,7 @@ var DiscordVoiceReceive = class {
2615
2041
  logVoiceVerbose(`realtime agent unauthorized: guild ${entry.guildId} channel ${entry.channelId} user ${userId}`);
2616
2042
  return "";
2617
2043
  }
2618
- logger$3.info(`discord voice: agent turn answer (${turn.text.length} chars) guild=${entry.guildId} channel=${entry.channelId} voiceSession=${entry.voiceSessionKey} supervisorSession=${entry.route.sessionKey} agent=${entry.route.agentId}: ${formatVoiceLogPreview(turn.text)}`);
2044
+ logger$4.info(`discord voice: agent turn answer (${turn.text.length} chars) guild=${entry.guildId} channel=${entry.channelId} voiceSession=${entry.voiceSessionKey} supervisorSession=${entry.route.sessionKey} agent=${entry.route.agentId}: ${formatVoiceLogPreview(turn.text)}`);
2619
2045
  return turn.text;
2620
2046
  }
2621
2047
  startDecryptRecovery(entry, force = false) {
@@ -2628,14 +2054,14 @@ var DiscordVoiceReceive = class {
2628
2054
  entry.receiveRecovery.decryptRecoveryInFlight = true;
2629
2055
  if (this.daveRecoveryAttempts.has(entry.guildId)) {
2630
2056
  const windowSeconds = DECRYPT_FAILURE_WINDOW_MS / 1e3;
2631
- logger$3.warn(`discord voice: DAVE recovery failed again within ${windowSeconds} seconds; disconnecting guild=${entry.guildId} channel=${entry.channelId} to avoid a reconnect loop; retry /vc join after the voice gateway recovers`);
2057
+ logger$4.warn(`discord voice: DAVE recovery failed again within ${windowSeconds} seconds; disconnecting guild=${entry.guildId} channel=${entry.channelId} to avoid a reconnect loop; retry /vc join after the voice gateway recovers`);
2632
2058
  recovery = this.params.leave({ guildId: entry.guildId }, { preserveFollowState: this.params.isFollowOwnedGuild(entry.guildId) });
2633
2059
  } else {
2634
2060
  this.daveRecoveryAttempts.set(entry.guildId, now);
2635
2061
  recovery = this.recoverFromDecryptFailures(entry);
2636
2062
  }
2637
2063
  } else recovery = this.recoverFromDecryptFailures(entry);
2638
- recovery.catch((recoverErr) => logger$3.warn(`discord voice: decrypt recovery failed: ${formatErrorMessage(recoverErr)}`)).finally(() => {
2064
+ recovery.catch((recoverErr) => logger$4.warn(`discord voice: decrypt recovery failed: ${formatErrorMessage(recoverErr)}`)).finally(() => {
2639
2065
  finishVoiceDecryptRecovery(entry.receiveRecovery);
2640
2066
  });
2641
2067
  }
@@ -2645,12 +2071,12 @@ var DiscordVoiceReceive = class {
2645
2071
  }
2646
2072
  async recoverFromDecryptFailures(entry) {
2647
2073
  const active = this.params.getSession(entry.guildId);
2648
- if (!active || active.connection !== entry.connection) return;
2074
+ if (!active || active.audio !== entry.audio) return;
2649
2075
  const preserveFollowState = this.params.isFollowOwnedGuild(entry.guildId);
2650
- logger$3.warn(`discord voice: repeated decrypt failures; attempting rejoin for guild ${entry.guildId} channel ${entry.channelId}`);
2076
+ logger$4.warn(`discord voice: repeated decrypt failures; attempting rejoin for guild ${entry.guildId} channel ${entry.channelId}`);
2651
2077
  const leaveResult = await this.params.leave({ guildId: entry.guildId }, { preserveFollowState });
2652
2078
  if (!leaveResult.ok) {
2653
- logger$3.warn(`discord voice: decrypt recovery leave failed: ${leaveResult.message}`);
2079
+ logger$4.warn(`discord voice: decrypt recovery leave failed: ${leaveResult.message}`);
2654
2080
  return;
2655
2081
  }
2656
2082
  const result = await this.params.join({
@@ -2661,10 +2087,334 @@ var DiscordVoiceReceive = class {
2661
2087
  autoJoinWhenOccupied: entry.autoJoinWhenOccupied,
2662
2088
  captureOnly: entry.captureOnly
2663
2089
  });
2664
- if (!result.ok) logger$3.warn(`discord voice: rejoin after decrypt failures failed: ${result.message}`);
2090
+ if (!result.ok) logger$4.warn(`discord voice: rejoin after decrypt failures failed: ${result.message}`);
2665
2091
  }
2666
2092
  };
2667
2093
  //#endregion
2094
+ //#region extensions/discord/src/voice/audio-worker-thread.ts
2095
+ /** Source, core-bundled and standalone plugin workers use the same launch owner. */
2096
+ function createDiscordAudioWorkerThread(options) {
2097
+ const url = resolveRuntimeWorkerUrl({
2098
+ currentModuleUrl: import.meta.url,
2099
+ sourceWorkerName: "audio-worker.runtime",
2100
+ distWorkerPath: "extensions/discord/src/voice/audio-worker.runtime.js",
2101
+ package: {
2102
+ name: "@openclaw/discord",
2103
+ distWorkerPath: "src/voice/audio-worker.runtime.js"
2104
+ }
2105
+ });
2106
+ return new Worker(url, {
2107
+ workerData: options,
2108
+ execArgv: resolveRuntimeWorkerArgv(url).slice(0, -1)
2109
+ });
2110
+ }
2111
+ //#endregion
2112
+ //#region extensions/discord/src/voice/audio-transport.ts
2113
+ const logger$3 = createSubsystemLogger("discord/voice");
2114
+ const STOP_GRACE_MS = 2e3;
2115
+ /** A received frame owns cloned PCM; its credit remains held until the consumer settles. */
2116
+ var DiscordAudioCapture = class extends Readable {
2117
+ finalizePhysicalSubscription() {
2118
+ if (this.physicalFinalized) return;
2119
+ this.physicalFinalized = true;
2120
+ this.emit("finalized");
2121
+ }
2122
+ constructor(id, send) {
2123
+ super({ objectMode: true });
2124
+ this.id = id;
2125
+ this.send = send;
2126
+ this.physicalFinalized = false;
2127
+ }
2128
+ _read() {}
2129
+ stopInput() {
2130
+ this.send({
2131
+ type: "capture-stop",
2132
+ id: this.id
2133
+ });
2134
+ }
2135
+ acknowledge(frame) {
2136
+ this.send({
2137
+ type: "capture-ack",
2138
+ id: this.id,
2139
+ bytes: frame.packet.byteLength
2140
+ });
2141
+ }
2142
+ _destroy(error, done) {
2143
+ this.send({
2144
+ type: "capture-stop",
2145
+ id: this.id
2146
+ });
2147
+ done(error);
2148
+ }
2149
+ };
2150
+ /** Control-plane handle, never a substitute SDK connection or audio player. */
2151
+ var DiscordAudioTransport = class extends EventEmitter {
2152
+ constructor(options, adapterCreator) {
2153
+ super();
2154
+ this.speakingUsers = /* @__PURE__ */ new Set();
2155
+ this.playerStatus = "idle";
2156
+ this.connectionStatus = "signalling";
2157
+ this.captures = /* @__PURE__ */ new Map();
2158
+ this.files = /* @__PURE__ */ new Map();
2159
+ this.nextId = 0;
2160
+ this.stopping = false;
2161
+ this.terminal = false;
2162
+ this.adapterDestroyed = false;
2163
+ this.physicalStopped = new Promise((resolve) => {
2164
+ this.physicalStopResolve = resolve;
2165
+ });
2166
+ this.ready = new Promise((resolve, reject) => {
2167
+ this.readyResolve = resolve;
2168
+ this.readyReject = reject;
2169
+ });
2170
+ this.ready.catch(() => {});
2171
+ this.worker = createDiscordAudioWorkerThread(options);
2172
+ try {
2173
+ this.adapter = adapterCreator({
2174
+ onVoiceServerUpdate: (data) => this.send({
2175
+ type: "gateway-server",
2176
+ data
2177
+ }),
2178
+ onVoiceStateUpdate: (data) => this.send({
2179
+ type: "gateway-state",
2180
+ data
2181
+ }),
2182
+ destroy: () => {
2183
+ this.stop();
2184
+ }
2185
+ });
2186
+ } catch (error) {
2187
+ this.worker.terminate();
2188
+ throw error;
2189
+ }
2190
+ this.worker.on("message", (event) => this.receive(event));
2191
+ this.worker.on("error", (error) => this.finish(toErrorObject(error, "Discord audio worker failed")));
2192
+ this.exited = new Promise((resolve) => {
2193
+ this.worker.once("exit", (code) => {
2194
+ this.finish(/* @__PURE__ */ new Error("Discord audio worker exited (" + code + ")."));
2195
+ resolve();
2196
+ });
2197
+ });
2198
+ }
2199
+ allocateId() {
2200
+ return ++this.nextId;
2201
+ }
2202
+ send(command, transferList) {
2203
+ if (this.terminal || this.stopping && command.type !== "stop") return;
2204
+ try {
2205
+ this.worker.postMessage(command, transferList);
2206
+ } catch (error) {
2207
+ this.finish(error instanceof Error ? error : new Error(String(error)));
2208
+ }
2209
+ }
2210
+ subscribe(userId, recordingEpoch) {
2211
+ if (this.stopping || this.terminal) throw new Error("Discord voice session stopped before capture.");
2212
+ const id = this.allocateId();
2213
+ const capture = new DiscordAudioCapture(id, (command) => this.send(command));
2214
+ this.captures.set(id, capture);
2215
+ capture.once("close", () => {
2216
+ if (capture.physicalFinalized) this.captures.delete(id);
2217
+ });
2218
+ this.send({
2219
+ type: "capture",
2220
+ id,
2221
+ userId,
2222
+ recordingEpoch
2223
+ });
2224
+ return capture;
2225
+ }
2226
+ enablePassthrough(reason, expirySeconds) {
2227
+ this.send({
2228
+ type: "passthrough",
2229
+ reason,
2230
+ expirySeconds
2231
+ });
2232
+ }
2233
+ stopPlayback() {
2234
+ this.send({ type: "player-stop" });
2235
+ }
2236
+ async play(input) {
2237
+ if (this.stopping || this.terminal) throw new Error("Discord voice session stopped before playback.");
2238
+ const id = this.allocateId();
2239
+ const abort = new AbortController();
2240
+ const complete = new Promise((resolve, reject) => {
2241
+ this.files.set(id, {
2242
+ resolve,
2243
+ reject,
2244
+ abort
2245
+ });
2246
+ });
2247
+ complete.catch(() => {});
2248
+ this.send(typeof input === "string" ? {
2249
+ type: "file-play",
2250
+ id,
2251
+ path: input
2252
+ } : {
2253
+ type: "stream-play",
2254
+ id
2255
+ });
2256
+ const pump = typeof input === "string" ? Promise.resolve() : (async () => {
2257
+ const destroy = () => input.destroy();
2258
+ abort.signal.addEventListener("abort", destroy, { once: true });
2259
+ try {
2260
+ for await (const chunk of input) {
2261
+ if (abort.signal.aborted) break;
2262
+ const frame = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2263
+ for (let offset = 0; offset < frame.length && !abort.signal.aborted; offset += 65536) {
2264
+ const playback = this.files.get(id);
2265
+ if (!playback) return;
2266
+ const drained = new Promise((resolve) => {
2267
+ playback.drain = resolve;
2268
+ });
2269
+ this.send({
2270
+ type: "stream-chunk",
2271
+ id,
2272
+ audio: frame.subarray(offset, offset + 65536)
2273
+ });
2274
+ await drained;
2275
+ }
2276
+ }
2277
+ this.send({
2278
+ type: "stream-end",
2279
+ id
2280
+ });
2281
+ } finally {
2282
+ abort.signal.removeEventListener("abort", destroy);
2283
+ }
2284
+ })();
2285
+ try {
2286
+ await Promise.all([pump, complete]);
2287
+ } catch (error) {
2288
+ this.stopPlayback();
2289
+ throw error;
2290
+ } finally {
2291
+ abort.abort();
2292
+ this.files.get(id)?.drain?.();
2293
+ this.files.delete(id);
2294
+ }
2295
+ }
2296
+ stop() {
2297
+ if (this.stopTask) return this.stopTask;
2298
+ this.stopping = true;
2299
+ this.send({ type: "stop" });
2300
+ const drained = (async () => {
2301
+ let timer;
2302
+ try {
2303
+ await Promise.race([this.exited, new Promise((resolve) => {
2304
+ timer = setTimeout(() => {
2305
+ this.worker.terminate().then(() => resolve());
2306
+ }, STOP_GRACE_MS);
2307
+ })]);
2308
+ } finally {
2309
+ clearTimeout(timer);
2310
+ this.finish(/* @__PURE__ */ new Error("Discord voice session stopped."));
2311
+ }
2312
+ })();
2313
+ this.stopTask = Promise.race([this.physicalStopped, drained]);
2314
+ return this.stopTask;
2315
+ }
2316
+ receive(event) {
2317
+ if (this.terminal) return;
2318
+ switch (event.type) {
2319
+ case "gateway-send":
2320
+ if (!this.adapter.sendPayload(event.payload)) this.send({ type: "gateway-failed" });
2321
+ break;
2322
+ case "gateway-destroy":
2323
+ this.destroyAdapter();
2324
+ this.physicalStopResolve();
2325
+ break;
2326
+ case "ready":
2327
+ this.readyResolve();
2328
+ break;
2329
+ case "stopped":
2330
+ this.finish(/* @__PURE__ */ new Error("Discord voice session stopped."), true);
2331
+ break;
2332
+ case "connection":
2333
+ this.connectionStatus = event.status;
2334
+ if (event.status === "destroyed") this.emit("stopped");
2335
+ break;
2336
+ case "player":
2337
+ this.playerStatus = event.status;
2338
+ break;
2339
+ case "speaking":
2340
+ if (event.speaking) this.speakingUsers.add(event.userId);
2341
+ else this.speakingUsers.delete(event.userId);
2342
+ if (!this.stopping) this.emit("speaking", event.userId, event.speaking);
2343
+ break;
2344
+ case "capture-frame": {
2345
+ const capture = this.captures.get(event.id);
2346
+ if (capture && !capture.destroyed) capture.push({
2347
+ ...event.frame,
2348
+ pcm: Buffer.from(event.frame.pcm),
2349
+ packet: Buffer.from(event.frame.packet)
2350
+ });
2351
+ break;
2352
+ }
2353
+ case "capture-error":
2354
+ this.captures.get(event.id)?.emit("error", restoreDiscordAudioError(event.error));
2355
+ break;
2356
+ case "capture-finalized": {
2357
+ const capture = this.captures.get(event.id);
2358
+ capture?.finalizePhysicalSubscription();
2359
+ if (capture?.destroyed) this.captures.delete(event.id);
2360
+ break;
2361
+ }
2362
+ case "capture-end":
2363
+ this.captures.get(event.id)?.push(null);
2364
+ break;
2365
+ case "file-end": {
2366
+ const playback = this.files.get(event.id);
2367
+ this.files.delete(event.id);
2368
+ playback?.abort.abort();
2369
+ playback?.drain?.();
2370
+ if (event.error) playback?.reject(restoreDiscordAudioError(event.error));
2371
+ else playback?.resolve();
2372
+ break;
2373
+ }
2374
+ case "stream-drain": {
2375
+ const playback = this.files.get(event.id);
2376
+ playback?.drain?.();
2377
+ if (playback) playback.drain = void 0;
2378
+ break;
2379
+ }
2380
+ case "error":
2381
+ this.readyReject(restoreDiscordAudioError(event.error));
2382
+ logger$3.warn("discord voice: " + event.error.message);
2383
+ break;
2384
+ case "log": if (event.level === "warn") logger$3.warn(event.message);
2385
+ else logVerbose(event.message);
2386
+ }
2387
+ if (!this.stopping) this.emit("event", event);
2388
+ }
2389
+ destroyAdapter() {
2390
+ if (this.adapterDestroyed) return;
2391
+ this.adapterDestroyed = true;
2392
+ this.adapter.destroy();
2393
+ }
2394
+ finish(error, drained = false) {
2395
+ if (this.terminal) return;
2396
+ this.terminal = true;
2397
+ this.connectionStatus = "destroyed";
2398
+ this.playerStatus = "idle";
2399
+ this.readyReject(error);
2400
+ this.destroyAdapter();
2401
+ this.speakingUsers.clear();
2402
+ for (const capture of this.captures.values()) if (drained) capture.push(null);
2403
+ else capture.destroy(error);
2404
+ this.captures.clear();
2405
+ for (const playback of this.files.values()) {
2406
+ playback.abort.abort();
2407
+ playback.drain?.();
2408
+ playback.reject(error);
2409
+ }
2410
+ this.files.clear();
2411
+ this.emit("stopped");
2412
+ }
2413
+ };
2414
+ function createDiscordAudioTransport(options, adapterCreator) {
2415
+ return new DiscordAudioTransport(options, adapterCreator);
2416
+ }
2417
+ //#endregion
2668
2418
  //#region extensions/discord/src/voice/voice-conversation-input.ts
2669
2419
  const MAX_PENDING_CONVERSATIONS = 8;
2670
2420
  const MAX_PENDING_CONVERSATION_AUDIO_BYTES = 1048576;
@@ -2889,37 +2639,19 @@ const logger$2 = createSubsystemLogger("discord/voice");
2889
2639
  function isVoiceSessionStopped(entry) {
2890
2640
  return entry.sessionLifecycle.status === "stopped";
2891
2641
  }
2892
- function isVoiceConnectionDestroyed(connection, voiceSdk) {
2893
- return connection.state.status === voiceSdk.VoiceConnectionStatus.Destroyed;
2894
- }
2895
- function destroyVoiceConnectionSafely(params) {
2896
- if (isVoiceConnectionDestroyed(params.connection, params.voiceSdk)) {
2897
- logVoiceVerbose(`destroy skipped: ${params.reason}; connection already destroyed`);
2898
- return;
2899
- }
2900
- try {
2901
- params.connection.destroy();
2902
- } catch (err) {
2903
- const message = formatErrorMessage(err);
2904
- if (message.includes("already been destroyed")) {
2905
- logVoiceVerbose(`destroy skipped: ${params.reason}; ${message}`);
2906
- return;
2907
- }
2908
- logger$2.warn(`discord voice: destroy failed: ${params.reason}: ${message}`);
2909
- }
2910
- }
2911
- function isRetryableVoiceJoinReadyError(error) {
2912
- return formatErrorMessage(error).toLowerCase().includes("operation was aborted");
2913
- }
2914
- function resolveVoiceConnectionGroup(accountId) {
2915
- return `openclaw:${accountId}`;
2916
- }
2917
2642
  var DiscordVoiceSessions = class {
2643
+ async stopTransport(guildId, audio = this.transports.get(guildId)) {
2644
+ if (!audio) return;
2645
+ await audio.stop();
2646
+ if (this.transports.get(guildId) === audio) this.transports.delete(guildId);
2647
+ }
2918
2648
  constructor(params) {
2919
2649
  this.params = params;
2920
2650
  this.pendingStops = /* @__PURE__ */ new Set();
2651
+ this.transports = /* @__PURE__ */ new Map();
2921
2652
  }
2922
2653
  async waitForStops() {
2654
+ await Promise.allSettled([...this.transports.keys()].map((guildId) => this.stopTransport(guildId)));
2923
2655
  await Promise.allSettled(this.pendingStops);
2924
2656
  }
2925
2657
  refreshGuildRoster(guildId) {
@@ -3016,77 +2748,37 @@ var DiscordVoiceSessions = class {
3016
2748
  const connectReadyTimeoutMs = resolveVoiceTimeoutMs(voiceConfig?.connectTimeoutMs, VOICE_CONNECT_READY_TIMEOUT_MS);
3017
2749
  const reconnectGraceMs = resolveVoiceTimeoutMs(voiceConfig?.reconnectGraceMs, VOICE_RECONNECT_GRACE_MS);
3018
2750
  logVoiceVerbose(`join: DAVE settings encryption=${daveEncryption === false ? "off" : "on"} tolerance=${decryptionFailureTolerance ?? "default"} connectTimeout=${connectReadyTimeoutMs}ms reconnectGrace=${reconnectGraceMs}ms`);
3019
- const voiceSdk = loadDiscordVoiceSdk();
3020
- const existingEntry = this.params.sessions.get(guildId);
3021
- if (existingEntry) existingEntry.stop();
3022
- const voiceConnectionGroup = resolveVoiceConnectionGroup(this.params.accountId);
3023
- const staleConnection = voiceSdk.getVoiceConnection(guildId, voiceConnectionGroup);
3024
- if (staleConnection) destroyVoiceConnectionSafely({
3025
- connection: staleConnection,
3026
- voiceSdk,
3027
- reason: `stale connection before join guild ${guildId}`
3028
- });
3029
- let connection;
3030
- const connectReadyDeadlineMs = Date.now() + connectReadyTimeoutMs;
3031
- for (let attempt = 1; attempt <= 2; attempt += 1) {
3032
- const joinedConnection = voiceSdk.joinVoiceChannel({
3033
- channelId,
3034
- guildId,
3035
- group: voiceConnectionGroup,
3036
- adapterCreator,
3037
- selfDeaf: false,
3038
- selfMute: false,
3039
- daveEncryption,
3040
- decryptionFailureTolerance
3041
- });
3042
- const remainingConnectReadyTimeoutMs = Math.max(1, connectReadyDeadlineMs - Date.now());
3043
- try {
3044
- await voiceSdk.entersState(joinedConnection, voiceSdk.VoiceConnectionStatus.Ready, remainingConnectReadyTimeoutMs);
3045
- connection = joinedConnection;
3046
- logVoiceVerbose(`join: connected to guild ${guildId} channel ${channelId}`);
3047
- break;
3048
- } catch (err) {
3049
- destroyVoiceConnectionSafely({
3050
- connection: joinedConnection,
3051
- voiceSdk,
3052
- reason: `failed join cleanup guild ${guildId} channel ${channelId}`
3053
- });
3054
- if (attempt === 1 && isRetryableVoiceJoinReadyError(err) && !this.params.destroyed() && connectReadyDeadlineMs > Date.now()) {
3055
- logVoiceVerbose(`join: retrying aborted ready wait guild ${guildId} channel ${channelId}`);
3056
- continue;
3057
- }
3058
- logger$2.warn(`discord voice: join failed before ready: guild ${guildId} channel ${channelId} timeout=${connectReadyTimeoutMs}ms error=${formatErrorMessage(err)}`);
3059
- return {
3060
- ok: false,
3061
- message: `Failed to join voice channel: ${formatErrorMessage(err)}`
3062
- };
3063
- }
3064
- }
3065
- if (!connection) return {
3066
- ok: false,
3067
- message: "Failed to join voice channel."
3068
- };
3069
- if (authority && !authority.isCurrent()) {
3070
- destroyVoiceConnectionSafely({
3071
- connection,
3072
- voiceSdk,
3073
- reason: `cancelled join guild ${guildId} channel ${channelId}`
3074
- });
3075
- return cancelledJoinResult();
3076
- }
3077
- if (this.params.destroyed()) {
3078
- destroyVoiceConnectionSafely({
3079
- connection,
3080
- voiceSdk,
3081
- reason: `manager stopped during join guild ${guildId} channel ${channelId}`
3082
- });
2751
+ await this.stopTransport(guildId);
2752
+ if (this.params.destroyed() || authority?.isCurrent() === false) return cancelledJoinResult();
2753
+ const audio = createDiscordAudioTransport({
2754
+ guildId,
2755
+ channelId,
2756
+ group: "openclaw:" + this.params.accountId,
2757
+ selfDeaf: false,
2758
+ selfMute: false,
2759
+ daveEncryption,
2760
+ decryptionFailureTolerance,
2761
+ connectTimeoutMs: connectReadyTimeoutMs,
2762
+ reconnectGraceMs,
2763
+ captureSilenceGraceMs: resolveVoiceTimeoutMs(voiceConfig?.captureSilenceGraceMs, CAPTURE_FINALIZE_GRACE_MS),
2764
+ realtime: isDiscordRealtimeVoiceMode(voiceMode)
2765
+ }, adapterCreator);
2766
+ this.transports.set(guildId, audio);
2767
+ try {
2768
+ await audio.ready;
2769
+ } catch (error) {
2770
+ await this.stopTransport(guildId);
3083
2771
  return {
3084
2772
  ok: false,
3085
- message: "Discord voice manager is stopped.",
2773
+ message: "Failed to join voice channel: " + formatErrorMessage(error),
3086
2774
  guildId,
3087
2775
  channelId
3088
2776
  };
3089
2777
  }
2778
+ if (this.params.destroyed() || authority?.isCurrent() === false) {
2779
+ await this.stopTransport(guildId);
2780
+ return cancelledJoinResult();
2781
+ }
3090
2782
  const sessionChannelId = channelInfo?.id ?? channelId;
3091
2783
  if (sessionChannelId !== channelId) logVoiceVerbose(`join: using session channel ${sessionChannelId} for voice channel ${channelId}`);
3092
2784
  let routeInfo;
@@ -3099,11 +2791,7 @@ var DiscordVoiceSessions = class {
3099
2791
  voiceConfig
3100
2792
  });
3101
2793
  } catch (err) {
3102
- destroyVoiceConnectionSafely({
3103
- connection,
3104
- voiceSdk,
3105
- reason: `voice agent session route failed guild ${guildId} channel ${channelId}`
3106
- });
2794
+ await this.stopTransport(guildId);
3107
2795
  return {
3108
2796
  ok: false,
3109
2797
  message: `Failed to resolve Discord voice agent session: ${formatErrorMessage(err)}`,
@@ -3113,8 +2801,6 @@ var DiscordVoiceSessions = class {
3113
2801
  }
3114
2802
  const { route, voiceRoute, agentSessionMode, agentSessionTarget } = routeInfo;
3115
2803
  logger$2.info(`discord voice: joining guild=${guildId} channel=${channelId} mode=${voiceMode} agent=${route.agentId} voiceSession=${voiceRoute.sessionKey} supervisorSession=${route.sessionKey} agentSessionMode=${agentSessionMode}${agentSessionTarget ? ` agentSessionTarget=${agentSessionTarget}` : ""} voiceModel=${voiceConfig?.model ?? "route-default"} realtimeProvider=${voiceConfig?.realtime?.provider ?? "auto"} realtimeModel=${voiceConfig?.realtime?.model ?? "provider-default"} realtimeVoice=${voiceConfig?.realtime?.speakerVoice ?? voiceConfig?.realtime?.speakerVoiceId ?? "provider-default"}`);
3116
- const player = isDiscordRealtimeVoiceMode(voiceMode) ? voiceSdk.createAudioPlayer({ behaviors: { maxMissedFrames: DISCORD_REALTIME_PLAYBACK_IDLE_MS / 20 } }) : voiceSdk.createAudioPlayer();
3117
- connection.subscribe(player);
3118
2804
  let stopCompletion;
3119
2805
  const stopEntry = (optionsLocal) => {
3120
2806
  if (entry.sessionLifecycle.status === "stopped") return stopCompletion;
@@ -3124,12 +2810,9 @@ var DiscordVoiceSessions = class {
3124
2810
  };
3125
2811
  if (this.params.sessions.get(guildId) === entry) this.params.sessions.delete(guildId);
3126
2812
  this.params.membership.deactivate(entry);
3127
- connection.receiver.speaking.off("start", speakingHandler);
3128
- connection.receiver.speaking.off("end", speakingEndHandler);
2813
+ audio.off("speaking", speakingHandler);
3129
2814
  stopVoiceCaptureState(entry.capture);
3130
- connection.off(voiceSdk.VoiceConnectionStatus.Disconnected, disconnectedHandler);
3131
- connection.off(voiceSdk.VoiceConnectionStatus.Destroyed, destroyedHandler);
3132
- player.off("error", playerErrorHandler);
2815
+ audio.off("stopped", destroyedHandler);
3133
2816
  const realtimeLifecycle = entry.realtimeLifecycle;
3134
2817
  entry.realtimeLifecycle = {
3135
2818
  status: "stopped",
@@ -3142,28 +2825,22 @@ var DiscordVoiceSessions = class {
3142
2825
  } catch (error) {
3143
2826
  logger$2.warn(`discord voice: realtime close failed: ${formatErrorMessage(error)}`);
3144
2827
  }
3145
- player.stop(true);
3146
- if (optionsLocal.destroyConnection) destroyVoiceConnectionSafely({
3147
- connection,
3148
- voiceSdk,
3149
- reason: optionsLocal.reason
3150
- });
2828
+ const audioCompletion = this.stopTransport(guildId, audio);
2829
+ realtimeCompletion = Promise.allSettled([realtimeCompletion, audioCompletion]).then(() => void 0);
3151
2830
  const finish = () => {
3152
2831
  entry.conversations.close();
3153
2832
  this.params.onSessionStopped(entry, optionsLocal.reason);
3154
2833
  };
3155
- if (realtimeCompletion) {
3156
- stopCompletion = realtimeCompletion.catch((error) => logger$2.warn(`discord voice: realtime close failed: ${formatErrorMessage(error)}`)).then(finish);
3157
- const completion = stopCompletion;
3158
- this.pendingStops.add(completion);
3159
- const forget = () => {
3160
- this.pendingStops.delete(completion);
3161
- };
3162
- completion.then(forget, (error) => {
3163
- forget();
3164
- logger$2.warn(`discord voice: session stop failed: ${formatErrorMessage(error)}`);
3165
- });
3166
- } else finish();
2834
+ stopCompletion = realtimeCompletion.catch((error) => logger$2.warn(`discord voice: realtime close failed: ${formatErrorMessage(error)}`)).then(finish);
2835
+ const completion = stopCompletion;
2836
+ this.pendingStops.add(completion);
2837
+ const forget = () => {
2838
+ this.pendingStops.delete(completion);
2839
+ };
2840
+ completion.then(forget, (error) => {
2841
+ forget();
2842
+ logger$2.warn(`discord voice: session stop failed: ${formatErrorMessage(error)}`);
2843
+ });
3167
2844
  return stopCompletion;
3168
2845
  };
3169
2846
  const getTranscripts = this.params.getTranscripts;
@@ -3179,8 +2856,7 @@ var DiscordVoiceSessions = class {
3179
2856
  sessionChannelId,
3180
2857
  voiceSessionKey: voiceRoute.sessionKey,
3181
2858
  route,
3182
- connection,
3183
- player,
2859
+ audio,
3184
2860
  playbackQueue: Promise.resolve(),
3185
2861
  processingQueue: Promise.resolve(),
3186
2862
  conversations: new DiscordVoiceConversationQueue(),
@@ -3196,47 +2872,19 @@ var DiscordVoiceSessions = class {
3196
2872
  generation: 0
3197
2873
  },
3198
2874
  stop(reason) {
3199
- return stopEntry({
3200
- destroyConnection: true,
3201
- reason: reason ?? `stop guild ${guildId} channel ${channelId}`
3202
- });
2875
+ return stopEntry({ reason: reason ?? `stop guild ${guildId} channel ${channelId}` });
3203
2876
  }
3204
2877
  };
3205
- const speakingHandler = (userId) => {
3206
- this.params.receive.handleSpeakingStart(entry, userId).catch((err) => {
3207
- logger$2.warn(`discord voice: capture failed: ${formatErrorMessage(err)}`);
2878
+ const speakingHandler = (userId, speaking) => {
2879
+ if (speaking) this.params.receive.handleSpeakingStart(entry, userId).catch((error) => {
2880
+ logger$2.warn("discord voice: capture failed: " + formatErrorMessage(error));
3208
2881
  });
3209
- };
3210
- const speakingEndHandler = (userId) => {
3211
- this.params.receive.scheduleCaptureFinalize(entry, userId, "speaker end");
3212
- };
3213
- const disconnectedHandler = () => {
3214
- (async () => {
3215
- try {
3216
- logVoiceVerbose(`disconnected: attempting recovery guild ${guildId} channel ${channelId} grace=${reconnectGraceMs}ms`);
3217
- await Promise.race([voiceSdk.entersState(connection, voiceSdk.VoiceConnectionStatus.Signalling, reconnectGraceMs), voiceSdk.entersState(connection, voiceSdk.VoiceConnectionStatus.Connecting, reconnectGraceMs)]);
3218
- logVoiceVerbose(`disconnected: recovery started guild ${guildId} channel ${channelId}`);
3219
- } catch (err) {
3220
- logger$2.warn(`discord voice: disconnect recovery failed: guild ${guildId} channel ${channelId} timeout=${reconnectGraceMs}ms error=${formatErrorMessage(err)}; destroying connection`);
3221
- stopEntry({
3222
- destroyConnection: true,
3223
- reason: `disconnect recovery failed guild ${guildId} channel ${channelId}`
3224
- });
3225
- }
3226
- })();
2882
+ else this.params.receive.scheduleCaptureFinalize(entry, userId, "speaker end");
3227
2883
  };
3228
2884
  const destroyedHandler = () => {
3229
- stopEntry({
3230
- destroyConnection: false,
3231
- reason: `destroyed guild ${guildId} channel ${channelId}`
3232
- });
3233
- };
3234
- const playerErrorHandler = (err) => {
3235
- logger$2.warn(`discord voice: playback error: ${formatErrorMessage(err)}`);
2885
+ stopEntry({ reason: "audio worker stopped" });
3236
2886
  };
3237
- connection.on(voiceSdk.VoiceConnectionStatus.Disconnected, disconnectedHandler);
3238
- connection.on(voiceSdk.VoiceConnectionStatus.Destroyed, destroyedHandler);
3239
- player.on("error", playerErrorHandler);
2887
+ audio.on("stopped", destroyedHandler);
3240
2888
  if (!entry.captureOnly && isDiscordRealtimeVoiceMode(voiceMode)) {
3241
2889
  const realtimeResult = await this.attachRealtimeSession(entry, voiceMode, { isCurrent: authority?.isCurrent });
3242
2890
  if (!realtimeResult.ok) {
@@ -3259,8 +2907,7 @@ var DiscordVoiceSessions = class {
3259
2907
  };
3260
2908
  }
3261
2909
  this.params.receive.enableDaveReceivePassthrough(entry, "post-join warmup", 30);
3262
- connection.receiver.speaking.on("start", speakingHandler);
3263
- connection.receiver.speaking.on("end", speakingEndHandler);
2910
+ audio.on("speaking", speakingHandler);
3264
2911
  this.params.sessions.set(guildId, entry);
3265
2912
  this.params.membership.activate(entry, this.params.botUserId());
3266
2913
  logger$2.info(`discord voice: joined guild=${guildId} channel=${channelId} mode=${voiceMode} agent=${route.agentId} voiceSession=${voiceRoute.sessionKey} supervisorSession=${route.sessionKey} voiceModel=${voiceConfig?.model ?? "route-default"}`);
@@ -3305,7 +2952,7 @@ var DiscordVoiceSessions = class {
3305
2952
  ok: false,
3306
2953
  message: "Discord realtime voice session stopped before startup completed."
3307
2954
  };
3308
- const { DiscordRealtimeVoiceSession } = await import("./realtime-session.runtime-DTccTgTM.mjs");
2955
+ const { DiscordRealtimeVoiceSession } = await import("./realtime-session.runtime-Dz8uVutq.mjs");
3309
2956
  const realtime = new DiscordRealtimeVoiceSession({
3310
2957
  accountId: this.params.accountId,
3311
2958
  bootstrapContextInstructions,
@@ -3473,6 +3120,11 @@ var DiscordVoiceManager = class {
3473
3120
  });
3474
3121
  this.membership = new DiscordVoiceMembershipTracker(params.client, speakerContext, params.accountId);
3475
3122
  this.receive = new DiscordVoiceReceive({
3123
+ bindCaptureReceipts: ({ guildId, channelId }) => bindDiscordCaptureReceipts({
3124
+ accountId: params.accountId,
3125
+ guildId,
3126
+ channelId
3127
+ }, this),
3476
3128
  readPolicy: this.readPolicy,
3477
3129
  accountId: params.accountId,
3478
3130
  admissionAllowFrom,
@@ -3497,7 +3149,7 @@ var DiscordVoiceManager = class {
3497
3149
  client: params.client,
3498
3150
  deleteRecoveryAttempt: (guildId) => this.receive.daveRecoveryAttempts.delete(guildId),
3499
3151
  destroyed: () => this.destroyed,
3500
- destroyVoiceConnection: destroyVoiceConnectionSafely,
3152
+ stopTransport: (guildId) => this.voiceSessions.stopTransport(guildId),
3501
3153
  discordConfig: params.discordConfig,
3502
3154
  getRecoveryAttempt: (guildId) => this.receive.daveRecoveryAttempts.get(guildId),
3503
3155
  getSession: (guildId) => this.sessions.get(guildId),
@@ -3952,4 +3604,4 @@ var DiscordVoiceManager = class {
3952
3604
  }
3953
3605
  };
3954
3606
  //#endregion
3955
- export { DiscordVoiceGuildCreateListener, DiscordVoiceManager, DiscordVoiceReadyListener, DiscordVoiceResumedListener, DiscordVoiceStateUpdateListener, loadDiscordVoiceSdk as _, createDiscordPcmToRealtimeConverter as a, handleDiscordVoiceAgentControlToolCall as c, formatRealtimeInterruptionLog as d, formatRealtimeLifecycleLog as f, DiscordRealtimePlayer as g, DISCORD_REALTIME_PLAYBACK_IDLE_MS as h, createDiscordOpusEncodeStream as i, logDiscordVoiceAgentControlResult as l, shouldLogRealtimeVerboseEvent as m, DiscordRealtimeRecording as n, createRealtimePcmToDiscordConverter as o, formatVoiceLogPreview as p, logVoiceVerbose as r, formatVoiceIngressPrompt as s, voice_runtime_exports as t, maybeControlDiscordVoiceAgentRun as u };
3607
+ export { DiscordVoiceGuildCreateListener, DiscordVoiceManager, DiscordVoiceReadyListener, DiscordVoiceResumedListener, DiscordVoiceStateUpdateListener, handleDiscordVoiceAgentControlToolCall as a, formatRealtimeInterruptionLog as c, shouldLogRealtimeVerboseEvent as d, formatVoiceIngressPrompt as i, formatRealtimeLifecycleLog as l, DiscordRealtimeRecording as n, logDiscordVoiceAgentControlResult as o, logVoiceVerbose as r, maybeControlDiscordVoiceAgentRun as s, voice_runtime_exports as t, formatVoiceLogPreview as u };