fix: treat reply-to-bot as implicit mention across channels

This commit is contained in:
Peter Steinberger
2026-01-16 21:50:44 +00:00
parent 97a41a6509
commit 05d149a49b
19 changed files with 427 additions and 20 deletions

View File

@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import { applyGroupGating } from "./group-gating.js";
const baseConfig = {
channels: {
whatsapp: {
groupPolicy: "open",
groups: { "*": { requireMention: true } },
},
},
session: { store: "/tmp/clawdbot-sessions.json" },
} as const;
describe("applyGroupGating", () => {
it("treats reply-to-bot as implicit mention", () => {
const groupHistories = new Map();
const result = applyGroupGating({
cfg: baseConfig as unknown as ReturnType<typeof import("../../../config/config.js").loadConfig>,
msg: {
id: "m1",
from: "123@g.us",
conversationId: "123@g.us",
to: "+15550000",
accountId: "default",
body: "following up",
timestamp: Date.now(),
chatType: "group",
chatId: "123@g.us",
selfJid: "15551234567@s.whatsapp.net",
selfE164: "+15551234567",
replyToId: "m0",
replyToBody: "bot said hi",
replyToSender: "+15551234567",
replyToSenderJid: "15551234567@s.whatsapp.net",
replyToSenderE164: "+15551234567",
sendComposing: async () => {},
reply: async () => {},
sendMedia: async () => {},
},
conversationId: "123@g.us",
groupHistoryKey: "group:123@g.us",
agentId: "main",
sessionKey: "agent:main:whatsapp:group:123@g.us",
baseMentionConfig: { mentionRegexes: [] },
groupHistories,
groupHistoryLimit: 10,
groupMemberNames: new Map(),
logVerbose: () => {},
replyLogger: { debug: () => {} },
});
expect(result.shouldProcess).toBe(true);
});
});

View File

@@ -2,6 +2,7 @@ import { hasControlCommand } from "../../../auto-reply/command-detection.js";
import { parseActivationCommand } from "../../../auto-reply/group-activation.js";
import type { loadConfig } from "../../../config/config.js";
import { normalizeE164 } from "../../../utils.js";
import { resolveMentionGating } from "../../../channels/mention-gating.js";
import type { MentionConfig } from "../mentions.js";
import { buildMentionConfig, debugMention, resolveOwnerList } from "../mentions.js";
import type { WebInboundMsg } from "../types.js";
@@ -94,7 +95,6 @@ export function applyGroupGating(params: {
"group mention debug",
);
const wasMentioned = mentionDebug.wasMentioned;
params.msg.wasMentioned = wasMentioned;
const activation = resolveGroupActivationFor({
cfg: params.cfg,
agentId: params.agentId,
@@ -102,7 +102,25 @@ export function applyGroupGating(params: {
conversationId: params.conversationId,
});
const requireMention = activation !== "always";
if (!shouldBypassMention && requireMention && !wasMentioned) {
const selfJid = params.msg.selfJid?.replace(/:\\d+/, "");
const replySenderJid = params.msg.replyToSenderJid?.replace(/:\\d+/, "");
const selfE164 = params.msg.selfE164 ? normalizeE164(params.msg.selfE164) : null;
const replySenderE164 = params.msg.replyToSenderE164
? normalizeE164(params.msg.replyToSenderE164)
: null;
const implicitMention = Boolean(
(selfJid && replySenderJid && selfJid === replySenderJid) ||
(selfE164 && replySenderE164 && selfE164 === replySenderE164),
);
const mentionGate = resolveMentionGating({
requireMention,
canDetectMention: true,
wasMentioned,
implicitMention,
shouldBypassMention,
});
params.msg.wasMentioned = mentionGate.effectiveWasMentioned;
if (!shouldBypassMention && requireMention && mentionGate.shouldSkip) {
params.logVerbose(
`Group message stored for context (no mention detected) in ${params.conversationId}: ${params.msg.body}`,
);

View File

@@ -238,6 +238,8 @@ export function describeReplyContext(rawMessage: proto.IMessage | undefined): {
id?: string;
body: string;
sender: string;
senderJid?: string;
senderE164?: string;
} | null {
const message = unwrapMessage(rawMessage);
if (!message) return null;
@@ -265,5 +267,7 @@ export function describeReplyContext(rawMessage: proto.IMessage | undefined): {
id: contextInfo?.stanzaId ? String(contextInfo.stanzaId) : undefined,
body,
sender,
senderJid,
senderE164,
};
}

View File

@@ -284,6 +284,8 @@ export async function monitorWebInbox(options: {
replyToId: replyContext?.id,
replyToBody: replyContext?.body,
replyToSender: replyContext?.sender,
replyToSenderJid: replyContext?.senderJid,
replyToSenderE164: replyContext?.senderE164,
groupSubject,
groupParticipants,
mentionedJids: mentionedJids ?? undefined,

View File

@@ -24,6 +24,8 @@ export type WebInboundMessage = {
replyToId?: string;
replyToBody?: string;
replyToSender?: string;
replyToSenderJid?: string;
replyToSenderE164?: string;
groupSubject?: string;
groupParticipants?: string[];
mentionedJids?: string[];