@openclaw/whatsapp 2026.7.2-beta.2 → 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-DbadDUDD.js → channel-B2I4Opzb.js} +18 -9
- 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-BDDy4Lhz.js → channel.runtime-CjkhQzk8.js} +4 -4
- package/dist/{channel.setup-BZ-po8fg.js → channel.setup-BMWE9LmN.js} +1 -1
- 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/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-BZ8QvCeV.js → monitor-_jqDKBXj.js} +544 -295
- 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-3aSOoK2T.js → setup-core-BbIgEl2M.js} +3 -3
- package/dist/{setup-finalize-DBf1jjBj.js → setup-finalize-Cc-4iWGj.js} +3 -3
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{setup-surface-CiKGdNJv.js → setup-surface-DhnXEZir.js} +2 -2
- package/dist/{session-mxWFn0eS.js → socket-close-jNGyZY4T.js} +221 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
- package/dist/account-config-LiT9wWH9.js +0 -43
- package/dist/directory-config-CLBQrjtC.js +0 -37
|
@@ -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
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,19 +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, shouldDebounceTextInbound, 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, 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
44
|
import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runtime";
|
|
46
45
|
import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, buildInboundHistoryFromEntries, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
47
46
|
import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
|
|
48
47
|
import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
|
|
49
|
-
import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
50
48
|
import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
|
|
51
49
|
import { getRuntimeConfig as getRuntimeConfig$1, getRuntimeConfigSourceSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
52
50
|
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
@@ -60,50 +58,6 @@ import { createInternalHookEvent, deriveInboundMessageHookContext, fireAndForget
|
|
|
60
58
|
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
61
59
|
import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
|
|
62
60
|
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
61
|
//#region extensions/whatsapp/src/inbound/message-aliases.ts
|
|
108
62
|
function normalizeQuoteSender(sender) {
|
|
109
63
|
if (!sender?.displayName && !sender?.jid && !sender?.e164) return;
|
|
@@ -428,9 +382,63 @@ function normalizeWebInboundMessage(msg) {
|
|
|
428
382
|
return normalizeLegacyFlatWebInboundMessage(msg);
|
|
429
383
|
}
|
|
430
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
|
|
431
439
|
//#region extensions/whatsapp/src/inbound/durable-receive.ts
|
|
432
440
|
const WHATSAPP_DURABLE_INBOUND_PENDING_MAX_ENTRIES = 450;
|
|
433
|
-
const WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES =
|
|
441
|
+
const WHATSAPP_DURABLE_INBOUND_COMPLETED_MAX_ENTRIES = 5e3;
|
|
434
442
|
const WHATSAPP_DURABLE_INBOUND_PENDING_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
435
443
|
const WHATSAPP_DURABLE_INBOUND_COMPLETED_TTL_MS = 10080 * 60 * 1e3;
|
|
436
444
|
function hashNamespacePart(value) {
|
|
@@ -439,31 +447,91 @@ function hashNamespacePart(value) {
|
|
|
439
447
|
function createWhatsAppDurableInboundMessageId(params) {
|
|
440
448
|
return createHash("sha256").update(`${params.remoteJid}\n${params.id}`).digest("hex");
|
|
441
449
|
}
|
|
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()
|
|
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
|
|
455
458
|
}),
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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);
|
|
463
521
|
}
|
|
464
522
|
});
|
|
465
523
|
}
|
|
466
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
|
|
467
535
|
//#region extensions/whatsapp/src/inbound/lifecycle.ts
|
|
468
536
|
function attachEmitterListener(emitter, event, listener) {
|
|
469
537
|
emitter.on(event, listener);
|
|
@@ -533,13 +601,8 @@ const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress
|
|
|
533
601
|
const GROUP_META_TTL_MS = 300 * 1e3;
|
|
534
602
|
const BAILEYS_MESSAGE_TTL_MS = 600 * 1e3;
|
|
535
603
|
const INBOUND_CLOSE_DRAIN_TIMEOUT_MS = 5e3;
|
|
536
|
-
const
|
|
604
|
+
const WHATSAPP_INGRESS_DRAIN_INTERVAL_MS = 1e3;
|
|
537
605
|
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
606
|
function resolveGroupMetadataExpiresAt(nowRaw = Date.now()) {
|
|
544
607
|
const now = asDateTimestampMs(nowRaw);
|
|
545
608
|
return now === void 0 ? void 0 : resolveExpiresAtMsFromDurationMs(GROUP_META_TTL_MS, { nowMs: now });
|
|
@@ -630,9 +693,6 @@ function isRetryableSendDisconnectError(err) {
|
|
|
630
693
|
function shouldClearSocketRefAfterSendFailure(err) {
|
|
631
694
|
return /closed|reset|disconnect|no active socket/i.test(formatError(err));
|
|
632
695
|
}
|
|
633
|
-
function isNonEmptyString(value) {
|
|
634
|
-
return Boolean(value);
|
|
635
|
-
}
|
|
636
696
|
async function attachWebInboxToSocket(options) {
|
|
637
697
|
const inboundLogger = getChildLogger({ module: "web-inbound" });
|
|
638
698
|
const inboundConsoleLog = createSubsystemLogger("gateway/channels/whatsapp").child("inbound");
|
|
@@ -673,11 +733,30 @@ async function attachWebInboxToSocket(options) {
|
|
|
673
733
|
if (!successorIdentity || !identitiesOverlap(self, successorIdentity)) return null;
|
|
674
734
|
return successor.getCurrentSock();
|
|
675
735
|
};
|
|
676
|
-
const
|
|
736
|
+
const durableInboundQueue = options.durableInboundQueue ?? createWhatsAppDurableInboundQueue(options.accountId);
|
|
677
737
|
const inboundDebounceMs = Math.max(0, Math.trunc(options.debounceMs ?? 0));
|
|
678
738
|
const pendingDebounceKeys = /* @__PURE__ */ new Set();
|
|
679
739
|
const activeInboundFlushes = /* @__PURE__ */ new Set();
|
|
680
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
|
+
};
|
|
681
760
|
let nextReceiveOrder = 0;
|
|
682
761
|
const publishPendingWorkState = (at = Date.now()) => {
|
|
683
762
|
options.onPendingWorkChanged?.(pendingMessageHandlers.size + pendingDebounceKeys.size + activeInboundFlushes.size, at);
|
|
@@ -695,18 +774,47 @@ async function attachWebInboxToSocket(options) {
|
|
|
695
774
|
if (timestampDiff !== 0) return timestampDiff;
|
|
696
775
|
return (a.receiveOrder ?? 0) - (b.receiveOrder ?? 0);
|
|
697
776
|
});
|
|
698
|
-
const
|
|
699
|
-
const
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
+
};
|
|
710
818
|
};
|
|
711
819
|
const debouncer = createInboundDebouncer({
|
|
712
820
|
debounceMs: inboundDebounceMs,
|
|
@@ -719,14 +827,17 @@ async function attachWebInboxToSocket(options) {
|
|
|
719
827
|
});
|
|
720
828
|
activeInboundFlushes.add(flushTask);
|
|
721
829
|
publishPendingWorkState();
|
|
830
|
+
notifyDebounceWork();
|
|
722
831
|
try {
|
|
723
832
|
const orderedEntries = orderDebouncedInboundEntries(entries);
|
|
724
833
|
const last = orderedEntries.at(-1);
|
|
725
834
|
if (!last) return;
|
|
835
|
+
const { lifecycle, settle, abandon } = buildFlushIngressLifecycle(orderedEntries);
|
|
726
836
|
try {
|
|
727
837
|
if (orderedEntries.length === 1) {
|
|
728
|
-
await options.onMessage(last);
|
|
729
|
-
await
|
|
838
|
+
await options.onMessage(attachWhatsAppIngressLifecycle(last, lifecycle));
|
|
839
|
+
await settle();
|
|
840
|
+
await Promise.all(orderedEntries.map((entry) => maybeMarkInboundAsRead(entry.readReceipt)));
|
|
730
841
|
return;
|
|
731
842
|
}
|
|
732
843
|
const mentioned = /* @__PURE__ */ new Set();
|
|
@@ -741,8 +852,9 @@ async function attachWebInboxToSocket(options) {
|
|
|
741
852
|
...last.group,
|
|
742
853
|
mentions: combinedMentions
|
|
743
854
|
} : void 0;
|
|
744
|
-
const combinedMessage = withDeprecatedWebInboundMessageFlatAliases({
|
|
855
|
+
const combinedMessage = attachWhatsAppIngressLifecycle(withDeprecatedWebInboundMessageFlatAliases({
|
|
745
856
|
...last,
|
|
857
|
+
...lifecycle ? { turnAdoptionLifecycle: lifecycle } : {},
|
|
746
858
|
payload: {
|
|
747
859
|
...last.payload,
|
|
748
860
|
body: combinedBody,
|
|
@@ -753,11 +865,12 @@ async function attachWebInboxToSocket(options) {
|
|
|
753
865
|
...last.event,
|
|
754
866
|
isBatched: true
|
|
755
867
|
}
|
|
756
|
-
});
|
|
868
|
+
}), lifecycle);
|
|
757
869
|
await options.onMessage(combinedMessage);
|
|
758
|
-
await
|
|
870
|
+
await settle();
|
|
871
|
+
await Promise.all(orderedEntries.map((entry) => maybeMarkInboundAsRead(entry.readReceipt)));
|
|
759
872
|
} catch (error) {
|
|
760
|
-
await
|
|
873
|
+
await abandon();
|
|
761
874
|
throw error;
|
|
762
875
|
}
|
|
763
876
|
} finally {
|
|
@@ -972,20 +1085,24 @@ async function attachWebInboxToSocket(options) {
|
|
|
972
1085
|
}
|
|
973
1086
|
return content;
|
|
974
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
|
+
};
|
|
975
1099
|
const normalizeInboundMessage = async (msg) => {
|
|
976
1100
|
const id = msg.key?.id ?? void 0;
|
|
977
1101
|
const remoteJid = msg.key?.remoteJid;
|
|
978
1102
|
if (!remoteJid) return null;
|
|
979
1103
|
if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) return null;
|
|
980
1104
|
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
|
-
}
|
|
1105
|
+
if (shouldSkipRecentOutboundEcho(msg)) return null;
|
|
989
1106
|
if (!hasInboundUserContent(msg.message ?? void 0)) return null;
|
|
990
1107
|
const participantJid = msg.key?.participant ?? void 0;
|
|
991
1108
|
const from = group ? remoteJid : await resolveInboundJid(remoteJid);
|
|
@@ -1061,15 +1178,6 @@ async function attachWebInboxToSocket(options) {
|
|
|
1061
1178
|
}
|
|
1062
1179
|
await maybeMarkInboundAsRead(target);
|
|
1063
1180
|
};
|
|
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
1181
|
const shouldSkipStaleAppend = (msg, upsertType) => {
|
|
1074
1182
|
if (upsertType !== "append") return false;
|
|
1075
1183
|
const APPEND_RECENT_GRACE_MS = 6e4;
|
|
@@ -1078,70 +1186,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1078
1186
|
const nowMs = Date.now();
|
|
1079
1187
|
return msgTsMs < (options.appendReplyWindow && nowMs <= options.appendReplyWindow.untilMs ? Math.max(options.appendReplyWindow.afterMs, nowMs - options.appendReplyWindow.maxAgeMs) : connectedAtMs - APPEND_RECENT_GRACE_MS);
|
|
1080
1188
|
};
|
|
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
|
-
};
|
|
1189
|
+
const preparedInboundByDurableId = /* @__PURE__ */ new Map();
|
|
1145
1190
|
const enrichInboundMessage = async (msg) => {
|
|
1146
1191
|
const location = extractLocationData(msg.message ?? void 0);
|
|
1147
1192
|
const locationText = location ? formatLocationText(location) : void 0;
|
|
@@ -1296,8 +1341,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1296
1341
|
}
|
|
1297
1342
|
} : void 0,
|
|
1298
1343
|
group,
|
|
1299
|
-
|
|
1300
|
-
durableId: durable.durableId,
|
|
1344
|
+
turnAdoptionLifecycle: durable.turnAdoptionLifecycle,
|
|
1301
1345
|
readReceipt: durable.readReceipt,
|
|
1302
1346
|
receiveOrder: durable.receiveOrder
|
|
1303
1347
|
});
|
|
@@ -1307,6 +1351,7 @@ async function attachWebInboxToSocket(options) {
|
|
|
1307
1351
|
if (inboundDebounceMs > 0 && shouldDebounceInboundMessage(inboundMessage)) {
|
|
1308
1352
|
pendingDebounceKeys.add(debounceKey);
|
|
1309
1353
|
publishPendingWorkState();
|
|
1354
|
+
notifyDebounceWork();
|
|
1310
1355
|
}
|
|
1311
1356
|
}
|
|
1312
1357
|
if (inboundMessage.event.id) {
|
|
@@ -1318,16 +1363,119 @@ async function attachWebInboxToSocket(options) {
|
|
|
1318
1363
|
fromMe: inboundMessage.platform.fromMe
|
|
1319
1364
|
});
|
|
1320
1365
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
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;
|
|
1325
1424
|
});
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
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);
|
|
1329
1436
|
}
|
|
1437
|
+
return durableDrainPass;
|
|
1438
|
+
};
|
|
1439
|
+
const requestDrainAfterLaneSettlement = () => {
|
|
1440
|
+
requestDurableInboundDrain().catch((error) => {
|
|
1441
|
+
inboundLogger.error({ error: formatError(error) }, "whatsapp durable inbound drain failed");
|
|
1442
|
+
});
|
|
1330
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?.();
|
|
1331
1479
|
const handleMessagesUpsert = async (upsert) => {
|
|
1332
1480
|
if (upsert.type !== "notify" && upsert.type !== "append") return;
|
|
1333
1481
|
for (const msg of upsert.messages ?? []) {
|
|
@@ -1343,7 +1491,78 @@ async function attachWebInboxToSocket(options) {
|
|
|
1343
1491
|
resolveReactionTargetJids,
|
|
1344
1492
|
logVerboseMessage: (message) => logWhatsAppVerbose(options.verbose, message)
|
|
1345
1493
|
})) continue;
|
|
1346
|
-
|
|
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
|
+
}
|
|
1347
1566
|
}
|
|
1348
1567
|
};
|
|
1349
1568
|
const handleMessagesUpsertEvent = (upsert) => {
|
|
@@ -1358,9 +1577,6 @@ async function attachWebInboxToSocket(options) {
|
|
|
1358
1577
|
publishPendingWorkState();
|
|
1359
1578
|
});
|
|
1360
1579
|
};
|
|
1361
|
-
const waitForPendingMessageHandlers = async () => {
|
|
1362
|
-
while (pendingMessageHandlers.size > 0) await Promise.all(Array.from(pendingMessageHandlers));
|
|
1363
|
-
};
|
|
1364
1580
|
const drainDebouncedInboundMessages = async () => {
|
|
1365
1581
|
while (pendingDebounceKeys.size > 0 || activeInboundFlushes.size > 0) {
|
|
1366
1582
|
const debounceKeys = Array.from(pendingDebounceKeys);
|
|
@@ -1372,8 +1588,21 @@ async function attachWebInboxToSocket(options) {
|
|
|
1372
1588
|
};
|
|
1373
1589
|
const drainInboundBeforeSocketClose = async () => {
|
|
1374
1590
|
groupMetadataCacheClosed = true;
|
|
1375
|
-
|
|
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
|
+
}
|
|
1376
1599
|
await drainDebouncedInboundMessages();
|
|
1600
|
+
try {
|
|
1601
|
+
await durableInboundDrain.waitForIdle();
|
|
1602
|
+
} finally {
|
|
1603
|
+
durableDrainClosed = true;
|
|
1604
|
+
durableInboundDrain.dispose();
|
|
1605
|
+
}
|
|
1377
1606
|
};
|
|
1378
1607
|
const drainInboundBeforeSocketCloseWithTimeout = async () => {
|
|
1379
1608
|
let timeout = null;
|
|
@@ -1386,6 +1615,8 @@ async function attachWebInboxToSocket(options) {
|
|
|
1386
1615
|
})]);
|
|
1387
1616
|
} finally {
|
|
1388
1617
|
if (timeout) clearTimeout(timeout);
|
|
1618
|
+
durableDrainClosed = true;
|
|
1619
|
+
durableInboundDrain.dispose();
|
|
1389
1620
|
}
|
|
1390
1621
|
};
|
|
1391
1622
|
const handleConnectionUpdate = (update) => {
|
|
@@ -1444,16 +1675,10 @@ async function attachWebInboxToSocket(options) {
|
|
|
1444
1675
|
const detachGroupParticipantsUpdate = attachSockListener("group-participants.update", ((update) => {
|
|
1445
1676
|
forgetFullGroupMetadata(update.id);
|
|
1446
1677
|
}));
|
|
1447
|
-
|
|
1678
|
+
requestDurableInboundDrain().catch((err) => {
|
|
1448
1679
|
inboundLogger.error({ error: String(err) }, "failed replaying durable WhatsApp inbound");
|
|
1449
1680
|
inboundConsoleLog.error(`Failed replaying durable WhatsApp inbound: ${String(err)}`);
|
|
1450
1681
|
});
|
|
1451
|
-
pendingMessageHandlers.add(replayTask);
|
|
1452
|
-
publishPendingWorkState();
|
|
1453
|
-
replayTask.finally(() => {
|
|
1454
|
-
pendingMessageHandlers.delete(replayTask);
|
|
1455
|
-
publishPendingWorkState();
|
|
1456
|
-
});
|
|
1457
1682
|
(async () => {
|
|
1458
1683
|
try {
|
|
1459
1684
|
const groups = await sock.groupFetchAllParticipating();
|
|
@@ -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,
|
|
@@ -2686,6 +2920,7 @@ async function buildWhatsAppInboundContext(params) {
|
|
|
2686
2920
|
},
|
|
2687
2921
|
route: {
|
|
2688
2922
|
agentId: params.route.agentId,
|
|
2923
|
+
dmScope: params.route.dmScope,
|
|
2689
2924
|
accountId: params.route.accountId,
|
|
2690
2925
|
routeSessionKey: params.route.sessionKey
|
|
2691
2926
|
},
|
|
@@ -2754,8 +2989,9 @@ function updateWhatsAppMainLastRoute(params) {
|
|
|
2754
2989
|
}
|
|
2755
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})`);
|
|
2756
2991
|
}
|
|
2757
|
-
|
|
2992
|
+
function createWhatsAppReplyPlan(params) {
|
|
2758
2993
|
const admission = requireWhatsAppInboundAdmission(params.msg);
|
|
2994
|
+
const ingressLifecycle = resolveWhatsAppIngressLifecycle(params.msg);
|
|
2759
2995
|
const conversationId = admission.conversation.id;
|
|
2760
2996
|
const conversationKind = admission.conversation.kind;
|
|
2761
2997
|
const statusReactionController = params.statusReactionController ?? null;
|
|
@@ -2821,11 +3057,10 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2821
3057
|
const mediaOnlyCoalescer = createWhatsAppMediaOnlyReplyCoalescer({ deliver: async (pending) => {
|
|
2822
3058
|
return await deliverNormalizedPayload(pending.payload, pending.info);
|
|
2823
3059
|
} });
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
replyResolver: params.replyResolver,
|
|
3060
|
+
return {
|
|
3061
|
+
afterRecord: () => {
|
|
3062
|
+
if (statusReactionController) statusReactionController.setThinking();
|
|
3063
|
+
},
|
|
2829
3064
|
dispatcherOptions: {
|
|
2830
3065
|
...params.replyPipeline,
|
|
2831
3066
|
onHeartbeatStrip: () => {
|
|
@@ -2834,6 +3069,14 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2834
3069
|
logVerbose$1("Stripped stray HEARTBEAT_OK token from web reply");
|
|
2835
3070
|
}
|
|
2836
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: {
|
|
2837
3080
|
deliver: async (payload, info) => {
|
|
2838
3081
|
const deliveryPayload = resolveWhatsAppDeliverablePayload(payload, info);
|
|
2839
3082
|
if (!deliveryPayload) return whatsAppReplyDeliveryVisibility(false);
|
|
@@ -2884,8 +3127,8 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2884
3127
|
return whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
|
|
2885
3128
|
}
|
|
2886
3129
|
if (durable.status === "handled_no_send") return flushResult.delivered > 0 ? whatsAppReplyDeliveryVisibility(true) : whatsAppReplyDeliveryVisibilityFromDurableResult(durable.delivery);
|
|
2887
|
-
const
|
|
2888
|
-
return flushResult.delivered > 0 && !
|
|
3130
|
+
const deliveryResult = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
|
|
3131
|
+
return flushResult.delivered > 0 && !deliveryResult.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : deliveryResult;
|
|
2889
3132
|
} catch (error) {
|
|
2890
3133
|
throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
|
|
2891
3134
|
}
|
|
@@ -2906,27 +3149,12 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2906
3149
|
const flushResult = await mediaOnlyCoalescer.flushExceptDuplicateMedia(mediaUrls);
|
|
2907
3150
|
logWhatsAppMediaOnlyFlushResult(flushResult);
|
|
2908
3151
|
try {
|
|
2909
|
-
const
|
|
2910
|
-
return flushResult.delivered > 0 && !
|
|
3152
|
+
const deliveryResult = await deliverNormalizedPayload(normalizedDeliveryPayload, info);
|
|
3153
|
+
return flushResult.delivered > 0 && !deliveryResult.visibleReplySent ? whatsAppReplyDeliveryVisibility(true) : deliveryResult;
|
|
2911
3154
|
} catch (error) {
|
|
2912
3155
|
throw markWhatsAppReplyDeliveryErrorVisibleAfterFlush(error, flushResult);
|
|
2913
3156
|
}
|
|
2914
3157
|
},
|
|
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
3158
|
onError: (err, info) => {
|
|
2931
3159
|
logWhatsAppReplyDeliveryError({
|
|
2932
3160
|
err,
|
|
@@ -2938,39 +3166,52 @@ async function dispatchWhatsAppBufferedReply(params) {
|
|
|
2938
3166
|
}
|
|
2939
3167
|
},
|
|
2940
3168
|
replyOptions: {
|
|
3169
|
+
...ingressLifecycle ? bindIngressLifecycleToReplyOptions(ingressLifecycle) : {},
|
|
2941
3170
|
suppressTyping: sourceRepliesAreToolOnly && conversationKind === "group" && !(params.msg.groupMention?.wasMentioned ?? params.msg.wasMentioned),
|
|
2942
3171
|
disableBlockStreaming,
|
|
2943
3172
|
...sourceReplyDeliveryMode ? { sourceReplyDeliveryMode } : {},
|
|
2944
3173
|
onModelSelected: params.onModelSelected,
|
|
2945
|
-
...statusReactionController ? {
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
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;
|
|
2949
3213
|
}
|
|
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;
|
|
3214
|
+
};
|
|
2974
3215
|
}
|
|
2975
3216
|
async function finalizeWhatsAppStatusReaction(params) {
|
|
2976
3217
|
if (params.outcome === "done") {
|
|
@@ -3377,6 +3618,7 @@ async function processMessage(params) {
|
|
|
3377
3618
|
updateLastRoute: updateLastRouteInBackground,
|
|
3378
3619
|
warn: params.replyLogger.warn.bind(params.replyLogger)
|
|
3379
3620
|
});
|
|
3621
|
+
let finalizeReply;
|
|
3380
3622
|
const turnResult = await runChannelInboundEvent({
|
|
3381
3623
|
channel: "whatsapp",
|
|
3382
3624
|
accountId: params.route.accountId,
|
|
@@ -3410,26 +3652,8 @@ async function processMessage(params) {
|
|
|
3410
3652
|
recordHistory: false
|
|
3411
3653
|
} };
|
|
3412
3654
|
},
|
|
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({
|
|
3655
|
+
resolveTurn: () => {
|
|
3656
|
+
const { finalize, ...replyPlan } = createWhatsAppReplyPlan({
|
|
3433
3657
|
cfg: params.cfg,
|
|
3434
3658
|
connectionId: params.connectionId,
|
|
3435
3659
|
context: ctxPayload,
|
|
@@ -3450,11 +3674,35 @@ async function processMessage(params) {
|
|
|
3450
3674
|
route: params.route,
|
|
3451
3675
|
shouldClearGroupHistory,
|
|
3452
3676
|
statusReactionController
|
|
3453
|
-
})
|
|
3454
|
-
|
|
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
|
+
}
|
|
3455
3703
|
}
|
|
3456
3704
|
});
|
|
3457
|
-
const didSendReply = turnResult.dispatched ? turnResult.dispatchResult : false;
|
|
3705
|
+
const didSendReply = turnResult.dispatched ? finalizeReply?.(turnResult.dispatchResult) ?? false : false;
|
|
3458
3706
|
removeAckReactionHandleAfterReply({
|
|
3459
3707
|
removeAfterReply: Boolean(params.cfg.messages?.removeAckAfterReply && didSendReply),
|
|
3460
3708
|
ackReaction,
|
|
@@ -3798,6 +4046,7 @@ function isRetryableAuthUnstableError(error) {
|
|
|
3798
4046
|
return error instanceof WhatsAppAuthUnstableError || typeof error === "object" && error !== null && "code" in error && error.code === "whatsapp-auth-unstable";
|
|
3799
4047
|
}
|
|
3800
4048
|
const DEFAULT_TRANSPORT_TIMEOUT_MS = 300 * 1e3;
|
|
4049
|
+
const WHATSAPP_RECONNECT_CATCH_UP_MAX_MS = 20 * 6e4;
|
|
3801
4050
|
async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSocket, keepAlive = true, replyResolver, runtime = defaultRuntime, abortSignal, tuning = {}) {
|
|
3802
4051
|
const activeReplyResolver = replyResolver ?? (await loadReplyResolverRuntime()).getReplyFromConfig;
|
|
3803
4052
|
const runId = newConnectionId();
|
|
@@ -3849,7 +4098,7 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
|
|
|
3849
4098
|
process.once("SIGINT", handleSigint);
|
|
3850
4099
|
const transportTimeoutMs = tuning.transportTimeoutMs ?? DEFAULT_TRANSPORT_TIMEOUT_MS;
|
|
3851
4100
|
const messageTimeoutMs = tuning.messageTimeoutMs ?? 1800 * 1e3;
|
|
3852
|
-
const reconnectCatchUpWindowMs = Math.min(Math.max(messageTimeoutMs, 6e4),
|
|
4101
|
+
const reconnectCatchUpWindowMs = Math.min(Math.max(messageTimeoutMs, 6e4), WHATSAPP_RECONNECT_CATCH_UP_MAX_MS);
|
|
3853
4102
|
const watchdogCheckMs = tuning.watchdogCheckMs ?? 60 * 1e3;
|
|
3854
4103
|
const controller = new WhatsAppConnectionController({
|
|
3855
4104
|
accountId: account.accountId,
|