Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/cli/src/acp/acpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ export async function runAcpClient(
}

export class GeminiAgent {
private static callIdCounter = 0;

static generateCallId(name: string): string {
return `${name}-${Date.now()}-${++GeminiAgent.callIdCounter}`;
}

private sessions: Map<string, Session> = new Map();
private clientCapabilities: acp.ClientCapabilities | undefined;
private apiKey: string | undefined;
Expand Down Expand Up @@ -897,7 +903,7 @@ export class Session {
promptId: string,
fc: FunctionCall,
): Promise<Part[]> {
const callId = fc.id ?? `${fc.name}-${Date.now()}`;
const callId = fc.id ?? GeminiAgent.generateCallId(fc.name || 'unknown');
const args = fc.args ?? {};

const startTime = Date.now();
Expand Down Expand Up @@ -1391,7 +1397,7 @@ export class Session {
include: pathSpecsToRead,
};

const callId = `${readManyFilesTool.name}-${Date.now()}`;
const callId = GeminiAgent.generateCallId(readManyFilesTool.name);

try {
const invocation = readManyFilesTool.build(toolArgs);
Expand Down
85 changes: 26 additions & 59 deletions packages/cli/src/ui/AppContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ import {
import { ConfigContext } from './contexts/ConfigContext.js';
import {
type HistoryItem,
type HistoryItemWithoutId,
type HistoryItemToolGroup,
AuthState,
type ConfirmationRequest,
type PermissionConfirmationRequest,
Expand Down Expand Up @@ -81,7 +79,6 @@ import {
type AgentsDiscoveredPayload,
ChangeAuthRequestedError,
ProjectIdRequiredError,
CoreToolCallStatus,
buildUserSteeringHintPrompt,
logBillingEvent,
ApiKeyUpdatedEvent,
Expand Down Expand Up @@ -170,29 +167,11 @@ import { useIsHelpDismissKey } from './utils/shortcutsHelp.js';
import { useSuspend } from './hooks/useSuspend.js';
import { useRunEventNotifications } from './hooks/useRunEventNotifications.js';
import { isNotificationsEnabled } from '../utils/terminalNotifications.js';

function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
return pendingHistoryItems.some((item) => {
if (item && item.type === 'tool_group') {
return item.tools.some(
(tool) => CoreToolCallStatus.Executing === tool.status,
);
}
return false;
});
}

function isToolAwaitingConfirmation(
pendingHistoryItems: HistoryItemWithoutId[],
) {
return pendingHistoryItems
.filter((item): item is HistoryItemToolGroup => item.type === 'tool_group')
.some((item) =>
item.tools.some(
(tool) => CoreToolCallStatus.AwaitingApproval === tool.status,
),
);
}
import {
isToolExecuting,
isToolAwaitingConfirmation,
getAllToolCalls,
} from './utils/historyUtils.js';

interface AppContainerProps {
config: Config;
Expand Down Expand Up @@ -1151,6 +1130,16 @@ Logging in with Google... Restarting Gemini CLI to continue.
consumePendingHints,
);

const pendingHistoryItems = useMemo(
() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems],
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
);

const hasPendingToolConfirmation = useMemo(
() => isToolAwaitingConfirmation(pendingHistoryItems),
[pendingHistoryItems],
);

toggleBackgroundShellRef.current = toggleBackgroundShell;
isBackgroundShellVisibleRef.current = isBackgroundShellVisible;
backgroundShellsRef.current = backgroundShells;
Expand Down Expand Up @@ -1222,10 +1211,6 @@ Logging in with Google... Restarting Gemini CLI to continue.

cancelHandlerRef.current = useCallback(
(shouldRestorePrompt: boolean = true) => {
const pendingHistoryItems = [
...pendingSlashCommandHistoryItems,
...pendingGeminiHistoryItems,
];
if (isToolAwaitingConfirmation(pendingHistoryItems)) {
return; // Don't clear - user may be composing a follow-up message
}
Expand Down Expand Up @@ -1259,8 +1244,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
inputHistory,
getQueuedMessagesText,
clearQueue,
pendingSlashCommandHistoryItems,
pendingGeminiHistoryItems,
pendingHistoryItems,
],
);

Expand Down Expand Up @@ -1296,10 +1280,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
const isIdle = streamingState === StreamingState.Idle;
const isAgentRunning =
streamingState === StreamingState.Responding ||
isToolExecuting([
...pendingSlashCommandHistoryItems,
...pendingGeminiHistoryItems,
]);
isToolExecuting(pendingHistoryItems);

if (isSlash && isAgentRunning) {
const { commandToExecute } = parseSlashCommand(
Expand Down Expand Up @@ -1361,8 +1342,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
isMcpReady,
streamingState,
messageQueue.length,
pendingSlashCommandHistoryItems,
pendingGeminiHistoryItems,
pendingHistoryItems,
config,
constrainHeight,
setConstrainHeight,
Expand Down Expand Up @@ -1684,6 +1664,11 @@ Logging in with Google... Restarting Gemini CLI to continue.

const handleGlobalKeypress = useCallback(
(key: Key): boolean => {
// Debug log keystrokes if enabled
if (settings.merged.general.debugKeystrokeLogging) {
debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key));
}

if (shortcutsHelpVisible && isHelpDismissKey(key)) {
setShortcutsHelpVisible(false);
}
Expand Down Expand Up @@ -1866,6 +1851,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
activePtyId,
handleSuspend,
embeddedShellFocused,
settings.merged.general.debugKeystrokeLogging,
refreshStatic,
setCopyModeEnabled,
tabFocusTimeoutRef,
Expand Down Expand Up @@ -2026,16 +2012,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
authState === AuthState.AwaitingApiKeyInput ||
!!newAgents;

const pendingHistoryItems = useMemo(
() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems],
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
);

const hasPendingToolConfirmation = useMemo(
() => isToolAwaitingConfirmation(pendingHistoryItems),
[pendingHistoryItems],
);

const hasConfirmUpdateExtensionRequests =
confirmUpdateExtensionRequests.length > 0;
const hasLoopDetectionConfirmationRequest =
Expand Down Expand Up @@ -2125,12 +2101,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
]);

const allToolCalls = useMemo(
() =>
pendingHistoryItems
.filter(
(item): item is HistoryItemToolGroup => item.type === 'tool_group',
)
.flatMap((item) => item.tools),
() => getAllToolCalls(pendingHistoryItems),
[pendingHistoryItems],
);

Expand Down Expand Up @@ -2295,11 +2266,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
newAgents,
showIsExpandableHint,
hintMode:
config.isModelSteeringEnabled() &&
isToolExecuting([
...pendingSlashCommandHistoryItems,
...pendingGeminiHistoryItems,
]),
config.isModelSteeringEnabled() && isToolExecuting(pendingHistoryItems),
hintBuffer: '',
}),
[
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/components/AskUserDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ describe('AskUserDialog', () => {
});

describe.each([
{ useAlternateBuffer: true, expectedArrows: false },
{ useAlternateBuffer: true, expectedArrows: true },
{ useAlternateBuffer: false, expectedArrows: true },
])(
'Scroll Arrows (useAlternateBuffer: $useAlternateBuffer)',
Expand Down
10 changes: 8 additions & 2 deletions packages/cli/src/ui/components/AskUserDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -865,8 +865,14 @@ const ChoiceQuestionView: React.FC<ChoiceQuestionViewProps> = ({
: undefined;

const maxItemsToShow =
listHeight && questionHeightLimit
? Math.max(1, Math.floor((listHeight - questionHeightLimit) / 2))
listHeight && (!isAlternateBuffer || availableHeight !== undefined)
? Math.min(
selectionItems.length,
Math.max(
1,
Math.floor((listHeight - (questionHeightLimit ?? 0)) / 2),
),
)
: selectionItems.length;

return (
Expand Down
12 changes: 6 additions & 6 deletions packages/cli/src/ui/components/MainContent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ describe('getToolGroupBorderAppearance', () => {
});

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 item = { type: 'tool_group' as const, tools: [], id: -1 };
const pendingItems = [
{
type: 'tool_group' as const,
Expand Down Expand Up @@ -158,7 +158,7 @@ describe('getToolGroupBorderAppearance', () => {
confirmationDetails: undefined,
} as IndividualToolCallDisplay,
],
id: 1,
id: -1,
};
const result = getToolGroupBorderAppearance(
item,
Expand Down Expand Up @@ -187,7 +187,7 @@ describe('getToolGroupBorderAppearance', () => {
confirmationDetails: undefined,
} as IndividualToolCallDisplay,
],
id: 1,
id: -1,
};
const result = getToolGroupBorderAppearance(
item,
Expand Down Expand Up @@ -276,7 +276,7 @@ describe('getToolGroupBorderAppearance', () => {
confirmationDetails: undefined,
} as IndividualToolCallDisplay,
],
id: 1,
id: -1,
};
const result = getToolGroupBorderAppearance(
item,
Expand All @@ -292,7 +292,7 @@ describe('getToolGroupBorderAppearance', () => {
});

it('handles empty tools with active shell turn (isCurrentlyInShellTurn)', () => {
const item = { type: 'tool_group' as const, tools: [], id: 1 };
const item = { type: 'tool_group' as const, tools: [], id: -1 };

// active shell turn
const result = getToolGroupBorderAppearance(
Expand Down Expand Up @@ -667,7 +667,7 @@ describe('MainContent', () => {
pendingHistoryItems: [
{
type: 'tool_group',
id: 1,
id: -1,
tools: [
{
callId: 'call_1',
Expand Down
11 changes: 7 additions & 4 deletions packages/cli/src/ui/components/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const MainContent = () => {

const pendingItems = useMemo(
() => (
<Box flexDirection="column">
<Box flexDirection="column" key="pending-items-group">
{pendingHistoryItems.map((item, i) => {
const prevType =
i === 0
Expand All @@ -140,12 +140,12 @@ export const MainContent = () => {

return (
<HistoryItemDisplay
key={i}
key={`pending-${i}`}
availableTerminalHeight={
uiState.constrainHeight ? availableTerminalHeight : undefined
}
terminalWidth={mainAreaWidth}
item={{ ...item, id: 0 }}
item={{ ...item, id: -(i + 1) }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you making these ids negative? seems odd.

Copy link
Copy Markdown
Contributor Author

@jwhelangoog jwhelangoog Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now assigns a sequential negative id to transient pending history items until they eventually are transitioned into a permanent (positive) history id. Previously pending items were based on an id of zero; potential for dup key errors.

isPending={true}
isExpandable={true}
isFirstThinking={isFirstThinking}
Expand All @@ -154,7 +154,10 @@ export const MainContent = () => {
);
})}
{showConfirmationQueue && confirmingTool && (
<ToolConfirmationQueue confirmingTool={confirmingTool} />
<ToolConfirmationQueue
key="confirmation-queue"
confirmingTool={confirmingTool}
/>
)}
</Box>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,37 +77,14 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = `
"Choose an option
● 1. Option 1
Description 1
2. Option 2
Description 2
3. Option 3
Description 3
4. Option 4
Description 4
5. Option 5
Description 5
6. Option 6
Description 6
7. Option 7
Description 7
8. Option 8
Description 8
9. Option 9
Description 9
10. Option 10
Description 10
11. Option 11
Description 11
12. Option 12
Description 12
13. Option 13
Description 13
14. Option 14
Description 14
15. Option 15
Description 15
16. Enter a custom value
Enter to select · ↑/↓ to navigate · Esc to cancel
"
Expand Down
Loading
Loading