Skip to content

Commit 034c389

Browse files
authored
fix(web): hide empty tool_call/tool_call_update status rows (#4621)
buildBlocks() in AssistantMessage filters a fixed list of internal status labels before they reach StatusPill, but tool_call / tool_call_update are not in it, so they fall through and render as raw, expandable status pills with no tool name, input, output, or detail — looking like "a tool ran but produced no output". These are transient ACP markers: on the live SSE path the daemon already normalizes them to `running` (TRANSIENT_ACP_STATUS_LABELS in providers/daemon.ts), which buildBlocks() skips. The persisted-events path does not normalize, so the bare labels survive into the UI. Add both labels to the existing skip list, with a comment pointing at the daemon source of truth. Add a red-spec test asserting the two empty status rows render no pill, plus a guard that status rows carrying a real detail still render.
1 parent 3162da5 commit 034c389

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

apps/web/src/components/AssistantMessage.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4004,7 +4004,15 @@ function buildBlocks(events: AgentEvent[]): Block[] {
40044004
ev.label === "running" ||
40054005
ev.label === "requesting" ||
40064006
ev.label === "thinking" ||
4007-
ev.label === "empty_response"
4007+
ev.label === "empty_response" ||
4008+
// Transient ACP tool-call markers (#4618). On the live SSE path the
4009+
// daemon normalizes these to `running` (TRANSIENT_ACP_STATUS_LABELS in
4010+
// providers/daemon.ts), which is already skipped above; the persisted-
4011+
// events path does not normalize, so they arrive here as bare labels
4012+
// with no tool name/input/output/detail and would otherwise render as
4013+
// empty, expandable "tool ran but produced no output" status pills.
4014+
ev.label === "tool_call" ||
4015+
ev.label === "tool_call_update"
40084016
)
40094017
continue;
40104018
const last = out[out.length - 1];

apps/web/tests/components/assistant-message-tool-status.test.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,44 @@ describe('AssistantMessage tool status', () => {
451451
expect(screen.queryByTestId('task-activity-terminal')).toBeNull();
452452
});
453453

454+
it('hides empty tool_call / tool_call_update status rows (no displayable detail) (#4618)', () => {
455+
const { container } = render(
456+
<AssistantMessage
457+
projectKind="prototype"
458+
conversationId="conv-1"
459+
message={messageWithEvents([
460+
{ kind: 'status', label: 'tool_call' },
461+
{ kind: 'status', label: 'tool_call_update' },
462+
])}
463+
streaming={false}
464+
projectId="project-1"
465+
/>,
466+
);
467+
468+
// These persisted ACP markers carry no tool name/input/output, so they must
469+
// not surface as empty, expandable status pills.
470+
expect(container.querySelector('[data-status="tool_call"]')).toBeNull();
471+
expect(container.querySelector('[data-status="tool_call_update"]')).toBeNull();
472+
expect(container.querySelector('.status-pill')).toBeNull();
473+
});
474+
475+
it('still renders status rows that carry a displayable detail', () => {
476+
const { container } = render(
477+
<AssistantMessage
478+
projectKind="prototype"
479+
conversationId="conv-1"
480+
message={messageWithEvents([
481+
{ kind: 'status', label: 'model', detail: 'claude-opus-4-7-high' },
482+
])}
483+
streaming={false}
484+
projectId="project-1"
485+
/>,
486+
);
487+
488+
expect(container.querySelector('[data-status="model"]')).not.toBeNull();
489+
expect(container.querySelector('.status-detail')?.textContent).toContain('claude-opus-4-7-high');
490+
});
491+
454492
it('renders URLs in JSON-like status details without trailing structural characters', () => {
455493
const { container } = render(
456494
<AssistantMessage

0 commit comments

Comments
 (0)