diff --git a/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx b/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx index 30879b13b3..586ce89ab2 100644 --- a/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx @@ -357,9 +357,8 @@ describe('DenseToolMessage', () => { await waitUntilReady(); const output = lastFrame(); expect(output).toContain('→ Found 2 matches'); - // Matches are rendered in a secondary list for high-signal summaries - expect(output).toContain('file1.ts:10: match 1'); - expect(output).toContain('file2.ts:20: match 2'); + // Matches should no longer be rendered in dense mode to keep it compact + expect(output).not.toContain('file1.ts:10: match 1'); expect(output).toMatchSnapshot(); }); @@ -400,9 +399,8 @@ describe('DenseToolMessage', () => { const output = lastFrame(); expect(output).toContain('Attempting to read files from **/*.ts'); expect(output).toContain('→ Read 3 file(s) (1 ignored)'); - expect(output).toContain('file1.ts'); - expect(output).toContain('file2.ts'); - expect(output).toContain('file3.ts'); + // File lists should no longer be rendered in dense mode + expect(output).not.toContain('file1.ts'); expect(output).toMatchSnapshot(); }); @@ -477,6 +475,28 @@ describe('DenseToolMessage', () => { expect(output).toMatchSnapshot(); }); + it('truncates long description but preserves tool name (< 25 chars)', async () => { + const longDescription = + 'This is a very long description that should definitely be truncated because it exceeds the available terminal width and we want to see how it behaves.'; + const toolName = 'tool-name-is-24-chars-!!'; // Exactly 24 chars + const { lastFrame, waitUntilReady } = await renderWithProviders( + , + ); + await waitUntilReady(); + const output = lastFrame(); + + // Tool name should be fully present (it plus one space is exactly 25, fitting the maxWidth) + expect(output).toContain(toolName); + // Description should be present but truncated + expect(output).toContain('This is a'); + expect(output).toMatchSnapshot(); + }); + describe('Toggleable Diff View (Alternate Buffer)', () => { const diffResult: FileDiff = { fileDiff: '@@ -1,1 +1,1 @@\n-old line\n+new line', diff --git a/packages/cli/src/ui/components/messages/DenseToolMessage.tsx b/packages/cli/src/ui/components/messages/DenseToolMessage.tsx index 6e81d07931..f5e4b31c66 100644 --- a/packages/cli/src/ui/components/messages/DenseToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/DenseToolMessage.tsx @@ -72,27 +72,6 @@ const hasPayload = (res: unknown): res is PayloadResult => { return typeof value === 'string'; }; -const RenderItemsList: React.FC<{ - items?: string[]; - maxVisible?: number; -}> = ({ items, maxVisible = 20 }) => { - if (!items || items.length === 0) return null; - return ( - - {items.slice(0, maxVisible).map((item, i) => ( - - {item} - - ))} - {items.length > maxVisible && ( - - ... and {items.length - maxVisible} more - - )} - - ); -}; - function getFileOpData( diff: FileDiff, status: CoreToolCallStatus, @@ -188,8 +167,6 @@ function getFileOpData( } function getReadManyFilesData(result: ReadManyFilesResult): ViewParts { - const items = result.files ?? []; - const maxVisible = 10; const includePatterns = result.include?.join(', ') ?? ''; const description = ( @@ -198,18 +175,12 @@ function getReadManyFilesData(result: ReadManyFilesResult): ViewParts { ); const skippedCount = result.skipped?.length ?? 0; - const summaryStr = `Read ${items.length} file(s)${ + const summaryStr = `Read ${result.files.length} file(s)${ skippedCount > 0 ? ` (${skippedCount} ignored)` : '' }`; const summary = → {summaryStr}; - const hasItems = items.length > 0; - const payload = hasItems ? ( - - {hasItems && } - - ) : undefined; - return { description, summary, payload }; + return { description, summary, payload: undefined }; } function getListDirectoryData( @@ -258,20 +229,11 @@ function getGenericSuccessData( ); } else if (isGrepResult(resultDisplay)) { - summary = → {resultDisplay.summary}; - const matches = resultDisplay.matches; - if (matches.length > 0) { - payload = ( - - `${m.filePath}:${m.lineNumber}: ${m.line.trim()}`, - )} - maxVisible={10} - /> - - ); - } + summary = ( + + → {resultDisplay.summary} + + ); } else if (isTodoList(resultDisplay)) { summary = ( @@ -488,15 +450,18 @@ export const DenseToolMessage: React.FC = (props) => { return ( - - - - {name}{' '} - - - - {description} + + + + + {name}{' '} + + + + {description} + + {summary && ( { // TODO(24053): Usage of type guards makes this class too aware of internals if (isFileDiff(res)) return true; if (tool.confirmationDetails?.type === 'edit') return true; - if (isGrepResult(res) && res.matches.length > 0) return true; - - // ReadManyFilesResult check (has 'include' and 'files') - if (isListResult(res) && 'include' in res) { - const includeProp = (res as { include?: unknown }).include; - if (Array.isArray(includeProp) && res.files.length > 0) { - return true; - } - } // Generic summary/payload pattern if ( diff --git a/packages/cli/src/ui/components/messages/__snapshots__/DenseToolMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/DenseToolMessage.test.tsx.snap index d08b84c1a9..01bb88b00e 100644 --- a/packages/cli/src/ui/components/messages/__snapshots__/DenseToolMessage.test.tsx.snap +++ b/packages/cli/src/ui/components/messages/__snapshots__/DenseToolMessage.test.tsx.snap @@ -51,10 +51,6 @@ exports[`DenseToolMessage > renders correctly for Errored Edit tool 1`] = ` exports[`DenseToolMessage > renders correctly for ReadManyFiles results 1`] = ` " ✓ test-tool Attempting to read files from **/*.ts → Read 3 file(s) (1 ignored) - - file1.ts - file2.ts - file3.ts " `; @@ -110,9 +106,6 @@ exports[`DenseToolMessage > renders correctly for file diff results with stats 1 exports[`DenseToolMessage > renders correctly for grep results 1`] = ` " ✓ test-tool Test description → Found 2 matches - - file1.ts:10: match 1 - file2.ts:20: match 2 " `; @@ -136,6 +129,12 @@ exports[`DenseToolMessage > renders generic output message for unknown object re " `; +exports[`DenseToolMessage > truncates long description but preserves tool name (< 25 chars) 1`] = ` +" ✓ tool-name-is-24-chars-!! This is a very long description that should definitely be truncated … + → Success result +" +`; + exports[`DenseToolMessage > truncates long string results 1`] = ` " ✓ test-tool Test description → AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA… diff --git a/packages/core/src/tools/ls.test.ts b/packages/core/src/tools/ls.test.ts index 372de8e8a6..e9a684719e 100644 --- a/packages/core/src/tools/ls.test.ts +++ b/packages/core/src/tools/ls.test.ts @@ -132,7 +132,7 @@ describe('LSTool', () => { expect(result.llmContent).toContain('[DIR] subdir'); expect(result.llmContent).toContain('file1.txt'); expect(result.returnDisplay).toEqual({ - summary: 'Listed 2 item(s).', + summary: 'Found 2 item(s).', files: ['[DIR] subdir', 'file1.txt'], }); }); @@ -150,7 +150,7 @@ describe('LSTool', () => { expect(result.llmContent).toContain('secondary-file.txt'); expect(result.returnDisplay).toEqual({ - summary: 'Listed 1 item(s).', + summary: 'Found 1 item(s).', files: expect.any(Array), }); }); @@ -178,7 +178,7 @@ describe('LSTool', () => { expect(result.llmContent).toContain('file1.txt'); expect(result.llmContent).not.toContain('file2.log'); expect(result.returnDisplay).toEqual({ - summary: 'Listed 1 item(s).', + summary: 'Found 1 item(s).', files: expect.any(Array), }); }); @@ -195,7 +195,7 @@ describe('LSTool', () => { expect(result.llmContent).not.toContain('file2.log'); // .git is always ignored by default. expect(result.returnDisplay).toEqual( - expect.objectContaining({ summary: 'Listed 2 item(s). (2 ignored)' }), + expect.objectContaining({ summary: 'Found 2 item(s). (2 ignored)' }), ); }); @@ -212,7 +212,7 @@ describe('LSTool', () => { expect(result.llmContent).toContain('file1.txt'); expect(result.llmContent).not.toContain('file2.log'); expect(result.returnDisplay).toEqual( - expect.objectContaining({ summary: 'Listed 2 item(s). (1 ignored)' }), + expect.objectContaining({ summary: 'Found 2 item(s). (1 ignored)' }), ); }); @@ -301,7 +301,7 @@ describe('LSTool', () => { expect(result.llmContent).toContain('file1.txt'); expect(result.llmContent).not.toContain('problematic.txt'); expect(result.returnDisplay).toEqual({ - summary: 'Listed 1 item(s).', + summary: 'Found 1 item(s).', files: expect.any(Array), }); @@ -364,7 +364,7 @@ describe('LSTool', () => { expect(result.llmContent).toContain('secondary-file.txt'); expect(result.returnDisplay).toEqual({ - summary: 'Listed 1 item(s).', + summary: 'Found 1 item(s).', files: expect.any(Array), }); }); diff --git a/packages/core/src/tools/ls.ts b/packages/core/src/tools/ls.ts index b8e2e6a803..249a28372b 100644 --- a/packages/core/src/tools/ls.ts +++ b/packages/core/src/tools/ls.ts @@ -276,7 +276,7 @@ class LSToolInvocation extends BaseToolInvocation { resultMessage = appendJitContext(resultMessage, jitContext); } - let displayMessage = `Listed ${entries.length} item(s).`; + let displayMessage = `Found ${entries.length} item(s).`; if (ignoredCount > 0) { displayMessage += ` (${ignoredCount} ignored)`; }