From 8846fe09981167b50ed012bb264cb1200e343e25 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 19:54:43 -0700 Subject: [PATCH] fix(channels): balance compact progress markdown --- CHANGELOG.md | 1 + src/plugin-sdk/channel-streaming.test.ts | 22 ++++++++++++++++++++++ src/plugin-sdk/channel-streaming.ts | 13 +++++++++++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af6432bff4b..cbd7a95c970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ Docs: https://docs.openclaw.ai - Agents/messaging: deliver distinct final commentary after same-target `message` tool sends while still deduping text/media already sent by the tool, so short closing remarks are no longer silently dropped. Fixes #76915. Thanks @hclsys. - Agents/messaging: preserve string thread IDs when matching message-tool reply dedupe routes, avoiding precision loss on numeric-looking topic IDs before channel plugin comparison. Thanks @vincentkoc. - Channels/streaming: honor `agents.defaults.toolProgressDetail: "raw"` in Slack, Discord, Telegram, Matrix, and Microsoft Teams progress drafts, so tool-start lines include raw command/detail output when debugging. Thanks @vincentkoc. +- Channels/streaming: strip unmatched inline-code backticks from compacted raw progress draft lines, avoiding stray markdown markers after long command details are shortened. Thanks @vincentkoc. - Discord/Slack/Mattermost: align draft preview tool-progress config help with the runtime behavior that hides interim tool updates when `streaming.preview.toolProgress` is false. Thanks @vincentkoc. - Feishu: use the shared channel progress formatter for streaming-card tool status lines, including raw command/detail output and message-tool filtering. Thanks @vincentkoc. - 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. diff --git a/src/plugin-sdk/channel-streaming.test.ts b/src/plugin-sdk/channel-streaming.test.ts index c3b0389fd46..f66201f964e 100644 --- a/src/plugin-sdk/channel-streaming.test.ts +++ b/src/plugin-sdk/channel-streaming.test.ts @@ -196,6 +196,28 @@ describe("channel-streaming", () => { ).toBe(`Shelling\n• \`${"x".repeat(71)}…\``); }); + it("keeps compacted raw progress lines from leaking unmatched markdown backticks", () => { + const line = formatChannelProgressDraftLine( + { + event: "tool", + name: "exec", + args: { + command: + "node scripts/check-something-with-a-very-long-path /tmp/openclaw/some/really/deep/path/that/keeps/going/and/going/index.ts --flag value", + }, + }, + { detailMode: "raw" }, + ); + + const text = formatChannelProgressDraftText({ + entry: { streaming: { progress: { label: "Shelling" } } }, + lines: [line ?? ""], + }); + + expect(text).toBe("Shelling\n🛠️ Exec: run node script…that/keeps/going/and/going/index…"); + expect(text.match(/`/g) ?? []).toHaveLength(0); + }); + it("formats progress draft lines with shared tool display labels", () => { expect( formatChannelProgressDraftLine({ diff --git a/src/plugin-sdk/channel-streaming.ts b/src/plugin-sdk/channel-streaming.ts index 880b5f7a779..5a38b6e439f 100644 --- a/src/plugin-sdk/channel-streaming.ts +++ b/src/plugin-sdk/channel-streaming.ts @@ -558,6 +558,11 @@ function compactProgressLineDetail(detail: string, maxChars: number): string { return `${start}…${chars.slice(-keepEnd).join("").trimStart()}`; } +function removeUnbalancedInlineBackticks(value: string): string { + const backtickCount = Array.from(value).filter((char) => char === "`").length; + return backtickCount % 2 === 1 ? value.replaceAll("`", "") : value; +} + function compactChannelProgressDraftLine(line: string, maxChars: number): string { const normalized = line.replace(/\s+/g, " ").trim(); if (!normalized) { @@ -577,11 +582,15 @@ function compactChannelProgressDraftLine(line: string, maxChars: number): string const prefixChars = Array.from(prefix).length; const detailLimit = maxChars - prefixChars; if (detailLimit >= 8) { - return `${prefix}${compactProgressLineDetail(normalized.slice(splitIndex + 2), detailLimit)}`; + return removeUnbalancedInlineBackticks( + `${prefix}${compactProgressLineDetail(normalized.slice(splitIndex + 2), detailLimit)}`, + ); } } - return `${sliceCodePoints(normalized, 0, maxChars - 1).trimEnd()}…`; + return removeUnbalancedInlineBackticks( + `${sliceCodePoints(normalized, 0, maxChars - 1).trimEnd()}…`, + ); } export function formatChannelProgressDraftText(params: {