mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:20:43 +00:00
Merged via squash.
Prepared head SHA: 357c62849f
Co-authored-by: juan-flores077 <112629487+juan-flores077@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
This commit is contained in:
@@ -90,6 +90,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/context engines: preserve the child agent's configured `agentDir` when subagent cleanup re-resolves a context engine, so `onSubagentEnded` hooks keep operating on the correct per-agent state. (#67243) Thanks @jarimustonen.
|
||||
- Channels/WhatsApp: restrict pairing verification replies to real inbound user content, preventing unsolicited prompts from receipts, typing indicators, presence updates, and other non-message Baileys upserts. Fixes #73797. (#73823) Thanks @hclsys.
|
||||
- Configure/Ollama: show the configured Ollama model allowlist after Cloud only or Cloud + Local setup and skip slow per-model cloud metadata fetches. (#73995) Thanks @obviyus.
|
||||
- Channels/WhatsApp: detect explicit group `@mentions` again when the bot's own E.164 is in `allowFrom`, so shared-number setups no longer skip group pings that directly mention the bot. Fixes #49317. (#73453) Thanks @juan-flores077.
|
||||
|
||||
## 2026.4.27
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
identitiesOverlap,
|
||||
type WhatsAppIdentity,
|
||||
} from "../identity.js";
|
||||
import { isWhatsAppGroupJid } from "../normalize-target.js";
|
||||
import { isSelfChatMode, normalizeE164 } from "../text-runtime.js";
|
||||
import type { WebInboundMsg } from "./types.js";
|
||||
|
||||
@@ -44,10 +45,21 @@ export function isBotMentionedFromTargets(
|
||||
// Remove zero-width and directionality markers WhatsApp injects around display names
|
||||
normalizeMentionText(text);
|
||||
|
||||
const isSelfChat =
|
||||
typeof mentionCfg.isSelfChat === "boolean"
|
||||
? mentionCfg.isSelfChat
|
||||
: isSelfChatMode(targets.self.e164, mentionCfg.allowFrom);
|
||||
const explicitSelfChatOverride = typeof mentionCfg.isSelfChat === "boolean";
|
||||
// `isSelfChatMode` is a config-shaped check ("is the bot's own E.164 in
|
||||
// allowFrom?"), not a conversation-shaped check, so it returns true even
|
||||
// for group conversations whenever the operator put their own number in
|
||||
// allowFrom — which is the common config. The original mention-skip path
|
||||
// was designed to prevent owner-mentioning-self in a true 1:1 self DM
|
||||
// from falsely triggering the bot, so when we derive the flag implicitly
|
||||
// from `allowFrom`, confine the suppression to non-group conversations
|
||||
// and let real group @mentions go through the identity-overlap check
|
||||
// (#49317). Explicit `mentionCfg.isSelfChat` overrides from the caller
|
||||
// are honored as-is so multi-account / precomputed paths keep working.
|
||||
const isGroupConversation = isWhatsAppGroupJid(msg.from);
|
||||
const isSelfChat = explicitSelfChatOverride
|
||||
? Boolean(mentionCfg.isSelfChat)
|
||||
: isSelfChatMode(targets.self.e164, mentionCfg.allowFrom) && !isGroupConversation;
|
||||
|
||||
const hasMentions = targets.normalizedMentions.length > 0;
|
||||
if (hasMentions && !isSelfChat) {
|
||||
@@ -59,7 +71,7 @@ export function isBotMentionedFromTargets(
|
||||
// If the message explicitly mentions someone else, do not fall back to regex matches.
|
||||
return false;
|
||||
} else if (hasMentions && isSelfChat) {
|
||||
// Self-chat mode: ignore WhatsApp @mention JIDs, otherwise @mentioning the owner in group chats triggers the bot.
|
||||
// Self-chat mode: ignore WhatsApp @mention JIDs, otherwise @mentioning the owner in self-chat triggers the bot.
|
||||
}
|
||||
const bodyClean = clean(msg.body);
|
||||
if (mentionCfg.mentionRegexes.some((re) => re.test(bodyClean))) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { buildMentionConfig } from "./mentions.js";
|
||||
import { applyGroupGating, type GroupHistoryEntry } from "./monitor/group-gating.js";
|
||||
import { buildInboundLine, formatReplyContext } from "./monitor/message-line.js";
|
||||
import type { WebInboundMsg } from "./types.js";
|
||||
|
||||
let sessionDir: string | undefined;
|
||||
let sessionStorePath: string;
|
||||
@@ -37,10 +38,11 @@ const makeConfig = (overrides: Record<string, unknown>) =>
|
||||
|
||||
async function runGroupGating(params: {
|
||||
cfg: import("openclaw/plugin-sdk/config-types").OpenClawConfig;
|
||||
msg: Record<string, unknown>;
|
||||
msg: WebInboundMsg;
|
||||
conversationId?: string;
|
||||
agentId?: string;
|
||||
selfChatMode?: boolean;
|
||||
authDir?: string;
|
||||
}) {
|
||||
const groupHistories = new Map<string, GroupHistoryEntry[]>();
|
||||
const conversationId = params.conversationId ?? "123@g.us";
|
||||
@@ -49,12 +51,13 @@ async function runGroupGating(params: {
|
||||
const baseMentionConfig = buildMentionConfig(params.cfg, undefined);
|
||||
const result = await applyGroupGating({
|
||||
cfg: params.cfg,
|
||||
msg: params.msg as any,
|
||||
msg: params.msg,
|
||||
conversationId,
|
||||
groupHistoryKey: `whatsapp:default:group:${conversationId}`,
|
||||
agentId,
|
||||
sessionKey,
|
||||
baseMentionConfig,
|
||||
authDir: params.authDir,
|
||||
selfChatMode: params.selfChatMode,
|
||||
groupHistories,
|
||||
groupHistoryLimit: 10,
|
||||
@@ -65,7 +68,7 @@ async function runGroupGating(params: {
|
||||
return { result, groupHistories };
|
||||
}
|
||||
|
||||
function createGroupMessage(overrides: Record<string, unknown> = {}) {
|
||||
function createGroupMessage(overrides: Partial<WebInboundMsg> = {}): WebInboundMsg {
|
||||
return {
|
||||
id: "g1",
|
||||
from: "123@g.us",
|
||||
@@ -73,13 +76,14 @@ function createGroupMessage(overrides: Record<string, unknown> = {}) {
|
||||
chatId: "123@g.us",
|
||||
chatType: "group",
|
||||
to: "+2",
|
||||
accountId: "default",
|
||||
body: "hello group",
|
||||
senderE164: "+111",
|
||||
senderName: "Alice",
|
||||
selfE164: "+999",
|
||||
sendComposing: async () => {},
|
||||
reply: async () => {},
|
||||
sendMedia: async () => {},
|
||||
reply: async (_text, _options) => {},
|
||||
sendMedia: async (_payload, _options) => {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -194,6 +198,45 @@ describe("applyGroupGating", () => {
|
||||
expect(result.shouldProcess).toBe(true);
|
||||
});
|
||||
|
||||
it("processes explicit group @mentions when self is in allowFrom (#49317)", async () => {
|
||||
if (!sessionDir) {
|
||||
throw new Error("sessionDir not initialized");
|
||||
}
|
||||
await fs.writeFile(
|
||||
path.join(sessionDir, "lid-mapping-216372600647751_reverse.json"),
|
||||
JSON.stringify("+15551234567"),
|
||||
);
|
||||
const cfg = makeConfig({
|
||||
channels: {
|
||||
whatsapp: {
|
||||
allowFrom: ["+15551234567"],
|
||||
groupPolicy: "open",
|
||||
groups: { "*": { requireMention: true } },
|
||||
},
|
||||
},
|
||||
});
|
||||
const msg = createGroupMessage({
|
||||
id: "g-self-lid-mention",
|
||||
accountId: "default",
|
||||
body: "@216372600647751 can you see this?",
|
||||
mentionedJids: ["216372600647751@lid"],
|
||||
senderE164: "+15550001111",
|
||||
senderName: "Alice",
|
||||
selfE164: "+15551234567",
|
||||
selfJid: "15551234567@s.whatsapp.net",
|
||||
});
|
||||
|
||||
const { result, groupHistories } = await runGroupGating({
|
||||
cfg,
|
||||
authDir: sessionDir,
|
||||
msg,
|
||||
});
|
||||
|
||||
expect(result.shouldProcess).toBe(true);
|
||||
expect(msg.wasMentioned).toBe(true);
|
||||
expect(groupHistories.get("whatsapp:default:group:123@g.us")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("honors per-account selfChatMode overrides before suppressing implicit mentions", async () => {
|
||||
const cfg = makeConfig({
|
||||
channels: {
|
||||
|
||||
@@ -70,9 +70,15 @@ describe("isBotMentionedFromTargets", () => {
|
||||
expectMentioned(msg, mentionCfg, true);
|
||||
});
|
||||
|
||||
it("ignores JID mentions in self-chat mode", () => {
|
||||
it("ignores JID mentions in a true 1:1 self-chat (not a group)", () => {
|
||||
const cfg = { mentionRegexes: [/\bopenclaw\b/i], allowFrom: ["+999"] };
|
||||
const msg = makeMsg({
|
||||
// Direct chat with self, not a group — the original "ignore mentions
|
||||
// in self-chat" suppression still applies here so that mentioning the
|
||||
// owner in their own DM does not falsely trigger the bot.
|
||||
from: "999@s.whatsapp.net",
|
||||
conversationId: "999@s.whatsapp.net",
|
||||
chatType: "direct",
|
||||
body: "@owner ping",
|
||||
mentionedJids: ["999@s.whatsapp.net"],
|
||||
selfE164: "+999",
|
||||
@@ -81,6 +87,9 @@ describe("isBotMentionedFromTargets", () => {
|
||||
expectMentioned(msg, cfg, false);
|
||||
|
||||
const msgTextMention = makeMsg({
|
||||
from: "999@s.whatsapp.net",
|
||||
conversationId: "999@s.whatsapp.net",
|
||||
chatType: "direct",
|
||||
body: "openclaw ping",
|
||||
selfE164: "+999",
|
||||
selfJid: "999@s.whatsapp.net",
|
||||
@@ -88,6 +97,25 @@ describe("isBotMentionedFromTargets", () => {
|
||||
expectMentioned(msgTextMention, cfg, true);
|
||||
});
|
||||
|
||||
it("detects an explicit group @mention even when self is in allowFrom (#49317)", () => {
|
||||
// Operator config commonly puts their own E.164 in allowFrom so they can
|
||||
// run owner-only commands in groups; previously, that flipped the gate
|
||||
// to "self-chat mode" and silently dropped mention detection in groups,
|
||||
// including LID-style WhatsApp mentions that resolve to the bot's own
|
||||
// E.164. After the fix, group conversations honor the identity-overlap
|
||||
// check regardless of allowFrom.
|
||||
const cfg = { mentionRegexes: [/\bopenclaw\b/i], allowFrom: ["+15551234567"] };
|
||||
const msg = makeMsg({
|
||||
// Default `from` is the @g.us group JID from `makeMsg`.
|
||||
body: "@216372600647751 can you see this?",
|
||||
mentionedJids: ["216372600647751@lid"],
|
||||
selfE164: "+15551234567",
|
||||
selfJid: "15551234567@s.whatsapp.net",
|
||||
selfLid: "216372600647751@lid",
|
||||
});
|
||||
expectMentioned(msg, cfg, true);
|
||||
});
|
||||
|
||||
it("honors explicit self-chat overrides without recomputing from allowFrom", () => {
|
||||
const cfg = {
|
||||
mentionRegexes: [/\bopenclaw\b/i],
|
||||
|
||||
Reference in New Issue
Block a user