From 68f6f3f0bd6133235c68d9686c6123f9e41e1788 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Sun, 11 Jan 2026 21:06:04 +0530 Subject: [PATCH] fix: normalize telegram command mentions --- src/auto-reply/command-detection.test.ts | 13 +++++++++++ src/auto-reply/command-detection.ts | 4 +++- src/auto-reply/commands-registry.test.ts | 23 +++++++++++++++++++ src/auto-reply/commands-registry.ts | 28 +++++++++++++++++++----- src/telegram/bot.ts | 6 +++-- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/auto-reply/command-detection.test.ts b/src/auto-reply/command-detection.test.ts index 7b55f82d9..f55df8101 100644 --- a/src/auto-reply/command-detection.test.ts +++ b/src/auto-reply/command-detection.test.ts @@ -71,4 +71,17 @@ describe("control command parsing", () => { expect(hasControlCommand("prefix /send on")).toBe(false); expect(hasControlCommand("/send on")).toBe(true); }); + + it("ignores telegram commands addressed to other bots", () => { + expect( + hasControlCommand("/help@otherbot", undefined, { + botUsername: "clawdbot", + }), + ).toBe(false); + expect( + hasControlCommand("/help@clawdbot", undefined, { + botUsername: "clawdbot", + }), + ).toBe(true); + }); }); diff --git a/src/auto-reply/command-detection.ts b/src/auto-reply/command-detection.ts index d96da65c4..3323903d9 100644 --- a/src/auto-reply/command-detection.ts +++ b/src/auto-reply/command-detection.ts @@ -1,5 +1,6 @@ import type { ClawdbotConfig } from "../config/types.js"; import { + type CommandNormalizeOptions, listChatCommands, listChatCommandsForConfig, normalizeCommandBody, @@ -8,11 +9,12 @@ import { export function hasControlCommand( text?: string, cfg?: ClawdbotConfig, + options?: CommandNormalizeOptions, ): boolean { if (!text) return false; const trimmed = text.trim(); if (!trimmed) return false; - const normalizedBody = normalizeCommandBody(trimmed); + const normalizedBody = normalizeCommandBody(trimmed, options); if (!normalizedBody) return false; const lowered = normalizedBody.toLowerCase(); const commands = cfg ? listChatCommandsForConfig(cfg) : listChatCommands(); diff --git a/src/auto-reply/commands-registry.test.ts b/src/auto-reply/commands-registry.test.ts index 8d9240cd3..c07d226e5 100644 --- a/src/auto-reply/commands-registry.test.ts +++ b/src/auto-reply/commands-registry.test.ts @@ -7,6 +7,7 @@ import { listChatCommandsForConfig, listNativeCommandSpecs, listNativeCommandSpecsForConfig, + normalizeCommandBody, shouldHandleTextCommands, } from "./commands-registry.js"; @@ -92,4 +93,26 @@ describe("commands registry", () => { }), ).toBe(true); }); + + it("normalizes telegram-style command mentions for the current bot", () => { + expect( + normalizeCommandBody("/help@clawdbot", { botUsername: "clawdbot" }), + ).toBe("/help"); + expect( + normalizeCommandBody("/help@clawdbot args", { + botUsername: "clawdbot", + }), + ).toBe("/help args"); + expect( + normalizeCommandBody("/help@clawdbot: args", { + botUsername: "clawdbot", + }), + ).toBe("/help args"); + }); + + it("keeps telegram-style command mentions for other bots", () => { + expect( + normalizeCommandBody("/help@otherbot", { botUsername: "clawdbot" }), + ).toBe("/help@otherbot"); + }); }); diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index 2d79a7a36..e0470d1fc 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -361,7 +361,14 @@ export function buildCommandText(commandName: string, args?: string): string { return trimmedArgs ? `/${commandName} ${trimmedArgs}` : `/${commandName}`; } -export function normalizeCommandBody(raw: string): string { +export type CommandNormalizeOptions = { + botUsername?: string; +}; + +export function normalizeCommandBody( + raw: string, + options?: CommandNormalizeOptions, +): string { const trimmed = raw.trim(); if (!trimmed.startsWith("/")) return trimmed; @@ -378,17 +385,26 @@ export function normalizeCommandBody(raw: string): string { })() : singleLine; - const lowered = normalized.toLowerCase(); + const normalizedBotUsername = options?.botUsername?.trim().toLowerCase(); + const mentionMatch = normalizedBotUsername + ? normalized.match(/^\/([^\s@]+)@([^\s]+)(.*)$/) + : null; + const commandBody = + mentionMatch && mentionMatch[2].toLowerCase() === normalizedBotUsername + ? `/${mentionMatch[1]}${mentionMatch[3] ?? ""}` + : normalized; + + const lowered = commandBody.toLowerCase(); const exact = TEXT_ALIAS_MAP.get(lowered); if (exact) return exact.canonical; - const tokenMatch = normalized.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/); - if (!tokenMatch) return normalized; + const tokenMatch = commandBody.match(/^\/([^\s]+)(?:\s+([\s\S]+))?$/); + if (!tokenMatch) return commandBody; const [, token, rest] = tokenMatch; const tokenKey = `/${token.toLowerCase()}`; const tokenSpec = TEXT_ALIAS_MAP.get(tokenKey); - if (!tokenSpec) return normalized; - if (rest && !tokenSpec.acceptsArgs) return normalized; + if (!tokenSpec) return commandBody; + if (rest && !tokenSpec.acceptsArgs) return commandBody; const normalizedRest = rest?.trimStart(); return normalizedRest ? `${tokenSpec.canonical} ${normalizedRest}` diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index 8e7f62f6a..ed78c284f 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -17,6 +17,7 @@ import { hasControlCommand } from "../auto-reply/command-detection.js"; import { buildCommandText, listNativeCommandSpecsForConfig, + normalizeCommandBody, } from "../auto-reply/commands-registry.js"; import { formatAgentEnvelope } from "../auto-reply/envelope.js"; import { @@ -557,7 +558,7 @@ export function createTelegramBot(opts: TelegramBotOptions) { !wasMentioned && !hasAnyMention && commandAuthorized && - hasControlCommand(msg.text ?? msg.caption ?? "", cfg); + hasControlCommand(msg.text ?? msg.caption ?? "", cfg, { botUsername }); const effectiveWasMentioned = wasMentioned || shouldBypassMention; const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0; if (isGroup && requireMention && canDetectMention) { @@ -682,10 +683,11 @@ export function createTelegramBot(opts: TelegramBotOptions) { ].filter((entry): entry is string => Boolean(entry)); const groupSystemPrompt = systemPromptParts.length > 0 ? systemPromptParts.join("\n\n") : undefined; + const commandBody = normalizeCommandBody(rawBody, { botUsername }); const ctxPayload = { Body: combinedBody, RawBody: rawBody, - CommandBody: rawBody, + CommandBody: commandBody, From: isGroup ? buildTelegramGroupFrom(chatId, messageThreadId) : `telegram:${chatId}`,