@openclaw/whatsapp 2026.7.2-beta.7 → 2026.7.34

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 (435) hide show
  1. package/README.md +1 -1
  2. package/dist/{access-control-pZcxTmd5.js → .setup/access-control-C0jumQuO.mjs} +5 -4
  3. package/dist/.setup/account-config-6PTOSfaI.mjs +47 -0
  4. package/dist/{accounts-CSqLvCY5.js → .setup/accounts-Dw16pdNl.mjs} +9 -47
  5. package/dist/{action-runtime-BzfDFPbH.js → .setup/action-runtime-DLK_9v4r.mjs} +3 -3
  6. package/dist/{active-listener-BEjWsCNM.js → .setup/active-listener-CloPBx0S.mjs} +3 -3
  7. package/dist/{agent-tools-login-DE9MU98T.js → .setup/agent-tools-login-DINqgaYo.mjs} +19 -7
  8. package/dist/{approval-handler.runtime-DrA59QSA.js → .setup/approval-handler.runtime-DtJZbu3y.mjs} +14 -20
  9. package/dist/{approval-resolver-DoeBBt22.js → .setup/approval-resolver-DF-lu8k5.mjs} +1 -2
  10. package/dist/{auth-store-DcEpIOmE.js → .setup/auth-store-BZKGa0tW.mjs} +25 -29
  11. package/dist/{channel-Dg62olhy.js → .setup/channel-9WGjBznd.mjs} +117 -132
  12. package/dist/{channel-react-action-J_YtyMGX.js → .setup/channel-react-action-mvIbAFhn.mjs} +15 -12
  13. package/dist/{channel.runtime-BP0xNmLi.js → .setup/channel.runtime-DmmMgRJQ.mjs} +6 -6
  14. package/dist/.setup/channel.setup-ByBYSeLd.mjs +22 -0
  15. package/dist/.setup/config-schema-mBX2nmn1.mjs +47 -0
  16. package/dist/{connection-controller-BOB2MVKY.js → .setup/connection-controller-Dt62u6fI.mjs} +29 -226
  17. package/dist/.setup/connection-controller-registry-TSX_udJp.mjs +22 -0
  18. package/dist/{constants-HU41RHGI.js → .setup/constants-C7m81BNe.mjs} +1 -1
  19. package/dist/.setup/directory-config-B7Obxrq2.mjs +37 -0
  20. package/dist/.setup/doctor-By3SB4lm.mjs +48 -0
  21. package/dist/{group-session-key-DVMZlO3x.js → .setup/group-session-key-CEwPRRRK.mjs} +5 -38
  22. package/dist/{identity-CPrT5aEU.js → .setup/identity-CDB1bT-p.mjs} +2 -2
  23. package/dist/.setup/legacy-session-surface-api-B2szjq4r.mjs +2 -0
  24. package/dist/.setup/legacy-state-migrations-api-DXCxufZz.mjs +3 -0
  25. package/dist/{login-CvY2Dhch.js → .setup/login-7b3M0iBp.mjs} +12 -51
  26. package/dist/{login-qr-Bncpb6js.js → .setup/login-qr-CbyA4QX5.mjs} +47 -23
  27. package/dist/{monitor-CfkaUSL2.js → .setup/monitor-Dh6Di_YO.mjs} +1451 -1843
  28. package/dist/.setup/normalize-eddWWsNV.mjs +2 -0
  29. package/dist/{normalize-target-CugRyONv.js → .setup/normalize-target-bVWjgftN.mjs} +10 -18
  30. package/dist/{resolve-outbound-target-1GqbT1uX.js → .setup/resolve-outbound-target-oI7VpU4w.mjs} +1 -1
  31. package/dist/.setup/runtime-BfAdAEYT.mjs +8 -0
  32. package/dist/.setup/send-BQjMC41n.mjs +777 -0
  33. package/dist/{send-api-7wk6eQyW.js → .setup/send-api-BiAkPLr3.mjs} +155 -105
  34. package/dist/{session-errors-JuazczrA.js → .setup/session-errors-BAj9D2La.mjs} +1 -2
  35. package/dist/.setup/session-vUI41Jel.mjs +252 -0
  36. package/dist/.setup/session.runtime-B-V3rXP6.mjs +12 -0
  37. package/dist/{shared-CAxd15bp.js → .setup/setup-core--FfzntMG.mjs} +94 -63
  38. package/dist/{setup-finalize-BZucx24W.js → .setup/setup-finalize-DCruIciL.mjs} +17 -46
  39. package/dist/.setup/setup-plugin-api-B6ck_20D.mjs +3 -0
  40. package/dist/{setup-surface-BkVcd769.js → .setup/setup-surface-Zvj2cLzz.mjs} +4 -4
  41. package/dist/.setup/socket-timing-DhbVFjah.mjs +77 -0
  42. package/dist/{state-migrations-CAMTrs2U.js → .setup/state-migrations-D_BmQUR9.mjs} +8 -1
  43. package/dist/.setup/targets-runtime-C-GiVn6Y.mjs +188 -0
  44. package/dist/.setup/text-runtime-CUrq1mFq.mjs +4 -0
  45. package/dist/action-runtime-api.js +1 -1
  46. package/dist/action-runtime.runtime.js +1 -1
  47. package/dist/{call-tool-api.js → agent-tools-api.js} +26 -11
  48. package/dist/api.js +27 -17
  49. package/dist/auth-presence.js +1 -1
  50. package/dist/channel-config-api.js +1 -1
  51. package/dist/channel-plugin-api.js +1 -1
  52. package/dist/constants.js +2 -0
  53. package/dist/contract-api.js +5 -5
  54. package/dist/directory-contract-api.js +1 -1
  55. package/dist/doctor-contract-api.js +5 -46
  56. package/dist/index.js +4 -4
  57. package/dist/legacy-session-surface-api.js +1 -1
  58. package/dist/legacy-state-migrations-api.js +1 -1
  59. package/dist/light-runtime-api.js +4 -4
  60. package/dist/login-qr-runtime.js +1 -1
  61. package/dist/runtime-api.js +14 -14
  62. package/dist/runtime-setter-api.js +1 -1
  63. package/dist/security-contract-api.js +1 -1
  64. package/dist/setup-entry.js +4 -3
  65. package/dist/setup-plugin-api.js +1 -1
  66. package/dist/targets.js +2 -0
  67. package/node_modules/audio-decode/node_modules/@thi.ng/bitstream/package.json +4 -4
  68. package/node_modules/audio-decode/node_modules/@thi.ng/errors/package.json +4 -4
  69. package/node_modules/audio-decode/node_modules/audio-type/package.json +2 -3
  70. package/node_modules/baileys/node_modules/@protobufjs/aspromise/LICENSE +26 -0
  71. package/node_modules/baileys/node_modules/@protobufjs/aspromise/README.md +13 -0
  72. package/node_modules/baileys/node_modules/{protobufjs/src/util/aspromise.d.ts → @protobufjs/aspromise/index.d.ts} +13 -13
  73. package/node_modules/baileys/node_modules/{protobufjs/src/util/aspromise.js → @protobufjs/aspromise/index.js} +52 -52
  74. package/node_modules/baileys/node_modules/@protobufjs/aspromise/package.json +21 -0
  75. package/node_modules/baileys/node_modules/@protobufjs/aspromise/tests/index.js +130 -0
  76. package/node_modules/baileys/node_modules/@protobufjs/base64/LICENSE +26 -0
  77. package/node_modules/baileys/node_modules/@protobufjs/base64/README.md +19 -0
  78. package/node_modules/baileys/node_modules/{protobufjs/src/util/base64.d.ts → @protobufjs/base64/index.d.ts} +32 -32
  79. package/node_modules/baileys/node_modules/{protobufjs/src/util/base64.js → @protobufjs/base64/index.js} +139 -146
  80. package/node_modules/baileys/node_modules/@protobufjs/base64/package.json +21 -0
  81. package/node_modules/baileys/node_modules/@protobufjs/base64/tests/index.js +46 -0
  82. package/node_modules/baileys/node_modules/@protobufjs/codegen/LICENSE +26 -0
  83. package/node_modules/baileys/node_modules/@protobufjs/codegen/README.md +49 -0
  84. package/node_modules/baileys/node_modules/{protobufjs/src/util/codegen.d.ts → @protobufjs/codegen/index.d.ts} +31 -31
  85. package/node_modules/baileys/node_modules/{protobufjs/src/util/codegen.js → @protobufjs/codegen/index.js} +112 -118
  86. package/node_modules/baileys/node_modules/@protobufjs/codegen/package.json +13 -0
  87. package/node_modules/baileys/node_modules/@protobufjs/codegen/tests/index.js +13 -0
  88. package/node_modules/baileys/node_modules/@protobufjs/eventemitter/CHANGELOG.md +8 -0
  89. package/node_modules/baileys/node_modules/@protobufjs/eventemitter/LICENSE +26 -0
  90. package/node_modules/baileys/node_modules/@protobufjs/eventemitter/README.md +22 -0
  91. package/node_modules/baileys/node_modules/{protobufjs/src/util/eventemitter.d.ts → @protobufjs/eventemitter/index.d.ts} +45 -45
  92. package/node_modules/baileys/node_modules/{protobufjs/src/util/eventemitter.js → @protobufjs/eventemitter/index.js} +86 -86
  93. package/node_modules/baileys/node_modules/@protobufjs/eventemitter/package.json +21 -0
  94. package/node_modules/baileys/node_modules/@protobufjs/eventemitter/tests/index.js +83 -0
  95. package/node_modules/baileys/node_modules/@protobufjs/fetch/CHANGELOG.md +8 -0
  96. package/node_modules/baileys/node_modules/@protobufjs/fetch/LICENSE +26 -0
  97. package/node_modules/baileys/node_modules/@protobufjs/fetch/README.md +13 -0
  98. package/node_modules/baileys/node_modules/{protobufjs/src/util/fetch.d.ts → @protobufjs/fetch/index.d.ts} +56 -56
  99. package/node_modules/baileys/node_modules/{protobufjs/src/util/fetch.js → @protobufjs/fetch/index.js} +112 -112
  100. package/node_modules/baileys/node_modules/@protobufjs/fetch/package.json +27 -0
  101. package/node_modules/baileys/node_modules/@protobufjs/fetch/tests/data/file.txt +1 -0
  102. package/node_modules/baileys/node_modules/@protobufjs/fetch/tests/index.js +158 -0
  103. package/node_modules/baileys/node_modules/@protobufjs/fetch/util/fs.js +11 -0
  104. package/node_modules/baileys/node_modules/@protobufjs/float/LICENSE +26 -0
  105. package/node_modules/baileys/node_modules/@protobufjs/float/README.md +102 -0
  106. package/node_modules/baileys/node_modules/@protobufjs/float/bench/index.js +87 -0
  107. package/node_modules/baileys/node_modules/@protobufjs/float/bench/suite.js +46 -0
  108. package/node_modules/baileys/node_modules/{protobufjs/src/util/float.js → @protobufjs/float/index.js} +335 -335
  109. package/node_modules/baileys/node_modules/@protobufjs/float/package.json +26 -0
  110. package/node_modules/baileys/node_modules/@protobufjs/float/tests/index.js +100 -0
  111. package/node_modules/baileys/node_modules/@protobufjs/inquire/CHANGELOG.md +8 -0
  112. package/node_modules/baileys/node_modules/@protobufjs/inquire/LICENSE +26 -0
  113. package/node_modules/baileys/node_modules/@protobufjs/inquire/README.md +13 -0
  114. package/node_modules/baileys/node_modules/@protobufjs/inquire/index.d.ts +10 -0
  115. package/node_modules/baileys/node_modules/@protobufjs/inquire/index.js +38 -0
  116. package/node_modules/baileys/node_modules/@protobufjs/inquire/package.json +21 -0
  117. package/node_modules/baileys/node_modules/@protobufjs/inquire/tests/data/array.js +1 -0
  118. package/node_modules/baileys/node_modules/@protobufjs/inquire/tests/data/emptyArray.js +1 -0
  119. package/node_modules/baileys/node_modules/@protobufjs/inquire/tests/data/emptyObject.js +1 -0
  120. package/node_modules/baileys/node_modules/@protobufjs/inquire/tests/data/object.js +1 -0
  121. package/node_modules/baileys/node_modules/@protobufjs/inquire/tests/index.js +20 -0
  122. package/node_modules/baileys/node_modules/@protobufjs/path/LICENSE +26 -0
  123. package/node_modules/baileys/node_modules/@protobufjs/path/README.md +19 -0
  124. package/node_modules/baileys/node_modules/{protobufjs/src/util/path.d.ts → @protobufjs/path/index.d.ts} +22 -22
  125. package/node_modules/baileys/node_modules/{protobufjs/src/util/path.js → @protobufjs/path/index.js} +65 -100
  126. package/node_modules/baileys/node_modules/@protobufjs/path/package.json +21 -0
  127. package/node_modules/baileys/node_modules/@protobufjs/path/tests/index.js +60 -0
  128. package/node_modules/baileys/node_modules/@protobufjs/pool/.npmignore +3 -0
  129. package/node_modules/baileys/node_modules/@protobufjs/pool/LICENSE +26 -0
  130. package/node_modules/baileys/node_modules/@protobufjs/pool/README.md +13 -0
  131. package/node_modules/baileys/node_modules/{protobufjs/src/util/pool.d.ts → @protobufjs/pool/index.d.ts} +32 -32
  132. package/node_modules/baileys/node_modules/{protobufjs/src/util/pool.js → @protobufjs/pool/index.js} +48 -48
  133. package/node_modules/baileys/node_modules/@protobufjs/pool/package.json +21 -0
  134. package/node_modules/baileys/node_modules/@protobufjs/pool/tests/index.js +33 -0
  135. package/node_modules/baileys/node_modules/@protobufjs/utf8/LICENSE +26 -0
  136. package/node_modules/baileys/node_modules/@protobufjs/utf8/README.md +20 -0
  137. package/node_modules/baileys/node_modules/{protobufjs/src/util/utf8.d.ts → @protobufjs/utf8/index.d.ts} +24 -24
  138. package/node_modules/baileys/node_modules/{protobufjs/src/util/utf8.js → @protobufjs/utf8/index.js} +104 -190
  139. package/node_modules/baileys/node_modules/@protobufjs/utf8/package.json +21 -0
  140. package/node_modules/baileys/node_modules/@protobufjs/utf8/tests/data/surrogate_pair_bug.txt +207 -0
  141. package/node_modules/baileys/node_modules/@protobufjs/utf8/tests/data/utf8.txt +216 -0
  142. package/node_modules/baileys/node_modules/@protobufjs/utf8/tests/index.js +74 -0
  143. package/node_modules/baileys/node_modules/@types/node/LICENSE +21 -0
  144. package/node_modules/baileys/node_modules/@types/node/README.md +15 -0
  145. package/node_modules/baileys/node_modules/@types/node/assert/strict.d.ts +68 -0
  146. package/node_modules/baileys/node_modules/@types/node/assert.d.ts +1077 -0
  147. package/node_modules/baileys/node_modules/@types/node/async_hooks.d.ts +711 -0
  148. package/node_modules/baileys/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  149. package/node_modules/baileys/node_modules/@types/node/buffer.d.ts +1799 -0
  150. package/node_modules/baileys/node_modules/@types/node/child_process.d.ts +1362 -0
  151. package/node_modules/baileys/node_modules/@types/node/cluster.d.ts +432 -0
  152. package/node_modules/baileys/node_modules/@types/node/console.d.ts +93 -0
  153. package/node_modules/baileys/node_modules/@types/node/constants.d.ts +14 -0
  154. package/node_modules/baileys/node_modules/@types/node/crypto.d.ts +3954 -0
  155. package/node_modules/baileys/node_modules/@types/node/dgram.d.ts +537 -0
  156. package/node_modules/baileys/node_modules/@types/node/diagnostics_channel.d.ts +794 -0
  157. package/node_modules/baileys/node_modules/@types/node/dns/promises.d.ts +497 -0
  158. package/node_modules/baileys/node_modules/@types/node/dns.d.ts +876 -0
  159. package/node_modules/baileys/node_modules/@types/node/domain.d.ts +150 -0
  160. package/node_modules/baileys/node_modules/@types/node/events.d.ts +1008 -0
  161. package/node_modules/baileys/node_modules/@types/node/ffi.d.ts +486 -0
  162. package/node_modules/baileys/node_modules/@types/node/fs/promises.d.ts +1471 -0
  163. package/node_modules/baileys/node_modules/@types/node/fs.d.ts +4786 -0
  164. package/node_modules/baileys/node_modules/@types/node/globals.d.ts +150 -0
  165. package/node_modules/baileys/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  166. package/node_modules/baileys/node_modules/@types/node/http.d.ts +2172 -0
  167. package/node_modules/baileys/node_modules/@types/node/http2.d.ts +2489 -0
  168. package/node_modules/baileys/node_modules/@types/node/https.d.ts +400 -0
  169. package/node_modules/baileys/node_modules/@types/node/index.d.ts +115 -0
  170. package/node_modules/baileys/node_modules/@types/node/inspector/promises.d.ts +35 -0
  171. package/node_modules/baileys/node_modules/@types/node/inspector.d.ts +264 -0
  172. package/node_modules/baileys/node_modules/@types/node/inspector.generated.d.ts +4406 -0
  173. package/node_modules/baileys/node_modules/@types/node/module.d.ts +726 -0
  174. package/node_modules/baileys/node_modules/@types/node/net.d.ts +970 -0
  175. package/node_modules/baileys/node_modules/@types/node/os.d.ts +498 -0
  176. package/node_modules/baileys/node_modules/@types/node/package.json +155 -0
  177. package/node_modules/baileys/node_modules/@types/node/path/posix.d.ts +8 -0
  178. package/node_modules/baileys/node_modules/@types/node/path/win32.d.ts +8 -0
  179. package/node_modules/baileys/node_modules/@types/node/path.d.ts +178 -0
  180. package/node_modules/baileys/node_modules/@types/node/perf_hooks.d.ts +604 -0
  181. package/node_modules/baileys/node_modules/@types/node/process.d.ts +2217 -0
  182. package/node_modules/baileys/node_modules/@types/node/punycode.d.ts +89 -0
  183. package/node_modules/baileys/node_modules/@types/node/querystring.d.ts +139 -0
  184. package/node_modules/baileys/node_modules/@types/node/quic.d.ts +978 -0
  185. package/node_modules/baileys/node_modules/@types/node/readline/promises.d.ts +158 -0
  186. package/node_modules/baileys/node_modules/@types/node/readline.d.ts +508 -0
  187. package/node_modules/baileys/node_modules/@types/node/repl.d.ts +420 -0
  188. package/node_modules/baileys/node_modules/@types/node/sea.d.ts +47 -0
  189. package/node_modules/baileys/node_modules/@types/node/sqlite.d.ts +1123 -0
  190. package/node_modules/baileys/node_modules/@types/node/stream/consumers.d.ts +114 -0
  191. package/node_modules/baileys/node_modules/@types/node/stream/iter.d.ts +451 -0
  192. package/node_modules/baileys/node_modules/@types/node/stream/promises.d.ts +211 -0
  193. package/node_modules/baileys/node_modules/@types/node/stream/web.d.ts +300 -0
  194. package/node_modules/baileys/node_modules/@types/node/stream.d.ts +1806 -0
  195. package/node_modules/baileys/node_modules/@types/node/string_decoder.d.ts +27 -0
  196. package/node_modules/baileys/node_modules/@types/node/test/reporters.d.ts +59 -0
  197. package/node_modules/baileys/node_modules/@types/node/test.d.ts +2389 -0
  198. package/node_modules/baileys/node_modules/@types/node/timers/promises.d.ts +93 -0
  199. package/node_modules/baileys/node_modules/@types/node/timers.d.ts +149 -0
  200. package/node_modules/baileys/node_modules/@types/node/tls.d.ts +1193 -0
  201. package/node_modules/baileys/node_modules/@types/node/trace_events.d.ts +103 -0
  202. package/node_modules/baileys/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  203. package/node_modules/baileys/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  204. package/node_modules/baileys/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  205. package/node_modules/baileys/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  206. package/node_modules/baileys/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  207. package/node_modules/baileys/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  208. package/node_modules/baileys/node_modules/@types/node/tty.d.ts +225 -0
  209. package/node_modules/baileys/node_modules/@types/node/url.d.ts +556 -0
  210. package/node_modules/baileys/node_modules/@types/node/util/types.d.ts +558 -0
  211. package/node_modules/baileys/node_modules/@types/node/util.d.ts +1677 -0
  212. package/node_modules/baileys/node_modules/@types/node/v8.d.ts +1062 -0
  213. package/node_modules/baileys/node_modules/@types/node/vm.d.ts +1136 -0
  214. package/node_modules/baileys/node_modules/@types/node/wasi.d.ts +131 -0
  215. package/node_modules/baileys/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  216. package/node_modules/baileys/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  217. package/node_modules/baileys/node_modules/@types/node/web-globals/console.d.ts +9 -0
  218. package/node_modules/baileys/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  219. package/node_modules/baileys/node_modules/@types/node/web-globals/domexception.d.ts +83 -0
  220. package/node_modules/baileys/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  221. package/node_modules/baileys/node_modules/@types/node/web-globals/events.d.ts +106 -0
  222. package/node_modules/baileys/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  223. package/node_modules/baileys/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  224. package/node_modules/baileys/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  225. package/node_modules/baileys/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  226. package/node_modules/baileys/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  227. package/node_modules/baileys/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  228. package/node_modules/baileys/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  229. package/node_modules/baileys/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  230. package/node_modules/baileys/node_modules/@types/node/web-globals/url.d.ts +24 -0
  231. package/node_modules/baileys/node_modules/@types/node/worker_threads.d.ts +669 -0
  232. package/node_modules/baileys/node_modules/@types/node/zlib/iter.d.ts +131 -0
  233. package/node_modules/baileys/node_modules/@types/node/zlib.d.ts +589 -0
  234. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -1
  235. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/browser/index.js +4 -11
  236. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -1
  237. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/browser/index.min.js +1 -1
  238. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +3 -3
  239. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
  240. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/index.js +4 -11
  241. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
  242. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
  243. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/index.min.js.map +3 -3
  244. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -1
  245. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/node/index.js +4 -11
  246. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -1
  247. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/node/index.min.js +1 -1
  248. package/node_modules/baileys/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +3 -3
  249. package/node_modules/baileys/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -1
  250. package/node_modules/baileys/node_modules/lru-cache/dist/esm/browser/index.js +4 -11
  251. package/node_modules/baileys/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -1
  252. package/node_modules/baileys/node_modules/lru-cache/dist/esm/browser/index.min.js +1 -1
  253. package/node_modules/baileys/node_modules/lru-cache/dist/esm/browser/index.min.js.map +3 -3
  254. package/node_modules/baileys/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
  255. package/node_modules/baileys/node_modules/lru-cache/dist/esm/index.js +4 -11
  256. package/node_modules/baileys/node_modules/lru-cache/dist/esm/index.js.map +1 -1
  257. package/node_modules/baileys/node_modules/lru-cache/dist/esm/index.min.js +1 -1
  258. package/node_modules/baileys/node_modules/lru-cache/dist/esm/index.min.js.map +3 -3
  259. package/node_modules/baileys/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -1
  260. package/node_modules/baileys/node_modules/lru-cache/dist/esm/node/index.js +4 -11
  261. package/node_modules/baileys/node_modules/lru-cache/dist/esm/node/index.js.map +1 -1
  262. package/node_modules/baileys/node_modules/lru-cache/dist/esm/node/index.min.js +1 -1
  263. package/node_modules/baileys/node_modules/lru-cache/dist/esm/node/index.min.js.map +3 -3
  264. package/node_modules/baileys/node_modules/lru-cache/package.json +1 -1
  265. package/node_modules/baileys/node_modules/music-metadata/lib/common/Util.d.ts +1 -1
  266. package/node_modules/baileys/node_modules/music-metadata/lib/common/Util.js +1 -7
  267. package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/FrameHeader.js +3 -23
  268. package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/FrameParser.js +2 -2
  269. package/node_modules/baileys/node_modules/music-metadata/lib/id3v2/ID3v2Token.js +1 -1
  270. package/node_modules/baileys/node_modules/music-metadata/lib/mp4/AtomToken.js +13 -38
  271. package/node_modules/baileys/node_modules/music-metadata/lib/mpeg/MpegParser.js +1 -1
  272. package/node_modules/baileys/node_modules/music-metadata/package.json +6 -6
  273. package/node_modules/baileys/node_modules/p-queue/dist/index.js +27 -37
  274. package/node_modules/baileys/node_modules/p-queue/package.json +1 -1
  275. package/node_modules/baileys/node_modules/protobufjs/README.md +553 -266
  276. package/node_modules/baileys/node_modules/protobufjs/dist/light/protobuf.js +1985 -3177
  277. package/node_modules/baileys/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
  278. package/node_modules/baileys/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
  279. package/node_modules/baileys/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
  280. package/node_modules/baileys/node_modules/protobufjs/dist/minimal/protobuf.js +1244 -1966
  281. package/node_modules/baileys/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
  282. package/node_modules/baileys/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
  283. package/node_modules/baileys/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
  284. package/node_modules/baileys/node_modules/protobufjs/dist/protobuf.js +2005 -3330
  285. package/node_modules/baileys/node_modules/protobufjs/dist/protobuf.js.map +1 -1
  286. package/node_modules/baileys/node_modules/protobufjs/dist/protobuf.min.js +3 -3
  287. package/node_modules/baileys/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
  288. package/node_modules/baileys/node_modules/protobufjs/ext/descriptor/README.md +70 -3
  289. package/node_modules/baileys/node_modules/protobufjs/ext/descriptor/index.d.ts +193 -0
  290. package/node_modules/baileys/node_modules/protobufjs/ext/descriptor/index.js +1185 -1
  291. package/node_modules/baileys/node_modules/protobufjs/ext/descriptor/test.js +54 -0
  292. package/node_modules/baileys/node_modules/protobufjs/google/api/annotations.json +1 -3
  293. package/node_modules/baileys/node_modules/protobufjs/google/api/http.json +1 -2
  294. package/node_modules/baileys/node_modules/protobufjs/google/protobuf/api.json +6 -12
  295. package/node_modules/baileys/node_modules/protobufjs/google/protobuf/descriptor.json +87 -170
  296. package/node_modules/baileys/node_modules/protobufjs/google/protobuf/descriptor.proto +8 -0
  297. package/node_modules/baileys/node_modules/protobufjs/google/protobuf/source_context.json +1 -2
  298. package/node_modules/baileys/node_modules/protobufjs/google/protobuf/type.json +7 -14
  299. package/node_modules/baileys/node_modules/protobufjs/index.d.ts +703 -1066
  300. package/node_modules/baileys/node_modules/protobufjs/package.json +52 -25
  301. package/node_modules/baileys/node_modules/protobufjs/scripts/postinstall.js +32 -0
  302. package/node_modules/baileys/node_modules/protobufjs/src/common.js +8 -14
  303. package/node_modules/baileys/node_modules/protobufjs/src/converter.js +51 -85
  304. package/node_modules/baileys/node_modules/protobufjs/src/decoder.js +61 -205
  305. package/node_modules/baileys/node_modules/protobufjs/src/encoder.js +7 -27
  306. package/node_modules/baileys/node_modules/protobufjs/src/enum.js +5 -18
  307. package/node_modules/baileys/node_modules/protobufjs/src/field.js +19 -71
  308. package/node_modules/baileys/node_modules/protobufjs/src/index-light.js +29 -29
  309. package/node_modules/baileys/node_modules/protobufjs/src/index-minimal.js +13 -12
  310. package/node_modules/baileys/node_modules/protobufjs/src/index.js +6 -6
  311. package/node_modules/baileys/node_modules/protobufjs/src/mapfield.js +8 -26
  312. package/node_modules/baileys/node_modules/protobufjs/src/message.js +11 -5
  313. package/node_modules/baileys/node_modules/protobufjs/src/method.js +7 -30
  314. package/node_modules/baileys/node_modules/protobufjs/src/namespace.js +10 -26
  315. package/node_modules/baileys/node_modules/protobufjs/src/object.js +31 -41
  316. package/node_modules/baileys/node_modules/protobufjs/src/oneof.js +1 -12
  317. package/node_modules/baileys/node_modules/protobufjs/src/parse.js +47 -169
  318. package/node_modules/baileys/node_modules/protobufjs/src/reader.js +50 -412
  319. package/node_modules/baileys/node_modules/protobufjs/src/reader_buffer.js +5 -31
  320. package/node_modules/baileys/node_modules/protobufjs/src/root.js +4 -16
  321. package/node_modules/baileys/node_modules/protobufjs/src/rpc/service.js +5 -18
  322. package/node_modules/baileys/node_modules/protobufjs/src/service.js +2 -15
  323. package/node_modules/baileys/node_modules/protobufjs/src/tokenize.js +1 -6
  324. package/node_modules/baileys/node_modules/protobufjs/src/type.js +36 -80
  325. package/node_modules/baileys/node_modules/protobufjs/src/typescript.jsdoc +15 -0
  326. package/node_modules/baileys/node_modules/protobufjs/src/util/longbits.js +6 -10
  327. package/node_modules/baileys/node_modules/protobufjs/src/util/minimal.js +54 -71
  328. package/node_modules/baileys/node_modules/protobufjs/src/util.js +17 -51
  329. package/node_modules/baileys/node_modules/protobufjs/src/verifier.js +14 -19
  330. package/node_modules/baileys/node_modules/protobufjs/src/wrappers.js +3 -2
  331. package/node_modules/baileys/node_modules/protobufjs/src/writer.js +191 -424
  332. package/node_modules/baileys/node_modules/protobufjs/src/writer_buffer.js +26 -41
  333. package/node_modules/baileys/node_modules/protobufjs/tsconfig.json +5 -3
  334. package/node_modules/baileys/node_modules/undici-types/LICENSE +21 -0
  335. package/node_modules/baileys/node_modules/undici-types/README.md +6 -0
  336. package/node_modules/baileys/node_modules/undici-types/agent.d.ts +30 -0
  337. package/node_modules/baileys/node_modules/undici-types/api.d.ts +43 -0
  338. package/node_modules/baileys/node_modules/undici-types/balanced-pool.d.ts +30 -0
  339. package/node_modules/baileys/node_modules/undici-types/cache-interceptor.d.ts +179 -0
  340. package/node_modules/baileys/node_modules/undici-types/cache.d.ts +36 -0
  341. package/node_modules/baileys/node_modules/undici-types/client-stats.d.ts +15 -0
  342. package/node_modules/baileys/node_modules/undici-types/client.d.ts +129 -0
  343. package/node_modules/baileys/node_modules/undici-types/connector.d.ts +36 -0
  344. package/node_modules/baileys/node_modules/undici-types/content-type.d.ts +21 -0
  345. package/node_modules/baileys/node_modules/undici-types/cookies.d.ts +30 -0
  346. package/node_modules/baileys/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  347. package/node_modules/baileys/node_modules/undici-types/dispatcher.d.ts +253 -0
  348. package/node_modules/baileys/node_modules/undici-types/dispatcher1-wrapper.d.ts +7 -0
  349. package/node_modules/baileys/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  350. package/node_modules/baileys/node_modules/undici-types/errors.d.ts +177 -0
  351. package/node_modules/baileys/node_modules/undici-types/eventsource.d.ts +66 -0
  352. package/node_modules/baileys/node_modules/undici-types/fetch.d.ts +231 -0
  353. package/node_modules/baileys/node_modules/undici-types/formdata.d.ts +108 -0
  354. package/node_modules/baileys/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  355. package/node_modules/baileys/node_modules/undici-types/global-origin.d.ts +7 -0
  356. package/node_modules/baileys/node_modules/undici-types/h2c-client.d.ts +73 -0
  357. package/node_modules/baileys/node_modules/undici-types/handlers.d.ts +14 -0
  358. package/node_modules/baileys/node_modules/undici-types/header.d.ts +165 -0
  359. package/node_modules/baileys/node_modules/undici-types/index.d.ts +93 -0
  360. package/node_modules/baileys/node_modules/undici-types/interceptors.d.ts +80 -0
  361. package/node_modules/baileys/node_modules/undici-types/mock-agent.d.ts +68 -0
  362. package/node_modules/baileys/node_modules/undici-types/mock-call-history.d.ts +111 -0
  363. package/node_modules/baileys/node_modules/undici-types/mock-client.d.ts +27 -0
  364. package/node_modules/baileys/node_modules/undici-types/mock-errors.d.ts +12 -0
  365. package/node_modules/baileys/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  366. package/node_modules/baileys/node_modules/undici-types/mock-pool.d.ts +27 -0
  367. package/node_modules/baileys/node_modules/undici-types/package.json +55 -0
  368. package/node_modules/baileys/node_modules/undici-types/patch.d.ts +29 -0
  369. package/node_modules/baileys/node_modules/undici-types/pool-stats.d.ts +19 -0
  370. package/node_modules/baileys/node_modules/undici-types/pool.d.ts +39 -0
  371. package/node_modules/baileys/node_modules/undici-types/proxy-agent.d.ts +29 -0
  372. package/node_modules/baileys/node_modules/undici-types/readable.d.ts +68 -0
  373. package/node_modules/baileys/node_modules/undici-types/retry-agent.d.ts +8 -0
  374. package/node_modules/baileys/node_modules/undici-types/retry-handler.d.ts +125 -0
  375. package/node_modules/baileys/node_modules/undici-types/round-robin-pool.d.ts +39 -0
  376. package/node_modules/baileys/node_modules/undici-types/snapshot-agent.d.ts +113 -0
  377. package/node_modules/baileys/node_modules/undici-types/socks5-proxy-agent.d.ts +25 -0
  378. package/node_modules/baileys/node_modules/undici-types/util.d.ts +18 -0
  379. package/node_modules/baileys/node_modules/undici-types/utility.d.ts +7 -0
  380. package/node_modules/baileys/node_modules/undici-types/webidl.d.ts +346 -0
  381. package/node_modules/baileys/node_modules/undici-types/websocket.d.ts +188 -0
  382. package/node_modules/baileys/node_modules/ws/lib/permessage-deflate.js +3 -1
  383. package/node_modules/baileys/node_modules/ws/package.json +1 -1
  384. package/node_modules/typebox/build/compile/validator.d.mts +4 -1
  385. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  386. package/node_modules/typebox/build/guard/guard.mjs +2 -5
  387. package/node_modules/typebox/build/guard/string.mjs +4 -16
  388. package/node_modules/typebox/build/schema/engine/_functions.mjs +15 -34
  389. package/node_modules/typebox/build/type/action/readonly_object.d.mts +0 -5
  390. package/node_modules/typebox/build/type/action/readonly_object.mjs +0 -8
  391. package/node_modules/typebox/build/typebox.d.mts +1 -1
  392. package/node_modules/typebox/build/typebox.mjs +1 -1
  393. package/node_modules/typebox/package.json +1 -1
  394. package/node_modules/typebox/readme.md +32 -29
  395. package/openclaw.plugin.json +239 -214
  396. package/package.json +11 -21
  397. package/dist/approval-reactions-Cu66aYdq.js +0 -373
  398. package/dist/channel.setup-pMwNVkCA.js +0 -19
  399. package/dist/config-schema-BQP5YCeK.js +0 -50
  400. package/dist/connection-controller-runtime-context-DG0tuYuw.js +0 -23
  401. package/dist/directory-config-DalGrfGN.js +0 -196
  402. package/dist/doctor-CwzVXKoE.js +0 -10
  403. package/dist/normalize-C7Hkc_vE.js +0 -2
  404. package/dist/question-reactions-CRsnyoMK.js +0 -142
  405. package/dist/runtime-BLlToOi6.js +0 -21
  406. package/dist/send-D37ddk22.js +0 -436
  407. package/dist/session.runtime-Nsqaehbd.js +0 -16
  408. package/dist/socket-close-DridEuzz.js +0 -622
  409. package/dist/targets-runtime-TMk0lGuy.js +0 -299
  410. package/dist/text-runtime-CaKLhowd.js +0 -4
  411. package/node_modules/baileys/node_modules/protobufjs/ext/README.md +0 -70
  412. package/node_modules/baileys/node_modules/protobufjs/ext/descriptor.d.ts +0 -87
  413. package/node_modules/baileys/node_modules/protobufjs/ext/descriptor.generated.d.ts +0 -417
  414. package/node_modules/baileys/node_modules/protobufjs/ext/descriptor.js +0 -1356
  415. package/node_modules/baileys/node_modules/protobufjs/ext/protojson.LICENSE +0 -201
  416. package/node_modules/baileys/node_modules/protobufjs/ext/protojson.d.ts +0 -20
  417. package/node_modules/baileys/node_modules/protobufjs/ext/protojson.generated.d.ts +0 -49
  418. package/node_modules/baileys/node_modules/protobufjs/ext/protojson.js +0 -951
  419. package/node_modules/baileys/node_modules/protobufjs/ext/textformat.d.ts +0 -19
  420. package/node_modules/baileys/node_modules/protobufjs/ext/textformat.generated.d.ts +0 -33
  421. package/node_modules/baileys/node_modules/protobufjs/ext/textformat.js +0 -1276
  422. package/node_modules/baileys/node_modules/protobufjs/google/protobuf/compiler/plugin.json +0 -126
  423. package/node_modules/baileys/node_modules/protobufjs/google/protobuf/compiler/plugin.proto +0 -47
  424. package/node_modules/baileys/node_modules/protobufjs/src/typescript.js +0 -25
  425. /package/dist/{account-ids-CB5SOWjc.js → .setup/account-ids-CB5SOWjc.mjs} +0 -0
  426. /package/dist/{audio-preflight.runtime-C_glQhZY.js → .setup/audio-preflight.runtime-C_glQhZY.mjs} +0 -0
  427. /package/dist/{command-policy-BIOSHySD.js → .setup/command-policy-BIOSHySD.mjs} +0 -0
  428. /package/dist/{creds-files-B1kSWtBg.js → .setup/creds-files-B1kSWtBg.mjs} +0 -0
  429. /package/dist/{group-config-path-BGyzT9Lg.js → .setup/group-config-path-BGyzT9Lg.mjs} +0 -0
  430. /package/dist/{group-session-contract-DDnZSsJ1.js → .setup/group-session-contract-DDnZSsJ1.mjs} +0 -0
  431. /package/dist/{reply-resolver.runtime-Ded8D58Y.js → .setup/reply-resolver.runtime-Ded8D58Y.mjs} +0 -0
  432. /package/dist/{rolldown-runtime-D7D4PA-g.js → .setup/rolldown-runtime-D7D4PA-g.mjs} +0 -0
  433. /package/dist/{security-contract-nAzD945y.js → .setup/security-contract-nAzD945y.mjs} +0 -0
  434. /package/dist/{session-contract-DMTm6Q_L.js → .setup/session-contract-DMTm6Q_L.mjs} +0 -0
  435. /package/node_modules/baileys/node_modules/{protobufjs/src/util/float.d.ts → @protobufjs/float/index.d.ts} +0 -0
