diff --git a/apps/mobile/src/components/agents/tool-card-display.test.ts b/apps/mobile/src/components/agents/tool-card-display.test.ts index bea755cc0d..aa53c491fe 100644 --- a/apps/mobile/src/components/agents/tool-card-display.test.ts +++ b/apps/mobile/src/components/agents/tool-card-display.test.ts @@ -320,6 +320,40 @@ describe('getToolDisplay badge rules', () => { ).toBeUndefined(); expect(getDisplay(makeToolPart('grep', running({ pattern: 'foo' }))).badge).toBeUndefined(); }); + + it('counts grep sheet rows, not the Found caption', () => { + expect( + getDisplay( + makeToolPart('grep', completed({ pattern: 'x' }, 'Found 1 match\nsrc/a.ts:\n Line 1: x')) + ).badge + ).toBe('2 matches'); + }); + + it('omits the grep badge when only a Found caption exists', () => { + expect( + getDisplay(makeToolPart('grep', completed({ pattern: 'x' }, 'Found 1 match'))).badge + ).toBeUndefined(); + }); + + it('counts glob sheet rows, not the Found caption', () => { + expect( + getDisplay( + makeToolPart( + 'glob', + completed( + { pattern: 'src/**/*.ts' }, + 'Found 2 file(s) matching "src/**/*.ts":\nsrc/a.ts\nsrc/b.ts' + ) + ) + ).badge + ).toBe('2 files'); + }); + + it('omits the glob badge when only a Found caption exists', () => { + expect( + getDisplay(makeToolPart('glob', completed({ pattern: 'src/**/*.ts' }, 'Found 1 file'))).badge + ).toBeUndefined(); + }); }); describe('toolPartHasDetails', () => { diff --git a/apps/mobile/src/components/agents/tool-card-display.ts b/apps/mobile/src/components/agents/tool-card-display.ts index 3f7839b3c3..07a95751a4 100644 --- a/apps/mobile/src/components/agents/tool-card-display.ts +++ b/apps/mobile/src/components/agents/tool-card-display.ts @@ -8,6 +8,7 @@ import { truncateText, } from './tool-card-utils'; import { listPatchFilePaths } from './tool-patch-model'; +import { buildResultRowsModel } from './tool-list-model'; export type ToolDisplay = { title: string; @@ -15,11 +16,8 @@ export type ToolDisplay = { badge?: string; }; -function countOutputLines(output: string): number { - if (output.length === 0) { - return 0; - } - return output.split('\n').filter(line => line.trim().length > 0).length; +function countResultRows(output: string, kind: 'grep' | 'glob'): number { + return buildResultRowsModel(output, kind).rows.length; } /** @@ -65,8 +63,8 @@ export function getToolDisplay(part: ToolPart): ToolDisplay { case 'glob': { const pattern = typeof input.pattern === 'string' ? input.pattern : ''; const output = status === 'completed' ? part.state.output : undefined; - const matchCount = output ? countOutputLines(output) : undefined; - const badge = matchCount !== undefined ? `${matchCount} files` : undefined; + const matchCount = output ? countResultRows(output, 'glob') : undefined; + const badge = matchCount !== undefined && matchCount > 0 ? `${matchCount} files` : undefined; return { title: 'glob', subtitle: pattern || 'glob', badge }; } case 'grep': { @@ -77,8 +75,9 @@ export function getToolDisplay(part: ToolPart): ToolDisplay { subtitle += ` (${include})`; } const output = status === 'completed' ? part.state.output : undefined; - const matchCount = output ? countOutputLines(output) : undefined; - const badge = matchCount !== undefined ? `${matchCount} matches` : undefined; + const matchCount = output ? countResultRows(output, 'grep') : undefined; + const badge = + matchCount !== undefined && matchCount > 0 ? `${matchCount} matches` : undefined; return { title: 'grep', subtitle, badge }; } case 'list': {