From 0203babfdd4aa66116b458538b89c65e43bdb53b Mon Sep 17 00:00:00 2001 From: qer Date: Thu, 25 Jun 2026 23:32:39 +0800 Subject: [PATCH 1/6] feat(web): show full accumulated subagent progress The subagent detail panel only showed the most recent 40 progress lines because the reducer truncated the accumulated output. Keep the full history so the panel reflects the entire process as it grows. --- .changeset/web-subagent-full-progress.md | 5 +++ apps/kimi-web/src/api/daemon/eventReducer.ts | 4 +- apps/kimi-web/test/event-reducer.test.ts | 45 +++++++++++++++++++- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 .changeset/web-subagent-full-progress.md diff --git a/.changeset/web-subagent-full-progress.md b/.changeset/web-subagent-full-progress.md new file mode 100644 index 0000000000..c6d4e38aa3 --- /dev/null +++ b/.changeset/web-subagent-full-progress.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Show the full accumulated progress of a subagent in its detail panel instead of only the most recent lines. diff --git a/apps/kimi-web/src/api/daemon/eventReducer.ts b/apps/kimi-web/src/api/daemon/eventReducer.ts index f1f68cc143..c119b8bc6e 100644 --- a/apps/kimi-web/src/api/daemon/eventReducer.ts +++ b/apps/kimi-web/src/api/daemon/eventReducer.ts @@ -534,7 +534,9 @@ export function reduceAppEvent( if (outputLines.at(-1) === event.outputChunk) return t; return { ...t, - outputLines: [...outputLines, event.outputChunk].slice(-40), + // Accumulate the full progress so the subagent panel can show the + // entire process, not just the most recent handful of lines. + outputLines: [...outputLines, event.outputChunk], }; }); break; diff --git a/apps/kimi-web/test/event-reducer.test.ts b/apps/kimi-web/test/event-reducer.test.ts index 8d0531aacc..069de3488b 100644 --- a/apps/kimi-web/test/event-reducer.test.ts +++ b/apps/kimi-web/test/event-reducer.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { createInitialState, reduceAppEvent } from '../src/api/daemon/eventReducer'; -import type { AppMessage, AppSession } from '../src/api/types'; +import type { AppMessage, AppSession, AppTask } from '../src/api/types'; function makeSession(id: string, updatedAt: string): AppSession { return { @@ -37,6 +37,17 @@ function makeMessage(sessionId: string, createdAt: string): AppMessage { }; } +function makeSubagentTask(id: string, sessionId: string): AppTask { + return { + id, + sessionId, + kind: 'subagent', + description: 'subagent task', + status: 'running', + createdAt: '2026-01-01T00:00:00.000Z', + }; +} + describe('reduceAppEvent messageCreated', () => { it('bumps the session updatedAt so it floats to the top of the sidebar', () => { const state = { @@ -81,3 +92,35 @@ describe('reduceAppEvent messageCreated', () => { expect(next.sessions.find((s) => s.id === 's-b')?.updatedAt).toBe('2026-01-01T00:00:00.000Z'); }); }); + +describe('reduceAppEvent taskProgress', () => { + it('accumulates the full progress output without truncating to a fixed window', () => { + const state = { + ...createInitialState(), + tasksBySession: { 's1': [makeSubagentTask('t1', 's1')] }, + }; + let next = state; + for (let i = 0; i < 60; i++) { + next = reduceAppEvent( + next, + { type: 'taskProgress', sessionId: 's1', taskId: 't1', outputChunk: `line ${i}`, stream: 'stdout' }, + { sessionId: 's1', seq: i + 1 }, + ); + } + const lines = next.tasksBySession['s1']?.[0]?.outputLines; + expect(lines).toHaveLength(60); + expect(lines?.[0]).toBe('line 0'); + expect(lines?.at(-1)).toBe('line 59'); + }); + + it('deduplicates a repeated trailing chunk', () => { + const state = { + ...createInitialState(), + tasksBySession: { 's1': [makeSubagentTask('t1', 's1')] }, + }; + const event = { type: 'taskProgress', sessionId: 's1', taskId: 't1', outputChunk: 'same', stream: 'stdout' } as const; + const once = reduceAppEvent(state, event, { sessionId: 's1', seq: 1 }); + const twice = reduceAppEvent(once, event, { sessionId: 's1', seq: 2 }); + expect(twice.tasksBySession['s1']?.[0]?.outputLines).toEqual(['same']); + }); +}); From 4e0270a1bd1ed2b712249b21dbbb9738c4011dda Mon Sep 17 00:00:00 2001 From: qer Date: Thu, 25 Jun 2026 23:40:14 +0800 Subject: [PATCH 2/6] fix(web): preserve subagent progress across task updates The projector emits a taskCreated (without reducer-owned outputLines) right before every taskProgress, and the taskCreated branch replaced the task object outright, resetting outputLines to empty on each progress event. So the panel still only showed the latest chunk. Preserve the accumulated outputLines when replacing a task, and update the test to mirror the real taskCreated-before-taskProgress path. --- apps/kimi-web/src/api/daemon/eventReducer.ts | 4 +++- apps/kimi-web/test/event-reducer.test.ts | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/kimi-web/src/api/daemon/eventReducer.ts b/apps/kimi-web/src/api/daemon/eventReducer.ts index c119b8bc6e..c90a673f8d 100644 --- a/apps/kimi-web/src/api/daemon/eventReducer.ts +++ b/apps/kimi-web/src/api/daemon/eventReducer.ts @@ -518,7 +518,9 @@ export function reduceAppEvent( next.tasksBySession[sid] = [...list, event.task]; } else { const patched = [...list]; - patched[idx] = event.task; + // The projected task does not carry reducer-owned accumulated progress; + // preserve it across the replacement so subagent output keeps growing. + patched[idx] = { ...event.task, outputLines: list[idx]!.outputLines }; next.tasksBySession[sid] = patched; } break; diff --git a/apps/kimi-web/test/event-reducer.test.ts b/apps/kimi-web/test/event-reducer.test.ts index 069de3488b..8af6361aa0 100644 --- a/apps/kimi-web/test/event-reducer.test.ts +++ b/apps/kimi-web/test/event-reducer.test.ts @@ -101,10 +101,18 @@ describe('reduceAppEvent taskProgress', () => { }; let next = state; for (let i = 0; i < 60; i++) { + // The real projector emits a taskCreated (without reducer-owned + // outputLines) right before every taskProgress; progress must survive + // that replacement. + next = reduceAppEvent( + next, + { type: 'taskCreated', sessionId: 's1', task: makeSubagentTask('t1', 's1') }, + { sessionId: 's1', seq: i * 2 + 1 }, + ); next = reduceAppEvent( next, { type: 'taskProgress', sessionId: 's1', taskId: 't1', outputChunk: `line ${i}`, stream: 'stdout' }, - { sessionId: 's1', seq: i + 1 }, + { sessionId: 's1', seq: i * 2 + 2 }, ); } const lines = next.tasksBySession['s1']?.[0]?.outputLines; From a3b238d148044a6abc7fb9161ef79d130e04e2ac Mon Sep 17 00:00:00 2001 From: qer Date: Thu, 25 Jun 2026 23:47:56 +0800 Subject: [PATCH 3/6] feat(web): clean up subagent progress text Drop the noisy 'Started a step' line and summarize tool calls with a concise target (path / command / pattern) instead of the full JSON args, so the subagent progress panel shows what the subagent is actually doing. --- .changeset/web-subagent-full-progress.md | 2 +- .../src/api/daemon/agentEventProjector.ts | 31 +++++++++--------- .../test/agent-event-projector.test.ts | 32 +++++++++++++++++++ 3 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 apps/kimi-web/test/agent-event-projector.test.ts diff --git a/.changeset/web-subagent-full-progress.md b/.changeset/web-subagent-full-progress.md index c6d4e38aa3..72cb94470a 100644 --- a/.changeset/web-subagent-full-progress.md +++ b/.changeset/web-subagent-full-progress.md @@ -2,4 +2,4 @@ "@moonshot-ai/kimi-code": patch --- -Show the full accumulated progress of a subagent in its detail panel instead of only the most recent lines. +Show the full accumulated progress of a subagent in its detail panel, with concise tool-call summaries instead of raw JSON. diff --git a/apps/kimi-web/src/api/daemon/agentEventProjector.ts b/apps/kimi-web/src/api/daemon/agentEventProjector.ts index 31a01c1c6a..09ff9ed825 100644 --- a/apps/kimi-web/src/api/daemon/agentEventProjector.ts +++ b/apps/kimi-web/src/api/daemon/agentEventProjector.ts @@ -26,6 +26,7 @@ import type { AppTask, } from '../types'; import { i18n } from '../../i18n'; +import { toolLabel, toolSummary } from '../../lib/toolMeta'; import { toAppMessageContent } from './mappers'; import type { WireMessageContent } from './wire'; @@ -212,22 +213,14 @@ function patchSubagent( return next; } -function shortJson(value: unknown): string { - if (value === undefined || value === null) return ''; - try { - const text = typeof value === 'string' ? value : JSON.stringify(value); - return text.length > 120 ? `${text.slice(0, 117)}...` : text; - } catch { - return ''; - } -} - -function subagentProgressText(rawType: string, payload: Record): string | null { - if (rawType === 'turn.step.started') return 'Started a step'; +export function subagentProgressText(rawType: string, payload: Record): string | null { + // "Started a step" fires on every step and adds no information — the phase + // badge already shows the subagent is working, so skip it to cut the noise. + if (rawType === 'turn.step.started') return null; if (rawType === 'tool.use' || rawType === 'tool.call.started') { const name = stringField(payload, 'name') ?? stringField(payload, 'toolName') ?? 'tool'; - const args = shortJson(payload['args'] ?? payload['input']); - return args ? `Calling ${name}: ${args}` : `Calling ${name}`; + const summary = toolArgSummary(name, payload['args'] ?? payload['input']); + return summary ? `Calling ${toolLabel(name)}: ${summary}` : `Calling ${toolLabel(name)}`; } if (rawType === 'tool.progress') { const update = payload['update']; @@ -242,11 +235,19 @@ function subagentProgressText(rawType: string, payload: Record) } if (rawType === 'tool.result') { const name = stringField(payload, 'name') ?? stringField(payload, 'toolName') ?? stringField(payload, 'toolCallId') ?? 'tool'; - return `Finished ${name}`; + return `Finished ${toolLabel(name)}`; } return null; } +/** A concise, human-readable summary of a tool call's arguments for progress + * lines (e.g. a file path or shell command), instead of the full JSON blob. */ +function toolArgSummary(name: string, args: unknown): string { + if (args === undefined || args === null) return ''; + const arg = typeof args === 'string' ? args : JSON.stringify(args); + return toolSummary(name, arg); +} + function projectSubagentProgress( state: SessionState, sessionId: string, diff --git a/apps/kimi-web/test/agent-event-projector.test.ts b/apps/kimi-web/test/agent-event-projector.test.ts new file mode 100644 index 0000000000..56af3e9bfa --- /dev/null +++ b/apps/kimi-web/test/agent-event-projector.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; +import { subagentProgressText } from '../src/api/daemon/agentEventProjector'; + +describe('subagentProgressText', () => { + it('drops turn.step.started as noise', () => { + expect(subagentProgressText('turn.step.started', {})).toBeNull(); + }); + + it('summarizes a read tool call with its path', () => { + const text = subagentProgressText('tool.use', { name: 'read', args: { path: 'src/foo.ts' } }); + expect(text).toContain('src/foo.ts'); + expect(text).not.toContain('"path"'); + }); + + it('summarizes a bash tool call with its command', () => { + const text = subagentProgressText('tool.call.started', { name: 'bash', args: { command: 'pnpm test' } }); + expect(text).toContain('pnpm test'); + expect(text).not.toContain('"command"'); + }); + + it('formats a tool result', () => { + expect(subagentProgressText('tool.result', { name: 'read' })).toContain('Finished'); + }); + + it('returns tool.progress update text', () => { + expect(subagentProgressText('tool.progress', { update: { text: 'working…' } })).toBe('working…'); + }); + + it('returns null for unknown event types', () => { + expect(subagentProgressText('turn.delta', {})).toBeNull(); + }); +}); From 584798044a87ac3fd54c62047549ff278ee9709f Mon Sep 17 00:00:00 2001 From: qer Date: Thu, 25 Jun 2026 23:53:57 +0800 Subject: [PATCH 4/6] fix(web): strip numeric index from subagent tool result names Some subagents name tool calls with a trailing index (e.g. Read_0, Bash_4), which surfaced in tool.result progress lines since the label did not resolve. Strip the index before resolving the label. --- apps/kimi-web/src/api/daemon/agentEventProjector.ts | 11 +++++++++-- apps/kimi-web/test/agent-event-projector.test.ts | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/kimi-web/src/api/daemon/agentEventProjector.ts b/apps/kimi-web/src/api/daemon/agentEventProjector.ts index 09ff9ed825..c6bcca75ff 100644 --- a/apps/kimi-web/src/api/daemon/agentEventProjector.ts +++ b/apps/kimi-web/src/api/daemon/agentEventProjector.ts @@ -219,8 +219,9 @@ export function subagentProgressText(rawType: string, payload: Record { expect(subagentProgressText('tool.result', { name: 'read' })).toContain('Finished'); }); + it('strips a trailing numeric index from tool.result names', () => { + const text = subagentProgressText('tool.result', { name: 'Read_0' }); + expect(text).toContain('Finished'); + expect(text).not.toContain('Read_0'); + }); + it('returns tool.progress update text', () => { expect(subagentProgressText('tool.progress', { update: { text: 'working…' } })).toBe('working…'); }); From 497efd14a0634b1f6fe49c203f116b9e229f2c8e Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 26 Jun 2026 00:04:18 +0800 Subject: [PATCH 5/6] fix(web): bound non-subagent task output and subagent progress text Restore a tail cap for background bash/tool task output (which can grow without bound) while keeping subagent progress in full, and cap individual subagent tool.progress chunks so a single huge output cannot dominate the panel. --- .../src/api/daemon/agentEventProjector.ts | 13 ++++++++++--- apps/kimi-web/src/api/daemon/eventReducer.ts | 13 ++++++++++--- .../kimi-web/test/agent-event-projector.test.ts | 8 ++++++++ apps/kimi-web/test/event-reducer.test.ts | 17 +++++++++++++++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/apps/kimi-web/src/api/daemon/agentEventProjector.ts b/apps/kimi-web/src/api/daemon/agentEventProjector.ts index c6bcca75ff..ef7eb363c9 100644 --- a/apps/kimi-web/src/api/daemon/agentEventProjector.ts +++ b/apps/kimi-web/src/api/daemon/agentEventProjector.ts @@ -227,12 +227,12 @@ export function subagentProgressText(rawType: string, payload: Record, 'text'); - if (text) return text; + if (text) return capProgressText(text); const message = stringField(update as Record, 'message'); - if (message) return message; + if (message) return capProgressText(message); } const message = stringField(payload, 'message'); - if (message) return message; + if (message) return capProgressText(message); } if (rawType === 'tool.result') { const name = stringField(payload, 'name') ?? stringField(payload, 'toolName') ?? stringField(payload, 'toolCallId') ?? 'tool'; @@ -247,6 +247,13 @@ function cleanToolName(name: string): string { return name.replace(/_\d+$/, ''); } +/** Cap a progress text chunk so a single huge tool output (e.g. a big command + * result) cannot dominate the panel. */ +const MAX_PROGRESS_TEXT = 2000; +function capProgressText(text: string): string { + return text.length > MAX_PROGRESS_TEXT ? `${text.slice(0, MAX_PROGRESS_TEXT)}…` : text; +} + /** A concise, human-readable summary of a tool call's arguments for progress * lines (e.g. a file path or shell command), instead of the full JSON blob. */ function toolArgSummary(name: string, args: unknown): string { diff --git a/apps/kimi-web/src/api/daemon/eventReducer.ts b/apps/kimi-web/src/api/daemon/eventReducer.ts index c90a673f8d..23a94e002b 100644 --- a/apps/kimi-web/src/api/daemon/eventReducer.ts +++ b/apps/kimi-web/src/api/daemon/eventReducer.ts @@ -26,6 +26,11 @@ import { i18n } from '../../i18n'; const OPTIMISTIC_USER_MESSAGE_METADATA_KEY = 'kimiWeb.optimisticUserMessage'; +/** Tail cap for accumulated output of non-subagent (bash / background tool) + * tasks, whose stdout can be noisy and unbounded. Subagent progress is kept + * in full (small synthesized lines). */ +const MAX_BACKGROUND_OUTPUT_LINES = 40; + // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- @@ -534,11 +539,13 @@ export function reduceAppEvent( if (t.id !== event.taskId) return t; const outputLines = t.outputLines ?? []; if (outputLines.at(-1) === event.outputChunk) return t; + const lines = [...outputLines, event.outputChunk]; return { ...t, - // Accumulate the full progress so the subagent panel can show the - // entire process, not just the most recent handful of lines. - outputLines: [...outputLines, event.outputChunk], + // Keep subagent progress in full (small synthesized lines) so the + // panel shows the whole process; cap background bash/tool output, + // which can grow without bound. + outputLines: t.kind === 'subagent' ? lines : lines.slice(-MAX_BACKGROUND_OUTPUT_LINES), }; }); break; diff --git a/apps/kimi-web/test/agent-event-projector.test.ts b/apps/kimi-web/test/agent-event-projector.test.ts index 344b6e2b6d..5ad23bb98c 100644 --- a/apps/kimi-web/test/agent-event-projector.test.ts +++ b/apps/kimi-web/test/agent-event-projector.test.ts @@ -32,6 +32,14 @@ describe('subagentProgressText', () => { expect(subagentProgressText('tool.progress', { update: { text: 'working…' } })).toBe('working…'); }); + it('caps a long tool.progress text', () => { + const long = 'x'.repeat(3000); + const text = subagentProgressText('tool.progress', { update: { text: long } }); + expect(text).not.toBeNull(); + expect(text!.length).toBeLessThan(long.length); + expect(text!.endsWith('…')).toBe(true); + }); + it('returns null for unknown event types', () => { expect(subagentProgressText('turn.delta', {})).toBeNull(); }); diff --git a/apps/kimi-web/test/event-reducer.test.ts b/apps/kimi-web/test/event-reducer.test.ts index 8af6361aa0..4df40777e4 100644 --- a/apps/kimi-web/test/event-reducer.test.ts +++ b/apps/kimi-web/test/event-reducer.test.ts @@ -131,4 +131,21 @@ describe('reduceAppEvent taskProgress', () => { const twice = reduceAppEvent(once, event, { sessionId: 's1', seq: 2 }); expect(twice.tasksBySession['s1']?.[0]?.outputLines).toEqual(['same']); }); + + it('caps accumulated output for non-subagent (background) tasks', () => { + const bash: AppTask = { ...makeSubagentTask('b1', 's1'), kind: 'bash' }; + const state = { ...createInitialState(), tasksBySession: { 's1': [bash] } }; + let next = state; + for (let i = 0; i < 60; i++) { + next = reduceAppEvent( + next, + { type: 'taskProgress', sessionId: 's1', taskId: 'b1', outputChunk: `line ${i}`, stream: 'stdout' }, + { sessionId: 's1', seq: i + 1 }, + ); + } + const lines = next.tasksBySession['s1']?.[0]?.outputLines; + expect(lines).toHaveLength(40); + expect(lines?.[0]).toBe('line 20'); + expect(lines?.at(-1)).toBe('line 59'); + }); }); From 4238c07438f9f675a36e7c176ab5dbc890f3b42d Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 26 Jun 2026 00:12:37 +0800 Subject: [PATCH 6/6] feat(web): group and fold subagent progress output Drop the noisy 'Finished' lines, group tool output under its call, and fold long output (first 5 + last 2 lines, expandable) so the subagent progress panel shows the call rhythm at a glance. --- .../src/api/daemon/agentEventProjector.ts | 7 +- .../src/components/chat/AgentDetailPanel.vue | 119 +++++++++++++++++- .../test/agent-event-projector.test.ts | 11 +- 3 files changed, 121 insertions(+), 16 deletions(-) diff --git a/apps/kimi-web/src/api/daemon/agentEventProjector.ts b/apps/kimi-web/src/api/daemon/agentEventProjector.ts index ef7eb363c9..5a55282b4c 100644 --- a/apps/kimi-web/src/api/daemon/agentEventProjector.ts +++ b/apps/kimi-web/src/api/daemon/agentEventProjector.ts @@ -234,10 +234,9 @@ export function subagentProgressText(rawType: string, payload: Record .filter((line) => line.length > 0), ); +interface ProgressGroup { + key: string; + /** The "Calling …" tool-call line, or '' for output with no preceding call. */ + call: string; + output: string[]; +} + +/** Group flat progress lines into tool-call groups: a "Calling …" line starts a + * group and subsequent non-call lines are its output. */ +function groupProgress(lines: string[]): ProgressGroup[] { + const groups: ProgressGroup[] = []; + let current: ProgressGroup | null = null; + let idx = 0; + for (const line of lines) { + if (line.startsWith('Calling ')) { + current = { key: `g${idx++}`, call: line, output: [] }; + groups.push(current); + } else if (current) { + current.output.push(line); + } else { + current = { key: `g${idx++}`, call: '', output: [line] }; + groups.push(current); + } + } + return groups; +} + +const progressGroups = computed(() => groupProgress(progressLines.value)); + +/** Group keys whose folded output is expanded. */ +const expandedGroups = ref>(new Set()); + +const OUTPUT_FOLD_THRESHOLD = 8; +const OUTPUT_HEAD = 5; +const OUTPUT_TAIL = 2; + +function isExpanded(key: string): boolean { + return expandedGroups.value.has(key); +} +function toggleGroup(key: string): void { + const next = new Set(expandedGroups.value); + if (next.has(key)) next.delete(key); + else next.add(key); + expandedGroups.value = next; +} +function foldCount(group: ProgressGroup): number { + return group.output.length - OUTPUT_HEAD - OUTPUT_TAIL; +} + function phaseLabel(phase: AgentMember['phase']): string { switch (phase) { case 'queued': return 'Queued'; @@ -66,10 +115,27 @@ watch( Task
{{ member.prompt }}
-
+
Progress
- {{ line }} +
+
+ + {{ group.call }} +
+
+ + +
+
@@ -189,14 +255,59 @@ watch( .ap-progress { display: flex; flex-direction: column; - gap: 3px; + gap: 6px; font-family: var(--mono); color: var(--text); min-width: 0; } -.ap-progress span { +.ap-group { + min-width: 0; +} +.ap-call { + display: flex; + align-items: baseline; + gap: 6px; + min-width: 0; + font-weight: 600; + color: var(--ink); + overflow-wrap: anywhere; + white-space: pre-wrap; +} +.ap-glyph { + flex: none; + color: var(--blue); + font-size: 0.85em; +} +.ap-output { + margin: 2px 0 0 16px; + padding-left: 8px; + color: var(--muted); + font-size: calc(var(--ui-font-size) - 1px); + line-height: 1.5; + border-left: 2px solid var(--line2, var(--line)); + min-width: 0; +} +.ap-out-line { min-width: 0; overflow-wrap: anywhere; white-space: pre-wrap; } +.ap-fold { + display: inline-block; + margin: 2px 0; + padding: 0; + background: none; + border: none; + color: var(--blue); + font-family: inherit; + font-size: inherit; + cursor: pointer; +} +.ap-fold:hover { + text-decoration: underline; +} +.ap-fold:focus-visible { + outline: 2px solid var(--blue); + outline-offset: 1px; +} diff --git a/apps/kimi-web/test/agent-event-projector.test.ts b/apps/kimi-web/test/agent-event-projector.test.ts index 5ad23bb98c..a01d6f83ce 100644 --- a/apps/kimi-web/test/agent-event-projector.test.ts +++ b/apps/kimi-web/test/agent-event-projector.test.ts @@ -18,14 +18,9 @@ describe('subagentProgressText', () => { expect(text).not.toContain('"command"'); }); - it('formats a tool result', () => { - expect(subagentProgressText('tool.result', { name: 'read' })).toContain('Finished'); - }); - - it('strips a trailing numeric index from tool.result names', () => { - const text = subagentProgressText('tool.result', { name: 'Read_0' }); - expect(text).toContain('Finished'); - expect(text).not.toContain('Read_0'); + it('drops tool.result lines as noise', () => { + expect(subagentProgressText('tool.result', { name: 'read' })).toBeNull(); + expect(subagentProgressText('tool.result', { name: 'Read_0' })).toBeNull(); }); it('returns tool.progress update text', () => {