From 01a22d4ec93eb0bfa8b59722cb54e872d888fa3e Mon Sep 17 00:00:00 2001 From: Kelaw - Keshav's Agent Date: Sun, 3 May 2026 01:27:41 +0530 Subject: [PATCH] fix(telegram): render interactive reply buttons --- .../telegram/src/bot/delivery.replies.ts | 9 +++- extensions/telegram/src/bot/delivery.test.ts | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/extensions/telegram/src/bot/delivery.replies.ts b/extensions/telegram/src/bot/delivery.replies.ts index f5f9ca074ea..4c09b20b88c 100644 --- a/extensions/telegram/src/bot/delivery.replies.ts +++ b/extensions/telegram/src/bot/delivery.replies.ts @@ -28,7 +28,7 @@ import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env"; import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env"; import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime"; import { loadWebMedia } from "openclaw/plugin-sdk/web-media"; -import type { TelegramInlineButtons } from "../button-types.js"; +import { resolveTelegramInlineButtons, type TelegramInlineButtons } from "../button-types.js"; import { splitTelegramCaption } from "../caption.js"; import { markdownToTelegramChunks, @@ -803,7 +803,12 @@ export async function deliverReplies(params: { try { const deliveredCountBeforeReply = progress.deliveredCount; const telegramData = reply.channelData?.telegram as TelegramReplyChannelData | undefined; - const replyMarkup = buildInlineKeyboard(telegramData?.buttons); + const replyMarkup = buildInlineKeyboard( + resolveTelegramInlineButtons({ + buttons: telegramData?.buttons, + interactive: reply.interactive, + }), + ); let firstDeliveredMessageId: number | undefined; if (mediaList.length === 0) { firstDeliveredMessageId = await deliverTextReply({ diff --git a/extensions/telegram/src/bot/delivery.test.ts b/extensions/telegram/src/bot/delivery.test.ts index b24ad47788e..28965d458c0 100644 --- a/extensions/telegram/src/bot/delivery.test.ts +++ b/extensions/telegram/src/bot/delivery.test.ts @@ -213,6 +213,50 @@ describe("deliverReplies", () => { expect(sendMessage.mock.calls[0]?.[1]).toBe("hello"); }); + it("renders shared interactive reply buttons as Telegram inline buttons", async () => { + const runtime = createRuntime(false); + const sendMessage = vi.fn().mockResolvedValue({ message_id: 2, chat: { id: "123" } }); + const bot = createBot({ sendMessage }); + + await deliverWith({ + replies: [ + { + text: "Plugin bind approval required", + interactive: { + blocks: [ + { + type: "buttons", + buttons: [ + { label: "Allow once", value: "pluginbind:req:o", style: "success" }, + { label: "Always allow", value: "pluginbind:req:a", style: "primary" }, + { label: "Deny", value: "pluginbind:req:d", style: "danger" }, + ], + }, + ], + }, + }, + ], + runtime, + bot, + }); + + expect(sendMessage).toHaveBeenCalledWith( + "123", + "Plugin bind approval required", + expect.objectContaining({ + reply_markup: { + inline_keyboard: [ + [ + { text: "Allow once", callback_data: "pluginbind:req:o", style: "success" }, + { text: "Always allow", callback_data: "pluginbind:req:a", style: "primary" }, + { text: "Deny", callback_data: "pluginbind:req:d", style: "danger" }, + ], + ], + }, + }), + ); + }); + it("reports message_sent success=false when hooks blank out a text-only reply", async () => { messageHookRunner.hasHooks.mockImplementation( (name: string) => name === "message_sending" || name === "message_sent",