@openclaw/whatsapp 2026.7.2-beta.3 → 2026.7.2-beta.5

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 (149) hide show
  1. package/README.md +1 -1
  2. package/dist/{access-control-tjy_em01.js → access-control-pZcxTmd5.js} +4 -5
  3. package/dist/{accounts-ejK8sXPU.js → accounts-CSqLvCY5.js} +1 -2
  4. package/dist/{action-runtime-p0V390gA.js → action-runtime-B2bNDUwE.js} +3 -3
  5. package/dist/action-runtime-api.js +1 -1
  6. package/dist/action-runtime.runtime.js +1 -1
  7. package/dist/{active-listener-BbdlXooT.js → active-listener-BEjWsCNM.js} +1 -1
  8. package/dist/api.js +13 -22
  9. package/dist/{approval-handler.runtime-BO1oJ-b8.js → approval-handler.runtime-CmtqgLSQ.js} +6 -6
  10. package/dist/{approval-reactions-CrZFAyf-.js → approval-reactions-Cu66aYdq.js} +4 -4
  11. package/dist/{auth-store-BQi9pZqQ.js → auth-store-DcEpIOmE.js} +4 -3
  12. package/dist/call-tool-api.js +4 -4
  13. package/dist/{channel-B2I4Opzb.js → channel-DcWBc3Hb.js} +39 -67
  14. package/dist/channel-config-api.js +1 -1
  15. package/dist/channel-plugin-api.js +1 -1
  16. package/dist/{channel-react-action-CPRQE-s3.js → channel-react-action-DUIkjl6w.js} +12 -15
  17. package/dist/{channel.runtime-CjkhQzk8.js → channel.runtime-Q5fJ2I2b.js} +5 -5
  18. package/dist/{channel.setup-BMWE9LmN.js → channel.setup-C_1u6HN6.js} +3 -6
  19. package/dist/{config-schema-BZ8h1hWA.js → config-schema-DARep0Nn.js} +7 -5
  20. package/dist/{connection-controller-HGaffeFb.js → connection-controller-BOB2MVKY.js} +8 -10
  21. package/dist/{connection-controller-runtime-context-9mEeAvu1.js → connection-controller-runtime-context-DG0tuYuw.js} +3 -3
  22. package/dist/contract-api.js +2 -2
  23. package/dist/{directory-config-BZ775eGX.js → directory-config-DalGrfGN.js} +7 -7
  24. package/dist/directory-contract-api.js +1 -1
  25. package/dist/doctor-CwzVXKoE.js +10 -0
  26. package/dist/doctor-contract-api.js +35 -79
  27. package/dist/{group-session-key-Dh8WZun_.js → group-session-key-DVMZlO3x.js} +11 -6
  28. package/dist/{identity-D7L8LCuH.js → identity-CPrT5aEU.js} +2 -2
  29. package/dist/light-runtime-api.js +2 -2
  30. package/dist/{login-ifOXCBjo.js → login-CvY2Dhch.js} +6 -7
  31. package/dist/{login-qr-DUHzVH_Y.js → login-qr-Bncpb6js.js} +7 -8
  32. package/dist/login-qr-runtime.js +1 -1
  33. package/dist/{monitor-_jqDKBXj.js → monitor-B7phZF9w.js} +427 -525
  34. package/dist/normalize-C7Hkc_vE.js +2 -0
  35. package/dist/{normalize-target-DSZnHkKF.js → normalize-target-CugRyONv.js} +6 -2
  36. package/dist/{question-reactions-C0qJ6wGA.js → question-reactions-CRsnyoMK.js} +1 -1
  37. package/dist/{resolve-outbound-target-BR97fAbB.js → resolve-outbound-target-1GqbT1uX.js} +1 -1
  38. package/dist/runtime-BLlToOi6.js +21 -0
  39. package/dist/runtime-api.js +10 -10
  40. package/dist/runtime-setter-api.js +1 -1
  41. package/dist/{send-SB-4JrVZ.js → send-BTXJAyK4.js} +40 -86
  42. package/dist/{send-api-BG0MgoFI.js → send-api-BY_AkSiB.js} +41 -35
  43. package/dist/{setup-finalize-Cc-4iWGj.js → setup-finalize-C-m-vCl9.js} +4 -4
  44. package/dist/setup-plugin-api.js +1 -1
  45. package/dist/{setup-surface-DhnXEZir.js → setup-surface-BjvUbR6M.js} +3 -3
  46. package/dist/{setup-core-BbIgEl2M.js → shared-UcAwtJaq.js} +79 -57
  47. package/dist/{socket-close-jNGyZY4T.js → socket-close-DridEuzz.js} +5 -6
  48. package/dist/{targets-runtime-Bsh0HR9y.js → targets-runtime-TMk0lGuy.js} +133 -29
  49. package/dist/text-runtime-CaKLhowd.js +4 -0
  50. package/node_modules/@protobufjs/utf8/CHANGELOG.md +8 -0
  51. package/node_modules/@protobufjs/utf8/index.js +29 -18
  52. package/node_modules/@protobufjs/utf8/package.json +2 -2
  53. package/node_modules/@protobufjs/utf8/tests/index.js +5 -0
  54. package/node_modules/@thi.ng/bitstream/package.json +4 -4
  55. package/node_modules/@thi.ng/errors/package.json +4 -4
  56. package/node_modules/@types/node/README.md +1 -1
  57. package/node_modules/@types/node/fs/promises.d.ts +16 -6
  58. package/node_modules/@types/node/package.json +2 -2
  59. package/node_modules/audio-type/package.json +3 -2
  60. package/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -1
  61. package/node_modules/lru-cache/dist/commonjs/browser/index.js +11 -4
  62. package/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -1
  63. package/node_modules/lru-cache/dist/commonjs/browser/index.min.js +1 -1
  64. package/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +3 -3
  65. package/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
  66. package/node_modules/lru-cache/dist/commonjs/index.js +11 -4
  67. package/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
  68. package/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
  69. package/node_modules/lru-cache/dist/commonjs/index.min.js.map +3 -3
  70. package/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -1
  71. package/node_modules/lru-cache/dist/commonjs/node/index.js +11 -4
  72. package/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -1
  73. package/node_modules/lru-cache/dist/commonjs/node/index.min.js +1 -1
  74. package/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +3 -3
  75. package/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -1
  76. package/node_modules/lru-cache/dist/esm/browser/index.js +11 -4
  77. package/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -1
  78. package/node_modules/lru-cache/dist/esm/browser/index.min.js +1 -1
  79. package/node_modules/lru-cache/dist/esm/browser/index.min.js.map +3 -3
  80. package/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
  81. package/node_modules/lru-cache/dist/esm/index.js +11 -4
  82. package/node_modules/lru-cache/dist/esm/index.js.map +1 -1
  83. package/node_modules/lru-cache/dist/esm/index.min.js +1 -1
  84. package/node_modules/lru-cache/dist/esm/index.min.js.map +3 -3
  85. package/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -1
  86. package/node_modules/lru-cache/dist/esm/node/index.js +11 -4
  87. package/node_modules/lru-cache/dist/esm/node/index.js.map +1 -1
  88. package/node_modules/lru-cache/dist/esm/node/index.min.js +1 -1
  89. package/node_modules/lru-cache/dist/esm/node/index.min.js.map +3 -3
  90. package/node_modules/lru-cache/package.json +1 -1
  91. package/node_modules/music-metadata/lib/common/Util.d.ts +1 -1
  92. package/node_modules/music-metadata/lib/common/Util.js +7 -1
  93. package/node_modules/music-metadata/lib/id3v2/FrameHeader.js +23 -3
  94. package/node_modules/music-metadata/lib/id3v2/FrameParser.js +2 -2
  95. package/node_modules/music-metadata/lib/id3v2/ID3v2Token.js +1 -1
  96. package/node_modules/music-metadata/lib/mp4/AtomToken.js +38 -13
  97. package/node_modules/music-metadata/lib/mpeg/MpegParser.js +1 -1
  98. package/node_modules/music-metadata/package.json +6 -6
  99. package/node_modules/p-queue/dist/index.js +17 -6
  100. package/node_modules/p-queue/package.json +1 -1
  101. package/node_modules/protobufjs/dist/light/protobuf.js +145 -188
  102. package/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
  103. package/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
  104. package/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
  105. package/node_modules/protobufjs/dist/minimal/protobuf.js +33 -76
  106. package/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
  107. package/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
  108. package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
  109. package/node_modules/protobufjs/dist/protobuf.js +168 -208
  110. package/node_modules/protobufjs/dist/protobuf.js.map +1 -1
  111. package/node_modules/protobufjs/dist/protobuf.min.js +3 -3
  112. package/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
  113. package/node_modules/protobufjs/index.d.ts +0 -8
  114. package/node_modules/protobufjs/package.json +2 -3
  115. package/node_modules/protobufjs/src/parse.js +3 -0
  116. package/node_modules/protobufjs/src/util/minimal.js +0 -3
  117. package/node_modules/typebox/build/compile/validator.d.mts +1 -4
  118. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  119. package/node_modules/typebox/build/guard/guard.mjs +5 -2
  120. package/node_modules/typebox/build/guard/string.mjs +16 -4
  121. package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
  122. package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
  123. package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
  124. package/node_modules/typebox/build/typebox.d.mts +1 -1
  125. package/node_modules/typebox/build/typebox.mjs +1 -1
  126. package/node_modules/typebox/package.json +1 -1
  127. package/node_modules/typebox/readme.md +29 -32
  128. package/node_modules/ws/lib/receiver.js +15 -32
  129. package/node_modules/ws/lib/websocket-server.js +4 -4
  130. package/node_modules/ws/lib/websocket.js +4 -4
  131. package/node_modules/ws/package.json +5 -1
  132. package/openclaw.plugin.json +138 -194
  133. package/package.json +21 -12
  134. package/dist/doctor-By3SB4lm.js +0 -48
  135. package/dist/normalize-C-Z16y7Q.js +0 -2
  136. package/dist/runtime-BfAdAEYT.js +0 -8
  137. package/dist/text-runtime-BTGkMN0o.js +0 -4
  138. package/node_modules/@protobufjs/inquire/CHANGELOG.md +0 -8
  139. package/node_modules/@protobufjs/inquire/LICENSE +0 -26
  140. package/node_modules/@protobufjs/inquire/README.md +0 -13
  141. package/node_modules/@protobufjs/inquire/index.d.ts +0 -10
  142. package/node_modules/@protobufjs/inquire/index.js +0 -38
  143. package/node_modules/@protobufjs/inquire/package.json +0 -21
  144. package/node_modules/@protobufjs/inquire/tests/data/array.js +0 -1
  145. package/node_modules/@protobufjs/inquire/tests/data/emptyArray.js +0 -1
  146. package/node_modules/@protobufjs/inquire/tests/data/emptyObject.js +0 -1
  147. package/node_modules/@protobufjs/inquire/tests/data/object.js +0 -1
  148. package/node_modules/@protobufjs/inquire/tests/index.js +0 -20
  149. package/npm-shrinkwrap.json +0 -958
@@ -1,42 +1,40 @@
1
- import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-ejK8sXPU.js";
2
- import { a as normalizeWhatsAppOutboundPayload, c as prepareWhatsAppOutboundMedia, r as sendReactionWhatsApp, s as normalizeWhatsAppPayloadTextPreservingIndentation, u as resolveWhatsAppReactionLevel } from "./send-SB-4JrVZ.js";
3
- import { n as getWhatsAppRuntime } from "./runtime-BfAdAEYT.js";
4
- import { r as getWhatsAppConnectionController } from "./connection-controller-runtime-context-9mEeAvu1.js";
5
- import { a as resolveEquivalentWhatsAppDirectChatJids, c as toWhatsappJidWithLid, i as markdownToWhatsApp, n as isSelfChatMode, o as resolveJidToE164, r as jidToE164, s as toWhatsappJid } from "./targets-runtime-Bsh0HR9y.js";
6
- import { n as normalizeE164, t as convertMarkdownTables$1 } from "./text-runtime-BTGkMN0o.js";
7
- import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-Dh8WZun_.js";
8
- import { g as readWebSelfId, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision } from "./auth-store-BQi9pZqQ.js";
9
- import { a as getSelfIdentity, c as resolveComparableIdentity, i as getReplyContext, n as getMentionIdentities, o as getSenderIdentity, r as getPrimaryIdentityId, s as identitiesOverlap, t as getComparableIdentityValues } from "./identity-D7L8LCuH.js";
1
+ import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-CSqLvCY5.js";
2
+ import { a as normalizeWhatsAppOutboundPayload, c as prepareWhatsAppOutboundMedia, r as sendReactionWhatsApp, s as normalizeWhatsAppPayloadTextPreservingIndentation, u as resolveWhatsAppReactionLevel } from "./send-BTXJAyK4.js";
3
+ import { i as getWhatsAppRuntime } from "./runtime-BLlToOi6.js";
4
+ import { r as getWhatsAppConnectionController } from "./connection-controller-runtime-context-DG0tuYuw.js";
5
+ import { a as markdownToWhatsAppChunks, c as toWhatsappJid, l as toWhatsappJidWithLid, n as isSelfChatMode, o as resolveEquivalentWhatsAppDirectChatJids, r as jidToE164, s as resolveJidToE164 } from "./targets-runtime-TMk0lGuy.js";
6
+ import { t as normalizeE164 } from "./text-runtime-CaKLhowd.js";
7
+ import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-DVMZlO3x.js";
8
+ import { g as readWebSelfId, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision } from "./auth-store-DcEpIOmE.js";
9
+ import { a as getSelfIdentity, c as resolveComparableIdentity, i as getReplyContext, n as getMentionIdentities, o as getSenderIdentity, r as getPrimaryIdentityId, s as identitiesOverlap, t as getComparableIdentityValues } from "./identity-CPrT5aEU.js";
10
10
  import { n as resolveWhatsAppGroupsConfigPath } from "./group-config-path-BGyzT9Lg.js";
