@openclaw/whatsapp 2026.5.12-beta.2 → 2026.5.12-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -5,12 +5,12 @@ import { t as resolveWhatsAppOutboundTarget } from "./resolve-outbound-target-BK
5
5
  import "./reaction-level-2m9sWGCw.js";
6
6
  import { c as assertWebChannel, d as markdownToWhatsApp, f as resolveJidToE164, l as isSelfChatMode, m as toWhatsappJidWithLid, n as normalizeE164, p as toWhatsappJid, r as resolveUserPath, u as jidToE164 } from "./text-runtime-CMxQczvs.js";
7
7
  import "./send-DBfXQDd4.js";
8
- import { t as whatsappPlugin } from "./channel-C-UI9SEF.js";
8
+ import { t as whatsappPlugin } from "./channel-DgVXntVm.js";
9
9
  import { n as WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./outbound-base-BgtywpNm.js";
10
10
  import { t as whatsappCommandPolicy } from "./command-policy-O8sRnqUV.js";
11
- import { a as resolveWhatsAppGroupToolPolicy, i as resolveWhatsAppGroupRequireMention, o as resolveWhatsAppGroupIntroHint } from "./shared-D4EwdloD.js";
11
+ import { a as resolveWhatsAppGroupToolPolicy, i as resolveWhatsAppGroupRequireMention, o as resolveWhatsAppGroupIntroHint } from "./shared-DW9W6SXy.js";
12
12
  import "./config-schema-Dmo66Ubb.js";
13
- import { t as whatsappSetupPlugin } from "./channel.setup-UUV-rS7F.js";
13
+ import { t as whatsappSetupPlugin } from "./channel.setup-DKp_w5Ru.js";
14
14
  import { t as DEFAULT_WEB_MEDIA_BYTES } from "./constants-DLGCIbfl.js";
15
15
  import { n as listWhatsAppDirectoryGroupsFromConfig, r as listWhatsAppDirectoryPeersFromConfig } from "./directory-config-CE74HYlw.js";
16
16
  import { t as __testing } from "./access-control-BEpPB-Jx.js";
@@ -10,7 +10,7 @@ import { t as createWhatsAppLoginTool } from "./agent-tools-login-DpRxLNRB.js";
10
10
  import { t as createWhatsAppOutboundBase } from "./outbound-base-BgtywpNm.js";
11
11
  import { t as getWhatsAppRuntime } from "./runtime-6fZ67abR.js";
12
12
  import { t as whatsappCommandPolicy } from "./command-policy-O8sRnqUV.js";
13
- import { a as resolveWhatsAppGroupToolPolicy, c as formatWhatsAppConfigAllowFromEntries, i as resolveWhatsAppGroupRequireMention, n as loadWhatsAppChannelRuntime, o as resolveWhatsAppGroupIntroHint, r as whatsappSetupWizardProxy, s as resolveWhatsAppMentionStripRegexes, t as createWhatsAppPluginBase } from "./shared-D4EwdloD.js";
13
+ import { a as resolveWhatsAppGroupToolPolicy, c as formatWhatsAppConfigAllowFromEntries, i as resolveWhatsAppGroupRequireMention, n as loadWhatsAppChannelRuntime, o as resolveWhatsAppGroupIntroHint, r as whatsappSetupWizardProxy, s as resolveWhatsAppMentionStripRegexes, t as createWhatsAppPluginBase } from "./shared-DW9W6SXy.js";
14
14
  import { d as readWebAuthExistsForDecision, n as WHATSAPP_AUTH_UNSTABLE_CODE } from "./auth-store-_ScMvp7r.js";
15
15
  import { t as whatsappSetupAdapter } from "./setup-core-DZKvpzbH.js";
16
16
  import { t as detectWhatsAppLegacyStateMigrations } from "./state-migrations-Bm0S67yH.js";
@@ -1,2 +1,2 @@
1
- import { t as whatsappPlugin } from "./channel-C-UI9SEF.js";
1
+ import { t as whatsappPlugin } from "./channel-DgVXntVm.js";
2
2
  export { whatsappPlugin };
@@ -1,7 +1,7 @@
1
1
  import { startWebLoginWithQr as startWebLoginWithQr$1, waitForWebLogin as waitForWebLogin$1 } from "./login-qr-runtime.js";
2
2
  import { a as getWebAuthAgeMs$1, b as webAuthExists$1, d as readWebAuthExistsForDecision$1, f as readWebAuthSnapshot$1, h as readWebSelfId$1, m as readWebAuthState$1, o as logWebSelfId$1, p as readWebAuthSnapshotBestEffort$1, s as logoutWeb$1, u as readWebAuthExistsBestEffort$1 } from "./auth-store-_ScMvp7r.js";
3
3
  import { t as getActiveWebListener$1 } from "./active-listener-DTbKS-j-.js";
4
- import { t as monitorWebChannel$1 } from "./monitor-CvCc8fd7.js";
4
+ import { t as monitorWebChannel$1 } from "./monitor-CmK0ER1u.js";
5
5
  import { t as loginWeb$1 } from "./login-B_pa3YAU.js";
6
6
  import { whatsappSetupWizard as whatsappSetupWizard$1 } from "./setup-surface-BbwSSEsy.js";
7
7
  //#region extensions/whatsapp/src/channel.runtime.ts
@@ -1,4 +1,4 @@
1
- import { a as resolveWhatsAppGroupToolPolicy, i as resolveWhatsAppGroupRequireMention, o as resolveWhatsAppGroupIntroHint, r as whatsappSetupWizardProxy, t as createWhatsAppPluginBase } from "./shared-D4EwdloD.js";
1
+ import { a as resolveWhatsAppGroupToolPolicy, i as resolveWhatsAppGroupRequireMention, o as resolveWhatsAppGroupIntroHint, r as whatsappSetupWizardProxy, t as createWhatsAppPluginBase } from "./shared-DW9W6SXy.js";
2
2
  import { m as readWebAuthState } from "./auth-store-_ScMvp7r.js";
3
3
  import { t as whatsappSetupAdapter } from "./setup-core-DZKvpzbH.js";
4
4
  import { t as detectWhatsAppLegacyStateMigrations } from "./state-migrations-Bm0S67yH.js";
@@ -214,6 +214,7 @@ async function downloadQuotedInboundMedia(msg, sock) {
214
214
  const LOGGED_OUT_STATUS = DisconnectReason$1?.loggedOut ?? 401;
215
215
  const RECONNECT_IN_PROGRESS_ERROR = "no active socket - reconnection in progress";
216
216
  const GROUP_META_TTL_MS = 300 * 1e3;
217
+ const INBOUND_CLOSE_DRAIN_TIMEOUT_MS = 5e3;
217
218
  function rememberGroupMetadataCacheEntry(cache, jid, entry) {
218
219
  if (cache.has(jid)) cache.delete(jid);
219
220
  cache.set(jid, entry);
@@ -287,6 +288,17 @@ async function attachWebInboxToSocket(options) {
287
288
  const selfIdentity = await readWebSelfIdentityForDecision(options.authDir, sock.user);
288
289
  if (selfIdentity.outcome === "unstable") throw new WhatsAppAuthUnstableError("WhatsApp auth state is still stabilizing; retrying inbox attach.");
289
290
  const self = selfIdentity.identity;
291
+ const inboundDebounceMs = Math.max(0, Math.trunc(options.debounceMs ?? 0));
292
+ const pendingDebounceKeys = /* @__PURE__ */ new Set();
293
+ const activeInboundFlushes = /* @__PURE__ */ new Set();
294
+ const buildInboundDebounceKey = (msg) => {
295
+ const sender = msg.sender;
296
+ const senderKey = msg.chatType === "group" ? getPrimaryIdentityId(sender ?? null) ?? msg.senderJid ?? msg.senderE164 ?? msg.senderName ?? msg.from : msg.from;
297
+ if (!senderKey) return null;
298
+ const conversationKey = msg.chatType === "group" ? msg.chatId : msg.from;
299
+ return `${msg.accountId}:${conversationKey}:${senderKey}`;
300
+ };
301
+ const shouldDebounceInboundMessage = (msg) => options.shouldDebounce?.(msg) ?? true;
290
302
  const finalizeInboundDedupe = async (entries, error) => {
291
303
  const dedupeKeys = [...new Set(entries.map((entry) => entry.dedupeKey).filter(isNonEmptyString))];
292
304
  if (dedupeKeys.length === 0) return;
@@ -297,39 +309,44 @@ async function attachWebInboxToSocket(options) {
297
309
  await Promise.all(dedupeKeys.map((dedupeKey) => commitRecentInboundMessage(dedupeKey)));
298
310
  };
299
311
  const debouncer = createInboundDebouncer({
300
- debounceMs: options.debounceMs ?? 0,
301
- buildKey: (msg) => {
302
- const sender = msg.sender;
303
- const senderKey = msg.chatType === "group" ? getPrimaryIdentityId(sender ?? null) ?? msg.senderJid ?? msg.senderE164 ?? msg.senderName ?? msg.from : msg.from;
304
- if (!senderKey) return null;
305
- const conversationKey = msg.chatType === "group" ? msg.chatId : msg.from;
306
- return `${msg.accountId}:${conversationKey}:${senderKey}`;
307
- },
308
- shouldDebounce: options.shouldDebounce,
312
+ debounceMs: inboundDebounceMs,
313
+ buildKey: (msg) => msg.debounceKey ?? buildInboundDebounceKey(msg),
314
+ shouldDebounce: shouldDebounceInboundMessage,
309
315
  onFlush: async (entries) => {
310
- const last = entries.at(-1);
311
- if (!last) return;
316
+ let finishFlush;
317
+ const flushTask = new Promise((resolve) => {
318
+ finishFlush = resolve;
319
+ });
320
+ activeInboundFlushes.add(flushTask);
312
321
  try {
313
- if (entries.length === 1) {
314
- await options.onMessage(last);
322
+ const last = entries.at(-1);
323
+ if (!last) return;
324
+ try {
325
+ if (entries.length === 1) {
326
+ await options.onMessage(last);
327
+ await finalizeInboundDedupe(entries);
328
+ return;
329
+ }
330
+ const mentioned = /* @__PURE__ */ new Set();
331
+ for (const entry of entries) for (const jid of entry.mentions ?? entry.mentionedJids ?? []) mentioned.add(jid);
332
+ const combinedBody = entries.map((entry) => entry.body).filter(Boolean).join("\n");
333
+ const combinedMessage = {
334
+ ...last,
335
+ body: combinedBody,
336
+ mentions: mentioned.size > 0 ? Array.from(mentioned) : void 0,
337
+ mentionedJids: mentioned.size > 0 ? Array.from(mentioned) : void 0,
338
+ isBatched: true
339
+ };
340
+ await options.onMessage(combinedMessage);
315
341
  await finalizeInboundDedupe(entries);
316
- return;
342
+ } catch (error) {
343
+ await finalizeInboundDedupe(entries, error);
344
+ throw error;
317
345
  }
318
- const mentioned = /* @__PURE__ */ new Set();
319
- for (const entry of entries) for (const jid of entry.mentions ?? entry.mentionedJids ?? []) mentioned.add(jid);
320
- const combinedBody = entries.map((entry) => entry.body).filter(Boolean).join("\n");
321
- const combinedMessage = {
322
- ...last,
323
- body: combinedBody,
324
- mentions: mentioned.size > 0 ? Array.from(mentioned) : void 0,
325
- mentionedJids: mentioned.size > 0 ? Array.from(mentioned) : void 0,
326
- isBatched: true
327
- };
328
- await options.onMessage(combinedMessage);
329
- await finalizeInboundDedupe(entries);
330
- } catch (error) {
331
- await finalizeInboundDedupe(entries, error);
332
- throw error;
346
+ } finally {
347
+ for (const entry of entries) if (entry.debounceKey) pendingDebounceKeys.delete(entry.debounceKey);
348
+ activeInboundFlushes.delete(flushTask);
349
+ finishFlush();
333
350
  }
334
351
  },
335
352
  onError: (err) => {
@@ -643,6 +660,11 @@ async function attachWebInboxToSocket(options) {
643
660
  mediaFileName: enriched.mediaFileName,
644
661
  dedupeKey: inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : void 0
645
662
  };
663
+ const debounceKey = buildInboundDebounceKey(inboundMessage);
664
+ if (debounceKey) {
665
+ inboundMessage.debounceKey = debounceKey;
666
+ if (inboundDebounceMs > 0 && shouldDebounceInboundMessage(inboundMessage)) pendingDebounceKeys.add(debounceKey);
667
+ }
646
668
  if (inboundMessage.id) cacheInboundMessageMeta(inboundMessage.accountId, inboundMessage.chatId, inboundMessage.id, {
647
669
  participant: inboundMessage.senderJid,
648
670
  participantE164: inboundMessage.chatType === "direct" ? inboundMessage.senderE164 : void 0,
@@ -659,6 +681,7 @@ async function attachWebInboxToSocket(options) {
659
681
  inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`);
660
682
  }
661
683
  };
684
+ const pendingMessageHandlers = /* @__PURE__ */ new Set();
662
685
  const handleMessagesUpsert = async (upsert) => {
663
686
  if (upsert.type !== "notify" && upsert.type !== "append") return;
664
687
  for (const msg of upsert.messages ?? []) {
@@ -679,6 +702,45 @@ async function attachWebInboxToSocket(options) {
679
702
  await enqueueInboundMessage(msg, inbound, enriched);
680
703
  }
681
704
  };
705
+ const handleMessagesUpsertEvent = (upsert) => {
706
+ const task = handleMessagesUpsert(upsert).catch((err) => {
707
+ inboundLogger.error({ error: String(err) }, "messages.upsert handler error");
708
+ inboundConsoleLog.error(`Messages upsert handler error: ${String(err)}`);
709
+ });
710
+ pendingMessageHandlers.add(task);
711
+ task.finally(() => {
712
+ pendingMessageHandlers.delete(task);
713
+ });
714
+ };
715
+ const waitForPendingMessageHandlers = async () => {
716
+ while (pendingMessageHandlers.size > 0) await Promise.all(Array.from(pendingMessageHandlers));
717
+ };
718
+ const drainDebouncedInboundMessages = async () => {
719
+ while (pendingDebounceKeys.size > 0 || activeInboundFlushes.size > 0) {
720
+ const debounceKeys = Array.from(pendingDebounceKeys);
721
+ if (debounceKeys.length > 0) await Promise.all(debounceKeys.map((key) => debouncer.flushKey(key)));
722
+ const flushes = Array.from(activeInboundFlushes);
723
+ if (flushes.length > 0) await Promise.allSettled(flushes);
724
+ await Promise.resolve();
725
+ }
726
+ };
727
+ const drainInboundBeforeSocketClose = async () => {
728
+ await waitForPendingMessageHandlers();
729
+ await drainDebouncedInboundMessages();
730
+ };
731
+ const drainInboundBeforeSocketCloseWithTimeout = async () => {
732
+ let timeout = null;
733
+ try {
734
+ await Promise.race([drainInboundBeforeSocketClose(), new Promise((_, reject) => {
735
+ timeout = setTimeout(() => {
736
+ reject(/* @__PURE__ */ new Error(`Timed out draining WhatsApp inbound debounce after ${INBOUND_CLOSE_DRAIN_TIMEOUT_MS}ms`));
737
+ }, INBOUND_CLOSE_DRAIN_TIMEOUT_MS);
738
+ timeout.unref?.();
739
+ })]);
740
+ } finally {
741
+ if (timeout) clearTimeout(timeout);
742
+ }
743
+ };
682
744
  const handleConnectionUpdate = (update) => {
683
745
  try {
684
746
  if (update.connection === "close") {
@@ -699,7 +761,7 @@ async function attachWebInboxToSocket(options) {
699
761
  });
700
762
  }
701
763
  };
702
- const detachMessagesUpsert = attachEmitterListener(sock.ev, "messages.upsert", handleMessagesUpsert);
764
+ const detachMessagesUpsert = attachEmitterListener(sock.ev, "messages.upsert", handleMessagesUpsertEvent);
703
765
  const detachConnectionUpdate = attachEmitterListener(sock.ev, "connection.update", handleConnectionUpdate);
704
766
  (async () => {
705
767
  try {
@@ -718,6 +780,11 @@ async function attachWebInboxToSocket(options) {
718
780
  try {
719
781
  detachMessagesUpsert();
720
782
  detachConnectionUpdate();
783
+ await drainInboundBeforeSocketCloseWithTimeout();
784
+ } catch (err) {
785
+ logWhatsAppVerbose(options.verbose, `Inbound close drain failed: ${String(err)}`);
786
+ }
787
+ try {
721
788
  closeInboundMonitorSocket(sock);
722
789
  } catch (err) {
723
790
  logWhatsAppVerbose(options.verbose, `Socket close failed: ${String(err)}`);
@@ -10,7 +10,7 @@ import { t as DEFAULT_WEB_MEDIA_BYTES } from "./constants-DLGCIbfl.js";
10
10
  import { c as extractContactContext, d as extractMediaPlaceholder, p as extractText, u as extractLocationData } from "./send-api-DD93aPIQ.js";
11
11
  import { n as newConnectionId, r as waitForWaConnection, t as createWaSocket } from "./session-ZZQKkOTz.js";
12
12
  import { n as resolveWebAccountId, t as getActiveWebListener } from "./active-listener-DTbKS-j-.js";
13
- import { a as loadWebMediaRaw, c as monitorWebInbox, i as loadWebMedia, l as resetWebInboundDedupe, n as LocalMediaAccessError, o as optimizeImageToJpeg, r as getDefaultLocalRoots, s as optimizeImageToPng, t as monitorWebChannel } from "./monitor-CvCc8fd7.js";
13
+ import { a as loadWebMediaRaw, c as monitorWebInbox, i as loadWebMedia, l as resetWebInboundDedupe, n as LocalMediaAccessError, o as optimizeImageToJpeg, r as getDefaultLocalRoots, s as optimizeImageToPng, t as monitorWebChannel } from "./monitor-CmK0ER1u.js";
14
14
  import { t as loginWeb } from "./login-B_pa3YAU.js";
15
15
  import { HEARTBEAT_PROMPT, HEARTBEAT_TOKEN, SILENT_REPLY_TOKEN, stripHeartbeatToken } from "openclaw/plugin-sdk/reply-runtime";
16
16
  export { DEFAULT_WEB_MEDIA_BYTES, HEARTBEAT_PROMPT, HEARTBEAT_TOKEN, LocalMediaAccessError, SILENT_REPLY_TOKEN, WA_WEB_AUTH_DIR, WHATSAPP_AUTH_UNSTABLE_CODE, WhatsAppAuthUnstableError, createWaSocket, createWhatsAppLoginTool, extractContactContext, extractLocationData, extractMediaPlaceholder, extractText, formatError, formatWhatsAppWebAuthStatusState, getActiveWebListener, getDefaultLocalRoots, getStatusCode, getWebAuthAgeMs, handleWhatsAppAction, hasWebCredsSync, loadWebMedia, loadWebMediaRaw, logWebSelfId, loginWeb, logoutWeb, monitorWebChannel, monitorWebInbox, newConnectionId, optimizeImageToJpeg, optimizeImageToPng, pickWebChannel, readCredsJsonRaw, readWebAuthExistsBestEffort, readWebAuthExistsForDecision, readWebAuthSnapshot, readWebAuthSnapshotBestEffort, readWebAuthState, readWebSelfId, readWebSelfIdentity, readWebSelfIdentityForDecision, resetWebInboundDedupe, resolveDefaultWebAuthDir, resolveWebAccountId, resolveWebCredsBackupPath, resolveWebCredsPath, restoreCredsFromBackupIfNeeded, sendMessageWhatsApp, sendPollWhatsApp, sendReactionWhatsApp, sendTypingWhatsApp, setWhatsAppRuntime, startWebLoginWithQr, stripHeartbeatToken, waitForCredsSaveQueue, waitForCredsSaveQueueWithTimeout, waitForWaConnection, waitForWebLogin, webAuthExists, whatsAppActionRuntime, writeCredsJsonAtomically };
@@ -1,2 +1,2 @@
1
- import { t as whatsappSetupPlugin } from "./channel.setup-UUV-rS7F.js";
1
+ import { t as whatsappSetupPlugin } from "./channel.setup-DKp_w5Ru.js";
2
2
  export { whatsappSetupPlugin };
@@ -124,7 +124,7 @@ function resolveWhatsAppConfigPath(params) {
124
124
  return `${resolveWhatsAppGroupScopeBasePath(params)}.${params.field}`;
125
125
  }
126
126
  async function loadWhatsAppChannelRuntime() {
127
- return await import("./channel.runtime-dC5DBvbA.js");
127
+ return await import("./channel.runtime-C4Ywu0dZ.js");
128
128
  }
129
129
  async function loadWhatsAppSetupSurface() {
130
130
  return await import("./setup-surface-BbwSSEsy.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/whatsapp",
3
- "version": "2026.5.12-beta.2",
3
+ "version": "2026.5.12-beta.4",
4
4
  "description": "OpenClaw WhatsApp channel plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "openclaw": "workspace:*"
20
20
  },
21
21
  "peerDependencies": {
22
- "openclaw": ">=2026.5.12-beta.2"
22
+ "openclaw": ">=2026.5.12-beta.4"
23
23
  },
24
24
  "peerDependenciesMeta": {
25
25
  "openclaw": {
@@ -61,10 +61,10 @@
61
61
  "minHostVersion": ">=2026.4.25"
62
62
  },
63
63
  "compat": {
64
- "pluginApi": ">=2026.5.12-beta.2"
64
+ "pluginApi": ">=2026.5.12-beta.4"
65
65
  },
66
66
  "build": {
67
- "openclawVersion": "2026.5.12-beta.2"
67
+ "openclawVersion": "2026.5.12-beta.4"
68
68
  },
69
69
  "release": {
70
70
  "publishToClawHub": true,