Compare commits

...

2 Commits

13 changed files with 479 additions and 113 deletions
-65
View File
@@ -1,65 +0,0 @@
---
name: code-reviewer
description:
Use this skill to review code. It supports both local changes (staged or working tree)
and remote Pull Requests (by ID or URL). It focuses on correctness, maintainability,
and adherence to project standards.
---
# Code Reviewer
This skill guides the agent in conducting professional and thorough code reviews for both local development and remote Pull Requests.
## Workflow
### 1. Determine Review Target
* **Remote PR**: If the user provides a PR number or URL (e.g., "Review PR #123"), target that remote PR.
* **Local Changes**: If no specific PR is mentioned, or if the user asks to "review my changes", target the current local file system states (staged and unstaged changes).
### 2. Preparation
#### For Remote PRs:
1. **Checkout**: Use the GitHub CLI to checkout the PR.
```bash
gh pr checkout <PR_NUMBER>
```
2. **Preflight**: Execute the project's standard verification suite to catch automated failures early.
```bash
npm run preflight
```
3. **Context**: Read the PR description and any existing comments to understand the goal and history.
#### For Local Changes:
1. **Identify Changes**:
* Check status: `git status`
* Read diffs: `git diff` (working tree) and/or `git diff --staged` (staged).
2. **Preflight (Optional)**: If the changes are substantial, ask the user if they want to run `npm run preflight` before reviewing.
### 3. In-Depth Analysis
Analyze the code changes based on the following pillars:
* **Correctness**: Does the code achieve its stated purpose without bugs or logical errors?
* **Maintainability**: Is the code clean, well-structured, and easy to understand and modify in the future? Consider factors like code clarity, modularity, and adherence to established design patterns.
* **Readability**: Is the code well-commented (where necessary) and consistently formatted according to our project's coding style guidelines?
* **Efficiency**: Are there any obvious performance bottlenecks or resource inefficiencies introduced by the changes?
* **Security**: Are there any potential security vulnerabilities or insecure coding practices?
* **Edge Cases and Error Handling**: Does the code appropriately handle edge cases and potential errors?
* **Testability**: Is the new or modified code adequately covered by tests (even if preflight checks pass)? Suggest additional test cases that would improve coverage or robustness.
### 4. Provide Feedback
#### Structure
* **Summary**: A high-level overview of the review.
* **Findings**:
* **Critical**: Bugs, security issues, or breaking changes.
* **Improvements**: Suggestions for better code quality or performance.
* **Nitpicks**: Formatting or minor style issues (optional).
* **Conclusion**: Clear recommendation (Approved / Request Changes).
#### Tone
* Be constructive, professional, and friendly.
* Explain *why* a change is requested.
* For approvals, acknowledge the specific value of the contribution.
### 5. Cleanup (Remote PRs only)
* After the review, ask the user if they want to switch back to the default branch (e.g., `main` or `master`).
+2
View File
@@ -524,6 +524,8 @@ const baseMockUiState = {
nightly: false,
updateInfo: null,
pendingHistoryItems: [],
mainControlsRef: () => {},
rootUiRef: { current: null },
};
export const mockAppState: AppState = {
+1 -3
View File
@@ -70,9 +70,7 @@ describe('App', () => {
cleanUiDetailsVisible: true,
quittingMessages: null,
dialogsVisible: false,
mainControlsRef: {
current: null,
} as unknown as React.MutableRefObject<DOMElement | null>,
mainControlsRef: vi.fn(),
rootUiRef: {
current: null,
} as unknown as React.MutableRefObject<DOMElement | null>,
+21 -10
View File
@@ -14,7 +14,7 @@ import {
} from 'react';
import {
type DOMElement,
measureElement,
ResizeObserver,
useApp,
useStdout,
useStdin,
@@ -397,7 +397,6 @@ export const AppContainer = (props: AppContainerProps) => {
const branchName = useGitBranchName(config.getTargetDir());
// Layout measurements
const mainControlsRef = useRef<DOMElement>(null);
// For performance profiling only
const rootUiRef = useRef<DOMElement>(null);
const lastTitleRef = useRef<string | null>(null);
@@ -1396,6 +1395,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
!proQuotaRequest &&
!copyModeEnabled;
const observerRef = useRef<ResizeObserver | null>(null);
const [controlsHeight, setControlsHeight] = useState(0);
const [lastNonCopyControlsHeight, setLastNonCopyControlsHeight] = useState(0);
@@ -1410,15 +1410,26 @@ Logging in with Google... Restarting Gemini CLI to continue.
? lastNonCopyControlsHeight
: controlsHeight;
useLayoutEffect(() => {
if (mainControlsRef.current) {
const fullFooterMeasurement = measureElement(mainControlsRef.current);
const roundedHeight = Math.round(fullFooterMeasurement.height);
if (roundedHeight > 0 && roundedHeight !== controlsHeight) {
setControlsHeight(roundedHeight);
}
const mainControlsRef = useCallback((node: DOMElement | null) => {
if (observerRef.current) {
observerRef.current.disconnect();
observerRef.current = null;
}
}, [buffer, terminalWidth, terminalHeight, controlsHeight, isInputActive]);
if (node) {
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
if (entry) {
const roundedHeight = Math.round(entry.contentRect.height);
setControlsHeight((prev) =>
roundedHeight !== prev ? roundedHeight : prev,
);
}
});
observer.observe(node);
observerRef.current = observer;
}
}, []);
// Compute available terminal height based on stable controls measurement
const availableTerminalHeight = Math.max(
@@ -1491,4 +1491,47 @@ describe('AskUserDialog', () => {
expect(frame).toContain('3. Option 3');
});
});
it('allows the question to exceed 15 lines in a tall terminal', async () => {
const longQuestion = Array.from(
{ length: 25 },
(_, i) => `Line ${i + 1}`,
).join('\n');
const questions: Question[] = [
{
question: longQuestion,
header: 'Tall Test',
type: QuestionType.CHOICE,
options: [
{ label: 'Option 1', description: 'D1' },
{ label: 'Option 2', description: 'D2' },
{ label: 'Option 3', description: 'D3' },
],
multiSelect: false,
unconstrainedHeight: false,
},
];
const { lastFrame, waitUntilReady } = await renderWithProviders(
<AskUserDialog
questions={questions}
onSubmit={vi.fn()}
onCancel={vi.fn()}
width={80}
availableHeight={40} // Tall terminal
/>,
{ width: 80 },
);
await waitFor(async () => {
await waitUntilReady();
const frame = lastFrame();
// Should show more than 15 lines of the question
// (The limit was previously 15, so showing Line 20 proves it's working)
expect(frame).toContain('Line 20');
expect(frame).toContain('Line 25');
// Should still show the options
expect(frame).toContain('1. Option 1');
});
});
});
@@ -855,13 +855,7 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
listHeight && !isAlternateBuffer
? question.unconstrainedHeight
? Math.max(1, listHeight - selectionItems.length * 2)
: Math.min(
15,
Math.max(
1,
listHeight - Math.max(DIALOG_PADDING, reservedListHeight),
),
)
: Math.max(1, listHeight - Math.max(DIALOG_PADDING, reservedListHeight))
: undefined;
const maxItemsToShow =
@@ -21,6 +21,7 @@ import {
type UIState,
} from '../contexts/UIStateContext.js';
import { type IndividualToolCallDisplay } from '../types.js';
import { type ConfirmingToolState } from '../hooks/useConfirmingTool.js';
// Mock dependencies
const mockUseSettings = vi.fn().mockReturnValue({
@@ -53,6 +54,10 @@ vi.mock('../hooks/useAlternateBuffer.js', () => ({
useAlternateBuffer: vi.fn(),
}));
vi.mock('../hooks/useConfirmingTool.js', () => ({
useConfirmingTool: vi.fn(),
}));
vi.mock('./AppHeader.js', () => ({
AppHeader: ({ showDetails = true }: { showDetails?: boolean }) => (
<Text>{showDetails ? 'AppHeader(full)' : 'AppHeader(minimal)'}</Text>
@@ -503,6 +508,53 @@ describe('MainContent', () => {
unmount();
});
it('renders a subagent with a complete box including bottom border', async () => {
const subagentCall = {
callId: 'subagent-1',
name: 'codebase_investigator',
description: 'Investigating codebase',
status: CoreToolCallStatus.Executing,
kind: 'agent',
resultDisplay: {
isSubagentProgress: true,
agentName: 'codebase_investigator',
recentActivity: [
{
id: '1',
type: 'tool_call',
content: 'run_shell_command',
args: '{"command": "echo hello"}',
status: 'running',
},
],
state: 'running',
},
} as Partial<IndividualToolCallDisplay> as IndividualToolCallDisplay;
const uiState = {
...defaultMockUiState,
history: [{ id: 1, type: 'user', text: 'Investigate' }],
pendingHistoryItems: [
{
type: 'tool_group' as const,
tools: [subagentCall],
borderBottom: true,
},
],
};
const { lastFrame, unmount } = await renderWithProviders(<MainContent />, {
uiState: uiState as Partial<UIState>,
config: makeFakeConfig({ useAlternateBuffer: false }),
});
const output = lastFrame();
expect(output).toContain('codebase_investigator');
expect(output).toMatchSnapshot();
unmount();
});
it('renders a split tool group without a gap between static and pending areas', async () => {
const toolCalls = [
{
@@ -557,6 +609,111 @@ describe('MainContent', () => {
unmount();
});
it('renders a ToolConfirmationQueue without an extra line when preceded by hidden tools', async () => {
const { ApprovalMode, WRITE_FILE_DISPLAY_NAME } = await import(
'@google/gemini-cli-core'
);
const hiddenToolCalls = [
{
callId: 'tool-hidden',
name: WRITE_FILE_DISPLAY_NAME,
approvalMode: ApprovalMode.PLAN,
status: CoreToolCallStatus.Success,
resultDisplay: 'Hidden content',
} as Partial<IndividualToolCallDisplay> as IndividualToolCallDisplay,
];
const confirmingTool = {
tool: {
callId: 'call-1',
name: 'exit_plan_mode',
status: CoreToolCallStatus.AwaitingApproval,
confirmationDetails: {
type: 'exit_plan_mode' as const,
planPath: '/path/to/plan',
},
},
index: 1,
total: 1,
};
const uiState = {
...defaultMockUiState,
history: [{ id: 1, type: 'user', text: 'Apply plan' }],
pendingHistoryItems: [
{
type: 'tool_group' as const,
tools: hiddenToolCalls,
borderBottom: true,
},
],
};
// We need to mock useConfirmingTool to return our confirmingTool
const { useConfirmingTool } = await import('../hooks/useConfirmingTool.js');
vi.mocked(useConfirmingTool).mockReturnValue(
confirmingTool as unknown as ConfirmingToolState,
);
mockUseSettings.mockReturnValue(
createMockSettings({
security: { enablePermanentToolApproval: true },
ui: { errorVerbosity: 'full' },
}),
);
const { lastFrame, unmount } = await renderWithProviders(<MainContent />, {
uiState: uiState as Partial<UIState>,
config: makeFakeConfig({ useAlternateBuffer: false }),
});
const output = lastFrame();
// The output should NOT contain 'Hidden content'
expect(output).not.toContain('Hidden content');
// The output should contain the confirmation header
expect(output).toContain('Ready to start implementation?');
// Snapshot will reveal if there are extra blank lines
expect(output).toMatchSnapshot();
unmount();
});
it('renders a spurious line when a tool group has only hidden tools and borderBottom true', async () => {
const { ApprovalMode, WRITE_FILE_DISPLAY_NAME } = await import(
'@google/gemini-cli-core'
);
const uiState = {
...defaultMockUiState,
history: [{ id: 1, type: 'user', text: 'Apply plan' }],
pendingHistoryItems: [
{
type: 'tool_group' as const,
tools: [
{
callId: 'tool-1',
name: WRITE_FILE_DISPLAY_NAME,
approvalMode: ApprovalMode.PLAN,
status: CoreToolCallStatus.Success,
resultDisplay: 'hidden',
} as Partial<IndividualToolCallDisplay> as IndividualToolCallDisplay,
],
borderBottom: true,
},
],
};
const { lastFrame, unmount } = await renderWithProviders(<MainContent />, {
uiState: uiState as Partial<UIState>,
config: makeFakeConfig({ useAlternateBuffer: false }),
});
const output = lastFrame();
// This snapshot will show no spurious line because the group is now correctly suppressed.
expect(output).toMatchSnapshot();
unmount();
});
it('renders multiple thinking messages sequentially correctly', async () => {
mockUseSettings.mockReturnValue({
merged: {
@@ -91,6 +91,19 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Unc
"
`;
exports[`MainContent > renders a ToolConfirmationQueue without an extra line when preceded by hidden tools 1`] = `
"AppHeader(full)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Apply plan
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
╭──────────────────────────────────────────────────────────────────────────────╮
│ Ready to start implementation? │
│ │
│ Error reading plan: Storage must be initialized before use │
╰──────────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`MainContent > renders a split tool group without a gap between static and pending areas 1`] = `
"AppHeader(full)
╭──────────────────────────────────────────────────────────────────────────╮
@@ -105,6 +118,30 @@ exports[`MainContent > renders a split tool group without a gap between static a
"
`;
exports[`MainContent > renders a spurious line when a tool group has only hidden tools and borderBottom true 1`] = `
"AppHeader(full)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Apply plan
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
"
`;
exports[`MainContent > renders a subagent with a complete box including bottom border 1`] = `
"AppHeader(full)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Investigate
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
╭──────────────────────────────────────────────────────────────────────────╮
│ ≡ Running Agent... (ctrl+o to collapse) │
│ │
│ Running subagent codebase_investigator... │
│ │
│ ⠋ run_shell_command echo hello │
│ │
╰──────────────────────────────────────────────────────────────────────────╯
"
`;
exports[`MainContent > renders mixed history items (user + gemini) with single line padding between them 1`] = `
"ScrollableList
AppHeader(full)
@@ -172,12 +172,10 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
// If all tools are filtered out (e.g., in-progress AskUser tools, low-verbosity
// internal errors, plan-mode hidden write/edit), we should not emit standalone
// border fragments. The only case where an empty group should render is the
// explicit "closing slice" (tools: []) used to bridge static/pending sections.
// explicit "closing slice" (tools: []) used to bridge static/pending sections,
// and only if it's actually continuing an open box from above.
const isExplicitClosingSlice = allToolCalls.length === 0;
if (
visibleToolCalls.length === 0 &&
(!isExplicitClosingSlice || borderBottomOverride !== true)
) {
if (visibleToolCalls.length === 0 && !isExplicitClosingSlice) {
return null;
}
@@ -269,19 +267,20 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
We have to keep the bottom border separate so it doesn't get
drawn over by the sticky header directly inside it.
*/
(visibleToolCalls.length > 0 || borderBottomOverride !== undefined) && (
<Box
height={0}
width={contentWidth}
borderLeft={true}
borderRight={true}
borderTop={false}
borderBottom={borderBottomOverride ?? true}
borderColor={borderColor}
borderDimColor={borderDimColor}
borderStyle="round"
/>
)
(visibleToolCalls.length > 0 || borderBottomOverride !== undefined) &&
borderBottomOverride !== false && (
<Box
height={0}
width={contentWidth}
borderLeft={true}
borderRight={true}
borderTop={false}
borderBottom={borderBottomOverride ?? true}
borderColor={borderColor}
borderDimColor={borderDimColor}
borderStyle="round"
/>
)
}
</Box>
);
@@ -0,0 +1,160 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { renderWithProviders } from '../../../test-utils/render.js';
import { describe, it, expect } from 'vitest';
import { ToolGroupMessage } from './ToolGroupMessage.js';
import {
makeFakeConfig,
CoreToolCallStatus,
ApprovalMode,
WRITE_FILE_DISPLAY_NAME,
Kind,
} from '@google/gemini-cli-core';
import os from 'node:os';
import { createMockSettings } from '../../../test-utils/settings.js';
import type { IndividualToolCallDisplay } from '../../types.js';
describe('ToolGroupMessage Regression Tests', () => {
const baseMockConfig = makeFakeConfig({
model: 'gemini-pro',
targetDir: os.tmpdir(),
});
const fullVerbositySettings = createMockSettings({
ui: { errorVerbosity: 'full' },
});
const createToolCall = (
overrides: Partial<IndividualToolCallDisplay> = {},
): IndividualToolCallDisplay =>
({
callId: 'tool-123',
name: 'test-tool',
status: CoreToolCallStatus.Success,
...overrides,
}) as IndividualToolCallDisplay;
const createItem = (tools: IndividualToolCallDisplay[]) => ({
id: 1,
type: 'tool_group' as const,
tools,
});
it('Plan Mode: suppresses phantom tool group (hidden tools)', async () => {
const toolCalls = [
createToolCall({
name: WRITE_FILE_DISPLAY_NAME,
approvalMode: ApprovalMode.PLAN,
status: CoreToolCallStatus.Success,
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = await renderWithProviders(
<ToolGroupMessage
terminalWidth={80}
item={item}
toolCalls={toolCalls}
borderBottom={true}
/>,
{ config: baseMockConfig, settings: fullVerbositySettings },
);
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('Agent Case: suppresses the bottom border box for ongoing agents (no vertical ticks)', async () => {
const toolCalls = [
createToolCall({
name: 'agent',
kind: Kind.Agent,
status: CoreToolCallStatus.Executing,
resultDisplay: {
isSubagentProgress: true,
agentName: 'TestAgent',
state: 'running',
recentActivity: [],
},
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = await renderWithProviders(
<ToolGroupMessage
terminalWidth={80}
item={item}
toolCalls={toolCalls}
borderBottom={false} // Ongoing
/>,
{ config: baseMockConfig, settings: fullVerbositySettings },
);
const output = lastFrame();
expect(output).toContain('Running Agent...');
// It should render side borders from the content
expect(output).toContain('│');
// It should NOT render the bottom border box (no corners ╰ ╯)
expect(output).not.toContain('╰');
expect(output).not.toContain('╯');
unmount();
});
it('Agent Case: renders a bottom border horizontal line for completed agents', async () => {
const toolCalls = [
createToolCall({
name: 'agent',
kind: Kind.Agent,
status: CoreToolCallStatus.Success,
resultDisplay: {
isSubagentProgress: true,
agentName: 'TestAgent',
state: 'completed',
recentActivity: [],
},
}),
];
const item = createItem(toolCalls);
const { lastFrame, unmount } = await renderWithProviders(
<ToolGroupMessage
terminalWidth={80}
item={item}
toolCalls={toolCalls}
borderBottom={true} // Completed
/>,
{ config: baseMockConfig, settings: fullVerbositySettings },
);
const output = lastFrame();
// Verify it rendered subagent content
expect(output).toContain('Agent');
// It should render the bottom horizontal line
expect(output).toContain(
'╰──────────────────────────────────────────────────────────────────────────╯',
);
unmount();
});
it('Bridges: still renders a bridge if it has a top border', async () => {
const toolCalls: IndividualToolCallDisplay[] = [];
const item = createItem(toolCalls);
const { lastFrame, unmount } = await renderWithProviders(
<ToolGroupMessage
terminalWidth={80}
item={item}
toolCalls={toolCalls}
borderTop={true}
borderBottom={true}
/>,
{ config: baseMockConfig, settings: fullVerbositySettings },
);
expect(lastFrame({ allowEmpty: true })).not.toBe('');
unmount();
});
});
@@ -191,7 +191,7 @@ export interface UIState {
sessionStats: SessionStatsState;
terminalWidth: number;
terminalHeight: number;
mainControlsRef: React.MutableRefObject<DOMElement | null>;
mainControlsRef: React.RefCallback<DOMElement | null>;
// NOTE: This is for performance profiling only.
rootUiRef: React.MutableRefObject<DOMElement | null>;
currentIDE: IdeInfo | null;
+38 -8
View File
@@ -26,7 +26,6 @@ import {
debugLogger,
runInDevTraceSpan,
EDIT_TOOL_NAMES,
ASK_USER_TOOL_NAME,
processRestorableToolCalls,
recordToolCallInteractions,
ToolErrorType,
@@ -40,6 +39,7 @@ import {
isBackgroundExecutionData,
Kind,
ACTIVATE_SKILL_TOOL_NAME,
shouldHideToolCall,
} from '@google/gemini-cli-core';
import type {
Config,
@@ -66,7 +66,12 @@ import type {
SlashCommandProcessorResult,
HistoryItemModel,
} from '../types.js';
import { StreamingState, MessageType } from '../types.js';
import {
StreamingState,
MessageType,
mapCoreStatusToDisplayStatus,
ToolCallStatus,
} from '../types.js';
import { isAtCommand, isSlashCommand } from '../utils/commandUtils.js';
import { useShellCommandProcessor } from './shellCommandProcessor.js';
import { handleAtCommand } from './atCommandProcessor.js';
@@ -541,14 +546,39 @@ export const useGeminiStream = (
const anyVisibleInHistory = pushedToolCallIds.size > 0;
const anyVisibleInPending = remainingTools.some((tc) => {
// AskUser tools are rendered by AskUserDialog, not ToolGroupMessage
const isInProgress =
tc.status !== 'success' &&
tc.status !== 'error' &&
tc.status !== 'cancelled';
if (tc.request.name === ASK_USER_TOOL_NAME && isInProgress) {
const displayName = tc.tool?.displayName ?? tc.request.name;
let hasResultDisplay = false;
if (
tc.status === CoreToolCallStatus.Success ||
tc.status === CoreToolCallStatus.Error ||
tc.status === CoreToolCallStatus.Cancelled
) {
hasResultDisplay = !!tc.response?.resultDisplay;
} else if (tc.status === CoreToolCallStatus.Executing) {
hasResultDisplay = !!tc.liveOutput;
}
// AskUser tools and Plan Mode write/edit are handled by this logic
if (
shouldHideToolCall({
displayName,
status: tc.status,
approvalMode: tc.approvalMode,
hasResultDisplay,
parentCallId: tc.request.parentCallId,
})
) {
return false;
}
// ToolGroupMessage explicitly hides Confirming tools because they are
// rendered in the interactive ToolConfirmationQueue instead.
const displayStatus = mapCoreStatusToDisplayStatus(tc.status);
if (displayStatus === ToolCallStatus.Confirming) {
return false;
}
// ToolGroupMessage now shows all non-canceled tools, so they are visible
// in pending and we need to draw the closing border for them.
return true;
@@ -25,7 +25,7 @@ const mockUIState = {
dialogsVisible: false,
streamingState: StreamingState.Idle,
isBackgroundShellListOpen: false,
mainControlsRef: { current: null },
mainControlsRef: vi.fn(),
customDialog: null,
historyManager: { addItem: vi.fn() },
history: [],