mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-24 10:17:35 -07:00
feat(cli): Moves tool confirmations to a queue UX (#17276)
Co-authored-by: Christian Gunderman <gundermanc@google.com>
This commit is contained in:
@@ -7,7 +7,6 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { mapCoreStatusToDisplayStatus, mapToDisplay } from './toolMapping.js';
|
||||
import {
|
||||
debugLogger,
|
||||
type AnyDeclarativeTool,
|
||||
type AnyToolInvocation,
|
||||
type ToolCallRequestInfo,
|
||||
@@ -40,7 +39,7 @@ describe('toolMapping', () => {
|
||||
|
||||
describe('mapCoreStatusToDisplayStatus', () => {
|
||||
it.each([
|
||||
['validating', ToolCallStatus.Executing],
|
||||
['validating', ToolCallStatus.Pending],
|
||||
['awaiting_approval', ToolCallStatus.Confirming],
|
||||
['executing', ToolCallStatus.Executing],
|
||||
['success', ToolCallStatus.Success],
|
||||
@@ -53,12 +52,10 @@ describe('toolMapping', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('logs warning and defaults to Error for unknown status', () => {
|
||||
const result = mapCoreStatusToDisplayStatus('unknown_status' as Status);
|
||||
expect(result).toBe(ToolCallStatus.Error);
|
||||
expect(debugLogger.warn).toHaveBeenCalledWith(
|
||||
'Unknown core status encountered: unknown_status',
|
||||
);
|
||||
it('throws error for unknown status due to checkExhaustive', () => {
|
||||
expect(() =>
|
||||
mapCoreStatusToDisplayStatus('unknown_status' as Status),
|
||||
).toThrow('unexpected value unknown_status!');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -18,12 +18,14 @@ import {
|
||||
type IndividualToolCallDisplay,
|
||||
} from '../types.js';
|
||||
|
||||
import { checkExhaustive } from '../../utils/checks.js';
|
||||
|
||||
export function mapCoreStatusToDisplayStatus(
|
||||
coreStatus: CoreStatus,
|
||||
): ToolCallStatus {
|
||||
switch (coreStatus) {
|
||||
case 'validating':
|
||||
return ToolCallStatus.Executing;
|
||||
return ToolCallStatus.Pending;
|
||||
case 'awaiting_approval':
|
||||
return ToolCallStatus.Confirming;
|
||||
case 'executing':
|
||||
@@ -37,8 +39,7 @@ export function mapCoreStatusToDisplayStatus(
|
||||
case 'scheduled':
|
||||
return ToolCallStatus.Pending;
|
||||
default:
|
||||
debugLogger.warn(`Unknown core status encountered: ${coreStatus}`);
|
||||
return ToolCallStatus.Error;
|
||||
return checkExhaustive(coreStatus);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import {
|
||||
ToolCallStatus,
|
||||
type IndividualToolCallDisplay,
|
||||
type HistoryItemToolGroup,
|
||||
} from '../types.js';
|
||||
|
||||
export interface ConfirmingToolState {
|
||||
tool: IndividualToolCallDisplay;
|
||||
index: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the "Head" of the confirmation queue.
|
||||
* Returns the first tool in the pending state that requires confirmation.
|
||||
*/
|
||||
export function useConfirmingTool(): ConfirmingToolState | null {
|
||||
// We use pendingHistoryItems to ensure we capture tools from both
|
||||
// Gemini responses and Slash commands.
|
||||
const { pendingHistoryItems } = useUIState();
|
||||
|
||||
return useMemo(() => {
|
||||
// 1. Flatten all pending tools from all pending history groups
|
||||
const allPendingTools = pendingHistoryItems
|
||||
.filter(
|
||||
(item): item is HistoryItemToolGroup => item.type === 'tool_group',
|
||||
)
|
||||
.flatMap((group) => group.tools);
|
||||
|
||||
// 2. Filter for those requiring confirmation
|
||||
const confirmingTools = allPendingTools.filter(
|
||||
(t) => t.status === ToolCallStatus.Confirming,
|
||||
);
|
||||
|
||||
if (confirmingTools.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. Select Head (FIFO)
|
||||
const head = confirmingTools[0];
|
||||
|
||||
// 4. Calculate progress based on the full tool list
|
||||
// This gives the user context of where they are in the current batch.
|
||||
const headIndexInFullList = allPendingTools.findIndex(
|
||||
(t) => t.callId === head.callId,
|
||||
);
|
||||
|
||||
return {
|
||||
tool: head,
|
||||
index: headIndexInFullList + 1,
|
||||
total: allPendingTools.length,
|
||||
};
|
||||
}, [pendingHistoryItems]);
|
||||
}
|
||||
@@ -938,7 +938,7 @@ describe('mapToDisplay', () => {
|
||||
name: 'validating',
|
||||
status: 'validating',
|
||||
extraProps: { tool: baseTool, invocation: baseInvocation },
|
||||
expectedStatus: ToolCallStatus.Executing,
|
||||
expectedStatus: ToolCallStatus.Pending,
|
||||
expectedName: baseTool.displayName,
|
||||
expectedDescription: baseInvocation.getDescription(),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user