@openclaw/whatsapp 2026.7.2-beta.1 → 2026.7.2-beta.3
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-tjy_em01.js} +1 -1
- package/dist/{accounts-CKOFMPf-.js → accounts-ejK8sXPU.js} +43 -3
- package/dist/action-runtime-api.js +1 -1
- package/dist/{action-runtime-Bf-12TIH.js → action-runtime-p0V390gA.js} +2 -2
- package/dist/action-runtime.runtime.js +1 -1
- package/dist/{active-listener-5kHap439.js → active-listener-BbdlXooT.js} +1 -1
- package/dist/api.js +10 -10
- package/dist/{approval-handler.runtime-CBiGQuZ5.js → approval-handler.runtime-BO1oJ-b8.js} +3 -3
- package/dist/{approval-reactions-BGQoz25s.js → approval-reactions-CrZFAyf-.js} +2 -2
- package/dist/call-tool-api.js +2 -2
- package/dist/{channel-D_1cK7-w.js → channel-B2I4Opzb.js} +30 -28
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-react-action-PoRWfP9x.js → channel-react-action-CPRQE-s3.js} +3 -3
- package/dist/{channel.runtime-BPrKzcYd.js → channel.runtime-CjkhQzk8.js} +4 -4
- package/dist/{channel.setup-eX64WZDx.js → channel.setup-BMWE9LmN.js} +2 -2
- package/dist/{connection-controller-C1rwfWb4.js → connection-controller-HGaffeFb.js} +183 -22
- package/dist/{connection-controller-runtime-context-CbTsqmPT.js → connection-controller-runtime-context-9mEeAvu1.js} +10 -1
- package/dist/contract-api.js +1 -1
- package/dist/directory-config-BZ775eGX.js +196 -0
- package/dist/directory-contract-api.js +1 -1
- package/dist/{group-session-key-CLTZeH_F.js → group-session-key-Dh8WZun_.js} +1 -1
- package/dist/legacy-state-migrations-api.js +1 -1
- package/dist/light-runtime-api.js +1 -1
- package/dist/{login-DGIoToX8.js → login-ifOXCBjo.js} +3 -3
- package/dist/{login-qr-X-CZHOk9.js → login-qr-DUHzVH_Y.js} +4 -4
- package/dist/login-qr-runtime.js +1 -1
- package/dist/{monitor-BKtA0lzq.js → monitor-_jqDKBXj.js} +586 -382
- package/dist/question-reactions-C0qJ6wGA.js +142 -0
- package/dist/runtime-api.js +7 -7
- package/dist/{send-D9Ydnn4o.js → send-SB-4JrVZ.js} +2 -3
- package/dist/{send-api-W33lI2nR.js → send-api-BG0MgoFI.js} +3 -3
- package/dist/{setup-core-BXDFGqky.js → setup-core-BbIgEl2M.js} +3 -3
- package/dist/{setup-finalize-DzOHXnPj.js → setup-finalize-Cc-4iWGj.js} +3 -3
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-5EUmMLHS.js → setup-surface-DhnXEZir.js} +2 -2
- package/dist/{session-mxWFn0eS.js → socket-close-jNGyZY4T.js} +221 -1
- package/dist/{state-migrations-D_BmQUR9.js → state-migrations-CAMTrs2U.js} +1 -8
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
- package/dist/account-config-LiT9wWH9.js +0 -43
- package/dist/constants.js +0 -2
- package/dist/directory-config-CLBQrjtC.js +0 -37
- package/dist/targets.js +0 -2
|
@@ -1,31 +1,31 @@
|
|
|
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-
|
|
1
|
+
import { a as resolveWhatsAppAccount, s as resolveWhatsAppMediaMaxBytes } from "./accounts-ejK8sXPU.js";
|
|
2
|
+
import { a as normalizeWhatsAppOutboundPayload, c as prepareWhatsAppOutboundMedia, r as sendReactionWhatsApp, s as normalizeWhatsAppPayloadTextPreservingIndentation, u as resolveWhatsAppReactionLevel } from "./send-SB-4JrVZ.js";
|
|
3
3
|
import { n as getWhatsAppRuntime } from "./runtime-BfAdAEYT.js";
|
|
4
|
-
import {
|
|
4
|
+
import { r as getWhatsAppConnectionController } from "./connection-controller-runtime-context-9mEeAvu1.js";
|
|
5
5
|
import { a as resolveEquivalentWhatsAppDirectChatJids, c as toWhatsappJidWithLid, i as markdownToWhatsApp, n as isSelfChatMode, o as resolveJidToE164, r as jidToE164, s as toWhatsappJid } from "./targets-runtime-Bsh0HR9y.js";
|
|
6
6
|
import { n as normalizeE164, t as convertMarkdownTables$1 } from "./text-runtime-BTGkMN0o.js";
|
|
7
|
-
import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-
|
|
7
|
+
import { a as cacheInboundMessageMeta, i as buildQuotedMessageOptions, n as resolveWhatsAppGroupSessionRoute, o as lookupInboundMessageMeta, r as resolveWhatsAppLegacyGroupSessionKey } from "./group-session-key-Dh8WZun_.js";
|
|
8
8
|
import { g as readWebSelfId, o as getWebAuthAgeMs, r as WhatsAppAuthUnstableError, v as readWebSelfIdentityForDecision } from "./auth-store-BQi9pZqQ.js";
|
|
9
9
|
import { a as getSelfIdentity, c as resolveComparableIdentity, i as getReplyContext, n as getMentionIdentities, o as getSenderIdentity, r as getPrimaryIdentityId, s as identitiesOverlap, t as getComparableIdentityValues } from "./identity-D7L8LCuH.js";
|
|
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-jNGyZY4T.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-tjy_em01.js";
|
|
15
|
+
import { _ as extractLocationData, a as mayContainWhatsAppOutboundMention, b as extractText, c as DisconnectReason, d as normalizeMessageContent, f as resolveInboundMediaMimetype, g as extractExternalAdReplyContext, h as extractContextInfo, i as addWhatsAppOutboundMentionsToContent, l as downloadMediaMessage, m as extractContactContext, n as listWhatsAppSendResultMessageIds, o as resolveWhatsAppOutboundMentions, p as describeReplyContext, r as normalizeWhatsAppSendResult, s as addWhatsAppImagePreviewFields, t as createWebSendApi, u as isJidGroup, v as extractMediaPlaceholder, x as hasInboundUserContent, y as extractMentionedJids } from "./send-api-BG0MgoFI.js";
|
|
16
|
+
import { c as computeBackoff, d as resolveReconnectPolicy, f as sleepWithAbort, l as newConnectionId, n as WHATSAPP_WATCHDOG_TIMEOUT_ERROR, r as WhatsAppConnectionController, s as DEFAULT_RECONNECT_POLICY, u as resolveHeartbeatSeconds } from "./connection-controller-HGaffeFb.js";
|
|
17
|
+
import { n as maybeResolveWhatsAppApprovalReaction } from "./approval-reactions-CrZFAyf-.js";
|
|
18
|
+
import { t as maybeResolveWhatsAppQuestionReaction } from "./question-reactions-C0qJ6wGA.js";
|
|
18
19
|
import { resolveAccountEntry } from "openclaw/plugin-sdk/account-core";
|
|
19
|
-
import { normalizeLowercaseStringOrEmpty, normalizeStringEntries
|
|
20
|
+
import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
20
21
|
import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
|
|
21
22
|
import { DEFAULT_TIMING, createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
|
|
22
23
|
import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
|
|
23
|
-
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 { DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions, createChannelIngressDrain, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, deliverInboundReplyWithMessageSendContext, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
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";
|
|
@@ -34,20 +34,17 @@ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
|
34
34
|
import { chunkMarkdownTextWithMode } from "openclaw/plugin-sdk/reply-chunking";
|
|
35
35
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, DEFAULT_MAIN_KEY, buildAgentMainSessionKey, buildAgentSessionKey, buildGroupHistoryKey, deriveLastRoutePolicy, normalizeAccountId as normalizeAccountId$1, normalizeAgentId, resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
36
36
|
import { asDateTimestampMs, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
37
|
-
import {
|
|
37
|
+
import { createHash } from "node:crypto";
|
|
38
38
|
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, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
39
|
+
import { buildChannelInboundEventContext, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, hasVisibleInboundReplyDispatch, resolveInboundSessionEnvelopeContext, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
40
40
|
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
41
|
-
import {
|
|
42
|
-
import { dispatchReplyWithBufferedBlockDispatcher, finalizeInboundContext, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
|
41
|
+
import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
|
43
42
|
import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
44
43
|
import { createInboundDebouncer, resolveInboundDebounceMs } from "openclaw/plugin-sdk/channel-inbound-debounce";
|
|
45
|
-
import { hasControlCommand, isControlCommandMessage, isControlCommandMessage as isControlCommandMessage$1, shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-detection";
|
|
46
44
|
import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runtime";
|
|
47
45
|
import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, buildInboundHistoryFromEntries, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
48
46
|
import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
49
47
|
import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
|
|
50
|
-
import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
51
48
|
import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
|
|
52
49
|
import { getRuntimeConfig as getRuntimeConfig$1, getRuntimeConfigSourceSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
53
50
|
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
@@ -56,60 +53,11 @@ import { createConnectedChannelStatusPatch, createTransportActivityStatusPatch }
|
|
|
56
53
|
import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute } from "openclaw/plugin-sdk/conversation-binding-runtime";
|
|
57
54
|
import { resolveAgentIdentity, resolveIdentityNamePrefix as resolveIdentityNamePrefix$1 } from "openclaw/plugin-sdk/agent-runtime";
|
|
58
55
|
import { normalizeGroupActivation, parseActivationCommand } from "openclaw/plugin-sdk/group-activation";
|
|
56
|
+
import { hasControlCommand, isControlCommandMessage, shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-detection";
|
|
59
57
|
import { createInternalHookEvent, deriveInboundMessageHookContext, fireAndForgetBoundedHook, toInternalMessageReceivedContext, toPluginMessageContext, toPluginMessageReceivedEvent, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
|
|
60
58
|
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
61
59
|
import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
|
|
62
|
-
import {
|
|
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 claimableInboundMessages = createClaimableDedupe({
|
|
69
|
-
ttlMs: WHATSAPP_INBOUND_DEDUPE_TTL_MS,
|
|
70
|
-
memoryMaxSize: RECENT_WEB_MESSAGE_MAX
|
|
71
|
-
});
|
|
72
|
-
const recentOutboundMessages = createDedupeCache({
|
|
73
|
-
ttlMs: RECENT_OUTBOUND_MESSAGE_TTL_MS,
|
|
74
|
-
maxSize: RECENT_OUTBOUND_MESSAGE_MAX
|
|
75
|
-
});
|
|
76
|
-
var WhatsAppRetryableInboundError = class extends Error {
|
|
77
|
-
constructor(message, options) {
|
|
78
|
-
super(message, options);
|
|
79
|
-
this.name = "WhatsAppRetryableInboundError";
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
function buildMessageKey(params) {
|
|
83
|
-
const accountId = params.accountId.trim();
|
|
84
|
-
const remoteJid = params.remoteJid.trim();
|
|
85
|
-
const messageId = params.messageId.trim();
|
|
86
|
-
if (!accountId || !remoteJid || !messageId || messageId === "unknown") return null;
|
|
87
|
-
return `${accountId}:${remoteJid}:${messageId}`;
|
|
88
|
-
}
|
|
89
|
-
function resetWebInboundDedupe() {
|
|
90
|
-
claimableInboundMessages.clearMemory();
|
|
91
|
-
recentOutboundMessages.clear();
|
|
92
|
-
}
|
|
93
|
-
async function claimRecentInboundMessageDelivery(key) {
|
|
94
|
-
return (await claimableInboundMessages.claim(key)).kind;
|
|
95
|
-
}
|
|
96
|
-
async function commitRecentInboundMessage(key) {
|
|
97
|
-
await claimableInboundMessages.commit(key);
|
|
98
|
-
}
|
|
99
|
-
function releaseRecentInboundMessage(key, error) {
|
|
100
|
-
claimableInboundMessages.release(key, { error });
|
|
101
|
-
}
|
|
102
|
-
function rememberRecentOutboundMessage(params) {
|
|
103
|
-
const key = buildMessageKey(params);
|
|
104
|
-
if (!key) return;
|
|
105
|
-
recentOutboundMessages.check(key);
|
|
106
|
-
}
|
|
107
|
-
function isRecentOutboundMessage(params) {
|
|
108
|
-
const key = buildMessageKey(params);
|
|
109
|
-
if (!key) return false;
|
|
110
|
-
return recentOutboundMessages.peek(key);
|
|
111
|
-
}
|
|
112
|
-
//#endregion
|
|
60
|
+
import { createChannelApiRetryRunner } from "openclaw/plugin-sdk/retry-runtime";
|
|
113
61
|
//#region extensions/whatsapp/src/inbound/message-aliases.ts
|
|
114
62
|
function normalizeQuoteSender(sender) {
|
|
115
63
|
if (!sender?.displayName && !sender?.jid && !sender?.e164) return;
|
|
@@ -434,9 +382,63 @@ function normalizeWebInboundMessage(msg) {
|
|
|
434
382
|
return normalizeLegacyFlatWebInboundMessage(msg);
|
|
435
383
|
}
|
|
436
384
|
//#endregion
|
|
385
|
+
//#region extensions/whatsapp/src/inbound/dedupe.ts
|
|
386
|
+
const recentOutboundMessages = createDedupeCache({
|
|
387
|
+
ttlMs: 20 * 6e4,
|
|
388
|
+
maxSize: 5e3
|
|
389
|
+
});
|
|
390
|
+
function buildMessageKey(params) {
|
|
391
|
+
const accountId = params.accountId.trim();
|
|
392
|
+
const remoteJid = params.remoteJid.trim();
|
|
393
|
+
const messageId = params.messageId.trim();
|
|
394
|
+
if (!accountId || !remoteJid || !messageId || messageId === "unknown") return null;
|
|
395
|
+
return `${accountId}:${remoteJid}:${messageId}`;
|
|
396
|
+
}
|
|
397
|
+
function resetWebInboundDedupe() {
|
|
398
|
+
recentOutboundMessages.clear();
|
|
399
|
+
}
|
|
400
|
+
function rememberRecentOutboundMessage(params) {
|
|
401
|
+
const key = buildMessageKey(params);
|
|
402
|
+
if (!key) return;
|
|
403
|
+
recentOutboundMessages.check(key);
|
|
404
|
+
}
|
|
405
|
+
function isRecentOutboundMessage(params) {
|
|
406
|
+
const key = buildMessageKey(params);
|
|
407
|
+
if (!key) return false;
|
|
408
|
+
return recentOutboundMessages.peek(key);
|
|
409
|
+
}
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region extensions/whatsapp/src/inbound/durable-payload.ts
|
|
412
|
+
var WhatsAppIngressPermanentError = class extends Error {
|
|
413
|
+
constructor(reason, message, options) {
|
|
414
|
+
super(message, options);
|
|
415
|
+
this.reason = reason;
|
|
416
|
+
this.name = "WhatsAppIngressPermanentError";
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
function serializeWhatsAppDurableInboundMessage(message) {
|
|
420
|
+
const timestamp = message.messageTimestamp;
|
|
421
|
+
let serializedMessage = message;
|
|
422
|
+
if (timestamp != null && typeof timestamp === "object") try {
|
|
423
|
+
const numericTimestamp = Number(timestamp);
|
|
424
|
+
if (Number.isFinite(numericTimestamp)) serializedMessage = {
|
|
425
|
+
...message,
|
|
426
|
+
messageTimestamp: numericTimestamp
|
|
427
|
+
};
|
|
428
|
+
} catch {}
|
|
429
|
+
return JSON.parse(JSON.stringify(serializedMessage, BufferJSON.replacer));
|
|
430
|
+
}
|
|
431
|
+
function deserializeWhatsAppDurableInboundMessage(message) {
|
|
432
|
+
try {
|
|
433
|
+
return JSON.parse(JSON.stringify(message), BufferJSON.reviver);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
throw new WhatsAppIngressPermanentError("invalid-payload", "WhatsApp ingress row contains an invalid serialized message", { cause: error });
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
//#endregion
|
|
437
439
|
//#region extensions/whatsapp/src/inbound/durable-receive.ts
|
|
438
440
|
const WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES = 450;
|
|
439
|
-
const WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES =
|
|
441
|
+
const WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES = 5e3;
|
|
440
442
|
const WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
441
443
|
const WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS = 10080 * 60 * 1e3;
|
|
442
444
|
function hashNamespacePart(value) {
|
|
@@ -445,31 +447,91 @@ function hashNamespacePart(value) {
|
|
|
445
447
|
function createWhatsAppDurableInboundMessageId(params) {
|
|
446
448
|
return createHash("sha256").update(`${params.remoteJid}\n${params.id}`).digest("hex");
|
|
447
449
|
}
|
|
448
|
-
function
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
return
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const runtime = getWhatsAppRuntime();
|
|
457
|
-
return createDurableInboundReceiveJournalFromQueue({
|
|
458
|
-
queue: runtime.state.openChannelIngressQueue({
|
|
459
|
-
accountId: accountPart,
|
|
460
|
-
stateDir: runtime.state.resolveStateDir()
|
|
450
|
+
function inspectWhatsAppIngressMessage(message) {
|
|
451
|
+
const remoteJid = message.key?.remoteJid?.trim();
|
|
452
|
+
const id = message.key?.id?.trim();
|
|
453
|
+
if (!remoteJid || !id) throw new WhatsAppIngressPermanentError("missing-message-key", "WhatsApp ingress message is missing key.remoteJid or key.id");
|
|
454
|
+
return {
|
|
455
|
+
eventId: createWhatsAppDurableInboundMessageId({
|
|
456
|
+
remoteJid,
|
|
457
|
+
id
|
|
461
458
|
}),
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
459
|
+
laneKey: remoteJid
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
/** Account-scoped queue shared with the pre-drain WhatsApp receive journal. */
|
|
463
|
+
function createWhatsAppDurableInboundQueue(accountId) {
|
|
464
|
+
return getWhatsAppRuntime().state.openChannelIngressQueue({
|
|
465
|
+
accountId: hashNamespacePart(accountId),
|
|
466
|
+
stateDir: getWhatsAppRuntime().state.resolveStateDir()
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
/** Raw receive chokepoint: append first, then let the drain normalize and dispatch. */
|
|
470
|
+
async function enqueueWhatsAppDurableInbound(params) {
|
|
471
|
+
const facts = inspectWhatsAppIngressMessage(params.message);
|
|
472
|
+
const receivedAt = params.receivedAt ?? Date.now();
|
|
473
|
+
await params.queue.prune({
|
|
474
|
+
pendingTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
|
|
475
|
+
completedTtlMs: WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS,
|
|
476
|
+
failedTtlMs: WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS,
|
|
477
|
+
pendingMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES,
|
|
478
|
+
completedMaxEntries: WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES,
|
|
479
|
+
failedMaxEntries: WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES
|
|
480
|
+
});
|
|
481
|
+
return await params.queue.enqueue(facts.eventId, {
|
|
482
|
+
message: serializeWhatsAppDurableInboundMessage(params.message),
|
|
483
|
+
upsertType: params.upsertType,
|
|
484
|
+
...params.skipStaleAppend === void 0 ? {} : { skipStaleAppend: params.skipStaleAppend },
|
|
485
|
+
...params.skipRecentOutboundEcho === void 0 ? {} : { skipRecentOutboundEcho: params.skipRecentOutboundEcho },
|
|
486
|
+
receivedAt,
|
|
487
|
+
...params.receiveOrder === void 0 ? {} : { receiveOrder: params.receiveOrder }
|
|
488
|
+
}, {
|
|
489
|
+
receivedAt,
|
|
490
|
+
laneKey: facts.laneKey
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
function resolveWhatsAppIngressNonRetryableFailure(error) {
|
|
494
|
+
return error instanceof WhatsAppIngressPermanentError ? {
|
|
495
|
+
reason: error.reason,
|
|
496
|
+
message: error.message
|
|
497
|
+
} : null;
|
|
498
|
+
}
|
|
499
|
+
/** Core drain with per-conversation lanes and completion at reply-lane adoption. */
|
|
500
|
+
function createWhatsAppIngressDrain(params) {
|
|
501
|
+
return createChannelIngressDrain({
|
|
502
|
+
queue: params.queue,
|
|
503
|
+
retryPolicy: {
|
|
504
|
+
maxAttempts: DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
|
|
505
|
+
deadLetterMinAgeMs: DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS
|
|
506
|
+
},
|
|
507
|
+
resolveNonRetryableFailure: resolveWhatsAppIngressNonRetryableFailure,
|
|
508
|
+
deriveLaneKey: (record) => {
|
|
509
|
+
try {
|
|
510
|
+
return inspectWhatsAppIngressMessage(deserializeWhatsAppDurableInboundMessage(record.payload.message)).laneKey;
|
|
511
|
+
} catch {
|
|
512
|
+
return record.id;
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
...params.onLog ? { onLog: params.onLog } : {},
|
|
516
|
+
...params.abortSignal ? { abortSignal: params.abortSignal } : {},
|
|
517
|
+
dispatchClaimedEvent: async (record, lifecycle) => {
|
|
518
|
+
const message = deserializeWhatsAppDurableInboundMessage(record.payload.message);
|
|
519
|
+
if (inspectWhatsAppIngressMessage(message).eventId !== record.id) throw new WhatsAppIngressPermanentError("event-id-mismatch", "WhatsApp ingress row id does not match its transport message key");
|
|
520
|
+
return await params.dispatch(message, record.payload, lifecycle);
|
|
469
521
|
}
|
|
470
522
|
});
|
|
471
523
|
}
|
|
472
524
|
//#endregion
|
|
525
|
+
//#region extensions/whatsapp/src/inbound/ingress-lifecycle.ts
|
|
526
|
+
const ingressLifecycleKey = Symbol("whatsappIngressLifecycle");
|
|
527
|
+
function attachWhatsAppIngressLifecycle(message, lifecycle) {
|
|
528
|
+
if (lifecycle) message[ingressLifecycleKey] = lifecycle;
|
|
529
|
+
return message;
|
|
530
|
+
}
|
|
531
|
+
function resolveWhatsAppIngressLifecycle(message) {
|
|
532
|
+
return message[ingressLifecycleKey];
|
|
533
|
+
}
|
|
534
|
+
//#endregion
|
|
473
535
|
//#region extensions/whatsapp/src/inbound/lifecycle.ts
|
|
474
536
|
function attachEmitterListener(emitter, event, listener) {
|
|
475
537
|
emitter.on(event, listener);
|
|
@@ -539,13 +601,8 @@ const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress
|
|
|
539
601
|
const GROUP_META_TTL_MS = 300 * 1e3;
|
|
540
602
|
const BAILEYS_MESSAGE_TTL_MS = 600 * 1e3;
|
|
541
603
|
const INBOUND_CLOSE_DRAIN_TIMEOUT_MS = 5e3;
|
|
542
|
-
const
|
|
604
|
+
const WHATSAPP_INGRESS_DRAIN_INTERVAL_MS = 1e3;
|
|
543
605
|
const WHATSAPP_GROUP_METADATA_CACHE_MAX_ENTRIES = 500;
|
|
544
|
-
function resolveRetryableWhatsAppInboundError(error) {
|
|
545
|
-
if (error instanceof WhatsAppRetryableInboundError) return error;
|
|
546
|
-
if (!collectErrorGraphCandidates(error, (current) => [current.cause, current.error]).some((candidate) => REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE.test(formatErrorMessage(candidate)))) return null;
|
|
547
|
-
return new WhatsAppRetryableInboundError(formatErrorMessage(error), { cause: error });
|
|
548
|
-
}
|
|
549
606
|
function resolveGroupMetadataExpiresAt(nowRaw = Date.now()) {
|
|
550
607
|
const now = asDateTimestampMs(nowRaw);
|
|
551
608
|
return now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(GROUP_META_TTL_MS, { nowMs: now });
|
|
@@ -636,9 +693,6 @@ function isRetryableSendDisconnectError(err) {
|
|
|
636
693
|
function shouldClearSocketRefAfterSendFailure(err) {
|
|
637
694
|
return /closed|reset|disconnect|no active socket/i.test(formatError(err));
|
|
638
695
|
}
|
|
639
|
-
function isNonEmptyString(value) {
|
|
640
|
-
return Boolean(value);
|
|
641
|
-
}
|
|
642
696
|
async function attachWebInboxToSocket(options) {
|
|
643
697
|
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
|
644
698
|
const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
|
|
@@ -679,11 +733,30 @@ async function attachWebInboxToSocket(options) {
|
|
|
679
733
|
if (!successorIdentity || !identitiesOverlap(self, successorIdentity)) return null;
|
|
680
734
|
return successor.getCurrentSock();
|
|
681
735
|
};
|
|
682
|
-
const
|
|
736
|
+
const durableInboundQueue = options.durableInboundQueue ?? createWhatsAppDurableInboundQueue(options.accountId);
|
|
683
737
|
const inboundDebounceMs = Math.max(0, Math.trunc(options.debounceMs ?? 0));
|
|
684
738
|
const pendingDebounceKeys = /* @__PURE__ */ new Set();
|
|
685
739
|
const activeInboundFlushes = /* @__PURE__ */ new Set();
|
|
686
740
|
const pendingMessageHandlers = /* @__PURE__ */ new Set();
|
|
741
|
+
const debounceWorkWaiters = /* @__PURE__ */ new Set();
|
|
742
|
+
const notifyDebounceWork = () => {
|
|
743
|
+
if (debounceWorkWaiters.size === 0) return;
|
|
744
|
+
const waiters = [...debounceWorkWaiters];
|
|
745
|
+
debounceWorkWaiters.clear();
|
|
746
|
+
for (const resolve of waiters) resolve();
|
|
747
|
+
};
|
|
748
|
+
const waitForDebounceWorkOrIdle = (handlers) => {
|
|
749
|
+
if (pendingDebounceKeys.size > 0 || activeInboundFlushes.size > 0) return Promise.resolve();
|
|
750
|
+
if (pendingMessageHandlers.size === 0) return Promise.resolve();
|
|
751
|
+
return new Promise((resolve) => {
|
|
752
|
+
const finish = () => {
|
|
753
|
+
debounceWorkWaiters.delete(finish);
|
|
754
|
+
resolve();
|
|
755
|
+
};
|
|
756
|
+
debounceWorkWaiters.add(finish);
|
|
757
|
+
Promise.allSettled(handlers).then(finish);
|
|
758
|
+
});
|
|
759
|
+
};
|
|
687
760
|
let nextReceiveOrder = 0;
|
|
688
761
|
const publishPendingWorkState = (at = Date.now()) => {
|
|
689
762
|
options.onPendingWorkChanged?.(pendingMessageHandlers.size + pendingDebounceKeys.size + activeInboundFlushes.size, at);
|
|
@@ -701,18 +774,47 @@ async function attachWebInboxToSocket(options) {
|
|
|
701
774
|
if (timestampDiff !== 0) return timestampDiff;
|
|
702
775
|
return (a.receiveOrder ?? 0) - (b.receiveOrder ?? 0);
|
|
703
776
|
});
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
777
|
+
const buildFlushIngressLifecycle = (entries) => {
|
|
778
|
+
const lifecycles = entries.map((entry) => entry.turnAdoptionLifecycle).filter((lifecycle) => lifecycle !== void 0);
|
|
779
|
+
const [firstLifecycle] = lifecycles;
|
|
780
|
+
if (!firstLifecycle) return {
|
|
781
|
+
lifecycle: void 0,
|
|
782
|
+
settle: async () => {},
|
|
783
|
+
abandon: async () => {}
|
|
784
|
+
};
|
|
785
|
+
let handedOff = false;
|
|
786
|
+
const adoptAll = async () => {
|
|
787
|
+
for (const lifecycle of lifecycles) await lifecycle.onAdopted();
|
|
788
|
+
};
|
|
789
|
+
return {
|
|
790
|
+
lifecycle: {
|
|
791
|
+
abortSignal: lifecycles.length === 1 ? firstLifecycle.abortSignal : AbortSignal.any(lifecycles.map((lifecycle) => lifecycle.abortSignal)),
|
|
792
|
+
onAdopted: async () => {
|
|
793
|
+
handedOff = true;
|
|
794
|
+
await adoptAll();
|
|
795
|
+
},
|
|
796
|
+
onDeferred: () => {
|
|
797
|
+
handedOff = true;
|
|
798
|
+
for (const lifecycle of lifecycles) lifecycle.onDeferred();
|
|
799
|
+
},
|
|
800
|
+
onAdoptionFinalizing: () => {
|
|
801
|
+
for (const lifecycle of lifecycles) lifecycle.onAdoptionFinalizing();
|
|
802
|
+
},
|
|
803
|
+
onAbandoned: async () => {
|
|
804
|
+
handedOff = true;
|
|
805
|
+
await Promise.all(lifecycles.map((lifecycle) => Promise.resolve(lifecycle.onAbandoned())));
|
|
806
|
+
}
|
|
807
|
+
},
|
|
808
|
+
settle: async () => {
|
|
809
|
+
if (!handedOff) await adoptAll();
|
|
810
|
+
},
|
|
811
|
+
abandon: async () => {
|
|
812
|
+
if (!handedOff) {
|
|
813
|
+
handedOff = true;
|
|
814
|
+
await Promise.all(lifecycles.map((lifecycle) => Promise.resolve(lifecycle.onAbandoned())));
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
};
|
|
716
818
|
};
|
|
717
819
|
const debouncer = createInboundDebouncer({
|
|
718
820
|
debounceMs: inboundDebounceMs,
|
|
@@ -725,14 +827,17 @@ async function attachWebInboxToSocket(options) {
|
|
|
725
827
|
});
|
|
726
828
|
activeInboundFlushes.add(flushTask);
|
|
727
829
|
publishPendingWorkState();
|
|
830
|
+
notifyDebounceWork();
|
|
728
831
|
try {
|
|
729
832
|
const orderedEntries = orderDebouncedInboundEntries(entries);
|
|
730
833
|
const last = orderedEntries.at(-1);
|
|
731
834
|
if (!last) return;
|
|
835
|
+
const { lifecycle, settle, abandon } = buildFlushIngressLifecycle(orderedEntries);
|
|
732
836
|
try {
|
|
733
837
|
if (orderedEntries.length === 1) {
|
|
734
|
-
await options.onMessage(last);
|
|
735
|
-
await
|
|
838
|
+
await options.onMessage(attachWhatsAppIngressLifecycle(last, lifecycle));
|
|
839
|
+
await settle();
|
|
840
|
+
await Promise.all(orderedEntries.map((entry) => maybeMarkInboundAsRead(entry.readReceipt)));
|
|
736
841
|
return;
|
|
737
842
|
}
|
|
738
843
|
const mentioned = /* @__PURE__ */ new Set();
|
|
@@ -747,8 +852,9 @@ async function attachWebInboxToSocket(options) {
|
|
|
747
852
|
...last.group,
|
|
748
853
|
mentions: combinedMentions
|
|
749
854
|
} : void 0;
|
|
750
|
-
const combinedMessage = withDeprecatedWebInboundMessageFlatAliases({
|
|
855
|
+
const combinedMessage = attachWhatsAppIngressLifecycle(withDeprecatedWebInboundMessageFlatAliases({
|
|
751
856
|
...last,
|
|
857
|
+
...lifecycle ? { turnAdoptionLifecycle: lifecycle } : {},
|
|
752
858
|
payload: {
|
|
753
859
|
...last.payload,
|
|
754
860
|
body: combinedBody,
|
|
@@ -759,11 +865,12 @@ async function attachWebInboxToSocket(options) {
|
|
|
759
865
|
...last.event,
|
|
760
866
|
isBatched: true
|
|
761
867
|
}
|
|
762
|
-
});
|
|
868
|
+
}), lifecycle);
|
|
763
869
|
await options.onMessage(combinedMessage);
|
|
764
|
-
await
|
|
870
|
+
await settle();
|
|
871
|
+
await Promise.all(orderedEntries.map((entry) => maybeMarkInboundAsRead(entry.readReceipt)));
|
|
765
872
|
} catch (error) {
|
|
766
|
-
await
|
|
873
|
+
await abandon();
|
|
767
874
|
throw error;
|
|
768
875
|
}
|
|
769
876
|
} finally {
|
|
@@ -978,20 +1085,24 @@ async function attachWebInboxToSocket(options) {
|
|
|
978
1085
|
}
|
|
979
1086
|
return content;
|
|
980
1087
|
};
|
|
1088
|
+
const shouldSkipRecentOutboundEcho = (msg) => {
|
|
1089
|
+
const id = msg.key?.id ?? void 0;
|
|
1090
|
+
const remoteJid = msg.key?.remoteJid;
|
|
1091
|
+
if (!msg.key?.fromMe || !id || !remoteJid || !isRecentOutboundMessage({
|
|
1092
|
+
accountId: options.accountId,
|
|
1093
|
+
remoteJid,
|
|
1094
|
+
messageId: id
|
|
1095
|
+
})) return false;
|
|
1096
|
+
logWhatsAppVerbose(options.verbose, `Skipping recent outbound WhatsApp echo ${id} for ${remoteJid}`);
|
|
1097
|
+
return true;
|
|
1098
|
+
};
|
|
981
1099
|
const normalizeInboundMessage = async (msg) => {
|
|
982
1100
|
const id = msg.key?.id ?? void 0;
|
|
983
1101
|
const remoteJid = msg.key?.remoteJid;
|
|
984
1102
|
if (!remoteJid) return null;
|
|
985
1103
|
if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) return null;
|
|
986
1104
|
const group = isJidGroup(remoteJid) === true;
|
|
987
|
-
if (
|
|
988
|
-
accountId: options.accountId,
|
|
989
|
-
remoteJid,
|
|
990
|
-
messageId: id
|
|
991
|
-
})) {
|
|
992
|
-
logWhatsAppVerbose(options.verbose, `Skipping recent outbound WhatsApp echo ${id} for ${remoteJid}`);
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
1105
|
+
if (shouldSkipRecentOutboundEcho(msg)) return null;
|
|
995
1106
|
if (!hasInboundUserContent(msg.message ?? void 0)) return null;
|
|
996
1107
|
const participantJid = msg.key?.participant ?? void 0;
|
|
997
1108
|
const from = group ? remoteJid : await resolveInboundJid(remoteJid);
|
|
@@ -1067,15 +1178,6 @@ async function attachWebInboxToSocket(options) {
|
|
|
1067
1178
|
}
|
|
1068
1179
|
await maybeMarkInboundAsRead(target);
|
|
1069
1180
|
};
|
|
1070
|
-
const completeUndeliverableDurableInbound = async (durableId, metadata) => {
|
|
1071
|
-
if (!durableId) return;
|
|
1072
|
-
await durableInboundJournal.complete(durableId, metadata?.readReceipt ? { metadata: { readReceipt: metadata.readReceipt } } : void 0);
|
|
1073
|
-
};
|
|
1074
|
-
const buildDurableInboundPayload = (msg, upsertType) => ({
|
|
1075
|
-
message: serializeWhatsAppDurableInboundMessage(msg),
|
|
1076
|
-
...upsertType ? { upsertType } : {},
|
|
1077
|
-
receivedAt: Date.now()
|
|
1078
|
-
});
|
|
1079
1181
|
const shouldSkipStaleAppend = (msg, upsertType) => {
|
|
1080
1182
|
if (upsertType !== "append") return false;
|
|
1081
1183
|
const APPEND_RECENT_GRACE_MS = 6e4;
|
|
@@ -1084,69 +1186,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1084
1186
|
const nowMs = Date.now();
|
|
1085
1187
|
return msgTsMs < (options.appendReplyWindow && nowMs <= options.appendReplyWindow.untilMs ? Math.max(options.appendReplyWindow.afterMs, nowMs - options.appendReplyWindow.maxAgeMs) : connectedAtMs - APPEND_RECENT_GRACE_MS);
|
|
1086
1188
|
};
|
|
1087
|
-
const
|
|
1088
|
-
const inbound = await normalizeInboundMessage(msg);
|
|
1089
|
-
if (!inbound) {
|
|
1090
|
-
if (stored) await completeUndeliverableDurableInbound(stored.id, stored.metadata);
|
|
1091
|
-
return;
|
|
1092
|
-
}
|
|
1093
|
-
const readReceipt = stored?.metadata?.readReceipt ?? buildReadReceiptTarget(inbound);
|
|
1094
|
-
const deliveryReadReceipt = inbound.access.isSelfChat ? void 0 : readReceipt;
|
|
1095
|
-
if (!stored && shouldSkipStaleAppend(msg, upsertType)) {
|
|
1096
|
-
await maybeMarkNonSelfChatReadReceipt(inbound, readReceipt);
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
let durableId = stored?.id ?? (inbound.id ? createWhatsAppDurableInboundMessageId({
|
|
1100
|
-
remoteJid: inbound.remoteJid,
|
|
1101
|
-
id: inbound.id
|
|
1102
|
-
}) : void 0);
|
|
1103
|
-
const durableMetadata = deliveryReadReceipt ? { readReceipt: deliveryReadReceipt } : void 0;
|
|
1104
|
-
if (durableId && !stored) try {
|
|
1105
|
-
const accepted = await durableInboundJournal.accept(durableId, buildDurableInboundPayload(msg, upsertType), {
|
|
1106
|
-
metadata: durableMetadata,
|
|
1107
|
-
receivedAt: inbound.messageTimestampMs
|
|
1108
|
-
});
|
|
1109
|
-
if (accepted.kind === "completed") {
|
|
1110
|
-
await maybeMarkNonSelfChatReadReceipt(inbound, accepted.record.metadata?.readReceipt ?? deliveryReadReceipt);
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
if (accepted.kind === "pending" && accepted.record.attempts === 0) return;
|
|
1114
|
-
} catch (err) {
|
|
1115
|
-
durableId = void 0;
|
|
1116
|
-
const error = formatError(err);
|
|
1117
|
-
inboundLogger.warn({ error }, "failed persisting durable WhatsApp inbound; delivering live");
|
|
1118
|
-
inboundConsoleLog.warn(`Failed persisting durable WhatsApp inbound; delivering live: ${error}`);
|
|
1119
|
-
}
|
|
1120
|
-
const enriched = await enrichInboundMessage(msg);
|
|
1121
|
-
if (!enriched) {
|
|
1122
|
-
await completeUndeliverableDurableInbound(durableId, durableMetadata);
|
|
1123
|
-
await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
const dedupeKey = inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : "";
|
|
1127
|
-
const dedupeClaim = dedupeKey ? await claimRecentInboundMessageDelivery(dedupeKey) : "claimed";
|
|
1128
|
-
if (dedupeClaim !== "claimed") {
|
|
1129
|
-
if (dedupeClaim === "duplicate") {
|
|
1130
|
-
await completeUndeliverableDurableInbound(durableId, durableMetadata);
|
|
1131
|
-
await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
|
|
1132
|
-
}
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
recordAcceptedInboundActivity(options.accountId);
|
|
1136
|
-
await enqueueInboundMessage(msg, inbound, enriched, {
|
|
1137
|
-
durableId,
|
|
1138
|
-
readReceipt: deliveryReadReceipt,
|
|
1139
|
-
receiveOrder
|
|
1140
|
-
});
|
|
1141
|
-
};
|
|
1142
|
-
const replayPendingDurableInboundMessages = async () => {
|
|
1143
|
-
const pending = await durableInboundJournal.pending();
|
|
1144
|
-
for (const record of pending) await processDurableInboundMessage(deserializeWhatsAppDurableInboundMessage(record.payload.message), record.payload.upsertType, record.payload.receivedAt, {
|
|
1145
|
-
id: record.id,
|
|
1146
|
-
payload: record.payload,
|
|
1147
|
-
metadata: record.metadata
|
|
1148
|
-
});
|
|
1149
|
-
};
|
|
1189
|
+
const preparedInboundByDurableId = /* @__PURE__ */ new Map();
|
|
1150
1190
|
const enrichInboundMessage = async (msg) => {
|
|
1151
1191
|
const location = extractLocationData(msg.message ?? void 0);
|
|
1152
1192
|
const locationText = location ? formatLocationText(location) : void 0;
|
|
@@ -1301,8 +1341,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1301
1341
|
}
|
|
1302
1342
|
} : void 0,
|
|
1303
1343
|
group,
|
|
1304
|
-
|
|
1305
|
-
durableId: durable.durableId,
|
|
1344
|
+
turnAdoptionLifecycle: durable.turnAdoptionLifecycle,
|
|
1306
1345
|
readReceipt: durable.readReceipt,
|
|
1307
1346
|
receiveOrder: durable.receiveOrder
|
|
1308
1347
|
});
|
|
@@ -1312,6 +1351,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1312
1351
|
if (inboundDebounceMs > 0 && shouldDebounceInboundMessage(inboundMessage)) {
|
|
1313
1352
|
pendingDebounceKeys.add(debounceKey);
|
|
1314
1353
|
publishPendingWorkState();
|
|
1354
|
+
notifyDebounceWork();
|
|
1315
1355
|
}
|
|
1316
1356
|
}
|
|
1317
1357
|
if (inboundMessage.event.id) {
|
|
@@ -1323,16 +1363,119 @@ async function attachWebInboxToSocket(options) {
|
|
|
1323
1363
|
fromMe: inboundMessage.platform.fromMe
|
|
1324
1364
|
});
|
|
1325
1365
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1366
|
+
await debouncer.enqueue(inboundMessage);
|
|
1367
|
+
};
|
|
1368
|
+
const processWhatsAppInboundMessage = async (msg, context) => {
|
|
1369
|
+
rememberBaileysMessage(msg.key?.remoteJid, msg.key?.id, msg.message);
|
|
1370
|
+
if (context.skipRecentOutboundEcho === true) return "completed";
|
|
1371
|
+
const preparation = context.eventId ? preparedInboundByDurableId.get(context.eventId) : void 0;
|
|
1372
|
+
if (context.eventId) preparedInboundByDurableId.delete(context.eventId);
|
|
1373
|
+
const prepared = await preparation;
|
|
1374
|
+
if (prepared === null) return "completed";
|
|
1375
|
+
const inbound = prepared ?? await normalizeInboundMessage(msg);
|
|
1376
|
+
if (!inbound) return "completed";
|
|
1377
|
+
if (await maybeResolveWhatsAppQuestionReaction({
|
|
1378
|
+
cfg: options.loadConfig?.() ?? options.cfg,
|
|
1379
|
+
accountId: options.accountId,
|
|
1380
|
+
msg,
|
|
1381
|
+
senderId: inbound.senderE164 ?? inbound.from,
|
|
1382
|
+
resolveReactionTargetJids,
|
|
1383
|
+
logDebug: (message) => logWhatsAppVerbose(options.verbose, message)
|
|
1384
|
+
})) return "completed";
|
|
1385
|
+
const readReceipt = buildReadReceiptTarget(inbound);
|
|
1386
|
+
const deliveryReadReceipt = inbound.access.isSelfChat ? void 0 : readReceipt;
|
|
1387
|
+
if (context.skipStaleAppend === true) {
|
|
1388
|
+
await maybeMarkNonSelfChatReadReceipt(inbound, readReceipt);
|
|
1389
|
+
return "completed";
|
|
1390
|
+
}
|
|
1391
|
+
const enriched = await enrichInboundMessage(msg);
|
|
1392
|
+
if (!enriched) {
|
|
1393
|
+
await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
|
|
1394
|
+
return "completed";
|
|
1395
|
+
}
|
|
1396
|
+
recordAcceptedInboundActivity(options.accountId);
|
|
1397
|
+
await enqueueInboundMessage(msg, inbound, enriched, {
|
|
1398
|
+
readReceipt: deliveryReadReceipt,
|
|
1399
|
+
receiveOrder: context.receiveOrder ?? context.receivedAt,
|
|
1400
|
+
turnAdoptionLifecycle: context.lifecycle
|
|
1401
|
+
});
|
|
1402
|
+
return "deferred";
|
|
1403
|
+
};
|
|
1404
|
+
let durableDrainPass;
|
|
1405
|
+
let durableDrainRequested = false;
|
|
1406
|
+
let durableDrainClosed = false;
|
|
1407
|
+
const requestDurableInboundDrain = () => {
|
|
1408
|
+
if (durableDrainClosed) return Promise.resolve();
|
|
1409
|
+
durableDrainRequested = true;
|
|
1410
|
+
if (!durableDrainPass) {
|
|
1411
|
+
let passFailed = false;
|
|
1412
|
+
const task = (async () => {
|
|
1413
|
+
await durableInboundDrain.recoverStaleClaims();
|
|
1414
|
+
while (durableDrainRequested) {
|
|
1415
|
+
durableDrainRequested = false;
|
|
1416
|
+
if ((await durableInboundDrain.drainOnce()).started > 0) {
|
|
1417
|
+
await durableInboundDrain.waitForIdle();
|
|
1418
|
+
durableDrainRequested = true;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
})().catch((error) => {
|
|
1422
|
+
passFailed = true;
|
|
1423
|
+
throw error;
|
|
1330
1424
|
});
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1425
|
+
durableDrainPass = task;
|
|
1426
|
+
pendingMessageHandlers.add(task);
|
|
1427
|
+
publishPendingWorkState();
|
|
1428
|
+
task.finally(() => {
|
|
1429
|
+
if (durableDrainPass === task) durableDrainPass = void 0;
|
|
1430
|
+
pendingMessageHandlers.delete(task);
|
|
1431
|
+
publishPendingWorkState();
|
|
1432
|
+
if (durableDrainRequested && !passFailed) requestDurableInboundDrain().catch((error) => {
|
|
1433
|
+
inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
|
|
1434
|
+
});
|
|
1435
|
+
}).catch(() => void 0);
|
|
1334
1436
|
}
|
|
1437
|
+
return durableDrainPass;
|
|
1438
|
+
};
|
|
1439
|
+
const requestDrainAfterLaneSettlement = () => {
|
|
1440
|
+
requestDurableInboundDrain().catch((error) => {
|
|
1441
|
+
inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
|
|
1442
|
+
});
|
|
1335
1443
|
};
|
|
1444
|
+
const continueDrainAfterLaneSettlement = (lifecycle) => ({
|
|
1445
|
+
abortSignal: lifecycle.abortSignal,
|
|
1446
|
+
onAdopted: async () => {
|
|
1447
|
+
await lifecycle.onAdopted();
|
|
1448
|
+
requestDrainAfterLaneSettlement();
|
|
1449
|
+
},
|
|
1450
|
+
onDeferred: lifecycle.onDeferred,
|
|
1451
|
+
onAdoptionFinalizing: lifecycle.onAdoptionFinalizing,
|
|
1452
|
+
onAbandoned: async () => {
|
|
1453
|
+
await lifecycle.onAbandoned();
|
|
1454
|
+
requestDrainAfterLaneSettlement();
|
|
1455
|
+
}
|
|
1456
|
+
});
|
|
1457
|
+
const durableInboundDrain = createWhatsAppIngressDrain({
|
|
1458
|
+
queue: durableInboundQueue,
|
|
1459
|
+
dispatch: async (msg, payload, lifecycle) => {
|
|
1460
|
+
const remoteJid = msg.key?.remoteJid;
|
|
1461
|
+
const id = msg.key?.id;
|
|
1462
|
+
return { kind: await processWhatsAppInboundMessage(msg, {
|
|
1463
|
+
...payload,
|
|
1464
|
+
...remoteJid && id ? { eventId: createWhatsAppDurableInboundMessageId({
|
|
1465
|
+
remoteJid,
|
|
1466
|
+
id
|
|
1467
|
+
}) } : {},
|
|
1468
|
+
lifecycle: continueDrainAfterLaneSettlement(lifecycle)
|
|
1469
|
+
}) };
|
|
1470
|
+
},
|
|
1471
|
+
onLog: (message) => inboundLogger.warn({ message }, "whatsapp ingress drain")
|
|
1472
|
+
});
|
|
1473
|
+
const durableDrainTimer = setInterval(() => {
|
|
1474
|
+
requestDurableInboundDrain().catch((error) => {
|
|
1475
|
+
inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
|
|
1476
|
+
});
|
|
1477
|
+
}, WHATSAPP_INGRESS_DRAIN_INTERVAL_MS);
|
|
1478
|
+
durableDrainTimer.unref?.();
|
|
1336
1479
|
const handleMessagesUpsert = async (upsert) => {
|
|
1337
1480
|
if (upsert.type !== "notify" && upsert.type !== "append") return;
|
|
1338
1481
|
for (const msg of upsert.messages ?? []) {
|
|
@@ -1348,7 +1491,78 @@ async function attachWebInboxToSocket(options) {
|
|
|
1348
1491
|
resolveReactionTargetJids,
|
|
1349
1492
|
logVerboseMessage: (message) => logWhatsAppVerbose(options.verbose, message)
|
|
1350
1493
|
})) continue;
|
|
1351
|
-
|
|
1494
|
+
const receivedAt = Date.now();
|
|
1495
|
+
const skipStaleAppend = shouldSkipStaleAppend(msg, upsert.type);
|
|
1496
|
+
const skipRecentOutboundEcho = shouldSkipRecentOutboundEcho(msg);
|
|
1497
|
+
const remoteJid = msg.key?.remoteJid;
|
|
1498
|
+
const id = msg.key?.id;
|
|
1499
|
+
const durableId = remoteJid && id ? createWhatsAppDurableInboundMessageId({
|
|
1500
|
+
remoteJid,
|
|
1501
|
+
id
|
|
1502
|
+
}) : void 0;
|
|
1503
|
+
let resolvePrepared;
|
|
1504
|
+
if (durableId && !preparedInboundByDurableId.has(durableId)) {
|
|
1505
|
+
if (preparedInboundByDurableId.size >= 1e3) {
|
|
1506
|
+
const oldest = preparedInboundByDurableId.keys().next().value;
|
|
1507
|
+
if (oldest !== void 0) preparedInboundByDurableId.delete(oldest);
|
|
1508
|
+
}
|
|
1509
|
+
preparedInboundByDurableId.set(durableId, new Promise((resolve) => {
|
|
1510
|
+
resolvePrepared = resolve;
|
|
1511
|
+
}));
|
|
1512
|
+
}
|
|
1513
|
+
const finishPreparation = (inbound, keepForDrain = false) => {
|
|
1514
|
+
resolvePrepared?.(inbound);
|
|
1515
|
+
if (!keepForDrain && durableId && resolvePrepared) preparedInboundByDurableId.delete(durableId);
|
|
1516
|
+
};
|
|
1517
|
+
let result;
|
|
1518
|
+
let appendError;
|
|
1519
|
+
for (const delayMs of [
|
|
1520
|
+
0,
|
|
1521
|
+
100,
|
|
1522
|
+
300
|
|
1523
|
+
]) {
|
|
1524
|
+
if (delayMs > 0) await new Promise((resolve) => {
|
|
1525
|
+
setTimeout(resolve, delayMs);
|
|
1526
|
+
});
|
|
1527
|
+
try {
|
|
1528
|
+
result = await enqueueWhatsAppDurableInbound({
|
|
1529
|
+
queue: durableInboundQueue,
|
|
1530
|
+
message: msg,
|
|
1531
|
+
upsertType: upsert.type,
|
|
1532
|
+
skipStaleAppend,
|
|
1533
|
+
skipRecentOutboundEcho,
|
|
1534
|
+
receivedAt,
|
|
1535
|
+
receiveOrder
|
|
1536
|
+
});
|
|
1537
|
+
appendError = void 0;
|
|
1538
|
+
break;
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
appendError = error;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (result === void 0) {
|
|
1544
|
+
finishPreparation(void 0);
|
|
1545
|
+
const formattedError = formatError(appendError);
|
|
1546
|
+
inboundLogger.error({ error: formattedError }, "failed persisting durable WhatsApp inbound after retries; message dropped");
|
|
1547
|
+
inboundConsoleLog.error(`Failed persisting durable WhatsApp inbound after retries; message dropped: ${formattedError}`);
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
if (result.kind === "completed") {
|
|
1551
|
+
finishPreparation(void 0);
|
|
1552
|
+
const inbound = await normalizeInboundMessage(msg);
|
|
1553
|
+
if (inbound) await maybeMarkNonSelfChatReadReceipt(inbound, buildReadReceiptTarget(inbound));
|
|
1554
|
+
} else {
|
|
1555
|
+
if (result.kind === "accepted") try {
|
|
1556
|
+
finishPreparation(skipRecentOutboundEcho ? null : await normalizeInboundMessage(msg), true);
|
|
1557
|
+
} catch (error) {
|
|
1558
|
+
finishPreparation(void 0);
|
|
1559
|
+
throw error;
|
|
1560
|
+
}
|
|
1561
|
+
else finishPreparation(void 0);
|
|
1562
|
+
requestDurableInboundDrain().catch((error) => {
|
|
1563
|
+
inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1352
1566
|
}
|
|
1353
1567
|
};
|
|
1354
1568
|
const handleMessagesUpsertEvent = (upsert) => {
|
|
@@ -1363,9 +1577,6 @@ async function attachWebInboxToSocket(options) {
|
|
|
1363
1577
|
publishPendingWorkState();
|
|
1364
1578
|
});
|
|
1365
1579
|
};
|
|
1366
|
-
const waitForPendingMessageHandlers = async () => {
|
|
1367
|
-
while (pendingMessageHandlers.size > 0) await Promise.all(Array.from(pendingMessageHandlers));
|
|
1368
|
-
};
|
|
1369
1580
|
const drainDebouncedInboundMessages = async () => {
|
|
1370
1581
|
while (pendingDebounceKeys.size > 0 || activeInboundFlushes.size > 0) {
|
|
1371
1582
|
const debounceKeys = Array.from(pendingDebounceKeys);
|
|
@@ -1377,8 +1588,21 @@ async function attachWebInboxToSocket(options) {
|
|
|
1377
1588
|
};
|
|
1378
1589
|
const drainInboundBeforeSocketClose = async () => {
|
|
1379
1590
|
groupMetadataCacheClosed = true;
|
|
1380
|
-
|
|
1591
|
+
clearInterval(durableDrainTimer);
|
|
1592
|
+
for (;;) {
|
|
1593
|
+
await drainDebouncedInboundMessages();
|
|
1594
|
+
if (pendingMessageHandlers.size === 0) break;
|
|
1595
|
+
const handlers = Array.from(pendingMessageHandlers);
|
|
1596
|
+
await Promise.race([Promise.allSettled(handlers), waitForDebounceWorkOrIdle(handlers)]);
|
|
1597
|
+
if (pendingMessageHandlers.size === 0 && pendingDebounceKeys.size === 0 && activeInboundFlushes.size === 0) break;
|
|
1598
|
+
}
|
|
1381
1599
|
await drainDebouncedInboundMessages();
|
|
1600
|
+
try {
|
|
1601
|
+
await durableInboundDrain.waitForIdle();
|
|
1602
|
+
} finally {
|
|
1603
|
+
durableDrainClosed = true;
|
|
1604
|
+
durableInboundDrain.dispose();
|
|
1605
|
+
}
|
|
1382
1606
|
};
|
|
1383
1607
|
const drainInboundBeforeSocketCloseWithTimeout = async () => {
|
|
1384
1608
|
let timeout = null;
|
|
@@ -1391,6 +1615,8 @@ async function attachWebInboxToSocket(options) {
|
|
|
1391
1615
|
})]);
|
|
1392
1616
|
} finally {
|
|
1393
1617
|
if (timeout) clearTimeout(timeout);
|
|
1618
|
+
durableDrainClosed = true;
|
|
1619
|
+
durableInboundDrain.dispose();
|
|
1394
1620
|
}
|
|
1395
1621
|
};
|
|
1396
1622
|
const handleConnectionUpdate = (update) => {
|
|
@@ -1449,16 +1675,10 @@ async function attachWebInboxToSocket(options) {
|
|
|
1449
1675
|
const detachGroupParticipantsUpdate = attachSockListener("group-participants.update", ((update) => {
|
|
1450
1676
|
forgetFullGroupMetadata(update.id);
|
|
1451
1677
|
}));
|
|
1452
|
-
|
|
1678
|
+
requestDurableInboundDrain().catch((err) => {
|
|
1453
1679
|
inboundLogger.error({ error: String(err) }, "failed replaying durable WhatsApp inbound");
|
|
1454
1680
|
inboundConsoleLog.error(`Failed replaying durable WhatsApp inbound: ${String(err)}`);
|
|
1455
1681
|
});
|
|
1456
|
-
pendingMessageHandlers.add(replayTask);
|
|
1457
|
-
publishPendingWorkState();
|
|
1458
|
-
replayTask.finally(() => {
|
|
1459
|
-
pendingMessageHandlers.delete(replayTask);
|
|
1460
|
-
publishPendingWorkState();
|
|
1461
|
-
});
|
|
1462
1682
|
(async () => {
|
|
1463
1683
|
try {
|
|
1464
1684
|
const groups = await sock.groupFetchAllParticipating();
|
|
@@ -2224,29 +2444,34 @@ function isRetryableWhatsAppOutboundError(error) {
|
|
|
2224
2444
|
return WHATSAPP_RETRYABLE_OUTBOUND_ERROR_PATTERN.test(formatError(error));
|
|
2225
2445
|
}
|
|
2226
2446
|
async function sendWhatsAppOutboundWithRetry(params) {
|
|
2447
|
+
const runWithRetry = createChannelApiRetryRunner({
|
|
2448
|
+
retry: {
|
|
2449
|
+
attempts: WHATSAPP_OUTBOUND_MAX_ATTEMPTS,
|
|
2450
|
+
minDelayMs: WHATSAPP_OUTBOUND_MIN_DELAY_MS,
|
|
2451
|
+
maxDelayMs: WHATSAPP_OUTBOUND_MAX_DELAY_MS,
|
|
2452
|
+
jitter: 0
|
|
2453
|
+
},
|
|
2454
|
+
strictShouldRetry: true,
|
|
2455
|
+
retryAfterMs: () => void 0,
|
|
2456
|
+
shouldRetry: (error, attempt) => {
|
|
2457
|
+
if (!(error instanceof WhatsAppOutboundRetryError) || !isRetryableWhatsAppOutboundError(error.original)) return false;
|
|
2458
|
+
params.onRetry?.({
|
|
2459
|
+
attempt,
|
|
2460
|
+
maxAttempts: WHATSAPP_OUTBOUND_MAX_ATTEMPTS,
|
|
2461
|
+
backoffMs: Math.min(WHATSAPP_OUTBOUND_MIN_DELAY_MS * 2 ** (attempt - 1), WHATSAPP_OUTBOUND_MAX_DELAY_MS),
|
|
2462
|
+
error: error.original,
|
|
2463
|
+
errorText: formatError(error.original)
|
|
2464
|
+
});
|
|
2465
|
+
return true;
|
|
2466
|
+
}
|
|
2467
|
+
});
|
|
2227
2468
|
try {
|
|
2228
|
-
return await
|
|
2469
|
+
return await runWithRetry(async () => {
|
|
2229
2470
|
try {
|
|
2230
2471
|
return await params.send();
|
|
2231
2472
|
} catch (error) {
|
|
2232
2473
|
throw new WhatsAppOutboundRetryError(error);
|
|
2233
2474
|
}
|
|
2234
|
-
}, {
|
|
2235
|
-
attempts: WHATSAPP_OUTBOUND_MAX_ATTEMPTS,
|
|
2236
|
-
minDelayMs: WHATSAPP_OUTBOUND_MIN_DELAY_MS,
|
|
2237
|
-
maxDelayMs: WHATSAPP_OUTBOUND_MAX_DELAY_MS,
|
|
2238
|
-
jitter: 0,
|
|
2239
|
-
shouldRetry: (error) => error instanceof WhatsAppOutboundRetryError && isRetryableWhatsAppOutboundError(error.original),
|
|
2240
|
-
onRetry: ({ attempt, maxAttempts, delayMs, err }) => {
|
|
2241
|
-
if (!(err instanceof WhatsAppOutboundRetryError)) return;
|
|
2242
|
-
params.onRetry?.({
|
|
2243
|
-
attempt,
|
|
2244
|
-
maxAttempts,
|
|
2245
|
-
backoffMs: delayMs,
|
|
2246
|
-
error: err.original,
|
|
2247
|
-
errorText: formatError(err.original)
|
|
2248
|
-
});
|
|
2249
|
-
}
|
|
2250
2475
|
});
|
|
2251
2476
|
} catch (error) {
|
|
2252
2477
|
if (error instanceof WhatsAppOutboundRetryError) throw error.original;
|
|
@@ -2505,25 +2730,34 @@ function isWhatsAppSupplementalSenderAllowed(params) {
|
|
|
2505
2730
|
return false;
|
|
2506
2731
|
}
|
|
2507
2732
|
function resolveVisibleWhatsAppGroupHistory(params) {
|
|
2508
|
-
if (params.groupPolicy !== "allowlist") return params.history;
|
|
2509
2733
|
return filterSupplementalContextItems({
|
|
2510
2734
|
items: params.history,
|
|
2511
2735
|
mode: params.mode,
|
|
2512
2736
|
kind: "history",
|
|
2513
|
-
isSenderAllowed: (entry) =>
|
|
2737
|
+
isSenderAllowed: (entry) => resolveInboundSupplementalSenderAllowed({
|
|
2738
|
+
isGroup: true,
|
|
2739
|
+
groupPolicy: params.groupPolicy,
|
|
2514
2740
|
allowFrom: params.groupAllowFrom,
|
|
2515
|
-
|
|
2516
|
-
|
|
2741
|
+
isSenderAllowed: (allowFrom) => isWhatsAppSupplementalSenderAllowed({
|
|
2742
|
+
allowFrom,
|
|
2743
|
+
authDir: params.authDir,
|
|
2744
|
+
sender: entry.senderJid ? { jid: entry.senderJid } : null
|
|
2745
|
+
})
|
|
2517
2746
|
})
|
|
2518
2747
|
}).items;
|
|
2519
2748
|
}
|
|
2520
2749
|
function resolveVisibleWhatsAppReplyContext(params) {
|
|
2521
2750
|
const replyTo = getReplyContext(params.msg, params.authDir);
|
|
2522
2751
|
if (!replyTo) return null;
|
|
2523
|
-
const senderAllowed =
|
|
2752
|
+
const senderAllowed = resolveInboundSupplementalSenderAllowed({
|
|
2753
|
+
isGroup: requireWhatsAppInboundAdmission(params.msg).conversation.kind === "group",
|
|
2754
|
+
groupPolicy: params.groupPolicy,
|
|
2524
2755
|
allowFrom: params.groupAllowFrom,
|
|
2525
|
-
|
|
2526
|
-
|
|
2756
|
+
isSenderAllowed: (allowFrom) => isWhatsAppSupplementalSenderAllowed({
|
|
2757
|
+
allowFrom,
|
|
2758
|
+
authDir: params.authDir,
|
|
2759
|
+
sender: replyTo.sender
|
|
2760
|
+
})
|
|
2527
2761
|
});
|
|
2528
2762
|
return filterChannelInboundQuoteContext(params.mode, {
|
|
2529
2763
|
id: replyTo.id,
|
|
@@ -2662,7 +2896,6 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2662
2896
|
}] : void 0, { transcribed: (_entry, index) => params.mediaTranscribedIndexes?.includes(index) === true });
|
|
2663
2897
|
return buildChannelInboundEventContext({
|
|
2664
2898
|
channel: "whatsapp",
|
|
2665
|
-
finalize: finalizeInboundContext,
|
|
2666
2899
|
supplemental: {
|
|
2667
2900
|
quote: params.visibleReplyTo ? {
|
|
2668
2901
|
id: params.visibleReplyTo.id,
|
|
@@ -2687,6 +2920,7 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2687
2920
|
},
|
|
2688
2921
|
route: {
|
|
2689
2922
|
agentId: params.route.agentId,
|
|
2923
|
+
dmScope: params.route.dmScope,
|
|
2690
2924
|
accountId: params.route.accountId,
|
|
2691
2925
|
routeSessionKey: params.route.sessionKey
|
|
2692
2926
|
},
|
|
@@ -2699,7 +2933,7 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2699
2933
|
bodyForAgent: params.bodyForAgent ?? params.msg.payload.body,
|
|
2700
2934
|
inboundHistory,
|
|
2701
2935
|
rawBody: params.rawBody ?? params.msg.payload.body,
|
|
2702
|
-
commandBody: params.
|
|
2936
|
+
commandBody: params.command?.body ?? params.msg.payload.body
|
|
2703
2937
|
},
|
|
2704
2938
|
access: {
|
|
2705
2939
|
...wasMentioned !== void 0 ? { mentions: {
|
|
@@ -2707,9 +2941,9 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2707
2941
|
wasMentioned,
|
|
2708
2942
|
requireMention: params.msg.groupMention?.requireMention
|
|
2709
2943
|
} } : {},
|
|
2710
|
-
commands: { authorized: params.
|
|
2944
|
+
commands: { authorized: params.command?.authorized === true }
|
|
2711
2945
|
},
|
|
2712
|
-
|
|
2946
|
+
command: params.command,
|
|
2713
2947
|
extra: {
|
|
2714
2948
|
Transcript: params.transcript,
|
|
2715
2949
|
GroupSubject: params.msg.group?.subject,
|
|
@@ -2719,40 +2953,12 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2719
2953
|
fallbackE164: params.sender.e164
|
|
2720
2954
|
}),
|
|
2721
2955
|
SenderE164: params.sender.e164,
|
|
2722
|
-
CommandSource: params.commandSource ?? (params.commandTurn?.source === "native" || params.commandTurn?.source === "text" ? params.commandTurn.source : void 0),
|
|
2723
2956
|
ReplyThreading: params.replyThreading,
|
|
2724
2957
|
SuppressMessageReceivedHooks: params.suppressMessageReceivedHooks,
|
|
2725
2958
|
...params.msg.payload.location ? toLocationContext(params.msg.payload.location) : {}
|
|
2726
2959
|
}
|
|
2727
2960
|
});
|
|
2728
2961
|
}
|
|
2729
|
-
function normalizeCommandTurnFromContext(value) {
|
|
2730
|
-
if (!value || typeof value !== "object") return;
|
|
2731
|
-
const record = value;
|
|
2732
|
-
const kind = record.kind;
|
|
2733
|
-
const source = record.source;
|
|
2734
|
-
if (kind === "native" && source === "native" && typeof record.authorized === "boolean") return {
|
|
2735
|
-
kind: "native",
|
|
2736
|
-
source: "native",
|
|
2737
|
-
authorized: record.authorized,
|
|
2738
|
-
commandName: typeof record.commandName === "string" ? record.commandName : void 0,
|
|
2739
|
-
body: typeof record.body === "string" ? record.body : void 0
|
|
2740
|
-
};
|
|
2741
|
-
if (kind === "text-slash" && source === "text" && typeof record.authorized === "boolean") return {
|
|
2742
|
-
kind: "text-slash",
|
|
2743
|
-
source: "text",
|
|
2744
|
-
authorized: record.authorized,
|
|
2745
|
-
commandName: typeof record.commandName === "string" ? record.commandName : void 0,
|
|
2746
|
-
body: typeof record.body === "string" ? record.body : void 0
|
|
2747
|
-
};
|
|
2748
|
-
if (kind === "normal" && source === "message") return {
|
|
2749
|
-
kind: "normal",
|
|
2750
|
-
source: "message",
|
|
2751
|
-
authorized: false,
|
|
2752
|
-
commandName: typeof record.commandName === "string" ? record.commandName : void 0,
|
|
2753
|
-
body: typeof record.body === "string" ? record.body : void 0
|
|
2754
|
-
};
|
|
2755
|
-
}
|
|
2756
2962
|
function resolveWhatsAppDmRouteTarget(params) {
|
|
2757
2963
|
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
2758
2964
|
const conversationId = admission.conversation.id;
|
|
@@ -2783,8 +2989,9 @@ function updateWhatsAppMainLastRoute(params) {
|
|
|
2783
2989
|
}
|
|
2784
2990
|
if (params.dmRouteTarget && inboundLastRouteSessionKey === params.route.mainSessionKey && params.pinnedMainDmRecipient) logVerbose$1(`Skipping main-session last route update for ${params.dmRouteTarget} (pinned owner ${params.pinnedMainDmRecipient})`);
|
|
2785
2991
|
}
|
|
2786
|
-
|
|
2992
|
+
function createWhatsAppReplyPlan(params) {
|
|
2787
2993
|
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
2994
|
+
const ingressLifecycle = resolveWhatsAppIngressLifecycle(params.msg);
|
|
2788
2995
|
const conversationId = admission.conversation.id;
|
|
2789
2996
|
const conversationKind = admission.conversation.kind;
|
|
2790
2997
|
const statusReactionController = params.statusReactionController ?? null;
|
|
@@ -2801,18 +3008,9 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2801
3008
|
accountId: params.route.accountId
|
|
2802
3009
|
});
|
|
2803
3010
|
const mediaLocalRoots = getAgentScopedMediaLocalRoots(params.cfg, params.route.agentId);
|
|
2804
|
-
const
|
|
2805
|
-
const sourceReplyCommandSource = params.context.CommandSource === "native" || params.context.CommandSource === "text" ? params.context.CommandSource : void 0;
|
|
2806
|
-
const sourceReplyCommandTurn = normalizeCommandTurnFromContext(params.context.CommandTurn);
|
|
2807
|
-
const sourceReplyCommandAuthorized = typeof params.context.CommandAuthorized === "boolean" ? params.context.CommandAuthorized : void 0;
|
|
2808
|
-
const sourceReplyDeliveryMode = sourceReplyChatType === "group" || sourceReplyChatType === "channel" ? resolveChannelMessageSourceReplyDeliveryMode({
|
|
3011
|
+
const sourceReplyDeliveryMode = params.context.ChatType === "group" || params.context.ChatType === "channel" ? resolveChannelMessageSourceReplyDeliveryMode({
|
|
2809
3012
|
cfg: params.cfg,
|
|
2810
|
-
ctx:
|
|
2811
|
-
ChatType: sourceReplyChatType,
|
|
2812
|
-
CommandTurn: sourceReplyCommandTurn,
|
|
2813
|
-
CommandSource: sourceReplyCommandSource,
|
|
2814
|
-
CommandAuthorized: sourceReplyCommandAuthorized
|
|
2815
|
-
}
|
|
3013
|
+
ctx: params.context
|
|
2816
3014
|
}) : void 0;
|
|
2817
3015
|
const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
|
|
2818
3016
|
const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveWhatsAppDisableBlockStreaming(params.cfg);
|
|
@@ -2859,11 +3057,10 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2859
3057
|
const mediaOnlyCoalescer = createWhatsAppMediaOnlyReplyCoalescer({ deliver: async (pending) => {
|
|
2860
3058
|
return await deliverNormalizedPayload(pending.payload, pending.info);
|
|
2861
3059
|
} });
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
replyResolver: params.replyResolver,
|
|
3060
|
+
return {
|
|
3061
|
+
afterRecord: () => {
|
|
3062
|
+
if (statusReactionController) statusReactionController.setThinking();
|
|
3063
|
+
},
|
|
2867
3064
|
dispatcherOptions: {
|
|
2868
3065
|
...params.replyPipeline,
|
|
2869
3066
|
onHeartbeatStrip: () => {
|
|
@@ -2872,6 +3069,14 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2872
3069
|
logVerbose$1("Stripped stray HEARTBEAT_OK token from web reply");
|
|
2873
3070
|
}
|
|
2874
3071
|
},
|
|
3072
|
+
onSettled: async () => {
|
|
3073
|
+
const flushResult = await mediaOnlyCoalescer.flushAll();
|
|
3074
|
+
logWhatsAppMediaOnlyFlushResult(flushResult);
|
|
3075
|
+
return whatsAppReplyDeliveryVisibility(flushResult.delivered > 0);
|
|
3076
|
+
},
|
|
3077
|
+
onReplyStart: params.msg.platform.sendComposing
|
|
3078
|
+
},
|
|
3079
|
+
delivery: {
|
|
2875
3080
|
deliver: async (payload, info) => {
|
|
2876
3081
|
const deliveryPayload = resolveWhatsAppDeliverablePayload(payload, info);
|
|
2877
3082
|
if (!deliveryPayload) return whatsAppReplyDeliveryVisibility(false);
|
|
@@ -2922,8 +3127,8 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2922
3127
|
return whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
|
|
2923
3128
|
}
|
|
2924
3129
|
if (durable.status === "handled_no_send") return flushResult.delivered > 0 ? whatsAppReplyDeliveryVisibility(true) : whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
|
|
2925
|
-
const
|
|
2926
|
-
return flushResult.delivered > 0 && !
|
|
3130
|
+
const deliveryResult = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
|
|
3131
|
+
return flushResult.delivered > 0 && !deliveryResult.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : deliveryResult;
|
|
2927
3132
|
} catch (error) {
|
|
2928
3133
|
throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
|
|
2929
3134
|
}
|
|
@@ -2944,27 +3149,12 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2944
3149
|
const flushResult = await mediaOnlyCoalescer.flushExceptDuplicateMedia(mediaUrls);
|
|
2945
3150
|
logWhatsAppMediaOnlyFlushResult(flushResult);
|
|
2946
3151
|
try {
|
|
2947
|
-
const
|
|
2948
|
-
return flushResult.delivered > 0 && !
|
|
3152
|
+
const deliveryResult = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
|
|
3153
|
+
return flushResult.delivered > 0 && !deliveryResult.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : deliveryResult;
|
|
2949
3154
|
} catch (error) {
|
|
2950
3155
|
throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
|
|
2951
3156
|
}
|
|
2952
3157
|
},
|
|
2953
|
-
onSettled: async () => {
|
|
2954
|
-
const flushResult = await mediaOnlyCoalescer.flushAll();
|
|
2955
|
-
logWhatsAppMediaOnlyFlushResult(flushResult);
|
|
2956
|
-
return whatsAppReplyDeliveryVisibility(flushResult.delivered > 0);
|
|
2957
|
-
},
|
|
2958
|
-
onReplyStart: params.msg.platform.sendComposing,
|
|
2959
|
-
...statusReactionController ? {
|
|
2960
|
-
onCompactionStart: async () => {
|
|
2961
|
-
await statusReactionController.setCompacting();
|
|
2962
|
-
},
|
|
2963
|
-
onCompactionEnd: async () => {
|
|
2964
|
-
statusReactionController.cancelPending();
|
|
2965
|
-
await statusReactionController.setThinking();
|
|
2966
|
-
}
|
|
2967
|
-
} : {},
|
|
2968
3158
|
onError: (err, info) => {
|
|
2969
3159
|
logWhatsAppReplyDeliveryError({
|
|
2970
3160
|
err,
|
|
@@ -2976,39 +3166,52 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2976
3166
|
}
|
|
2977
3167
|
},
|
|
2978
3168
|
replyOptions: {
|
|
3169
|
+
...ingressLifecycle ? bindIngressLifecycleToReplyOptions(ingressLifecycle) : {},
|
|
2979
3170
|
suppressTyping: sourceRepliesAreToolOnly && conversationKind === "group" && !(params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned),
|
|
2980
3171
|
disableBlockStreaming,
|
|
2981
3172
|
...sourceReplyDeliveryMode ? { sourceReplyDeliveryMode } : {},
|
|
2982
3173
|
onModelSelected: params.onModelSelected,
|
|
2983
|
-
...statusReactionController ? {
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
3174
|
+
...statusReactionController ? {
|
|
3175
|
+
onToolStart: async (payload) => {
|
|
3176
|
+
const toolName = payload.name?.trim();
|
|
3177
|
+
if (toolName) await statusReactionController.setTool(toolName);
|
|
3178
|
+
},
|
|
3179
|
+
onCompactionStart: async () => {
|
|
3180
|
+
await statusReactionController.setCompacting();
|
|
3181
|
+
},
|
|
3182
|
+
onCompactionEnd: async () => {
|
|
3183
|
+
statusReactionController.cancelPending();
|
|
3184
|
+
await statusReactionController.setThinking();
|
|
3185
|
+
}
|
|
3186
|
+
} : {}
|
|
3187
|
+
},
|
|
3188
|
+
replyResolver: params.replyResolver,
|
|
3189
|
+
finalize: (dispatchResult) => {
|
|
3190
|
+
const didQueueVisibleReply = hasVisibleInboundReplyDispatch(dispatchResult);
|
|
3191
|
+
const didDeliverVisibleReply = didSendReply || dispatchResult.observedReplyDelivery === true;
|
|
3192
|
+
if (!didQueueVisibleReply) {
|
|
3193
|
+
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
3194
|
+
controller: statusReactionController,
|
|
3195
|
+
outcome: "error",
|
|
3196
|
+
hasFinalResponse: false,
|
|
3197
|
+
removeAckAfterReply,
|
|
3198
|
+
timing: statusReactionTiming
|
|
3199
|
+
});
|
|
3200
|
+
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
3201
|
+
logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
|
|
3202
|
+
return false;
|
|
3203
|
+
}
|
|
3204
|
+
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
3205
|
+
controller: statusReactionController,
|
|
3206
|
+
outcome: didDeliverVisibleReply ? "done" : "error",
|
|
3207
|
+
hasFinalResponse: didDeliverVisibleReply,
|
|
3208
|
+
removeAckAfterReply,
|
|
3209
|
+
timing: statusReactionTiming
|
|
3210
|
+
});
|
|
3211
|
+
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
3212
|
+
return didDeliverVisibleReply;
|
|
2987
3213
|
}
|
|
2988
|
-
}
|
|
2989
|
-
const didQueueVisibleReply = hasVisibleInboundReplyDispatch(dispatchResult);
|
|
2990
|
-
const didDeliverVisibleReply = didSendReply || dispatchResult.observedReplyDelivery === true;
|
|
2991
|
-
if (!didQueueVisibleReply) {
|
|
2992
|
-
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
2993
|
-
controller: statusReactionController,
|
|
2994
|
-
outcome: "error",
|
|
2995
|
-
hasFinalResponse: false,
|
|
2996
|
-
removeAckAfterReply,
|
|
2997
|
-
timing: statusReactionTiming
|
|
2998
|
-
});
|
|
2999
|
-
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
3000
|
-
logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
|
|
3001
|
-
return false;
|
|
3002
|
-
}
|
|
3003
|
-
if (statusReactionController) finalizeWhatsAppStatusReaction({
|
|
3004
|
-
controller: statusReactionController,
|
|
3005
|
-
outcome: didDeliverVisibleReply ? "done" : "error",
|
|
3006
|
-
hasFinalResponse: didDeliverVisibleReply,
|
|
3007
|
-
removeAckAfterReply,
|
|
3008
|
-
timing: statusReactionTiming
|
|
3009
|
-
});
|
|
3010
|
-
if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
|
|
3011
|
-
return didDeliverVisibleReply;
|
|
3214
|
+
};
|
|
3012
3215
|
}
|
|
3013
3216
|
async function finalizeWhatsAppStatusReaction(params) {
|
|
3014
3217
|
if (params.outcome === "done") {
|
|
@@ -3343,24 +3546,13 @@ async function processMessage(params) {
|
|
|
3343
3546
|
normalizeE164
|
|
3344
3547
|
});
|
|
3345
3548
|
const shouldCheckCommandAuth = shouldComputeCommandAuthorized(commandBody, params.cfg);
|
|
3346
|
-
const isTextCommand = isControlCommandMessage
|
|
3549
|
+
const isTextCommand = isControlCommandMessage(commandBody, params.cfg);
|
|
3347
3550
|
const commandAuthorized = shouldCheckCommandAuth ? await resolveWhatsAppCommandAuthorized({
|
|
3348
3551
|
cfg: params.cfg,
|
|
3349
3552
|
msg: params.msg,
|
|
3350
3553
|
policy: inboundPolicy,
|
|
3351
3554
|
authDir: account.authDir
|
|
3352
3555
|
}) : void 0;
|
|
3353
|
-
const commandTurn = isTextCommand ? {
|
|
3354
|
-
kind: "text-slash",
|
|
3355
|
-
source: "text",
|
|
3356
|
-
authorized: Boolean(commandAuthorized),
|
|
3357
|
-
body: commandBody
|
|
3358
|
-
} : {
|
|
3359
|
-
kind: "normal",
|
|
3360
|
-
source: "message",
|
|
3361
|
-
authorized: false,
|
|
3362
|
-
body: commandBody
|
|
3363
|
-
};
|
|
3364
3556
|
const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
|
|
3365
3557
|
cfg: params.cfg,
|
|
3366
3558
|
agentId: params.route.agentId,
|
|
@@ -3384,9 +3576,11 @@ async function processMessage(params) {
|
|
|
3384
3576
|
const ctxPayload = await buildWhatsAppInboundContext({
|
|
3385
3577
|
bodyForAgent: msgForAgent.payload.body,
|
|
3386
3578
|
combinedBody,
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3579
|
+
command: {
|
|
3580
|
+
kind: isTextCommand ? "text-slash" : "normal",
|
|
3581
|
+
body: commandBody,
|
|
3582
|
+
authorized: commandAuthorized
|
|
3583
|
+
},
|
|
3390
3584
|
groupHistory: visibleGroupHistory,
|
|
3391
3585
|
groupMemberRoster: params.groupMemberNames.get(params.groupHistoryKey),
|
|
3392
3586
|
groupSystemPrompt: conversationSystemPrompt,
|
|
@@ -3424,6 +3618,7 @@ async function processMessage(params) {
|
|
|
3424
3618
|
updateLastRoute: updateLastRouteInBackground,
|
|
3425
3619
|
warn: params.replyLogger.warn.bind(params.replyLogger)
|
|
3426
3620
|
});
|
|
3621
|
+
let finalizeReply;
|
|
3427
3622
|
const turnResult = await runChannelInboundEvent({
|
|
3428
3623
|
channel: "whatsapp",
|
|
3429
3624
|
accountId: params.route.accountId,
|
|
@@ -3457,26 +3652,8 @@ async function processMessage(params) {
|
|
|
3457
3652
|
recordHistory: false
|
|
3458
3653
|
} };
|
|
3459
3654
|
},
|
|
3460
|
-
resolveTurn: () =>
|
|
3461
|
-
|
|
3462
|
-
accountId: params.route.accountId,
|
|
3463
|
-
routeSessionKey: params.route.sessionKey,
|
|
3464
|
-
storePath,
|
|
3465
|
-
ctxPayload,
|
|
3466
|
-
recordInboundSession,
|
|
3467
|
-
record: {
|
|
3468
|
-
onRecordError: (err) => {
|
|
3469
|
-
params.replyLogger.warn({
|
|
3470
|
-
error: formatError(err),
|
|
3471
|
-
storePath,
|
|
3472
|
-
sessionKey: params.route.sessionKey
|
|
3473
|
-
}, "failed updating session meta");
|
|
3474
|
-
},
|
|
3475
|
-
trackSessionMetaTask: (task) => {
|
|
3476
|
-
trackBackgroundTask(params.backgroundTasks, task);
|
|
3477
|
-
}
|
|
3478
|
-
},
|
|
3479
|
-
runDispatch: () => dispatchWhatsAppBufferedReply({
|
|
3655
|
+
resolveTurn: () => {
|
|
3656
|
+
const { finalize, ...replyPlan } = createWhatsAppReplyPlan({
|
|
3480
3657
|
cfg: params.cfg,
|
|
3481
3658
|
connectionId: params.connectionId,
|
|
3482
3659
|
context: ctxPayload,
|
|
@@ -3497,11 +3674,35 @@ async function processMessage(params) {
|
|
|
3497
3674
|
route: params.route,
|
|
3498
3675
|
shouldClearGroupHistory,
|
|
3499
3676
|
statusReactionController
|
|
3500
|
-
})
|
|
3501
|
-
|
|
3677
|
+
});
|
|
3678
|
+
finalizeReply = finalize;
|
|
3679
|
+
return {
|
|
3680
|
+
cfg: params.cfg,
|
|
3681
|
+
channel: "whatsapp",
|
|
3682
|
+
accountId: params.route.accountId,
|
|
3683
|
+
route: {
|
|
3684
|
+
agentId: params.route.agentId,
|
|
3685
|
+
sessionKey: params.route.sessionKey
|
|
3686
|
+
},
|
|
3687
|
+
ctxPayload,
|
|
3688
|
+
record: {
|
|
3689
|
+
onRecordError: (err) => {
|
|
3690
|
+
params.replyLogger.warn({
|
|
3691
|
+
error: formatError(err),
|
|
3692
|
+
storePath,
|
|
3693
|
+
sessionKey: params.route.sessionKey
|
|
3694
|
+
}, "failed updating session meta");
|
|
3695
|
+
},
|
|
3696
|
+
trackSessionMetaTask: (task) => {
|
|
3697
|
+
trackBackgroundTask(params.backgroundTasks, task);
|
|
3698
|
+
}
|
|
3699
|
+
},
|
|
3700
|
+
...replyPlan
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3502
3703
|
}
|
|
3503
3704
|
});
|
|
3504
|
-
const didSendReply = turnResult.dispatched ? turnResult.dispatchResult : false;
|
|
3705
|
+
const didSendReply = turnResult.dispatched ? finalizeReply?.(turnResult.dispatchResult) ?? false : false;
|
|
3505
3706
|
removeAckReactionHandleAfterReply({
|
|
3506
3707
|
removeAfterReply: Boolean(params.cfg.messages?.removeAckAfterReply && didSendReply),
|
|
3507
3708
|
ackReaction,
|
|
@@ -3845,6 +4046,7 @@ function isRetryableAuthUnstableError(error) {
|
|
|
3845
4046
|
return error instanceof WhatsAppAuthUnstableError || typeof error === "object" && error !== null && "code" in error && error.code === "whatsapp-auth-unstable";
|
|
3846
4047
|
}
|
|
3847
4048
|
const DEFAULT_TRANSPORT_TIMEOUT_MS = 300 * 1e3;
|
|
4049
|
+
const WHATSAPP_RECONNECT_CATCH_UP_MAX_MS = 20 * 6e4;
|
|
3848
4050
|
async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSocket, keepAlive = true, replyResolver, runtime = defaultRuntime, abortSignal, tuning = {}) {
|
|
3849
4051
|
const activeReplyResolver = replyResolver ?? (await loadReplyResolverRuntime()).getReplyFromConfig;
|
|
3850
4052
|
const runId = newConnectionId();
|
|
@@ -3896,7 +4098,7 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
3896
4098
|
process.once("SIGINT", handleSigint);
|
|
3897
4099
|
const transportTimeoutMs = tuning.transportTimeoutMs ?? DEFAULT_TRANSPORT_TIMEOUT_MS;
|
|
3898
4100
|
const messageTimeoutMs = tuning.messageTimeoutMs ?? 1800 * 1e3;
|
|
3899
|
-
const reconnectCatchUpWindowMs = Math.min(Math.max(messageTimeoutMs, 6e4),
|
|
4101
|
+
const reconnectCatchUpWindowMs = Math.min(Math.max(messageTimeoutMs, 6e4), WHATSAPP_RECONNECT_CATCH_UP_MAX_MS);
|
|
3900
4102
|
const watchdogCheckMs = tuning.watchdogCheckMs ?? 60 * 1e3;
|
|
3901
4103
|
const controller = new WhatsAppConnectionController({
|
|
3902
4104
|
accountId: account.accountId,
|
|
@@ -3930,10 +4132,12 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
3930
4132
|
});
|
|
3931
4133
|
const shouldDebounce = (msg) => {
|
|
3932
4134
|
const normalized = normalizeWebInboundMessage(msg);
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
4135
|
+
return shouldDebounceTextInbound({
|
|
4136
|
+
text: normalized.payload.commandBody ?? normalized.payload.body,
|
|
4137
|
+
cfg,
|
|
4138
|
+
hasMedia: Boolean(normalized.payload.media?.path || normalized.payload.media?.type),
|
|
4139
|
+
allowDebounce: !(normalized.payload.location || normalized.quote?.id || normalized.quote?.body)
|
|
4140
|
+
});
|
|
3937
4141
|
};
|
|
3938
4142
|
let connection;
|
|
3939
4143
|
try {
|