fix(whatsapp): preserve injected listener normalization

This commit is contained in:
Vincent Koc
2026-07-29 19:01:53 +08:00
committed by Josh Avant
parent f970e5093b
commit 5c296b5a54
6 changed files with 57 additions and 30 deletions
+12 -7
View File
@@ -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);
@@ -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<Parameters<typeof monitorWebInbox>[0]["onMessage"]>[0];
type MonitorWebChannel = typeof import("../auto-reply/monitor.js").monitorWebChannel;
type ListenerFactory = NonNullable<Parameters<MonitorWebChannel>[1]>;
type ListenerFactoryMessage = Parameters<Parameters<ListenerFactory>[0]["onMessage"]>[0];
function createCanonicalMessage(overrides: Partial<WebInboundCallbackMessage> = {}) {
return withDeprecatedWebInboundMessageFlatAliases({
@@ -109,6 +117,16 @@ describe("WhatsApp inbound flat aliases", () => {
}>();
});
it("keeps deprecated flat inputs at both WhatsApp listener boundaries", () => {
expectTypeOf<WebInboundMessageInput>().toMatchTypeOf<ListenerFactoryMessage>();
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"));
@@ -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;
}
@@ -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;
+8 -15
View File
@@ -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<MonitorWebInboxOptions, "socketTiming"> & {
type AttachWebInboxToSocketOptions = Omit<
MonitorWebInboxOptions,
"onMessage" | "shouldDebounce" | "socketTiming"
> & {
socketTiming: Required<WhatsAppSocketTimingOptions>;
onMessage: (msg: WebInboundMessageInput) => Promise<void>;
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) => {
+4
View File
@@ -245,6 +245,10 @@ export type AdmittedWebInboundMessage = Omit<
admission: WhatsAppInboundAdmission;
};
export type AdmittedWebInboundCallbackMessage = WebInboundMessage & {
admission: WhatsAppInboundAdmission;
};
export type LegacyFlatWebInboundMessage = DeprecatedWebInboundAdmissionTopLevelFields &
Pick<WebInboundCallbackMessageCommon, "wasMentioned"> & {
admission?: WhatsAppInboundAdmission;