mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
fix: use plain agent progress labels (#107260)
* fix(channels): simplify agent progress labels * chore: keep changelog release-owned * chore: refresh bundled channel metadata
This commit is contained in:
committed by
GitHub
parent
9da229c8ba
commit
b940339c32
@@ -1,2 +1,2 @@
|
||||
33404fe321c4413d7d0d4bdd9697fe942ea17dbc6505761a68b528fc1d978437 plugin-sdk-api-baseline.json
|
||||
c84e9d2181a2c8e2889984e180405846028def0dc347f489c333c0794f54a4c7 plugin-sdk-api-baseline.jsonl
|
||||
0302d93c3f37910e833b21b33588d154793f3c2c9bcb4e3008cbec432afaaf27 plugin-sdk-api-baseline.json
|
||||
a657e8e63db0f185d0917fc3660420dd0aa55f1a58544863b70bd6f36752f621 plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -15,7 +15,7 @@ message once real work starts, edits it as the agent reads, plans, calls
|
||||
tools, or waits for approval, then turns it into the final answer.
|
||||
|
||||
```text
|
||||
Shelling...
|
||||
Working...
|
||||
📖 from docs/concepts/progress-drafts.md
|
||||
🔎 Web Search: for "discord edit message"
|
||||
🛠️ Bash: run tests
|
||||
@@ -58,7 +58,7 @@ migration, see [Streaming and chunking](/concepts/streaming).
|
||||
| Part | Purpose |
|
||||
| --------------- | --------------------------------------------------------------------------------- |
|
||||
| Status headline | On Discord and Telegram, the model preamble; Discord adds a utility filler. |
|
||||
| Label | Optional starter/status line such as `Working` or `Shelling`. |
|
||||
| Label | Optional starter/status line such as `Working`. |
|
||||
| Progress lines | Compact run updates using the same tool icons and detail formatter as `/verbose`. |
|
||||
|
||||
For raw tool progress, the label appears once the agent starts meaningful work
|
||||
@@ -94,14 +94,12 @@ block-reply delivery — use `streaming.block.enabled` for that.
|
||||
## Configure labels
|
||||
|
||||
Progress labels live under `channels.<channel>.streaming.progress`. The default
|
||||
raw tool-line label is `"auto"`, which picks from OpenClaw's built-in
|
||||
single-word label pool. A status headline hides that implicit label; set
|
||||
raw tool-line label is `"auto"`, which uses the plain built-in `Working`
|
||||
label. A status headline hides that implicit label; set
|
||||
`label: "auto"` explicitly if you want a label above it too:
|
||||
|
||||
```text
|
||||
Working, Shelling, Scuttling, Clawing, Pinching, Molting, Bubbling, Tiding,
|
||||
Reefing, Cracking, Sifting, Brining, Nautiling, Krilling, Barnacling,
|
||||
Lobstering, Tidepooling, Pearling, Snapping, Surfacing
|
||||
Working
|
||||
```
|
||||
|
||||
Use a fixed label:
|
||||
|
||||
@@ -2318,7 +2318,7 @@ describe("processDiscordMessage draft streaming", () => {
|
||||
await runProcessDiscordMessage(ctx);
|
||||
|
||||
const updates = draftStream.update.mock.calls.map((call) => call[0]);
|
||||
expect(updates).toEqual(["Pinching\n\n🛠️ Exec\n• exec done"]);
|
||||
expect(updates).toEqual(["Working\n\n🛠️ Exec\n• exec done"]);
|
||||
expectFinalWithProgressReceipt("done", "🛠️ 1 tool call");
|
||||
// The working draft deletes once the receipt-bearing final landed.
|
||||
expect(editMessageDiscord).not.toHaveBeenCalled();
|
||||
@@ -2647,7 +2647,7 @@ describe("processDiscordMessage draft streaming", () => {
|
||||
await runProcessDiscordMessage(ctx);
|
||||
|
||||
expect(getLastDispatchReplyOptions()?.sourceReplyDeliveryMode).toBe("message_tool_only");
|
||||
expect(draftStream.update).toHaveBeenCalledWith("Pinching\n\n🛠️ Exec\n• exec done");
|
||||
expect(draftStream.update).toHaveBeenCalledWith("Working\n\n🛠️ Exec\n• exec done");
|
||||
expect(deliverDiscordReply).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -3116,7 +3116,7 @@ describe("processDiscordMessage draft streaming", () => {
|
||||
expect(firstDispatchParams().replyOptions?.disableBlockStreaming).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps progress label visible when Discord tool progress lines are disabled", async () => {
|
||||
it("uses the plain default progress label when Discord tool lines are disabled", async () => {
|
||||
const elapseProgressDraftStartDelay = useProgressDraftStartDelay();
|
||||
const draftStream = createMockDraftStreamForTest();
|
||||
|
||||
@@ -3133,7 +3133,6 @@ describe("processDiscordMessage draft streaming", () => {
|
||||
streaming: {
|
||||
mode: "progress",
|
||||
progress: {
|
||||
label: "Shelling",
|
||||
toolProgress: false,
|
||||
},
|
||||
},
|
||||
@@ -3143,7 +3142,7 @@ describe("processDiscordMessage draft streaming", () => {
|
||||
await runProcessDiscordMessage(ctx);
|
||||
|
||||
expect(draftStream.update).toHaveBeenCalledTimes(1);
|
||||
expect(draftStream.update).toHaveBeenCalledWith("Shelling");
|
||||
expect(draftStream.update).toHaveBeenCalledWith("Working");
|
||||
expect(draftStream.flush).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
requireRecord(firstDispatchParams().replyOptions, "dispatch reply options")
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -97,7 +97,7 @@ function createChannelProgressUiHints(params: {
|
||||
},
|
||||
"streaming.progress.labels": {
|
||||
label: `${channelLabel} Progress Label Pool`,
|
||||
help: 'Candidate labels for streaming.progress.label="auto". Leave unset to use OpenClaw built-in progress labels.',
|
||||
help: 'Candidate labels for streaming.progress.label="auto". Leave unset to use the built-in "Working" label.',
|
||||
},
|
||||
"streaming.progress.maxLines": {
|
||||
label: `${channelLabel} Progress Max Lines`,
|
||||
|
||||
@@ -237,7 +237,7 @@ describe("channel-streaming", () => {
|
||||
});
|
||||
|
||||
it("uses auto progress labels when no explicit label is configured", () => {
|
||||
expect(DEFAULT_PROGRESS_DRAFT_LABELS[0]).toBe("Working");
|
||||
expect(DEFAULT_PROGRESS_DRAFT_LABELS).toEqual(["Working"]);
|
||||
expect(resolveChannelProgressDraftLabel({ random: () => 0 })).toBe(
|
||||
DEFAULT_PROGRESS_DRAFT_LABELS[0],
|
||||
);
|
||||
|
||||
@@ -1,25 +1,4 @@
|
||||
export const DEFAULT_PROGRESS_DRAFT_LABELS = [
|
||||
"Working",
|
||||
"Shelling",
|
||||
"Scuttling",
|
||||
"Clawing",
|
||||
"Pinching",
|
||||
"Molting",
|
||||
"Bubbling",
|
||||
"Tiding",
|
||||
"Reefing",
|
||||
"Cracking",
|
||||
"Sifting",
|
||||
"Brining",
|
||||
"Nautiling",
|
||||
"Krilling",
|
||||
"Barnacling",
|
||||
"Lobstering",
|
||||
"Tidepooling",
|
||||
"Pearling",
|
||||
"Snapping",
|
||||
"Surfacing",
|
||||
] as const;
|
||||
export const DEFAULT_PROGRESS_DRAFT_LABELS = ["Working"] as const;
|
||||
|
||||
function hashProgressSeed(seed: string): number {
|
||||
let hash = 2166136261;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Control UI E2E tests cover chat run lifecycle behavior through the Gateway WebSocket.
|
||||
import { chromium, type Browser, type Page } from "playwright";
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
||||
import { DEFAULT_PROGRESS_DRAFT_LABELS } from "../../../src/shared/progress-labels.js";
|
||||
import { CHAT_RUN_STATUS_TOAST_DURATION_MS } from "../pages/chat/run-lifecycle.ts";
|
||||
import {
|
||||
canRunPlaywrightChromium,
|
||||
@@ -66,22 +65,13 @@ describeControlUiE2e("Control UI chat run lifecycle", () => {
|
||||
await currentPage.getByRole("button", { name: "Send message" }).click();
|
||||
await gateway.waitForRequest("chat.send");
|
||||
await currentPage.locator(".chat-working-indicator").waitFor();
|
||||
const progressLabelBefore = await currentPage
|
||||
.locator(".chat-working-indicator__status span:last-child")
|
||||
.textContent();
|
||||
expect(DEFAULT_PROGRESS_DRAFT_LABELS.slice(1).map((label) => `${label}…`)).toContain(
|
||||
progressLabelBefore,
|
||||
);
|
||||
|
||||
await currentPage.clock.runFor(177_000);
|
||||
|
||||
await expect
|
||||
.poll(() => currentPage.locator(".chat-working-indicator__elapsed").textContent())
|
||||
.toBe("2m 57s");
|
||||
const progressLabelAfter = await currentPage
|
||||
.locator(".chat-working-indicator__status span:last-child")
|
||||
.textContent();
|
||||
expect(progressLabelAfter).toBe(progressLabelBefore);
|
||||
await currentPage.getByText("Working…", { exact: true }).waitFor();
|
||||
});
|
||||
|
||||
it("clears shared session activity when chat final arrives first", async () => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { copyFile, mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { chromium, type Browser, type BrowserContext } from "playwright";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { DEFAULT_PROGRESS_DRAFT_LABELS } from "../../../src/shared/progress-labels.js";
|
||||
import {
|
||||
canRunPlaywrightChromium,
|
||||
installMockGateway,
|
||||
@@ -110,19 +109,11 @@ describeMantisWebUiChat("Mantis Control UI web chat proof", () => {
|
||||
|
||||
await page.getByText("saved 875.3k tokens", { exact: true }).waitFor();
|
||||
await page.locator(".chat-working-indicator").waitFor();
|
||||
const progressLabel = await page
|
||||
.locator(".chat-working-indicator__status span:last-child")
|
||||
.textContent();
|
||||
expect(DEFAULT_PROGRESS_DRAFT_LABELS.slice(1).map((label) => `${label}…`)).toContain(
|
||||
progressLabel,
|
||||
);
|
||||
await page.getByText("Working…", { exact: true }).waitFor();
|
||||
await page.clock.runFor(177_000);
|
||||
await expect
|
||||
.poll(() => page.locator(".chat-working-indicator__elapsed").textContent())
|
||||
.toBe("2m 57s");
|
||||
expect(
|
||||
await page.locator(".chat-working-indicator__status span:last-child").textContent(),
|
||||
).toBe(progressLabel);
|
||||
await page.screenshot({ fullPage: true, path: path.join(artifactDir, "web-ui-chat.png") });
|
||||
|
||||
await gateway.emitChatFinal({ runId: params.idempotencyKey ?? "", text: reply });
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { html, render } from "lit";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { i18n } from "../../../i18n/index.ts";
|
||||
import type { MessageGroup } from "../../../lib/chat/chat-types.ts";
|
||||
import { setUiTimeFormatPreference } from "../../../lib/format.ts";
|
||||
import { renderMessageGroup, renderStreamGroup } from "./chat-message.ts";
|
||||
@@ -453,8 +452,7 @@ function mediaTicketPayload(mediaTicket: string, ttlMs = 5 * 60 * 1000) {
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await i18n.setLocale("en");
|
||||
afterEach(() => {
|
||||
markdownRenderMock.mockClear();
|
||||
document.querySelectorAll("[data-delete-confirm-fixture]").forEach((element) => {
|
||||
element.remove();
|
||||
@@ -1049,7 +1047,7 @@ describe("grouped chat rendering", () => {
|
||||
expect(container.querySelector(".chat-reading-indicator")).not.toBeNull();
|
||||
expect(container.querySelector(".chat-working-indicator__elapsed")).not.toBeNull();
|
||||
expect(container.querySelector(".chat-working-indicator__status")?.textContent).toContain(
|
||||
"Snapping…",
|
||||
"Working…",
|
||||
);
|
||||
expect(container.querySelector(".chat-group-footer")).toBeNull();
|
||||
});
|
||||
@@ -1095,7 +1093,7 @@ describe("grouped chat rendering", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps one progress label stable per run while varying labels across runs", () => {
|
||||
it("keeps the progress label plain across runs", () => {
|
||||
const labelFor = (startedAt: number) => {
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
@@ -1106,26 +1104,9 @@ describe("grouped chat rendering", () => {
|
||||
?.textContent;
|
||||
};
|
||||
|
||||
expect(labelFor(1_000)).toBe("Snapping…");
|
||||
expect(labelFor(1_500)).toBe("Snapping…");
|
||||
expect(labelFor(8_000)).toBe("Clawing…");
|
||||
});
|
||||
|
||||
it("localizes branded progress labels", async () => {
|
||||
i18n.registerTranslation("pt-BR", {
|
||||
chat: { progressLabels: { snapping: "Estalando" } },
|
||||
});
|
||||
await i18n.setLocale("pt-BR");
|
||||
|
||||
const container = document.createElement("div");
|
||||
render(
|
||||
renderStreamGroup([{ kind: "reading-indicator", key: "reading", startedAt: 1_000 }]),
|
||||
container,
|
||||
);
|
||||
|
||||
expect(
|
||||
container.querySelector(".chat-working-indicator__status span:last-child")?.textContent,
|
||||
).toBe("Estalando…");
|
||||
expect(labelFor(1_000)).toBe("Working…");
|
||||
expect(labelFor(1_500)).toBe("Working…");
|
||||
expect(labelFor(8_000)).toBe("Working…");
|
||||
});
|
||||
|
||||
it("renders configured local user names", () => {
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { html } from "lit";
|
||||
import {
|
||||
DEFAULT_PROGRESS_DRAFT_LABELS,
|
||||
selectProgressLabel,
|
||||
} from "../../../../../src/shared/progress-labels.js";
|
||||
import "../../../components/elapsed-time.ts";
|
||||
import { icons } from "../../../components/icons.ts";
|
||||
import { t } from "../../../i18n/index.ts";
|
||||
@@ -16,14 +12,6 @@ const PUNCH_STANCES: Array<[stance: string, weight: number]> = [
|
||||
["chat-reading-indicator--flurry", 12],
|
||||
["chat-reading-indicator--haymaker", 6],
|
||||
];
|
||||
const CHAT_PROGRESS_LABELS = DEFAULT_PROGRESS_DRAFT_LABELS.slice(1);
|
||||
|
||||
function localizeProgressLabel(label: string): string {
|
||||
const key = `chat.progressLabels.${label.toLowerCase()}`;
|
||||
const translated = t(key);
|
||||
return translated === key ? label : translated;
|
||||
}
|
||||
|
||||
function punchStanceClass(key: string): string {
|
||||
let hash = 0x811c9dc5;
|
||||
for (let i = 0; i < key.length; i++) {
|
||||
@@ -42,14 +30,6 @@ function punchStanceClass(key: string): string {
|
||||
}
|
||||
|
||||
export function renderChatWorkingIndicator(part: Extract<ChatItem, { kind: "reading-indicator" }>) {
|
||||
// Run start changes between turns but stays fixed across re-renders, so the
|
||||
// phrase varies without flickering while the elapsed timer advances.
|
||||
const selectedLabel =
|
||||
selectProgressLabel({
|
||||
labels: CHAT_PROGRESS_LABELS,
|
||||
seed: String(Math.floor(part.startedAt / 1_000)),
|
||||
}) ?? CHAT_PROGRESS_LABELS[0];
|
||||
const progressLabel = localizeProgressLabel(selectedLabel ?? DEFAULT_PROGRESS_DRAFT_LABELS[0]);
|
||||
// The animated claw stays decorative; the text status exposes progress without
|
||||
// announcing every elapsed-time tick to screen readers.
|
||||
return html`
|
||||
@@ -66,7 +46,7 @@ export function renderChatWorkingIndicator(part: Extract<ChatItem, { kind: "read
|
||||
.startMs=${part.startedAt}
|
||||
></openclaw-elapsed-time>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>${progressLabel}…</span>
|
||||
<span>${t("common.working")}</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user