diff --git a/packages/cli/src/ui/__snapshots__/App.test.tsx.snap b/packages/cli/src/ui/__snapshots__/App.test.tsx.snap
index f9799c2b07..94b1f9b1a4 100644
--- a/packages/cli/src/ui/__snapshots__/App.test.tsx.snap
+++ b/packages/cli/src/ui/__snapshots__/App.test.tsx.snap
@@ -124,13 +124,14 @@ HistoryItemDisplay
│ │
│ ? ls list directory │
│ │
-│ ls │
-│ Allow execution of: 'ls'? │
+│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
+│ │ ls │ │
+│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
+│ Allow execution of [ls]? │
│ │
│ ● 1. Allow once │
│ 2. Allow for this session │
│ 3. No, suggest changes (esc) │
-│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -138,7 +139,6 @@ HistoryItemDisplay
-
Notifications
Composer
diff --git a/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame-Full-Terminal-Tool-Confirmation-Snapshot-renders-tool-confirmation-box-in-the-frame-of-the-entire-terminal.snap.svg b/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame-Full-Terminal-Tool-Confirmation-Snapshot-renders-tool-confirmation-box-in-the-frame-of-the-entire-terminal.snap.svg
index b83d79928c..7565185d93 100644
--- a/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame-Full-Terminal-Tool-Confirmation-Snapshot-renders-tool-confirmation-box-in-the-frame-of-the-entire-terminal.snap.svg
+++ b/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame-Full-Terminal-Tool-Confirmation-Snapshot-renders-tool-confirmation-box-in-the-frame-of-the-entire-terminal.snap.svg
@@ -12,253 +12,283 @@
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
- ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
- │
- Action Required
- │
- │
- │
- │
- ?
- Edit
- packages/.../InputPrompt.tsx: return kittyProtocolSupporte... => return kittyProto
- …
- │
- │
- │
- │
- ... first 44 lines hidden (Ctrl+O to show) ...
- │
- │
- 45
- const
- line45
- =
- true
- ;
- │
- │
- 46
- const
- line46
- =
- true
- ;
- │
- │
- 47
- const
- line47
- =
- true
- ;
- │
- ▄
- │
- 48
- const
- line48
- =
- true
- ;
- │
- █
- │
- 49
- const
- line49
- =
- true
- ;
- │
- █
- │
- 50
- const
- line50
- =
- true
- ;
- │
- █
- │
- 51
- const
- line51
- =
- true
- ;
- │
- █
- │
- 52
- const
- line52
- =
- true
- ;
- │
- █
- │
- 53
- const
- line53
- =
- true
- ;
- │
- █
- │
- 54
- const
- line54
- =
- true
- ;
- │
- █
- │
- 55
- const
- line55
- =
- true
- ;
- │
- █
- │
- 56
- const
- line56
- =
- true
- ;
- │
- █
- │
- 57
- const
- line57
- =
- true
- ;
- │
- █
- │
- 58
- const
- line58
- =
- true
- ;
- │
- █
- │
- 59
- const
- line59
- =
- true
- ;
- │
- █
- │
- 60
- const
- line60
- =
- true
- ;
- │
- █
- │
-
- 61
-
-
- -
+ ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
+ │
+ ? Edit
+ │
+ │
+ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮
+ │
+ │
+ │
+ ... first 42 lines hidden (Ctrl+O to show) ...
+ │
+ │
+ │
+ │
+ 43
+ const
+ line43
+ =
+ true
+ ;
+ │
+ │
+ │
+ │
+ 44
+ const
+ line44
+ =
+ true
+ ;
+ │
+ │
+ │
+ │
+ 45
+ const
+ line45
+ =
+ true
+ ;
+ │
+ │
+ │
+ │
+ 46
+ const
+ line46
+ =
+ true
+ ;
+ │
+ │
+ │
+ │
+ 47
+ const
+ line47
+ =
+ true
+ ;
+ │
+ │▄
+ │
+ │
+ 48
+ const
+ line48
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 49
+ const
+ line49
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 50
+ const
+ line50
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 51
+ const
+ line51
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 52
+ const
+ line52
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 53
+ const
+ line53
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 54
+ const
+ line54
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 55
+ const
+ line55
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 56
+ const
+ line56
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 57
+ const
+ line57
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 58
+ const
+ line58
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 59
+ const
+ line59
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+ 60
+ const
+ line60
+ =
+ true
+ ;
+ │
+ │█
+ │
+ │
+
+ 61
-
- return
-
- kittyProtocolSupporte...;
- │
- █
- │
-
- 61
-
-
- +
+ -
+
+
+
+ return
+
+ kittyProtocolSupporte...;
+ │
+ │█
+ │
+ │
+
+ 61
-
- return
-
- kittyProtocolSupporte...;
- │
- █
- │
- 62
- buffer: TextBuffer;
- │
- █
- │
- 63
- onSubmit
- : (
- value
- :
- string
- ) =>
- void
- ;
- │
- █
- │
- Apply this change?
- │
- █
- │
- │
- █
- │
-
- ●
-
-
- 1.
-
-
- Allow once
-
- │
- █
- │
- 2.
- Allow for this session
- │
- █
- │
- 3.
- Allow for this file in all future sessions
- │
- █
- │
- 4.
- Modify with external editor
- │
- █
- │
- 5.
- No, suggest changes (esc)
- │
- █
- │
- │
- █
- ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
- █
+ +
+
+
+
+ return
+
+ kittyProtocolSupporte...;
+ │
+ │█
+ │
+ │
+ 62
+ buffer: TextBuffer;
+ │
+ │█
+ │
+ │
+ 63
+ onSubmit
+ : (
+ value
+ :
+ string
+ ) =>
+ void
+ ;
+ │
+ │█
+ │
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯
+ │█
+ │
+ Apply this change?
+ │█
+ │
+ │█
+ │
+
+ ●
+
+
+ 1.
+
+
+ Allow once
+
+ │█
+ │
+ 2.
+ Allow for this session
+ │█
+ │
+ 3.
+ Allow for this file in all future sessions
+ ~/.gemini/policies/auto-saved.toml
+ │█
+ │
+ 4.
+ Modify with external editor
+ │█
+ │
+ 5.
+ No, suggest changes (esc)
+ │█
+ ╰─────────────────────────────────────────────────────────────────────────────────────────────────╯█
\ No newline at end of file
diff --git a/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame.test.tsx.snap b/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame.test.tsx.snap
index 6841182785..d9cc9f7ce3 100644
--- a/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame.test.tsx.snap
+++ b/packages/cli/src/ui/__snapshots__/ToolConfirmationFullFrame.test.tsx.snap
@@ -5,39 +5,39 @@ exports[`Full Terminal Tool Confirmation Snapshot > renders tool confirmation bo
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ Action Required │
-│ │
-│ ? Edit packages/.../InputPrompt.tsx: return kittyProtocolSupporte... => return kittyProto… │
-│ │
-│ ... first 44 lines hidden (Ctrl+O to show) ... │
-│ 45 const line45 = true; │
-│ 46 const line46 = true; │
-│ 47 const line47 = true; │▄
-│ 48 const line48 = true; │█
-│ 49 const line49 = true; │█
-│ 50 const line50 = true; │█
-│ 51 const line51 = true; │█
-│ 52 const line52 = true; │█
-│ 53 const line53 = true; │█
-│ 54 const line54 = true; │█
-│ 55 const line55 = true; │█
-│ 56 const line56 = true; │█
-│ 57 const line57 = true; │█
-│ 58 const line58 = true; │█
-│ 59 const line59 = true; │█
-│ 60 const line60 = true; │█
-│ 61 - return kittyProtocolSupporte...; │█
-│ 61 + return kittyProtocolSupporte...; │█
-│ 62 buffer: TextBuffer; │█
-│ 63 onSubmit: (value: string) => void; │█
+│ ? Edit │
+│ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │
+│ │ ... first 42 lines hidden (Ctrl+O to show) ... │ │
+│ │ 43 const line43 = true; │ │
+│ │ 44 const line44 = true; │ │
+│ │ 45 const line45 = true; │ │
+│ │ 46 const line46 = true; │ │
+│ │ 47 const line47 = true; │ │▄
+│ │ 48 const line48 = true; │ │█
+│ │ 49 const line49 = true; │ │█
+│ │ 50 const line50 = true; │ │█
+│ │ 51 const line51 = true; │ │█
+│ │ 52 const line52 = true; │ │█
+│ │ 53 const line53 = true; │ │█
+│ │ 54 const line54 = true; │ │█
+│ │ 55 const line55 = true; │ │█
+│ │ 56 const line56 = true; │ │█
+│ │ 57 const line57 = true; │ │█
+│ │ 58 const line58 = true; │ │█
+│ │ 59 const line59 = true; │ │█
+│ │ 60 const line60 = true; │ │█
+│ │ 61 - return kittyProtocolSupporte...; │ │█
+│ │ 61 + return kittyProtocolSupporte...; │ │█
+│ │ 62 buffer: TextBuffer; │ │█
+│ │ 63 onSubmit: (value: string) => void; │ │█
+│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │█
│ Apply this change? │█
│ │█
│ ● 1. Allow once │█
│ 2. Allow for this session │█
-│ 3. Allow for this file in all future sessions │█
+│ 3. Allow for this file in all future sessions ~/.gemini/policies/auto-saved.toml │█
│ 4. Modify with external editor │█
│ 5. No, suggest changes (esc) │█
-│ │█
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯█
"
`;
diff --git a/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx b/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
index 451d0f4bb7..58a78d3c24 100644
--- a/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
+++ b/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
@@ -70,7 +70,7 @@ describe('ToolConfirmationQueue', () => {
const confirmingTool = {
tool: {
callId: 'call-1',
- name: 'ls',
+ name: 'run_shell_command',
description: 'list files',
status: CoreToolCallStatus.AwaitingApproval,
confirmationDetails: {
@@ -98,15 +98,12 @@ describe('ToolConfirmationQueue', () => {
);
const output = lastFrame();
- expect(output).toContain('Action Required');
expect(output).toContain('1 of 3');
expect(output).toContain('ls'); // Tool name
expect(output).toContain('list files'); // Tool description
- expect(output).toContain("Allow execution of: 'ls'?");
+ expect(output).toContain('Allow execution of [ls]?');
expect(output).toMatchSnapshot();
- const stickyHeaderProps = vi.mocked(StickyHeader).mock.calls[0][0];
- expect(stickyHeaderProps.borderColor).toBe(theme.status.warning);
unmount();
});
@@ -183,7 +180,7 @@ describe('ToolConfirmationQueue', () => {
// availableContentHeight = Math.max(9 - 6, 4) = 4
// MaxSizedBox in ToolConfirmationMessage will use 4
// It should show truncation message
- await waitFor(() => expect(lastFrame()).toContain('49 hidden (Ctrl+O)'));
+ await waitFor(() => expect(lastFrame()).toContain('48 hidden (Ctrl+O)'));
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/components/ToolConfirmationQueue.tsx b/packages/cli/src/ui/components/ToolConfirmationQueue.tsx
index e5294e9614..1a836662b7 100644
--- a/packages/cli/src/ui/components/ToolConfirmationQueue.tsx
+++ b/packages/cli/src/ui/components/ToolConfirmationQueue.tsx
@@ -9,7 +9,11 @@ import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { ToolConfirmationMessage } from './messages/ToolConfirmationMessage.js';
-import { ToolStatusIndicator, ToolInfo } from './messages/ToolShared.js';
+import {
+ isShellTool,
+ ToolStatusIndicator,
+ ToolInfo,
+} from './messages/ToolShared.js';
import { useUIState } from '../contexts/UIStateContext.js';
import type { ConfirmingToolState } from '../hooks/useConfirmingTool.js';
import { StickyHeader } from './StickyHeader.js';
@@ -31,6 +35,16 @@ function getConfirmationHeader(
return headers[details.type] ?? 'Action Required';
}
+function getConfirmationLabel(
+ toolName: string,
+ details: SerializableConfirmationDetails | undefined,
+): string {
+ if (details?.type === 'ask_user') return 'Questions';
+ if (details?.type === 'exit_plan_mode') return 'Implementation';
+ if (isShellTool(toolName)) return 'Shell';
+ return toolName;
+}
+
interface ToolConfirmationQueueProps {
confirmingTool: ConfirmingToolState;
}
@@ -58,22 +72,78 @@ export const ToolConfirmationQueue: React.FC = ({
? Math.max(uiAvailableHeight, 4)
: Math.floor(terminalHeight * 0.5);
+ const isShell = isShellTool(tool.name);
+ const isEdit = tool.confirmationDetails?.type === 'edit';
+
+ if (isShell || isEdit) {
+ // Use the new simplified layout for Shell and Edit tools
+ const borderColor = theme.border.default;
+ const availableContentHeight = constrainHeight
+ ? Math.max(maxHeight - 3, 4)
+ : undefined;
+
+ const toolLabel = getConfirmationLabel(tool.name, tool.confirmationDetails);
+
+ return (
+
+ {/* Header Line */}
+
+
+
+ ? {toolLabel}
+ {!isEdit && !!tool.description && ' '}
+
+ {!isEdit && !!tool.description && (
+
+
+ {tool.description}
+
+
+ )}
+
+ {total > 1 && (
+
+ {index} of {total}
+
+ )}
+
+
+ {/* Interactive Area */}
+
+
+
+
+ );
+ }
+
+ // Restore original logic for other tools
const isRoutine =
tool.confirmationDetails?.type === 'ask_user' ||
tool.confirmationDetails?.type === 'exit_plan_mode';
const borderColor = isRoutine ? theme.status.success : theme.status.warning;
const hideToolIdentity = isRoutine;
- // ToolConfirmationMessage needs to know the height available for its OWN content.
- // We subtract the lines used by the Queue wrapper:
- // - 2 lines for the rounded border
- // - 2 lines for the Header (text + margin)
- // - 2 lines for Tool Identity (text + margin)
const availableContentHeight = constrainHeight
? Math.max(maxHeight - (hideToolIdentity ? 4 : 6), 4)
: undefined;
- const content = (
+ return (
= ({
paddingX={1}
flexDirection="column"
>
- {/* Interactive Area */}
- {/*
- Note: We force isFocused={true} because if this component is rendered,
- it effectively acts as a modal over the shell/composer.
- */}
= ({
getPreferredEditor={getPreferredEditor}
terminalWidth={mainAreaWidth - 4} // Adjust for parent border/padding
availableTerminalHeight={availableContentHeight}
+ toolName={tool.name}
isFocused={true}
/>
@@ -149,6 +215,4 @@ export const ToolConfirmationQueue: React.FC = ({
/>
);
-
- return content;
};
diff --git a/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue-ToolConfirmationQueue-height-allocation-and-layout-should-handle-security-warning-height-correctly.snap.svg b/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue-ToolConfirmationQueue-height-allocation-and-layout-should-handle-security-warning-height-correctly.snap.svg
index 678d4b42b3..8e57fe107e 100644
--- a/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue-ToolConfirmationQueue-height-allocation-and-layout-should-handle-security-warning-height-correctly.snap.svg
+++ b/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue-ToolConfirmationQueue-height-allocation-and-layout-should-handle-security-warning-height-correctly.snap.svg
@@ -1,130 +1,113 @@
-
\ No newline at end of file
diff --git a/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue-ToolConfirmationQueue-height-allocation-and-layout-should-render-the-full-queue-wrapper-with-borders-and-content-for-large-exec-commands.snap.svg b/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue-ToolConfirmationQueue-height-allocation-and-layout-should-render-the-full-queue-wrapper-with-borders-and-content-for-large-exec-commands.snap.svg
index 508fc9d3c4..3f2d8451a8 100644
--- a/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue-ToolConfirmationQueue-height-allocation-and-layout-should-render-the-full-queue-wrapper-with-borders-and-content-for-large-exec-commands.snap.svg
+++ b/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue-ToolConfirmationQueue-height-allocation-and-layout-should-render-the-full-queue-wrapper-with-borders-and-content-for-large-exec-commands.snap.svg
@@ -4,153 +4,217 @@
- ╭──────────────────────────────────────────────────────────────────────────────╮
- │
- Action Required
+ ╭──────────────────────────────────────────────────────────────────────────────╮
+ │
+ ? Shell
+ Executes a bash command
2 of 3
- │
- │
- │
- │
- ?
- run_shell_command
- Executes a bash command
- │
- │
- │
- │
- ... 24 hidden (Ctrl+O) ...
- │
- │
- echo
- "Line 25"
- │
- │
- echo
- "Line 26"
- │
- │
- echo
- "Line 27"
- │
- │
- echo
- "Line 28"
- │
- │
- echo
- "Line 29"
- │
- │
- echo
- "Line 30"
- │
- │
- echo
- "Line 31"
- │
- │
- echo
- "Line 32"
- │
- │
- echo
- "Line 33"
- │
- │
- echo
- "Line 34"
- │
- │
- echo
- "Line 35"
- │
- │
- echo
- "Line 36"
- │
- │
- echo
- "Line 37"
- │
- │
- echo
- "Line 38"
- │
- │
- echo
- "Line 39"
- │
- │
- echo
- "Line 40"
- │
- │
- echo
- "Line 41"
- │
- │
- echo
- "Line 42"
- │
- │
- echo
- "Line 43"
- │
- │
- echo
- "Line 44"
- │
- │
- echo
- "Line 45"
- │
- │
- echo
- "Line 46"
- │
- │
- echo
- "Line 47"
- │
- │
- echo
- "Line 48"
- │
- │
- echo
- "Line 49"
- │
- │
- echo
- "Line 50"
- │
- │
- Allow execution of: 'echo'?
- │
- │
- │
- │
-
- ●
-
-
- 1.
-
-
- Allow once
-
- │
- │
- 2.
- Allow for this session
- │
- │
- 3.
- No, suggest changes (esc)
- │
- │
- │
- ╰──────────────────────────────────────────────────────────────────────────────╯
+ │
+ │
+ ╭──────────────────────────────────────────────────────────────────────────╮
+ │
+ │
+ │
+ ... 22 hidden (Ctrl+O) ...
+ │
+ │
+ │
+ │
+ echo
+ "Line 23"
+ │
+ │
+ │
+ │
+ echo
+ "Line 24"
+ │
+ │
+ │
+ │
+ echo
+ "Line 25"
+ │
+ │
+ │
+ │
+ echo
+ "Line 26"
+ │
+ │
+ │
+ │
+ echo
+ "Line 27"
+ │
+ │
+ │
+ │
+ echo
+ "Line 28"
+ │
+ │
+ │
+ │
+ echo
+ "Line 29"
+ │
+ │
+ │
+ │
+ echo
+ "Line 30"
+ │
+ │
+ │
+ │
+ echo
+ "Line 31"
+ │
+ │
+ │
+ │
+ echo
+ "Line 32"
+ │
+ │
+ │
+ │
+ echo
+ "Line 33"
+ │
+ │
+ │
+ │
+ echo
+ "Line 34"
+ │
+ │
+ │
+ │
+ echo
+ "Line 35"
+ │
+ │
+ │
+ │
+ echo
+ "Line 36"
+ │
+ │
+ │
+ │
+ echo
+ "Line 37"
+ │
+ │
+ │
+ │
+ echo
+ "Line 38"
+ │
+ │
+ │
+ │
+ echo
+ "Line 39"
+ │
+ │
+ │
+ │
+ echo
+ "Line 40"
+ │
+ │
+ │
+ │
+ echo
+ "Line 41"
+ │
+ │
+ │
+ │
+ echo
+ "Line 42"
+ │
+ │
+ │
+ │
+ echo
+ "Line 43"
+ │
+ │
+ │
+ │
+ echo
+ "Line 44"
+ │
+ │
+ │
+ │
+ echo
+ "Line 45"
+ │
+ │
+ │
+ │
+ echo
+ "Line 46"
+ │
+ │
+ │
+ │
+ echo
+ "Line 47"
+ │
+ │
+ │
+ │
+ echo
+ "Line 48"
+ │
+ │
+ │
+ │
+ echo
+ "Line 49"
+ │
+ │
+ │
+ │
+ echo
+ "Line 50"
+ │
+ │
+ │
+ ╰──────────────────────────────────────────────────────────────────────────╯
+ │
+ │
+ Allow execution of
+ [echo]
+ ?
+ │
+ │
+ │
+ │
+
+ ●
+
+
+ 1.
+
+
+ Allow once
+
+ │
+ │
+ 2.
+ Allow for this session
+ │
+ │
+ 3.
+ No, suggest changes (esc)
+ │
+ ╰──────────────────────────────────────────────────────────────────────────────╯
\ No newline at end of file
diff --git a/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue.test.tsx.snap
index fdbb216cde..8d8667b51d 100644
--- a/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/ToolConfirmationQueue.test.tsx.snap
@@ -2,32 +2,25 @@
exports[`ToolConfirmationQueue > calculates availableContentHeight based on availableTerminalHeight from UI state 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
-│ Action Required │
-│ │
-│ ? replace edit file │
-│ │
-│ ... 49 hidden (Ctrl+O) ... │
-│ 50 line │
+│ ? replace │
+│ ╭──────────────────────────────────────────────────────────────────────────╮ │
+│ ╰─... 48 hidden (Ctrl+O) ...───────────────────────────────────────────────╯ │
│ Apply this change? │
│ │
│ ● 1. Allow once │
│ 2. Allow for this session │
│ 3. Modify with external editor │
│ 4. No, suggest changes (esc) │
-│ │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`ToolConfirmationQueue > does not render expansion hint when constrainHeight is false 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
-│ Action Required │
-│ │
-│ ? replace edit file │
-│ │
+│ ? replace │
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
│ │ │ │
-│ │ No changes detected. │ │
+│ │ No changes detected. │ │
│ │ │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│ Apply this change? │
@@ -36,131 +29,120 @@ exports[`ToolConfirmationQueue > does not render expansion hint when constrainHe
│ 2. Allow for this session │
│ 3. Modify with external editor │
│ 4. No, suggest changes (esc) │
-│ │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`ToolConfirmationQueue > height allocation and layout > should handle security warning height correctly 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
-│ Action Required 3 of 3 │
-│ │
-│ ? run_shell_command Executes a bash command with a deceptive URL │
-│ │
+│ ? Shell Executes a bash command with a deceptive URL 3 of 3 │
│ ... 6 hidden (Ctrl+O) ... │
-│ echo "Line 37" │
-│ echo "Line 38" │
-│ echo "Line 39" │
-│ echo "Line 40" │
-│ echo "Line 41" │
-│ echo "Line 42" │
-│ echo "Line 43" │
-│ echo "Line 44" │
-│ echo "Line 45" │
-│ echo "Line 46" │
-│ echo "Line 47" │
-│ echo "Line 48" │
-│ echo "Line 49" │
-│ echo "Line 50" │
-│ curl https://täst.com │
+│ │ echo "Line 44" │ │
+│ │ echo "Line 45" │ │
+│ │ echo "Line 46" │ │
+│ │ echo "Line 47" │ │
+│ │ echo "Line 48" │ │
+│ │ echo "Line 49" │ │
+│ │ echo "Line 50" │ │
+│ │ curl https://täst.com │ │
+│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│ │
│ ⚠ Warning: Deceptive URL(s) detected: │
│ │
│ Original: https://täst.com/ │
│ Actual Host (Punycode): https://xn--tst-qla.com/ │
│ │
-│ Allow execution of: 'echo'? │
+│ Allow execution of [echo]? │
│ │
│ ● 1. Allow once │
│ 2. Allow for this session │
│ 3. No, suggest changes (esc) │
-│ │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`ToolConfirmationQueue > height allocation and layout > should render the full queue wrapper with borders and content for large edit diffs 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
-│ Action Required │
-│ │
-│ ? replace Replaces content in a file │
-│ │
-│ ... 15 hidden (Ctrl+O) ... │
-│ 8 + const newLine8 = true; │
-│ 9 - const oldLine9 = true; │
-│ 9 + const newLine9 = true; │
-│ 10 - const oldLine10 = true; │
-│ 10 + const newLine10 = true; │
-│ 11 - const oldLine11 = true; │
-│ 11 + const newLine11 = true; │
-│ 12 - const oldLine12 = true; │
-│ 12 + const newLine12 = true; │
-│ 13 - const oldLine13 = true; │
-│ 13 + const newLine13 = true; │
-│ 14 - const oldLine14 = true; │
-│ 14 + const newLine14 = true; │
-│ 15 - const oldLine15 = true; │
-│ 15 + const newLine15 = true; │
-│ 16 - const oldLine16 = true; │
-│ 16 + const newLine16 = true; │
-│ 17 - const oldLine17 = true; │
-│ 17 + const newLine17 = true; │
-│ 18 - const oldLine18 = true; │
-│ 18 + const newLine18 = true; │
-│ 19 - const oldLine19 = true; │
-│ 19 + const newLine19 = true; │
-│ 20 - const oldLine20 = true; │
-│ 20 + const newLine20 = true; │
+│ ? replace │
+│ ╭──────────────────────────────────────────────────────────────────────────╮ │
+│ │ ... 13 hidden (Ctrl+O) ... │ │
+│ │ 7 + const newLine7 = true; │ │
+│ │ 8 - const oldLine8 = true; │ │
+│ │ 8 + const newLine8 = true; │ │
+│ │ 9 - const oldLine9 = true; │ │
+│ │ 9 + const newLine9 = true; │ │
+│ │ 10 - const oldLine10 = true; │ │
+│ │ 10 + const newLine10 = true; │ │
+│ │ 11 - const oldLine11 = true; │ │
+│ │ 11 + const newLine11 = true; │ │
+│ │ 12 - const oldLine12 = true; │ │
+│ │ 12 + const newLine12 = true; │ │
+│ │ 13 - const oldLine13 = true; │ │
+│ │ 13 + const newLine13 = true; │ │
+│ │ 14 - const oldLine14 = true; │ │
+│ │ 14 + const newLine14 = true; │ │
+│ │ 15 - const oldLine15 = true; │ │
+│ │ 15 + const newLine15 = true; │ │
+│ │ 16 - const oldLine16 = true; │ │
+│ │ 16 + const newLine16 = true; │ │
+│ │ 17 - const oldLine17 = true; │ │
+│ │ 17 + const newLine17 = true; │ │
+│ │ 18 - const oldLine18 = true; │ │
+│ │ 18 + const newLine18 = true; │ │
+│ │ 19 - const oldLine19 = true; │ │
+│ │ 19 + const newLine19 = true; │ │
+│ │ 20 - const oldLine20 = true; │ │
+│ │ 20 + const newLine20 = true; │ │
+│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│ Apply this change? │
│ │
│ ● 1. Allow once │
│ 2. Allow for this session │
│ 3. Modify with external editor │
│ 4. No, suggest changes (esc) │
-│ │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`ToolConfirmationQueue > height allocation and layout > should render the full queue wrapper with borders and content for large exec commands 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
-│ Action Required 2 of 3 │
-│ │
-│ ? run_shell_command Executes a bash command │
-│ │
-│ ... 24 hidden (Ctrl+O) ... │
-│ echo "Line 25" │
-│ echo "Line 26" │
-│ echo "Line 27" │
-│ echo "Line 28" │
-│ echo "Line 29" │
-│ echo "Line 30" │
-│ echo "Line 31" │
-│ echo "Line 32" │
-│ echo "Line 33" │
-│ echo "Line 34" │
-│ echo "Line 35" │
-│ echo "Line 36" │
-│ echo "Line 37" │
-│ echo "Line 38" │
-│ echo "Line 39" │
-│ echo "Line 40" │
-│ echo "Line 41" │
-│ echo "Line 42" │
-│ echo "Line 43" │
-│ echo "Line 44" │
-│ echo "Line 45" │
-│ echo "Line 46" │
-│ echo "Line 47" │
-│ echo "Line 48" │
-│ echo "Line 49" │
-│ echo "Line 50" │
-│ Allow execution of: 'echo'? │
+│ ? Shell Executes a bash command 2 of 3 │
+│ ╭──────────────────────────────────────────────────────────────────────────╮ │
+│ │ ... 22 hidden (Ctrl+O) ... │ │
+│ │ echo "Line 23" │ │
+│ │ echo "Line 24" │ │
+│ │ echo "Line 25" │ │
+│ │ echo "Line 26" │ │
+│ │ echo "Line 27" │ │
+│ │ echo "Line 28" │ │
+│ │ echo "Line 29" │ │
+│ │ echo "Line 30" │ │
+│ │ echo "Line 31" │ │
+│ │ echo "Line 32" │ │
+│ │ echo "Line 33" │ │
+│ │ echo "Line 34" │ │
+│ │ echo "Line 35" │ │
+│ │ echo "Line 36" │ │
+│ │ echo "Line 37" │ │
+│ │ echo "Line 38" │ │
+│ │ echo "Line 39" │ │
+│ │ echo "Line 40" │ │
+│ │ echo "Line 41" │ │
+│ │ echo "Line 42" │ │
+│ │ echo "Line 43" │ │
+│ │ echo "Line 44" │ │
+│ │ echo "Line 45" │ │
+│ │ echo "Line 46" │ │
+│ │ echo "Line 47" │ │
+│ │ echo "Line 48" │ │
+│ │ echo "Line 49" │ │
+│ │ echo "Line 50" │ │
+│ ╰──────────────────────────────────────────────────────────────────────────╯ │
+│ Allow execution of [echo]? │
│ │
│ ● 1. Allow once │
│ 2. Allow for this session │
│ 3. No, suggest changes (esc) │
-│ │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`;
@@ -216,17 +198,15 @@ exports[`ToolConfirmationQueue > renders ExitPlanMode tool confirmation with Suc
exports[`ToolConfirmationQueue > renders the confirming tool with progress indicator 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
-│ Action Required 1 of 3 │
-│ │
-│ ? ls list files │
-│ │
-│ ls │
-│ Allow execution of: 'ls'? │
+│ ? Shell list files 1 of 3 │
+│ ╭──────────────────────────────────────────────────────────────────────────╮ │
+│ │ ls │ │
+│ ╰──────────────────────────────────────────────────────────────────────────╯ │
+│ Allow execution of [ls]? │
│ │
│ ● 1. Allow once │
│ 2. Allow for this session │
│ 3. No, suggest changes (esc) │
-│ │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`;
diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
index 5f75d6e009..aa5a95fd8d 100644
--- a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
+++ b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
@@ -48,15 +48,18 @@ index 0000000..e69de29
},
);
await waitFor(() =>
- expect(mockColorizeCode).toHaveBeenCalledWith({
- code: 'print("hello world")',
- language: 'python',
- availableHeight: undefined,
- maxWidth: 80,
- theme: undefined,
- settings: expect.anything(),
- disableColor: false,
- }),
+ expect(mockColorizeCode).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: 'print("hello world")',
+ language: 'python',
+ availableHeight: undefined,
+ maxWidth: 80,
+ theme: undefined,
+ settings: expect.anything(),
+ disableColor: false,
+ paddingX: 0,
+ }),
+ ),
);
});
@@ -83,15 +86,18 @@ index 0000000..e69de29
},
);
await waitFor(() =>
- expect(mockColorizeCode).toHaveBeenCalledWith({
- code: 'some content',
- language: null,
- availableHeight: undefined,
- maxWidth: 80,
- theme: undefined,
- settings: expect.anything(),
- disableColor: false,
- }),
+ expect(mockColorizeCode).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: 'some content',
+ language: null,
+ availableHeight: undefined,
+ maxWidth: 80,
+ theme: undefined,
+ settings: expect.anything(),
+ disableColor: false,
+ paddingX: 0,
+ }),
+ ),
);
});
@@ -114,15 +120,18 @@ index 0000000..e69de29
},
);
await waitFor(() =>
- expect(mockColorizeCode).toHaveBeenCalledWith({
- code: 'some text content',
- language: null,
- availableHeight: undefined,
- maxWidth: 80,
- theme: undefined,
- settings: expect.anything(),
- disableColor: false,
- }),
+ expect(mockColorizeCode).toHaveBeenCalledWith(
+ expect.objectContaining({
+ code: 'some text content',
+ language: null,
+ availableHeight: undefined,
+ maxWidth: 80,
+ theme: undefined,
+ settings: expect.anything(),
+ disableColor: false,
+ paddingX: 0,
+ }),
+ ),
);
});
diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.tsx
index ddee2e55df..3eaadf8365 100644
--- a/packages/cli/src/ui/components/messages/DiffRenderer.tsx
+++ b/packages/cli/src/ui/components/messages/DiffRenderer.tsx
@@ -32,6 +32,7 @@ export function parseDiffWithLineNumbers(diffContent: string): DiffLine[] {
for (const line of lines) {
const hunkMatch = line.match(hunkHeaderRegex);
if (hunkMatch) {
+ currentOldLine = parseInt(hunkMatch[1], 10);
currentOldLine = parseInt(hunkMatch[1], 10);
currentNewLine = parseInt(hunkMatch[2], 10);
inHunk = true;
@@ -89,6 +90,7 @@ interface DiffRendererProps {
terminalWidth: number;
theme?: Theme;
disableColor?: boolean;
+ paddingX?: number;
}
const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization
@@ -101,6 +103,7 @@ export const DiffRenderer: React.FC = ({
terminalWidth,
theme,
disableColor = false,
+ paddingX = 0,
}) => {
const settings = useSettings();
@@ -122,11 +125,7 @@ export const DiffRenderer: React.FC = ({
if (parsedLines.length === 0) {
return (
-
+
No changes detected.
);
@@ -162,12 +161,14 @@ export const DiffRenderer: React.FC = ({
theme,
settings,
disableColor,
+ paddingX,
});
} else {
const key = filename ? `diff-box-${filename}` : undefined;
return (
= ({
settings,
tabWidth,
disableColor,
+ paddingX,
]);
return renderedOutput;
@@ -239,12 +241,7 @@ export const renderDiffLines = ({
if (displayableLines.length === 0) {
return [
-
+
No changes detected.
,
];
diff --git a/packages/cli/src/ui/components/messages/RedirectionConfirmation.test.tsx b/packages/cli/src/ui/components/messages/RedirectionConfirmation.test.tsx
index 95f0cffb69..2b09401e55 100644
--- a/packages/cli/src/ui/components/messages/RedirectionConfirmation.test.tsx
+++ b/packages/cli/src/ui/components/messages/RedirectionConfirmation.test.tsx
@@ -42,6 +42,7 @@ describe('ToolConfirmationMessage Redirection', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={100}
+ toolName="shell"
/>,
);
diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
index f04b47a63e..3a3a4df557 100644
--- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
@@ -62,6 +62,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -88,6 +89,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -111,6 +113,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -140,6 +143,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -169,6 +173,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -197,6 +202,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -225,6 +231,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -253,6 +260,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
await result.waitUntilReady();
@@ -338,6 +346,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -361,6 +370,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -396,6 +406,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
{
settings: createMockSettings({
@@ -423,6 +434,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
{
settings: createMockSettings({
@@ -474,6 +486,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -505,6 +518,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -536,6 +550,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -562,6 +577,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -607,6 +623,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -638,6 +655,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
@@ -672,13 +690,14 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={40}
terminalWidth={80}
+ toolName="shell"
/>,
);
await waitUntilReady();
const outputLines = lastFrame().split('\n');
- // Should use the entire terminal height minus 1 line for the "Press Ctrl+O to show more lines" hint
- expect(outputLines.length).toBe(39);
+ // Should use the entire terminal height
+ expect(outputLines.length).toBe(40);
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
unmount();
@@ -712,13 +731,14 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={40}
terminalWidth={80}
+ toolName="shell"
/>,
);
await waitUntilReady();
const outputLines = lastFrame().split('\n');
- // Should use the entire terminal height minus 1 line for the "Press Ctrl+O to show more lines" hint
- expect(outputLines.length).toBe(39);
+ // Should use the entire terminal height
+ expect(outputLines.length).toBe(40);
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
unmount();
@@ -761,6 +781,7 @@ describe('ToolConfirmationMessage', () => {
getPreferredEditor={vi.fn()}
availableTerminalHeight={30}
terminalWidth={80}
+ toolName="shell"
/>,
);
diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
index fa565bc103..b23282959e 100644
--- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
+++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.tsx
@@ -24,13 +24,14 @@ import {
RadioButtonSelect,
type RadioSelectItem,
} from '../shared/RadioButtonSelect.js';
-import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js';
+import { MaxSizedBox } from '../shared/MaxSizedBox.js';
import {
sanitizeForDisplay,
stripUnsafeCharacters,
} from '../../utils/textUtils.js';
import { useKeypress } from '../../hooks/useKeypress.js';
import { theme } from '../../semantic-colors.js';
+import { themeManager } from '../../themes/theme-manager.js';
import { useSettings } from '../../contexts/SettingsContext.js';
import { Command } from '../../key/keyMatchers.js';
import { formatCommand } from '../../key/keybindingUtils.js';
@@ -44,6 +45,7 @@ import {
type DeceptiveUrlDetails,
} from '../../utils/urlSecurityUtils.js';
import { useKeyMatchers } from '../../hooks/useKeyMatchers.js';
+import { isShellTool } from './ToolShared.js';
export interface ToolConfirmationMessageProps {
callId: string;
@@ -53,13 +55,9 @@ export interface ToolConfirmationMessageProps {
isFocused?: boolean;
availableTerminalHeight?: number;
terminalWidth: number;
+ toolName: string;
}
-const REDIRECTION_WARNING_NOTE_LABEL = 'Note: ';
-const REDIRECTION_WARNING_NOTE_TEXT =
- 'Command contains redirection which can be undesirable.';
-const REDIRECTION_WARNING_TIP_LABEL = 'Tip: '; // Padded to align with "Note: "
-
export const ToolConfirmationMessage: React.FC<
ToolConfirmationMessageProps
> = ({
@@ -70,6 +68,7 @@ export const ToolConfirmationMessage: React.FC<
isFocused = true,
availableTerminalHeight,
terminalWidth,
+ toolName,
}) => {
const keyMatchers = useKeyMatchers();
const { confirm, isDiffingEnabled } = useToolActions();
@@ -152,6 +151,7 @@ export const ToolConfirmationMessage: React.FC<
}, []);
const settings = useSettings();
+ const activeTheme = themeManager.getActiveTheme();
const allowPermanentApproval =
settings.merged.security.enablePermanentToolApproval &&
!config.getDisableAlwaysAllow();
@@ -254,8 +254,6 @@ export const ToolConfirmationMessage: React.FC<
return true;
}
if (keyMatchers[Command.QUIT](key)) {
- // Return false to let ctrl-C bubble up to AppContainer for exit flow.
- // AppContainer will call cancelOngoingRequest which will cancel the tool.
return false;
}
return false;
@@ -398,7 +396,6 @@ export const ToolConfirmationMessage: React.FC<
key: 'No, suggest changes (esc)',
});
} else if (confirmationDetails.type === 'mcp') {
- // mcp tool confirmation
options.push({
label: 'Allow once',
value: ToolConfirmationOutcome.ProceedOnce,
@@ -449,40 +446,66 @@ export const ToolConfirmationMessage: React.FC<
// Calculate the vertical space (in lines) consumed by UI elements
// surrounding the main body content.
- const PADDING_OUTER_Y = 1; // Main container has `paddingBottom={1}`.
- const HEIGHT_QUESTION = 1; // The question text is one line.
- const MARGIN_QUESTION_BOTTOM = 1; // Margin on the question container.
- const SECURITY_WARNING_BOTTOM_MARGIN = 1; // Margin on the securityWarnings container.
- const SHOW_MORE_LINES_HEIGHT = 1; // The "Press Ctrl+O to show more lines" hint.
+ const PADDING_OUTER_Y = 0;
+ const HEIGHT_QUESTION = 1;
+ const MARGIN_QUESTION_TOP = 0;
+ const MARGIN_QUESTION_BOTTOM = 1;
+ const SECURITY_WARNING_BOTTOM_MARGIN = 1;
+ const SHOW_MORE_LINES_HEIGHT = 1;
const optionsCount = getOptions().length;
- // The measured height includes the margin inside WarningMessage (1 line).
- // We also add 1 line for the marginBottom on the securityWarnings container.
const securityWarningsHeight = deceptiveUrlWarningText
? measuredSecurityWarningsHeight + SECURITY_WARNING_BOTTOM_MARGIN
: 0;
+ let extraInfoLines = 0;
+ if (confirmationDetails.type === 'sandbox_expansion') {
+ const { additionalPermissions } = confirmationDetails;
+ if (additionalPermissions?.network) extraInfoLines++;
+ extraInfoLines += additionalPermissions?.fileSystem?.read?.length || 0;
+ extraInfoLines += additionalPermissions?.fileSystem?.write?.length || 0;
+ } else if (confirmationDetails.type === 'exec') {
+ const executionProps = confirmationDetails;
+ const commandsToDisplay =
+ executionProps.commands && executionProps.commands.length > 0
+ ? executionProps.commands
+ : [executionProps.command];
+ const containsRedirection = commandsToDisplay.some((cmd) =>
+ hasRedirection(cmd),
+ );
+ const isAutoEdit =
+ config.getApprovalMode() === ApprovalMode.YOLO ||
+ config.getApprovalMode() === ApprovalMode.AUTO_EDIT;
+ if (containsRedirection && !isAutoEdit) {
+ extraInfoLines = 1; // Warning line
+ }
+ }
+
const surroundingElementsHeight =
PADDING_OUTER_Y +
HEIGHT_QUESTION +
+ MARGIN_QUESTION_TOP +
MARGIN_QUESTION_BOTTOM +
SHOW_MORE_LINES_HEIGHT +
optionsCount +
- securityWarningsHeight;
+ securityWarningsHeight +
+ extraInfoLines;
- return Math.max(availableTerminalHeight - surroundingElementsHeight, 1);
+ return Math.max(availableTerminalHeight - surroundingElementsHeight, 2);
}, [
availableTerminalHeight,
handlesOwnUI,
getOptions,
measuredSecurityWarningsHeight,
deceptiveUrlWarningText,
+ confirmationDetails,
+ config,
]);
const { question, bodyContent, options, securityWarnings, initialIndex } =
useMemo<{
- question: string;
+ question: React.ReactNode;
bodyContent: React.ReactNode;
options: Array>;
securityWarnings: React.ReactNode;
@@ -490,7 +513,7 @@ export const ToolConfirmationMessage: React.FC<
}>(() => {
let bodyContent: React.ReactNode | null = null;
let securityWarnings: React.ReactNode | null = null;
- let question = '';
+ let question: React.ReactNode = '';
const options = getOptions();
let initialIndex = 0;
@@ -519,6 +542,8 @@ export const ToolConfirmationMessage: React.FC<
securityWarnings = ;
}
+ const bodyHeight = availableBodyContentHeight();
+
if (confirmationDetails.type === 'ask_user') {
bodyContent = (
);
return {
@@ -563,7 +588,7 @@ export const ToolConfirmationMessage: React.FC<
handleConfirm(ToolConfirmationOutcome.Cancel);
}}
width={terminalWidth}
- availableHeight={availableBodyContentHeight()}
+ availableHeight={bodyHeight}
/>
);
return {
@@ -578,85 +603,109 @@ export const ToolConfirmationMessage: React.FC<
if (confirmationDetails.type === 'edit') {
if (!confirmationDetails.isModifying) {
question = `Apply this change?`;
- }
- } else if (confirmationDetails.type === 'sandbox_expansion') {
- question = `Allow sandbox expansion for: '${sanitizeForDisplay(confirmationDetails.rootCommand)}'?`;
- } else if (confirmationDetails.type === 'exec') {
- const executionProps = confirmationDetails;
-
- if (executionProps.commands && executionProps.commands.length > 1) {
- question = `Allow execution of ${executionProps.commands.length} commands?`;
- } else {
- question = `Allow execution of: '${sanitizeForDisplay(executionProps.rootCommand)}'?`;
- }
- } else if (confirmationDetails.type === 'info') {
- question = `Do you want to proceed?`;
- } else if (confirmationDetails.type === 'mcp') {
- // mcp tool confirmation
- const mcpProps = confirmationDetails;
- question = `Allow execution of MCP tool "${sanitizeForDisplay(mcpProps.toolName)}" from server "${sanitizeForDisplay(mcpProps.serverName)}"?`;
- }
-
- if (confirmationDetails.type === 'edit') {
- if (!confirmationDetails.isModifying) {
bodyContent = (
-
+ <>
+
+
+
+ >
);
}
} else if (confirmationDetails.type === 'sandbox_expansion') {
- const { additionalPermissions } = confirmationDetails;
+ const { additionalPermissions, command, rootCommand } =
+ confirmationDetails;
const readPaths = additionalPermissions?.fileSystem?.read || [];
const writePaths = additionalPermissions?.fileSystem?.write || [];
const network = additionalPermissions?.network;
+ const isShell = isShellTool(toolName);
+
+ const rootCmds = rootCommand
+ .split(',')
+ .map((c) => c.trim().split(/\s+/)[0])
+ .filter((c) => c && !c.startsWith('redirection'));
+ const commandNames = Array.from(new Set(rootCmds)).join(', ');
+ question = '';
bodyContent = (
-
-
- The agent is requesting additional sandbox permissions to execute
- this command:
-
-
-
- {sanitizeForDisplay(confirmationDetails.command)}
-
+ <>
+
+ {colorizeCode({
+ code: command.trim(),
+ language: 'bash',
+ maxWidth: Math.max(terminalWidth, 1) - 6,
+ settings,
+ theme: activeTheme,
+ hideLineNumbers: true,
+ availableHeight:
+ bodyHeight !== undefined
+ ? Math.max(bodyHeight - 2, 2)
+ : undefined,
+ })}
- {network && (
-
- • Network Access
-
- )}
- {readPaths.length > 0 && (
-
- • Read Access:
- {readPaths.map((p, i) => (
-
- {' '}
- {sanitizeForDisplay(p)}
-
- ))}
-
- )}
- {writePaths.length > 0 && (
-
- • Write Access:
- {writePaths.map((p, i) => (
-
- {' '}
- {sanitizeForDisplay(p)}
-
- ))}
-
- )}
-
+
+
+ To run{' '}
+
+ [{sanitizeForDisplay(commandNames)}]
+
+ , allow access to the following?
+
+ {network && (
+
+
+ • Network:
+ {' '}
+ All Urls
+
+ )}
+ {writePaths.length > 0 && (
+
+
+ • Write:
+ {' '}
+ {writePaths.map((p) => sanitizeForDisplay(p)).join(', ')}
+
+ )}
+ {readPaths.length > 0 && (
+
+
+ • Read:
+ {' '}
+ {readPaths.map((p) => sanitizeForDisplay(p)).join(', ')}
+
+ )}
+
+ >
);
} else if (confirmationDetails.type === 'exec') {
const executionProps = confirmationDetails;
-
+ const isShell = isShellTool(toolName);
const commandsToDisplay =
executionProps.commands && executionProps.commands.length > 1
? executionProps.commands
@@ -664,80 +713,96 @@ export const ToolConfirmationMessage: React.FC<
const containsRedirection = commandsToDisplay.some((cmd) =>
hasRedirection(cmd),
);
+ const isAutoEdit =
+ config.getApprovalMode() === ApprovalMode.YOLO ||
+ config.getApprovalMode() === ApprovalMode.AUTO_EDIT;
- let bodyContentHeight = availableBodyContentHeight();
let warnings: React.ReactNode = null;
-
- const isAutoEdit = config.getApprovalMode() === ApprovalMode.AUTO_EDIT;
if (containsRedirection && !isAutoEdit) {
- // Calculate lines needed for Note and Tip
- const safeWidth = Math.max(terminalWidth, 1);
- const noteLength =
- REDIRECTION_WARNING_NOTE_LABEL.length +
- REDIRECTION_WARNING_NOTE_TEXT.length;
- const tipText = `Toggle auto-edit (${formatCommand(Command.CYCLE_APPROVAL_MODE)}) to allow redirection in the future.`;
- const tipLength =
- REDIRECTION_WARNING_TIP_LABEL.length + tipText.length;
-
- const noteLines = Math.ceil(noteLength / safeWidth);
- const tipLines = Math.ceil(tipLength / safeWidth);
- const spacerLines = 1;
- const warningHeight = noteLines + tipLines + spacerLines;
-
- if (bodyContentHeight !== undefined) {
- bodyContentHeight = Math.max(
- bodyContentHeight - warningHeight,
- MINIMUM_MAX_HEIGHT,
- );
- }
-
+ const tipText = `To auto-accept, press ${formatCommand(Command.CYCLE_APPROVAL_MODE)}`;
warnings = (
- <>
-
-
-
- {REDIRECTION_WARNING_NOTE_LABEL}
- {REDIRECTION_WARNING_NOTE_TEXT}
-
-
-
-
- {REDIRECTION_WARNING_TIP_LABEL}
- {tipText}
-
-
- >
+
+
+ Redirection detected.{' '}
+ {tipText}
+
+
);
}
- bodyContent = (
-
- cmd.trim().split(/\s+/)[0])
+ .filter(Boolean),
+ ),
+ ).join(', ');
+
+ const allowQuestion = (
+
+ Allow execution of{' '}
+
-
- {commandsToDisplay.map((cmd, idx) => (
-
- {colorizeCode({
- code: cmd,
- language: 'bash',
- maxWidth: Math.max(terminalWidth, 1),
- settings,
- hideLineNumbers: true,
- })}
-
- ))}
-
-
+ [{sanitizeForDisplay(commandNames)}]
+
+ {'?'}
+
+ );
+
+ question = (
+
+ {allowQuestion}
{warnings}
);
+
+ bodyContent = (
+ <>
+
+
+
+ {commandsToDisplay.map((cmd, idx) => (
+
+ {colorizeCode({
+ code: cmd.trim(),
+ language: 'bash',
+ maxWidth: Math.max(terminalWidth, 1) - 6,
+ settings,
+ theme: activeTheme,
+ hideLineNumbers: true,
+ availableHeight:
+ bodyHeight !== undefined
+ ? Math.max(bodyHeight - 2, 2)
+ : undefined,
+ })}
+
+ ))}
+
+
+
+ >
+ );
} else if (confirmationDetails.type === 'info') {
+ question = `Do you want to proceed?`;
const infoProps = confirmationDetails;
const displayUrls =
infoProps.urls &&
@@ -768,8 +833,8 @@ export const ToolConfirmationMessage: React.FC<
);
} else if (confirmationDetails.type === 'mcp') {
- // mcp tool confirmation
const mcpProps = confirmationDetails;
+ question = `Allow execution of MCP tool "${sanitizeForDisplay(mcpProps.toolName)}" from server "${sanitizeForDisplay(mcpProps.serverName)}"?`;
bodyContent = (
@@ -790,7 +855,26 @@ export const ToolConfirmationMessage: React.FC<
(press {expandDetailsHintKey} to collapse MCP tool
details)
- {mcpToolDetailsText}
+
+ {colorizeCode({
+ code: mcpToolDetailsText || '',
+ language: 'json',
+ maxWidth: Math.max(terminalWidth, 1) - 4,
+ settings,
+ theme: activeTheme,
+ hideLineNumbers: true,
+ availableHeight:
+ bodyHeight !== undefined
+ ? Math.max(bodyHeight - 2, 2)
+ : undefined,
+ })}
+
>
) : (
@@ -819,7 +903,9 @@ export const ToolConfirmationMessage: React.FC<
isTrustedFolder,
allowPermanentApproval,
settings,
+ activeTheme,
config,
+ toolName,
]);
const bodyOverflowDirection: 'top' | 'bottom' =
@@ -827,6 +913,30 @@ export const ToolConfirmationMessage: React.FC<
? 'bottom'
: 'top';
+ const renderRadioItem = useCallback(
+ (
+ item: RadioSelectItem,
+ { titleColor }: { titleColor: string },
+ ) => {
+ if (item.value === ToolConfirmationOutcome.ProceedAlwaysAndSave) {
+ return (
+
+ {item.label}{' '}
+
+ ~/.gemini/policies/auto-saved.toml
+
+
+ );
+ }
+ return (
+
+ {item.label}
+
+ );
+ },
+ [],
+ );
+
if (confirmationDetails.type === 'edit') {
if (confirmationDetails.isModifying) {
return (
@@ -849,13 +959,8 @@ export const ToolConfirmationMessage: React.FC<
}
return (
-
- {/* System message from hook */}
- {confirmationDetails.systemMessage && (
+
+ {!!confirmationDetails.systemMessage && (
{confirmationDetails.systemMessage}
@@ -867,7 +972,11 @@ export const ToolConfirmationMessage: React.FC<
bodyContent
) : (
<>
-
+
)}
-
- {question}
-
+ {!!question && (
+
+ {typeof question === 'string' ? (
+ {question}
+ ) : (
+ question
+ )}
+
+ )}
>
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/DiffRenderer.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/DiffRenderer.test.tsx.snap
index fed8b32bd0..7a36d3f840 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/DiffRenderer.test.tsx.snap
+++ b/packages/cli/src/ui/components/messages/__snapshots__/DiffRenderer.test.tsx.snap
@@ -50,11 +50,8 @@ exports[` > with useAlterna
`;
exports[` > with useAlternateBuffer = false > should handle diff with only header and no changes 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ │
-│ No changes detected. │
-│ │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
+"
+ No changes detected.
"
`;
@@ -143,11 +140,8 @@ exports[` > with useAlterna
`;
exports[` > with useAlternateBuffer = true > should handle diff with only header and no changes 1`] = `
-"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
-│ │
-│ No changes detected. │
-│ │
-╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
+"
+ No changes detected.
"
`;
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/RedirectionConfirmation.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/RedirectionConfirmation.test.tsx.snap
index f584e7f483..1694ca2350 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/RedirectionConfirmation.test.tsx.snap
+++ b/packages/cli/src/ui/components/messages/__snapshots__/RedirectionConfirmation.test.tsx.snap
@@ -1,11 +1,11 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`ToolConfirmationMessage Redirection > should display redirection warning and tip for redirected commands 1`] = `
-"echo "hello" > test.txt
-
-Note: Command contains redirection which can be undesirable.
-Tip: Toggle auto-edit (Shift+Tab) to allow redirection in the future.
-Allow execution of: 'echo, redirection (>)'?
+"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
+│ echo "hello" > test.txt │
+╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
+Allow execution of [echo]?
+Redirection detected. To auto-accept, press Shift+Tab
● 1. Allow once
2. Allow for this session
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-height-allocation-and-layout-should-expand-to-available-height-for-large-edit-diffs.snap.svg b/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-height-allocation-and-layout-should-expand-to-available-height-for-large-edit-diffs.snap.svg
index 4c570fb451..ffc73fdd5e 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-height-allocation-and-layout-should-expand-to-available-height-for-large-edit-diffs.snap.svg
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-height-allocation-and-layout-should-expand-to-available-height-for-large-edit-diffs.snap.svg
@@ -1,468 +1,517 @@
-
+
-
+
- ... first 9 lines hidden (Ctrl+O to show) ...
-
-
- 5
-
-
- +
-
-
- const
-
- newLine5 =
-
- true
-
- ;
-
-
- 6
+ ╭──────────────────────────────────────────────────────────────────────────────╮
+ │
+ ... 10 hidden (Ctrl+O) ...
+ │
+ │
- -
+ 6
-
- const
-
- oldLine6 =
-
- true
-
- ;
-
-
- 6
+
+ -
+
+
+ const
+
+ oldLine6 =
+
+ true
+
+ ;
+ │
+ │
- +
+ 6
-
- const
-
- newLine6 =
-
- true
-
- ;
-
-
- 7
+
+ +
+
+
+ const
+
+ newLine6 =
+
+ true
+
+ ;
+ │
+ │
- -
+ 7
-
- const
-
- oldLine7 =
-
- true
-
- ;
-
-
- 7
+
+ -
+
+
+ const
+
+ oldLine7 =
+
+ true
+
+ ;
+ │
+ │
- +
+ 7
-
- const
-
- newLine7 =
-
- true
-
- ;
-
-
- 8
+
+ +
+
+
+ const
+
+ newLine7 =
+
+ true
+
+ ;
+ │
+ │
- -
+ 8
-
- const
-
- oldLine8 =
-
- true
-
- ;
-
-
- 8
+
+ -
+
+
+ const
+
+ oldLine8 =
+
+ true
+
+ ;
+ │
+ │
- +
+ 8
-
- const
-
- newLine8 =
-
- true
-
- ;
-
-
- 9
+
+ +
+
+
+ const
+
+ newLine8 =
+
+ true
+
+ ;
+ │
+ │
- -
+ 9
-
- const
-
- oldLine9 =
-
- true
-
- ;
-
-
- 9
+
+ -
+
+
+ const
+
+ oldLine9 =
+
+ true
+
+ ;
+ │
+ │
- +
+ 9
-
- const
-
- newLine9 =
-
- true
-
- ;
-
- 10
-
-
- -
+
+ +
+
+
+ const
+
+ newLine9 =
+
+ true
+
+ ;
+ │
+ │
+
+ 10
-
- const
-
- oldLine10 =
-
- true
-
- ;
-
- 10
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine10 =
+
+ true
+
+ ;
+ │
+ │
+
+ 10
-
- const
-
- newLine10 =
-
- true
-
- ;
-
- 11
-
-
- -
+
+ +
+
+
+ const
+
+ newLine10 =
+
+ true
+
+ ;
+ │
+ │
+
+ 11
-
- const
-
- oldLine11 =
-
- true
-
- ;
-
- 11
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine11 =
+
+ true
+
+ ;
+ │
+ │
+
+ 11
-
- const
-
- newLine11 =
-
- true
-
- ;
-
- 12
-
-
- -
+
+ +
+
+
+ const
+
+ newLine11 =
+
+ true
+
+ ;
+ │
+ │
+
+ 12
-
- const
-
- oldLine12 =
-
- true
-
- ;
-
- 12
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine12 =
+
+ true
+
+ ;
+ │
+ │
+
+ 12
-
- const
-
- newLine12 =
-
- true
-
- ;
-
- 13
-
-
- -
+
+ +
+
+
+ const
+
+ newLine12 =
+
+ true
+
+ ;
+ │
+ │
+
+ 13
-
- const
-
- oldLine13 =
-
- true
-
- ;
-
- 13
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine13 =
+
+ true
+
+ ;
+ │
+ │
+
+ 13
-
- const
-
- newLine13 =
-
- true
-
- ;
-
- 14
-
-
- -
+
+ +
+
+
+ const
+
+ newLine13 =
+
+ true
+
+ ;
+ │
+ │
+
+ 14
-
- const
-
- oldLine14 =
-
- true
-
- ;
-
- 14
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine14 =
+
+ true
+
+ ;
+ │
+ │
+
+ 14
-
- const
-
- newLine14 =
-
- true
-
- ;
-
- 15
-
-
- -
+
+ +
+
+
+ const
+
+ newLine14 =
+
+ true
+
+ ;
+ │
+ │
+
+ 15
-
- const
-
- oldLine15 =
-
- true
-
- ;
-
- 15
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine15 =
+
+ true
+
+ ;
+ │
+ │
+
+ 15
-
- const
-
- newLine15 =
-
- true
-
- ;
-
- 16
-
-
- -
+
+ +
+
+
+ const
+
+ newLine15 =
+
+ true
+
+ ;
+ │
+ │
+
+ 16
-
- const
-
- oldLine16 =
-
- true
-
- ;
-
- 16
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine16 =
+
+ true
+
+ ;
+ │
+ │
+
+ 16
-
- const
-
- newLine16 =
-
- true
-
- ;
-
- 17
-
-
- -
+
+ +
+
+
+ const
+
+ newLine16 =
+
+ true
+
+ ;
+ │
+ │
+
+ 17
-
- const
-
- oldLine17 =
-
- true
-
- ;
-
- 17
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine17 =
+
+ true
+
+ ;
+ │
+ │
+
+ 17
-
- const
-
- newLine17 =
-
- true
-
- ;
-
- 18
-
-
- -
+
+ +
+
+
+ const
+
+ newLine17 =
+
+ true
+
+ ;
+ │
+ │
+
+ 18
-
- const
-
- oldLine18 =
-
- true
-
- ;
-
- 18
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine18 =
+
+ true
+
+ ;
+ │
+ │
+
+ 18
-
- const
-
- newLine18 =
-
- true
-
- ;
-
- 19
-
-
- -
+
+ +
+
+
+ const
+
+ newLine18 =
+
+ true
+
+ ;
+ │
+ │
+
+ 19
-
- const
-
- oldLine19 =
-
- true
-
- ;
-
- 19
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine19 =
+
+ true
+
+ ;
+ │
+ │
+
+ 19
-
- const
-
- newLine19 =
-
- true
-
- ;
-
- 20
-
-
- -
+
+ +
+
+
+ const
+
+ newLine19 =
+
+ true
+
+ ;
+ │
+ │
+
+ 20
-
- const
-
- oldLine20 =
-
- true
-
- ;
-
- 20
-
-
- +
+
+ -
+
+
+ const
+
+ oldLine20 =
+
+ true
+
+ ;
+ │
+ │
+
+ 20
-
- const
-
- newLine20 =
-
- true
-
- ;
- Apply this change?
-
- ●
-
-
- 1.
-
-
- Allow once
-
- 2.
- Allow for this session
- 3.
- Modify with external editor
- 4.
- No, suggest changes (esc)
+
+ +
+
+
+ const
+
+ newLine20 =
+
+ true
+
+ ;
+ │
+ ╰──────────────────────────────────────────────────────────────────────────────╯
+ Apply this change?
+
+ ●
+
+
+ 1.
+
+
+ Allow once
+
+ 2.
+ Allow for this session
+ 3.
+ Modify with external editor
+ 4.
+ No, suggest changes (esc)
\ No newline at end of file
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-height-allocation-and-layout-should-expand-to-available-height-for-large-exec-commands.snap.svg b/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-height-allocation-and-layout-should-expand-to-available-height-for-large-exec-commands.snap.svg
index 4b34a3405f..68e2eb2247 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-height-allocation-and-layout-should-expand-to-available-height-for-large-exec-commands.snap.svg
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-height-allocation-and-layout-should-expand-to-available-height-for-large-exec-commands.snap.svg
@@ -1,87 +1,151 @@
-
+
-
+
- ... first 18 lines hidden (Ctrl+O to show) ...
- echo
- "Line 19"
- echo
- "Line 20"
- echo
- "Line 21"
- echo
- "Line 22"
- echo
- "Line 23"
- echo
- "Line 24"
- echo
- "Line 25"
- echo
- "Line 26"
- echo
- "Line 27"
- echo
- "Line 28"
- echo
- "Line 29"
- echo
- "Line 30"
- echo
- "Line 31"
- echo
- "Line 32"
- echo
- "Line 33"
- echo
- "Line 34"
- echo
- "Line 35"
- echo
- "Line 36"
- echo
- "Line 37"
- echo
- "Line 38"
- echo
- "Line 39"
- echo
- "Line 40"
- echo
- "Line 41"
- echo
- "Line 42"
- echo
- "Line 43"
- echo
- "Line 44"
- echo
- "Line 45"
- echo
- "Line 46"
- echo
- "Line 47"
- echo
- "Line 48"
- echo
- "Line 49"
- echo
- "Line 50"
- Allow execution of: 'echo'?
-
- ●
-
-
- 1.
-
-
- Allow once
-
- 2.
- Allow for this session
- 3.
- No, suggest changes (esc)
+ ╭──────────────────────────────────────────────────────────────────────────────╮
+ │
+ ... 19 hidden (Ctrl+O) ...
+ │
+ │
+ echo
+ "Line 20"
+ │
+ │
+ echo
+ "Line 21"
+ │
+ │
+ echo
+ "Line 22"
+ │
+ │
+ echo
+ "Line 23"
+ │
+ │
+ echo
+ "Line 24"
+ │
+ │
+ echo
+ "Line 25"
+ │
+ │
+ echo
+ "Line 26"
+ │
+ │
+ echo
+ "Line 27"
+ │
+ │
+ echo
+ "Line 28"
+ │
+ │
+ echo
+ "Line 29"
+ │
+ │
+ echo
+ "Line 30"
+ │
+ │
+ echo
+ "Line 31"
+ │
+ │
+ echo
+ "Line 32"
+ │
+ │
+ echo
+ "Line 33"
+ │
+ │
+ echo
+ "Line 34"
+ │
+ │
+ echo
+ "Line 35"
+ │
+ │
+ echo
+ "Line 36"
+ │
+ │
+ echo
+ "Line 37"
+ │
+ │
+ echo
+ "Line 38"
+ │
+ │
+ echo
+ "Line 39"
+ │
+ │
+ echo
+ "Line 40"
+ │
+ │
+ echo
+ "Line 41"
+ │
+ │
+ echo
+ "Line 42"
+ │
+ │
+ echo
+ "Line 43"
+ │
+ │
+ echo
+ "Line 44"
+ │
+ │
+ echo
+ "Line 45"
+ │
+ │
+ echo
+ "Line 46"
+ │
+ │
+ echo
+ "Line 47"
+ │
+ │
+ echo
+ "Line 48"
+ │
+ │
+ echo
+ "Line 49"
+ │
+ │
+ echo
+ "Line 50"
+ │
+ ╰──────────────────────────────────────────────────────────────────────────────╯
+ Allow execution of [echo]?
+
+ ●
+
+
+ 1.
+
+
+ Allow once
+
+ 2.
+ Allow for this session
+ 3.
+ No, suggest changes (esc)
\ No newline at end of file
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-should-render-multiline-shell-scripts-with-correct-newlines-and-syntax-highlighting.snap.svg b/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-should-render-multiline-shell-scripts-with-correct-newlines-and-syntax-highlighting.snap.svg
index d1396e2335..a30b871f41 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-should-render-multiline-shell-scripts-with-correct-newlines-and-syntax-highlighting.snap.svg
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage-ToolConfirmationMessage-should-render-multiline-shell-scripts-with-correct-newlines-and-syntax-highlighting.snap.svg
@@ -1,32 +1,42 @@
-
+
-
+
- echo
- "hello"
- for
- i
- in
- 1 2 3;
- do
- echo
- $i
- done
- Allow execution of: 'echo'?
-
- ●
-
-
- 1.
-
-
- Allow once
-
- 2.
- Allow for this session
- 3.
- No, suggest changes (esc)
+ ╭──────────────────────────────────────────────────────────────────────────────╮
+ │
+ echo
+ "hello"
+ │
+ │
+ for
+ i
+ in
+ 1 2 3;
+ do
+ │
+ │
+ echo
+ $i
+ │
+ │
+ done
+ │
+ ╰──────────────────────────────────────────────────────────────────────────────╯
+ Allow execution of [echo]?
+
+ ●
+
+
+ 1.
+
+
+ Allow once
+
+ 2.
+ Allow for this session
+ 3.
+ No, suggest changes (esc)
\ No newline at end of file
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage.test.tsx.snap
index eb9f856b0b..6d33b6fbfb 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage.test.tsx.snap
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolConfirmationMessage.test.tsx.snap
@@ -3,52 +3,53 @@
exports[`ToolConfirmationMessage > enablePermanentToolApproval setting > should show "Allow for all future sessions" when trusted 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ │
-│ No changes detected. │
+│ No changes detected. │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
Apply this change?
-● 1. Allow once
+● 1. Allow once
2. Allow for this session
- 3. Allow for this file in all future sessions
+ 3. Allow for this file in all future sessions ~/.gemini/policies/auto-saved.toml
4. Modify with external editor
5. No, suggest changes (esc)
"
`;
exports[`ToolConfirmationMessage > height allocation and layout > should expand to available height for large edit diffs 1`] = `
-"... first 9 lines hidden (Ctrl+O to show) ...
- 5 + const newLine5 = true;
- 6 - const oldLine6 = true;
- 6 + const newLine6 = true;
- 7 - const oldLine7 = true;
- 7 + const newLine7 = true;
- 8 - const oldLine8 = true;
- 8 + const newLine8 = true;
- 9 - const oldLine9 = true;
- 9 + const newLine9 = true;
-10 - const oldLine10 = true;
-10 + const newLine10 = true;
-11 - const oldLine11 = true;
-11 + const newLine11 = true;
-12 - const oldLine12 = true;
-12 + const newLine12 = true;
-13 - const oldLine13 = true;
-13 + const newLine13 = true;
-14 - const oldLine14 = true;
-14 + const newLine14 = true;
-15 - const oldLine15 = true;
-15 + const newLine15 = true;
-16 - const oldLine16 = true;
-16 + const newLine16 = true;
-17 - const oldLine17 = true;
-17 + const newLine17 = true;
-18 - const oldLine18 = true;
-18 + const newLine18 = true;
-19 - const oldLine19 = true;
-19 + const newLine19 = true;
-20 - const oldLine20 = true;
-20 + const newLine20 = true;
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ... 10 hidden (Ctrl+O) ... │
+│ 6 - const oldLine6 = true; │
+│ 6 + const newLine6 = true; │
+│ 7 - const oldLine7 = true; │
+│ 7 + const newLine7 = true; │
+│ 8 - const oldLine8 = true; │
+│ 8 + const newLine8 = true; │
+│ 9 - const oldLine9 = true; │
+│ 9 + const newLine9 = true; │
+│ 10 - const oldLine10 = true; │
+│ 10 + const newLine10 = true; │
+│ 11 - const oldLine11 = true; │
+│ 11 + const newLine11 = true; │
+│ 12 - const oldLine12 = true; │
+│ 12 + const newLine12 = true; │
+│ 13 - const oldLine13 = true; │
+│ 13 + const newLine13 = true; │
+│ 14 - const oldLine14 = true; │
+│ 14 + const newLine14 = true; │
+│ 15 - const oldLine15 = true; │
+│ 15 + const newLine15 = true; │
+│ 16 - const oldLine16 = true; │
+│ 16 + const newLine16 = true; │
+│ 17 - const oldLine17 = true; │
+│ 17 + const newLine17 = true; │
+│ 18 - const oldLine18 = true; │
+│ 18 + const newLine18 = true; │
+│ 19 - const oldLine19 = true; │
+│ 19 + const newLine19 = true; │
+│ 20 - const oldLine20 = true; │
+│ 20 + const newLine20 = true; │
+╰──────────────────────────────────────────────────────────────────────────────╯
Apply this change?
● 1. Allow once
@@ -59,40 +60,41 @@ Apply this change?
`;
exports[`ToolConfirmationMessage > height allocation and layout > should expand to available height for large exec commands 1`] = `
-"... first 18 lines hidden (Ctrl+O to show) ...
-echo "Line 19"
-echo "Line 20"
-echo "Line 21"
-echo "Line 22"
-echo "Line 23"
-echo "Line 24"
-echo "Line 25"
-echo "Line 26"
-echo "Line 27"
-echo "Line 28"
-echo "Line 29"
-echo "Line 30"
-echo "Line 31"
-echo "Line 32"
-echo "Line 33"
-echo "Line 34"
-echo "Line 35"
-echo "Line 36"
-echo "Line 37"
-echo "Line 38"
-echo "Line 39"
-echo "Line 40"
-echo "Line 41"
-echo "Line 42"
-echo "Line 43"
-echo "Line 44"
-echo "Line 45"
-echo "Line 46"
-echo "Line 47"
-echo "Line 48"
-echo "Line 49"
-echo "Line 50"
-Allow execution of: 'echo'?
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ ... 19 hidden (Ctrl+O) ... │
+│ echo "Line 20" │
+│ echo "Line 21" │
+│ echo "Line 22" │
+│ echo "Line 23" │
+│ echo "Line 24" │
+│ echo "Line 25" │
+│ echo "Line 26" │
+│ echo "Line 27" │
+│ echo "Line 28" │
+│ echo "Line 29" │
+│ echo "Line 30" │
+│ echo "Line 31" │
+│ echo "Line 32" │
+│ echo "Line 33" │
+│ echo "Line 34" │
+│ echo "Line 35" │
+│ echo "Line 36" │
+│ echo "Line 37" │
+│ echo "Line 38" │
+│ echo "Line 39" │
+│ echo "Line 40" │
+│ echo "Line 41" │
+│ echo "Line 42" │
+│ echo "Line 43" │
+│ echo "Line 44" │
+│ echo "Line 45" │
+│ echo "Line 46" │
+│ echo "Line 47" │
+│ echo "Line 48" │
+│ echo "Line 49" │
+│ echo "Line 50" │
+╰──────────────────────────────────────────────────────────────────────────────╯
+Allow execution of [echo]?
● 1. Allow once
2. Allow for this session
@@ -101,12 +103,14 @@ Allow execution of: 'echo'?
`;
exports[`ToolConfirmationMessage > should display multiple commands for exec type when provided 1`] = `
-"echo "hello"
-
-ls -la
-
-whoami
-Allow execution of 3 commands?
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ echo "hello" │
+│ │
+│ ls -la │
+│ │
+│ whoami │
+╰──────────────────────────────────────────────────────────────────────────────╯
+Allow execution of [echo, ls, whoami]?
● 1. Allow once
2. Allow for this session
@@ -138,16 +142,17 @@ Do you want to proceed?
`;
exports[`ToolConfirmationMessage > should render multiline shell scripts with correct newlines and syntax highlighting 1`] = `
-"echo "hello"
-for i in 1 2 3; do
- echo $i
-done
-Allow execution of: 'echo'?
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ echo "hello" │
+│ for i in 1 2 3; do │
+│ echo $i │
+│ done │
+╰──────────────────────────────────────────────────────────────────────────────╯
+Allow execution of [echo]?
● 1. Allow once
2. Allow for this session
- 3. No, suggest changes (esc)
-"
+ 3. No, suggest changes (esc)"
`;
exports[`ToolConfirmationMessage > should strip BiDi characters from MCP tool and server names 1`] = `
@@ -165,7 +170,7 @@ Allow execution of MCP tool "testtool" from server "testserver"?
exports[`ToolConfirmationMessage > with folder trust > 'for edit confirmations' > should NOT show "allow always" when folder is untrusted 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ │
-│ No changes detected. │
+│ No changes detected. │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
Apply this change?
@@ -179,7 +184,7 @@ Apply this change?
exports[`ToolConfirmationMessage > with folder trust > 'for edit confirmations' > should show "allow always" when folder is trusted 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────╮
│ │
-│ No changes detected. │
+│ No changes detected. │
│ │
╰──────────────────────────────────────────────────────────────────────────────╯
Apply this change?
@@ -192,8 +197,10 @@ Apply this change?
`;
exports[`ToolConfirmationMessage > with folder trust > 'for exec confirmations' > should NOT show "allow always" when folder is untrusted 1`] = `
-"echo "hello"
-Allow execution of: 'echo'?
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ echo "hello" │
+╰──────────────────────────────────────────────────────────────────────────────╯
+Allow execution of [echo]?
● 1. Allow once
2. No, suggest changes (esc)
@@ -201,8 +208,10 @@ Allow execution of: 'echo'?
`;
exports[`ToolConfirmationMessage > with folder trust > 'for exec confirmations' > should show "allow always" when folder is trusted 1`] = `
-"echo "hello"
-Allow execution of: 'echo'?
+"╭──────────────────────────────────────────────────────────────────────────────╮
+│ echo "hello" │
+╰──────────────────────────────────────────────────────────────────────────────╯
+Allow execution of [echo]?
● 1. Allow once
2. Allow for this session
diff --git a/packages/cli/src/ui/components/messages/__snapshots__/ToolResultDisplay.test.tsx.snap b/packages/cli/src/ui/components/messages/__snapshots__/ToolResultDisplay.test.tsx.snap
index 77d99b2792..12eff841b8 100644
--- a/packages/cli/src/ui/components/messages/__snapshots__/ToolResultDisplay.test.tsx.snap
+++ b/packages/cli/src/ui/components/messages/__snapshots__/ToolResultDisplay.test.tsx.snap
@@ -16,11 +16,8 @@ exports[`ToolResultDisplay > renders ANSI output result 1`] = `
`;
exports[`ToolResultDisplay > renders file diff result 1`] = `
-"╭─────────────────────────────────────────────────────────────────────────╮
-│ │
-│ No changes detected. │
-│ │
-╰─────────────────────────────────────────────────────────────────────────╯
+"
+ No changes detected.
"
`;
diff --git a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx
index 7aa40cfc62..baadb3b9d8 100644
--- a/packages/cli/src/ui/components/shared/MaxSizedBox.tsx
+++ b/packages/cli/src/ui/components/shared/MaxSizedBox.tsx
@@ -26,6 +26,7 @@ export interface MaxSizedBoxProps {
maxHeight?: number;
overflowDirection?: 'top' | 'bottom';
additionalHiddenLinesCount?: number;
+ paddingX?: number;
}
/**
@@ -38,6 +39,7 @@ export const MaxSizedBox: React.FC = ({
maxHeight,
overflowDirection = 'top',
additionalHiddenLinesCount = 0,
+ paddingX = 0,
}) => {
const id = useId();
const { addOverflowingId, removeOverflowingId } = useOverflowActions() || {};
@@ -132,11 +134,13 @@ export const MaxSizedBox: React.FC = ({
flexShrink={0}
>
{totalHiddenLines > 0 && overflowDirection === 'top' && (
-
- {isNarrow
- ? `... ${totalHiddenLines} hidden (${showMoreKey}) ...`
- : `... first ${totalHiddenLines} line${totalHiddenLines === 1 ? '' : 's'} hidden (${showMoreKey} to show) ...`}
-
+
+
+ {isNarrow
+ ? `... ${totalHiddenLines} hidden (${showMoreKey}) ...`
+ : `... first ${totalHiddenLines} line${totalHiddenLines === 1 ? '' : 's'} hidden (${showMoreKey} to show) ...`}
+
+
)}
= ({
{totalHiddenLines > 0 && overflowDirection === 'bottom' && (
-
- {isNarrow
- ? `... ${totalHiddenLines} hidden (${showMoreKey}) ...`
- : `... last ${totalHiddenLines} line${totalHiddenLines === 1 ? '' : 's'} hidden (${showMoreKey} to show) ...`}
-
+
+
+ {isNarrow
+ ? `... ${totalHiddenLines} hidden (${showMoreKey}) ...`
+ : `... last ${totalHiddenLines} line${totalHiddenLines === 1 ? '' : 's'} hidden (${showMoreKey} to show) ...`}
+
+
)}
);
diff --git a/packages/cli/src/ui/utils/CodeColorizer.tsx b/packages/cli/src/ui/utils/CodeColorizer.tsx
index 828e041493..07d6429dbe 100644
--- a/packages/cli/src/ui/utils/CodeColorizer.tsx
+++ b/packages/cli/src/ui/utils/CodeColorizer.tsx
@@ -136,6 +136,7 @@ export interface ColorizeCodeOptions {
hideLineNumbers?: boolean;
disableColor?: boolean;
returnLines?: boolean;
+ paddingX?: number;
}
/**
@@ -160,6 +161,7 @@ export function colorizeCode({
hideLineNumbers = false,
disableColor = false,
returnLines = false,
+ paddingX = 0,
}: ColorizeCodeOptions): React.ReactNode | React.ReactNode[] {
const codeToHighlight = code.replace(/\n$/, '');
const activeTheme = theme || themeManager.getActiveTheme();
@@ -167,26 +169,29 @@ export function colorizeCode({
? false
: settings.merged.ui.showLineNumbers;
- const useMaxSizedBox = !settings.merged.ui.useAlternateBuffer && !returnLines;
+ // We force MaxSizedBox if availableHeight is provided, even if alternate buffer is enabled,
+ // because this might be rendered in a constrained UI box (like tool confirmation).
+ const useMaxSizedBox =
+ (!settings.merged.ui.useAlternateBuffer || availableHeight !== undefined) &&
+ !returnLines;
+
+ let hiddenLinesCount = 0;
+ let finalLines = codeToHighlight.split(/\r?\n/);
+
try {
- // Render the HAST tree using the adapted theme
- // Apply the theme's default foreground color to the top-level Text element
- let lines = codeToHighlight.split(/\r?\n/);
- const padWidth = String(lines.length).length; // Calculate padding width based on number of lines
-
- let hiddenLinesCount = 0;
-
// Optimization to avoid highlighting lines that cannot possibly be displayed.
if (availableHeight !== undefined && useMaxSizedBox) {
availableHeight = Math.max(availableHeight, MINIMUM_MAX_HEIGHT);
- if (lines.length > availableHeight) {
- const sliceIndex = lines.length - availableHeight;
+ if (finalLines.length > availableHeight) {
+ const sliceIndex = finalLines.length - availableHeight;
hiddenLinesCount = sliceIndex;
- lines = lines.slice(sliceIndex);
+ finalLines = finalLines.slice(sliceIndex);
}
}
- const renderedLines = lines.map((line, index) => {
+ const padWidth = String(finalLines.length + hiddenLinesCount).length;
+
+ const renderedLines = finalLines.map((line, index) => {
const contentToRender = disableColor
? line
: highlightAndRenderLine(line, language, activeTheme);
@@ -223,6 +228,7 @@ export function colorizeCode({
if (useMaxSizedBox) {
return (
(
+ const padWidth = String(finalLines.length + hiddenLinesCount).length;
+ const fallbackLines = finalLines.map((line, index) => (
{showLineNumbers && (
- {`${index + 1}`}
+ {`${index + 1 + hiddenLinesCount}`}
)}
@@ -275,8 +279,10 @@ export function colorizeCode({
if (useMaxSizedBox) {
return (
{fallbackLines}
diff --git a/packages/core/src/tools/shell.ts b/packages/core/src/tools/shell.ts
index 81ac9d9a32..7ca475808a 100644
--- a/packages/core/src/tools/shell.ts
+++ b/packages/core/src/tools/shell.ts
@@ -136,7 +136,7 @@ export class ShellToolInvocation extends BaseToolInvocation<
}
getDescription(): string {
- return `${this.params.command} ${this.getContextualDetails()}`;
+ return this.params.description || '';
}
private simplifyPaths(paths: Set): string[] {