fix(telegram): send interactive fallback replies · openclaw/openclaw@b0b5983
vincentkoc
·
2026-05-04
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
|
63 | 63 | - Feishu: use the shared channel progress formatter for streaming-card tool status lines, including raw command/detail output and message-tool filtering. Thanks @vincentkoc. |
64 | 64 | - Mattermost: use the shared progress draft formatter for tool status previews, including raw command/detail output when `agents.defaults.toolProgressDetail: "raw"` is enabled. Thanks @vincentkoc. |
65 | 65 | - Mattermost: suppress standalone default tool-progress messages while draft previews are active, including when draft tool lines are disabled. Thanks @vincentkoc. |
| 66 | +- Telegram: deliver button-only interactive replies by sending the shared fallback button-label text with the inline keyboard instead of dropping the reply as empty. Thanks @vincentkoc. |
66 | 67 | - OpenAI Codex: honor `auth.order.openai-codex` when starting app-server clients without an explicit auth profile, so status/model probes and implicit startup use the configured Codex account instead of falling back to the default profile. Thanks @vincentkoc. |
67 | 68 | - OpenAI Codex: let SSRF-guarded provider requests inherit OpenClaw's undici IPv4/IPv6 fallback policy, so ChatGPT-backed Codex runs recover on IPv4-working hosts when DNS still returns unreachable IPv6 addresses. Fixes #76857. Thanks @jplavoiemtl and @SymbolStar. |
68 | 69 | - Gateway/systemd: preserve operator-added secrets in the Gateway env file across re-stage while clearing OpenClaw-managed keys (such as `OPENCLAW_GATEWAY_TOKEN`) so a fresh staging value is never shadowed by a stale env-file copy; operator secrets are also retained when the state-dir `.env` is empty. Fixes #76860. Thanks @hclsys. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -36,6 +36,7 @@ import {
|
36 | 36 | renderTelegramHtmlText, |
37 | 37 | wrapFileReferencesInHtml, |
38 | 38 | } from "../format.js"; |
| 39 | +import { resolveTelegramInteractiveTextFallback } from "../interactive-fallback.js"; |
39 | 40 | import { buildInlineKeyboard } from "../send.js"; |
40 | 41 | import { resolveTelegramVoiceSend } from "../voice.js"; |
41 | 42 | import { |
@@ -751,7 +752,17 @@ export async function deliverReplies(params: {
|
751 | 752 | ? [reply.mediaUrl] |
752 | 753 | : []; |
753 | 754 | const hasMedia = mediaList.length > 0; |
754 | | -if (!reply?.text && !hasMedia) { |
| 755 | +const resolvedReplyText = |
| 756 | +resolveTelegramInteractiveTextFallback({ |
| 757 | +text: reply?.text, |
| 758 | +interactive: reply?.interactive, |
| 759 | +}) ?? |
| 760 | +reply?.text ?? |
| 761 | +""; |
| 762 | +if (reply && resolvedReplyText !== (reply.text ?? "")) { |
| 763 | +reply = { ...reply, text: resolvedReplyText }; |
| 764 | +} |
| 765 | +if (!resolvedReplyText && !hasMedia) { |
755 | 766 | if (reply?.audioAsVoice) { |
756 | 767 | logVerbose("telegram reply has audioAsVoice without media/text; skipping"); |
757 | 768 | continue; |
@@ -760,7 +771,7 @@ export async function deliverReplies(params: {
|
760 | 771 | continue; |
761 | 772 | } |
762 | 773 | |
763 | | -const rawContent = reply.text || ""; |
| 774 | +const rawContent = resolvedReplyText; |
764 | 775 | const replyToId = |
765 | 776 | params.replyToMode === "off" ? undefined : resolveTelegramReplyId(reply.replyToId); |
766 | 777 | const replyQuote = resolveReplyQuoteForSend({ |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -257,6 +257,35 @@ describe("deliverReplies", () => {
|
257 | 257 | ); |
258 | 258 | }); |
259 | 259 | |
| 260 | +it("uses interactive button labels as fallback text for button-only replies", async () => { |
| 261 | +const runtime = createRuntime(false); |
| 262 | +const sendMessage = vi.fn().mockResolvedValue({ message_id: 3, chat: { id: "123" } }); |
| 263 | +const bot = createBot({ sendMessage }); |
| 264 | + |
| 265 | +await deliverWith({ |
| 266 | +replies: [ |
| 267 | +{ |
| 268 | +interactive: { |
| 269 | +blocks: [{ type: "buttons", buttons: [{ label: "Retry", value: "cmd:retry" }] }], |
| 270 | +}, |
| 271 | +}, |
| 272 | +], |
| 273 | + runtime, |
| 274 | + bot, |
| 275 | +}); |
| 276 | + |
| 277 | +expect(runtime.error).not.toHaveBeenCalled(); |
| 278 | +expect(sendMessage).toHaveBeenCalledWith( |
| 279 | +"123", |
| 280 | +expect.stringContaining("Retry"), |
| 281 | +expect.objectContaining({ |
| 282 | +reply_markup: { |
| 283 | +inline_keyboard: [[{ text: "Retry", callback_data: "cmd:retry" }]], |
| 284 | +}, |
| 285 | +}), |
| 286 | +); |
| 287 | +}); |
| 288 | + |
260 | 289 | it("reports message_sent success=false when hooks blank out a text-only reply", async () => { |
261 | 290 | messageHookRunner.hasHooks.mockImplementation( |
262 | 291 | (name: string) => name === "message_sending" || name === "message_sent", |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。