@openclaw/whatsapp 2026.7.2-beta.1 → 2026.7.2-beta.2

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,11 +5,11 @@ import { t as resolveWhatsAppOutboundTarget } from "./resolve-outbound-target-BR
5
5
  import "./send-D9Ydnn4o.js";
6
6
  import { c as toWhatsappJidWithLid, i as markdownToWhatsApp, n as isSelfChatMode, o as resolveJidToE164, r as jidToE164, s as toWhatsappJid, t as assertWebChannel } from "./targets-runtime-Bsh0HR9y.js";
7
7
  import { n as normalizeE164, r as resolveUserPath } from "./text-runtime-BTGkMN0o.js";
8
- import { n as resolveWhatsAppGroupIntroHint, r as WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS, t as whatsappPlugin } from "./channel-D_1cK7-w.js";
8
+ import { n as resolveWhatsAppGroupIntroHint, r as WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS, t as whatsappPlugin } from "./channel-DbadDUDD.js";
9
9
  import { t as whatsappCommandPolicy } from "./command-policy-BIOSHySD.js";
10
- import { a as resolveWhatsAppGroupRequireMention, o as resolveWhatsAppGroupToolPolicy } from "./setup-core-BXDFGqky.js";
10
+ import { a as resolveWhatsAppGroupRequireMention, o as resolveWhatsAppGroupToolPolicy } from "./setup-core-3aSOoK2T.js";
11
11
  import "./config-schema-BZ8h1hWA.js";
12
- import { t as whatsappSetupPlugin } from "./channel.setup-eX64WZDx.js";
12
+ import { t as whatsappSetupPlugin } from "./channel.setup-BZ-po8fg.js";
13
13
  import { t as DEFAULT_WEB_MEDIA_BYTES } from "./constants-HU41RHGI.js";
14
14
  import { n as listWhatsAppDirectoryGroupsFromConfig, r as listWhatsAppDirectoryPeersFromConfig } from "./directory-config-CLBQrjtC.js";
15
15
  import { n as testing } from "./access-control-DazDX3RB.js";
@@ -22,7 +22,7 @@ import "openclaw/plugin-sdk/core";
22
22
  import "openclaw/plugin-sdk/account-id";
23
23
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
24
24
  import { formatLocationText } from "openclaw/plugin-sdk/channel-inbound";
25
- createLazyRuntimeModule(() => import("./channel.runtime-BPrKzcYd.js"));
25
+ createLazyRuntimeModule(() => import("./channel.runtime-BDDy4Lhz.js"));
26
26
  //#endregion
27
27
  //#region extensions/whatsapp/src/qa-driver.runtime.ts
