Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
142ccf2
feat(ui): Re-apply Dense Tool Output features
jwhelangoog Feb 10, 2026
e88d4ff
feat(cli): core logic for dense tool output and history merging
jwhelangoog Feb 10, 2026
5f99cbe
feat(cli): implement dense tool rendering and UI refinements
jwhelangoog Feb 10, 2026
6d053e4
feat(cli): decouple history filtering from tool output layout
jwhelangoog Feb 10, 2026
9949616
docs(cli): update configuration documentation for compact tool output
jwhelangoog Feb 10, 2026
785c60a
test(cli): update tests and snapshots for enableCompactToolOutput
jwhelangoog Feb 10, 2026
53e1fa1
test: remove unnecessary changes introduced to address EPERM failures
jwhelangoog Feb 10, 2026
f58d4ba
test: tidy up and declutter test files for dense tool output
jwhelangoog Feb 10, 2026
fbfb2a7
feat(cli): remove output verbosity settings and logic
jwhelangoog Feb 10, 2026
25d1c54
docs(cli): rename "Enable Compact Tool Output" to "Compact Tool Output"
jwhelangoog Feb 10, 2026
204d629
refactor(cli): remove unused isFirst prop from DenseToolMessage
jwhelangoog Feb 10, 2026
2db8fb2
feat: support structured tool results and preserve terminal state con…
jwhelangoog Feb 11, 2026
78ec824
feat(cli): enhance DiffRenderer for uncolored layout preservation
jwhelangoog Feb 11, 2026
22ea05e
feat(cli): refactor dense tool output to modular summary+payload pattern
jwhelangoog Feb 11, 2026
3ce0351
feat(core): introduce structured tool results for grep, ls, and read-…
jwhelangoog Feb 11, 2026
35f56a5
test(cli): update test utilities and mock settings for compact output
jwhelangoog Feb 11, 2026
03de289
feat(cli): implement modular dense tool output with summary/payload p…
jwhelangoog Feb 11, 2026
f03cfb0
feat(cli): refine dense output for ListDirectory and ReadManyFiles re…
jwhelangoog Feb 11, 2026
5750c58
refactor(cli): update CodeColorizer and related components to use Mer…
jwhelangoog Feb 12, 2026
96ea72d
refactor(cli): enhance CodeColorizer and DiffRenderer for line-based …
jwhelangoog Feb 12, 2026
ea310d3
feat(cli): implement toggleable and scrollable diff views in DenseToo…
jwhelangoog Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/cli/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -63,6 +64,7 @@ they appear in the UI.
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
| Enable Compact Tool Output | `ui.enableCompactToolOutput` | Render tool outputs in a compact, single-line format when possible. | `true` |
| Enable Loading Phrases | `ui.accessibility.enableLoadingPhrases` | Enable loading phrases during operations. | `true` |
| Screen Reader Mode | `ui.accessibility.screenReader` | Render output in plain-text to be more screen reader accessible | `false` |

Expand Down
11 changes: 11 additions & 0 deletions docs/get-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -278,6 +284,11 @@ their corresponding top-level category object in your `settings.json` file.
- **Description:** Show the spinner during operations.
- **Default:** `true`

- **`ui.enableCompactToolOutput`** (boolean):
- **Description:** Render tool outputs in a compact, single-line format when
possible.
- **Default:** `true`

- **`ui.customWittyPhrases`** (array):
- **Description:** Custom witty phrases to display during loading. When
provided, the CLI cycles through these instead of the defaults.
Expand Down
6 changes: 4 additions & 2 deletions packages/a2a-server/src/utils/testing_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
import { createMockMessageBus } from '@google/gemini-cli-core/src/test-utils/mock-message-bus.js';
import type { Config, Storage } from '@google/gemini-cli-core';
import { expect, vi } from 'vitest';
import os from 'node:os';
import path from 'node:path';

