diff --git a/docs/cli/settings.md b/docs/cli/settings.md
index c7ae96b21e..6db1110f2b 100644
--- a/docs/cli/settings.md
+++ b/docs/cli/settings.md
@@ -105,7 +105,7 @@ they appear in the UI.
| UI Label | Setting | Description | Default |
| ------------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Memory Discovery Max Dirs | `context.discoveryMaxDirs` | Maximum number of directories to search for memory. | `200` |
-| Load Memory From Include Directories | `context.loadMemoryFromIncludeDirectories` | Controls how /memory refresh loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used. | `false` |
+| Load Memory From Include Directories | `context.loadMemoryFromIncludeDirectories` | Controls how /memory reload loads GEMINI.md files. When true, include directories are scanned; when false, only the current directory is used. | `false` |
| Respect .gitignore | `context.fileFiltering.respectGitIgnore` | Respect .gitignore files when searching. | `true` |
| Respect .geminiignore | `context.fileFiltering.respectGeminiIgnore` | Respect .geminiignore files when searching. | `true` |
| Enable Recursive File Search | `context.fileFiltering.enableRecursiveFileSearch` | Enable recursive file search functionality when completing @ references in the prompt. | `true` |
diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md
index b85b873162..2742c03023 100644
--- a/docs/reference/configuration.md
+++ b/docs/reference/configuration.md
@@ -1070,7 +1070,7 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `[]`
- **`context.loadMemoryFromIncludeDirectories`** (boolean):
- - **Description:** Controls how /memory refresh loads GEMINI.md files. When
+ - **Description:** Controls how /memory reload loads GEMINI.md files. When
true, include directories are scanned; when false, only the current
directory is used.
- **Default:** `false`
@@ -1420,8 +1420,8 @@ their corresponding top-level category object in your `settings.json` file.
- **Requires restart:** Yes
- **`experimental.gemmaModelRouter.enabled`** (boolean):
- - **Description:** Enable the Gemma Model Router. Requires a local endpoint
- serving Gemma via the Gemini API using LiteRT-LM shim.
+ - **Description:** Enable the Gemma Model Router (experimental). Requires a
+ local endpoint serving Gemma via the Gemini API using LiteRT-LM shim.
- **Default:** `false`
- **Requires restart:** Yes
diff --git a/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx b/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx
index 68feb0ed1e..bbc72ce2b4 100644
--- a/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx
@@ -118,7 +118,9 @@ describe('DenseToolMessage', () => {
name="Edit"
status={CoreToolCallStatus.AwaitingApproval}
resultDisplay={undefined}
- confirmationDetails={confirmationDetails as SerializableConfirmationDetails}
+ confirmationDetails={
+ confirmationDetails as SerializableConfirmationDetails
+ }
/>,
{ useAlternateBuffer: false },
);
@@ -194,7 +196,9 @@ describe('DenseToolMessage', () => {
name="Edit"
status={CoreToolCallStatus.Cancelled}
resultDisplay={undefined}
- confirmationDetails={confirmationDetails as unknown as SerializableConfirmationDetails}
+ confirmationDetails={
+ confirmationDetails as unknown as SerializableConfirmationDetails
+ }
/>,
{ useAlternateBuffer: false },
);
@@ -391,7 +395,7 @@ describe('DenseToolMessage', () => {
,
);
await waitUntilReady();
@@ -497,4 +501,3 @@ describe('DenseToolMessage', () => {
});
});
});
-
diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
index 9063606146..8edda13bdf 100644
--- a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
+++ b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
@@ -52,6 +52,7 @@ index 0000000..e69de29
maxWidth: 80,
theme: undefined,
settings: expect.anything(),
+ disableColor: false,
}),
);
});
@@ -84,6 +85,7 @@ index 0000000..e69de29
maxWidth: 80,
theme: undefined,
settings: expect.anything(),
+ disableColor: false,
}),
);
});
@@ -112,6 +114,7 @@ index 0000000..e69de29
maxWidth: 80,
theme: undefined,
settings: expect.anything(),
+ disableColor: false,
}),
);
});
diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.compact.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.compact.test.tsx
index fcc64a0fe2..a75e442b80 100644
--- a/packages/cli/src/ui/components/messages/ToolGroupMessage.compact.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.compact.test.tsx
@@ -1,3 +1,8 @@
+/**
+ * @license
+ * Copyright 2026 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
import { renderWithProviders } from '../../../test-utils/render.js';
import { ToolGroupMessage } from './ToolGroupMessage.js';
@@ -7,73 +12,92 @@ import {
READ_FILE_DISPLAY_NAME,
} from '@google/gemini-cli-core';
import { expect, it, describe } from 'vitest';
+import type { IndividualToolCallDisplay } from '../../types.js';
+import type { LoadedSettings } from '../../../config/settings.js';
describe('ToolGroupMessage Compact Rendering', () => {
const defaultProps = {
- item: {
- id: '1',
- role: 'assistant',
- content: '',
+ item: {
+ id: '1',
+ role: 'assistant',
+ content: '',
timestamp: new Date(),
type: 'help' as const, // Adding type property to satisfy HistoryItem type
},
terminalWidth: 80,
};
- const compactSettings = {
+ const compactSettings: LoadedSettings = {
merged: {
ui: {
compactToolOutput: true,
},
},
- };
+ sources: [],
+ } as unknown as LoadedSettings; // Test mock of settings
it('renders consecutive compact tools without empty lines between them', async () => {
- const toolCalls = [
+ const toolCalls: IndividualToolCallDisplay[] = [
{
callId: 'call1',
name: LS_DISPLAY_NAME,
status: CoreToolCallStatus.Success,
resultDisplay: 'file1.txt\nfile2.txt',
+ description: 'Listing files',
+ confirmationDetails: undefined,
+ isClientInitiated: true,
+ parentCallId: undefined,
},
{
callId: 'call2',
name: LS_DISPLAY_NAME,
status: CoreToolCallStatus.Success,
resultDisplay: 'file3.txt',
+ description: 'Listing files',
+ confirmationDetails: undefined,
+ isClientInitiated: true,
+ parentCallId: undefined,
},
];
const { lastFrame, waitUntilReady } = renderWithProviders(
- ,
- { settings: compactSettings as any }
+ ,
+ { settings: compactSettings },
);
await waitUntilReady();
const output = lastFrame();
-
+
expect(output).toMatchSnapshot();
});
it('does not add an extra empty line between a compact tool and a standard tool', async () => {
- const toolCalls = [
+ const toolCalls: IndividualToolCallDisplay[] = [
{
callId: 'call1',
name: LS_DISPLAY_NAME,
status: CoreToolCallStatus.Success,
resultDisplay: 'file1.txt',
+ description: 'Listing files',
+ confirmationDetails: undefined,
+ isClientInitiated: true,
+ parentCallId: undefined,
},
{
callId: 'call2',
name: 'non-compact-tool',
status: CoreToolCallStatus.Success,
resultDisplay: 'some large output',
+ description: 'Doing something',
+ confirmationDetails: undefined,
+ isClientInitiated: true,
+ parentCallId: undefined,
},
];
const { lastFrame, waitUntilReady } = renderWithProviders(
- ,
- { settings: compactSettings as any }
+ ,
+ { settings: compactSettings },
);
await waitUntilReady();
@@ -82,24 +106,32 @@ describe('ToolGroupMessage Compact Rendering', () => {
});
it('does not add an extra empty line if a compact tool has a dense payload', async () => {
- const toolCalls = [
+ const toolCalls: IndividualToolCallDisplay[] = [
{
callId: 'call1',
name: LS_DISPLAY_NAME,
status: CoreToolCallStatus.Success,
resultDisplay: 'file1.txt',
+ description: 'Listing files',
+ confirmationDetails: undefined,
+ isClientInitiated: true,
+ parentCallId: undefined,
},
{
callId: 'call2',
name: READ_FILE_DISPLAY_NAME,
status: CoreToolCallStatus.Success,
resultDisplay: { summary: 'read file', payload: 'file content' }, // Dense payload
+ description: 'Reading file',
+ confirmationDetails: undefined,
+ isClientInitiated: true,
+ parentCallId: undefined,
},
];
const { lastFrame, waitUntilReady } = renderWithProviders(
- ,
- { settings: compactSettings as any }
+ ,
+ { settings: compactSettings },
);
await waitUntilReady();
@@ -108,24 +140,32 @@ describe('ToolGroupMessage Compact Rendering', () => {
});
it('does not add an extra empty line between a standard tool and a compact tool', async () => {
- const toolCalls = [
+ const toolCalls: IndividualToolCallDisplay[] = [
{
callId: 'call1',
name: 'non-compact-tool',
status: CoreToolCallStatus.Success,
resultDisplay: 'some large output',
+ description: 'Doing something',
+ confirmationDetails: undefined,
+ isClientInitiated: true,
+ parentCallId: undefined,
},
{
callId: 'call2',
name: LS_DISPLAY_NAME,
status: CoreToolCallStatus.Success,
resultDisplay: 'file1.txt',
+ description: 'Listing files',
+ confirmationDetails: undefined,
+ isClientInitiated: true,
+ parentCallId: undefined,
},
];
const { lastFrame, waitUntilReady } = renderWithProviders(
- ,
- { settings: compactSettings as any }
+ ,
+ { settings: compactSettings },
);
await waitUntilReady();
diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
index eff418a609..db72522388 100644
--- a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
@@ -5,6 +5,7 @@
*/
import { renderWithProviders } from '../../../test-utils/render.js';
+import { act } from 'react';
import { describe, it, expect, vi, afterEach } from 'vitest';
import { ToolGroupMessage } from './ToolGroupMessage.js';
import type {
@@ -94,7 +95,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -119,7 +122,9 @@ describe('', () => {
);
// Should now hide confirming tools (to avoid duplication with Global Queue)
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -139,7 +144,9 @@ describe('', () => {
{ config: baseMockConfig, settings: fullVerbositySettings },
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
const output = lastFrame();
expect(output).toMatchSnapshot('canceled_tool');
unmount();
@@ -184,7 +191,9 @@ describe('', () => {
},
);
// pending-tool should now be visible
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
const output = lastFrame();
expect(output).toContain('successful-tool');
expect(output).toContain('pending-tool');
@@ -223,7 +232,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
const output = lastFrame();
expect(output).toContain('successful-tool');
expect(output).not.toContain('error-tool');
@@ -257,7 +268,9 @@ describe('', () => {
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
const output = lastFrame();
expect(output).toContain('client-error-tool');
unmount();
@@ -302,7 +315,9 @@ describe('', () => {
},
);
// write_file (Pending) should now be visible
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
const output = lastFrame();
expect(output).toContain('read_file');
expect(output).toContain('run_shell_command');
@@ -348,7 +363,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -382,7 +399,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -405,7 +424,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -444,7 +465,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -475,7 +498,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -530,7 +555,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -560,7 +587,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -590,7 +619,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -633,7 +664,9 @@ describe('', () => {
},
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -684,7 +717,9 @@ describe('', () => {
,
{ config: baseMockConfig, settings: fullVerbositySettings },
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
if (shouldHide) {
expect(lastFrame({ allowEmpty: true })).toBe('');
@@ -715,7 +750,9 @@ describe('', () => {
{ config: baseMockConfig, settings: fullVerbositySettings },
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -743,7 +780,9 @@ describe('', () => {
{ config: baseMockConfig, settings: fullVerbositySettings },
);
// AskUser tools in progress are rendered by AskUserDialog, so we expect nothing.
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -774,7 +813,9 @@ describe('', () => {
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -797,7 +838,9 @@ describe('', () => {
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).not.toBe('');
unmount();
});
@@ -828,7 +871,9 @@ describe('', () => {
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -861,7 +906,9 @@ describe('', () => {
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -956,7 +1003,9 @@ describe('', () => {
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
const output = lastFrame();
expect(output).toContain('visible-tool');
expect(output).not.toContain('hidden-error-0');
@@ -982,7 +1031,9 @@ describe('', () => {
},
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
expect(lastFrame({ allowEmpty: true })).not.toBe('');
unmount();
});
@@ -1020,7 +1071,9 @@ describe('', () => {
{ config: baseMockConfig, settings: fullVerbositySettings },
);
- await waitUntilReady();
+ await act(async () => {
+ await waitUntilReady();
+ });
if (visible) {
expect(lastFrame()).toContain(name);
diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
index f9f8b5c67a..efbf2375f4 100644
--- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
@@ -17,6 +17,7 @@ import {
mapCoreStatusToDisplayStatus,
isFileDiff,
isGrepResult,
+ isListResult,
} from '../../types.js';
import { ToolMessage } from './ToolMessage.js';
import { ShellToolMessage } from './ShellToolMessage.js';
@@ -78,14 +79,7 @@ export const hasDensePayload = (tool: IndividualToolCallDisplay): boolean => {
if (isGrepResult(res) && (res.matches?.length ?? 0) > 0) return true;
// ReadManyFilesResult check (has 'include' and 'files')
- if (
- typeof res === 'object' &&
- res !== null &&
- 'include' in res &&
- 'files' in res &&
- Array.isArray((res as any).files) &&
- (res as any).files.length > 0
- ) {
+ if (isListResult(res) && 'include' in res && (res.files?.length ?? 0) > 0) {
return true;
}
@@ -213,14 +207,14 @@ export const ToolGroupMessage: React.FC = ({
height += 1; // Base height for compact tool
// Spacing logic (matching marginTop)
if (isFirst) {
- height += borderTopOverride ?? true ? 1 : 0;
+ height += (borderTopOverride ?? true) ? 1 : 0;
} else if (!prevIsCompact) {
height += 1;
}
} else {
height += 3; // Static overhead for standard tool
if (isFirst) {
- height += borderTopOverride ?? true ? 1 : 0;
+ height += (borderTopOverride ?? true) ? 1 : 0;
} else {
height += 1; // marginTop is always 1 for non-compact tools (not first)
}
@@ -271,7 +265,7 @@ export const ToolGroupMessage: React.FC = ({
*/
width={terminalWidth}
paddingRight={TOOL_MESSAGE_HORIZONTAL_MARGIN}
- marginBottom={borderBottomOverride ?? true ? 1 : 0}
+ marginBottom={(borderBottomOverride ?? true) ? 1 : 0}
>
{visibleToolCalls.map((tool, index) => {
const isFirst = index === 0;
@@ -291,7 +285,7 @@ export const ToolGroupMessage: React.FC = ({
let marginTop = 0;
if (isFirst) {
- marginTop = borderTopOverride ?? true ? 1 : 0;
+ marginTop = (borderTopOverride ?? true) ? 1 : 0;
} else if (!(isCompact && prevIsCompact)) {
marginTop = 1;
}
diff --git a/packages/cli/src/ui/components/messages/ToolResultDisplay.tsx b/packages/cli/src/ui/components/messages/ToolResultDisplay.tsx
index 0bbe3446e0..4c8e590147 100644
--- a/packages/cli/src/ui/components/messages/ToolResultDisplay.tsx
+++ b/packages/cli/src/ui/components/messages/ToolResultDisplay.tsx
@@ -15,6 +15,7 @@ import {
type AnsiOutput,
type AnsiLine,
isSubagentProgress,
+ isStructuredToolResult,
} from '@google/gemini-cli-core';
import { useUIState } from '../../contexts/UIStateContext.js';
import { tryParseJSON } from '../../../utils/jsonoutput.js';
@@ -118,7 +119,28 @@ export const ToolResultDisplay: React.FC = ({
{contentData}
);
- } else if (typeof contentData === 'object' && 'fileDiff' in contentData) {
+ } else if (isStructuredToolResult(contentData)) {
+ if (renderOutputAsMarkdown) {
+ content = (
+
+ );
+ } else {
+ content = (
+
+ {contentData.summary}
+
+ );
+ }
+ } else if (
+ typeof contentData === 'object' &&
+ contentData !== null &&
+ 'fileDiff' in contentData
+ ) {
content = (
does not add an extra empty line between a compact tool and a standard tool 1`] = `
"
- ✓ ReadFolder → file1.txt
+ ✓ ReadFolder Listing files → file1.txt
╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ non-compact-tool │
+│ ✓ non-compact-tool Doing something │
│ │
│ some large output │
╰──────────────────────────────────────────────────────────────────────────╯
@@ -15,25 +15,25 @@ exports[`ToolGroupMessage Compact Rendering > does not add an extra empty line b
exports[`ToolGroupMessage Compact Rendering > does not add an extra empty line between a standard tool and a compact tool 1`] = `
"
╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ non-compact-tool │
+│ ✓ non-compact-tool Doing something │
│ │
│ some large output │
╰──────────────────────────────────────────────────────────────────────────╯
- ✓ ReadFolder → file1.txt
+ ✓ ReadFolder Listing files → file1.txt
"
`;
exports[`ToolGroupMessage Compact Rendering > does not add an extra empty line if a compact tool has a dense payload 1`] = `
"
- ✓ ReadFolder → file1.txt
- ✓ ReadFile → read file
+ ✓ ReadFolder Listing files → file1.txt
+ ✓ ReadFile Reading file → read file
"
`;
exports[`ToolGroupMessage Compact Rendering > renders consecutive compact tools without empty lines between them 1`] = `
"
- ✓ ReadFolder → file1.txt file2.txt
- ✓ ReadFolder → file3.txt
+ ✓ ReadFolder Listing files → file1.txt file2.txt
+ ✓ ReadFolder Listing files → file3.txt
"
`;
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap
index 710c24ae75..9d332387b3 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolGroupMessage.test.tsx.snap
@@ -36,6 +36,7 @@ exports[` > Border Color Logic > uses gray border when all t
│ ✓ test-tool A tool for testing │
│ │
│ Test result │
+
│ │
│ ✓ another-tool A tool for testing │
│ │
@@ -66,10 +67,10 @@ exports[` > Golden Snapshots > renders canceled tool calls >
exports[` > Golden Snapshots > renders empty tool calls array 1`] = `""`;
exports[` > Golden Snapshots > renders header when scrolled 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ tool-1 Description 1. This is a long description that will need to b… │
+"│ ✓ tool-1 Description 1. This is a long description that will need to b… │
│──────────────────────────────────────────────────────────────────────────│
-│ │ ▄
+
+│ │
│ ✓ tool-2 Description 2 │ █
│ │ █
│ line1 │ █
@@ -85,6 +86,7 @@ exports[` > Golden Snapshots > renders mixed tool calls incl
│ ✓ read_file Read a file │
│ │
│ Test result │
+
│ │
│ ⊶ run_shell_command Run command │
│ │
@@ -103,6 +105,7 @@ exports[` > Golden Snapshots > renders multiple tool calls w
│ ✓ successful-tool This tool succeeded │
│ │
│ Test result │
+
│ │
│ o pending-tool This tool is pending │
│ │
@@ -152,6 +155,7 @@ exports[` > Golden Snapshots > renders with limited terminal
│ ✓ tool-with-result Tool with output │
│ │
│ This is a long result that might need height constraints │
+
│ │
│ ✓ another-tool Another tool │
│ │
@@ -176,10 +180,12 @@ exports[` > Height Calculation > calculates available height
│ ✓ test-tool A tool for testing │
│ │
│ Result 1 │
+
│ │
│ ✓ test-tool A tool for testing │
│ │
│ Result 2 │
+
│ │
│ ✓ test-tool A tool for testing │
│ │
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolStickyHeaderRegression.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolStickyHeaderRegression.test.tsx.snap
index da341abe37..eb4531299d 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ToolStickyHeaderRegression.test.tsx.snap
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolStickyHeaderRegression.test.tsx.snap
@@ -40,7 +40,7 @@ exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessa
"│ │
│ ✓ tool-2 Description for tool-2 │
│────────────────────────────────────────────────────────────────────────│
-│ c2-09 │ ▄
-│ c2-10 │ ▀
+│ c2-08 │ ▄
+│ c2-09 │ ▀
"
`;
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.ts b/packages/cli/src/ui/hooks/useGeminiStream.ts
index b314f1382b..4aa05fb57a 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.ts
+++ b/packages/cli/src/ui/hooks/useGeminiStream.ts
@@ -75,9 +75,7 @@ import type { UseHistoryManagerReturn } from './useHistoryManager.js';
import { useLogger } from './useLogger.js';
import { SHELL_COMMAND_NAME } from '../constants.js';
import { mapToDisplay as mapTrackedToolCallsToDisplay } from './toolMapping.js';
-import {
- isCompactTool,
-} from '../components/messages/ToolGroupMessage.js';
+import { isCompactTool } from '../components/messages/ToolGroupMessage.js';
import {
useToolScheduler,
type TrackedToolCall,
@@ -305,13 +303,11 @@ export const useGeminiStream = (
// If the first tool in this push is non-compact but follows a compact tool,
// we must start a new border group.
const currentIsCompact = isCompactTool(
- mapTrackedToolCallsToDisplay(firstToolToPush as TrackedToolCall)
- .tools[0],
+ mapTrackedToolCallsToDisplay(firstToolToPush).tools[0],
isCompactModeEnabled,
);
const prevWasCompact = isCompactTool(
- mapTrackedToolCallsToDisplay(prevTool as TrackedToolCall)
- .tools[0],
+ mapTrackedToolCallsToDisplay(prevTool).tools[0],
isCompactModeEnabled,
);
if (!currentIsCompact && prevWasCompact) {
@@ -356,9 +352,7 @@ export const useGeminiStream = (
}
// Handle tool response submission immediately when tools complete
- await handleCompletedTools(
- completedToolCallsFromScheduler as TrackedToolCall[],
- );
+ await handleCompletedTools(completedToolCallsFromScheduler);
}
},
config,
diff --git a/packages/core/src/tools/read-many-files.test.ts b/packages/core/src/tools/read-many-files.test.ts
index 6a526d2b62..dd9d146c97 100644
--- a/packages/core/src/tools/read-many-files.test.ts
+++ b/packages/core/src/tools/read-many-files.test.ts
@@ -31,6 +31,7 @@ import {
import * as glob from 'glob';
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
import { GEMINI_IGNORE_FILE_NAME } from '../config/constants.js';
+import type { ReadManyFilesResult } from './tools.js';
vi.mock('glob', { spy: true });
@@ -277,7 +278,7 @@ describe('ReadManyFilesTool', () => {
`--- ${expectedPath} ---\n\nContent of file1\n\n`,
`\n--- End of content ---`,
]);
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **1 file(s)**',
);
});
@@ -301,7 +302,7 @@ describe('ReadManyFilesTool', () => {
c.includes(`--- ${expectedPath2} ---\n\nContent2\n\n`),
),
).toBe(true);
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **2 file(s)**',
);
});
@@ -327,7 +328,7 @@ describe('ReadManyFilesTool', () => {
),
).toBe(true);
expect(content.find((c) => c.includes('sub/data.json'))).toBeUndefined();
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **2 file(s)**',
);
});
@@ -347,7 +348,7 @@ describe('ReadManyFilesTool', () => {
expect(
content.find((c) => c.includes('src/main.test.ts')),
).toBeUndefined();
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **1 file(s)**',
);
});
@@ -359,7 +360,7 @@ describe('ReadManyFilesTool', () => {
expect(result.llmContent).toEqual([
'No files matching the criteria were found or all were skipped.',
]);
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'No files were read and concatenated based on the criteria.',
);
});
@@ -379,7 +380,7 @@ describe('ReadManyFilesTool', () => {
expect(
content.find((c) => c.includes('node_modules/some-lib/index.js')),
).toBeUndefined();
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **1 file(s)**',
);
});
@@ -406,7 +407,7 @@ describe('ReadManyFilesTool', () => {
c.includes(`--- ${expectedPath2} ---\n\napp code\n\n`),
),
).toBe(true);
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **2 file(s)**',
);
});
@@ -430,7 +431,7 @@ describe('ReadManyFilesTool', () => {
},
'\n--- End of content ---',
]);
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **1 file(s)**',
);
});
@@ -471,8 +472,10 @@ describe('ReadManyFilesTool', () => {
c.includes(`--- ${expectedPath} ---\n\ntext notes\n\n`),
),
).toBe(true);
- expect(result.returnDisplay).toContain('**Skipped 1 item(s):**');
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
+ '**Skipped 1 item(s):**',
+ );
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'- `document.pdf` (Reason: asset file (image/pdf/audio) was not explicitly requested by name or extension)',
);
});
@@ -516,9 +519,15 @@ describe('ReadManyFilesTool', () => {
const params = { include: ['foo.bar', 'bar.ts', 'foo.quux'] };
const invocation = tool.build(params);
const result = await invocation.execute(new AbortController().signal);
- expect(result.returnDisplay).not.toContain('foo.bar');
- expect(result.returnDisplay).not.toContain('foo.quux');
- expect(result.returnDisplay).toContain('bar.ts');
+ expect((result.returnDisplay as ReadManyFilesResult).files).not.toContain(
+ 'foo.bar',
+ );
+ expect((result.returnDisplay as ReadManyFilesResult).files).not.toContain(
+ 'foo.quux',
+ );
+ expect((result.returnDisplay as ReadManyFilesResult).files).toContain(
+ 'bar.ts',
+ );
});
it('should read files from multiple workspace directories', async () => {
@@ -594,7 +603,7 @@ describe('ReadManyFilesTool', () => {
c.includes(`--- ${expectedPath2} ---\n\nContent2\n\n`),
),
).toBe(true);
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **2 file(s)**',
);
@@ -646,7 +655,7 @@ Content of receive-detail
`,
`\n--- End of content ---`,
]);
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **1 file(s)**',
);
});
@@ -665,7 +674,7 @@ Content of file[1]
`,
`\n--- End of content ---`,
]);
- expect(result.returnDisplay).toContain(
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
'Successfully read and concatenated content from **1 file(s)**',
);
});
@@ -764,7 +773,9 @@ Content of file[1]
// Should successfully process valid files despite one failure
expect(content.length).toBeGreaterThanOrEqual(3);
- expect(result.returnDisplay).toContain('Successfully read');
+ expect((result.returnDisplay as ReadManyFilesResult).summary).toContain(
+ 'Successfully read',
+ );
// Verify valid files were processed
const expectedPath1 = path.join(tempRootDir, 'valid1.txt');