28
28
  function isRecord(value) {
@@ -10,13 +10,13 @@ import "./text-runtime-BTGkMN0o.js";
10
10
  import { t as createWhatsAppLoginTool } from "./agent-tools-login-DfyToBpG.js";
11
11
  import { c as getWhatsAppApprovalApprovers, l as whatsappApprovalAuth, s as lookupInboundMessageMetaForTarget, t as resolveWhatsAppGroupSessionKey } from "./group-session-key-CLTZeH_F.js";
12
12
  import { t as whatsappCommandPolicy } from "./command-policy-BIOSHySD.js";
13
- import { a as resolveWhatsAppGroupRequireMention, i as whatsappSetupWizardProxy, n as createWhatsAppPluginBase, o as resolveWhatsAppGroupToolPolicy, r as loadWhatsAppChannelRuntime, s as formatWhatsAppConfigAllowFromEntries, t as whatsappSetupAdapter } from "./setup-core-BXDFGqky.js";
13
+ import { a as resolveWhatsAppGroupRequireMention, i as whatsappSetupWizardProxy, n as createWhatsAppPluginBase, o as resolveWhatsAppGroupToolPolicy, r as loadWhatsAppChannelRuntime, s as formatWhatsAppConfigAllowFromEntries, t as whatsappSetupAdapter } from "./setup-core-3aSOoK2T.js";
14
14
  import { f as readWebAuthExistsForDecision, n as WHATSAPP_AUTH_UNSTABLE_CODE } from "./auth-store-BQi9pZqQ.js";
15
- import { t as detectWhatsAppLegacyStateMigrations } from "./state-migrations-D_BmQUR9.js";
15
+ import { t as detectWhatsAppLegacyStateMigrations } from "./state-migrations-CAMTrs2U.js";
16
16
  import { createActionGate as createActionGate$1 } from "openclaw/plugin-sdk/channel-actions";
17
17
  import { normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-core";
18
18
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
19
- import { asString, collectIssuesForEnabledAccounts, createAsyncComputedAccountStatusAdapter, createDefaultChannelRuntimeState, isRecord as isRecord$1 } from "openclaw/plugin-sdk/status-helpers";
19
+ import { asString, collectIssuesForEnabledAccounts, createAsyncComputedAccountStatusAdapter, createDefaultChannelRuntimeState, isRecord as isRecord$1, readAccountStatusSnapshot } from "openclaw/plugin-sdk/status-helpers";
20
20
  import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
21
21
  import { buildChannelOutboundSessionRoute } from "openclaw/plugin-sdk/core";
22
22
  import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";
@@ -541,23 +541,16 @@ function resolveWhatsAppOutboundSessionRoute(params) {
541
541
  }
542
542
  //#endregion
543
543
  //#region extensions/whatsapp/src/status-issues.ts
544
+ const WHATSAPP_ACCOUNT_STATUS_FIELDS = [
545
+ "statusState",
546
+ "linked",
547
+ "reconnectAttempts",
548
+ "lastDisconnect",
549
+ "lastInboundAt",
550
+ "lastError",
551
+ "healthState"
552
+ ];
544
553
  const RECENT_DISCONNECT_WARNING_WINDOW_MS = 900 * 1e3;
545
- function readWhatsAppAccountStatus(value) {
546
- if (!isRecord$1(value)) return null;
547
- return {
548
- accountId: value.accountId,
549
- statusState: value.statusState,
550
- enabled: value.enabled,
551
- linked: value.linked,
552
- connected: value.connected,
553
- running: value.running,
554
- reconnectAttempts: value.reconnectAttempts,
555
- lastDisconnect: value.lastDisconnect,
556
- lastInboundAt: value.lastInboundAt,
557
- lastError: value.lastError,
558
- healthState: value.healthState
559
- };
560
- }
561
554
  function readLastDisconnect(value) {
562
555
  if (typeof value === "string") {
563
556
  const error = asString(value);
@@ -579,7 +572,7 @@ function isRecentDisconnect(disconnect, now = Date.now()) {
579
572
  function collectWhatsAppStatusIssues(accounts) {
580
573
  return collectIssuesForEnabledAccounts({
581
574
  accounts,
582
- readAccount: readWhatsAppAccountStatus,
575
+ readAccount: (value) => readAccountStatusSnapshot(value, WHATSAPP_ACCOUNT_STATUS_FIELDS),
583
576
  collectIssues: ({ account, accountId, issues }) => {
584
577
  const linked = account.linked === true;
585
578
  const statusState = asString(account.statusState);
@@ -1,2 +1,2 @@
1
- import { t as whatsappPlugin } from "./channel-D_1cK7-w.js";
1
+ import { t as whatsappPlugin } from "./channel-DbadDUDD.js";
2
2
  export { whatsappPlugin };
@@ -1,9 +1,9 @@
1
1
  import { startWebLoginWithQr as startWebLoginWithQr$1, waitForWebLogin as waitForWebLogin$1 } from "./login-qr-runtime.js";
2
2
  import { c as logoutWeb$1, d as readWebAuthExistsBestEffort$1, f as readWebAuthExistsForDecision$1, g as readWebSelfId$1, h as readWebAuthState$1, m as readWebAuthSnapshotBestEffort$1, o as getWebAuthAgeMs$1, p as readWebAuthSnapshot$1, s as logWebSelfId$1, x as webAuthExists$1 } from "./auth-store-BQi9pZqQ.js";
3
3
  import { t as getActiveWebListener$1 } from "./active-listener-5kHap439.js";
4
- import { t as monitorWebChannel$1 } from "./monitor-BKtA0lzq.js";
4
+ import { t as monitorWebChannel$1 } from "./monitor-BZ8QvCeV.js";
5
5
  import { t as loginWeb$1 } from "./login-DGIoToX8.js";
6
- import { whatsappSetupWizard as whatsappSetupWizard$1 } from "./setup-surface-5EUmMLHS.js";
6
+ import { whatsappSetupWizard as whatsappSetupWizard$1 } from "./setup-surface-CiKGdNJv.js";
7
7
  //#region extensions/whatsapp/src/channel.runtime.ts
8
8
  function getActiveWebListener(...args) {
9
9
  return getActiveWebListener$1(...args);
@@ -1,5 +1,5 @@
1
- import { a as resolveWhatsAppGroupRequireMention, i as whatsappSetupWizardProxy, n as createWhatsAppPluginBase, o as resolveWhatsAppGroupToolPolicy, t as whatsappSetupAdapter } from "./setup-core-BXDFGqky.js";
2
- import { t as detectWhatsAppLegacyStateMigrations } from "./state-migrations-D_BmQUR9.js";
1
+ import { a as resolveWhatsAppGroupRequireMention, i as whatsappSetupWizardProxy, n as createWhatsAppPluginBase, o as resolveWhatsAppGroupToolPolicy, t as whatsappSetupAdapter } from "./setup-core-3aSOoK2T.js";
2
+ import { t as detectWhatsAppLegacyStateMigrations } from "./state-migrations-CAMTrs2U.js";
3
3
  //#region extensions/whatsapp/src/channel.setup.ts
4
4
  async function isWhatsAppAuthConfigured(account) {
5
5
  const { readWebAuthState } = await import("./auth-store-BQi9pZqQ.js").then((n) => n.i);
@@ -1,2 +1,2 @@
1
- import { t as detectWhatsAppLegacyStateMigrations } from "./state-migrations-D_BmQUR9.js";
1
+ import { t as detectWhatsAppLegacyStateMigrations } from "./state-migrations-CAMTrs2U.js";
2
2
  export { detectWhatsAppLegacyStateMigrations };
@@ -16,7 +16,7 @@ import { t as BufferJSON } from "./session.runtime-Nsqaehbd.js";
16
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-C1rwfWb4.js";
17
17
  import { n as maybeResolveWhatsAppApprovalReaction } from "./approval-reactions-BGQoz25s.js";
18
18
  import { resolveAccountEntry } from "openclaw/plugin-sdk/account-core";
19
- import { normalizeLowercaseStringOrEmpty, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
19
+ import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
20
20
  import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
21
21
  import { DEFAULT_TIMING, createAckReactionHandle, createStatusReactionController, logAckFailure, removeAckReactionHandleAfterReply, shouldAckReactionForWhatsApp } from "openclaw/plugin-sdk/channel-feedback";
22
22
  import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
@@ -36,18 +36,17 @@ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, DEFAULT_MAIN_KEY, buildAgen
36
36
  import { asDateTimestampMs, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
37
37
  import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
38
38
  import { getSessionEntry, patchSessionEntry, resolveStorePath, resolveStorePath as resolveStorePath$1, updateLastRoute } from "openclaw/plugin-sdk/session-store-runtime";
39
- import { buildChannelInboundEventContext, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, hasVisibleInboundReplyDispatch, resolveInboundSessionEnvelopeContext, runChannelInboundEvent, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
39
+ import { buildChannelInboundEventContext, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundEnvelope as formatInboundEnvelope$1, formatInboundMediaUnavailableText, formatLocationText, hasVisibleInboundReplyDispatch, resolveInboundSessionEnvelopeContext, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
40
40
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
41
41
  import { createHash } from "node:crypto";
42
- import { dispatchReplyWithBufferedBlockDispatcher, finalizeInboundContext, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
42
+ import { dispatchReplyWithBufferedBlockDispatcher, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
43
43
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
44
44
  import { createInboundDebouncer, resolveInboundDebounceMs } from "openclaw/plugin-sdk/channel-inbound-debounce";
45
- import { hasControlCommand, isControlCommandMessage, isControlCommandMessage as isControlCommandMessage$1, shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-detection";
46
45
  import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runtime";
47
46
  import { DEFAULT_GROUP_HISTORY_LIMIT, buildHistoryContextFromEntries, buildInboundHistoryFromEntries, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
48
47
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
49
48
  import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
50
- import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
49
+ import { createChannelReplayGuard } from "openclaw/plugin-sdk/persistent-dedupe";
51
50
  import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
52
51
  import { getRuntimeConfig as getRuntimeConfig$1, getRuntimeConfigSourceSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
53
52
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
@@ -56,18 +55,22 @@ import { createConnectedChannelStatusPatch, createTransportActivityStatusPatch }
56
55
  import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute } from "openclaw/plugin-sdk/conversation-binding-runtime";
57
56
  import { resolveAgentIdentity, resolveIdentityNamePrefix as resolveIdentityNamePrefix$1 } from "openclaw/plugin-sdk/agent-runtime";
58
57
  import { normalizeGroupActivation, parseActivationCommand } from "openclaw/plugin-sdk/group-activation";
58
+ import { hasControlCommand, isControlCommandMessage, shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-detection";
59
59
  import { createInternalHookEvent, deriveInboundMessageHookContext, fireAndForgetBoundedHook, toInternalMessageReceivedContext, toPluginMessageContext, toPluginMessageReceivedEvent, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
60
60
  import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
61
61
  import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
62
- import { retryAsync } from "openclaw/plugin-sdk/retry-runtime";
62
+ import { createChannelApiRetryRunner } from "openclaw/plugin-sdk/retry-runtime";
63
63
  //#region extensions/whatsapp/src/inbound/dedupe.ts
64
64
  const WHATSAPP_INBOUND_DEDUPE_TTL_MS = 20 * 6e4;
65
65
  const RECENT_WEB_MESSAGE_MAX = 5e3;
66
66
  const RECENT_OUTBOUND_MESSAGE_TTL_MS = 20 * 6e4;
67
67
  const RECENT_OUTBOUND_MESSAGE_MAX = 5e3;
68
- const claimableInboundMessages = createClaimableDedupe({
69
- ttlMs: WHATSAPP_INBOUND_DEDUPE_TTL_MS,
70
- memoryMaxSize: RECENT_WEB_MESSAGE_MAX
68
+ const whatsAppInboundReplayGuard = createChannelReplayGuard({
69
+ dedupe: {
70
+ ttlMs: WHATSAPP_INBOUND_DEDUPE_TTL_MS,
71
+ memoryMaxSize: RECENT_WEB_MESSAGE_MAX
72
+ },
73
+ buildReplayKey: (keys) => keys
71
74
  });
72
75
  const recentOutboundMessages = createDedupeCache({
73
76
  ttlMs: RECENT_OUTBOUND_MESSAGE_TTL_MS,
@@ -87,18 +90,9 @@ function buildMessageKey(params) {
87
90
  return `${accountId}:${remoteJid}:${messageId}`;
88
91
  }
89
92
  function resetWebInboundDedupe() {
90
- claimableInboundMessages.clearMemory();
93
+ whatsAppInboundReplayGuard.clearMemory();
91
94
  recentOutboundMessages.clear();
92
95
  }
93
- async function claimRecentInboundMessageDelivery(key) {
94
- return (await claimableInboundMessages.claim(key)).kind;
95
- }
96
- async function commitRecentInboundMessage(key) {
97
- await claimableInboundMessages.commit(key);
98
- }
99
- function releaseRecentInboundMessage(key, error) {
100
- claimableInboundMessages.release(key, { error });
101
- }
102
96
  function rememberRecentOutboundMessage(params) {
103
97
  const key = buildMessageKey(params);
104
98
  if (!key) return;
@@ -702,16 +696,16 @@ async function attachWebInboxToSocket(options) {
702
696
  return (a.receiveOrder ?? 0) - (b.receiveOrder ?? 0);
703
697
  });
704
698
  const finalizeInboundDelivery = async (entries, error) => {
705
- const dedupeKeys = uniqueStrings(entries.map((entry) => entry.dedupeKey).filter(isNonEmptyString));
699
+ const replayClaims = entries.map((entry) => entry.replayClaim).filter((claim) => claim !== void 0);
706
700
  const durableEntries = entries.filter((entry) => isNonEmptyString(entry.durableId));
707
701
  const readReceiptEntries = entries.filter((entry) => Boolean(entry.readReceipt));
708
702
  const retryableError = resolveRetryableWhatsAppInboundError(error);
709
703
  if (retryableError) {
710
- dedupeKeys.forEach((dedupeKey) => releaseRecentInboundMessage(dedupeKey, retryableError));
704
+ for (const claim of replayClaims) claim.release({ error: retryableError });
711
705
  await Promise.all(durableEntries.map((entry) => durableInboundJournal.release(entry.durableId, { lastError: formatError(retryableError) })));
712
706
  return;
713
707
  }
714
- await Promise.all([...dedupeKeys.map((dedupeKey) => commitRecentInboundMessage(dedupeKey)), ...durableEntries.map((entry) => durableInboundJournal.complete(entry.durableId, entry.readReceipt ? { metadata: { readReceipt: entry.readReceipt } } : void 0))]);
708
+ await Promise.all([...replayClaims.map((claim) => claim.commit()), ...durableEntries.map((entry) => durableInboundJournal.complete(entry.durableId, entry.readReceipt ? { metadata: { readReceipt: entry.readReceipt } } : void 0))]);
715
709
  await Promise.all(readReceiptEntries.map((entry) => maybeMarkInboundAsRead(entry.readReceipt)));
716
710
  };
717
711
  const debouncer = createInboundDebouncer({
@@ -1124,9 +1118,9 @@ async function attachWebInboxToSocket(options) {
1124
1118
  return;
1125
1119
  }
1126
1120
  const dedupeKey = inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : "";
1127
- const dedupeClaim = dedupeKey ? await claimRecentInboundMessageDelivery(dedupeKey) : "claimed";
1128
- if (dedupeClaim !== "claimed") {
1129
- if (dedupeClaim === "duplicate") {
1121
+ const dedupeClaim = dedupeKey ? await whatsAppInboundReplayGuard.claim(dedupeKey) : { kind: "invalid" };
1122
+ if (dedupeClaim.kind === "duplicate" || dedupeClaim.kind === "inflight") {
1123
+ if (dedupeClaim.kind === "duplicate") {
1130
1124
  await completeUndeliverableDurableInbound(durableId, durableMetadata);
1131
1125
  await maybeMarkNonSelfChatReadReceipt(inbound, deliveryReadReceipt);
1132
1126
  }
@@ -1136,7 +1130,8 @@ async function attachWebInboxToSocket(options) {
1136
1130
  await enqueueInboundMessage(msg, inbound, enriched, {
1137
1131
  durableId,
1138
1132
  readReceipt: deliveryReadReceipt,
1139
- receiveOrder
1133
+ receiveOrder,
1134
+ ...dedupeClaim.kind === "claimed" ? { replayClaim: dedupeClaim.handle } : {}
1140
1135
  });
1141
1136
  };
1142
1137
  const replayPendingDurableInboundMessages = async () => {
@@ -1301,7 +1296,7 @@ async function attachWebInboxToSocket(options) {
1301
1296
  }
1302
1297
  } : void 0,
1303
1298
  group,
1304
- dedupeKey: inbound.id ? `${options.accountId}:${inbound.remoteJid}:${inbound.id}` : void 0,
1299
+ replayClaim: durable.replayClaim,
1305
1300
  durableId: durable.durableId,
1306
1301
  readReceipt: durable.readReceipt,
1307
1302
  receiveOrder: durable.receiveOrder
@@ -2224,29 +2219,34 @@ function isRetryableWhatsAppOutboundError(error) {
2224
2219
  return WHATSAPP_RETRYABLE_OUTBOUND_ERROR_PATTERN.test(formatError(error));
2225
2220
  }
2226
2221
  async function sendWhatsAppOutboundWithRetry(params) {
2222
+ const runWithRetry = createChannelApiRetryRunner({
2223
+ retry: {
2224
+ attempts: WHATSAPP_OUTBOUND_MAX_ATTEMPTS,
2225
+ minDelayMs: WHATSAPP_OUTBOUND_MIN_DELAY_MS,
2226
+ maxDelayMs: WHATSAPP_OUTBOUND_MAX_DELAY_MS,
2227
+ jitter: 0
2228
+ },
2229
+ strictShouldRetry: true,
2230
+ retryAfterMs: () => void 0,
2231
+ shouldRetry: (error, attempt) => {
2232
+ if (!(error instanceof WhatsAppOutboundRetryError) || !isRetryableWhatsAppOutboundError(error.original)) return false;
2233
+ params.onRetry?.({
2234
+ attempt,
2235
+ maxAttempts: WHATSAPP_OUTBOUND_MAX_ATTEMPTS,
2236
+ backoffMs: Math.min(WHATSAPP_OUTBOUND_MIN_DELAY_MS * 2 ** (attempt - 1), WHATSAPP_OUTBOUND_MAX_DELAY_MS),
2237
+ error: error.original,
2238
+ errorText: formatError(error.original)
2239
+ });
2240
+ return true;
2241
+ }
2242
+ });
2227
2243
  try {
2228
- return await retryAsync(async () => {
2244
+ return await runWithRetry(async () => {
2229
2245
  try {
2230
2246
  return await params.send();
2231
2247
  } catch (error) {
2232
2248
  throw new WhatsAppOutboundRetryError(error);
2233
2249
  }
2234
- }, {
2235
- attempts: WHATSAPP_OUTBOUND_MAX_ATTEMPTS,
2236
- minDelayMs: WHATSAPP_OUTBOUND_MIN_DELAY_MS,
2237
- maxDelayMs: WHATSAPP_OUTBOUND_MAX_DELAY_MS,
2238
- jitter: 0,
2239
- shouldRetry: (error) => error instanceof WhatsAppOutboundRetryError && isRetryableWhatsAppOutboundError(error.original),
2240
- onRetry: ({ attempt, maxAttempts, delayMs, err }) => {
2241
- if (!(err instanceof WhatsAppOutboundRetryError)) return;
2242
- params.onRetry?.({
2243
- attempt,
2244
- maxAttempts,
2245
- backoffMs: delayMs,
2246
- error: err.original,
2247
- errorText: formatError(err.original)
2248
- });
2249
- }
2250
2250
  });
2251
2251
  } catch (error) {
2252
2252
  if (error instanceof WhatsAppOutboundRetryError) throw error.original;
@@ -2662,7 +2662,6 @@ async function buildWhatsAppInboundContext(params) {
2662
2662
  }] : void 0, { transcribed: (_entry, index) => params.mediaTranscribedIndexes?.includes(index) === true });
2663
2663
  return buildChannelInboundEventContext({
2664
2664
  channel: "whatsapp",
2665
- finalize: finalizeInboundContext,
2666
2665
  supplemental: {
2667
2666
  quote: params.visibleReplyTo ? {
2668
2667
  id: params.visibleReplyTo.id,
@@ -2699,7 +2698,7 @@ async function buildWhatsAppInboundContext(params) {
2699
2698
  bodyForAgent: params.bodyForAgent ?? params.msg.payload.body,
2700
2699
  inboundHistory,
2701
2700
  rawBody: params.rawBody ?? params.msg.payload.body,
2702
- commandBody: params.commandBody ?? params.msg.payload.body
2701
+ commandBody: params.command?.body ?? params.msg.payload.body
2703
2702
  },
2704
2703
  access: {
2705
2704
  ...wasMentioned !== void 0 ? { mentions: {
@@ -2707,9 +2706,9 @@ async function buildWhatsAppInboundContext(params) {
2707
2706
  wasMentioned,
2708
2707
  requireMention: params.msg.groupMention?.requireMention
2709
2708
  } } : {},
2710
- commands: { authorized: params.commandAuthorized }
2709
+ commands: { authorized: params.command?.authorized === true }
2711
2710
  },
2712
- commandTurn: params.commandTurn,
2711
+ command: params.command,
2713
2712
  extra: {
2714
2713
  Transcript: params.transcript,
2715
2714
  GroupSubject: params.msg.group?.subject,
@@ -2719,40 +2718,12 @@ async function buildWhatsAppInboundContext(params) {
2719
2718
  fallbackE164: params.sender.e164
2720
2719
  }),
2721
2720
  SenderE164: params.sender.e164,
2722
- CommandSource: params.commandSource ?? (params.commandTurn?.source === "native" || params.commandTurn?.source === "text" ? params.commandTurn.source : void 0),
2723
2721
  ReplyThreading: params.replyThreading,
2724
2722
  SuppressMessageReceivedHooks: params.suppressMessageReceivedHooks,
2725
2723
  ...params.msg.payload.location ? toLocationContext(params.msg.payload.location) : {}
2726
2724
  }
2727
2725
  });
2728
2726
  }
2729
- function normalizeCommandTurnFromContext(value) {
2730
- if (!value || typeof value !== "object") return;
2731
- const record = value;
2732
- const kind = record.kind;
2733
- const source = record.source;
2734
- if (kind === "native" && source === "native" && typeof record.authorized === "boolean") return {
2735
- kind: "native",
2736
- source: "native",
2737
- authorized: record.authorized,
2738
- commandName: typeof record.commandName === "string" ? record.commandName : void 0,
2739
- body: typeof record.body === "string" ? record.body : void 0
2740
- };
2741
- if (kind === "text-slash" && source === "text" && typeof record.authorized === "boolean") return {
2742
- kind: "text-slash",
2743
- source: "text",
2744
- authorized: record.authorized,
2745
- commandName: typeof record.commandName === "string" ? record.commandName : void 0,
2746
- body: typeof record.body === "string" ? record.body : void 0
2747
- };
2748
- if (kind === "normal" && source === "message") return {
2749
- kind: "normal",
2750
- source: "message",
2751
- authorized: false,
2752
- commandName: typeof record.commandName === "string" ? record.commandName : void 0,
2753
- body: typeof record.body === "string" ? record.body : void 0
2754
- };
2755
- }
2756
2727
  function resolveWhatsAppDmRouteTarget(params) {
2757
2728
  const admission = requireWhatsAppInboundAdmission(params.msg);
2758
2729
  const conversationId = admission.conversation.id;
@@ -2801,18 +2772,9 @@ async function dispatchWhatsAppBufferedReply(params) {
2801
2772
  accountId: params.route.accountId
2802
2773
  });
2803
2774
  const mediaLocalRoots = getAgentScopedMediaLocalRoots(params.cfg, params.route.agentId);
2804
- const sourceReplyChatType = typeof params.context.ChatType === "string" ? params.context.ChatType : conversationKind;
2805
- const sourceReplyCommandSource = params.context.CommandSource === "native" || params.context.CommandSource === "text" ? params.context.CommandSource : void 0;
2806
- const sourceReplyCommandTurn = normalizeCommandTurnFromContext(params.context.CommandTurn);
2807
- const sourceReplyCommandAuthorized = typeof params.context.CommandAuthorized === "boolean" ? params.context.CommandAuthorized : void 0;
2808
- const sourceReplyDeliveryMode = sourceReplyChatType === "group" || sourceReplyChatType === "channel" ? resolveChannelMessageSourceReplyDeliveryMode({
2775
+ const sourceReplyDeliveryMode = params.context.ChatType === "group" || params.context.ChatType === "channel" ? resolveChannelMessageSourceReplyDeliveryMode({
2809
2776
  cfg: params.cfg,
2810
- ctx: {
2811
- ChatType: sourceReplyChatType,
2812
- CommandTurn: sourceReplyCommandTurn,
2813
- CommandSource: sourceReplyCommandSource,
2814
- CommandAuthorized: sourceReplyCommandAuthorized
2815
- }
2777
+ ctx: params.context
2816
2778
  }) : void 0;
2817
2779
  const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
2818
2780
  const disableBlockStreaming = sourceRepliesAreToolOnly ? true : resolveWhatsAppDisableBlockStreaming(params.cfg);
@@ -3343,24 +3305,13 @@ async function processMessage(params) {
3343
3305
  normalizeE164
3344
3306
  });
3345
3307
  const shouldCheckCommandAuth = shouldComputeCommandAuthorized(commandBody, params.cfg);
3346
- const isTextCommand = isControlCommandMessage$1(commandBody, params.cfg);
3308
+ const isTextCommand = isControlCommandMessage(commandBody, params.cfg);
3347
3309
  const commandAuthorized = shouldCheckCommandAuth ? await resolveWhatsAppCommandAuthorized({
3348
3310
  cfg: params.cfg,
3349
3311
  msg: params.msg,
3350
3312
  policy: inboundPolicy,
3351
3313
  authDir: account.authDir
3352
3314
  }) : void 0;
3353
- const commandTurn = isTextCommand ? {
3354
- kind: "text-slash",
3355
- source: "text",
3356
- authorized: Boolean(commandAuthorized),
3357
- body: commandBody
3358
- } : {
3359
- kind: "normal",
3360
- source: "message",
3361
- authorized: false,
3362
- body: commandBody
3363
- };
3364
3315
  const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
3365
3316
  cfg: params.cfg,
3366
3317
  agentId: params.route.agentId,
@@ -3384,9 +3335,11 @@ async function processMessage(params) {
3384
3335
  const ctxPayload = await buildWhatsAppInboundContext({
3385
3336
  bodyForAgent: msgForAgent.payload.body,
3386
3337
  combinedBody,
3387
- commandBody,
3388
- commandAuthorized,
3389
- commandTurn,
3338
+ command: {
3339
+ kind: isTextCommand ? "text-slash" : "normal",
3340
+ body: commandBody,
3341
+ authorized: commandAuthorized
3342
+ },
3390
3343
  groupHistory: visibleGroupHistory,
3391
3344
  groupMemberRoster: params.groupMemberNames.get(params.groupHistoryKey),
3392
3345
  groupSystemPrompt: conversationSystemPrompt,
@@ -3930,10 +3883,12 @@ async function monitorWebChannel(verbose, listenerFactory = attachWebInboxToSock
3930
3883
  });
3931
3884
  const shouldDebounce = (msg) => {
3932
3885
  const normalized = normalizeWebInboundMessage(msg);
3933
- if (normalized.payload.media?.path || normalized.payload.media?.type) return false;
3934
- if (normalized.payload.location) return false;
3935
- if (normalized.quote?.id || normalized.quote?.body) return false;
3936
- return !isControlCommandMessage(normalized.payload.commandBody ?? normalized.payload.body, cfg);
3886
+ return shouldDebounceTextInbound({
3887
+ text: normalized.payload.commandBody ?? normalized.payload.body,
3888
+ cfg,
3889
+ hasMedia: Boolean(normalized.payload.media?.path || normalized.payload.media?.type),
3890
+ allowDebounce: !(normalized.payload.location || normalized.quote?.id || normalized.quote?.body)
3891
+ });
3937
3892
  };
3938
3893
  let connection;
3939
3894
  try {
@@ -10,7 +10,7 @@ import { _ as extractLocationData, b as extractText, m as extractContactContext,
10
10
  import { n as newConnectionId, r as waitForWaConnection, t as createWaSocket } from "./session-mxWFn0eS.js";
11
11
  import { n as getStatusCode, t as formatError } from "./session-errors-JuazczrA.js";
12
12
  import { n as resolveWebAccountId, t as getActiveWebListener } from "./active-listener-5kHap439.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-BKtA0lzq.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-BZ8QvCeV.js";
14
14
  import { t as loginWeb } from "./login-DGIoToX8.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 };
@@ -84,10 +84,10 @@ async function applyWhatsAppSecurityConfigFixes(params) {
84
84
  //#region extensions/whatsapp/src/shared.ts
85
85
  const WHATSAPP_CHANNEL = "whatsapp";
86
86
  async function loadWhatsAppChannelRuntime() {
87
- return await import("./channel.runtime-BPrKzcYd.js");
87
+ return await import("./channel.runtime-BDDy4Lhz.js");
88
88
  }
89
89
  async function loadWhatsAppSetupSurface() {
90
- return await import("./setup-surface-5EUmMLHS.js");
90
+ return await import("./setup-surface-CiKGdNJv.js");
91
91
  }
92
92
  const whatsappSetupWizardProxy = createWhatsAppSetupWizardProxy(async () => (await loadWhatsAppSetupSurface()).whatsappSetupWizard);
93
93
  const whatsappConfigAdapter = createScopedChannelConfigAdapter({
@@ -2,7 +2,7 @@ import { r as resolveDefaultWhatsAppAccountId } from "./account-ids-CB5SOWjc.js"
2
2
  import { r as hasWebCredsSync } from "./creds-files-B1kSWtBg.js";
3
3
  import { a as resolveWhatsAppAccount, o as resolveWhatsAppAuthDir } from "./accounts-CKOFMPf-.js";
4
4
  import { a as normalizeWhatsAppAllowFromEntries, o as normalizeWhatsAppAllowFromEntry } from "./normalize-target-DSZnHkKF.js";
5
- import { t as whatsappSetupAdapter } from "./setup-core-BXDFGqky.js";
5
+ import { t as whatsappSetupAdapter } from "./setup-core-3aSOoK2T.js";
6
6
  import { DEFAULT_ACCOUNT_ID, createSetupTranslator, splitSetupEntries } from "openclaw/plugin-sdk/setup";
7
7
  import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
8
8
  //#region extensions/whatsapp/src/setup-finalize.ts
@@ -1,2 +1,2 @@
1
- import { t as whatsappSetupPlugin } from "./channel.setup-eX64WZDx.js";
1
+ import { t as whatsappSetupPlugin } from "./channel.setup-BZ-po8fg.js";
2
2
  export { whatsappSetupPlugin };
@@ -36,7 +36,7 @@ const whatsappSetupWizard = {
36
36
  },
37
37
  resolveShouldPromptAccountIds: ({ shouldPromptAccountIds }) => shouldPromptAccountIds,
38
38
  credentials: [],
39
- finalize: async (params) => await (await import("./setup-finalize-DzOHXnPj.js")).finalizeWhatsAppSetup(params),
39
+ finalize: async (params) => await (await import("./setup-finalize-DBf1jjBj.js")).finalizeWhatsAppSetup(params),
40
40
  disable: (cfg) => setSetupChannelEnabled(cfg, channel, false),
41
41
  onAccountRecorded: (accountId, options) => {
42
42
  options?.onAccountId?.(channel, accountId);
@@ -1,15 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { statRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
3
+ import { fileExists } from "openclaw/plugin-sdk/security-runtime";
4
4
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
5
5
  //#region extensions/whatsapp/src/state-migrations.ts
6
- function fileExists(pathValue) {
7
- try {
8
- return !statRegularFileSync(pathValue).missing;
9
- } catch {
10
- return false;
11
- }
12
- }
13
6
  function isLegacyWhatsAppAuthFile(name) {
14
7
  if (name === "creds.json" || name === "creds.json.bak") return true;
15
8
  if (!name.endsWith(".json")) return false;
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@openclaw/whatsapp",
3
- "version": "2026.7.2-beta.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/whatsapp",
9
- "version": "2026.7.2-beta.1",
9
+ "version": "2026.7.2-beta.2",
10
10
  "dependencies": {
11
11
  "audio-decode": "2.2.3",
12
12
  "baileys": "7.0.0-rc13",
13
13
  "typebox": "1.3.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.7.2-beta.1"
16
+ "openclaw": ">=2026.7.2-beta.2"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/whatsapp",
3
- "version": "2026.7.2-beta.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "description": "OpenClaw WhatsApp channel plugin for WhatsApp Web chats.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,7 +13,7 @@
13
13
  "typebox": "1.3.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.7.2-beta.1"
16
+ "openclaw": ">=2026.7.2-beta.2"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -56,10 +56,10 @@
56
56
  "minHostVersion": ">=2026.4.25"
57
57
  },
58
58
  "compat": {
59
- "pluginApi": ">=2026.7.2-beta.1"
59
+ "pluginApi": ">=2026.7.2-beta.2"
60
60
  },
61
61
  "build": {
62
- "openclawVersion": "2026.7.2-beta.1"
62
+ "openclawVersion": "2026.7.2-beta.2"
63
63
  },
64
64
  "release": {
65
65
  "publishToClawHub": true,
package/dist/constants.js DELETED
@@ -1,2 +0,0 @@
1
- import { t as DEFAULT_WEB_MEDIA_BYTES } from "./constants-HU41RHGI.js";
2
- export { DEFAULT_WEB_MEDIA_BYTES };
package/dist/targets.js DELETED
@@ -1,2 +0,0 @@
1
- import { c as normalizeWhatsAppTarget, r as isWhatsAppUserTarget, t as isWhatsAppGroupJid } from "./normalize-target-DSZnHkKF.js";
2
- export { isWhatsAppGroupJid, isWhatsAppUserTarget, normalizeWhatsAppTarget };