From ede944371f1e161418300559f5deb68114d958cf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 2 Mar 2026 03:49:16 +0000 Subject: [PATCH] fix(telegram): land #31067 first-chunk voice-fallback reply refs (@xdanger) Landed from contributor PR #31067 by @xdanger. Co-authored-by: Kros Dai --- CHANGELOG.md | 2 ++ src/telegram/bot/delivery.test.ts | 17 ++++++++++++++++- src/telegram/bot/delivery.ts | 5 +++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b3114d11d..51a1764ccaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,8 +114,10 @@ Docs: https://docs.openclaw.ai - Signal/Sync message null-handling: treat `syncMessage` presence (including `null`) as sync envelope traffic so replayed sentTranscript payloads cannot bypass loop guards after daemon restart. Landed from contributor PR #31138 by @Sid-Qin. Thanks @Sid-Qin. - Inbound metadata/Multi-account routing: include `account_id` in trusted inbound metadata so multi-account channel sessions can reliably disambiguate the receiving account in prompt context. Landed from contributor PR #30984 by @Stxle2. Thanks @Stxle2. - Web tools/RFC2544 fake-IP compatibility: allow RFC2544 benchmark range (`198.18.0.0/15`) for trusted web-tool fetch endpoints so proxy fake-IP networking modes do not trigger false SSRF blocks. Landed from contributor PR #31176 by @sunkinux. Thanks @sunkinux. +- Telegram/Voice fallback reply chunking: apply reply reference, quote text, and inline buttons only to the first fallback text chunk when voice delivery is blocked, preventing over-quoted multi-chunk replies. Landed from contributor PR #31067 by @xdanger. Thanks @xdanger. - Feishu/System preview prompt leakage: stop enqueuing inbound Feishu message previews as system events so user preview text is not injected into later turns as trusted `System:` context. Landed from contributor PR #31209 by @stakeswky. Thanks @stakeswky. - Feishu/Multi-account + reply reliability: add `channels.feishu.defaultAccount` outbound routing support with schema validation, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as `msg_type: "file"`, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #29610, #30432, #30331, and #29501. Thanks @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff. + ## Unreleased ### Changes diff --git a/src/telegram/bot/delivery.test.ts b/src/telegram/bot/delivery.test.ts index 87210c26390..9fe98be8f39 100644 --- a/src/telegram/bot/delivery.test.ts +++ b/src/telegram/bot/delivery.test.ts @@ -459,20 +459,35 @@ describe("deliverReplies", () => { text: "chunk-one\n\nchunk-two", replyToId: "77", audioAsVoice: true, + channelData: { + telegram: { + buttons: [[{ text: "Ack", callback_data: "ack" }]], + }, + }, }, ], runtime, bot, replyToMode: "first", + replyQuoteText: "quoted context", textLimit: 12, }); expect(sendVoice).toHaveBeenCalledTimes(1); expect(sendMessage.mock.calls.length).toBeGreaterThanOrEqual(2); expect(sendMessage.mock.calls[0][2]).toEqual( + expect.objectContaining({ + reply_to_message_id: 77, + reply_markup: { + inline_keyboard: [[{ text: "Ack", callback_data: "ack" }]], + }, + }), + ); + expect(sendMessage.mock.calls[1][2]).not.toEqual( expect.objectContaining({ reply_to_message_id: 77 }), ); - expect(sendMessage.mock.calls[1][2]).not.toHaveProperty("reply_to_message_id"); + expect(sendMessage.mock.calls[1][2]).not.toHaveProperty("reply_parameters"); + expect(sendMessage.mock.calls[1][2]).not.toHaveProperty("reply_markup"); }); it("rethrows non-VOICE_MESSAGES_FORBIDDEN errors from sendVoice", async () => { diff --git a/src/telegram/bot/delivery.ts b/src/telegram/bot/delivery.ts index eb47956ed0b..cf5f183ba19 100644 --- a/src/telegram/bot/delivery.ts +++ b/src/telegram/bot/delivery.ts @@ -682,15 +682,16 @@ async function sendTelegramVoiceFallbackText(opts: { let appliedReplyTo = false; for (let i = 0; i < chunks.length; i += 1) { const chunk = chunks[i]; + // Only apply reply reference, quote text, and buttons to the first chunk. const replyToForChunk = !appliedReplyTo ? opts.replyToId : undefined; await sendTelegramText(opts.bot, opts.chatId, chunk.html, opts.runtime, { replyToMessageId: replyToForChunk, - replyQuoteText: opts.replyQuoteText, + replyQuoteText: !appliedReplyTo ? opts.replyQuoteText : undefined, thread: opts.thread, textMode: "html", plainText: chunk.text, linkPreview: opts.linkPreview, - replyMarkup: i === 0 ? opts.replyMarkup : undefined, + replyMarkup: !appliedReplyTo ? opts.replyMarkup : undefined, }); if (replyToForChunk) { appliedReplyTo = true;