惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
V
V2EX
博客园 - 【当耐特】
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
量子位
MyScale Blog
MyScale Blog
Hugging Face - Blog
Hugging Face - Blog
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
月光博客
月光博客
I
InfoQ
WordPress大学
WordPress大学
Martin Fowler
Martin Fowler
T
The Blog of Author Tim Ferriss
爱范儿
爱范儿
小众软件
小众软件
罗磊的独立博客
Recent Announcements
Recent Announcements
Blog — PlanetScale
Blog — PlanetScale
J
Java Code Geeks
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(telegram): treat targeted bot commands as mentions (#...
luoyanglang · 2026-05-27 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -230,6 +230,37 @@ describe("resolveTelegramInboundBody", () => {

230230

expect(result).toBeNull();

231231

});

232232
233+

it("accepts targeted bot commands as explicit mentions in requireMention groups", async () => {

234+

const logger = { info: vi.fn() };

235+

const text = "/deploy@bot check status";

236+
237+

const result = await resolveTelegramBody({

238+

cfg: { channels: { telegram: {} } } as never,

239+

msg: {

240+

message_id: 8,

241+

date: 1_700_000_008,

242+

chat: { id: -1001234567890, type: "supergroup", title: "Test Group" },

243+

from: { id: 46, first_name: "Eve" },

244+

text,

245+

entities: [{ type: "bot_command", offset: 0, length: "/deploy@bot".length }],

246+

} as never,

247+

isGroup: true,

248+

chatId: -1001234567890,

249+

senderId: "46",

250+

senderUsername: "",

251+

groupConfig: { requireMention: true } as never,

252+

requireMention: true,

253+

logger,

254+

});

255+
256+

expect(logger.info).not.toHaveBeenCalledWith(

257+

{ chatId: -1001234567890, reason: "no-mention" },

258+

"skipping group message",

259+

);

260+

expect(result?.rawBody).toBe(text);

261+

expect(result?.effectiveWasMentioned).toBe(true);

262+

});

263+
233264

it("does not transcribe group audio for unauthorized senders", async () => {

234265

transcribeFirstAudioMock.mockReset();

235266

const logger = { info: vi.fn() };

Original file line numberDiff line numberDiff line change

@@ -443,6 +443,54 @@ describe("createTelegramBot channel_post media", () => {

443443

}

444444

});

445445
446+

it("treats targeted bot command captions as mentions before media download", async () => {

447+

loadConfig.mockReturnValue({

448+

channels: {

449+

telegram: {

450+

groupPolicy: "open",

451+

groups: { "*": { requireMention: true } },

452+

},

453+

},

454+

});

455+

const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => {

456+

throw new Error("MediaFetchError: ECONNRESET");

457+

});

458+
459+

try {

460+

createTelegramBot({ token: "tok" });

461+

const handler = getOnHandler("message") as (ctx: Record<string, unknown>) => Promise<void>;

462+

const caption = "/inspect@openclaw_bot";

463+
464+

await handler({

465+

message: {

466+

chat: { id: -100456, type: "supergroup", title: "Ops Chat" },

467+

message_id: 81184,

468+

date: 1736380800,

469+

caption,

470+

caption_entities: [{ type: "bot_command", offset: 0, length: caption.length }],

471+

photo: [{ file_id: "p1" }],

472+

from: { id: 55, is_bot: false, first_name: "u" },

473+

},

474+

me: { id: 999, username: "openclaw_bot" },

475+

getFile: async () => ({ file_path: "photos/p1.jpg" }),

476+

});

477+
478+

expect(sendMessageSpy).toHaveBeenCalledWith(

479+

-100456,

480+

"⚠️ Failed to download media. Please try again.",

481+

{

482+

reply_parameters: {

483+

message_id: 81184,

484+

allow_sending_without_reply: true,

485+

},

486+

},

487+

);

488+

expect(replySpy).not.toHaveBeenCalled();

489+

} finally {

490+

fetchSpy.mockRestore();

491+

}

492+

});

493+
446494

it("notifies requireMention group replies to the bot when media download fails", async () => {

447495

loadConfig.mockReturnValue({

448496

channels: {

Original file line numberDiff line numberDiff line change

@@ -139,18 +139,27 @@ function hasStandaloneTelegramMention(text: string, mention: string): boolean {

139139

return false;

140140

}

141141
142+

function isBotCommandAddressedToMention(command: string, mention: string): boolean {

143+

const normalized = normalizeLowercaseStringOrEmpty(command);

144+

if (!normalized.startsWith("/") || !normalized.endsWith(mention)) {

145+

return false;

146+

}

147+

const atIndex = normalized.lastIndexOf(mention);

148+

return atIndex > 1;

149+

}

150+
142151

export function hasBotMention(msg: Message, botUsername: string) {

143152

const { text, entities } = getTelegramTextParts(msg);

144153

const mention = normalizeLowercaseStringOrEmpty(`@${botUsername}`);

145154

if (hasStandaloneTelegramMention(normalizeLowercaseStringOrEmpty(text), mention)) {

146155

return true;

147156

}

148157

for (const ent of entities) {

149-

if (ent.type !== "mention") {

150-

continue;

151-

}

152158

const slice = text.slice(ent.offset, ent.offset + ent.length);

153-

if (normalizeLowercaseStringOrEmpty(slice) === mention) {

159+

if (ent.type === "mention" && normalizeLowercaseStringOrEmpty(slice) === mention) {

160+

return true;

161+

}

162+

if (ent.type === "bot_command" && isBotCommandAddressedToMention(slice, mention)) {

154163

return true;

155164

}

156165

}

Original file line numberDiff line numberDiff line change

@@ -754,6 +754,36 @@ describe("hasBotMention", () => {

754754

).toBe(true);

755755

});

756756
757+

it("matches bot command entities addressed to this bot", () => {

758+

const text = "/deploy@gaian check status";

759+
760+

expect(

761+

hasBotMention(

762+

{

763+

text,

764+

entities: [{ type: "bot_command", offset: 0, length: "/deploy@gaian".length }],

765+

chat: { id: 1, type: "supergroup" },

766+

} as any,

767+

"gaian",

768+

),

769+

).toBe(true);

770+

});

771+
772+

it("does not match bot command entities addressed to another bot", () => {

773+

const text = "/deploy@other_bot check status";

774+
775+

expect(

776+

hasBotMention(

777+

{

778+

text,

779+

entities: [{ type: "bot_command", offset: 0, length: "/deploy@other_bot".length }],

780+

chat: { id: 1, type: "supergroup" },

781+

} as any,

782+

"gaian",

783+

),

784+

).toBe(false);

785+

});

786+
757787

it("matches mention followed by punctuation", () => {

758788

expect(

759789

hasBotMention(