diff --git a/docs/cli/settings.md b/docs/cli/settings.md
index 9a60f89a53..7faf68de40 100644
--- a/docs/cli/settings.md
+++ b/docs/cli/settings.md
@@ -32,9 +32,10 @@ they appear in the UI.
### Output
-| UI Label | Setting | Description | Default |
-| ------------- | --------------- | ------------------------------------------------------ | -------- |
-| Output Format | `output.format` | The format of the CLI output. Can be `text` or `json`. | `"text"` |
+| UI Label | Setting | Description | Default |
+| ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------- | ----------- |
+| Output Format | `output.format` | The format of the CLI output. Can be `text` or `json`. | `"text"` |
+| Verbose Output History | `output.verbosity` | Show verbose output history. When enabled, output history will include autonomous tool calls, additional logs, etc. | `"verbose"` |
### UI
diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md
index 28578ae364..da28808d40 100644
--- a/docs/get-started/configuration.md
+++ b/docs/get-started/configuration.md
@@ -163,6 +163,12 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `"text"`
- **Values:** `"text"`, `"json"`
+- **`output.verbosity`** (enum):
+ - **Description:** Show verbose output history. When enabled, output history
+ will include autonomous tool calls, additional logs, etc.
+ - **Default:** `"verbose"`
+ - **Values:** `"info"`, `"verbose"`
+
#### `ui`
- **`ui.theme`** (string):
diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts
index 2e53997a5d..95b8f9917c 100644
--- a/packages/cli/src/config/settingsSchema.ts
+++ b/packages/cli/src/config/settingsSchema.ts
@@ -328,6 +328,21 @@ const SETTINGS_SCHEMA = {
{ value: 'json', label: 'JSON' },
],
},
+ // Defined as enum type to support the addition of more verbosity levels
+ verbosity: {
+ type: 'enum',
+ label: 'Verbose Output History',
+ category: 'General',
+ requiresRestart: false,
+ default: 'verbose',
+ description:
+ 'Show verbose output history. When enabled, output history will include autonomous tool calls, additional logs, etc.',
+ showInDialog: true,
+ options: [
+ { value: 'info', label: 'false' },
+ { value: 'verbose', label: 'true' },
+ ],
+ },
},
},
diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx
index fbfa93ac3a..4422930896 100644
--- a/packages/cli/src/ui/AppContainer.tsx
+++ b/packages/cli/src/ui/AppContainer.tsx
@@ -862,7 +862,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
const performMemoryRefresh = useCallback(async () => {
historyManager.addItem(
{
- type: MessageType.INFO,
+ type: MessageType.VERBOSE,
text: 'Refreshing hierarchical memory (GEMINI.md or other context files)...',
},
Date.now(),
@@ -873,7 +873,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
historyManager.addItem(
{
- type: MessageType.INFO,
+ type: MessageType.VERBOSE,
text: `Memory refreshed successfully. ${
memoryContent.length > 0
? `Loaded ${memoryContent.length} characters from ${fileCount} file(s).`
diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
index 1aecb9a0ba..06bdf155c5 100644
--- a/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
+++ b/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
@@ -171,6 +171,18 @@ describe('', () => {
expect(lastFrame()).toContain('Agent powering down. Goodbye!');
});
+ it('renders InfoMessage for "debug" type with gear icon', () => {
+ const item: HistoryItem = {
+ ...baseItem,
+ type: MessageType.DEBUG,
+ text: 'Debug info',
+ };
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ expect(lastFrame()).toContain('⚙ Debug info');
+ });
+
it('should escape ANSI codes in text content', () => {
const historyItem: HistoryItem = {
id: 1,
diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.tsx
index ed399dd38f..15e68909a3 100644
--- a/packages/cli/src/ui/components/HistoryItemDisplay.tsx
+++ b/packages/cli/src/ui/components/HistoryItemDisplay.tsx
@@ -18,6 +18,7 @@ import { GeminiMessageContent } from './messages/GeminiMessageContent.js';
import { CompressionMessage } from './messages/CompressionMessage.js';
import { WarningMessage } from './messages/WarningMessage.js';
import { Box } from 'ink';
+import { theme } from '../semantic-colors.js';
import { AboutBox } from './AboutBox.js';
import { StatsDisplay } from './StatsDisplay.js';
import { ModelStatsDisplay } from './ModelStatsDisplay.js';
@@ -96,6 +97,27 @@ export const HistoryItemDisplay: React.FC = ({
color={itemForDisplay.color}
/>
)}
+ {itemForDisplay.type === 'verbose' && (
+
+ )}
+ {itemForDisplay.type === 'debug' && (
+
+ )}
+ {itemForDisplay.type === 'trace' && (
+
+ )}
{itemForDisplay.type === 'warning' && (
)}
diff --git a/packages/cli/src/ui/components/MainContent.test.tsx b/packages/cli/src/ui/components/MainContent.test.tsx
index 0445b11b4b..395d5acc5b 100644
--- a/packages/cli/src/ui/components/MainContent.test.tsx
+++ b/packages/cli/src/ui/components/MainContent.test.tsx
@@ -7,13 +7,14 @@
import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { MainContent } from './MainContent.js';
-import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { Box, Text } from 'ink';
import type React from 'react';
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
import { ToolCallStatus } from '../types.js';
import { SHELL_COMMAND_NAME } from '../constants.js';
import type { UIState } from '../contexts/UIStateContext.js';
+import type { HistoryItem } from '../types.js';
// Mock dependencies
vi.mock('../contexts/AppContext.js', async () => {
@@ -26,6 +27,23 @@ vi.mock('../contexts/AppContext.js', async () => {
};
});
+const mockSettings = {
+ merged: {
+ output: {
+ verbosity: 'info',
+ },
+ },
+};
+
+vi.mock('../contexts/SettingsContext.js', async (importOriginal) => {
+ const actual =
+ await importOriginal();
+ return {
+ ...actual,
+ useSettings: () => mockSettings,
+ };
+});
+
vi.mock('../hooks/useAlternateBuffer.js', () => ({
useAlternateBuffer: vi.fn(),
}));
@@ -78,6 +96,11 @@ describe('MainContent', () => {
beforeEach(() => {
vi.mocked(useAlternateBuffer).mockReturnValue(false);
+ mockSettings.merged.output.verbosity = 'info';
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
});
it('renders in normal buffer mode', async () => {
@@ -221,4 +244,38 @@ describe('MainContent', () => {
},
);
});
+
+ it('filters out verbose items when verbosity is info', async () => {
+ const history: HistoryItem[] = [
+ { id: 1, type: 'user', text: 'Visible User Message' },
+ { id: 2, type: 'verbose', text: 'Hidden Verbose Log' },
+ ];
+ mockSettings.merged.output.verbosity = 'info';
+
+ const { lastFrame } = renderWithProviders(, {
+ uiState: { ...defaultMockUiState, history } as Partial,
+ });
+ await waitFor(() => expect(lastFrame()).toContain('AppHeader'));
+ const output = lastFrame();
+
+ expect(output).toContain('Visible User Message');
+ expect(output).not.toContain('Hidden Verbose Log');
+ });
+
+ it('shows verbose items when verbosity is verbose', async () => {
+ const history: HistoryItem[] = [
+ { id: 1, type: 'user', text: 'Visible User Message' },
+ { id: 2, type: 'verbose', text: 'Visible Verbose Log' },
+ ];
+ mockSettings.merged.output.verbosity = 'verbose';
+
+ const { lastFrame } = renderWithProviders(, {
+ uiState: { ...defaultMockUiState, history } as Partial,
+ });
+ await waitFor(() => expect(lastFrame()).toContain('AppHeader'));
+ const output = lastFrame();
+
+ expect(output).toContain('Visible User Message');
+ expect(output).toContain('Visible Verbose Log');
+ });
});
diff --git a/packages/cli/src/ui/components/MainContent.tsx b/packages/cli/src/ui/components/MainContent.tsx
index 32c70e8cad..bbf0144b34 100644
--- a/packages/cli/src/ui/components/MainContent.tsx
+++ b/packages/cli/src/ui/components/MainContent.tsx
@@ -20,6 +20,8 @@ import { MAX_GEMINI_MESSAGE_LINES } from '../constants.js';
import { useConfirmingTool } from '../hooks/useConfirmingTool.js';
import { ToolConfirmationQueue } from './ToolConfirmationQueue.js';
import { useConfig } from '../contexts/ConfigContext.js';
+import { VERBOSITY_MAPPING, Verbosity } from '../types.js';
+import { useSettings } from '../contexts/SettingsContext.js';
const MemoizedHistoryItemDisplay = memo(HistoryItemDisplay);
const MemoizedAppHeader = memo(AppHeader);
@@ -32,6 +34,7 @@ export const MainContent = () => {
const { version } = useAppContext();
const uiState = useUIState();
const config = useConfig();
+ const settings = useSettings();
const isAlternateBuffer = useAlternateBuffer();
const confirmingTool = useConfirmingTool();
@@ -53,9 +56,24 @@ export const MainContent = () => {
availableTerminalHeight,
} = uiState;
+ const currentVerbosity =
+ VERBOSITY_MAPPING[settings.merged.output?.verbosity ?? 'info'] ??
+ Verbosity.INFO;
+
+ const filteredHistory = useMemo(
+ () =>
+ uiState.history.filter((item) => {
+ const itemType = item.type;
+ const itemVerbosity =
+ item.verbosity ?? VERBOSITY_MAPPING[itemType] ?? Verbosity.INFO;
+ return itemVerbosity <= currentVerbosity;
+ }),
+ [uiState.history, currentVerbosity],
+ );
+
const historyItems = useMemo(
() =>
- uiState.history.map((h) => (
+ filteredHistory.map((h) => (
{
/>
)),
[
- uiState.history,
+ filteredHistory,
mainAreaWidth,
staticAreaMaxItemHeight,
uiState.slashCommands,
@@ -116,10 +134,10 @@ export const MainContent = () => {
const virtualizedData = useMemo(
() => [
{ type: 'header' as const },
- ...uiState.history.map((item) => ({ type: 'history' as const, item })),
+ ...filteredHistory.map((item) => ({ type: 'history' as const, item })),
{ type: 'pending' as const },
],
- [uiState.history],
+ [filteredHistory],
);
const renderItem = useCallback(
diff --git a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
index 72a031d7f3..7cfa551edf 100644
--- a/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/AlternateBufferQuittingDisplay.test.tsx.snap
@@ -1,28 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`AlternateBufferQuittingDisplay > renders with a tool awaiting confirmation > with_confirming_tool 1`] = `
-"
- ███ █████████
-░░░███ ███░░░░░███
- ░░░███ ███ ░░░
- ░░░███░███
- ███░ ░███ █████
- ███░ ░░███ ░░███
- ███░ ░░█████████
-░░░ ░░░░░░░░░
-
-Tips for getting started:
-1. Ask questions, edit files, or run commands.
-2. Be specific for the best results.
-3. Create GEMINI.md files to customize your interactions with Gemini.
-4. /help for more information.
-
-Action Required (was prompted):
-
-? confirming_tool Confirming tool description
-"
-`;
-
exports[`AlternateBufferQuittingDisplay > renders with active and pending tool messages > with_history_and_pending 1`] = `
"
███ █████████
@@ -39,14 +16,21 @@ Tips for getting started:
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information.
-╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ tool1 Description for tool 1 │
-│ │
-╰──────────────────────────────────────────────────────────────────────────╯
-╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ tool2 Description for tool 2 │
-│ │
-╰──────────────────────────────────────────────────────────────────────────╯"
+╭─────────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool1 Description for tool 1 │
+│ │
+╰─────────────────────────────────────────────────────────────────────────────╯
+
+╭─────────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool2 Description for tool 2 │
+│ │
+╰─────────────────────────────────────────────────────────────────────────────╯
+
+╭─────────────────────────────────────────────────────────────────────────────╮
+│ o tool3 Description for tool 3 │
+│ │
+╰─────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[`AlternateBufferQuittingDisplay > renders with empty history and no pending items > empty 1`] = `
@@ -83,14 +67,16 @@ Tips for getting started:
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information.
-╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ tool1 Description for tool 1 │
-│ │
-╰──────────────────────────────────────────────────────────────────────────╯
-╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ tool2 Description for tool 2 │
-│ │
-╰──────────────────────────────────────────────────────────────────────────╯"
+╭─────────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool1 Description for tool 1 │
+│ │
+╰─────────────────────────────────────────────────────────────────────────────╯
+
+╭─────────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool2 Description for tool 2 │
+│ │
+╰─────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[`AlternateBufferQuittingDisplay > renders with pending items but no history > with_pending_no_history 1`] = `
@@ -108,7 +94,12 @@ Tips for getting started:
1. Ask questions, edit files, or run commands.
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
-4. /help for more information."
+4. /help for more information.
+╭─────────────────────────────────────────────────────────────────────────────╮
+│ o tool3 Description for tool 3 │
+│ │
+╰─────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[`AlternateBufferQuittingDisplay > renders with user and gemini messages > with_user_gemini_messages 1`] = `
@@ -127,9 +118,8 @@ Tips for getting started:
2. Be specific for the best results.
3. Create GEMINI.md files to customize your interactions with Gemini.
4. /help for more information.
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
- > Hello Gemini
-▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
-✦ Hello User!
-"
+
+> Hello Gemini
+
+✦ Hello User!"
`;
diff --git a/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap
index c134cde022..cae69d7064 100644
--- a/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap
@@ -28,7 +28,8 @@ AppHeader
│ Line 20 │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
- ShowMoreLines"
+ ShowMoreLines
+"
`;
exports[`MainContent > MainContent Tool Output Height Logic > 'ASB mode - Unfocused shell' 1`] = `
@@ -53,7 +54,8 @@ AppHeader
│ Line 19 █ │
│ Line 20 █ │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
- ShowMoreLines"
+ ShowMoreLines
+"
`;
exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Constrained height' 1`] = `
@@ -77,7 +79,8 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Con
│ Line 19 │
│ Line 20 │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
- ShowMoreLines"
+ ShowMoreLines
+"
`;
exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Unconstrained height' 1`] = `
@@ -101,7 +104,8 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Unc
│ Line 19 │
│ Line 20 │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
- ShowMoreLines"
+ ShowMoreLines
+"
`;
exports[`MainContent > does not constrain height in alternate buffer mode 1`] = `
diff --git a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap
index 786867ccc0..ef63c3e244 100644
--- a/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/SettingsDialog.test.tsx.snap
@@ -10,236 +10,9 @@ exports[`SettingsDialog > Initial Rendering > should render settings list with v
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ▲ │
-│ ● Vim Mode false │
-│ Enable Vim keybindings │
+│ ● Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
│ │
-│ Enable Auto Update true │
-│ Enable automatic updates. │
-│ │
-│ Enable Prompt Completion false │
-│ Enable AI-powered prompt completion suggestions while typing. │
-│ │
-│ Debug Keystroke Logging false │
-│ Enable debug logging of keystrokes to the console. │
-│ │
-│ Enable Session Cleanup false │
-│ Enable automatic session cleanup │
-│ │
-│ Output Format Text │
-│ The format of the CLI output. Can be \`text\` or \`json\`. │
-│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
-│ │
-│ ▼ │
-│ │
-│ Apply To │
-│ ● User Settings │
-│ Workspace Settings │
-│ System Settings │
-│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[`SettingsDialog > Snapshot Tests > should render 'accessibility settings enabled' correctly 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ │
-│ > Settings │
-│ │
-│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
-│ │ Search to filter │ │
-│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
-│ │
-│ ▲ │
-│ ● Vim Mode true* │
-│ Enable Vim keybindings │
-│ │
-│ Enable Auto Update true │
-│ Enable automatic updates. │
-│ │
-│ Enable Prompt Completion false │
-│ Enable AI-powered prompt completion suggestions while typing. │
-│ │
-│ Debug Keystroke Logging false │
-│ Enable debug logging of keystrokes to the console. │
-│ │
-│ Enable Session Cleanup false │
-│ Enable automatic session cleanup │
-│ │
-│ Output Format Text │
-│ The format of the CLI output. Can be \`text\` or \`json\`. │
-│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
-│ │
-│ ▼ │
-│ │
-│ Apply To │
-│ ● User Settings │
-│ Workspace Settings │
-│ System Settings │
-│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[`SettingsDialog > Snapshot Tests > should render 'all boolean settings disabled' correctly 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ │
-│ > Settings │
-│ │
-│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
-│ │ Search to filter │ │
-│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
-│ │
-│ ▲ │
-│ ● Vim Mode false* │
-│ Enable Vim keybindings │
-│ │
-│ Enable Auto Update true* │
-│ Enable automatic updates. │
-│ │
-│ Enable Prompt Completion false* │
-│ Enable AI-powered prompt completion suggestions while typing. │
-│ │
-│ Debug Keystroke Logging false* │
-│ Enable debug logging of keystrokes to the console. │
-│ │
-│ Enable Session Cleanup false │
-│ Enable automatic session cleanup │
-│ │
-│ Output Format Text │
-│ The format of the CLI output. Can be \`text\` or \`json\`. │
-│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
-│ │
-│ ▼ │
-│ │
-│ Apply To │
-│ ● User Settings │
-│ Workspace Settings │
-│ System Settings │
-│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[`SettingsDialog > Snapshot Tests > should render 'default state' correctly 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ │
-│ > Settings │
-│ │
-│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
-│ │ Search to filter │ │
-│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
-│ │
-│ ▲ │
-│ ● Vim Mode false │
-│ Enable Vim keybindings │
-│ │
-│ Enable Auto Update true │
-│ Enable automatic updates. │
-│ │
-│ Enable Prompt Completion false │
-│ Enable AI-powered prompt completion suggestions while typing. │
-│ │
-│ Debug Keystroke Logging false │
-│ Enable debug logging of keystrokes to the console. │
-│ │
-│ Enable Session Cleanup false │
-│ Enable automatic session cleanup │
-│ │
-│ Output Format Text │
-│ The format of the CLI output. Can be \`text\` or \`json\`. │
-│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
-│ │
-│ ▼ │
-│ │
-│ Apply To │
-│ ● User Settings │
-│ Workspace Settings │
-│ System Settings │
-│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[`SettingsDialog > Snapshot Tests > should render 'file filtering settings configured' correctly 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ │
-│ > Settings │
-│ │
-│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
-│ │ Search to filter │ │
-│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
-│ │
-│ ▲ │
-│ ● Vim Mode false │
-│ Enable Vim keybindings │
-│ │
-│ Enable Auto Update true │
-│ Enable automatic updates. │
-│ │
-│ Enable Prompt Completion false │
-│ Enable AI-powered prompt completion suggestions while typing. │
-│ │
-│ Debug Keystroke Logging false │
-│ Enable debug logging of keystrokes to the console. │
-│ │
-│ Enable Session Cleanup false │
-│ Enable automatic session cleanup │
-│ │
-│ Output Format Text │
-│ The format of the CLI output. Can be \`text\` or \`json\`. │
-│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
-│ │
-│ ▼ │
-│ │
-│ Apply To │
-│ ● User Settings │
-│ Workspace Settings │
-│ System Settings │
-│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[`SettingsDialog > Snapshot Tests > should render 'focused on scope selector' correctly 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ │
-│ Settings │
-│ │
-│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
-│ │ Search to filter │ │
-│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
-│ │
-│ ▲ │
│ Vim Mode false │
│ Enable Vim keybindings │
│ │
@@ -258,57 +31,8 @@ exports[`SettingsDialog > Snapshot Tests > should render 'focused on scope selec
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
-│ │
-│ ▼ │
-│ │
-│ > Apply To │
-│ ● 1. User Settings │
-│ 2. Workspace Settings │
-│ 3. System Settings │
-│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[`SettingsDialog > Snapshot Tests > should render 'mixed boolean and number settings' correctly 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ │
-│ > Settings │
-│ │
-│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
-│ │ Search to filter │ │
-│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
-│ │
-│ ▲ │
-│ ● Vim Mode false* │
-│ Enable Vim keybindings │
-│ │
-│ Enable Auto Update false* │
-│ Enable automatic updates. │
-│ │
-│ Enable Prompt Completion false │
-│ Enable AI-powered prompt completion suggestions while typing. │
-│ │
-│ Debug Keystroke Logging false │
-│ Enable debug logging of keystrokes to the console. │
-│ │
-│ Enable Session Cleanup false │
-│ Enable automatic session cleanup │
-│ │
-│ Output Format Text │
-│ The format of the CLI output. Can be \`text\` or \`json\`. │
-│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
│ │
│ ▼ │
│ │
@@ -317,12 +41,12 @@ exports[`SettingsDialog > Snapshot Tests > should render 'mixed boolean and numb
│ Workspace Settings │
│ System Settings │
│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
-exports[`SettingsDialog > Snapshot Tests > should render 'tools and security settings' correctly 1`] = `
+exports[`SettingsDialog > Snapshot Tests > should render 'accessibility settings enabled' correctly 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
│ > Settings │
@@ -332,7 +56,10 @@ exports[`SettingsDialog > Snapshot Tests > should render 'tools and security set
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ▲ │
-│ ● Vim Mode false │
+│ ● Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
+│ │
+│ Vim Mode true* │
│ Enable Vim keybindings │
│ │
│ Enable Auto Update true │
@@ -350,11 +77,8 @@ exports[`SettingsDialog > Snapshot Tests > should render 'tools and security set
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
│ │
│ ▼ │
│ │
@@ -363,7 +87,283 @@ exports[`SettingsDialog > Snapshot Tests > should render 'tools and security set
│ Workspace Settings │
│ System Settings │
│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
+`;
+
+exports[`SettingsDialog > Snapshot Tests > should render 'all boolean settings disabled' correctly 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ │
+│ > Settings │
+│ │
+│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
+│ │ Search to filter │ │
+│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
+│ │
+│ ▲ │
+│ ● Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
+│ │
+│ Vim Mode false* │
+│ Enable Vim keybindings │
+│ │
+│ Enable Auto Update true* │
+│ Enable automatic updates. │
+│ │
+│ Enable Prompt Completion false* │
+│ Enable AI-powered prompt completion suggestions while typing. │
+│ │
+│ Debug Keystroke Logging false* │
+│ Enable debug logging of keystrokes to the console. │
+│ │
+│ Enable Session Cleanup false │
+│ Enable automatic session cleanup │
+│ │
+│ Output Format Text │
+│ The format of the CLI output. Can be \`text\` or \`json\`. │
+│ │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
+│ │
+│ ▼ │
+│ │
+│ Apply To │
+│ ● User Settings │
+│ Workspace Settings │
+│ System Settings │
+│ │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
+`;
+
+exports[`SettingsDialog > Snapshot Tests > should render 'default state' correctly 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ │
+│ > Settings │
+│ │
+│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
+│ │ Search to filter │ │
+│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
+│ │
+│ ▲ │
+│ ● Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
+│ │
+│ Vim Mode false │
+│ Enable Vim keybindings │
+│ │
+│ Enable Auto Update true │
+│ Enable automatic updates. │
+│ │
+│ Enable Prompt Completion false │
+│ Enable AI-powered prompt completion suggestions while typing. │
+│ │
+│ Debug Keystroke Logging false │
+│ Enable debug logging of keystrokes to the console. │
+│ │
+│ Enable Session Cleanup false │
+│ Enable automatic session cleanup │
+│ │
+│ Output Format Text │
+│ The format of the CLI output. Can be \`text\` or \`json\`. │
+│ │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
+│ │
+│ ▼ │
+│ │
+│ Apply To │
+│ ● User Settings │
+│ Workspace Settings │
+│ System Settings │
+│ │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
+`;
+
+exports[`SettingsDialog > Snapshot Tests > should render 'file filtering settings configured' correctly 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ │
+│ > Settings │
+│ │
+│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
+│ │ Search to filter │ │
+│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
+│ │
+│ ▲ │
+│ ● Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
+│ │
+│ Vim Mode false │
+│ Enable Vim keybindings │
+│ │
+│ Enable Auto Update true │
+│ Enable automatic updates. │
+│ │
+│ Enable Prompt Completion false │
+│ Enable AI-powered prompt completion suggestions while typing. │
+│ │
+│ Debug Keystroke Logging false │
+│ Enable debug logging of keystrokes to the console. │
+│ │
+│ Enable Session Cleanup false │
+│ Enable automatic session cleanup │
+│ │
+│ Output Format Text │
+│ The format of the CLI output. Can be \`text\` or \`json\`. │
+│ │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
+│ │
+│ ▼ │
+│ │
+│ Apply To │
+│ ● User Settings │
+│ Workspace Settings │
+│ System Settings │
+│ │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
+`;
+
+exports[`SettingsDialog > Snapshot Tests > should render 'focused on scope selector' correctly 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ │
+│ Settings │
+│ │
+│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
+│ │ Search to filter │ │
+│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
+│ │
+│ ▲ │
+│ Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
+│ │
+│ Vim Mode false │
+│ Enable Vim keybindings │
+│ │
+│ Enable Auto Update true │
+│ Enable automatic updates. │
+│ │
+│ Enable Prompt Completion false │
+│ Enable AI-powered prompt completion suggestions while typing. │
+│ │
+│ Debug Keystroke Logging false │
+│ Enable debug logging of keystrokes to the console. │
+│ │
+│ Enable Session Cleanup false │
+│ Enable automatic session cleanup │
+│ │
+│ Output Format Text │
+│ The format of the CLI output. Can be \`text\` or \`json\`. │
+│ │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
+│ │
+│ ▼ │
+│ │
+│ > Apply To │
+│ ● 1. User Settings │
+│ 2. Workspace Settings │
+│ 3. System Settings │
+│ │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
+`;
+
+exports[`SettingsDialog > Snapshot Tests > should render 'mixed boolean and number settings' correctly 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ │
+│ > Settings │
+│ │
+│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
+│ │ Search to filter │ │
+│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
+│ │
+│ ▲ │
+│ ● Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
+│ │
+│ Vim Mode false* │
+│ Enable Vim keybindings │
+│ │
+│ Enable Auto Update false* │
+│ Enable automatic updates. │
+│ │
+│ Enable Prompt Completion false │
+│ Enable AI-powered prompt completion suggestions while typing. │
+│ │
+│ Debug Keystroke Logging false │
+│ Enable debug logging of keystrokes to the console. │
+│ │
+│ Enable Session Cleanup false │
+│ Enable automatic session cleanup │
+│ │
+│ Output Format Text │
+│ The format of the CLI output. Can be \`text\` or \`json\`. │
+│ │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
+│ │
+│ ▼ │
+│ │
+│ Apply To │
+│ ● User Settings │
+│ Workspace Settings │
+│ System Settings │
+│ │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
+`;
+
+exports[`SettingsDialog > Snapshot Tests > should render 'tools and security settings' correctly 1`] = `
+"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ │
+│ > Settings │
+│ │
+│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
+│ │ Search to filter │ │
+│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
+│ │
+│ ▲ │
+│ ● Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
+│ │
+│ Vim Mode false │
+│ Enable Vim keybindings │
+│ │
+│ Enable Auto Update true │
+│ Enable automatic updates. │
+│ │
+│ Enable Prompt Completion false │
+│ Enable AI-powered prompt completion suggestions while typing. │
+│ │
+│ Debug Keystroke Logging false │
+│ Enable debug logging of keystrokes to the console. │
+│ │
+│ Enable Session Cleanup false │
+│ Enable automatic session cleanup │
+│ │
+│ Output Format Text │
+│ The format of the CLI output. Can be \`text\` or \`json\`. │
+│ │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
+│ │
+│ ▼ │
+│ │
+│ Apply To │
+│ ● User Settings │
+│ Workspace Settings │
+│ System Settings │
+│ │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
@@ -378,7 +378,10 @@ exports[`SettingsDialog > Snapshot Tests > should render 'various boolean settin
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ▲ │
-│ ● Vim Mode true* │
+│ ● Preview Features (e.g., models) false │
+│ Enable preview features (e.g., preview models). │
+│ │
+│ Vim Mode true* │
│ Enable Vim keybindings │
│ │
│ Enable Auto Update false* │
@@ -396,11 +399,8 @@ exports[`SettingsDialog > Snapshot Tests > should render 'various boolean settin
│ Output Format Text │
│ The format of the CLI output. Can be \`text\` or \`json\`. │
│ │
-│ Auto Theme Switching true │
-│ Automatically switch between default light and dark themes based on terminal backgro… │
-│ │
-│ Terminal Background Polling Interval 60 │
-│ Interval in seconds to poll the terminal background color. │
+│ Verbose Output History true │
+│ Show verbose output history. When enabled, output history will include autonomous to… │
│ │
│ ▼ │
│ │
@@ -409,7 +409,7 @@ exports[`SettingsDialog > Snapshot Tests > should render 'various boolean settin
│ Workspace Settings │
│ System Settings │
│ │
-│ (Use Enter to select, Ctrl+L to reset, Tab to change focus, Esc to close) │
+│ (Use Enter to select, Tab to change focus, Esc to close) │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
diff --git a/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx b/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx
new file mode 100644
index 0000000000..8ea4a184b5
--- /dev/null
+++ b/packages/cli/src/ui/components/messages/DenseToolMessage.test.tsx
@@ -0,0 +1,125 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { describe, it, expect } from 'vitest';
+import { renderWithProviders } from '../../../test-utils/render.js';
+import { DenseToolMessage } from './DenseToolMessage.js';
+import type { ToolResultDisplay } from '../../types.js';
+import { ToolCallStatus } from '../../types.js';
+
+describe('DenseToolMessage', () => {
+ const defaultProps = {
+ callId: 'call-1',
+ name: 'test-tool',
+ description: 'Test description',
+ status: ToolCallStatus.Success,
+ resultDisplay: 'Success result',
+ confirmationDetails: undefined,
+ isFirst: true,
+ };
+
+ it('renders correctly for a successful string result', () => {
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ const output = lastFrame();
+ expect(output).toContain('test-tool');
+ expect(output).toContain('Test description');
+ expect(output).toContain('→ Success result');
+ });
+
+ it('truncates long string results', () => {
+ const longResult = 'A'.repeat(200);
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ // Remove all whitespace to check the continuous string content truncation
+ const output = lastFrame()?.replace(/\s/g, '');
+ expect(output).toContain('A'.repeat(117) + '...');
+ });
+
+ it('flattens newlines in string results', () => {
+ const multilineResult = 'Line 1\nLine 2';
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ const output = lastFrame();
+ expect(output).toContain('→ Line 1 Line 2');
+ });
+
+ it('renders correctly for file diff results', () => {
+ const diffResult = {
+ fileDiff: 'diff content',
+ fileName: 'test.ts',
+ filePath: '/path/to/test.ts',
+ originalContent: 'old content',
+ newContent: 'new content',
+ };
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ const output = lastFrame();
+ expect(output).toContain('→ Diff applied to test.ts');
+ });
+
+ it('renders correctly for todo updates', () => {
+ const todoResult = {
+ todos: [],
+ };
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ const output = lastFrame();
+ expect(output).toContain('→ Todos updated');
+ });
+
+ it('renders generic output message for unknown object results', () => {
+ const genericResult = {
+ some: 'data',
+ } as unknown as ToolResultDisplay;
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ const output = lastFrame();
+ expect(output).toContain('→ Output received');
+ });
+
+ it('renders correctly for error status with string message', () => {
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ const output = lastFrame();
+ expect(output).toContain('→ Error occurred');
+ });
+
+ it('renders generic failure message for error status without string message', () => {
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ const output = lastFrame();
+ expect(output).toContain('→ Failed');
+ });
+
+ it('does not render result arrow if resultDisplay is missing', () => {
+ const { lastFrame } = renderWithProviders(
+ ,
+ );
+ const output = lastFrame();
+ expect(output).not.toContain('→');
+ });
+});
diff --git a/packages/cli/src/ui/components/messages/DenseToolMessage.tsx b/packages/cli/src/ui/components/messages/DenseToolMessage.tsx
new file mode 100644
index 0000000000..1c54f2f0fd
--- /dev/null
+++ b/packages/cli/src/ui/components/messages/DenseToolMessage.tsx
@@ -0,0 +1,78 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type React from 'react';
+import { Box, Text } from 'ink';
+import { ToolCallStatus } from '../../types.js';
+import type { IndividualToolCallDisplay } from '../../types.js';
+import { ToolStatusIndicator } from './ToolShared.js';
+import { theme } from '../../semantic-colors.js';
+
+interface DenseToolMessageProps extends IndividualToolCallDisplay {
+ isFirst: boolean;
+}
+
+interface FileDiffResult {
+ fileDiff: string;
+ fileName: string;
+}
+
+export const DenseToolMessage: React.FC = ({
+ name,
+ description,
+ status,
+ resultDisplay,
+}) => {
+ let denseResult: string | undefined;
+
+ if (status === ToolCallStatus.Success && resultDisplay) {
+ if (typeof resultDisplay === 'string') {
+ const flattened = resultDisplay.replace(/\n/g, ' ').trim();
+ denseResult =
+ flattened.length > 120 ? flattened.slice(0, 117) + '...' : flattened;
+ } else if (typeof resultDisplay === 'object') {
+ if ('fileDiff' in resultDisplay) {
+ denseResult = `Diff applied to ${(resultDisplay as FileDiffResult).fileName}`;
+ } else if ('todos' in resultDisplay) {
+ denseResult = 'Todos updated';
+ } else {
+ // Fallback for AnsiOutput or other objects
+ denseResult = 'Output received';
+ }
+ }
+ } else if (status === ToolCallStatus.Error) {
+ if (typeof resultDisplay === 'string') {
+ const flattened = resultDisplay.replace(/\n/g, ' ').trim();
+ denseResult =
+ flattened.length > 120 ? flattened.slice(0, 117) + '...' : flattened;
+ } else {
+ denseResult = 'Failed';
+ }
+ }
+
+ return (
+
+
+
+
+ {name}
+
+
+
+
+ {description}
+
+
+ {denseResult && (
+
+
+ → {denseResult}
+
+
+ )}
+
+ );
+};
diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
index 118b198edf..c45383fc39 100644
--- a/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.tsx
@@ -18,6 +18,8 @@ import { isShellTool, isThisShellFocused } from './ToolShared.js';
import { ASK_USER_DISPLAY_NAME } from '@google/gemini-cli-core';
import { ShowMoreLines } from '../ShowMoreLines.js';
import { useUIState } from '../../contexts/UIStateContext.js';
+import { useSettings } from '../../contexts/SettingsContext.js';
+import { DenseToolMessage } from './DenseToolMessage.js';
interface ToolGroupMessageProps {
groupId: number;
@@ -63,6 +65,8 @@ export const ToolGroupMessage: React.FC = ({
const config = useConfig();
const { constrainHeight } = useUIState();
+ const { merged: settings } = useSettings();
+ const isVerboseMode = settings.output?.verbosity === 'verbose';
const isEventDriven = config.isEventDrivenSchedulerEnabled();
@@ -161,12 +165,25 @@ export const ToolGroupMessage: React.FC = ({
*/
width={terminalWidth}
paddingRight={TOOL_MESSAGE_HORIZONTAL_MARGIN}
+ marginBottom={1}
>
{visibleToolCalls.map((tool, index) => {
const isConfirming = toolAwaitingApproval?.callId === tool.callId;
const isFirst = index === 0;
const isShellToolCall = isShellTool(tool.name);
+ // Use dense view if not verbose, not a shell tool (for interactivity), and not confirming (needs prompt)
+ const useDenseView =
+ !isVerboseMode &&
+ !isShellToolCall &&
+ tool.status !== ToolCallStatus.Confirming;
+
+ if (useDenseView) {
+ return (
+
+ );
+ }
+
const commonProps = {
...tool,
availableTerminalHeight: availableTerminalHeightPerToolMessage,
@@ -242,22 +259,59 @@ export const ToolGroupMessage: React.FC = ({
})}
{
/*
- We have to keep the bottom border separate so it doesn't get
- drawn over by the sticky header directly inside it.
- */
- (visibleToolCalls.length > 0 || borderBottomOverride !== undefined) && (
-
- )
+ We have to keep the bottom border separate so it doesn't get
+ drawn over by the sticky header directly inside it.
+ Only render if the last tool was displayed in full/verbose mode,
+ or if explicitly overridden.
+ */
+ (() => {
+ if (visibleToolCalls.length === 0) {
+ if (borderBottomOverride !== undefined) {
+ return (
+
+ );
+ }
+ return null;
+ }
+
+ const lastTool = visibleToolCalls[visibleToolCalls.length - 1];
+ const isShell = isShellTool(lastTool.name);
+ const isConfirming = lastTool.status === ToolCallStatus.Confirming;
+
+ // Logic: If dense view (not verbose, not shell, not confirming), hide border by default
+ const isDense = !isVerboseMode && !isShell && !isConfirming;
+ let showBottomBorder = !isDense;
+
+ if (borderBottomOverride !== undefined) {
+ showBottomBorder = borderBottomOverride;
+ }
+
+ if (!showBottomBorder) return null;
+
+ return (
+
+ );
+ })()
}
{(borderBottomOverride ?? true) && visibleToolCalls.length > 0 && (
diff --git a/packages/cli/src/ui/components/messages/ToolStickyHeaderRegression.test.tsx b/packages/cli/src/ui/components/messages/ToolStickyHeaderRegression.test.tsx
index eaba97a8eb..7755d882f2 100644
--- a/packages/cli/src/ui/components/messages/ToolStickyHeaderRegression.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolStickyHeaderRegression.test.tsx
@@ -110,7 +110,7 @@ describe('ToolMessage Sticky Header Regression', () => {
// Scroll down so that tool-1's header should be stuck
await act(async () => {
- listRef?.scrollBy(5);
+ listRef?.scrollBy(6);
});
// tool-1 header should still be visible because it is sticky
@@ -120,10 +120,10 @@ describe('ToolMessage Sticky Header Regression', () => {
expect(lastFrame()).toContain('Description for tool-1');
// Content lines 1-4 should be scrolled off
expect(lastFrame()).not.toContain('c1-01');
- expect(lastFrame()).not.toContain('c1-04');
- // Line 6 and 7 should be visible (terminalHeight=5 means only 2 lines of content show below 3-line header)
- expect(lastFrame()).toContain('c1-06');
+ expect(lastFrame()).not.toContain('c1-05');
+ // Line 7 and 8 should be visible (terminalHeight=5 means only 2 lines of content show below 3-line header)
expect(lastFrame()).toContain('c1-07');
+ expect(lastFrame()).toContain('c1-08');
expect(lastFrame()).toMatchSnapshot();
// Scroll further so tool-1 is completely gone and tool-2's header should be stuck
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 369fa59174..a40802e3dc 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
@@ -1,260 +1,238 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[` > Ask User Filtering > does NOT filter out ask_user when status is Error 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ x Ask User │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[` > Ask User Filtering > does NOT filter out ask_user when status is Success 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ Ask User │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[` > Ask User Filtering > filters out ask_user when status is Confirming 1`] = `""`;
-
-exports[` > Ask User Filtering > filters out ask_user when status is Executing 1`] = `""`;
-
-exports[` > Ask User Filtering > filters out ask_user when status is Pending 1`] = `""`;
-
-exports[` > Ask User Filtering > shows other tools when ask_user is filtered out 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ other-tool A tool for testing │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
-`;
-
exports[` > Border Color Logic > uses gray border when all tools are successful and no shell commands 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ test-tool A tool for testing │
-│ │
-│ Test result │
-│ │
-│ ✓ another-tool A tool for testing │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ test-tool A tool for testing │
+│ │
+│ Test result │
+│ │
+│ ✓ another-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Border Color Logic > uses yellow border for shell commands even when successful 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ run_shell_command A tool for testing │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ run_shell_command A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Border Color Logic > uses yellow border when tools are pending 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ o test-tool A tool for testing │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ o test-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Confirmation Handling > renders confirmation with permanent approval disabled 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ? confirm-tool A tool for testing ← │
-│ │
-│ Test result │
-│ Do you want to proceed? │
-│ Do you want to proceed? │
-│ │
-│ ● 1. Allow once │
-│ 2. Allow for this session │
-│ 3. No, suggest changes (esc) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ? confirm-tool A tool for testing ← │
+│ │
+│ Test result │
+│ Do you want to proceed? │
+│ │
+│ Do you want to proceed? │
+│ │
+│ ● 1. Allow once │
+│ 2. Allow for this session │
+│ 3. No, suggest changes (esc) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Confirmation Handling > renders confirmation with permanent approval enabled 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ? confirm-tool A tool for testing ← │
-│ │
-│ Test result │
-│ Do you want to proceed? │
-│ Do you want to proceed? │
-│ │
-│ ● 1. Allow once │
-│ 2. Allow for this session │
-│ 3. Allow for all future sessions │
-│ 4. No, suggest changes (esc) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ? confirm-tool A tool for testing ← │
+│ │
+│ Test result │
+│ Do you want to proceed? │
+│ │
+│ Do you want to proceed? │
+│ │
+│ ● 1. Allow once │
+│ 2. Allow for this session │
+│ 3. Allow for all future sessions │
+│ 4. No, suggest changes (esc) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Confirmation Handling > shows confirmation dialog for first confirming tool only 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ? first-confirm A tool for testing ← │
-│ │
-│ Test result │
-│ Confirm first tool │
-│ Do you want to proceed? │
-│ │
-│ ● 1. Allow once │
-│ 2. Allow for this session │
-│ 3. No, suggest changes (esc) │
-│ │
-│ │
-│ ? second-confirm A tool for testing │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
-`;
-
-exports[` > Event-Driven Scheduler > hides confirming tools when event-driven scheduler is enabled 1`] = `""`;
-
-exports[` > Event-Driven Scheduler > renders nothing when only tool is in-progress AskUser with borderBottom=false 1`] = `""`;
-
-exports[` > Event-Driven Scheduler > shows only successful tools when mixed with confirming tools 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ success-tool A tool for testing │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ? first-confirm A tool for testing ← │
+│ │
+│ Test result │
+│ Confirm first tool │
+│ │
+│ Do you want to proceed? │
+│ │
+│ ● 1. Allow once │
+│ 2. Allow for this session │
+│ 3. No, suggest changes (esc) │
+│ │
+│ │
+│ ? second-confirm A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
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… │
-│──────────────────────────────────────────────────────────────────────────│
-│ line5 │ █
-│ │ █
-│ ✓ tool-2 Description 2 │ █
-│ │ █
-│ line1 │ █
-│ line2 │ █
-╰──────────────────────────────────────────────────────────────────────────╯ █"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool-1 Description 1. This is a long description that will need to be tr… │
+│──────────────────────────────────────────────────────────────────────────────│
+│ │ ▄
+│ ✓ tool-2 Description 2 │ █
+│ │ █
+│ line1 │ █
+│ line2 │ █
+╰──────────────────────────────────────────────────────────────────────────────╯ █
+ █"
`;
exports[` > Golden Snapshots > renders mixed tool calls including shell command 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ read_file Read a file │
-│ │
-│ Test result │
-│ │
-│ ⊷ run_shell_command Run command │
-│ │
-│ Test result │
-│ │
-│ o write_file Write to file │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ read_file Read a file │
+│ │
+│ Test result │
+│ │
+│ ⊷ run_shell_command Run command │
+│ │
+│ Test result │
+│ │
+│ o write_file Write to file │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Golden Snapshots > renders multiple tool calls with different statuses 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ successful-tool This tool succeeded │
-│ │
-│ Test result │
-│ │
-│ o pending-tool This tool is pending │
-│ │
-│ Test result │
-│ │
-│ x error-tool This tool failed │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ successful-tool This tool succeeded │
+│ │
+│ Test result │
+│ │
+│ o pending-tool This tool is pending │
+│ │
+│ Test result │
+│ │
+│ x error-tool This tool failed │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Golden Snapshots > renders shell command with yellow border 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ run_shell_command Execute shell command │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ run_shell_command Execute shell command │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Golden Snapshots > renders single successful tool call 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ test-tool A tool for testing │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ test-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Golden Snapshots > renders tool call awaiting confirmation 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ? confirmation-tool This tool needs confirmation ← │
-│ │
-│ Test result │
-│ Are you sure you want to proceed? │
-│ Do you want to proceed? │
-│ │
-│ ● 1. Allow once │
-│ 2. Allow for this session │
-│ 3. No, suggest changes (esc) │
-│ │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ? confirmation-tool This tool needs confirmation ← │
+│ │
+│ Test result │
+│ Are you sure you want to proceed? │
+│ │
+│ Do you want to proceed? │
+│ │
+│ ● 1. Allow once │
+│ 2. Allow for this session │
+│ 3. No, suggest changes (esc) │
+│ │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Golden Snapshots > renders tool call with outputFile 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ tool-with-file Tool that saved output to file │
-│ │
-│ Test result │
-│ Output too long and was saved to: /path/to/output.txt │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool-with-file Tool that saved output to file │
+│ │
+│ Test result │
+│ Output too long and was saved to: /path/to/output.txt │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Golden Snapshots > renders two tool groups where only the last line of the previous group is visible 1`] = `
-"╰──────────────────────────────────────────────────────────────────────────╯
-╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ tool-2 Description 2 │
-│ │ ▄
-│ line1 │ █
-╰──────────────────────────────────────────────────────────────────────────╯ █"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool-2 Description 2 │
+│ │
+│ line1 │ ▄
+╰──────────────────────────────────────────────────────────────────────────────╯ █
+ █"
`;
exports[` > Golden Snapshots > renders when not focused 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ test-tool A tool for testing │
-│ │
-│ Test result │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ test-tool A tool for testing │
+│ │
+│ Test result │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Golden Snapshots > renders with limited terminal height 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ tool-with-result Tool with output │
-│ │
-│ This is a long result that might need height constraints │
-│ │
-│ ✓ another-tool Another tool │
-│ │
-│ More output here │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ tool-with-result Tool with output │
+│ │
+│ This is a long result that might need height constraints │
+│ │
+│ ✓ another-tool Another tool │
+│ │
+│ More output here │
+╰──────────────────────────────────────────────────────────────────────────────╯
+"
`;
exports[` > Golden Snapshots > renders with narrow terminal width 1`] = `
-"╭──────────────────────────────────╮
-│ ✓ very-long-tool-name-that-mig… │
-│ │
-│ Test result │
-╰──────────────────────────────────╯"
+"╭──────────────────────────────────────╮
+│ ✓ very-long-tool-name-that-might-w… │
+│ │
+│ Test result │
+╰──────────────────────────────────────╯
+"
`;
exports[` > Height Calculation > calculates available height correctly with multiple tools with results 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────╮
-│ ✓ test-tool A tool for testing │
-│ │
-│ Result 1 │
-│ │
-│ ✓ test-tool A tool for testing │
-│ │
-│ Result 2 │
-│ │
-│ ✓ test-tool A tool for testing │
-│ │
-╰──────────────────────────────────────────────────────────────────────────╯"
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ✓ 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 58cb3697f3..1a3a8d6117 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
@@ -2,7 +2,7 @@
exports[`ToolMessage Sticky Header Regression > verifies that ShellToolMessage in a ToolGroupMessage in a ScrollableList has sticky headers 1`] = `
"╭────────────────────────────────────────────────────────────────────────╮ █
-│ ✓ Shell Command Description for Shell Command │ █
+│ ✓ Shell Command Description for Shell Command │ ▀
│ │
│ shell-01 │
│ shell-02 │"
@@ -10,7 +10,7 @@ exports[`ToolMessage Sticky Header Regression > verifies that ShellToolMessage i
exports[`ToolMessage Sticky Header Regression > verifies that ShellToolMessage in a ToolGroupMessage in a ScrollableList has sticky headers 2`] = `
"╭────────────────────────────────────────────────────────────────────────╮
-│ ✓ Shell Command Description for Shell Command │ ▄
+│ ✓ Shell Command Description for Shell Command │
│────────────────────────────────────────────────────────────────────────│ █
│ shell-06 │ ▀
│ shell-07 │"
@@ -28,14 +28,14 @@ exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessa
"╭────────────────────────────────────────────────────────────────────────╮
│ ✓ tool-1 Description for tool-1 │ █
│────────────────────────────────────────────────────────────────────────│
-│ c1-06 │
-│ c1-07 │"
+│ c1-07 │
+│ c1-08 │"
`;
exports[`ToolMessage Sticky Header Regression > verifies that multiple ToolMessages in a ToolGroupMessage in a ScrollableList have sticky headers 3`] = `
"│ │
│ ✓ tool-2 Description for tool-2 │
│────────────────────────────────────────────────────────────────────────│
-│ c2-10 │
-╰────────────────────────────────────────────────────────────────────────╯ █"
+╰────────────────────────────────────────────────────────────────────────╯
+ █"
`;
diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.ts b/packages/cli/src/ui/hooks/shellCommandProcessor.ts
index 860bece5d8..441f7e6665 100644
--- a/packages/cli/src/ui/hooks/shellCommandProcessor.ts
+++ b/packages/cli/src/ui/hooks/shellCommandProcessor.ts
@@ -8,7 +8,7 @@ import type {
HistoryItemWithoutId,
IndividualToolCallDisplay,
} from '../types.js';
-import { ToolCallStatus } from '../types.js';
+import { ToolCallStatus, Verbosity } from '../types.js';
import { useCallback, useReducer, useRef, useEffect } from 'react';
import type { AnsiOutput, Config, GeminiClient } from '@google/gemini-cli-core';
import { isBinary, ShellExecutionService } from '@google/gemini-cli-core';
@@ -480,10 +480,14 @@ export const useShellCommandProcessor = (
resultDisplay: finalOutput,
};
+ // Add the complete, contextual result to the local UI history.
+ // We skip this for cancelled commands because useGeminiStream handles the
+ // immediate addition of the cancelled item to history to prevent flickering/duplicates.
if (finalStatus !== ToolCallStatus.Canceled) {
addItemToHistory(
{
type: 'tool_group',
+ verbosity: Verbosity.INFO,
tools: [finalToolDisplay],
} as HistoryItemWithoutId,
userMessageTimestamp,
diff --git a/packages/cli/src/ui/hooks/toolMapping.test.ts b/packages/cli/src/ui/hooks/toolMapping.test.ts
index 16900f3ad7..6fcded6a3c 100644
--- a/packages/cli/src/ui/hooks/toolMapping.test.ts
+++ b/packages/cli/src/ui/hooks/toolMapping.test.ts
@@ -19,7 +19,7 @@ import {
type WaitingToolCall,
type CancelledToolCall,
} from '@google/gemini-cli-core';
-import { ToolCallStatus } from '../types.js';
+import { ToolCallStatus, Verbosity } from '../types.js';
describe('toolMapping', () => {
beforeEach(() => {
@@ -274,5 +274,27 @@ describe('toolMapping', () => {
expect(result.tools[0].resultDisplay).toBeUndefined();
expect(result.tools[0].status).toBe(ToolCallStatus.Pending);
});
+
+ it('sets verbosity to INFO for client-initiated tools', () => {
+ const toolCall: ScheduledToolCall = {
+ status: 'scheduled',
+ request: { ...mockRequest, isClientInitiated: true },
+ tool: mockTool,
+ invocation: mockInvocation,
+ };
+ const result = mapToDisplay(toolCall);
+ expect(result.verbosity).toBe(Verbosity.INFO);
+ });
+
+ it('sets verbosity to undefined (defaulting to VERBOSE) for autonomous tools', () => {
+ const toolCall: ScheduledToolCall = {
+ status: 'scheduled',
+ request: { ...mockRequest, isClientInitiated: false },
+ tool: mockTool,
+ invocation: mockInvocation,
+ };
+ const result = mapToDisplay(toolCall);
+ expect(result.verbosity).toBeUndefined();
+ });
});
});
diff --git a/packages/cli/src/ui/hooks/toolMapping.ts b/packages/cli/src/ui/hooks/toolMapping.ts
index e83fb583bf..6de60c4f25 100644
--- a/packages/cli/src/ui/hooks/toolMapping.ts
+++ b/packages/cli/src/ui/hooks/toolMapping.ts
@@ -14,6 +14,7 @@ import {
} from '@google/gemini-cli-core';
import {
ToolCallStatus,
+ Verbosity,
type HistoryItemToolGroup,
type IndividualToolCallDisplay,
} from '../types.js';
@@ -54,6 +55,9 @@ export function mapToDisplay(
): HistoryItemToolGroup {
const toolCalls = Array.isArray(toolOrTools) ? toolOrTools : [toolOrTools];
const { borderTop, borderBottom } = options;
+ const isClientInitiated = toolCalls.some(
+ (tc) => tc.request.isClientInitiated,
+ );
const toolDisplays = toolCalls.map((call): IndividualToolCallDisplay => {
let description: string;
@@ -129,6 +133,7 @@ export function mapToDisplay(
return {
type: 'tool_group',
+ verbosity: isClientInitiated ? Verbosity.INFO : undefined,
tools: toolDisplays,
borderTop,
borderBottom,
diff --git a/packages/cli/src/ui/hooks/useHistoryManager.test.ts b/packages/cli/src/ui/hooks/useHistoryManager.test.ts
index 696f9d60c0..d91bf67a7c 100644
--- a/packages/cli/src/ui/hooks/useHistoryManager.test.ts
+++ b/packages/cli/src/ui/hooks/useHistoryManager.test.ts
@@ -8,7 +8,12 @@ import { describe, it, expect } from 'vitest';
import { act } from 'react';
import { renderHook } from '../../test-utils/render.js';
import { useHistory } from './useHistoryManager.js';
-import type { HistoryItem } from '../types.js';
+import type {
+ HistoryItem,
+ HistoryItemWithoutId,
+ IndividualToolCallDisplay,
+ HistoryItemToolGroup,
+} from '../types.js';
describe('useHistoryManager', () => {
it('should initialize with an empty history', () => {
@@ -255,4 +260,67 @@ describe('useHistoryManager', () => {
expect(result.current.history[0].type).toBe('info');
});
});
+
+ it('should store all items regardless of verbosity level (filtering is done at render time)', () => {
+ // @ts-expect-error - verbosity prop was removed, but we want to ensure it's ignored if passed by mistake
+ const { result } = renderHook(() => useHistory({ verbosity: 'info' }));
+ const timestamp = Date.now();
+ const verboseItem: HistoryItemWithoutId = {
+ type: 'verbose',
+ text: 'Hidden detail',
+ verbosity: 3, // Verbosity.VERBOSE
+ };
+
+ act(() => {
+ result.current.addItem(verboseItem, timestamp);
+ });
+
+ expect(result.current.history).toHaveLength(1);
+ expect(result.current.history[0]).toEqual(
+ expect.objectContaining({
+ text: 'Hidden detail',
+ }),
+ );
+ });
+
+ it('should merge consecutive tool_group items', () => {
+ const { result } = renderHook(() => useHistory());
+ const timestamp = Date.now();
+ const toolGroup1: HistoryItemWithoutId = {
+ type: 'tool_group',
+ tools: [{ callId: '1', name: 'tool-a' } as IndividualToolCallDisplay],
+ };
+ const toolGroup2: HistoryItemWithoutId = {
+ type: 'tool_group',
+ tools: [{ callId: '2', name: 'tool-b' } as IndividualToolCallDisplay],
+ };
+ const userMessage: HistoryItemWithoutId = {
+ type: 'user',
+ text: 'hello',
+ };
+ const toolGroup3: HistoryItemWithoutId = {
+ type: 'tool_group',
+ tools: [{ callId: '3', name: 'tool-c' } as IndividualToolCallDisplay],
+ };
+
+ act(() => {
+ result.current.addItem(toolGroup1, timestamp);
+ result.current.addItem(toolGroup2, timestamp + 1);
+ result.current.addItem(userMessage, timestamp + 2);
+ result.current.addItem(toolGroup3, timestamp + 3);
+ });
+
+ expect(result.current.history).toHaveLength(3);
+ expect(result.current.history[0].type).toBe('tool_group');
+ const firstItem = result.current.history[0] as HistoryItemToolGroup;
+ expect(firstItem.tools).toHaveLength(2);
+ expect(firstItem.tools[0].name).toBe('tool-a');
+ expect(firstItem.tools[1].name).toBe('tool-b');
+
+ expect(result.current.history[1].type).toBe('user');
+
+ const thirdItem = result.current.history[2] as HistoryItemToolGroup;
+ expect(thirdItem.type).toBe('tool_group');
+ expect(thirdItem.tools).toHaveLength(1);
+ });
});
diff --git a/packages/cli/src/ui/hooks/useHistoryManager.ts b/packages/cli/src/ui/hooks/useHistoryManager.ts
index 93f7f01f28..23346cf389 100644
--- a/packages/cli/src/ui/hooks/useHistoryManager.ts
+++ b/packages/cli/src/ui/hooks/useHistoryManager.ts
@@ -66,16 +66,30 @@ export function useHistory({
const newItem: HistoryItem = { ...itemData, id } as HistoryItem;
setHistory((prevHistory) => {
- if (prevHistory.length > 0) {
- const lastItem = prevHistory[prevHistory.length - 1];
- // Prevent adding duplicate consecutive user messages
- if (
- lastItem.type === 'user' &&
- newItem.type === 'user' &&
- lastItem.text === newItem.text
- ) {
- return prevHistory; // Don't add the duplicate
- }
+ const lastItem =
+ prevHistory.length > 0 ? prevHistory[prevHistory.length - 1] : null;
+
+ // If the last item and the new item are both tool groups, merge them.
+ if (
+ lastItem &&
+ lastItem.type === 'tool_group' &&
+ newItem.type === 'tool_group'
+ ) {
+ const updatedLastItem: HistoryItem = {
+ ...lastItem,
+ tools: [...lastItem.tools, ...newItem.tools],
+ };
+ return [...prevHistory.slice(0, -1), updatedLastItem];
+ }
+
+ // Prevent adding duplicate consecutive user messages
+ if (
+ lastItem &&
+ lastItem.type === 'user' &&
+ newItem.type === 'user' &&
+ lastItem.text === newItem.text
+ ) {
+ return prevHistory; // Don't add the duplicate
}
return [...prevHistory, newItem];
});
@@ -83,36 +97,38 @@ export function useHistory({
// Record UI-specific messages, but don't do it if we're actually loading
// an existing session.
if (!isResuming && chatRecordingService) {
+ // Safe access to text property
+ const content = itemData.text || '';
+
switch (itemData.type) {
case 'compression':
+ case 'verbose':
case 'info':
chatRecordingService?.recordMessage({
model: undefined,
type: 'info',
- content: itemData.text ?? '',
+ content: content || '',
});
break;
case 'warning':
chatRecordingService?.recordMessage({
model: undefined,
type: 'warning',
- content: itemData.text ?? '',
+ content: content || '',
});
break;
case 'error':
chatRecordingService?.recordMessage({
model: undefined,
type: 'error',
- content: itemData.text ?? '',
+ content: content || '',
});
break;
case 'user':
- case 'gemini':
- case 'gemini_content':
- // Core conversation recording handled by GeminiChat.
+ // User messages are recorded by the input handler
break;
default:
- // Ignore the rest.
+ // Other types might not need recording or are recorded elsewhere
break;
}
}
@@ -128,7 +144,6 @@ export function useHistory({
* rendering all history items in for performance reasons. Only use
* if ABSOLUTELY NECESSARY
*/
- //
const updateItem = useCallback(
(
id: number,
diff --git a/packages/cli/src/ui/types.ts b/packages/cli/src/ui/types.ts
index 08452c98f5..5cdaa1ed35 100644
--- a/packages/cli/src/ui/types.ts
+++ b/packages/cli/src/ui/types.ts
@@ -19,7 +19,7 @@ import type {
import type { PartListUnion } from '@google/genai';
import { type ReactNode } from 'react';
-export type { ThoughtSummary, SkillDefinition };
+export type { ThoughtSummary, SkillDefinition, ToolResultDisplay };
export enum AuthState {
// Attempting to authenticate or re-authenticate
@@ -101,6 +101,7 @@ export const emptyIcon = ' ';
export interface HistoryItemBase {
text?: string; // Text content for user/gemini/info/error messages
+ verbosity?: Verbosity;
}
export type HistoryItemUser = HistoryItemBase & {
@@ -135,6 +136,23 @@ export type HistoryItemWarning = HistoryItemBase & {
text: string;
};
+export type HistoryItemVerbose = HistoryItemBase & {
+ type: 'verbose';
+ text: string;
+ icon?: string;
+ color?: string;
+};
+
+export type HistoryItemDebug = HistoryItemBase & {
+ type: 'debug';
+ text: string;
+};
+
+export type HistoryItemTrace = HistoryItemBase & {
+ type: 'trace';
+ text: string;
+};
+
export type HistoryItemAbout = HistoryItemBase & {
type: 'about';
cliVersion: string;
@@ -318,6 +336,9 @@ export type HistoryItemWithoutId =
| HistoryItemInfo
| HistoryItemError
| HistoryItemWarning
+ | HistoryItemVerbose
+ | HistoryItemDebug
+ | HistoryItemTrace
| HistoryItemAbout
| HistoryItemHelp
| HistoryItemToolGroup
@@ -335,11 +356,16 @@ export type HistoryItemWithoutId =
| HistoryItemChatList
| HistoryItemHooksList;
+export type HistoryItemType = HistoryItemWithoutId['type'];
+
export type HistoryItem = HistoryItemWithoutId & { id: number };
// Message types used by internal command feedback (subset of HistoryItem types)
export enum MessageType {
INFO = 'info',
+ VERBOSE = 'verbose',
+ DEBUG = 'debug',
+ TRACE = 'trace',
ERROR = 'error',
WARNING = 'warning',
USER = 'user',
@@ -360,10 +386,52 @@ export enum MessageType {
HOOKS_LIST = 'hooks_list',
}
+export enum Verbosity {
+ ERROR = 0,
+ WARN = 1,
+ INFO = 2,
+ VERBOSE = 3,
+ DEBUG = 4,
+ TRACE = 5,
+}
+
+export const VERBOSITY_MAPPING: Record = {
+ error: Verbosity.ERROR,
+ warning: Verbosity.WARN,
+ info: Verbosity.INFO,
+ user: Verbosity.INFO,
+ gemini: Verbosity.INFO,
+ gemini_content: Verbosity.INFO,
+ tool_group: Verbosity.INFO,
+ user_shell: Verbosity.INFO,
+ about: Verbosity.INFO,
+ stats: Verbosity.INFO,
+ model_stats: Verbosity.INFO,
+ tool_stats: Verbosity.INFO,
+ model: Verbosity.INFO,
+ quit: Verbosity.INFO,
+ extensions_list: Verbosity.INFO,
+ tools_list: Verbosity.INFO,
+ skills_list: Verbosity.INFO,
+ agents_list: Verbosity.INFO,
+ mcp_status: Verbosity.INFO,
+ chat_list: Verbosity.INFO,
+ hooks_list: Verbosity.INFO,
+ help: Verbosity.INFO,
+ verbose: Verbosity.VERBOSE,
+ compression: Verbosity.VERBOSE,
+ debug: Verbosity.DEBUG,
+ trace: Verbosity.TRACE,
+};
+
// Simplified message structure for internal feedback
export type Message =
| {
- type: MessageType.INFO | MessageType.ERROR | MessageType.USER;
+ type:
+ | MessageType.INFO
+ | MessageType.VERBOSE
+ | MessageType.ERROR
+ | MessageType.USER;
content: string; // Renamed from text for clarity in this context
timestamp: Date;
}
diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts
index 68fa8cfb20..57a156e2f1 100644
--- a/packages/core/src/tools/ripGrep.ts
+++ b/packages/core/src/tools/ripGrep.ts
@@ -475,10 +475,10 @@ class GrepToolInvocation extends BaseToolInvocation<
if (resolvedPath === this.config.getTargetDir() || pathParam === '.') {
description += ` within ./`;
} else {
- const relativePath = makeRelative(
- resolvedPath,
- this.config.getTargetDir(),
- );
+ let relativePath = makeRelative(resolvedPath, this.config.getTargetDir());
+ if (!relativePath.startsWith('.') && !path.isAbsolute(relativePath)) {
+ relativePath = `.${path.sep}${relativePath}`;
+ }
description += ` within ${shortenPath(relativePath)}`;
}
return description;
diff --git a/packages/core/src/tools/web-search.test.ts b/packages/core/src/tools/web-search.test.ts
index 3812a54879..6e3d17c5bf 100644
--- a/packages/core/src/tools/web-search.test.ts
+++ b/packages/core/src/tools/web-search.test.ts
@@ -94,9 +94,7 @@ describe('WebSearchTool', () => {
expect(result.llmContent).toBe(
'Web search results for "successful query":\n\nHere are your results.',
);
- expect(result.returnDisplay).toBe(
- 'Search results for "successful query" returned.',
- );
+ expect(result.returnDisplay).toBe('Search results returned.');
expect(result.sources).toBeUndefined();
});
@@ -177,9 +175,7 @@ Sources:
[2] Google (https://google.com)`;
expect(result.llmContent).toBe(expectedLlmContent);
- expect(result.returnDisplay).toBe(
- 'Search results for "grounding query" returned.',
- );
+ expect(result.returnDisplay).toBe('Search results returned.');
expect(result.sources).toHaveLength(2);
});
@@ -249,9 +245,7 @@ Sources:
[3] Gemini CLI: your open-source AI agent (https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent/)`;
expect(result.llmContent).toBe(expectedLlmContent);
- expect(result.returnDisplay).toBe(
- 'Search results for "multibyte query" returned.',
- );
+ expect(result.returnDisplay).toBe('Search results returned.');
expect(result.sources).toHaveLength(3);
});
});
diff --git a/packages/core/src/tools/web-search.ts b/packages/core/src/tools/web-search.ts
index 4a1a6d0ae8..d2c8bfc897 100644
--- a/packages/core/src/tools/web-search.ts
+++ b/packages/core/src/tools/web-search.ts
@@ -162,7 +162,7 @@ class WebSearchToolInvocation extends BaseToolInvocation<
return {
llmContent: `Web search results for "${this.params.query}":\n\n${modifiedResponseText}`,
- returnDisplay: `Search results for "${this.params.query}" returned.`,
+ returnDisplay: 'Search results returned.',
sources,
};
} catch (error: unknown) {
diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json
index 80bc484a3b..a73533d737 100644
--- a/schemas/settings.schema.json
+++ b/schemas/settings.schema.json
@@ -153,6 +153,14 @@
"default": "text",
"type": "string",
"enum": ["text", "json"]
+ },
+ "verbosity": {
+ "title": "Verbose Output History",
+ "description": "Show verbose output history. When enabled, output history will include autonomous tool calls, additional logs, etc.",
+ "markdownDescription": "Show verbose output history. When enabled, output history will include autonomous tool calls, additional logs, etc.\n\n- Category: `General`\n- Requires restart: `no`\n- Default: `verbose`",
+ "default": "verbose",
+ "type": "string",
+ "enum": ["info", "verbose"]
}
},
"additionalProperties": false