From 7bb9a7dcfc3194a953f0ca20f86f9caba8a22923 Mon Sep 17 00:00:00 2001 From: artale Date: Mon, 16 Feb 2026 12:20:51 +0100 Subject: [PATCH] fix(telegram): wire sendPollTelegram into channel action handler (#16977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Telegram channel adapter listed no 'poll' action, so agents could not create polls via the unified action interface. The underlying sendPollTelegram function was already implemented but unreachable. Changes: - telegram.ts: add 'poll' to listActions (enabled by default via gate), add handleAction branch that reads pollQuestion/pollOption params and delegates to handleTelegramAction with action 'sendPoll'. - telegram-actions.ts: add 'sendPoll' handler that validates question, options (≥2), and forwards to sendPollTelegram with threading, silent, and anonymous options. - actions.test.ts: add test verifying poll action routes correctly. Fixes #16977 --- src/agents/tools/telegram-actions.ts | 37 ++++++++++++++++++++ src/channels/plugins/actions/actions.test.ts | 25 +++++++++++++ src/channels/plugins/actions/telegram.ts | 28 +++++++++++++++ src/config/types.telegram.ts | 2 ++ 4 files changed, 92 insertions(+) diff --git a/src/agents/tools/telegram-actions.ts b/src/agents/tools/telegram-actions.ts index bef3522dc..97d2889d8 100644 --- a/src/agents/tools/telegram-actions.ts +++ b/src/agents/tools/telegram-actions.ts @@ -402,5 +402,42 @@ export async function handleTelegramAction( return jsonResult({ ok: true, ...stats }); } + if (action === "sendPoll") { + const to = readStringParam(params, "to", { required: true }); + const question = readStringParam(params, "question") ?? readStringParam(params, "pollQuestion"); + if (!question) { + throw new Error("sendPoll requires 'question'"); + } + const options = (params.options ?? params.pollOption) as string[] | undefined; + if (!options || options.length < 2) { + throw new Error("sendPoll requires at least 2 options"); + } + const maxSelections = + typeof params.maxSelections === "number" ? params.maxSelections : undefined; + const isAnonymous = typeof params.isAnonymous === "boolean" ? params.isAnonymous : undefined; + const silent = typeof params.silent === "boolean" ? params.silent : undefined; + const replyToMessageId = readNumberParam(params, "replyTo"); + const messageThreadId = readNumberParam(params, "threadId"); + const pollAccountId = readStringParam(params, "accountId"); + + const res = await sendPollTelegram( + to, + { question, options, maxSelections }, + { + accountId: pollAccountId?.trim() || undefined, + replyToMessageId, + messageThreadId, + isAnonymous, + silent, + }, + ); + return jsonResult({ + ok: true, + messageId: res.messageId, + chatId: res.chatId, + pollId: res.pollId, + }); + } + throw new Error(`Unsupported Telegram action: ${action}`); } diff --git a/src/channels/plugins/actions/actions.test.ts b/src/channels/plugins/actions/actions.test.ts index 2621c4e4a..09742cd22 100644 --- a/src/channels/plugins/actions/actions.test.ts +++ b/src/channels/plugins/actions/actions.test.ts @@ -489,6 +489,31 @@ describe("telegramMessageActions", () => { expect(String(call.messageId)).toBe("456"); expect(call.emoji).toBe("ok"); }); + + it("routes poll action to sendPoll with question and options", async () => { + const cfg = { channels: { telegram: { botToken: "tok" } } } as OpenClawConfig; + + await telegramMessageActions.handleAction({ + action: "poll", + params: { + to: "-100123", + pollQuestion: "Ready?", + pollOption: ["Yes", "No", "Maybe"], + }, + cfg, + accountId: undefined, + }); + + expect(handleTelegramAction).toHaveBeenCalledWith( + expect.objectContaining({ + action: "sendPoll", + to: "-100123", + question: "Ready?", + options: ["Yes", "No", "Maybe"], + }), + cfg, + ); + }); }); describe("signalMessageActions", () => { diff --git a/src/channels/plugins/actions/telegram.ts b/src/channels/plugins/actions/telegram.ts index a29510ecf..2e89c5601 100644 --- a/src/channels/plugins/actions/telegram.ts +++ b/src/channels/plugins/actions/telegram.ts @@ -52,6 +52,9 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { const gate = (key: keyof TelegramActionConfig, defaultValue = true) => gates.some((g) => g(key, defaultValue)); const actions = new Set(["send"]); + if (gate("poll")) { + actions.add("poll"); + } if (gate("reactions")) { actions.add("react"); } @@ -232,6 +235,31 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { ); } + if (action === "poll") { + const to = readStringParam(params, "to", { required: true }); + const question = + readStringParam(params, "pollQuestion") ?? + readStringParam(params, "question", { required: true }); + const options = + readStringArrayParam(params, "pollOption") ?? readStringArrayParam(params, "options"); + const threadId = readStringParam(params, "threadId"); + const replyTo = readStringParam(params, "replyTo"); + const silent = typeof params.silent === "boolean" ? params.silent : undefined; + return await handleTelegramAction( + { + action: "sendPoll", + to, + question, + options, + replyTo: replyTo != null ? Number(replyTo) : undefined, + threadId: threadId != null ? Number(threadId) : undefined, + silent, + accountId: accountId ?? undefined, + }, + cfg, + ); + } + throw new Error(`Action ${action} is not supported for provider ${providerId}.`); }, }; diff --git a/src/config/types.telegram.ts b/src/config/types.telegram.ts index 1682e4827..451c2c9b2 100644 --- a/src/config/types.telegram.ts +++ b/src/config/types.telegram.ts @@ -18,6 +18,8 @@ export type TelegramActionConfig = { polls?: boolean; deleteMessage?: boolean; editMessage?: boolean; + /** Enable poll actions (sendPoll). */ + poll?: boolean; /** Enable sticker actions (send and search). */ sticker?: boolean; };