export function createMockConfig(
overrides: Partial<Config> = {},
Expand All @@ -43,8 +45,8 @@ export function createMockConfig(
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
storage: {
getProjectTempDir: () => '/tmp',
getProjectTempCheckpointsDir: () => '/tmp/checkpoints',
getProjectTempDir: () => os.tmpdir(),
getProjectTempCheckpointsDir: () => path.join(os.tmpdir(), 'checkpoints'),
} as Storage,
getTruncateToolOutputThreshold: () =>
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/config/extension-manager-themes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import {
beforeAll,
afterAll,
Expand All @@ -29,7 +30,7 @@ describe('ExtensionManager theme loading', () => {

beforeAll(async () => {
tempHomeDir = await fs.promises.mkdtemp(
path.join(fs.realpathSync('/tmp'), 'gemini-cli-test-'),
path.join(os.tmpdir(), 'gemini-cli-test-'),
);
});

Expand Down
25 changes: 25 additions & 0 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
],
},
},
},

Expand Down Expand Up @@ -591,6 +606,16 @@ const SETTINGS_SCHEMA = {
description: 'Show the spinner during operations.',
showInDialog: true,
},
enableCompactToolOutput: {
type: 'boolean',
label: 'Enable Compact Tool Output',
category: 'UI',
requiresRestart: false,
default: true,
description:
'Render tool outputs in a compact, single-line format when possible.',
showInDialog: true,
},
customWittyPhrases: {
type: 'array',
label: 'Custom Witty Phrases',
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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).`
Expand Down
12 changes: 12 additions & 0 deletions packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,18 @@ describe('<HistoryItemDisplay />', () => {
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(
<HistoryItemDisplay {...baseItem} item={item} />,
);
expect(lastFrame()).toContain('⚙ Debug info');
});

it('should escape ANSI codes in text content', () => {
const historyItem: HistoryItem = {
id: 1,
Expand Down
22 changes: 22 additions & 0 deletions packages/cli/src/ui/components/HistoryItemDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -96,6 +97,27 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
color={itemForDisplay.color}
/>
)}
{itemForDisplay.type === 'verbose' && (
<InfoMessage
text={itemForDisplay.text}
icon={itemForDisplay.icon || 'ℹ '}
color={theme.text.secondary}
/>
)}
{itemForDisplay.type === 'debug' && (
<InfoMessage
text={itemForDisplay.text}
icon={'⚙ '}
color={theme.text.accent}
/>
)}
{itemForDisplay.type === 'trace' && (
<InfoMessage
text={itemForDisplay.text}
icon={'🔍 '}
color={theme.text.accent}
/>
)}
{itemForDisplay.type === 'warning' && (
<WarningMessage text={itemForDisplay.text} />
)}
Expand Down
63 changes: 62 additions & 1 deletion packages/cli/src/ui/components/MainContent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -26,6 +27,26 @@ vi.mock('../contexts/AppContext.js', async () => {
};
});

const mockSettings = {
merged: {
output: {
verbosity: 'info',
},
ui: {
enableCompactToolOutput: false,
},
},
};

vi.mock('../contexts/SettingsContext.js', async (importOriginal) => {
const actual =
await importOriginal<typeof import('../contexts/SettingsContext.js')>();
return {
...actual,
useSettings: () => mockSettings,
};
});

vi.mock('../hooks/useAlternateBuffer.js', () => ({
useAlternateBuffer: vi.fn(),
}));
Expand Down Expand Up @@ -78,6 +99,12 @@ describe('MainContent', () => {

beforeEach(() => {
vi.mocked(useAlternateBuffer).mockReturnValue(false);
mockSettings.merged.output.verbosity = 'info';
mockSettings.merged.ui.enableCompactToolOutput = false;
});

afterEach(() => {
vi.clearAllMocks();
});

it('renders in normal buffer mode', async () => {
Expand Down Expand Up @@ -221,4 +248,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(<MainContent />, {
uiState: { ...defaultMockUiState, history } as Partial<UIState>,
});
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(<MainContent />, {
uiState: { ...defaultMockUiState, history } as Partial<UIState>,
});
await waitFor(() => expect(lastFrame()).toContain('AppHeader'));
const output = lastFrame();

expect(output).toContain('Visible User Message');
expect(output).toContain('Visible Verbose Log');
});
});
26 changes: 22 additions & 4 deletions packages/cli/src/ui/components/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
Expand All @@ -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) => (
<MemoizedHistoryItemDisplay
terminalWidth={mainAreaWidth}
availableTerminalHeight={staticAreaMaxItemHeight}
Expand All @@ -67,7 +85,7 @@ export const MainContent = () => {
/>
)),
[
uiState.history,
filteredHistory,
mainAreaWidth,
staticAreaMaxItemHeight,
uiState.slashCommands,
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/components/ShowMoreLines.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const ShowMoreLines = ({ constrainHeight }: ShowMoreLinesProps) => {
}

return (
<Box>
<Box paddingX={1}>
<Text color={theme.text.secondary} wrap="truncate">
Press ctrl-o to show more lines
</Text>
Expand Down
Loading