@openclaw/whatsapp 2026.7.2-beta.2 → 2026.7.2-beta.4
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.
- package/dist/{access-control-DazDX3RB.js → access-control-CRz_v6bR.js} +4 -5
- package/dist/{accounts-CKOFMPf-.js → accounts-CxPuEFi-.js} +44 -4
- package/dist/{action-runtime-Bf-12TIH.js → action-runtime-CHU7xb6o.js} +2 -2
- package/dist/action-runtime-api.js +1 -1
- package/dist/action-runtime.runtime.js +1 -1
- package/dist/{active-listener-5kHap439.js → active-listener-BEjWsCNM.js} +1 -1
- package/dist/api.js +12 -15
- package/dist/{approval-handler.runtime-CBiGQuZ5.js → approval-handler.runtime-C4ddB7Kc.js} +4 -4
- package/dist/{approval-reactions-BGQoz25s.js → approval-reactions-P2HXjmln.js} +3 -3
- package/dist/{auth-store-BQi9pZqQ.js → auth-store-DirYMTbo.js} +4 -3
- package/dist/call-tool-api.js +4 -4
- package/dist/{channel-DbadDUDD.js → channel-B9xjrOGG.js} +37 -19
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-react-action-PoRWfP9x.js → channel-react-action-CBW-PA8q.js} +10 -13
- package/dist/{channel.runtime-BDDy4Lhz.js → channel.runtime-DdKuhlNu.js} +5 -5
- package/dist/{channel.setup-BZ-po8fg.js → channel.setup-DXUCPHel.js} +3 -6
- package/dist/{config-schema-BZ8h1hWA.js → config-schema-C6IeKIC0.js} +6 -0
- package/dist/{connection-controller-C1rwfWb4.js → connection-controller-BVRExqrB.js} +186 -27
- package/dist/connection-controller-runtime-context-DG0tuYuw.js +23 -0
- package/dist/contract-api.js +1 -1
- package/dist/directory-config-BTbJkV1W.js +196 -0
- package/dist/directory-contract-api.js +1 -1
- package/dist/doctor-CwzVXKoE.js +10 -0
- package/dist/doctor-contract-api.js +35 -79
- package/dist/{group-session-key-CLTZeH_F.js → group-session-key-DJSdYfnb.js} +9 -4
- package/dist/{identity-D7L8LCuH.js → identity-BM4_wZeM.js} +2 -2
- package/dist/light-runtime-api.js +2 -2
- package/dist/{login-DGIoToX8.js → login-BKUm7KSk.js} +6 -7
- package/dist/{login-qr-X-CZHOk9.js → login-qr-DagJvMbg.js} +7 -8
- package/dist/login-qr-runtime.js +1 -1
- package/dist/{monitor-BZ8QvCeV.js → monitor-CBVNJ2lb.js} +623 -400
- package/dist/question-reactions-CaOrSYzU.js +142 -0
- package/dist/runtime-BLlToOi6.js +21 -0
- package/dist/runtime-api.js +10 -10
- package/dist/runtime-setter-api.js +1 -1
- package/dist/{send-D9Ydnn4o.js → send-DhJVN30g.js} +38 -85
- package/dist/{send-api-W33lI2nR.js → send-api-CpHwbor_.js} +38 -32
- package/dist/{setup-finalize-DBf1jjBj.js → setup-finalize-9XzKdFq6.js} +3 -3
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-CiKGdNJv.js → setup-surface-Kv3mPki2.js} +3 -3
- package/dist/{setup-core-3aSOoK2T.js → shared-CuqHFnKd.js} +78 -56
- package/dist/{session-mxWFn0eS.js → socket-close-BoSHhQNJ.js} +226 -7
- package/dist/{targets-runtime-Bsh0HR9y.js → targets-runtime-CKFrhoX4.js} +142 -29
- package/dist/text-runtime-CU_IlbK5.js +4 -0
- package/node_modules/@protobufjs/utf8/CHANGELOG.md +8 -0
- package/node_modules/@protobufjs/utf8/index.js +29 -18
- package/node_modules/@protobufjs/utf8/package.json +2 -2
- package/node_modules/@protobufjs/utf8/tests/index.js +5 -0
- package/node_modules/@thi.ng/bitstream/package.json +4 -4
- package/node_modules/@thi.ng/errors/package.json +4 -4
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/fs/promises.d.ts +16 -6
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/audio-type/package.json +3 -2
- package/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -1
- package/node_modules/lru-cache/dist/commonjs/browser/index.js +11 -4
- package/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -1
- package/node_modules/lru-cache/dist/commonjs/browser/index.min.js +1 -1
- package/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +3 -3
- package/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/lru-cache/dist/commonjs/index.js +11 -4
- package/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
- package/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
- package/node_modules/lru-cache/dist/commonjs/index.min.js.map +3 -3
- package/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -1
- package/node_modules/lru-cache/dist/commonjs/node/index.js +11 -4
- package/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -1
- package/node_modules/lru-cache/dist/commonjs/node/index.min.js +1 -1
- package/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +3 -3
- package/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -1
- package/node_modules/lru-cache/dist/esm/browser/index.js +11 -4
- package/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -1
- package/node_modules/lru-cache/dist/esm/browser/index.min.js +1 -1
- package/node_modules/lru-cache/dist/esm/browser/index.min.js.map +3 -3
- package/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
- package/node_modules/lru-cache/dist/esm/index.js +11 -4
- package/node_modules/lru-cache/dist/esm/index.js.map +1 -1
- package/node_modules/lru-cache/dist/esm/index.min.js +1 -1
- package/node_modules/lru-cache/dist/esm/index.min.js.map +3 -3
- package/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -1
- package/node_modules/lru-cache/dist/esm/node/index.js +11 -4
- package/node_modules/lru-cache/dist/esm/node/index.js.map +1 -1
- package/node_modules/lru-cache/dist/esm/node/index.min.js +1 -1
- package/node_modules/lru-cache/dist/esm/node/index.min.js.map +3 -3
- package/node_modules/lru-cache/package.json +1 -1
- package/node_modules/music-metadata/lib/common/Util.d.ts +1 -1
- package/node_modules/music-metadata/lib/common/Util.js +7 -1
- package/node_modules/music-metadata/lib/id3v2/FrameHeader.js +23 -3
- package/node_modules/music-metadata/lib/id3v2/FrameParser.js +2 -2
- package/node_modules/music-metadata/lib/id3v2/ID3v2Token.js +1 -1
- package/node_modules/music-metadata/lib/mp4/AtomToken.js +38 -13
- package/node_modules/music-metadata/lib/mpeg/MpegParser.js +1 -1
- package/node_modules/music-metadata/package.json +6 -6
- package/node_modules/p-queue/dist/index.js +17 -6
- package/node_modules/p-queue/package.json +1 -1
- package/node_modules/protobufjs/dist/light/protobuf.js +145 -188
- package/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/dist/minimal/protobuf.js +33 -76
- package/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/dist/protobuf.js +168 -208
- package/node_modules/protobufjs/dist/protobuf.js.map +1 -1
- package/node_modules/protobufjs/dist/protobuf.min.js +3 -3
- package/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
- package/node_modules/protobufjs/index.d.ts +0 -8
- package/node_modules/protobufjs/package.json +2 -3
- package/node_modules/protobufjs/src/parse.js +3 -0
- package/node_modules/protobufjs/src/util/minimal.js +0 -3
- package/node_modules/typebox/build/compile/validator.d.mts +1 -4
- package/node_modules/typebox/build/guard/guard.d.mts +1 -1
- package/node_modules/typebox/build/guard/guard.mjs +5 -2
- package/node_modules/typebox/build/guard/string.mjs +16 -4
- package/node_modules/typebox/build/schema/engine/_functions.mjs +34 -15
- package/node_modules/typebox/build/type/action/readonly_object.d.mts +5 -0
- package/node_modules/typebox/build/type/action/readonly_object.mjs +8 -0
- package/node_modules/typebox/build/typebox.d.mts +1 -1
- package/node_modules/typebox/build/typebox.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +29 -32
- package/node_modules/ws/lib/receiver.js +15 -32
- package/node_modules/ws/lib/websocket-server.js +4 -4
- package/node_modules/ws/lib/websocket.js +4 -4
- package/node_modules/ws/package.json +5 -1
- package/npm-shrinkwrap.json +41 -45
- package/openclaw.plugin.json +139 -179
- package/package.json +21 -11
- package/dist/account-config-LiT9wWH9.js +0 -43
- package/dist/connection-controller-runtime-context-CbTsqmPT.js +0 -14
- package/dist/directory-config-CLBQrjtC.js +0 -37
- package/dist/doctor-By3SB4lm.js +0 -48
- package/dist/runtime-BfAdAEYT.js +0 -8
- package/dist/text-runtime-BTGkMN0o.js +0 -4
- package/node_modules/@protobufjs/inquire/CHANGELOG.md +0 -8
- package/node_modules/@protobufjs/inquire/LICENSE +0 -26
- package/node_modules/@protobufjs/inquire/README.md +0 -13
- package/node_modules/@protobufjs/inquire/index.d.ts +0 -10
- package/node_modules/@protobufjs/inquire/index.js +0 -38
- package/node_modules/@protobufjs/inquire/package.json +0 -21
- package/node_modules/@protobufjs/inquire/tests/data/array.js +0 -1
- package/node_modules/@protobufjs/inquire/tests/data/emptyArray.js +0 -1
- package/node_modules/@protobufjs/inquire/tests/data/emptyObject.js +0 -1
- package/node_modules/@protobufjs/inquire/tests/data/object.js +0 -1
- package/node_modules/@protobufjs/inquire/tests/index.js +0 -20
|
@@ -1,52 +1,48 @@
|
|
|
1
|
-
import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-
|
|
2
|
-
import { a as normalizeWhatsAppOutboundPayload, c as prepareWhatsAppOutboundMedia, r as sendReactionWhatsApp, s as normalizeWhatsAppPayloadTextPreservingIndentation, u as resolveWhatsAppReactionLevel } from "./send-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { a as
|
|
6
|
-
import {
|
|
7
|
-
import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-
|
|
8
|
-
import { g as readWebSelfId, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision } from "./auth-store-
|
|
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-
|
|
1
|
+
import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-CxPuEFi-.js";
|
|
2
|
+
import { a as normalizeWhatsAppOutboundPayload, c as prepareWhatsAppOutboundMedia, r as sendReactionWhatsApp, s as normalizeWhatsAppPayloadTextPreservingIndentation, u as resolveWhatsAppReactionLevel } from "./send-DhJVN30g.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-CKFrhoX4.js";
|
|
6
|
+
import { t as normalizeE164 } from "./text-runtime-CU_IlbK5.js";
|
|
7
|
+
import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-DJSdYfnb.js";
|
|
8
|
+
import { g as readWebSelfId, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision } from "./auth-store-DirYMTbo.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-BM4_wZeM.js";
|
|
10
10
|
import { n as resolveWhatsAppGroupsConfigPath } from "./group-config-path-BGyzT9Lg.js";
|
|
11
|
-
import {
|
|
12
|
-
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-W33lI2nR.js";
|
|
13
|
-
import { a as createWhatsAppSocketOperationTimeoutAdapter, c as resolveWhatsAppSocketTiming, l as withWhatsAppSocketOperationTimeout, o as isWhatsAppSocketOperationTimeoutError, r as waitForWaConnection, s as resolveWhatsAppSocketOperationTimeoutMs, t as createWaSocket } from "./session-mxWFn0eS.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-BoSHhQNJ.js";
|
|
14
12
|
import { n as getStatusCode, t as formatError } from "./session-errors-JuazczrA.js";
|
|
15
13
|
import { t as BufferJSON } from "./session.runtime-Nsqaehbd.js";
|
|
16
|
-
import { c as
|
|
17
|
-
import { n as
|
|
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-CRz_v6bR.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-CpHwbor_.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-BVRExqrB.js";
|
|
17
|
+
import { n as maybeResolveWhatsAppApprovalReaction } from "./approval-reactions-P2HXjmln.js";
|
|
18
|
+
import { t as maybeResolveWhatsAppQuestionReaction } from "./question-reactions-CaOrSYzU.js";
|
|
18
19
|
import { resolveAccountEntry } from "openclaw/plugin-sdk/account-core";
|
|
19
20
|
import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
20
21
|
import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
|
|
21
|
-
import {
|
|
22
|
+
import { createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
|
|
22
23
|
import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
|
|
23
|
-
import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
24
24
|
import { getChildLogger } from "openclaw/plugin-sdk/logging-core";
|
|
25
25
|
import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
26
26
|
import { createSubsystemLogger, defaultRuntime, formatDurationPrecise, getChildLogger as getChildLogger$1, logVerbose, logVerbose as logVerbose$1, registerUnhandledRejectionHandler, shouldLogVerbose, shouldLogVerbose as shouldLogVerbose$1, warn } from "openclaw/plugin-sdk/runtime-env";
|
|
27
27
|
import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
|
|
28
|
-
import {
|
|
28
|
+
import { bindIngressLifecycleToReplyOptions, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, deliverInboundReplyWithMessageSendContext, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
29
29
|
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
|
30
30
|
import { isReasoningReplyPayload, resolveSendableOutboundReplyParts, sendMediaWithLeadingCaption } from "openclaw/plugin-sdk/reply-payload";
|
|
31
31
|
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
32
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
34
|
-
import {
|
|
33
|
+
import { buildChannelInboundEventContext, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, formatMediaPlaceholderText, hasVisibleInboundReplyDispatch, resolveInboundSessionEnvelopeContext, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
35
34
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, DEFAULT_MAIN_KEY, buildAgentMainSessionKey, buildAgentSessionKey, buildGroupHistoryKey, deriveLastRoutePolicy, normalizeAccountId as normalizeAccountId$1, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
36
35
|
import { asDateTimestampMs, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
37
|
-
import {
|
|
36
|
+
import { createHash } from "node:crypto";
|
|
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, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
40
38
|
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
41
|
-
import {
|
|
42
|
-
import { dispatchReplyWithBufferedBlockDispatcher, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
|
39
|
+
import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
|
43
40
|
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
44
41
|
import { createInboundDebouncer, resolveInboundDebounceMs } from "openclaw/plugin-sdk/channel-inbound-debounce";
|
|
45
42
|
import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runtime";
|
|
46
43
|
import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, buildInboundHistoryFromEntries, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
47
44
|
import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
48
45
|
import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
|
|
49
|
-
import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
50
46
|
import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
|
|
51
47
|
import { getRuntimeConfig as getRuntimeConfig$1, getRuntimeConfigSourceSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
52
48
|
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
@@ -59,51 +55,8 @@ import { hasControlCommand, isControlCommandMessage, shouldComputeCommandAuthori
|
|
|
59
55
|
import { createInternalHookEvent, deriveInboundMessageHookContext, fireAndForgetBoundedHook, toInternalMessageReceivedContext, toPluginMessageContext, toPluginMessageReceivedEvent, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
|
|
60
56
|
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
61
57
|
import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
|
|
58
|
+
import { LocalMediaAccessError, getDefaultLocalRoots, loadWebMedia, loadWebMediaRaw, optimizeImageToJpeg, optimizeImageToPng } from "openclaw/plugin-sdk/web-media";
|
|
62
59
|
import { createChannelApiRetryRunner } from "openclaw/plugin-sdk/retry-runtime";
|
|
63
|
-
//#region extensions/whatsapp/src/inbound/dedupe.ts
|
|
64
|
-
const WHATSAPP_INBOUND_DEDUPE_TTL_MS = 20 * 6e4;
|
|
65
|
-
const RECENT_WEB_MESSAGE_MAX = 5e3;
|
|
66
|
-
const RECENT_OUTBOUND_MESSAGE_TTL_MS = 20 * 6e4;
|
|
67
|
-
const RECENT_OUTBOUND_MESSAGE_MAX = 5e3;
|
|
68
|
-
const whatsAppInboundReplayGuard = createChannelReplayGuard({
|
|
69
|
-
dedupe: {
|
|
70
|
-
ttlMs: WHATSAPP_INBOUND_DEDUPE_TTL_MS,
|
|
71
|
-
memoryMaxSize: RECENT_WEB_MESSAGE_MAX
|
|
72
|
-
},
|
|
73
|
-
buildReplayKey: (keys) => keys
|
|
74
|
-
});
|
|
75
|
-
const recentOutboundMessages = createDedupeCache({
|
|
76
|
-
ttlMs: RECENT_OUTBOUND_MESSAGE_TTL_MS,
|
|
77
|
-
maxSize: RECENT_OUTBOUND_MESSAGE_MAX
|
|
78
|
-
});
|
|
79
|
-
var WhatsAppRetryableInboundError = class extends Error {
|
|
80
|
-
constructor(message, options) {
|
|
81
|
-
super(message, options);
|
|
82
|
-
this.name = "WhatsAppRetryableInboundError";
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
function buildMessageKey(params) {
|
|
86
|
-
const accountId = params.accountId.trim();
|
|
87
|
-
const remoteJid = params.remoteJid.trim();
|
|
88
|
-
const messageId = params.messageId.trim();
|
|
89
|
-
if (!accountId || !remoteJid || !messageId || messageId === "unknown") return null;
|
|
90
|
-
return `${accountId}:${remoteJid}:${messageId}`;
|
|
91
|
-
}
|
|
92
|
-
function resetWebInboundDedupe() {
|
|
93
|
-
whatsAppInboundReplayGuard.clearMemory();
|
|
94
|
-
recentOutboundMessages.clear();
|
|
95
|
-
}
|
|
96
|
-
function rememberRecentOutboundMessage(params) {
|
|
97
|
-
const key = buildMessageKey(params);
|
|
98
|
-
if (!key) return;
|
|
99
|
-
recentOutboundMessages.check(key);
|
|
100
|
-
}
|
|
101
|
-
function isRecentOutboundMessage(params) {
|
|
102
|
-
const key = buildMessageKey(params);
|
|
103
|
-
if (!key) return false;
|
|
104
|
-
return recentOutboundMessages.peek(key);
|
|
105
|
-
}
|
|
106
|
-
//#endregion
|
|
107
60
|
//#region extensions/whatsapp/src/inbound/message-aliases.ts
|
|
108
61
|
function normalizeQuoteSender(sender) {
|
|
109
62
|
if (!sender?.displayName && !sender?.jid && !sender?.e164) return;
|
|
@@ -428,42 +381,173 @@ function normalizeWebInboundMessage(msg) {
|
|
|
428
381
|
return normalizeLegacyFlatWebInboundMessage(msg);
|
|
429
382
|
}
|
|
430
383
|
//#endregion
|
|
384
|
+
//#region extensions/whatsapp/src/inbound/dedupe.ts
|
|
385
|
+
const recentOutboundMessages = createDedupeCache({
|
|
386
|
+
ttlMs: 20 * 6e4,
|
|
387
|
+
maxSize: 5e3
|
|
388
|
+
});
|
|
389
|
+
function buildMessageKey(params) {
|
|
390
|
+
const accountId = params.accountId.trim();
|
|
391
|
+
const remoteJid = params.remoteJid.trim();
|
|
392
|
+
const messageId = params.messageId.trim();
|
|
393
|
+
if (!accountId || !remoteJid || !messageId || messageId === "unknown") return null;
|
|
394
|
+
return `${accountId}:${remoteJid}:${messageId}`;
|
|
395
|
+
}
|
|
396
|
+
function resetWebInboundDedupe() {
|
|
397
|
+
recentOutboundMessages.clear();
|
|
398
|
+
}
|
|
399
|
+
function rememberRecentOutboundMessage(params) {
|
|
400
|
+
const key = buildMessageKey(params);
|
|
401
|
+
if (!key) return;
|
|
402
|
+
recentOutboundMessages.check(key);
|
|
403
|
+
}
|
|
404
|
+
function isRecentOutboundMessage(params) {
|
|
405
|
+
const key = buildMessageKey(params);
|
|
406
|
+
if (!key) return false;
|
|
407
|
+
return recentOutboundMessages.peek(key);
|
|
408
|
+
}
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region extensions/whatsapp/src/inbound/durable-payload.ts
|
|
411
|
+
var WhatsAppIngressPermanentError = class extends Error {
|
|
412
|
+
constructor(reason, message, options) {
|
|
413
|
+
super(message, options);
|
|
414
|
+
this.reason = reason;
|
|
415
|
+
this.name = "WhatsAppIngressPermanentError";
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
function serializeWhatsAppDurableInboundMessage(message) {
|
|
419
|
+
const timestamp = message.messageTimestamp;
|
|
420
|
+
let serializedMessage = message;
|
|
421
|
+
if (timestamp != null && typeof timestamp === "object") try {
|
|
422
|
+
const numericTimestamp = Number(timestamp);
|
|
423
|
+
if (Number.isFinite(numericTimestamp)) serializedMessage = {
|
|
424
|
+
...message,
|
|
425
|
+
messageTimestamp: numericTimestamp
|
|
426
|
+
};
|
|
427
|
+
} catch {}
|
|
428
|
+
return JSON.parse(JSON.stringify(serializedMessage, BufferJSON.replacer));
|
|
429
|
+
}
|
|
430
|
+
function deserializeWhatsAppDurableInboundMessage(message) {
|
|
431
|
+
try {
|
|
432
|
+
return JSON.parse(JSON.stringify(message), BufferJSON.reviver);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
throw new WhatsAppIngressPermanentError("invalid-payload", "WhatsApp ingress row contains an invalid serialized message", { cause: error });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
//#endregion
|
|
431
438
|
//#region extensions/whatsapp/src/inbound/durable-receive.ts
|
|
432
439
|
const WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES = 450;
|
|
433
|
-
const WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES =
|
|
440
|
+
const WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES = 5e3;
|
|
434
441
|
const WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
435
442
|
const WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS = 10080 * 60 * 1e3;
|
|
443
|
+
const WHATSAPP_DURABLE_INBOUND_PRUNE_INTERVAL_MS = 3600 * 1e3;
|
|
444
|
+
const WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION = 1;
|
|
436
445
|
function hashNamespacePart(value) {
|
|
437
446
|
return createHash("sha256").update(value).digest("hex").slice(0, 24);
|
|
438
447
|
}
|
|
439
448
|
function createWhatsAppDurableInboundMessageId(params) {
|
|
440
449
|
return createHash("sha256").update(`${params.remoteJid}\n${params.id}`).digest("hex");
|
|
441
450
|
}
|
|
442
|
-
function
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
return
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const runtime = getWhatsAppRuntime();
|
|
451
|
-
return createDurableInboundReceiveJournalFromQueue({
|
|
452
|
-
queue: runtime.state.openChannelIngressQueue({
|
|
453
|
-
accountId: accountPart,
|
|
454
|
-
stateDir: runtime.state.resolveStateDir()
|
|
451
|
+
function inspectWhatsAppIngressMessage(message) {
|
|
452
|
+
const remoteJid = message.key?.remoteJid?.trim();
|
|
453
|
+
const id = message.key?.id?.trim();
|
|
454
|
+
if (!remoteJid || !id) throw new WhatsAppIngressPermanentError("missing-message-key", "WhatsApp ingress message is missing key.remoteJid or key.id");
|
|
455
|
+
return {
|
|
456
|
+
eventId: createWhatsAppDurableInboundMessageId({
|
|
457
|
+
remoteJid,
|
|
458
|
+
id
|
|
455
459
|
}),
|
|
460
|
+
laneKey: remoteJid
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
/** Account-scoped queue shared with the pre-drain WhatsApp receive journal. */
|
|
464
|
+
function createWhatsAppDurableInboundQueue(accountId) {
|
|
465
|
+
return getWhatsAppRuntime().state.openChannelIngressQueue({
|
|
466
|
+
accountId: hashNamespacePart(accountId),
|
|
467
|
+
stateDir: getWhatsAppRuntime().state.resolveStateDir()
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
/** Raw receive chokepoint: append first, then let the drain normalize and dispatch. */
|
|
471
|
+
async function enqueueWhatsAppDurableInbound(params) {
|
|
472
|
+
const facts = inspectWhatsAppIngressMessage(params.message);
|
|
473
|
+
const receivedAt = params.receivedAt ?? Date.now();
|
|
474
|
+
return await params.queue.enqueue(facts.eventId, {
|
|
475
|
+
message: serializeWhatsAppDurableInboundMessage(params.message),
|
|
476
|
+
upsertType: params.upsertType,
|
|
477
|
+
...params.skipStaleAppend === void 0 ? {} : { skipStaleAppend: params.skipStaleAppend },
|
|
478
|
+
...params.skipRecentOutboundEcho === void 0 ? {} : { skipRecentOutboundEcho: params.skipRecentOutboundEcho },
|
|
479
|
+
receivedAt,
|
|
480
|
+
...params.receiveOrder === void 0 ? {} : { receiveOrder: params.receiveOrder }
|
|
481
|
+
}, {
|
|
482
|
+
receivedAt,
|
|
483
|
+
laneKey: facts.laneKey
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
function resolveWhatsAppIngressNonRetryableFailure(error) {
|
|
487
|
+
return error instanceof WhatsAppIngressPermanentError ? {
|
|
488
|
+
reason: error.reason,
|
|
489
|
+
message: error.message
|
|
490
|
+
} : null;
|
|
491
|
+
}
|
|
492
|
+
/** Shared monitor with per-conversation lanes and completion at reply-lane adoption. */
|
|
493
|
+
function createWhatsAppIngressMonitor(params) {
|
|
494
|
+
return createChannelIngressMonitor({
|
|
495
|
+
queue: params.queue,
|
|
496
|
+
inspect: (message) => inspectWhatsAppIngressMessage(message),
|
|
497
|
+
payload: {
|
|
498
|
+
version: WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION,
|
|
499
|
+
serialize: (message, { receivedAt }) => ({
|
|
500
|
+
message: serializeWhatsAppDurableInboundMessage(message),
|
|
501
|
+
receivedAt
|
|
502
|
+
}),
|
|
503
|
+
deserialize: (payload) => deserializeWhatsAppDurableInboundMessage(payload.message),
|
|
504
|
+
encode: ({ body }) => body,
|
|
505
|
+
decode: (payload) => ({
|
|
506
|
+
version: WHATSAPP_DURABLE_INBOUND_PAYLOAD_VERSION,
|
|
507
|
+
body: payload
|
|
508
|
+
}),
|
|
509
|
+
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")
|
|
510
|
+
},
|
|
511
|
+
deliver: (message, lifecycle, claim) => params.dispatch(message, claim.payload, lifecycle),
|
|
512
|
+
pollIntervalMs: params.pollIntervalMs,
|
|
456
513
|
retention: {
|
|
514
|
+
pruneIntervalMs: WHATSAPP_DURABLE_INBOUND_PRUNE_INTERVAL_MS,
|
|
457
515
|
pendingTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
|
|
458
|
-
completedTtlMs: WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS,
|
|
459
|
-
failedTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
|
|
460
516
|
pendingMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES,
|
|
517
|
+
completedTtlMs: WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS,
|
|
461
518
|
completedMaxEntries: WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES,
|
|
519
|
+
failedTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
|
|
462
520
|
failedMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES
|
|
463
|
-
}
|
|
521
|
+
},
|
|
522
|
+
drain: {
|
|
523
|
+
resolveNonRetryableFailure: resolveWhatsAppIngressNonRetryableFailure,
|
|
524
|
+
deriveLaneKey: (record) => {
|
|
525
|
+
try {
|
|
526
|
+
return inspectWhatsAppIngressMessage(deserializeWhatsAppDurableInboundMessage(record.payload.message)).laneKey;
|
|
527
|
+
} catch {
|
|
528
|
+
return record.id;
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
...params.onLog ? { onLog: params.onLog } : {}
|
|
532
|
+
},
|
|
533
|
+
...params.abortSignal ? { abortSignal: params.abortSignal } : {},
|
|
534
|
+
admissionMode: "while-running",
|
|
535
|
+
createStoppedError: () => /* @__PURE__ */ new Error("WhatsApp ingress monitor is stopped."),
|
|
536
|
+
...params.onError ? { onError: params.onError } : {},
|
|
537
|
+
...params.onActivityChange ? { onActivityChange: params.onActivityChange } : {}
|
|
464
538
|
});
|
|
465
539
|
}
|
|
466
540
|
//#endregion
|
|
541
|
+
//#region extensions/whatsapp/src/inbound/ingress-lifecycle.ts
|
|
542
|
+
const ingressLifecycleKey = Symbol("whatsappIngressLifecycle");
|
|
543
|
+
function attachWhatsAppIngressLifecycle(message, lifecycle) {
|
|
544
|
+
if (lifecycle) message[ingressLifecycleKey] = lifecycle;
|
|
545
|
+
return message;
|
|
546
|
+
}
|
|
547
|
+
function resolveWhatsAppIngressLifecycle(message) {
|
|
548
|
+
return message[ingressLifecycleKey];
|
|
549
|
+
}
|
|
550
|
+
//#endregion
|
|
467
551
|
//#region extensions/whatsapp/src/inbound/lifecycle.ts
|
|
468
552
|
function attachEmitterListener(emitter, event, listener) {
|
|
469
553
|
emitter.on(event, listener);
|
|
@@ -533,13 +617,8 @@ const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress
|
|
|
533
617
|
const GROUP_META_TTL_MS = 300 * 1e3;
|
|
534
618
|
const BAILEYS_MESSAGE_TTL_MS = 600 * 1e3;
|
|
535
619
|
const INBOUND_CLOSE_DRAIN_TIMEOUT_MS = 5e3;
|
|
536
|
-
const
|
|
620
|
+
const WHATSAPP_INGRESS_DRAIN_INTERVAL_MS = 1e3;
|
|
537
621
|
const WHATSAPP_GROUP_METADATA_CACHE_MAX_ENTRIES = 500;
|
|
538
|
-
function resolveRetryableWhatsAppInboundError(error) {
|
|
539
|
-
if (error instanceof WhatsAppRetryableInboundError) return error;
|
|
540
|
-
if (!collectErrorGraphCandidates(error, (current) => [current.cause, current.error]).some((candidate) => REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE.test(formatErrorMessage(candidate)))) return null;
|
|
541
|
-
return new WhatsAppRetryableInboundError(formatErrorMessage(error), { cause: error });
|
|
542
|
-
}
|
|
543
622
|
function resolveGroupMetadataExpiresAt(nowRaw = Date.now()) {
|
|
544
623
|
const now = asDateTimestampMs(nowRaw);
|
|
545
624
|
return now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(GROUP_META_TTL_MS, { nowMs: now });
|
|
@@ -630,9 +709,6 @@ function isRetryableSendDisconnectError(err) {
|
|
|
630
709
|
function shouldClearSocketRefAfterSendFailure(err) {
|
|
631
710
|
return /closed|reset|disconnect|no active socket/i.test(formatError(err));
|
|
632
711
|
}
|
|
633
|
-
function isNonEmptyString(value) {
|
|
634
|
-
return Boolean(value);
|
|
635
|
-
}
|
|
636
712
|
async function attachWebInboxToSocket(options) {
|
|
637
713
|
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
|
638
714
|
const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
|
|
@@ -673,14 +749,34 @@ async function attachWebInboxToSocket(options) {
|
|
|
673
749
|
if (!successorIdentity || !identitiesOverlap(self, successorIdentity)) return null;
|
|
674
750
|
return successor.getCurrentSock();
|
|
675
751
|
};
|
|
676
|
-
const
|
|
752
|
+
const durableInboundQueue = options.durableInboundQueue ?? createWhatsAppDurableInboundQueue(options.accountId);
|
|
677
753
|
const inboundDebounceMs = Math.max(0, Math.trunc(options.debounceMs ?? 0));
|
|
678
754
|
const pendingDebounceKeys = /* @__PURE__ */ new Set();
|
|
679
755
|
const activeInboundFlushes = /* @__PURE__ */ new Set();
|
|
680
756
|
const pendingMessageHandlers = /* @__PURE__ */ new Set();
|
|
757
|
+
let durableIngressActive = false;
|
|
758
|
+
const debounceWorkWaiters = /* @__PURE__ */ new Set();
|
|
759
|
+
const notifyDebounceWork = () => {
|
|
760
|
+
if (debounceWorkWaiters.size === 0) return;
|
|
761
|
+
const waiters = [...debounceWorkWaiters];
|
|
762
|
+
debounceWorkWaiters.clear();
|
|
763
|
+
for (const resolve of waiters) resolve();
|
|
764
|
+
};
|
|
765
|
+
const waitForDebounceWorkOrIdle = (handlers) => {
|
|
766
|
+
if (pendingDebounceKeys.size > 0 || activeInboundFlushes.size > 0) return Promise.resolve();
|
|
767
|
+
if (pendingMessageHandlers.size === 0) return Promise.resolve();
|
|
768
|
+
return new Promise((resolve) => {
|
|
769
|
+
const finish = () => {
|
|
770
|
+
debounceWorkWaiters.delete(finish);
|
|
771
|
+
resolve();
|
|
772
|
+
};
|
|
773
|
+
debounceWorkWaiters.add(finish);
|
|
774
|
+
Promise.allSettled(handlers).then(finish);
|
|
775
|
+
});
|
|
776
|
+
};
|
|
681
777
|
let nextReceiveOrder = 0;
|
|
682
778
|
const publishPendingWorkState = (at = Date.now()) => {
|
|
683
|
-
options.onPendingWorkChanged?.(pendingMessageHandlers.size + pendingDebounceKeys.size + activeInboundFlushes.size, at);
|
|
779
|
+
options.onPendingWorkChanged?.(pendingMessageHandlers.size + pendingDebounceKeys.size + activeInboundFlushes.size + (durableIngressActive ? 1 : 0), at);
|
|
684
780
|
};
|
|
685
781
|
const buildInboundDebounceKey = (msg) => {
|
|
686
782
|
const admission = requireWhatsAppInboundAdmission(msg);
|
|
@@ -695,18 +791,47 @@ async function attachWebInboxToSocket(options) {
|
|
|
695
791
|
if (timestampDiff !== 0) return timestampDiff;
|
|
696
792
|
return (a.receiveOrder ?? 0) - (b.receiveOrder ?? 0);
|
|
697
793
|
});
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
794
|
+
const buildFlushIngressLifecycle = (entries) => {
|
|
795
|
+
const lifecycles = entries.map((entry) => entry.turnAdoptionLifecycle).filter((lifecycle) => lifecycle !== void 0);
|
|
796
|
+
const [firstLifecycle] = lifecycles;
|
|
797
|
+
if (!firstLifecycle) return {
|
|
798
|
+
lifecycle: void 0,
|
|
799
|
+
settle: async () => {},
|
|
800
|
+
abandon: async () => {}
|
|
801
|
+
};
|
|
802
|
+
let handedOff = false;
|
|
803
|
+
const adoptAll = async () => {
|
|
804
|
+
for (const lifecycle of lifecycles) await lifecycle.onAdopted();
|
|
805
|
+
};
|
|
806
|
+
return {
|
|
807
|
+
lifecycle: {
|
|
808
|
+
abortSignal: lifecycles.length === 1 ? firstLifecycle.abortSignal : AbortSignal.any(lifecycles.map((lifecycle) => lifecycle.abortSignal)),
|
|
809
|
+
onAdopted: async () => {
|
|
810
|
+
handedOff = true;
|
|
811
|
+
await adoptAll();
|
|
812
|
+
},
|
|
813
|
+
onDeferred: () => {
|
|
814
|
+
handedOff = true;
|
|
815
|
+
for (const lifecycle of lifecycles) lifecycle.onDeferred();
|
|
816
|
+
},
|
|
817
|
+
onAdoptionFinalizing: () => {
|
|
818
|
+
for (const lifecycle of lifecycles) lifecycle.onAdoptionFinalizing();
|
|
819
|
+
},
|
|
820
|
+
onAbandoned: async () => {
|
|
821
|
+
handedOff = true;
|
|
822
|
+
await Promise.all(lifecycles.map((lifecycle) => Promise.resolve(lifecycle.onAbandoned())));
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
settle: async () => {
|
|
826
|
+
if (!handedOff) await adoptAll();
|
|
827
|
+
},
|
|
828
|
+
abandon: async () => {
|
|
829
|
+
if (!handedOff) {
|
|
830
|
+
handedOff = true;
|
|
831
|
+
await Promise.all(lifecycles.map((lifecycle) => Promise.resolve(lifecycle.onAbandoned())));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
};
|
|
710
835
|
};
|
|
711
836
|
const debouncer = createInboundDebouncer({
|
|
712
837
|
debounceMs: inboundDebounceMs,
|
|
@@ -719,14 +844,17 @@ async function attachWebInboxToSocket(options) {
|
|
|
719
844
|
});
|
|
720
845
|
activeInboundFlushes.add(flushTask);
|
|
721
846
|
publishPendingWorkState();
|
|
847
|
+
notifyDebounceWork();
|
|
722
848
|
try {
|
|
723
849
|
const orderedEntries = orderDebouncedInboundEntries(entries);
|
|
724
850
|
const last = orderedEntries.at(-1);
|
|
725
851
|
if (!last) return;
|
|
852
|
+
const { lifecycle, settle, abandon } = buildFlushIngressLifecycle(orderedEntries);
|
|
726
853
|
try {
|
|
727
854
|
if (orderedEntries.length === 1) {
|
|
728
|
-
await options.onMessage(last);
|
|
729
|
-
await
|
|
855
|
+
await options.onMessage(attachWhatsAppIngressLifecycle(last, lifecycle));
|
|
856
|
+
await settle();
|
|
857
|
+
await Promise.all(orderedEntries.map((entry) => maybeMarkInboundAsRead(entry.readReceipt)));
|
|
730
858
|
return;
|
|
731
859
|
}
|
|
732
860
|
const mentioned = /* @__PURE__ */ new Set();
|
|
@@ -741,8 +869,9 @@ async function attachWebInboxToSocket(options) {
|
|
|
741
869
|
...last.group,
|
|
742
870
|
mentions: combinedMentions
|
|
743
871
|
} : void 0;
|
|
744
|
-
const combinedMessage = withDeprecatedWebInboundMessageFlatAliases({
|
|
872
|
+
const combinedMessage = attachWhatsAppIngressLifecycle(withDeprecatedWebInboundMessageFlatAliases({
|
|
745
873
|
...last,
|
|
874
|
+
...lifecycle ? { turnAdoptionLifecycle: lifecycle } : {},
|
|
746
875
|
payload: {
|
|
747
876
|
...last.payload,
|
|
748
877
|
body: combinedBody,
|
|
@@ -753,11 +882,12 @@ async function attachWebInboxToSocket(options) {
|
|
|
753
882
|
...last.event,
|
|
754
883
|
isBatched: true
|
|
755
884
|
}
|
|
756
|
-
});
|
|
885
|
+
}), lifecycle);
|
|
757
886
|
await options.onMessage(combinedMessage);
|
|
758
|
-
await
|
|
887
|
+
await settle();
|
|
888
|
+
await Promise.all(orderedEntries.map((entry) => maybeMarkInboundAsRead(entry.readReceipt)));
|
|
759
889
|
} catch (error) {
|
|
760
|
-
await
|
|
890
|
+
await abandon();
|
|
761
891
|
throw error;
|
|
762
892
|
}
|
|
763
893
|
} finally {
|
|
@@ -972,20 +1102,24 @@ async function attachWebInboxToSocket(options) {
|
|
|
972
1102
|
}
|
|
973
1103
|
return content;
|
|
974
1104
|
};
|
|
1105
|
+
const shouldSkipRecentOutboundEcho = (msg) => {
|
|
1106
|
+
const id = msg.key?.id ?? void 0;
|
|
1107
|
+
const remoteJid = msg.key?.remoteJid;
|
|
1108
|
+
if (!msg.key?.fromMe || !id || !remoteJid || !isRecentOutboundMessage({
|
|
1109
|
+
accountId: options.accountId,
|
|
1110
|
+
remoteJid,
|
|
1111
|
+
messageId: id
|
|
1112
|
+
})) return false;
|
|
1113
|
+
logWhatsAppVerbose(options.verbose, `Skipping recent outbound WhatsApp echo ${id} for ${remoteJid}`);
|
|
1114
|
+
return true;
|
|
1115
|
+
};
|
|
975
1116
|
const normalizeInboundMessage = async (msg) => {
|
|
976
1117
|
const id = msg.key?.id ?? void 0;
|
|
977
1118
|
const remoteJid = msg.key?.remoteJid;
|
|
978
1119
|
if (!remoteJid) return null;
|
|
979
1120
|
if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) return null;
|
|
980
1121
|
const group = isJidGroup(remoteJid) === true;
|
|
981
|
-
if (
|
|
982
|
-
accountId: options.accountId,
|
|
983
|
-
remoteJid,
|
|
984
|
-
messageId: id
|
|
985
|
-
})) {
|
|
986
|
-
logWhatsAppVerbose(options.verbose, `Skipping recent outbound WhatsApp echo ${id} for ${remoteJid}`);
|
|
987
|
-
return null;
|
|
988
|
-
}
|
|
1122
|
+
if (shouldSkipRecentOutboundEcho(msg)) return null;
|
|
989
1123
|
if (!hasInboundUserContent(msg.message ?? void 0)) return null;
|
|
990
1124
|
const participantJid = msg.key?.participant ?? void 0;
|
|
991
1125
|
const from = group ? remoteJid : await resolveInboundJid(remoteJid);
|
|
@@ -1061,15 +1195,6 @@ async function attachWebInboxToSocket(options) {
|
|
|
1061
1195
|
}
|
|
1062
1196
|
await maybeMarkInboundAsRead(target);
|
|
1063
1197
|
};
|
|
1064
|
-
const completeUndeliverableDurableInbound = async (durableId, metadata) => {
|
|
1065
|
-
if (!durableId) return;
|
|
1066
|
-
await durableInboundJournal.complete(durableId, metadata?.readReceipt ? { metadata: { readReceipt: metadata.readReceipt } } : void 0);
|
|
1067
|
-
};
|
|
1068
|
-
const buildDurableInboundPayload = (msg, upsertType) => ({
|
|
1069
|
-
message: serializeWhatsAppDurableInboundMessage(msg),
|
|
1070
|
-
...upsertType ? { upsertType } : {},
|
|
1071
|
-
receivedAt: Date.now()
|
|
1072
|
-
});
|
|
1073
1198
|
const shouldSkipStaleAppend = (msg, upsertType) => {
|
|
1074
1199
|
if (upsertType !== "append") return false;
|
|
1075
1200
|
const APPEND_RECENT_GRACE_MS = 6e4;
|
|
@@ -1078,86 +1203,25 @@ async function attachWebInboxToSocket(options) {
|
|
|
1078
1203
|
const nowMs = Date.now();
|
|
1079
1204
|
return msgTsMs < (options.appendReplyWindow && nowMs <= options.appendReplyWindow.untilMs ? Math.max(options.appendReplyWindow.afterMs, nowMs - options.appendReplyWindow.maxAgeMs) : connectedAtMs - APPEND_RECENT_GRACE_MS);
|
|
1080
1205
|
};
|
|
1081
|
-
const
|
|
1082
|
-
const inbound = await normalizeInboundMessage(msg);
|
|
1083
|
-
if (!inbound) {
|
|
1084
|
-
if (stored) await completeUndeliverableDurableInbound(stored.id, stored.metadata);
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
const readReceipt = stored?.metadata?.readReceipt ?? buildReadReceiptTarget(inbound);
|
|
1088
|
-
const deliveryReadReceipt = inbound.access.isSelfChat ? void 0 : readReceipt;
|
|
1089
|
-
if (!stored && shouldSkipStaleAppend(msg, upsertType)) {
|
|
1090
|
-
await maybeMarkNonSelfChatReadReceipt(inbound, readReceipt);
|
|
1091
|
-
return;
|
|
1092
|
-
}
|
|
1093
|
-
let durableId = stored?.id ?? (inbound.id ? createWhatsAppDurableInboundMessageId({
|
|
1094
|
-
remoteJid: inbound.remoteJid,
|
|
1095
|
-
id: inbound.id
|
|
1096
|
-
}) : void 0);
|
|
1097
|
-
const durableMetadata = deliveryReadReceipt ? { readReceipt: deliveryReadReceipt } : void 0;
|
|
1098
|
-
if (durableId && !stored) try {
|
|
1099
|
-
const accepted = await durableInboundJournal.accept(durableId, buildDurableInboundPayload(msg, upsertType), {
|
|
1100
|
-
metadata: durableMetadata,
|
|
1101
|
-
receivedAt: inbound.messageTimestampMs
|
|
1102
|
-
});
|
|
1103
|
-
if (accepted.kind === "completed") {
|
|
1104
|
-
await maybeMarkNonSelfChatReadReceipt(inbound, accepted.record.metadata?.readReceipt ?? deliveryReadReceipt);
|
|
1105
|
-
return;
|
|
1106
|
-
}
|
|
1107
|
-
if (accepted.kind === "pending" && accepted.record.attempts === 0) return;
|
|
1108
|
-
} catch (err) {
|
|
1109
|
-
durableId = void 0;
|
|
1110
|
-
const error = formatError(err);
|
|
1111
|
-
inboundLogger.warn({ error }, "failed persisting durable WhatsApp inbound; delivering live");
|
|
1112
|
-
inboundConsoleLog.warn(`Failed persisting durable WhatsApp inbound; delivering live: ${error}`);
|
|
1113
|
-
}
|
|
1114
|
-
const enriched = await enrichInboundMessage(msg);
|
|
1115
|
-
if (!enriched) {
|
|
1116
|
-
await completeUndeliverableDurableInbound(durableId, durableMetadata);
|
|
1117
|
-
await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
|
|
1118
|
-
return;
|
|
1119
|
-
}
|
|
1120
|
-
const dedupeKey = inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : "";
|
|
1121
|
-
const dedupeClaim = dedupeKey ? await whatsAppInboundReplayGuard.claim(dedupeKey) : { kind: "invalid" };
|
|
1122
|
-
if (dedupeClaim.kind === "duplicate" || dedupeClaim.kind === "inflight") {
|
|
1123
|
-
if (dedupeClaim.kind === "duplicate") {
|
|
1124
|
-
await completeUndeliverableDurableInbound(durableId, durableMetadata);
|
|
1125
|
-
await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
|
|
1126
|
-
}
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
recordAcceptedInboundActivity(options.accountId);
|
|
1130
|
-
await enqueueInboundMessage(msg, inbound, enriched, {
|
|
1131
|
-
durableId,
|
|
1132
|
-
readReceipt: deliveryReadReceipt,
|
|
1133
|
-
receiveOrder,
|
|
1134
|
-
...dedupeClaim.kind === "claimed" ? { replayClaim: dedupeClaim.handle } : {}
|
|
1135
|
-
});
|
|
1136
|
-
};
|
|
1137
|
-
const replayPendingDurableInboundMessages = async () => {
|
|
1138
|
-
const pending = await durableInboundJournal.pending();
|
|
1139
|
-
for (const record of pending) await processDurableInboundMessage(deserializeWhatsAppDurableInboundMessage(record.payload.message), record.payload.upsertType, record.payload.receivedAt, {
|
|
1140
|
-
id: record.id,
|
|
1141
|
-
payload: record.payload,
|
|
1142
|
-
metadata: record.metadata
|
|
1143
|
-
});
|
|
1144
|
-
};
|
|
1206
|
+
const preparedInboundByDurableId = /* @__PURE__ */ new Map();
|
|
1145
1207
|
const enrichInboundMessage = async (msg) => {
|
|
1146
1208
|
const location = extractLocationData(msg.message ?? void 0);
|
|
1147
1209
|
const locationText = location ? formatLocationText(location) : void 0;
|
|
1148
1210
|
const contactContext = extractContactContext(msg.message ?? void 0);
|
|
1149
1211
|
const externalAdReplyContext = extractExternalAdReplyContext(msg.message ?? void 0);
|
|
1150
|
-
|
|
1212
|
+
let mediaKind = extractMediaKind(msg.message ?? void 0);
|
|
1151
1213
|
let body = extractText(msg.message ?? void 0);
|
|
1152
1214
|
if (locationText) body = [body, locationText].filter(Boolean).join("\n").trim();
|
|
1153
|
-
if (!body)
|
|
1154
|
-
|
|
1155
|
-
if (!body) return null;
|
|
1156
|
-
}
|
|
1215
|
+
if (!body && !mediaKind) return null;
|
|
1216
|
+
body = body ?? "";
|
|
1157
1217
|
const commandBody = body;
|
|
1158
1218
|
const replyContext = describeReplyContext(msg.message);
|
|
1159
1219
|
let mediaPath;
|
|
1160
|
-
let mediaType;
|
|
1220
|
+
let mediaType = mediaKind ? resolveInboundMediaMimetype(msg.message) : void 0;
|
|
1221
|
+
const nativeMedia = mediaKind ? {
|
|
1222
|
+
contentType: mediaType,
|
|
1223
|
+
kind: mediaKind
|
|
1224
|
+
} : void 0;
|
|
1161
1225
|
let mediaFileName;
|
|
1162
1226
|
const maxBytes = (typeof options.mediaMaxMb === "number" && options.mediaMaxMb > 0 ? options.mediaMaxMb : 50) * 1024 * 1024;
|
|
1163
1227
|
const saveInboundMedia = async (inboundMedia) => {
|
|
@@ -1172,12 +1236,13 @@ async function attachWebInboxToSocket(options) {
|
|
|
1172
1236
|
logWhatsAppVerbose(options.verbose, `Inbound media download failed: ${String(err)}`);
|
|
1173
1237
|
body = formatInboundMediaUnavailableText({
|
|
1174
1238
|
body,
|
|
1175
|
-
mediaPlaceholder,
|
|
1176
1239
|
notice: "[whatsapp attachment unavailable]"
|
|
1177
1240
|
});
|
|
1178
1241
|
}
|
|
1179
|
-
if (!mediaPath && replyContext) try {
|
|
1242
|
+
if (!mediaPath && !mediaKind && replyContext?.media) try {
|
|
1180
1243
|
await saveInboundMedia(await downloadQuotedInboundMedia(msg, sock, maxBytes));
|
|
1244
|
+
mediaKind = replyContext.media.kind ?? void 0;
|
|
1245
|
+
mediaType = mediaType ?? replyContext.media.contentType ?? void 0;
|
|
1181
1246
|
} catch (err) {
|
|
1182
1247
|
logWhatsAppVerbose(options.verbose, `Quoted media download failed: ${String(err)}`);
|
|
1183
1248
|
body = formatInboundMediaUnavailableText({
|
|
@@ -1194,7 +1259,9 @@ async function attachWebInboxToSocket(options) {
|
|
|
1194
1259
|
replyContext,
|
|
1195
1260
|
mediaPath,
|
|
1196
1261
|
mediaType,
|
|
1197
|
-
mediaFileName
|
|
1262
|
+
mediaFileName,
|
|
1263
|
+
mediaKind,
|
|
1264
|
+
nativeMedia
|
|
1198
1265
|
};
|
|
1199
1266
|
};
|
|
1200
1267
|
const enqueueInboundMessage = async (msg, inbound, enriched, durable) => {
|
|
@@ -1229,10 +1296,11 @@ async function attachWebInboxToSocket(options) {
|
|
|
1229
1296
|
mediaFileName: enriched.mediaFileName,
|
|
1230
1297
|
timestamp
|
|
1231
1298
|
}, "inbound message");
|
|
1232
|
-
const media = enriched.mediaPath || enriched.mediaType || enriched.mediaFileName ? {
|
|
1299
|
+
const media = enriched.mediaPath || enriched.mediaType || enriched.mediaFileName || enriched.mediaKind ? {
|
|
1233
1300
|
path: enriched.mediaPath,
|
|
1234
1301
|
type: enriched.mediaType,
|
|
1235
|
-
fileName: enriched.mediaFileName
|
|
1302
|
+
fileName: enriched.mediaFileName,
|
|
1303
|
+
kind: enriched.mediaKind
|
|
1236
1304
|
} : void 0;
|
|
1237
1305
|
const groupMentions = mentionedJids ? { jids: mentionedJids } : void 0;
|
|
1238
1306
|
const group = inbound.group && (inbound.groupSubject || inbound.groupParticipants?.length || groupMentions) ? {
|
|
@@ -1240,17 +1308,26 @@ async function attachWebInboxToSocket(options) {
|
|
|
1240
1308
|
participants: inbound.groupParticipants,
|
|
1241
1309
|
mentions: groupMentions
|
|
1242
1310
|
} : void 0;
|
|
1243
|
-
const untrustedStructuredContext = [
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1311
|
+
const untrustedStructuredContext = [
|
|
1312
|
+
...enriched.nativeMedia ? [{
|
|
1313
|
+
label: "WhatsApp media",
|
|
1314
|
+
source: "whatsapp",
|
|
1315
|
+
type: "media",
|
|
1316
|
+
payload: enriched.nativeMedia
|
|
1317
|
+
}] : [],
|
|
1318
|
+
...enriched.contactContext ? [{
|
|
1319
|
+
label: "WhatsApp contact",
|
|
1320
|
+
source: "whatsapp",
|
|
1321
|
+
type: enriched.contactContext.kind,
|
|
1322
|
+
payload: enriched.contactContext
|
|
1323
|
+
}] : [],
|
|
1324
|
+
...enriched.externalAdReplyContext ? [{
|
|
1325
|
+
label: "WhatsApp external ad reply",
|
|
1326
|
+
source: "whatsapp",
|
|
1327
|
+
type: "external_ad_reply",
|
|
1328
|
+
payload: enriched.externalAdReplyContext
|
|
1329
|
+
}] : []
|
|
1330
|
+
];
|
|
1254
1331
|
const inboundMessage = withDeprecatedWebInboundMessageFlatAliases({
|
|
1255
1332
|
admission: inbound.access.admission,
|
|
1256
1333
|
event: {
|
|
@@ -1289,6 +1366,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1289
1366
|
context: enriched.replyContext,
|
|
1290
1367
|
id: enriched.replyContext.id,
|
|
1291
1368
|
body: enriched.replyContext.body,
|
|
1369
|
+
media: enriched.replyContext.media,
|
|
1292
1370
|
sender: {
|
|
1293
1371
|
displayName: enriched.replyContext.sender?.label ?? void 0,
|
|
1294
1372
|
jid: enriched.replyContext.sender?.jid ?? void 0,
|
|
@@ -1296,8 +1374,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1296
1374
|
}
|
|
1297
1375
|
} : void 0,
|
|
1298
1376
|
group,
|
|
1299
|
-
|
|
1300
|
-
durableId: durable.durableId,
|
|
1377
|
+
turnAdoptionLifecycle: durable.turnAdoptionLifecycle,
|
|
1301
1378
|
readReceipt: durable.readReceipt,
|
|
1302
1379
|
receiveOrder: durable.receiveOrder
|
|
1303
1380
|
});
|
|
@@ -1307,6 +1384,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1307
1384
|
if (inboundDebounceMs > 0 && shouldDebounceInboundMessage(inboundMessage)) {
|
|
1308
1385
|
pendingDebounceKeys.add(debounceKey);
|
|
1309
1386
|
publishPendingWorkState();
|
|
1387
|
+
notifyDebounceWork();
|
|
1310
1388
|
}
|
|
1311
1389
|
}
|
|
1312
1390
|
if (inboundMessage.event.id) {
|
|
@@ -1315,19 +1393,70 @@ async function attachWebInboxToSocket(options) {
|
|
|
1315
1393
|
participant: inboundMessage.platform.senderJid,
|
|
1316
1394
|
participantE164: admission.conversation.kind === "direct" ? inboundMessage.platform.senderE164 : void 0,
|
|
1317
1395
|
body: inboundMessage.payload.body,
|
|
1396
|
+
media: enriched.nativeMedia,
|
|
1318
1397
|
fromMe: inboundMessage.platform.fromMe
|
|
1319
1398
|
});
|
|
1320
1399
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1400
|
+
await debouncer.enqueue(inboundMessage);
|
|
1401
|
+
};
|
|
1402
|
+
const processDurableInboundMessage = async (msg, context) => {
|
|
1403
|
+
rememberBaileysMessage(msg.key?.remoteJid, msg.key?.id, msg.message);
|
|
1404
|
+
if (context.skipRecentOutboundEcho === true) return "completed";
|
|
1405
|
+
const preparation = context.eventId ? preparedInboundByDurableId.get(context.eventId) : void 0;
|
|
1406
|
+
if (context.eventId) preparedInboundByDurableId.delete(context.eventId);
|
|
1407
|
+
const prepared = await preparation;
|
|
1408
|
+
if (prepared === null) return "completed";
|
|
1409
|
+
const inbound = prepared ?? await normalizeInboundMessage(msg);
|
|
1410
|
+
if (!inbound) return "completed";
|
|
1411
|
+
if (await maybeResolveWhatsAppQuestionReaction({
|
|
1412
|
+
cfg: options.loadConfig?.() ?? options.cfg,
|
|
1413
|
+
accountId: options.accountId,
|
|
1414
|
+
msg,
|
|
1415
|
+
senderId: inbound.senderE164 ?? inbound.from,
|
|
1416
|
+
resolveReactionTargetJids,
|
|
1417
|
+
logDebug: (message) => logWhatsAppVerbose(options.verbose, message)
|
|
1418
|
+
})) return "completed";
|
|
1419
|
+
const readReceipt = buildReadReceiptTarget(inbound);
|
|
1420
|
+
const deliveryReadReceipt = inbound.access.isSelfChat ? void 0 : readReceipt;
|
|
1421
|
+
if (context.skipStaleAppend === true) {
|
|
1422
|
+
await maybeMarkNonSelfChatReadReceipt(inbound, readReceipt);
|
|
1423
|
+
return "completed";
|
|
1424
|
+
}
|
|
1425
|
+
const enriched = await enrichInboundMessage(msg);
|
|
1426
|
+
if (!enriched) {
|
|
1427
|
+
await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
|
|
1428
|
+
return "completed";
|
|
1329
1429
|
}
|
|
1430
|
+
recordAcceptedInboundActivity(options.accountId);
|
|
1431
|
+
await enqueueInboundMessage(msg, inbound, enriched, {
|
|
1432
|
+
readReceipt: deliveryReadReceipt,
|
|
1433
|
+
receiveOrder: context.receiveOrder ?? context.receivedAt,
|
|
1434
|
+
turnAdoptionLifecycle: context.lifecycle
|
|
1435
|
+
});
|
|
1436
|
+
return "deferred";
|
|
1330
1437
|
};
|
|
1438
|
+
const durableInboundMonitor = createWhatsAppIngressMonitor({
|
|
1439
|
+
queue: durableInboundQueue,
|
|
1440
|
+
dispatch: async (msg, payload, lifecycle) => {
|
|
1441
|
+
const remoteJid = msg.key?.remoteJid;
|
|
1442
|
+
const id = msg.key?.id;
|
|
1443
|
+
return { kind: await processDurableInboundMessage(msg, {
|
|
1444
|
+
...payload,
|
|
1445
|
+
...remoteJid && id ? { eventId: createWhatsAppDurableInboundMessageId({
|
|
1446
|
+
remoteJid,
|
|
1447
|
+
id
|
|
1448
|
+
}) } : {},
|
|
1449
|
+
lifecycle
|
|
1450
|
+
}) };
|
|
1451
|
+
},
|
|
1452
|
+
pollIntervalMs: WHATSAPP_INGRESS_DRAIN_INTERVAL_MS,
|
|
1453
|
+
onLog: (message) => inboundLogger.warn({ message }, "whatsapp ingress drain"),
|
|
1454
|
+
onError: (error) => inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed"),
|
|
1455
|
+
onActivityChange: (active) => {
|
|
1456
|
+
durableIngressActive = active;
|
|
1457
|
+
publishPendingWorkState();
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1331
1460
|
const handleMessagesUpsert = async (upsert) => {
|
|
1332
1461
|
if (upsert.type !== "notify" && upsert.type !== "append") return;
|
|
1333
1462
|
for (const msg of upsert.messages ?? []) {
|
|
@@ -1343,7 +1472,76 @@ async function attachWebInboxToSocket(options) {
|
|
|
1343
1472
|
resolveReactionTargetJids,
|
|
1344
1473
|
logVerboseMessage: (message) => logWhatsAppVerbose(options.verbose, message)
|
|
1345
1474
|
})) continue;
|
|
1346
|
-
|
|
1475
|
+
const receivedAt = Date.now();
|
|
1476
|
+
const skipStaleAppend = shouldSkipStaleAppend(msg, upsert.type);
|
|
1477
|
+
const skipRecentOutboundEcho = shouldSkipRecentOutboundEcho(msg);
|
|
1478
|
+
const remoteJid = msg.key?.remoteJid;
|
|
1479
|
+
const id = msg.key?.id;
|
|
1480
|
+
const durableId = remoteJid && id ? createWhatsAppDurableInboundMessageId({
|
|
1481
|
+
remoteJid,
|
|
1482
|
+
id
|
|
1483
|
+
}) : void 0;
|
|
1484
|
+
let resolvePrepared;
|
|
1485
|
+
if (durableId && !preparedInboundByDurableId.has(durableId)) {
|
|
1486
|
+
if (preparedInboundByDurableId.size >= 1e3) {
|
|
1487
|
+
const oldest = preparedInboundByDurableId.keys().next().value;
|
|
1488
|
+
if (oldest !== void 0) preparedInboundByDurableId.delete(oldest);
|
|
1489
|
+
}
|
|
1490
|
+
preparedInboundByDurableId.set(durableId, new Promise((resolve) => {
|
|
1491
|
+
resolvePrepared = resolve;
|
|
1492
|
+
}));
|
|
1493
|
+
}
|
|
1494
|
+
const finishPreparation = (inbound, keepForDrain = false) => {
|
|
1495
|
+
resolvePrepared?.(inbound);
|
|
1496
|
+
if (!keepForDrain && durableId && resolvePrepared) preparedInboundByDurableId.delete(durableId);
|
|
1497
|
+
};
|
|
1498
|
+
let result;
|
|
1499
|
+
let appendError;
|
|
1500
|
+
for (const delayMs of [
|
|
1501
|
+
0,
|
|
1502
|
+
100,
|
|
1503
|
+
300
|
|
1504
|
+
]) {
|
|
1505
|
+
if (delayMs > 0) await new Promise((resolve) => {
|
|
1506
|
+
setTimeout(resolve, delayMs);
|
|
1507
|
+
});
|
|
1508
|
+
try {
|
|
1509
|
+
result = await enqueueWhatsAppDurableInbound({
|
|
1510
|
+
queue: durableInboundQueue,
|
|
1511
|
+
message: msg,
|
|
1512
|
+
upsertType: upsert.type,
|
|
1513
|
+
skipStaleAppend,
|
|
1514
|
+
skipRecentOutboundEcho,
|
|
1515
|
+
receivedAt,
|
|
1516
|
+
receiveOrder
|
|
1517
|
+
});
|
|
1518
|
+
appendError = void 0;
|
|
1519
|
+
break;
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
appendError = error;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if (result === void 0) {
|
|
1525
|
+
finishPreparation(void 0);
|
|
1526
|
+
const formattedError = formatError(appendError);
|
|
1527
|
+
inboundLogger.error({ error: formattedError }, "failed persisting durable WhatsApp inbound after retries; message dropped");
|
|
1528
|
+
inboundConsoleLog.error(`Failed persisting durable WhatsApp inbound after retries; message dropped: ${formattedError}`);
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
if (result.kind === "completed") {
|
|
1532
|
+
finishPreparation(void 0);
|
|
1533
|
+
const inbound = await normalizeInboundMessage(msg);
|
|
1534
|
+
if (inbound) await maybeMarkNonSelfChatReadReceipt(inbound, buildReadReceiptTarget(inbound));
|
|
1535
|
+
} else {
|
|
1536
|
+
if (result.kind === "accepted") try {
|
|
1537
|
+
finishPreparation(skipRecentOutboundEcho ? null : await normalizeInboundMessage(msg), true);
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
finishPreparation(void 0);
|
|
1540
|
+
throw error;
|
|
1541
|
+
}
|
|
1542
|
+
else finishPreparation(void 0);
|
|
1543
|
+
durableInboundMonitor.requestDrain();
|
|
1544
|
+
}
|
|
1347
1545
|
}
|
|
1348
1546
|
};
|
|
1349
1547
|
const handleMessagesUpsertEvent = (upsert) => {
|
|
@@ -1358,9 +1556,6 @@ async function attachWebInboxToSocket(options) {
|
|
|
1358
1556
|
publishPendingWorkState();
|
|
1359
1557
|
});
|
|
1360
1558
|
};
|
|
1361
|
-
const waitForPendingMessageHandlers = async () => {
|
|
1362
|
-
while (pendingMessageHandlers.size > 0) await Promise.all(Array.from(pendingMessageHandlers));
|
|
1363
|
-
};
|
|
1364
1559
|
const drainDebouncedInboundMessages = async () => {
|
|
1365
1560
|
while (pendingDebounceKeys.size > 0 || activeInboundFlushes.size > 0) {
|
|
1366
1561
|
const debounceKeys = Array.from(pendingDebounceKeys);
|
|
@@ -1372,8 +1567,20 @@ async function attachWebInboxToSocket(options) {
|
|
|
1372
1567
|
};
|
|
1373
1568
|
const drainInboundBeforeSocketClose = async () => {
|
|
1374
1569
|
groupMetadataCacheClosed = true;
|
|
1375
|
-
|
|
1570
|
+
for (;;) {
|
|
1571
|
+
await drainDebouncedInboundMessages();
|
|
1572
|
+
if (pendingMessageHandlers.size === 0) break;
|
|
1573
|
+
const handlers = Array.from(pendingMessageHandlers);
|
|
1574
|
+
await Promise.race([Promise.allSettled(handlers), waitForDebounceWorkOrIdle(handlers)]);
|
|
1575
|
+
if (pendingMessageHandlers.size === 0 && pendingDebounceKeys.size === 0 && activeInboundFlushes.size === 0) break;
|
|
1576
|
+
}
|
|
1376
1577
|
await drainDebouncedInboundMessages();
|
|
1578
|
+
for (;;) {
|
|
1579
|
+
await durableInboundMonitor.waitForIdle();
|
|
1580
|
+
if (pendingDebounceKeys.size === 0 && activeInboundFlushes.size === 0) break;
|
|
1581
|
+
await drainDebouncedInboundMessages();
|
|
1582
|
+
}
|
|
1583
|
+
await durableInboundMonitor.stop();
|
|
1377
1584
|
};
|
|
1378
1585
|
const drainInboundBeforeSocketCloseWithTimeout = async () => {
|
|
1379
1586
|
let timeout = null;
|
|
@@ -1386,6 +1593,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1386
1593
|
})]);
|
|
1387
1594
|
} finally {
|
|
1388
1595
|
if (timeout) clearTimeout(timeout);
|
|
1596
|
+
durableInboundMonitor.stop();
|
|
1389
1597
|
}
|
|
1390
1598
|
};
|
|
1391
1599
|
const handleConnectionUpdate = (update) => {
|
|
@@ -1444,16 +1652,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1444
1652
|
const detachGroupParticipantsUpdate = attachSockListener("group-participants.update", ((update) => {
|
|
1445
1653
|
forgetFullGroupMetadata(update.id);
|
|
1446
1654
|
}));
|
|
1447
|
-
|
|
1448
|
-
inboundLogger.error({ error: String(err) }, "failed replaying durable WhatsApp inbound");
|
|
1449
|
-
inboundConsoleLog.error(`Failed replaying durable WhatsApp inbound: ${String(err)}`);
|
|
1450
|
-
});
|
|
1451
|
-
pendingMessageHandlers.add(replayTask);
|
|
1452
|
-
publishPendingWorkState();
|
|
1453
|
-
replayTask.finally(() => {
|
|
1454
|
-
pendingMessageHandlers.delete(replayTask);
|
|
1455
|
-
publishPendingWorkState();
|
|
1456
|
-
});
|
|
1655
|
+
durableInboundMonitor.start();
|
|
1457
1656
|
(async () => {
|
|
1458
1657
|
try {
|
|
1459
1658
|
const groups = await sock.groupFetchAllParticipating();
|
|
@@ -1511,7 +1710,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1511
1710
|
};
|
|
1512
1711
|
}
|
|
1513
1712
|
async function monitorWebInbox(options) {
|
|
1514
|
-
const socketTiming = options.socketTiming ?? resolveWhatsAppSocketTiming(
|
|
1713
|
+
const socketTiming = options.socketTiming ?? resolveWhatsAppSocketTiming();
|
|
1515
1714
|
const recentMessageKeys = options.recentMessageKeys ?? /* @__PURE__ */ new Map();
|
|
1516
1715
|
const baileysGroupMetaCache = options.baileysGroupMetaCache ?? /* @__PURE__ */ new Map();
|
|
1517
1716
|
const sock = await createWaSocket(false, options.verbose, {
|
|
@@ -1569,12 +1768,13 @@ function isBotMentionedFromTargets(msg, mentionCfg, targets) {
|
|
|
1569
1768
|
const isGroupConversation = requireWhatsAppInboundAdmission(msg).conversation.kind === "group";
|
|
1570
1769
|
const isSelfChat = explicitSelfChatOverride ? Boolean(mentionCfg.isSelfChat) : isSelfChatMode(targets.self.e164, mentionCfg.allowFrom) && !isGroupConversation;
|
|
1571
1770
|
const hasMentions = targets.normalizedMentions.length > 0;
|
|
1572
|
-
|
|
1771
|
+
const hasNativeMentionsOutsideSelfChat = hasMentions && !isSelfChat;
|
|
1772
|
+
if (hasNativeMentionsOutsideSelfChat) {
|
|
1573
1773
|
for (const mention of targets.normalizedMentions) if (identitiesOverlap(targets.self, mention)) return true;
|
|
1574
|
-
return false;
|
|
1575
1774
|
} else if (hasMentions && isSelfChat) {}
|
|
1576
1775
|
const bodyClean = clean(msg.payload.body);
|
|
1577
1776
|
if (mentionCfg.mentionRegexes.some((re) => re.test(bodyClean))) return true;
|
|
1777
|
+
if (hasNativeMentionsOutsideSelfChat) return false;
|
|
1578
1778
|
if (targets.self.e164) {
|
|
1579
1779
|
const selfDigits = targets.self.e164.replace(/\D/g, "");
|
|
1580
1780
|
if (selfDigits) {
|
|
@@ -1755,8 +1955,7 @@ function formatWhatsAppInboundListeningLog(account) {
|
|
|
1755
1955
|
const DEFAULT_WHATSAPP_ACK_REACTION = "👀";
|
|
1756
1956
|
function resolveWhatsAppAckEmoji(params) {
|
|
1757
1957
|
if (!params.ackConfig) return "";
|
|
1758
|
-
|
|
1759
|
-
return resolveAgentIdentityEmoji(params.cfg, params.agentId) ?? DEFAULT_WHATSAPP_ACK_REACTION;
|
|
1958
|
+
return (typeof params.ackConfig === "string" ? params.ackConfig : params.ackConfig.emoji)?.trim() || resolveAgentIdentityEmoji(params.cfg, params.agentId) || DEFAULT_WHATSAPP_ACK_REACTION;
|
|
1760
1959
|
}
|
|
1761
1960
|
function resolveAgentIdentityEmoji(cfg, agentId) {
|
|
1762
1961
|
return resolveAgentIdentity(cfg, agentId)?.emoji?.trim() || void 0;
|
|
@@ -1809,6 +2008,12 @@ async function resolveGroupActivationFor(params) {
|
|
|
1809
2008
|
return normalizeGroupActivation(activation) ?? defaultActivation;
|
|
1810
2009
|
}
|
|
1811
2010
|
//#endregion
|
|
2011
|
+
//#region extensions/whatsapp/src/auto-reply/monitor/reaction-participant.ts
|
|
2012
|
+
function resolveReactionParticipant(msg) {
|
|
2013
|
+
const sender = getSenderIdentity(msg);
|
|
2014
|
+
return sender.jid ?? sender.lid ?? void 0;
|
|
2015
|
+
}
|
|
2016
|
+
//#endregion
|
|
1812
2017
|
//#region extensions/whatsapp/src/auto-reply/monitor/ack-reaction.ts
|
|
1813
2018
|
async function maybeSendAckReaction(params) {
|
|
1814
2019
|
if (!params.msg.event.id) return null;
|
|
@@ -1818,14 +2023,16 @@ async function maybeSendAckReaction(params) {
|
|
|
1818
2023
|
cfg: params.cfg,
|
|
1819
2024
|
accountId
|
|
1820
2025
|
}).level === "off") return null;
|
|
1821
|
-
const ackConfig = params.cfg.
|
|
2026
|
+
const ackConfig = params.cfg.messages?.ackReaction;
|
|
2027
|
+
const scope = params.cfg.messages?.ackReactionScope ?? "group-mentions";
|
|
2028
|
+
if (scope === "off" || scope === "none") return null;
|
|
1822
2029
|
const emoji = resolveWhatsAppAckEmoji({
|
|
1823
2030
|
cfg: params.cfg,
|
|
1824
2031
|
agentId: params.agentId,
|
|
1825
2032
|
ackConfig
|
|
1826
2033
|
});
|
|
1827
|
-
const directEnabled =
|
|
1828
|
-
const groupMode =
|
|
2034
|
+
const directEnabled = scope === "all" || scope === "direct";
|
|
2035
|
+
const groupMode = scope === "all" || scope === "group-all" ? "always" : scope === "direct" ? "never" : "mentions";
|
|
1829
2036
|
const isGroup = admission.conversation.kind === "group";
|
|
1830
2037
|
const conversationIdForCheck = admission.conversation.id;
|
|
1831
2038
|
const activation = isGroup ? await resolveGroupActivationFor({
|
|
@@ -1850,11 +2057,11 @@ async function maybeSendAckReaction(params) {
|
|
|
1850
2057
|
messageId: params.msg.event.id,
|
|
1851
2058
|
emoji
|
|
1852
2059
|
}, "sending ack reaction");
|
|
1853
|
-
const
|
|
2060
|
+
const participant = resolveReactionParticipant(params.msg);
|
|
1854
2061
|
const reactionOptions = {
|
|
1855
2062
|
verbose: params.verbose,
|
|
1856
2063
|
fromMe: false,
|
|
1857
|
-
...
|
|
2064
|
+
...participant ? { participant } : {},
|
|
1858
2065
|
accountId,
|
|
1859
2066
|
cfg: params.cfg
|
|
1860
2067
|
};
|
|
@@ -2024,7 +2231,13 @@ function recordPendingGroupHistoryEntry(params) {
|
|
|
2024
2231
|
body: params.body ?? params.msg.payload.body,
|
|
2025
2232
|
timestamp: params.msg.event.timestamp,
|
|
2026
2233
|
id: params.msg.event.id,
|
|
2027
|
-
senderJid: senderIdentity.jid ?? params.msg.platform.senderJid
|
|
2234
|
+
senderJid: senderIdentity.jid ?? params.msg.platform.senderJid,
|
|
2235
|
+
...params.body === void 0 && params.msg.payload.media ? { media: [{
|
|
2236
|
+
path: params.msg.payload.media.path,
|
|
2237
|
+
url: params.msg.payload.media.url ?? params.msg.payload.media.path,
|
|
2238
|
+
contentType: params.msg.payload.media.type,
|
|
2239
|
+
kind: params.msg.payload.media.kind ?? void 0
|
|
2240
|
+
}] } : {}
|
|
2028
2241
|
}
|
|
2029
2242
|
});
|
|
2030
2243
|
}
|
|
@@ -2360,7 +2573,7 @@ async function deliverWebReply(params) {
|
|
|
2360
2573
|
const tableMode = params.tableMode ?? "code";
|
|
2361
2574
|
const chunkMode = params.chunkMode ?? "length";
|
|
2362
2575
|
const normalizedReply = params.normalizedReplyResult ?? normalizeWhatsAppOutboundPayload(replyResult, { normalizeText: normalizeWhatsAppPayloadTextPreservingIndentation });
|
|
2363
|
-
const textChunks =
|
|
2576
|
+
const textChunks = markdownToWhatsAppChunks(normalizedReply.text ?? "", textLimit, tableMode, chunkMode);
|
|
2364
2577
|
const mediaList = normalizedReply.mediaUrls ?? [];
|
|
2365
2578
|
const getQuote = () => {
|
|
2366
2579
|
if (!replyResult.replyToId) return;
|
|
@@ -2370,7 +2583,8 @@ async function deliverWebReply(params) {
|
|
|
2370
2583
|
remoteJid: msg.platform.chatJid,
|
|
2371
2584
|
fromMe: cached?.fromMe ?? false,
|
|
2372
2585
|
participant: cached?.participant ?? (isGroupConversation ? msg.platform.senderJid : void 0),
|
|
2373
|
-
messageText: cached?.body ?? ""
|
|
2586
|
+
messageText: cached?.body ?? "",
|
|
2587
|
+
media: cached?.media
|
|
2374
2588
|
});
|
|
2375
2589
|
};
|
|
2376
2590
|
const sendWithRetry = async (fn, label) => {
|
|
@@ -2418,7 +2632,7 @@ async function deliverWebReply(params) {
|
|
|
2418
2632
|
mediaUrls: mediaList,
|
|
2419
2633
|
caption: remainingText.shift() || "",
|
|
2420
2634
|
send: async ({ mediaUrl, caption }) => {
|
|
2421
|
-
const media = await prepareWhatsAppOutboundMedia(await loadWebMedia
|
|
2635
|
+
const media = await prepareWhatsAppOutboundMedia(await loadWebMedia(mediaUrl, {
|
|
2422
2636
|
maxBytes: maxMediaBytes,
|
|
2423
2637
|
localRoots: params.mediaLocalRoots
|
|
2424
2638
|
}), mediaUrl);
|
|
@@ -2505,32 +2719,47 @@ function isWhatsAppSupplementalSenderAllowed(params) {
|
|
|
2505
2719
|
return false;
|
|
2506
2720
|
}
|
|
2507
2721
|
function resolveVisibleWhatsAppGroupHistory(params) {
|
|
2508
|
-
if (params.groupPolicy !== "allowlist") return params.history;
|
|
2509
2722
|
return filterSupplementalContextItems({
|
|
2510
2723
|
items: params.history,
|
|
2511
2724
|
mode: params.mode,
|
|
2512
2725
|
kind: "history",
|
|
2513
|
-
isSenderAllowed: (entry) =>
|
|
2726
|
+
isSenderAllowed: (entry) => resolveInboundSupplementalSenderAllowed({
|
|
2727
|
+
isGroup: true,
|
|
2728
|
+
groupPolicy: params.groupPolicy,
|
|
2514
2729
|
allowFrom: params.groupAllowFrom,
|
|
2515
|
-
|
|
2516
|
-
|
|
2730
|
+
isSenderAllowed: (allowFrom) => isWhatsAppSupplementalSenderAllowed({
|
|
2731
|
+
allowFrom,
|
|
2732
|
+
authDir: params.authDir,
|
|
2733
|
+
sender: entry.senderJid ? { jid: entry.senderJid } : null
|
|
2734
|
+
})
|
|
2517
2735
|
})
|
|
2518
2736
|
}).items;
|
|
2519
2737
|
}
|
|
2520
2738
|
function resolveVisibleWhatsAppReplyContext(params) {
|
|
2521
2739
|
const replyTo = getReplyContext(params.msg, params.authDir);
|
|
2522
2740
|
if (!replyTo) return null;
|
|
2523
|
-
const
|
|
2741
|
+
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
2742
|
+
const previewBody = [replyTo.body, formatMediaPlaceholderText(replyTo.media ? [replyTo.media] : [])].filter(Boolean).join("\n");
|
|
2743
|
+
const senderAllowed = resolveInboundSupplementalSenderAllowed({
|
|
2744
|
+
isGroup: admission.conversation.kind === "group",
|
|
2745
|
+
groupPolicy: params.groupPolicy,
|
|
2524
2746
|
allowFrom: params.groupAllowFrom,
|
|
2525
|
-
|
|
2526
|
-
|
|
2747
|
+
isSenderAllowed: (allowFrom) => isWhatsAppSupplementalSenderAllowed({
|
|
2748
|
+
allowFrom,
|
|
2749
|
+
authDir: params.authDir,
|
|
2750
|
+
sender: replyTo.sender
|
|
2751
|
+
})
|
|
2527
2752
|
});
|
|
2528
|
-
|
|
2753
|
+
const visible = filterChannelInboundQuoteContext(params.mode, {
|
|
2529
2754
|
id: replyTo.id,
|
|
2530
|
-
body:
|
|
2755
|
+
body: previewBody,
|
|
2531
2756
|
sender: replyTo.sender?.label ?? void 0,
|
|
2532
2757
|
senderAllowed
|
|
2533
|
-
})
|
|
2758
|
+
});
|
|
2759
|
+
return visible ? {
|
|
2760
|
+
...replyTo,
|
|
2761
|
+
body: visible.body ?? ""
|
|
2762
|
+
} : null;
|
|
2534
2763
|
}
|
|
2535
2764
|
//#endregion
|
|
2536
2765
|
//#region extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts
|
|
@@ -2639,7 +2868,7 @@ function logWhatsAppMediaOnlyFlushResult(result) {
|
|
|
2639
2868
|
}
|
|
2640
2869
|
function resolveWhatsAppResponsePrefix(params) {
|
|
2641
2870
|
const configuredResponsePrefix = params.cfg.messages?.responsePrefix;
|
|
2642
|
-
return params.pipelineResponsePrefix ?? (configuredResponsePrefix ===
|
|
2871
|
+
return params.pipelineResponsePrefix ?? (configuredResponsePrefix === "auto" ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : configuredResponsePrefix) ?? (params.isSelfChat ? resolveIdentityNamePrefix$1(params.cfg, params.agentId) : void 0);
|
|
2643
2872
|
}
|
|
2644
2873
|
async function buildWhatsAppInboundContext(params) {
|
|
2645
2874
|
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
@@ -2651,14 +2880,16 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2651
2880
|
sender: entry.sender,
|
|
2652
2881
|
body: entry.body,
|
|
2653
2882
|
timestamp: entry.timestamp,
|
|
2654
|
-
messageId: entry.id
|
|
2883
|
+
messageId: entry.id,
|
|
2884
|
+
media: entry.media
|
|
2655
2885
|
})),
|
|
2656
2886
|
limit: params.groupHistory?.length ?? 1
|
|
2657
2887
|
}) : void 0;
|
|
2658
|
-
const media = toInboundMediaFacts(params.msg.payload.media
|
|
2888
|
+
const media = toInboundMediaFacts(params.msg.payload.media ? [{
|
|
2659
2889
|
path: params.msg.payload.media?.path,
|
|
2660
2890
|
url: params.msg.payload.media?.url ?? params.msg.payload.media?.path,
|
|
2661
|
-
contentType: params.msg.payload.media?.type
|
|
2891
|
+
contentType: params.msg.payload.media?.type,
|
|
2892
|
+
kind: params.msg.payload.media?.kind
|
|
2662
2893
|
}] : void 0, { transcribed: (_entry, index) => params.mediaTranscribedIndexes?.includes(index) === true });
|
|
2663
2894
|
return buildChannelInboundEventContext({
|
|
2664
2895
|
channel: "whatsapp",
|
|
@@ -2686,6 +2917,7 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2686
2917
|
},
|
|
2687
2918
|
route: {
|
|
2688
2919
|
agentId: params.route.agentId,
|
|
2920
|
+
dmScope: params.route.dmScope,
|
|
2689
2921
|
accountId: params.route.accountId,
|
|
2690
2922
|
routeSessionKey: params.route.sessionKey
|
|
2691
2923
|
},
|
|
@@ -2700,6 +2932,7 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2700
2932
|
rawBody: params.rawBody ?? params.msg.payload.body,
|
|
2701
2933
|
commandBody: params.command?.body ?? params.msg.payload.body
|
|
2702
2934
|
},
|
|
2935
|
+
sessionTranscript: { historyLimit: conversationKind === "group" ? params.groupHistoryLimit ?? params.groupHistory?.length ?? 0 : 0 },
|
|
2703
2936
|
access: {
|
|
2704
2937
|
...wasMentioned !== void 0 ? { mentions: {
|
|
2705
2938
|
canDetectMention: conversationKind === "group",
|
|
@@ -2754,16 +2987,12 @@ function updateWhatsAppMainLastRoute(params) {
|
|
|
2754
2987
|
}
|
|
2755
2988
|
if (params.dmRouteTarget && inboundLastRouteSessionKey === params.route.mainSessionKey && params.pinnedMainDmRecipient) logVerbose$1(`Skipping main-session last route update for ${params.dmRouteTarget} (pinned owner ${params.pinnedMainDmRecipient})`);
|
|
2756
2989
|
}
|
|
2757
|
-
|
|
2990
|
+
function createWhatsAppReplyPlan(params) {
|
|
2758
2991
|
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
2992
|
+
const ingressLifecycle = resolveWhatsAppIngressLifecycle(params.msg);
|
|
2759
2993
|
const conversationId = admission.conversation.id;
|
|
2760
2994
|
const conversationKind = admission.conversation.kind;
|
|
2761
2995
|
const statusReactionController = params.statusReactionController ?? null;
|
|
2762
|
-
const statusReactionTiming = {
|
|
2763
|
-
...DEFAULT_TIMING,
|
|
2764
|
-
...params.cfg.messages?.statusReactions?.timing
|
|
2765
|
-
};
|
|
2766
|
-
const removeAckAfterReply = params.cfg.messages?.removeAckAfterReply ?? false;
|
|
2767
2996
|
const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp");
|
|
2768
2997
|
const chunkMode = resolveChunkMode(params.cfg, "whatsapp", params.route.accountId);
|
|
2769
2998
|
const tableMode = resolveMarkdownTableMode$1({
|
|
@@ -2821,11 +3050,10 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2821
3050
|
const mediaOnlyCoalescer = createWhatsAppMediaOnlyReplyCoalescer({ deliver: async (pending) => {
|
|
2822
3051
|
return await deliverNormalizedPayload(pending.payload, pending.info);
|
|
2823
3052
|
} });
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
replyResolver: params.replyResolver,
|
|
3053
|
+
return {
|
|
3054
|
+
afterRecord: () => {
|
|
3055
|
+
if (statusReactionController) statusReactionController.setThinking();
|
|
3056
|
+
},
|
|
2829
3057
|
dispatcherOptions: {
|
|
2830
3058
|
...params.replyPipeline,
|
|
2831
3059
|
onHeartbeatStrip: () => {
|
|
@@ -2834,6 +3062,14 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2834
3062
|
logVerbose$1("Stripped stray HEARTBEAT_OK token from web reply");
|
|
2835
3063
|
}
|
|
2836
3064
|
},
|
|
3065
|
+
onSettled: async () => {
|
|
3066
|
+
const flushResult = await mediaOnlyCoalescer.flushAll();
|
|
3067
|
+
logWhatsAppMediaOnlyFlushResult(flushResult);
|
|
3068
|
+
return whatsAppReplyDeliveryVisibility(flushResult.delivered > 0);
|
|
3069
|
+
},
|
|
3070
|
+
onReplyStart: params.msg.platform.sendComposing
|
|
3071
|
+
},
|
|
3072
|
+
delivery: {
|
|
2837
3073
|
deliver: async (payload, info) => {
|
|
2838
3074
|
const deliveryPayload = resolveWhatsAppDeliverablePayload(payload, info);
|
|
2839
3075
|
if (!deliveryPayload) return whatsAppReplyDeliveryVisibility(false);
|
|
@@ -2884,8 +3120,8 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2884
3120
|
return whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
|
|
2885
3121
|
}
|
|
2886
3122
|
if (durable.status === "handled_no_send") return flushResult.delivered > 0 ? whatsAppReplyDeliveryVisibility(true) : whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
|
|
2887
|
-
const
|
|
2888
|
-
return flushResult.delivered > 0 && !
|
|
3123
|
+
const deliveryResult = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
|
|
3124
|
+
return flushResult.delivered > 0 && !deliveryResult.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : deliveryResult;
|
|
2889
3125
|
} catch (error) {
|
|
2890
3126
|
throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
|
|
2891
3127
|
}
|
|
@@ -2906,27 +3142,12 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2906
3142
|
const flushResult = await mediaOnlyCoalescer.flushExceptDuplicateMedia(mediaUrls);
|
|
2907
3143
|
logWhatsAppMediaOnlyFlushResult(flushResult);
|
|
2908
3144
|
try {
|
|
2909
|
-
const
|
|
2910
|
-
return flushResult.delivered > 0 && !
|
|
3145
|
+
const deliveryResult = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
|
|
3146
|
+
return flushResult.delivered > 0 && !deliveryResult.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : deliveryResult;
|
|
2911
3147
|
} catch (error) {
|
|
2912
3148
|
throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
|
|
2913
3149
|
}
|
|
2914
3150
|
},
|
|
2915
|
-
onSettled: async () => {
|
|
2916
|
-
const flushResult = await mediaOnlyCoalescer.flushAll();
|
|
2917
|
-
logWhatsAppMediaOnlyFlushResult(flushResult);
|
|
2918
|
-
return whatsAppReplyDeliveryVisibility(flushResult.delivered > 0);
|
|
2919
|
-
},
|
|
2920
|
-
onReplyStart: params.msg.platform.sendComposing,
|
|
2921
|
-
...statusReactionController ? {
|
|
2922
|
-
onCompactionStart: async () => {
|
|
2923
|
-
await statusReactionController.setCompacting();
|
|
2924
|
-
},
|
|
2925
|
-
onCompactionEnd: async () => {
|
|
2926
|
-
statusReactionController.cancelPending();
|
|
2927
|
-
await statusReactionController.setThinking();
|
|
2928
|
-
}
|
|
2929
|
-
} : {},
|
|
2930
3151
|
onError: (err, info) => {
|
|
2931
3152
|
logWhatsAppReplyDeliveryError({
|
|
2932
3153
|
err,
|
|
@@ -2938,64 +3159,50 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2938
3159
|
}
|
|
2939
3160
|
},
|
|
2940
3161
|
replyOptions: {
|
|
3162
|
+
...ingressLifecycle ? bindIngressLifecycleToReplyOptions(ingressLifecycle) : {},
|
|
2941
3163
|
suppressTyping: sourceRepliesAreToolOnly && conversationKind === "group" && !(params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned),
|
|
2942
3164
|
disableBlockStreaming,
|
|
2943
3165
|
...sourceReplyDeliveryMode ? { sourceReplyDeliveryMode } : {},
|
|
2944
3166
|
onModelSelected: params.onModelSelected,
|
|
2945
|
-
...statusReactionController ? {
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3167
|
+
...statusReactionController ? {
|
|
3168
|
+
onToolStart: async (payload) => {
|
|
3169
|
+
const toolName = payload.name?.trim();
|
|
3170
|
+
if (toolName) await statusReactionController.setTool(toolName);
|
|
3171
|
+
},
|
|
3172
|
+
onCompactionStart: async () => {
|
|
3173
|
+
await statusReactionController.setCompacting();
|
|
3174
|
+
},
|
|
3175
|
+
onCompactionEnd: async () => {
|
|
3176
|
+
statusReactionController.cancelPending();
|
|
3177
|
+
await statusReactionController.setThinking();
|
|
3178
|
+
}
|
|
3179
|
+
} : {}
|
|
3180
|
+
},
|
|
3181
|
+
replyResolver: params.replyResolver,
|
|
3182
|
+
finalize: (dispatchResult) => {
|
|
3183
|
+
const didQueueVisibleReply = hasVisibleInboundReplyDispatch(dispatchResult);
|
|
3184
|
+
const didDeliverVisibleReply = didSendReply || dispatchResult.observedReplyDelivery === true;
|
|
3185
|
+
if (!didQueueVisibleReply) {
|
|
3186
|
+
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
3187
|
+
controller: statusReactionController,
|
|
3188
|
+
outcome: "error"
|
|
3189
|
+
});
|
|
3190
|
+
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
3191
|
+
logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
|
|
3192
|
+
return false;
|
|
3193
|
+
}
|
|
3194
|
+
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
3195
|
+
controller: statusReactionController,
|
|
3196
|
+
outcome: didDeliverVisibleReply ? "done" : "error"
|
|
3197
|
+
});
|
|
3198
|
+
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
3199
|
+
return didDeliverVisibleReply;
|
|
2949
3200
|
}
|
|
2950
|
-
}
|
|
2951
|
-
const didQueueVisibleReply = hasVisibleInboundReplyDispatch(dispatchResult);
|
|
2952
|
-
const didDeliverVisibleReply = didSendReply || dispatchResult.observedReplyDelivery === true;
|
|
2953
|
-
if (!didQueueVisibleReply) {
|
|
2954
|
-
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
2955
|
-
controller: statusReactionController,
|
|
2956
|
-
outcome: "error",
|
|
2957
|
-
hasFinalResponse: false,
|
|
2958
|
-
removeAckAfterReply,
|
|
2959
|
-
timing: statusReactionTiming
|
|
2960
|
-
});
|
|
2961
|
-
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
2962
|
-
logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
|
|
2963
|
-
return false;
|
|
2964
|
-
}
|
|
2965
|
-
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
2966
|
-
controller: statusReactionController,
|
|
2967
|
-
outcome: didDeliverVisibleReply ? "done" : "error",
|
|
2968
|
-
hasFinalResponse: didDeliverVisibleReply,
|
|
2969
|
-
removeAckAfterReply,
|
|
2970
|
-
timing: statusReactionTiming
|
|
2971
|
-
});
|
|
2972
|
-
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
2973
|
-
return didDeliverVisibleReply;
|
|
3201
|
+
};
|
|
2974
3202
|
}
|
|
2975
3203
|
async function finalizeWhatsAppStatusReaction(params) {
|
|
2976
|
-
if (params.outcome === "done")
|
|
2977
|
-
|
|
2978
|
-
if (params.removeAckAfterReply) {
|
|
2979
|
-
await new Promise((resolve) => {
|
|
2980
|
-
setTimeout(resolve, params.timing.doneHoldMs);
|
|
2981
|
-
});
|
|
2982
|
-
await params.controller.clear();
|
|
2983
|
-
} else await params.controller.restoreInitial();
|
|
2984
|
-
return;
|
|
2985
|
-
}
|
|
2986
|
-
await params.controller.setError();
|
|
2987
|
-
if (params.hasFinalResponse) {
|
|
2988
|
-
if (params.removeAckAfterReply) {
|
|
2989
|
-
await new Promise((resolve) => {
|
|
2990
|
-
setTimeout(resolve, params.timing.errorHoldMs);
|
|
2991
|
-
});
|
|
2992
|
-
await params.controller.clear();
|
|
2993
|
-
} else await params.controller.restoreInitial();
|
|
2994
|
-
return;
|
|
2995
|
-
}
|
|
2996
|
-
if (params.removeAckAfterReply) await new Promise((resolve) => {
|
|
2997
|
-
setTimeout(resolve, params.timing.errorHoldMs);
|
|
2998
|
-
});
|
|
3204
|
+
if (params.outcome === "done") await params.controller.setDone();
|
|
3205
|
+
else await params.controller.setError();
|
|
2999
3206
|
await params.controller.restoreInitial();
|
|
3000
3207
|
}
|
|
3001
3208
|
//#endregion
|
|
@@ -3009,7 +3216,7 @@ function resolveIdentityNamePrefix(cfg, agentId) {
|
|
|
3009
3216
|
return identityName ? `[${identityName}]` : void 0;
|
|
3010
3217
|
}
|
|
3011
3218
|
function resolveMessagePrefix(cfg, agentId, opts) {
|
|
3012
|
-
const configured = opts?.configured
|
|
3219
|
+
const configured = opts?.configured;
|
|
3013
3220
|
if (configured !== void 0) return configured;
|
|
3014
3221
|
if (opts?.hasAllowFrom === true) return "";
|
|
3015
3222
|
return resolveIdentityNamePrefix(cfg, agentId) ?? opts?.fallback ?? "[openclaw]";
|
|
@@ -3026,7 +3233,7 @@ function formatReplyContext(msg) {
|
|
|
3026
3233
|
function buildInboundLine(params) {
|
|
3027
3234
|
const { cfg, msg, agentId, previousTimestamp, envelope } = params;
|
|
3028
3235
|
const messagePrefix = resolveMessagePrefix(cfg, agentId, {
|
|
3029
|
-
configured: cfg.channels?.whatsapp?.
|
|
3236
|
+
configured: cfg.channels?.whatsapp?.responsePrefix,
|
|
3030
3237
|
hasAllowFrom: (cfg.channels?.whatsapp?.allowFrom?.length ?? 0) > 0
|
|
3031
3238
|
});
|
|
3032
3239
|
const admission = requireWhatsAppInboundAdmission(msg);
|
|
@@ -3056,23 +3263,24 @@ function buildInboundLine(params) {
|
|
|
3056
3263
|
//#region extensions/whatsapp/src/auto-reply/monitor/status-reaction.ts
|
|
3057
3264
|
async function createWhatsAppStatusReactionController(params) {
|
|
3058
3265
|
if (!params.msg.event.id) return null;
|
|
3059
|
-
|
|
3060
|
-
if (statusReactionsConfig?.enabled !== true) return null;
|
|
3266
|
+
if ((params.cfg.messages?.statusReactions)?.enabled !== true) return null;
|
|
3061
3267
|
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
3062
3268
|
const accountId = admission.accountId;
|
|
3063
3269
|
if (resolveWhatsAppReactionLevel({
|
|
3064
3270
|
cfg: params.cfg,
|
|
3065
3271
|
accountId
|
|
3066
3272
|
}).level === "off") return null;
|
|
3067
|
-
const ackConfig = params.cfg.
|
|
3273
|
+
const ackConfig = params.cfg.messages?.ackReaction;
|
|
3274
|
+
const scope = params.cfg.messages?.ackReactionScope ?? "group-mentions";
|
|
3275
|
+
if (scope === "off" || scope === "none") return null;
|
|
3068
3276
|
const ackEmoji = resolveWhatsAppAckEmoji({
|
|
3069
3277
|
cfg: params.cfg,
|
|
3070
3278
|
agentId: params.agentId,
|
|
3071
3279
|
ackConfig
|
|
3072
3280
|
});
|
|
3073
3281
|
if (!ackEmoji) return null;
|
|
3074
|
-
const directEnabled =
|
|
3075
|
-
const groupMode =
|
|
3282
|
+
const directEnabled = scope === "all" || scope === "direct";
|
|
3283
|
+
const groupMode = scope === "all" || scope === "group-all" ? "always" : scope === "direct" ? "never" : "mentions";
|
|
3076
3284
|
const isGroup = admission.conversation.kind === "group";
|
|
3077
3285
|
const conversationIdForCheck = admission.conversation.id;
|
|
3078
3286
|
const activation = isGroup ? await resolveGroupActivationFor({
|
|
@@ -3091,11 +3299,11 @@ async function createWhatsAppStatusReactionController(params) {
|
|
|
3091
3299
|
wasMentioned: (params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned) === true,
|
|
3092
3300
|
groupActivated: activation === "always"
|
|
3093
3301
|
})) return null;
|
|
3094
|
-
const
|
|
3302
|
+
const participant = resolveReactionParticipant(params.msg);
|
|
3095
3303
|
const reactionOptions = {
|
|
3096
3304
|
verbose: params.verbose,
|
|
3097
3305
|
fromMe: false,
|
|
3098
|
-
...
|
|
3306
|
+
...participant ? { participant } : {},
|
|
3099
3307
|
accountId,
|
|
3100
3308
|
cfg: params.cfg
|
|
3101
3309
|
};
|
|
@@ -3112,8 +3320,7 @@ async function createWhatsAppStatusReactionController(params) {
|
|
|
3112
3320
|
}
|
|
3113
3321
|
},
|
|
3114
3322
|
initialEmoji: ackEmoji,
|
|
3115
|
-
emojis:
|
|
3116
|
-
timing: statusReactionsConfig.timing,
|
|
3323
|
+
emojis: void 0,
|
|
3117
3324
|
onError: (err) => {
|
|
3118
3325
|
logVerbose(`WhatsApp status-reaction error for chat ${chatId}/${msgId}: ${String(err)}`);
|
|
3119
3326
|
}
|
|
@@ -3182,13 +3389,16 @@ async function processMessage(params) {
|
|
|
3182
3389
|
sessionKey: params.route.sessionKey
|
|
3183
3390
|
});
|
|
3184
3391
|
let audioTranscript = params.preflightAudioTranscript ?? void 0;
|
|
3185
|
-
const hasAudioBody = params.msg.payload.media?.type?.startsWith("audio/") === true && params.msg.payload.body
|
|
3392
|
+
const hasAudioBody = (params.msg.payload.media?.kind === "audio" || params.msg.payload.media?.type?.startsWith("audio/") === true) && !params.msg.payload.body.trim();
|
|
3186
3393
|
if (params.preflightAudioTranscript === void 0 && hasAudioBody && params.msg.payload.media?.path) try {
|
|
3187
3394
|
const { transcribeFirstAudio } = await import("./audio-preflight.runtime-C_glQhZY.js");
|
|
3188
3395
|
audioTranscript = await transcribeFirstAudio({
|
|
3189
3396
|
ctx: {
|
|
3190
|
-
|
|
3191
|
-
|
|
3397
|
+
media: [{
|
|
3398
|
+
path: params.msg.payload.media.path,
|
|
3399
|
+
contentType: params.msg.payload.media.type,
|
|
3400
|
+
kind: params.msg.payload.media.kind ?? void 0
|
|
3401
|
+
}],
|
|
3192
3402
|
From: conversationId,
|
|
3193
3403
|
To: params.msg.platform.recipientJid,
|
|
3194
3404
|
Provider: "whatsapp",
|
|
@@ -3200,7 +3410,7 @@ async function processMessage(params) {
|
|
|
3200
3410
|
cfg: params.cfg
|
|
3201
3411
|
});
|
|
3202
3412
|
} catch {
|
|
3203
|
-
if (shouldLogVerbose$1()) logVerbose$1("whatsapp: audio preflight transcription failed,
|
|
3413
|
+
if (shouldLogVerbose$1()) logVerbose$1("whatsapp: audio preflight transcription failed, keeping structured audio");
|
|
3204
3414
|
}
|
|
3205
3415
|
const msgForAgent = audioTranscript !== void 0 ? {
|
|
3206
3416
|
...params.msg,
|
|
@@ -3238,7 +3448,8 @@ async function processMessage(params) {
|
|
|
3238
3448
|
entries: history.map((m) => ({
|
|
3239
3449
|
sender: m.sender,
|
|
3240
3450
|
body: m.body,
|
|
3241
|
-
timestamp: m.timestamp
|
|
3451
|
+
timestamp: m.timestamp,
|
|
3452
|
+
media: m.media
|
|
3242
3453
|
})),
|
|
3243
3454
|
currentMessage: combinedBody,
|
|
3244
3455
|
excludeLast: false,
|
|
@@ -3247,7 +3458,7 @@ async function processMessage(params) {
|
|
|
3247
3458
|
channel: "WhatsApp",
|
|
3248
3459
|
from: conversationId,
|
|
3249
3460
|
timestamp: entry.timestamp,
|
|
3250
|
-
body: entry.body,
|
|
3461
|
+
body: [entry.body, formatMediaPlaceholderText(entry.media ?? [])].filter(Boolean).join("\n"),
|
|
3251
3462
|
chatType: "group",
|
|
3252
3463
|
senderLabel: entry.sender,
|
|
3253
3464
|
envelope: envelopeOptions
|
|
@@ -3341,6 +3552,7 @@ async function processMessage(params) {
|
|
|
3341
3552
|
authorized: commandAuthorized
|
|
3342
3553
|
},
|
|
3343
3554
|
groupHistory: visibleGroupHistory,
|
|
3555
|
+
groupHistoryLimit: params.groupHistoryLimit,
|
|
3344
3556
|
groupMemberRoster: params.groupMemberNames.get(params.groupHistoryKey),
|
|
3345
3557
|
groupSystemPrompt: conversationSystemPrompt,
|
|
3346
3558
|
msg: params.msg,
|
|
@@ -3377,6 +3589,7 @@ async function processMessage(params) {
|
|
|
3377
3589
|
updateLastRoute: updateLastRouteInBackground,
|
|
3378
3590
|
warn: params.replyLogger.warn.bind(params.replyLogger)
|
|
3379
3591
|
});
|
|
3592
|
+
let finalizeReply;
|
|
3380
3593
|
const turnResult = await runChannelInboundEvent({
|
|
3381
3594
|
channel: "whatsapp",
|
|
3382
3595
|
accountId: params.route.accountId,
|
|
@@ -3410,26 +3623,8 @@ async function processMessage(params) {
|
|
|
3410
3623
|
recordHistory: false
|
|
3411
3624
|
} };
|
|
3412
3625
|
},
|
|
3413
|
-
resolveTurn: () =>
|
|
3414
|
-
|
|
3415
|
-
accountId: params.route.accountId,
|
|
3416
|
-
routeSessionKey: params.route.sessionKey,
|
|
3417
|
-
storePath,
|
|
3418
|
-
ctxPayload,
|
|
3419
|
-
recordInboundSession,
|
|
3420
|
-
record: {
|
|
3421
|
-
onRecordError: (err) => {
|
|
3422
|
-
params.replyLogger.warn({
|
|
3423
|
-
error: formatError(err),
|
|
3424
|
-
storePath,
|
|
3425
|
-
sessionKey: params.route.sessionKey
|
|
3426
|
-
}, "failed updating session meta");
|
|
3427
|
-
},
|
|
3428
|
-
trackSessionMetaTask: (task) => {
|
|
3429
|
-
trackBackgroundTask(params.backgroundTasks, task);
|
|
3430
|
-
}
|
|
3431
|
-
},
|
|
3432
|
-
runDispatch: () => dispatchWhatsAppBufferedReply({
|
|
3626
|
+
resolveTurn: () => {
|
|
3627
|
+
const { finalize, ...replyPlan } = createWhatsAppReplyPlan({
|
|
3433
3628
|
cfg: params.cfg,
|
|
3434
3629
|
connectionId: params.connectionId,
|
|
3435
3630
|
context: ctxPayload,
|
|
@@ -3450,13 +3645,37 @@ async function processMessage(params) {
|
|
|
3450
3645
|
route: params.route,
|
|
3451
3646
|
shouldClearGroupHistory,
|
|
3452
3647
|
statusReactionController
|
|
3453
|
-
})
|
|
3454
|
-
|
|
3648
|
+
});
|
|
3649
|
+
finalizeReply = finalize;
|
|
3650
|
+
return {
|
|
3651
|
+
cfg: params.cfg,
|
|
3652
|
+
channel: "whatsapp",
|
|
3653
|
+
accountId: params.route.accountId,
|
|
3654
|
+
route: {
|
|
3655
|
+
agentId: params.route.agentId,
|
|
3656
|
+
sessionKey: params.route.sessionKey
|
|
3657
|
+
},
|
|
3658
|
+
ctxPayload,
|
|
3659
|
+
record: {
|
|
3660
|
+
onRecordError: (err) => {
|
|
3661
|
+
params.replyLogger.warn({
|
|
3662
|
+
error: formatError(err),
|
|
3663
|
+
storePath,
|
|
3664
|
+
sessionKey: params.route.sessionKey
|
|
3665
|
+
}, "failed updating session meta");
|
|
3666
|
+
},
|
|
3667
|
+
trackSessionMetaTask: (task) => {
|
|
3668
|
+
trackBackgroundTask(params.backgroundTasks, task);
|
|
3669
|
+
}
|
|
3670
|
+
},
|
|
3671
|
+
...replyPlan
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3455
3674
|
}
|
|
3456
3675
|
});
|
|
3457
|
-
const didSendReply = turnResult.dispatched ? turnResult.dispatchResult : false;
|
|
3676
|
+
const didSendReply = turnResult.dispatched ? finalizeReply?.(turnResult.dispatchResult) ?? false : false;
|
|
3458
3677
|
removeAckReactionHandleAfterReply({
|
|
3459
|
-
removeAfterReply:
|
|
3678
|
+
removeAfterReply: false,
|
|
3460
3679
|
ackReaction,
|
|
3461
3680
|
onError: (err) => {
|
|
3462
3681
|
logAckFailure({
|
|
@@ -3496,6 +3715,7 @@ function createWebOnMessageHandler(params) {
|
|
|
3496
3715
|
route,
|
|
3497
3716
|
groupHistoryKey,
|
|
3498
3717
|
groupHistories: params.groupHistories,
|
|
3718
|
+
groupHistoryLimit: params.groupHistoryLimit,
|
|
3499
3719
|
groupMemberNames: params.groupMemberNames,
|
|
3500
3720
|
connectionId: params.connectionId,
|
|
3501
3721
|
verbose: params.verbose,
|
|
@@ -3563,7 +3783,7 @@ function createWebOnMessageHandler(params) {
|
|
|
3563
3783
|
peerId
|
|
3564
3784
|
}) : route.sessionKey;
|
|
3565
3785
|
let preflightAudioTranscript;
|
|
3566
|
-
const hasAudioBody = msg.payload.media?.type?.startsWith("audio/") === true && msg.payload.body
|
|
3786
|
+
const hasAudioBody = (msg.payload.media?.kind === "audio" || msg.payload.media?.type?.startsWith("audio/") === true) && !msg.payload.body.trim();
|
|
3567
3787
|
const canRunEarlyAudioPreflight = conversationKind === "group" || canRunDirectEarlyAudioPreflight;
|
|
3568
3788
|
let ackAlreadySent = false;
|
|
3569
3789
|
let ackReaction = null;
|
|
@@ -3589,8 +3809,11 @@ function createWebOnMessageHandler(params) {
|
|
|
3589
3809
|
const { transcribeFirstAudio } = await import("./audio-preflight.runtime-C_glQhZY.js");
|
|
3590
3810
|
preflightAudioTranscript = await transcribeFirstAudio({
|
|
3591
3811
|
ctx: {
|
|
3592
|
-
|
|
3593
|
-
|
|
3812
|
+
media: [{
|
|
3813
|
+
path: msg.payload.media.path,
|
|
3814
|
+
contentType: msg.payload.media.type,
|
|
3815
|
+
kind: msg.payload.media.kind ?? void 0
|
|
3816
|
+
}],
|
|
3594
3817
|
From: conversationId,
|
|
3595
3818
|
To: msg.platform.recipientJid,
|
|
3596
3819
|
Provider: "whatsapp",
|
|
@@ -3761,8 +3984,7 @@ function resolveWebMonitorConfigSnapshot(params) {
|
|
|
3761
3984
|
...params.cfg.channels,
|
|
3762
3985
|
whatsapp: {
|
|
3763
3986
|
...params.cfg.channels?.whatsapp,
|
|
3764
|
-
|
|
3765
|
-
messagePrefix: account.messagePrefix,
|
|
3987
|
+
responsePrefix: account.messagePrefix,
|
|
3766
3988
|
allowFrom: account.allowFrom,
|
|
3767
3989
|
groupAllowFrom: account.groupAllowFrom,
|
|
3768
3990
|
groupPolicy: account.groupPolicy,
|
|
@@ -3798,6 +4020,7 @@ function isRetryableAuthUnstableError(error) {
|
|
|
3798
4020
|
return error instanceof WhatsAppAuthUnstableError || typeof error === "object" && error !== null && "code" in error && error.code === "whatsapp-auth-unstable";
|
|
3799
4021
|
}
|
|
3800
4022
|
const DEFAULT_TRANSPORT_TIMEOUT_MS = 300 * 1e3;
|
|
4023
|
+
const WHATSAPP_RECONNECT_CATCH_UP_MAX_MS = 20 * 6e4;
|
|
3801
4024
|
async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSocket, keepAlive = true, replyResolver, runtime = defaultRuntime, abortSignal, tuning = {}) {
|
|
3802
4025
|
const activeReplyResolver = replyResolver ?? (await loadReplyResolverRuntime()).getReplyFromConfig;
|
|
3803
4026
|
const runId = newConnectionId();
|
|
@@ -3826,7 +4049,7 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
3826
4049
|
const maxMediaBytes = resolveWhatsAppMediaMaxBytes(account);
|
|
3827
4050
|
const heartbeatSeconds = resolveHeartbeatSeconds(cfg, tuning.heartbeatSeconds);
|
|
3828
4051
|
const reconnectPolicy = resolveReconnectPolicy(cfg, tuning.reconnect);
|
|
3829
|
-
const socketTiming = resolveWhatsAppSocketTiming(
|
|
4052
|
+
const socketTiming = resolveWhatsAppSocketTiming(tuning.socketTiming);
|
|
3830
4053
|
const baseMentionConfig = buildMentionConfig(cfg);
|
|
3831
4054
|
const groupHistoryLimit = account.historyLimit ?? cfg.channels?.whatsapp?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT;
|
|
3832
4055
|
const groupHistories = /* @__PURE__ */ new Map();
|
|
@@ -3849,7 +4072,7 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
3849
4072
|
process.once("SIGINT", handleSigint);
|
|
3850
4073
|
const transportTimeoutMs = tuning.transportTimeoutMs ?? DEFAULT_TRANSPORT_TIMEOUT_MS;
|
|
3851
4074
|
const messageTimeoutMs = tuning.messageTimeoutMs ?? 1800 * 1e3;
|
|
3852
|
-
const reconnectCatchUpWindowMs = Math.min(Math.max(messageTimeoutMs, 6e4),
|
|
4075
|
+
const reconnectCatchUpWindowMs = Math.min(Math.max(messageTimeoutMs, 6e4), WHATSAPP_RECONNECT_CATCH_UP_MAX_MS);
|
|
3853
4076
|
const watchdogCheckMs = tuning.watchdogCheckMs ?? 60 * 1e3;
|
|
3854
4077
|
const controller = new WhatsAppConnectionController({
|
|
3855
4078
|
accountId: account.accountId,
|
|
@@ -4225,4 +4448,4 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
4225
4448
|
}
|
|
4226
4449
|
}
|
|
4227
4450
|
//#endregion
|
|
4228
|
-
export { loadWebMediaRaw as a, monitorWebInbox as c, loadWebMedia
|
|
4451
|
+
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 };
|