fix: unify mention gating across providers

This commit is contained in:
Peter Steinberger
2026-01-06 01:32:17 +01:00
parent 48d52d13f1
commit 811ec8b78b
10 changed files with 253 additions and 51 deletions

View File

@@ -126,6 +126,73 @@ describe("createTelegramBot", () => {
expect(sendChatActionSpy).toHaveBeenCalledWith(42, "typing");
});
it("accepts group messages when mentionPatterns match (without @botUsername)", async () => {
onSpy.mockReset();
const replySpy = replyModule.__replySpy as unknown as ReturnType<
typeof vi.fn
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
identity: { name: "Bert" },
routing: { groupChat: { mentionPatterns: ["\\bbert\\b"] } },
telegram: { groups: { "*": { requireMention: true } } },
});
createTelegramBot({ token: "tok" });
const handler = onSpy.mock.calls[0][1] as (
ctx: Record<string, unknown>,
) => Promise<void>;
await handler({
message: {
chat: { id: 7, type: "group", title: "Test Group" },
text: "bert: introduce yourself",
date: 1736380800,
message_id: 1,
from: { id: 9, first_name: "Ada" },
},
me: { username: "clawdbot_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(replySpy).toHaveBeenCalledTimes(1);
const payload = replySpy.mock.calls[0][0];
expect(payload.WasMentioned).toBe(true);
});
it("skips group messages when requireMention is enabled and no mention matches", async () => {
onSpy.mockReset();
const replySpy = replyModule.__replySpy as unknown as ReturnType<
typeof vi.fn
>;
replySpy.mockReset();
loadConfig.mockReturnValue({
routing: { groupChat: { mentionPatterns: ["\\bbert\\b"] } },
telegram: { groups: { "*": { requireMention: true } } },
});
createTelegramBot({ token: "tok" });
const handler = onSpy.mock.calls[0][1] as (
ctx: Record<string, unknown>,
) => Promise<void>;
await handler({
message: {
chat: { id: 7, type: "group", title: "Test Group" },
text: "hello everyone",
date: 1736380800,
message_id: 2,
from: { id: 9, first_name: "Ada" },
},
me: { username: "clawdbot_bot" },
getFile: async () => ({ download: async () => new Uint8Array() }),
});
expect(replySpy).not.toHaveBeenCalled();
});
it("includes reply-to context when a Telegram reply is received", async () => {
onSpy.mockReset();
sendMessageSpy.mockReset();

View File

@@ -7,6 +7,10 @@ import { Bot, InputFile, webhookCallback } from "grammy";
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
import { hasControlCommand } from "../auto-reply/command-detection.js";
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
import {
buildMentionRegexes,
matchesMentionPatterns,
} from "../auto-reply/reply/mentions.js";
import { createReplyDispatcher } from "../auto-reply/reply/reply-dispatcher.js";
import { getReplyFromConfig } from "../auto-reply/reply.js";
import type { ReplyPayload } from "../auto-reply/types.js";
@@ -67,6 +71,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
const mediaMaxBytes =
(opts.mediaMaxMb ?? cfg.telegram?.mediaMaxMb ?? 5) * 1024 * 1024;
const logger = getChildLogger({ module: "telegram-auto-reply" });
const mentionRegexes = buildMentionRegexes(cfg);
const resolveGroupRequireMention = (chatId: string | number) => {
const groupId = String(chatId);
const groupConfig = cfg.telegram?.groups?.[groupId];
@@ -132,7 +137,8 @@ export function createTelegramBot(opts: TelegramBotOptions) {
entry.toLowerCase() === `@${senderUsername.toLowerCase()}`,
));
const wasMentioned =
Boolean(botUsername) && hasBotMention(msg, botUsername);
(Boolean(botUsername) && hasBotMention(msg, botUsername)) ||
matchesMentionPatterns(msg.text ?? msg.caption ?? "", mentionRegexes);
const hasAnyMention = (msg.entities ?? msg.caption_entities ?? []).some(
(ent) => ent.type === "mention",
);
@@ -143,7 +149,9 @@ export function createTelegramBot(opts: TelegramBotOptions) {
!hasAnyMention &&
commandAuthorized &&
hasControlCommand(msg.text ?? msg.caption ?? "");
if (isGroup && resolveGroupRequireMention(chatId) && botUsername) {
const canDetectMention =
Boolean(botUsername) || mentionRegexes.length > 0;
if (isGroup && resolveGroupRequireMention(chatId) && canDetectMention) {
if (!wasMentioned && !shouldBypassMention) {
logger.info(
{ chatId, reason: "no-mention" },
@@ -196,7 +204,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
ReplyToBody: replyTarget?.body,
ReplyToSender: replyTarget?.sender,
Timestamp: msg.date ? msg.date * 1000 : undefined,
WasMentioned: isGroup && botUsername ? wasMentioned : undefined,
WasMentioned: isGroup ? wasMentioned : undefined,
MediaPath: media?.path,
MediaType: media?.contentType,
MediaUrl: media?.path,