fix(telegram): harden progress preview rendering

This commit is contained in:
Neerav Makwana
2026-04-24 22:08:58 -04:00
committed by Peter Steinberger
parent c4a8b80dfa
commit e54a37a91e
3 changed files with 26 additions and 6 deletions

View File

@@ -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("<a ");
expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledWith(
expect.objectContaining({
replyOptions: expect.objectContaining({

View File

@@ -207,6 +207,15 @@ function resolveTelegramReasoningLevel(params: {
return "off";
}
function formatProgressAsMarkdownCode(text: string): string {
const maxBacktickRun = Math.max(
0,
...Array.from(text.matchAll(/`+/g), (match) => 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);
};

View File

@@ -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(