From e54a37a91ee07c7fb43c141902c04518cd460dd6 Mon Sep 17 00:00:00 2001 From: Neerav Makwana <261249544+neeravmakwana@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:08:58 -0400 Subject: [PATCH] fix(telegram): harden progress preview rendering --- .../telegram/src/bot-message-dispatch.test.ts | 6 +++++- extensions/telegram/src/bot-message-dispatch.ts | 16 +++++++++++++--- .../reply/dispatch-from-config.test.ts | 10 ++++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index e89f7bff96f..ee1697acf4d 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -7,6 +7,7 @@ import { createSequencedTestDraftStream, createTestDraftStream, } from "./draft-stream.test-helpers.js"; +import { renderTelegramHtmlText } from "./format.js"; type DispatchReplyWithBufferedBlockDispatcherArgs = Parameters< TelegramBotDeps["dispatchReplyWithBufferedBlockDispatcher"] @@ -524,6 +525,7 @@ describe("dispatchTelegramMessage draft streaming", () => { createTelegramDraftStream.mockReturnValue(draftStream); dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => { await replyOptions?.onToolStart?.({ name: "exec", phase: "start" }); + await replyOptions?.onItemEvent?.({ progressText: "read [label](tg://user?id=123)" }); return { queuedFinal: false }; }); @@ -533,7 +535,9 @@ describe("dispatchTelegramMessage draft streaming", () => { telegramCfg: { streaming: { preview: { toolProgress: true } } }, }); - expect(draftStream.update).toHaveBeenCalledWith("Working…\n• tool: exec"); + const lastPreviewText = draftStream.update.mock.calls.at(-1)?.[0]; + expect(lastPreviewText).toBe("Working…\n• `tool: exec`\n• `read [label](tg://user?id=123)`"); + expect(renderTelegramHtmlText(lastPreviewText ?? "")).not.toContain(" match[0].length), + ); + const fence = "`".repeat(maxBacktickRun + 1); + return `${fence}${text}${fence}`; +} + export const dispatchTelegramMessage = async ({ context, bot, @@ -404,9 +413,10 @@ export const dispatchTelegramMessage = async ({ return; } previewToolProgressLines = [...previewToolProgressLines, normalized].slice(-8); - const previewText = ["Working…", ...previewToolProgressLines.map((entry) => `• ${entry}`)].join( - "\n", - ); + const previewText = [ + "Working…", + ...previewToolProgressLines.map((entry) => `• ${formatProgressAsMarkdownCode(entry)}`), + ].join("\n"); answerLane.lastPartialText = previewText; answerLane.stream.update(previewText); }; diff --git a/src/auto-reply/reply/dispatch-from-config.test.ts b/src/auto-reply/reply/dispatch-from-config.test.ts index c643e261d2a..d62a34b5e49 100644 --- a/src/auto-reply/reply/dispatch-from-config.test.ts +++ b/src/auto-reply/reply/dispatch-from-config.test.ts @@ -1636,7 +1636,7 @@ describe("dispatchReplyFromConfig", () => { expect(dispatcher.sendFinalReply).toHaveBeenCalledWith({ text: "done" }); }); - it("delivers deterministic exec approval tool payloads for native commands", async () => { + it("delivers deterministic exec approval tool payloads for native commands with progress suppression", async () => { setNoAbort(); const cfg = emptyConfig; const dispatcher = createDispatcher(); @@ -1663,7 +1663,13 @@ describe("dispatchReplyFromConfig", () => { return { text: "NO_REPLY" } satisfies ReplyPayload; }; - await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver }); + await dispatchReplyFromConfig({ + ctx, + cfg, + dispatcher, + replyResolver, + replyOptions: { suppressDefaultToolProgressMessages: true }, + }); expect(dispatcher.sendToolResult).toHaveBeenCalledTimes(1); expect(firstToolResultPayload(dispatcher)).toEqual(