@openclaw/whatsapp 2026.5.2 → 2026.5.3-beta.1

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 (302) hide show
  1. package/dist/access-control-BQ3tCkH8.js +288 -0
  2. package/dist/account-config-CNQ33oav.js +47 -0
  3. package/dist/account-ids-DG18xddX.js +5 -0
  4. package/dist/accounts-BDqgpFEB.js +116 -0
  5. package/dist/action-runtime-DTGNgG-i.js +74 -0
  6. package/dist/action-runtime-api.js +2 -0
  7. package/dist/action-runtime.runtime.js +2 -0
  8. package/dist/active-listener-N7GFKsuN.js +11 -0
  9. package/dist/agent-tools-login-DpRxLNRB.js +89 -0
  10. package/dist/api.js +148 -0
  11. package/dist/audio-preflight.runtime-Bcdsl_ri.js +7 -0
  12. package/dist/auth-presence.js +32 -0
  13. package/dist/auth-store-BjCb50Ry.js +381 -0
  14. package/dist/channel-DQ-5Xoj9.js +542 -0
  15. package/dist/channel-config-api.js +2 -0
  16. package/dist/channel-plugin-api.js +2 -0
  17. package/dist/channel-react-action-Df20qAy9.js +42 -0
  18. package/dist/channel.runtime-DKXRnA8x.js +55 -0
  19. package/dist/channel.setup-DOHR2BGp.js +20 -0
  20. package/dist/command-policy-O8sRnqUV.js +8 -0
  21. package/dist/config-schema-Dmo66Ubb.js +27 -0
  22. package/dist/connection-controller-BiHAsTM8.js +596 -0
  23. package/dist/connection-controller-registry-D_iIGz5m.js +22 -0
  24. package/dist/constants-DLGCIbfl.js +4 -0
  25. package/dist/constants.js +2 -0
  26. package/dist/contract-api.js +19 -0
  27. package/dist/creds-files-_2V8jzQa.js +19 -0
  28. package/dist/directory-config-DVs4ffXu.js +37 -0
  29. package/dist/directory-contract-api.js +2 -0
  30. package/dist/doctor-A9tXm9Om.js +48 -0
  31. package/dist/doctor-contract-api.js +10 -0
  32. package/dist/group-session-contract-B8QBn0I2.js +15 -0
  33. package/dist/identity-QHAlV7Fu.js +75 -0
  34. package/dist/index.js +18 -0
  35. package/dist/legacy-session-surface-api.js +8 -0
  36. package/dist/legacy-state-migrations-api.js +2 -0
  37. package/dist/light-runtime-api.js +5 -0
  38. package/dist/login-DymdrKca.js +52 -0
  39. package/dist/login-qr-DIS0Iv-7.js +378 -0
  40. package/dist/login-qr-api.js +2 -0
  41. package/dist/login-qr-runtime.js +16 -0
  42. package/dist/monitor-C4Fhg_lD.js +3349 -0
  43. package/dist/normalize-CWYiGkXw.js +2 -0
  44. package/dist/normalize-target-BPVHNsM5.js +74 -0
  45. package/dist/outbound-adapter-BssVnZdz.js +29 -0
  46. package/dist/outbound-base-Bq1UYEK5.js +119 -0
  47. package/dist/outbound-media-contract-CGss_QHy.js +163 -0
  48. package/dist/outbound-payload-test-api.js +2 -0
  49. package/dist/quoted-message-TENQzyxV.js +108 -0
  50. package/dist/reaction-level-CRVEVAfi.js +16 -0
  51. package/dist/reply-resolver.runtime-BRCgoO4C.js +2 -0
  52. package/dist/resolve-outbound-target-CN-Wo6IY.js +39 -0
  53. package/dist/rolldown-runtime-RkAeH_Qm.js +27 -0
  54. package/dist/runtime-6fZ67abR.js +8 -0
  55. package/dist/runtime-api.js +15 -0
  56. package/dist/runtime-group-policy-Gp1lQ0at.js +11 -0
  57. package/dist/secret-contract-api.js +5 -0
  58. package/dist/security-contract-DqH8JDX6.js +26 -0
  59. package/dist/security-contract-api.js +2 -0
  60. package/dist/send-CIyLFxdZ.js +234 -0
  61. package/dist/session-contract-B0efQf9P.js +27 -0
  62. package/dist/session-errors-CSuKZPub.js +63 -0
  63. package/dist/session.runtime-C1eSE_KK.js +13 -0
  64. package/dist/setup-core-DZKvpzbH.js +46 -0
  65. package/dist/setup-entry.js +23 -0
  66. package/dist/setup-plugin-api.js +2 -0
  67. package/dist/setup-surface-ieFnIW4g.js +344 -0
  68. package/dist/shared-D_wzSVar.js +307 -0
  69. package/dist/state-migrations-DnCdvoYD.js +39 -0
  70. package/dist/targets.js +2 -0
  71. package/dist/test-api.js +3 -0
  72. package/dist/text-runtime-C1zNiZeO.js +116 -0
  73. package/package.json +14 -6
  74. package/action-runtime-api.ts +0 -1
  75. package/action-runtime.runtime.ts +0 -1
  76. package/api.ts +0 -213
  77. package/auth-presence.ts +0 -80
  78. package/channel-config-api.ts +0 -1
  79. package/channel-plugin-api.ts +0 -3
  80. package/config-api.ts +0 -4
  81. package/constants.ts +0 -1
  82. package/contract-api.ts +0 -29
  83. package/directory-contract-api.ts +0 -4
  84. package/doctor-contract-api.ts +0 -8
  85. package/index.test.ts +0 -13
  86. package/index.ts +0 -16
  87. package/legacy-session-surface-api.ts +0 -6
  88. package/legacy-state-migrations-api.ts +0 -1
  89. package/light-runtime-api.ts +0 -12
  90. package/login-qr-api.ts +0 -1
  91. package/login-qr-runtime.ts +0 -23
  92. package/outbound-payload-test-api.ts +0 -1
  93. package/runtime-api.ts +0 -84
  94. package/secret-contract-api.ts +0 -4
  95. package/security-contract-api.ts +0 -4
  96. package/setup-entry.test.ts +0 -21
  97. package/setup-entry.ts +0 -21
  98. package/setup-plugin-api.ts +0 -3
  99. package/src/account-config.ts +0 -77
  100. package/src/account-ids.ts +0 -13
  101. package/src/account-types.ts +0 -5
  102. package/src/accounts.test.ts +0 -182
  103. package/src/accounts.ts +0 -176
  104. package/src/accounts.whatsapp-auth.test.ts +0 -59
  105. package/src/action-runtime-target-auth.ts +0 -27
  106. package/src/action-runtime.test.ts +0 -330
  107. package/src/action-runtime.ts +0 -76
  108. package/src/active-listener.test.ts +0 -65
  109. package/src/active-listener.ts +0 -17
  110. package/src/agent-tools-login.test.ts +0 -81
  111. package/src/agent-tools-login.ts +0 -113
  112. package/src/approval-auth.test.ts +0 -24
  113. package/src/approval-auth.ts +0 -27
  114. package/src/auth-store.runtime.ts +0 -1
  115. package/src/auth-store.test.ts +0 -311
  116. package/src/auth-store.ts +0 -502
  117. package/src/auto-reply/config.runtime.ts +0 -16
  118. package/src/auto-reply/constants.ts +0 -1
  119. package/src/auto-reply/deliver-reply.test.ts +0 -843
  120. package/src/auto-reply/deliver-reply.ts +0 -279
  121. package/src/auto-reply/loggers.ts +0 -6
  122. package/src/auto-reply/mentions.ts +0 -131
  123. package/src/auto-reply/monitor/ack-reaction.test.ts +0 -170
  124. package/src/auto-reply/monitor/ack-reaction.ts +0 -99
  125. package/src/auto-reply/monitor/audio-preflight.runtime.ts +0 -9
  126. package/src/auto-reply/monitor/broadcast.ts +0 -153
  127. package/src/auto-reply/monitor/commands.ts +0 -19
  128. package/src/auto-reply/monitor/echo.ts +0 -64
  129. package/src/auto-reply/monitor/group-activation.runtime.ts +0 -1
  130. package/src/auto-reply/monitor/group-activation.test.ts +0 -189
  131. package/src/auto-reply/monitor/group-activation.ts +0 -73
  132. package/src/auto-reply/monitor/group-gating.audio-preflight.test.ts +0 -103
  133. package/src/auto-reply/monitor/group-gating.runtime.ts +0 -8
  134. package/src/auto-reply/monitor/group-gating.ts +0 -217
  135. package/src/auto-reply/monitor/group-members.test.ts +0 -56
  136. package/src/auto-reply/monitor/group-members.ts +0 -65
  137. package/src/auto-reply/monitor/inbound-context.test.ts +0 -97
  138. package/src/auto-reply/monitor/inbound-context.ts +0 -92
  139. package/src/auto-reply/monitor/inbound-dispatch.runtime.ts +0 -21
  140. package/src/auto-reply/monitor/inbound-dispatch.test.ts +0 -895
  141. package/src/auto-reply/monitor/inbound-dispatch.ts +0 -422
  142. package/src/auto-reply/monitor/last-route.test.ts +0 -37
  143. package/src/auto-reply/monitor/last-route.ts +0 -61
  144. package/src/auto-reply/monitor/message-line.runtime.ts +0 -38
  145. package/src/auto-reply/monitor/message-line.ts +0 -54
  146. package/src/auto-reply/monitor/on-message.audio-preflight.test.ts +0 -354
  147. package/src/auto-reply/monitor/on-message.ts +0 -291
  148. package/src/auto-reply/monitor/peer.ts +0 -17
  149. package/src/auto-reply/monitor/process-message.audio-preflight.test.ts +0 -420
  150. package/src/auto-reply/monitor/process-message.test.ts +0 -355
  151. package/src/auto-reply/monitor/process-message.ts +0 -531
  152. package/src/auto-reply/monitor/runtime-api.ts +0 -30
  153. package/src/auto-reply/monitor-state.test.ts +0 -64
  154. package/src/auto-reply/monitor-state.ts +0 -106
  155. package/src/auto-reply/monitor.ts +0 -600
  156. package/src/auto-reply/reply-resolver.runtime.ts +0 -1
  157. package/src/auto-reply/types.ts +0 -48
  158. package/src/auto-reply/util.ts +0 -63
  159. package/src/auto-reply/web-auto-reply-monitor.test.ts +0 -687
  160. package/src/auto-reply/web-auto-reply-utils.test.ts +0 -392
  161. package/src/auto-reply.broadcast-groups.combined.test.ts +0 -246
  162. package/src/auto-reply.broadcast-groups.test-harness.ts +0 -47
  163. package/src/auto-reply.impl.ts +0 -6
  164. package/src/auto-reply.test-harness.ts +0 -421
  165. package/src/auto-reply.ts +0 -1
  166. package/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts +0 -427
  167. package/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts +0 -908
  168. package/src/auto-reply.web-auto-reply.last-route.test.ts +0 -184
  169. package/src/channel-actions.runtime.ts +0 -7
  170. package/src/channel-actions.test.ts +0 -216
  171. package/src/channel-actions.ts +0 -84
  172. package/src/channel-outbound.test.ts +0 -131
  173. package/src/channel-outbound.ts +0 -36
  174. package/src/channel-react-action.runtime.ts +0 -7
  175. package/src/channel-react-action.test.ts +0 -294
  176. package/src/channel-react-action.ts +0 -84
  177. package/src/channel.runtime.ts +0 -117
  178. package/src/channel.setup.test.ts +0 -461
  179. package/src/channel.setup.ts +0 -28
  180. package/src/channel.ts +0 -339
  181. package/src/command-policy.ts +0 -7
  182. package/src/config-accessors.test.ts +0 -34
  183. package/src/config-accessors.ts +0 -22
  184. package/src/config-schema.test.ts +0 -121
  185. package/src/config-schema.ts +0 -6
  186. package/src/config-ui-hints.ts +0 -24
  187. package/src/connection-controller-registry.test.ts +0 -27
  188. package/src/connection-controller-registry.ts +0 -49
  189. package/src/connection-controller.test.ts +0 -282
  190. package/src/connection-controller.ts +0 -679
  191. package/src/creds-files.ts +0 -19
  192. package/src/creds-persistence.ts +0 -102
  193. package/src/directory-config.test.ts +0 -60
  194. package/src/directory-config.ts +0 -40
  195. package/src/directory-contract.test.ts +0 -56
  196. package/src/doctor-contract.ts +0 -11
  197. package/src/doctor.test.ts +0 -74
  198. package/src/doctor.ts +0 -56
  199. package/src/group-intro.ts +0 -15
  200. package/src/group-policy.test.ts +0 -36
  201. package/src/group-policy.ts +0 -40
  202. package/src/group-session-contract.ts +0 -20
  203. package/src/group-session-key.test.ts +0 -53
  204. package/src/group-session-key.ts +0 -41
  205. package/src/heartbeat.ts +0 -34
  206. package/src/identity.ts +0 -164
  207. package/src/inbound/access-control.test-harness.ts +0 -37
  208. package/src/inbound/access-control.test.ts +0 -375
  209. package/src/inbound/access-control.ts +0 -237
  210. package/src/inbound/dedupe.ts +0 -132
  211. package/src/inbound/extract.test.ts +0 -282
  212. package/src/inbound/extract.ts +0 -488
  213. package/src/inbound/lifecycle.ts +0 -39
  214. package/src/inbound/media.node.test.ts +0 -83
  215. package/src/inbound/media.ts +0 -102
  216. package/src/inbound/monitor.ts +0 -890
  217. package/src/inbound/runtime-api.ts +0 -7
  218. package/src/inbound/save-media.runtime.ts +0 -1
  219. package/src/inbound/send-api.test.ts +0 -319
  220. package/src/inbound/send-api.ts +0 -145
  221. package/src/inbound/send-result.ts +0 -61
  222. package/src/inbound/types.ts +0 -106
  223. package/src/inbound-context.contract.test.ts +0 -32
  224. package/src/inbound-policy.ts +0 -231
  225. package/src/inbound.media.test.ts +0 -320
  226. package/src/inbound.test.ts +0 -290
  227. package/src/inbound.ts +0 -9
  228. package/src/login-qr.test.ts +0 -483
  229. package/src/login-qr.ts +0 -542
  230. package/src/login.coverage.test.ts +0 -133
  231. package/src/login.test.ts +0 -89
  232. package/src/login.ts +0 -70
  233. package/src/logout.test.ts +0 -162
  234. package/src/media.test.ts +0 -480
  235. package/src/media.ts +0 -10
  236. package/src/monitor-inbox.allows-messages-from-senders-allowfrom-list.test-support.ts +0 -400
  237. package/src/monitor-inbox.append-upsert.test-support.ts +0 -133
  238. package/src/monitor-inbox.behavior.test.ts +0 -5
  239. package/src/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test-support.ts +0 -362
  240. package/src/monitor-inbox.captures-media-path-image-messages.test-support.ts +0 -306
  241. package/src/monitor-inbox.streams-inbound-messages.test-support.ts +0 -729
  242. package/src/monitor-inbox.test-harness.ts +0 -304
  243. package/src/normalize-target.ts +0 -123
  244. package/src/normalize.ts +0 -7
  245. package/src/outbound-adapter.poll.test.ts +0 -62
  246. package/src/outbound-adapter.sendpayload.test.ts +0 -195
  247. package/src/outbound-adapter.ts +0 -36
  248. package/src/outbound-base.test.ts +0 -560
  249. package/src/outbound-base.ts +0 -244
  250. package/src/outbound-media-contract.ts +0 -278
  251. package/src/outbound-media.runtime.ts +0 -36
  252. package/src/outbound-payload.contract.test.ts +0 -61
  253. package/src/outbound-send-deps.ts +0 -1
  254. package/src/outbound-test-support.ts +0 -16
  255. package/src/pairing-security.test-harness.ts +0 -59
  256. package/src/qr-image.ts +0 -1
  257. package/src/qr-terminal.ts +0 -1
  258. package/src/quoted-message.test.ts +0 -83
  259. package/src/quoted-message.ts +0 -184
  260. package/src/reaction-level.test.ts +0 -94
  261. package/src/reaction-level.ts +0 -21
  262. package/src/reconnect.test.ts +0 -51
  263. package/src/reconnect.ts +0 -55
  264. package/src/resolve-outbound-target.test.ts +0 -288
  265. package/src/resolve-outbound-target.ts +0 -58
  266. package/src/resolve-target.test.ts +0 -120
  267. package/src/runtime-api.ts +0 -59
  268. package/src/runtime-group-policy.ts +0 -16
  269. package/src/runtime.ts +0 -9
  270. package/src/security-contract.ts +0 -47
  271. package/src/security-fix.ts +0 -71
  272. package/src/send.test.ts +0 -534
  273. package/src/send.ts +0 -292
  274. package/src/session-contract.test.ts +0 -31
  275. package/src/session-contract.ts +0 -43
  276. package/src/session-errors.ts +0 -125
  277. package/src/session-route.test.ts +0 -41
  278. package/src/session-route.ts +0 -32
  279. package/src/session.runtime.ts +0 -8
  280. package/src/session.test.ts +0 -559
  281. package/src/session.ts +0 -330
  282. package/src/setup-core.ts +0 -52
  283. package/src/setup-finalize.ts +0 -446
  284. package/src/setup-surface.test.ts +0 -367
  285. package/src/setup-surface.ts +0 -69
  286. package/src/setup-test-helpers.ts +0 -216
  287. package/src/shared.ts +0 -291
  288. package/src/socket-timing.test.ts +0 -49
  289. package/src/socket-timing.ts +0 -38
  290. package/src/state-migrations.ts +0 -54
  291. package/src/status-issues.test.ts +0 -137
  292. package/src/status-issues.ts +0 -185
  293. package/src/system-prompt.test.ts +0 -199
  294. package/src/system-prompt.ts +0 -31
  295. package/src/targets-runtime.ts +0 -180
  296. package/src/test-helpers.ts +0 -690
  297. package/src/text-runtime.test.ts +0 -162
  298. package/src/text-runtime.ts +0 -11
  299. package/src/vcard.ts +0 -84
  300. package/targets.ts +0 -5
  301. package/test-api.ts +0 -2
  302. package/tsconfig.json +0 -16
