@openclaw/whatsapp 2026.5.12-beta.8 → 2026.5.14-beta.1

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-DgVXntVm.js";
8
+ import { t as whatsappPlugin } from "./channel-yIjmVwof.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-DW9W6SXy.js";
11
+ import { a as resolveWhatsAppGroupToolPolicy, i as resolveWhatsAppGroupRequireMention, o as resolveWhatsAppGroupIntroHint } from "./shared-B2VGBfCy.js";
12
12
  import "./config-schema-Dmo66Ubb.js";
13
- import { t as whatsappSetupPlugin } from "./channel.setup-DKp_w5Ru.js";
13
+ import { t as whatsappSetupPlugin } from "./channel.setup-KnsEGgS2.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";
@@ -1,2 +1,2 @@
1
- import { t as whatsappPlugin } from "./channel-DgVXntVm.js";
1
+ import { t as whatsappPlugin } from "./channel-yIjmVwof.js";
2
2
  export { whatsappPlugin };
@@ -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-DW9W6SXy.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-B2VGBfCy.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,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-CmK0ER1u.js";
4
+ import { t as monitorWebChannel$1 } from "./monitor-DTi0QRV4.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-DW9W6SXy.js";
1
+ import { a as resolveWhatsAppGroupToolPolicy, i as resolveWhatsAppGroupRequireMention, o as resolveWhatsAppGroupIntroHint, r as whatsappSetupWizardProxy, t as createWhatsAppPluginBase } from "./shared-B2VGBfCy.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";
@@ -14,7 +14,7 @@ import { i as resolveWhatsAppSocketTiming, r as waitForWaConnection, t as create
14
14
  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-CT8NA6Zf.js";
15
15
  import { resolveAccountEntry } from "openclaw/plugin-sdk/account-core";
16
16
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
17
- import { createAckReactionHandle, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
17
+ import { DEFAULT_TIMING, createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
18
18
  import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
19
19
  import { getChildLogger } from "openclaw/plugin-sdk/logging-core";
20
20
  import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
@@ -38,7 +38,7 @@ import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runti
38
38
  import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
39
39
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
40
40
  import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
41
- import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
41
+ import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
42
42
  import { getRuntimeConfig as getRuntimeConfig$1, getRuntimeConfigSourceSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
43
43
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
44
44
  import { buildMentionRegexes, implicitMentionKindWhen, normalizeMentionText, resolveInboundMentionDecision } from "openclaw/plugin-sdk/channel-mention-gating";
@@ -159,6 +159,12 @@ function closeInboundMonitorSocket(sock) {
159
159
  }
160
160
  //#endregion
161
161
  //#region extensions/whatsapp/src/inbound/media.ts
162
+ var WhatsAppInboundMediaLimitExceededError = class extends Error {
163
+ constructor(maxBytes) {
164
+ super(`Media exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit`);
165
+ this.name = "WhatsAppInboundMediaLimitExceededError";
166
+ }
167
+ };
162
168
  function unwrapMessage(message) {
163
169
  return normalizeMessageContent$1(message);
164
170
  }
@@ -174,7 +180,7 @@ function resolveMediaMimetype(message) {
174
180
  if (message.videoMessage) return "video/mp4";
175
181
  if (message.stickerMessage) return "image/webp";
176
182
  }
177
- async function downloadInboundMedia(msg, sock) {
183
+ async function downloadInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024) {
178
184
  const message = unwrapMessage(msg.message);
179
185
  if (!message) return;
180
186
  const mimetype = resolveMediaMimetype(message);
@@ -182,19 +188,23 @@ async function downloadInboundMedia(msg, sock) {
182
188
  if (!message.imageMessage && !message.videoMessage && !message.documentMessage && !message.audioMessage && !message.stickerMessage) return;
183
189
  try {
184
190
  return {
185
- buffer: await downloadMediaMessage(msg, "buffer", {}, {
191
+ saved: await saveMediaStream(await downloadMediaMessage(msg, "stream", {}, {
186
192
  reuploadRequest: sock.updateMediaMessage,
187
193
  logger: sock.logger
194
+ }), mimetype, "inbound", maxBytes, fileName).catch((err) => {
195
+ if (err instanceof Error && /Media exceeds/i.test(err.message)) throw new WhatsAppInboundMediaLimitExceededError(maxBytes);
196
+ throw err;
188
197
  }),
189
198
  mimetype,
190
199
  fileName
191
200
  };
192
201
  } catch (err) {
202
+ if (err instanceof WhatsAppInboundMediaLimitExceededError) throw err;
193
203
  logVerbose(`downloadMediaMessage failed: ${String(err)}`);
194
204
  return;
195
205
  }
196
206
  }
197
- async function downloadQuotedInboundMedia(msg, sock) {
207
+ async function downloadQuotedInboundMedia(msg, sock, maxBytes = 50 * 1024 * 1024) {
198
208
  const contextInfo = extractContextInfo(unwrapMessage(msg.message));
199
209
  if (!contextInfo?.quotedMessage) return;
200
210
  const quotedMessage = contextInfo.quotedMessage;
@@ -207,7 +217,7 @@ async function downloadQuotedInboundMedia(msg, sock) {
207
217
  },
208
218
  message: quotedMessage,
209
219
  messageTimestamp: msg.messageTimestamp
210
- }, sock);
220
+ }, sock, maxBytes);
211
221
  }
212
222
  //#endregion
213
223
  //#region extensions/whatsapp/src/inbound/monitor.ts
@@ -557,16 +567,16 @@ async function attachWebInboxToSocket(options) {
557
567
  let mediaPath;
558
568
  let mediaType;
559
569
  let mediaFileName;
570
+ const maxBytes = (typeof options.mediaMaxMb === "number" && options.mediaMaxMb > 0 ? options.mediaMaxMb : 50) * 1024 * 1024;
560
571
  const saveInboundMedia = async (inboundMedia) => {
561
572
  if (!inboundMedia) return;
562
- const maxBytes = (typeof options.mediaMaxMb === "number" && options.mediaMaxMb > 0 ? options.mediaMaxMb : 50) * 1024 * 1024;
563
- mediaPath = (await saveMediaBuffer(inboundMedia.buffer, inboundMedia.mimetype, "inbound", maxBytes, inboundMedia.fileName)).path;
573
+ mediaPath = inboundMedia.saved.path;
564
574
  mediaType = inboundMedia.mimetype;
565
575
  mediaFileName = inboundMedia.fileName;
566
576
  };
567
577
  try {
568
- await saveInboundMedia(await downloadInboundMedia(msg, sock));
569
- if (!mediaPath && replyContext) await saveInboundMedia(await downloadQuotedInboundMedia(msg, sock));
578
+ await saveInboundMedia(await downloadInboundMedia(msg, sock, maxBytes));
579
+ if (!mediaPath && replyContext) await saveInboundMedia(await downloadQuotedInboundMedia(msg, sock, maxBytes));
570
580
  } catch (err) {
571
581
  logWhatsAppVerbose(options.verbose, `Inbound media download failed: ${String(err)}`);
572
582
  }
@@ -1810,6 +1820,12 @@ function updateWhatsAppMainLastRoute(params) {
1810
1820
  if (params.dmRouteTarget && inboundLastRouteSessionKey === params.route.mainSessionKey && params.pinnedMainDmRecipient) logVerbose$1(`Skipping main-session last route update for ${params.dmRouteTarget} (pinned owner ${params.pinnedMainDmRecipient})`);
1811
1821
  }
1812
1822
  async function dispatchWhatsAppBufferedReply(params) {
1823
+ const statusReactionController = params.statusReactionController ?? null;
1824
+ const statusReactionTiming = {
1825
+ ...DEFAULT_TIMING,
1826
+ ...params.cfg.messages?.statusReactions?.timing
1827
+ };
1828
+ const removeAckAfterReply = params.cfg.messages?.removeAckAfterReply ?? false;
1813
1829
  const textLimit = params.maxMediaTextChunkLimit ?? resolveTextChunkLimit(params.cfg, "whatsapp");
1814
1830
  const chunkMode = resolveChunkMode(params.cfg, "whatsapp", params.route.accountId);
1815
1831
  const tableMode = resolveMarkdownTableMode$1({
@@ -1869,6 +1885,7 @@ async function dispatchWhatsAppBufferedReply(params) {
1869
1885
  const mediaOnlyCoalescer = createWhatsAppMediaOnlyReplyCoalescer({ deliver: async (pending) => {
1870
1886
  await deliverNormalizedPayload(pending.payload, pending.info);
1871
1887
  } });
1888
+ if (statusReactionController) statusReactionController.setThinking();
1872
1889
  const { queuedFinal, counts } = await dispatchReplyWithBufferedBlockDispatcher({
1873
1890
  ctx: params.context,
1874
1891
  cfg: params.cfg,
@@ -1940,6 +1957,15 @@ async function dispatchWhatsAppBufferedReply(params) {
1940
1957
  await deliverNormalizedPayload(normalizedDeliveryPayload, info);
1941
1958
  },
1942
1959
  onReplyStart: params.msg.sendComposing,
1960
+ ...statusReactionController ? {
1961
+ onCompactionStart: async () => {
1962
+ await statusReactionController.setCompacting();
1963
+ },
1964
+ onCompactionEnd: async () => {
1965
+ statusReactionController.cancelPending();
1966
+ await statusReactionController.setThinking();
1967
+ }
1968
+ } : {},
1943
1969
  onError: (err, info) => {
1944
1970
  logWhatsAppReplyDeliveryError({
1945
1971
  err,
@@ -1954,7 +1980,11 @@ async function dispatchWhatsAppBufferedReply(params) {
1954
1980
  replyOptions: {
1955
1981
  disableBlockStreaming,
1956
1982
  ...sourceReplyDeliveryMode ? { sourceReplyDeliveryMode } : {},
1957
- onModelSelected: params.onModelSelected
1983
+ onModelSelected: params.onModelSelected,
1984
+ ...statusReactionController ? { onToolStart: async (payload) => {
1985
+ const toolName = payload.name?.trim();
1986
+ if (toolName) await statusReactionController.setTool(toolName);
1987
+ } } : {}
1958
1988
  }
1959
1989
  });
1960
1990
  logWhatsAppMediaOnlyFlushResult(await mediaOnlyCoalescer.flushAll());
@@ -1962,13 +1992,47 @@ async function dispatchWhatsAppBufferedReply(params) {
1962
1992
  queuedFinal,
1963
1993
  counts
1964
1994
  })) {
1995
+ if (statusReactionController) finalizeWhatsAppStatusReaction({
1996
+ controller: statusReactionController,
1997
+ outcome: "error",
1998
+ hasFinalResponse: false,
1999
+ removeAckAfterReply,
2000
+ timing: statusReactionTiming
2001
+ });
1965
2002
  if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
1966
2003
  logVerbose$1("Skipping auto-reply: silent token or no text/media returned from resolver");
1967
2004
  return false;
1968
2005
  }
2006
+ if (statusReactionController) finalizeWhatsAppStatusReaction({
2007
+ controller: statusReactionController,
2008
+ outcome: didSendReply ? "done" : "error",
2009
+ hasFinalResponse: didSendReply,
2010
+ removeAckAfterReply,
2011
+ timing: statusReactionTiming
2012
+ });
1969
2013
  if (params.shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []);
1970
2014
  return didSendReply;
1971
2015
  }
2016
+ async function finalizeWhatsAppStatusReaction(params) {
2017
+ if (params.outcome === "done") {
2018
+ await params.controller.setDone();
2019
+ if (params.removeAckAfterReply) {
2020
+ await new Promise((resolve) => setTimeout(resolve, params.timing.doneHoldMs));
2021
+ await params.controller.clear();
2022
+ } else await params.controller.restoreInitial();
2023
+ return;
2024
+ }
2025
+ await params.controller.setError();
2026
+ if (params.hasFinalResponse) {
2027
+ if (params.removeAckAfterReply) {
2028
+ await new Promise((resolve) => setTimeout(resolve, params.timing.errorHoldMs));
2029
+ await params.controller.clear();
2030
+ } else await params.controller.restoreInitial();
2031
+ return;
2032
+ }
2033
+ if (params.removeAckAfterReply) await new Promise((resolve) => setTimeout(resolve, params.timing.errorHoldMs));
2034
+ await params.controller.restoreInitial();
2035
+ }
1972
2036
  //#endregion
1973
2037
  //#region extensions/whatsapp/src/auto-reply/monitor/message-line.runtime.ts
1974
2038
  function normalizeAgentId$1(agentId) {
@@ -2019,6 +2083,66 @@ function buildInboundLine(params) {
2019
2083
  });
2020
2084
  }
2021
2085
  //#endregion
2086
+ //#region extensions/whatsapp/src/auto-reply/monitor/status-reaction.ts
2087
+ async function createWhatsAppStatusReactionController(params) {
2088
+ if (!params.msg.id) return null;
2089
+ const statusReactionsConfig = params.cfg.messages?.statusReactions;
2090
+ if (statusReactionsConfig?.enabled !== true) return null;
2091
+ if (resolveWhatsAppReactionLevel({
2092
+ cfg: params.cfg,
2093
+ accountId: params.accountId
2094
+ }).level === "off") return null;
2095
+ const ackConfig = params.cfg.channels?.whatsapp?.ackReaction;
2096
+ const ackEmoji = (ackConfig?.emoji ?? "").trim();
2097
+ if (!ackEmoji) return null;
2098
+ const directEnabled = ackConfig?.direct ?? true;
2099
+ const groupMode = ackConfig?.group ?? "mentions";
2100
+ const conversationIdForCheck = params.msg.conversationId ?? params.msg.from;
2101
+ const activation = params.msg.chatType === "group" ? await resolveGroupActivationFor({
2102
+ cfg: params.cfg,
2103
+ accountId: params.accountId,
2104
+ agentId: params.agentId,
2105
+ sessionKey: params.sessionKey,
2106
+ conversationId: conversationIdForCheck
2107
+ }) : null;
2108
+ if (!shouldAckReactionForWhatsApp({
2109
+ emoji: ackEmoji,
2110
+ isDirect: params.msg.chatType === "direct",
2111
+ isGroup: params.msg.chatType === "group",
2112
+ directEnabled,
2113
+ groupMode,
2114
+ wasMentioned: params.msg.wasMentioned === true,
2115
+ groupActivated: activation === "always"
2116
+ })) return null;
2117
+ const sender = getSenderIdentity(params.msg);
2118
+ const reactionOptions = {
2119
+ verbose: params.verbose,
2120
+ fromMe: false,
2121
+ ...sender.jid ? { participant: sender.jid } : {},
2122
+ ...params.accountId ? { accountId: params.accountId } : {},
2123
+ cfg: params.cfg
2124
+ };
2125
+ const chatId = params.msg.chatId;
2126
+ const msgId = params.msg.id;
2127
+ return createStatusReactionController({
2128
+ enabled: true,
2129
+ adapter: {
2130
+ setReaction: async (emoji) => {
2131
+ await sendReactionWhatsApp(chatId, msgId, emoji, reactionOptions);
2132
+ },
2133
+ clearReaction: async () => {
2134
+ await sendReactionWhatsApp(chatId, msgId, "", reactionOptions);
2135
+ }
2136
+ },
2137
+ initialEmoji: ackEmoji,
2138
+ emojis: statusReactionsConfig.emojis,
2139
+ timing: statusReactionsConfig.timing,
2140
+ onError: (err) => {
2141
+ logVerbose(`WhatsApp status-reaction error for chat ${chatId}/${msgId}: ${String(err)}`);
2142
+ }
2143
+ });
2144
+ }
2145
+ //#endregion
2022
2146
  //#region extensions/whatsapp/src/auto-reply/monitor/process-message.ts
2023
2147
  const WHATSAPP_MESSAGE_RECEIVED_HOOK_LIMITS = {
2024
2148
  maxConcurrency: 8,
@@ -2147,8 +2271,18 @@ async function processMessage(params) {
2147
2271
  params.echoForget(combinedEchoKey);
2148
2272
  return false;
2149
2273
  }
2274
+ const statusReactionController = params.statusReactionController ?? (params.cfg.messages?.statusReactions?.enabled === true && !params.ackAlreadySent ? await createWhatsAppStatusReactionController({
2275
+ cfg: params.cfg,
2276
+ msg: params.msg,
2277
+ agentId: params.route.agentId,
2278
+ sessionKey: params.route.sessionKey,
2279
+ conversationId,
2280
+ verbose: params.verbose,
2281
+ accountId: account.accountId
2282
+ }) : null);
2283
+ if (statusReactionController && !params.statusReactionController) statusReactionController.setQueued();
2150
2284
  let ackReaction = params.ackReaction ?? null;
2151
- if (!ackReaction && params.ackAlreadySent !== true) ackReaction = await maybeSendAckReaction({
2285
+ if (!statusReactionController && !ackReaction && params.ackAlreadySent !== true) ackReaction = await maybeSendAckReaction({
2152
2286
  cfg: params.cfg,
2153
2287
  msg: params.msg,
2154
2288
  agentId: params.route.agentId,
@@ -2305,7 +2439,8 @@ async function processMessage(params) {
2305
2439
  },
2306
2440
  replyResolver: params.replyResolver,
2307
2441
  route: params.route,
2308
- shouldClearGroupHistory
2442
+ shouldClearGroupHistory,
2443
+ statusReactionController
2309
2444
  })
2310
2445
  })
2311
2446
  }
@@ -2352,6 +2487,7 @@ function createWebOnMessageHandler(params) {
2352
2487
  if (opts?.preflightAudioTranscript !== void 0) processParams.preflightAudioTranscript = opts.preflightAudioTranscript;
2353
2488
  if (opts?.ackAlreadySent === true) processParams.ackAlreadySent = true;
2354
2489
  if (opts?.ackReaction !== void 0) processParams.ackReaction = opts.ackReaction;
2490
+ if (opts?.statusReactionController !== void 0) processParams.statusReactionController = opts.statusReactionController;
2355
2491
  return processMessage(processParams);
2356
2492
  };
2357
2493
  return async (msg) => {
@@ -2390,20 +2526,34 @@ function createWebOnMessageHandler(params) {
2390
2526
  const canRunEarlyAudioPreflight = msg.chatType === "group" || msg.accessControlPassed === true;
2391
2527
  let ackAlreadySent = false;
2392
2528
  let ackReaction = null;
2529
+ let statusReactionController = null;
2393
2530
  const runAudioPreflightOnce = async () => {
2394
2531
  if (preflightAudioTranscript !== void 0 || !canRunEarlyAudioPreflight || !hasAudioBody || !msg.mediaPath) return;
2395
- ackReaction = await maybeSendAckReaction({
2396
- cfg,
2397
- msg,
2398
- agentId: route.agentId,
2399
- sessionKey: route.sessionKey,
2400
- conversationId,
2401
- verbose: params.verbose,
2402
- accountId: route.accountId,
2403
- info: params.replyLogger.info.bind(params.replyLogger),
2404
- warn: params.replyLogger.warn.bind(params.replyLogger)
2405
- });
2406
- ackAlreadySent = ackReaction !== null;
2532
+ if (cfg.messages?.statusReactions?.enabled === true) {
2533
+ statusReactionController = await createWhatsAppStatusReactionController({
2534
+ cfg,
2535
+ msg,
2536
+ agentId: route.agentId,
2537
+ sessionKey: route.sessionKey,
2538
+ conversationId,
2539
+ verbose: params.verbose,
2540
+ accountId: route.accountId
2541
+ });
2542
+ if (statusReactionController) await statusReactionController.setQueued();
2543
+ } else {
2544
+ ackReaction = await maybeSendAckReaction({
2545
+ cfg,
2546
+ msg,
2547
+ agentId: route.agentId,
2548
+ sessionKey: route.sessionKey,
2549
+ conversationId,
2550
+ verbose: params.verbose,
2551
+ accountId: route.accountId,
2552
+ info: params.replyLogger.info.bind(params.replyLogger),
2553
+ warn: params.replyLogger.warn.bind(params.replyLogger)
2554
+ });
2555
+ ackAlreadySent = ackReaction !== null;
2556
+ }
2407
2557
  try {
2408
2558
  const { transcribeFirstAudio } = await import("./audio-preflight.runtime-MmkB-pgX.js");
2409
2559
  preflightAudioTranscript = await transcribeFirstAudio({
@@ -2512,12 +2662,14 @@ function createWebOnMessageHandler(params) {
2512
2662
  ...preflightAudioTranscript !== void 0 ? { preflightAudioTranscript } : {},
2513
2663
  ...ackAlreadySent && msg.chatType !== "group" ? { ackAlreadySent: true } : {},
2514
2664
  ...ackReaction && msg.chatType !== "group" ? { ackReaction } : {},
2665
+ ...statusReactionController && msg.chatType !== "group" ? { ackAlreadySent: true } : {},
2515
2666
  processMessage: (m, r, k, opts) => processForRoute(cfg, m, r, k, opts)
2516
2667
  })) return;
2517
2668
  await processForRoute(cfg, msg, route, groupHistoryKey, {
2518
2669
  ...preflightAudioTranscript !== void 0 ? { preflightAudioTranscript } : {},
2519
2670
  ...ackAlreadySent ? { ackAlreadySent: true } : {},
2520
- ...ackReaction ? { ackReaction } : {}
2671
+ ...ackReaction ? { ackReaction } : {},
2672
+ ...statusReactionController ? { statusReactionController } : {}
2521
2673
  });
2522
2674
  };
2523
2675
  }
@@ -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-CmK0ER1u.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-DTi0QRV4.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-DKp_w5Ru.js";
1
+ import { t as whatsappSetupPlugin } from "./channel.setup-KnsEGgS2.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-C4Ywu0dZ.js");
127
+ return await import("./channel.runtime-Dc5rfgsF.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.8",
3
+ "version": "2026.5.14-beta.1",
4
4
  "description": "OpenClaw WhatsApp channel plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,7 +20,7 @@
20
20
  "openclaw": "workspace:*"
21
21
  },
22
22
  "peerDependencies": {
23
- "openclaw": ">=2026.5.12-beta.8"
23
+ "openclaw": ">=2026.5.14-beta.1"
24
24
  },
25
25
  "peerDependenciesMeta": {
26
26
  "openclaw": {
@@ -63,10 +63,10 @@
63
63
  "minHostVersion": ">=2026.4.25"
64
64
  },
65
65
  "compat": {
66
- "pluginApi": ">=2026.5.12-beta.8"
66
+ "pluginApi": ">=2026.5.14-beta.1"
67
67
  },
68
68
  "build": {
69
- "openclawVersion": "2026.5.12-beta.8"
69
+ "openclawVersion": "2026.5.14-beta.1"
70
70
  },
71
71
  "release": {
72
72
  "publishToClawHub": true,