fix(channels): keep markup out of the status label and make the line opt-in

Reasoning and commentary lines arrive channel-formatted, so falling back to their
raw text leaked <i> markup into the status label; only structured work lines carry a
clean label/detail pair. Defaulting the line to off keeps existing progress drafts
byte-identical until a channel opts in.
This commit is contained in:
Peter Steinberger
2026-07-19 23:30:19 -07:00
parent fe8c30a230
commit 17101e9e4a
5 changed files with 23 additions and 18 deletions
+9 -4
View File
@@ -173,9 +173,9 @@ Configure with `channels.<channel>.streaming.progress.status`:
| Mode | Behavior |
| ---------- | -------------------------------------------------------------- |
| `activity` | Names the current work, e.g. `▸ Exec: run tests` (default) |
| `minimal` | Static `▸ Working` label, still with elapsed time |
| `off` | No status line |
| `off` | No status line (default) |
| `activity` | Names the current work, e.g. `▸ Exec: run tests` |
| `minimal` | Static `▸ Working` label, still with elapsed time |
```json5
{
@@ -184,7 +184,7 @@ Configure with `channels.<channel>.streaming.progress.status`:
streaming: {
mode: "progress",
progress: {
status: "minimal",
status: "activity",
},
},
},
@@ -192,6 +192,11 @@ Configure with `channels.<channel>.streaming.progress.status`:
}
```
The line is off by default: progress drafts already name the current work, so the status
line mainly adds elapsed time and the steer hint, and enabling it changes what every
draft in that channel looks like. Turn it on per channel when a turn's duration matters
more than draft compactness.
Because the line is part of the progress draft, it appears only when that channel runs
`streaming.mode: "progress"`. It is transport-only: it never enters the session
transcript or the model context.
+6 -6
View File
@@ -244,14 +244,14 @@ export function createChannelProgressDraftCompositor(params: {
};
const resolveActivityLabel = (): string | undefined => {
const newest = lines.at(-1);
if (!newest) {
// Only structured work lines carry a clean label/detail pair. String lines are
// already channel-formatted (reasoning and commentary arrive with markup), so using
// their text would leak `<i>` and friends into the status line.
const newest = lines.findLast((line) => typeof line !== "string");
if (!newest || typeof newest === "string" || !newest.label) {
return undefined;
}
if (typeof newest === "string") {
return newest;
}
return newest.detail ? `${newest.label}: ${newest.detail}` : newest.label || newest.text;
return newest.detail ? `${newest.label}: ${newest.detail}` : newest.label;
};
const resolveStatusLine = (): string | undefined => {
+6 -6
View File
@@ -181,11 +181,11 @@ describe("streaming config resolution", () => {
});
describe("progress draft status mode", () => {
it("defaults to activity", () => {
expect(resolveChannelProgressDraftStatusMode(undefined)).toBe("activity");
expect(
resolveChannelProgressDraftStatusMode({ streaming: { mode: "progress" } } as never),
).toBe("activity");
it("stays off until a channel opts in", () => {
expect(resolveChannelProgressDraftStatusMode(undefined)).toBe("off");
expect(resolveChannelProgressDraftStatusMode({ streaming: { mode: "progress" } } as never)).toBe(
"off",
);
});
it("honors an explicit per-channel mode", () => {
@@ -203,7 +203,7 @@ describe("progress draft status mode", () => {
resolveChannelProgressDraftStatusMode({
streaming: { mode: "progress", progress: { status: "loud" } },
} as never),
).toBe("activity");
).toBe("off");
});
});
+1 -1
View File
@@ -923,7 +923,7 @@ export function resolveChannelProgressDraftMaxLines(
export function resolveChannelProgressDraftStatusMode(
entry: StreamingCompatEntry | null | undefined,
defaultValue: StatusFooterMode = "activity",
defaultValue: StatusFooterMode = "off",
): StatusFooterMode {
const configured = resolveChannelProgressDraftConfig(entry).status;
return configured === "off" || configured === "minimal" || configured === "activity"
+1 -1
View File
@@ -59,7 +59,7 @@ export type StatusFooterMode = "off" | "minimal" | "activity";
export type ChannelStreamingProgressConfig = {
/** Initial progress title. "auto" picks from labels; false hides the title. Default: "auto". */
label?: string | false;
/** Trailing status line: "activity" names the current work, "minimal" shows only elapsed time. Default: "activity". */
/** Trailing status line: "activity" names the current work, "minimal" shows only elapsed time. Default: "off". */
status?: StatusFooterMode;
/** Candidate labels for label="auto". Defaults to OpenClaw's built-in progress labels. */
labels?: string[];