fix(coding-agent/task): fixed task result rendering to show error and success glyphs correctly

- Updated `renderResult` to inspect `isError` on detail-less task results and render an error state with the error glyph.
- Replaced success/completed status symbols in agent, review, and result outputs with the shared `status.done` symbol.
- Added tests for detail-less task rendering to verify failed results show error and successful results show the done glyph.
This commit is contained in:
can1357
2026-06-08 20:58:33 +02:00
parent 5a92a35c4c
commit d6f2ff38bb
2 changed files with 73 additions and 12 deletions
+24 -12
View File
@@ -632,7 +632,7 @@ function renderAgentProgress(
const indent = prefix ? `${prefix} ` : "";
let statusLine: string;
if (progress.status === "running") {
const bullet = theme.fg("accent", "•");
const bullet = theme.styledSymbol("status.done", "text");
const name = theme.fg("accent", description ? theme.bold(displayId) : displayId);
statusLine = `${indent}${bullet} ${name}`;
if (description) {
@@ -640,7 +640,11 @@ function renderAgentProgress(
statusLine += `${theme.fg("accent", ":")} ${desc}`;
}
} else {
statusLine = `${indent}${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)}`;
const glyph =
progress.status === "completed"
? theme.styledSymbol("status.done", "accent")
: theme.fg(iconColor, icon);
statusLine = `${indent}${glyph} ${theme.fg("accent", titlePart)}`;
}
// Show retry-blocked badge so the parent immediately sees that a child
@@ -809,7 +813,7 @@ function renderReviewResult(
const verdictColor = summary.overall_correctness === "correct" ? "success" : "error";
const isCorrect = summary.overall_correctness === "correct";
const verdictIcon = isCorrect
? theme.styledSymbol("tool.task", "accent")
? theme.styledSymbol("status.done", "accent")
: theme.fg(verdictColor, theme.status.error);
lines.push(
`${continuePrefix} Patch is ${theme.fg(verdictColor, summary.overall_correctness)} ${verdictIcon} ${theme.fg(
@@ -916,7 +920,7 @@ function renderAgentResult(
: needsWarning
? theme.status.warning
: success
? theme.styledSymbol("tool.task", "accent")
? theme.styledSymbol("status.done", "accent")
: theme.status.error;
const iconColor = needsWarning ? "warning" : success ? "success" : mergeFailed ? "warning" : "error";
const statusText = aborted
@@ -1074,7 +1078,7 @@ function renderAgentResult(
* Render the tool result.
*/
export function renderResult(
result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails },
result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails; isError?: boolean },
options: RenderResultOptions,
theme: Theme,
args?: TaskParams,
@@ -1085,18 +1089,25 @@ export function renderResult(
if (!details) {
const text = result.content.find(c => c.type === "text")?.text || "";
const header = renderStatusLine(
{ iconOverride: theme.styledSymbol("tool.task", "accent"), title: "Task" },
theme,
);
const errored = result.isError === true;
const header = errored
? renderStatusLine({ icon: "error", title: "Task", description: args?.agent }, theme)
: renderStatusLine(
{
iconOverride: theme.styledSymbol("status.done", "accent"),
title: "Task",
description: args?.agent,
},
theme,
);
return framedBlock(theme, width => ({
header,
sections: [
...(contextSectionRenderer ? [contextSectionRenderer(width)] : []),
...(text ? [{ separator: true, lines: [theme.fg("dim", truncateToWidth(text, width))] }] : []),
],
state: "success",
borderColor: "borderMuted",
state: errored ? "error" : "success",
borderColor: errored ? "error" : "borderMuted",
width,
}));
}
@@ -1116,7 +1127,8 @@ export function renderResult(
const metaLabel = countLabel ? (agentName ? `${countLabel}: ${agentName}` : countLabel) : agentName;
const header = renderStatusLine(
{
icon,
icon: icon === "success" ? undefined : icon,
iconOverride: icon === "success" ? theme.styledSymbol("status.done", "accent") : undefined,
title: "Task",
meta: metaLabel ? [metaLabel] : undefined,
},
@@ -102,3 +102,52 @@ describe("task progress rendering", () => {
expect(strippedRow).not.toContain(theme.getSpinnerFrames("status")[0]);
});
});
describe("task result detail-less state", () => {
beforeEach(async () => {
resetSettingsForTest();
await Settings.init({ inMemory: true });
});
afterEach(() => {
vi.restoreAllMocks();
resetSettingsForTest();
});
it("renders a validation failure with the error glyph, not a success bullet", async () => {
const theme = (await getThemeByName("dark"))!;
const options: RenderResultOptions = { expanded: false, isPartial: false };
const component = taskToolRenderer.renderResult(
{
content: [{ type: "text", text: 'Validation failed for tool "task": tasks: Invalid input' }],
isError: true,
},
options,
theme,
{ agent: "explore", tasks: [] },
);
const stripped = Bun.stripANSI(component.render(120).join("\n"));
// A failed task must surface the error glyph and never the "done" bullet.
expect(stripped).toContain(theme.status.error);
expect(stripped).not.toContain(theme.status.done);
expect(stripped).toContain("Task");
expect(stripped).toContain("explore");
expect(stripped).toContain("Validation failed");
});
it("renders a detail-less success with the accent bullet, not an error glyph", async () => {
const theme = (await getThemeByName("dark"))!;
const options: RenderResultOptions = { expanded: false, isPartial: false };
const component = taskToolRenderer.renderResult(
{ content: [{ type: "text", text: "done" }] },
options,
theme,
{ agent: "explore", tasks: [] },
);
const stripped = Bun.stripANSI(component.render(120).join("\n"));
expect(stripped).toContain(theme.status.done);
expect(stripped).not.toContain(theme.status.error);
});
});