From 4b86c9e5558dc65ec7fcf6e1bb16daedd6312dd5 Mon Sep 17 00:00:00 2001 From: 0xRain Date: Thu, 12 Feb 2026 14:28:47 +0800 Subject: [PATCH] fix(telegram): surface REACTION_INVALID as non-fatal warning (#14340) Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> --- src/agents/tools/telegram-actions.test.ts | 31 +++++++++++++++++++++++ src/agents/tools/telegram-actions.ts | 9 ++++++- src/telegram/send.ts | 12 +++++++-- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/agents/tools/telegram-actions.test.ts b/src/agents/tools/telegram-actions.test.ts index 397edf036f5..5718454e757 100644 --- a/src/agents/tools/telegram-actions.test.ts +++ b/src/agents/tools/telegram-actions.test.ts @@ -59,6 +59,37 @@ describe("handleTelegramAction", () => { ); }); + it("surfaces non-fatal reaction warnings", async () => { + reactMessageTelegram.mockResolvedValueOnce({ + ok: false, + warning: "Reaction unavailable: ✅", + }); + const cfg = { + channels: { telegram: { botToken: "tok", reactionLevel: "minimal" } }, + } as OpenClawConfig; + const result = await handleTelegramAction( + { + action: "react", + chatId: "123", + messageId: "456", + emoji: "✅", + }, + cfg, + ); + const textPayload = result.content.find((item) => item.type === "text"); + expect(textPayload?.type).toBe("text"); + const parsed = JSON.parse((textPayload as { type: "text"; text: string }).text) as { + ok: boolean; + warning?: string; + added?: string; + }; + expect(parsed).toMatchObject({ + ok: false, + warning: "Reaction unavailable: ✅", + added: "✅", + }); + }); + it("adds reactions when reactionLevel is extensive", async () => { const cfg = { channels: { telegram: { botToken: "tok", reactionLevel: "extensive" } }, diff --git a/src/agents/tools/telegram-actions.ts b/src/agents/tools/telegram-actions.ts index 56ebcdd56cb..091055f0278 100644 --- a/src/agents/tools/telegram-actions.ts +++ b/src/agents/tools/telegram-actions.ts @@ -109,11 +109,18 @@ export async function handleTelegramAction( "Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.", ); } - await reactMessageTelegram(chatId ?? "", messageId ?? 0, emoji ?? "", { + const reactionResult = await reactMessageTelegram(chatId ?? "", messageId ?? 0, emoji ?? "", { token, remove, accountId: accountId ?? undefined, }); + if (!reactionResult.ok) { + return jsonResult({ + ok: false, + warning: reactionResult.warning, + ...(remove || isEmpty ? { removed: true } : { added: emoji }), + }); + } if (!remove && !isEmpty) { return jsonResult({ ok: true, added: emoji }); } diff --git a/src/telegram/send.ts b/src/telegram/send.ts index 141780d431e..ead53ff90d1 100644 --- a/src/telegram/send.ts +++ b/src/telegram/send.ts @@ -596,7 +596,7 @@ export async function reactMessageTelegram( messageIdInput: string | number, emoji: string, opts: TelegramReactionOpts = {}, -): Promise<{ ok: true }> { +): Promise<{ ok: true } | { ok: false; warning: string }> { const cfg = loadConfig(); const account = resolveTelegramAccount({ cfg, @@ -633,7 +633,15 @@ export async function reactMessageTelegram( if (typeof api.setMessageReaction !== "function") { throw new Error("Telegram reactions are unavailable in this bot API."); } - await requestWithDiag(() => api.setMessageReaction(chatId, messageId, reactions), "reaction"); + try { + await requestWithDiag(() => api.setMessageReaction(chatId, messageId, reactions), "reaction"); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : String(err); + if (/REACTION_INVALID/i.test(msg)) { + return { ok: false as const, warning: `Reaction unavailable: ${trimmedEmoji}` }; + } + throw err; + } return { ok: true }; }