diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f40aa37346..2f510ef4ef9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ Docs: https://docs.openclaw.ai - Agents/status: use the persisted runtime session model in `session_status` when no explicit override exists, and honor per-agent `thinkingDefault` in both `session_status` and `/status`. (#55425) Thanks @scoootscooob, @xaeon2026, and @ysfbsf. - Heartbeat/runner: guarantee the interval timer is re-armed after heartbeat runs and unexpected runner errors so scheduled heartbeats do not silently stop after an interrupted cycle. (#52270) Thanks @MiloStack. - Config/Doctor: rewrite stale bundled plugin load paths from legacy `extensions/*` locations to the packaged bundled path, including directory-name mismatches and slash-suffixed config entries. (#55054) Thanks @SnowSky1. +- WhatsApp/mentions: stop treating mentions embedded in quoted messages as direct mentions so replying to a message that @mentioned the bot no longer falsely triggers mention gating. (#52711) Thanks @lurebat. ## 2026.3.24 diff --git a/extensions/whatsapp/src/inbound/extract.test.ts b/extensions/whatsapp/src/inbound/extract.test.ts new file mode 100644 index 00000000000..62e1971aee6 --- /dev/null +++ b/extensions/whatsapp/src/inbound/extract.test.ts @@ -0,0 +1,103 @@ +import type { proto } from "@whiskeysockets/baileys"; +import { describe, expect, it } from "vitest"; +import { extractMentionedJids } from "./extract.js"; + +describe("extractMentionedJids", () => { + const botJid = "5511999999999@s.whatsapp.net"; + const otherJid = "5511888888888@s.whatsapp.net"; + + it("returns direct mentions from the current message", () => { + const message: proto.IMessage = { + extendedTextMessage: { + text: "Hey @bot", + contextInfo: { + mentionedJid: [botJid], + }, + }, + }; + expect(extractMentionedJids(message)).toEqual([botJid]); + }); + + it("ignores mentionedJids from quoted messages", () => { + const message: proto.IMessage = { + extendedTextMessage: { + text: "I agree", + contextInfo: { + // The quoted message originally @mentioned the bot, but the + // current message does not — this should NOT leak through. + quotedMessage: { + extendedTextMessage: { + text: "Hey @bot what do you think?", + contextInfo: { + mentionedJid: [botJid], + }, + }, + }, + }, + }, + }; + expect(extractMentionedJids(message)).toBeUndefined(); + }); + + it("returns direct mentions even when quoted message also has mentions", () => { + const message: proto.IMessage = { + extendedTextMessage: { + text: "Hey @other", + contextInfo: { + mentionedJid: [otherJid], + quotedMessage: { + extendedTextMessage: { + text: "Hey @bot", + contextInfo: { + mentionedJid: [botJid], + }, + }, + }, + }, + }, + }; + // Should return only the direct mention, not the quoted one. + expect(extractMentionedJids(message)).toEqual([otherJid]); + }); + + it("returns mentions from media message types", () => { + const message: proto.IMessage = { + imageMessage: { + contextInfo: { + mentionedJid: [botJid], + }, + }, + }; + expect(extractMentionedJids(message)).toEqual([botJid]); + }); + + it("returns undefined for messages with no mentions", () => { + const message: proto.IMessage = { + extendedTextMessage: { + text: "Just a regular message", + }, + }; + expect(extractMentionedJids(message)).toBeUndefined(); + }); + + it("returns undefined for undefined input", () => { + expect(extractMentionedJids(undefined)).toBeUndefined(); + }); + + it("deduplicates mentions across message types", () => { + const message: proto.IMessage = { + extendedTextMessage: { + text: "Hey @bot", + contextInfo: { + mentionedJid: [botJid], + }, + }, + imageMessage: { + contextInfo: { + mentionedJid: [botJid], + }, + }, + }; + expect(extractMentionedJids(message)).toEqual([botJid]); + }); +});