@@ -0,0 +1,3349 @@
1
+ import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-BDqgpFEB.js";
2
+ import { n as isWhatsAppNewsletterJid, t as isWhatsAppGroupJid$1 } from "./normalize-target-BPVHNsM5.js";
3
+ import { t as resolveWhatsAppReactionLevel } from "./reaction-level-CRVEVAfi.js";
4
+ import "./normalize-CWYiGkXw.js";
5
+ import { n as getStatusCode, t as formatError } from "./session-errors-CSuKZPub.js";
6
+ import { a as markdownToWhatsApp, i as jidToE164, o as resolveJidToE164, r as isSelfChatMode, s as toWhatsappJid, t as text_runtime_exports } from "./text-runtime-C1zNiZeO.js";
7
+ import { i as prepareWhatsAppOutboundMedia, o as sendWhatsAppOutboundWithRetry, r as normalizeWhatsAppPayloadTextPreservingIndentation, t as normalizeWhatsAppOutboundPayload } from "./outbound-media-contract-CGss_QHy.js";
8
+ import { r as sendReactionWhatsApp } from "./send-CIyLFxdZ.js";
9
+ import { n as cacheInboundMessageMeta, r as lookupInboundMessageMeta, t as buildQuotedMessageOptions } from "./quoted-message-TENQzyxV.js";
10
+ import { _ as readWebSelfIdentityForDecision, a as getWebAuthAgeMs, h as readWebSelfId, r as WhatsAppAuthUnstableError, s as logoutWeb } from "./auth-store-BjCb50Ry.js";
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-QHAlV7Fu.js";
12
+ import { i as resolveWhatsAppInboundPolicy, n as checkInboundAccessControl, r as resolveWhatsAppCommandAuthorized } from "./access-control-BQ3tCkH8.js";
13
+ import { c as waitForWaConnection, d as computeBackoff, f as newConnectionId, h as sleepWithAbort, l as resolveWhatsAppSocketTiming, m as resolveReconnectPolicy, n as WhatsAppConnectionController, o as createWaSocket, p as resolveHeartbeatSeconds, u as DEFAULT_RECONNECT_POLICY } from "./connection-controller-BiHAsTM8.js";
14
+ import { resolveAccountEntry } from "openclaw/plugin-sdk/account-core";
15
+ import { getChildLogger, normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
16
+ import { createAckReactionHandle, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
17
+ import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
18
+ import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
19
+ import { createSubsystemLogger, defaultRuntime, formatDurationPrecise, getChildLogger as getChildLogger$1, logVerbose, logVerbose as logVerbose$1, registerUnhandledRejectionHandler, shouldLogVerbose, shouldLogVerbose as shouldLogVerbose$1 } from "openclaw/plugin-sdk/runtime-env";
20
+ import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
21
+ import { LocalMediaAccessError, getDefaultLocalRoots, loadWebMedia as loadWebMedia$1, loadWebMediaRaw, optimizeImageToJpeg, optimizeImageToPng } from "openclaw/plugin-sdk/web-media";
22
+ import { chunkMarkdownTextWithMode } from "openclaw/plugin-sdk/reply-chunking";
23
+ import { isReasoningReplyPayload, resolveSendableOutboundReplyParts, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
24
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, DEFAULT_MAIN_KEY, buildAgentMainSessionKey, buildAgentSessionKey, buildGroupHistoryKey, deriveLastRoutePolicy, normalizeAccountId as normalizeAccountId$1, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
25
+ import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
26
+ import { evaluateSupplementalContextVisibility, filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
27
+ import { loadSessionStore, resolveStorePath, updateLastRoute, updateSessionStore } from "openclaw/plugin-sdk/session-store-runtime";
28
+ import { dispatchReplyWithBufferedBlockDispatcher, finalizeInboundContext, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
29
+ import { createInboundDebouncer, resolveInboundDebounceMs } from "openclaw/plugin-sdk/channel-inbound-debounce";
30
+ import { hasControlCommand, hasControlCommand as hasControlCommand$1, shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-detection";
31
+ import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runtime";
32
+ import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
33
+ import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
34
+ import { DisconnectReason as DisconnectReason$1, downloadMediaMessage, extractMessageContent, getContentType, isJidGroup, normalizeMessageContent, normalizeMessageContent as normalizeMessageContent$1 } from "@whiskeysockets/baileys";
35
+ import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
36
+ import { formatLocationText, resolveInboundSessionEnvelopeContext } from "openclaw/plugin-sdk/channel-inbound";
37
+ import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
38
+ import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
39
+ import { getRuntimeConfig as getRuntimeConfig$1, getRuntimeConfigSourceSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
40
+ import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
41
+ import { buildMentionRegexes, implicitMentionKindWhen, normalizeMentionText, resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-mention-gating";
42
+ import { createConnectedChannelStatusPatch, createTransportActivityStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
43
+ import { normalizeGroupActivation, parseActivationCommand } from "openclaw/plugin-sdk/group-activation";
44
+ import { createInternalHookEvent, deriveInboundMessageHookContext, fireAndForgetBoundedHook, toInternalMessageReceivedContext, toPluginMessageContext, toPluginMessageReceivedEvent, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
45
+ import { hasVisibleInboundReplyDispatch, runInboundReplyTurn } from "openclaw/plugin-sdk/inbound-reply-dispatch";
46
+ import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
47
+ import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
48
+ import { resolveIdentityNamePrefix as resolveIdentityNamePrefix$1 } from "openclaw/plugin-sdk/agent-runtime";
49
+ import { formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1 } from "openclaw/plugin-sdk/channel-envelope";
50
+ import { toLocationContext } from "openclaw/plugin-sdk/channel-location";
51
+ import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
52
+ //#region extensions/whatsapp/src/inbound/dedupe.ts
53
+ const RECENT_WEB_MESSAGE_TTL_MS = 20 * 6e4;
54
+ const RECENT_WEB_MESSAGE_MAX = 5e3;
55
+ const RECENT_OUTBOUND_MESSAGE_TTL_MS = 20 * 6e4;
56
+ const RECENT_OUTBOUND_MESSAGE_MAX = 5e3;
57
+ const claimableInboundMessages = createClaimableDedupe({
58
+ ttlMs: RECENT_WEB_MESSAGE_TTL_MS,
59
+ memoryMaxSize: RECENT_WEB_MESSAGE_MAX
60
+ });
61
+ const recentOutboundMessages = createRecentMessageCache({
62
+ ttlMs: RECENT_OUTBOUND_MESSAGE_TTL_MS,
63
+ maxSize: RECENT_OUTBOUND_MESSAGE_MAX
64
+ });
65
+ function createRecentMessageCache(options) {
66
+ const ttlMs = Math.max(0, options.ttlMs);
67
+ const maxSize = Math.max(0, Math.floor(options.maxSize));
68
+ const cache = /* @__PURE__ */ new Map();
69
+ const prune = (now) => {
70
+ if (ttlMs > 0) {
71
+ const cutoff = now - ttlMs;
72
+ for (const [key, timestamp] of cache) if (timestamp < cutoff) cache.delete(key);
73
+ }
74
+ while (cache.size > maxSize) {
75
+ const oldest = cache.keys().next().value;
76
+ if (!oldest) break;
77
+ cache.delete(oldest);
78
+ }
79
+ };
80
+ const peek = (key, now = Date.now()) => {
81
+ if (!key) return false;
82
+ const timestamp = cache.get(key);
83
+ if (timestamp === void 0) return false;
84
+ if (ttlMs > 0 && now - timestamp >= ttlMs) {
85
+ cache.delete(key);
86
+ return false;
87
+ }
88
+ return true;
89
+ };
90
+ return {
91
+ check: (key, now = Date.now()) => {
92
+ if (!key) return false;
93
+ const existed = peek(key, now);
94
+ cache.delete(key);
95
+ cache.set(key, now);
96
+ prune(now);
97
+ return existed;
98
+ },
99
+ peek,
100
+ clear: () => cache.clear()
101
+ };
102
+ }
103
+ var WhatsAppRetryableInboundError = class extends Error {
104
+ constructor(message, options) {
105
+ super(message, options);
106
+ this.name = "WhatsAppRetryableInboundError";
107
+ }
108
+ };
109
+ function buildMessageKey(params) {
110
+ const accountId = params.accountId.trim();
111
+ const remoteJid = params.remoteJid.trim();
112
+ const messageId = params.messageId.trim();
113
+ if (!accountId || !remoteJid || !messageId || messageId === "unknown") return null;
114
+ return `${accountId}:${remoteJid}:${messageId}`;
115
+ }
116
+ function resetWebInboundDedupe() {
117
+ claimableInboundMessages.clearMemory();
118
+ recentOutboundMessages.clear();
119
+ }
120
+ async function claimRecentInboundMessage(key) {
121
+ return (await claimableInboundMessages.claim(key)).kind === "claimed";
122
+ }
123
+ async function commitRecentInboundMessage(key) {
124
+ await claimableInboundMessages.commit(key);
125
+ }
126
+ function releaseRecentInboundMessage(key, error) {
127
+ claimableInboundMessages.release(key, { error });
128
+ }
129
+ function rememberRecentOutboundMessage(params) {
130
+ const key = buildMessageKey(params);
131
+ if (!key) return;
132
+ recentOutboundMessages.check(key);
133
+ }
134
+ function isRecentOutboundMessage(params) {
135
+ const key = buildMessageKey(params);
136
+ if (!key) return false;
137
+ return recentOutboundMessages.peek(key);
138
+ }
139
+ //#endregion
140
+ //#region extensions/whatsapp/src/vcard.ts
141
+ const ALLOWED_VCARD_KEYS = new Set([
142
+ "FN",
143
+ "N",
144
+ "TEL"
145
+ ]);
146
+ function parseVcard(vcard) {
147
+ if (!vcard) return { phones: [] };
148
+ const lines = vcard.split(/\r?\n/);
149
+ let nameFromN;
150
+ let nameFromFn;
151
+ const phones = [];
152
+ for (const rawLine of lines) {
153
+ const line = rawLine.trim();
154
+ if (!line) continue;
155
+ const colonIndex = line.indexOf(":");
156
+ if (colonIndex === -1) continue;
157
+ const key = line.slice(0, colonIndex).toUpperCase();
158
+ const rawValue = line.slice(colonIndex + 1).trim();
159
+ if (!rawValue) continue;
160
+ const baseKey = normalizeVcardKey(key);
161
+ if (!baseKey || !ALLOWED_VCARD_KEYS.has(baseKey)) continue;
162
+ const value = cleanVcardValue(rawValue);
163
+ if (!value) continue;
164
+ if (baseKey === "FN" && !nameFromFn) {
165
+ nameFromFn = normalizeVcardName(value);
166
+ continue;
167
+ }
168
+ if (baseKey === "N" && !nameFromN) {
169
+ nameFromN = normalizeVcardName(value);
170
+ continue;
171
+ }
172
+ if (baseKey === "TEL") {
173
+ const phone = normalizeVcardPhone(value);
174
+ if (phone) phones.push(phone);
175
+ }
176
+ }
177
+ return {
178
+ name: nameFromFn ?? nameFromN,
179
+ phones
180
+ };
181
+ }
182
+ function normalizeVcardKey(key) {
183
+ const [primary] = key.split(";");
184
+ if (!primary) return;
185
+ const segments = primary.split(".");
186
+ return segments[segments.length - 1] || void 0;
187
+ }
188
+ function cleanVcardValue(value) {
189
+ return value.replace(/\\n/gi, " ").replace(/\\,/g, ",").replace(/\\;/g, ";").trim();
190
+ }
191
+ function normalizeVcardName(value) {
192
+ return value.replace(/;/g, " ").replace(/\s+/g, " ").trim();
193
+ }
194
+ function normalizeVcardPhone(value) {
195
+ const trimmed = value.trim();
196
+ if (!trimmed) return "";
197
+ if (normalizeLowercaseStringOrEmpty(trimmed).startsWith("tel:")) return trimmed.slice(4).trim();
198
+ return trimmed;
199
+ }
200
+ //#endregion
201
+ //#region extensions/whatsapp/src/inbound/extract.ts
202
+ const MESSAGE_WRAPPER_KEYS = [
203
+ "botInvokeMessage",
204
+ "ephemeralMessage",
205
+ "viewOnceMessage",
206
+ "viewOnceMessageV2",
207
+ "viewOnceMessageV2Extension",
208
+ "documentWithCaptionMessage",
209
+ "groupMentionedMessage"
210
+ ];
211
+ const MESSAGE_CONTENT_KEYS = [
212
+ "conversation",
213
+ "extendedTextMessage",
214
+ "imageMessage",
215
+ "videoMessage",
216
+ "audioMessage",
217
+ "documentMessage",
218
+ "stickerMessage",
219
+ "locationMessage",
220
+ "liveLocationMessage",
221
+ "contactMessage",
222
+ "contactsArrayMessage",
223
+ "buttonsResponseMessage",
224
+ "listResponseMessage",
225
+ "templateButtonReplyMessage",
226
+ "interactiveResponseMessage",
227
+ "buttonsMessage",
228
+ "listMessage"
229
+ ];
230
+ function fallbackNormalizeMessageContent(message) {
231
+ let current = message;
232
+ while (current && typeof current === "object") {
233
+ let unwrapped = false;
234
+ for (const key of MESSAGE_WRAPPER_KEYS) {
235
+ const candidate = current[key];
236
+ if (candidate && typeof candidate === "object" && "message" in candidate && candidate.message) {
237
+ current = candidate.message;
238
+ unwrapped = true;
239
+ break;
240
+ }
241
+ }
242
+ if (!unwrapped) break;
243
+ }
244
+ return current;
245
+ }
246
+ function normalizeMessage(message) {
247
+ if (typeof normalizeMessageContent === "function") return normalizeMessageContent(message);
248
+ return fallbackNormalizeMessageContent(message);
249
+ }
250
+ function fallbackGetContentType(message) {
251
+ const normalized = fallbackNormalizeMessageContent(message);
252
+ if (!normalized || typeof normalized !== "object") return;
253
+ for (const key of MESSAGE_CONTENT_KEYS) if (normalized[key] != null) return key;
254
+ }
255
+ function getMessageContentType(message) {
256
+ if (typeof getContentType === "function") return getContentType(message);
257
+ return fallbackGetContentType(message);
258
+ }
259
+ function extractMessage(message) {
260
+ if (typeof extractMessageContent === "function") return extractMessageContent(message);
261
+ const normalized = fallbackNormalizeMessageContent(message);
262
+ const contentType = fallbackGetContentType(normalized);
263
+ if (!normalized || !contentType || contentType === "conversation") return normalized;
264
+ const candidate = normalized[contentType];
265
+ return candidate && typeof candidate === "object" ? candidate : normalized;
266
+ }
267
+ function getFutureProofInnerMessage(message) {
268
+ const contentType = getMessageContentType(message);
269
+ const candidate = contentType ? message[contentType] : void 0;
270
+ if (candidate && typeof candidate === "object" && "message" in candidate && candidate.message && typeof candidate.message === "object") {
271
+ const inner = normalizeMessage(candidate.message);
272
+ if (inner) {
273
+ const innerType = getMessageContentType(inner);
274
+ if (innerType && innerType !== contentType) return inner;
275
+ }
276
+ }
277
+ }
278
+ function buildMessageChain(message) {
279
+ const chain = [];
280
+ let current = normalizeMessage(message);
281
+ while (current && chain.length < 4) {
282
+ chain.push(current);
283
+ current = getFutureProofInnerMessage(current);
284
+ }
285
+ return chain;
286
+ }
287
+ function unwrapMessage$1(message) {
288
+ return buildMessageChain(message).at(-1);
289
+ }
290
+ function extractContextInfoFromMessage(message) {
291
+ const contentType = getMessageContentType(message);
292
+ const candidate = contentType ? message[contentType] : void 0;
293
+ const contextInfo = candidate && typeof candidate === "object" && "contextInfo" in candidate ? candidate.contextInfo : void 0;
294
+ if (contextInfo) return contextInfo;
295
+ const fallback = message.extendedTextMessage?.contextInfo ?? message.imageMessage?.contextInfo ?? message.videoMessage?.contextInfo ?? message.documentMessage?.contextInfo ?? message.audioMessage?.contextInfo ?? message.stickerMessage?.contextInfo ?? message.buttonsResponseMessage?.contextInfo ?? message.listResponseMessage?.contextInfo ?? message.templateButtonReplyMessage?.contextInfo ?? message.interactiveResponseMessage?.contextInfo ?? message.buttonsMessage?.contextInfo ?? message.listMessage?.contextInfo;
296
+ if (fallback) return fallback;
297
+ for (const value of Object.values(message)) {
298
+ if (!value || typeof value !== "object") continue;
299
+ if ("contextInfo" in value) {
300
+ const candidateContext = value.contextInfo;
301
+ if (candidateContext) return candidateContext;
302
+ }
303
+ if ("message" in value) {
304
+ const inner = value.message;
305
+ if (inner) {
306
+ const innerCtx = extractContextInfo(inner);
307
+ if (innerCtx) return innerCtx;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ function extractContextInfo(message) {
313
+ for (const candidate of buildMessageChain(message)) {
314
+ const contextInfo = extractContextInfoFromMessage(candidate);
315
+ if (contextInfo) return contextInfo;
316
+ }
317
+ }
318
+ function extractMentionedJids(rawMessage) {
319
+ const message = unwrapMessage$1(rawMessage);
320
+ if (!message) return;
321
+ const flattened = [
322
+ message.extendedTextMessage?.contextInfo?.mentionedJid,
323
+ message.imageMessage?.contextInfo?.mentionedJid,
324
+ message.videoMessage?.contextInfo?.mentionedJid,
325
+ message.documentMessage?.contextInfo?.mentionedJid,
326
+ message.audioMessage?.contextInfo?.mentionedJid,
327
+ message.stickerMessage?.contextInfo?.mentionedJid,
328
+ message.buttonsResponseMessage?.contextInfo?.mentionedJid,
329
+ message.listResponseMessage?.contextInfo?.mentionedJid
330
+ ].flatMap((arr) => arr ?? []).filter(Boolean);
331
+ if (flattened.length === 0) return;
332
+ return Array.from(new Set(flattened));
333
+ }
334
+ function extractText(rawMessage) {
335
+ const message = unwrapMessage$1(rawMessage);
336
+ if (!message) return;
337
+ const extracted = extractMessage(message);
338
+ const candidates = [message, extracted && extracted !== message ? extracted : void 0];
339
+ for (const candidate of candidates) {
340
+ if (!candidate) continue;
341
+ if (typeof candidate.conversation === "string" && candidate.conversation.trim()) return candidate.conversation.trim();
342
+ const extended = candidate.extendedTextMessage?.text;
343
+ if (extended?.trim()) return extended.trim();
344
+ const caption = candidate.imageMessage?.caption ?? candidate.videoMessage?.caption ?? candidate.documentMessage?.caption;
345
+ if (caption?.trim()) return caption.trim();
346
+ }
347
+ const contactPlaceholder = extractContactPlaceholder(message) ?? (extracted && extracted !== message ? extractContactPlaceholder(extracted) : void 0);
348
+ if (contactPlaceholder) return contactPlaceholder;
349
+ }
350
+ function extractMediaPlaceholder(rawMessage) {
351
+ const message = unwrapMessage$1(rawMessage);
352
+ if (!message) return;
353
+ if (message.imageMessage) return "<media:image>";
354
+ if (message.videoMessage) return "<media:video>";
355
+ if (message.audioMessage) return "<media:audio>";
356
+ if (message.documentMessage) return "<media:document>";
357
+ if (message.stickerMessage) return "<media:sticker>";
358
+ }
359
+ function extractContactPlaceholder(rawMessage) {
360
+ const contactContext = extractContactContext(rawMessage);
361
+ if (!contactContext) return;
362
+ if (contactContext.kind === "contact") return "<contact>";
363
+ const suffix = contactContext.total === 1 ? "contact" : "contacts";
364
+ return `<contacts: ${contactContext.total} ${suffix}>`;
365
+ }
366
+ function extractContactContext(rawMessage) {
367
+ const message = unwrapMessage$1(rawMessage);
368
+ if (!message) return;
369
+ const contact = message.contactMessage ?? void 0;
370
+ if (contact) {
371
+ const { name, phones } = describeContact({
372
+ displayName: contact.displayName,
373
+ vcard: contact.vcard
374
+ });
375
+ return {
376
+ kind: "contact",
377
+ total: 1,
378
+ contacts: [{
379
+ name,
380
+ phones
381
+ }]
382
+ };
383
+ }
384
+ const contactsArray = message.contactsArrayMessage?.contacts ?? void 0;
385
+ if (!contactsArray || contactsArray.length === 0) return;
386
+ return {
387
+ kind: "contacts",
388
+ total: contactsArray.length,
389
+ contacts: contactsArray.map((entry) => describeContact({
390
+ displayName: entry.displayName,
391
+ vcard: entry.vcard
392
+ }))
393
+ };
394
+ }
395
+ function describeContact(input) {
396
+ const displayName = (input.displayName ?? "").trim();
397
+ const parsed = parseVcard(input.vcard ?? void 0);
398
+ return {
399
+ name: displayName || parsed.name,
400
+ phones: parsed.phones
401
+ };
402
+ }
403
+ function extractLocationData(rawMessage) {
404
+ const message = unwrapMessage$1(rawMessage);
405
+ if (!message) return null;
406
+ const live = message.liveLocationMessage ?? void 0;
407
+ if (live) {
408
+ const latitudeRaw = live.degreesLatitude;
409
+ const longitudeRaw = live.degreesLongitude;
410
+ if (latitudeRaw != null && longitudeRaw != null) {
411
+ const latitude = latitudeRaw;
412
+ const longitude = longitudeRaw;
413
+ if (Number.isFinite(latitude) && Number.isFinite(longitude)) return {
414
+ latitude,
415
+ longitude,
416
+ accuracy: live.accuracyInMeters ?? void 0,
417
+ caption: live.caption ?? void 0,
418
+ source: "live",
419
+ isLive: true
420
+ };
421
+ }
422
+ }
423
+ const location = message.locationMessage ?? void 0;
424
+ if (location) {
425
+ const latitudeRaw = location.degreesLatitude;
426
+ const longitudeRaw = location.degreesLongitude;
427
+ if (latitudeRaw != null && longitudeRaw != null) {
428
+ const latitude = latitudeRaw;
429
+ const longitude = longitudeRaw;
430
+ if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
431
+ const isLive = Boolean(location.isLive);
432
+ return {
433
+ latitude,
434
+ longitude,
435
+ accuracy: location.accuracyInMeters ?? void 0,
436
+ name: location.name ?? void 0,
437
+ address: location.address ?? void 0,
438
+ caption: location.comment ?? void 0,
439
+ source: isLive ? "live" : location.name || location.address ? "place" : "pin",
440
+ isLive
441
+ };
442
+ }
443
+ }
444
+ }
445
+ return null;
446
+ }
447
+ function describeReplyContext(rawMessage) {
448
+ const message = unwrapMessage$1(rawMessage);
449
+ if (!message) return null;
450
+ const contextInfo = extractContextInfo(message);
451
+ const quoted = normalizeMessage(contextInfo?.quotedMessage);
452
+ if (!quoted) return null;
453
+ const location = extractLocationData(quoted);
454
+ const locationText = location ? formatLocationText(location) : void 0;
455
+ let body = [extractText(quoted), locationText].filter(Boolean).join("\n").trim();
456
+ if (!body) body = extractMediaPlaceholder(quoted);
457
+ if (!body) {
458
+ const quotedType = quoted ? getMessageContentType(quoted) : void 0;
459
+ logVerbose(`Quoted message missing extractable body${quotedType ? ` (type ${quotedType})` : ""}`);
460
+ return null;
461
+ }
462
+ const senderJid = contextInfo?.participant ?? void 0;
463
+ const sender = resolveComparableIdentity({
464
+ jid: senderJid,
465
+ label: senderJid ? jidToE164(senderJid) ?? senderJid : "unknown sender"
466
+ });
467
+ return {
468
+ id: contextInfo?.stanzaId || void 0,
469
+ body,
470
+ sender
471
+ };
472
+ }
473
+ function hasInteractiveResponseContent(message) {
474
+ if (!message) return false;
475
+ return Boolean(message.buttonsResponseMessage || message.listResponseMessage || message.templateButtonReplyMessage || message.interactiveResponseMessage);
476
+ }
477
+ /**
478
+ * Fast check that a Baileys message carries user-visible inbound content
479
+ * (text, media, contact, location, button/list selection). Returns false for
480
+ * protocol/receipt/typing notifications that arrive on the same
481
+ * `messages.upsert` stream as real messages but should not trigger pairing
482
+ * access-control side effects.
483
+ */
484
+ function hasInboundUserContent(rawMessage) {
485
+ if (!rawMessage) return false;
486
+ if (extractText(rawMessage)) return true;
487
+ if (extractMediaPlaceholder(rawMessage)) return true;
488
+ if (extractLocationData(rawMessage)) return true;
489
+ for (const candidate of buildMessageChain(rawMessage)) if (hasInteractiveResponseContent(candidate)) return true;
490
+ return false;
491
+ }
492
+ //#endregion
493
+ //#region extensions/whatsapp/src/inbound/lifecycle.ts
494
+ function attachEmitterListener(emitter, event, listener) {
495
+ emitter.on(event, listener);
496
+ return () => {
497
+ if (typeof emitter.off === "function") {
498
+ emitter.off(event, listener);
499
+ return;
500
+ }
501
+ if (typeof emitter.removeListener === "function") emitter.removeListener(event, listener);
502
+ };
503
+ }
504
+ function closeInboundMonitorSocket(sock) {
505
+ if (typeof sock.end === "function") {
506
+ sock.end(/* @__PURE__ */ new Error("OpenClaw WhatsApp listener close"));
507
+ return;
508
+ }
509
+ sock.ws?.close?.();
510
+ }
511
+ //#endregion
512
+ //#region extensions/whatsapp/src/inbound/media.ts
513
+ function unwrapMessage(message) {
514
+ return normalizeMessageContent$1(message);
515
+ }
516
+ /**
517
+ * Resolve the MIME type for an inbound media message.
518
+ * Falls back to WhatsApp's standard formats when Baileys omits the MIME.
519
+ */
520
+ function resolveMediaMimetype(message) {
521
+ const explicit = message.imageMessage?.mimetype ?? message.videoMessage?.mimetype ?? message.documentMessage?.mimetype ?? message.audioMessage?.mimetype ?? message.stickerMessage?.mimetype ?? void 0;
522
+ if (explicit) return explicit;
523
+ if (message.audioMessage) return "audio/ogg; codecs=opus";
524
+ if (message.imageMessage) return "image/jpeg";
525
+ if (message.videoMessage) return "video/mp4";
526
+ if (message.stickerMessage) return "image/webp";
527
+ }
528
+ async function downloadInboundMedia(msg, sock) {
529
+ const message = unwrapMessage(msg.message);
530
+ if (!message) return;
531
+ const mimetype = resolveMediaMimetype(message);
532
+ const fileName = message.documentMessage?.fileName ?? void 0;
533
+ if (!message.imageMessage && !message.videoMessage && !message.documentMessage && !message.audioMessage && !message.stickerMessage) return;
534
+ try {
535
+ return {
536
+ buffer: await downloadMediaMessage(msg, "buffer", {}, {
537
+ reuploadRequest: sock.updateMediaMessage,
538
+ logger: sock.logger
539
+ }),
540
+ mimetype,
541
+ fileName
542
+ };
543
+ } catch (err) {
544
+ logVerbose(`downloadMediaMessage failed: ${String(err)}`);
545
+ return;
546
+ }
547
+ }
548
+ async function downloadQuotedInboundMedia(msg, sock) {
549
+ const contextInfo = extractContextInfo(unwrapMessage(msg.message));
550
+ if (!contextInfo?.quotedMessage) return;
551
+ const quotedMessage = contextInfo.quotedMessage;
552
+ return downloadInboundMedia({
553
+ key: {
554
+ id: contextInfo?.stanzaId || void 0,
555
+ remoteJid: contextInfo.remoteJid ?? msg.key?.remoteJid ?? void 0,
556
+ participant: contextInfo?.participant ?? void 0,
557
+ fromMe: false
558
+ },
559
+ message: quotedMessage,
560
+ messageTimestamp: msg.messageTimestamp
561
+ }, sock);
562
+ }
563
+ //#endregion
564
+ //#region extensions/whatsapp/src/inbound/outbound-mentions.ts
565
+ const CODE_FENCE_RE = /```[\s\S]*?```/g;
566
+ const INLINE_CODE_RE = /`[^`\n]+`/g;
567
+ const OUTBOUND_MENTION_RE = /@(\+?\d+)/g;
568
+ const KNOWN_USER_JID_RE = /^(\d+)(?::\d+)?@(s\.whatsapp\.net|hosted|lid|hosted\.lid|c\.us)$/i;
569
+ const PHONE_JID_DOMAIN_RE = /^(s\.whatsapp\.net|hosted|c\.us)$/i;
570
+ const LID_JID_DOMAIN_RE = /^(lid|hosted\.lid)$/i;
571
+ function isWhatsAppGroupJid(jid) {
572
+ return jid.endsWith("@g.us");
573
+ }
574
+ function mayContainWhatsAppOutboundMention(text) {
575
+ return /@\+?\d/.test(text);
576
+ }
577
+ function collectCodeRanges(text) {
578
+ const ranges = [];
579
+ for (const match of text.matchAll(CODE_FENCE_RE)) ranges.push({
580
+ start: match.index,
581
+ end: match.index + match[0].length
582
+ });
583
+ for (const match of text.matchAll(INLINE_CODE_RE)) {
584
+ const start = match.index;
585
+ if (ranges.some((range) => start >= range.start && start < range.end)) continue;
586
+ ranges.push({
587
+ start,
588
+ end: start + match[0].length
589
+ });
590
+ }
591
+ return ranges.toSorted((a, b) => a.start - b.start);
592
+ }
593
+ function isInRange(index, ranges) {
594
+ return ranges.some((range) => index >= range.start && index < range.end);
595
+ }
596
+ function normalizeKnownUserJid(value) {
597
+ const trimmed = value.replace(/^whatsapp:/i, "").trim();
598
+ const jidMatch = trimmed.match(KNOWN_USER_JID_RE);
599
+ if (jidMatch) {
600
+ const domain = jidMatch[2].toLowerCase() === "c.us" ? "s.whatsapp.net" : jidMatch[2].toLowerCase();
601
+ return `${jidMatch[1]}@${domain}`;
602
+ }
603
+ const digits = trimmed.startsWith("+") ? trimmed.replace(/\D/g, "") : /^\d+$/.test(trimmed) ? trimmed : "";
604
+ return digits ? `${digits}@s.whatsapp.net` : null;
605
+ }
606
+ function extractKnownJidParts(value) {
607
+ const normalized = normalizeKnownUserJid(value);
608
+ if (!normalized) return null;
609
+ const match = normalized.match(/^(\d+)@(.+)$/);
610
+ return match ? {
611
+ user: match[1],
612
+ domain: match[2]
613
+ } : null;
614
+ }
615
+ function extractPhoneDigits(value) {
616
+ if (!value) return null;
617
+ const trimmed = value.replace(/^whatsapp:/i, "").trim();
618
+ if (trimmed.startsWith("+") || /^\d+$/.test(trimmed)) return trimmed.replace(/\D/g, "") || null;
619
+ const parts = extractKnownJidParts(trimmed);
620
+ return parts && PHONE_JID_DOMAIN_RE.test(parts.domain) ? parts.user : null;
621
+ }
622
+ function extractLidDigits(value) {
623
+ if (!value) return null;
624
+ const parts = extractKnownJidParts(value);
625
+ return parts && LID_JID_DOMAIN_RE.test(parts.domain) ? parts.user : null;
626
+ }
627
+ function isLidJid(jid) {
628
+ const parts = extractKnownJidParts(jid);
629
+ return Boolean(parts && LID_JID_DOMAIN_RE.test(parts.domain));
630
+ }
631
+ function lidReplacementText(jid) {
632
+ const parts = extractKnownJidParts(jid);
633
+ if (!parts || !LID_JID_DOMAIN_RE.test(parts.domain)) return;
634
+ return `@${parts.user}`;
635
+ }
636
+ function participantValues(participant) {
637
+ return typeof participant === "string" ? { id: participant } : participant;
638
+ }
639
+ function chooseMentionJid(participant) {
640
+ const values = participantValues(participant);
641
+ const idJid = normalizeKnownUserJid(values.id ?? "");
642
+ const lidJid = normalizeKnownUserJid(values.lid ?? "");
643
+ return (idJid && isLidJid(idJid) ? idJid : null) ?? (lidJid && isLidJid(lidJid) ? lidJid : null) ?? idJid ?? lidJid ?? normalizeKnownUserJid(values.phoneNumber ?? "") ?? normalizeKnownUserJid(values.e164 ?? "");
644
+ }
645
+ function buildMentionTargetMaps(participants) {
646
+ const byPhone = /* @__PURE__ */ new Map();
647
+ const byLid = /* @__PURE__ */ new Map();
648
+ for (const participant of participants) {
649
+ const mentionJid = chooseMentionJid(participant);
650
+ if (!mentionJid) continue;
651
+ const target = {
652
+ mentionJid,
653
+ ...isLidJid(mentionJid) ? { replacementText: lidReplacementText(mentionJid) } : {}
654
+ };
655
+ const values = participantValues(participant);
656
+ for (const value of [
657
+ values.id,
658
+ values.phoneNumber,
659
+ values.e164
660
+ ]) {
661
+ const digits = extractPhoneDigits(value);
662
+ if (digits && !byPhone.has(digits)) byPhone.set(digits, target);
663
+ }
664
+ for (const value of [values.id, values.lid]) {
665
+ const digits = extractLidDigits(value);
666
+ if (digits && !byLid.has(digits)) byLid.set(digits, target);
667
+ }
668
+ }
669
+ return {
670
+ byPhone,
671
+ byLid
672
+ };
673
+ }
674
+ function shouldSkipMentionAt(text, index, end, codeRanges) {
675
+ if (isInRange(index, codeRanges)) return true;
676
+ const previous = index > 0 ? text[index - 1] : "";
677
+ const next = text[end] ?? "";
678
+ return Boolean(previous && /[\w@]/.test(previous) || next && /[\w@]/.test(next));
679
+ }
680
+ function resolveWhatsAppOutboundMentions(params) {
681
+ if (!isWhatsAppGroupJid(params.chatJid) || !mayContainWhatsAppOutboundMention(params.text) || !params.participants?.length) return {
682
+ text: params.text,
683
+ mentionedJids: []
684
+ };
685
+ const { byPhone, byLid } = buildMentionTargetMaps(params.participants);
686
+ if (byPhone.size === 0 && byLid.size === 0) return {
687
+ text: params.text,
688
+ mentionedJids: []
689
+ };
690
+ const codeRanges = collectCodeRanges(params.text);
691
+ const replacements = [];
692
+ const mentionedJids = [];
693
+ const seenMentionJids = /* @__PURE__ */ new Set();
694
+ for (const match of params.text.matchAll(OUTBOUND_MENTION_RE)) {
695
+ const start = match.index;
696
+ const token = match[0];
697
+ if (shouldSkipMentionAt(params.text, start, start + token.length, codeRanges)) continue;
698
+ const digits = match[1].replace(/\D/g, "");
699
+ const target = token.startsWith("@+") ? byPhone.get(digits) ?? byLid.get(digits) : byLid.get(digits) ?? byPhone.get(digits);
700
+ if (!target) continue;
701
+ if (!seenMentionJids.has(target.mentionJid)) {
702
+ seenMentionJids.add(target.mentionJid);
703
+ mentionedJids.push(target.mentionJid);
704
+ }
705
+ if (target.replacementText && target.replacementText !== token) replacements.push({
706
+ start,
707
+ end: start + token.length,
708
+ text: target.replacementText
709
+ });
710
+ }
711
+ if (replacements.length === 0) return {
712
+ text: params.text,
713
+ mentionedJids
714
+ };
715
+ let text = "";
716
+ let cursor = 0;
717
+ for (const replacement of replacements) {
718
+ text += params.text.slice(cursor, replacement.start);
719
+ text += replacement.text;
720
+ cursor = replacement.end;
721
+ }
722
+ text += params.text.slice(cursor);
723
+ return {
724
+ text,
725
+ mentionedJids
726
+ };
727
+ }
728
+ function addWhatsAppOutboundMentionsToContent(content, mentionedJids) {
729
+ return mentionedJids.length > 0 ? {
730
+ ...content,
731
+ mentions: [...mentionedJids]
732
+ } : content;
733
+ }
734
+ //#endregion
735
+ //#region extensions/whatsapp/src/inbound/send-result.ts
736
+ function normalizeKey(key) {
737
+ const id = typeof key?.id === "string" ? key.id.trim() : "";
738
+ if (!id) return;
739
+ return {
740
+ id,
741
+ remoteJid: key?.remoteJid,
742
+ fromMe: key?.fromMe,
743
+ participant: key?.participant
744
+ };
745
+ }
746
+ function normalizeWhatsAppSendResult(result, kind) {
747
+ const key = normalizeKey(result?.key);
748
+ return {
749
+ kind,
750
+ messageId: key?.id ?? "unknown",
751
+ messageIds: key ? [key.id] : [],
752
+ keys: key ? [key] : [],
753
+ providerAccepted: Boolean(key)
754
+ };
755
+ }
756
+ function combineWhatsAppSendResults(kind, results) {
757
+ const messageIds = [...new Set(results.flatMap((result) => result.messageIds))];
758
+ const keys = results.flatMap((result) => result.keys);
759
+ return {
760
+ kind,
761
+ messageId: messageIds[0] ?? "unknown",
762
+ messageIds,
763
+ keys,
764
+ providerAccepted: results.some((result) => result.providerAccepted)
765
+ };
766
+ }
767
+ //#endregion
768
+ //#region extensions/whatsapp/src/inbound/send-api.ts
769
+ function recordWhatsAppOutbound(accountId) {
770
+ recordChannelActivity({
771
+ channel: "whatsapp",
772
+ accountId,
773
+ direction: "outbound"
774
+ });
775
+ }
776
+ function createWebSendApi(params) {
777
+ const resolveMentions = async (jid, text) => params.resolveOutboundMentions ? await params.resolveOutboundMentions({
778
+ jid,
779
+ text
780
+ }) : {
781
+ text,
782
+ mentionedJids: []
783
+ };
784
+ return {
785
+ sendMessage: async (to, text, mediaBuffer, mediaType, sendOptions) => {
786
+ const jid = toWhatsappJid(to);
787
+ let payload;
788
+ if (mediaBuffer) mediaType ??= "application/octet-stream";
789
+ const shouldSendAudioText = Boolean(mediaBuffer && mediaType?.startsWith("audio/") && text.trim());
790
+ const resolvedPayloadText = shouldSendAudioText ? {
791
+ text,
792
+ mentionedJids: []
793
+ } : await resolveMentions(jid, text);
794
+ if (mediaBuffer && mediaType) if (mediaType.startsWith("image/")) payload = {
795
+ image: mediaBuffer,
796
+ caption: resolvedPayloadText.text || void 0,
797
+ mimetype: mediaType
798
+ };
799
+ else if (mediaType.startsWith("audio/")) payload = {
800
+ audio: mediaBuffer,
801
+ ptt: true,
802
+ mimetype: mediaType
803
+ };
804
+ else if (mediaType.startsWith("video/")) {
805
+ const gifPlayback = sendOptions?.gifPlayback;
806
+ payload = {
807
+ video: mediaBuffer,
808
+ caption: resolvedPayloadText.text || void 0,
809
+ mimetype: mediaType,
810
+ ...gifPlayback ? { gifPlayback: true } : {}
811
+ };
812
+ } else payload = {
813
+ document: mediaBuffer,
814
+ fileName: sendOptions?.fileName?.trim() || "file",
815
+ caption: resolvedPayloadText.text || void 0,
816
+ mimetype: mediaType
817
+ };
818
+ else payload = { text: resolvedPayloadText.text };
819
+ payload = addWhatsAppOutboundMentionsToContent(payload, resolvedPayloadText.mentionedJids);
820
+ const quotedOpts = buildQuotedMessageOptions({
821
+ messageId: sendOptions?.quotedMessageKey?.id,
822
+ remoteJid: sendOptions?.quotedMessageKey?.remoteJid,
823
+ fromMe: sendOptions?.quotedMessageKey?.fromMe,
824
+ participant: sendOptions?.quotedMessageKey?.participant,
825
+ messageText: sendOptions?.quotedMessageKey?.messageText
826
+ });
827
+ const results = [normalizeWhatsAppSendResult(quotedOpts ? await params.sock.sendMessage(jid, payload, quotedOpts) : await params.sock.sendMessage(jid, payload), mediaBuffer ? "media" : "text")];
828
+ if (shouldSendAudioText) {
829
+ const resolvedAudioText = await resolveMentions(jid, text);
830
+ const textPayload = addWhatsAppOutboundMentionsToContent({ text: resolvedAudioText.text }, resolvedAudioText.mentionedJids);
831
+ const textResult = quotedOpts ? await params.sock.sendMessage(jid, textPayload, quotedOpts) : await params.sock.sendMessage(jid, textPayload);
832
+ results.push(normalizeWhatsAppSendResult(textResult, "text"));
833
+ }
834
+ recordWhatsAppOutbound(sendOptions?.accountId ?? params.defaultAccountId);
835
+ return combineWhatsAppSendResults(mediaBuffer ? "media" : "text", results);
836
+ },
837
+ sendPoll: async (to, poll) => {
838
+ const jid = toWhatsappJid(to);
839
+ const result = await params.sock.sendMessage(jid, { poll: {
840
+ name: poll.question,
841
+ values: poll.options,
842
+ selectableCount: poll.maxSelections ?? 1
843
+ } });
844
+ recordWhatsAppOutbound(params.defaultAccountId);
845
+ return normalizeWhatsAppSendResult(result, "poll");
846
+ },
847
+ sendReaction: async (chatJid, messageId, emoji, fromMe, participant) => {
848
+ const jid = toWhatsappJid(chatJid);
849
+ return normalizeWhatsAppSendResult(await params.sock.sendMessage(jid, { react: {
850
+ text: emoji,
851
+ key: {
852
+ remoteJid: jid,
853
+ id: messageId,
854
+ fromMe,
855
+ participant: participant ? toWhatsappJid(participant) : void 0
856
+ }
857
+ } }), "reaction");
858
+ },
859
+ sendComposingTo: async (to) => {
860
+ const jid = toWhatsappJid(to);
861
+ if (isWhatsAppNewsletterJid(jid)) return;
862
+ await params.sock.sendPresenceUpdate("composing", jid);
863
+ }
864
+ };
865
+ }
866
+ //#endregion
867
+ //#region extensions/whatsapp/src/inbound/monitor.ts
868
+ const LOGGED_OUT_STATUS = DisconnectReason$1?.loggedOut ?? 401;
869
+ const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress";
870
+ const GROUP_META_TTL_MS = 300 * 1e3;
871
+ function rememberGroupMetadataCacheEntry(cache, jid, entry) {
872
+ if (cache.has(jid)) cache.delete(jid);
873
+ cache.set(jid, entry);
874
+ while (cache.size > 500) {
875
+ const oldest = cache.keys().next();
876
+ if (oldest.done) break;
877
+ cache.delete(oldest.value);
878
+ }
879
+ }
880
+ function readGroupMetadataCacheEntry(cache, jid) {
881
+ const entry = cache.get(jid);
882
+ if (!entry) return null;
883
+ if (entry.expires <= Date.now()) {
884
+ cache.delete(jid);
885
+ return null;
886
+ }
887
+ cache.delete(jid);
888
+ cache.set(jid, entry);
889
+ return entry;
890
+ }
891
+ function logWhatsAppVerbose(enabled, message) {
892
+ if (!enabled) return;
893
+ defaultRuntime.log(message);
894
+ }
895
+ function isGroupJid(jid) {
896
+ return (typeof isJidGroup === "function" ? isJidGroup(jid) : jid.endsWith("@g.us")) === true;
897
+ }
898
+ function isRetryableSendDisconnectError(err) {
899
+ return /closed|reset|timed\s*out|disconnect|no active socket/i.test(formatError(err));
900
+ }
901
+ function shouldClearSocketRefAfterSendFailure(err) {
902
+ return /closed|reset|disconnect|no active socket/i.test(formatError(err));
903
+ }
904
+ function isNonEmptyString(value) {
905
+ return Boolean(value);
906
+ }
907
+ async function attachWebInboxToSocket(options) {
908
+ const inboundLogger = getChildLogger({ module: "web-inbound" });
909
+ const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
910
+ const sock = options.sock;
911
+ const connectedAtMs = Date.now();
912
+ if (options.socketRef) options.socketRef.current = sock;
913
+ const getCurrentSock = () => options.socketRef ? options.socketRef.current : sock;
914
+ const shouldRetryDisconnect = () => options.shouldRetryDisconnect?.() === true;
915
+ const disconnectRetryPolicy = options.disconnectRetryPolicy ?? DEFAULT_RECONNECT_POLICY;
916
+ const sendRetryMaxAttempts = disconnectRetryPolicy.maxAttempts > 0 ? disconnectRetryPolicy.maxAttempts : DEFAULT_RECONNECT_POLICY.maxAttempts;
917
+ let onCloseResolve = null;
918
+ const onClose = new Promise((resolve) => {
919
+ onCloseResolve = resolve;
920
+ });
921
+ const resolveClose = (reason) => {
922
+ if (!onCloseResolve) return;
923
+ const resolver = onCloseResolve;
924
+ onCloseResolve = null;
925
+ resolver(reason);
926
+ };
927
+ const presence = options.selfChatMode ? "unavailable" : "available";
928
+ try {
929
+ await sock.sendPresenceUpdate(presence);
930
+ logWhatsAppVerbose(options.verbose, `Sent global '${presence}' presence on connect`);
931
+ } catch (err) {
932
+ logWhatsAppVerbose(options.verbose, `Failed to send '${presence}' presence on connect: ${String(err)}`);
933
+ }
934
+ const selfIdentity = await readWebSelfIdentityForDecision(options.authDir, sock.user);
935
+ if (selfIdentity.outcome === "unstable") throw new WhatsAppAuthUnstableError("WhatsApp auth state is still stabilizing; retrying inbox attach.");
936
+ const self = selfIdentity.identity;
937
+ const finalizeInboundDedupe = async (entries, error) => {
938
+ const dedupeKeys = [...new Set(entries.map((entry) => entry.dedupeKey).filter(isNonEmptyString))];
939
+ if (dedupeKeys.length === 0) return;
940
+ if (error instanceof WhatsAppRetryableInboundError) {
941
+ dedupeKeys.forEach((dedupeKey) => releaseRecentInboundMessage(dedupeKey, error));
942
+ return;
943
+ }
944
+ await Promise.all(dedupeKeys.map((dedupeKey) => commitRecentInboundMessage(dedupeKey)));
945
+ };
946
+ const debouncer = createInboundDebouncer({
947
+ debounceMs: options.debounceMs ?? 0,
948
+ buildKey: (msg) => {
949
+ const sender = msg.sender;
950
+ const senderKey = msg.chatType === "group" ? getPrimaryIdentityId(sender ?? null) ?? msg.senderJid ?? msg.senderE164 ?? msg.senderName ?? msg.from : msg.from;
951
+ if (!senderKey) return null;
952
+ const conversationKey = msg.chatType === "group" ? msg.chatId : msg.from;
953
+ return `${msg.accountId}:${conversationKey}:${senderKey}`;
954
+ },
955
+ shouldDebounce: options.shouldDebounce,
956
+ onFlush: async (entries) => {
957
+ const last = entries.at(-1);
958
+ if (!last) return;
959
+ try {
960
+ if (entries.length === 1) {
961
+ await options.onMessage(last);
962
+ await finalizeInboundDedupe(entries);
963
+ return;
964
+ }
965
+ const mentioned = /* @__PURE__ */ new Set();
966
+ for (const entry of entries) for (const jid of entry.mentions ?? entry.mentionedJids ?? []) mentioned.add(jid);
967
+ const combinedBody = entries.map((entry) => entry.body).filter(Boolean).join("\n");
968
+ const combinedMessage = {
969
+ ...last,
970
+ body: combinedBody,
971
+ mentions: mentioned.size > 0 ? Array.from(mentioned) : void 0,
972
+ mentionedJids: mentioned.size > 0 ? Array.from(mentioned) : void 0,
973
+ isBatched: true
974
+ };
975
+ await options.onMessage(combinedMessage);
976
+ await finalizeInboundDedupe(entries);
977
+ } catch (error) {
978
+ await finalizeInboundDedupe(entries, error);
979
+ throw error;
980
+ }
981
+ },
982
+ onError: (err) => {
983
+ inboundLogger.error({ error: String(err) }, "failed handling inbound web message");
984
+ inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
985
+ }
986
+ });
987
+ const groupMetadataCache = options.groupMetadataCache ?? /* @__PURE__ */ new Map();
988
+ const groupMetaCache = /* @__PURE__ */ new Map();
989
+ const lidLookup = sock.signalRepository?.lidMapping;
990
+ const resolveInboundJid = async (jid) => resolveJidToE164(jid, {
991
+ authDir: options.authDir,
992
+ lidLookup
993
+ });
994
+ const rememberOutboundMessage = (remoteJid, result) => {
995
+ const messageId = typeof result === "object" && result && "key" in result ? result.key?.id ?? "" : "";
996
+ if (!messageId) return;
997
+ rememberRecentOutboundMessage({
998
+ accountId: options.accountId,
999
+ remoteJid,
1000
+ messageId
1001
+ });
1002
+ };
1003
+ const sendTrackedMessage = async (jid, content, sendOptions) => {
1004
+ let lastErr = new Error(RECONNECT_IN_PROGRESS_ERROR);
1005
+ for (let attempt = 1;; attempt++) {
1006
+ const currentSock = getCurrentSock();
1007
+ if (currentSock) try {
1008
+ const result = sendOptions ? await currentSock.sendMessage(jid, content, sendOptions) : await currentSock.sendMessage(jid, content);
1009
+ rememberOutboundMessage(jid, result);
1010
+ return result;
1011
+ } catch (err) {
1012
+ if (!shouldRetryDisconnect() || !isRetryableSendDisconnectError(err)) throw err;
1013
+ lastErr = err;
1014
+ if (shouldClearSocketRefAfterSendFailure(err) && options.socketRef?.current === currentSock) options.socketRef.current = null;
1015
+ }
1016
+ else if (!shouldRetryDisconnect()) throw lastErr;
1017
+ if (attempt >= sendRetryMaxAttempts) throw lastErr;
1018
+ const delayMs = computeBackoff(disconnectRetryPolicy, attempt);
1019
+ logWhatsAppVerbose(options.verbose, `Waiting ${delayMs}ms for WhatsApp reconnect before retrying send to ${jid}: ${formatError(lastErr)}`);
1020
+ try {
1021
+ await sleepWithAbort(delayMs, options.disconnectRetryAbortSignal);
1022
+ } catch {
1023
+ throw lastErr;
1024
+ }
1025
+ }
1026
+ };
1027
+ const summarizeGroupMeta = async (meta) => {
1028
+ const participantEntries = await Promise.all(meta.participants?.map(async (p) => {
1029
+ const mapped = await resolveInboundJid(p.id);
1030
+ return {
1031
+ display: mapped ?? p.id,
1032
+ mention: {
1033
+ id: p.id,
1034
+ lid: p.lid,
1035
+ phoneNumber: p.phoneNumber,
1036
+ e164: mapped
1037
+ }
1038
+ };
1039
+ }) ?? []);
1040
+ const participants = participantEntries.map((entry) => entry.display).filter(Boolean);
1041
+ const mentionParticipants = participantEntries.map((entry) => entry.mention);
1042
+ return {
1043
+ subject: meta.subject,
1044
+ participants,
1045
+ mentionParticipants,
1046
+ expires: Date.now() + GROUP_META_TTL_MS
1047
+ };
1048
+ };
1049
+ const summarizeGroupMetaForReconnectCache = (meta) => ({
1050
+ subject: meta.subject,
1051
+ expires: Date.now() + GROUP_META_TTL_MS
1052
+ });
1053
+ const getGroupMeta = async (jid) => {
1054
+ const cached = readGroupMetadataCacheEntry(groupMetaCache, jid);
1055
+ if (cached) return cached;
1056
+ try {
1057
+ const entry = await summarizeGroupMeta(await (getCurrentSock() ?? sock).groupMetadata(jid));
1058
+ rememberGroupMetadataCacheEntry(groupMetadataCache, jid, {
1059
+ subject: entry.subject,
1060
+ expires: entry.expires
1061
+ });
1062
+ rememberGroupMetadataCacheEntry(groupMetaCache, jid, entry);
1063
+ return entry;
1064
+ } catch (err) {
1065
+ const hydrated = readGroupMetadataCacheEntry(groupMetadataCache, jid);
1066
+ if (hydrated) {
1067
+ rememberGroupMetadataCacheEntry(groupMetaCache, jid, hydrated);
1068
+ logWhatsAppVerbose(options.verbose, `Using cached group metadata for ${jid} after fetch failure: ${String(err)}`);
1069
+ return hydrated;
1070
+ }
1071
+ logWhatsAppVerbose(options.verbose, `Failed to fetch group metadata for ${jid}: ${String(err)}`);
1072
+ return { expires: Date.now() + GROUP_META_TTL_MS };
1073
+ }
1074
+ };
1075
+ const resolveOutboundMentionsForGroup = async (jid, text) => {
1076
+ if (!isGroupJid(jid) || !mayContainWhatsAppOutboundMention(text)) return {
1077
+ text,
1078
+ mentionedJids: []
1079
+ };
1080
+ return resolveWhatsAppOutboundMentions({
1081
+ chatJid: jid,
1082
+ text,
1083
+ participants: (await getGroupMeta(jid)).mentionParticipants
1084
+ });
1085
+ };
1086
+ const applyOutboundMentionsToContent = async (jid, content) => {
1087
+ if ("text" in content && typeof content.text === "string") {
1088
+ const resolved = await resolveOutboundMentionsForGroup(jid, content.text);
1089
+ return addWhatsAppOutboundMentionsToContent({
1090
+ ...content,
1091
+ text: resolved.text
1092
+ }, resolved.mentionedJids);
1093
+ }
1094
+ const caption = content.caption;
1095
+ if (typeof caption === "string") {
1096
+ const resolved = await resolveOutboundMentionsForGroup(jid, caption);
1097
+ return addWhatsAppOutboundMentionsToContent({
1098
+ ...content,
1099
+ caption: resolved.text
1100
+ }, resolved.mentionedJids);
1101
+ }
1102
+ return content;
1103
+ };
1104
+ const normalizeInboundMessage = async (msg) => {
1105
+ const id = msg.key?.id ?? void 0;
1106
+ const remoteJid = msg.key?.remoteJid;
1107
+ if (!remoteJid) return null;
1108
+ if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) return null;
1109
+ const group = isGroupJid(remoteJid);
1110
+ if (Boolean(msg.key?.fromMe) && id && isRecentOutboundMessage({
1111
+ accountId: options.accountId,
1112
+ remoteJid,
1113
+ messageId: id
1114
+ })) {
1115
+ logWhatsAppVerbose(options.verbose, `Skipping recent outbound WhatsApp echo ${id} for ${remoteJid}`);
1116
+ return null;
1117
+ }
1118
+ if (!hasInboundUserContent(msg.message ?? void 0)) return null;
1119
+ const participantJid = msg.key?.participant ?? void 0;
1120
+ const from = group ? remoteJid : await resolveInboundJid(remoteJid);
1121
+ if (!from) return null;
1122
+ const senderE164 = group ? participantJid ? await resolveInboundJid(participantJid) : null : from;
1123
+ let groupSubject;
1124
+ let groupParticipants;
1125
+ if (group) {
1126
+ const meta = await getGroupMeta(remoteJid);
1127
+ groupSubject = meta.subject;
1128
+ groupParticipants = meta.participants;
1129
+ }
1130
+ const messageTimestampMs = msg.messageTimestamp ? Number(msg.messageTimestamp) * 1e3 : void 0;
1131
+ const access = await checkInboundAccessControl({
1132
+ cfg: options.cfg,
1133
+ accountId: options.accountId,
1134
+ from,
1135
+ selfE164: self.e164 ?? null,
1136
+ senderE164,
1137
+ group,
1138
+ pushName: msg.pushName ?? void 0,
1139
+ isFromMe: Boolean(msg.key?.fromMe),
1140
+ messageTimestampMs,
1141
+ connectedAtMs,
1142
+ verbose: options.verbose,
1143
+ sock: { sendMessage: (jid, content) => sendTrackedMessage(jid, content) },
1144
+ remoteJid
1145
+ });
1146
+ if (!access.allowed) return null;
1147
+ return {
1148
+ id,
1149
+ remoteJid,
1150
+ group,
1151
+ participantJid,
1152
+ from,
1153
+ senderE164,
1154
+ groupSubject,
1155
+ groupParticipants,
1156
+ messageTimestampMs,
1157
+ access
1158
+ };
1159
+ };
1160
+ const maybeMarkInboundAsRead = async (inbound) => {
1161
+ const { id, remoteJid, participantJid, access } = inbound;
1162
+ if (id && !access.isSelfChat && options.sendReadReceipts !== false) try {
1163
+ await sock.readMessages([{
1164
+ remoteJid,
1165
+ id,
1166
+ participant: participantJid,
1167
+ fromMe: false
1168
+ }]);
1169
+ const suffix = participantJid ? ` (participant ${participantJid})` : "";
1170
+ logWhatsAppVerbose(options.verbose, `Marked message ${id} as read for ${remoteJid}${suffix}`);
1171
+ } catch (err) {
1172
+ logWhatsAppVerbose(options.verbose, `Failed to mark message ${id} read: ${String(err)}`);
1173
+ }
1174
+ else if (id && access.isSelfChat && options.verbose) logWhatsAppVerbose(options.verbose, `Self-chat mode: skipping read receipt for ${id}`);
1175
+ };
1176
+ const enrichInboundMessage = async (msg) => {
1177
+ const location = extractLocationData(msg.message ?? void 0);
1178
+ const locationText = location ? formatLocationText(location) : void 0;
1179
+ const contactContext = extractContactContext(msg.message ?? void 0);
1180
+ let body = extractText(msg.message ?? void 0);
1181
+ if (locationText) body = [body, locationText].filter(Boolean).join("\n").trim();
1182
+ if (!body) {
1183
+ body = extractMediaPlaceholder(msg.message ?? void 0);
1184
+ if (!body) return null;
1185
+ }
1186
+ const replyContext = describeReplyContext(msg.message);
1187
+ let mediaPath;
1188
+ let mediaType;
1189
+ let mediaFileName;
1190
+ const saveInboundMedia = async (inboundMedia) => {
1191
+ if (!inboundMedia) return;
1192
+ const maxBytes = (typeof options.mediaMaxMb === "number" && options.mediaMaxMb > 0 ? options.mediaMaxMb : 50) * 1024 * 1024;
1193
+ mediaPath = (await saveMediaBuffer(inboundMedia.buffer, inboundMedia.mimetype, "inbound", maxBytes, inboundMedia.fileName)).path;
1194
+ mediaType = inboundMedia.mimetype;
1195
+ mediaFileName = inboundMedia.fileName;
1196
+ };
1197
+ try {
1198
+ await saveInboundMedia(await downloadInboundMedia(msg, sock));
1199
+ if (!mediaPath && replyContext) await saveInboundMedia(await downloadQuotedInboundMedia(msg, sock));
1200
+ } catch (err) {
1201
+ logWhatsAppVerbose(options.verbose, `Inbound media download failed: ${String(err)}`);
1202
+ }
1203
+ return {
1204
+ body,
1205
+ location: location ?? void 0,
1206
+ contactContext,
1207
+ replyContext,
1208
+ mediaPath,
1209
+ mediaType,
1210
+ mediaFileName
1211
+ };
1212
+ };
1213
+ const enqueueInboundMessage = async (msg, inbound, enriched) => {
1214
+ const chatJid = inbound.remoteJid;
1215
+ const sendComposing = async () => {
1216
+ const currentSock = getCurrentSock();
1217
+ if (!currentSock) return;
1218
+ try {
1219
+ await currentSock.sendPresenceUpdate("composing", chatJid);
1220
+ } catch (err) {
1221
+ logWhatsAppVerbose(options.verbose, `Presence update failed: ${String(err)}`);
1222
+ }
1223
+ };
1224
+ const reply = async (text, options) => {
1225
+ const resolved = await resolveOutboundMentionsForGroup(chatJid, text);
1226
+ return normalizeWhatsAppSendResult(await sendTrackedMessage(chatJid, addWhatsAppOutboundMentionsToContent({ text: resolved.text }, resolved.mentionedJids), options), "text");
1227
+ };
1228
+ const sendMedia = async (payload, options) => {
1229
+ return normalizeWhatsAppSendResult(await sendTrackedMessage(chatJid, await applyOutboundMentionsToContent(chatJid, payload), options), "media");
1230
+ };
1231
+ const timestamp = inbound.messageTimestampMs;
1232
+ const mentionedJids = extractMentionedJids(msg.message);
1233
+ const senderName = msg.pushName ?? void 0;
1234
+ inboundLogger.info({
1235
+ from: inbound.from,
1236
+ to: self.e164 ?? "me",
1237
+ body: enriched.body,
1238
+ mediaPath: enriched.mediaPath,
1239
+ mediaType: enriched.mediaType,
1240
+ mediaFileName: enriched.mediaFileName,
1241
+ timestamp
1242
+ }, "inbound message");
1243
+ const inboundMessage = {
1244
+ id: inbound.id,
1245
+ from: inbound.from,
1246
+ conversationId: inbound.from,
1247
+ to: self.e164 ?? "me",
1248
+ accountId: inbound.access.resolvedAccountId,
1249
+ accessControlPassed: true,
1250
+ body: enriched.body,
1251
+ pushName: senderName,
1252
+ timestamp,
1253
+ chatType: inbound.group ? "group" : "direct",
1254
+ chatId: inbound.remoteJid,
1255
+ sender: resolveComparableIdentity({
1256
+ jid: inbound.participantJid,
1257
+ e164: inbound.senderE164 ?? void 0,
1258
+ name: senderName
1259
+ }),
1260
+ senderJid: inbound.participantJid,
1261
+ senderE164: inbound.senderE164 ?? void 0,
1262
+ senderName,
1263
+ replyTo: enriched.replyContext ?? void 0,
1264
+ replyToId: enriched.replyContext?.id,
1265
+ replyToBody: enriched.replyContext?.body,
1266
+ replyToSender: enriched.replyContext?.sender?.label ?? void 0,
1267
+ replyToSenderJid: enriched.replyContext?.sender?.jid ?? void 0,
1268
+ replyToSenderE164: enriched.replyContext?.sender?.e164 ?? void 0,
1269
+ groupSubject: inbound.groupSubject,
1270
+ groupParticipants: inbound.groupParticipants,
1271
+ mentions: mentionedJids ?? void 0,
1272
+ mentionedJids: mentionedJids ?? void 0,
1273
+ self,
1274
+ selfJid: self.jid ?? void 0,
1275
+ selfLid: self.lid ?? void 0,
1276
+ selfE164: self.e164 ?? void 0,
1277
+ fromMe: Boolean(msg.key?.fromMe),
1278
+ location: enriched.location ?? void 0,
1279
+ untrustedStructuredContext: enriched.contactContext ? [{
1280
+ label: "WhatsApp contact",
1281
+ source: "whatsapp",
1282
+ type: enriched.contactContext.kind,
1283
+ payload: enriched.contactContext
1284
+ }] : void 0,
1285
+ sendComposing,
1286
+ reply,
1287
+ sendMedia,
1288
+ mediaPath: enriched.mediaPath,
1289
+ mediaType: enriched.mediaType,
1290
+ mediaFileName: enriched.mediaFileName,
1291
+ dedupeKey: inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : void 0
1292
+ };
1293
+ if (inboundMessage.id) cacheInboundMessageMeta(inboundMessage.accountId, inboundMessage.chatId, inboundMessage.id, {
1294
+ participant: inboundMessage.senderJid,
1295
+ participantE164: inboundMessage.chatType === "direct" ? inboundMessage.senderE164 : void 0,
1296
+ body: inboundMessage.body,
1297
+ fromMe: inboundMessage.fromMe
1298
+ });
1299
+ try {
1300
+ Promise.resolve(debouncer.enqueue(inboundMessage)).catch((err) => {
1301
+ inboundLogger.error({ error: String(err) }, "failed handling inbound web message");
1302
+ inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
1303
+ });
1304
+ } catch (err) {
1305
+ inboundLogger.error({ error: String(err) }, "failed handling inbound web message");
1306
+ inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
1307
+ }
1308
+ };
1309
+ const handleMessagesUpsert = async (upsert) => {
1310
+ if (upsert.type !== "notify" && upsert.type !== "append") return;
1311
+ for (const msg of upsert.messages ?? []) {
1312
+ recordChannelActivity({
1313
+ channel: "whatsapp",
1314
+ accountId: options.accountId,
1315
+ direction: "inbound"
1316
+ });
1317
+ const inbound = await normalizeInboundMessage(msg);
1318
+ if (!inbound) continue;
1319
+ await maybeMarkInboundAsRead(inbound);
1320
+ if (upsert.type === "append") {
1321
+ const APPEND_RECENT_GRACE_MS = 6e4;
1322
+ const msgTsRaw = msg.messageTimestamp;
1323
+ const msgTsNum = msgTsRaw != null ? Number(msgTsRaw) : NaN;
1324
+ if ((Number.isFinite(msgTsNum) ? msgTsNum * 1e3 : 0) < connectedAtMs - APPEND_RECENT_GRACE_MS) continue;
1325
+ }
1326
+ const enriched = await enrichInboundMessage(msg);
1327
+ if (!enriched) continue;
1328
+ const dedupeKey = inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : "";
1329
+ if (dedupeKey && !await claimRecentInboundMessage(dedupeKey)) continue;
1330
+ await enqueueInboundMessage(msg, inbound, enriched);
1331
+ }
1332
+ };
1333
+ const handleConnectionUpdate = (update) => {
1334
+ try {
1335
+ if (update.connection === "close") {
1336
+ if (options.socketRef?.current === sock) options.socketRef.current = null;
1337
+ const status = getStatusCode(update.lastDisconnect?.error);
1338
+ resolveClose({
1339
+ status,
1340
+ isLoggedOut: status === LOGGED_OUT_STATUS,
1341
+ error: update.lastDisconnect?.error
1342
+ });
1343
+ }
1344
+ } catch (err) {
1345
+ inboundLogger.error({ error: String(err) }, "connection.update handler error");
1346
+ resolveClose({
1347
+ status: void 0,
1348
+ isLoggedOut: false,
1349
+ error: err
1350
+ });
1351
+ }
1352
+ };
1353
+ const detachMessagesUpsert = attachEmitterListener(sock.ev, "messages.upsert", handleMessagesUpsert);
1354
+ const detachConnectionUpdate = attachEmitterListener(sock.ev, "connection.update", handleConnectionUpdate);
1355
+ (async () => {
1356
+ try {
1357
+ const groups = await sock.groupFetchAllParticipating();
1358
+ for (const [jid, meta] of Object.entries(groups ?? {})) if (meta) rememberGroupMetadataCacheEntry(groupMetadataCache, jid, summarizeGroupMetaForReconnectCache(meta));
1359
+ logWhatsAppVerbose(options.verbose, `Hydrated ${Object.keys(groups ?? {}).length} participating groups on connect`);
1360
+ } catch (err) {
1361
+ const error = String(err);
1362
+ inboundLogger.warn({ error }, "failed hydrating participating groups on connect");
1363
+ inboundConsoleLog.warn(`Failed hydrating participating groups on connect: ${error}`);
1364
+ logWhatsAppVerbose(options.verbose, `Failed to hydrate participating groups on connect: ${error}`);
1365
+ }
1366
+ })();
1367
+ return {
1368
+ close: async () => {
1369
+ try {
1370
+ detachMessagesUpsert();
1371
+ detachConnectionUpdate();
1372
+ closeInboundMonitorSocket(sock);
1373
+ } catch (err) {
1374
+ logWhatsAppVerbose(options.verbose, `Socket close failed: ${String(err)}`);
1375
+ }
1376
+ },
1377
+ onClose,
1378
+ signalClose: (reason) => {
1379
+ resolveClose(reason ?? {
1380
+ status: void 0,
1381
+ isLoggedOut: false,
1382
+ error: "closed"
1383
+ });
1384
+ },
1385
+ ...createWebSendApi({
1386
+ sock: {
1387
+ sendMessage: (jid, content, options) => sendTrackedMessage(jid, content, options),
1388
+ sendPresenceUpdate: async (presence, jid) => {
1389
+ const currentSock = getCurrentSock();
1390
+ if (!currentSock) throw new Error(RECONNECT_IN_PROGRESS_ERROR);
1391
+ return currentSock.sendPresenceUpdate(presence, jid);
1392
+ }
1393
+ },
1394
+ defaultAccountId: options.accountId,
1395
+ resolveOutboundMentions: ({ jid, text }) => resolveOutboundMentionsForGroup(jid, text)
1396
+ })
1397
+ };
1398
+ }
1399
+ async function monitorWebInbox(options) {
1400
+ const sock = await createWaSocket(false, options.verbose, {
1401
+ authDir: options.authDir,
1402
+ ...resolveWhatsAppSocketTiming(options.cfg)
1403
+ });
1404
+ await waitForWaConnection(sock);
1405
+ return attachWebInboxToSocket({
1406
+ ...options,
1407
+ sock
1408
+ });
1409
+ }
1410
+ //#endregion
1411
+ //#region extensions/whatsapp/src/auto-reply/loggers.ts
1412
+ const whatsappLog = createSubsystemLogger("gateway/channels/whatsapp");
1413
+ const whatsappInboundLog = whatsappLog.child("inbound");
1414
+ const whatsappOutboundLog = whatsappLog.child("outbound");
1415
+ const whatsappHeartbeatLog = whatsappLog.child("heartbeat");
1416
+ //#endregion
1417
+ //#region extensions/whatsapp/src/auto-reply/mentions.ts
1418
+ function buildMentionConfig(cfg, agentId) {
1419
+ return {
1420
+ mentionRegexes: buildMentionRegexes(cfg, agentId),
1421
+ allowFrom: cfg.channels?.whatsapp?.allowFrom
1422
+ };
1423
+ }
1424
+ function resolveMentionTargets(msg, authDir) {
1425
+ return {
1426
+ normalizedMentions: getMentionIdentities(msg, authDir),
1427
+ self: getSelfIdentity(msg, authDir)
1428
+ };
1429
+ }
1430
+ function isBotMentionedFromTargets(msg, mentionCfg, targets) {
1431
+ const clean = (text) => normalizeMentionText(text);
1432
+ const explicitSelfChatOverride = typeof mentionCfg.isSelfChat === "boolean";
1433
+ const isGroupConversation = isWhatsAppGroupJid$1(msg.from);
1434
+ const isSelfChat = explicitSelfChatOverride ? Boolean(mentionCfg.isSelfChat) : isSelfChatMode(targets.self.e164, mentionCfg.allowFrom) && !isGroupConversation;
1435
+ const hasMentions = targets.normalizedMentions.length > 0;
1436
+ if (hasMentions && !isSelfChat) {
1437
+ for (const mention of targets.normalizedMentions) if (identitiesOverlap(targets.self, mention)) return true;
1438
+ return false;
1439
+ } else if (hasMentions && isSelfChat) {}
1440
+ const bodyClean = clean(msg.body);
1441
+ if (mentionCfg.mentionRegexes.some((re) => re.test(bodyClean))) return true;
1442
+ if (targets.self.e164) {
1443
+ const selfDigits = targets.self.e164.replace(/\D/g, "");
1444
+ if (selfDigits) {
1445
+ if (bodyClean.replace(/[^\d]/g, "").includes(selfDigits)) return true;
1446
+ const bodyNoSpace = msg.body.replace(/[\s-]/g, "");
1447
+ if (new RegExp(`\\+?${selfDigits}`, "i").test(bodyNoSpace)) return true;
1448
+ }
1449
+ }
1450
+ return false;
1451
+ }
1452
+ function debugMention(msg, mentionCfg, authDir) {
1453
+ const mentionTargets = resolveMentionTargets(msg, authDir);
1454
+ return {
1455
+ wasMentioned: isBotMentionedFromTargets(msg, mentionCfg, mentionTargets),
1456
+ details: {
1457
+ from: msg.from,
1458
+ body: msg.body,
1459
+ bodyClean: normalizeMentionText(msg.body),
1460
+ mentionedJids: msg.mentions ?? msg.mentionedJids ?? null,
1461
+ normalizedMentionedJids: mentionTargets.normalizedMentions.length ? mentionTargets.normalizedMentions.map((identity) => getComparableIdentityValues(identity)) : null,
1462
+ selfJid: msg.self?.jid ?? msg.selfJid ?? null,
1463
+ selfLid: msg.self?.lid ?? msg.selfLid ?? null,
1464
+ selfE164: msg.self?.e164 ?? msg.selfE164 ?? null,
1465
+ resolvedSelf: mentionTargets.self
1466
+ }
1467
+ };
1468
+ }
1469
+ function resolveOwnerList(mentionCfg, selfE164) {
1470
+ const allowFrom = mentionCfg.allowFrom;
1471
+ return (Array.isArray(allowFrom) && allowFrom.length > 0 ? allowFrom : selfE164 ? [selfE164] : []).filter((entry) => Boolean(entry && entry !== "*")).map((entry) => (0, text_runtime_exports.normalizeE164)(entry)).filter((entry) => Boolean(entry));
1472
+ }
1473
+ //#endregion
1474
+ //#region extensions/whatsapp/src/auto-reply/monitor-state.ts
1475
+ function cloneStatus(status) {
1476
+ return {
1477
+ ...status,
1478
+ lastDisconnect: status.lastDisconnect ? { ...status.lastDisconnect } : null
1479
+ };
1480
+ }
1481
+ function isTerminalHealthState(healthState) {
1482
+ return healthState === "conflict" || healthState === "logged-out" || healthState === "stopped";
1483
+ }
1484
+ function createWebChannelStatusController(statusSink) {
1485
+ const status = {
1486
+ running: true,
1487
+ connected: false,
1488
+ reconnectAttempts: 0,
1489
+ lastConnectedAt: null,
1490
+ lastDisconnect: null,
1491
+ lastInboundAt: null,
1492
+ lastMessageAt: null,
1493
+ lastEventAt: null,
1494
+ lastError: null,
1495
+ healthState: "starting"
1496
+ };
1497
+ const emit = () => {
1498
+ statusSink?.(cloneStatus(status));
1499
+ };
1500
+ return {
1501
+ emit,
1502
+ snapshot: () => status,
1503
+ noteConnected(at = Date.now()) {
1504
+ Object.assign(status, createConnectedChannelStatusPatch(at));
1505
+ Object.assign(status, createTransportActivityStatusPatch(at));
1506
+ status.lastError = null;
1507
+ status.healthState = "healthy";
1508
+ emit();
1509
+ },
1510
+ noteInbound(at = Date.now()) {
1511
+ status.lastInboundAt = at;
1512
+ status.lastMessageAt = at;
1513
+ status.lastEventAt = at;
1514
+ Object.assign(status, createTransportActivityStatusPatch(at));
1515
+ if (status.connected) status.healthState = "healthy";
1516
+ emit();
1517
+ },
1518
+ noteTransportActivity(at = Date.now()) {
1519
+ if (status.lastTransportActivityAt === at) return;
1520
+ Object.assign(status, createTransportActivityStatusPatch(at));
1521
+ emit();
1522
+ },
1523
+ noteWatchdogStale(at = Date.now()) {
1524
+ status.lastEventAt = at;
1525
+ if (status.connected) status.healthState = "stale";
1526
+ emit();
1527
+ },
1528
+ noteReconnectAttempts(reconnectAttempts) {
1529
+ status.reconnectAttempts = reconnectAttempts;
1530
+ emit();
1531
+ },
1532
+ noteClose(params) {
1533
+ const at = params.at ?? Date.now();
1534
+ status.connected = false;
1535
+ status.lastEventAt = at;
1536
+ status.lastDisconnect = {
1537
+ at,
1538
+ status: params.statusCode,
1539
+ error: params.error,
1540
+ loggedOut: Boolean(params.loggedOut)
1541
+ };
1542
+ status.lastError = params.error ?? null;
1543
+ status.reconnectAttempts = params.reconnectAttempts;
1544
+ status.healthState = params.healthState;
1545
+ emit();
1546
+ },
1547
+ markStopped(at = Date.now()) {
1548
+ status.running = false;
1549
+ status.connected = false;
1550
+ status.lastEventAt = at;
1551
+ if (!isTerminalHealthState(status.healthState)) status.healthState = "stopped";
1552
+ emit();
1553
+ }
1554
+ };
1555
+ }
1556
+ //#endregion
1557
+ //#region extensions/whatsapp/src/auto-reply/monitor/echo.ts
1558
+ function createEchoTracker(params) {
1559
+ const recentlySent = /* @__PURE__ */ new Set();
1560
+ const maxItems = Math.max(1, params.maxItems ?? 100);
1561
+ const buildCombinedKey = (p) => `combined:${p.sessionKey}:${p.combinedBody}`;
1562
+ const trim = () => {
1563
+ while (recentlySent.size > maxItems) {
1564
+ const firstKey = recentlySent.values().next().value;
1565
+ if (!firstKey) break;
1566
+ recentlySent.delete(firstKey);
1567
+ }
1568
+ };
1569
+ const rememberText = (text, opts) => {
1570
+ if (!text) return;
1571
+ recentlySent.add(text);
1572
+ if (opts.combinedBody && opts.combinedBodySessionKey) recentlySent.add(buildCombinedKey({
1573
+ sessionKey: opts.combinedBodySessionKey,
1574
+ combinedBody: opts.combinedBody
1575
+ }));
1576
+ if (opts.logVerboseMessage) params.logVerbose?.(`Added to echo detection set (size now: ${recentlySent.size}): ${text.slice(0, 50)}...`);
1577
+ trim();
1578
+ };
1579
+ return {
1580
+ rememberText,
1581
+ has: (key) => recentlySent.has(key),
1582
+ forget: (key) => {
1583
+ recentlySent.delete(key);
1584
+ },
1585
+ buildCombinedKey
1586
+ };
1587
+ }
1588
+ //#endregion
1589
+ //#region extensions/whatsapp/src/group-session-key.ts
1590
+ function resolveWhatsAppGroupAccountThreadId(accountId) {
1591
+ return `whatsapp-account-${normalizeAccountId$1(accountId)}`;
1592
+ }
1593
+ function resolveWhatsAppLegacyGroupSessionKey(params) {
1594
+ const accountId = normalizeAccountId$1(params.accountId);
1595
+ if (!accountId || accountId === DEFAULT_ACCOUNT_ID$1 || !params.sessionKey.includes(":group:")) return null;
1596
+ const suffix = `:thread:${resolveWhatsAppGroupAccountThreadId(accountId)}`;
1597
+ return params.sessionKey.endsWith(suffix) ? params.sessionKey.slice(0, -suffix.length) : null;
1598
+ }
1599
+ function resolveWhatsAppGroupSessionRoute(route) {
1600
+ if (route.accountId === DEFAULT_ACCOUNT_ID$1 || !route.sessionKey.includes(":group:")) return route;
1601
+ const scopedSession = resolveThreadSessionKeys({
1602
+ baseSessionKey: route.sessionKey,
1603
+ threadId: resolveWhatsAppGroupAccountThreadId(route.accountId)
1604
+ });
1605
+ return {
1606
+ ...route,
1607
+ sessionKey: scopedSession.sessionKey
1608
+ };
1609
+ }
1610
+ //#endregion
1611
+ //#region extensions/whatsapp/src/auto-reply/monitor/group-activation.ts
1612
+ function hasNamedWhatsAppAccounts(cfg) {
1613
+ return Object.keys(cfg.channels?.whatsapp?.accounts ?? {}).some((accountId) => normalizeAccountId$1(accountId) !== DEFAULT_ACCOUNT_ID$1);
1614
+ }
1615
+ function isActivationOnlyEntry(entry) {
1616
+ return entry?.groupActivation !== void 0 && typeof entry?.sessionId !== "string" && typeof entry?.updatedAt !== "number";
1617
+ }
1618
+ async function resolveGroupActivationFor(params) {
1619
+ const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.agentId });
1620
+ const store = loadSessionStore(storePath);
1621
+ const legacySessionKey = resolveWhatsAppLegacyGroupSessionKey({
1622
+ sessionKey: params.sessionKey,
1623
+ accountId: params.accountId
1624
+ });
1625
+ const legacyEntry = legacySessionKey ? store[legacySessionKey] : void 0;
1626
+ const scopedEntry = store[params.sessionKey];
1627
+ const activation = (normalizeAccountId$1(params.accountId) === DEFAULT_ACCOUNT_ID$1 && hasNamedWhatsAppAccounts(params.cfg) && isActivationOnlyEntry(scopedEntry) ? void 0 : scopedEntry?.groupActivation) ?? legacyEntry?.groupActivation;
1628
+ if (activation !== void 0 && scopedEntry?.groupActivation === void 0) await updateSessionStore(storePath, (nextStore) => {
1629
+ const nextScopedEntry = nextStore[params.sessionKey];
1630
+ if (nextScopedEntry?.groupActivation !== void 0) return;
1631
+ nextStore[params.sessionKey] = {
1632
+ ...nextScopedEntry,
1633
+ groupActivation: activation
1634
+ };
1635
+ });
1636
+ const defaultActivation = !resolveWhatsAppInboundPolicy({
1637
+ cfg: params.cfg,
1638
+ accountId: params.accountId
1639
+ }).resolveConversationRequireMention(params.conversationId) ? "always" : "mention";
1640
+ return normalizeGroupActivation(activation) ?? defaultActivation;
1641
+ }
1642
+ //#endregion
1643
+ //#region extensions/whatsapp/src/auto-reply/monitor/ack-reaction.ts
1644
+ async function maybeSendAckReaction(params) {
1645
+ if (!params.msg.id) return null;
1646
+ if (resolveWhatsAppReactionLevel({
1647
+ cfg: params.cfg,
1648
+ accountId: params.accountId
1649
+ }).level === "off") return null;
1650
+ const ackConfig = params.cfg.channels?.whatsapp?.ackReaction;
1651
+ const emoji = (ackConfig?.emoji ?? "").trim();
1652
+ const directEnabled = ackConfig?.direct ?? true;
1653
+ const groupMode = ackConfig?.group ?? "mentions";
1654
+ const conversationIdForCheck = params.msg.conversationId ?? params.msg.from;
1655
+ const activation = params.msg.chatType === "group" ? await resolveGroupActivationFor({
1656
+ cfg: params.cfg,
1657
+ accountId: params.accountId,
1658
+ agentId: params.agentId,
1659
+ sessionKey: params.sessionKey,
1660
+ conversationId: conversationIdForCheck
1661
+ }) : null;
1662
+ const shouldSendReaction = () => shouldAckReactionForWhatsApp({
1663
+ emoji,
1664
+ isDirect: params.msg.chatType === "direct",
1665
+ isGroup: params.msg.chatType === "group",
1666
+ directEnabled,
1667
+ groupMode,
1668
+ wasMentioned: params.msg.wasMentioned === true,
1669
+ groupActivated: activation === "always"
1670
+ });
1671
+ if (!shouldSendReaction()) return null;
1672
+ params.info({
1673
+ chatId: params.msg.chatId,
1674
+ messageId: params.msg.id,
1675
+ emoji
1676
+ }, "sending ack reaction");
1677
+ const sender = getSenderIdentity(params.msg);
1678
+ const reactionOptions = {
1679
+ verbose: params.verbose,
1680
+ fromMe: false,
1681
+ ...sender.jid ? { participant: sender.jid } : {},
1682
+ ...params.accountId ? { accountId: params.accountId } : {},
1683
+ cfg: params.cfg
1684
+ };
1685
+ return createAckReactionHandle({
1686
+ ackReactionValue: emoji,
1687
+ send: () => sendReactionWhatsApp(params.msg.chatId, params.msg.id, emoji, reactionOptions),
1688
+ remove: () => sendReactionWhatsApp(params.msg.chatId, params.msg.id, "", reactionOptions),
1689
+ onSendError: (err) => {
1690
+ params.warn({
1691
+ error: formatError(err),
1692
+ chatId: params.msg.chatId,
1693
+ messageId: params.msg.id
1694
+ }, "failed to send ack reaction");
1695
+ logVerbose(`WhatsApp ack reaction failed for chat ${params.msg.chatId}: ${formatError(err)}`);
1696
+ }
1697
+ });
1698
+ }
1699
+ //#endregion
1700
+ //#region extensions/whatsapp/src/auto-reply/monitor/broadcast.ts
1701
+ function buildBroadcastRouteKeys(params) {
1702
+ const sessionKey = buildAgentSessionKey({
1703
+ agentId: params.agentId,
1704
+ channel: "whatsapp",
1705
+ accountId: params.route.accountId,
1706
+ peer: {
1707
+ kind: params.msg.chatType === "group" ? "group" : "direct",
1708
+ id: params.peerId
1709
+ },
1710
+ dmScope: params.cfg.session?.dmScope,
1711
+ identityLinks: params.cfg.session?.identityLinks
1712
+ });
1713
+ const mainSessionKey = buildAgentMainSessionKey({
1714
+ agentId: params.agentId,
1715
+ mainKey: DEFAULT_MAIN_KEY
1716
+ });
1717
+ return {
1718
+ sessionKey,
1719
+ mainSessionKey,
1720
+ lastRoutePolicy: deriveLastRoutePolicy({
1721
+ sessionKey,
1722
+ mainSessionKey
1723
+ })
1724
+ };
1725
+ }
1726
+ async function maybeBroadcastMessage(params) {
1727
+ const broadcastAgents = params.cfg.broadcast?.[params.peerId];
1728
+ if (!broadcastAgents || !Array.isArray(broadcastAgents)) return false;
1729
+ if (broadcastAgents.length === 0) return false;
1730
+ const strategy = params.cfg.broadcast?.strategy || "parallel";
1731
+ whatsappInboundLog.info(`Broadcasting message to ${broadcastAgents.length} agents (${strategy})`);
1732
+ const agentIds = params.cfg.agents?.list?.map((agent) => normalizeAgentId(agent.id));
1733
+ const hasKnownAgents = (agentIds?.length ?? 0) > 0;
1734
+ const groupHistorySnapshot = params.msg.chatType === "group" ? params.groupHistories.get(params.groupHistoryKey) ?? [] : void 0;
1735
+ const processForAgent = async (agentId) => {
1736
+ const normalizedAgentId = normalizeAgentId(agentId);
1737
+ if (hasKnownAgents && !agentIds?.includes(normalizedAgentId)) {
1738
+ whatsappInboundLog.warn(`Broadcast agent ${agentId} not found in agents.list; skipping`);
1739
+ return false;
1740
+ }
1741
+ const routeKeys = buildBroadcastRouteKeys({
1742
+ cfg: params.cfg,
1743
+ msg: params.msg,
1744
+ route: params.route,
1745
+ peerId: params.peerId,
1746
+ agentId: normalizedAgentId
1747
+ });
1748
+ const baseAgentRoute = {
1749
+ ...params.route,
1750
+ agentId: normalizedAgentId,
1751
+ ...routeKeys
1752
+ };
1753
+ const agentRoute = params.msg.chatType === "group" ? resolveWhatsAppGroupSessionRoute(baseAgentRoute) : baseAgentRoute;
1754
+ try {
1755
+ const opts = {
1756
+ groupHistory: groupHistorySnapshot,
1757
+ suppressGroupHistoryClear: true
1758
+ };
1759
+ if (params.preflightAudioTranscript !== void 0) opts.preflightAudioTranscript = params.preflightAudioTranscript;
1760
+ if (params.ackAlreadySent === true) opts.ackAlreadySent = true;
1761
+ if (params.ackReaction !== void 0) opts.ackReaction = params.ackReaction;
1762
+ return await params.processMessage(params.msg, agentRoute, params.groupHistoryKey, opts);
1763
+ } catch (err) {
1764
+ whatsappInboundLog.error(`Broadcast agent ${agentId} failed: ${formatError(err)}`);
1765
+ return false;
1766
+ }
1767
+ };
1768
+ if (strategy === "sequential") for (const agentId of broadcastAgents) await processForAgent(agentId);
1769
+ else await Promise.allSettled(broadcastAgents.map(processForAgent));
1770
+ if (params.msg.chatType === "group") params.groupHistories.set(params.groupHistoryKey, []);
1771
+ return true;
1772
+ }
1773
+ //#endregion
1774
+ //#region extensions/whatsapp/src/auto-reply/monitor/commands.ts
1775
+ function stripMentionsForCommand(text, mentionRegexes, selfE164) {
1776
+ let result = text;
1777
+ for (const re of mentionRegexes) result = result.replace(re, " ");
1778
+ if (selfE164) {
1779
+ const digits = selfE164.replace(/\D/g, "");
1780
+ if (digits) {
1781
+ const pattern = new RegExp(`\\+?${digits}`, "g");
1782
+ result = result.replace(pattern, " ");
1783
+ }
1784
+ }
1785
+ return result.replace(/\s+/g, " ").trim();
1786
+ }
1787
+ //#endregion
1788
+ //#region extensions/whatsapp/src/auto-reply/monitor/group-members.ts
1789
+ function appendNormalizedUnique(entries, seen, ordered) {
1790
+ for (const entry of entries) {
1791
+ const normalized = (0, text_runtime_exports.normalizeE164)(entry) ?? entry;
1792
+ if (!normalized || seen.has(normalized)) continue;
1793
+ seen.add(normalized);
1794
+ ordered.push(normalized);
1795
+ }
1796
+ }
1797
+ function noteGroupMember(groupMemberNames, conversationId, e164, name) {
1798
+ if (!e164 || !name) return;
1799
+ const key = (0, text_runtime_exports.normalizeE164)(e164) ?? e164;
1800
+ if (!key) return;
1801
+ let roster = groupMemberNames.get(conversationId);
1802
+ if (!roster) {
1803
+ roster = /* @__PURE__ */ new Map();
1804
+ groupMemberNames.set(conversationId, roster);
1805
+ }
1806
+ roster.set(key, name);
1807
+ }
1808
+ function formatGroupMembers(params) {
1809
+ const { participants, roster, fallbackE164 } = params;
1810
+ const seen = /* @__PURE__ */ new Set();
1811
+ const ordered = [];
1812
+ if (participants?.length) appendNormalizedUnique(participants, seen, ordered);
1813
+ if (roster) appendNormalizedUnique(roster.keys(), seen, ordered);
1814
+ if (ordered.length === 0 && fallbackE164) {
1815
+ const normalized = (0, text_runtime_exports.normalizeE164)(fallbackE164) ?? fallbackE164;
1816
+ if (normalized) ordered.push(normalized);
1817
+ }
1818
+ if (ordered.length === 0) return;
1819
+ return ordered.map((entry) => {
1820
+ const name = roster?.get(entry);
1821
+ return name ? `${name} (${entry})` : entry;
1822
+ }).join(", ");
1823
+ }
1824
+ //#endregion
1825
+ //#region extensions/whatsapp/src/auto-reply/monitor/group-gating.ts
1826
+ function isOwnerSender(baseMentionConfig, msg) {
1827
+ const sender = (0, text_runtime_exports.normalizeE164)(getSenderIdentity(msg).e164 ?? "");
1828
+ if (!sender) return false;
1829
+ return resolveOwnerList(baseMentionConfig, getSelfIdentity(msg).e164 ?? void 0).includes(sender);
1830
+ }
1831
+ function recordPendingGroupHistoryEntry(params) {
1832
+ const senderIdentity = getSenderIdentity(params.msg);
1833
+ const sender = senderIdentity.name && senderIdentity.e164 ? `${senderIdentity.name} (${senderIdentity.e164})` : senderIdentity.name ?? senderIdentity.e164 ?? getPrimaryIdentityId(senderIdentity) ?? "Unknown";
1834
+ recordPendingHistoryEntryIfEnabled({
1835
+ historyMap: params.groupHistories,
1836
+ historyKey: params.groupHistoryKey,
1837
+ limit: params.groupHistoryLimit,
1838
+ entry: {
1839
+ sender,
1840
+ body: params.body ?? params.msg.body,
1841
+ timestamp: params.msg.timestamp,
1842
+ id: params.msg.id,
1843
+ senderJid: senderIdentity.jid ?? params.msg.senderJid
1844
+ }
1845
+ });
1846
+ }
1847
+ function skipGroupMessageAndStoreHistory(params, verboseMessage, body) {
1848
+ params.logVerbose(verboseMessage);
1849
+ recordPendingGroupHistoryEntry({
1850
+ msg: params.msg,
1851
+ body,
1852
+ groupHistories: params.groupHistories,
1853
+ groupHistoryKey: params.groupHistoryKey,
1854
+ groupHistoryLimit: params.groupHistoryLimit
1855
+ });
1856
+ return { shouldProcess: false };
1857
+ }
1858
+ async function applyGroupGating(params) {
1859
+ const sender = getSenderIdentity(params.msg);
1860
+ const self = getSelfIdentity(params.msg, params.authDir);
1861
+ const inboundPolicy = resolveWhatsAppInboundPolicy({
1862
+ cfg: params.cfg,
1863
+ accountId: params.msg.accountId,
1864
+ selfE164: self.e164 ?? null
1865
+ });
1866
+ const conversationGroupPolicy = inboundPolicy.resolveConversationGroupPolicy(params.conversationId);
1867
+ if (conversationGroupPolicy.allowlistEnabled && !conversationGroupPolicy.allowed) {
1868
+ params.logVerbose(`Skipping group message ${params.conversationId} (not in allowlist)`);
1869
+ return { shouldProcess: false };
1870
+ }
1871
+ noteGroupMember(params.groupMemberNames, params.groupHistoryKey, sender.e164 ?? void 0, sender.name ?? void 0);
1872
+ const baseMentionConfig = {
1873
+ ...params.baseMentionConfig,
1874
+ allowFrom: inboundPolicy.configuredAllowFrom
1875
+ };
1876
+ const mentionConfig = {
1877
+ ...buildMentionConfig(params.cfg, params.agentId),
1878
+ allowFrom: inboundPolicy.configuredAllowFrom
1879
+ };
1880
+ const mentionMsg = params.mentionText !== void 0 ? {
1881
+ ...params.msg,
1882
+ body: params.mentionText
1883
+ } : params.msg;
1884
+ const commandBody = stripMentionsForCommand(mentionMsg.body, mentionConfig.mentionRegexes, self.e164);
1885
+ const activationCommand = parseActivationCommand(commandBody);
1886
+ const owner = isOwnerSender(baseMentionConfig, params.msg);
1887
+ const shouldBypassMention = owner && hasControlCommand$1(commandBody, params.cfg);
1888
+ if (activationCommand.hasCommand && !owner) return skipGroupMessageAndStoreHistory(params, `Ignoring /activation from non-owner in group ${params.conversationId}`);
1889
+ const mentionDebug = debugMention(mentionMsg, mentionConfig, params.authDir);
1890
+ params.replyLogger.debug({
1891
+ conversationId: params.conversationId,
1892
+ wasMentioned: mentionDebug.wasMentioned,
1893
+ ...mentionDebug.details
1894
+ }, "group mention debug");
1895
+ const wasMentioned = mentionDebug.wasMentioned;
1896
+ const requireMention = await resolveGroupActivationFor({
1897
+ cfg: params.cfg,
1898
+ accountId: inboundPolicy.account.accountId,
1899
+ agentId: params.agentId,
1900
+ sessionKey: params.sessionKey,
1901
+ conversationId: params.conversationId
1902
+ }) !== "always";
1903
+ const replyContext = getReplyContext(params.msg, params.authDir);
1904
+ const mentionDecision = resolveInboundMentionDecision({
1905
+ facts: {
1906
+ canDetectMention: true,
1907
+ wasMentioned,
1908
+ implicitMentionKinds: implicitMentionKindWhen("quoted_bot", !(params.selfChatMode === true && identitiesOverlap(self, sender)) && identitiesOverlap(self, replyContext?.sender))
1909
+ },
1910
+ policy: {
1911
+ isGroup: true,
1912
+ requireMention,
1913
+ allowTextCommands: false,
1914
+ hasControlCommand: false,
1915
+ commandAuthorized: false
1916
+ }
1917
+ });
1918
+ const effectiveWasMentioned = mentionDecision.effectiveWasMentioned || shouldBypassMention;
1919
+ params.msg.wasMentioned = effectiveWasMentioned;
1920
+ if (!shouldBypassMention && requireMention && mentionDecision.shouldSkip) {
1921
+ if (params.deferMissingMention === true) {
1922
+ params.logVerbose(`Deferring group mention skip until audio preflight completes in ${params.conversationId}`);
1923
+ return {
1924
+ shouldProcess: false,
1925
+ needsMentionText: true
1926
+ };
1927
+ }
1928
+ return skipGroupMessageAndStoreHistory(params, `Group message stored for context (no mention detected) in ${params.conversationId}: ${mentionMsg.body}`, params.mentionText);
1929
+ }
1930
+ return { shouldProcess: true };
1931
+ }
1932
+ //#endregion
1933
+ //#region extensions/whatsapp/src/auto-reply/monitor/last-route.ts
1934
+ function trackBackgroundTask(backgroundTasks, task) {
1935
+ backgroundTasks.add(task);
1936
+ const cleanup = () => {
1937
+ backgroundTasks.delete(task);
1938
+ };
1939
+ task.then(cleanup, cleanup);
1940
+ }
1941
+ function updateLastRouteInBackground(params) {
1942
+ const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.storeAgentId });
1943
+ const task = updateLastRoute({
1944
+ storePath,
1945
+ sessionKey: params.sessionKey,
1946
+ deliveryContext: {
1947
+ channel: params.channel,
1948
+ to: params.to,
1949
+ accountId: params.accountId
1950
+ },
1951
+ ctx: params.ctx
1952
+ }).catch((err) => {
1953
+ params.warn({
1954
+ error: formatError(err),
1955
+ storePath,
1956
+ sessionKey: params.sessionKey,
1957
+ to: params.to
1958
+ }, "failed updating last route");
1959
+ });
1960
+ trackBackgroundTask(params.backgroundTasks, task);
1961
+ }
1962
+ //#endregion
1963
+ //#region extensions/whatsapp/src/auto-reply/monitor/peer.ts
1964
+ function resolvePeerId(msg) {
1965
+ if (msg.chatType === "group") return msg.conversationId ?? msg.from;
1966
+ const sender = getSenderIdentity(msg);
1967
+ if (sender.e164) return (0, text_runtime_exports.normalizeE164)(sender.e164) ?? sender.e164;
1968
+ if (msg.from.includes("@")) return jidToE164(msg.from) ?? msg.from;
1969
+ return (0, text_runtime_exports.normalizeE164)(msg.from) ?? msg.from;
1970
+ }
1971
+ //#endregion
1972
+ //#region extensions/whatsapp/src/system-prompt.ts
1973
+ function resolveWhatsAppGroupSystemPrompt(params) {
1974
+ if (!params.groupId) return;
1975
+ const groups = params.accountConfig?.groups;
1976
+ const specific = groups?.[params.groupId];
1977
+ if (specific != null && specific.systemPrompt != null) return specific.systemPrompt.trim() || void 0;
1978
+ const wildcard = groups?.["*"]?.systemPrompt;
1979
+ return wildcard != null ? wildcard.trim() || void 0 : void 0;
1980
+ }
1981
+ function resolveWhatsAppDirectSystemPrompt(params) {
1982
+ if (!params.peerId) return;
1983
+ const direct = params.accountConfig?.direct;
1984
+ const specific = direct?.[params.peerId];
1985
+ if (specific != null && specific.systemPrompt != null) return specific.systemPrompt.trim() || void 0;
1986
+ const wildcard = direct?.["*"]?.systemPrompt;
1987
+ return wildcard != null ? wildcard.trim() || void 0 : void 0;
1988
+ }
1989
+ //#endregion
1990
+ //#region extensions/whatsapp/src/auto-reply/util.ts
1991
+ function elide(text, limit = 400) {
1992
+ if (!text) return text;
1993
+ if (text.length <= limit) return text;
1994
+ return `${text.slice(0, limit)}… (truncated ${text.length - limit} chars)`;
1995
+ }
1996
+ function isLikelyWhatsAppCryptoError(reason) {
1997
+ const formatReason = (value) => {
1998
+ if (value == null) return "";
1999
+ if (typeof value === "string") return value;
2000
+ if (value instanceof Error) return `${value.message}\n${value.stack ?? ""}`;
2001
+ if (typeof value === "object") try {
2002
+ return JSON.stringify(value);
2003
+ } catch {
2004
+ return Object.prototype.toString.call(value);
2005
+ }
2006
+ if (typeof value === "number") return String(value);
2007
+ if (typeof value === "boolean") return String(value);
2008
+ if (typeof value === "bigint") return String(value);
2009
+ if (typeof value === "symbol") return value.description ?? value.toString();
2010
+ if (typeof value === "function") return value.name ? `[function ${value.name}]` : "[function]";
2011
+ return Object.prototype.toString.call(value);
2012
+ };
2013
+ const haystack = normalizeLowercaseStringOrEmpty(reason instanceof Error ? `${reason.message}\n${reason.stack ?? ""}` : formatReason(reason));
2014
+ if (!(haystack.includes("unsupported state or unable to authenticate data") || haystack.includes("bad mac"))) return false;
2015
+ return haystack.includes("@whiskeysockets/baileys") || haystack.includes("baileys") || haystack.includes("noise-handler") || haystack.includes("aesdecryptgcm");
2016
+ }
2017
+ //#endregion
2018
+ //#region extensions/whatsapp/src/auto-reply/deliver-reply.ts
2019
+ async function deliverWebReply(params) {
2020
+ const { replyResult, msg, maxMediaBytes, textLimit, replyLogger, connectionId, skipLog } = params;
2021
+ const replyStarted = Date.now();
2022
+ const sendResults = [];
2023
+ const rememberSendResult = (result) => {
2024
+ if (result) sendResults.push(result);
2025
+ };
2026
+ const finishDelivery = () => {
2027
+ return {
2028
+ results: sendResults,
2029
+ messageIds: [...new Set(sendResults.flatMap((result) => result.messageIds))],
2030
+ providerAccepted: sendResults.some((result) => result.providerAccepted)
2031
+ };
2032
+ };
2033
+ if (isReasoningReplyPayload(replyResult)) {
2034
+ whatsappOutboundLog.debug(`Suppressed reasoning payload to ${msg.from}`);
2035
+ return finishDelivery();
2036
+ }
2037
+ const tableMode = params.tableMode ?? "code";
2038
+ const chunkMode = params.chunkMode ?? "length";
2039
+ const normalizedReply = params.normalizedReplyResult ?? normalizeWhatsAppOutboundPayload(replyResult, { normalizeText: normalizeWhatsAppPayloadTextPreservingIndentation });
2040
+ const textChunks = chunkMarkdownTextWithMode(markdownToWhatsApp((0, text_runtime_exports.convertMarkdownTables)(normalizedReply.text ?? "", tableMode)), textLimit, chunkMode);
2041
+ const mediaList = normalizedReply.mediaUrls ?? [];
2042
+ const getQuote = () => {
2043
+ if (!replyResult.replyToId) return;
2044
+ const cached = lookupInboundMessageMeta(msg.accountId, msg.chatId, replyResult.replyToId);
2045
+ return buildQuotedMessageOptions({
2046
+ messageId: replyResult.replyToId,
2047
+ remoteJid: msg.chatId,
2048
+ fromMe: cached?.fromMe ?? false,
2049
+ participant: cached?.participant ?? (msg.chatType === "group" ? msg.senderJid : void 0),
2050
+ messageText: cached?.body ?? ""
2051
+ });
2052
+ };
2053
+ const sendWithRetry = async (fn, label, maxAttempts = 3) => {
2054
+ return await sendWhatsAppOutboundWithRetry({
2055
+ send: fn,
2056
+ maxAttempts,
2057
+ onRetry: ({ attempt, maxAttempts: retryMaxAttempts, backoffMs, errorText }) => {
2058
+ logVerbose(`Retrying ${label} to ${msg.from} after failure (${attempt}/${retryMaxAttempts - 1}) in ${backoffMs}ms: ${errorText}`);
2059
+ }
2060
+ });
2061
+ };
2062
+ if (mediaList.length === 0 && textChunks.length) {
2063
+ const totalChunks = textChunks.length;
2064
+ for (const [index, chunk] of textChunks.entries()) {
2065
+ const chunkStarted = Date.now();
2066
+ const quote = getQuote();
2067
+ rememberSendResult(await sendWithRetry(() => msg.reply(chunk, quote), "text"));
2068
+ if (!skipLog) {
2069
+ const durationMs = Date.now() - chunkStarted;
2070
+ whatsappOutboundLog.debug(`Sent chunk ${index + 1}/${totalChunks} to ${msg.from} (${durationMs.toFixed(0)}ms)`);
2071
+ }
2072
+ }
2073
+ const delivery = finishDelivery();
2074
+ const logPayload = {
2075
+ correlationId: msg.id ?? newConnectionId(),
2076
+ connectionId: connectionId ?? null,
2077
+ to: msg.from,
2078
+ from: msg.to,
2079
+ text: elide(replyResult.text, 240),
2080
+ mediaUrl: null,
2081
+ mediaSizeBytes: null,
2082
+ mediaKind: null,
2083
+ durationMs: Date.now() - replyStarted
2084
+ };
2085
+ if (delivery.providerAccepted) replyLogger.info(logPayload, "auto-reply sent (text)");
2086
+ else replyLogger.warn(logPayload, "auto-reply text was not accepted by WhatsApp provider");
2087
+ return delivery;
2088
+ }
2089
+ const remainingText = [...textChunks];
2090
+ await sendMediaWithLeadingCaption({
2091
+ mediaUrls: mediaList,
2092
+ caption: remainingText.shift() || "",
2093
+ send: async ({ mediaUrl, caption }) => {
2094
+ const media = await prepareWhatsAppOutboundMedia(await loadWebMedia$1(mediaUrl, {
2095
+ maxBytes: maxMediaBytes,
2096
+ localRoots: params.mediaLocalRoots
2097
+ }), mediaUrl);
2098
+ if (shouldLogVerbose()) {
2099
+ logVerbose(`Web auto-reply media size: ${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB`);
2100
+ logVerbose(`Web auto-reply media source: ${mediaUrl} (kind ${media.kind})`);
2101
+ }
2102
+ if (media.kind === "image") {
2103
+ const quote = getQuote();
2104
+ rememberSendResult(await sendWithRetry(() => msg.sendMedia({
2105
+ image: media.buffer,
2106
+ caption,
2107
+ mimetype: media.mimetype
2108
+ }, quote), "media:image"));
2109
+ } else if (media.kind === "audio") {
2110
+ const quote = getQuote();
2111
+ rememberSendResult(await sendWithRetry(() => msg.sendMedia({
2112
+ audio: media.buffer,
2113
+ ptt: true,
2114
+ mimetype: media.mimetype
2115
+ }, quote), "media:audio"));
2116
+ if (caption) rememberSendResult(await sendWithRetry(() => msg.reply(caption, quote), "media:audio-text"));
2117
+ } else if (media.kind === "video") {
2118
+ const quote = getQuote();
2119
+ rememberSendResult(await sendWithRetry(() => msg.sendMedia({
2120
+ video: media.buffer,
2121
+ caption,
2122
+ mimetype: media.mimetype
2123
+ }, quote), "media:video"));
2124
+ } else {
2125
+ const quote = getQuote();
2126
+ rememberSendResult(await sendWithRetry(() => msg.sendMedia({
2127
+ document: media.buffer,
2128
+ fileName: media.fileName,
2129
+ caption,
2130
+ mimetype: media.mimetype
2131
+ }, quote), "media:document"));
2132
+ }
2133
+ whatsappOutboundLog.info(`Sent media reply to ${msg.from} (${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB)`);
2134
+ replyLogger.info({
2135
+ correlationId: msg.id ?? newConnectionId(),
2136
+ connectionId: connectionId ?? null,
2137
+ to: msg.from,
2138
+ from: msg.to,
2139
+ text: caption ?? null,
2140
+ mediaUrl,
2141
+ mediaSizeBytes: media.buffer.length,
2142
+ mediaKind: media.kind,
2143
+ durationMs: Date.now() - replyStarted
2144
+ }, "auto-reply sent (media)");
2145
+ },
2146
+ onError: async ({ error, mediaUrl, caption, isFirst }) => {
2147
+ whatsappOutboundLog.error(`Failed sending web media to ${msg.from}: ${formatError(error)}`);
2148
+ replyLogger.warn({
2149
+ err: error,
2150
+ mediaUrl
2151
+ }, "failed to send web media reply");
2152
+ if (!isFirst) return;
2153
+ const fallbackText = [remainingText.shift() ?? caption ?? "", "⚠️ Media failed."].filter(Boolean).join("\n");
2154
+ if (!fallbackText) return;
2155
+ whatsappOutboundLog.warn(`Media skipped; sent text-only to ${msg.from}`);
2156
+ rememberSendResult(await sendWithRetry(() => msg.reply(fallbackText, getQuote()), "media:fallback-text"));
2157
+ }
2158
+ });
2159
+ for (const chunk of remainingText) rememberSendResult(await sendWithRetry(() => msg.reply(chunk, getQuote()), "media:text"));
2160
+ return finishDelivery();
2161
+ }
2162
+ //#endregion
2163
+ //#region extensions/whatsapp/src/auto-reply/monitor/inbound-context.ts
2164
+ function isWhatsAppSupplementalSenderAllowed(params) {
2165
+ if (params.allowFrom.includes("*")) return true;
2166
+ const senderValues = new Set(getComparableIdentityValues(params.sender));
2167
+ if (senderValues.size === 0) return false;
2168
+ for (const entry of params.allowFrom) {
2169
+ const rawEntry = entry.trim();
2170
+ if (!rawEntry) continue;
2171
+ const normalizedEntry = (0, text_runtime_exports.normalizeE164)(rawEntry);
2172
+ if (normalizedEntry && senderValues.has(normalizedEntry) || senderValues.has(rawEntry)) return true;
2173
+ }
2174
+ return false;
2175
+ }
2176
+ function resolveVisibleWhatsAppGroupHistory(params) {
2177
+ if (params.groupPolicy !== "allowlist") return params.history;
2178
+ return filterSupplementalContextItems({
2179
+ items: params.history,
2180
+ mode: params.mode,
2181
+ kind: "history",
2182
+ isSenderAllowed: (entry) => isWhatsAppSupplementalSenderAllowed({
2183
+ allowFrom: params.groupAllowFrom,
2184
+ sender: entry.senderJid ? { jid: entry.senderJid } : null
2185
+ })
2186
+ }).items;
2187
+ }
2188
+ function resolveVisibleWhatsAppReplyContext(params) {
2189
+ const replyTo = getReplyContext(params.msg, params.authDir);
2190
+ if (!replyTo) return null;
2191
+ return evaluateSupplementalContextVisibility({
2192
+ mode: params.mode,
2193
+ kind: "quote",
2194
+ senderAllowed: params.msg.chatType !== "group" || params.groupPolicy !== "allowlist" ? true : isWhatsAppSupplementalSenderAllowed({
2195
+ allowFrom: params.groupAllowFrom,
2196
+ sender: replyTo.sender
2197
+ })
2198
+ }).include ? replyTo : null;
2199
+ }
2200
+ //#endregion
2201
+ //#region extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts
2202
+ function logWhatsAppReplyDeliveryError(params) {
2203
+ params.replyLogger.error({
2204
+ err: params.err,
2205
+ replyKind: params.info.kind,
2206
+ correlationId: params.msg.id ?? null,
2207
+ connectionId: params.connectionId,
2208
+ conversationId: params.conversationId,
2209
+ chatId: params.msg.chatId ?? null,
2210
+ to: params.msg.from ?? null,
2211
+ from: params.msg.to ?? null
2212
+ }, "auto-reply delivery failed");
2213
+ }
2214
+ function resolveWhatsAppDisableBlockStreaming(cfg) {
2215
+ if (typeof cfg.channels?.whatsapp?.blockStreaming !== "boolean") return;
2216
+ return !cfg.channels.whatsapp.blockStreaming;
2217
+ }
2218
+ function resolveWhatsAppDeliverablePayload(payload, info) {
2219
+ if (payload.isReasoning === true || payload.isCompactionNotice === true) return null;
2220
+ if (payload.isError === true) return null;
2221
+ if (info.kind === "tool") {
2222
+ if (!resolveSendableOutboundReplyParts(payload).hasMedia) return null;
2223
+ return {
2224
+ ...payload,
2225
+ text: void 0
2226
+ };
2227
+ }
2228
+ return payload;
2229
+ }
2230
+ function resolveWhatsAppResponsePrefix(params) {
2231
+ const configuredResponsePrefix = params.cfg.messages?.responsePrefix;
2232
+ return params.pipelineResponsePrefix ?? (configuredResponsePrefix === void 0 && params.isSelfChat ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : void 0);
2233
+ }
2234
+ function buildWhatsAppInboundContext(params) {
2235
+ const inboundHistory = params.msg.chatType === "group" ? (params.groupHistory ?? []).map((entry) => ({
2236
+ sender: entry.sender,
2237
+ body: entry.body,
2238
+ timestamp: entry.timestamp
2239
+ })) : void 0;
2240
+ return finalizeInboundContext({
2241
+ Body: params.combinedBody,
2242
+ BodyForAgent: params.bodyForAgent ?? params.msg.body,
2243
+ InboundHistory: inboundHistory,
2244
+ RawBody: params.rawBody ?? params.msg.body,
2245
+ CommandBody: params.commandBody ?? params.msg.body,
2246
+ Transcript: params.transcript,
2247
+ From: params.msg.from,
2248
+ To: params.msg.to,
2249
+ SessionKey: params.route.sessionKey,
2250
+ AccountId: params.route.accountId,
2251
+ MessageSid: params.msg.id,
2252
+ ReplyToId: params.visibleReplyTo?.id,
2253
+ ReplyToBody: params.visibleReplyTo?.body,
2254
+ ReplyToSender: params.visibleReplyTo?.sender?.label,
2255
+ MediaPath: params.msg.mediaPath,
2256
+ MediaUrl: params.msg.mediaUrl,
2257
+ MediaType: params.msg.mediaType,
2258
+ MediaTranscribedIndexes: params.mediaTranscribedIndexes,
2259
+ ChatType: params.msg.chatType,
2260
+ Timestamp: params.msg.timestamp,
2261
+ ConversationLabel: params.msg.chatType === "group" ? params.conversationId : params.msg.from,
2262
+ GroupSubject: params.msg.groupSubject,
2263
+ GroupMembers: formatGroupMembers({
2264
+ participants: params.msg.groupParticipants,
2265
+ roster: params.groupMemberRoster,
2266
+ fallbackE164: params.sender.e164
2267
+ }),
2268
+ SenderName: params.sender.name,
2269
+ SenderId: params.sender.id ?? params.sender.e164,
2270
+ SenderE164: params.sender.e164,
2271
+ CommandAuthorized: params.commandAuthorized,
2272
+ ReplyThreading: params.replyThreading,
2273
+ WasMentioned: params.msg.wasMentioned,
2274
+ GroupSystemPrompt: params.groupSystemPrompt,
2275
+ UntrustedStructuredContext: params.msg.untrustedStructuredContext,
2276
+ ...params.msg.location ? toLocationContext(params.msg.location) : {},
2277
+ Provider: "whatsapp",
2278
+ Surface: "whatsapp",
2279
+ OriginatingChannel: "whatsapp",
2280
+ OriginatingTo: params.msg.from
2281
+ });
2282
+ }
2283
+ function resolveWhatsAppDmRouteTarget(params) {
2284
+ if (params.msg.chatType === "group") return;
2285
+ if (params.senderE164) return params.normalizeE164(params.senderE164) ?? void 0;
2286
+ if (params.msg.from.includes("@")) return jidToE164(params.msg.from) ?? void 0;
2287
+ return params.normalizeE164(params.msg.from) ?? void 0;
2288
+ }
2289
+ function updateWhatsAppMainLastRoute(params) {
2290
+ const shouldUpdateMainLastRoute = !params.pinnedMainDmRecipient || params.pinnedMainDmRecipient === params.dmRouteTarget;
2291
+ const inboundLastRouteSessionKey = resolveInboundLastRouteSessionKey({
2292
+ route: params.route,
2293
+ sessionKey: params.route.sessionKey
2294
+ });
2295
+ if (params.dmRouteTarget && inboundLastRouteSessionKey === params.route.mainSessionKey && shouldUpdateMainLastRoute) {
2296
+ params.updateLastRoute({
2297
+ cfg: params.cfg,
2298
+ backgroundTasks: params.backgroundTasks,
2299
+ storeAgentId: params.route.agentId,
2300
+ sessionKey: params.route.mainSessionKey,
2301
+ channel: "whatsapp",
2302
+ to: params.dmRouteTarget,
2303
+ accountId: params.route.accountId,
2304
+ ctx: params.ctx,
2305
+ warn: params.warn
2306
+ });
2307
+ return;
2308
+ }
2309
+ if (params.dmRouteTarget && inboundLastRouteSessionKey === params.route.mainSessionKey && params.pinnedMainDmRecipient) logVerbose$1(`Skipping main-session last route update for ${params.dmRouteTarget} (pinned owner ${params.pinnedMainDmRecipient})`);
2310
+ }
2311
+ async function dispatchWhatsAppBufferedReply(params) {
2312
+ const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp");
2313
+ const chunkMode = resolveChunkMode(params.cfg, "whatsapp", params.route.accountId);
2314
+ const tableMode = resolveMarkdownTableMode$1({
2315
+ cfg: params.cfg,
2316
+ channel: "whatsapp",
2317
+ accountId: params.route.accountId
2318
+ });
2319
+ const mediaLocalRoots = getAgentScopedMediaLocalRoots(params.cfg, params.route.agentId);
2320
+ const disableBlockStreaming = resolveWhatsAppDisableBlockStreaming(params.cfg);
2321
+ let didSendReply = false;
2322
+ let didLogHeartbeatStrip = false;
2323
+ const { queuedFinal, counts } = await dispatchReplyWithBufferedBlockDispatcher({
2324
+ ctx: params.context,
2325
+ cfg: params.cfg,
2326
+ replyResolver: params.replyResolver,
2327
+ dispatcherOptions: {
2328
+ ...params.replyPipeline,
2329
+ onHeartbeatStrip: () => {
2330
+ if (!didLogHeartbeatStrip) {
2331
+ didLogHeartbeatStrip = true;
2332
+ logVerbose$1("Stripped stray HEARTBEAT_OK token from web reply");
2333
+ }
2334
+ },
2335
+ deliver: async (payload, info) => {
2336
+ const deliveryPayload = resolveWhatsAppDeliverablePayload(payload, info);
2337
+ if (!deliveryPayload) return;
2338
+ const normalizedOutboundPayload = normalizeWhatsAppOutboundPayload(deliveryPayload, { normalizeText: normalizeWhatsAppPayloadTextPreservingIndentation });
2339
+ const normalizedDeliveryPayload = deliveryPayload.text === void 0 ? {
2340
+ ...normalizedOutboundPayload,
2341
+ text: void 0
2342
+ } : normalizedOutboundPayload;
2343
+ const reply = resolveSendableOutboundReplyParts(normalizedDeliveryPayload);
2344
+ if (!reply.hasMedia && !reply.text.trim()) return;
2345
+ if (!(await params.deliverReply({
2346
+ replyResult: normalizedDeliveryPayload,
2347
+ normalizedReplyResult: normalizedDeliveryPayload,
2348
+ msg: params.msg,
2349
+ mediaLocalRoots,
2350
+ maxMediaBytes: params.maxMediaBytes,
2351
+ textLimit,
2352
+ chunkMode,
2353
+ replyLogger: params.replyLogger,
2354
+ connectionId: params.connectionId,
2355
+ skipLog: false,
2356
+ tableMode
2357
+ })).providerAccepted) {
2358
+ params.replyLogger.warn({
2359
+ correlationId: params.msg.id ?? null,
2360
+ connectionId: params.connectionId,
2361
+ conversationId: params.conversationId,
2362
+ chatId: params.msg.chatId,
2363
+ to: params.msg.from,
2364
+ from: params.msg.to,
2365
+ replyKind: info.kind
2366
+ }, "auto-reply was not accepted by WhatsApp provider");
2367
+ return;
2368
+ }
2369
+ didSendReply = true;
2370
+ const shouldLog = normalizedDeliveryPayload.text ? true : void 0;
2371
+ params.rememberSentText(normalizedDeliveryPayload.text, {
2372
+ combinedBody: params.context.Body,
2373
+ combinedBodySessionKey: params.route.sessionKey,
2374
+ logVerboseMessage: shouldLog
2375
+ });
2376
+ const fromDisplay = params.msg.chatType === "group" ? params.conversationId : params.msg.from ?? "unknown";
2377
+ if (shouldLogVerbose$1()) logVerbose$1(`Reply body: ${normalizedDeliveryPayload.text != null ? reply.text : "<media>"}${reply.hasMedia ? " (media)" : ""} -> ${fromDisplay}`);
2378
+ },
2379
+ onReplyStart: params.msg.sendComposing,
2380
+ onError: (err, info) => {
2381
+ logWhatsAppReplyDeliveryError({
2382
+ err,
2383
+ info,
2384
+ connectionId: params.connectionId,
2385
+ conversationId: params.conversationId,
2386
+ msg: params.msg,
2387
+ replyLogger: params.replyLogger
2388
+ });
2389
+ }
2390
+ },
2391
+ replyOptions: {
2392
+ disableBlockStreaming,
2393
+ onModelSelected: params.onModelSelected
2394
+ }
2395
+ });
2396
+ if (!hasVisibleInboundReplyDispatch({
2397
+ queuedFinal,
2398
+ counts
2399
+ })) {
2400
+ if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
2401
+ logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
2402
+ return false;
2403
+ }
2404
+ if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
2405
+ return didSendReply;
2406
+ }
2407
+ //#endregion
2408
+ //#region extensions/whatsapp/src/auto-reply/monitor/message-line.runtime.ts
2409
+ function normalizeAgentId$1(agentId) {
2410
+ return agentId.trim().toLowerCase() || "main";
2411
+ }
2412
+ function resolveIdentityNamePrefix(cfg, agentId) {
2413
+ const normalizedAgentId = normalizeAgentId$1(agentId);
2414
+ const identityName = cfg.agents?.list?.find((agent) => normalizeAgentId$1(agent.id ?? "") === normalizedAgentId)?.identity?.name?.trim();
2415
+ return identityName ? `[${identityName}]` : void 0;
2416
+ }
2417
+ function resolveMessagePrefix(cfg, agentId, opts) {
2418
+ const configured = opts?.configured ?? cfg.messages?.messagePrefix;
2419
+ if (configured !== void 0) return configured;
2420
+ if (opts?.hasAllowFrom === true) return "";
2421
+ return resolveIdentityNamePrefix(cfg, agentId) ?? opts?.fallback ?? "[openclaw]";
2422
+ }
2423
+ //#endregion
2424
+ //#region extensions/whatsapp/src/auto-reply/monitor/message-line.ts
2425
+ function formatReplyContext(msg) {
2426
+ const replyTo = getReplyContext(msg);
2427
+ if (!replyTo?.body) return null;
2428
+ return `[Replying to ${replyTo.sender?.label ?? replyTo.sender?.e164 ?? "unknown sender"}${replyTo.id ? ` id:${replyTo.id}` : ""}]\n${replyTo.body}\n[/Replying]`;
2429
+ }
2430
+ function buildInboundLine(params) {
2431
+ const { cfg, msg, agentId, previousTimestamp, envelope } = params;
2432
+ const messagePrefix = resolveMessagePrefix(cfg, agentId, {
2433
+ configured: cfg.channels?.whatsapp?.messagePrefix,
2434
+ hasAllowFrom: (cfg.channels?.whatsapp?.allowFrom?.length ?? 0) > 0
2435
+ });
2436
+ const prefixStr = messagePrefix ? `${messagePrefix} ` : "";
2437
+ const replyContext = formatReplyContext(msg);
2438
+ const baseLine = `${prefixStr}${msg.body}${replyContext ? `\n\n${replyContext}` : ""}`;
2439
+ const sender = getSenderIdentity(msg);
2440
+ return formatInboundEnvelope({
2441
+ channel: "WhatsApp",
2442
+ from: msg.chatType === "group" ? msg.from : msg.from?.replace(/^whatsapp:/, ""),
2443
+ timestamp: msg.timestamp,
2444
+ body: baseLine,
2445
+ chatType: msg.chatType,
2446
+ sender: {
2447
+ name: sender.name ?? void 0,
2448
+ e164: sender.e164 ?? void 0,
2449
+ id: getPrimaryIdentityId(sender) ?? void 0
2450
+ },
2451
+ previousTimestamp,
2452
+ envelope,
2453
+ fromMe: msg.fromMe
2454
+ });
2455
+ }
2456
+ //#endregion
2457
+ //#region extensions/whatsapp/src/auto-reply/monitor/process-message.ts
2458
+ const WHATSAPP_MESSAGE_RECEIVED_HOOK_LIMITS = {
2459
+ maxConcurrency: 8,
2460
+ maxQueue: 128,
2461
+ timeoutMs: 2e3
2462
+ };
2463
+ function readWhatsAppMessageReceivedHookOptIn(value) {
2464
+ if (!value || typeof value !== "object") return;
2465
+ return value.pluginHooks?.messageReceived === true ? true : void 0;
2466
+ }
2467
+ function shouldEmitWhatsAppMessageReceivedHooks(params) {
2468
+ const channelConfig = params.cfg.channels?.whatsapp;
2469
+ return readWhatsAppMessageReceivedHookOptIn(params.accountId && channelConfig?.accounts ? channelConfig.accounts[params.accountId] : void 0) ?? readWhatsAppMessageReceivedHookOptIn(channelConfig) ?? false;
2470
+ }
2471
+ function emitWhatsAppMessageReceivedHooks(params) {
2472
+ const canonical = deriveInboundMessageHookContext(params.ctx);
2473
+ const hookRunner = getGlobalHookRunner();
2474
+ if (hookRunner?.hasHooks("message_received")) fireAndForgetBoundedHook(() => hookRunner.runMessageReceived(toPluginMessageReceivedEvent(canonical), toPluginMessageContext(canonical)), "whatsapp: message_received plugin hook failed", void 0, WHATSAPP_MESSAGE_RECEIVED_HOOK_LIMITS);
2475
+ fireAndForgetBoundedHook(() => triggerInternalHook(createInternalHookEvent("message", "received", params.sessionKey, toInternalMessageReceivedContext(canonical))), "whatsapp: message_received internal hook failed", void 0, WHATSAPP_MESSAGE_RECEIVED_HOOK_LIMITS);
2476
+ }
2477
+ function emitWhatsAppMessageReceivedHooksIfEnabled(params) {
2478
+ if (!shouldEmitWhatsAppMessageReceivedHooks({
2479
+ cfg: params.cfg,
2480
+ accountId: params.accountId
2481
+ })) return;
2482
+ emitWhatsAppMessageReceivedHooks({
2483
+ ctx: params.ctx,
2484
+ sessionKey: params.sessionKey
2485
+ });
2486
+ }
2487
+ function resolvePinnedMainDmRecipient(params) {
2488
+ return resolvePinnedMainDmOwnerFromAllowlist({
2489
+ dmScope: params.cfg.session?.dmScope,
2490
+ allowFrom: params.allowFrom,
2491
+ normalizeEntry: (entry) => (0, text_runtime_exports.normalizeE164)(entry)
2492
+ });
2493
+ }
2494
+ async function processMessage(params) {
2495
+ const conversationId = params.msg.conversationId ?? params.msg.from;
2496
+ const self = getSelfIdentity(params.msg);
2497
+ const inboundPolicy = resolveWhatsAppInboundPolicy({
2498
+ cfg: params.cfg,
2499
+ accountId: params.route.accountId ?? params.msg.accountId,
2500
+ selfE164: self.e164 ?? null
2501
+ });
2502
+ const account = inboundPolicy.account;
2503
+ const contextVisibilityMode = resolveChannelContextVisibilityMode({
2504
+ cfg: params.cfg,
2505
+ channel: "whatsapp",
2506
+ accountId: account.accountId
2507
+ });
2508
+ const { storePath, envelopeOptions, previousTimestamp } = resolveInboundSessionEnvelopeContext({
2509
+ cfg: params.cfg,
2510
+ agentId: params.route.agentId,
2511
+ sessionKey: params.route.sessionKey
2512
+ });
2513
+ let audioTranscript = params.preflightAudioTranscript ?? void 0;
2514
+ const hasAudioBody = params.msg.mediaType?.startsWith("audio/") === true && params.msg.body === "<media:audio>";
2515
+ if (params.preflightAudioTranscript === void 0 && hasAudioBody && params.msg.mediaPath) try {
2516
+ const { transcribeFirstAudio } = await import("./audio-preflight.runtime-Bcdsl_ri.js");
2517
+ audioTranscript = await transcribeFirstAudio({
2518
+ ctx: {
2519
+ MediaPaths: [params.msg.mediaPath],
2520
+ MediaTypes: params.msg.mediaType ? [params.msg.mediaType] : void 0
2521
+ },
2522
+ cfg: params.cfg
2523
+ });
2524
+ } catch {
2525
+ if (shouldLogVerbose$1()) logVerbose$1("whatsapp: audio preflight transcription failed, using placeholder");
2526
+ }
2527
+ const msgForAgent = audioTranscript !== void 0 ? {
2528
+ ...params.msg,
2529
+ body: audioTranscript
2530
+ } : params.msg;
2531
+ let combinedBody = buildInboundLine({
2532
+ cfg: params.cfg,
2533
+ msg: msgForAgent,
2534
+ agentId: params.route.agentId,
2535
+ previousTimestamp,
2536
+ envelope: envelopeOptions
2537
+ });
2538
+ let shouldClearGroupHistory = false;
2539
+ const visibleGroupHistory = params.msg.chatType === "group" ? resolveVisibleWhatsAppGroupHistory({
2540
+ history: params.groupHistory ?? params.groupHistories.get(params.groupHistoryKey) ?? [],
2541
+ mode: contextVisibilityMode,
2542
+ groupPolicy: inboundPolicy.groupPolicy,
2543
+ groupAllowFrom: inboundPolicy.groupAllowFrom
2544
+ }) : void 0;
2545
+ if (params.msg.chatType === "group") {
2546
+ const history = visibleGroupHistory ?? [];
2547
+ if (history.length > 0) combinedBody = buildHistoryContextFromEntries({
2548
+ entries: history.map((m) => ({
2549
+ sender: m.sender,
2550
+ body: m.body,
2551
+ timestamp: m.timestamp
2552
+ })),
2553
+ currentMessage: combinedBody,
2554
+ excludeLast: false,
2555
+ formatEntry: (entry) => {
2556
+ return formatInboundEnvelope$1({
2557
+ channel: "WhatsApp",
2558
+ from: conversationId,
2559
+ timestamp: entry.timestamp,
2560
+ body: entry.body,
2561
+ chatType: "group",
2562
+ senderLabel: entry.sender,
2563
+ envelope: envelopeOptions
2564
+ });
2565
+ }
2566
+ });
2567
+ shouldClearGroupHistory = !(params.suppressGroupHistoryClear ?? false);
2568
+ }
2569
+ const combinedEchoKey = params.buildCombinedEchoKey({
2570
+ sessionKey: params.route.sessionKey,
2571
+ combinedBody
2572
+ });
2573
+ if (params.echoHas(combinedEchoKey)) {
2574
+ logVerbose$1("Skipping auto-reply: detected echo for combined message");
2575
+ params.echoForget(combinedEchoKey);
2576
+ return false;
2577
+ }
2578
+ let ackReaction = params.ackReaction ?? null;
2579
+ if (!ackReaction && params.ackAlreadySent !== true) ackReaction = await maybeSendAckReaction({
2580
+ cfg: params.cfg,
2581
+ msg: params.msg,
2582
+ agentId: params.route.agentId,
2583
+ sessionKey: params.route.sessionKey,
2584
+ conversationId,
2585
+ verbose: params.verbose,
2586
+ accountId: account.accountId,
2587
+ info: params.replyLogger.info.bind(params.replyLogger),
2588
+ warn: params.replyLogger.warn.bind(params.replyLogger)
2589
+ });
2590
+ const correlationId = params.msg.id ?? newConnectionId();
2591
+ params.replyLogger.info({
2592
+ connectionId: params.connectionId,
2593
+ correlationId,
2594
+ from: params.msg.chatType === "group" ? conversationId : params.msg.from,
2595
+ to: params.msg.to,
2596
+ body: elide(combinedBody, 240),
2597
+ mediaType: params.msg.mediaType ?? null,
2598
+ mediaPath: params.msg.mediaPath ?? null
2599
+ }, "inbound web message");
2600
+ const fromDisplay = params.msg.chatType === "group" ? conversationId : params.msg.from;
2601
+ const kindLabel = params.msg.mediaType ? `, ${params.msg.mediaType}` : "";
2602
+ whatsappInboundLog.info(`Inbound message ${fromDisplay} -> ${params.msg.to} (${params.msg.chatType}${kindLabel}, ${combinedBody.length} chars)`);
2603
+ if (shouldLogVerbose$1()) whatsappInboundLog.debug(`Inbound body: ${elide(combinedBody, 400)}`);
2604
+ const sender = getSenderIdentity(params.msg);
2605
+ const visibleReplyTo = resolveVisibleWhatsAppReplyContext({
2606
+ msg: params.msg,
2607
+ authDir: account.authDir,
2608
+ mode: contextVisibilityMode,
2609
+ groupPolicy: inboundPolicy.groupPolicy,
2610
+ groupAllowFrom: inboundPolicy.groupAllowFrom
2611
+ });
2612
+ const dmRouteTarget = resolveWhatsAppDmRouteTarget({
2613
+ msg: params.msg,
2614
+ senderE164: sender.e164 ?? void 0,
2615
+ normalizeE164: text_runtime_exports.normalizeE164
2616
+ });
2617
+ const commandAuthorized = shouldComputeCommandAuthorized(params.msg.body, params.cfg) ? await resolveWhatsAppCommandAuthorized({
2618
+ cfg: params.cfg,
2619
+ msg: params.msg,
2620
+ policy: inboundPolicy
2621
+ }) : void 0;
2622
+ const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
2623
+ cfg: params.cfg,
2624
+ agentId: params.route.agentId,
2625
+ channel: "whatsapp",
2626
+ accountId: params.route.accountId
2627
+ });
2628
+ const responsePrefix = resolveWhatsAppResponsePrefix({
2629
+ cfg: params.cfg,
2630
+ agentId: params.route.agentId,
2631
+ isSelfChat: params.msg.chatType !== "group" && inboundPolicy.isSelfChat,
2632
+ pipelineResponsePrefix: replyPipeline.responsePrefix
2633
+ });
2634
+ const replyThreading = resolveBatchedReplyThreadingPolicy(account.replyToMode ?? "off", params.msg.isBatched === true);
2635
+ const conversationSystemPrompt = params.msg.chatType === "group" ? resolveWhatsAppGroupSystemPrompt({
2636
+ accountConfig: account,
2637
+ groupId: conversationId
2638
+ }) : resolveWhatsAppDirectSystemPrompt({
2639
+ accountConfig: account,
2640
+ peerId: dmRouteTarget ?? params.msg.from
2641
+ });
2642
+ const ctxPayload = buildWhatsAppInboundContext({
2643
+ bodyForAgent: msgForAgent.body,
2644
+ combinedBody,
2645
+ commandBody: params.msg.body,
2646
+ commandAuthorized,
2647
+ conversationId,
2648
+ groupHistory: visibleGroupHistory,
2649
+ groupMemberRoster: params.groupMemberNames.get(params.groupHistoryKey),
2650
+ groupSystemPrompt: conversationSystemPrompt,
2651
+ msg: params.msg,
2652
+ rawBody: params.msg.body,
2653
+ route: params.route,
2654
+ sender: {
2655
+ id: getPrimaryIdentityId(sender) ?? void 0,
2656
+ name: sender.name ?? void 0,
2657
+ e164: sender.e164 ?? void 0
2658
+ },
2659
+ ...audioTranscript !== void 0 ? { transcript: audioTranscript } : {},
2660
+ ...audioTranscript !== void 0 ? { mediaTranscribedIndexes: [0] } : {},
2661
+ replyThreading,
2662
+ visibleReplyTo: visibleReplyTo ?? void 0
2663
+ });
2664
+ emitWhatsAppMessageReceivedHooksIfEnabled({
2665
+ cfg: params.cfg,
2666
+ ctx: ctxPayload,
2667
+ accountId: params.route.accountId,
2668
+ sessionKey: params.route.sessionKey
2669
+ });
2670
+ const pinnedMainDmRecipient = resolvePinnedMainDmRecipient({
2671
+ cfg: params.cfg,
2672
+ allowFrom: inboundPolicy.configuredAllowFrom
2673
+ });
2674
+ updateWhatsAppMainLastRoute({
2675
+ backgroundTasks: params.backgroundTasks,
2676
+ cfg: params.cfg,
2677
+ ctx: ctxPayload,
2678
+ dmRouteTarget,
2679
+ pinnedMainDmRecipient,
2680
+ route: params.route,
2681
+ updateLastRoute: updateLastRouteInBackground,
2682
+ warn: params.replyLogger.warn.bind(params.replyLogger)
2683
+ });
2684
+ const turnResult = await runInboundReplyTurn({
2685
+ channel: "whatsapp",
2686
+ accountId: params.route.accountId,
2687
+ raw: params.msg,
2688
+ adapter: {
2689
+ ingest: () => ({
2690
+ id: params.msg.id ?? `${conversationId}:${Date.now()}`,
2691
+ timestamp: params.msg.timestamp,
2692
+ rawText: ctxPayload.RawBody ?? "",
2693
+ textForAgent: ctxPayload.BodyForAgent,
2694
+ textForCommands: ctxPayload.CommandBody,
2695
+ raw: params.msg
2696
+ }),
2697
+ resolveTurn: () => ({
2698
+ channel: "whatsapp",
2699
+ accountId: params.route.accountId,
2700
+ routeSessionKey: params.route.sessionKey,
2701
+ storePath,
2702
+ ctxPayload,
2703
+ recordInboundSession,
2704
+ record: {
2705
+ onRecordError: (err) => {
2706
+ params.replyLogger.warn({
2707
+ error: formatError(err),
2708
+ storePath,
2709
+ sessionKey: params.route.sessionKey
2710
+ }, "failed updating session meta");
2711
+ },
2712
+ trackSessionMetaTask: (task) => {
2713
+ trackBackgroundTask(params.backgroundTasks, task);
2714
+ }
2715
+ },
2716
+ runDispatch: () => dispatchWhatsAppBufferedReply({
2717
+ cfg: params.cfg,
2718
+ connectionId: params.connectionId,
2719
+ context: ctxPayload,
2720
+ conversationId,
2721
+ deliverReply: deliverWebReply,
2722
+ groupHistories: params.groupHistories,
2723
+ groupHistoryKey: params.groupHistoryKey,
2724
+ maxMediaBytes: params.maxMediaBytes,
2725
+ maxMediaTextChunkLimit: params.maxMediaTextChunkLimit,
2726
+ msg: params.msg,
2727
+ onModelSelected,
2728
+ rememberSentText: params.rememberSentText,
2729
+ replyLogger: params.replyLogger,
2730
+ replyPipeline: {
2731
+ ...replyPipeline,
2732
+ responsePrefix
2733
+ },
2734
+ replyResolver: params.replyResolver,
2735
+ route: params.route,
2736
+ shouldClearGroupHistory
2737
+ })
2738
+ })
2739
+ }
2740
+ });
2741
+ const didSendReply = turnResult.dispatched ? turnResult.dispatchResult : false;
2742
+ removeAckReactionHandleAfterReply({
2743
+ removeAfterReply: Boolean(params.cfg.messages?.removeAckAfterReply && didSendReply),
2744
+ ackReaction,
2745
+ onError: (err) => {
2746
+ logAckFailure({
2747
+ log: logVerbose$1,
2748
+ channel: "whatsapp",
2749
+ target: `${params.msg.chatId ?? conversationId}/${params.msg.id ?? "unknown"}`,
2750
+ error: err
2751
+ });
2752
+ }
2753
+ });
2754
+ return didSendReply;
2755
+ }
2756
+ //#endregion
2757
+ //#region extensions/whatsapp/src/auto-reply/monitor/on-message.ts
2758
+ function createWebOnMessageHandler(params) {
2759
+ const processForRoute = async (msg, route, groupHistoryKey, opts) => {
2760
+ const processParams = {
2761
+ cfg: params.cfg,
2762
+ msg,
2763
+ route,
2764
+ groupHistoryKey,
2765
+ groupHistories: params.groupHistories,
2766
+ groupMemberNames: params.groupMemberNames,
2767
+ connectionId: params.connectionId,
2768
+ verbose: params.verbose,
2769
+ maxMediaBytes: params.maxMediaBytes,
2770
+ replyResolver: params.replyResolver,
2771
+ replyLogger: params.replyLogger,
2772
+ backgroundTasks: params.backgroundTasks,
2773
+ rememberSentText: params.echoTracker.rememberText,
2774
+ echoHas: params.echoTracker.has,
2775
+ echoForget: params.echoTracker.forget,
2776
+ buildCombinedEchoKey: params.echoTracker.buildCombinedKey
2777
+ };
2778
+ if (opts?.groupHistory !== void 0) processParams.groupHistory = opts.groupHistory;
2779
+ if (opts?.suppressGroupHistoryClear !== void 0) processParams.suppressGroupHistoryClear = opts.suppressGroupHistoryClear;
2780
+ if (opts?.preflightAudioTranscript !== void 0) processParams.preflightAudioTranscript = opts.preflightAudioTranscript;
2781
+ if (opts?.ackAlreadySent === true) processParams.ackAlreadySent = true;
2782
+ if (opts?.ackReaction !== void 0) processParams.ackReaction = opts.ackReaction;
2783
+ return processMessage(processParams);
2784
+ };
2785
+ return async (msg) => {
2786
+ const conversationId = msg.conversationId ?? msg.from;
2787
+ const peerId = resolvePeerId(msg);
2788
+ const baseRoute = resolveAgentRoute({
2789
+ cfg: getRuntimeConfig$1(),
2790
+ channel: "whatsapp",
2791
+ accountId: msg.accountId,
2792
+ peer: {
2793
+ kind: msg.chatType === "group" ? "group" : "direct",
2794
+ id: peerId
2795
+ }
2796
+ });
2797
+ const route = msg.chatType === "group" ? resolveWhatsAppGroupSessionRoute(baseRoute) : baseRoute;
2798
+ const groupHistoryKey = msg.chatType === "group" ? buildGroupHistoryKey({
2799
+ channel: "whatsapp",
2800
+ accountId: route.accountId,
2801
+ peerKind: "group",
2802
+ peerId
2803
+ }) : route.sessionKey;
2804
+ if (msg.from === msg.to) logVerbose(`📱 Same-phone mode detected (from === to: ${msg.from})`);
2805
+ if (params.echoTracker.has(msg.body)) {
2806
+ logVerbose("Skipping auto-reply: detected echo (message matches recently sent text)");
2807
+ params.echoTracker.forget(msg.body);
2808
+ return;
2809
+ }
2810
+ let preflightAudioTranscript;
2811
+ const hasAudioBody = msg.mediaType?.startsWith("audio/") === true && msg.body === "<media:audio>";
2812
+ const canRunEarlyAudioPreflight = msg.chatType === "group" || msg.accessControlPassed === true;
2813
+ let ackAlreadySent = false;
2814
+ let ackReaction = null;
2815
+ const runAudioPreflightOnce = async () => {
2816
+ if (preflightAudioTranscript !== void 0 || !canRunEarlyAudioPreflight || !hasAudioBody || !msg.mediaPath) return;
2817
+ ackReaction = await maybeSendAckReaction({
2818
+ cfg: params.cfg,
2819
+ msg,
2820
+ agentId: route.agentId,
2821
+ sessionKey: route.sessionKey,
2822
+ conversationId,
2823
+ verbose: params.verbose,
2824
+ accountId: route.accountId,
2825
+ info: params.replyLogger.info.bind(params.replyLogger),
2826
+ warn: params.replyLogger.warn.bind(params.replyLogger)
2827
+ });
2828
+ ackAlreadySent = ackReaction !== null;
2829
+ try {
2830
+ const { transcribeFirstAudio } = await import("./audio-preflight.runtime-Bcdsl_ri.js");
2831
+ preflightAudioTranscript = await transcribeFirstAudio({
2832
+ ctx: {
2833
+ MediaPaths: [msg.mediaPath],
2834
+ MediaTypes: msg.mediaType ? [msg.mediaType] : void 0
2835
+ },
2836
+ cfg: params.cfg
2837
+ }) ?? null;
2838
+ } catch {
2839
+ preflightAudioTranscript = null;
2840
+ }
2841
+ };
2842
+ if (msg.chatType === "group") {
2843
+ const sender = getSenderIdentity(msg);
2844
+ const metaCtx = {
2845
+ From: msg.from,
2846
+ To: msg.to,
2847
+ SessionKey: route.sessionKey,
2848
+ AccountId: route.accountId,
2849
+ ChatType: msg.chatType,
2850
+ ConversationLabel: conversationId,
2851
+ GroupSubject: msg.groupSubject,
2852
+ SenderName: sender.name ?? void 0,
2853
+ SenderId: getPrimaryIdentityId(sender) ?? void 0,
2854
+ SenderE164: sender.e164 ?? void 0,
2855
+ Provider: "whatsapp",
2856
+ Surface: "whatsapp",
2857
+ OriginatingChannel: "whatsapp",
2858
+ OriginatingTo: conversationId
2859
+ };
2860
+ updateLastRouteInBackground({
2861
+ cfg: params.cfg,
2862
+ backgroundTasks: params.backgroundTasks,
2863
+ storeAgentId: route.agentId,
2864
+ sessionKey: route.sessionKey,
2865
+ channel: "whatsapp",
2866
+ to: conversationId,
2867
+ accountId: route.accountId,
2868
+ ctx: metaCtx,
2869
+ warn: params.replyLogger.warn.bind(params.replyLogger)
2870
+ });
2871
+ let gating = await applyGroupGating({
2872
+ cfg: params.cfg,
2873
+ msg,
2874
+ deferMissingMention: hasAudioBody && Boolean(msg.mediaPath),
2875
+ conversationId,
2876
+ groupHistoryKey,
2877
+ agentId: route.agentId,
2878
+ sessionKey: route.sessionKey,
2879
+ baseMentionConfig: params.baseMentionConfig,
2880
+ authDir: params.account.authDir,
2881
+ selfChatMode: params.account.selfChatMode,
2882
+ groupHistories: params.groupHistories,
2883
+ groupHistoryLimit: params.groupHistoryLimit,
2884
+ groupMemberNames: params.groupMemberNames,
2885
+ logVerbose,
2886
+ replyLogger: params.replyLogger
2887
+ });
2888
+ if (!gating.shouldProcess && "needsMentionText" in gating && gating.needsMentionText === true) {
2889
+ await runAudioPreflightOnce();
2890
+ gating = await applyGroupGating({
2891
+ cfg: params.cfg,
2892
+ msg,
2893
+ ...typeof preflightAudioTranscript === "string" ? { mentionText: preflightAudioTranscript } : {},
2894
+ conversationId,
2895
+ groupHistoryKey,
2896
+ agentId: route.agentId,
2897
+ sessionKey: route.sessionKey,
2898
+ baseMentionConfig: params.baseMentionConfig,
2899
+ authDir: params.account.authDir,
2900
+ selfChatMode: params.account.selfChatMode,
2901
+ groupHistories: params.groupHistories,
2902
+ groupHistoryLimit: params.groupHistoryLimit,
2903
+ groupMemberNames: params.groupMemberNames,
2904
+ logVerbose,
2905
+ replyLogger: params.replyLogger
2906
+ });
2907
+ }
2908
+ if (!gating.shouldProcess) return;
2909
+ } else if (!msg.sender?.e164 && !msg.senderE164 && peerId && peerId.startsWith("+")) {
2910
+ const normalized = (0, text_runtime_exports.normalizeE164)(peerId);
2911
+ if (normalized) {
2912
+ msg.sender = {
2913
+ ...msg.sender,
2914
+ e164: normalized
2915
+ };
2916
+ msg.senderE164 = normalized;
2917
+ }
2918
+ }
2919
+ await runAudioPreflightOnce();
2920
+ if (await maybeBroadcastMessage({
2921
+ cfg: params.cfg,
2922
+ msg,
2923
+ peerId,
2924
+ route,
2925
+ groupHistoryKey,
2926
+ groupHistories: params.groupHistories,
2927
+ ...preflightAudioTranscript !== void 0 ? { preflightAudioTranscript } : {},
2928
+ ...ackAlreadySent && msg.chatType !== "group" ? { ackAlreadySent: true } : {},
2929
+ ...ackReaction && msg.chatType !== "group" ? { ackReaction } : {},
2930
+ processMessage: (m, r, k, opts) => processForRoute(m, r, k, opts)
2931
+ })) return;
2932
+ await processForRoute(msg, route, groupHistoryKey, {
2933
+ ...preflightAudioTranscript !== void 0 ? { preflightAudioTranscript } : {},
2934
+ ...ackAlreadySent ? { ackAlreadySent: true } : {},
2935
+ ...ackReaction ? { ackReaction } : {}
2936
+ });
2937
+ };
2938
+ }
2939
+ //#endregion
2940
+ //#region extensions/whatsapp/src/auto-reply/monitor.ts
2941
+ function isNonRetryableWebCloseStatus(statusCode) {
2942
+ return statusCode === 440;
2943
+ }
2944
+ let replyResolverRuntimePromise = null;
2945
+ function loadReplyResolverRuntime() {
2946
+ replyResolverRuntimePromise ??= import("./reply-resolver.runtime-BRCgoO4C.js");
2947
+ return replyResolverRuntimePromise;
2948
+ }
2949
+ function normalizeReconnectAccountId(accountId) {
2950
+ return (accountId ?? "").trim() || "default";
2951
+ }
2952
+ function isNoListenerReconnectError(lastError) {
2953
+ return typeof lastError === "string" && /No active WhatsApp Web listener/i.test(lastError);
2954
+ }
2955
+ function resolveExplicitWhatsAppDebounceOverride(params) {
2956
+ const channel = params.sourceCfg?.channels?.whatsapp;
2957
+ if (!channel) return;
2958
+ const accountId = normalizeReconnectAccountId(params.accountId);
2959
+ const accountDebounce = resolveAccountEntry(channel.accounts, accountId)?.debounceMs;
2960
+ if (accountDebounce !== void 0) return accountDebounce;
2961
+ if (accountId !== "default") {
2962
+ const defaultAccountDebounce = resolveAccountEntry(channel.accounts, "default")?.debounceMs;
2963
+ if (defaultAccountDebounce !== void 0) return defaultAccountDebounce;
2964
+ }
2965
+ return channel.debounceMs;
2966
+ }
2967
+ function isRetryableAuthUnstableError(error) {
2968
+ return error instanceof WhatsAppAuthUnstableError || typeof error === "object" && error !== null && "code" in error && error.code === "whatsapp-auth-unstable";
2969
+ }
2970
+ async function clearTerminalWebAuthState(params) {
2971
+ try {
2972
+ const cleared = await logoutWeb({
2973
+ authDir: params.account.authDir,
2974
+ isLegacyAuthDir: params.account.isLegacyAuthDir,
2975
+ runtime: params.runtime
2976
+ });
2977
+ params.log.warn({
2978
+ accountId: params.account.accountId,
2979
+ cleared,
2980
+ healthState: params.healthState,
2981
+ status: params.statusLabel
2982
+ }, "web reconnect: cleared cached auth after terminal close");
2983
+ } catch (error) {
2984
+ params.log.warn({
2985
+ accountId: params.account.accountId,
2986
+ error: formatError(error),
2987
+ healthState: params.healthState,
2988
+ status: params.statusLabel
2989
+ }, "web reconnect: failed clearing cached auth after terminal close");
2990
+ params.runtime.error(`WhatsApp Web cleanup failed after terminal close (status ${params.statusLabel}). Run \`${formatCliCommand("openclaw channels logout --channel whatsapp")}\`, then relink with \`${formatCliCommand("openclaw channels login --channel whatsapp")}\`.`);
2991
+ }
2992
+ }
2993
+ const DEFAULT_TRANSPORT_TIMEOUT_MS = 300 * 1e3;
2994
+ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSocket, keepAlive = true, replyResolver, runtime = defaultRuntime, abortSignal, tuning = {}) {
2995
+ const activeReplyResolver = replyResolver ?? (await loadReplyResolverRuntime()).getReplyFromConfig;
2996
+ const runId = newConnectionId();
2997
+ const replyLogger = getChildLogger$1({
2998
+ module: "web-auto-reply",
2999
+ runId
3000
+ });
3001
+ const heartbeatLogger = getChildLogger$1({
3002
+ module: "web-heartbeat",
3003
+ runId
3004
+ });
3005
+ const reconnectLogger = getChildLogger$1({
3006
+ module: "web-reconnect",
3007
+ runId
3008
+ });
3009
+ const statusController = createWebChannelStatusController(tuning.statusSink);
3010
+ statusController.emit();
3011
+ const baseCfg = getRuntimeConfig$1();
3012
+ const sourceCfg = getRuntimeConfigSourceSnapshot();
3013
+ const account = resolveWhatsAppAccount({
3014
+ cfg: baseCfg,
3015
+ accountId: tuning.accountId
3016
+ });
3017
+ const cfg = {
3018
+ ...baseCfg,
3019
+ channels: {
3020
+ ...baseCfg.channels,
3021
+ whatsapp: {
3022
+ ...baseCfg.channels?.whatsapp,
3023
+ ackReaction: account.ackReaction,
3024
+ messagePrefix: account.messagePrefix,
3025
+ allowFrom: account.allowFrom,
3026
+ groupAllowFrom: account.groupAllowFrom,
3027
+ groupPolicy: account.groupPolicy,
3028
+ textChunkLimit: account.textChunkLimit,
3029
+ chunkMode: account.chunkMode,
3030
+ mediaMaxMb: account.mediaMaxMb,
3031
+ blockStreaming: account.blockStreaming,
3032
+ groups: account.groups
3033
+ }
3034
+ }
3035
+ };
3036
+ const maxMediaBytes = resolveWhatsAppMediaMaxBytes(account);
3037
+ const heartbeatSeconds = resolveHeartbeatSeconds(cfg, tuning.heartbeatSeconds);
3038
+ const reconnectPolicy = resolveReconnectPolicy(cfg, tuning.reconnect);
3039
+ const socketTiming = resolveWhatsAppSocketTiming(cfg, tuning.socketTiming);
3040
+ const baseMentionConfig = buildMentionConfig(cfg);
3041
+ const groupHistoryLimit = account.historyLimit ?? cfg.channels?.whatsapp?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT;
3042
+ const groupHistories = /* @__PURE__ */ new Map();
3043
+ const groupMemberNames = /* @__PURE__ */ new Map();
3044
+ const groupMetadataCache = /* @__PURE__ */ new Map();
3045
+ const echoTracker = createEchoTracker({
3046
+ maxItems: 100,
3047
+ logVerbose
3048
+ });
3049
+ const sleep = tuning.sleep ?? ((ms, signal) => sleepWithAbort(ms, signal ?? abortSignal));
3050
+ const stopRequested = () => abortSignal?.aborted === true;
3051
+ const currentMaxListeners = process.getMaxListeners?.() ?? 10;
3052
+ if (process.setMaxListeners && currentMaxListeners < 50) process.setMaxListeners(50);
3053
+ let sigintStop = false;
3054
+ const handleSigint = () => {
3055
+ sigintStop = true;
3056
+ };
3057
+ process.once("SIGINT", handleSigint);
3058
+ const transportTimeoutMs = tuning.transportTimeoutMs ?? DEFAULT_TRANSPORT_TIMEOUT_MS;
3059
+ const messageTimeoutMs = tuning.messageTimeoutMs ?? 1800 * 1e3;
3060
+ const watchdogCheckMs = tuning.watchdogCheckMs ?? 60 * 1e3;
3061
+ const controller = new WhatsAppConnectionController({
3062
+ accountId: account.accountId,
3063
+ authDir: account.authDir,
3064
+ verbose,
3065
+ keepAlive,
3066
+ heartbeatSeconds,
3067
+ transportTimeoutMs,
3068
+ messageTimeoutMs,
3069
+ watchdogCheckMs,
3070
+ reconnectPolicy,
3071
+ socketTiming,
3072
+ abortSignal,
3073
+ sleep,
3074
+ isNonRetryableStatus: isNonRetryableWebCloseStatus
3075
+ });
3076
+ try {
3077
+ while (true) {
3078
+ if (stopRequested()) break;
3079
+ const connectionId = newConnectionId();
3080
+ const inboundDebounceMs = resolveInboundDebounceMs({
3081
+ cfg,
3082
+ channel: "whatsapp",
3083
+ overrideMs: resolveExplicitWhatsAppDebounceOverride({
3084
+ cfg,
3085
+ sourceCfg,
3086
+ accountId: account.accountId
3087
+ })
3088
+ });
3089
+ const shouldDebounce = (msg) => {
3090
+ if (msg.mediaPath || msg.mediaType) return false;
3091
+ if (msg.location) return false;
3092
+ if (msg.replyToId || msg.replyToBody) return false;
3093
+ return !hasControlCommand(msg.body, cfg);
3094
+ };
3095
+ let connection;
3096
+ try {
3097
+ connection = await controller.openConnection({
3098
+ connectionId,
3099
+ createListener: async ({ sock, connection }) => {
3100
+ const onMessage = createWebOnMessageHandler({
3101
+ cfg,
3102
+ verbose,
3103
+ connectionId,
3104
+ maxMediaBytes,
3105
+ groupHistoryLimit,
3106
+ groupHistories,
3107
+ groupMemberNames,
3108
+ echoTracker,
3109
+ backgroundTasks: connection.backgroundTasks,
3110
+ replyResolver: activeReplyResolver,
3111
+ replyLogger,
3112
+ baseMentionConfig,
3113
+ account
3114
+ });
3115
+ return await (listenerFactory ?? attachWebInboxToSocket)({
3116
+ cfg,
3117
+ verbose,
3118
+ accountId: account.accountId,
3119
+ authDir: account.authDir,
3120
+ mediaMaxMb: account.mediaMaxMb,
3121
+ selfChatMode: account.selfChatMode,
3122
+ sendReadReceipts: account.sendReadReceipts,
3123
+ debounceMs: inboundDebounceMs,
3124
+ shouldDebounce,
3125
+ socketRef: controller.socketRef,
3126
+ shouldRetryDisconnect: () => !sigintStop && controller.shouldRetryDisconnect(),
3127
+ disconnectRetryPolicy: reconnectPolicy,
3128
+ disconnectRetryAbortSignal: controller.getDisconnectRetryAbortSignal(),
3129
+ groupMetadataCache,
3130
+ onMessage: async (msg) => {
3131
+ const inboundAt = Date.now();
3132
+ controller.noteInbound(inboundAt);
3133
+ statusController.noteInbound(inboundAt);
3134
+ await onMessage(msg);
3135
+ },
3136
+ sock
3137
+ });
3138
+ },
3139
+ onHeartbeat: (snapshot) => {
3140
+ const authAgeMs = getWebAuthAgeMs(account.authDir);
3141
+ const minutesSinceLastMessage = snapshot.lastInboundAt ? Math.floor((Date.now() - snapshot.lastInboundAt) / 6e4) : null;
3142
+ const logData = {
3143
+ connectionId: snapshot.connectionId,
3144
+ reconnectAttempts: snapshot.reconnectAttempts,
3145
+ messagesHandled: snapshot.handledMessages,
3146
+ lastInboundAt: snapshot.lastInboundAt,
3147
+ lastTransportActivityAt: snapshot.lastTransportActivityAt,
3148
+ authAgeMs,
3149
+ uptimeMs: snapshot.uptimeMs,
3150
+ ...minutesSinceLastMessage !== null && minutesSinceLastMessage > 30 ? { minutesSinceLastMessage } : {}
3151
+ };
3152
+ statusController.noteTransportActivity(snapshot.lastTransportActivityAt);
3153
+ if (minutesSinceLastMessage && minutesSinceLastMessage > 30) heartbeatLogger.warn(logData, "⚠️ web gateway heartbeat - no messages in 30+ minutes");
3154
+ else heartbeatLogger.info(logData, "web gateway heartbeat");
3155
+ },
3156
+ onWatchdogTimeout: (snapshot) => {
3157
+ const now = Date.now();
3158
+ const transportSilentMs = now - snapshot.lastTransportActivityAt;
3159
+ const appBaselineAt = snapshot.lastInboundAt ?? snapshot.startedAt;
3160
+ const minutesSinceTransportActivity = Math.floor(transportSilentMs / 6e4);
3161
+ const minutesSinceAppActivity = Math.floor((now - appBaselineAt) / 6e4);
3162
+ const watchdogReason = transportSilentMs > transportTimeoutMs ? "transport-inactive" : "app-silent";
3163
+ statusController.noteWatchdogStale();
3164
+ heartbeatLogger.warn({
3165
+ connectionId: snapshot.connectionId,
3166
+ watchdogReason,
3167
+ minutesSinceTransportActivity,
3168
+ minutesSinceAppActivity,
3169
+ lastInboundAt: snapshot.lastInboundAt ? new Date(snapshot.lastInboundAt) : null,
3170
+ lastTransportActivityAt: new Date(snapshot.lastTransportActivityAt),
3171
+ messagesHandled: snapshot.handledMessages
3172
+ }, "WhatsApp watchdog timeout detected - forcing reconnect");
3173
+ whatsappHeartbeatLog.warn(`WhatsApp watchdog timeout (${watchdogReason}) - restarting connection`);
3174
+ }
3175
+ });
3176
+ } catch (error) {
3177
+ if (!isRetryableAuthUnstableError(error)) throw error;
3178
+ const retryDecision = controller.consumeReconnectAttempt();
3179
+ statusController.noteReconnectAttempts(retryDecision.reconnectAttempts);
3180
+ statusController.noteClose({
3181
+ error: error.message,
3182
+ reconnectAttempts: retryDecision.reconnectAttempts,
3183
+ healthState: retryDecision.healthState
3184
+ });
3185
+ if (retryDecision.action === "stop") {
3186
+ reconnectLogger.warn({
3187
+ connectionId,
3188
+ reconnectAttempts: retryDecision.reconnectAttempts,
3189
+ maxAttempts: reconnectPolicy.maxAttempts
3190
+ }, "web reconnect: auth state stayed unstable; max attempts reached");
3191
+ runtime.error(`WhatsApp auth state is still stabilizing after ${retryDecision.reconnectAttempts}/${reconnectPolicy.maxAttempts} attempts. Stopping web monitoring.`);
3192
+ await controller.shutdown();
3193
+ break;
3194
+ }
3195
+ reconnectLogger.info({
3196
+ connectionId,
3197
+ reconnectAttempts: retryDecision.reconnectAttempts,
3198
+ delayMs: retryDecision.delayMs
3199
+ }, "web reconnect: auth state still stabilizing during inbox attach; retrying");
3200
+ runtime.error(`WhatsApp auth state is still stabilizing. Retry ${retryDecision.reconnectAttempts}/${reconnectPolicy.maxAttempts || "∞"} for inbox attach in ${formatDurationPrecise(retryDecision.delayMs ?? 0)}.`);
3201
+ try {
3202
+ await controller.waitBeforeRetry(retryDecision.delayMs ?? 0);
3203
+ } catch {
3204
+ break;
3205
+ }
3206
+ continue;
3207
+ }
3208
+ statusController.noteConnected();
3209
+ controller.setUnhandledRejectionCleanup(registerUnhandledRejectionHandler((reason) => {
3210
+ if (!isLikelyWhatsAppCryptoError(reason)) return false;
3211
+ const errorStr = formatError(reason);
3212
+ reconnectLogger.warn({
3213
+ connectionId: connection.connectionId,
3214
+ error: errorStr
3215
+ }, "web reconnect: unhandled rejection from WhatsApp socket; forcing reconnect");
3216
+ controller.forceClose({
3217
+ status: 499,
3218
+ isLoggedOut: false,
3219
+ error: reason
3220
+ });
3221
+ return true;
3222
+ }));
3223
+ const { e164: selfE164 } = readWebSelfId(account.authDir);
3224
+ const connectRoute = resolveAgentRoute({
3225
+ cfg,
3226
+ channel: "whatsapp",
3227
+ accountId: account.accountId
3228
+ });
3229
+ enqueueSystemEvent(`WhatsApp gateway connected${selfE164 ? ` as ${selfE164}` : ""}.`, {
3230
+ sessionKey: connectRoute.sessionKey,
3231
+ trusted: true
3232
+ });
3233
+ const normalizedAccountId = normalizeReconnectAccountId(account.accountId);
3234
+ drainPendingDeliveries({
3235
+ drainKey: `whatsapp:${normalizedAccountId}`,
3236
+ logLabel: "WhatsApp reconnect drain",
3237
+ cfg,
3238
+ log: reconnectLogger,
3239
+ selectEntry: (entry) => ({
3240
+ match: entry.channel === "whatsapp" && normalizeReconnectAccountId(entry.accountId) === normalizedAccountId,
3241
+ bypassBackoff: isNoListenerReconnectError(entry.lastError)
3242
+ })
3243
+ }).catch((err) => {
3244
+ reconnectLogger.warn({
3245
+ connectionId: connection.connectionId,
3246
+ error: String(err)
3247
+ }, "reconnect drain failed");
3248
+ });
3249
+ whatsappLog.info("Listening for personal WhatsApp inbound messages.");
3250
+ if (process.stdout.isTTY || process.stderr.isTTY) whatsappLog.raw("Ctrl+C to stop.");
3251
+ if (!keepAlive) {
3252
+ await controller.shutdown();
3253
+ return;
3254
+ }
3255
+ const reason = await controller.waitForClose();
3256
+ if (stopRequested() || sigintStop || reason === "aborted") {
3257
+ await controller.shutdown();
3258
+ break;
3259
+ }
3260
+ const decision = controller.resolveCloseDecision(reason);
3261
+ if (decision === "aborted") {
3262
+ await controller.shutdown();
3263
+ break;
3264
+ }
3265
+ statusController.noteReconnectAttempts(controller.getReconnectAttempts());
3266
+ reconnectLogger.info({
3267
+ connectionId: connection.connectionId,
3268
+ status: decision.normalized.statusLabel,
3269
+ loggedOut: decision.normalized.isLoggedOut,
3270
+ reconnectAttempts: decision.reconnectAttempts,
3271
+ error: decision.normalized.errorText
3272
+ }, "web reconnect: connection closed");
3273
+ enqueueSystemEvent(`WhatsApp gateway disconnected (status ${decision.normalized.statusLabel})`, {
3274
+ sessionKey: connectRoute.sessionKey,
3275
+ trusted: true
3276
+ });
3277
+ if (decision.action === "stop") {
3278
+ await controller.closeCurrentConnection();
3279
+ statusController.noteClose({
3280
+ statusCode: decision.normalized.statusCode,
3281
+ loggedOut: decision.normalized.isLoggedOut,
3282
+ error: decision.normalized.errorText,
3283
+ reconnectAttempts: decision.reconnectAttempts,
3284
+ healthState: decision.healthState
3285
+ });
3286
+ if (decision.healthState === "logged-out") {
3287
+ await clearTerminalWebAuthState({
3288
+ account,
3289
+ runtime,
3290
+ statusLabel: decision.normalized.statusLabel,
3291
+ healthState: decision.healthState,
3292
+ log: reconnectLogger
3293
+ });
3294
+ runtime.error(`WhatsApp session logged out. Run \`${formatCliCommand("openclaw channels login --channel whatsapp")}\` to relink.`);
3295
+ } else if (decision.healthState === "conflict") {
3296
+ await clearTerminalWebAuthState({
3297
+ account,
3298
+ runtime,
3299
+ statusLabel: decision.normalized.statusLabel,
3300
+ healthState: decision.healthState,
3301
+ log: reconnectLogger
3302
+ });
3303
+ reconnectLogger.warn({
3304
+ connectionId: connection.connectionId,
3305
+ status: decision.normalized.statusLabel,
3306
+ error: decision.normalized.errorText
3307
+ }, "web reconnect: non-retryable close status; stopping monitor");
3308
+ runtime.error(`WhatsApp Web connection closed (status ${decision.normalized.statusLabel}: session conflict). Resolve conflicting WhatsApp Web sessions, then relink with \`${formatCliCommand("openclaw channels login --channel whatsapp")}\`. Stopping web monitoring.`);
3309
+ } else {
3310
+ reconnectLogger.warn({
3311
+ connectionId: connection.connectionId,
3312
+ status: decision.normalized.statusLabel,
3313
+ reconnectAttempts: decision.reconnectAttempts,
3314
+ maxAttempts: reconnectPolicy.maxAttempts
3315
+ }, "web reconnect: max attempts reached; continuing in degraded mode");
3316
+ runtime.error(`WhatsApp Web reconnect: max attempts reached (${decision.reconnectAttempts}/${reconnectPolicy.maxAttempts}). Stopping web monitoring.`);
3317
+ }
3318
+ await controller.shutdown();
3319
+ break;
3320
+ }
3321
+ statusController.noteClose({
3322
+ statusCode: decision.normalized.statusCode,
3323
+ error: decision.normalized.errorText,
3324
+ reconnectAttempts: decision.reconnectAttempts,
3325
+ healthState: decision.healthState
3326
+ });
3327
+ reconnectLogger.info({
3328
+ connectionId: connection.connectionId,
3329
+ status: decision.normalized.statusLabel,
3330
+ reconnectAttempts: decision.reconnectAttempts,
3331
+ maxAttempts: reconnectPolicy.maxAttempts || "unlimited",
3332
+ delayMs: decision.delayMs
3333
+ }, "web reconnect: scheduling retry");
3334
+ runtime.error(`WhatsApp Web connection closed (status ${decision.normalized.statusLabel}). Retry ${decision.reconnectAttempts}/${reconnectPolicy.maxAttempts || "∞"} in ${formatDurationPrecise(decision.delayMs ?? 0)}… (${decision.normalized.errorText})`);
3335
+ await controller.closeCurrentConnection();
3336
+ try {
3337
+ await controller.waitBeforeRetry(decision.delayMs ?? 0);
3338
+ } catch {
3339
+ break;
3340
+ }
3341
+ }
3342
+ } finally {
3343
+ statusController.markStopped();
3344
+ process.removeListener("SIGINT", handleSigint);
3345
+ await controller.shutdown();
3346
+ }
3347
+ }
3348
+ //#endregion
3349
+ export { loadWebMediaRaw as a, monitorWebInbox as c, extractMediaPlaceholder as d, extractText as f, loadWebMedia$1 as i, extractContactContext as l, LocalMediaAccessError as n, optimizeImageToJpeg as o, resetWebInboundDedupe as p, getDefaultLocalRoots as r, optimizeImageToPng as s, monitorWebChannel as t, extractLocationData as u };