@@ -1,87 +1,149 @@
1
- import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-CSqLvCY5.js";
2
- import { a as normalizeWhatsAppOutboundPayload, c as prepareWhatsAppOutboundMedia, r as sendReactionWhatsApp, s as normalizeWhatsAppPayloadTextPreservingIndentation, u as resolveWhatsAppReactionLevel } from "./send-D37ddk22.js";
3
- import { i as getWhatsAppRuntime } from "./runtime-BLlToOi6.js";
4
- import { r as getWhatsAppConnectionController } from "./connection-controller-runtime-context-DG0tuYuw.js";
5
- import { a as markdownToWhatsAppChunks, c as toWhatsappJid, l as toWhatsappJidWithLid, n as isSelfChatMode, o as resolveEquivalentWhatsAppDirectChatJids, r as jidToE164, s as resolveJidToE164 } from "./targets-runtime-TMk0lGuy.js";
6
- import { t as normalizeE164 } from "./text-runtime-CaKLhowd.js";
7
- import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-DVMZlO3x.js";
8
- import { g as readWebSelfId, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision } from "./auth-store-DcEpIOmE.js";
9
- import { a as getSelfIdentity, c as resolveComparableIdentity, i as getReplyContext, n as getMentionIdentities, o as getSenderIdentity, r as getPrimaryIdentityId, s as identitiesOverlap, t as getComparableIdentityValues } from "./identity-CPrT5aEU.js";
10
- import { n as resolveWhatsAppGroupsConfigPath } from "./group-config-path-BGyzT9Lg.js";
11
- import { a as waitForWaConnection, c as isWhatsAppSocketOperationTimeoutError, d as withWhatsAppSocketOperationTimeout, l as resolveWhatsAppSocketOperationTimeoutMs, r as createWaSocket, s as createWhatsAppSocketOperationTimeoutAdapter, u as resolveWhatsAppSocketTiming } from "./socket-close-DridEuzz.js";
12
- import { n as getStatusCode, t as formatError } from "./session-errors-JuazczrA.js";
13
- import { t as BufferJSON } from "./session.runtime-Nsqaehbd.js";
14
- import { c as requireWhatsAppInboundAdmission, i as resolveWhatsAppInboundPolicy, l as resolveWhatsAppGroupConversationId, o as buildDeprecatedFlatWhatsAppInboundAdmission, r as resolveWhatsAppCommandAuthorized, s as requireAdmittedWhatsAppInboundMessage, t as checkInboundAccessControl } from "./access-control-pZcxTmd5.js";
15
- import { _ as extractMediaKind, a as mayContainWhatsAppOutboundMention, b as hasInboundUserContent, c as DisconnectReason, d as normalizeMessageContent, f as describeReplyContext, g as extractLocationData, h as extractExternalAdReplyContext, i as addWhatsAppOutboundMentionsToContent, l as downloadMediaMessage, m as extractContextInfo, n as listWhatsAppSendResultMessageIds, o as resolveWhatsAppOutboundMentions, p as extractContactContext, r as normalizeWhatsAppSendResult, s as addWhatsAppImagePreviewFields, t as createWebSendApi, u as isJidGroup, v as extractMentionedJids, x as resolveInboundMediaMimetype, y as extractText } from "./send-api-7wk6eQyW.js";
16
- import { c as computeBackoff, d as resolveReconnectPolicy, f as sleepWithAbort, l as newConnectionId, n as WHATSAPP_WATCHDOG_TIMEOUT_ERROR, r as WhatsAppConnectionController, s as DEFAULT_RECONNECT_POLICY, u as resolveHeartbeatSeconds } from "./connection-controller-BOB2MVKY.js";
17
- import { n as maybeResolveWhatsAppApprovalReaction } from "./approval-reactions-Cu66aYdq.js";
18
- import { t as maybeResolveWhatsAppQuestionReaction } from "./question-reactions-CRsnyoMK.js";
19
- import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
1
+ import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-Dw16pdNl.mjs";
2
+ import { a as normalizeWhatsAppOutboundPayload, c as prepareWhatsAppOutboundMedia, d as maybeResolveWhatsAppApprovalReaction, g as resolveWhatsAppReactionLevel, l as sendWhatsAppOutboundWithRetry, r as sendReactionWhatsApp, s as normalizeWhatsAppPayloadTextPreservingIndentation } from "./send-BQjMC41n.mjs";
3
+ import { n as getWhatsAppRuntime } from "./runtime-BfAdAEYT.mjs";
4
+ import { t as getRegisteredWhatsAppConnectionController } from "./connection-controller-registry-TSX_udJp.mjs";
5
+ import { n as getStatusCode, t as formatError } from "./session-errors-BAj9D2La.mjs";
6
+ import { a as resolveWhatsAppSocketTiming, i as resolveWhatsAppSocketOperationTimeoutMs, n as createWhatsAppSocketOperationTimeoutAdapter, o as withWhatsAppSocketOperationTimeout, r as isWhatsAppSocketOperationTimeoutError } from "./socket-timing-DhbVFjah.mjs";
7
+ import { a as resolveEquivalentWhatsAppDirectChatJids, c as toWhatsappJidWithLid, i as markdownToWhatsApp, n as isSelfChatMode, o as resolveJidToE164, r as jidToE164, s as toWhatsappJid } from "./targets-runtime-C-GiVn6Y.mjs";
8
+ import { n as normalizeE164, t as convertMarkdownTables$1 } from "./text-runtime-CUrq1mFq.mjs";
9
+ import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-CEwPRRRK.mjs";
10
+ import { g as readWebSelfId, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision } from "./auth-store-BZKGa0tW.mjs";
11
+ import { a as getSelfIdentity, c as resolveComparableIdentity, i as getReplyContext, n as getMentionIdentities, o as getSenderIdentity, r as getPrimaryIdentityId, s as identitiesOverlap, t as getComparableIdentityValues } from "./identity-CDB1bT-p.mjs";
12
+ import { n as resolveWhatsAppGroupsConfigPath } from "./group-config-path-BGyzT9Lg.mjs";
13
+ import { c as requireWhatsAppInboundAdmission, i as resolveWhatsAppInboundPolicy, l as resolveWhatsAppGroupConversationId, o as buildDeprecatedFlatWhatsAppInboundAdmission, r as resolveWhatsAppCommandAuthorized, s as requireAdmittedWhatsAppInboundMessage, t as checkInboundAccessControl } from "./access-control-C0jumQuO.mjs";
14
+ import { _ as extractLocationData, a as mayContainWhatsAppOutboundMention, b as extractText, c as DisconnectReason, d as normalizeMessageContent, f as resolveInboundMediaMimetype, g as extractExternalAdReplyContext, h as extractContextInfo, i as addWhatsAppOutboundMentionsToContent, l as downloadMediaMessage, m as extractContactContext, n as listWhatsAppSendResultMessageIds, o as resolveWhatsAppOutboundMentions, p as describeReplyContext, r as normalizeWhatsAppSendResult, s as addWhatsAppImagePreviewFields, t as createWebSendApi, u as isJidGroup, v as extractMediaPlaceholder, x as hasInboundUserContent, y as extractMentionedJids } from "./send-api-BiAkPLr3.mjs";
15
+ import { r as waitForWaConnection, t as createWaSocket } from "./session-vUI41Jel.mjs";
16
+ import { t as BufferJSON } from "./session.runtime-B-V3rXP6.mjs";
17
+ import { c as computeBackoff, d as resolveReconnectPolicy, f as sleepWithAbort, l as newConnectionId, n as WHATSAPP_WATCHDOG_TIMEOUT_ERROR, r as WhatsAppConnectionController, s as DEFAULT_RECONNECT_POLICY, u as resolveHeartbeatSeconds } from "./connection-controller-Dt62u6fI.mjs";
18
+ import { resolveAccountEntry } from "openclaw/plugin-sdk/account-core";
19
+ import { normalizeLowercaseStringOrEmpty, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
20
+ import { createChannelMessageReplyPipeline, createDurableInboundReceiveJournalFromQueue, createMessageReceiptFromOutboundResults, deliverInboundReplyWithMessageSendContext, resolveChannelMessageSourceReplyDeliveryMode } from "openclaw/plugin-sdk/channel-outbound";
20
21
  import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
21
- import { createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
22
+ import { DEFAULT_TIMING, createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
22
23
  import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
23
24
  import { getChildLogger } from "openclaw/plugin-sdk/logging-core";
24
25
  import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
25
26
  import { createSubsystemLogger, defaultRuntime, formatDurationPrecise, getChildLogger as getChildLogger$1, logVerbose, logVerbose as logVerbose$1, registerUnhandledRejectionHandler, shouldLogVerbose, shouldLogVerbose as shouldLogVerbose$1, warn } from "openclaw/plugin-sdk/runtime-env";
26
- import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
27
- import { bindIngressLifecycleToReplyOptions, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, listMessageReceiptPlatformIds, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
28
- import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
29
- import { isReasoningReplyPayload, resolveSendableOutboundReplyParts, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
30
- import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
31
27
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
32
- import { buildChannelInboundEventContext, createChannelPartialDeliveryError, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, formatMediaPlaceholderText, hasVisibleInboundReplyDispatch, isChannelPartialDeliveryError, resolveInboundSessionEnvelopeContext, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFactsWithMetadata, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
33
- import { DEFAULT_ACCOUNT_ID, DEFAULT_MAIN_KEY, buildAgentMainSessionKey, buildAgentSessionKey, buildGroupHistoryKey, deriveLastRoutePolicy, normalizeAccountId, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
28
+ import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
34
29
  import { asDateTimestampMs, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
35
- import { createHash } from "node:crypto";
36
- import { fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
30
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
31
+ import { LocalMediaAccessError, getDefaultLocalRoots, loadWebMedia as loadWebMedia$1, loadWebMediaRaw, optimizeImageToJpeg, optimizeImageToPng } from "openclaw/plugin-sdk/web-media";
32
+ import { chunkMarkdownTextWithMode } from "openclaw/plugin-sdk/reply-chunking";
33
+ import { isReasoningReplyPayload, resolveSendableOutboundReplyParts, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
34
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, DEFAULT_MAIN_KEY, buildAgentMainSessionKey, buildAgentSessionKey, buildGroupHistoryKey, deriveLastRoutePolicy, normalizeAccountId as normalizeAccountId$1, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
35
+ import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
37
36
  import { getSessionEntry, patchSessionEntry, resolveStorePath, resolveStorePath as resolveStorePath$1, updateLastRoute } from "openclaw/plugin-sdk/session-store-runtime";
37
+ import { buildChannelInboundEventContext, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, hasVisibleInboundReplyDispatch, resolveInboundSessionEnvelopeContext, runChannelInboundEvent, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
38
+ import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
38
39
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
39
- import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
40
+ import { createHash } from "node:crypto";
41
+ import { dispatchReplyWithBufferedBlockDispatcher, finalizeInboundContext, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
40
42
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
41
43
  import { createInboundDebouncer, resolveInboundDebounceMs } from "openclaw/plugin-sdk/channel-inbound-debounce";
44
+ import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
45
+ import { hasControlCommand, isControlCommandMessage, isControlCommandMessage as isControlCommandMessage$1, shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-detection";
42
46
  import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runtime";
43
47
  import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, buildInboundHistoryFromEntries, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
44
48
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
45
- import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
46
- import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
47
- import { getRuntimeConfig as getRuntimeConfig$1 } from "openclaw/plugin-sdk/runtime-config-snapshot";
49
+ import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
50
+ import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
51
+ import { getRuntimeConfig as getRuntimeConfig$1, getRuntimeConfigSourceSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
48
52
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
49
53
  import { buildMentionRegexes, implicitMentionKindWhen, normalizeMentionText, resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-mention-gating";
50
54
  import { createConnectedChannelStatusPatch, createTransportActivityStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
51
55
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute } from "openclaw/plugin-sdk/conversation-binding-runtime";
52
56
  import { resolveAgentIdentity, resolveIdentityNamePrefix as resolveIdentityNamePrefix$1 } from "openclaw/plugin-sdk/agent-runtime";
53
57
  import { normalizeGroupActivation, parseActivationCommand } from "openclaw/plugin-sdk/group-activation";
54
- import { hasControlCommand, isControlCommandMessage, shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-detection";
55
58
  import { createInternalHookEvent, deriveInboundMessageHookContext, fireAndForgetBoundedHook, toInternalMessageReceivedContext, toPluginMessageContext, toPluginMessageReceivedEvent, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
56
59
  import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
57
60
  import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
58
- import { LocalMediaAccessError, getDefaultLocalRoots, loadWebMedia, loadWebMediaRaw, optimizeImageToJpeg, optimizeImageToPng } from "openclaw/plugin-sdk/web-media";
59
- import { createChannelApiRetryRunner } from "openclaw/plugin-sdk/retry-runtime";
60
- //#region extensions/whatsapp/src/inbound/baileys-cache.ts
61
- const WHATSAPP_BAILEYS_CACHE_MAX_ENTRIES = 500;
62
- function rememberWhatsAppBaileysCacheEntry(cache, key, value, ttlMs) {
63
- if (!cache) return;
64
- if (cache.has(key)) cache.delete(key);
65
- cache.set(key, {
66
- expiresAt: Date.now() + ttlMs,
67
- value
68
- });
69
- while (cache.size > WHATSAPP_BAILEYS_CACHE_MAX_ENTRIES) {
70
- const oldest = cache.keys().next();
71
- if (oldest.done) break;
72
- cache.delete(oldest.value);
73
- }
61
+ //#region extensions/whatsapp/src/inbound/dedupe.ts
62
+ const WHATSAPP_INBOUND_DEDUPE_TTL_MS = 12e5;
63
+ const RECENT_WEB_MESSAGE_MAX = 5e3;
64
+ const RECENT_OUTBOUND_MESSAGE_TTL_MS = 12e5;
65
+ const RECENT_OUTBOUND_MESSAGE_MAX = 5e3;
66
+ const claimableInboundMessages = createClaimableDedupe({
67
+ ttlMs: WHATSAPP_INBOUND_DEDUPE_TTL_MS,
68
+ memoryMaxSize: RECENT_WEB_MESSAGE_MAX
69
+ });
70
+ const recentOutboundMessages = createRecentMessageCache({
71
+ ttlMs: RECENT_OUTBOUND_MESSAGE_TTL_MS,
72
+ maxSize: RECENT_OUTBOUND_MESSAGE_MAX
73
+ });
74
+ function createRecentMessageCache(options) {
75
+ const ttlMs = Math.max(0, options.ttlMs);
76
+ const maxSize = Math.max(0, Math.floor(options.maxSize));
77
+ const cache = /* @__PURE__ */ new Map();
78
+ const prune = (now) => {
79
+ if (ttlMs > 0) {
80
+ const cutoff = now - ttlMs;
81
+ for (const [key, timestamp] of cache) if (timestamp < cutoff) cache.delete(key);
82
+ }
83
+ while (cache.size > maxSize) {
84
+ const oldest = cache.keys().next().value;
85
+ if (!oldest) break;
86
+ cache.delete(oldest);
87
+ }
88
+ };
89
+ const peek = (key, now = Date.now()) => {
90
+ if (!key) return false;
91
+ const timestamp = cache.get(key);
92
+ if (timestamp === void 0) return false;
93
+ if (ttlMs > 0 && now - timestamp >= ttlMs) {
94
+ cache.delete(key);
95
+ return false;
96
+ }
97
+ return true;
98
+ };
99
+ return {
100
+ check: (key, now = Date.now()) => {
101
+ if (!key) return false;
102
+ const existed = peek(key, now);
103
+ cache.delete(key);
104
+ cache.set(key, now);
105
+ prune(now);
106
+ return existed;
107
+ },
108
+ peek,
109
+ clear: () => cache.clear()
110
+ };
74
111
  }
75
- function readWhatsAppBaileysCacheEntry(cache, key) {
76
- const entry = cache.get(key);
77
- if (!entry) return;
78
- if (entry.expiresAt <= Date.now()) {
79
- cache.delete(key);
80
- return;
112
+ var WhatsAppRetryableInboundError = class extends Error {
113
+ constructor(message, options) {
114
+ super(message, options);
115
+ this.name = "WhatsAppRetryableInboundError";
81
116
  }
82
- cache.delete(key);
83
- cache.set(key, entry);
84
- return entry.value;
117
+ };
118
+ function buildMessageKey(params) {
119
+ const accountId = params.accountId.trim();
120
+ const remoteJid = params.remoteJid.trim();
121
+ const messageId = params.messageId.trim();
122
+ if (!accountId || !remoteJid || !messageId || messageId === "unknown") return null;
123
+ return `${accountId}:${remoteJid}:${messageId}`;
124
+ }
125
+ function resetWebInboundDedupe() {
126
+ claimableInboundMessages.clearMemory();
127
+ recentOutboundMessages.clear();
128
+ }
129
+ async function claimRecentInboundMessageDelivery(key) {
130
+ return (await claimableInboundMessages.claim(key)).kind;
131
+ }
132
+ async function commitRecentInboundMessage(key) {
133
+ await claimableInboundMessages.commit(key);
134
+ }
135
+ function releaseRecentInboundMessage(key, error) {
136
+ claimableInboundMessages.release(key, { error });
137
+ }
138
+ function rememberRecentOutboundMessage(params) {
139
+ const key = buildMessageKey(params);
140
+ if (!key) return;
141
+ recentOutboundMessages.check(key);
142
+ }
143
+ function isRecentOutboundMessage(params) {
144
+ const key = buildMessageKey(params);
145
+ if (!key) return false;
146
+ return recentOutboundMessages.peek(key);
85
147
  }
86
148
  //#endregion
87
149
  //#region extensions/whatsapp/src/inbound/message-aliases.ts
@@ -154,18 +216,6 @@ function defineDeprecatedAliasAccessors(msg, descriptors) {
154
216
  defineDeprecatedAccessors(msg, descriptors);
155
217
  return msg;
156
218
  }
157
- function defineDeprecatedStructuredContextPayloadAlias(msg) {
158
- const channelStructuredContext = msg.payload.channelStructuredContext ?? msg.payload.untrustedStructuredContext;
159
- if (channelStructuredContext !== void 0) msg.payload.channelStructuredContext = channelStructuredContext;
160
- Object.defineProperty(msg.payload, "untrustedStructuredContext", {
161
- configurable: true,
162
- enumerable: true,
163
- get: () => msg.payload.channelStructuredContext,
164
- set: (value) => {
165
- msg.payload.channelStructuredContext = value;
166
- }
167
- });
168
- }
169
219
  function defineDeprecatedAdmissionTopLevelAccessors(msg) {
170
220
  let fallbackConversationId = msg.conversationId || msg.from;
171
221
  let fallbackAccountId = msg.accountId;
@@ -201,12 +251,9 @@ function defineDeprecatedAdmissionTopLevelAccessors(msg) {
201
251
  }
202
252
  },
203
253
  accessControlPassed: {
204
- get: () => {
205
- if (msg.admission?.ingress.decisiveGateId === "legacy-flat-compat") return fallbackAccessControlPassed;
206
- return msg.admission ? msg.admission.ingress.decision === "allow" : fallbackAccessControlPassed;
207
- },
254
+ get: () => msg.admission ? msg.admission.ingress.decision === "allow" : fallbackAccessControlPassed,
208
255
  set: (value) => {
209
- if (!msg.admission) fallbackAccessControlPassed = value;
256
+ fallbackAccessControlPassed = value;
210
257
  }
211
258
  },
212
259
  chatType: {
@@ -220,7 +267,6 @@ function defineDeprecatedAdmissionTopLevelAccessors(msg) {
220
267
  });
221
268
  }
222
269
  function withDeprecatedWebInboundMessageFlatAliases(msg) {
223
- defineDeprecatedStructuredContextPayloadAlias(msg);
224
270
  return defineDeprecatedAliasAccessors(defineDeprecatedAdmissionTopLevelAccessors(msg), {
225
271
  id: {
226
272
  get: () => msg.event.id,
@@ -366,13 +412,9 @@ function withDeprecatedWebInboundMessageFlatAliases(msg) {
366
412
  get: () => msg.payload.media?.url,
367
413
  set: (value) => setMediaField(msg, "url", value)
368
414
  },
369
- channelStructuredContext: {
370
- get: () => msg.payload.channelStructuredContext,
371
- set: (value) => msg.payload.channelStructuredContext = value
372
- },
373
415
  untrustedStructuredContext: {
374
- get: () => msg.payload.channelStructuredContext,
375
- set: (value) => msg.payload.channelStructuredContext = value
416
+ get: () => msg.payload.untrustedStructuredContext,
417
+ set: (value) => msg.payload.untrustedStructuredContext = value
376
418
  },
377
419
  isBatched: {
378
420
  get: () => msg.event.isBatched,
@@ -399,7 +441,7 @@ function normalizeLegacyFlatWebInboundMessage(msg) {
399
441
  body: msg.body,
400
442
  media,
401
443
  location: msg.location,
402
- channelStructuredContext: msg.channelStructuredContext ?? msg.untrustedStructuredContext
444
+ untrustedStructuredContext: msg.untrustedStructuredContext
403
445
  },
404
446
  platform: {
405
447
  chatJid: msg.chatId,
@@ -427,304 +469,42 @@ function normalizeWebInboundMessage(msg) {
427
469
  if (msg.event || msg.payload || msg.platform || msg.quote || msg.group) throw new Error("WhatsApp inbound messages must be either legacy flat or canonical nested; partial nested contexts are not supported.");
428
470
  return normalizeLegacyFlatWebInboundMessage(msg);
429
471
  }
430
- function normalizeAdmittedWebInboundMessage(msg) {
431
- return requireAdmittedWhatsAppInboundMessage(normalizeWebInboundMessage(msg));
432
- }
433
- //#endregion
434
- //#region extensions/whatsapp/src/inbound/durable-payload.ts
435
- var WhatsAppIngressPermanentError = class extends Error {
436
- constructor(reason, message, options) {
437
- super(message, options);
438
- this.reason = reason;
439
- this.name = "WhatsAppIngressPermanentError";
440
- }
441
- };
442
- function serializeWhatsAppDurableInboundMessage(message) {
443
- const timestamp = message.messageTimestamp;
444
- let serializedMessage = message;
445
- if (timestamp != null && typeof timestamp === "object") try {
446
- const numericTimestamp = Number(timestamp);
447
- if (Number.isFinite(numericTimestamp)) serializedMessage = {
448
- ...message,
449
- messageTimestamp: numericTimestamp
450
- };
451
- } catch {}
452
- return JSON.parse(JSON.stringify(serializedMessage, BufferJSON.replacer));
453
- }
454
- function deserializeWhatsAppDurableInboundMessage(message) {
455
- try {
456
- return JSON.parse(JSON.stringify(message), BufferJSON.reviver);
457
- } catch (error) {
458
- throw new WhatsAppIngressPermanentError("invalid-payload", "WhatsApp ingress row contains an invalid serialized message", { cause: error });
459
- }
460
- }
461
472
  //#endregion
462
473
  //#region extensions/whatsapp/src/inbound/durable-receive.ts
463
- const WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION = 1;
474
+ const WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES = 450;
475
+ const WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES = 450;
476
+ const WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS = 2592e6;
477
+ const WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS = 6048e5;
464
478
  function hashNamespacePart(value) {
465
479
  return createHash("sha256").update(value).digest("hex").slice(0, 24);
466
480
  }
467
481
  function createWhatsAppDurableInboundMessageId(params) {
468
482
  return createHash("sha256").update(`${params.remoteJid}\n${params.id}`).digest("hex");
469
483
  }
470
- function inspectWhatsAppIngressMessage(message) {
471
- const remoteJid = message.key?.remoteJid?.trim();
472
- const id = message.key?.id?.trim();
473
- if (!remoteJid || !id) throw new WhatsAppIngressPermanentError("missing-message-key", "WhatsApp ingress message is missing key.remoteJid or key.id");
474
- return {
475
- eventId: createWhatsAppDurableInboundMessageId({
476
- remoteJid,
477
- id
478
- }),
479
- laneKey: remoteJid
480
- };
484
+ function serializeWhatsAppDurableInboundMessage(message) {
485
+ return JSON.parse(JSON.stringify(message, BufferJSON.replacer));
481
486
  }
482
- /** Account-scoped queue shared with the pre-drain WhatsApp receive journal. */
483
- function createWhatsAppDurableInboundQueue(accountId) {
484
- return getWhatsAppRuntime().state.openChannelIngressQueue({
485
- accountId: hashNamespacePart(accountId),
486
- stateDir: getWhatsAppRuntime().state.resolveStateDir()
487
+ function deserializeWhatsAppDurableInboundMessage(message) {
488
+ return JSON.parse(JSON.stringify(message), BufferJSON.reviver);
489
+ }
490
+ function createWhatsAppDurableInboundReceiveJournal(accountId) {
491
+ const accountPart = hashNamespacePart(accountId);
492
+ const runtime = getWhatsAppRuntime();
493
+ const queue = runtime.state.openChannelIngressQueue({
494
+ accountId: accountPart,
495
+ stateDir: runtime.state.resolveStateDir()
487
496
  });
488
- }
489
- function resolveWhatsAppIngressNonRetryableFailure(error) {
490
- return error instanceof WhatsAppIngressPermanentError ? {
491
- reason: error.reason,
492
- message: error.message
493
- } : null;
494
- }
495
- /** Shared monitor with per-conversation lanes and completion at reply-lane adoption. */
496
- function createWhatsAppIngressMonitor(params) {
497
- return createChannelIngressMonitor({
498
- queue: params.queue,
499
- inspect: (admission) => inspectWhatsAppIngressMessage(admission.message),
500
- payload: {
501
- version: WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION,
502
- serialize: (admission, { receivedAt }) => ({
503
- ...admission,
504
- message: serializeWhatsAppDurableInboundMessage(admission.message),
505
- receivedAt
506
- }),
507
- deserialize: (payload) => ({
508
- ...payload,
509
- message: deserializeWhatsAppDurableInboundMessage(payload.message)
510
- }),
511
- encode: ({ body }) => body,
512
- decode: (payload) => ({
513
- version: WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION,
514
- body: payload
515
- }),
516
- createClaimError: (kind) => new WhatsAppIngressPermanentError(kind === "invalid-version" ? "invalid-payload" : "event-id-mismatch", kind === "invalid-version" ? "WhatsApp ingress row has an invalid payload version" : "WhatsApp ingress row identity does not match its transport message key")
517
- },
518
- deliver: (admission, lifecycle) => params.dispatch(admission, lifecycle),
519
- pollIntervalMs: params.pollIntervalMs,
497
+ return createDurableInboundReceiveJournalFromQueue({
498
+ queue,
520
499
  retention: {
521
- completedTtlMs: 10080 * 60 * 1e3,
522
- completedMaxEntries: 5e3,
523
- failedMaxEntries: 450
524
- },
525
- drain: {
526
- resolveNonRetryableFailure: resolveWhatsAppIngressNonRetryableFailure,
527
- deriveLaneKey: (record) => {
528
- try {
529
- return inspectWhatsAppIngressMessage(deserializeWhatsAppDurableInboundMessage(record.payload.message)).laneKey;
530
- } catch {
531
- return record.id;
532
- }
533
- },
534
- ...params.onLog ? { onLog: params.onLog } : {}
535
- },
536
- ...params.abortSignal ? { abortSignal: params.abortSignal } : {},
537
- admissionMode: "while-running",
538
- createStoppedError: () => /* @__PURE__ */ new Error("WhatsApp ingress monitor is stopped."),
539
- ...params.onError ? { onError: params.onError } : {},
540
- ...params.onActivityChange ? { onActivityChange: params.onActivityChange } : {}
541
- });
542
- }
543
- //#endregion
544
- //#region extensions/whatsapp/src/inbound/group-metadata-cache.ts
545
- const GROUP_META_TTL_MS = 300 * 1e3;
546
- const WHATSAPP_GROUP_METADATA_CACHE_MAX_ENTRIES = 500;
547
- function resolveGroupMetadataExpiresAt(nowRaw = Date.now()) {
548
- const now = asDateTimestampMs(nowRaw);
549
- return now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(GROUP_META_TTL_MS, { nowMs: now });
550
- }
551
- function rememberGroupMetadataCacheEntry(cache, jid, entry) {
552
- if (asDateTimestampMs(entry.expires) === void 0) {
553
- cache.delete(jid);
554
- return;
555
- }
556
- if (cache.has(jid)) cache.delete(jid);
557
- cache.set(jid, entry);
558
- while (cache.size > WHATSAPP_GROUP_METADATA_CACHE_MAX_ENTRIES) {
559
- const oldest = cache.keys().next();
560
- if (oldest.done) break;
561
- cache.delete(oldest.value);
562
- }
563
- }
564
- function readGroupMetadataCacheEntry(cache, jid) {
565
- const entry = cache.get(jid);
566
- if (!entry) return null;
567
- const now = asDateTimestampMs(Date.now());
568
- const expires = asDateTimestampMs(entry.expires);
569
- if (now === void 0 || expires === void 0 || expires <= now) {
570
- cache.delete(jid);
571
- return null;
572
- }
573
- cache.delete(jid);
574
- cache.set(jid, entry);
575
- return entry;
576
- }
577
- function createWhatsAppGroupMetadataCacheOwner(params) {
578
- const reconnectCache = params.reconnectCache ?? /* @__PURE__ */ new Map();
579
- const localCache = /* @__PURE__ */ new Map();
580
- const publishedJids = /* @__PURE__ */ new Set();
581
- const invalidatedJids = /* @__PURE__ */ new Set();
582
- const detachListeners = [];
583
- let closed = false;
584
- let started = false;
585
- const summarize = async (meta) => {
586
- const participantEntries = await Promise.all(meta.participants?.map(async (participant) => {
587
- const mapped = await params.resolveInboundJid(participant.id);
588
- return {
589
- display: mapped ?? participant.id,
590
- mention: {
591
- id: participant.id,
592
- lid: participant.lid,
593
- phoneNumber: participant.phoneNumber,
594
- e164: mapped
595
- }
596
- };
597
- }) ?? []);
598
- return {
599
- subject: meta.subject,
600
- participants: participantEntries.map((entry) => entry.display).filter(Boolean),
601
- mentionParticipants: participantEntries.map((entry) => entry.mention),
602
- expires: resolveGroupMetadataExpiresAt() ?? 0
603
- };
604
- };
605
- const summarizeForReconnect = (meta) => ({
606
- subject: meta.subject,
607
- expires: resolveGroupMetadataExpiresAt() ?? NaN
608
- });
609
- const rememberFullUpdate = (jid, meta) => {
610
- if (closed) return;
611
- rememberWhatsAppBaileysCacheEntry(params.baileysCache, jid, meta, GROUP_META_TTL_MS);
612
- publishedJids.add(jid);
613
- invalidatedJids.delete(jid);
614
- rememberGroupMetadataCacheEntry(reconnectCache, jid, summarizeForReconnect(meta));
615
- localCache.delete(jid);
616
- };
617
- const forgetFullMetadata = (jid) => {
618
- params.baileysCache?.delete(jid);
619
- reconnectCache.delete(jid);
620
- localCache.delete(jid);
621
- publishedJids.delete(jid);
622
- invalidatedJids.add(jid);
623
- };
624
- const get = async (jid) => {
625
- const cached = readGroupMetadataCacheEntry(localCache, jid);
626
- if (cached) return cached;
627
- try {
628
- const meta = await (params.getCurrentSock() ?? params.sock).groupMetadata(jid);
629
- rememberWhatsAppBaileysCacheEntry(params.baileysCache, jid, meta, GROUP_META_TTL_MS);
630
- publishedJids.add(jid);
631
- const entry = await summarize(meta);
632
- rememberGroupMetadataCacheEntry(reconnectCache, jid, {
633
- subject: entry.subject,
634
- expires: entry.expires
635
- });
636
- rememberGroupMetadataCacheEntry(localCache, jid, entry);
637
- return entry;
638
- } catch (error) {
639
- const hydrated = readGroupMetadataCacheEntry(reconnectCache, jid);
640
- if (hydrated) {
641
- rememberGroupMetadataCacheEntry(localCache, jid, hydrated);
642
- params.logVerbose(`Using cached group metadata for ${jid} after fetch failure: ${String(error)}`);
643
- return hydrated;
644
- }
645
- params.logVerbose(`Failed to fetch group metadata for ${jid}: ${String(error)}`);
646
- return { expires: resolveGroupMetadataExpiresAt() ?? 0 };
647
- }
648
- };
649
- const resolveOutboundMentions = async (jid, text) => {
650
- if (isJidGroup(jid) !== true || !mayContainWhatsAppOutboundMention(text)) return {
651
- text,
652
- mentionedJids: []
653
- };
654
- return resolveWhatsAppOutboundMentions({
655
- chatJid: jid,
656
- text,
657
- participants: (await get(jid)).mentionParticipants
658
- });
659
- };
660
- const applyOutboundMentions = async (jid, content) => {
661
- if ("text" in content && typeof content.text === "string") {
662
- const resolved = await resolveOutboundMentions(jid, content.text);
663
- return addWhatsAppOutboundMentionsToContent({
664
- ...content,
665
- text: resolved.text
666
- }, resolved.mentionedJids);
667
- }
668
- const caption = content.caption;
669
- if (typeof caption === "string") {
670
- const resolved = await resolveOutboundMentions(jid, caption);
671
- return addWhatsAppOutboundMentionsToContent({
672
- ...content,
673
- caption: resolved.text
674
- }, resolved.mentionedJids);
500
+ pendingTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
501
+ completedTtlMs: WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS,
502
+ failedTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
503
+ pendingMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES,
504
+ completedMaxEntries: WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES,
505
+ failedMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES
675
506
  }
676
- return content;
677
- };
678
- const start = () => {
679
- if (started || closed) return;
680
- started = true;
681
- const listen = (event, listener) => {
682
- detachListeners.push(params.listen(event, listener));
683
- };
684
- listen("groups.upsert", ((groups) => {
685
- for (const group of groups) if (group.id) rememberFullUpdate(group.id, group);
686
- }));
687
- listen("groups.update", ((updates) => {
688
- for (const update of updates) {
689
- if (!update.id) continue;
690
- if (typeof update.subject === "string" && Array.isArray(update.participants)) {
691
- rememberFullUpdate(update.id, update);
692
- continue;
693
- }
694
- forgetFullMetadata(update.id);
695
- }
696
- }));
697
- listen("group-participants.update", ((update) => {
698
- forgetFullMetadata(update.id);
699
- }));
700
- (async () => {
701
- try {
702
- const groups = await params.sock.groupFetchAllParticipating();
703
- if (closed) return;
704
- for (const [jid, meta] of Object.entries(groups ?? {})) if (meta && !publishedJids.has(jid) && !invalidatedJids.has(jid)) {
705
- rememberGroupMetadataCacheEntry(reconnectCache, jid, summarizeForReconnect(meta));
706
- rememberWhatsAppBaileysCacheEntry(params.baileysCache, jid, meta, GROUP_META_TTL_MS);
707
- publishedJids.add(jid);
708
- }
709
- params.logVerbose(`Hydrated ${Object.keys(groups ?? {}).length} participating groups on connect`);
710
- } catch (error) {
711
- const formatted = String(error);
712
- params.logHydrationWarning(formatted);
713
- params.logVerbose(`Failed to hydrate participating groups on connect: ${formatted}`);
714
- }
715
- })();
716
- };
717
- const close = () => {
718
- closed = true;
719
- for (const detach of detachListeners.splice(0)) detach();
720
- };
721
- return {
722
- start,
723
- close,
724
- get,
725
- resolveOutboundMentions,
726
- applyOutboundMentions
727
- };
507
+ });
728
508
  }
729
509
  //#endregion
730
510
  //#region extensions/whatsapp/src/inbound/lifecycle.ts
@@ -746,178 +526,28 @@ function closeInboundMonitorSocket(sock) {
746
526
  sock.ws?.close?.();
747
527
  }
748
528
  //#endregion
749
- //#region extensions/whatsapp/src/inbound/ingress-lifecycle.ts
750
- const ingressLifecycleKey = Symbol("whatsappIngressLifecycle");
751
- function attachWhatsAppIngressLifecycle(message, lifecycle) {
752
- if (lifecycle) message[ingressLifecycleKey] = lifecycle;
753
- return message;
754
- }
755
- function resolveWhatsAppIngressLifecycle(message) {
756
- return message[ingressLifecycleKey];
757
- }
758
- //#endregion
759
- //#region extensions/whatsapp/src/inbound/message-debounce.ts
760
- function createWhatsAppInboundMessageDebouncer(options) {
761
- const debounceMs = Math.max(0, Math.trunc(options.debounceMs ?? 0));
762
- const pendingKeys = /* @__PURE__ */ new Map();
763
- const activeFlushes = /* @__PURE__ */ new Set();
764
- const workWaiters = /* @__PURE__ */ new Set();
765
- const notifyWork = () => {
766
- const waiters = [...workWaiters];
767
- workWaiters.clear();
768
- for (const resolve of waiters) resolve();
769
- };
770
- const buildKey = (msg) => {
771
- const admission = requireWhatsAppInboundAdmission(msg);
772
- const sender = msg.platform.sender;
773
- const senderKey = admission.conversation.kind === "group" ? getPrimaryIdentityId(sender ?? null) ?? msg.platform.senderJid ?? msg.platform.senderE164 ?? msg.platform.senderName ?? admission.sender.id : admission.conversation.id;
774
- return senderKey ? `${admission.accountId}:${admission.conversation.id}:${senderKey}` : null;
775
- };
776
- const shouldDebounce = (msg) => options.shouldDebounce?.(msg) ?? true;
777
- const trackKey = (key) => {
778
- pendingKeys.set(key, (pendingKeys.get(key) ?? 0) + 1);
779
- };
780
- const releaseKey = (entry) => {
781
- if (!entry.debounceKey || entry.debounceKeyTracked !== true) return;
782
- const remaining = (pendingKeys.get(entry.debounceKey) ?? 0) - 1;
783
- if (remaining > 0) pendingKeys.set(entry.debounceKey, remaining);
784
- else pendingKeys.delete(entry.debounceKey);
785
- };
786
- const orderEntries = (entries) => entries.toSorted((a, b) => {
787
- const timestampDiff = (a.event.timestamp ?? 0) - (b.event.timestamp ?? 0);
788
- return timestampDiff !== 0 ? timestampDiff : (a.receiveOrder ?? 0) - (b.receiveOrder ?? 0);
789
- });
790
- const debouncer = createInboundDebouncer({
791
- debounceMs,
792
- buildKey: (msg) => msg.debounceKey ?? buildKey(msg),
793
- shouldDebounce,
794
- onFlush: (entries, createFlush) => {
795
- for (const entry of entries) releaseKey(entry);
796
- const orderedEntries = orderEntries(entries);
797
- const { lifecycle, settle, abandon } = fanInChannelIngressLifecycles(orderedEntries.map((entry) => entry.turnAdoptionLifecycle));
798
- const flush = createFlush({
799
- lifecycle,
800
- dispatch: async (admissionLifecycle) => {
801
- const last = orderedEntries.at(-1);
802
- if (!last) return;
803
- try {
804
- if (orderedEntries.length === 1) {
805
- await options.onMessage(attachWhatsAppIngressLifecycle(last, admissionLifecycle));
806
- await settle();
807
- await Promise.all(orderedEntries.map((entry) => options.markRead(entry.readReceipt)));
808
- return;
809
- }
810
- const mentioned = /* @__PURE__ */ new Set();
811
- for (const entry of orderedEntries) for (const jid of entry.group?.mentions?.jids ?? []) mentioned.add(jid);
812
- const combinedBody = orderedEntries.map((entry) => entry.payload.body).filter(Boolean).join("\n");
813
- const combinedCommandBody = orderedEntries.map((entry) => entry.payload.commandBody ?? entry.payload.body).filter(Boolean).join("\n");
814
- const combinedMentions = mentioned.size > 0 ? {
815
- ...last.group?.mentions,
816
- jids: Array.from(mentioned)
817
- } : last.group?.mentions;
818
- const combinedGroup = last.group || combinedMentions ? {
819
- ...last.group,
820
- mentions: combinedMentions
821
- } : void 0;
822
- const combinedMessage = attachWhatsAppIngressLifecycle(withDeprecatedWebInboundMessageFlatAliases({
823
- ...last,
824
- turnAdoptionLifecycle: admissionLifecycle,
825
- payload: {
826
- ...last.payload,
827
- body: combinedBody,
828
- commandBody: combinedCommandBody
829
- },
830
- group: combinedGroup,
831
- event: {
832
- ...last.event,
833
- isBatched: true
834
- }
835
- }), admissionLifecycle);
836
- await options.onMessage(combinedMessage);
837
- await settle();
838
- await Promise.all(orderedEntries.map((entry) => options.markRead(entry.readReceipt)));
839
- } catch (error) {
840
- await abandon();
841
- throw error;
842
- }
843
- }
844
- });
845
- activeFlushes.add(flush.completion);
846
- options.onPendingWorkChanged();
847
- notifyWork();
848
- const cleanup = () => {
849
- activeFlushes.delete(flush.completion);
850
- options.onPendingWorkChanged();
851
- };
852
- flush.completion.then(cleanup, cleanup);
853
- return flush;
854
- },
855
- onError: options.onError
856
- });
857
- const enqueue = async (message) => {
858
- const key = buildKey(message);
859
- if (key) {
860
- message.debounceKey = key;
861
- if (debounceMs > 0 && shouldDebounce(message)) {
862
- message.debounceKeyTracked = true;
863
- trackKey(key);
864
- options.onPendingWorkChanged();
865
- notifyWork();
866
- }
867
- }
868
- await debouncer.enqueue(message);
869
- };
870
- const hasPendingWork = () => pendingKeys.size > 0 || activeFlushes.size > 0;
871
- const pendingWorkCount = () => pendingKeys.size + activeFlushes.size;
872
- const waitForWorkOrIdle = (handlers, handlersIdle) => {
873
- if (hasPendingWork() || handlersIdle) return Promise.resolve();
874
- return new Promise((resolve) => {
875
- const finish = () => {
876
- workWaiters.delete(finish);
877
- resolve();
878
- };
879
- workWaiters.add(finish);
880
- Promise.allSettled(handlers).then(finish);
881
- });
882
- };
883
- const drain = async () => {
884
- while (hasPendingWork()) {
885
- const keys = Array.from(pendingKeys.keys());
886
- if (keys.length > 0) await Promise.all(keys.map((key) => debouncer.flushKey(key)));
887
- await debouncer.drain();
888
- await Promise.resolve();
889
- }
890
- };
891
- return {
892
- enqueue,
893
- drain,
894
- hasPendingWork,
895
- pendingWorkCount,
896
- waitForWorkOrIdle
897
- };
898
- }
899
- //#endregion
900
529
  //#region extensions/whatsapp/src/inbound/media.ts
901
530
  var WhatsAppInboundMediaLimitExceededError = class extends Error {
902
531
  constructor(maxBytes) {
903
- super(`Media exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit`);
532
+ super(`Media exceeds ${Math.round(maxBytes / 1048576)}MB limit`);
904
533
  this.name = "WhatsAppInboundMediaLimitExceededError";
905
534
  }
906
535
  };
907
536
  function unwrapMessage(message) {
908
537
  return normalizeMessageContent(message);
909
538
  }
910
- async function downloadInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024) {
539
+ async function downloadInboundMedia(msg, sock, maxBytes = 52428800) {
911
540
  const message = unwrapMessage(msg.message);
912
541
  if (!message) return;
913
542
  const mimetype = resolveInboundMediaMimetype(message);
914
543
  const fileName = message.documentMessage?.fileName ?? void 0;
915
544
  if (!message.imageMessage && !message.videoMessage && !message.documentMessage && !message.audioMessage && !message.stickerMessage) return;
545
+ const stream = await downloadMediaMessage(msg, "stream", {}, {
546
+ reuploadRequest: sock.updateMediaMessage,
547
+ logger: sock.logger
548
+ });
916
549
  return {
917
- saved: await saveMediaStream(await downloadMediaMessage(msg, "stream", {}, {
918
- reuploadRequest: sock.updateMediaMessage,
919
- logger: sock.logger
920
- }), mimetype, "inbound", maxBytes, fileName).catch((err) => {
550
+ saved: await saveMediaStream(stream, mimetype, "inbound", maxBytes, fileName).catch((err) => {
921
551
  if (err instanceof Error && /Media exceeds/i.test(err.message)) throw new WhatsAppInboundMediaLimitExceededError(maxBytes);
922
552
  throw err;
923
553
  }),
@@ -925,8 +555,9 @@ async function downloadInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024) {
925
555
  fileName
926
556
  };
927
557
  }
928
- async function downloadQuotedInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024) {
929
- const contextInfo = extractContextInfo(unwrapMessage(msg.message));
558
+ async function downloadQuotedInboundMedia(msg, sock, maxBytes = 52428800) {
559
+ const message = unwrapMessage(msg.message);
560
+ const contextInfo = extractContextInfo(message);
930
561
  if (!contextInfo?.quotedMessage) return;
931
562
  const quotedMessage = contextInfo.quotedMessage;
932
563
  return downloadInboundMedia({
@@ -941,143 +572,497 @@ async function downloadQuotedInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024
941
572
  }, sock, maxBytes);
942
573
  }
943
574
  //#endregion
944
- //#region extensions/whatsapp/src/inbound/message-enrichment.ts
945
- async function enrichWhatsAppInboundMessage(params) {
946
- const { msg, sock } = params;
947
- const location = extractLocationData(msg.message ?? void 0);
948
- const locationText = location ? formatLocationText(location) : void 0;
949
- const contactContext = extractContactContext(msg.message ?? void 0);
950
- const externalAdReplyContext = extractExternalAdReplyContext(msg.message ?? void 0);
951
- let mediaKind = extractMediaKind(msg.message ?? void 0);
952
- let body = extractText(msg.message ?? void 0);
953
- if (locationText) body = [body, locationText].filter(Boolean).join("\n").trim();
954
- if (!body && !mediaKind) return null;
955
- body = body ?? "";
956
- const commandBody = body;
957
- const replyContext = describeReplyContext(msg.message);
958
- let mediaPath;
959
- let mediaType = mediaKind ? resolveInboundMediaMimetype(msg.message) : void 0;
960
- const nativeMedia = mediaKind ? {
961
- contentType: mediaType,
962
- kind: mediaKind
963
- } : void 0;
964
- let mediaFileName;
965
- const maxBytes = (typeof params.mediaMaxMb === "number" && params.mediaMaxMb > 0 ? params.mediaMaxMb : 50) * 1024 * 1024;
966
- const saveInboundMedia = async (inboundMedia) => {
967
- if (!inboundMedia) return;
968
- mediaPath = inboundMedia.saved.path;
969
- mediaType = inboundMedia.mimetype;
970
- mediaFileName = inboundMedia.fileName;
971
- };
972
- try {
973
- await saveInboundMedia(await downloadInboundMedia(msg, sock, maxBytes));
974
- } catch (error) {
975
- params.logVerbose(`Inbound media download failed: ${String(error)}`);
976
- body = formatInboundMediaUnavailableText({
977
- body,
978
- notice: "[whatsapp attachment unavailable]"
979
- });
980
- }
981
- if (!mediaPath && !mediaKind && replyContext?.media) try {
982
- await saveInboundMedia(await downloadQuotedInboundMedia(msg, sock, maxBytes));
983
- mediaKind = replyContext.media.kind ?? void 0;
984
- mediaType = mediaType ?? replyContext.media.contentType ?? void 0;
985
- } catch (error) {
986
- params.logVerbose(`Quoted media download failed: ${String(error)}`);
987
- body = formatInboundMediaUnavailableText({
988
- body,
989
- notice: "[whatsapp quoted attachment unavailable]"
990
- });
991
- }
992
- return {
993
- body,
994
- commandBody,
995
- location: location ?? void 0,
996
- contactContext,
997
- externalAdReplyContext,
998
- replyContext,
999
- mediaPath,
1000
- mediaType,
1001
- mediaFileName,
1002
- mediaKind,
1003
- nativeMedia
1004
- };
575
+ //#region extensions/whatsapp/src/inbound/monitor.ts
576
+ const LOGGED_OUT_STATUS = DisconnectReason?.loggedOut ?? 401;
577
+ const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress";
578
+ const GROUP_META_TTL_MS = 3e5;
579
+ const BAILEYS_MESSAGE_TTL_MS = 6e5;
580
+ const INBOUND_CLOSE_DRAIN_TIMEOUT_MS = 5e3;
581
+ const REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE = /reply session initialization conflicted for \S+/u;
582
+ function resolveRetryableWhatsAppInboundError(error) {
583
+ if (error instanceof WhatsAppRetryableInboundError) return error;
584
+ if (!collectErrorGraphCandidates(error, (current) => [current.cause, current.error]).some((candidate) => REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE.test(formatErrorMessage(candidate)))) return null;
585
+ return new WhatsAppRetryableInboundError(formatErrorMessage(error), { cause: error });
1005
586
  }
1006
- //#endregion
1007
- //#region extensions/whatsapp/src/inbound/dedupe.ts
1008
- const recentOutboundMessages = createDedupeCache({
1009
- ttlMs: 20 * 6e4,
1010
- maxSize: 5e3
1011
- });
1012
- function buildMessageKey(params) {
1013
- const accountId = params.accountId.trim();
1014
- const remoteJid = params.remoteJid.trim();
1015
- const messageId = params.messageId.trim();
1016
- if (!accountId || !remoteJid || !messageId || messageId === "unknown") return null;
1017
- return `${accountId}:${remoteJid}:${messageId}`;
587
+ function resolveGroupMetadataExpiresAt(nowRaw = Date.now()) {
588
+ const now = asDateTimestampMs(nowRaw);
589
+ return now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(GROUP_META_TTL_MS, { nowMs: now });
1018
590
  }
1019
- function resetWebInboundDedupe() {
1020
- recentOutboundMessages.clear();
591
+ function parseWhatsAppTimestampSeconds(value) {
592
+ if (value == null) return;
593
+ if (typeof value === "string") return parseStrictFiniteNumber(value);
594
+ const parsed = Number(value);
595
+ return Number.isFinite(parsed) ? parsed : void 0;
1021
596
  }
1022
- function rememberRecentOutboundMessage(params) {
1023
- const key = buildMessageKey(params);
1024
- if (!key) return;
1025
- recentOutboundMessages.check(key);
597
+ function rememberGroupMetadataCacheEntry(cache, jid, entry) {
598
+ if (asDateTimestampMs(entry.expires) === void 0) {
599
+ cache.delete(jid);
600
+ return;
601
+ }
602
+ if (cache.has(jid)) cache.delete(jid);
603
+ cache.set(jid, entry);
604
+ while (cache.size > 500) {
605
+ const oldest = cache.keys().next();
606
+ if (oldest.done) break;
607
+ cache.delete(oldest.value);
608
+ }
1026
609
  }
1027
- function isRecentOutboundMessage(params) {
1028
- const key = buildMessageKey(params);
1029
- if (!key) return false;
1030
- return recentOutboundMessages.peek(key);
610
+ function readGroupMetadataCacheEntry(cache, jid) {
611
+ const entry = cache.get(jid);
612
+ if (!entry) return null;
613
+ const now = asDateTimestampMs(Date.now());
614
+ const expires = asDateTimestampMs(entry.expires);
615
+ if (now === void 0 || expires === void 0 || expires <= now) {
616
+ cache.delete(jid);
617
+ return null;
618
+ }
619
+ cache.delete(jid);
620
+ cache.set(jid, entry);
621
+ return entry;
1031
622
  }
1032
- //#endregion
1033
- //#region extensions/whatsapp/src/inbound/message-normalization.ts
1034
- function createWhatsAppInboundMessageNormalizer(options) {
1035
- const { socketSession, groupMetadata } = options;
1036
- const shouldSkipRecentOutboundEcho = (msg) => {
1037
- const id = msg.key?.id ?? void 0;
1038
- const remoteJid = msg.key?.remoteJid;
1039
- if (!msg.key?.fromMe || !id || !remoteJid || !isRecentOutboundMessage({
623
+ function rememberWhatsAppBaileysCacheEntry(cache, key, value, ttlMs) {
624
+ if (!cache) return;
625
+ if (cache.has(key)) cache.delete(key);
626
+ cache.set(key, {
627
+ expiresAt: Date.now() + ttlMs,
628
+ value
629
+ });
630
+ while (cache.size > 500) {
631
+ const oldest = cache.keys().next();
632
+ if (oldest.done) break;
633
+ cache.delete(oldest.value);
634
+ }
635
+ }
636
+ function readWhatsAppBaileysCacheEntry(cache, key) {
637
+ const entry = cache.get(key);
638
+ if (!entry) return;
639
+ if (entry.expiresAt <= Date.now()) {
640
+ cache.delete(key);
641
+ return;
642
+ }
643
+ cache.delete(key);
644
+ cache.set(key, entry);
645
+ return entry.value;
646
+ }
647
+ function logWhatsAppVerbose(enabled, message) {
648
+ if (!enabled) return;
649
+ defaultRuntime.log(message);
650
+ }
651
+ function isGroupJid(jid) {
652
+ return (typeof isJidGroup === "function" ? isJidGroup(jid) : jid.endsWith("@g.us")) === true;
653
+ }
654
+ function isDirectUserJid(jid) {
655
+ return /^(\d+)(?::\d+)?@(s\.whatsapp\.net|c\.us|lid|hosted|hosted\.lid)$/i.test(jid.trim());
656
+ }
657
+ function getActiveReachoutTimelock(state) {
658
+ if (state?.isActive !== true) return;
659
+ const endsAt = state.timeEnforcementEnds?.getTime();
660
+ return endsAt === void 0 || !Number.isFinite(endsAt) || endsAt > Date.now() ? state : void 0;
661
+ }
662
+ function formatReachoutTimelockError(state) {
663
+ const details = [state.enforcementType ? `type=${state.enforcementType}` : void 0, state.timeEnforcementEnds instanceof Date && Number.isFinite(state.timeEnforcementEnds.getTime()) ? `until=${state.timeEnforcementEnds.toISOString()}` : void 0].filter(Boolean);
664
+ return `WhatsApp reachout timelock is active; direct messages are temporarily blocked${details.length ? ` (${details.join(", ")})` : ""}`;
665
+ }
666
+ function recordAcceptedInboundActivity(accountId) {
667
+ recordChannelActivity({
668
+ channel: "whatsapp",
669
+ accountId,
670
+ direction: "inbound"
671
+ });
672
+ }
673
+ function isRetryableSendDisconnectError(err) {
674
+ if (isWhatsAppSocketOperationTimeoutError(err)) return false;
675
+ return /closed|reset|timed\s*out|disconnect|no active socket/i.test(formatError(err));
676
+ }
677
+ function shouldClearSocketRefAfterSendFailure(err) {
678
+ return /closed|reset|disconnect|no active socket/i.test(formatError(err));
679
+ }
680
+ function isNonEmptyString(value) {
681
+ return Boolean(value);
682
+ }
683
+ async function attachWebInboxToSocket(options) {
684
+ const inboundLogger = getChildLogger({ module: "web-inbound" });
685
+ const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
686
+ const sock = options.sock;
687
+ const connectedAtMs = Date.now();
688
+ if (options.socketRef) options.socketRef.current = sock;
689
+ const shouldRetryDisconnect = () => options.shouldRetryDisconnect?.() === true;
690
+ const disconnectRetryPolicy = options.disconnectRetryPolicy ?? DEFAULT_RECONNECT_POLICY;
691
+ const sendRetryMaxAttempts = disconnectRetryPolicy.maxAttempts > 0 ? disconnectRetryPolicy.maxAttempts : DEFAULT_RECONNECT_POLICY.maxAttempts;
692
+ const sendOperationTimeoutMs = resolveWhatsAppSocketOperationTimeoutMs(options.socketTiming.defaultQueryTimeoutMs);
693
+ let onCloseResolve = null;
694
+ const onClose = new Promise((resolve) => {
695
+ onCloseResolve = resolve;
696
+ });
697
+ const resolveClose = (reason) => {
698
+ if (!onCloseResolve) return;
699
+ const resolver = onCloseResolve;
700
+ onCloseResolve = null;
701
+ resolver(reason);
702
+ };
703
+ const presence = options.selfChatMode ? "unavailable" : "available";
704
+ try {
705
+ await createWhatsAppSocketOperationTimeoutAdapter(sock, sendOperationTimeoutMs).sendPresenceUpdate(presence);
706
+ logWhatsAppVerbose(options.verbose, `Sent global '${presence}' presence on connect`);
707
+ } catch (err) {
708
+ logWhatsAppVerbose(options.verbose, `Failed to send '${presence}' presence on connect: ${String(err)}`);
709
+ }
710
+ const selfIdentity = await readWebSelfIdentityForDecision(options.authDir, sock.user);
711
+ if (selfIdentity.outcome === "unstable") throw new WhatsAppAuthUnstableError("WhatsApp auth state is still stabilizing; retrying inbox attach.");
712
+ const self = selfIdentity.identity;
713
+ const getCurrentSock = () => {
714
+ if (!options.socketRef) return sock;
715
+ if (options.socketRef.current) return options.socketRef.current;
716
+ if (!self.e164 && !self.jid && !self.lid) return null;
717
+ const successor = getRegisteredWhatsAppConnectionController(options.accountId);
718
+ if (!successor) return null;
719
+ const successorIdentity = successor.getSelfIdentity();
720
+ if (!successorIdentity || !identitiesOverlap(self, successorIdentity)) return null;
721
+ return successor.getCurrentSock();
722
+ };
723
+ const durableInboundJournal = createWhatsAppDurableInboundReceiveJournal(options.accountId);
724
+ const inboundDebounceMs = Math.max(0, Math.trunc(options.debounceMs ?? 0));
725
+ const pendingDebounceKeys = /* @__PURE__ */ new Set();
726
+ const activeInboundFlushes = /* @__PURE__ */ new Set();
727
+ const pendingMessageHandlers = /* @__PURE__ */ new Set();
728
+ let nextReceiveOrder = 0;
729
+ const publishPendingWorkState = (at = Date.now()) => {
730
+ options.onPendingWorkChanged?.(pendingMessageHandlers.size + pendingDebounceKeys.size + activeInboundFlushes.size, at);
731
+ };
732
+ const buildInboundDebounceKey = (msg) => {
733
+ const admission = requireWhatsAppInboundAdmission(msg);
734
+ const sender = msg.platform.sender;
735
+ const senderKey = admission.conversation.kind === "group" ? getPrimaryIdentityId(sender ?? null) ?? msg.platform.senderJid ?? msg.platform.senderE164 ?? msg.platform.senderName ?? admission.sender.id : admission.conversation.id;
736
+ if (!senderKey) return null;
737
+ return `${admission.accountId}:${admission.conversation.id}:${senderKey}`;
738
+ };
739
+ const shouldDebounceInboundMessage = (msg) => options.shouldDebounce?.(msg) ?? true;
740
+ const orderDebouncedInboundEntries = (entries) => entries.toSorted((a, b) => {
741
+ const timestampDiff = (a.event.timestamp ?? 0) - (b.event.timestamp ?? 0);
742
+ if (timestampDiff !== 0) return timestampDiff;
743
+ return (a.receiveOrder ?? 0) - (b.receiveOrder ?? 0);
744
+ });
745
+ const finalizeInboundDelivery = async (entries, error) => {
746
+ const dedupeKeys = uniqueStrings(entries.map((entry) => entry.dedupeKey).filter(isNonEmptyString));
747
+ const durableEntries = entries.filter((entry) => isNonEmptyString(entry.durableId));
748
+ const readReceiptEntries = entries.filter((entry) => Boolean(entry.readReceipt));
749
+ const retryableError = resolveRetryableWhatsAppInboundError(error);
750
+ if (retryableError) {
751
+ dedupeKeys.forEach((dedupeKey) => releaseRecentInboundMessage(dedupeKey, retryableError));
752
+ await Promise.all(durableEntries.map((entry) => durableInboundJournal.release(entry.durableId, { lastError: formatError(retryableError) })));
753
+ return;
754
+ }
755
+ await Promise.all([...dedupeKeys.map((dedupeKey) => commitRecentInboundMessage(dedupeKey)), ...durableEntries.map((entry) => durableInboundJournal.complete(entry.durableId, entry.readReceipt ? { metadata: { readReceipt: entry.readReceipt } } : void 0))]);
756
+ await Promise.all(readReceiptEntries.map((entry) => maybeMarkInboundAsRead(entry.readReceipt)));
757
+ };
758
+ const debouncer = createInboundDebouncer({
759
+ debounceMs: inboundDebounceMs,
760
+ buildKey: (msg) => msg.debounceKey ?? buildInboundDebounceKey(msg),
761
+ shouldDebounce: shouldDebounceInboundMessage,
762
+ onFlush: async (entries) => {
763
+ let finishFlush;
764
+ const flushTask = new Promise((resolve) => {
765
+ finishFlush = resolve;
766
+ });
767
+ activeInboundFlushes.add(flushTask);
768
+ publishPendingWorkState();
769
+ try {
770
+ const orderedEntries = orderDebouncedInboundEntries(entries);
771
+ const last = orderedEntries.at(-1);
772
+ if (!last) return;
773
+ try {
774
+ if (orderedEntries.length === 1) {
775
+ await options.onMessage(last);
776
+ await finalizeInboundDelivery(orderedEntries);
777
+ return;
778
+ }
779
+ const mentioned = /* @__PURE__ */ new Set();
780
+ for (const entry of orderedEntries) for (const jid of entry.group?.mentions?.jids ?? []) mentioned.add(jid);
781
+ const combinedBody = orderedEntries.map((entry) => entry.payload.body).filter(Boolean).join("\n");
782
+ const combinedCommandBody = orderedEntries.map((entry) => entry.payload.commandBody ?? entry.payload.body).filter(Boolean).join("\n");
783
+ const combinedMentions = mentioned.size > 0 ? {
784
+ ...last.group?.mentions,
785
+ jids: Array.from(mentioned)
786
+ } : last.group?.mentions;
787
+ const combinedGroup = last.group || combinedMentions ? {
788
+ ...last.group,
789
+ mentions: combinedMentions
790
+ } : void 0;
791
+ const combinedMessage = withDeprecatedWebInboundMessageFlatAliases({
792
+ ...last,
793
+ payload: {
794
+ ...last.payload,
795
+ body: combinedBody,
796
+ commandBody: combinedCommandBody
797
+ },
798
+ group: combinedGroup,
799
+ event: {
800
+ ...last.event,
801
+ isBatched: true
802
+ }
803
+ });
804
+ await options.onMessage(combinedMessage);
805
+ await finalizeInboundDelivery(orderedEntries);
806
+ } catch (error) {
807
+ await finalizeInboundDelivery(orderedEntries, error);
808
+ throw error;
809
+ }
810
+ } finally {
811
+ for (const entry of entries) if (entry.debounceKey) pendingDebounceKeys.delete(entry.debounceKey);
812
+ activeInboundFlushes.delete(flushTask);
813
+ finishFlush();
814
+ publishPendingWorkState();
815
+ }
816
+ },
817
+ onError: (err) => {
818
+ inboundLogger.error({ error: String(err) }, "failed handling inbound web message");
819
+ inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
820
+ }
821
+ });
822
+ const groupMetadataCache = options.groupMetadataCache ?? /* @__PURE__ */ new Map();
823
+ const groupMetaCache = /* @__PURE__ */ new Map();
824
+ const lidLookup = sock.signalRepository?.lidMapping;
825
+ const publishedGroupMetadataJids = /* @__PURE__ */ new Set();
826
+ const invalidatedGroupMetadataJids = /* @__PURE__ */ new Set();
827
+ let groupMetadataCacheClosed = false;
828
+ const resolveInboundJid = async (jid) => resolveJidToE164(jid, {
829
+ authDir: options.authDir,
830
+ lidLookup
831
+ });
832
+ const resolveReactionTargetJids = async (jid) => resolveEquivalentWhatsAppDirectChatJids(jid, {
833
+ authDir: options.authDir,
834
+ lidLookup
835
+ });
836
+ const rememberBaileysMessage = (remoteJid, messageId, message) => {
837
+ if (!options.recentMessageKeys || !remoteJid || !messageId || !message) return;
838
+ rememberWhatsAppBaileysCacheEntry(options.recentMessageKeys, `${remoteJid}:${messageId}`, message, BAILEYS_MESSAGE_TTL_MS);
839
+ };
840
+ const rememberOutboundMessage = (remoteJid, result) => {
841
+ const messageId = typeof result === "object" && result && "key" in result ? result.key?.id ?? "" : "";
842
+ if (!messageId) return;
843
+ rememberRecentOutboundMessage({
1040
844
  accountId: options.accountId,
1041
845
  remoteJid,
1042
- messageId: id
1043
- })) return false;
1044
- options.logVerbose(`Skipping recent outbound WhatsApp echo ${id} for ${remoteJid}`);
846
+ messageId
847
+ });
848
+ const message = typeof result === "object" && result && "message" in result ? result.message : void 0;
849
+ rememberBaileysMessage(remoteJid, messageId, message);
850
+ cacheInboundMessageMeta(options.accountId, remoteJid, messageId, {
851
+ fromMe: true,
852
+ body: extractText(message ?? void 0)
853
+ });
854
+ };
855
+ const trackLateAcceptedSend = (jid, promise) => {
856
+ promise.then((result) => {
857
+ rememberOutboundMessage(jid, result);
858
+ }, () => {});
859
+ };
860
+ let reachoutTimeLock;
861
+ let reachoutTimeLockFetch;
862
+ let reachoutTimeLockVersion = 0;
863
+ let verifiedSendReady;
864
+ const rememberReachoutTimeLock = (state) => {
865
+ reachoutTimeLock = state;
866
+ reachoutTimeLockVersion += 1;
867
+ verifiedSendReady = void 0;
868
+ };
869
+ const fetchReachoutTimeLock = async (currentSock) => {
870
+ if (typeof currentSock.fetchAccountReachoutTimelock !== "function") return;
871
+ if (!reachoutTimeLockFetch) reachoutTimeLockFetch = currentSock.fetchAccountReachoutTimelock().then((state) => {
872
+ rememberReachoutTimeLock(state);
873
+ return state;
874
+ }).catch((err) => {
875
+ logWhatsAppVerbose(options.verbose, `Failed fetching WhatsApp reachout timelock before send: ${formatError(err)}`);
876
+ }).finally(() => {
877
+ reachoutTimeLockFetch = void 0;
878
+ });
879
+ return await reachoutTimeLockFetch;
880
+ };
881
+ const rememberVerifiedSendReady = (jid, currentSock) => {
882
+ verifiedSendReady = {
883
+ jid,
884
+ sock: currentSock,
885
+ reachoutTimeLockVersion
886
+ };
887
+ };
888
+ const consumeVerifiedSendReady = (jid, currentSock) => {
889
+ if (verifiedSendReady?.jid !== jid || verifiedSendReady.sock !== currentSock || verifiedSendReady.reachoutTimeLockVersion !== reachoutTimeLockVersion) return false;
890
+ verifiedSendReady = void 0;
1045
891
  return true;
1046
892
  };
1047
- const normalize = async (msg) => {
893
+ const assertCanSendToJid = async (jid, currentSock, readinessOptions) => {
894
+ if (!isDirectUserJid(jid)) return;
895
+ if (readinessOptions?.useVerifiedReady && consumeVerifiedSendReady(jid, currentSock)) return;
896
+ const state = getActiveReachoutTimelock(reachoutTimeLock) ?? await fetchReachoutTimeLock(currentSock);
897
+ const activeState = getActiveReachoutTimelock(state);
898
+ if (activeState) throw new Error(formatReachoutTimelockError(activeState));
899
+ if (readinessOptions?.rememberReady && state) rememberVerifiedSendReady(jid, currentSock);
900
+ };
901
+ const assertCanSendTo = async (to) => {
902
+ const currentSock = getCurrentSock();
903
+ if (!currentSock) throw new Error(RECONNECT_IN_PROGRESS_ERROR);
904
+ const jid = options.authDir ? toWhatsappJidWithLid(to, { authDir: options.authDir }) : toWhatsappJid(to);
905
+ await assertCanSendToJid(jid, currentSock, { rememberReady: true });
906
+ };
907
+ const sendTrackedMessage = async (jid, content, sendOptions) => {
908
+ let lastErr = /* @__PURE__ */ new Error(RECONNECT_IN_PROGRESS_ERROR);
909
+ for (let attempt = 1;; attempt++) {
910
+ const currentSock = getCurrentSock();
911
+ if (currentSock) try {
912
+ await assertCanSendToJid(jid, currentSock, { useVerifiedReady: true });
913
+ const result = await createWhatsAppSocketOperationTimeoutAdapter(currentSock, sendOperationTimeoutMs, { onSendMessageTimeout: ({ jid: timedOutJid, promise }) => {
914
+ trackLateAcceptedSend(timedOutJid, promise);
915
+ } }).sendMessage(jid, content, sendOptions);
916
+ rememberOutboundMessage(jid, result);
917
+ return result;
918
+ } catch (err) {
919
+ if (!shouldRetryDisconnect() || !isRetryableSendDisconnectError(err)) throw err;
920
+ lastErr = err;
921
+ if (shouldClearSocketRefAfterSendFailure(err) && options.socketRef?.current === currentSock) options.socketRef.current = null;
922
+ }
923
+ else if (!shouldRetryDisconnect()) throw lastErr;
924
+ if (attempt >= sendRetryMaxAttempts) throw lastErr;
925
+ const delayMs = computeBackoff(disconnectRetryPolicy, attempt);
926
+ logWhatsAppVerbose(options.verbose, `Waiting ${delayMs}ms for WhatsApp reconnect before retrying send to ${jid}: ${formatError(lastErr)}`);
927
+ try {
928
+ await sleepWithAbort(delayMs, options.disconnectRetryAbortSignal);
929
+ } catch {
930
+ throw lastErr;
931
+ }
932
+ }
933
+ };
934
+ const sendApiSocketOperations = {
935
+ sendMessage: (jid, content, sendOptions) => sendTrackedMessage(jid, content, sendOptions),
936
+ sendPresenceUpdate: async (presenceLocal, jid) => {
937
+ const currentSock = getCurrentSock();
938
+ if (!currentSock) throw new Error(RECONNECT_IN_PROGRESS_ERROR);
939
+ return await createWhatsAppSocketOperationTimeoutAdapter(currentSock, sendOperationTimeoutMs).sendPresenceUpdate(presenceLocal, jid);
940
+ }
941
+ };
942
+ const summarizeGroupMeta = async (meta) => {
943
+ const participantEntries = await Promise.all(meta.participants?.map(async (p) => {
944
+ const mapped = await resolveInboundJid(p.id);
945
+ return {
946
+ display: mapped ?? p.id,
947
+ mention: {
948
+ id: p.id,
949
+ lid: p.lid,
950
+ phoneNumber: p.phoneNumber,
951
+ e164: mapped
952
+ }
953
+ };
954
+ }) ?? []);
955
+ const participants = participantEntries.map((entry) => entry.display).filter(Boolean);
956
+ const mentionParticipants = participantEntries.map((entry) => entry.mention);
957
+ return {
958
+ subject: meta.subject,
959
+ participants,
960
+ mentionParticipants,
961
+ expires: resolveGroupMetadataExpiresAt() ?? 0
962
+ };
963
+ };
964
+ const summarizeGroupMetaForReconnectCache = (meta) => ({
965
+ subject: meta.subject,
966
+ expires: resolveGroupMetadataExpiresAt() ?? NaN
967
+ });
968
+ const getGroupMeta = async (jid) => {
969
+ const cached = readGroupMetadataCacheEntry(groupMetaCache, jid);
970
+ if (cached) return cached;
971
+ try {
972
+ const meta = await (getCurrentSock() ?? sock).groupMetadata(jid);
973
+ rememberWhatsAppBaileysCacheEntry(options.baileysGroupMetaCache, jid, meta, GROUP_META_TTL_MS);
974
+ publishedGroupMetadataJids.add(jid);
975
+ const entry = await summarizeGroupMeta(meta);
976
+ rememberGroupMetadataCacheEntry(groupMetadataCache, jid, {
977
+ subject: entry.subject,
978
+ expires: entry.expires
979
+ });
980
+ rememberGroupMetadataCacheEntry(groupMetaCache, jid, entry);
981
+ return entry;
982
+ } catch (err) {
983
+ const hydrated = readGroupMetadataCacheEntry(groupMetadataCache, jid);
984
+ if (hydrated) {
985
+ rememberGroupMetadataCacheEntry(groupMetaCache, jid, hydrated);
986
+ logWhatsAppVerbose(options.verbose, `Using cached group metadata for ${jid} after fetch failure: ${String(err)}`);
987
+ return hydrated;
988
+ }
989
+ logWhatsAppVerbose(options.verbose, `Failed to fetch group metadata for ${jid}: ${String(err)}`);
990
+ return { expires: resolveGroupMetadataExpiresAt() ?? 0 };
991
+ }
992
+ };
993
+ const resolveOutboundMentionsForGroup = async (jid, text) => {
994
+ if (!isGroupJid(jid) || !mayContainWhatsAppOutboundMention(text)) return {
995
+ text,
996
+ mentionedJids: []
997
+ };
998
+ const meta = await getGroupMeta(jid);
999
+ return resolveWhatsAppOutboundMentions({
1000
+ chatJid: jid,
1001
+ text,
1002
+ participants: meta.mentionParticipants
1003
+ });
1004
+ };
1005
+ const applyOutboundMentionsToContent = async (jid, content) => {
1006
+ if ("text" in content && typeof content.text === "string") {
1007
+ const resolved = await resolveOutboundMentionsForGroup(jid, content.text);
1008
+ return addWhatsAppOutboundMentionsToContent({
1009
+ ...content,
1010
+ text: resolved.text
1011
+ }, resolved.mentionedJids);
1012
+ }
1013
+ const caption = content.caption;
1014
+ if (typeof caption === "string") {
1015
+ const resolved = await resolveOutboundMentionsForGroup(jid, caption);
1016
+ return addWhatsAppOutboundMentionsToContent({
1017
+ ...content,
1018
+ caption: resolved.text
1019
+ }, resolved.mentionedJids);
1020
+ }
1021
+ return content;
1022
+ };
1023
+ const normalizeInboundMessage = async (msg) => {
1048
1024
  const id = msg.key?.id ?? void 0;
1049
1025
  const remoteJid = msg.key?.remoteJid;
1050
- if (!remoteJid || remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) return null;
1051
- const group = isJidGroup(remoteJid) === true;
1052
- if (shouldSkipRecentOutboundEcho(msg)) return null;
1026
+ if (!remoteJid) return null;
1027
+ if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) return null;
1028
+ const group = isGroupJid(remoteJid);
1029
+ if (Boolean(msg.key?.fromMe) && id && isRecentOutboundMessage({
1030
+ accountId: options.accountId,
1031
+ remoteJid,
1032
+ messageId: id
1033
+ })) {
1034
+ logWhatsAppVerbose(options.verbose, `Skipping recent outbound WhatsApp echo ${id} for ${remoteJid}`);
1035
+ return null;
1036
+ }
1053
1037
  if (!hasInboundUserContent(msg.message ?? void 0)) return null;
1054
1038
  const participantJid = msg.key?.participant ?? void 0;
1055
- const from = group ? remoteJid : await socketSession.resolveInboundJid(remoteJid);
1039
+ const from = group ? remoteJid : await resolveInboundJid(remoteJid);
1056
1040
  if (!from) return null;
1057
- const senderE164 = group ? participantJid ? await socketSession.resolveInboundJid(participantJid) : null : from;
1041
+ const senderE164 = group ? participantJid ? await resolveInboundJid(participantJid) : null : from;
1058
1042
  let groupSubject;
1059
1043
  let groupParticipants;
1060
1044
  if (group) {
1061
- const meta = await groupMetadata.get(remoteJid);
1045
+ const meta = await getGroupMeta(remoteJid);
1062
1046
  groupSubject = meta.subject;
1063
1047
  groupParticipants = meta.participants;
1064
1048
  }
1065
- const messageTimestampSeconds = options.parseTimestampSeconds(msg.messageTimestamp);
1049
+ const messageTimestampSeconds = parseWhatsAppTimestampSeconds(msg.messageTimestamp);
1066
1050
  const messageTimestampMs = messageTimestampSeconds !== void 0 ? messageTimestampSeconds * 1e3 : void 0;
1051
+ const accessCfg = options.loadConfig?.() ?? options.cfg;
1067
1052
  const access = await checkInboundAccessControl({
1068
- cfg: options.loadConfig?.() ?? options.cfg,
1053
+ cfg: accessCfg,
1069
1054
  accountId: options.accountId,
1070
1055
  from,
1071
- selfE164: socketSession.self.e164 ?? null,
1056
+ selfE164: self.e164 ?? null,
1072
1057
  senderE164,
1073
1058
  senderJid: participantJid,
1074
1059
  group,
1075
1060
  pushName: msg.pushName ?? void 0,
1076
1061
  isFromMe: Boolean(msg.key?.fromMe),
1077
1062
  messageTimestampMs,
1078
- connectedAtMs: socketSession.connectedAtMs,
1063
+ connectedAtMs,
1079
1064
  verbose: options.verbose,
1080
- sock: { sendMessage: (jid, content) => socketSession.sendTrackedMessage(jid, content) },
1065
+ sock: { sendMessage: (jid, content) => sendTrackedMessage(jid, content) },
1081
1066
  remoteJid
1082
1067
  });
1083
1068
  if (!access.allowed) return null;
@@ -1094,58 +1079,6 @@ function createWhatsAppInboundMessageNormalizer(options) {
1094
1079
  access
1095
1080
  };
1096
1081
  };
1097
- return {
1098
- normalize,
1099
- shouldSkipRecentOutboundEcho
1100
- };
1101
- }
1102
- //#endregion
1103
- //#region extensions/whatsapp/src/inbound/message-delivery.ts
1104
- const INBOUND_CLOSE_DRAIN_TIMEOUT_MS = 5e3;
1105
- const WHATSAPP_INGRESS_DRAIN_INTERVAL_MS = 1e3;
1106
- function parseWhatsAppTimestampSeconds(value) {
1107
- if (value == null) return;
1108
- if (typeof value === "string") return parseStrictFiniteNumber(value);
1109
- const parsed = Number(value);
1110
- return Number.isFinite(parsed) ? parsed : void 0;
1111
- }
1112
- function logWhatsAppVerbose$1(enabled, message) {
1113
- if (!enabled) return;
1114
- defaultRuntime.log(message);
1115
- }
1116
- function recordAcceptedInboundActivity(accountId) {
1117
- recordChannelActivity({
1118
- channel: "whatsapp",
1119
- accountId,
1120
- direction: "inbound"
1121
- });
1122
- }
1123
- function createWhatsAppMessageDeliveryCoordinator(options) {
1124
- const inboundLogger = getChildLogger({ module: "web-inbound" });
1125
- const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
1126
- const sock = options.sock;
1127
- const socketSession = options.socketSession;
1128
- const groupMetadata = options.groupMetadata;
1129
- const { connectedAtMs, self, getCurrentSock, resolveInboundJid, resolveReactionTargetJids, rememberBaileysMessage, assertCanSendToJid, sendTrackedMessage, socketOperations } = socketSession;
1130
- const durableInboundQueue = options.durableInboundQueue ?? createWhatsAppDurableInboundQueue(options.accountId);
1131
- const pendingMessageHandlers = /* @__PURE__ */ new Set();
1132
- let durableIngressActive = false;
1133
- let nextReceiveOrder = 0;
1134
- const publishPendingWorkState = (at = Date.now()) => {
1135
- options.onPendingWorkChanged?.(pendingMessageHandlers.size + messageDebouncer.pendingWorkCount() + (durableIngressActive ? 1 : 0), at);
1136
- };
1137
- const messageNormalizer = createWhatsAppInboundMessageNormalizer({
1138
- cfg: options.cfg,
1139
- loadConfig: options.loadConfig,
1140
- accountId: options.accountId,
1141
- verbose: options.verbose,
1142
- socketSession,
1143
- groupMetadata,
1144
- parseTimestampSeconds: parseWhatsAppTimestampSeconds,
1145
- logVerbose: (message) => logWhatsAppVerbose$1(options.verbose, message)
1146
- });
1147
- const normalizeInboundMessage = messageNormalizer.normalize;
1148
- const shouldSkipRecentOutboundEcho = messageNormalizer.shouldSkipRecentOutboundEcho;
1149
1082
  const buildReadReceiptTarget = (inbound) => inbound.id ? {
1150
1083
  remoteJid: inbound.remoteJid,
1151
1084
  id: inbound.id,
@@ -1155,15 +1088,20 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1155
1088
  if (!target || options.sendReadReceipts === false) return;
1156
1089
  const { id, remoteJid, participant } = target;
1157
1090
  try {
1158
- await socketSession.markRead(target);
1091
+ await withWhatsAppSocketOperationTimeout("readMessages", (getCurrentSock() ?? sock).readMessages([{
1092
+ remoteJid,
1093
+ id,
1094
+ participant,
1095
+ fromMe: false
1096
+ }]), sendOperationTimeoutMs);
1159
1097
  const suffix = participant ? ` (participant ${participant})` : "";
1160
- logWhatsAppVerbose$1(options.verbose, `Marked message ${id} as read for ${remoteJid}${suffix}`);
1098
+ logWhatsAppVerbose(options.verbose, `Marked message ${id} as read for ${remoteJid}${suffix}`);
1161
1099
  } catch (err) {
1162
- logWhatsAppVerbose$1(options.verbose, `Failed to mark message ${id} read: ${String(err)}`);
1100
+ logWhatsAppVerbose(options.verbose, `Failed to mark message ${id} read: ${String(err)}`);
1163
1101
  }
1164
1102
  };
1165
1103
  const maybeLogSkippedSelfChatReadReceipt = (inbound, target) => {
1166
- if (target?.id && inbound.access.isSelfChat && options.verbose) logWhatsAppVerbose$1(options.verbose, `Self-chat mode: skipping read receipt for ${target.id}`);
1104
+ if (target?.id && inbound.access.isSelfChat && options.verbose) logWhatsAppVerbose(options.verbose, `Self-chat mode: skipping read receipt for ${target.id}`);
1167
1105
  };
1168
1106
  const maybeMarkNonSelfChatReadReceipt = async (inbound, target) => {
1169
1107
  if (inbound.access.isSelfChat) {
@@ -1172,16 +1110,14 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1172
1110
  }
1173
1111
  await maybeMarkInboundAsRead(target);
1174
1112
  };
1175
- const messageDebouncer = createWhatsAppInboundMessageDebouncer({
1176
- debounceMs: options.debounceMs,
1177
- onMessage: options.onMessage,
1178
- shouldDebounce: options.shouldDebounce,
1179
- markRead: maybeMarkInboundAsRead,
1180
- onPendingWorkChanged: publishPendingWorkState,
1181
- onError: (error) => {
1182
- inboundLogger.error({ error: String(error) }, "failed handling inbound web message");
1183
- inboundConsoleLog.error(`Failed handling inbound web message: ${String(error)}`);
1184
- }
1113
+ const completeUndeliverableDurableInbound = async (durableId, metadata) => {
1114
+ if (!durableId) return;
1115
+ await durableInboundJournal.complete(durableId, metadata?.readReceipt ? { metadata: { readReceipt: metadata.readReceipt } } : void 0);
1116
+ };
1117
+ const buildDurableInboundPayload = (msg, upsertType) => ({
1118
+ message: serializeWhatsAppDurableInboundMessage(msg),
1119
+ ...upsertType ? { upsertType } : {},
1120
+ receivedAt: Date.now()
1185
1121
  });
1186
1122
  const shouldSkipStaleAppend = (msg, upsertType) => {
1187
1123
  if (upsertType !== "append") return false;
@@ -1191,7 +1127,124 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1191
1127
  const nowMs = Date.now();
1192
1128
  return msgTsMs < (options.appendReplyWindow && nowMs <= options.appendReplyWindow.untilMs ? Math.max(options.appendReplyWindow.afterMs, nowMs - options.appendReplyWindow.maxAgeMs) : connectedAtMs - APPEND_RECENT_GRACE_MS);
1193
1129
  };
1194
- const preparedInboundByDurableId = /* @__PURE__ */ new Map();
1130
+ const processDurableInboundMessage = async (msg, upsertType, receiveOrder, stored) => {
1131
+ const inbound = await normalizeInboundMessage(msg);
1132
+ if (!inbound) {
1133
+ if (stored) await completeUndeliverableDurableInbound(stored.id, stored.metadata);
1134
+ return;
1135
+ }
1136
+ const readReceipt = stored?.metadata?.readReceipt ?? buildReadReceiptTarget(inbound);
1137
+ const deliveryReadReceipt = inbound.access.isSelfChat ? void 0 : readReceipt;
1138
+ if (!stored && shouldSkipStaleAppend(msg, upsertType)) {
1139
+ await maybeMarkNonSelfChatReadReceipt(inbound, readReceipt);
1140
+ return;
1141
+ }
1142
+ let durableId = stored?.id ?? (inbound.id ? createWhatsAppDurableInboundMessageId({
1143
+ remoteJid: inbound.remoteJid,
1144
+ id: inbound.id
1145
+ }) : void 0);
1146
+ const durableMetadata = deliveryReadReceipt ? { readReceipt: deliveryReadReceipt } : void 0;
1147
+ if (durableId && !stored) try {
1148
+ const accepted = await durableInboundJournal.accept(durableId, buildDurableInboundPayload(msg, upsertType), {
1149
+ metadata: durableMetadata,
1150
+ receivedAt: inbound.messageTimestampMs
1151
+ });
1152
+ if (accepted.kind === "completed") {
1153
+ await maybeMarkNonSelfChatReadReceipt(inbound, accepted.record.metadata?.readReceipt ?? deliveryReadReceipt);
1154
+ return;
1155
+ }
1156
+ if (accepted.kind === "pending" && accepted.record.attempts === 0) return;
1157
+ } catch (err) {
1158
+ durableId = void 0;
1159
+ const error = formatError(err);
1160
+ inboundLogger.warn({ error }, "failed persisting durable WhatsApp inbound; delivering live");
1161
+ inboundConsoleLog.warn(`Failed persisting durable WhatsApp inbound; delivering live: ${error}`);
1162
+ }
1163
+ const enriched = await enrichInboundMessage(msg);
1164
+ if (!enriched) {
1165
+ await completeUndeliverableDurableInbound(durableId, durableMetadata);
1166
+ await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
1167
+ return;
1168
+ }
1169
+ const dedupeKey = inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : "";
1170
+ const dedupeClaim = dedupeKey ? await claimRecentInboundMessageDelivery(dedupeKey) : "claimed";
1171
+ if (dedupeClaim !== "claimed") {
1172
+ if (dedupeClaim === "duplicate") {
1173
+ await completeUndeliverableDurableInbound(durableId, durableMetadata);
1174
+ await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
1175
+ }
1176
+ return;
1177
+ }
1178
+ recordAcceptedInboundActivity(options.accountId);
1179
+ await enqueueInboundMessage(msg, inbound, enriched, {
1180
+ durableId,
1181
+ readReceipt: deliveryReadReceipt,
1182
+ receiveOrder
1183
+ });
1184
+ };
1185
+ const replayPendingDurableInboundMessages = async () => {
1186
+ const pending = await durableInboundJournal.pending();
1187
+ for (const record of pending) await processDurableInboundMessage(deserializeWhatsAppDurableInboundMessage(record.payload.message), record.payload.upsertType, record.payload.receivedAt, {
1188
+ id: record.id,
1189
+ payload: record.payload,
1190
+ metadata: record.metadata
1191
+ });
1192
+ };
1193
+ const enrichInboundMessage = async (msg) => {
1194
+ const location = extractLocationData(msg.message ?? void 0);
1195
+ const locationText = location ? formatLocationText(location) : void 0;
1196
+ const contactContext = extractContactContext(msg.message ?? void 0);
1197
+ const externalAdReplyContext = extractExternalAdReplyContext(msg.message ?? void 0);
1198
+ const mediaPlaceholder = extractMediaPlaceholder(msg.message ?? void 0);
1199
+ let body = extractText(msg.message ?? void 0);
1200
+ if (locationText) body = [body, locationText].filter(Boolean).join("\n").trim();
1201
+ if (!body) {
1202
+ body = mediaPlaceholder;
1203
+ if (!body) return null;
1204
+ }
1205
+ const commandBody = body;
1206
+ const replyContext = describeReplyContext(msg.message);
1207
+ let mediaPath;
1208
+ let mediaType;
1209
+ let mediaFileName;
1210
+ const maxBytes = (typeof options.mediaMaxMb === "number" && options.mediaMaxMb > 0 ? options.mediaMaxMb : 50) * 1024 * 1024;
1211
+ const saveInboundMedia = async (inboundMedia) => {
1212
+ if (!inboundMedia) return;
1213
+ mediaPath = inboundMedia.saved.path;
1214
+ mediaType = inboundMedia.mimetype;
1215
+ mediaFileName = inboundMedia.fileName;
1216
+ };
1217
+ try {
1218
+ await saveInboundMedia(await downloadInboundMedia(msg, sock, maxBytes));
1219
+ } catch (err) {
1220
+ logWhatsAppVerbose(options.verbose, `Inbound media download failed: ${String(err)}`);
1221
+ body = formatInboundMediaUnavailableText({
1222
+ body,
1223
+ mediaPlaceholder,
1224
+ notice: "[whatsapp attachment unavailable]"
1225
+ });
1226
+ }
1227
+ if (!mediaPath && replyContext) try {
1228
+ await saveInboundMedia(await downloadQuotedInboundMedia(msg, sock, maxBytes));
1229
+ } catch (err) {
1230
+ logWhatsAppVerbose(options.verbose, `Quoted media download failed: ${String(err)}`);
1231
+ body = formatInboundMediaUnavailableText({
1232
+ body,
1233
+ notice: "[whatsapp quoted attachment unavailable]"
1234
+ });
1235
+ }
1236
+ return {
1237
+ body,
1238
+ commandBody,
1239
+ location: location ?? void 0,
1240
+ contactContext,
1241
+ externalAdReplyContext,
1242
+ replyContext,
1243
+ mediaPath,
1244
+ mediaType,
1245
+ mediaFileName
1246
+ };
1247
+ };
1195
1248
  const enqueueInboundMessage = async (msg, inbound, enriched, durable) => {
1196
1249
  const chatJid = inbound.remoteJid;
1197
1250
  const sendComposing = async () => {
@@ -1199,18 +1252,20 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1199
1252
  if (!currentSock) return;
1200
1253
  try {
1201
1254
  await assertCanSendToJid(chatJid, currentSock);
1202
- await socketOperations.sendPresenceUpdate("composing", chatJid);
1255
+ await sendApiSocketOperations.sendPresenceUpdate("composing", chatJid);
1203
1256
  } catch (err) {
1204
- logWhatsAppVerbose$1(options.verbose, `Presence update failed: ${String(err)}`);
1257
+ logWhatsAppVerbose(options.verbose, `Presence update failed: ${String(err)}`);
1205
1258
  }
1206
1259
  };
1207
1260
  const reply = async (text, optionsResult) => {
1208
- const resolved = await groupMetadata.resolveOutboundMentions(chatJid, text);
1209
- return normalizeWhatsAppSendResult(await sendTrackedMessage(chatJid, addWhatsAppOutboundMentionsToContent({ text: resolved.text }, resolved.mentionedJids), optionsResult), "text");
1261
+ const resolved = await resolveOutboundMentionsForGroup(chatJid, text);
1262
+ const result = await sendTrackedMessage(chatJid, addWhatsAppOutboundMentionsToContent({ text: resolved.text }, resolved.mentionedJids), optionsResult);
1263
+ return normalizeWhatsAppSendResult(result, "text");
1210
1264
  };
1211
1265
  const sendMedia = async (payload, optionsValue) => {
1212
1266
  const previewPayload = await addWhatsAppImagePreviewFields(payload);
1213
- return normalizeWhatsAppSendResult(await sendTrackedMessage(chatJid, await groupMetadata.applyOutboundMentions(chatJid, previewPayload), optionsValue), "media");
1267
+ const result = await sendTrackedMessage(chatJid, await applyOutboundMentionsToContent(chatJid, previewPayload), optionsValue);
1268
+ return normalizeWhatsAppSendResult(result, "media");
1214
1269
  };
1215
1270
  const timestamp = inbound.messageTimestampMs;
1216
1271
  const mentionedJids = extractMentionedJids(msg.message);
@@ -1224,11 +1279,10 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1224
1279
  mediaFileName: enriched.mediaFileName,
1225
1280
  timestamp
1226
1281
  }, "inbound message");
1227
- const media = enriched.mediaPath || enriched.mediaType || enriched.mediaFileName || enriched.mediaKind ? {
1282
+ const media = enriched.mediaPath || enriched.mediaType || enriched.mediaFileName ? {
1228
1283
  path: enriched.mediaPath,
1229
1284
  type: enriched.mediaType,
1230
- fileName: enriched.mediaFileName,
1231
- kind: enriched.mediaKind
1285
+ fileName: enriched.mediaFileName
1232
1286
  } : void 0;
1233
1287
  const groupMentions = mentionedJids ? { jids: mentionedJids } : void 0;
1234
1288
  const group = inbound.group && (inbound.groupSubject || inbound.groupParticipants?.length || groupMentions) ? {
@@ -1236,26 +1290,17 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1236
1290
  participants: inbound.groupParticipants,
1237
1291
  mentions: groupMentions
1238
1292
  } : void 0;
1239
- const channelStructuredContext = [
1240
- ...enriched.nativeMedia ? [{
1241
- label: "WhatsApp media",
1242
- source: "whatsapp",
1243
- type: "media",
1244
- payload: enriched.nativeMedia
1245
- }] : [],
1246
- ...enriched.contactContext ? [{
1247
- label: "WhatsApp contact",
1248
- source: "whatsapp",
1249
- type: enriched.contactContext.kind,
1250
- payload: enriched.contactContext
1251
- }] : [],
1252
- ...enriched.externalAdReplyContext ? [{
1253
- label: "WhatsApp external ad reply",
1254
- source: "whatsapp",
1255
- type: "external_ad_reply",
1256
- payload: enriched.externalAdReplyContext
1257
- }] : []
1258
- ];
1293
+ const untrustedStructuredContext = [...enriched.contactContext ? [{
1294
+ label: "WhatsApp contact",
1295
+ source: "whatsapp",
1296
+ type: enriched.contactContext.kind,
1297
+ payload: enriched.contactContext
1298
+ }] : [], ...enriched.externalAdReplyContext ? [{
1299
+ label: "WhatsApp external ad reply",
1300
+ source: "whatsapp",
1301
+ type: "external_ad_reply",
1302
+ payload: enriched.externalAdReplyContext
1303
+ }] : []];
1259
1304
  const inboundMessage = withDeprecatedWebInboundMessageFlatAliases({
1260
1305
  admission: inbound.access.admission,
1261
1306
  event: {
@@ -1266,7 +1311,7 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1266
1311
  body: enriched.body,
1267
1312
  commandBody: enriched.commandBody,
1268
1313
  location: enriched.location ?? void 0,
1269
- channelStructuredContext: channelStructuredContext.length > 0 ? channelStructuredContext : void 0,
1314
+ untrustedStructuredContext: untrustedStructuredContext.length > 0 ? untrustedStructuredContext : void 0,
1270
1315
  media
1271
1316
  },
1272
1317
  platform: {
@@ -1294,7 +1339,6 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1294
1339
  context: enriched.replyContext,
1295
1340
  id: enriched.replyContext.id,
1296
1341
  body: enriched.replyContext.body,
1297
- media: enriched.replyContext.media,
1298
1342
  sender: {
1299
1343
  displayName: enriched.replyContext.sender?.label ?? void 0,
1300
1344
  jid: enriched.replyContext.sender?.jid ?? void 0,
@@ -1302,78 +1346,38 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1302
1346
  }
1303
1347
  } : void 0,
1304
1348
  group,
1305
- turnAdoptionLifecycle: durable.turnAdoptionLifecycle,
1349
+ dedupeKey: inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : void 0,
1350
+ durableId: durable.durableId,
1306
1351
  readReceipt: durable.readReceipt,
1307
1352
  receiveOrder: durable.receiveOrder
1308
1353
  });
1354
+ const debounceKey = buildInboundDebounceKey(inboundMessage);
1355
+ if (debounceKey) {
1356
+ inboundMessage.debounceKey = debounceKey;
1357
+ if (inboundDebounceMs > 0 && shouldDebounceInboundMessage(inboundMessage)) {
1358
+ pendingDebounceKeys.add(debounceKey);
1359
+ publishPendingWorkState();
1360
+ }
1361
+ }
1309
1362
  if (inboundMessage.event.id) {
1310
1363
  const admission = requireWhatsAppInboundAdmission(inboundMessage);
1311
1364
  cacheInboundMessageMeta(admission.accountId, inboundMessage.platform.chatJid, inboundMessage.event.id, {
1312
1365
  participant: inboundMessage.platform.senderJid,
1313
1366
  participantE164: admission.conversation.kind === "direct" ? inboundMessage.platform.senderE164 : void 0,
1314
1367
  body: inboundMessage.payload.body,
1315
- media: enriched.nativeMedia,
1316
1368
  fromMe: inboundMessage.platform.fromMe
1317
1369
  });
1318
1370
  }
1319
- await messageDebouncer.enqueue(inboundMessage);
1320
- };
1321
- const processDurableInboundMessage = async (admission, lifecycle) => {
1322
- const { message: msg, ...context } = admission;
1323
- rememberBaileysMessage(msg.key?.remoteJid, msg.key?.id, msg.message);
1324
- const remoteJid = msg.key?.remoteJid;
1325
- const id = msg.key?.id;
1326
- const durableId = remoteJid && id ? createHash("sha256").update(`${remoteJid}\n${id}`).digest("hex") : void 0;
1327
- const preparation = durableId ? preparedInboundByDurableId.get(durableId) : void 0;
1328
- if (durableId) preparedInboundByDurableId.delete(durableId);
1329
- if (context.skipRecentOutboundEcho === true) return "completed";
1330
- const prepared = await preparation;
1331
- if (prepared === null) return "completed";
1332
- const inbound = prepared ?? await normalizeInboundMessage(msg);
1333
- if (!inbound) return "completed";
1334
- if (await maybeResolveWhatsAppQuestionReaction({
1335
- cfg: options.loadConfig?.() ?? options.cfg,
1336
- accountId: options.accountId,
1337
- msg,
1338
- senderId: inbound.senderE164 ?? inbound.from,
1339
- resolveReactionTargetJids,
1340
- logDebug: (message) => logWhatsAppVerbose$1(options.verbose, message)
1341
- })) return "completed";
1342
- const readReceipt = buildReadReceiptTarget(inbound);
1343
- const deliveryReadReceipt = inbound.access.isSelfChat ? void 0 : readReceipt;
1344
- if (context.skipStaleAppend === true) {
1345
- await maybeMarkNonSelfChatReadReceipt(inbound, readReceipt);
1346
- return "completed";
1347
- }
1348
- const enriched = await enrichWhatsAppInboundMessage({
1349
- msg,
1350
- sock,
1351
- mediaMaxMb: options.mediaMaxMb,
1352
- logVerbose: (message) => logWhatsAppVerbose$1(options.verbose, message)
1353
- });
1354
- if (!enriched) {
1355
- await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
1356
- return "completed";
1371
+ try {
1372
+ Promise.resolve(debouncer.enqueue(inboundMessage)).catch((err) => {
1373
+ inboundLogger.error({ error: String(err) }, "failed handling inbound web message");
1374
+ inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
1375
+ });
1376
+ } catch (err) {
1377
+ inboundLogger.error({ error: String(err) }, "failed handling inbound web message");
1378
+ inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
1357
1379
  }
1358
- recordAcceptedInboundActivity(options.accountId);
1359
- await enqueueInboundMessage(msg, inbound, enriched, {
1360
- readReceipt: deliveryReadReceipt,
1361
- receiveOrder: context.receiveOrder ?? context.receivedAt,
1362
- turnAdoptionLifecycle: lifecycle
1363
- });
1364
- return "deferred";
1365
1380
  };
1366
- const durableInboundMonitor = createWhatsAppIngressMonitor({
1367
- queue: durableInboundQueue,
1368
- dispatch: async (admission, lifecycle) => ({ kind: await processDurableInboundMessage(admission, lifecycle) }),
1369
- pollIntervalMs: WHATSAPP_INGRESS_DRAIN_INTERVAL_MS,
1370
- onLog: (message) => inboundLogger.warn({ message }, "whatsapp ingress drain"),
1371
- onError: (error) => inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed"),
1372
- onActivityChange: (active) => {
1373
- durableIngressActive = active;
1374
- publishPendingWorkState();
1375
- }
1376
- });
1377
1381
  const handleMessagesUpsert = async (upsert) => {
1378
1382
  if (upsert.type !== "notify" && upsert.type !== "append") return;
1379
1383
  for (const msg of upsert.messages ?? []) {
@@ -1387,57 +1391,9 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1387
1391
  selfLid: self.lid,
1388
1392
  resolveInboundJid,
1389
1393
  resolveReactionTargetJids,
1390
- logVerboseMessage: (message) => logWhatsAppVerbose$1(options.verbose, message)
1394
+ logVerboseMessage: (message) => logWhatsAppVerbose(options.verbose, message)
1391
1395
  })) continue;
1392
- const receivedAt = Date.now();
1393
- const skipStaleAppend = shouldSkipStaleAppend(msg, upsert.type);
1394
- const skipRecentOutboundEcho = shouldSkipRecentOutboundEcho(msg);
1395
- const remoteJid = msg.key?.remoteJid;
1396
- const id = msg.key?.id;
1397
- const durableId = remoteJid && id ? createHash("sha256").update(`${remoteJid}\n${id}`).digest("hex") : void 0;
1398
- let resolvePrepared;
1399
- if (durableId && !preparedInboundByDurableId.has(durableId)) {
1400
- if (preparedInboundByDurableId.size >= 1e3) {
1401
- const oldest = preparedInboundByDurableId.keys().next().value;
1402
- if (oldest !== void 0) preparedInboundByDurableId.delete(oldest);
1403
- }
1404
- preparedInboundByDurableId.set(durableId, new Promise((resolve) => {
1405
- resolvePrepared = resolve;
1406
- }));
1407
- }
1408
- const finishPreparation = (inbound, keepForDrain = false) => {
1409
- resolvePrepared?.(inbound);
1410
- if (!keepForDrain && durableId && resolvePrepared) preparedInboundByDurableId.delete(durableId);
1411
- };
1412
- let result;
1413
- try {
1414
- result = await durableInboundMonitor.admit({
1415
- message: msg,
1416
- upsertType: upsert.type,
1417
- skipStaleAppend,
1418
- skipRecentOutboundEcho,
1419
- receivedAt,
1420
- receiveOrder
1421
- }, { receivedAt });
1422
- } catch (error) {
1423
- finishPreparation(void 0);
1424
- const formattedError = formatError(error);
1425
- inboundLogger.error({ error: formattedError }, "failed persisting durable WhatsApp inbound after retries; message dropped");
1426
- inboundConsoleLog.error(`Failed persisting durable WhatsApp inbound after retries; message dropped: ${formattedError}`);
1427
- continue;
1428
- }
1429
- if (result.kind === "durable" && result.queueResult.kind === "completed") {
1430
- finishPreparation(void 0);
1431
- const inbound = await normalizeInboundMessage(msg);
1432
- if (inbound) await maybeMarkNonSelfChatReadReceipt(inbound, buildReadReceiptTarget(inbound));
1433
- } else if (result.kind === "durable" && result.queueResult.kind === "accepted") if (skipRecentOutboundEcho) finishPreparation(null);
1434
- else try {
1435
- finishPreparation(await normalizeInboundMessage(msg), true);
1436
- } catch (error) {
1437
- finishPreparation(void 0);
1438
- inboundLogger.warn({ error: formatError(error) }, "failed preparing WhatsApp inbound identity; durable drain will normalize again");
1439
- }
1440
- else finishPreparation(void 0);
1396
+ await processDurableInboundMessage(msg, upsert.type, receiveOrder);
1441
1397
  }
1442
1398
  };
1443
1399
  const handleMessagesUpsertEvent = (upsert) => {
@@ -1452,24 +1408,22 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1452
1408
  publishPendingWorkState();
1453
1409
  });
1454
1410
  };
1411
+ const waitForPendingMessageHandlers = async () => {
1412
+ while (pendingMessageHandlers.size > 0) await Promise.all(Array.from(pendingMessageHandlers));
1413
+ };
1455
1414
  const drainDebouncedInboundMessages = async () => {
1456
- await messageDebouncer.drain();
1415
+ while (pendingDebounceKeys.size > 0 || activeInboundFlushes.size > 0) {
1416
+ const debounceKeys = Array.from(pendingDebounceKeys);
1417
+ if (debounceKeys.length > 0) await Promise.all(debounceKeys.map((key) => debouncer.flushKey(key)));
1418
+ const flushes = Array.from(activeInboundFlushes);
1419
+ if (flushes.length > 0) await Promise.allSettled(flushes);
1420
+ await Promise.resolve();
1421
+ }
1457
1422
  };
1458
1423
  const drainInboundBeforeSocketClose = async () => {
1459
- for (;;) {
1460
- await drainDebouncedInboundMessages();
1461
- if (pendingMessageHandlers.size === 0) break;
1462
- const handlers = Array.from(pendingMessageHandlers);
1463
- await Promise.race([Promise.allSettled(handlers), messageDebouncer.waitForWorkOrIdle(handlers, pendingMessageHandlers.size === 0)]);
1464
- if (pendingMessageHandlers.size === 0 && !messageDebouncer.hasPendingWork()) break;
1465
- }
1424
+ groupMetadataCacheClosed = true;
1425
+ await waitForPendingMessageHandlers();
1466
1426
  await drainDebouncedInboundMessages();
1467
- for (;;) {
1468
- await durableInboundMonitor.waitForIdle();
1469
- if (!messageDebouncer.hasPendingWork()) break;
1470
- await drainDebouncedInboundMessages();
1471
- }
1472
- await durableInboundMonitor.stop();
1473
1427
  };
1474
1428
  const drainInboundBeforeSocketCloseWithTimeout = async () => {
1475
1429
  let timeout = null;
@@ -1482,212 +1436,8 @@ function createWhatsAppMessageDeliveryCoordinator(options) {
1482
1436
  })]);
1483
1437
  } finally {
1484
1438
  if (timeout) clearTimeout(timeout);
1485
- durableInboundMonitor.stop();
1486
- }
1487
- };
1488
- let detachMessagesUpsert;
1489
- const start = () => {
1490
- if (detachMessagesUpsert) return;
1491
- detachMessagesUpsert = socketSession.listen("messages.upsert", handleMessagesUpsertEvent);
1492
- durableInboundMonitor.start();
1493
- };
1494
- const stopIntake = () => {
1495
- detachMessagesUpsert?.();
1496
- detachMessagesUpsert = void 0;
1497
- };
1498
- return {
1499
- start,
1500
- stopIntake,
1501
- drain: drainInboundBeforeSocketCloseWithTimeout
1502
- };
1503
- }
1504
- //#endregion
1505
- //#region extensions/whatsapp/src/inbound/socket-session.ts
1506
- const LOGGED_OUT_STATUS = DisconnectReason.loggedOut;
1507
- const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress";
1508
- const BAILEYS_MESSAGE_TTL_MS = 600 * 1e3;
1509
- function isDirectUserJid(jid) {
1510
- return /^(\d+)(?::\d+)?@(s\.whatsapp\.net|c\.us|lid|hosted|hosted\.lid)$/i.test(jid.trim());
1511
- }
1512
- function getActiveReachoutTimelock(state) {
1513
- if (state?.isActive !== true) return;
1514
- const endsAt = state.timeEnforcementEnds?.getTime();
1515
- return endsAt === void 0 || !Number.isFinite(endsAt) || endsAt > Date.now() ? state : void 0;
1516
- }
1517
- function formatReachoutTimelockError(state) {
1518
- const details = [state.enforcementType ? `type=${state.enforcementType}` : void 0, state.timeEnforcementEnds instanceof Date && Number.isFinite(state.timeEnforcementEnds.getTime()) ? `until=${state.timeEnforcementEnds.toISOString()}` : void 0].filter(Boolean);
1519
- return `WhatsApp reachout timelock is active; direct messages are temporarily blocked${details.length ? ` (${details.join(", ")})` : ""}`;
1520
- }
1521
- function isRetryableSendDisconnectError(error) {
1522
- if (isWhatsAppSocketOperationTimeoutError(error)) return false;
1523
- return /closed|reset|timed\s*out|disconnect|no active socket/i.test(formatError(error));
1524
- }
1525
- function shouldClearSocketRefAfterSendFailure(error) {
1526
- return /closed|reset|disconnect|no active socket/i.test(formatError(error));
1527
- }
1528
- async function createWhatsAppAttachedSocketSession(options) {
1529
- const { sock } = options;
1530
- const connectedAtMs = Date.now();
1531
- if (options.socketRef) options.socketRef.current = sock;
1532
- const shouldRetryDisconnect = () => options.shouldRetryDisconnect?.() === true;
1533
- const disconnectRetryPolicy = options.disconnectRetryPolicy ?? DEFAULT_RECONNECT_POLICY;
1534
- const sendRetryMaxAttempts = disconnectRetryPolicy.maxAttempts > 0 ? disconnectRetryPolicy.maxAttempts : DEFAULT_RECONNECT_POLICY.maxAttempts;
1535
- const sendOperationTimeoutMs = resolveWhatsAppSocketOperationTimeoutMs(options.socketTiming.defaultQueryTimeoutMs);
1536
- let onCloseResolve = null;
1537
- const onClose = new Promise((resolve) => {
1538
- onCloseResolve = resolve;
1539
- });
1540
- const resolveClose = (reason) => {
1541
- if (!onCloseResolve) return;
1542
- const resolver = onCloseResolve;
1543
- onCloseResolve = null;
1544
- resolver(reason);
1545
- };
1546
- const presence = options.selfChatMode ? "unavailable" : "available";
1547
- try {
1548
- await createWhatsAppSocketOperationTimeoutAdapter(sock, sendOperationTimeoutMs).sendPresenceUpdate(presence);
1549
- options.logVerbose(`Sent global '${presence}' presence on connect`);
1550
- } catch (error) {
1551
- options.logVerbose(`Failed to send '${presence}' presence on connect: ${String(error)}`);
1552
- }
1553
- const selfIdentity = await readWebSelfIdentityForDecision(options.authDir, sock.user);
1554
- if (selfIdentity.outcome === "unstable") throw new WhatsAppAuthUnstableError("WhatsApp auth state is still stabilizing; retrying inbox attach.");
1555
- const self = selfIdentity.identity;
1556
- const getCurrentSock = () => {
1557
- if (!options.socketRef) return sock;
1558
- if (options.socketRef.current) return options.socketRef.current;
1559
- if (!self.e164 && !self.jid && !self.lid) return null;
1560
- const successor = getWhatsAppConnectionController(options.accountId);
1561
- if (!successor) return null;
1562
- const successorIdentity = successor.getSelfIdentity();
1563
- if (!successorIdentity || !identitiesOverlap(self, successorIdentity)) return null;
1564
- return successor.getCurrentSock();
1565
- };
1566
- const lidLookup = sock.signalRepository?.lidMapping;
1567
- const resolveInboundJid = async (jid) => resolveJidToE164(jid, {
1568
- authDir: options.authDir,
1569
- lidLookup
1570
- });
1571
- const resolveReactionTargetJids = async (jid) => resolveEquivalentWhatsAppDirectChatJids(jid, {
1572
- authDir: options.authDir,
1573
- lidLookup
1574
- });
1575
- const rememberBaileysMessage = (remoteJid, messageId, message) => {
1576
- if (!options.recentMessageKeys || !remoteJid || !messageId || !message) return;
1577
- rememberWhatsAppBaileysCacheEntry(options.recentMessageKeys, `${remoteJid}:${messageId}`, message, BAILEYS_MESSAGE_TTL_MS);
1578
- };
1579
- const rememberOutboundMessage = (remoteJid, result) => {
1580
- const messageId = typeof result === "object" && result && "key" in result ? result.key?.id ?? "" : "";
1581
- if (!messageId) return;
1582
- rememberRecentOutboundMessage({
1583
- accountId: options.accountId,
1584
- remoteJid,
1585
- messageId
1586
- });
1587
- const message = typeof result === "object" && result && "message" in result ? result.message : void 0;
1588
- rememberBaileysMessage(remoteJid, messageId, message);
1589
- cacheInboundMessageMeta(options.accountId, remoteJid, messageId, {
1590
- fromMe: true,
1591
- body: extractText(message ?? void 0)
1592
- });
1593
- };
1594
- const trackLateAcceptedSend = (jid, promise) => {
1595
- promise.then((result) => {
1596
- rememberOutboundMessage(jid, result);
1597
- }, () => {});
1598
- };
1599
- let reachoutTimeLock;
1600
- let reachoutTimeLockFetch;
1601
- let reachoutTimeLockVersion = 0;
1602
- let verifiedSendReady;
1603
- const rememberReachoutTimeLock = (state) => {
1604
- reachoutTimeLock = state;
1605
- reachoutTimeLockVersion += 1;
1606
- verifiedSendReady = void 0;
1607
- };
1608
- const fetchReachoutTimeLock = async (currentSock) => {
1609
- if (typeof currentSock.fetchAccountReachoutTimelock !== "function") return;
1610
- if (!reachoutTimeLockFetch) reachoutTimeLockFetch = currentSock.fetchAccountReachoutTimelock().then((state) => {
1611
- rememberReachoutTimeLock(state);
1612
- return state;
1613
- }).catch((error) => {
1614
- options.logVerbose(`Failed fetching WhatsApp reachout timelock before send: ${formatError(error)}`);
1615
- }).finally(() => {
1616
- reachoutTimeLockFetch = void 0;
1617
- });
1618
- return await reachoutTimeLockFetch;
1619
- };
1620
- const rememberVerifiedSendReady = (jid, currentSock) => {
1621
- verifiedSendReady = {
1622
- jid,
1623
- sock: currentSock,
1624
- reachoutTimeLockVersion
1625
- };
1626
- };
1627
- const consumeVerifiedSendReady = (jid, currentSock) => {
1628
- if (verifiedSendReady?.jid !== jid || verifiedSendReady.sock !== currentSock || verifiedSendReady.reachoutTimeLockVersion !== reachoutTimeLockVersion) return false;
1629
- verifiedSendReady = void 0;
1630
- return true;
1631
- };
1632
- const assertCanSendToJid = async (jid, currentSock, readinessOptions) => {
1633
- if (!isDirectUserJid(jid)) return;
1634
- if (readinessOptions?.useVerifiedReady && consumeVerifiedSendReady(jid, currentSock)) return;
1635
- const state = getActiveReachoutTimelock(reachoutTimeLock) ?? await fetchReachoutTimeLock(currentSock);
1636
- const activeState = getActiveReachoutTimelock(state);
1637
- if (activeState) throw new Error(formatReachoutTimelockError(activeState));
1638
- if (readinessOptions?.rememberReady && state) rememberVerifiedSendReady(jid, currentSock);
1639
- };
1640
- const assertSendReady = async (to) => {
1641
- const currentSock = getCurrentSock();
1642
- if (!currentSock) throw new Error(RECONNECT_IN_PROGRESS_ERROR);
1643
- const jid = options.authDir ? toWhatsappJidWithLid(to, { authDir: options.authDir }) : toWhatsappJid(to);
1644
- await assertCanSendToJid(jid, currentSock, { rememberReady: true });
1645
- };
1646
- const sendTrackedMessage = async (jid, content, sendOptions) => {
1647
- let lastError = /* @__PURE__ */ new Error(RECONNECT_IN_PROGRESS_ERROR);
1648
- for (let attempt = 1;; attempt += 1) {
1649
- const currentSock = getCurrentSock();
1650
- if (currentSock) try {
1651
- await assertCanSendToJid(jid, currentSock, { useVerifiedReady: true });
1652
- const result = await createWhatsAppSocketOperationTimeoutAdapter(currentSock, sendOperationTimeoutMs, { onSendMessageTimeout: ({ jid: timedOutJid, promise }) => {
1653
- trackLateAcceptedSend(timedOutJid, promise);
1654
- } }).sendMessage(jid, content, sendOptions);
1655
- rememberOutboundMessage(jid, result);
1656
- return result;
1657
- } catch (error) {
1658
- if (!shouldRetryDisconnect() || !isRetryableSendDisconnectError(error)) throw error;
1659
- lastError = error;
1660
- if (shouldClearSocketRefAfterSendFailure(error) && options.socketRef?.current === currentSock) options.socketRef.current = null;
1661
- }
1662
- else if (!shouldRetryDisconnect()) throw lastError;
1663
- if (attempt >= sendRetryMaxAttempts) throw lastError;
1664
- const delayMs = computeBackoff(disconnectRetryPolicy, attempt);
1665
- options.logVerbose(`Waiting ${delayMs}ms for WhatsApp reconnect before retrying send to ${jid}: ${formatError(lastError)}`);
1666
- try {
1667
- await sleepWithAbort(delayMs, options.disconnectRetryAbortSignal);
1668
- } catch {
1669
- throw lastError;
1670
- }
1671
1439
  }
1672
1440
  };
1673
- const socketOperations = {
1674
- sendMessage: (jid, content, sendOptions) => sendTrackedMessage(jid, content, sendOptions),
1675
- sendPresenceUpdate: async (presenceLocal, jid) => {
1676
- const currentSock = getCurrentSock();
1677
- if (!currentSock) throw new Error(RECONNECT_IN_PROGRESS_ERROR);
1678
- return await createWhatsAppSocketOperationTimeoutAdapter(currentSock, sendOperationTimeoutMs).sendPresenceUpdate(presenceLocal, jid);
1679
- }
1680
- };
1681
- const markRead = async (target) => {
1682
- const { id, remoteJid, participant } = target;
1683
- await withWhatsAppSocketOperationTimeout("readMessages", (getCurrentSock() ?? sock).readMessages([{
1684
- remoteJid,
1685
- id,
1686
- participant,
1687
- fromMe: false
1688
- }]), sendOperationTimeoutMs);
1689
- };
1690
- const attachSockListener = (event, listener) => attachEmitterListener(sock.ev, event, listener);
1691
1441
  const handleConnectionUpdate = (update) => {
1692
1442
  try {
1693
1443
  if ("reachoutTimeLock" in update) rememberReachoutTimeLock(update.reachoutTimeLock);
@@ -1700,135 +1450,110 @@ async function createWhatsAppAttachedSocketSession(options) {
1700
1450
  error: update.lastDisconnect?.error
1701
1451
  });
1702
1452
  }
1703
- } catch (error) {
1704
- options.logConnectionError(error);
1453
+ } catch (err) {
1454
+ inboundLogger.error({ error: String(err) }, "connection.update handler error");
1705
1455
  resolveClose({
1706
1456
  status: void 0,
1707
1457
  isLoggedOut: false,
1708
- error
1458
+ error: err
1709
1459
  });
1710
1460
  }
1711
1461
  };
1712
- let detachConnectionUpdate;
1713
- const start = () => {
1714
- detachConnectionUpdate ??= attachSockListener("connection.update", handleConnectionUpdate);
1715
- };
1716
- const stop = () => {
1717
- detachConnectionUpdate?.();
1718
- detachConnectionUpdate = void 0;
1719
- };
1720
- const closeSocket = () => {
1721
- try {
1722
- closeInboundMonitorSocket(sock);
1723
- } catch (error) {
1724
- options.logVerbose(`Socket close failed: ${String(error)}`);
1725
- }
1462
+ const attachSockListener = (event, listener) => attachEmitterListener(sock.ev, event, listener);
1463
+ const detachMessagesUpsert = attachSockListener("messages.upsert", handleMessagesUpsertEvent);
1464
+ const detachConnectionUpdate = attachSockListener("connection.update", handleConnectionUpdate);
1465
+ const isFullGroupMetadataUpdate = (update) => typeof update.id === "string" && typeof update.subject === "string" && Array.isArray(update.participants);
1466
+ const rememberFullGroupMetadataUpdate = (jid, meta) => {
1467
+ if (groupMetadataCacheClosed) return;
1468
+ rememberWhatsAppBaileysCacheEntry(options.baileysGroupMetaCache, jid, meta, GROUP_META_TTL_MS);
1469
+ publishedGroupMetadataJids.add(jid);
1470
+ invalidatedGroupMetadataJids.delete(jid);
1471
+ rememberGroupMetadataCacheEntry(groupMetadataCache, jid, summarizeGroupMetaForReconnectCache(meta));
1472
+ groupMetaCache.delete(jid);
1726
1473
  };
1727
- return {
1728
- connectedAtMs,
1729
- self,
1730
- onClose,
1731
- signalClose: (reason) => {
1732
- resolveClose(reason ?? {
1733
- status: void 0,
1734
- isLoggedOut: false,
1735
- error: "closed"
1736
- });
1737
- },
1738
- start,
1739
- stop,
1740
- closeSocket,
1741
- listen: attachSockListener,
1742
- getCurrentSock,
1743
- resolveInboundJid,
1744
- resolveReactionTargetJids,
1745
- rememberBaileysMessage,
1746
- assertCanSendToJid,
1747
- assertSendReady,
1748
- sendTrackedMessage,
1749
- socketOperations,
1750
- markRead
1474
+ const forgetFullGroupMetadata = (jid) => {
1475
+ options.baileysGroupMetaCache?.delete(jid);
1476
+ groupMetadataCache.delete(jid);
1477
+ groupMetaCache.delete(jid);
1478
+ publishedGroupMetadataJids.delete(jid);
1479
+ invalidatedGroupMetadataJids.add(jid);
1751
1480
  };
1752
- }
1753
- //#endregion
1754
- //#region extensions/whatsapp/src/inbound/monitor.ts
1755
- function logWhatsAppVerbose(enabled, message) {
1756
- if (enabled) defaultRuntime.log(message);
1757
- }
1758
- async function attachWebInboxToSocket(options) {
1759
- const inboundLogger = getChildLogger({ module: "web-inbound" });
1760
- const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
1761
- const socketSession = await createWhatsAppAttachedSocketSession({
1762
- sock: options.sock,
1763
- socketRef: options.socketRef,
1764
- accountId: options.accountId,
1765
- authDir: options.authDir,
1766
- selfChatMode: options.selfChatMode,
1767
- socketTiming: options.socketTiming,
1768
- shouldRetryDisconnect: options.shouldRetryDisconnect,
1769
- disconnectRetryPolicy: options.disconnectRetryPolicy,
1770
- disconnectRetryAbortSignal: options.disconnectRetryAbortSignal,
1771
- recentMessageKeys: options.recentMessageKeys,
1772
- logVerbose: (message) => logWhatsAppVerbose(options.verbose, message),
1773
- logConnectionError: (error) => {
1774
- inboundLogger.error({ error: String(error) }, "connection.update handler error");
1481
+ const detachGroupsUpsert = attachSockListener("groups.upsert", ((groups) => {
1482
+ for (const group of groups) if (group.id) rememberFullGroupMetadataUpdate(group.id, group);
1483
+ }));
1484
+ const detachGroupsUpdate = attachSockListener("groups.update", ((updates) => {
1485
+ for (const update of updates) {
1486
+ if (!update.id) continue;
1487
+ if (isFullGroupMetadataUpdate(update)) {
1488
+ rememberFullGroupMetadataUpdate(update.id, update);
1489
+ continue;
1490
+ }
1491
+ forgetFullGroupMetadata(update.id);
1775
1492
  }
1493
+ }));
1494
+ const detachGroupParticipantsUpdate = attachSockListener("group-participants.update", ((update) => {
1495
+ forgetFullGroupMetadata(update.id);
1496
+ }));
1497
+ const replayTask = replayPendingDurableInboundMessages().catch((err) => {
1498
+ inboundLogger.error({ error: String(err) }, "failed replaying durable WhatsApp inbound");
1499
+ inboundConsoleLog.error(`Failed replaying durable WhatsApp inbound: ${String(err)}`);
1500
+ });
1501
+ pendingMessageHandlers.add(replayTask);
1502
+ publishPendingWorkState();
1503
+ replayTask.finally(() => {
1504
+ pendingMessageHandlers.delete(replayTask);
1505
+ publishPendingWorkState();
1776
1506
  });
1777
- const groupMetadata = createWhatsAppGroupMetadataCacheOwner({
1778
- sock: options.sock,
1779
- getCurrentSock: socketSession.getCurrentSock,
1780
- resolveInboundJid: socketSession.resolveInboundJid,
1781
- reconnectCache: options.groupMetadataCache,
1782
- baileysCache: options.baileysGroupMetaCache,
1783
- listen: socketSession.listen,
1784
- logVerbose: (message) => logWhatsAppVerbose(options.verbose, message),
1785
- logHydrationWarning: (error) => {
1507
+ (async () => {
1508
+ try {
1509
+ const groups = await sock.groupFetchAllParticipating();
1510
+ if (groupMetadataCacheClosed) return;
1511
+ for (const [jid, meta] of Object.entries(groups ?? {})) if (meta && !publishedGroupMetadataJids.has(jid) && !invalidatedGroupMetadataJids.has(jid)) {
1512
+ rememberGroupMetadataCacheEntry(groupMetadataCache, jid, summarizeGroupMetaForReconnectCache(meta));
1513
+ rememberWhatsAppBaileysCacheEntry(options.baileysGroupMetaCache, jid, meta, GROUP_META_TTL_MS);
1514
+ publishedGroupMetadataJids.add(jid);
1515
+ }
1516
+ logWhatsAppVerbose(options.verbose, `Hydrated ${Object.keys(groups ?? {}).length} participating groups on connect`);
1517
+ } catch (err) {
1518
+ const error = String(err);
1786
1519
  inboundLogger.warn({ error }, "failed hydrating participating groups on connect");
1787
1520
  inboundConsoleLog.warn(`Failed hydrating participating groups on connect: ${error}`);
1521
+ logWhatsAppVerbose(options.verbose, `Failed to hydrate participating groups on connect: ${error}`);
1788
1522
  }
1789
- });
1790
- const delivery = createWhatsAppMessageDeliveryCoordinator({
1791
- cfg: options.cfg,
1792
- loadConfig: options.loadConfig,
1793
- verbose: options.verbose,
1794
- accountId: options.accountId,
1795
- sock: options.sock,
1796
- socketSession,
1797
- groupMetadata,
1798
- onMessage: options.onMessage,
1799
- mediaMaxMb: options.mediaMaxMb,
1800
- sendReadReceipts: options.sendReadReceipts,
1801
- debounceMs: options.debounceMs,
1802
- appendReplyWindow: options.appendReplyWindow,
1803
- shouldDebounce: options.shouldDebounce,
1804
- onPendingWorkChanged: options.onPendingWorkChanged,
1805
- durableInboundQueue: options.durableInboundQueue ?? createWhatsAppDurableInboundQueue(options.accountId)
1806
- });
1523
+ })();
1807
1524
  const sendApi = createWebSendApi({
1808
- sock: socketSession.socketOperations,
1525
+ sock: sendApiSocketOperations,
1809
1526
  defaultAccountId: options.accountId,
1810
- resolveOutboundMentions: ({ jid, text }) => groupMetadata.resolveOutboundMentions(jid, text),
1527
+ resolveOutboundMentions: ({ jid, text }) => resolveOutboundMentionsForGroup(jid, text),
1811
1528
  authDir: options.authDir
1812
1529
  });
1813
- delivery.start();
1814
- socketSession.start();
1815
- groupMetadata.start();
1816
1530
  return {
1817
1531
  close: async () => {
1818
- delivery.stopIntake();
1819
- socketSession.stop();
1820
- groupMetadata.close();
1821
1532
  try {
1822
- await delivery.drain();
1823
- } catch (error) {
1824
- logWhatsAppVerbose(options.verbose, `Inbound close drain failed: ${String(error)}`);
1825
- } finally {
1826
- socketSession.closeSocket();
1533
+ detachMessagesUpsert();
1534
+ detachConnectionUpdate();
1535
+ detachGroupsUpsert();
1536
+ detachGroupsUpdate();
1537
+ detachGroupParticipantsUpdate();
1538
+ await drainInboundBeforeSocketCloseWithTimeout();
1539
+ } catch (err) {
1540
+ logWhatsAppVerbose(options.verbose, `Inbound close drain failed: ${String(err)}`);
1827
1541
  }
1542
+ try {
1543
+ closeInboundMonitorSocket(sock);
1544
+ } catch (err) {
1545
+ logWhatsAppVerbose(options.verbose, `Socket close failed: ${String(err)}`);
1546
+ }
1547
+ },
1548
+ onClose,
1549
+ signalClose: (reason) => {
1550
+ resolveClose(reason ?? {
1551
+ status: void 0,
1552
+ isLoggedOut: false,
1553
+ error: "closed"
1554
+ });
1828
1555
  },
1829
- onClose: socketSession.onClose,
1830
- signalClose: socketSession.signalClose,
1831
- assertSendReady: socketSession.assertSendReady,
1556
+ assertSendReady: assertCanSendTo,
1832
1557
  sendComposingTo: sendApi.sendComposingTo,
1833
1558
  sendMessage: sendApi.sendMessage,
1834
1559
  sendPoll: sendApi.sendPoll,
@@ -1836,7 +1561,7 @@ async function attachWebInboxToSocket(options) {
1836
1561
  };
1837
1562
  }
1838
1563
  async function monitorWebInbox(options) {
1839
- const socketTiming = options.socketTiming ?? resolveWhatsAppSocketTiming();
1564
+ const socketTiming = options.socketTiming ?? resolveWhatsAppSocketTiming(options.cfg);
1840
1565
  const recentMessageKeys = options.recentMessageKeys ?? /* @__PURE__ */ new Map();
1841
1566
  const baileysGroupMetaCache = options.baileysGroupMetaCache ?? /* @__PURE__ */ new Map();
1842
1567
  const sock = await createWaSocket(false, options.verbose, {
@@ -1850,16 +1575,18 @@ async function monitorWebInbox(options) {
1850
1575
  });
1851
1576
  try {
1852
1577
  await waitForWaConnection(sock, { timeoutMs: socketTiming.connectTimeoutMs });
1853
- } catch (error) {
1578
+ } catch (err) {
1854
1579
  closeInboundMonitorSocket(sock);
1855
- throw error;
1580
+ throw err;
1856
1581
  }
1582
+ const shouldDebounce = options.shouldDebounce;
1583
+ const normalizeAdmittedWebInboundMessage = (msg) => requireAdmittedWhatsAppInboundMessage(normalizeWebInboundMessage(msg));
1857
1584
  return attachWebInboxToSocket({
1858
1585
  ...options,
1859
1586
  onMessage: async (msg) => {
1860
1587
  await options.onMessage(normalizeAdmittedWebInboundMessage(msg));
1861
1588
  },
1862
- shouldDebounce: options.shouldDebounce ? (msg) => options.shouldDebounce?.(normalizeAdmittedWebInboundMessage(msg)) ?? true : void 0,
1589
+ shouldDebounce: shouldDebounce ? (msg) => shouldDebounce(normalizeAdmittedWebInboundMessage(msg)) : void 0,
1863
1590
  socketTiming,
1864
1591
  sock,
1865
1592
  recentMessageKeys,
@@ -1892,13 +1619,12 @@ function isBotMentionedFromTargets(msg, mentionCfg, targets) {
1892
1619
  const isGroupConversation = requireWhatsAppInboundAdmission(msg).conversation.kind === "group";
1893
1620
  const isSelfChat = explicitSelfChatOverride ? Boolean(mentionCfg.isSelfChat) : isSelfChatMode(targets.self.e164, mentionCfg.allowFrom) && !isGroupConversation;
1894
1621
  const hasMentions = targets.normalizedMentions.length > 0;
1895
- const hasNativeMentionsOutsideSelfChat = hasMentions && !isSelfChat;
1896
- if (hasNativeMentionsOutsideSelfChat) {
1622
+ if (hasMentions && !isSelfChat) {
1897
1623
  for (const mention of targets.normalizedMentions) if (identitiesOverlap(targets.self, mention)) return true;
1624
+ return false;
1898
1625
  } else if (hasMentions && isSelfChat) {}
1899
1626
  const bodyClean = clean(msg.payload.body);
1900
1627
  if (mentionCfg.mentionRegexes.some((re) => re.test(bodyClean))) return true;
1901
- if (hasNativeMentionsOutsideSelfChat) return false;
1902
1628
  if (targets.self.e164) {
1903
1629
  const selfDigits = targets.self.e164.replace(/\D/g, "");
1904
1630
  if (selfDigits) {
@@ -2037,7 +1763,6 @@ function createEchoTracker(params) {
2037
1763
  const recentlySent = /* @__PURE__ */ new Set();
2038
1764
  const maxItems = Math.max(1, params.maxItems ?? 100);
2039
1765
  const buildCombinedKey = (p) => `combined:${p.sessionKey}:${p.combinedBody}`;
2040
- const buildTextKey = (text, conversationId) => conversationId ? `${conversationId}\0${text}` : text;
2041
1766
  const trim = () => {
2042
1767
  while (recentlySent.size > maxItems) {
2043
1768
  const firstKey = recentlySent.values().next().value;
@@ -2047,19 +1772,19 @@ function createEchoTracker(params) {
2047
1772
  };
2048
1773
  const rememberText = (text, opts) => {
2049
1774
  if (!text) return;
2050
- recentlySent.add(buildTextKey(text, opts.conversationId));
1775
+ recentlySent.add(text);
2051
1776
  if (opts.combinedBody && opts.combinedBodySessionKey) recentlySent.add(buildCombinedKey({
2052
1777
  sessionKey: opts.combinedBodySessionKey,
2053
1778
  combinedBody: opts.combinedBody
2054
1779
  }));
2055
- if (opts.logVerboseMessage) params.logVerbose?.(`Added to echo detection set (size now: ${recentlySent.size}): ${truncateUtf16Safe(text, 50)}...`);
1780
+ if (opts.logVerboseMessage) params.logVerbose?.(`Added to echo detection set (size now: ${recentlySent.size}): ${text.slice(0, 50)}...`);
2056
1781
  trim();
2057
1782
  };
2058
1783
  return {
2059
1784
  rememberText,
2060
- has: (key, conversationId) => recentlySent.has(buildTextKey(key, conversationId)),
2061
- forget: (key, conversationId) => {
2062
- recentlySent.delete(buildTextKey(key, conversationId));
1785
+ has: (key) => recentlySent.has(key),
1786
+ forget: (key) => {
1787
+ recentlySent.delete(key);
2063
1788
  },
2064
1789
  buildCombinedKey
2065
1790
  };
@@ -2080,7 +1805,8 @@ function formatWhatsAppInboundListeningLog(account) {
2080
1805
  const DEFAULT_WHATSAPP_ACK_REACTION = "👀";
2081
1806
  function resolveWhatsAppAckEmoji(params) {
2082
1807
  if (!params.ackConfig) return "";
2083
- return (typeof params.ackConfig === "string" ? params.ackConfig : params.ackConfig.emoji)?.trim() || resolveAgentIdentityEmoji(params.cfg, params.agentId) || DEFAULT_WHATSAPP_ACK_REACTION;
1808
+ if (params.ackConfig.emoji !== void 0) return params.ackConfig.emoji.trim();
1809
+ return resolveAgentIdentityEmoji(params.cfg, params.agentId) ?? DEFAULT_WHATSAPP_ACK_REACTION;
2084
1810
  }
2085
1811
  function resolveAgentIdentityEmoji(cfg, agentId) {
2086
1812
  return resolveAgentIdentity(cfg, agentId)?.emoji?.trim() || void 0;
@@ -2088,7 +1814,7 @@ function resolveAgentIdentityEmoji(cfg, agentId) {
2088
1814
  //#endregion
2089
1815
  //#region extensions/whatsapp/src/auto-reply/monitor/group-activation.ts
2090
1816
  function hasNamedWhatsAppAccounts(cfg) {
2091
- return Object.keys(cfg.channels?.whatsapp?.accounts ?? {}).some((accountId) => normalizeAccountId(accountId) !== DEFAULT_ACCOUNT_ID);
1817
+ return Object.keys(cfg.channels?.whatsapp?.accounts ?? {}).some((accountId) => normalizeAccountId$1(accountId) !== DEFAULT_ACCOUNT_ID$1);
2092
1818
  }
2093
1819
  function isActivationOnlyEntry(entry) {
2094
1820
  return entry?.groupActivation !== void 0 && typeof entry?.sessionId !== "string" && typeof entry?.updatedAt !== "number";
@@ -2111,21 +1837,20 @@ async function resolveGroupActivationFor(params) {
2111
1837
  ...sessionScope,
2112
1838
  sessionKey: params.sessionKey
2113
1839
  });
2114
- const activation = (normalizeAccountId(params.accountId) === DEFAULT_ACCOUNT_ID && hasNamedWhatsAppAccounts(params.cfg) && isActivationOnlyEntry(scopedEntry) ? void 0 : scopedEntry?.groupActivation) ?? legacyEntry?.groupActivation;
2115
- if (activation !== void 0 && scopedEntry?.groupActivation === void 0) {
2116
- if (scopedEntry) await patchSessionEntry({
2117
- ...sessionScope,
2118
- sessionKey: params.sessionKey,
2119
- replaceEntry: true,
2120
- update: (entry) => {
2121
- if (entry.groupActivation !== void 0) return null;
2122
- return {
2123
- ...entry,
2124
- groupActivation: activation
2125
- };
2126
- }
2127
- });
2128
- }
1840
+ const activation = (normalizeAccountId$1(params.accountId) === DEFAULT_ACCOUNT_ID$1 && hasNamedWhatsAppAccounts(params.cfg) && isActivationOnlyEntry(scopedEntry) ? void 0 : scopedEntry?.groupActivation) ?? legacyEntry?.groupActivation;
1841
+ if (activation !== void 0 && scopedEntry?.groupActivation === void 0) await patchSessionEntry({
1842
+ ...sessionScope,
1843
+ sessionKey: params.sessionKey,
1844
+ fallbackEntry: {},
1845
+ replaceEntry: true,
1846
+ update: (entry) => {
1847
+ if (entry.groupActivation !== void 0) return null;
1848
+ return {
1849
+ ...entry,
1850
+ groupActivation: activation
1851
+ };
1852
+ }
1853
+ });
2129
1854
  const defaultActivation = !resolveWhatsAppInboundPolicy({
2130
1855
  cfg: params.cfg,
2131
1856
  accountId: params.accountId
@@ -2133,41 +1858,33 @@ async function resolveGroupActivationFor(params) {
2133
1858
  return normalizeGroupActivation(activation) ?? defaultActivation;
2134
1859
  }
2135
1860
  //#endregion
2136
- //#region extensions/whatsapp/src/auto-reply/monitor/reaction-participant.ts
2137
- function resolveReactionParticipant(msg) {
2138
- const sender = getSenderIdentity(msg);
2139
- return sender.jid ?? sender.lid ?? void 0;
2140
- }
2141
- //#endregion
2142
- //#region extensions/whatsapp/src/auto-reply/monitor/reaction-eligibility.ts
2143
- const DISABLED_REACTION = { status: "disabled" };
2144
- async function resolveWhatsAppReactionEligibility(params) {
2145
- const messageId = params.msg.event.id;
2146
- if (!messageId) return DISABLED_REACTION;
1861
+ //#region extensions/whatsapp/src/auto-reply/monitor/ack-reaction.ts
1862
+ async function maybeSendAckReaction(params) {
1863
+ if (!params.msg.event.id) return null;
2147
1864
  const admission = requireWhatsAppInboundAdmission(params.msg);
2148
1865
  const accountId = admission.accountId;
2149
1866
  if (resolveWhatsAppReactionLevel({
2150
1867
  cfg: params.cfg,
2151
1868
  accountId
2152
- }).level === "off") return DISABLED_REACTION;
2153
- const scope = params.cfg.messages?.ackReactionScope ?? "group-mentions";
2154
- if (scope === "off" || scope === "none") return DISABLED_REACTION;
1869
+ }).level === "off") return null;
1870
+ const ackConfig = params.cfg.channels?.whatsapp?.ackReaction;
2155
1871
  const emoji = resolveWhatsAppAckEmoji({
2156
1872
  cfg: params.cfg,
2157
1873
  agentId: params.agentId,
2158
- ackConfig: params.cfg.messages?.ackReaction
1874
+ ackConfig
2159
1875
  });
2160
- const directEnabled = scope === "all" || scope === "direct";
2161
- const groupMode = scope === "all" || scope === "group-all" ? "always" : scope === "direct" ? "never" : "mentions";
1876
+ const directEnabled = ackConfig?.direct ?? true;
1877
+ const groupMode = ackConfig?.group ?? "mentions";
2162
1878
  const isGroup = admission.conversation.kind === "group";
1879
+ const conversationIdForCheck = admission.conversation.id;
2163
1880
  const activation = isGroup ? await resolveGroupActivationFor({
2164
1881
  cfg: params.cfg,
2165
1882
  accountId,
2166
1883
  agentId: params.agentId,
2167
1884
  sessionKey: params.sessionKey,
2168
- conversationId: admission.conversation.id
1885
+ conversationId: conversationIdForCheck
2169
1886
  }) : null;
2170
- if (!shouldAckReactionForWhatsApp({
1887
+ const shouldSendReaction = () => shouldAckReactionForWhatsApp({
2171
1888
  emoji,
2172
1889
  isDirect: admission.conversation.kind === "direct",
2173
1890
  isGroup,
@@ -2175,50 +1892,32 @@ async function resolveWhatsAppReactionEligibility(params) {
2175
1892
  groupMode,
2176
1893
  wasMentioned: (params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned) === true,
2177
1894
  groupActivated: activation === "always"
2178
- })) return DISABLED_REACTION;
2179
- const participant = resolveReactionParticipant(params.msg);
2180
- return {
2181
- status: "prepared",
2182
- chatId: params.msg.platform.chatJid,
2183
- messageId,
2184
- emoji,
2185
- reactionOptions: {
2186
- verbose: params.verbose,
2187
- fromMe: false,
2188
- ...participant ? { participant } : {},
2189
- accountId,
2190
- cfg: params.cfg
2191
- }
2192
- };
2193
- }
2194
- //#endregion
2195
- //#region extensions/whatsapp/src/auto-reply/monitor/ack-reaction.ts
2196
- async function maybeSendAckReaction(params) {
2197
- const eligibility = await resolveWhatsAppReactionEligibility({
2198
- cfg: params.cfg,
2199
- msg: params.msg,
2200
- agentId: params.agentId,
2201
- sessionKey: params.sessionKey,
2202
- verbose: params.verbose
2203
1895
  });
2204
- if (eligibility.status === "disabled") return null;
2205
- const { chatId, messageId, emoji, reactionOptions } = eligibility;
1896
+ if (!shouldSendReaction()) return null;
2206
1897
  params.info({
2207
- chatId,
2208
- messageId,
1898
+ chatId: params.msg.platform.chatJid,
1899
+ messageId: params.msg.event.id,
2209
1900
  emoji
2210
1901
  }, "sending ack reaction");
1902
+ const sender = getSenderIdentity(params.msg);
1903
+ const reactionOptions = {
1904
+ verbose: params.verbose,
1905
+ fromMe: false,
1906
+ ...sender.jid ? { participant: sender.jid } : {},
1907
+ accountId,
1908
+ cfg: params.cfg
1909
+ };
2211
1910
  return createAckReactionHandle({
2212
1911
  ackReactionValue: emoji,
2213
- send: () => sendReactionWhatsApp(chatId, messageId, emoji, reactionOptions),
2214
- remove: () => sendReactionWhatsApp(chatId, messageId, "", reactionOptions),
1912
+ send: () => sendReactionWhatsApp(params.msg.platform.chatJid, params.msg.event.id, emoji, reactionOptions),
1913
+ remove: () => sendReactionWhatsApp(params.msg.platform.chatJid, params.msg.event.id, "", reactionOptions),
2215
1914
  onSendError: (err) => {
2216
1915
  params.warn({
2217
1916
  error: formatError(err),
2218
- chatId,
2219
- messageId
1917
+ chatId: params.msg.platform.chatJid,
1918
+ messageId: params.msg.event.id
2220
1919
  }, "failed to send ack reaction");
2221
- logVerbose(`WhatsApp ack reaction failed for chat ${chatId}: ${formatError(err)}`);
1920
+ logVerbose(`WhatsApp ack reaction failed for chat ${params.msg.platform.chatJid}: ${formatError(err)}`);
2222
1921
  }
2223
1922
  });
2224
1923
  }
@@ -2351,12 +2050,17 @@ function formatGroupMembers(params) {
2351
2050
  }
2352
2051
  //#endregion
2353
2052
  //#region extensions/whatsapp/src/auto-reply/monitor/group-gating.ts
2354
- const groupDropWarned = createDedupeCache({
2355
- ttlMs: 0,
2356
- maxSize: 100
2357
- });
2053
+ const MAX_GROUP_DROP_WARNINGS = 100;
2054
+ const groupDropWarned = /* @__PURE__ */ new Set();
2358
2055
  function shouldWarnForGroupDrop(warnKey) {
2359
- return !groupDropWarned.check(warnKey);
2056
+ if (groupDropWarned.has(warnKey)) return false;
2057
+ groupDropWarned.add(warnKey);
2058
+ while (groupDropWarned.size > MAX_GROUP_DROP_WARNINGS) {
2059
+ const oldest = groupDropWarned.values().next().value;
2060
+ if (!oldest) break;
2061
+ groupDropWarned.delete(oldest);
2062
+ }
2063
+ return true;
2360
2064
  }
2361
2065
  function isOwnerSender(baseMentionConfig, msg, authDir) {
2362
2066
  const sender = normalizeE164(getSenderIdentity(msg, authDir).e164 ?? "");
@@ -2374,13 +2078,7 @@ function recordPendingGroupHistoryEntry(params) {
2374
2078
  body: params.body ?? params.msg.payload.body,
2375
2079
  timestamp: params.msg.event.timestamp,
2376
2080
  id: params.msg.event.id,
2377
- senderJid: senderIdentity.jid ?? params.msg.platform.senderJid,
2378
- ...params.body === void 0 && params.msg.payload.media ? { media: [{
2379
- path: params.msg.payload.media.path,
2380
- url: params.msg.payload.media.url ?? params.msg.payload.media.path,
2381
- contentType: params.msg.payload.media.type,
2382
- kind: params.msg.payload.media.kind ?? void 0
2383
- }] } : {}
2081
+ senderJid: senderIdentity.jid ?? params.msg.platform.senderJid
2384
2082
  }
2385
2083
  });
2386
2084
  }
@@ -2468,11 +2166,13 @@ async function applyGroupGating(params) {
2468
2166
  conversationId
2469
2167
  }) !== "always";
2470
2168
  const replyContext = getReplyContext(params.msg, params.authDir);
2169
+ const implicitReplyToSelf = params.selfChatMode === true && identitiesOverlap(self, sender);
2170
+ const implicitMentionKinds = implicitMentionKindWhen("quoted_bot", !implicitReplyToSelf && identitiesOverlap(self, replyContext?.sender));
2471
2171
  const mentionDecision = resolveInboundMentionDecision({
2472
2172
  facts: {
2473
2173
  canDetectMention: true,
2474
2174
  wasMentioned,
2475
- implicitMentionKinds: implicitMentionKindWhen("quoted_bot", !(params.selfChatMode === true && identitiesOverlap(self, sender)) && identitiesOverlap(self, replyContext?.sender))
2175
+ implicitMentionKinds
2476
2176
  },
2477
2177
  policy: {
2478
2178
  isGroup: true,
@@ -2559,57 +2259,6 @@ function resolveWhatsAppDirectSystemPrompt(params) {
2559
2259
  return wildcard != null ? wildcard.trim() || void 0 : void 0;
2560
2260
  }
2561
2261
  //#endregion
2562
- //#region extensions/whatsapp/src/outbound-retry.ts
2563
- const WHATSAPP_OUTBOUND_MAX_ATTEMPTS = 3;
2564
- const WHATSAPP_OUTBOUND_MIN_DELAY_MS = 500;
2565
- const WHATSAPP_OUTBOUND_MAX_DELAY_MS = 1e3;
2566
- const WHATSAPP_RETRYABLE_OUTBOUND_ERROR_PATTERN = /closed|reset|timed\s*out|disconnect/i;
2567
- var WhatsAppOutboundRetryError = class extends Error {
2568
- constructor(original) {
2569
- super(formatError(original), { cause: original });
2570
- this.original = original;
2571
- }
2572
- };
2573
- function isRetryableWhatsAppOutboundError(error) {
2574
- if (isWhatsAppSocketOperationTimeoutError(error)) return false;
2575
- return WHATSAPP_RETRYABLE_OUTBOUND_ERROR_PATTERN.test(formatError(error));
2576
- }
2577
- async function sendWhatsAppOutboundWithRetry(params) {
2578
- const runWithRetry = createChannelApiRetryRunner({
2579
- retry: {
2580
- attempts: WHATSAPP_OUTBOUND_MAX_ATTEMPTS,
2581
- minDelayMs: WHATSAPP_OUTBOUND_MIN_DELAY_MS,
2582
- maxDelayMs: WHATSAPP_OUTBOUND_MAX_DELAY_MS,
2583
- jitter: 0
2584
- },
2585
- strictShouldRetry: true,
2586
- retryAfterMs: () => void 0,
2587
- shouldRetry: (error, attempt) => {
2588
- if (!(error instanceof WhatsAppOutboundRetryError) || !isRetryableWhatsAppOutboundError(error.original)) return false;
2589
- params.onRetry?.({
2590
- attempt,
2591
- maxAttempts: WHATSAPP_OUTBOUND_MAX_ATTEMPTS,
2592
- backoffMs: Math.min(WHATSAPP_OUTBOUND_MIN_DELAY_MS * 2 ** (attempt - 1), WHATSAPP_OUTBOUND_MAX_DELAY_MS),
2593
- error: error.original,
2594
- errorText: formatError(error.original)
2595
- });
2596
- return true;
2597
- }
2598
- });
2599
- try {
2600
- return await runWithRetry(async () => {
2601
- try {
2602
- return await params.send();
2603
- } catch (error) {
2604
- throw new WhatsAppOutboundRetryError(error);
2605
- }
2606
- });
2607
- } catch (error) {
2608
- if (error instanceof WhatsAppOutboundRetryError) throw error.original;
2609
- throw error;
2610
- }
2611
- }
2612
- //#endregion
2613
2262
  //#region extensions/whatsapp/src/auto-reply/util.ts
2614
2263
  function elide(text, limit = 400) {
2615
2264
  if (!text) return text;
@@ -2649,26 +2298,13 @@ function isLikelyWhatsAppCryptoError(reason) {
2649
2298
  if (typeof value === "function") return value.name ? `[function ${value.name}]` : "[function]";
2650
2299
  return Object.prototype.toString.call(value);
2651
2300
  };
2652
- const haystack = normalizeLowercaseStringOrEmpty(reason instanceof Error ? `${reason.message}\n${reason.stack ?? ""}` : formatReason(reason));
2301
+ const raw = reason instanceof Error ? `${reason.message}\n${reason.stack ?? ""}` : formatReason(reason);
2302
+ const haystack = normalizeLowercaseStringOrEmpty(raw);
2653
2303
  if (!(haystack.includes("unsupported state or unable to authenticate data") || haystack.includes("bad mac"))) return false;
2654
2304
  return haystack.includes("baileys") || haystack.includes("noise-handler") || haystack.includes("aesdecryptgcm");
2655
2305
  }
2656
2306
  //#endregion
2657
2307
  //#region extensions/whatsapp/src/auto-reply/deliver-reply.ts
2658
- function createWhatsAppReplyTransportContext(msg) {
2659
- const admission = requireWhatsAppInboundAdmission(msg);
2660
- return {
2661
- accountId: admission.accountId,
2662
- conversationId: admission.conversation.id,
2663
- conversationKind: admission.conversation.kind,
2664
- chatJid: msg.platform.chatJid,
2665
- senderJid: msg.platform.senderJid,
2666
- recipientJid: msg.platform.recipientJid,
2667
- correlationId: msg.event.id,
2668
- reply: msg.platform.reply,
2669
- sendMedia: msg.platform.sendMedia
2670
- };
2671
- }
2672
2308
  function resolveWhatsAppReceiptKind(results) {
2673
2309
  if (results.length > 0 && results.every((result) => result.kind === "text")) return "text";
2674
2310
  if (results.length > 0 && results.every((result) => result.kind === "media")) return "media";
@@ -2706,9 +2342,10 @@ function createWhatsAppReplyDeliveryReceipt(results) {
2706
2342
  });
2707
2343
  }
2708
2344
  async function deliverWebReply(params) {
2709
- const { replyResult, transport, maxMediaBytes, textLimit, replyLogger, connectionId, skipLog } = params;
2710
- const conversationId = transport.conversationId;
2711
- const isGroupConversation = transport.conversationKind === "group";
2345
+ const { replyResult, msg, maxMediaBytes, textLimit, replyLogger, connectionId, skipLog } = params;
2346
+ const admission = requireWhatsAppInboundAdmission(msg);
2347
+ const conversationId = admission.conversation.id;
2348
+ const isGroupConversation = admission.conversation.kind === "group";
2712
2349
  const replyStarted = Date.now();
2713
2350
  const sendResults = [];
2714
2351
  const rememberSendResult = (result) => {
@@ -2729,24 +2366,25 @@ async function deliverWebReply(params) {
2729
2366
  const tableMode = params.tableMode ?? "code";
2730
2367
  const chunkMode = params.chunkMode ?? "length";
2731
2368
  const normalizedReply = params.normalizedReplyResult ?? normalizeWhatsAppOutboundPayload(replyResult, { normalizeText: normalizeWhatsAppPayloadTextPreservingIndentation });
2732
- const textChunks = markdownToWhatsAppChunks(normalizedReply.text ?? "", textLimit, tableMode, chunkMode);
2369
+ const convertedText = markdownToWhatsApp(convertMarkdownTables$1(normalizedReply.text ?? "", tableMode));
2370
+ const textChunks = chunkMarkdownTextWithMode(convertedText, textLimit, chunkMode);
2733
2371
  const mediaList = normalizedReply.mediaUrls ?? [];
2734
2372
  const getQuote = () => {
2735
2373
  if (!replyResult.replyToId) return;
2736
- const cached = lookupInboundMessageMeta(transport.accountId, transport.chatJid, replyResult.replyToId);
2374
+ const cached = lookupInboundMessageMeta(admission.accountId, msg.platform.chatJid, replyResult.replyToId);
2737
2375
  return buildQuotedMessageOptions({
2738
2376
  messageId: replyResult.replyToId,
2739
- remoteJid: transport.chatJid,
2377
+ remoteJid: msg.platform.chatJid,
2740
2378
  fromMe: cached?.fromMe ?? false,
2741
- participant: cached?.participant ?? (isGroupConversation ? transport.senderJid : void 0),
2742
- messageText: cached?.body ?? "",
2743
- media: cached?.media
2379
+ participant: cached?.participant ?? (isGroupConversation ? msg.platform.senderJid : void 0),
2380
+ messageText: cached?.body ?? ""
2744
2381
  });
2745
2382
  };
2746
- const sendWithRetry = async (fn, label) => {
2383
+ const sendWithRetry = async (fn, label, maxAttempts = 3) => {
2747
2384
  try {
2748
2385
  return await sendWhatsAppOutboundWithRetry({
2749
2386
  send: fn,
2387
+ maxAttempts,
2750
2388
  onRetry: ({ attempt, maxAttempts: retryMaxAttempts, backoffMs, errorText }) => {
2751
2389
  logVerbose(`Retrying ${label} to ${conversationId} after failure (${attempt}/${retryMaxAttempts - 1}) in ${backoffMs}ms: ${errorText}`);
2752
2390
  }
@@ -2761,7 +2399,7 @@ async function deliverWebReply(params) {
2761
2399
  for (const [index, chunk] of textChunks.entries()) {
2762
2400
  const chunkStarted = Date.now();
2763
2401
  const quote = getQuote();
2764
- rememberSendResult(await sendWithRetry(() => transport.reply(chunk, quote), "text"));
2402
+ rememberSendResult(await sendWithRetry(() => msg.platform.reply(chunk, quote), "text"));
2765
2403
  if (!skipLog) {
2766
2404
  const durationMs = Date.now() - chunkStarted;
2767
2405
  whatsappOutboundLog.debug(`Sent chunk ${index + 1}/${totalChunks} to ${conversationId} (${durationMs.toFixed(0)}ms)`);
@@ -2769,10 +2407,10 @@ async function deliverWebReply(params) {
2769
2407
  }
2770
2408
  const delivery = finishDelivery();
2771
2409
  const logPayload = {
2772
- correlationId: transport.correlationId ?? newConnectionId(),
2410
+ correlationId: msg.event.id ?? newConnectionId(),
2773
2411
  connectionId: connectionId ?? null,
2774
2412
  to: conversationId,
2775
- from: transport.recipientJid,
2413
+ from: msg.platform.recipientJid,
2776
2414
  text: elide(replyResult.text, 240),
2777
2415
  mediaUrl: null,
2778
2416
  mediaSizeBytes: null,
@@ -2784,55 +2422,56 @@ async function deliverWebReply(params) {
2784
2422
  return delivery;
2785
2423
  }
2786
2424
  const remainingText = [...textChunks];
2425
+ const leadingCaption = remainingText.shift() || "";
2787
2426
  await sendMediaWithLeadingCaption({
2788
2427
  mediaUrls: mediaList,
2789
- caption: remainingText.shift() || "",
2428
+ caption: leadingCaption,
2790
2429
  send: async ({ mediaUrl, caption }) => {
2791
- const media = await prepareWhatsAppOutboundMedia(await loadWebMedia(mediaUrl, {
2430
+ const media = await prepareWhatsAppOutboundMedia(await loadWebMedia$1(mediaUrl, {
2792
2431
  maxBytes: maxMediaBytes,
2793
2432
  localRoots: params.mediaLocalRoots
2794
2433
  }), mediaUrl);
2795
2434
  if (shouldLogVerbose()) {
2796
- logVerbose(`Web auto-reply media size: ${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB`);
2435
+ logVerbose(`Web auto-reply media size: ${(media.buffer.length / 1048576).toFixed(2)}MB`);
2797
2436
  logVerbose(`Web auto-reply media source: ${mediaUrl} (kind ${media.kind})`);
2798
2437
  }
2799
2438
  if (media.kind === "image") {
2800
2439
  const quote = getQuote();
2801
- rememberSendResult(await sendWithRetry(() => transport.sendMedia({
2440
+ rememberSendResult(await sendWithRetry(() => msg.platform.sendMedia({
2802
2441
  image: media.buffer,
2803
2442
  caption,
2804
2443
  mimetype: media.mimetype
2805
2444
  }, quote), "media:image"));
2806
2445
  } else if (media.kind === "audio") {
2807
2446
  const quote = getQuote();
2808
- rememberSendResult(await sendWithRetry(() => transport.sendMedia({
2447
+ rememberSendResult(await sendWithRetry(() => msg.platform.sendMedia({
2809
2448
  audio: media.buffer,
2810
2449
  ptt: true,
2811
2450
  mimetype: media.mimetype
2812
2451
  }, quote), "media:audio"));
2813
- if (caption) rememberSendResult(await sendWithRetry(() => transport.reply(caption, quote), "media:audio-text"));
2452
+ if (caption) rememberSendResult(await sendWithRetry(() => msg.platform.reply(caption, quote), "media:audio-text"));
2814
2453
  } else if (media.kind === "video") {
2815
2454
  const quote = getQuote();
2816
- rememberSendResult(await sendWithRetry(() => transport.sendMedia({
2455
+ rememberSendResult(await sendWithRetry(() => msg.platform.sendMedia({
2817
2456
  video: media.buffer,
2818
2457
  caption,
2819
2458
  mimetype: media.mimetype
2820
2459
  }, quote), "media:video"));
2821
2460
  } else {
2822
2461
  const quote = getQuote();
2823
- rememberSendResult(await sendWithRetry(() => transport.sendMedia({
2462
+ rememberSendResult(await sendWithRetry(() => msg.platform.sendMedia({
2824
2463
  document: media.buffer,
2825
2464
  fileName: media.fileName,
2826
2465
  caption,
2827
2466
  mimetype: media.mimetype
2828
2467
  }, quote), "media:document"));
2829
2468
  }
2830
- whatsappOutboundLog.info(`Sent media reply to ${conversationId} (${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB)`);
2469
+ whatsappOutboundLog.info(`Sent media reply to ${conversationId} (${(media.buffer.length / 1048576).toFixed(2)}MB)`);
2831
2470
  replyLogger.info({
2832
- correlationId: transport.correlationId ?? newConnectionId(),
2471
+ correlationId: msg.event.id ?? newConnectionId(),
2833
2472
  connectionId: connectionId ?? null,
2834
2473
  to: conversationId,
2835
- from: transport.recipientJid,
2474
+ from: msg.platform.recipientJid,
2836
2475
  text: caption ?? null,
2837
2476
  mediaUrl,
2838
2477
  mediaSizeBytes: media.buffer.length,
@@ -2848,16 +2487,16 @@ async function deliverWebReply(params) {
2848
2487
  }, "failed to send web media reply");
2849
2488
  if (!isFirst) {
2850
2489
  whatsappOutboundLog.warn(`Trailing media failed; sent warning to ${conversationId}`);
2851
- rememberSendResult(await sendWithRetry(() => transport.reply("⚠️ Media unavailable.", getQuote()), "media:fallback-unavailable"));
2490
+ rememberSendResult(await sendWithRetry(() => msg.platform.reply("⚠️ Media unavailable.", getQuote()), "media:fallback-unavailable"));
2852
2491
  return;
2853
2492
  }
2854
2493
  const fallbackText = [caption ?? "", "⚠️ Media failed."].filter(Boolean).join("\n");
2855
2494
  if (!fallbackText) return;
2856
2495
  whatsappOutboundLog.warn(`Media skipped; sent text-only to ${conversationId}`);
2857
- rememberSendResult(await sendWithRetry(() => transport.reply(fallbackText, getQuote()), "media:fallback-text"));
2496
+ rememberSendResult(await sendWithRetry(() => msg.platform.reply(fallbackText, getQuote()), "media:fallback-text"));
2858
2497
  }
2859
2498
  });
2860
- for (const chunk of remainingText) rememberSendResult(await sendWithRetry(() => transport.reply(chunk, getQuote()), "media:text"));
2499
+ for (const chunk of remainingText) rememberSendResult(await sendWithRetry(() => msg.platform.reply(chunk, getQuote()), "media:text"));
2861
2500
  return finishDelivery();
2862
2501
  }
2863
2502
  //#endregion
@@ -2875,106 +2514,32 @@ function isWhatsAppSupplementalSenderAllowed(params) {
2875
2514
  return false;
2876
2515
  }
2877
2516
  function resolveVisibleWhatsAppGroupHistory(params) {
2517
+ if (params.groupPolicy !== "allowlist") return params.history;
2878
2518
  return filterSupplementalContextItems({
2879
2519
  items: params.history,
2880
2520
  mode: params.mode,
2881
2521
  kind: "history",
2882
- isSenderAllowed: (entry) => resolveInboundSupplementalSenderAllowed({
2883
- isGroup: true,
2884
- groupPolicy: params.groupPolicy,
2522
+ isSenderAllowed: (entry) => isWhatsAppSupplementalSenderAllowed({
2885
2523
  allowFrom: params.groupAllowFrom,
2886
- isSenderAllowed: (allowFrom) => isWhatsAppSupplementalSenderAllowed({
2887
- allowFrom,
2888
- authDir: params.authDir,
2889
- sender: entry.senderJid ? { jid: entry.senderJid } : null
2890
- })
2524
+ authDir: params.authDir,
2525
+ sender: entry.senderJid ? { jid: entry.senderJid } : null
2891
2526
  })
2892
2527
  }).items;
2893
2528
  }
2894
2529
  function resolveVisibleWhatsAppReplyContext(params) {
2895
2530
  const replyTo = getReplyContext(params.msg, params.authDir);
2896
2531
  if (!replyTo) return null;
2897
- const admission = requireWhatsAppInboundAdmission(params.msg);
2898
- const previewBody = [replyTo.body, formatMediaPlaceholderText(replyTo.media ? [replyTo.media] : [])].filter(Boolean).join("\n");
2899
- const senderAllowed = resolveInboundSupplementalSenderAllowed({
2900
- isGroup: admission.conversation.kind === "group",
2901
- groupPolicy: params.groupPolicy,
2532
+ const senderAllowed = requireWhatsAppInboundAdmission(params.msg).conversation.kind !== "group" || params.groupPolicy !== "allowlist" ? true : isWhatsAppSupplementalSenderAllowed({
2902
2533
  allowFrom: params.groupAllowFrom,
2903
- isSenderAllowed: (allowFrom) => isWhatsAppSupplementalSenderAllowed({
2904
- allowFrom,
2905
- authDir: params.authDir,
2906
- sender: replyTo.sender
2907
- })
2534
+ authDir: params.authDir,
2535
+ sender: replyTo.sender
2908
2536
  });
2909
- const visible = filterChannelInboundQuoteContext(params.mode, {
2537
+ return filterChannelInboundQuoteContext(params.mode, {
2910
2538
  id: replyTo.id,
2911
- body: previewBody,
2539
+ body: replyTo.body,
2912
2540
  sender: replyTo.sender?.label ?? void 0,
2913
2541
  senderAllowed
2914
- });
2915
- return visible ? {
2916
- ...replyTo,
2917
- body: visible.body ?? ""
2918
- } : null;
2919
- }
2920
- //#endregion
2921
- //#region extensions/whatsapp/src/auto-reply/monitor/prepared-inbound.ts
2922
- function resolvePreparedCommandFacts(command) {
2923
- if (!command) return;
2924
- const { authorization, ...facts } = command;
2925
- return {
2926
- ...facts,
2927
- ...authorization.kind === "not_checked" ? {} : { authorized: authorization.kind === "authorized" }
2928
- };
2929
- }
2930
- function projectPreparedChannelInbound(params) {
2931
- const { inbound, control } = params;
2932
- const command = resolvePreparedCommandFacts(inbound.command);
2933
- const commandAuthorization = inbound.command?.authorization;
2934
- const commandAccess = commandAuthorization && commandAuthorization.kind !== "not_checked" ? { authorized: commandAuthorization.kind === "authorized" } : void 0;
2935
- return {
2936
- input: {
2937
- id: inbound.event.id,
2938
- timestamp: inbound.event.timestamp,
2939
- rawText: inbound.message.rawBody,
2940
- textForAgent: inbound.message.bodyForAgent,
2941
- textForCommands: inbound.message.commandBody,
2942
- raw: inbound
2943
- },
2944
- context: buildChannelInboundEventContext({
2945
- ...inbound,
2946
- messageId: inbound.event.id,
2947
- messageIdFull: inbound.event.fullId,
2948
- timestamp: inbound.event.timestamp,
2949
- command,
2950
- access: inbound.mentions || commandAccess ? {
2951
- mentions: inbound.mentions,
2952
- commands: commandAccess
2953
- } : void 0,
2954
- extra: {
2955
- Transcript: inbound.context?.transcript,
2956
- ...inbound.context && "groupSubject" in inbound.context ? { GroupSubject: inbound.context.groupSubject ?? void 0 } : {},
2957
- GroupMembers: inbound.context?.groupMembers,
2958
- SenderE164: inbound.context?.senderE164,
2959
- ReplyThreading: inbound.context?.replyThreading,
2960
- SuppressMessageReceivedHooks: control.messageReceivedHooks === "channel",
2961
- ...inbound.context?.location ? toLocationContext(inbound.context.location) : {}
2962
- }
2963
- })
2964
- };
2965
- }
2966
- /** Keeps WhatsApp reply-policy mapping beside the transport that applies it. */
2967
- function resolveWhatsAppInboundReplyPolicy(params) {
2968
- const sourceReplyDeliveryMode = params.ctx.ChatType === "group" || params.ctx.ChatType === "channel" ? resolveChannelMessageSourceReplyDeliveryMode({
2969
- cfg: params.cfg,
2970
- ctx: params.ctx
2971
- }) : void 0;
2972
- const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
2973
- return {
2974
- sourceReplyDeliveryMode,
2975
- disableBlockStreaming: sourceRepliesAreToolOnly ? true : typeof params.blockStreamingEnabled === "boolean" ? !params.blockStreamingEnabled : void 0,
2976
- suppressTyping: sourceRepliesAreToolOnly && params.ctx.ChatType === "group" && params.ctx.WasMentioned !== true
2977
- };
2542
+ }) ? replyTo : null;
2978
2543
  }
2979
2544
  //#endregion
2980
2545
  //#region extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts
@@ -2990,36 +2555,26 @@ function normalizeErrForLog(err) {
2990
2555
  function whatsAppReplyDeliveryVisibility(visibleReplySent) {
2991
2556
  return { visibleReplySent };
2992
2557
  }
2993
- function createWhatsAppChannelDeliveryResult(params) {
2994
- const messageIds = listMessageReceiptPlatformIds(params.delivery.receipt);
2995
- return {
2996
- receipt: params.delivery.receipt,
2997
- ...messageIds.length > 0 ? { messageIds } : {},
2998
- content: params.content,
2999
- visibleReplySent: params.delivery.providerAccepted
3000
- };
3001
- }
3002
- function isWhatsAppVisibleDeliveryError(error) {
3003
- return typeof error === "object" && error !== null && !Array.isArray(error) && error.visibleReplySent === true || isChannelPartialDeliveryError(error) && error.deliveryResult.visibleReplySent;
2558
+ function whatsAppReplyDeliveryVisibilityFromDurableResult(result) {
2559
+ return whatsAppReplyDeliveryVisibility(result.visibleReplySent === true);
3004
2560
  }
3005
2561
  function readTrimmedString(value) {
3006
2562
  return typeof value === "string" ? value.trim() : "";
3007
2563
  }
3008
2564
  function markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult) {
3009
- if (flushResult.delivered === 0) return error;
3010
- if (isWhatsAppVisibleDeliveryError(error)) return error;
3011
- return markWhatsAppVisibleDeliveryError(new Error("deferred WhatsApp media delivery failed after an earlier visible send", { cause: error }));
2565
+ return flushResult.delivered > 0 ? markWhatsAppVisibleDeliveryError(error) : error;
3012
2566
  }
3013
2567
  function logWhatsAppReplyDeliveryError(params) {
2568
+ const admission = requireWhatsAppInboundAdmission(params.msg);
3014
2569
  params.replyLogger.error({
3015
2570
  err: normalizeErrForLog(params.err),
3016
2571
  replyKind: params.info.kind,
3017
- correlationId: params.transport.correlationId ?? null,
2572
+ correlationId: params.msg.event.id ?? null,
3018
2573
  connectionId: params.connectionId,
3019
- conversationId: params.transport.conversationId,
3020
- chatId: params.transport.chatJid,
3021
- to: params.transport.conversationId,
3022
- from: params.transport.recipientJid
2574
+ conversationId: admission.conversation.id,
2575
+ chatId: params.msg.platform.chatJid ?? null,
2576
+ to: admission.conversation.id,
2577
+ from: params.msg.platform.recipientJid ?? null
3023
2578
  }, "auto-reply delivery failed");
3024
2579
  }
3025
2580
  function resolveWhatsAppDurableReplyToId(params) {
@@ -3027,10 +2582,14 @@ function resolveWhatsAppDurableReplyToId(params) {
3027
2582
  const explicitPayloadReplyToId = readTrimmedString(params.payload.replyToId);
3028
2583
  if (explicitPayloadReplyToId) return explicitPayloadReplyToId;
3029
2584
  const hasVisibleInboundReplyTarget = Boolean(readTrimmedString(params.context.ReplyToId)) || Boolean(readTrimmedString(params.context.ReplyToIdFull));
3030
- const currentInboundMessageId = readTrimmedString(params.currentMessageId);
2585
+ const currentInboundMessageId = readTrimmedString(params.msg.event.id);
3031
2586
  if (params.info.kind === "final" && hasVisibleInboundReplyTarget && currentInboundMessageId) return currentInboundMessageId;
3032
2587
  return null;
3033
2588
  }
2589
+ function resolveWhatsAppDisableBlockStreaming(cfg) {
2590
+ if (typeof cfg.channels?.whatsapp?.blockStreaming !== "boolean") return;
2591
+ return !cfg.channels.whatsapp.blockStreaming;
2592
+ }
3034
2593
  function resolveWhatsAppDeliverablePayload(payload, info) {
3035
2594
  if (payload.isReasoning === true || payload.isCompactionNotice === true) return null;
3036
2595
  if (payload.isError === true) return null;
@@ -3055,58 +2614,31 @@ function shouldDeferWhatsAppMediaOnlyPayload(params) {
3055
2614
  }
3056
2615
  function createWhatsAppMediaOnlyReplyCoalescer(params) {
3057
2616
  const pendingMediaOnlyPayloads = [];
3058
- const flushWhere = async (shouldFlush) => {
2617
+ const flushExceptDuplicateMedia = async (mediaUrls) => {
3059
2618
  const flushResult = {
3060
2619
  delivered: 0,
3061
2620
  droppedDuplicateMedia: 0
3062
2621
  };
3063
- const candidates = [];
3064
- const retained = [];
3065
- for (const pending of pendingMediaOnlyPayloads.splice(0)) if (shouldFlush(pending)) candidates.push(pending);
3066
- else retained.push(pending);
3067
- pendingMediaOnlyPayloads.push(...retained);
3068
- for (const [index, candidate] of candidates.entries()) try {
3069
- const delivery = await params.deliver(candidate);
3070
- candidate.resolveFinalization(delivery);
3071
- if (delivery.visibleReplySent) flushResult.delivered += 1;
3072
- } catch (error) {
3073
- const visibleError = markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
3074
- candidate.rejectFinalization(error);
3075
- for (const remaining of candidates.slice(index + 1)) remaining.rejectFinalization(new Error("deferred WhatsApp media delivery was not attempted", { cause: error }));
3076
- throw visibleError;
2622
+ const pending = pendingMediaOnlyPayloads.splice(0);
2623
+ for (const candidate of pending) {
2624
+ if (mediaUrls && hasWhatsAppMediaUrlOverlap(candidate.mediaUrls, mediaUrls)) {
2625
+ flushResult.droppedDuplicateMedia += 1;
2626
+ continue;
2627
+ }
2628
+ try {
2629
+ if ((await params.deliver(candidate)).visibleReplySent) flushResult.delivered += 1;
2630
+ } catch (error) {
2631
+ throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
2632
+ }
3077
2633
  }
3078
2634
  return flushResult;
3079
2635
  };
3080
2636
  return {
3081
2637
  defer(pending) {
3082
- let resolveFinalization;
3083
- let rejectFinalization;
3084
- const finalization = new Promise((resolve, reject) => {
3085
- resolveFinalization = resolve;
3086
- rejectFinalization = reject;
3087
- });
3088
- pendingMediaOnlyPayloads.push({
3089
- ...pending,
3090
- resolveFinalization,
3091
- rejectFinalization
3092
- });
3093
- return finalization;
3094
- },
3095
- flushNonDuplicateMedia: (mediaUrls) => flushWhere((pending) => !hasWhatsAppMediaUrlOverlap(pending.mediaUrls, mediaUrls)),
3096
- dropDuplicateMedia(mediaUrls) {
3097
- const flushResult = {
3098
- delivered: 0,
3099
- droppedDuplicateMedia: 0
3100
- };
3101
- const retained = [];
3102
- for (const pending of pendingMediaOnlyPayloads.splice(0)) if (hasWhatsAppMediaUrlOverlap(pending.mediaUrls, mediaUrls)) {
3103
- pending.resolveFinalization(whatsAppReplyDeliveryVisibility(false));
3104
- flushResult.droppedDuplicateMedia += 1;
3105
- } else retained.push(pending);
3106
- pendingMediaOnlyPayloads.push(...retained);
3107
- return flushResult;
2638
+ pendingMediaOnlyPayloads.push(pending);
3108
2639
  },
3109
- flushAll: () => flushWhere(() => true)
2640
+ flushExceptDuplicateMedia,
2641
+ flushAll: () => flushExceptDuplicateMedia()
3110
2642
  };
3111
2643
  }
3112
2644
  function logWhatsAppMediaOnlyFlushResult(result) {
@@ -3116,15 +2648,9 @@ function logWhatsAppMediaOnlyFlushResult(result) {
3116
2648
  }
3117
2649
  function resolveWhatsAppResponsePrefix(params) {
3118
2650
  const configuredResponsePrefix = params.cfg.messages?.responsePrefix;
3119
- return params.pipelineResponsePrefix ?? (configuredResponsePrefix === "auto" ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : configuredResponsePrefix) ?? (params.isSelfChat ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : void 0);
3120
- }
3121
- function buildWhatsAppInboundTransportContext(msg) {
3122
- return {
3123
- ...createWhatsAppReplyTransportContext(msg),
3124
- sendComposing: msg.platform.sendComposing
3125
- };
2651
+ return params.pipelineResponsePrefix ?? (configuredResponsePrefix === void 0 && params.isSelfChat ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : void 0);
3126
2652
  }
3127
- async function prepareWhatsAppInboundContext(params) {
2653
+ async function buildWhatsAppInboundContext(params) {
3128
2654
  const admission = requireWhatsAppInboundAdmission(params.msg);
3129
2655
  const conversationId = admission.conversation.id;
3130
2656
  const conversationKind = admission.conversation.kind;
@@ -3134,20 +2660,18 @@ async function prepareWhatsAppInboundContext(params) {
3134
2660
  sender: entry.sender,
3135
2661
  body: entry.body,
3136
2662
  timestamp: entry.timestamp,
3137
- messageId: entry.id,
3138
- media: entry.media
2663
+ messageId: entry.id
3139
2664
  })),
3140
2665
  limit: params.groupHistory?.length ?? 1
3141
2666
  }) : void 0;
3142
- const media = await toInboundMediaFactsWithMetadata(params.msg.payload.media ? [{
2667
+ const media = toInboundMediaFacts(params.msg.payload.media?.path || params.msg.payload.media?.url ? [{
3143
2668
  path: params.msg.payload.media?.path,
3144
2669
  url: params.msg.payload.media?.url ?? params.msg.payload.media?.path,
3145
- contentType: params.msg.payload.media?.type,
3146
- kind: params.msg.payload.media?.kind
2670
+ contentType: params.msg.payload.media?.type
3147
2671
  }] : void 0, { transcribed: (_entry, index) => params.mediaTranscribedIndexes?.includes(index) === true });
3148
- const control = { messageReceivedHooks: params.suppressMessageReceivedHooks ? "channel" : "core" };
3149
- const inbound = {
2672
+ return buildChannelInboundEventContext({
3150
2673
  channel: "whatsapp",
2674
+ finalize: finalizeInboundContext,
3151
2675
  supplemental: {
3152
2676
  quote: params.visibleReplyTo ? {
3153
2677
  id: params.visibleReplyTo.id,
@@ -3155,13 +2679,11 @@ async function prepareWhatsAppInboundContext(params) {
3155
2679
  sender: params.visibleReplyTo.sender?.label ?? void 0
3156
2680
  } : void 0,
3157
2681
  groupSystemPrompt: params.groupSystemPrompt,
3158
- channelStructuredContext: params.msg.payload.channelStructuredContext
2682
+ untrustedContext: params.msg.payload.untrustedStructuredContext
3159
2683
  },
3160
2684
  media,
3161
- event: {
3162
- id: params.msg.event.id ?? `${conversationId}:${newConnectionId()}`,
3163
- timestamp: params.msg.event.timestamp
3164
- },
2685
+ messageId: params.msg.event.id,
2686
+ timestamp: params.msg.event.timestamp,
3165
2687
  from: conversationId,
3166
2688
  sender: {
3167
2689
  id: params.sender.id ?? params.sender.e164,
@@ -3174,51 +2696,70 @@ async function prepareWhatsAppInboundContext(params) {
3174
2696
  },
3175
2697
  route: {
3176
2698
  agentId: params.route.agentId,
3177
- dmScope: params.route.dmScope,
3178
2699
  accountId: params.route.accountId,
3179
2700
  routeSessionKey: params.route.sessionKey
3180
2701
  },
3181
2702
  reply: {
3182
2703
  to: params.msg.platform.recipientJid,
3183
- originatingTo: conversationId,
3184
- replyToId: params.visibleReplyTo?.id
2704
+ originatingTo: conversationId
3185
2705
  },
3186
2706
  message: {
3187
2707
  body: params.combinedBody,
3188
2708
  bodyForAgent: params.bodyForAgent ?? params.msg.payload.body,
3189
2709
  inboundHistory,
3190
2710
  rawBody: params.rawBody ?? params.msg.payload.body,
3191
- commandBody: params.command?.body ?? params.msg.payload.body
2711
+ commandBody: params.commandBody ?? params.msg.payload.body
3192
2712
  },
3193
- sessionTranscript: { historyLimit: conversationKind === "group" ? params.groupHistoryLimit ?? params.groupHistory?.length ?? 0 : 0 },
3194
- mentions: wasMentioned !== void 0 ? {
3195
- canDetectMention: conversationKind === "group",
3196
- wasMentioned,
3197
- requireMention: params.msg.groupMention?.requireMention
3198
- } : void 0,
3199
- command: params.command,
3200
- context: {
3201
- transcript: params.transcript,
3202
- groupSubject: params.msg.group?.subject ?? null,
3203
- groupMembers: formatGroupMembers({
2713
+ access: {
2714
+ ...wasMentioned !== void 0 ? { mentions: {
2715
+ canDetectMention: conversationKind === "group",
2716
+ wasMentioned,
2717
+ requireMention: params.msg.groupMention?.requireMention
2718
+ } } : {},
2719
+ commands: { authorized: params.commandAuthorized }
2720
+ },
2721
+ commandTurn: params.commandTurn,
2722
+ extra: {
2723
+ Transcript: params.transcript,
2724
+ GroupSubject: params.msg.group?.subject,
2725
+ GroupMembers: formatGroupMembers({
3204
2726
  participants: params.msg.group?.participants,
3205
2727
  roster: params.groupMemberRoster,
3206
2728
  fallbackE164: params.sender.e164
3207
2729
  }),
3208
- senderE164: params.sender.e164,
3209
- replyThreading: params.replyThreading,
3210
- location: params.msg.payload.location
2730
+ SenderE164: params.sender.e164,
2731
+ CommandSource: params.commandSource ?? (params.commandTurn?.source === "native" || params.commandTurn?.source === "text" ? params.commandTurn.source : void 0),
2732
+ ReplyThreading: params.replyThreading,
2733
+ SuppressMessageReceivedHooks: params.suppressMessageReceivedHooks,
2734
+ ...params.msg.payload.location ? toLocationContext(params.msg.payload.location) : {}
3211
2735
  }
3212
- };
3213
- const projected = projectPreparedChannelInbound({
3214
- inbound,
3215
- control
3216
2736
  });
3217
- return {
3218
- inbound,
3219
- control,
3220
- turnInput: projected.input,
3221
- ctxPayload: projected.context
2737
+ }
2738
+ function normalizeCommandTurnFromContext(value) {
2739
+ if (!value || typeof value !== "object") return;
2740
+ const record = value;
2741
+ const kind = record.kind;
2742
+ const source = record.source;
2743
+ if (kind === "native" && source === "native" && typeof record.authorized === "boolean") return {
2744
+ kind: "native",
2745
+ source: "native",
2746
+ authorized: record.authorized,
2747
+ commandName: typeof record.commandName === "string" ? record.commandName : void 0,
2748
+ body: typeof record.body === "string" ? record.body : void 0
2749
+ };
2750
+ if (kind === "text-slash" && source === "text" && typeof record.authorized === "boolean") return {
2751
+ kind: "text-slash",
2752
+ source: "text",
2753
+ authorized: record.authorized,
2754
+ commandName: typeof record.commandName === "string" ? record.commandName : void 0,
2755
+ body: typeof record.body === "string" ? record.body : void 0
2756
+ };
2757
+ if (kind === "normal" && source === "message") return {
2758
+ kind: "normal",
2759
+ source: "message",
2760
+ authorized: false,
2761
+ commandName: typeof record.commandName === "string" ? record.commandName : void 0,
2762
+ body: typeof record.body === "string" ? record.body : void 0
3222
2763
  };
3223
2764
  }
3224
2765
  function resolveWhatsAppDmRouteTarget(params) {
@@ -3251,9 +2792,16 @@ function updateWhatsAppMainLastRoute(params) {
3251
2792
  }
3252
2793
  if (params.dmRouteTarget && inboundLastRouteSessionKey === params.route.mainSessionKey && params.pinnedMainDmRecipient) logVerbose$1(`Skipping main-session last route update for ${params.dmRouteTarget} (pinned owner ${params.pinnedMainDmRecipient})`);
3253
2794
  }
3254
- function createWhatsAppReplyPlan(params) {
3255
- const conversationId = params.inbound.conversation.id;
2795
+ async function dispatchWhatsAppBufferedReply(params) {
2796
+ const admission = requireWhatsAppInboundAdmission(params.msg);
2797
+ const conversationId = admission.conversation.id;
2798
+ const conversationKind = admission.conversation.kind;
3256
2799
  const statusReactionController = params.statusReactionController ?? null;
2800
+ const statusReactionTiming = {
2801
+ ...DEFAULT_TIMING,
2802
+ ...params.cfg.messages?.statusReactions?.timing
2803
+ };
2804
+ const removeAckAfterReply = params.cfg.messages?.removeAckAfterReply ?? false;
3257
2805
  const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp");
3258
2806
  const chunkMode = resolveChunkMode(params.cfg, "whatsapp", params.route.accountId);
3259
2807
  const tableMode = resolveMarkdownTableMode$1({
@@ -3262,85 +2810,72 @@ function createWhatsAppReplyPlan(params) {
3262
2810
  accountId: params.route.accountId
3263
2811
  });
3264
2812
  const mediaLocalRoots = getAgentScopedMediaLocalRoots(params.cfg, params.route.agentId);
3265
- const replyPolicy = resolveWhatsAppInboundReplyPolicy({
2813
+ const sourceReplyChatType = typeof params.context.ChatType === "string" ? params.context.ChatType : conversationKind;
2814
+ const sourceReplyCommandSource = params.context.CommandSource === "native" || params.context.CommandSource === "text" ? params.context.CommandSource : void 0;
2815
+ const sourceReplyCommandTurn = normalizeCommandTurnFromContext(params.context.CommandTurn);
2816
+ const sourceReplyCommandAuthorized = typeof params.context.CommandAuthorized === "boolean" ? params.context.CommandAuthorized : void 0;
2817
+ const sourceReplyDeliveryMode = sourceReplyChatType === "group" || sourceReplyChatType === "channel" ? resolveChannelMessageSourceReplyDeliveryMode({
3266
2818
  cfg: params.cfg,
3267
- ctx: params.context,
3268
- blockStreamingEnabled: resolveChannelStreamingBlockEnabled(params.cfg.channels?.whatsapp)
3269
- });
2819
+ ctx: {
2820
+ ChatType: sourceReplyChatType,
2821
+ CommandTurn: sourceReplyCommandTurn,
2822
+ CommandSource: sourceReplyCommandSource,
2823
+ CommandAuthorized: sourceReplyCommandAuthorized
2824
+ }
2825
+ }) : void 0;
2826
+ const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
2827
+ const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveWhatsAppDisableBlockStreaming(params.cfg);
3270
2828
  let didSendReply = false;
3271
2829
  let didLogHeartbeatStrip = false;
3272
- const recordDeliveredPayload = (payload) => {
3273
- didSendReply = true;
3274
- const shouldLog = payload.text ? true : void 0;
3275
- params.rememberSentText(payload.text, {
3276
- combinedBody: params.context.Body,
3277
- combinedBodySessionKey: params.route.sessionKey,
3278
- conversationId,
3279
- logVerboseMessage: shouldLog
3280
- });
3281
- if (shouldLogVerbose$1()) {
3282
- const reply = resolveSendableOutboundReplyParts(payload);
3283
- logVerbose$1(`Reply body: ${payload.text != null ? reply.text : "<media>"}${reply.hasMedia ? " (media)" : ""} -> ${conversationId}`);
3284
- }
3285
- };
3286
- const deliverNormalizedPayload = async (normalizedDeliveryPayload, info, options) => {
2830
+ const deliverNormalizedPayload = async (normalizedDeliveryPayload, info) => {
3287
2831
  const reply = resolveSendableOutboundReplyParts(normalizedDeliveryPayload);
3288
2832
  if (!reply.hasMedia && !reply.text.trim()) return whatsAppReplyDeliveryVisibility(false);
3289
- let delivery;
3290
- try {
3291
- delivery = await params.deliverReply({
3292
- replyResult: normalizedDeliveryPayload,
3293
- normalizedReplyResult: normalizedDeliveryPayload,
3294
- transport: params.transport,
3295
- mediaLocalRoots,
3296
- maxMediaBytes: params.maxMediaBytes,
3297
- textLimit,
3298
- chunkMode,
3299
- replyLogger: params.replyLogger,
3300
- connectionId: params.connectionId,
3301
- skipLog: false,
3302
- tableMode
3303
- });
3304
- } catch (error) {
3305
- if (isWhatsAppVisibleDeliveryError(error) && !isChannelPartialDeliveryError(error)) throw createChannelPartialDeliveryError(error, {
3306
- content: reply.text,
3307
- visibleReplySent: true
3308
- });
3309
- throw error;
3310
- }
3311
- const result = createWhatsAppChannelDeliveryResult({
3312
- content: reply.text,
3313
- delivery
3314
- });
3315
- if (!result.visibleReplySent) {
2833
+ if (!(await params.deliverReply({
2834
+ replyResult: normalizedDeliveryPayload,
2835
+ normalizedReplyResult: normalizedDeliveryPayload,
2836
+ msg: params.msg,
2837
+ mediaLocalRoots,
2838
+ maxMediaBytes: params.maxMediaBytes,
2839
+ textLimit,
2840
+ chunkMode,
2841
+ replyLogger: params.replyLogger,
2842
+ connectionId: params.connectionId,
2843
+ skipLog: false,
2844
+ tableMode
2845
+ })).providerAccepted) {
3316
2846
  params.replyLogger.warn({
3317
- correlationId: params.transport.correlationId ?? null,
2847
+ correlationId: params.msg.event.id ?? null,
3318
2848
  connectionId: params.connectionId,
3319
2849
  conversationId,
3320
- chatId: params.transport.chatJid,
2850
+ chatId: params.msg.platform.chatJid,
3321
2851
  to: conversationId,
3322
- from: params.transport.recipientJid,
2852
+ from: params.msg.platform.recipientJid,
3323
2853
  replyKind: info.kind
3324
2854
  }, "auto-reply was not accepted by WhatsApp provider");
3325
- return result;
2855
+ return whatsAppReplyDeliveryVisibility(false);
3326
2856
  }
3327
- if (options?.recordDelivery !== false) try {
3328
- recordDeliveredPayload(normalizedDeliveryPayload);
3329
- } catch (error) {
3330
- throw createChannelPartialDeliveryError(error, {
3331
- ...result,
3332
- visibleReplySent: true
3333
- });
2857
+ didSendReply = true;
2858
+ const shouldLog = normalizedDeliveryPayload.text ? true : void 0;
2859
+ params.rememberSentText(normalizedDeliveryPayload.text, {
2860
+ combinedBody: params.context.Body,
2861
+ combinedBodySessionKey: params.route.sessionKey,
2862
+ logVerboseMessage: shouldLog
2863
+ });
2864
+ const fromDisplay = conversationId;
2865
+ if (shouldLogVerbose$1()) {
2866
+ const preview = normalizedDeliveryPayload.text != null ? reply.text : "<media>";
2867
+ logVerbose$1(`Reply body: ${preview}${reply.hasMedia ? " (media)" : ""} -> ${fromDisplay}`);
3334
2868
  }
3335
- return result;
2869
+ return whatsAppReplyDeliveryVisibility(true);
3336
2870
  };
3337
2871
  const mediaOnlyCoalescer = createWhatsAppMediaOnlyReplyCoalescer({ deliver: async (pending) => {
3338
2872
  return await deliverNormalizedPayload(pending.payload, pending.info);
3339
2873
  } });
3340
- return {
3341
- afterRecord: () => {
3342
- if (statusReactionController) statusReactionController.setThinking();
3343
- },
2874
+ if (statusReactionController) statusReactionController.setThinking();
2875
+ const dispatchResult = await dispatchReplyWithBufferedBlockDispatcher({
2876
+ ctx: params.context,
2877
+ cfg: params.cfg,
2878
+ replyResolver: params.replyResolver,
3344
2879
  dispatcherOptions: {
3345
2880
  ...params.replyPipeline,
3346
2881
  onHeartbeatStrip: () => {
@@ -3349,140 +2884,168 @@ function createWhatsAppReplyPlan(params) {
3349
2884
  logVerbose$1("Stripped stray HEARTBEAT_OK token from web reply");
3350
2885
  }
3351
2886
  },
3352
- onSettled: async () => {
3353
- const flushResult = await mediaOnlyCoalescer.flushAll();
3354
- logWhatsAppMediaOnlyFlushResult(flushResult);
3355
- return whatsAppReplyDeliveryVisibility(didSendReply || flushResult.delivered > 0);
3356
- },
3357
- onReplyStart: params.transport.sendComposing
3358
- },
3359
- delivery: {
3360
- observeMessageSent: true,
3361
- preparePayload: async (payload, info) => {
2887
+ deliver: async (payload, info) => {
3362
2888
  const deliveryPayload = resolveWhatsAppDeliverablePayload(payload, info);
3363
- if (!deliveryPayload) return null;
2889
+ if (!deliveryPayload) return whatsAppReplyDeliveryVisibility(false);
3364
2890
  const normalizedOutboundPayload = normalizeWhatsAppOutboundPayload(deliveryPayload, { normalizeText: normalizeWhatsAppPayloadTextPreservingIndentation });
3365
2891
  const normalizedDeliveryPayload = deliveryPayload.text === void 0 ? {
3366
2892
  ...normalizedOutboundPayload,
3367
2893
  text: void 0
3368
2894
  } : normalizedOutboundPayload;
3369
2895
  const reply = resolveSendableOutboundReplyParts(normalizedDeliveryPayload);
3370
- if (!reply.hasMedia && !reply.text.trim()) return normalizedDeliveryPayload;
3371
- const mediaUrls = getWhatsAppPayloadMediaUrls(normalizedDeliveryPayload);
3372
- logWhatsAppMediaOnlyFlushResult(reply.hasMedia ? shouldDeferWhatsAppMediaOnlyPayload({
3373
- info,
3374
- mediaUrls,
3375
- reply
3376
- }) ? {
3377
- delivered: 0,
3378
- droppedDuplicateMedia: 0
3379
- } : await mediaOnlyCoalescer.flushNonDuplicateMedia(mediaUrls) : await mediaOnlyCoalescer.flushAll());
3380
- return normalizedDeliveryPayload;
3381
- },
3382
- durable: (payload, info) => {
3383
- const reply = resolveSendableOutboundReplyParts(payload);
3384
- if (reply.hasMedia || !reply.text.trim()) return false;
3385
- return {
3386
- to: conversationId,
3387
- replyToId: resolveWhatsAppDurableReplyToId({
3388
- context: params.context,
3389
- info,
3390
- currentMessageId: params.transport.correlationId,
3391
- payload
3392
- }),
3393
- formatting: {
3394
- textLimit,
3395
- tableMode,
3396
- chunkMode
3397
- }
3398
- };
3399
- },
3400
- deliver: async (payload, info) => {
3401
- const normalizedDeliveryPayload = payload;
3402
- const reply = resolveSendableOutboundReplyParts(normalizedDeliveryPayload);
3403
2896
  if (!reply.hasMedia && !reply.text.trim()) return whatsAppReplyDeliveryVisibility(false);
3404
- if (!reply.hasMedia) return await deliverNormalizedPayload(normalizedDeliveryPayload, info, { recordDelivery: false });
2897
+ if (!reply.hasMedia) {
2898
+ const flushResult = await mediaOnlyCoalescer.flushAll();
2899
+ logWhatsAppMediaOnlyFlushResult(flushResult);
2900
+ try {
2901
+ const durable = await deliverInboundReplyWithMessageSendContext({
2902
+ cfg: params.cfg,
2903
+ channel: "whatsapp",
2904
+ accountId: params.route.accountId,
2905
+ agentId: params.route.agentId,
2906
+ ctxPayload: params.context,
2907
+ payload: normalizedDeliveryPayload,
2908
+ info,
2909
+ to: conversationId,
2910
+ replyToId: resolveWhatsAppDurableReplyToId({
2911
+ context: params.context,
2912
+ info,
2913
+ msg: params.msg,
2914
+ payload: normalizedDeliveryPayload
2915
+ }),
2916
+ formatting: {
2917
+ textLimit,
2918
+ tableMode,
2919
+ chunkMode
2920
+ }
2921
+ });
2922
+ if (durable.status === "failed") {
2923
+ if (durable.sentBeforeError === true) throw markWhatsAppVisibleDeliveryError(durable.error);
2924
+ throw durable.error;
2925
+ }
2926
+ if (durable.status === "handled_visible") {
2927
+ didSendReply = true;
2928
+ const shouldLog = normalizedDeliveryPayload.text ? true : void 0;
2929
+ params.rememberSentText(normalizedDeliveryPayload.text, {
2930
+ combinedBody: params.context.Body,
2931
+ combinedBodySessionKey: params.route.sessionKey,
2932
+ logVerboseMessage: shouldLog
2933
+ });
2934
+ return whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
2935
+ }
2936
+ if (durable.status === "handled_no_send") return flushResult.delivered > 0 ? whatsAppReplyDeliveryVisibility(true) : whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
2937
+ const delivery = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
2938
+ return flushResult.delivered > 0 && !delivery.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : delivery;
2939
+ } catch (error) {
2940
+ throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
2941
+ }
2942
+ }
3405
2943
  const mediaUrls = getWhatsAppPayloadMediaUrls(normalizedDeliveryPayload);
3406
2944
  if (shouldDeferWhatsAppMediaOnlyPayload({
3407
2945
  info,
3408
2946
  mediaUrls,
3409
2947
  reply
3410
- })) return {
3411
- visibleReplySent: false,
3412
- finalization: mediaOnlyCoalescer.defer({
2948
+ })) {
2949
+ mediaOnlyCoalescer.defer({
3413
2950
  info,
3414
2951
  mediaUrls,
3415
2952
  payload: normalizedDeliveryPayload
3416
- })
3417
- };
2953
+ });
2954
+ return whatsAppReplyDeliveryVisibility(false);
2955
+ }
2956
+ const flushResult = await mediaOnlyCoalescer.flushExceptDuplicateMedia(mediaUrls);
2957
+ logWhatsAppMediaOnlyFlushResult(flushResult);
3418
2958
  try {
3419
- const result = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
3420
- if (result.visibleReplySent) logWhatsAppMediaOnlyFlushResult(mediaOnlyCoalescer.dropDuplicateMedia(mediaUrls));
3421
- return result;
2959
+ const delivery = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
2960
+ return flushResult.delivered > 0 && !delivery.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : delivery;
3422
2961
  } catch (error) {
3423
- if (isWhatsAppVisibleDeliveryError(error)) logWhatsAppMediaOnlyFlushResult(mediaOnlyCoalescer.dropDuplicateMedia(mediaUrls));
3424
- throw error;
2962
+ throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
3425
2963
  }
3426
2964
  },
3427
- onDelivered: (payload, _info, result) => {
3428
- if (!resolveSendableOutboundReplyParts(payload).hasMedia && result?.visibleReplySent === true) recordDeliveredPayload(payload);
2965
+ onSettled: async () => {
2966
+ const flushResult = await mediaOnlyCoalescer.flushAll();
2967
+ logWhatsAppMediaOnlyFlushResult(flushResult);
2968
+ return whatsAppReplyDeliveryVisibility(flushResult.delivered > 0);
3429
2969
  },
2970
+ onReplyStart: params.msg.platform.sendComposing,
2971
+ ...statusReactionController ? {
2972
+ onCompactionStart: async () => {
2973
+ await statusReactionController.setCompacting();
2974
+ },
2975
+ onCompactionEnd: async () => {
2976
+ statusReactionController.cancelPending();
2977
+ await statusReactionController.setThinking();
2978
+ }
2979
+ } : {},
3430
2980
  onError: (err, info) => {
3431
- if (didSendReply) markWhatsAppVisibleDeliveryError(err);
3432
2981
  logWhatsAppReplyDeliveryError({
3433
2982
  err,
3434
2983
  info,
3435
2984
  connectionId: params.connectionId,
3436
- transport: params.transport,
2985
+ msg: params.msg,
3437
2986
  replyLogger: params.replyLogger
3438
2987
  });
3439
2988
  }
3440
2989
  },
3441
2990
  replyOptions: {
3442
- ...params.turnAdoptionLifecycle ? { turnAdoptionLifecycle: params.turnAdoptionLifecycle } : {},
3443
- suppressTyping: replyPolicy.suppressTyping,
3444
- disableBlockStreaming: replyPolicy.disableBlockStreaming,
3445
- ...replyPolicy.sourceReplyDeliveryMode ? { sourceReplyDeliveryMode: replyPolicy.sourceReplyDeliveryMode } : {},
2991
+ suppressTyping: sourceRepliesAreToolOnly && conversationKind === "group" && !(params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned),
2992
+ disableBlockStreaming,
2993
+ ...sourceReplyDeliveryMode ? { sourceReplyDeliveryMode } : {},
3446
2994
  onModelSelected: params.onModelSelected,
3447
- ...statusReactionController ? {
3448
- onToolStart: async (payload) => {
3449
- const toolName = payload.name?.trim();
3450
- if (toolName) await statusReactionController.setTool(toolName);
3451
- },
3452
- onCompactionStart: async () => {
3453
- await statusReactionController.setCompacting();
3454
- },
3455
- onCompactionEnd: async () => {
3456
- statusReactionController.cancelPending();
3457
- await statusReactionController.setThinking();
3458
- }
3459
- } : {}
3460
- },
3461
- replyResolver: params.replyResolver,
3462
- finalize: (dispatchResult) => {
3463
- const didQueueVisibleReply = hasVisibleInboundReplyDispatch(dispatchResult);
3464
- const didDeliverVisibleReply = didSendReply || dispatchResult.observedReplyDelivery === true;
3465
- if (!didQueueVisibleReply && !didDeliverVisibleReply) {
3466
- if (statusReactionController) finalizeWhatsAppStatusReaction({
3467
- controller: statusReactionController,
3468
- outcome: "error"
3469
- });
3470
- if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
3471
- logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
3472
- return false;
3473
- }
3474
- if (statusReactionController) finalizeWhatsAppStatusReaction({
3475
- controller: statusReactionController,
3476
- outcome: didDeliverVisibleReply ? "done" : "error"
3477
- });
3478
- if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
3479
- return didDeliverVisibleReply;
2995
+ ...statusReactionController ? { onToolStart: async (payload) => {
2996
+ const toolName = payload.name?.trim();
2997
+ if (toolName) await statusReactionController.setTool(toolName);
2998
+ } } : {}
3480
2999
  }
3481
- };
3000
+ });
3001
+ const didQueueVisibleReply = hasVisibleInboundReplyDispatch(dispatchResult);
3002
+ const didDeliverVisibleReply = didSendReply || dispatchResult.observedReplyDelivery === true;
3003
+ if (!didQueueVisibleReply) {
3004
+ if (statusReactionController) finalizeWhatsAppStatusReaction({
3005
+ controller: statusReactionController,
3006
+ outcome: "error",
3007
+ hasFinalResponse: false,
3008
+ removeAckAfterReply,
3009
+ timing: statusReactionTiming
3010
+ });
3011
+ if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
3012
+ logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
3013
+ return false;
3014
+ }
3015
+ if (statusReactionController) finalizeWhatsAppStatusReaction({
3016
+ controller: statusReactionController,
3017
+ outcome: didDeliverVisibleReply ? "done" : "error",
3018
+ hasFinalResponse: didDeliverVisibleReply,
3019
+ removeAckAfterReply,
3020
+ timing: statusReactionTiming
3021
+ });
3022
+ if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
3023
+ return didDeliverVisibleReply;
3482
3024
  }
3483
3025
  async function finalizeWhatsAppStatusReaction(params) {
3484
- if (params.outcome === "done") await params.controller.setDone();
3485
- else await params.controller.setError();
3026
+ if (params.outcome === "done") {
3027
+ await params.controller.setDone();
3028
+ if (params.removeAckAfterReply) {
3029
+ await new Promise((resolve) => {
3030
+ setTimeout(resolve, params.timing.doneHoldMs);
3031
+ });
3032
+ await params.controller.clear();
3033
+ } else await params.controller.restoreInitial();
3034
+ return;
3035
+ }
3036
+ await params.controller.setError();
3037
+ if (params.hasFinalResponse) {
3038
+ if (params.removeAckAfterReply) {
3039
+ await new Promise((resolve) => {
3040
+ setTimeout(resolve, params.timing.errorHoldMs);
3041
+ });
3042
+ await params.controller.clear();
3043
+ } else await params.controller.restoreInitial();
3044
+ return;
3045
+ }
3046
+ if (params.removeAckAfterReply) await new Promise((resolve) => {
3047
+ setTimeout(resolve, params.timing.errorHoldMs);
3048
+ });
3486
3049
  await params.controller.restoreInitial();
3487
3050
  }
3488
3051
  //#endregion
@@ -3496,7 +3059,7 @@ function resolveIdentityNamePrefix(cfg, agentId) {
3496
3059
  return identityName ? `[${identityName}]` : void 0;
3497
3060
  }
3498
3061
  function resolveMessagePrefix(cfg, agentId, opts) {
3499
- const configured = opts?.configured;
3062
+ const configured = opts?.configured ?? cfg.messages?.messagePrefix;
3500
3063
  if (configured !== void 0) return configured;
3501
3064
  if (opts?.hasAllowFrom === true) return "";
3502
3065
  return resolveIdentityNamePrefix(cfg, agentId) ?? opts?.fallback ?? "[openclaw]";
@@ -3513,7 +3076,7 @@ function formatReplyContext(msg) {
3513
3076
  function buildInboundLine(params) {
3514
3077
  const { cfg, msg, agentId, previousTimestamp, envelope } = params;
3515
3078
  const messagePrefix = resolveMessagePrefix(cfg, agentId, {
3516
- configured: cfg.channels?.whatsapp?.responsePrefix,
3079
+ configured: cfg.channels?.whatsapp?.messagePrefix,
3517
3080
  hasAllowFrom: (cfg.channels?.whatsapp?.allowFrom?.length ?? 0) > 0
3518
3081
  });
3519
3082
  const admission = requireWhatsAppInboundAdmission(msg);
@@ -3543,30 +3106,66 @@ function buildInboundLine(params) {
3543
3106
  //#region extensions/whatsapp/src/auto-reply/monitor/status-reaction.ts
3544
3107
  async function createWhatsAppStatusReactionController(params) {
3545
3108
  if (!params.msg.event.id) return null;
3546
- if ((params.cfg.messages?.statusReactions)?.enabled !== true) return null;
3547
- const eligibility = await resolveWhatsAppReactionEligibility({
3109
+ const statusReactionsConfig = params.cfg.messages?.statusReactions;
3110
+ if (statusReactionsConfig?.enabled !== true) return null;
3111
+ const admission = requireWhatsAppInboundAdmission(params.msg);
3112
+ const accountId = admission.accountId;
3113
+ if (resolveWhatsAppReactionLevel({
3114
+ cfg: params.cfg,
3115
+ accountId
3116
+ }).level === "off") return null;
3117
+ const ackConfig = params.cfg.channels?.whatsapp?.ackReaction;
3118
+ const ackEmoji = resolveWhatsAppAckEmoji({
3548
3119
  cfg: params.cfg,
3549
- msg: params.msg,
3550
3120
  agentId: params.agentId,
3551
- sessionKey: params.sessionKey,
3552
- verbose: params.verbose
3121
+ ackConfig
3553
3122
  });
3554
- if (eligibility.status === "disabled") return null;
3555
- const { chatId, messageId, emoji: initialEmoji, reactionOptions } = eligibility;
3123
+ if (!ackEmoji) return null;
3124
+ const directEnabled = ackConfig?.direct ?? true;
3125
+ const groupMode = ackConfig?.group ?? "mentions";
3126
+ const isGroup = admission.conversation.kind === "group";
3127
+ const conversationIdForCheck = admission.conversation.id;
3128
+ const activation = isGroup ? await resolveGroupActivationFor({
3129
+ cfg: params.cfg,
3130
+ accountId,
3131
+ agentId: params.agentId,
3132
+ sessionKey: params.sessionKey,
3133
+ conversationId: conversationIdForCheck
3134
+ }) : null;
3135
+ if (!shouldAckReactionForWhatsApp({
3136
+ emoji: ackEmoji,
3137
+ isDirect: admission.conversation.kind === "direct",
3138
+ isGroup,
3139
+ directEnabled,
3140
+ groupMode,
3141
+ wasMentioned: (params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned) === true,
3142
+ groupActivated: activation === "always"
3143
+ })) return null;
3144
+ const sender = getSenderIdentity(params.msg);
3145
+ const reactionOptions = {
3146
+ verbose: params.verbose,
3147
+ fromMe: false,
3148
+ ...sender.jid ? { participant: sender.jid } : {},
3149
+ accountId,
3150
+ cfg: params.cfg
3151
+ };
3152
+ const chatId = params.msg.platform.chatJid;
3153
+ const msgId = params.msg.event.id;
3556
3154
  return createStatusReactionController({
3557
3155
  enabled: true,
3558
3156
  adapter: {
3559
3157
  setReaction: async (emoji) => {
3560
- await sendReactionWhatsApp(chatId, messageId, emoji, reactionOptions);
3158
+ await sendReactionWhatsApp(chatId, msgId, emoji, reactionOptions);
3561
3159
  },
3562
3160
  clearReaction: async () => {
3563
- await sendReactionWhatsApp(chatId, messageId, "", reactionOptions);
3161
+ await sendReactionWhatsApp(chatId, msgId, "", reactionOptions);
3564
3162
  }
3565
3163
  },
3566
- initialEmoji,
3567
- emojis: void 0,
3164
+ initialEmoji: ackEmoji,
3165
+ emojis: statusReactionsConfig.emojis,
3166
+ timing: statusReactionsConfig.timing,
3568
3167
  onError: (err) => {
3569
- logVerbose(`WhatsApp status-reaction error for chat ${chatId}/${messageId}: ${String(err)}`);
3168
+ logVerbose(`WhatsApp status-reaction error for chat ${chatId}/${msgId}: ${String(err)}`);
3570
3169
  }
3571
3170
  });
3572
3171
  }
@@ -3633,16 +3232,13 @@ async function processMessage(params) {
3633
3232
  sessionKey: params.route.sessionKey
3634
3233
  });
3635
3234
  let audioTranscript = params.preflightAudioTranscript ?? void 0;
3636
- const hasAudioBody = (params.msg.payload.media?.kind === "audio" || params.msg.payload.media?.type?.startsWith("audio/") === true) && !params.msg.payload.body.trim();
3235
+ const hasAudioBody = params.msg.payload.media?.type?.startsWith("audio/") === true && params.msg.payload.body === "<media:audio>";
3637
3236
  if (params.preflightAudioTranscript === void 0 && hasAudioBody && params.msg.payload.media?.path) try {
3638
- const { transcribeFirstAudio } = await import("./audio-preflight.runtime-C_glQhZY.js");
3237
+ const { transcribeFirstAudio } = await import("./audio-preflight.runtime-C_glQhZY.mjs");
3639
3238
  audioTranscript = await transcribeFirstAudio({
3640
3239
  ctx: {
3641
- media: [{
3642
- path: params.msg.payload.media.path,
3643
- contentType: params.msg.payload.media.type,
3644
- kind: params.msg.payload.media.kind ?? void 0
3645
- }],
3240
+ MediaPaths: [params.msg.payload.media?.path],
3241
+ MediaTypes: params.msg.payload.media?.type ? [params.msg.payload.media?.type] : void 0,
3646
3242
  From: conversationId,
3647
3243
  To: params.msg.platform.recipientJid,
3648
3244
  Provider: "whatsapp",
@@ -3654,7 +3250,7 @@ async function processMessage(params) {
3654
3250
  cfg: params.cfg
3655
3251
  });
3656
3252
  } catch {
3657
- if (shouldLogVerbose$1()) logVerbose$1("whatsapp: audio preflight transcription failed, keeping structured audio");
3253
+ if (shouldLogVerbose$1()) logVerbose$1("whatsapp: audio preflight transcription failed, using placeholder");
3658
3254
  }
3659
3255
  const msgForAgent = audioTranscript !== void 0 ? {
3660
3256
  ...params.msg,
@@ -3688,27 +3284,29 @@ async function processMessage(params) {
3688
3284
  }) : void 0;
3689
3285
  if (conversationKind === "group") {
3690
3286
  const history = visibleGroupHistory ?? [];
3691
- if (history.length > 0) combinedBody = buildHistoryContextFromEntries({
3692
- entries: history.map((m) => ({
3287
+ if (history.length > 0) {
3288
+ const historyEntries = history.map((m) => ({
3693
3289
  sender: m.sender,
3694
3290
  body: m.body,
3695
- timestamp: m.timestamp,
3696
- media: m.media
3697
- })),
3698
- currentMessage: combinedBody,
3699
- excludeLast: false,
3700
- formatEntry: (entry) => {
3701
- return formatInboundEnvelope$1({
3702
- channel: "WhatsApp",
3703
- from: conversationId,
3704
- timestamp: entry.timestamp,
3705
- body: [entry.body, formatMediaPlaceholderText(entry.media ?? [])].filter(Boolean).join("\n"),
3706
- chatType: "group",
3707
- senderLabel: entry.sender,
3708
- envelope: envelopeOptions
3709
- });
3710
- }
3711
- });
3291
+ timestamp: m.timestamp
3292
+ }));
3293
+ combinedBody = buildHistoryContextFromEntries({
3294
+ entries: historyEntries,
3295
+ currentMessage: combinedBody,
3296
+ excludeLast: false,
3297
+ formatEntry: (entry) => {
3298
+ return formatInboundEnvelope$1({
3299
+ channel: "WhatsApp",
3300
+ from: conversationId,
3301
+ timestamp: entry.timestamp,
3302
+ body: entry.body,
3303
+ chatType: "group",
3304
+ senderLabel: entry.sender,
3305
+ envelope: envelopeOptions
3306
+ });
3307
+ }
3308
+ });
3309
+ }
3712
3310
  shouldClearGroupHistory = !(params.suppressGroupHistoryClear ?? false);
3713
3311
  }
3714
3312
  const combinedEchoKey = params.buildCombinedEchoKey({
@@ -3760,13 +3358,24 @@ async function processMessage(params) {
3760
3358
  normalizeE164
3761
3359
  });
3762
3360
  const shouldCheckCommandAuth = shouldComputeCommandAuthorized(commandBody, params.cfg);
3763
- const isTextCommand = isControlCommandMessage(commandBody, params.cfg);
3361
+ const isTextCommand = isControlCommandMessage$1(commandBody, params.cfg);
3764
3362
  const commandAuthorized = shouldCheckCommandAuth ? await resolveWhatsAppCommandAuthorized({
3765
3363
  cfg: params.cfg,
3766
3364
  msg: params.msg,
3767
3365
  policy: inboundPolicy,
3768
3366
  authDir: account.authDir
3769
3367
  }) : void 0;
3368
+ const commandTurn = isTextCommand ? {
3369
+ kind: "text-slash",
3370
+ source: "text",
3371
+ authorized: Boolean(commandAuthorized),
3372
+ body: commandBody
3373
+ } : {
3374
+ kind: "normal",
3375
+ source: "message",
3376
+ authorized: false,
3377
+ body: commandBody
3378
+ };
3770
3379
  const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
3771
3380
  cfg: params.cfg,
3772
3381
  agentId: params.route.agentId,
@@ -3787,17 +3396,13 @@ async function processMessage(params) {
3787
3396
  accountConfig: account,
3788
3397
  peerId: dmRouteTarget ?? conversationId
3789
3398
  });
3790
- const commandAuthorization = commandAuthorized === void 0 ? { kind: "not_checked" } : commandAuthorized ? { kind: "authorized" } : { kind: "denied" };
3791
- const { inbound, turnInput, ctxPayload } = await prepareWhatsAppInboundContext({
3399
+ const ctxPayload = await buildWhatsAppInboundContext({
3792
3400
  bodyForAgent: msgForAgent.payload.body,
3793
3401
  combinedBody,
3794
- command: {
3795
- kind: isTextCommand ? "text-slash" : "normal",
3796
- body: commandBody,
3797
- authorization: commandAuthorization
3798
- },
3402
+ commandBody,
3403
+ commandAuthorized,
3404
+ commandTurn,
3799
3405
  groupHistory: visibleGroupHistory,
3800
- groupHistoryLimit: params.groupHistoryLimit,
3801
3406
  groupMemberRoster: params.groupMemberNames.get(params.groupHistoryKey),
3802
3407
  groupSystemPrompt: conversationSystemPrompt,
3803
3408
  msg: params.msg,
@@ -3814,9 +3419,6 @@ async function processMessage(params) {
3814
3419
  visibleReplyTo: visibleReplyTo ?? void 0,
3815
3420
  suppressMessageReceivedHooks: true
3816
3421
  });
3817
- const transport = buildWhatsAppInboundTransportContext(params.msg);
3818
- const ingressLifecycle = resolveWhatsAppIngressLifecycle(params.msg);
3819
- const turnAdoptionLifecycle = ingressLifecycle ? bindIngressLifecycleToReplyOptions(ingressLifecycle).turnAdoptionLifecycle : void 0;
3820
3422
  emitWhatsAppMessageReceivedHooksIfEnabled({
3821
3423
  cfg: params.cfg,
3822
3424
  ctx: ctxPayload,
@@ -3837,14 +3439,19 @@ async function processMessage(params) {
3837
3439
  updateLastRoute: updateLastRouteInBackground,
3838
3440
  warn: params.replyLogger.warn.bind(params.replyLogger)
3839
3441
  });
3840
- let finalizeReply;
3841
3442
  const turnResult = await runChannelInboundEvent({
3842
3443
  channel: "whatsapp",
3843
3444
  accountId: params.route.accountId,
3844
- raw: inbound,
3845
- ...turnAdoptionLifecycle ? { turnAdoptionLifecycle } : {},
3445
+ raw: params.msg,
3846
3446
  adapter: {
3847
- ingest: () => turnInput,
3447
+ ingest: () => ({
3448
+ id: params.msg.event.id ?? `${conversationId}:${Date.now()}`,
3449
+ timestamp: params.msg.event.timestamp,
3450
+ rawText: ctxPayload.RawBody ?? "",
3451
+ textForAgent: ctxPayload.BodyForAgent,
3452
+ textForCommands: ctxPayload.CommandBody,
3453
+ raw: params.msg
3454
+ }),
3848
3455
  preflight: () => {
3849
3456
  const reason = admission.ingress.reasonCode;
3850
3457
  if (admission.ingress.admission === "dispatch") return { admission: {
@@ -3865,8 +3472,26 @@ async function processMessage(params) {
3865
3472
  recordHistory: false
3866
3473
  } };
3867
3474
  },
3868
- resolveTurn: () => {
3869
- const { finalize, ...replyPlan } = createWhatsAppReplyPlan({
3475
+ resolveTurn: () => ({
3476
+ channel: "whatsapp",
3477
+ accountId: params.route.accountId,
3478
+ routeSessionKey: params.route.sessionKey,
3479
+ storePath,
3480
+ ctxPayload,
3481
+ recordInboundSession,
3482
+ record: {
3483
+ onRecordError: (err) => {
3484
+ params.replyLogger.warn({
3485
+ error: formatError(err),
3486
+ storePath,
3487
+ sessionKey: params.route.sessionKey
3488
+ }, "failed updating session meta");
3489
+ },
3490
+ trackSessionMetaTask: (task) => {
3491
+ trackBackgroundTask(params.backgroundTasks, task);
3492
+ }
3493
+ },
3494
+ runDispatch: () => dispatchWhatsAppBufferedReply({
3870
3495
  cfg: params.cfg,
3871
3496
  connectionId: params.connectionId,
3872
3497
  context: ctxPayload,
@@ -3875,7 +3500,7 @@ async function processMessage(params) {
3875
3500
  groupHistoryKey: params.groupHistoryKey,
3876
3501
  maxMediaBytes: params.maxMediaBytes,
3877
3502
  maxMediaTextChunkLimit: params.maxMediaTextChunkLimit,
3878
- inbound,
3503
+ msg: params.msg,
3879
3504
  onModelSelected,
3880
3505
  rememberSentText: params.rememberSentText,
3881
3506
  replyLogger: params.replyLogger,
@@ -3886,40 +3511,14 @@ async function processMessage(params) {
3886
3511
  replyResolver: params.replyResolver,
3887
3512
  route: params.route,
3888
3513
  shouldClearGroupHistory,
3889
- statusReactionController,
3890
- transport,
3891
- turnAdoptionLifecycle
3892
- });
3893
- finalizeReply = finalize;
3894
- return {
3895
- cfg: params.cfg,
3896
- channel: "whatsapp",
3897
- accountId: params.route.accountId,
3898
- route: {
3899
- agentId: params.route.agentId,
3900
- sessionKey: params.route.sessionKey
3901
- },
3902
- ctxPayload,
3903
- record: {
3904
- onRecordError: (err) => {
3905
- params.replyLogger.warn({
3906
- error: formatError(err),
3907
- storePath,
3908
- sessionKey: params.route.sessionKey
3909
- }, "failed updating session meta");
3910
- },
3911
- trackSessionMetaTask: (task) => {
3912
- trackBackgroundTask(params.backgroundTasks, task);
3913
- }
3914
- },
3915
- ...replyPlan
3916
- };
3917
- }
3514
+ statusReactionController
3515
+ })
3516
+ })
3918
3517
  }
3919
3518
  });
3920
- const didSendReply = turnResult.dispatched ? finalizeReply?.(turnResult.dispatchResult) ?? false : false;
3519
+ const didSendReply = turnResult.dispatched ? turnResult.dispatchResult : false;
3921
3520
  removeAckReactionHandleAfterReply({
3922
- removeAfterReply: false,
3521
+ removeAfterReply: Boolean(params.cfg.messages?.removeAckAfterReply && didSendReply),
3923
3522
  ackReaction,
3924
3523
  onError: (err) => {
3925
3524
  logAckFailure({
@@ -3934,14 +3533,8 @@ async function processMessage(params) {
3934
3533
  }
3935
3534
  //#endregion
3936
3535
  //#region extensions/whatsapp/src/auto-reply/monitor/on-message.ts
3937
- function readDeprecatedAccessControlPassed(msg) {
3938
- return msg.accessControlPassed;
3939
- }
3940
3536
  function createWebOnMessageHandler(params) {
3941
- const hasExplicitlyPassedInboundAccess = (msg) => {
3942
- if (msg.admission.ingress.decisiveGateId === "legacy-flat-compat") return readDeprecatedAccessControlPassed(msg) === true;
3943
- return msg.admission.ingress.decision === "allow";
3944
- };
3537
+ const hasExplicitlyPassedInboundAccess = (msg) => msg.admission ? msg.admission.ingress.decision === "allow" : msg.accessControlPassed === true;
3945
3538
  const withDirectSenderPeer = (msg, peerId) => {
3946
3539
  if (requireWhatsAppInboundAdmission(msg).conversation.kind === "group" || msg.platform.sender?.e164 || msg.platform.senderE164 || !peerId.startsWith("+")) return msg;
3947
3540
  const normalized = normalizeE164(peerId);
@@ -3965,7 +3558,6 @@ function createWebOnMessageHandler(params) {
3965
3558
  route,
3966
3559
  groupHistoryKey,
3967
3560
  groupHistories: params.groupHistories,
3968
- groupHistoryLimit: params.groupHistoryLimit,
3969
3561
  groupMemberNames: params.groupMemberNames,
3970
3562
  connectionId: params.connectionId,
3971
3563
  verbose: params.verbose,
@@ -3986,8 +3578,9 @@ function createWebOnMessageHandler(params) {
3986
3578
  if (opts?.statusReactionController !== void 0) processParams.statusReactionController = opts.statusReactionController;
3987
3579
  return processMessage(processParams);
3988
3580
  };
3989
- return async (normalizedMsg) => {
3990
- const canRunDirectEarlyAudioPreflight = hasExplicitlyPassedInboundAccess(normalizedMsg);
3581
+ return async (rawMsg) => {
3582
+ const canRunDirectEarlyAudioPreflight = hasExplicitlyPassedInboundAccess(rawMsg);
3583
+ const normalizedMsg = requireAdmittedWhatsAppInboundMessage(normalizeWebInboundMessage(rawMsg));
3991
3584
  const cfg = params.loadConfig?.() ?? params.cfg;
3992
3585
  const peerId = resolvePeerId(normalizedMsg);
3993
3586
  const msg = withDirectSenderPeer(normalizedMsg, peerId);
@@ -4012,9 +3605,9 @@ function createWebOnMessageHandler(params) {
4012
3605
  });
4013
3606
  const baseMentionConfig = buildMentionConfig(cfg);
4014
3607
  if (conversationId === msg.platform.recipientJid) logVerbose(`📱 Same-phone mode detected (from === to: ${conversationId})`);
4015
- if (params.echoTracker.has(msg.payload.body, conversationId)) {
3608
+ if (params.echoTracker.has(msg.payload.body)) {
4016
3609
  logVerbose("Skipping auto-reply: detected echo (message matches recently sent text)");
4017
- params.echoTracker.forget(msg.payload.body, conversationId);
3610
+ params.echoTracker.forget(msg.payload.body);
4018
3611
  return;
4019
3612
  }
4020
3613
  const configuredRoute = resolveConfiguredBindingRoute({
@@ -4032,7 +3625,7 @@ function createWebOnMessageHandler(params) {
4032
3625
  peerId
4033
3626
  }) : route.sessionKey;
4034
3627
  let preflightAudioTranscript;
4035
- const hasAudioBody = (msg.payload.media?.kind === "audio" || msg.payload.media?.type?.startsWith("audio/") === true) && !msg.payload.body.trim();
3628
+ const hasAudioBody = msg.payload.media?.type?.startsWith("audio/") === true && msg.payload.body === "<media:audio>";
4036
3629
  const canRunEarlyAudioPreflight = conversationKind === "group" || canRunDirectEarlyAudioPreflight;
4037
3630
  let ackAlreadySent = false;
4038
3631
  let ackReaction = null;
@@ -4055,14 +3648,11 @@ function createWebOnMessageHandler(params) {
4055
3648
  const transcribeAudioOnce = async () => {
4056
3649
  if (preflightAudioTranscript !== void 0 || !hasAudioBody || !msg.payload.media?.path) return;
4057
3650
  try {
4058
- const { transcribeFirstAudio } = await import("./audio-preflight.runtime-C_glQhZY.js");
3651
+ const { transcribeFirstAudio } = await import("./audio-preflight.runtime-C_glQhZY.mjs");
4059
3652
  preflightAudioTranscript = await transcribeFirstAudio({
4060
3653
  ctx: {
4061
- media: [{
4062
- path: msg.payload.media.path,
4063
- contentType: msg.payload.media.type,
4064
- kind: msg.payload.media.kind ?? void 0
4065
- }],
3654
+ MediaPaths: [msg.payload.media?.path],
3655
+ MediaTypes: msg.payload.media?.type ? [msg.payload.media?.type] : void 0,
4066
3656
  From: conversationId,
4067
3657
  To: msg.platform.recipientJid,
4068
3658
  Provider: "whatsapp",
@@ -4220,7 +3810,7 @@ function createWebOnMessageHandler(params) {
4220
3810
  function isNonRetryableWebCloseStatus(statusCode) {
4221
3811
  return statusCode === 440;
4222
3812
  }
4223
- const loadReplyResolverRuntime = createLazyRuntimeModule(() => import("./reply-resolver.runtime-Ded8D58Y.js"));
3813
+ const loadReplyResolverRuntime = createLazyRuntimeModule(() => import("./reply-resolver.runtime-Ded8D58Y.mjs"));
4224
3814
  function resolveWebMonitorConfigSnapshot(params) {
4225
3815
  const account = resolveWhatsAppAccount({
4226
3816
  cfg: params.cfg,
@@ -4233,13 +3823,15 @@ function resolveWebMonitorConfigSnapshot(params) {
4233
3823
  ...params.cfg.channels,
4234
3824
  whatsapp: {
4235
3825
  ...params.cfg.channels?.whatsapp,
4236
- responsePrefix: account.messagePrefix,
3826
+ ackReaction: account.ackReaction,
3827
+ messagePrefix: account.messagePrefix,
4237
3828
  allowFrom: account.allowFrom,
4238
3829
  groupAllowFrom: account.groupAllowFrom,
4239
3830
  groupPolicy: account.groupPolicy,
4240
3831
  textChunkLimit: account.textChunkLimit,
4241
- streaming: account.streaming,
3832
+ chunkMode: account.chunkMode,
4242
3833
  mediaMaxMb: account.mediaMaxMb,
3834
+ blockStreaming: account.blockStreaming,
4243
3835
  groups: account.groups
4244
3836
  }
4245
3837
  }
@@ -4247,17 +3839,28 @@ function resolveWebMonitorConfigSnapshot(params) {
4247
3839
  account
4248
3840
  };
4249
3841
  }
3842
+ function normalizeReconnectAccountId(accountId) {
3843
+ return (accountId ?? "").trim() || "default";
3844
+ }
4250
3845
  function isNoListenerReconnectError(lastError) {
4251
3846
  return typeof lastError === "string" && /No active WhatsApp Web listener/i.test(lastError);
4252
3847
  }
4253
- function normalizeReconnectAccountId(accountId) {
4254
- return (accountId ?? "").trim() || "default";
3848
+ function resolveExplicitWhatsAppDebounceOverride(params) {
3849
+ const channel = params.sourceCfg?.channels?.whatsapp;
3850
+ if (!channel) return;
3851
+ const accountId = normalizeReconnectAccountId(params.accountId);
3852
+ const accountDebounce = resolveAccountEntry(channel.accounts, accountId)?.debounceMs;
3853
+ if (accountDebounce !== void 0) return accountDebounce;
3854
+ if (accountId !== "default") {
3855
+ const defaultAccountDebounce = resolveAccountEntry(channel.accounts, "default")?.debounceMs;
3856
+ if (defaultAccountDebounce !== void 0) return defaultAccountDebounce;
3857
+ }
3858
+ return channel.debounceMs;
4255
3859
  }
4256
3860
  function isRetryableAuthUnstableError(error) {
4257
3861
  return error instanceof WhatsAppAuthUnstableError || typeof error === "object" && error !== null && "code" in error && error.code === "whatsapp-auth-unstable";
4258
3862
  }
4259
- const DEFAULT_TRANSPORT_TIMEOUT_MS = 300 * 1e3;
4260
- const WHATSAPP_RECONNECT_CATCH_UP_MAX_MS = 20 * 6e4;
3863
+ const DEFAULT_TRANSPORT_TIMEOUT_MS = 3e5;
4261
3864
  async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSocket, keepAlive = true, replyResolver, runtime = defaultRuntime, abortSignal, tuning = {}) {
4262
3865
  const activeReplyResolver = replyResolver ?? (await loadReplyResolverRuntime()).getReplyFromConfig;
4263
3866
  const runId = newConnectionId();
@@ -4273,8 +3876,10 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4273
3876
  module: "web-reconnect",
4274
3877
  runId
4275
3878
  });
3879
+ const baseCfg = getRuntimeConfig$1();
3880
+ const sourceCfg = getRuntimeConfigSourceSnapshot();
4276
3881
  const { cfg, account } = resolveWebMonitorConfigSnapshot({
4277
- cfg: getRuntimeConfig$1(),
3882
+ cfg: baseCfg,
4278
3883
  accountId: tuning.accountId
4279
3884
  });
4280
3885
  const loadCurrentMonitorConfig = () => resolveWebMonitorConfigSnapshot({
@@ -4284,7 +3889,7 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4284
3889
  const maxMediaBytes = resolveWhatsAppMediaMaxBytes(account);
4285
3890
  const heartbeatSeconds = resolveHeartbeatSeconds(cfg, tuning.heartbeatSeconds);
4286
3891
  const reconnectPolicy = resolveReconnectPolicy(cfg, tuning.reconnect);
4287
- const socketTiming = resolveWhatsAppSocketTiming(tuning.socketTiming);
3892
+ const socketTiming = resolveWhatsAppSocketTiming(cfg, tuning.socketTiming);
4288
3893
  const baseMentionConfig = buildMentionConfig(cfg);
4289
3894
  const groupHistoryLimit = account.historyLimit ?? cfg.channels?.whatsapp?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT;
4290
3895
  const groupHistories = /* @__PURE__ */ new Map();
@@ -4306,9 +3911,9 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4306
3911
  };
4307
3912
  process.once("SIGINT", handleSigint);
4308
3913
  const transportTimeoutMs = tuning.transportTimeoutMs ?? DEFAULT_TRANSPORT_TIMEOUT_MS;
4309
- const messageTimeoutMs = tuning.messageTimeoutMs ?? 1800 * 1e3;
4310
- const reconnectCatchUpWindowMs = Math.min(Math.max(messageTimeoutMs, 6e4), WHATSAPP_RECONNECT_CATCH_UP_MAX_MS);
4311
- const watchdogCheckMs = tuning.watchdogCheckMs ?? 60 * 1e3;
3914
+ const messageTimeoutMs = tuning.messageTimeoutMs ?? 18e5;
3915
+ const reconnectCatchUpWindowMs = Math.min(Math.max(messageTimeoutMs, 6e4), WHATSAPP_INBOUND_DEDUPE_TTL_MS);
3916
+ const watchdogCheckMs = tuning.watchdogCheckMs ?? 6e4;
4312
3917
  const controller = new WhatsAppConnectionController({
4313
3918
  accountId: account.accountId,
4314
3919
  authDir: account.authDir,
@@ -4332,16 +3937,19 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4332
3937
  const connectionId = newConnectionId();
4333
3938
  const inboundDebounceMs = resolveInboundDebounceMs({
4334
3939
  cfg,
4335
- channel: "whatsapp"
3940
+ channel: "whatsapp",
3941
+ overrideMs: resolveExplicitWhatsAppDebounceOverride({
3942
+ cfg,
3943
+ sourceCfg,
3944
+ accountId: account.accountId
3945
+ })
4336
3946
  });
4337
3947
  const shouldDebounce = (msg) => {
4338
- const admitted = normalizeAdmittedWebInboundMessage(msg);
4339
- return shouldDebounceTextInbound({
4340
- text: admitted.payload.commandBody ?? admitted.payload.body,
4341
- cfg,
4342
- hasMedia: Boolean(admitted.payload.media?.path || admitted.payload.media?.type),
4343
- allowDebounce: !(admitted.payload.location || admitted.quote?.id || admitted.quote?.body)
4344
- });
3948
+ const normalized = normalizeWebInboundMessage(msg);
3949
+ if (normalized.payload.media?.path || normalized.payload.media?.type) return false;
3950
+ if (normalized.payload.location) return false;
3951
+ if (normalized.quote?.id || normalized.quote?.body) return false;
3952
+ return !isControlCommandMessage(normalized.payload.commandBody ?? normalized.payload.body, cfg);
4345
3953
  };
4346
3954
  let connection;
4347
3955
  try {
@@ -4394,11 +4002,11 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4394
4002
  recentMessageKeys,
4395
4003
  baileysGroupMetaCache,
4396
4004
  onMessage: async (msg) => {
4397
- const admitted = normalizeAdmittedWebInboundMessage(msg);
4005
+ const normalized = normalizeWebInboundMessage(msg);
4398
4006
  const inboundAt = Date.now();
4399
4007
  controller.noteInbound(inboundAt);
4400
4008
  statusController.noteInbound(inboundAt);
4401
- await onMessage(admitted);
4009
+ await onMessage(normalized);
4402
4010
  },
4403
4011
  onPendingWorkChanged: (pendingWorkCount, at) => {
4404
4012
  statusController.noteBusy(pendingWorkCount > 0, at);
@@ -4678,4 +4286,4 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4678
4286
  }
4679
4287
  }
4680
4288
  //#endregion
4681
- export { loadWebMediaRaw as a, monitorWebInbox as c, loadWebMedia as i, resetWebInboundDedupe as l, LocalMediaAccessError as n, optimizeImageToJpeg as o, getDefaultLocalRoots as r, optimizeImageToPng as s, monitorWebChannel as t };
4289
+ export { loadWebMediaRaw as a, monitorWebInbox as c, loadWebMedia$1 as i, resetWebInboundDedupe as l, LocalMediaAccessError as n, optimizeImageToJpeg as o, getDefaultLocalRoots as r, optimizeImageToPng as s, monitorWebChannel as t };