diff --git a/extensions/discord/src/monitor/message-handler.process.test.ts b/extensions/discord/src/monitor/message-handler.process.test.ts
index 09473c9bc56a..9bd5fbbd5ec5 100644
--- a/extensions/discord/src/monitor/message-handler.process.test.ts
+++ b/extensions/discord/src/monitor/message-handler.process.test.ts
@@ -3057,8 +3057,8 @@ describe("processDiscordMessage draft streaming", () => {
await runProcessDiscordMessage(ctx);
const lastUpdate = draftStream.update.mock.calls.at(-1)?.[0];
- expect(lastUpdate).toContain("completed");
- expect(lastUpdate).not.toContain("install dependencies");
+ expect(lastUpdate).toContain("install dependencies");
+ expect(lastUpdate).not.toContain("completed");
});
it("drops later tool warning finals after progress preview final replies", async () => {
diff --git a/extensions/matrix/src/matrix/monitor/handler.test.ts b/extensions/matrix/src/matrix/monitor/handler.test.ts
index 7bb8a20a6810..d4eebc4c4f94 100644
--- a/extensions/matrix/src/matrix/monitor/handler.test.ts
+++ b/extensions/matrix/src/matrix/monitor/handler.test.ts
@@ -3229,13 +3229,13 @@ describe("matrix monitor handler draft streaming", () => {
expect(editMessageMatrixMock).toHaveBeenCalledWith(
"!room:example.org",
"$draft1",
- expect.stringContaining("completed"),
+ expect.stringContaining("Exec"),
expect.any(Object),
);
const recoveredEdit = mockCalls(editMessageMatrixMock, "editMessageMatrix").find(
- ([, eventId, body]) =>
- eventId === "$draft1" && typeof body === "string" && body.includes("completed"),
+ ([, eventId, body]) => eventId === "$draft1" && typeof body === "string",
);
+ expect(recoveredEdit?.[2]).not.toContain("completed");
expect(recoveredEdit?.[2]).not.toContain("failed");
expect(recoveredEdit?.[2]).not.toContain("run openclaw cron -> run jq");
});
@@ -3293,7 +3293,8 @@ describe("matrix monitor handler draft streaming", () => {
([, eventId, body]) =>
eventId === "$draft1" && typeof body === "string" && body.includes("completed"),
);
- expect(completedEdit?.[2]).not.toContain("install dependencies");
+ expect(completedEdit).toBeUndefined();
+ expect(singleTextMessageBody()).toContain("install dependencies");
});
it("replaces Matrix patch progress when the patch summary completes", async () => {
diff --git a/extensions/msteams/src/reply-dispatcher.test.ts b/extensions/msteams/src/reply-dispatcher.test.ts
index f8dcea1f4f9e..3f6b19325b2b 100644
--- a/extensions/msteams/src/reply-dispatcher.test.ts
+++ b/extensions/msteams/src/reply-dispatcher.test.ts
@@ -454,8 +454,8 @@ describe("createMSTeamsReplyDispatcher", () => {
});
const lastUpdate = getStreamMock().update.mock.calls.at(-1)?.[0];
- expect(lastUpdate).toContain("completed");
- expect(lastUpdate).not.toContain("install dependencies");
+ expect(lastUpdate).toContain("install dependencies");
+ expect(lastUpdate).not.toContain("completed");
});
it("replaces reasoning progress snapshots in progress mode", async () => {
diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts
index 6eebb0e12524..57672686829b 100644
--- a/extensions/telegram/src/bot-message-dispatch.test.ts
+++ b/extensions/telegram/src/bot-message-dispatch.test.ts
@@ -2625,10 +2625,10 @@ describe("dispatchTelegramMessage draft streaming", () => {
});
const lastUpdate = answerDraftStream.updatePreview.mock.calls.at(-1)?.[0];
- expect(lastUpdate?.text).toContain("completed");
- expect(lastUpdate?.text).not.toContain("install dependencies");
+ expect(lastUpdate?.text).toContain("install dependencies");
+ expect(lastUpdate?.text).not.toContain("completed");
expect(lastUpdate?.richMessage).toEqual({
- html: "Shelling
🛠️ Exec completed",
+ html: "Shelling
🛠️ Exec install dependencies",
skip_entity_detection: true,
});
});
diff --git a/src/channels/streaming.test.ts b/src/channels/streaming.test.ts
index 47437bd10a13..10e67e373d06 100644
--- a/src/channels/streaming.test.ts
+++ b/src/channels/streaming.test.ts
@@ -24,7 +24,7 @@ describe("buildChannelProgressDraftLine", () => {
});
});
- it("uses completed status when successful command output has no title", () => {
+ it("uses the tool label when successful command output has no title", () => {
const line = buildChannelProgressDraftLine({
event: "command-output",
phase: "end",
@@ -34,10 +34,10 @@ describe("buildChannelProgressDraftLine", () => {
expect(line).toMatchObject({
kind: "command-output",
- text: "🛠️ completed",
- detail: "completed",
+ text: "🛠️ Exec",
status: "completed",
});
+ expect(line?.detail).toBeUndefined();
});
it("keeps command status and title in raw command progress lines", () => {
diff --git a/src/channels/streaming.ts b/src/channels/streaming.ts
index 41723768e0e3..179441805121 100644
--- a/src/channels/streaming.ts
+++ b/src/channels/streaming.ts
@@ -411,6 +411,15 @@ function resolveCommandProgressCorrelationKey(input: { toolCallId?: string }): s
return toolCallId ? `command:${toolCallId}` : undefined;
}
+function isTerminalProgressStatus(status: string | undefined): boolean {
+ const normalized = normalizeOptionalLowercaseString(status);
+ return (
+ normalized === "completed" ||
+ normalized === "failed" ||
+ normalized?.startsWith("exit ") === true
+ );
+}
+
function isEmptyReasoningProgressItem(
input: Extract,
meta: string | undefined,
@@ -444,15 +453,6 @@ function buildCommandOutputProgressLine(
return line;
}
if (status === "completed") {
- if (!line.detail) {
- const statusLine = {
- ...line,
- detail: status,
- text: formatToolAggregate(name, [status], { markdown: options?.markdown }),
- };
- setProgressDraftLineCorrelationKey(statusLine, correlationKey);
- return statusLine;
- }
return line;
}
if (!line.detail || line.detail === status) {
@@ -1153,13 +1153,16 @@ function mergeProgressDraftLineUpdate {
expect(updated[0]).toMatchObject({
id: "tool:call-1-output",
kind: "command-output",
- detail: "completed",
+ detail: "install dependencies",
status: "completed",
- text: "🛠️ completed",
+ text: "🛠️ install dependencies",
});
expect(
formatChannelProgressDraftText({
lines: updated,
entry: { streaming: { progress: { label: false } } },
}),
- ).toBe("🛠️ completed");
+ ).toBe("🛠️ install dependencies");
const recoveredItemLine = buildChannelProgressDraftLine({
event: "item",
@@ -632,19 +632,20 @@ describe("channel-streaming", () => {
if (!recoveredItemLine || !recoveredCommandLine) {
throw new Error("expected recovered command progress lines");
}
- expect(
- mergeChannelProgressDraftLine([recoveredItemLine], recoveredCommandLine, {
- maxLines: 4,
- }),
- ).toMatchObject([
+ const recoveredUpdated = mergeChannelProgressDraftLine(
+ [recoveredItemLine],
+ recoveredCommandLine,
+ { maxLines: 4 },
+ );
+ expect(recoveredUpdated).toMatchObject([
{
id: "command-2",
kind: "command-output",
- detail: "completed",
status: "completed",
- text: "🛠️ completed",
+ text: "🛠️ Bash",
},
]);
+ expect(recoveredUpdated[0]).not.toHaveProperty("detail");
});
it("starts progress drafts after five seconds or a second work event", async () => {