From a3c36a09312934610f623ce42de7567230e2d898 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 4 May 2026 03:50:11 +0100 Subject: [PATCH] fix: compact progress draft lines --- CHANGELOG.md | 1 + docs/.generated/config-baseline.sha256 | 4 +- .../.generated/plugin-sdk-api-baseline.sha256 | 4 +- docs/concepts/progress-drafts.md | 6 +++ src/plugin-sdk/channel-streaming.test.ts | 10 ++++ src/plugin-sdk/channel-streaming.ts | 49 ++++++++++++++++++- 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc7671db84e..3b11221d221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai ### Changes - Channels/streaming: add unified `streaming.mode: "progress"` drafts with auto single-word status labels and shared progress configuration across Discord, Telegram, Matrix, Slack, and Microsoft Teams. +- Channels/streaming: cap progress-draft tool lines by default so edited progress boxes avoid jumpy reflow from long wrapped lines. - Agents/verbose: use compact explain-mode tool summaries for `/verbose` and progress drafts by default, with `agents.defaults.toolProgressDetail: "raw"` and per-agent overrides for debugging raw command/detail output. - Agents/commands: add `/steer ` for queue-independent steering of the active current-session run without starting a new turn when the session is idle. (#76934) - Tools/BTW: add `/side` as a text and native slash-command alias for `/btw` side questions. diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index ab535ee92a4..7f5a359b522 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -3e7cbffbe3849b5201716f359dde9089d61d618c1a4206255c20887a855d85a9 config-baseline.json +ac95b4ab62408454636ce559e6d023df3c29b8b936b3aa4dde37779d29a5a099 config-baseline.json 31ec333df9f8b92c7656ac7107cecd5860dd02e08f7e18c7c674dc47a8811baa config-baseline.core.json 655d1309b70505e73198df20c5088784290b33098efd42027d3c09beeb3704a7 config-baseline.channel.json -055fae0d0067a751dc10125af7421da45633f73519c94c982d02b0c4eb2bdf67 config-baseline.plugin.json +9458dc89aa13dd07d83f69d943535099a96e8278eb7ac8ae5cf2f713631592f7 config-baseline.plugin.json diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index de6541ac2e6..0cb30730876 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -0dd4f5abaf72f0d6b3fe5777cbf16c7a8c8052eece17436dc0ac2809b0ea27de plugin-sdk-api-baseline.json -2c2170cf2f1193f7dbecdef3ccd1b601992407e3d99863d1aa13cb1817c238fd plugin-sdk-api-baseline.jsonl +701356478634a8f3e71f941ed21a00e0456d947d287edcafb56231013b27a057 plugin-sdk-api-baseline.json +ed17426dd5e9db4b83db77162e7490eee3c0439170c1a9d1e84c01d7027d580c plugin-sdk-api-baseline.jsonl diff --git a/docs/concepts/progress-drafts.md b/docs/concepts/progress-drafts.md index 7689bf69959..1df12e76bc4 100644 --- a/docs/concepts/progress-drafts.md +++ b/docs/concepts/progress-drafts.md @@ -217,6 +217,12 @@ Limit how many lines stay visible: } ``` +Progress lines are compacted automatically to reduce chat-bubble reflow while the draft is edited. + +OpenClaw truncates long progress lines by default so repeated draft edits do not +wrap differently on every update. The prefix stays readable, and long details +such as paths or raw commands are shortened with an ellipsis. + Keep the single progress draft but hide tool and task lines: ```json5 diff --git a/src/plugin-sdk/channel-streaming.test.ts b/src/plugin-sdk/channel-streaming.test.ts index 91f31ffebe5..c3b0389fd46 100644 --- a/src/plugin-sdk/channel-streaming.test.ts +++ b/src/plugin-sdk/channel-streaming.test.ts @@ -186,6 +186,16 @@ describe("channel-streaming", () => { ).toBe("Shelling\n🛠️ Exec\n• plain update"); }); + it("bounds progress draft line length to reduce edit reflow", () => { + expect( + formatChannelProgressDraftText({ + entry: { streaming: { progress: { label: "Shelling" } } }, + lines: ["x".repeat(80)], + formatLine: (line) => `\`${line}\``, + }), + ).toBe(`Shelling\n• \`${"x".repeat(71)}…\``); + }); + 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 bfcd51cba52..880b5f7a779 100644 --- a/src/plugin-sdk/channel-streaming.ts +++ b/src/plugin-sdk/channel-streaming.ts @@ -110,6 +110,7 @@ export const DEFAULT_PROGRESS_DRAFT_LABELS = [ ] as const; export const DEFAULT_PROGRESS_DRAFT_INITIAL_DELAY_MS = 5_000; +const DEFAULT_PROGRESS_DRAFT_MAX_LINE_CHARS = 72; const NON_WORK_PROGRESS_TOOL_NAMES = new Set([ "message", @@ -537,6 +538,52 @@ export function resolveChannelProgressDraftMaxLines( return configured && configured > 0 ? configured : defaultValue; } +function sliceCodePoints(value: string, start: number, end?: number): string { + return Array.from(value).slice(start, end).join(""); +} + +function compactProgressLineDetail(detail: string, maxChars: number): string { + const chars = Array.from(detail); + if (chars.length <= maxChars) { + return detail; + } + if (maxChars <= 1) { + return "…"; + } + const keepStart = Math.max(1, Math.ceil((maxChars - 1) * 0.45)); + const keepEnd = Math.max(1, maxChars - keepStart - 1); + const rawStart = chars.slice(0, keepStart).join("").trimEnd(); + const start = + rawStart.length > 8 && /\s+\S+$/.test(rawStart) ? rawStart.replace(/\s+\S+$/, "") : rawStart; + return `${start}…${chars.slice(-keepEnd).join("").trimStart()}`; +} + +function compactChannelProgressDraftLine(line: string, maxChars: number): string { + const normalized = line.replace(/\s+/g, " ").trim(); + if (!normalized) { + return ""; + } + const chars = Array.from(normalized); + if (chars.length <= maxChars) { + return normalized; + } + if (maxChars <= 1) { + return "…"; + } + + const splitIndex = normalized.indexOf(": "); + if (splitIndex > 0) { + const prefix = normalized.slice(0, splitIndex + 2); + const prefixChars = Array.from(prefix).length; + const detailLimit = maxChars - prefixChars; + if (detailLimit >= 8) { + return `${prefix}${compactProgressLineDetail(normalized.slice(splitIndex + 2), detailLimit)}`; + } + } + + return `${sliceCodePoints(normalized, 0, maxChars - 1).trimEnd()}…`; +} + export function formatChannelProgressDraftText(params: { entry?: StreamingCompatEntry | null; lines: string[]; @@ -554,7 +601,7 @@ export function formatChannelProgressDraftText(params: { const formatLine = params.formatLine ?? ((line: string) => line); const bullet = params.bullet ?? "•"; const lines = params.lines - .map((line) => line.replace(/\s+/g, " ").trim()) + .map((line) => compactChannelProgressDraftLine(line, DEFAULT_PROGRESS_DRAFT_MAX_LINE_CHARS)) .filter((line) => line.length > 0) .slice(-maxLines) .map((line) =>