revert: restore progress draft behavior

This commit is contained in:
Shakker
2026-05-08 11:32:51 +01:00
parent ee495603d1
commit baffa57c00
8 changed files with 33 additions and 47 deletions

View File

@@ -12,7 +12,7 @@ Docs: https://docs.openclaw.ai
- Control UI: read the Quick Settings exec policy badge from `tools.exec.security` instead of the non-schema `agents.defaults.exec.security` path, so configured `full`/`deny` values render accurately. Fixes #78311. Thanks @FriedBack.
- Control UI/usage: add transcript-backed historical lineage rollups for rotated logical sessions, with current-instance vs historical-lineage scope controls and long-range presets so usage history stays visible after restarts and updates. Fixes #50701. Thanks @dev-gideon-llc and @BunsDev.
- Agents/failover: harden state-aware lane suspension by persisting quota resume transitions, restoring configured lane concurrency, preserving non-quota failure reasons, and exporting model failover events through diagnostics OTLP. Thanks @BunsDev.
- Channels/streaming: render structured tool rows as compact emoji/title/details, show web-search queries from provider-native argument shapes, and skip empty Discord apply-patch starts until a patch summary exists. (#79146)
- Channels/streaming: make progress draft labels scroll away with other progress lines, render structured tool rows as compact emoji/title/details, show web-search queries from provider-native argument shapes, and skip empty Discord apply-patch starts until a patch summary exists. (#79146)
- Workspace/oc-path: add the `oc://` addressing substrate (`src/oc-path/`) — a universal, kind-dispatched path scheme for addressing leaves and nodes inside markdown, jsonc, jsonl, and yaml workspace files, with `parseOcPath`/`formatOcPath`, per-kind `parseXxx`/`emitXxx`, universal `resolveOcPath`/`setOcPath`/`findOcPaths` verbs, the `__OPENCLAW_REDACTED__` sentinel emit guard, and the new `openclaw path resolve|find|set|validate|emit` CLI for shell-level inspection and surgical edits. Implements #78051. (#78678) Thanks @giodl73-repo.
- Runtime/performance: avoid full-array sorting while auto-selecting providers, resolving supported thinking levels, picking node last-seen timestamps, and extracting Codex usage-limit messages. Thanks @shakkernerd.
- Plugins/doctor: avoid full-array sorting while selecting ClawHub search/archive results and bounded dreaming doctor entries. Thanks @shakkernerd.
@@ -185,7 +185,6 @@ Docs: https://docs.openclaw.ai
### Fixes
- Agents/compaction: keep the recent tail after manual `/compact` when Pi returns an empty or no-op compaction summary, preventing blank checkpoints from replacing the live context.
- Channels/streaming: keep progress draft labels visible above the last `streaming.progress.maxLines` progress rows instead of counting the label against the rolling line limit. Thanks @shakkernerd.
- fix(discord): gate user allowlist name resolution [AI]. (#79002) Thanks @pgondhi987.
- fix(msteams): gate startup user allowlist resolution [AI]. (#79003) Thanks @pgondhi987.
- Harden macOS shell wrapper allowlist parsing [AI]. (#78518) Thanks @pgondhi987.

View File

@@ -662,7 +662,7 @@ Default slash command settings:
</Accordion>
<Accordion title="Live stream preview">
OpenClaw can stream draft replies by sending a temporary message and editing it as text arrives. `channels.discord.streaming` takes `off` | `partial` | `block` | `progress` (default). `progress` keeps one editable status draft and updates it with tool progress until final delivery; the shared starter label stays visible while `streaming.progress.maxLines` limits the rolling progress lines below it. `streamMode` is a legacy runtime alias. Run `openclaw doctor --fix` to rewrite persisted config to the canonical key.
OpenClaw can stream draft replies by sending a temporary message and editing it as text arrives. `channels.discord.streaming` takes `off` | `partial` | `block` | `progress` (default). `progress` keeps one editable status draft and updates it with tool progress until final delivery; the shared starter label is a rolling line, so it scrolls away like the rest once enough work appears. `streamMode` is a legacy runtime alias. Run `openclaw doctor --fix` to rewrite persisted config to the canonical key.
Set `channels.discord.streaming.mode` to `off` to disable Discord preview edits. If Discord block streaming is explicitly enabled, OpenClaw skips the preview stream to avoid double-streaming.

View File

@@ -57,10 +57,9 @@ A progress draft has two parts:
| Progress lines | Compact run updates using the same tool icons and detail formatter as verbose output. |
The label appears after the agent starts meaningful work and either remains busy
for five seconds or emits a second work event. It stays visible while the agent
is still working; `streaming.progress.maxLines` limits only the rolling progress
lines below the label. In other words, progress drafts render as `label + last N
progress lines`. Plain text-only replies do not show a progress draft. Progress lines are added
for five seconds or emits a second work event. It is part of the rolling progress
line list, so the starter status scrolls away once enough concrete work appears.
Plain text-only replies do not show a progress draft. Progress lines are added
only when the agent emits useful work updates, for example `🛠️ Bash: run tests`,
`🔎 Web Search: for "discord edit message"`, or `✍️ Write: to /tmp/file`.
By default they use the same compact explain mode as `/verbose`; set

View File

@@ -1656,7 +1656,7 @@ describe("processDiscordMessage draft streaming", () => {
expect(draftStream.update).toHaveBeenCalledWith("Shelling\n🛠 Exec\n• done");
});
it("keeps Discord progress labels visible above rolling lines", async () => {
it("keeps Discord progress labels as rolling lines", async () => {
const draftStream = createMockDraftStreamForTest();
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
@@ -1680,7 +1680,7 @@ describe("processDiscordMessage draft streaming", () => {
await runProcessDiscordMessage(ctx);
expect(draftStream.update).toHaveBeenCalledWith("Clawing...\n🧩 First\n🧩 Second\n🧩 Third");
expect(draftStream.update).toHaveBeenCalledWith("🧩 First\n🧩 Second\n🧩 Third");
});
it("skips empty apply_patch starts and renders the patch summary", async () => {

View File

@@ -320,9 +320,7 @@ describe("createTeamsReplyStreamController", () => {
expect(ctrl.shouldSuppressDefaultToolProgressMessages()).toBe(true);
expect(ctrl.shouldStreamPreviewToolProgress()).toBe(true);
expect(streamInstances[0]?.sendInformativeUpdate).toHaveBeenLastCalledWith(
"Working\n- tool: exec",
);
expect(streamInstances[0]?.sendInformativeUpdate).toHaveBeenLastCalledWith("- tool: exec");
});
it("suppresses Teams default progress messages without stream lines when tool progress is disabled", async () => {

View File

@@ -354,8 +354,9 @@ vi.mock("openclaw/plugin-sdk/channel-streaming", () => ({
const label = params.entry?.streaming?.progress?.label;
const maxLines = params.entry?.streaming?.progress?.maxLines ?? 8;
const formatLine = params.formatLine ?? ((line: string) => line);
const progressLines = params.lines
.map((line) => {
const lines = [
label === false ? undefined : (label ?? "Thinking"),
...params.lines.map((line) => {
const text =
typeof line === "string"
? line
@@ -366,12 +367,10 @@ vi.mock("openclaw/plugin-sdk/channel-streaming", () => ({
: line.text;
const formatted = formatLine(text);
return /^\p{Extended_Pictographic}/u.test(text) ? formatted : ` ${formatted}`;
})
}),
]
.filter((line): line is string => Boolean(line))
.slice(-maxLines);
const lines = [label === false ? undefined : (label ?? "Thinking"), ...progressLines].filter(
(line): line is string => Boolean(line),
);
return lines.join("\n");
},
formatChannelProgressDraftLine: (params: {
@@ -828,7 +827,6 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
expect(draftStream.update).toHaveBeenLastCalledWith(
[
"Shelling",
"• step 1",
"• step 2",
"• step 3",

View File

@@ -211,28 +211,16 @@ describe("channel-streaming", () => {
lines: [" tool: read ", "patch applied", "tests done"],
formatLine: (line) => `\`${line}\``,
}),
).toBe("Shelling\n• `patch applied`\n• `tests done`");
).toBe("• `patch applied`\n• `tests done`");
expect(
formatChannelProgressDraftText({
entry,
lines: ["🛠️ Exec", "plain update"],
}),
).toBe("Shelling\n🛠️ Exec\n• plain update");
).toBe("🛠️ Exec\n• plain update");
});
it("keeps progress labels outside the rolling line limit", () => {
const entry = { streaming: { progress: { label: "Working", maxLines: 1 } } };
expect(
formatChannelProgressDraftText({
entry,
lines: ["tool: search", "tool: exec"],
bullet: "-",
}),
).toBe("Working\n- tool: exec");
});
it("keeps progress labels visible with bounded rolling lines", () => {
it("renders progress labels as rolling lines", () => {
const entry = { streaming: { progress: { label: "Shelling", maxLines: 3 } } };
expect(
@@ -240,7 +228,7 @@ describe("channel-streaming", () => {
entry,
lines: ["🛠️ Exec", "📖 Read", "🩹 Patch"],
}),
).toBe("Shelling\n🛠️ Exec\n📖 Read\n🩹 Patch");
).toBe("🛠️ Exec\n📖 Read\n🩹 Patch");
});
it("renders structured progress lines with compact details", () => {

View File

@@ -792,21 +792,25 @@ export function formatChannelProgressDraftText(params: {
const maxLines = resolveChannelProgressDraftMaxLines(params.entry);
const formatLine = params.formatLine ?? ((line: string) => line);
const bullet = params.bullet ?? "•";
const labelLine = label
? compactChannelProgressDraftLine(label, DEFAULT_PROGRESS_DRAFT_MAX_LINE_CHARS)
: "";
const progressLines = params.lines
const rawLines: Array<string | ChannelProgressDraftLine | { draftLabel: string }> = label
? [{ draftLabel: label }, ...params.lines]
: params.lines;
const lines = rawLines
.map((line) => {
const rawText = typeof line === "string" ? line : getProgressDraftLineText(line);
const isLabelLine = typeof line === "object" && line !== null && "draftLabel" in line;
const rawText = isLabelLine
? line.draftLabel
: typeof line === "string"
? line
: getProgressDraftLineText(line);
const text = compactChannelProgressDraftLine(rawText, DEFAULT_PROGRESS_DRAFT_MAX_LINE_CHARS);
return text || undefined;
return text ? { text, isLabelLine } : undefined;
})
.filter((line): line is string => Boolean(line))
.filter((line): line is { text: string; isLabelLine: boolean } => Boolean(line))
.slice(-maxLines)
.map((text) => {
const formatted = formatLine(text);
return shouldPrefixProgressLine(text) ? `${bullet} ${formatted}` : formatted;
.map(({ text, isLabelLine }) => {
const formatted = isLabelLine ? text : formatLine(text);
return !isLabelLine && shouldPrefixProgressLine(text) ? `${bullet} ${formatted}` : formatted;
});
const lines = labelLine ? [labelLine, ...progressLines] : progressLines;
return lines.join("\n");
return lines.filter((line): line is string => Boolean(line)).join("\n");
}