mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
Merge branch 'fix_commands' of https://github.com/google-gemini/gemini-cli into fix_commands
This commit is contained in:
@@ -216,8 +216,18 @@ describe('App', () => {
|
|||||||
|
|
||||||
const stateWithConfirmingTool = {
|
const stateWithConfirmingTool = {
|
||||||
...mockUIState,
|
...mockUIState,
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
pendingGeminiHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pendingGeminiHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
} as UIState;
|
} as UIState;
|
||||||
|
|
||||||
const configWithExperiment = makeFakeConfig();
|
const configWithExperiment = makeFakeConfig();
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ export const AlternateBufferQuittingDisplay = () => {
|
|||||||
terminalWidth={uiState.mainAreaWidth}
|
terminalWidth={uiState.mainAreaWidth}
|
||||||
item={{ ...item, id: 0 }}
|
item={{ ...item, id: 0 }}
|
||||||
isPending={true}
|
isPending={true}
|
||||||
activeShellPtyId={uiState.activePtyId}
|
|
||||||
embeddedShellFocused={uiState.embeddedShellFocused}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{showPromptedTool && (
|
{showPromptedTool && (
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ interface HistoryItemDisplayProps {
|
|||||||
terminalWidth: number;
|
terminalWidth: number;
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
commands?: readonly SlashCommand[];
|
commands?: readonly SlashCommand[];
|
||||||
activeShellPtyId?: number | null;
|
|
||||||
embeddedShellFocused?: boolean;
|
|
||||||
availableTerminalHeightGemini?: number;
|
availableTerminalHeightGemini?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +53,6 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
terminalWidth,
|
terminalWidth,
|
||||||
isPending,
|
isPending,
|
||||||
commands,
|
commands,
|
||||||
activeShellPtyId,
|
|
||||||
embeddedShellFocused,
|
|
||||||
availableTerminalHeightGemini,
|
availableTerminalHeightGemini,
|
||||||
}) => {
|
}) => {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
@@ -173,12 +169,10 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
|||||||
)}
|
)}
|
||||||
{itemForDisplay.type === 'tool_group' && (
|
{itemForDisplay.type === 'tool_group' && (
|
||||||
<ToolGroupMessage
|
<ToolGroupMessage
|
||||||
|
item={itemForDisplay}
|
||||||
toolCalls={itemForDisplay.tools}
|
toolCalls={itemForDisplay.tools}
|
||||||
groupId={itemForDisplay.id}
|
|
||||||
availableTerminalHeight={availableTerminalHeight}
|
availableTerminalHeight={availableTerminalHeight}
|
||||||
terminalWidth={terminalWidth}
|
terminalWidth={terminalWidth}
|
||||||
activeShellPtyId={activeShellPtyId}
|
|
||||||
embeddedShellFocused={embeddedShellFocused}
|
|
||||||
borderTop={itemForDisplay.borderTop}
|
borderTop={itemForDisplay.borderTop}
|
||||||
borderBottom={itemForDisplay.borderBottom}
|
borderBottom={itemForDisplay.borderBottom}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import { renderWithProviders } from '../../test-utils/render.js';
|
import { renderWithProviders } from '../../test-utils/render.js';
|
||||||
import { waitFor } from '../../test-utils/async.js';
|
import { waitFor } from '../../test-utils/async.js';
|
||||||
import { MainContent } from './MainContent.js';
|
import { MainContent } from './MainContent.js';
|
||||||
|
import { getToolGroupBorderAppearance } from '../utils/borderStyles.js';
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import { act, useState, type JSX } from 'react';
|
import { act, useState, type JSX } from 'react';
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
type UIState,
|
type UIState,
|
||||||
} from '../contexts/UIStateContext.js';
|
} from '../contexts/UIStateContext.js';
|
||||||
import { CoreToolCallStatus } from '@google/gemini-cli-core';
|
import { CoreToolCallStatus } from '@google/gemini-cli-core';
|
||||||
|
import { type IndividualToolCallDisplay } from '../types.js';
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
vi.mock('../contexts/SettingsContext.js', async () => {
|
vi.mock('../contexts/SettingsContext.js', async () => {
|
||||||
@@ -76,6 +78,209 @@ vi.mock('./shared/ScrollableList.js', () => ({
|
|||||||
SCROLL_TO_ITEM_END: 0,
|
SCROLL_TO_ITEM_END: 0,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
import { theme } from '../semantic-colors.js';
|
||||||
|
import { type BackgroundShell } from '../hooks/shellReducer.js';
|
||||||
|
|
||||||
|
describe('getToolGroupBorderAppearance', () => {
|
||||||
|
const mockBackgroundShells = new Map<number, BackgroundShell>();
|
||||||
|
const activeShellPtyId = 123;
|
||||||
|
|
||||||
|
it('returns default empty values for non-tool_group items', () => {
|
||||||
|
const item = { type: 'user' as const, text: 'Hello', id: 1 };
|
||||||
|
const result = getToolGroupBorderAppearance(
|
||||||
|
item,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
mockBackgroundShells,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ borderColor: '', borderDimColor: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inspects only the last pending tool_group item if current has no tools', () => {
|
||||||
|
const item = { type: 'tool_group' as const, tools: [], id: 1 };
|
||||||
|
const pendingItems = [
|
||||||
|
{
|
||||||
|
type: 'tool_group' as const,
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
callId: '1',
|
||||||
|
name: 'some_tool',
|
||||||
|
description: '',
|
||||||
|
status: CoreToolCallStatus.Executing,
|
||||||
|
ptyId: undefined,
|
||||||
|
resultDisplay: undefined,
|
||||||
|
confirmationDetails: undefined,
|
||||||
|
} as IndividualToolCallDisplay,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'tool_group' as const,
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
callId: '2',
|
||||||
|
name: 'other_tool',
|
||||||
|
description: '',
|
||||||
|
status: CoreToolCallStatus.Success,
|
||||||
|
ptyId: undefined,
|
||||||
|
resultDisplay: undefined,
|
||||||
|
confirmationDetails: undefined,
|
||||||
|
} as IndividualToolCallDisplay,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Only the last item (Success) should be inspected, so hasPending = false.
|
||||||
|
// The previous item was Executing (pending) but it shouldn't be counted.
|
||||||
|
const result = getToolGroupBorderAppearance(
|
||||||
|
item,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
pendingItems,
|
||||||
|
mockBackgroundShells,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
borderColor: theme.border.default,
|
||||||
|
borderDimColor: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns default border for completed normal tools', () => {
|
||||||
|
const item = {
|
||||||
|
type: 'tool_group' as const,
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
callId: '1',
|
||||||
|
name: 'some_tool',
|
||||||
|
description: '',
|
||||||
|
status: CoreToolCallStatus.Success,
|
||||||
|
ptyId: undefined,
|
||||||
|
resultDisplay: undefined,
|
||||||
|
confirmationDetails: undefined,
|
||||||
|
} as IndividualToolCallDisplay,
|
||||||
|
],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const result = getToolGroupBorderAppearance(
|
||||||
|
item,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
mockBackgroundShells,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
borderColor: theme.border.default,
|
||||||
|
borderDimColor: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns warning border for pending normal tools', () => {
|
||||||
|
const item = {
|
||||||
|
type: 'tool_group' as const,
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
callId: '1',
|
||||||
|
name: 'some_tool',
|
||||||
|
description: '',
|
||||||
|
status: CoreToolCallStatus.Executing,
|
||||||
|
ptyId: undefined,
|
||||||
|
resultDisplay: undefined,
|
||||||
|
confirmationDetails: undefined,
|
||||||
|
} as IndividualToolCallDisplay,
|
||||||
|
],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const result = getToolGroupBorderAppearance(
|
||||||
|
item,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
mockBackgroundShells,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
borderColor: theme.status.warning,
|
||||||
|
borderDimColor: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns symbol border for executing shell commands', () => {
|
||||||
|
const item = {
|
||||||
|
type: 'tool_group' as const,
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
callId: '1',
|
||||||
|
name: SHELL_COMMAND_NAME,
|
||||||
|
description: '',
|
||||||
|
status: CoreToolCallStatus.Executing,
|
||||||
|
ptyId: activeShellPtyId,
|
||||||
|
resultDisplay: undefined,
|
||||||
|
confirmationDetails: undefined,
|
||||||
|
} as IndividualToolCallDisplay,
|
||||||
|
],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
// While executing shell commands, it's dim false, border symbol
|
||||||
|
const result = getToolGroupBorderAppearance(
|
||||||
|
item,
|
||||||
|
activeShellPtyId,
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
mockBackgroundShells,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
borderColor: theme.ui.symbol,
|
||||||
|
borderDimColor: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns symbol border and dims color for background executing shell command when another shell is active', () => {
|
||||||
|
const item = {
|
||||||
|
type: 'tool_group' as const,
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
callId: '1',
|
||||||
|
name: SHELL_COMMAND_NAME,
|
||||||
|
description: '',
|
||||||
|
status: CoreToolCallStatus.Executing,
|
||||||
|
ptyId: 456, // Different ptyId, not active
|
||||||
|
resultDisplay: undefined,
|
||||||
|
confirmationDetails: undefined,
|
||||||
|
} as IndividualToolCallDisplay,
|
||||||
|
],
|
||||||
|
id: 1,
|
||||||
|
};
|
||||||
|
const result = getToolGroupBorderAppearance(
|
||||||
|
item,
|
||||||
|
activeShellPtyId,
|
||||||
|
false,
|
||||||
|
[],
|
||||||
|
mockBackgroundShells,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
borderColor: theme.ui.symbol,
|
||||||
|
borderDimColor: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty tools with active shell turn (isCurrentlyInShellTurn)', () => {
|
||||||
|
const item = { type: 'tool_group' as const, tools: [], id: 1 };
|
||||||
|
|
||||||
|
// active shell turn
|
||||||
|
const result = getToolGroupBorderAppearance(
|
||||||
|
item,
|
||||||
|
activeShellPtyId,
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
mockBackgroundShells,
|
||||||
|
);
|
||||||
|
// Since there are no tools to inspect, it falls back to empty pending, but isCurrentlyInShellTurn=true
|
||||||
|
// so it counts as pending shell.
|
||||||
|
expect(result.borderColor).toEqual(theme.ui.symbol);
|
||||||
|
// It shouldn't be dim because there are no tools to say it isEmbeddedShellFocused = false
|
||||||
|
expect(result.borderDimColor).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('MainContent', () => {
|
describe('MainContent', () => {
|
||||||
const defaultMockUiState = {
|
const defaultMockUiState = {
|
||||||
history: [
|
history: [
|
||||||
@@ -258,7 +463,7 @@ describe('MainContent', () => {
|
|||||||
history: [],
|
history: [],
|
||||||
pendingHistoryItems: [
|
pendingHistoryItems: [
|
||||||
{
|
{
|
||||||
type: 'tool_group' as const,
|
type: 'tool_group',
|
||||||
id: 1,
|
id: 1,
|
||||||
tools: [
|
tools: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -88,8 +88,6 @@ export const MainContent = () => {
|
|||||||
terminalWidth={mainAreaWidth}
|
terminalWidth={mainAreaWidth}
|
||||||
item={{ ...item, id: 0 }}
|
item={{ ...item, id: 0 }}
|
||||||
isPending={true}
|
isPending={true}
|
||||||
activeShellPtyId={uiState.activePtyId}
|
|
||||||
embeddedShellFocused={uiState.embeddedShellFocused}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{showConfirmationQueue && confirmingTool && (
|
{showConfirmationQueue && confirmingTool && (
|
||||||
@@ -103,8 +101,6 @@ export const MainContent = () => {
|
|||||||
isAlternateBuffer,
|
isAlternateBuffer,
|
||||||
availableTerminalHeight,
|
availableTerminalHeight,
|
||||||
mainAreaWidth,
|
mainAreaWidth,
|
||||||
uiState.activePtyId,
|
|
||||||
uiState.embeddedShellFocused,
|
|
||||||
showConfirmationQueue,
|
showConfirmationQueue,
|
||||||
confirmingTool,
|
confirmingTool,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -88,20 +88,16 @@ describe('<ShellToolMessage />', () => {
|
|||||||
CoreToolCallStatus.Executing,
|
CoreToolCallStatus.Executing,
|
||||||
);
|
);
|
||||||
updateStatus = setStatus;
|
updateStatus = setStatus;
|
||||||
return (
|
return <ShellToolMessage {...baseProps} status={status} ptyId={1} />;
|
||||||
<ShellToolMessage
|
|
||||||
{...baseProps}
|
|
||||||
status={status}
|
|
||||||
embeddedShellFocused={true}
|
|
||||||
activeShellPtyId={1}
|
|
||||||
ptyId={1}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { lastFrame } = renderWithProviders(<Wrapper />, {
|
const { lastFrame } = renderWithProviders(<Wrapper />, {
|
||||||
uiActions,
|
uiActions,
|
||||||
uiState: { streamingState: StreamingState.Idle },
|
uiState: {
|
||||||
|
streamingState: StreamingState.Idle,
|
||||||
|
embeddedShellFocused: true,
|
||||||
|
activePtyId: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify it is initially focused
|
// Verify it is initially focused
|
||||||
@@ -143,21 +139,29 @@ describe('<ShellToolMessage />', () => {
|
|||||||
'renders in Alternate Buffer mode while focused',
|
'renders in Alternate Buffer mode while focused',
|
||||||
{
|
{
|
||||||
status: CoreToolCallStatus.Executing,
|
status: CoreToolCallStatus.Executing,
|
||||||
embeddedShellFocused: true,
|
|
||||||
activeShellPtyId: 1,
|
|
||||||
ptyId: 1,
|
ptyId: 1,
|
||||||
},
|
},
|
||||||
{ useAlternateBuffer: true },
|
{
|
||||||
|
useAlternateBuffer: true,
|
||||||
|
uiState: {
|
||||||
|
embeddedShellFocused: true,
|
||||||
|
activePtyId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'renders in Alternate Buffer mode while unfocused',
|
'renders in Alternate Buffer mode while unfocused',
|
||||||
{
|
{
|
||||||
status: CoreToolCallStatus.Executing,
|
status: CoreToolCallStatus.Executing,
|
||||||
embeddedShellFocused: false,
|
|
||||||
activeShellPtyId: 1,
|
|
||||||
ptyId: 1,
|
ptyId: 1,
|
||||||
},
|
},
|
||||||
{ useAlternateBuffer: true },
|
{
|
||||||
|
useAlternateBuffer: true,
|
||||||
|
uiState: {
|
||||||
|
embeddedShellFocused: false,
|
||||||
|
activePtyId: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
])('%s', async (_, props, options) => {
|
])('%s', async (_, props, options) => {
|
||||||
const { lastFrame } = renderShell(props, options);
|
const { lastFrame } = renderShell(props, options);
|
||||||
@@ -199,12 +203,16 @@ describe('<ShellToolMessage />', () => {
|
|||||||
resultDisplay: LONG_OUTPUT,
|
resultDisplay: LONG_OUTPUT,
|
||||||
renderOutputAsMarkdown: false,
|
renderOutputAsMarkdown: false,
|
||||||
availableTerminalHeight,
|
availableTerminalHeight,
|
||||||
activeShellPtyId: 1,
|
ptyId: 1,
|
||||||
ptyId: focused ? 1 : 2,
|
|
||||||
status: CoreToolCallStatus.Executing,
|
status: CoreToolCallStatus.Executing,
|
||||||
embeddedShellFocused: focused,
|
|
||||||
},
|
},
|
||||||
{ useAlternateBuffer: true },
|
{
|
||||||
|
useAlternateBuffer: true,
|
||||||
|
uiState: {
|
||||||
|
activePtyId: focused ? 1 : 2,
|
||||||
|
embeddedShellFocused: focused,
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ import {
|
|||||||
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
|
import { useAlternateBuffer } from '../../hooks/useAlternateBuffer.js';
|
||||||
import { type Config, CoreToolCallStatus } from '@google/gemini-cli-core';
|
import { type Config, CoreToolCallStatus } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
|
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||||
|
|
||||||
export interface ShellToolMessageProps extends ToolMessageProps {
|
export interface ShellToolMessageProps extends ToolMessageProps {
|
||||||
activeShellPtyId?: number | null;
|
|
||||||
embeddedShellFocused?: boolean;
|
|
||||||
config?: Config;
|
config?: Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +52,6 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
|||||||
|
|
||||||
renderOutputAsMarkdown = true,
|
renderOutputAsMarkdown = true,
|
||||||
|
|
||||||
activeShellPtyId,
|
|
||||||
|
|
||||||
embeddedShellFocused,
|
|
||||||
|
|
||||||
ptyId,
|
ptyId,
|
||||||
|
|
||||||
config,
|
config,
|
||||||
@@ -66,6 +62,7 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
|
|||||||
|
|
||||||
borderDimColor,
|
borderDimColor,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { activePtyId: activeShellPtyId, embeddedShellFocused } = useUIState();
|
||||||
const isAlternateBuffer = useAlternateBuffer();
|
const isAlternateBuffer = useAlternateBuffer();
|
||||||
const isThisShellFocused = checkIsShellFocused(
|
const isThisShellFocused = checkIsShellFocused(
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -7,7 +7,11 @@
|
|||||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||||
import { ToolGroupMessage } from './ToolGroupMessage.js';
|
import { ToolGroupMessage } from './ToolGroupMessage.js';
|
||||||
import type { IndividualToolCallDisplay } from '../../types.js';
|
import type {
|
||||||
|
HistoryItem,
|
||||||
|
HistoryItemWithoutId,
|
||||||
|
IndividualToolCallDisplay,
|
||||||
|
} from '../../types.js';
|
||||||
import { Scrollable } from '../shared/Scrollable.js';
|
import { Scrollable } from '../shared/Scrollable.js';
|
||||||
import {
|
import {
|
||||||
makeFakeConfig,
|
makeFakeConfig,
|
||||||
@@ -40,10 +44,17 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
groupId: 1,
|
|
||||||
terminalWidth: 80,
|
terminalWidth: 80,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createItem = (
|
||||||
|
tools: IndividualToolCallDisplay[],
|
||||||
|
): HistoryItem | HistoryItemWithoutId => ({
|
||||||
|
id: 1,
|
||||||
|
type: 'tool_group',
|
||||||
|
tools,
|
||||||
|
});
|
||||||
|
|
||||||
const baseMockConfig = makeFakeConfig({
|
const baseMockConfig = makeFakeConfig({
|
||||||
model: 'gemini-pro',
|
model: 'gemini-pro',
|
||||||
targetDir: os.tmpdir(),
|
targetDir: os.tmpdir(),
|
||||||
@@ -56,12 +67,18 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
describe('Golden Snapshots', () => {
|
describe('Golden Snapshots', () => {
|
||||||
it('renders single successful tool call', () => {
|
it('renders single successful tool call', () => {
|
||||||
const toolCalls = [createToolCall()];
|
const toolCalls = [createToolCall()];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -81,9 +98,10 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
|
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{ config: baseMockConfig },
|
{ config: baseMockConfig },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -113,13 +131,19 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
status: CoreToolCallStatus.Error,
|
status: CoreToolCallStatus.Error,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
|
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -153,13 +177,19 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
status: CoreToolCallStatus.Scheduled,
|
status: CoreToolCallStatus.Scheduled,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
|
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -188,16 +218,23 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
resultDisplay: 'More output here',
|
resultDisplay: 'More output here',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage
|
<ToolGroupMessage
|
||||||
{...baseProps}
|
{...baseProps}
|
||||||
|
item={item}
|
||||||
toolCalls={toolCalls}
|
toolCalls={toolCalls}
|
||||||
availableTerminalHeight={10}
|
availableTerminalHeight={10}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -213,16 +250,23 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
'This is a very long description that might cause wrapping issues',
|
'This is a very long description that might cause wrapping issues',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage
|
<ToolGroupMessage
|
||||||
{...baseProps}
|
{...baseProps}
|
||||||
|
item={item}
|
||||||
toolCalls={toolCalls}
|
toolCalls={toolCalls}
|
||||||
terminalWidth={40}
|
terminalWidth={40}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -231,12 +275,19 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders empty tool calls array', () => {
|
it('renders empty tool calls array', () => {
|
||||||
|
const toolCalls: IndividualToolCallDisplay[] = [];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={[]} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: [] }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -260,14 +311,20 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
resultDisplay: 'line1\nline2',
|
resultDisplay: 'line1\nline2',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<Scrollable height={10} hasFocus={true} scrollToBottom={true}>
|
<Scrollable height={10} hasFocus={true} scrollToBottom={true}>
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />
|
||||||
</Scrollable>,
|
</Scrollable>,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -285,12 +342,18 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
outputFile: '/path/to/output.txt',
|
outputFile: '/path/to/output.txt',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -307,6 +370,7 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
resultDisplay: 'line1\nline2\nline3\nline4\nline5',
|
resultDisplay: 'line1\nline2\nline3\nline4\nline5',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item1 = createItem(toolCalls1);
|
||||||
const toolCalls2 = [
|
const toolCalls2 = [
|
||||||
createToolCall({
|
createToolCall({
|
||||||
callId: '2',
|
callId: '2',
|
||||||
@@ -315,18 +379,33 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
resultDisplay: 'line1',
|
resultDisplay: 'line1',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item2 = createItem(toolCalls2);
|
||||||
|
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<Scrollable height={6} hasFocus={true} scrollToBottom={true}>
|
<Scrollable height={6} hasFocus={true} scrollToBottom={true}>
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls1} />
|
<ToolGroupMessage
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls2} />
|
{...baseProps}
|
||||||
|
item={item1}
|
||||||
|
toolCalls={toolCalls1}
|
||||||
|
/>
|
||||||
|
<ToolGroupMessage
|
||||||
|
{...baseProps}
|
||||||
|
item={item2}
|
||||||
|
toolCalls={toolCalls2}
|
||||||
|
/>
|
||||||
</Scrollable>,
|
</Scrollable>,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [
|
pendingHistoryItems: [
|
||||||
{ type: 'tool_group', tools: toolCalls1 },
|
{
|
||||||
{ type: 'tool_group', tools: toolCalls2 },
|
type: 'tool_group',
|
||||||
|
tools: toolCalls1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls2,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -344,12 +423,18 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
status: CoreToolCallStatus.Success,
|
status: CoreToolCallStatus.Success,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -366,12 +451,18 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
status: CoreToolCallStatus.Success,
|
status: CoreToolCallStatus.Success,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -396,16 +487,23 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
resultDisplay: '', // No result
|
resultDisplay: '', // No result
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage
|
<ToolGroupMessage
|
||||||
{...baseProps}
|
{...baseProps}
|
||||||
|
item={item}
|
||||||
toolCalls={toolCalls}
|
toolCalls={toolCalls}
|
||||||
availableTerminalHeight={20}
|
availableTerminalHeight={20}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
config: baseMockConfig,
|
config: baseMockConfig,
|
||||||
uiState: {
|
uiState: {
|
||||||
pendingHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: toolCalls,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -453,9 +551,10 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
resultDisplay,
|
resultDisplay,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
|
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{ config: baseMockConfig },
|
{ config: baseMockConfig },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -481,9 +580,10 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
status: CoreToolCallStatus.Scheduled,
|
status: CoreToolCallStatus.Scheduled,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
|
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{ config: baseMockConfig },
|
{ config: baseMockConfig },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -502,10 +602,12 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
status: CoreToolCallStatus.Executing,
|
status: CoreToolCallStatus.Executing,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
|
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage
|
<ToolGroupMessage
|
||||||
{...baseProps}
|
{...baseProps}
|
||||||
|
item={item}
|
||||||
toolCalls={toolCalls}
|
toolCalls={toolCalls}
|
||||||
borderBottom={false}
|
borderBottom={false}
|
||||||
/>,
|
/>,
|
||||||
@@ -540,9 +642,10 @@ describe('<ToolGroupMessage />', () => {
|
|||||||
approvalMode: mode,
|
approvalMode: mode,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
const item = createItem(toolCalls);
|
||||||
|
|
||||||
const { lastFrame, unmount } = renderWithProviders(
|
const { lastFrame, unmount } = renderWithProviders(
|
||||||
<ToolGroupMessage {...baseProps} toolCalls={toolCalls} />,
|
<ToolGroupMessage {...baseProps} item={item} toolCalls={toolCalls} />,
|
||||||
{ config: baseMockConfig },
|
{ config: baseMockConfig },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -7,27 +7,27 @@
|
|||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import type { IndividualToolCallDisplay } from '../../types.js';
|
import type {
|
||||||
|
HistoryItem,
|
||||||
|
HistoryItemWithoutId,
|
||||||
|
IndividualToolCallDisplay,
|
||||||
|
} from '../../types.js';
|
||||||
import { ToolCallStatus, mapCoreStatusToDisplayStatus } from '../../types.js';
|
import { ToolCallStatus, mapCoreStatusToDisplayStatus } from '../../types.js';
|
||||||
import { ToolMessage } from './ToolMessage.js';
|
import { ToolMessage } from './ToolMessage.js';
|
||||||
import { ShellToolMessage } from './ShellToolMessage.js';
|
import { ShellToolMessage } from './ShellToolMessage.js';
|
||||||
import { theme } from '../../semantic-colors.js';
|
import { theme } from '../../semantic-colors.js';
|
||||||
import { useConfig } from '../../contexts/ConfigContext.js';
|
import { useConfig } from '../../contexts/ConfigContext.js';
|
||||||
import { isShellTool, isThisShellFocused } from './ToolShared.js';
|
import { isShellTool } from './ToolShared.js';
|
||||||
import {
|
import { shouldHideToolCall } from '@google/gemini-cli-core';
|
||||||
CoreToolCallStatus,
|
|
||||||
shouldHideToolCall,
|
|
||||||
} from '@google/gemini-cli-core';
|
|
||||||
import { ShowMoreLines } from '../ShowMoreLines.js';
|
import { ShowMoreLines } from '../ShowMoreLines.js';
|
||||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||||
|
import { getToolGroupBorderAppearance } from '../../utils/borderStyles.js';
|
||||||
|
|
||||||
interface ToolGroupMessageProps {
|
interface ToolGroupMessageProps {
|
||||||
groupId: number;
|
item: HistoryItem | HistoryItemWithoutId;
|
||||||
toolCalls: IndividualToolCallDisplay[];
|
toolCalls: IndividualToolCallDisplay[];
|
||||||
availableTerminalHeight?: number;
|
availableTerminalHeight?: number;
|
||||||
terminalWidth: number;
|
terminalWidth: number;
|
||||||
activeShellPtyId?: number | null;
|
|
||||||
embeddedShellFocused?: boolean;
|
|
||||||
onShellInputSubmit?: (input: string) => void;
|
onShellInputSubmit?: (input: string) => void;
|
||||||
borderTop?: boolean;
|
borderTop?: boolean;
|
||||||
borderBottom?: boolean;
|
borderBottom?: boolean;
|
||||||
@@ -37,11 +37,10 @@ interface ToolGroupMessageProps {
|
|||||||
const TOOL_MESSAGE_HORIZONTAL_MARGIN = 4;
|
const TOOL_MESSAGE_HORIZONTAL_MARGIN = 4;
|
||||||
|
|
||||||
export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
||||||
|
item,
|
||||||
toolCalls: allToolCalls,
|
toolCalls: allToolCalls,
|
||||||
availableTerminalHeight,
|
availableTerminalHeight,
|
||||||
terminalWidth,
|
terminalWidth,
|
||||||
activeShellPtyId,
|
|
||||||
embeddedShellFocused,
|
|
||||||
borderTop: borderTopOverride,
|
borderTop: borderTopOverride,
|
||||||
borderBottom: borderBottomOverride,
|
borderBottom: borderBottomOverride,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -61,7 +60,31 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const { constrainHeight } = useUIState();
|
const {
|
||||||
|
constrainHeight,
|
||||||
|
activePtyId,
|
||||||
|
embeddedShellFocused,
|
||||||
|
backgroundShells,
|
||||||
|
pendingHistoryItems,
|
||||||
|
} = useUIState();
|
||||||
|
|
||||||
|
const { borderColor, borderDimColor } = useMemo(
|
||||||
|
() =>
|
||||||
|
getToolGroupBorderAppearance(
|
||||||
|
item,
|
||||||
|
activePtyId,
|
||||||
|
embeddedShellFocused,
|
||||||
|
pendingHistoryItems,
|
||||||
|
backgroundShells,
|
||||||
|
),
|
||||||
|
[
|
||||||
|
item,
|
||||||
|
activePtyId,
|
||||||
|
embeddedShellFocused,
|
||||||
|
pendingHistoryItems,
|
||||||
|
backgroundShells,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
// We HIDE tools that are still in pre-execution states (Confirming, Pending)
|
// We HIDE tools that are still in pre-execution states (Confirming, Pending)
|
||||||
// from the History log. They live in the Global Queue or wait for their turn.
|
// from the History log. They live in the Global Queue or wait for their turn.
|
||||||
@@ -80,31 +103,6 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
|||||||
[toolCalls],
|
[toolCalls],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEmbeddedShellFocused = visibleToolCalls.some((t) =>
|
|
||||||
isThisShellFocused(
|
|
||||||
t.name,
|
|
||||||
t.status,
|
|
||||||
t.ptyId,
|
|
||||||
activeShellPtyId,
|
|
||||||
embeddedShellFocused,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasPending = !visibleToolCalls.every(
|
|
||||||
(t) => t.status === CoreToolCallStatus.Success,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isShellCommand = toolCalls.some((t) => isShellTool(t.name));
|
|
||||||
const borderColor =
|
|
||||||
(isShellCommand && hasPending) || isEmbeddedShellFocused
|
|
||||||
? theme.ui.symbol
|
|
||||||
: hasPending
|
|
||||||
? theme.status.warning
|
|
||||||
: theme.border.default;
|
|
||||||
|
|
||||||
const borderDimColor =
|
|
||||||
hasPending && (!isShellCommand || !isEmbeddedShellFocused);
|
|
||||||
|
|
||||||
const staticHeight = /* border */ 2 + /* marginBottom */ 1;
|
const staticHeight = /* border */ 2 + /* marginBottom */ 1;
|
||||||
|
|
||||||
// If all tools are filtered out (e.g., in-progress AskUser tools, confirming tools),
|
// If all tools are filtered out (e.g., in-progress AskUser tools, confirming tools),
|
||||||
@@ -175,12 +173,7 @@ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
|
|||||||
width={contentWidth}
|
width={contentWidth}
|
||||||
>
|
>
|
||||||
{isShellToolCall ? (
|
{isShellToolCall ? (
|
||||||
<ShellToolMessage
|
<ShellToolMessage {...commonProps} config={config} />
|
||||||
{...commonProps}
|
|
||||||
activeShellPtyId={activeShellPtyId}
|
|
||||||
embeddedShellFocused={embeddedShellFocused}
|
|
||||||
config={config}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<ToolMessage {...commonProps} />
|
<ToolMessage {...commonProps} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ describe('ToolResultDisplay Overflow', () => {
|
|||||||
const { lastFrame } = renderWithProviders(
|
const { lastFrame } = renderWithProviders(
|
||||||
<OverflowProvider>
|
<OverflowProvider>
|
||||||
<ToolGroupMessage
|
<ToolGroupMessage
|
||||||
groupId={1}
|
item={{ id: 1, type: 'tool_group', tools: toolCalls }}
|
||||||
toolCalls={toolCalls}
|
toolCalls={toolCalls}
|
||||||
availableTerminalHeight={15} // Small height to force overflow
|
availableTerminalHeight={15} // Small height to force overflow
|
||||||
terminalWidth={80}
|
terminalWidth={80}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ describe('ToolMessage Sticky Header Regression', () => {
|
|||||||
data={['item1']}
|
data={['item1']}
|
||||||
renderItem={() => (
|
renderItem={() => (
|
||||||
<ToolGroupMessage
|
<ToolGroupMessage
|
||||||
groupId={1}
|
item={{ id: 1, type: 'tool_group', tools: toolCalls }}
|
||||||
toolCalls={toolCalls}
|
toolCalls={toolCalls}
|
||||||
terminalWidth={terminalWidth - 2} // Account for ScrollableList padding
|
terminalWidth={terminalWidth - 2} // Account for ScrollableList padding
|
||||||
/>
|
/>
|
||||||
@@ -165,7 +165,7 @@ describe('ToolMessage Sticky Header Regression', () => {
|
|||||||
data={['item1']}
|
data={['item1']}
|
||||||
renderItem={() => (
|
renderItem={() => (
|
||||||
<ToolGroupMessage
|
<ToolGroupMessage
|
||||||
groupId={1}
|
item={{ id: 1, type: 'tool_group', tools: toolCalls }}
|
||||||
toolCalls={toolCalls}
|
toolCalls={toolCalls}
|
||||||
terminalWidth={terminalWidth - 2}
|
terminalWidth={terminalWidth - 2}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1286,7 +1286,9 @@ describe('handleAtCommand', () => {
|
|||||||
// Assert
|
// Assert
|
||||||
// It SHOULD be called for the tool_group
|
// It SHOULD be called for the tool_group
|
||||||
expect(mockAddItem).toHaveBeenCalledWith(
|
expect(mockAddItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ type: 'tool_group' }),
|
expect.objectContaining({
|
||||||
|
type: 'tool_group',
|
||||||
|
}),
|
||||||
999,
|
999,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1343,7 +1345,9 @@ describe('handleAtCommand', () => {
|
|||||||
});
|
});
|
||||||
expect(containsResourceText).toBe(true);
|
expect(containsResourceText).toBe(true);
|
||||||
expect(mockAddItem).toHaveBeenCalledWith(
|
expect(mockAddItem).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ type: 'tool_group' }),
|
expect.objectContaining({
|
||||||
|
type: 'tool_group',
|
||||||
|
}),
|
||||||
expect.any(Number),
|
expect.any(Number),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ import {
|
|||||||
*/
|
*/
|
||||||
export function mapToDisplay(
|
export function mapToDisplay(
|
||||||
toolOrTools: ToolCall[] | ToolCall,
|
toolOrTools: ToolCall[] | ToolCall,
|
||||||
options: { borderTop?: boolean; borderBottom?: boolean } = {},
|
options: {
|
||||||
|
borderTop?: boolean;
|
||||||
|
borderBottom?: boolean;
|
||||||
|
borderColor?: string;
|
||||||
|
borderDimColor?: boolean;
|
||||||
|
} = {},
|
||||||
): HistoryItemToolGroup {
|
): HistoryItemToolGroup {
|
||||||
const toolCalls = Array.isArray(toolOrTools) ? toolOrTools : [toolOrTools];
|
const toolCalls = Array.isArray(toolOrTools) ? toolOrTools : [toolOrTools];
|
||||||
const { borderTop, borderBottom } = options;
|
const { borderTop, borderBottom, borderColor, borderDimColor } = options;
|
||||||
|
|
||||||
const toolDisplays = toolCalls.map((call): IndividualToolCallDisplay => {
|
const toolDisplays = toolCalls.map((call): IndividualToolCallDisplay => {
|
||||||
let description: string;
|
let description: string;
|
||||||
@@ -104,5 +109,7 @@ export function mapToDisplay(
|
|||||||
tools: toolDisplays,
|
tools: toolDisplays,
|
||||||
borderTop,
|
borderTop,
|
||||||
borderBottom,
|
borderBottom,
|
||||||
|
borderColor,
|
||||||
|
borderDimColor,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import type { Part, PartListUnion } from '@google/genai';
|
|||||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||||
import type { SlashCommandProcessorResult } from '../types.js';
|
import type { SlashCommandProcessorResult } from '../types.js';
|
||||||
import { MessageType, StreamingState } from '../types.js';
|
import { MessageType, StreamingState } from '../types.js';
|
||||||
|
|
||||||
import type { LoadedSettings } from '../../config/settings.js';
|
import type { LoadedSettings } from '../../config/settings.js';
|
||||||
|
|
||||||
// --- MOCKS ---
|
// --- MOCKS ---
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ import {
|
|||||||
type TrackedWaitingToolCall,
|
type TrackedWaitingToolCall,
|
||||||
type TrackedExecutingToolCall,
|
type TrackedExecutingToolCall,
|
||||||
} from './useToolScheduler.js';
|
} from './useToolScheduler.js';
|
||||||
|
import { theme } from '../semantic-colors.js';
|
||||||
|
import { getToolGroupBorderAppearance } from '../utils/borderStyles.js';
|
||||||
import { promises as fs } from 'node:fs';
|
import { promises as fs } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||||
@@ -250,6 +252,8 @@ export const useGeminiStream = (
|
|||||||
mapTrackedToolCallsToDisplay(toolsToPush as TrackedToolCall[], {
|
mapTrackedToolCallsToDisplay(toolsToPush as TrackedToolCall[], {
|
||||||
borderTop: isFirstToolInGroupRef.current,
|
borderTop: isFirstToolInGroupRef.current,
|
||||||
borderBottom: true,
|
borderBottom: true,
|
||||||
|
borderColor: theme.border.default,
|
||||||
|
borderDimColor: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -290,6 +294,45 @@ export const useGeminiStream = (
|
|||||||
getPreferredEditor,
|
getPreferredEditor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const activeToolPtyId = useMemo(() => {
|
||||||
|
const executingShellTool = toolCalls.find(
|
||||||
|
(tc) =>
|
||||||
|
tc.status === 'executing' && tc.request.name === 'run_shell_command',
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||||
|
return (executingShellTool as TrackedExecutingToolCall | undefined)?.pid;
|
||||||
|
}, [toolCalls]);
|
||||||
|
|
||||||
|
const onExec = useCallback(async (done: Promise<void>) => {
|
||||||
|
setIsResponding(true);
|
||||||
|
await done;
|
||||||
|
setIsResponding(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleShellCommand,
|
||||||
|
activeShellPtyId,
|
||||||
|
lastShellOutputTime,
|
||||||
|
backgroundShellCount,
|
||||||
|
isBackgroundShellVisible,
|
||||||
|
toggleBackgroundShell,
|
||||||
|
backgroundCurrentShell,
|
||||||
|
registerBackgroundShell,
|
||||||
|
dismissBackgroundShell,
|
||||||
|
backgroundShells,
|
||||||
|
} = useShellCommandProcessor(
|
||||||
|
addItem,
|
||||||
|
setPendingHistoryItem,
|
||||||
|
onExec,
|
||||||
|
onDebugMessage,
|
||||||
|
config,
|
||||||
|
geminiClient,
|
||||||
|
setShellInputFocused,
|
||||||
|
terminalWidth,
|
||||||
|
terminalHeight,
|
||||||
|
activeToolPtyId,
|
||||||
|
);
|
||||||
|
|
||||||
const streamingState = useMemo(
|
const streamingState = useMemo(
|
||||||
() => calculateStreamingState(isResponding, toolCalls),
|
() => calculateStreamingState(isResponding, toolCalls),
|
||||||
[isResponding, toolCalls],
|
[isResponding, toolCalls],
|
||||||
@@ -347,6 +390,13 @@ export const useGeminiStream = (
|
|||||||
const historyItem = mapTrackedToolCallsToDisplay(tc, {
|
const historyItem = mapTrackedToolCallsToDisplay(tc, {
|
||||||
borderTop: isFirst,
|
borderTop: isFirst,
|
||||||
borderBottom: isLastInBatch,
|
borderBottom: isLastInBatch,
|
||||||
|
...getToolGroupBorderAppearance(
|
||||||
|
{ type: 'tool_group', tools: toolCalls },
|
||||||
|
activeShellPtyId,
|
||||||
|
!!isShellFocused,
|
||||||
|
[],
|
||||||
|
backgroundShells,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
addItem(historyItem);
|
addItem(historyItem);
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
@@ -362,6 +412,9 @@ export const useGeminiStream = (
|
|||||||
setPushedToolCallIds,
|
setPushedToolCallIds,
|
||||||
setIsFirstToolInGroup,
|
setIsFirstToolInGroup,
|
||||||
addItem,
|
addItem,
|
||||||
|
activeShellPtyId,
|
||||||
|
isShellFocused,
|
||||||
|
backgroundShells,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const pendingToolGroupItems = useMemo((): HistoryItemWithoutId[] => {
|
const pendingToolGroupItems = useMemo((): HistoryItemWithoutId[] => {
|
||||||
@@ -371,11 +424,20 @@ export const useGeminiStream = (
|
|||||||
|
|
||||||
const items: HistoryItemWithoutId[] = [];
|
const items: HistoryItemWithoutId[] = [];
|
||||||
|
|
||||||
|
const appearance = getToolGroupBorderAppearance(
|
||||||
|
{ type: 'tool_group', tools: toolCalls },
|
||||||
|
activeShellPtyId,
|
||||||
|
!!isShellFocused,
|
||||||
|
[],
|
||||||
|
backgroundShells,
|
||||||
|
);
|
||||||
|
|
||||||
if (remainingTools.length > 0) {
|
if (remainingTools.length > 0) {
|
||||||
items.push(
|
items.push(
|
||||||
mapTrackedToolCallsToDisplay(remainingTools, {
|
mapTrackedToolCallsToDisplay(remainingTools, {
|
||||||
borderTop: pushedToolCallIds.size === 0,
|
borderTop: pushedToolCallIds.size === 0,
|
||||||
borderBottom: false, // Stay open to connect with the slice below
|
borderBottom: false, // Stay open to connect with the slice below
|
||||||
|
...appearance,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -423,20 +485,18 @@ export const useGeminiStream = (
|
|||||||
tools: [] as IndividualToolCallDisplay[],
|
tools: [] as IndividualToolCallDisplay[],
|
||||||
borderTop: false,
|
borderTop: false,
|
||||||
borderBottom: true,
|
borderBottom: true,
|
||||||
|
...appearance,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}, [toolCalls, pushedToolCallIds]);
|
}, [
|
||||||
|
toolCalls,
|
||||||
const activeToolPtyId = useMemo(() => {
|
pushedToolCallIds,
|
||||||
const executingShellTool = toolCalls.find(
|
activeShellPtyId,
|
||||||
(tc) =>
|
isShellFocused,
|
||||||
tc.status === 'executing' && tc.request.name === 'run_shell_command',
|
backgroundShells,
|
||||||
);
|
]);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
||||||
return (executingShellTool as TrackedExecutingToolCall | undefined)?.pid;
|
|
||||||
}, [toolCalls]);
|
|
||||||
|
|
||||||
const lastQueryRef = useRef<PartListUnion | null>(null);
|
const lastQueryRef = useRef<PartListUnion | null>(null);
|
||||||
const lastPromptIdRef = useRef<string | null>(null);
|
const lastPromptIdRef = useRef<string | null>(null);
|
||||||
@@ -448,36 +508,6 @@ export const useGeminiStream = (
|
|||||||
onComplete: (result: { userSelection: 'disable' | 'keep' }) => void;
|
onComplete: (result: { userSelection: 'disable' | 'keep' }) => void;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const onExec = useCallback(async (done: Promise<void>) => {
|
|
||||||
setIsResponding(true);
|
|
||||||
await done;
|
|
||||||
setIsResponding(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const {
|
|
||||||
handleShellCommand,
|
|
||||||
activeShellPtyId,
|
|
||||||
lastShellOutputTime,
|
|
||||||
backgroundShellCount,
|
|
||||||
isBackgroundShellVisible,
|
|
||||||
toggleBackgroundShell,
|
|
||||||
backgroundCurrentShell,
|
|
||||||
registerBackgroundShell,
|
|
||||||
dismissBackgroundShell,
|
|
||||||
backgroundShells,
|
|
||||||
} = useShellCommandProcessor(
|
|
||||||
addItem,
|
|
||||||
setPendingHistoryItem,
|
|
||||||
onExec,
|
|
||||||
onDebugMessage,
|
|
||||||
config,
|
|
||||||
geminiClient,
|
|
||||||
setShellInputFocused,
|
|
||||||
terminalWidth,
|
|
||||||
terminalHeight,
|
|
||||||
activeToolPtyId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const activePtyId = activeShellPtyId || activeToolPtyId;
|
const activePtyId = activeShellPtyId || activeToolPtyId;
|
||||||
|
|
||||||
const prevActiveShellPtyIdRef = useRef<number | null>(null);
|
const prevActiveShellPtyIdRef = useRef<number | null>(null);
|
||||||
|
|||||||
@@ -221,6 +221,8 @@ export type HistoryItemToolGroup = HistoryItemBase & {
|
|||||||
tools: IndividualToolCallDisplay[];
|
tools: IndividualToolCallDisplay[];
|
||||||
borderTop?: boolean;
|
borderTop?: boolean;
|
||||||
borderBottom?: boolean;
|
borderBottom?: boolean;
|
||||||
|
borderColor?: string;
|
||||||
|
borderDimColor?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HistoryItemUserShell = HistoryItemBase & {
|
export type HistoryItemUserShell = HistoryItemBase & {
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2026 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CoreToolCallStatus } from '@google/gemini-cli-core';
|
||||||
|
import { isShellTool } from '../components/messages/ToolShared.js';
|
||||||
|
import { theme } from '../semantic-colors.js';
|
||||||
|
import type {
|
||||||
|
HistoryItem,
|
||||||
|
HistoryItemWithoutId,
|
||||||
|
HistoryItemToolGroup,
|
||||||
|
IndividualToolCallDisplay,
|
||||||
|
} from '../types.js';
|
||||||
|
import type { BackgroundShell } from '../hooks/shellReducer.js';
|
||||||
|
import type { TrackedToolCall } from '../hooks/useToolScheduler.js';
|
||||||
|
|
||||||
|
function isTrackedToolCall(
|
||||||
|
tool: IndividualToolCallDisplay | TrackedToolCall,
|
||||||
|
): tool is TrackedToolCall {
|
||||||
|
return 'request' in tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the border color and dimming state for a tool group message.
|
||||||
|
*/
|
||||||
|
export function getToolGroupBorderAppearance(
|
||||||
|
item:
|
||||||
|
| HistoryItem
|
||||||
|
| HistoryItemWithoutId
|
||||||
|
| { type: 'tool_group'; tools: TrackedToolCall[] },
|
||||||
|
activeShellPtyId: number | null | undefined,
|
||||||
|
embeddedShellFocused: boolean | undefined,
|
||||||
|
allPendingItems: HistoryItemWithoutId[] = [],
|
||||||
|
backgroundShells: Map<number, BackgroundShell> = new Map(),
|
||||||
|
): { borderColor: string; borderDimColor: boolean } {
|
||||||
|
if (item.type !== 'tool_group') {
|
||||||
|
return { borderColor: '', borderDimColor: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this item has no tools, it's a closing slice for the current batch.
|
||||||
|
// We need to look at the last pending item to determine the batch's appearance.
|
||||||
|
const toolsToInspect: Array<IndividualToolCallDisplay | TrackedToolCall> =
|
||||||
|
item.tools.length > 0
|
||||||
|
? item.tools
|
||||||
|
: allPendingItems
|
||||||
|
.filter(
|
||||||
|
(i): i is HistoryItemToolGroup =>
|
||||||
|
i !== null && i !== undefined && i.type === 'tool_group',
|
||||||
|
)
|
||||||
|
.slice(-1)
|
||||||
|
.flatMap((i) => i.tools);
|
||||||
|
|
||||||
|
const hasPending = toolsToInspect.some((t) => {
|
||||||
|
if (isTrackedToolCall(t)) {
|
||||||
|
return (
|
||||||
|
t.status !== 'success' &&
|
||||||
|
t.status !== 'error' &&
|
||||||
|
t.status !== 'cancelled'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
t.status !== CoreToolCallStatus.Success &&
|
||||||
|
t.status !== CoreToolCallStatus.Error &&
|
||||||
|
t.status !== CoreToolCallStatus.Cancelled
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEmbeddedShellFocused = toolsToInspect.some((t) => {
|
||||||
|
if (isTrackedToolCall(t)) {
|
||||||
|
return (
|
||||||
|
isShellTool(t.request.name) &&
|
||||||
|
t.status === 'executing' &&
|
||||||
|
t.pid === activeShellPtyId &&
|
||||||
|
!!embeddedShellFocused
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
isShellTool(t.name) &&
|
||||||
|
t.status === CoreToolCallStatus.Executing &&
|
||||||
|
t.ptyId === activeShellPtyId &&
|
||||||
|
!!embeddedShellFocused
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isShellCommand = toolsToInspect.some((t) => {
|
||||||
|
if (isTrackedToolCall(t)) {
|
||||||
|
return isShellTool(t.request.name);
|
||||||
|
} else {
|
||||||
|
return isShellTool(t.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we have an active PTY that isn't a background shell, then the current
|
||||||
|
// pending batch is definitely a shell batch.
|
||||||
|
const isCurrentlyInShellTurn =
|
||||||
|
!!activeShellPtyId && !backgroundShells.has(activeShellPtyId);
|
||||||
|
|
||||||
|
const isShell =
|
||||||
|
isShellCommand || (item.tools.length === 0 && isCurrentlyInShellTurn);
|
||||||
|
const isPending =
|
||||||
|
hasPending || (item.tools.length === 0 && isCurrentlyInShellTurn);
|
||||||
|
|
||||||
|
const isEffectivelyFocused =
|
||||||
|
isEmbeddedShellFocused ||
|
||||||
|
(item.tools.length === 0 &&
|
||||||
|
isCurrentlyInShellTurn &&
|
||||||
|
!!embeddedShellFocused);
|
||||||
|
|
||||||
|
const borderColor =
|
||||||
|
(isShell && isPending) || isEffectivelyFocused
|
||||||
|
? theme.ui.symbol
|
||||||
|
: isPending
|
||||||
|
? theme.status.warning
|
||||||
|
: theme.border.default;
|
||||||
|
|
||||||
|
const borderDimColor = isPending && (!isShell || !isEffectivelyFocused);
|
||||||
|
|
||||||
|
return { borderColor, borderDimColor };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user