From e0d003b372bda022fede391ec4348db9a5572002 Mon Sep 17 00:00:00 2001 From: bladin Date: Thu, 28 May 2026 10:31:47 +0800 Subject: [PATCH] fix(whatsapp): support pluginHooks.messageReceived in channel/account config schema (#86426) Merged via squash. Prepared head SHA: 27003a8d5af2d4f3fc0769989a04be6e574d4875 Co-authored-by: bladin <1740879+bladin@users.noreply.github.com> Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com> Reviewed-by: @mcaxtr --- .../src/auto-reply/monitor/process-message.ts | 8 +- src/config/types.whatsapp.ts | 10 +++ .../zod-schema.providers-whatsapp.test.ts | 76 +++++++++++++++++++ src/config/zod-schema.providers-whatsapp.ts | 8 ++ 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/extensions/whatsapp/src/auto-reply/monitor/process-message.ts b/extensions/whatsapp/src/auto-reply/monitor/process-message.ts index 60bfdbdf578..798aece49ab 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/process-message.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/process-message.ts @@ -80,7 +80,7 @@ const WHATSAPP_MESSAGE_RECEIVED_HOOK_LIMITS = { type WhatsAppMessageReceivedHookConfig = { pluginHooks?: { - messageReceived?: unknown; + messageReceived?: boolean; }; accounts?: Record; }; @@ -90,7 +90,10 @@ function readWhatsAppMessageReceivedHookOptIn(value: unknown): boolean | undefin return undefined; } const pluginHooks = (value as WhatsAppMessageReceivedHookConfig).pluginHooks; - return pluginHooks?.messageReceived === true ? true : undefined; + if (pluginHooks?.messageReceived === undefined) { + return undefined; + } + return pluginHooks.messageReceived; } function shouldEmitWhatsAppMessageReceivedHooks(params: { @@ -104,6 +107,7 @@ function shouldEmitWhatsAppMessageReceivedHooks(params: { params.accountId && channelConfig?.accounts ? channelConfig.accounts[params.accountId] : undefined; + return ( readWhatsAppMessageReceivedHookOptIn(accountConfig) ?? readWhatsAppMessageReceivedHookOptIn(channelConfig) ?? diff --git a/src/config/types.whatsapp.ts b/src/config/types.whatsapp.ts index 8c93b6e6e00..7d53c5b5d43 100644 --- a/src/config/types.whatsapp.ts +++ b/src/config/types.whatsapp.ts @@ -134,6 +134,11 @@ export type WhatsAppConfig = WhatsAppConfigCore & defaultAccount?: string; /** Per-action tool gating (default: true for all). */ actions?: WhatsAppActionConfig; + /** Plugin hook opt-in configuration for privacy-sensitive inbound events. */ + pluginHooks?: { + /** Enable message_received hooks to broadcast inbound WhatsApp messages to plugins. */ + messageReceived?: boolean; + }; }; export type WhatsAppAccountConfig = WhatsAppConfigCore & @@ -144,4 +149,9 @@ export type WhatsAppAccountConfig = WhatsAppConfigCore & enabled?: boolean; /** Override auth directory (Baileys multi-file auth state). */ authDir?: string; + /** Plugin hook opt-in configuration for privacy-sensitive inbound events. */ + pluginHooks?: { + /** Enable message_received hooks to broadcast inbound WhatsApp messages to plugins. */ + messageReceived?: boolean; + }; }; diff --git a/src/config/zod-schema.providers-whatsapp.test.ts b/src/config/zod-schema.providers-whatsapp.test.ts index fab8510c2d4..d29c14395f9 100644 --- a/src/config/zod-schema.providers-whatsapp.test.ts +++ b/src/config/zod-schema.providers-whatsapp.test.ts @@ -136,4 +136,80 @@ describe("WhatsApp prompt config Zod validation", () => { undefined, ); }); + + it("accepts channel-level pluginHooks.messageReceived", () => { + const config = { + pluginHooks: { + messageReceived: true, + }, + }; + + const result = WhatsAppConfigSchema.safeParse(config); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.pluginHooks?.messageReceived).toBe(true); + } + }); + + it("accepts account-level pluginHooks.messageReceived", () => { + const config = { + accounts: { + work: { + pluginHooks: { + messageReceived: true, + }, + }, + }, + }; + + const result = WhatsAppConfigSchema.safeParse(config); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.accounts?.work?.pluginHooks?.messageReceived).toBe(true); + } + }); + + it("rejects extra properties in pluginHooks", () => { + const config = { + pluginHooks: { + messageReceived: true, + otherProp: "invalid", + }, + }; + + const result = WhatsAppConfigSchema.safeParse(config); + expect(result.success).toBe(false); + }); + + it("accepts channel-level pluginHooks.messageReceived: false", () => { + const config = { + pluginHooks: { + messageReceived: false, + }, + }; + + const result = WhatsAppConfigSchema.safeParse(config); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.pluginHooks?.messageReceived).toBe(false); + } + }); + + it("accepts account-level pluginHooks.messageReceived: false", () => { + const config = { + accounts: { + work: { + pluginHooks: { + messageReceived: false, + }, + }, + }, + }; + + const result = WhatsAppConfigSchema.safeParse(config); + expect(result.success).toBe(true); + if (result.success) { + expect(result.data.accounts?.work?.pluginHooks?.messageReceived).toBe(false); + } + }); }); diff --git a/src/config/zod-schema.providers-whatsapp.ts b/src/config/zod-schema.providers-whatsapp.ts index 08d9c5aa774..b150d5b119a 100644 --- a/src/config/zod-schema.providers-whatsapp.ts +++ b/src/config/zod-schema.providers-whatsapp.ts @@ -48,6 +48,13 @@ const WhatsAppAckReactionSchema = z .strict() .optional(); +const WhatsAppPluginHooksSchema = z + .object({ + messageReceived: z.boolean().optional(), + }) + .strict() + .optional(); + function stripDeprecatedWhatsAppNoopKeys(value: unknown): unknown { if (!value || typeof value !== "object" || Array.isArray(value)) { return value; @@ -97,6 +104,7 @@ function buildWhatsAppCommonShape(params: { useDefaults: boolean }) { replyToMode: ReplyToModeSchema.optional(), heartbeat: ChannelHeartbeatVisibilitySchema, healthMonitor: ChannelHealthMonitorSchema, + pluginHooks: WhatsAppPluginHooksSchema, }; }