From 556b531a140330540a10299cd6c4907750a2c0b6 Mon Sep 17 00:00:00 2001 From: Krish Date: Mon, 16 Feb 2026 19:54:49 +0530 Subject: [PATCH] Fix Telegram poll action wiring --- src/agents/tools/message-tool.ts | 3 + src/agents/tools/telegram-actions.e2e.test.ts | 31 ++++++++++ src/agents/tools/telegram-actions.ts | 61 +++++++++++++++++++ src/channels/plugins/actions/actions.test.ts | 36 +++++++++++ src/channels/plugins/actions/telegram.ts | 37 +++++++++++ 5 files changed, 168 insertions(+) diff --git a/src/agents/tools/message-tool.ts b/src/agents/tools/message-tool.ts index 324c8aa48be..35b4e284da1 100644 --- a/src/agents/tools/message-tool.ts +++ b/src/agents/tools/message-tool.ts @@ -262,8 +262,11 @@ function buildPollSchema() { return { pollQuestion: Type.Optional(Type.String()), pollOption: Type.Optional(Type.Array(Type.String())), + pollDurationSeconds: Type.Optional(Type.Number()), pollDurationHours: Type.Optional(Type.Number()), pollMulti: Type.Optional(Type.Boolean()), + pollAnonymous: Type.Optional(Type.Boolean()), + pollPublic: Type.Optional(Type.Boolean()), }; } diff --git a/src/agents/tools/telegram-actions.e2e.test.ts b/src/agents/tools/telegram-actions.e2e.test.ts index 37688b6fd5a..3ffaa4730b2 100644 --- a/src/agents/tools/telegram-actions.e2e.test.ts +++ b/src/agents/tools/telegram-actions.e2e.test.ts @@ -11,6 +11,11 @@ const sendStickerTelegram = vi.fn(async () => ({ messageId: "456", chatId: "123", })); +const sendPollTelegram = vi.fn(async () => ({ + messageId: "999", + chatId: "123", + pollId: "poll-1", +})); const deleteMessageTelegram = vi.fn(async () => ({ ok: true })); const originalToken = process.env.TELEGRAM_BOT_TOKEN; @@ -18,6 +23,7 @@ vi.mock("../../telegram/send.js", () => ({ reactMessageTelegram: (...args: unknown[]) => reactMessageTelegram(...args), sendMessageTelegram: (...args: unknown[]) => sendMessageTelegram(...args), sendStickerTelegram: (...args: unknown[]) => sendStickerTelegram(...args), + sendPollTelegram: (...args: unknown[]) => sendPollTelegram(...args), deleteMessageTelegram: (...args: unknown[]) => deleteMessageTelegram(...args), })); @@ -49,6 +55,7 @@ describe("handleTelegramAction", () => { reactMessageTelegram.mockClear(); sendMessageTelegram.mockClear(); sendStickerTelegram.mockClear(); + sendPollTelegram.mockClear(); deleteMessageTelegram.mockClear(); process.env.TELEGRAM_BOT_TOKEN = "tok"; }); @@ -362,6 +369,30 @@ describe("handleTelegramAction", () => { ); }); + it("sends a poll", async () => { + const cfg = { + channels: { telegram: { botToken: "tok" } }, + } as OpenClawConfig; + await handleTelegramAction( + { + action: "poll", + to: "123", + question: "Ready?", + options: ["Yes", "No"], + }, + cfg, + ); + expect(sendPollTelegram).toHaveBeenCalledWith( + "123", + expect.objectContaining({ + question: "Ready?", + options: ["Yes", "No"], + maxSelections: 1, + }), + expect.objectContaining({ token: "tok" }), + ); + }); + it("respects deleteMessage gating", async () => { const cfg = { channels: { diff --git a/src/agents/tools/telegram-actions.ts b/src/agents/tools/telegram-actions.ts index 3f01392ad3c..a2628146469 100644 --- a/src/agents/tools/telegram-actions.ts +++ b/src/agents/tools/telegram-actions.ts @@ -12,6 +12,7 @@ import { editMessageTelegram, reactMessageTelegram, sendMessageTelegram, + sendPollTelegram, sendStickerTelegram, } from "../../telegram/send.js"; import { getCacheStats, searchStickers } from "../../telegram/sticker-cache.js"; @@ -213,6 +214,66 @@ export async function handleTelegramAction( }); } + if (action === "poll") { + if (!isActionEnabled("polls")) { + throw new Error("Telegram polls are disabled."); + } + const to = readStringParam(params, "to", { required: true }); + const question = readStringParam(params, "question", { required: true }); + const options = (params.options ?? params.answers) as unknown; + if (!Array.isArray(options)) { + throw new Error("options must be an array of strings"); + } + const pollOptions = options.filter((option): option is string => typeof option === "string"); + if (pollOptions.length !== options.length) { + throw new Error("options must be an array of strings"); + } + const durationSeconds = readNumberParam(params, "durationSeconds", { + integer: true, + }); + const durationHours = readNumberParam(params, "durationHours", { + integer: true, + }); + const replyToMessageId = readNumberParam(params, "replyToMessageId", { + integer: true, + }); + const messageThreadId = readNumberParam(params, "messageThreadId", { + integer: true, + }); + const maxSelections = + typeof params.allowMultiselect === "boolean" && params.allowMultiselect === true ? 2 : 1; + const token = resolveTelegramToken(cfg, { accountId }).token; + if (!token) { + throw new Error( + "Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.", + ); + } + const result = await sendPollTelegram( + to, + { + question, + options: pollOptions, + maxSelections, + durationSeconds: durationSeconds ?? undefined, + durationHours: durationHours ?? undefined, + }, + { + token, + accountId: accountId ?? undefined, + replyToMessageId: replyToMessageId ?? undefined, + messageThreadId: messageThreadId ?? undefined, + silent: typeof params.silent === "boolean" ? params.silent : undefined, + isAnonymous: typeof params.isAnonymous === "boolean" ? params.isAnonymous : undefined, + }, + ); + return jsonResult({ + ok: true, + messageId: result.messageId, + chatId: result.chatId, + pollId: result.pollId, + }); + } + if (action === "deleteMessage") { if (!isActionEnabled("deleteMessage")) { throw new Error("Telegram deleteMessage is disabled."); diff --git a/src/channels/plugins/actions/actions.test.ts b/src/channels/plugins/actions/actions.test.ts index 423c3c79a0b..2621c4e4a45 100644 --- a/src/channels/plugins/actions/actions.test.ts +++ b/src/channels/plugins/actions/actions.test.ts @@ -128,6 +128,42 @@ describe("discord message actions", () => { }); }); +describe("telegram message actions", () => { + it("lists poll action when telegram is configured", () => { + const cfg = { channels: { telegram: { botToken: "t0" } } } as OpenClawConfig; + const actions = telegramMessageActions.listActions?.({ cfg }) ?? []; + expect(actions).toContain("poll"); + }); + + it("routes poll with normalized params", async () => { + await telegramMessageActions.handleAction({ + action: "poll", + params: { + to: "123", + pollQuestion: "Ready?", + pollOption: ["Yes", "No"], + pollMulti: true, + pollDurationSeconds: 60, + }, + cfg: { channels: { telegram: { botToken: "tok" } } } as OpenClawConfig, + accountId: "ops", + }); + + expect(handleTelegramAction).toHaveBeenCalledWith( + expect.objectContaining({ + action: "poll", + to: "123", + question: "Ready?", + options: ["Yes", "No"], + allowMultiselect: true, + durationSeconds: 60, + accountId: "ops", + }), + expect.any(Object), + ); + }); +}); + describe("handleDiscordMessageAction", () => { it("forwards context accountId for send", async () => { await handleDiscordMessageAction({ diff --git a/src/channels/plugins/actions/telegram.ts b/src/channels/plugins/actions/telegram.ts index 5e29b53f226..a29510ecf62 100644 --- a/src/channels/plugins/actions/telegram.ts +++ b/src/channels/plugins/actions/telegram.ts @@ -58,6 +58,9 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { if (gate("deleteMessage")) { actions.add("delete"); } + if (gate("polls")) { + actions.add("poll"); + } if (gate("editMessage")) { actions.add("edit"); } @@ -116,6 +119,40 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { ); } + if (action === "poll") { + const to = readStringParam(params, "to", { required: true }); + const question = readStringParam(params, "pollQuestion", { required: true }); + const options = readStringArrayParam(params, "pollOption", { required: true }) ?? []; + const allowMultiselect = typeof params.pollMulti === "boolean" ? params.pollMulti : undefined; + const durationSeconds = readNumberParam(params, "pollDurationSeconds", { + integer: true, + }); + const durationHours = readNumberParam(params, "pollDurationHours", { + integer: true, + }); + const replyToMessageId = readNumberParam(params, "replyTo", { integer: true }); + const messageThreadId = readNumberParam(params, "threadId", { integer: true }); + const silent = typeof params.silent === "boolean" ? params.silent : undefined; + const isAnonymous = typeof params.isAnonymous === "boolean" ? params.isAnonymous : undefined; + return await handleTelegramAction( + { + action: "poll", + to, + question, + options, + allowMultiselect, + durationSeconds: durationSeconds ?? undefined, + durationHours: durationHours ?? undefined, + replyToMessageId: replyToMessageId ?? undefined, + messageThreadId: messageThreadId ?? undefined, + silent, + isAnonymous, + accountId: accountId ?? undefined, + }, + cfg, + ); + } + if (action === "delete") { const chatId = readStringOrNumberParam(params, "chatId") ??