fix(telegram): make reaction handling soft-fail and message-id resilient (#20236)

* Telegram: soft-fail reactions and fallback to inbound message id

* Telegram: soft-fail missing reaction message id

* Update CHANGELOG.md

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
LI SHANXIN
2026-02-23 23:25:14 +08:00
committed by GitHub
parent ea47ab29bd
commit c1b75ab8e2
17 changed files with 317 additions and 69 deletions

View File

@@ -102,6 +102,46 @@ describe("handleTelegramAction", () => {
await expectReactionAdded("extensive");
});
it("accepts snake_case message_id for reactions", async () => {
const cfg = {
channels: { telegram: { botToken: "tok", reactionLevel: "minimal" } },
} as OpenClawConfig;
await handleTelegramAction(
{
action: "react",
chatId: "123",
message_id: "456",
emoji: "✅",
},
cfg,
);
expect(reactMessageTelegram).toHaveBeenCalledWith(
"123",
456,
"✅",
expect.objectContaining({ token: "tok", remove: false }),
);
});
it("soft-fails when messageId is missing", async () => {
const cfg = {
channels: { telegram: { botToken: "tok", reactionLevel: "minimal" } },
} as OpenClawConfig;
const result = await handleTelegramAction(
{
action: "react",
chatId: "123",
emoji: "✅",
},
cfg,
);
expect(result.details).toMatchObject({
ok: false,
reason: "missing_message_id",
});
expect(reactMessageTelegram).not.toHaveBeenCalled();
});
it("removes reactions on empty emoji", async () => {
const cfg = {
channels: { telegram: { botToken: "tok", reactionLevel: "minimal" } },
@@ -177,18 +217,10 @@ describe("handleTelegramAction", () => {
);
});
it.each([
{
level: "off" as const,
expectedMessage: /Telegram agent reactions disabled.*reactionLevel="off"/,
},
{
level: "ack" as const,
expectedMessage: /Telegram agent reactions disabled.*reactionLevel="ack"/,
},
])("blocks reactions when reactionLevel is $level", async ({ level, expectedMessage }) => {
await expect(
handleTelegramAction(
it.each(["off", "ack"] as const)(
"soft-fails reactions when reactionLevel is %s",
async (level) => {
const result = await handleTelegramAction(
{
action: "react",
chatId: "123",
@@ -196,11 +228,15 @@ describe("handleTelegramAction", () => {
emoji: "✅",
},
reactionConfig(level),
),
).rejects.toThrow(expectedMessage);
});
);
expect(result.details).toMatchObject({
ok: false,
reason: "disabled",
});
},
);
it("also respects legacy actions.reactions gating", async () => {
it("soft-fails when reactions are disabled via actions.reactions", async () => {
const cfg = {
channels: {
telegram: {
@@ -210,17 +246,19 @@ describe("handleTelegramAction", () => {
},
},
} as OpenClawConfig;
await expect(
handleTelegramAction(
{
action: "react",
chatId: "123",
messageId: "456",
emoji: "✅",
},
cfg,
),
).rejects.toThrow(/Telegram reactions are disabled via actions.reactions/);
const result = await handleTelegramAction(
{
action: "react",
chatId: "123",
messageId: "456",
emoji: "",
},
cfg,
);
expect(result.details).toMatchObject({
ok: false,
reason: "disabled",
});
});
it("sends a text message", async () => {
@@ -634,18 +672,20 @@ describe("handleTelegramAction per-account gating", () => {
},
} as OpenClawConfig;
await expect(
handleTelegramAction(
{
action: "react",
chatId: "123",
messageId: 1,
emoji: "👀",
accountId: "media",
},
cfg,
),
).rejects.toThrow(/reactions are disabled via actions.reactions/i);
const result = await handleTelegramAction(
{
action: "react",
chatId: "123",
messageId: 1,
emoji: "👀",
accountId: "media",
},
cfg,
);
expect(result.details).toMatchObject({
ok: false,
reason: "disabled",
});
});
it("allows account to explicitly re-enable top-level disabled reaction gate", async () => {