diff --git a/extensions/whatsapp/src/auto-reply/monitor.ts b/extensions/whatsapp/src/auto-reply/monitor.ts index 9bf4b095859e..de9db4ca0163 100644 --- a/extensions/whatsapp/src/auto-reply/monitor.ts +++ b/extensions/whatsapp/src/auto-reply/monitor.ts @@ -27,6 +27,7 @@ import { type ManagedWhatsAppListener, } from "../connection-controller.js"; import { resolveWhatsAppInboundPolicy } from "../inbound-policy.js"; +import { normalizeAdmittedWebInboundMessage } from "../inbound/message-aliases.js"; import { readWhatsAppBaileysCacheEntry, type WhatsAppBaileysGroupMetadataCache, @@ -34,7 +35,7 @@ import { } from "../inbound/baileys-cache.js"; import type { WhatsAppGroupMetadataCache } from "../inbound/group-metadata-cache.js"; import { attachWebInboxToSocket } from "../inbound/monitor.js"; -import type { AdmittedWebInboundMessage } from "../inbound/types.js"; +import type { WebInboundMessageInput } from "../inbound/types.js"; import { newConnectionId, resolveHeartbeatSeconds, @@ -229,12 +230,13 @@ export async function monitorWebChannel( cfg, channel: "whatsapp", }); - const shouldDebounce = (msg: AdmittedWebInboundMessage) => { + const shouldDebounce = (msg: WebInboundMessageInput) => { + const admitted = normalizeAdmittedWebInboundMessage(msg); return shouldDebounceTextInbound({ - text: msg.payload.commandBody ?? msg.payload.body, + text: admitted.payload.commandBody ?? admitted.payload.body, cfg, - hasMedia: Boolean(msg.payload.media?.path || msg.payload.media?.type), - allowDebounce: !(msg.payload.location || msg.quote?.id || msg.quote?.body), + hasMedia: Boolean(admitted.payload.media?.path || admitted.payload.media?.type), + allowDebounce: !(admitted.payload.location || admitted.quote?.id || admitted.quote?.body), }); }; @@ -293,11 +295,14 @@ export async function monitorWebChannel( groupMetadataCache, recentMessageKeys, baileysGroupMetaCache, - onMessage: async (msg: AdmittedWebInboundMessage) => { + onMessage: async (msg: WebInboundMessageInput) => { + // Keep the deprecated injected-listener input contract at the WhatsApp edge. + // Auto-reply only receives the admitted canonical message. + const admitted = normalizeAdmittedWebInboundMessage(msg); const inboundAt = Date.now(); controller.noteInbound(inboundAt); statusController.noteInbound(inboundAt); - await onMessage(msg); + await onMessage(admitted); }, onPendingWorkChanged: (pendingWorkCount, at) => { statusController.noteBusy(pendingWorkCount > 0, at); diff --git a/extensions/whatsapp/src/inbound/message-aliases.test.ts b/extensions/whatsapp/src/inbound/message-aliases.test.ts index ef001bf447b6..3152ad592634 100644 --- a/extensions/whatsapp/src/inbound/message-aliases.test.ts +++ b/extensions/whatsapp/src/inbound/message-aliases.test.ts @@ -1,6 +1,7 @@ // WhatsApp tests cover inbound message alias compatibility. import { describe, expect, expectTypeOf, it, vi } from "vitest"; import { + normalizeAdmittedWebInboundMessage, normalizeWebInboundMessage, withDeprecatedWebInboundMessageFlatAliases, } from "./message-aliases.js"; @@ -10,9 +11,16 @@ import { createTestLegacyFlatWebInboundMessage, createTestWhatsAppInboundAdmission, } from "./test-message.test-helper.js"; -import type { LegacyFlatWebInboundMessage, WebInboundCallbackMessage } from "./types.js"; +import type { + LegacyFlatWebInboundMessage, + WebInboundCallbackMessage, + WebInboundMessageInput, +} from "./types.js"; type MonitorWebInboxMessage = Parameters[0]["onMessage"]>[0]; +type MonitorWebChannel = typeof import("../auto-reply/monitor.js").monitorWebChannel; +type ListenerFactory = NonNullable[1]>; +type ListenerFactoryMessage = Parameters[0]["onMessage"]>[0]; function createCanonicalMessage(overrides: Partial = {}) { return withDeprecatedWebInboundMessageFlatAliases({ @@ -109,6 +117,16 @@ describe("WhatsApp inbound flat aliases", () => { }>(); }); + it("keeps deprecated flat inputs at both WhatsApp listener boundaries", () => { + expectTypeOf().toMatchTypeOf(); + + const admitted = normalizeAdmittedWebInboundMessage(createTestLegacyFlatWebInboundMessage()); + expect(admitted.admission.ingress).toMatchObject({ + admission: "dispatch", + decisiveGateId: "legacy-flat-compat", + }); + }); + it("keeps deprecated flat aliases live against canonical contexts", async () => { const msg = createCanonicalMessage(); const nextReply = vi.fn(async () => createAcceptedWhatsAppSendResult("text", "reply-2")); diff --git a/extensions/whatsapp/src/inbound/message-aliases.ts b/extensions/whatsapp/src/inbound/message-aliases.ts index ce4be8803d28..3785540a0e7c 100644 --- a/extensions/whatsapp/src/inbound/message-aliases.ts +++ b/extensions/whatsapp/src/inbound/message-aliases.ts @@ -1,6 +1,10 @@ -import { buildDeprecatedFlatWhatsAppInboundAdmission } from "./admission.js"; +import { + buildDeprecatedFlatWhatsAppInboundAdmission, + requireAdmittedWhatsAppInboundMessage, +} from "./admission.js"; import { resolveWhatsAppGroupConversationId } from "./group-conversation.js"; import type { + AdmittedWebInboundCallbackMessage, DeprecatedWebInboundAdmissionTopLevelFields, DeprecatedWebInboundMessageFlatAliases, LegacyFlatWebInboundMessage, @@ -454,3 +458,11 @@ export function normalizeWebInboundMessage(msg: WebInboundMessageInput): WebInbo return normalizeLegacyFlatWebInboundMessage(msg); } + +export function normalizeAdmittedWebInboundMessage( + msg: WebInboundMessageInput, +): AdmittedWebInboundCallbackMessage { + return requireAdmittedWhatsAppInboundMessage( + normalizeWebInboundMessage(msg), + ) as AdmittedWebInboundCallbackMessage; +} diff --git a/extensions/whatsapp/src/inbound/message-debounce.ts b/extensions/whatsapp/src/inbound/message-debounce.ts index 969d5ba54ea5..953f1fcd963b 100644 --- a/extensions/whatsapp/src/inbound/message-debounce.ts +++ b/extensions/whatsapp/src/inbound/message-debounce.ts @@ -7,15 +7,10 @@ import type { WhatsAppIngressLifecycle, WhatsAppReadReceiptTarget } from "./dura import { attachWhatsAppIngressLifecycle } from "./ingress-lifecycle.js"; import { withDeprecatedWebInboundMessageFlatAliases } from "./message-aliases.js"; import type { - AdmittedWebInboundMessage, - WebInboundMessage, + AdmittedWebInboundCallbackMessage, WebInboundMessageInput, } from "./types.js"; -type AdmittedWebInboundCallbackMessage = WebInboundMessage & { - admission: AdmittedWebInboundMessage["admission"]; -}; - export type WhatsAppQueuedInboundMessage = AdmittedWebInboundCallbackMessage & { debounceKey?: string; debounceKeyTracked?: boolean; diff --git a/extensions/whatsapp/src/inbound/monitor.ts b/extensions/whatsapp/src/inbound/monitor.ts index 353c4886c9ed..f38bcc95bb6b 100644 --- a/extensions/whatsapp/src/inbound/monitor.ts +++ b/extensions/whatsapp/src/inbound/monitor.ts @@ -5,7 +5,6 @@ import { createSubsystemLogger, defaultRuntime } from "openclaw/plugin-sdk/runti import type { OpenClawConfig } from "../runtime-api.js"; import { createWaSocket, waitForWaConnection } from "../session.js"; import { resolveWhatsAppSocketTiming, type WhatsAppSocketTimingOptions } from "../socket-timing.js"; -import { requireAdmittedWhatsAppInboundMessage } from "./admission.js"; import { readWhatsAppBaileysCacheEntry, type WhatsAppBaileysGroupMetadataCache, @@ -20,7 +19,7 @@ import { type WhatsAppGroupMetadataCache, } from "./group-metadata-cache.js"; import { closeInboundMonitorSocket } from "./lifecycle.js"; -import { normalizeWebInboundMessage } from "./message-aliases.js"; +import { normalizeAdmittedWebInboundMessage } from "./message-aliases.js"; import { createWhatsAppMessageDeliveryCoordinator, type WhatsAppAppendReplyWindow, @@ -28,8 +27,7 @@ import { import { createWebSendApi } from "./send-api.js"; import { createWhatsAppAttachedSocketSession } from "./socket-session.js"; import type { - AdmittedWebInboundMessage, - WebInboundMessage, + AdmittedWebInboundCallbackMessage, WebInboundMessageInput, } from "./types.js"; @@ -39,10 +37,6 @@ function logWhatsAppVerbose(enabled: boolean | undefined, message: string) { } } -type AdmittedWebInboundCallbackMessage = WebInboundMessage & { - admission: AdmittedWebInboundMessage["admission"]; -}; - type MonitorWebInboxOptions = { cfg: OpenClawConfig; loadConfig?: () => OpenClawConfig; @@ -84,8 +78,13 @@ type MonitorWebInboxOptions = { durableInboundQueue?: WhatsAppDurableInboundQueue; }; -type AttachWebInboxToSocketOptions = Omit & { +type AttachWebInboxToSocketOptions = Omit< + MonitorWebInboxOptions, + "onMessage" | "shouldDebounce" | "socketTiming" +> & { socketTiming: Required; + onMessage: (msg: WebInboundMessageInput) => Promise; + shouldDebounce?: (msg: WebInboundMessageInput) => boolean; }; export async function attachWebInboxToSocket( @@ -200,12 +199,6 @@ export async function monitorWebInbox(options: MonitorWebInboxOptions) { closeInboundMonitorSocket(sock); throw error; } - const normalizeAdmittedWebInboundMessage = ( - msg: WebInboundMessageInput, - ): AdmittedWebInboundCallbackMessage => - requireAdmittedWhatsAppInboundMessage( - normalizeWebInboundMessage(msg), - ) as AdmittedWebInboundCallbackMessage; return attachWebInboxToSocket({ ...options, onMessage: async (msg) => { diff --git a/extensions/whatsapp/src/inbound/types.ts b/extensions/whatsapp/src/inbound/types.ts index de75dcff5aff..d2f911d5556c 100644 --- a/extensions/whatsapp/src/inbound/types.ts +++ b/extensions/whatsapp/src/inbound/types.ts @@ -245,6 +245,10 @@ export type AdmittedWebInboundMessage = Omit< admission: WhatsAppInboundAdmission; }; +export type AdmittedWebInboundCallbackMessage = WebInboundMessage & { + admission: WhatsAppInboundAdmission; +}; + export type LegacyFlatWebInboundMessage = DeprecatedWebInboundAdmissionTopLevelFields & Pick & { admission?: WhatsAppInboundAdmission;