From 2daead27cf22d5f81a180b1d19a68c95ec4e403a Mon Sep 17 00:00:00 2001 From: sheeek Date: Sat, 10 Jan 2026 00:54:49 +0100 Subject: [PATCH] feat(whatsapp): redesign ack-reaction as whatsapp-specific feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move config from messages.ackReaction to whatsapp.ackReaction - New structure: {emoji, direct, group} with granular control - Support per-account overrides in whatsapp.accounts.*.ackReaction - Add Zod schema validation for new config - Maintain backward compatibility with old messages.ackReaction format - Update tests to new config structure (14 tests, all passing) - Add comprehensive documentation in docs/providers/whatsapp.md - Timing: reactions sent immediately upon message receipt (before bot reply) Breaking changes: - Config moved from messages.ackReaction to whatsapp.ackReaction - Scope values changed: 'all'/'direct'/'group-all'/'group-mentions' → direct: boolean + group: 'always'/'mentions'/'never' - Old config still supported via fallback for smooth migration --- docs/providers/whatsapp.md | 50 +++ src/config/types.ts | 30 ++ src/config/zod-schema.ts | 20 + src/web/auto-reply.ack-reaction.test.ts | 509 +++++++++++------------- src/web/auto-reply.ts | 131 ++++-- 5 files changed, 422 insertions(+), 318 deletions(-) diff --git a/docs/providers/whatsapp.md b/docs/providers/whatsapp.md index e425adb17..67b415652 100644 --- a/docs/providers/whatsapp.md +++ b/docs/providers/whatsapp.md @@ -159,6 +159,54 @@ Behavior: - WhatsApp Web sends standard messages (no quoted reply threading in the current gateway). - Reply tags are ignored on this provider. +## Acknowledgment reactions (auto-react on receipt) + +WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received. + +**Configuration:** +```json +{ + "whatsapp": { + "ackReaction": { + "emoji": "👀", + "direct": true, + "group": "mentions" + } + } +} +``` + +**Options:** +- `emoji` (string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled. +- `direct` (boolean, default: `true`): Send reactions in direct/DM chats. +- `group` (string, default: `"mentions"`): Group chat behavior: + - `"always"`: React to all group messages (even without @mention) + - `"mentions"`: React only when bot is @mentioned + - `"never"`: Never react in groups + +**Per-account override:** +```json +{ + "whatsapp": { + "accounts": { + "work": { + "ackReaction": { + "emoji": "✅", + "direct": false, + "group": "always" + } + } + } + } +} +``` + +**Behavior notes:** +- Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies. +- In groups with `requireMention: false` (activation: always), `group: "mentions"` will react to all messages (not just @mentions). +- Fire-and-forget: reaction failures are logged but don't prevent the bot from replying. +- Participant JID is automatically included for group reactions. + ## Agent tool (reactions) - Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`). - Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account). @@ -205,8 +253,10 @@ Behavior: - `whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number). - `whatsapp.allowFrom` (DM allowlist). - `whatsapp.mediaMaxMb` (inbound media save cap). +- `whatsapp.ackReaction` (auto-reaction on message receipt: `{emoji, direct, group}`). - `whatsapp.accounts..*` (per-account settings + optional `authDir`). - `whatsapp.accounts..mediaMaxMb` (per-account inbound media cap). +- `whatsapp.accounts..ackReaction` (per-account ack reaction override). - `whatsapp.groupAllowFrom` (group sender allowlist). - `whatsapp.groupPolicy` (group policy). - `whatsapp.historyLimit` / `whatsapp.accounts..historyLimit` (group history context; `0` disables). diff --git a/src/config/types.ts b/src/config/types.ts index 545566495..235177962 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -169,6 +169,21 @@ export type WhatsAppConfig = { requireMention?: boolean; } >; + /** Acknowledgment reaction sent immediately upon message receipt. */ + ackReaction?: { + /** Emoji to use for acknowledgment (e.g., "👀"). Empty = disabled. */ + emoji?: string; + /** Send reactions in direct chats. Default: true. */ + direct?: boolean; + /** + * Send reactions in group chats: + * - "always": react to all group messages + * - "mentions": react only when bot is mentioned + * - "never": never react in groups + * Default: "mentions" + */ + group?: "always" | "mentions" | "never"; + }; }; export type WhatsAppAccountConfig = { @@ -202,6 +217,21 @@ export type WhatsAppAccountConfig = { requireMention?: boolean; } >; + /** Acknowledgment reaction sent immediately upon message receipt. */ + ackReaction?: { + /** Emoji to use for acknowledgment (e.g., "👀"). Empty = disabled. */ + emoji?: string; + /** Send reactions in direct chats. Default: true. */ + direct?: boolean; + /** + * Send reactions in group chats: + * - "always": react to all group messages + * - "mentions": react only when bot is mentioned + * - "never": never react in groups + * Default: "mentions" + */ + group?: "always" | "mentions" | "never"; + }; }; export type BrowserProfileConfig = { diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 0d38a8c64..d7fd6d04b 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -1375,6 +1375,16 @@ export const ClawdbotSchema = z .optional(), ) .optional(), + ackReaction: z + .object({ + emoji: z.string().optional(), + direct: z.boolean().optional().default(true), + group: z + .enum(["always", "mentions", "never"]) + .optional() + .default("mentions"), + }) + .optional(), }) .superRefine((value, ctx) => { if (value.dmPolicy !== "open") return; @@ -1421,6 +1431,16 @@ export const ClawdbotSchema = z .optional(), ) .optional(), + ackReaction: z + .object({ + emoji: z.string().optional(), + direct: z.boolean().optional().default(true), + group: z + .enum(["always", "mentions", "never"]) + .optional() + .default("mentions"), + }) + .optional(), }) .superRefine((value, ctx) => { if (value.dmPolicy !== "open") return; diff --git a/src/web/auto-reply.ack-reaction.test.ts b/src/web/auto-reply.ack-reaction.test.ts index 96471ad76..f34bd6735 100644 --- a/src/web/auto-reply.ack-reaction.test.ts +++ b/src/web/auto-reply.ack-reaction.test.ts @@ -1,303 +1,260 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { describe, expect, it } from "vitest"; import type { ClawdbotConfig } from "../config/types.js"; -describe("WhatsApp ack reaction", () => { - beforeEach(() => { - vi.clearAllMocks(); - }); +describe("WhatsApp ack reaction logic", () => { + // Helper to simulate the logic from auto-reply.ts + function shouldSendReaction( + cfg: ClawdbotConfig, + msg: { + id?: string; + chatType: "direct" | "group"; + wasMentioned?: boolean; + }, + groupActivation?: "always" | "mention", + ): boolean { + const ackConfig = cfg.whatsapp?.ackReaction; + const emoji = (ackConfig?.emoji ?? "").trim(); + const directEnabled = ackConfig?.direct ?? true; + const groupMode = ackConfig?.group ?? "mentions"; - it("should send ack reaction in direct chat when scope is 'all'", async () => { - const cfg: ClawdbotConfig = { - messages: { - ackReaction: "👀", - ackReactionScope: "all", - }, - }; + if (!emoji) return false; + if (!msg.id) return false; - // Simulate the logic from auto-reply.ts - const msg = { - id: "msg123", - chatId: "123456789@s.whatsapp.net", - chatType: "direct" as const, - from: "+1234567890", - to: "+9876543210", - body: "hello", - }; + // Direct chat logic + if (msg.chatType === "direct") { + return directEnabled; + } - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions"; - const didSendReply = true; - - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - if (ackReactionScope === "all") return true; - if (ackReactionScope === "direct") return msg.chatType === "direct"; - if (ackReactionScope === "group-all") return msg.chatType === "group"; - if (ackReactionScope === "group-mentions") { - if (msg.chatType !== "group") return false; - return false; // Would check wasMentioned - } - return false; - }; - - expect(shouldAckReaction()).toBe(true); - }); - - it("should send ack reaction in direct chat when scope is 'direct'", async () => { - const cfg: ClawdbotConfig = { - messages: { - ackReaction: "👀", - ackReactionScope: "direct", - }, - }; - - const msg = { - id: "msg123", - chatId: "123456789@s.whatsapp.net", - chatType: "direct" as const, - from: "+1234567890", - to: "+9876543210", - body: "hello", - }; - - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions"; - const didSendReply = true; - - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - if (ackReactionScope === "all") return true; - if (ackReactionScope === "direct") return msg.chatType === "direct"; - if (ackReactionScope === "group-all") return msg.chatType === "group"; - return false; - }; - - expect(shouldAckReaction()).toBe(true); - }); - - it("should NOT send ack reaction in group when scope is 'direct'", async () => { - const cfg: ClawdbotConfig = { - messages: { - ackReaction: "👀", - ackReactionScope: "direct", - }, - }; - - const msg = { - id: "msg123", - chatId: "123456789-group@g.us", - chatType: "group" as const, - from: "123456789-group@g.us", - to: "+9876543210", - body: "hello", - wasMentioned: true, - }; - - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions"; - const didSendReply = true; - - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - if (ackReactionScope === "all") return true; - if (ackReactionScope === "direct") return msg.chatType === "direct"; - if (ackReactionScope === "group-all") return msg.chatType === "group"; - return false; - }; - - expect(shouldAckReaction()).toBe(false); - }); - - it("should send ack reaction in group when mentioned and scope is 'group-mentions' (requireMention=true)", async () => { - const cfg: ClawdbotConfig = { - messages: { - ackReaction: "👀", - ackReactionScope: "group-mentions", - }, - }; - - const msg = { - id: "msg123", - chatId: "123456789-group@g.us", - chatType: "group" as const, - from: "123456789-group@g.us", - to: "+9876543210", - body: "hello @bot", - wasMentioned: true, - }; - - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions"; - const didSendReply = true; - const requireMention = true; // Simulated from activation check - - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - if (ackReactionScope === "all") return true; - if (ackReactionScope === "direct") return msg.chatType === "direct"; - if (ackReactionScope === "group-all") return msg.chatType === "group"; - if (ackReactionScope === "group-mentions") { - if (msg.chatType !== "group") return false; - // If mention is not required (activation === "always"), always react - if (!requireMention) return true; + // Group chat logic + if (msg.chatType === "group") { + if (groupMode === "never") return false; + if (groupMode === "always") return true; + if (groupMode === "mentions") { + // If group activation is "always", always react + if (groupActivation === "always") return true; // Otherwise, only react if bot was mentioned return msg.wasMentioned === true; } - return false; - }; + } - expect(shouldAckReaction()).toBe(true); + return false; + } + + describe("direct chat", () => { + it("should react when direct=true", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", direct: true } }, + }; + expect( + shouldSendReaction(cfg, { + id: "msg1", + chatType: "direct", + }), + ).toBe(true); + }); + + it("should not react when direct=false", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", direct: false } }, + }; + expect( + shouldSendReaction(cfg, { + id: "msg1", + chatType: "direct", + }), + ).toBe(false); + }); + + it("should not react when emoji is empty", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "", direct: true } }, + }; + expect( + shouldSendReaction(cfg, { + id: "msg1", + chatType: "direct", + }), + ).toBe(false); + }); }); - it("should send ack reaction in group when requireMention=false and scope is 'group-mentions' (activation: always)", async () => { - const cfg: ClawdbotConfig = { - messages: { - ackReaction: "👀", - ackReactionScope: "group-mentions", - }, - }; + describe("group chat - always mode", () => { + it("should react to all messages when group=always", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", group: "always" } }, + }; + expect( + shouldSendReaction(cfg, { + id: "msg1", + chatType: "group", + wasMentioned: false, + }), + ).toBe(true); + }); - const msg = { - id: "msg123", - chatId: "123456789-group@g.us", - chatType: "group" as const, - from: "123456789-group@g.us", - to: "+9876543210", - body: "hello", - wasMentioned: false, // No mention, but activation is "always" - }; - - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions"; - const didSendReply = true; - const requireMention = false; // activation === "always" - - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - if (ackReactionScope === "all") return true; - if (ackReactionScope === "direct") return msg.chatType === "direct"; - if (ackReactionScope === "group-all") return msg.chatType === "group"; - if (ackReactionScope === "group-mentions") { - if (msg.chatType !== "group") return false; - // If mention is not required (activation === "always"), always react - if (!requireMention) return true; - return msg.wasMentioned === true; - } - return false; - }; - - expect(shouldAckReaction()).toBe(true); + it("should react even with mention when group=always", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", group: "always" } }, + }; + expect( + shouldSendReaction(cfg, { + id: "msg1", + chatType: "group", + wasMentioned: true, + }), + ).toBe(true); + }); }); - it("should NOT send ack reaction in group when NOT mentioned and scope is 'group-mentions' (requireMention=true)", async () => { - const cfg: ClawdbotConfig = { - messages: { - ackReaction: "👀", - ackReactionScope: "group-mentions", - }, - }; + describe("group chat - mentions mode", () => { + it("should react when mentioned", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } }, + }; + expect( + shouldSendReaction(cfg, { + id: "msg1", + chatType: "group", + wasMentioned: true, + }), + ).toBe(true); + }); - const msg = { - id: "msg123", - chatId: "123456789-group@g.us", - chatType: "group" as const, - from: "123456789-group@g.us", - to: "+9876543210", - body: "hello", - wasMentioned: false, - }; + it("should not react when not mentioned", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } }, + }; + expect( + shouldSendReaction( + cfg, + { + id: "msg1", + chatType: "group", + wasMentioned: false, + }, + "mention", // group activation + ), + ).toBe(false); + }); - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const ackReactionScope = cfg.messages?.ackReactionScope ?? "group-mentions"; - const didSendReply = true; - const requireMention = true; - - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - if (ackReactionScope === "all") return true; - if (ackReactionScope === "direct") return msg.chatType === "direct"; - if (ackReactionScope === "group-all") return msg.chatType === "group"; - if (ackReactionScope === "group-mentions") { - if (msg.chatType !== "group") return false; - // If mention is not required (activation === "always"), always react - if (!requireMention) return true; - return msg.wasMentioned === true; - } - return false; - }; - - expect(shouldAckReaction()).toBe(false); + it("should react to all messages when group activation is always", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", group: "mentions" } }, + }; + expect( + shouldSendReaction( + cfg, + { + id: "msg1", + chatType: "group", + wasMentioned: false, + }, + "always", // group has requireMention=false + ), + ).toBe(true); + }); }); - it("should NOT send ack reaction when no reply was sent", async () => { - const cfg: ClawdbotConfig = { - messages: { - ackReaction: "👀", - ackReactionScope: "all", - }, - }; + describe("group chat - never mode", () => { + it("should not react even with mention", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", group: "never" } }, + }; + expect( + shouldSendReaction(cfg, { + id: "msg1", + chatType: "group", + wasMentioned: true, + }), + ).toBe(false); + }); - const msg = { - id: "msg123", - chatId: "123456789@s.whatsapp.net", - chatType: "direct" as const, - from: "+1234567890", - to: "+9876543210", - body: "hello", - }; - - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const didSendReply = false; // No reply sent - - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - return true; - }; - - expect(shouldAckReaction()).toBe(false); + it("should not react without mention", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀", group: "never" } }, + }; + expect( + shouldSendReaction(cfg, { + id: "msg1", + chatType: "group", + wasMentioned: false, + }), + ).toBe(false); + }); }); - it("should NOT send ack reaction when ackReaction is empty", async () => { - const cfg: ClawdbotConfig = { - messages: { - ackReaction: "", - ackReactionScope: "all", - }, - }; + describe("combinations", () => { + it("direct=false, group=always: only groups", () => { + const cfg: ClawdbotConfig = { + whatsapp: { + ackReaction: { emoji: "✅", direct: false, group: "always" }, + }, + }; - const msg = { - id: "msg123", - chatId: "123456789@s.whatsapp.net", - chatType: "direct" as const, - from: "+1234567890", - to: "+9876543210", - body: "hello", - }; + expect( + shouldSendReaction(cfg, { id: "m1", chatType: "direct" }), + ).toBe(false); - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const didSendReply = true; + expect( + shouldSendReaction(cfg, { + id: "m2", + chatType: "group", + wasMentioned: false, + }), + ).toBe(true); + }); - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - return true; - }; + it("direct=true, group=never: only direct", () => { + const cfg: ClawdbotConfig = { + whatsapp: { + ackReaction: { emoji: "🤖", direct: true, group: "never" }, + }, + }; - expect(shouldAckReaction()).toBe(false); + expect( + shouldSendReaction(cfg, { id: "m1", chatType: "direct" }), + ).toBe(true); + + expect( + shouldSendReaction(cfg, { + id: "m2", + chatType: "group", + wasMentioned: true, + }), + ).toBe(false); + }); + }); + + describe("defaults", () => { + it("should default direct=true", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀" } }, + }; + expect( + shouldSendReaction(cfg, { id: "m1", chatType: "direct" }), + ).toBe(true); + }); + + it("should default group=mentions", () => { + const cfg: ClawdbotConfig = { + whatsapp: { ackReaction: { emoji: "👀" } }, + }; + + expect( + shouldSendReaction(cfg, { + id: "m1", + chatType: "group", + wasMentioned: false, + }), + ).toBe(false); + + expect( + shouldSendReaction(cfg, { + id: "m2", + chatType: "group", + wasMentioned: true, + }), + ).toBe(true); + }); }); }); diff --git a/src/web/auto-reply.ts b/src/web/auto-reply.ts index a0c769bfe..3f6a4df0a 100644 --- a/src/web/auto-reply.ts +++ b/src/web/auto-reply.ts @@ -1149,6 +1149,95 @@ export async function monitorWebProvider( status.lastMessageAt = Date.now(); status.lastEventAt = status.lastMessageAt; emitStatus(); + + // Send ack reaction immediately upon message receipt + if (msg.id) { + const ackConfig = cfg.whatsapp?.ackReaction; + // Backward compatibility: support old messages.ackReaction format + const legacyEmoji = (cfg.messages as any)?.ackReaction; + const legacyScope = (cfg.messages as any)?.ackReactionScope; + let emoji = (ackConfig?.emoji ?? "").trim(); + let directEnabled = ackConfig?.direct ?? true; + let groupMode = ackConfig?.group ?? "mentions"; + + // Fallback to legacy config if new config is not set + if (!emoji && typeof legacyEmoji === "string") { + emoji = legacyEmoji.trim(); + if (legacyScope === "all") { + directEnabled = true; + groupMode = "always"; + } else if (legacyScope === "direct") { + directEnabled = true; + groupMode = "never"; + } else if (legacyScope === "group-all") { + directEnabled = false; + groupMode = "always"; + } else if (legacyScope === "group-mentions") { + directEnabled = false; + groupMode = "mentions"; + } + } + + const conversationIdForCheck = msg.conversationId ?? msg.from; + + const shouldSendReaction = () => { + if (!emoji) return false; + + // Direct chat logic + if (msg.chatType === "direct") { + return directEnabled; + } + + // Group chat logic + if (msg.chatType === "group") { + if (groupMode === "never") return false; + if (groupMode === "always") { + // Always react to group messages + return true; + } + if (groupMode === "mentions") { + // Check if group has requireMention setting + const activation = resolveGroupActivationFor({ + agentId: route.agentId, + sessionKey: route.sessionKey, + conversationId: conversationIdForCheck, + }); + // If group activation is "always" (requireMention=false), react to all + if (activation === "always") return true; + // Otherwise, only react if bot was mentioned + return msg.wasMentioned === true; + } + } + + return false; + }; + + if (shouldSendReaction()) { + replyLogger.info( + { chatId: msg.chatId, messageId: msg.id, emoji }, + "sending ack reaction", + ); + sendReactionWhatsApp(msg.chatId, msg.id, emoji, { + verbose, + fromMe: false, + participant: msg.senderJid, + accountId: route.accountId, + }).catch((err) => { + replyLogger.warn( + { + error: formatError(err), + chatId: msg.chatId, + messageId: msg.id, + }, + "failed to send ack reaction", + ); + logVerbose( + `WhatsApp ack reaction failed for chat ${msg.chatId}: ${formatError(err)}`, + ); + }); + } + } + const conversationId = msg.conversationId ?? msg.from; let combinedBody = buildLine(msg, route.agentId); let shouldClearGroupHistory = false; @@ -1387,48 +1476,6 @@ export async function monitorWebProvider( groupHistories.set(groupHistoryKey, []); } - // Send ack reaction after successful reply - const ackReaction = (cfg.messages?.ackReaction ?? "").trim(); - const ackReactionScope = - cfg.messages?.ackReactionScope ?? "group-mentions"; - const shouldAckReaction = () => { - if (!ackReaction) return false; - if (!msg.id) return false; - if (!didSendReply) return false; - if (ackReactionScope === "all") return true; - if (ackReactionScope === "direct") return msg.chatType === "direct"; - if (ackReactionScope === "group-all") return msg.chatType === "group"; - if (ackReactionScope === "group-mentions") { - if (msg.chatType !== "group") return false; - const activation = resolveGroupActivationFor({ - agentId: route.agentId, - sessionKey: route.sessionKey, - conversationId, - }); - const requireMention = activation !== "always"; - // If mention is not required (activation === "always"), always react - if (!requireMention) return true; - // Otherwise, only react if bot was mentioned - return msg.wasMentioned === true; - } - return false; - }; - - if (shouldAckReaction() && msg.id) { - sendReactionWhatsApp(msg.chatId, msg.id, ackReaction, { - verbose, - fromMe: false, - }).catch((err) => { - replyLogger.warn( - { error: formatError(err), chatId: msg.chatId, messageId: msg.id }, - "failed to send ack reaction", - ); - logVerbose( - `WhatsApp ack reaction failed for chat ${msg.chatId}: ${formatError(err)}`, - ); - }); - } - return didSendReply; };