11
- import { a as waitForWaConnection, c as isWhatsAppSocketOperationTimeoutError, d as withWhatsAppSocketOperationTimeout, l as resolveWhatsAppSocketOperationTimeoutMs, r as createWaSocket, s as createWhatsAppSocketOperationTimeoutAdapter, u as resolveWhatsAppSocketTiming } from "./socket-close-jNGyZY4T.js";
11
+ import { a as waitForWaConnection, c as isWhatsAppSocketOperationTimeoutError, d as withWhatsAppSocketOperationTimeout, l as resolveWhatsAppSocketOperationTimeoutMs, r as createWaSocket, s as createWhatsAppSocketOperationTimeoutAdapter, u as resolveWhatsAppSocketTiming } from "./socket-close-DridEuzz.js";
12
12
  import { n as getStatusCode, t as formatError } from "./session-errors-JuazczrA.js";
13
13
  import { t as BufferJSON } from "./session.runtime-Nsqaehbd.js";
14
- import { c as requireWhatsAppInboundAdmission, i as resolveWhatsAppInboundPolicy, l as resolveWhatsAppGroupConversationId, o as buildDeprecatedFlatWhatsAppInboundAdmission, r as resolveWhatsAppCommandAuthorized, s as requireAdmittedWhatsAppInboundMessage, t as checkInboundAccessControl } from "./access-control-tjy_em01.js";
15
- import { _ as extractLocationData, a as mayContainWhatsAppOutboundMention, b as extractText, c as DisconnectReason, d as normalizeMessageContent, f as resolveInboundMediaMimetype, g as extractExternalAdReplyContext, h as extractContextInfo, i as addWhatsAppOutboundMentionsToContent, l as downloadMediaMessage, m as extractContactContext, n as listWhatsAppSendResultMessageIds, o as resolveWhatsAppOutboundMentions, p as describeReplyContext, r as normalizeWhatsAppSendResult, s as addWhatsAppImagePreviewFields, t as createWebSendApi, u as isJidGroup, v as extractMediaPlaceholder, x as hasInboundUserContent, y as extractMentionedJids } from "./send-api-BG0MgoFI.js";
16
- import { c as computeBackoff, d as resolveReconnectPolicy, f as sleepWithAbort, l as newConnectionId, n as WHATSAPP_WATCHDOG_TIMEOUT_ERROR, r as WhatsAppConnectionController, s as DEFAULT_RECONNECT_POLICY, u as resolveHeartbeatSeconds } from "./connection-controller-HGaffeFb.js";
17
- import { n as maybeResolveWhatsAppApprovalReaction } from "./approval-reactions-CrZFAyf-.js";
18
- import { t as maybeResolveWhatsAppQuestionReaction } from "./question-reactions-C0qJ6wGA.js";
19
- import { resolveAccountEntry } from "openclaw/plugin-sdk/account-core";
14
+ import { c as requireWhatsAppInboundAdmission, i as resolveWhatsAppInboundPolicy, l as resolveWhatsAppGroupConversationId, o as buildDeprecatedFlatWhatsAppInboundAdmission, r as resolveWhatsAppCommandAuthorized, s as requireAdmittedWhatsAppInboundMessage, t as checkInboundAccessControl } from "./access-control-pZcxTmd5.js";
15
+ import { _ as extractMediaKind, a as mayContainWhatsAppOutboundMention, b as hasInboundUserContent, c as DisconnectReason, d as normalizeMessageContent, f as describeReplyContext, g as extractLocationData, h as extractExternalAdReplyContext, i as addWhatsAppOutboundMentionsToContent, l as downloadMediaMessage, m as extractContextInfo, n as listWhatsAppSendResultMessageIds, o as resolveWhatsAppOutboundMentions, p as extractContactContext, r as normalizeWhatsAppSendResult, s as addWhatsAppImagePreviewFields, t as createWebSendApi, u as isJidGroup, v as extractMentionedJids, x as resolveInboundMediaMimetype, y as extractText } from "./send-api-BY_AkSiB.js";
16
+ import { c as computeBackoff, d as resolveReconnectPolicy, f as sleepWithAbort, l as newConnectionId, n as WHATSAPP_WATCHDOG_TIMEOUT_ERROR, r as WhatsAppConnectionController, s as DEFAULT_RECONNECT_POLICY, u as resolveHeartbeatSeconds } from "./connection-controller-BOB2MVKY.js";
17
+ import { n as maybeResolveWhatsAppApprovalReaction } from "./approval-reactions-Cu66aYdq.js";
18
+ import { t as maybeResolveWhatsAppQuestionReaction } from "./question-reactions-CRsnyoMK.js";
20
19
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
21
20
  import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
