Skip to content

Commit b0b5983

Browse files
committed
fix(telegram): send interactive fallback replies
1 parent be438cf commit b0b5983

3 files changed

Lines changed: 43 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Docs: https://docs.openclaw.ai
6363
- Feishu: use the shared channel progress formatter for streaming-card tool status lines, including raw command/detail output and message-tool filtering. Thanks @vincentkoc.
6464
- 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.
6565
- 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.
6667
- 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.
6768
- 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.
6869
- 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.

extensions/telegram/src/bot/delivery.replies.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
renderTelegramHtmlText,
3737
wrapFileReferencesInHtml,
3838
} from "../format.js";
39+
import { resolveTelegramInteractiveTextFallback } from "../interactive-fallback.js";
3940
import { buildInlineKeyboard } from "../send.js";
4041
import { resolveTelegramVoiceSend } from "../voice.js";
4142
import {
@@ -751,7 +752,17 @@ export async function deliverReplies(params: {
751752
? [reply.mediaUrl]
752753
: [];
753754
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) {
755766
if (reply?.audioAsVoice) {
756767
logVerbose("telegram reply has audioAsVoice without media/text; skipping");
757768
continue;
@@ -760,7 +771,7 @@ export async function deliverReplies(params: {
760771
continue;
761772
}
762773

763-
const rawContent = reply.text || "";
774+
const rawContent = resolvedReplyText;
764775
const replyToId =
765776
params.replyToMode === "off" ? undefined : resolveTelegramReplyId(reply.replyToId);
766777
const replyQuote = resolveReplyQuoteForSend({

extensions/telegram/src/bot/delivery.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,35 @@ describe("deliverReplies", () => {
257257
);
258258
});
259259

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+
260289
it("reports message_sent success=false when hooks blank out a text-only reply", async () => {
261290
messageHookRunner.hasHooks.mockImplementation(
262291
(name: string) => name === "message_sending" || name === "message_sent",

0 commit comments

Comments
 (0)