mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-13 21:07:00 -07:00
feat(cli): improve visibility of shell commands in chat history
- Render the high-level intent description in the tool header. - Display the full colorized bash command inside the tool's content box. - Pass tool arguments to the UI to enable detailed rendering of completed tools.
This commit is contained in:
@@ -132,6 +132,15 @@ describe('<ShellToolMessage />', () => {
|
||||
{ status: CoreToolCallStatus.Success },
|
||||
undefined,
|
||||
],
|
||||
[
|
||||
'renders with colorized command in body',
|
||||
{
|
||||
status: CoreToolCallStatus.Success,
|
||||
args: { command: 'for i in {1..3}; do echo $i; done' },
|
||||
description: 'Loop through numbers',
|
||||
},
|
||||
undefined,
|
||||
],
|
||||
[
|
||||
'renders in Error state',
|
||||
{ status: CoreToolCallStatus.Error, resultDisplay: 'Error output' },
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, type DOMElement } from 'ink';
|
||||
import { Box, Text, type DOMElement } from 'ink';
|
||||
import { ShellInputPrompt } from '../ShellInputPrompt.js';
|
||||
import { StickyHeader } from '../StickyHeader.js';
|
||||
import { useUIActions } from '../../contexts/UIActionsContext.js';
|
||||
@@ -24,6 +24,9 @@ import type { ToolMessageProps } from './ToolMessage.js';
|
||||
import { ACTIVE_SHELL_MAX_LINES } from '../../constants.js';
|
||||
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
import { colorizeCode } from '../../utils/CodeColorizer.js';
|
||||
import { useSettings } from '../../contexts/SettingsContext.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import {
|
||||
type Config,
|
||||
ShellExecutionService,
|
||||
@@ -45,6 +48,8 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
|
||||
description,
|
||||
|
||||
args,
|
||||
|
||||
resultDisplay,
|
||||
|
||||
status,
|
||||
@@ -77,6 +82,7 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
constrainHeight,
|
||||
} = useUIState();
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const settings = useSettings();
|
||||
|
||||
const isThisShellFocused = checkIsShellFocused(
|
||||
name,
|
||||
@@ -165,6 +171,8 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
resultDisplay,
|
||||
);
|
||||
|
||||
const shellCommand = args?.['command'];
|
||||
|
||||
return (
|
||||
<>
|
||||
<StickyHeader
|
||||
@@ -209,6 +217,18 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
||||
paddingX={1}
|
||||
flexDirection="column"
|
||||
>
|
||||
{typeof shellCommand === 'string' && (
|
||||
<Box flexDirection="row" marginBottom={1}>
|
||||
<Text color={theme.text.secondary}>$ </Text>
|
||||
{colorizeCode({
|
||||
code: shellCommand,
|
||||
language: 'bash',
|
||||
maxWidth: terminalWidth - 4, // account for padding and borders
|
||||
settings,
|
||||
hideLineNumbers: true,
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
<ToolResultDisplay
|
||||
resultDisplay={resultDisplay}
|
||||
availableTerminalHeight={availableTerminalHeight}
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface ToolMessageProps extends IndividualToolCallDisplay {
|
||||
export const ToolMessage: React.FC<ToolMessageProps> = ({
|
||||
name,
|
||||
description,
|
||||
args: _args,
|
||||
resultDisplay,
|
||||
status,
|
||||
kind,
|
||||
|
||||
@@ -331,3 +331,13 @@ exports[`<ShellToolMessage /> > Snapshots > renders in Success state (history mo
|
||||
│ Test result │
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<ShellToolMessage /> > Snapshots > renders with colorized command in body 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ✓ Shell Command Loop through numbers │
|
||||
│ │
|
||||
│ $ for i in {1..3}; do echo $i; done │
|
||||
│ │
|
||||
│ Test result │
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -51,6 +51,7 @@ export function mapToDisplay(
|
||||
parentCallId: call.request.parentCallId,
|
||||
name: displayName,
|
||||
description,
|
||||
args: call.request.args,
|
||||
renderOutputAsMarkdown,
|
||||
};
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ export interface IndividualToolCallDisplay {
|
||||
parentCallId?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
args?: Record<string, unknown>;
|
||||
resultDisplay: ToolResultDisplay | undefined;
|
||||
status: CoreToolCallStatus;
|
||||
// True when the tool was initiated directly by the user (slash/@/shell flows).
|
||||
|
||||
@@ -658,6 +658,33 @@ describe('ShellTool', () => {
|
||||
expect(shellTool.description).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return the intent description if provided', () => {
|
||||
const invocation = shellTool.build({
|
||||
command: 'ls',
|
||||
description: 'Listing files',
|
||||
});
|
||||
expect(invocation.getDescription()).toBe('Listing files');
|
||||
});
|
||||
|
||||
it('should return the intent description with background suffix if provided', () => {
|
||||
const invocation = shellTool.build({
|
||||
command: 'ls',
|
||||
description: 'Listing files',
|
||||
is_background: true,
|
||||
});
|
||||
expect(invocation.getDescription()).toBe('Listing files [background]');
|
||||
});
|
||||
|
||||
it('should return command and directory if intent description is not provided', () => {
|
||||
const invocation = shellTool.build({
|
||||
command: 'ls',
|
||||
dir_path: 'subdir',
|
||||
});
|
||||
const description = invocation.getDescription();
|
||||
expect(description).toContain('ls');
|
||||
expect(description).toContain('[in subdir]');
|
||||
});
|
||||
|
||||
it('should not include efficiency guidelines when disabled', () => {
|
||||
mockPlatform.mockReturnValue('linux');
|
||||
vi.mocked(mockConfig.getEnableShellOutputEfficiency).mockReturnValue(
|
||||
|
||||
@@ -73,6 +73,14 @@ export class ShellToolInvocation extends BaseToolInvocation<
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
if (this.params.description) {
|
||||
let description = this.params.description.replace(/\n/g, ' ');
|
||||
if (this.params.is_background) {
|
||||
description += ' [background]';
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
let description = `${this.params.command}`;
|
||||
// append optional [in directory]
|
||||
// note description is needed even if validation fails due to absolute path
|
||||
@@ -81,10 +89,6 @@ export class ShellToolInvocation extends BaseToolInvocation<
|
||||
} else {
|
||||
description += ` [current working directory ${process.cwd()}]`;
|
||||
}
|
||||
// append optional (description), replacing any line breaks with spaces
|
||||
if (this.params.description) {
|
||||
description += ` (${this.params.description.replace(/\n/g, ' ')})`;
|
||||
}
|
||||
if (this.params.is_background) {
|
||||
description += ' [background]';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user