22
- import { DEFAULT_TIMING, createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
21
+ import { createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
23
22
  import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
24
23
  import { getChildLogger } from "openclaw/plugin-sdk/logging-core";
25
24
  import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
26
25
  import { createSubsystemLogger, defaultRuntime, formatDurationPrecise, getChildLogger as getChildLogger$1, logVerbose, logVerbose as logVerbose$1, registerUnhandledRejectionHandler, shouldLogVerbose, shouldLogVerbose as shouldLogVerbose$1, warn } from "openclaw/plugin-sdk/runtime-env";
27
26
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
28
- import { DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions, createChannelIngressDrain, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, deliverInboundReplyWithMessageSendContext, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
27
+ import { bindIngressLifecycleToReplyOptions, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
29
28
  import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
30
29
  import { isReasoningReplyPayload, resolveSendableOutboundReplyParts, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
31
30
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
32
- import { LocalMediaAccessError, getDefaultLocalRoots, loadWebMedia as loadWebMedia$1, loadWebMediaRaw, optimizeImageToJpeg, optimizeImageToPng } from "openclaw/plugin-sdk/web-media";
33
31
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
34
- import { chunkMarkdownTextWithMode } from "openclaw/plugin-sdk/reply-chunking";
35
- import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, DEFAULT_MAIN_KEY, buildAgentMainSessionKey, buildAgentSessionKey, buildGroupHistoryKey, deriveLastRoutePolicy, normalizeAccountId as normalizeAccountId$1, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
32
+ import { buildChannelInboundEventContext, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, formatMediaPlaceholderText, hasVisibleInboundReplyDispatch, isChannelPartialDeliveryError, resolveInboundSessionEnvelopeContext, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
33
+ import { DEFAULT_ACCOUNT_ID, DEFAULT_MAIN_KEY, buildAgentMainSessionKey, buildAgentSessionKey, buildGroupHistoryKey, deriveLastRoutePolicy, normalizeAccountId, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
36
34
  import { asDateTimestampMs, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
37
35
  import { createHash } from "node:crypto";
36
+ import { fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
38
37
  import { getSessionEntry, patchSessionEntry, resolveStorePath, resolveStorePath as resolveStorePath$1, updateLastRoute } from "openclaw/plugin-sdk/session-store-runtime";
39
- import { buildChannelInboundEventContext, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, hasVisibleInboundReplyDispatch, resolveInboundSessionEnvelopeContext, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
40
38
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
41
39
  import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
42
40
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
@@ -46,7 +44,7 @@ import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, buildInbou
46
44
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
47
45
  import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
48
46
  import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
49
- import { getRuntimeConfig as getRuntimeConfig$1, getRuntimeConfigSourceSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
47
+ import { getRuntimeConfig as getRuntimeConfig$1 } from "openclaw/plugin-sdk/runtime-config-snapshot";
50
48
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
51
49
  import { buildMentionRegexes, implicitMentionKindWhen, normalizeMentionText, resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-mention-gating";
52
50
  import { createConnectedChannelStatusPatch, createTransportActivityStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
@@ -57,6 +55,7 @@ import { hasControlCommand, isControlCommandMessage, shouldComputeCommandAuthori
57
55
  import { createInternalHookEvent, deriveInboundMessageHookContext, fireAndForgetBoundedHook, toInternalMessageReceivedContext, toPluginMessageContext, toPluginMessageReceivedEvent, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
58
56
  import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
59
57
  import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
58
+ import { LocalMediaAccessError, getDefaultLocalRoots, loadWebMedia, loadWebMediaRaw, optimizeImageToJpeg, optimizeImageToPng } from "openclaw/plugin-sdk/web-media";
60
59
  import { createChannelApiRetryRunner } from "openclaw/plugin-sdk/retry-runtime";
61
60
  //#region extensions/whatsapp/src/inbound/message-aliases.ts
62
61
  function normalizeQuoteSender(sender) {
@@ -128,6 +127,18 @@ function defineDeprecatedAliasAccessors(msg, descriptors) {
128
127
  defineDeprecatedAccessors(msg, descriptors);
129
128
  return msg;
130
129
  }
130
+ function defineDeprecatedStructuredContextPayloadAlias(msg) {
131
+ const channelStructuredContext = msg.payload.channelStructuredContext ?? msg.payload.untrustedStructuredContext;
132
+ if (channelStructuredContext !== void 0) msg.payload.channelStructuredContext = channelStructuredContext;
133
+ Object.defineProperty(msg.payload, "untrustedStructuredContext", {
134
+ configurable: true,
135
+ enumerable: true,
136
+ get: () => msg.payload.channelStructuredContext,
137
+ set: (value) => {
138
+ msg.payload.channelStructuredContext = value;
139
+ }
140
+ });
141
+ }
131
142
  function defineDeprecatedAdmissionTopLevelAccessors(msg) {
132
143
  let fallbackConversationId = msg.conversationId || msg.from;
133
144
  let fallbackAccountId = msg.accountId;
@@ -179,6 +190,7 @@ function defineDeprecatedAdmissionTopLevelAccessors(msg) {
179
190
  });
180
191
  }
181
192
  function withDeprecatedWebInboundMessageFlatAliases(msg) {
193
+ defineDeprecatedStructuredContextPayloadAlias(msg);
182
194
  return defineDeprecatedAliasAccessors(defineDeprecatedAdmissionTopLevelAccessors(msg), {
183
195
  id: {
184
196
  get: () => msg.event.id,
@@ -324,9 +336,13 @@ function withDeprecatedWebInboundMessageFlatAliases(msg) {
324
336
  get: () => msg.payload.media?.url,
325
337
  set: (value) => setMediaField(msg, "url", value)
326
338
  },
339
+ channelStructuredContext: {
340
+ get: () => msg.payload.channelStructuredContext,
341
+ set: (value) => msg.payload.channelStructuredContext = value
342
+ },
327
343
  untrustedStructuredContext: {
328
- get: () => msg.payload.untrustedStructuredContext,
329
- set: (value) => msg.payload.untrustedStructuredContext = value
344
+ get: () => msg.payload.channelStructuredContext,
345
+ set: (value) => msg.payload.channelStructuredContext = value
330
346
  },
331
347
  isBatched: {
332
348
  get: () => msg.event.isBatched,
@@ -353,7 +369,7 @@ function normalizeLegacyFlatWebInboundMessage(msg) {
353
369
  body: msg.body,
354
370
  media,
355
371
  location: msg.location,
356
- untrustedStructuredContext: msg.untrustedStructuredContext
372
+ channelStructuredContext: msg.channelStructuredContext ?? msg.untrustedStructuredContext
357
373
  },
358
374
  platform: {
359
375
  chatJid: msg.chatId,
@@ -441,6 +457,8 @@ const WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES = 450;
441
457
  const WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES = 5e3;
442
458
  const WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS = 720 * 60 * 60 * 1e3;
443
459
  const WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS = 10080 * 60 * 1e3;
460
+ const WHATSAPP_DURABLE_INBOUND_PRUNE_INTERVAL_MS = 3600 * 1e3;
461
+ const WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION = 1;
444
462
  function hashNamespacePart(value) {
445
463
  return createHash("sha256").update(value).digest("hex").slice(0, 24);
446
464
  }
@@ -466,59 +484,62 @@ function createWhatsAppDurableInboundQueue(accountId) {
466
484
  stateDir: getWhatsAppRuntime().state.resolveStateDir()
467
485
  });
468
486
  }
469
- /** Raw receive chokepoint: append first, then let the drain normalize and dispatch. */
470
- async function enqueueWhatsAppDurableInbound(params) {
471
- const facts = inspectWhatsAppIngressMessage(params.message);
472
- const receivedAt = params.receivedAt ?? Date.now();
473
- await params.queue.prune({
474
- pendingTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
475
- completedTtlMs: WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS,
476
- failedTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
477
- pendingMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES,
478
- completedMaxEntries: WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES,
479
- failedMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES
480
- });
481
- return await params.queue.enqueue(facts.eventId, {
482
- message: serializeWhatsAppDurableInboundMessage(params.message),
483
- upsertType: params.upsertType,
484
- ...params.skipStaleAppend === void 0 ? {} : { skipStaleAppend: params.skipStaleAppend },
485
- ...params.skipRecentOutboundEcho === void 0 ? {} : { skipRecentOutboundEcho: params.skipRecentOutboundEcho },
486
- receivedAt,
487
- ...params.receiveOrder === void 0 ? {} : { receiveOrder: params.receiveOrder }
488
- }, {
489
- receivedAt,
490
- laneKey: facts.laneKey
491
- });
492
- }
493
487
  function resolveWhatsAppIngressNonRetryableFailure(error) {
494
488
  return error instanceof WhatsAppIngressPermanentError ? {
495
489
  reason: error.reason,
496
490
  message: error.message
497
491
  } : null;
498
492
  }
499
- /** Core drain with per-conversation lanes and completion at reply-lane adoption. */
500
- function createWhatsAppIngressDrain(params) {
501
- return createChannelIngressDrain({
493
+ /** Shared monitor with per-conversation lanes and completion at reply-lane adoption. */
494
+ function createWhatsAppIngressMonitor(params) {
495
+ return createChannelIngressMonitor({
502
496
  queue: params.queue,
503
- retryPolicy: {
504
- maxAttempts: DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
505
- deadLetterMinAgeMs: DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS
497
+ inspect: (admission) => inspectWhatsAppIngressMessage(admission.message),
498
+ payload: {
499
+ version: WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION,
500
+ serialize: (admission, { receivedAt }) => ({
501
+ ...admission,
502
+ message: serializeWhatsAppDurableInboundMessage(admission.message),
503
+ receivedAt
504
+ }),
505
+ deserialize: (payload) => ({
506
+ ...payload,
507
+ message: deserializeWhatsAppDurableInboundMessage(payload.message)
508
+ }),
509
+ encode: ({ body }) => body,
510
+ decode: (payload) => ({
511
+ version: WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION,
512
+ body: payload
513
+ }),
514
+ createClaimError: (kind) => new WhatsAppIngressPermanentError(kind === "invalid-version" ? "invalid-payload" : "event-id-mismatch", kind === "invalid-version" ? "WhatsApp ingress row has an invalid payload version" : "WhatsApp ingress row identity does not match its transport message key")
506
515
  },
507
- resolveNonRetryableFailure: resolveWhatsAppIngressNonRetryableFailure,
508
- deriveLaneKey: (record) => {
509
- try {
510
- return inspectWhatsAppIngressMessage(deserializeWhatsAppDurableInboundMessage(record.payload.message)).laneKey;
511
- } catch {
512
- return record.id;
513
- }
516
+ deliver: (admission, lifecycle) => params.dispatch(admission, lifecycle),
517
+ pollIntervalMs: params.pollIntervalMs,
518
+ retention: {
519
+ pruneIntervalMs: WHATSAPP_DURABLE_INBOUND_PRUNE_INTERVAL_MS,
520
+ pendingTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
521
+ pendingMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES,
522
+ completedTtlMs: WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS,
523
+ completedMaxEntries: WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES,
524
+ failedTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
525
+ failedMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES
526
+ },
527
+ drain: {
528
+ resolveNonRetryableFailure: resolveWhatsAppIngressNonRetryableFailure,
529
+ deriveLaneKey: (record) => {
530
+ try {
531
+ return inspectWhatsAppIngressMessage(deserializeWhatsAppDurableInboundMessage(record.payload.message)).laneKey;
532
+ } catch {
533
+ return record.id;
534
+ }
535
+ },
536
+ ...params.onLog ? { onLog: params.onLog } : {}
514
537
  },
515
- ...params.onLog ? { onLog: params.onLog } : {},
516
538
  ...params.abortSignal ? { abortSignal: params.abortSignal } : {},
517
- dispatchClaimedEvent: async (record, lifecycle) => {
518
- const message = deserializeWhatsAppDurableInboundMessage(record.payload.message);
519
- if (inspectWhatsAppIngressMessage(message).eventId !== record.id) throw new WhatsAppIngressPermanentError("event-id-mismatch", "WhatsApp ingress row id does not match its transport message key");
520
- return await params.dispatch(message, record.payload, lifecycle);
521
- }
539
+ admissionMode: "while-running",
540
+ createStoppedError: () => /* @__PURE__ */ new Error("WhatsApp ingress monitor is stopped."),
541
+ ...params.onError ? { onError: params.onError } : {},
542
+ ...params.onActivityChange ? { onActivityChange: params.onActivityChange } : {}
522
543
  });
523
544
  }
524
545
  //#endregion
@@ -738,6 +759,7 @@ async function attachWebInboxToSocket(options) {
738
759
  const pendingDebounceKeys = /* @__PURE__ */ new Set();
739
760
  const activeInboundFlushes = /* @__PURE__ */ new Set();
740
761
  const pendingMessageHandlers = /* @__PURE__ */ new Set();
762
+ let durableIngressActive = false;
741
763
  const debounceWorkWaiters = /* @__PURE__ */ new Set();
742
764
  const notifyDebounceWork = () => {
743
765
  if (debounceWorkWaiters.size === 0) return;
@@ -759,7 +781,7 @@ async function attachWebInboxToSocket(options) {
759
781
  };
760
782
  let nextReceiveOrder = 0;
761
783
  const publishPendingWorkState = (at = Date.now()) => {
762
- options.onPendingWorkChanged?.(pendingMessageHandlers.size + pendingDebounceKeys.size + activeInboundFlushes.size, at);
784
+ options.onPendingWorkChanged?.(pendingMessageHandlers.size + pendingDebounceKeys.size + activeInboundFlushes.size + (durableIngressActive ? 1 : 0), at);
763
785
  };
764
786
  const buildInboundDebounceKey = (msg) => {
765
787
  const admission = requireWhatsAppInboundAdmission(msg);
@@ -774,48 +796,6 @@ async function attachWebInboxToSocket(options) {
774
796
  if (timestampDiff !== 0) return timestampDiff;
775
797
  return (a.receiveOrder ?? 0) - (b.receiveOrder ?? 0);
776
798
  });
777
- const buildFlushIngressLifecycle = (entries) => {
778
- const lifecycles = entries.map((entry) => entry.turnAdoptionLifecycle).filter((lifecycle) => lifecycle !== void 0);
779
- const [firstLifecycle] = lifecycles;
780
- if (!firstLifecycle) return {
781
- lifecycle: void 0,
782
- settle: async () => {},
783
- abandon: async () => {}
784
- };
785
- let handedOff = false;
786
- const adoptAll = async () => {
787
- for (const lifecycle of lifecycles) await lifecycle.onAdopted();
788
- };
789
- return {
790
- lifecycle: {
791
- abortSignal: lifecycles.length === 1 ? firstLifecycle.abortSignal : AbortSignal.any(lifecycles.map((lifecycle) => lifecycle.abortSignal)),
792
- onAdopted: async () => {
793
- handedOff = true;
794
- await adoptAll();
795
- },
796
- onDeferred: () => {
797
- handedOff = true;
798
- for (const lifecycle of lifecycles) lifecycle.onDeferred();
799
- },
800
- onAdoptionFinalizing: () => {
801
- for (const lifecycle of lifecycles) lifecycle.onAdoptionFinalizing();
802
- },
803
- onAbandoned: async () => {
804
- handedOff = true;
805
- await Promise.all(lifecycles.map((lifecycle) => Promise.resolve(lifecycle.onAbandoned())));
806
- }
807
- },
808
- settle: async () => {
809
- if (!handedOff) await adoptAll();
810
- },
811
- abandon: async () => {
812
- if (!handedOff) {
813
- handedOff = true;
814
- await Promise.all(lifecycles.map((lifecycle) => Promise.resolve(lifecycle.onAbandoned())));
815
- }
816
- }
817
- };
818
- };
819
799
  const debouncer = createInboundDebouncer({
820
800
  debounceMs: inboundDebounceMs,
821
801
  buildKey: (msg) => msg.debounceKey ?? buildInboundDebounceKey(msg),
@@ -832,7 +812,7 @@ async function attachWebInboxToSocket(options) {
832
812
  const orderedEntries = orderDebouncedInboundEntries(entries);
833
813
  const last = orderedEntries.at(-1);
834
814
  if (!last) return;
835
- const { lifecycle, settle, abandon } = buildFlushIngressLifecycle(orderedEntries);
815
+ const { lifecycle, settle, abandon } = fanInChannelIngressLifecycles(orderedEntries.map((entry) => entry.turnAdoptionLifecycle));
836
816
  try {
837
817
  if (orderedEntries.length === 1) {
838
818
  await options.onMessage(attachWhatsAppIngressLifecycle(last, lifecycle));
@@ -1192,17 +1172,19 @@ async function attachWebInboxToSocket(options) {
1192
1172
  const locationText = location ? formatLocationText(location) : void 0;
1193
1173
  const contactContext = extractContactContext(msg.message ?? void 0);
1194
1174
  const externalAdReplyContext = extractExternalAdReplyContext(msg.message ?? void 0);
1195
- const mediaPlaceholder = extractMediaPlaceholder(msg.message ?? void 0);
1175
+ let mediaKind = extractMediaKind(msg.message ?? void 0);
1196
1176
  let body = extractText(msg.message ?? void 0);
1197
1177
  if (locationText) body = [body, locationText].filter(Boolean).join("\n").trim();
1198
- if (!body) {
1199
- body = mediaPlaceholder;
1200
- if (!body) return null;
1201
- }
1178
+ if (!body && !mediaKind) return null;
1179
+ body = body ?? "";
1202
1180
  const commandBody = body;
1203
1181
  const replyContext = describeReplyContext(msg.message);
1204
1182
  let mediaPath;
1205
- let mediaType;
1183
+ let mediaType = mediaKind ? resolveInboundMediaMimetype(msg.message) : void 0;
1184
+ const nativeMedia = mediaKind ? {
1185
+ contentType: mediaType,
1186
+ kind: mediaKind
1187
+ } : void 0;
1206
1188
  let mediaFileName;
1207
1189
  const maxBytes = (typeof options.mediaMaxMb === "number" && options.mediaMaxMb > 0 ? options.mediaMaxMb : 50) * 1024 * 1024;
1208
1190
  const saveInboundMedia = async (inboundMedia) => {
@@ -1217,12 +1199,13 @@ async function attachWebInboxToSocket(options) {
1217
1199
  logWhatsAppVerbose(options.verbose, `Inbound media download failed: ${String(err)}`);
1218
1200
  body = formatInboundMediaUnavailableText({
1219
1201
  body,
1220
- mediaPlaceholder,
1221
1202
  notice: "[whatsapp attachment unavailable]"
1222
1203
  });
1223
1204
  }
1224
- if (!mediaPath && replyContext) try {
1205
+ if (!mediaPath && !mediaKind && replyContext?.media) try {
1225
1206
  await saveInboundMedia(await downloadQuotedInboundMedia(msg, sock, maxBytes));
1207
+ mediaKind = replyContext.media.kind ?? void 0;
1208
+ mediaType = mediaType ?? replyContext.media.contentType ?? void 0;
1226
1209
  } catch (err) {
1227
1210
  logWhatsAppVerbose(options.verbose, `Quoted media download failed: ${String(err)}`);
1228
1211
  body = formatInboundMediaUnavailableText({
@@ -1239,7 +1222,9 @@ async function attachWebInboxToSocket(options) {
1239
1222
  replyContext,
1240
1223
  mediaPath,
1241
1224
  mediaType,
1242
- mediaFileName
1225
+ mediaFileName,
1226
+ mediaKind,
1227
+ nativeMedia
1243
1228
  };
1244
1229
  };
1245
1230
  const enqueueInboundMessage = async (msg, inbound, enriched, durable) => {
@@ -1274,10 +1259,11 @@ async function attachWebInboxToSocket(options) {
1274
1259
  mediaFileName: enriched.mediaFileName,
1275
1260
  timestamp
1276
1261
  }, "inbound message");
1277
- const media = enriched.mediaPath || enriched.mediaType || enriched.mediaFileName ? {
1262
+ const media = enriched.mediaPath || enriched.mediaType || enriched.mediaFileName || enriched.mediaKind ? {
1278
1263
  path: enriched.mediaPath,
1279
1264
  type: enriched.mediaType,
1280
- fileName: enriched.mediaFileName
1265
+ fileName: enriched.mediaFileName,
1266
+ kind: enriched.mediaKind
1281
1267
  } : void 0;
1282
1268
  const groupMentions = mentionedJids ? { jids: mentionedJids } : void 0;
1283
1269
  const group = inbound.group && (inbound.groupSubject || inbound.groupParticipants?.length || groupMentions) ? {
@@ -1285,17 +1271,26 @@ async function attachWebInboxToSocket(options) {
1285
1271
  participants: inbound.groupParticipants,
1286
1272
  mentions: groupMentions
1287
1273
  } : void 0;
1288
- const untrustedStructuredContext = [...enriched.contactContext ? [{
1289
- label: "WhatsApp contact",
1290
- source: "whatsapp",
1291
- type: enriched.contactContext.kind,
1292
- payload: enriched.contactContext
1293
- }] : [], ...enriched.externalAdReplyContext ? [{
1294
- label: "WhatsApp external ad reply",
1295
- source: "whatsapp",
1296
- type: "external_ad_reply",
1297
- payload: enriched.externalAdReplyContext
1298
- }] : []];
1274
+ const channelStructuredContext = [
1275
+ ...enriched.nativeMedia ? [{
1276
+ label: "WhatsApp media",
1277
+ source: "whatsapp",
1278
+ type: "media",
1279
+ payload: enriched.nativeMedia
1280
+ }] : [],
1281
+ ...enriched.contactContext ? [{
1282
+ label: "WhatsApp contact",
1283
+ source: "whatsapp",
1284
+ type: enriched.contactContext.kind,
1285
+ payload: enriched.contactContext
1286
+ }] : [],
1287
+ ...enriched.externalAdReplyContext ? [{
1288
+ label: "WhatsApp external ad reply",
1289
+ source: "whatsapp",
1290
+ type: "external_ad_reply",
1291
+ payload: enriched.externalAdReplyContext
1292
+ }] : []
1293
+ ];
1299
1294
  const inboundMessage = withDeprecatedWebInboundMessageFlatAliases({
1300
1295
  admission: inbound.access.admission,
1301
1296
  event: {
@@ -1306,7 +1301,7 @@ async function attachWebInboxToSocket(options) {
1306
1301
  body: enriched.body,
1307
1302
  commandBody: enriched.commandBody,
1308
1303
  location: enriched.location ?? void 0,
1309
- untrustedStructuredContext: untrustedStructuredContext.length > 0 ? untrustedStructuredContext : void 0,
1304
+ channelStructuredContext: channelStructuredContext.length > 0 ? channelStructuredContext : void 0,
1310
1305
  media
1311
1306
  },
1312
1307
  platform: {
@@ -1334,6 +1329,7 @@ async function attachWebInboxToSocket(options) {
1334
1329
  context: enriched.replyContext,
1335
1330
  id: enriched.replyContext.id,
1336
1331
  body: enriched.replyContext.body,
1332
+ media: enriched.replyContext.media,
1337
1333
  sender: {
1338
1334
  displayName: enriched.replyContext.sender?.label ?? void 0,
1339
1335
  jid: enriched.replyContext.sender?.jid ?? void 0,
@@ -1360,16 +1356,21 @@ async function attachWebInboxToSocket(options) {
1360
1356
  participant: inboundMessage.platform.senderJid,
1361
1357
  participantE164: admission.conversation.kind === "direct" ? inboundMessage.platform.senderE164 : void 0,
1362
1358
  body: inboundMessage.payload.body,
1359
+ media: enriched.nativeMedia,
1363
1360
  fromMe: inboundMessage.platform.fromMe
1364
1361
  });
1365
1362
  }
1366
1363
  await debouncer.enqueue(inboundMessage);
1367
1364
  };
1368
- const processWhatsAppInboundMessage = async (msg, context) => {
1365
+ const processDurableInboundMessage = async (admission, lifecycle) => {
1366
+ const { message: msg, ...context } = admission;
1369
1367
  rememberBaileysMessage(msg.key?.remoteJid, msg.key?.id, msg.message);
1368
+ const remoteJid = msg.key?.remoteJid;
1369
+ const id = msg.key?.id;
1370
+ const durableId = remoteJid && id ? createHash("sha256").update(`${remoteJid}\n${id}`).digest("hex") : void 0;
1371
+ const preparation = durableId ? preparedInboundByDurableId.get(durableId) : void 0;
1372
+ if (durableId) preparedInboundByDurableId.delete(durableId);
1370
1373
  if (context.skipRecentOutboundEcho === true) return "completed";
1371
- const preparation = context.eventId ? preparedInboundByDurableId.get(context.eventId) : void 0;
1372
- if (context.eventId) preparedInboundByDurableId.delete(context.eventId);
1373
1374
  const prepared = await preparation;
1374
1375
  if (prepared === null) return "completed";
1375
1376
  const inbound = prepared ?? await normalizeInboundMessage(msg);
@@ -1397,85 +1398,21 @@ async function attachWebInboxToSocket(options) {
1397
1398
  await enqueueInboundMessage(msg, inbound, enriched, {
1398
1399
  readReceipt: deliveryReadReceipt,
1399
1400
  receiveOrder: context.receiveOrder ?? context.receivedAt,
1400
- turnAdoptionLifecycle: context.lifecycle
1401
+ turnAdoptionLifecycle: lifecycle
1401
1402
  });
1402
1403
  return "deferred";
1403
1404
  };
1404
- let durableDrainPass;
1405
- let durableDrainRequested = false;
1406
- let durableDrainClosed = false;
1407
- const requestDurableInboundDrain = () => {
1408
- if (durableDrainClosed) return Promise.resolve();
1409
- durableDrainRequested = true;
1410
- if (!durableDrainPass) {
1411
- let passFailed = false;
1412
- const task = (async () => {
1413
- await durableInboundDrain.recoverStaleClaims();
1414
- while (durableDrainRequested) {
1415
- durableDrainRequested = false;
1416
- if ((await durableInboundDrain.drainOnce()).started > 0) {
1417
- await durableInboundDrain.waitForIdle();
1418
- durableDrainRequested = true;
1419
- }
1420
- }
1421
- })().catch((error) => {
1422
- passFailed = true;
1423
- throw error;
1424
- });
1425
- durableDrainPass = task;
1426
- pendingMessageHandlers.add(task);
1405
+ const durableInboundMonitor = createWhatsAppIngressMonitor({
1406
+ queue: durableInboundQueue,
1407
+ dispatch: async (admission, lifecycle) => ({ kind: await processDurableInboundMessage(admission, lifecycle) }),
1408
+ pollIntervalMs: WHATSAPP_INGRESS_DRAIN_INTERVAL_MS,
1409
+ onLog: (message) => inboundLogger.warn({ message }, "whatsapp ingress drain"),
1410
+ onError: (error) => inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed"),
1411
+ onActivityChange: (active) => {
1412
+ durableIngressActive = active;
1427
1413
  publishPendingWorkState();
1428
- task.finally(() => {
1429
- if (durableDrainPass === task) durableDrainPass = void 0;
1430
- pendingMessageHandlers.delete(task);
1431
- publishPendingWorkState();
1432
- if (durableDrainRequested && !passFailed) requestDurableInboundDrain().catch((error) => {
1433
- inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
1434
- });
1435
- }).catch(() => void 0);
1436
- }
1437
- return durableDrainPass;
1438
- };
1439
- const requestDrainAfterLaneSettlement = () => {
1440
- requestDurableInboundDrain().catch((error) => {
1441
- inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
1442
- });
1443
- };
1444
- const continueDrainAfterLaneSettlement = (lifecycle) => ({
1445
- abortSignal: lifecycle.abortSignal,
1446
- onAdopted: async () => {
1447
- await lifecycle.onAdopted();
1448
- requestDrainAfterLaneSettlement();
1449
- },
1450
- onDeferred: lifecycle.onDeferred,
1451
- onAdoptionFinalizing: lifecycle.onAdoptionFinalizing,
1452
- onAbandoned: async () => {
1453
- await lifecycle.onAbandoned();
1454
- requestDrainAfterLaneSettlement();
1455
1414
  }
1456
1415
  });
1457
- const durableInboundDrain = createWhatsAppIngressDrain({
1458
- queue: durableInboundQueue,
1459
- dispatch: async (msg, payload, lifecycle) => {
1460
- const remoteJid = msg.key?.remoteJid;
1461
- const id = msg.key?.id;
1462
- return { kind: await processWhatsAppInboundMessage(msg, {
1463
- ...payload,
1464
- ...remoteJid && id ? { eventId: createWhatsAppDurableInboundMessageId({
1465
- remoteJid,
1466
- id
1467
- }) } : {},
1468
- lifecycle: continueDrainAfterLaneSettlement(lifecycle)
1469
- }) };
1470
- },
1471
- onLog: (message) => inboundLogger.warn({ message }, "whatsapp ingress drain")
1472
- });
1473
- const durableDrainTimer = setInterval(() => {
1474
- requestDurableInboundDrain().catch((error) => {
1475
- inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
1476
- });
1477
- }, WHATSAPP_INGRESS_DRAIN_INTERVAL_MS);
1478
- durableDrainTimer.unref?.();
1479
1416
  const handleMessagesUpsert = async (upsert) => {
1480
1417
  if (upsert.type !== "notify" && upsert.type !== "append") return;
1481
1418
  for (const msg of upsert.messages ?? []) {
@@ -1496,10 +1433,7 @@ async function attachWebInboxToSocket(options) {
1496
1433
  const skipRecentOutboundEcho = shouldSkipRecentOutboundEcho(msg);
1497
1434
  const remoteJid = msg.key?.remoteJid;
1498
1435
  const id = msg.key?.id;
1499
- const durableId = remoteJid && id ? createWhatsAppDurableInboundMessageId({
1500
- remoteJid,
1501
- id
1502
- }) : void 0;
1436
+ const durableId = remoteJid && id ? createHash("sha256").update(`${remoteJid}\n${id}`).digest("hex") : void 0;
1503
1437
  let resolvePrepared;
1504
1438
  if (durableId && !preparedInboundByDurableId.has(durableId)) {
1505
1439
  if (preparedInboundByDurableId.size >= 1e3) {
@@ -1515,54 +1449,34 @@ async function attachWebInboxToSocket(options) {
1515
1449
  if (!keepForDrain && durableId && resolvePrepared) preparedInboundByDurableId.delete(durableId);
1516
1450
  };
1517
1451
  let result;
1518
- let appendError;
1519
- for (const delayMs of [
1520
- 0,
1521
- 100,
1522
- 300
1523
- ]) {
1524
- if (delayMs > 0) await new Promise((resolve) => {
1525
- setTimeout(resolve, delayMs);
1526
- });
1527
- try {
1528
- result = await enqueueWhatsAppDurableInbound({
1529
- queue: durableInboundQueue,
1530
- message: msg,
1531
- upsertType: upsert.type,
1532
- skipStaleAppend,
1533
- skipRecentOutboundEcho,
1534
- receivedAt,
1535
- receiveOrder
1536
- });
1537
- appendError = void 0;
1538
- break;
1539
- } catch (error) {
1540
- appendError = error;
1541
- }
1542
- }
1543
- if (result === void 0) {
1452
+ try {
1453
+ result = await durableInboundMonitor.admit({
1454
+ message: msg,
1455
+ upsertType: upsert.type,
1456
+ skipStaleAppend,
1457
+ skipRecentOutboundEcho,
1458
+ receivedAt,
1459
+ receiveOrder
1460
+ }, { receivedAt });
1461
+ } catch (error) {
1544
1462
  finishPreparation(void 0);
1545
- const formattedError = formatError(appendError);
1463
+ const formattedError = formatError(error);
1546
1464
  inboundLogger.error({ error: formattedError }, "failed persisting durable WhatsApp inbound after retries; message dropped");
1547
1465
  inboundConsoleLog.error(`Failed persisting durable WhatsApp inbound after retries; message dropped: ${formattedError}`);
1548
1466
  continue;
1549
1467
  }
1550
- if (result.kind === "completed") {
1468
+ if (result.kind === "durable" && result.queueResult.kind === "completed") {
1551
1469
  finishPreparation(void 0);
1552
1470
  const inbound = await normalizeInboundMessage(msg);
1553
1471
  if (inbound) await maybeMarkNonSelfChatReadReceipt(inbound, buildReadReceiptTarget(inbound));
1554
- } else {
1555
- if (result.kind === "accepted") try {
1556
- finishPreparation(skipRecentOutboundEcho ? null : await normalizeInboundMessage(msg), true);
1557
- } catch (error) {
1558
- finishPreparation(void 0);
1559
- throw error;
1560
- }
1561
- else finishPreparation(void 0);
1562
- requestDurableInboundDrain().catch((error) => {
1563
- inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
1564
- });
1472
+ } else if (result.kind === "durable" && result.queueResult.kind === "accepted") if (skipRecentOutboundEcho) finishPreparation(null);
1473
+ else try {
1474
+ finishPreparation(await normalizeInboundMessage(msg), true);
1475
+ } catch (error) {
1476
+ finishPreparation(void 0);
1477
+ inboundLogger.warn({ error: formatError(error) }, "failed preparing WhatsApp inbound identity; durable drain will normalize again");
1565
1478
  }
1479
+ else finishPreparation(void 0);
1566
1480
  }
1567
1481
  };
1568
1482
  const handleMessagesUpsertEvent = (upsert) => {
@@ -1588,7 +1502,6 @@ async function attachWebInboxToSocket(options) {
1588
1502
  };
1589
1503
  const drainInboundBeforeSocketClose = async () => {
1590
1504
  groupMetadataCacheClosed = true;
1591
- clearInterval(durableDrainTimer);
1592
1505
  for (;;) {
1593
1506
  await drainDebouncedInboundMessages();
1594
1507
  if (pendingMessageHandlers.size === 0) break;
@@ -1597,12 +1510,12 @@ async function attachWebInboxToSocket(options) {
1597
1510
  if (pendingMessageHandlers.size === 0 && pendingDebounceKeys.size === 0 && activeInboundFlushes.size === 0) break;
1598
1511
  }
1599
1512
  await drainDebouncedInboundMessages();
1600
- try {
1601
- await durableInboundDrain.waitForIdle();
1602
- } finally {
1603
- durableDrainClosed = true;
1604
- durableInboundDrain.dispose();
1513
+ for (;;) {
1514
+ await durableInboundMonitor.waitForIdle();
1515
+ if (pendingDebounceKeys.size === 0 && activeInboundFlushes.size === 0) break;
1516
+ await drainDebouncedInboundMessages();
1605
1517
  }
1518
+ await durableInboundMonitor.stop();
1606
1519
  };
1607
1520
  const drainInboundBeforeSocketCloseWithTimeout = async () => {
1608
1521
  let timeout = null;
@@ -1615,8 +1528,7 @@ async function attachWebInboxToSocket(options) {
1615
1528
  })]);
1616
1529
  } finally {
1617
1530
  if (timeout) clearTimeout(timeout);
1618
- durableDrainClosed = true;
1619
- durableInboundDrain.dispose();
1531
+ durableInboundMonitor.stop();
1620
1532
  }
1621
1533
  };
1622
1534
  const handleConnectionUpdate = (update) => {
@@ -1675,10 +1587,7 @@ async function attachWebInboxToSocket(options) {
1675
1587
  const detachGroupParticipantsUpdate = attachSockListener("group-participants.update", ((update) => {
1676
1588
  forgetFullGroupMetadata(update.id);
1677
1589
  }));
1678
- requestDurableInboundDrain().catch((err) => {
1679
- inboundLogger.error({ error: String(err) }, "failed replaying durable WhatsApp inbound");
1680
- inboundConsoleLog.error(`Failed replaying durable WhatsApp inbound: ${String(err)}`);
1681
- });
1590
+ durableInboundMonitor.start();
1682
1591
  (async () => {
1683
1592
  try {
1684
1593
  const groups = await sock.groupFetchAllParticipating();
@@ -1736,7 +1645,7 @@ async function attachWebInboxToSocket(options) {
1736
1645
  };
1737
1646
  }
1738
1647
  async function monitorWebInbox(options) {
1739
- const socketTiming = options.socketTiming ?? resolveWhatsAppSocketTiming(options.cfg);
1648
+ const socketTiming = options.socketTiming ?? resolveWhatsAppSocketTiming();
1740
1649
  const recentMessageKeys = options.recentMessageKeys ?? /* @__PURE__ */ new Map();
1741
1650
  const baileysGroupMetaCache = options.baileysGroupMetaCache ?? /* @__PURE__ */ new Map();
1742
1651
  const sock = await createWaSocket(false, options.verbose, {
@@ -1794,12 +1703,13 @@ function isBotMentionedFromTargets(msg, mentionCfg, targets) {
1794
1703
  const isGroupConversation = requireWhatsAppInboundAdmission(msg).conversation.kind === "group";
1795
1704
  const isSelfChat = explicitSelfChatOverride ? Boolean(mentionCfg.isSelfChat) : isSelfChatMode(targets.self.e164, mentionCfg.allowFrom) && !isGroupConversation;
1796
1705
  const hasMentions = targets.normalizedMentions.length > 0;
1797
- if (hasMentions && !isSelfChat) {
1706
+ const hasNativeMentionsOutsideSelfChat = hasMentions && !isSelfChat;
1707
+ if (hasNativeMentionsOutsideSelfChat) {
1798
1708
  for (const mention of targets.normalizedMentions) if (identitiesOverlap(targets.self, mention)) return true;
1799
- return false;
1800
1709
  } else if (hasMentions && isSelfChat) {}
1801
1710
  const bodyClean = clean(msg.payload.body);
1802
1711
  if (mentionCfg.mentionRegexes.some((re) => re.test(bodyClean))) return true;
1712
+ if (hasNativeMentionsOutsideSelfChat) return false;
1803
1713
  if (targets.self.e164) {
1804
1714
  const selfDigits = targets.self.e164.replace(/\D/g, "");
1805
1715
  if (selfDigits) {
@@ -1980,8 +1890,7 @@ function formatWhatsAppInboundListeningLog(account) {
1980
1890
  const DEFAULT_WHATSAPP_ACK_REACTION = "👀";
1981
1891
  function resolveWhatsAppAckEmoji(params) {
1982
1892
  if (!params.ackConfig) return "";
1983
- if (params.ackConfig.emoji !== void 0) return params.ackConfig.emoji.trim();
1984
- return resolveAgentIdentityEmoji(params.cfg, params.agentId) ?? DEFAULT_WHATSAPP_ACK_REACTION;
1893
+ return (typeof params.ackConfig === "string" ? params.ackConfig : params.ackConfig.emoji)?.trim() || resolveAgentIdentityEmoji(params.cfg, params.agentId) || DEFAULT_WHATSAPP_ACK_REACTION;
1985
1894
  }
1986
1895
  function resolveAgentIdentityEmoji(cfg, agentId) {
1987
1896
  return resolveAgentIdentity(cfg, agentId)?.emoji?.trim() || void 0;
@@ -1989,7 +1898,7 @@ function resolveAgentIdentityEmoji(cfg, agentId) {
1989
1898
  //#endregion
1990
1899
  //#region extensions/whatsapp/src/auto-reply/monitor/group-activation.ts
1991
1900
  function hasNamedWhatsAppAccounts(cfg) {
1992
- return Object.keys(cfg.channels?.whatsapp?.accounts ?? {}).some((accountId) => normalizeAccountId$1(accountId) !== DEFAULT_ACCOUNT_ID$1);
1901
+ return Object.keys(cfg.channels?.whatsapp?.accounts ?? {}).some((accountId) => normalizeAccountId(accountId) !== DEFAULT_ACCOUNT_ID);
1993
1902
  }
1994
1903
  function isActivationOnlyEntry(entry) {
1995
1904
  return entry?.groupActivation !== void 0 && typeof entry?.sessionId !== "string" && typeof entry?.updatedAt !== "number";
@@ -2012,7 +1921,7 @@ async function resolveGroupActivationFor(params) {
2012
1921
  ...sessionScope,
2013
1922
  sessionKey: params.sessionKey
2014
1923
  });
2015
- const activation = (normalizeAccountId$1(params.accountId) === DEFAULT_ACCOUNT_ID$1 && hasNamedWhatsAppAccounts(params.cfg) && isActivationOnlyEntry(scopedEntry) ? void 0 : scopedEntry?.groupActivation) ?? legacyEntry?.groupActivation;
1924
+ const activation = (normalizeAccountId(params.accountId) === DEFAULT_ACCOUNT_ID && hasNamedWhatsAppAccounts(params.cfg) && isActivationOnlyEntry(scopedEntry) ? void 0 : scopedEntry?.groupActivation) ?? legacyEntry?.groupActivation;
2016
1925
  if (activation !== void 0 && scopedEntry?.groupActivation === void 0) {
2017
1926
  if (scopedEntry) await patchSessionEntry({
2018
1927
  ...sessionScope,
@@ -2034,33 +1943,41 @@ async function resolveGroupActivationFor(params) {
2034
1943
  return normalizeGroupActivation(activation) ?? defaultActivation;
2035
1944
  }
2036
1945
  //#endregion
2037
- //#region extensions/whatsapp/src/auto-reply/monitor/ack-reaction.ts
2038
- async function maybeSendAckReaction(params) {
2039
- if (!params.msg.event.id) return null;
1946
+ //#region extensions/whatsapp/src/auto-reply/monitor/reaction-participant.ts
1947
+ function resolveReactionParticipant(msg) {
1948
+ const sender = getSenderIdentity(msg);
1949
+ return sender.jid ?? sender.lid ?? void 0;
1950
+ }
1951
+ //#endregion
1952
+ //#region extensions/whatsapp/src/auto-reply/monitor/reaction-eligibility.ts
1953
+ const DISABLED_REACTION = { status: "disabled" };
1954
+ async function resolveWhatsAppReactionEligibility(params) {
1955
+ const messageId = params.msg.event.id;
1956
+ if (!messageId) return DISABLED_REACTION;
2040
1957
  const admission = requireWhatsAppInboundAdmission(params.msg);
2041
1958
  const accountId = admission.accountId;
2042
1959
  if (resolveWhatsAppReactionLevel({
2043
1960
  cfg: params.cfg,
2044
1961
  accountId
2045
- }).level === "off") return null;
2046
- const ackConfig = params.cfg.channels?.whatsapp?.ackReaction;
1962
+ }).level === "off") return DISABLED_REACTION;
1963
+ const scope = params.cfg.messages?.ackReactionScope ?? "group-mentions";
1964
+ if (scope === "off" || scope === "none") return DISABLED_REACTION;
2047
1965
  const emoji = resolveWhatsAppAckEmoji({
2048
1966
  cfg: params.cfg,
2049
1967
  agentId: params.agentId,
2050
- ackConfig
1968
+ ackConfig: params.cfg.messages?.ackReaction
2051
1969
  });
2052
- const directEnabled = ackConfig?.direct ?? true;
2053
- const groupMode = ackConfig?.group ?? "mentions";
1970
+ const directEnabled = scope === "all" || scope === "direct";
1971
+ const groupMode = scope === "all" || scope === "group-all" ? "always" : scope === "direct" ? "never" : "mentions";
2054
1972
  const isGroup = admission.conversation.kind === "group";
2055
- const conversationIdForCheck = admission.conversation.id;
2056
1973
  const activation = isGroup ? await resolveGroupActivationFor({
2057
1974
  cfg: params.cfg,
2058
1975
  accountId,
2059
1976
  agentId: params.agentId,
2060
1977
  sessionKey: params.sessionKey,
2061
- conversationId: conversationIdForCheck
1978
+ conversationId: admission.conversation.id
2062
1979
  }) : null;
2063
- const shouldSendReaction = () => shouldAckReactionForWhatsApp({
1980
+ if (!shouldAckReactionForWhatsApp({
2064
1981
  emoji,
2065
1982
  isDirect: admission.conversation.kind === "direct",
2066
1983
  isGroup,
@@ -2068,32 +1985,50 @@ async function maybeSendAckReaction(params) {
2068
1985
  groupMode,
2069
1986
  wasMentioned: (params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned) === true,
2070
1987
  groupActivated: activation === "always"
1988
+ })) return DISABLED_REACTION;
1989
+ const participant = resolveReactionParticipant(params.msg);
1990
+ return {
1991
+ status: "prepared",
1992
+ chatId: params.msg.platform.chatJid,
1993
+ messageId,
1994
+ emoji,
1995
+ reactionOptions: {
1996
+ verbose: params.verbose,
1997
+ fromMe: false,
1998
+ ...participant ? { participant } : {},
1999
+ accountId,
2000
+ cfg: params.cfg
2001
+ }
2002
+ };
2003
+ }
2004
+ //#endregion
2005
+ //#region extensions/whatsapp/src/auto-reply/monitor/ack-reaction.ts
2006
+ async function maybeSendAckReaction(params) {
2007
+ const eligibility = await resolveWhatsAppReactionEligibility({
2008
+ cfg: params.cfg,
2009
+ msg: params.msg,
2010
+ agentId: params.agentId,
2011
+ sessionKey: params.sessionKey,
2012
+ verbose: params.verbose
2071
2013
  });
2072
- if (!shouldSendReaction()) return null;
2014
+ if (eligibility.status === "disabled") return null;
2015
+ const { chatId, messageId, emoji, reactionOptions } = eligibility;
2073
2016
  params.info({
2074
- chatId: params.msg.platform.chatJid,
2075
- messageId: params.msg.event.id,
2017
+ chatId,
2018
+ messageId,
2076
2019
  emoji
2077
2020
  }, "sending ack reaction");
2078
- const sender = getSenderIdentity(params.msg);
2079
- const reactionOptions = {
2080
- verbose: params.verbose,
2081
- fromMe: false,
2082
- ...sender.jid ? { participant: sender.jid } : {},
2083
- accountId,
2084
- cfg: params.cfg
2085
- };
2086
2021
  return createAckReactionHandle({
2087
2022
  ackReactionValue: emoji,
2088
- send: () => sendReactionWhatsApp(params.msg.platform.chatJid, params.msg.event.id, emoji, reactionOptions),
2089
- remove: () => sendReactionWhatsApp(params.msg.platform.chatJid, params.msg.event.id, "", reactionOptions),
2023
+ send: () => sendReactionWhatsApp(chatId, messageId, emoji, reactionOptions),
2024
+ remove: () => sendReactionWhatsApp(chatId, messageId, "", reactionOptions),
2090
2025
  onSendError: (err) => {
2091
2026
  params.warn({
2092
2027
  error: formatError(err),
2093
- chatId: params.msg.platform.chatJid,
2094
- messageId: params.msg.event.id
2028
+ chatId,
2029
+ messageId
2095
2030
  }, "failed to send ack reaction");
2096
- logVerbose(`WhatsApp ack reaction failed for chat ${params.msg.platform.chatJid}: ${formatError(err)}`);
2031
+ logVerbose(`WhatsApp ack reaction failed for chat ${chatId}: ${formatError(err)}`);
2097
2032
  }
2098
2033
  });
2099
2034
  }
@@ -2249,7 +2184,13 @@ function recordPendingGroupHistoryEntry(params) {
2249
2184
  body: params.body ?? params.msg.payload.body,
2250
2185
  timestamp: params.msg.event.timestamp,
2251
2186
  id: params.msg.event.id,
2252
- senderJid: senderIdentity.jid ?? params.msg.platform.senderJid
2187
+ senderJid: senderIdentity.jid ?? params.msg.platform.senderJid,
2188
+ ...params.body === void 0 && params.msg.payload.media ? { media: [{
2189
+ path: params.msg.payload.media.path,
2190
+ url: params.msg.payload.media.url ?? params.msg.payload.media.path,
2191
+ contentType: params.msg.payload.media.type,
2192
+ kind: params.msg.payload.media.kind ?? void 0
2193
+ }] } : {}
2253
2194
  }
2254
2195
  });
2255
2196
  }
@@ -2585,7 +2526,7 @@ async function deliverWebReply(params) {
2585
2526
  const tableMode = params.tableMode ?? "code";
2586
2527
  const chunkMode = params.chunkMode ?? "length";
2587
2528
  const normalizedReply = params.normalizedReplyResult ?? normalizeWhatsAppOutboundPayload(replyResult, { normalizeText: normalizeWhatsAppPayloadTextPreservingIndentation });
2588
- const textChunks = chunkMarkdownTextWithMode(markdownToWhatsApp(convertMarkdownTables$1(normalizedReply.text ?? "", tableMode)), textLimit, chunkMode);
2529
+ const textChunks = markdownToWhatsAppChunks(normalizedReply.text ?? "", textLimit, tableMode, chunkMode);
2589
2530
  const mediaList = normalizedReply.mediaUrls ?? [];
2590
2531
  const getQuote = () => {
2591
2532
  if (!replyResult.replyToId) return;
@@ -2595,7 +2536,8 @@ async function deliverWebReply(params) {
2595
2536
  remoteJid: msg.platform.chatJid,
2596
2537
  fromMe: cached?.fromMe ?? false,
2597
2538
  participant: cached?.participant ?? (isGroupConversation ? msg.platform.senderJid : void 0),
2598
- messageText: cached?.body ?? ""
2539
+ messageText: cached?.body ?? "",
2540
+ media: cached?.media
2599
2541
  });
2600
2542
  };
2601
2543
  const sendWithRetry = async (fn, label) => {
@@ -2643,7 +2585,7 @@ async function deliverWebReply(params) {
2643
2585
  mediaUrls: mediaList,
2644
2586
  caption: remainingText.shift() || "",
2645
2587
  send: async ({ mediaUrl, caption }) => {
2646
- const media = await prepareWhatsAppOutboundMedia(await loadWebMedia$1(mediaUrl, {
2588
+ const media = await prepareWhatsAppOutboundMedia(await loadWebMedia(mediaUrl, {
2647
2589
  maxBytes: maxMediaBytes,
2648
2590
  localRoots: params.mediaLocalRoots
2649
2591
  }), mediaUrl);
@@ -2749,8 +2691,10 @@ function resolveVisibleWhatsAppGroupHistory(params) {
2749
2691
  function resolveVisibleWhatsAppReplyContext(params) {
2750
2692
  const replyTo = getReplyContext(params.msg, params.authDir);
2751
2693
  if (!replyTo) return null;
2694
+ const admission = requireWhatsAppInboundAdmission(params.msg);
2695
+ const previewBody = [replyTo.body, formatMediaPlaceholderText(replyTo.media ? [replyTo.media] : [])].filter(Boolean).join("\n");
2752
2696
  const senderAllowed = resolveInboundSupplementalSenderAllowed({
2753
- isGroup: requireWhatsAppInboundAdmission(params.msg).conversation.kind === "group",
2697
+ isGroup: admission.conversation.kind === "group",
2754
2698
  groupPolicy: params.groupPolicy,
2755
2699
  allowFrom: params.groupAllowFrom,
2756
2700
  isSenderAllowed: (allowFrom) => isWhatsAppSupplementalSenderAllowed({
@@ -2759,12 +2703,16 @@ function resolveVisibleWhatsAppReplyContext(params) {
2759
2703
  sender: replyTo.sender
2760
2704
  })
2761
2705
  });
2762
- return filterChannelInboundQuoteContext(params.mode, {
2706
+ const visible = filterChannelInboundQuoteContext(params.mode, {
2763
2707
  id: replyTo.id,
2764
- body: replyTo.body,
2708
+ body: previewBody,
2765
2709
  sender: replyTo.sender?.label ?? void 0,
2766
2710
  senderAllowed
2767
- }) ? replyTo : null;
2711
+ });
2712
+ return visible ? {
2713
+ ...replyTo,
2714
+ body: visible.body ?? ""
2715
+ } : null;
2768
2716
  }
2769
2717
  //#endregion
2770
2718
  //#region extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts
@@ -2780,14 +2728,16 @@ function normalizeErrForLog(err) {
2780
2728
  function whatsAppReplyDeliveryVisibility(visibleReplySent) {
2781
2729
  return { visibleReplySent };
2782
2730
  }
2783
- function whatsAppReplyDeliveryVisibilityFromDurableResult(result) {
2784
- return whatsAppReplyDeliveryVisibility(result.visibleReplySent === true);
2731
+ function isWhatsAppVisibleDeliveryError(error) {
2732
+ return typeof error === "object" && error !== null && !Array.isArray(error) && error.visibleReplySent === true || isChannelPartialDeliveryError(error) && error.deliveryResult.visibleReplySent;
2785
2733
  }
2786
2734
  function readTrimmedString(value) {
2787
2735
  return typeof value === "string" ? value.trim() : "";
2788
2736
  }
2789
2737
  function markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult) {
2790
- return flushResult.delivered > 0 ? markWhatsAppVisibleDeliveryError(error) : error;
2738
+ if (flushResult.delivered === 0) return error;
2739
+ if (isWhatsAppVisibleDeliveryError(error)) return error;
2740
+ return markWhatsAppVisibleDeliveryError(new Error("deferred WhatsApp media delivery failed after an earlier visible send", { cause: error }));
2791
2741
  }
2792
2742
  function logWhatsAppReplyDeliveryError(params) {
2793
2743
  const admission = requireWhatsAppInboundAdmission(params.msg);
@@ -2839,31 +2789,58 @@ function shouldDeferWhatsAppMediaOnlyPayload(params) {
2839
2789
  }
2840
2790
  function createWhatsAppMediaOnlyReplyCoalescer(params) {
2841
2791
  const pendingMediaOnlyPayloads = [];
2842
- const flushExceptDuplicateMedia = async (mediaUrls) => {
2792
+ const flushWhere = async (shouldFlush) => {
2843
2793
  const flushResult = {
2844
2794
  delivered: 0,
2845
2795
  droppedDuplicateMedia: 0
2846
2796
  };
2847
- const pending = pendingMediaOnlyPayloads.splice(0);
2848
- for (const candidate of pending) {
2849
- if (mediaUrls && hasWhatsAppMediaUrlOverlap(candidate.mediaUrls, mediaUrls)) {
2850
- flushResult.droppedDuplicateMedia += 1;
2851
- continue;
2852
- }
2853
- try {
2854
- if ((await params.deliver(candidate)).visibleReplySent) flushResult.delivered += 1;
2855
- } catch (error) {
2856
- throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
2857
- }
2797
+ const candidates = [];
2798
+ const retained = [];
2799
+ for (const pending of pendingMediaOnlyPayloads.splice(0)) if (shouldFlush(pending)) candidates.push(pending);
2800
+ else retained.push(pending);
2801
+ pendingMediaOnlyPayloads.push(...retained);
2802
+ for (const [index, candidate] of candidates.entries()) try {
2803
+ const delivery = await params.deliver(candidate);
2804
+ candidate.resolveFinalization(delivery);
2805
+ if (delivery.visibleReplySent) flushResult.delivered += 1;
2806
+ } catch (error) {
2807
+ const visibleError = markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
2808
+ candidate.rejectFinalization(error);
2809
+ for (const remaining of candidates.slice(index + 1)) remaining.rejectFinalization(new Error("deferred WhatsApp media delivery was not attempted", { cause: error }));
2810
+ throw visibleError;
2858
2811
  }
2859
2812
  return flushResult;
2860
2813
  };
2861
2814
  return {
2862
2815
  defer(pending) {
2863
- pendingMediaOnlyPayloads.push(pending);
2816
+ let resolveFinalization;
2817
+ let rejectFinalization;
2818
+ const finalization = new Promise((resolve, reject) => {
2819
+ resolveFinalization = resolve;
2820
+ rejectFinalization = reject;
2821
+ });
2822
+ pendingMediaOnlyPayloads.push({
2823
+ ...pending,
2824
+ resolveFinalization,
2825
+ rejectFinalization
2826
+ });
2827
+ return finalization;
2828
+ },
2829
+ flushNonDuplicateMedia: (mediaUrls) => flushWhere((pending) => !hasWhatsAppMediaUrlOverlap(pending.mediaUrls, mediaUrls)),
2830
+ dropDuplicateMedia(mediaUrls) {
2831
+ const flushResult = {
2832
+ delivered: 0,
2833
+ droppedDuplicateMedia: 0
2834
+ };
2835
+ const retained = [];
2836
+ for (const pending of pendingMediaOnlyPayloads.splice(0)) if (hasWhatsAppMediaUrlOverlap(pending.mediaUrls, mediaUrls)) {
2837
+ pending.resolveFinalization(whatsAppReplyDeliveryVisibility(false));
2838
+ flushResult.droppedDuplicateMedia += 1;
2839
+ } else retained.push(pending);
2840
+ pendingMediaOnlyPayloads.push(...retained);
2841
+ return flushResult;
2864
2842
  },
2865
- flushExceptDuplicateMedia,
2866
- flushAll: () => flushExceptDuplicateMedia()
2843
+ flushAll: () => flushWhere(() => true)
2867
2844
  };
2868
2845
  }
2869
2846
  function logWhatsAppMediaOnlyFlushResult(result) {
@@ -2873,7 +2850,7 @@ function logWhatsAppMediaOnlyFlushResult(result) {
2873
2850
  }
2874
2851
  function resolveWhatsAppResponsePrefix(params) {
2875
2852
  const configuredResponsePrefix = params.cfg.messages?.responsePrefix;
2876
- return params.pipelineResponsePrefix ?? (configuredResponsePrefix === void 0 && params.isSelfChat ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : void 0);
2853
+ return params.pipelineResponsePrefix ?? (configuredResponsePrefix === "auto" ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : configuredResponsePrefix) ?? (params.isSelfChat ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : void 0);
2877
2854
  }
2878
2855
  async function buildWhatsAppInboundContext(params) {
2879
2856
  const admission = requireWhatsAppInboundAdmission(params.msg);
@@ -2885,14 +2862,16 @@ async function buildWhatsAppInboundContext(params) {
2885
2862
  sender: entry.sender,
2886
2863
  body: entry.body,
2887
2864
  timestamp: entry.timestamp,
2888
- messageId: entry.id
2865
+ messageId: entry.id,
2866
+ media: entry.media
2889
2867
  })),
2890
2868
  limit: params.groupHistory?.length ?? 1
2891
2869
  }) : void 0;
2892
- const media = toInboundMediaFacts(params.msg.payload.media?.path || params.msg.payload.media?.url ? [{
2870
+ const media = toInboundMediaFacts(params.msg.payload.media ? [{
2893
2871
  path: params.msg.payload.media?.path,
2894
2872
  url: params.msg.payload.media?.url ?? params.msg.payload.media?.path,
2895
- contentType: params.msg.payload.media?.type
2873
+ contentType: params.msg.payload.media?.type,
2874
+ kind: params.msg.payload.media?.kind
2896
2875
  }] : void 0, { transcribed: (_entry, index) => params.mediaTranscribedIndexes?.includes(index) === true });
2897
2876
  return buildChannelInboundEventContext({
2898
2877
  channel: "whatsapp",
@@ -2903,7 +2882,7 @@ async function buildWhatsAppInboundContext(params) {
2903
2882
  sender: params.visibleReplyTo.sender?.label ?? void 0
2904
2883
  } : void 0,
2905
2884
  groupSystemPrompt: params.groupSystemPrompt,
2906
- untrustedContext: params.msg.payload.untrustedStructuredContext
2885
+ channelStructuredContext: params.msg.payload.channelStructuredContext
2907
2886
  },
2908
2887
  media,
2909
2888
  messageId: params.msg.event.id,
@@ -2935,6 +2914,7 @@ async function buildWhatsAppInboundContext(params) {
2935
2914
  rawBody: params.rawBody ?? params.msg.payload.body,
2936
2915
  commandBody: params.command?.body ?? params.msg.payload.body
2937
2916
  },
2917
+ sessionTranscript: { historyLimit: conversationKind === "group" ? params.groupHistoryLimit ?? params.groupHistory?.length ?? 0 : 0 },
2938
2918
  access: {
2939
2919
  ...wasMentioned !== void 0 ? { mentions: {
2940
2920
  canDetectMention: conversationKind === "group",
@@ -2995,11 +2975,6 @@ function createWhatsAppReplyPlan(params) {
2995
2975
  const conversationId = admission.conversation.id;
2996
2976
  const conversationKind = admission.conversation.kind;
2997
2977
  const statusReactionController = params.statusReactionController ?? null;
2998
- const statusReactionTiming = {
2999
- ...DEFAULT_TIMING,
3000
- ...params.cfg.messages?.statusReactions?.timing
3001
- };
3002
- const removeAckAfterReply = params.cfg.messages?.removeAckAfterReply ?? false;
3003
2978
  const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp");
3004
2979
  const chunkMode = resolveChunkMode(params.cfg, "whatsapp", params.route.accountId);
3005
2980
  const tableMode = resolveMarkdownTableMode$1({
@@ -3016,7 +2991,20 @@ function createWhatsAppReplyPlan(params) {
3016
2991
  const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveWhatsAppDisableBlockStreaming(params.cfg);
3017
2992
  let didSendReply = false;
3018
2993
  let didLogHeartbeatStrip = false;
3019
- const deliverNormalizedPayload = async (normalizedDeliveryPayload, info) => {
2994
+ const recordDeliveredPayload = (payload) => {
2995
+ didSendReply = true;
2996
+ const shouldLog = payload.text ? true : void 0;
2997
+ params.rememberSentText(payload.text, {
2998
+ combinedBody: params.context.Body,
2999
+ combinedBodySessionKey: params.route.sessionKey,
3000
+ logVerboseMessage: shouldLog
3001
+ });
3002
+ if (shouldLogVerbose$1()) {
3003
+ const reply = resolveSendableOutboundReplyParts(payload);
3004
+ logVerbose$1(`Reply body: ${payload.text != null ? reply.text : "<media>"}${reply.hasMedia ? " (media)" : ""} -> ${conversationId}`);
3005
+ }
3006
+ };
3007
+ const deliverNormalizedPayload = async (normalizedDeliveryPayload, info, options) => {
3020
3008
  const reply = resolveSendableOutboundReplyParts(normalizedDeliveryPayload);
3021
3009
  if (!reply.hasMedia && !reply.text.trim()) return whatsAppReplyDeliveryVisibility(false);
3022
3010
  if (!(await params.deliverReply({
@@ -3043,15 +3031,11 @@ function createWhatsAppReplyPlan(params) {
3043
3031
  }, "auto-reply was not accepted by WhatsApp provider");
3044
3032
  return whatsAppReplyDeliveryVisibility(false);
3045
3033
  }
3046
- didSendReply = true;
3047
- const shouldLog = normalizedDeliveryPayload.text ? true : void 0;
3048
- params.rememberSentText(normalizedDeliveryPayload.text, {
3049
- combinedBody: params.context.Body,
3050
- combinedBodySessionKey: params.route.sessionKey,
3051
- logVerboseMessage: shouldLog
3052
- });
3053
- const fromDisplay = conversationId;
3054
- if (shouldLogVerbose$1()) logVerbose$1(`Reply body: ${normalizedDeliveryPayload.text != null ? reply.text : "<media>"}${reply.hasMedia ? " (media)" : ""} -> ${fromDisplay}`);
3034
+ if (options?.recordDelivery !== false) try {
3035
+ recordDeliveredPayload(normalizedDeliveryPayload);
3036
+ } catch (error) {
3037
+ throw markWhatsAppVisibleDeliveryError(error);
3038
+ }
3055
3039
  return whatsAppReplyDeliveryVisibility(true);
3056
3040
  };
3057
3041
  const mediaOnlyCoalescer = createWhatsAppMediaOnlyReplyCoalescer({ deliver: async (pending) => {
@@ -3072,90 +3056,82 @@ function createWhatsAppReplyPlan(params) {
3072
3056
  onSettled: async () => {
3073
3057
  const flushResult = await mediaOnlyCoalescer.flushAll();
3074
3058
  logWhatsAppMediaOnlyFlushResult(flushResult);
3075
- return whatsAppReplyDeliveryVisibility(flushResult.delivered > 0);
3059
+ return whatsAppReplyDeliveryVisibility(didSendReply || flushResult.delivered > 0);
3076
3060
  },
3077
3061
  onReplyStart: params.msg.platform.sendComposing
3078
3062
  },
3079
3063
  delivery: {
3080
- deliver: async (payload, info) => {
3064
+ preparePayload: async (payload, info) => {
3081
3065
  const deliveryPayload = resolveWhatsAppDeliverablePayload(payload, info);
3082
- if (!deliveryPayload) return whatsAppReplyDeliveryVisibility(false);
3066
+ if (!deliveryPayload) return null;
3083
3067
  const normalizedOutboundPayload = normalizeWhatsAppOutboundPayload(deliveryPayload, { normalizeText: normalizeWhatsAppPayloadTextPreservingIndentation });
3084
3068
  const normalizedDeliveryPayload = deliveryPayload.text === void 0 ? {
3085
3069
  ...normalizedOutboundPayload,
3086
3070
  text: void 0
3087
3071
  } : normalizedOutboundPayload;
3088
3072
  const reply = resolveSendableOutboundReplyParts(normalizedDeliveryPayload);
3089
- if (!reply.hasMedia && !reply.text.trim()) return whatsAppReplyDeliveryVisibility(false);
3090
- if (!reply.hasMedia) {
3091
- const flushResult = await mediaOnlyCoalescer.flushAll();
3092
- logWhatsAppMediaOnlyFlushResult(flushResult);
3093
- try {
3094
- const durable = await deliverInboundReplyWithMessageSendContext({
3095
- cfg: params.cfg,
3096
- channel: "whatsapp",
3097
- accountId: params.route.accountId,
3098
- agentId: params.route.agentId,
3099
- ctxPayload: params.context,
3100
- payload: normalizedDeliveryPayload,
3101
- info,
3102
- to: conversationId,
3103
- replyToId: resolveWhatsAppDurableReplyToId({
3104
- context: params.context,
3105
- info,
3106
- msg: params.msg,
3107
- payload: normalizedDeliveryPayload
3108
- }),
3109
- formatting: {
3110
- textLimit,
3111
- tableMode,
3112
- chunkMode
3113
- }
3114
- });
3115
- if (durable.status === "failed") {
3116
- if (durable.sentBeforeError === true) throw markWhatsAppVisibleDeliveryError(durable.error);
3117
- throw durable.error;
3118
- }
3119
- if (durable.status === "handled_visible") {
3120
- didSendReply = true;
3121
- const shouldLog = normalizedDeliveryPayload.text ? true : void 0;
3122
- params.rememberSentText(normalizedDeliveryPayload.text, {
3123
- combinedBody: params.context.Body,
3124
- combinedBodySessionKey: params.route.sessionKey,
3125
- logVerboseMessage: shouldLog
3126
- });
3127
- return whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
3128
- }
3129
- if (durable.status === "handled_no_send") return flushResult.delivered > 0 ? whatsAppReplyDeliveryVisibility(true) : whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
3130
- const deliveryResult = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
3131
- return flushResult.delivered > 0 && !deliveryResult.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : deliveryResult;
3132
- } catch (error) {
3133
- throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
3073
+ if (!reply.hasMedia && !reply.text.trim()) return normalizedDeliveryPayload;
3074
+ const mediaUrls = getWhatsAppPayloadMediaUrls(normalizedDeliveryPayload);
3075
+ logWhatsAppMediaOnlyFlushResult(reply.hasMedia ? shouldDeferWhatsAppMediaOnlyPayload({
3076
+ info,
3077
+ mediaUrls,
3078
+ reply
3079
+ }) ? {
3080
+ delivered: 0,
3081
+ droppedDuplicateMedia: 0
3082
+ } : await mediaOnlyCoalescer.flushNonDuplicateMedia(mediaUrls) : await mediaOnlyCoalescer.flushAll());
3083
+ return normalizedDeliveryPayload;
3084
+ },
3085
+ durable: (payload, info) => {
3086
+ const reply = resolveSendableOutboundReplyParts(payload);
3087
+ if (reply.hasMedia || !reply.text.trim()) return false;
3088
+ return {
3089
+ to: conversationId,
3090
+ replyToId: resolveWhatsAppDurableReplyToId({
3091
+ context: params.context,
3092
+ info,
3093
+ msg: params.msg,
3094
+ payload
3095
+ }),
3096
+ formatting: {
3097
+ textLimit,
3098
+ tableMode,
3099
+ chunkMode
3134
3100
  }
3135
- }
3101
+ };
3102
+ },
3103
+ deliver: async (payload, info) => {
3104
+ const normalizedDeliveryPayload = payload;
3105
+ const reply = resolveSendableOutboundReplyParts(normalizedDeliveryPayload);
3106
+ if (!reply.hasMedia && !reply.text.trim()) return whatsAppReplyDeliveryVisibility(false);
3107
+ if (!reply.hasMedia) return await deliverNormalizedPayload(normalizedDeliveryPayload, info, { recordDelivery: false });
3136
3108
  const mediaUrls = getWhatsAppPayloadMediaUrls(normalizedDeliveryPayload);
3137
3109
  if (shouldDeferWhatsAppMediaOnlyPayload({
3138
3110
  info,
3139
3111
  mediaUrls,
3140
3112
  reply
3141
- })) {
3142
- mediaOnlyCoalescer.defer({
3113
+ })) return {
3114
+ visibleReplySent: false,
3115
+ finalization: mediaOnlyCoalescer.defer({
3143
3116
  info,
3144
3117
  mediaUrls,
3145
3118
  payload: normalizedDeliveryPayload
3146
- });
3147
- return whatsAppReplyDeliveryVisibility(false);
3148
- }
3149
- const flushResult = await mediaOnlyCoalescer.flushExceptDuplicateMedia(mediaUrls);
3150
- logWhatsAppMediaOnlyFlushResult(flushResult);
3119
+ })
3120
+ };
3151
3121
  try {
3152
- const deliveryResult = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
3153
- return flushResult.delivered > 0 && !deliveryResult.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : deliveryResult;
3122
+ const result = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
3123
+ if (result.visibleReplySent) logWhatsAppMediaOnlyFlushResult(mediaOnlyCoalescer.dropDuplicateMedia(mediaUrls));
3124
+ return result;
3154
3125
  } catch (error) {
3155
- throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
3126
+ if (isWhatsAppVisibleDeliveryError(error)) logWhatsAppMediaOnlyFlushResult(mediaOnlyCoalescer.dropDuplicateMedia(mediaUrls));
3127
+ throw error;
3156
3128
  }
3157
3129
  },
3130
+ onDelivered: (payload, _info, result) => {
3131
+ if (!resolveSendableOutboundReplyParts(payload).hasMedia && result?.visibleReplySent === true) recordDeliveredPayload(payload);
3132
+ },
3158
3133
  onError: (err, info) => {
3134
+ if (didSendReply) markWhatsAppVisibleDeliveryError(err);
3159
3135
  logWhatsAppReplyDeliveryError({
3160
3136
  err,
3161
3137
  info,
@@ -3189,13 +3165,10 @@ function createWhatsAppReplyPlan(params) {
3189
3165
  finalize: (dispatchResult) => {
3190
3166
  const didQueueVisibleReply = hasVisibleInboundReplyDispatch(dispatchResult);
3191
3167
  const didDeliverVisibleReply = didSendReply || dispatchResult.observedReplyDelivery === true;
3192
- if (!didQueueVisibleReply) {
3168
+ if (!didQueueVisibleReply && !didDeliverVisibleReply) {
3193
3169
  if (statusReactionController) finalizeWhatsAppStatusReaction({
3194
3170
  controller: statusReactionController,
3195
- outcome: "error",
3196
- hasFinalResponse: false,
3197
- removeAckAfterReply,
3198
- timing: statusReactionTiming
3171
+ outcome: "error"
3199
3172
  });
3200
3173
  if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
3201
3174
  logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
@@ -3203,10 +3176,7 @@ function createWhatsAppReplyPlan(params) {
3203
3176
  }
3204
3177
  if (statusReactionController) finalizeWhatsAppStatusReaction({
3205
3178
  controller: statusReactionController,
3206
- outcome: didDeliverVisibleReply ? "done" : "error",
3207
- hasFinalResponse: didDeliverVisibleReply,
3208
- removeAckAfterReply,
3209
- timing: statusReactionTiming
3179
+ outcome: didDeliverVisibleReply ? "done" : "error"
3210
3180
  });
3211
3181
  if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
3212
3182
  return didDeliverVisibleReply;
@@ -3214,29 +3184,8 @@ function createWhatsAppReplyPlan(params) {
3214
3184
  };
3215
3185
  }
3216
3186
  async function finalizeWhatsAppStatusReaction(params) {
3217
- if (params.outcome === "done") {
3218
- await params.controller.setDone();
3219
- if (params.removeAckAfterReply) {
3220
- await new Promise((resolve) => {
3221
- setTimeout(resolve, params.timing.doneHoldMs);
3222
- });
3223
- await params.controller.clear();
3224
- } else await params.controller.restoreInitial();
3225
- return;
3226
- }
3227
- await params.controller.setError();
3228
- if (params.hasFinalResponse) {
3229
- if (params.removeAckAfterReply) {
3230
- await new Promise((resolve) => {
3231
- setTimeout(resolve, params.timing.errorHoldMs);
3232
- });
3233
- await params.controller.clear();
3234
- } else await params.controller.restoreInitial();
3235
- return;
3236
- }
3237
- if (params.removeAckAfterReply) await new Promise((resolve) => {
3238
- setTimeout(resolve, params.timing.errorHoldMs);
3239
- });
3187
+ if (params.outcome === "done") await params.controller.setDone();
3188
+ else await params.controller.setError();
3240
3189
  await params.controller.restoreInitial();
3241
3190
  }
3242
3191
  //#endregion
@@ -3250,7 +3199,7 @@ function resolveIdentityNamePrefix(cfg, agentId) {
3250
3199
  return identityName ? `[${identityName}]` : void 0;
3251
3200
  }
3252
3201
  function resolveMessagePrefix(cfg, agentId, opts) {
3253
- const configured = opts?.configured ?? cfg.messages?.messagePrefix;
3202
+ const configured = opts?.configured;
3254
3203
  if (configured !== void 0) return configured;
3255
3204
  if (opts?.hasAllowFrom === true) return "";
3256
3205
  return resolveIdentityNamePrefix(cfg, agentId) ?? opts?.fallback ?? "[openclaw]";
@@ -3267,7 +3216,7 @@ function formatReplyContext(msg) {
3267
3216
  function buildInboundLine(params) {
3268
3217
  const { cfg, msg, agentId, previousTimestamp, envelope } = params;
3269
3218
  const messagePrefix = resolveMessagePrefix(cfg, agentId, {
3270
- configured: cfg.channels?.whatsapp?.messagePrefix,
3219
+ configured: cfg.channels?.whatsapp?.responsePrefix,
3271
3220
  hasAllowFrom: (cfg.channels?.whatsapp?.allowFrom?.length ?? 0) > 0
3272
3221
  });
3273
3222
  const admission = requireWhatsAppInboundAdmission(msg);
@@ -3297,66 +3246,30 @@ function buildInboundLine(params) {
3297
3246
  //#region extensions/whatsapp/src/auto-reply/monitor/status-reaction.ts
3298
3247
  async function createWhatsAppStatusReactionController(params) {
3299
3248
  if (!params.msg.event.id) return null;
3300
- const statusReactionsConfig = params.cfg.messages?.statusReactions;
3301
- if (statusReactionsConfig?.enabled !== true) return null;
3302
- const admission = requireWhatsAppInboundAdmission(params.msg);
3303
- const accountId = admission.accountId;
3304
- if (resolveWhatsAppReactionLevel({
3305
- cfg: params.cfg,
3306
- accountId
3307
- }).level === "off") return null;
3308
- const ackConfig = params.cfg.channels?.whatsapp?.ackReaction;
3309
- const ackEmoji = resolveWhatsAppAckEmoji({
3310
- cfg: params.cfg,
3311
- agentId: params.agentId,
3312
- ackConfig
3313
- });
3314
- if (!ackEmoji) return null;
3315
- const directEnabled = ackConfig?.direct ?? true;
3316
- const groupMode = ackConfig?.group ?? "mentions";
3317
- const isGroup = admission.conversation.kind === "group";
3318
- const conversationIdForCheck = admission.conversation.id;
3319
- const activation = isGroup ? await resolveGroupActivationFor({
3249
+ if ((params.cfg.messages?.statusReactions)?.enabled !== true) return null;
3250
+ const eligibility = await resolveWhatsAppReactionEligibility({
3320
3251
  cfg: params.cfg,
3321
- accountId,
3252
+ msg: params.msg,
3322
3253
  agentId: params.agentId,
3323
3254
  sessionKey: params.sessionKey,
3324
- conversationId: conversationIdForCheck
3325
- }) : null;
3326
- if (!shouldAckReactionForWhatsApp({
3327
- emoji: ackEmoji,
3328
- isDirect: admission.conversation.kind === "direct",
3329
- isGroup,
3330
- directEnabled,
3331
- groupMode,
3332
- wasMentioned: (params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned) === true,
3333
- groupActivated: activation === "always"
3334
- })) return null;
3335
- const sender = getSenderIdentity(params.msg);
3336
- const reactionOptions = {
3337
- verbose: params.verbose,
3338
- fromMe: false,
3339
- ...sender.jid ? { participant: sender.jid } : {},
3340
- accountId,
3341
- cfg: params.cfg
3342
- };
3343
- const chatId = params.msg.platform.chatJid;
3344
- const msgId = params.msg.event.id;
3255
+ verbose: params.verbose
3256
+ });
3257
+ if (eligibility.status === "disabled") return null;
3258
+ const { chatId, messageId, emoji: initialEmoji, reactionOptions } = eligibility;
3345
3259
  return createStatusReactionController({
3346
3260
  enabled: true,
3347
3261
  adapter: {
3348
3262
  setReaction: async (emoji) => {
3349
- await sendReactionWhatsApp(chatId, msgId, emoji, reactionOptions);
3263
+ await sendReactionWhatsApp(chatId, messageId, emoji, reactionOptions);
3350
3264
  },
3351
3265
  clearReaction: async () => {
3352
- await sendReactionWhatsApp(chatId, msgId, "", reactionOptions);
3266
+ await sendReactionWhatsApp(chatId, messageId, "", reactionOptions);
3353
3267
  }
3354
3268
  },
3355
- initialEmoji: ackEmoji,
3356
- emojis: statusReactionsConfig.emojis,
3357
- timing: statusReactionsConfig.timing,
3269
+ initialEmoji,
3270
+ emojis: void 0,
3358
3271
  onError: (err) => {
3359
- logVerbose(`WhatsApp status-reaction error for chat ${chatId}/${msgId}: ${String(err)}`);
3272
+ logVerbose(`WhatsApp status-reaction error for chat ${chatId}/${messageId}: ${String(err)}`);
3360
3273
  }
3361
3274
  });
3362
3275
  }
@@ -3423,13 +3336,16 @@ async function processMessage(params) {
3423
3336
  sessionKey: params.route.sessionKey
3424
3337
  });
3425
3338
  let audioTranscript = params.preflightAudioTranscript ?? void 0;
3426
- const hasAudioBody = params.msg.payload.media?.type?.startsWith("audio/") === true && params.msg.payload.body === "<media:audio>";
3339
+ const hasAudioBody = (params.msg.payload.media?.kind === "audio" || params.msg.payload.media?.type?.startsWith("audio/") === true) && !params.msg.payload.body.trim();
3427
3340
  if (params.preflightAudioTranscript === void 0 && hasAudioBody && params.msg.payload.media?.path) try {
3428
3341
  const { transcribeFirstAudio } = await import("./audio-preflight.runtime-C_glQhZY.js");
3429
3342
  audioTranscript = await transcribeFirstAudio({
3430
3343
  ctx: {
3431
- MediaPaths: [params.msg.payload.media?.path],
3432
- MediaTypes: params.msg.payload.media?.type ? [params.msg.payload.media?.type] : void 0,
3344
+ media: [{
3345
+ path: params.msg.payload.media.path,
3346
+ contentType: params.msg.payload.media.type,
3347
+ kind: params.msg.payload.media.kind ?? void 0
3348
+ }],
3433
3349
  From: conversationId,
3434
3350
  To: params.msg.platform.recipientJid,
3435
3351
  Provider: "whatsapp",
@@ -3441,7 +3357,7 @@ async function processMessage(params) {
3441
3357
  cfg: params.cfg
3442
3358
  });
3443
3359
  } catch {
3444
- if (shouldLogVerbose$1()) logVerbose$1("whatsapp: audio preflight transcription failed, using placeholder");
3360
+ if (shouldLogVerbose$1()) logVerbose$1("whatsapp: audio preflight transcription failed, keeping structured audio");
3445
3361
  }
3446
3362
  const msgForAgent = audioTranscript !== void 0 ? {
3447
3363
  ...params.msg,
@@ -3479,7 +3395,8 @@ async function processMessage(params) {
3479
3395
  entries: history.map((m) => ({
3480
3396
  sender: m.sender,
3481
3397
  body: m.body,
3482
- timestamp: m.timestamp
3398
+ timestamp: m.timestamp,
3399
+ media: m.media
3483
3400
  })),
3484
3401
  currentMessage: combinedBody,
3485
3402
  excludeLast: false,
@@ -3488,7 +3405,7 @@ async function processMessage(params) {
3488
3405
  channel: "WhatsApp",
3489
3406
  from: conversationId,
3490
3407
  timestamp: entry.timestamp,
3491
- body: entry.body,
3408
+ body: [entry.body, formatMediaPlaceholderText(entry.media ?? [])].filter(Boolean).join("\n"),
3492
3409
  chatType: "group",
3493
3410
  senderLabel: entry.sender,
3494
3411
  envelope: envelopeOptions
@@ -3582,6 +3499,7 @@ async function processMessage(params) {
3582
3499
  authorized: commandAuthorized
3583
3500
  },
3584
3501
  groupHistory: visibleGroupHistory,
3502
+ groupHistoryLimit: params.groupHistoryLimit,
3585
3503
  groupMemberRoster: params.groupMemberNames.get(params.groupHistoryKey),
3586
3504
  groupSystemPrompt: conversationSystemPrompt,
3587
3505
  msg: params.msg,
@@ -3704,7 +3622,7 @@ async function processMessage(params) {
3704
3622
  });
3705
3623
  const didSendReply = turnResult.dispatched ? finalizeReply?.(turnResult.dispatchResult) ?? false : false;
3706
3624
  removeAckReactionHandleAfterReply({
3707
- removeAfterReply: Boolean(params.cfg.messages?.removeAckAfterReply && didSendReply),
3625
+ removeAfterReply: false,
3708
3626
  ackReaction,
3709
3627
  onError: (err) => {
3710
3628
  logAckFailure({
@@ -3744,6 +3662,7 @@ function createWebOnMessageHandler(params) {
3744
3662
  route,
3745
3663
  groupHistoryKey,
3746
3664
  groupHistories: params.groupHistories,
3665
+ groupHistoryLimit: params.groupHistoryLimit,
3747
3666
  groupMemberNames: params.groupMemberNames,
3748
3667
  connectionId: params.connectionId,
3749
3668
  verbose: params.verbose,
@@ -3811,7 +3730,7 @@ function createWebOnMessageHandler(params) {
3811
3730
  peerId
3812
3731
  }) : route.sessionKey;
3813
3732
  let preflightAudioTranscript;
3814
- const hasAudioBody = msg.payload.media?.type?.startsWith("audio/") === true && msg.payload.body === "<media:audio>";
3733
+ const hasAudioBody = (msg.payload.media?.kind === "audio" || msg.payload.media?.type?.startsWith("audio/") === true) && !msg.payload.body.trim();
3815
3734
  const canRunEarlyAudioPreflight = conversationKind === "group" || canRunDirectEarlyAudioPreflight;
3816
3735
  let ackAlreadySent = false;
3817
3736
  let ackReaction = null;
@@ -3837,8 +3756,11 @@ function createWebOnMessageHandler(params) {
3837
3756
  const { transcribeFirstAudio } = await import("./audio-preflight.runtime-C_glQhZY.js");
3838
3757
  preflightAudioTranscript = await transcribeFirstAudio({
3839
3758
  ctx: {
3840
- MediaPaths: [msg.payload.media?.path],
3841
- MediaTypes: msg.payload.media?.type ? [msg.payload.media?.type] : void 0,
3759
+ media: [{
3760
+ path: msg.payload.media.path,
3761
+ contentType: msg.payload.media.type,
3762
+ kind: msg.payload.media.kind ?? void 0
3763
+ }],
3842
3764
  From: conversationId,
3843
3765
  To: msg.platform.recipientJid,
3844
3766
  Provider: "whatsapp",
@@ -4009,8 +3931,7 @@ function resolveWebMonitorConfigSnapshot(params) {
4009
3931
  ...params.cfg.channels,
4010
3932
  whatsapp: {
4011
3933
  ...params.cfg.channels?.whatsapp,
4012
- ackReaction: account.ackReaction,
4013
- messagePrefix: account.messagePrefix,
3934
+ responsePrefix: account.messagePrefix,
4014
3935
  allowFrom: account.allowFrom,
4015
3936
  groupAllowFrom: account.groupAllowFrom,
4016
3937
  groupPolicy: account.groupPolicy,
@@ -4024,23 +3945,11 @@ function resolveWebMonitorConfigSnapshot(params) {
4024
3945
  account
4025
3946
  };
4026
3947
  }
4027
- function normalizeReconnectAccountId(accountId) {
4028
- return (accountId ?? "").trim() || "default";
4029
- }
4030
3948
  function isNoListenerReconnectError(lastError) {
4031
3949
  return typeof lastError === "string" && /No active WhatsApp Web listener/i.test(lastError);
4032
3950
  }
4033
- function resolveExplicitWhatsAppDebounceOverride(params) {
4034
- const channel = params.sourceCfg?.channels?.whatsapp;
4035
- if (!channel) return;
4036
- const accountId = normalizeReconnectAccountId(params.accountId);
4037
- const accountDebounce = resolveAccountEntry(channel.accounts, accountId)?.debounceMs;
4038
- if (accountDebounce !== void 0) return accountDebounce;
4039
- if (accountId !== "default") {
4040
- const defaultAccountDebounce = resolveAccountEntry(channel.accounts, "default")?.debounceMs;
4041
- if (defaultAccountDebounce !== void 0) return defaultAccountDebounce;
4042
- }
4043
- return channel.debounceMs;
3951
+ function normalizeReconnectAccountId(accountId) {
3952
+ return (accountId ?? "").trim() || "default";
4044
3953
  }
4045
3954
  function isRetryableAuthUnstableError(error) {
4046
3955
  return error instanceof WhatsAppAuthUnstableError || typeof error === "object" && error !== null && "code" in error && error.code === "whatsapp-auth-unstable";
@@ -4062,10 +3971,8 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4062
3971
  module: "web-reconnect",
4063
3972
  runId
4064
3973
  });
4065
- const baseCfg = getRuntimeConfig$1();
4066
- const sourceCfg = getRuntimeConfigSourceSnapshot();
4067
3974
  const { cfg, account } = resolveWebMonitorConfigSnapshot({
4068
- cfg: baseCfg,
3975
+ cfg: getRuntimeConfig$1(),
4069
3976
  accountId: tuning.accountId
4070
3977
  });
4071
3978
  const loadCurrentMonitorConfig = () => resolveWebMonitorConfigSnapshot({
@@ -4075,7 +3982,7 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4075
3982
  const maxMediaBytes = resolveWhatsAppMediaMaxBytes(account);
4076
3983
  const heartbeatSeconds = resolveHeartbeatSeconds(cfg, tuning.heartbeatSeconds);
4077
3984
  const reconnectPolicy = resolveReconnectPolicy(cfg, tuning.reconnect);
4078
- const socketTiming = resolveWhatsAppSocketTiming(cfg, tuning.socketTiming);
3985
+ const socketTiming = resolveWhatsAppSocketTiming(tuning.socketTiming);
4079
3986
  const baseMentionConfig = buildMentionConfig(cfg);
4080
3987
  const groupHistoryLimit = account.historyLimit ?? cfg.channels?.whatsapp?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT;
4081
3988
  const groupHistories = /* @__PURE__ */ new Map();
@@ -4123,12 +4030,7 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4123
4030
  const connectionId = newConnectionId();
4124
4031
  const inboundDebounceMs = resolveInboundDebounceMs({
4125
4032
  cfg,
4126
- channel: "whatsapp",
4127
- overrideMs: resolveExplicitWhatsAppDebounceOverride({
4128
- cfg,
4129
- sourceCfg,
4130
- accountId: account.accountId
4131
- })
4033
+ channel: "whatsapp"
4132
4034
  });
4133
4035
  const shouldDebounce = (msg) => {
4134
4036
  const normalized = normalizeWebInboundMessage(msg);
@@ -4474,4 +4376,4 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
4474
4376
  }
4475
4377
  }
4476
4378
  //#endregion
4477
- export { loadWebMediaRaw as a, monitorWebInbox as c, loadWebMedia$1 as i, resetWebInboundDedupe as l, LocalMediaAccessError as n, optimizeImageToJpeg as o, getDefaultLocalRoots as r, optimizeImageToPng as s, monitorWebChannel as t };
4379
+ export { loadWebMediaRaw as a, monitorWebInbox as c, loadWebMedia as i, resetWebInboundDedupe as l, LocalMediaAccessError as n, optimizeImageToJpeg as o, getDefaultLocalRoots as r, optimizeImageToPng as s, monitorWebChannel as t };