mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 01:51:20 -07:00
feat(cli): Add state management and plumbing for agent configuration dialog (#17259)
This commit is contained in:
@@ -151,6 +151,8 @@ const mockUIActions: UIActions = {
|
||||
exitPrivacyNotice: vi.fn(),
|
||||
closeSettingsDialog: vi.fn(),
|
||||
closeModelDialog: vi.fn(),
|
||||
openAgentConfigDialog: vi.fn(),
|
||||
closeAgentConfigDialog: vi.fn(),
|
||||
openPermissionsDialog: vi.fn(),
|
||||
openSessionBrowser: vi.fn(),
|
||||
closeSessionBrowser: vi.fn(),
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
type UserFeedbackPayload,
|
||||
type ResumedSessionData,
|
||||
AuthType,
|
||||
type AgentDefinition,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
// Mock coreEvents
|
||||
@@ -2147,6 +2148,77 @@ describe('AppContainer State Management', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Agent Configuration Dialog Integration', () => {
|
||||
it('should initialize with dialog closed and no agent selected', async () => {
|
||||
let unmount: () => void;
|
||||
await act(async () => {
|
||||
const result = renderAppContainer();
|
||||
unmount = result.unmount;
|
||||
});
|
||||
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
||||
|
||||
expect(capturedUIState.isAgentConfigDialogOpen).toBe(false);
|
||||
expect(capturedUIState.selectedAgentName).toBeUndefined();
|
||||
expect(capturedUIState.selectedAgentDisplayName).toBeUndefined();
|
||||
expect(capturedUIState.selectedAgentDefinition).toBeUndefined();
|
||||
unmount!();
|
||||
});
|
||||
|
||||
it('should update state when openAgentConfigDialog is called', async () => {
|
||||
let unmount: () => void;
|
||||
await act(async () => {
|
||||
const result = renderAppContainer();
|
||||
unmount = result.unmount;
|
||||
});
|
||||
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
||||
|
||||
const agentDefinition = { name: 'test-agent' };
|
||||
act(() => {
|
||||
capturedUIActions.openAgentConfigDialog(
|
||||
'test-agent',
|
||||
'Test Agent',
|
||||
agentDefinition as unknown as AgentDefinition,
|
||||
);
|
||||
});
|
||||
|
||||
expect(capturedUIState.isAgentConfigDialogOpen).toBe(true);
|
||||
expect(capturedUIState.selectedAgentName).toBe('test-agent');
|
||||
expect(capturedUIState.selectedAgentDisplayName).toBe('Test Agent');
|
||||
expect(capturedUIState.selectedAgentDefinition).toEqual(agentDefinition);
|
||||
unmount!();
|
||||
});
|
||||
|
||||
it('should clear state when closeAgentConfigDialog is called', async () => {
|
||||
let unmount: () => void;
|
||||
await act(async () => {
|
||||
const result = renderAppContainer();
|
||||
unmount = result.unmount;
|
||||
});
|
||||
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
||||
|
||||
const agentDefinition = { name: 'test-agent' };
|
||||
act(() => {
|
||||
capturedUIActions.openAgentConfigDialog(
|
||||
'test-agent',
|
||||
'Test Agent',
|
||||
agentDefinition as unknown as AgentDefinition,
|
||||
);
|
||||
});
|
||||
|
||||
expect(capturedUIState.isAgentConfigDialogOpen).toBe(true);
|
||||
|
||||
act(() => {
|
||||
capturedUIActions.closeAgentConfigDialog();
|
||||
});
|
||||
|
||||
expect(capturedUIState.isAgentConfigDialogOpen).toBe(false);
|
||||
expect(capturedUIState.selectedAgentName).toBeUndefined();
|
||||
expect(capturedUIState.selectedAgentDisplayName).toBeUndefined();
|
||||
expect(capturedUIState.selectedAgentDefinition).toBeUndefined();
|
||||
unmount!();
|
||||
});
|
||||
});
|
||||
|
||||
describe('CoreEvents Integration', () => {
|
||||
it('subscribes to UserFeedback and drains backlog on mount', async () => {
|
||||
let unmount: () => void;
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
type IdeContext,
|
||||
type UserTierId,
|
||||
type UserFeedbackPayload,
|
||||
type AgentDefinition,
|
||||
IdeClient,
|
||||
ideContextStore,
|
||||
getErrorMessage,
|
||||
@@ -253,6 +254,34 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
setPermissionsDialogProps(null);
|
||||
}, []);
|
||||
|
||||
const [isAgentConfigDialogOpen, setIsAgentConfigDialogOpen] = useState(false);
|
||||
const [selectedAgentName, setSelectedAgentName] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [selectedAgentDisplayName, setSelectedAgentDisplayName] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [selectedAgentDefinition, setSelectedAgentDefinition] = useState<
|
||||
AgentDefinition | undefined
|
||||
>();
|
||||
|
||||
const openAgentConfigDialog = useCallback(
|
||||
(name: string, displayName: string, definition: AgentDefinition) => {
|
||||
setSelectedAgentName(name);
|
||||
setSelectedAgentDisplayName(displayName);
|
||||
setSelectedAgentDefinition(definition);
|
||||
setIsAgentConfigDialogOpen(true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const closeAgentConfigDialog = useCallback(() => {
|
||||
setIsAgentConfigDialogOpen(false);
|
||||
setSelectedAgentName(undefined);
|
||||
setSelectedAgentDisplayName(undefined);
|
||||
setSelectedAgentDefinition(undefined);
|
||||
}, []);
|
||||
|
||||
const toggleDebugProfiler = useCallback(
|
||||
() => setShowDebugProfiler((prev) => !prev),
|
||||
[],
|
||||
@@ -679,6 +708,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
openSettingsDialog,
|
||||
openSessionBrowser,
|
||||
openModelDialog,
|
||||
openAgentConfigDialog,
|
||||
openPermissionsDialog,
|
||||
quit: (messages: HistoryItem[]) => {
|
||||
setQuittingMessages(messages);
|
||||
@@ -701,6 +731,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
openSettingsDialog,
|
||||
openSessionBrowser,
|
||||
openModelDialog,
|
||||
openAgentConfigDialog,
|
||||
setQuittingMessages,
|
||||
setDebugMessage,
|
||||
setShowPrivacyNotice,
|
||||
@@ -1477,6 +1508,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
isThemeDialogOpen ||
|
||||
isSettingsDialogOpen ||
|
||||
isModelDialogOpen ||
|
||||
isAgentConfigDialogOpen ||
|
||||
isPermissionsDialogOpen ||
|
||||
isAuthenticating ||
|
||||
isAuthDialogOpen ||
|
||||
@@ -1570,6 +1602,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
isSettingsDialogOpen,
|
||||
isSessionBrowserOpen,
|
||||
isModelDialogOpen,
|
||||
isAgentConfigDialogOpen,
|
||||
selectedAgentName,
|
||||
selectedAgentDisplayName,
|
||||
selectedAgentDefinition,
|
||||
isPermissionsDialogOpen,
|
||||
permissionsDialogProps,
|
||||
slashCommands,
|
||||
@@ -1662,6 +1698,10 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
isSettingsDialogOpen,
|
||||
isSessionBrowserOpen,
|
||||
isModelDialogOpen,
|
||||
isAgentConfigDialogOpen,
|
||||
selectedAgentName,
|
||||
selectedAgentDisplayName,
|
||||
selectedAgentDefinition,
|
||||
isPermissionsDialogOpen,
|
||||
permissionsDialogProps,
|
||||
slashCommands,
|
||||
@@ -1761,6 +1801,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
exitPrivacyNotice,
|
||||
closeSettingsDialog,
|
||||
closeModelDialog,
|
||||
openAgentConfigDialog,
|
||||
closeAgentConfigDialog,
|
||||
openPermissionsDialog,
|
||||
closePermissionsDialog,
|
||||
setShellModeActive,
|
||||
@@ -1802,6 +1844,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
exitPrivacyNotice,
|
||||
closeSettingsDialog,
|
||||
closeModelDialog,
|
||||
openAgentConfigDialog,
|
||||
closeAgentConfigDialog,
|
||||
openPermissionsDialog,
|
||||
closePermissionsDialog,
|
||||
setShellModeActive,
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
GitService,
|
||||
Logger,
|
||||
CommandActionReturn,
|
||||
AgentDefinition,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import type { UseHistoryManagerReturn } from '../hooks/useHistoryManager.js';
|
||||
@@ -74,6 +75,11 @@ export interface CommandContext {
|
||||
toggleDebugProfiler: () => void;
|
||||
toggleVimEnabled: () => Promise<boolean>;
|
||||
reloadCommands: () => void;
|
||||
openAgentConfigDialog: (
|
||||
name: string,
|
||||
displayName: string,
|
||||
definition: AgentDefinition,
|
||||
) => void;
|
||||
extensionsUpdateState: Map<string, ExtensionUpdateStatus>;
|
||||
dispatchExtensionStateUpdate: (action: ExtensionUpdateAction) => void;
|
||||
addConfirmUpdateExtensionRequest: (value: ConfirmationRequest) => void;
|
||||
@@ -111,6 +117,7 @@ export interface OpenDialogActionReturn {
|
||||
| 'settings'
|
||||
| 'sessionBrowser'
|
||||
| 'model'
|
||||
| 'agentConfig'
|
||||
| 'permissions';
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ import { createContext, useContext } from 'react';
|
||||
import { type Key } from '../hooks/useKeypress.js';
|
||||
import { type IdeIntegrationNudgeResult } from '../IdeIntegrationNudge.js';
|
||||
import { type FolderTrustChoice } from '../components/FolderTrustDialog.js';
|
||||
import { type AuthType, type EditorType } from '@google/gemini-cli-core';
|
||||
import {
|
||||
type AuthType,
|
||||
type EditorType,
|
||||
type AgentDefinition,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { type LoadableSettingScope } from '../../config/settings.js';
|
||||
import type { AuthState } from '../types.js';
|
||||
import { type PermissionsDialogProps } from '../components/PermissionsModifyTrustDialog.js';
|
||||
@@ -32,6 +36,12 @@ export interface UIActions {
|
||||
exitPrivacyNotice: () => void;
|
||||
closeSettingsDialog: () => void;
|
||||
closeModelDialog: () => void;
|
||||
openAgentConfigDialog: (
|
||||
name: string,
|
||||
displayName: string,
|
||||
definition: AgentDefinition,
|
||||
) => void;
|
||||
closeAgentConfigDialog: () => void;
|
||||
openPermissionsDialog: (props?: PermissionsDialogProps) => void;
|
||||
closePermissionsDialog: () => void;
|
||||
setShellModeActive: (value: boolean) => void;
|
||||
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
IdeInfo,
|
||||
FallbackIntent,
|
||||
ValidationIntent,
|
||||
AgentDefinition,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { DOMElement } from 'ink';
|
||||
import type { SessionStatsState } from '../contexts/SessionContext.js';
|
||||
@@ -70,6 +71,10 @@ export interface UIState {
|
||||
isSettingsDialogOpen: boolean;
|
||||
isSessionBrowserOpen: boolean;
|
||||
isModelDialogOpen: boolean;
|
||||
isAgentConfigDialogOpen: boolean;
|
||||
selectedAgentName?: string;
|
||||
selectedAgentDisplayName?: string;
|
||||
selectedAgentDefinition?: AgentDefinition;
|
||||
isPermissionsDialogOpen: boolean;
|
||||
permissionsDialogProps: { targetDirectory?: string } | null;
|
||||
slashCommands: readonly SlashCommand[] | undefined;
|
||||
|
||||
@@ -188,6 +188,7 @@ describe('useSlashCommandProcessor', () => {
|
||||
openSettingsDialog: vi.fn(),
|
||||
openSessionBrowser: vi.fn(),
|
||||
openModelDialog: mockOpenModelDialog,
|
||||
openAgentConfigDialog: vi.fn(),
|
||||
openPermissionsDialog: vi.fn(),
|
||||
quit: mockSetQuittingMessages,
|
||||
setDebugMessage: vi.fn(),
|
||||
@@ -520,6 +521,82 @@ describe('useSlashCommandProcessor', () => {
|
||||
expect(mockFn).toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it('should handle "dialog: agentConfig" action with props', async () => {
|
||||
const mockOpenAgentConfigDialog = vi.fn();
|
||||
const agentDefinition = { name: 'test-agent' };
|
||||
const commandName = 'agentconfigcmd';
|
||||
const command = createTestCommand({
|
||||
name: commandName,
|
||||
action: vi.fn().mockResolvedValue({
|
||||
type: 'dialog',
|
||||
dialog: 'agentConfig',
|
||||
props: {
|
||||
name: 'test-agent',
|
||||
displayName: 'Test Agent',
|
||||
definition: agentDefinition,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Re-setup the hook with the mock action that we can inspect
|
||||
mockBuiltinLoadCommands.mockResolvedValue(Object.freeze([command]));
|
||||
mockFileLoadCommands.mockResolvedValue(Object.freeze([]));
|
||||
mockMcpLoadCommands.mockResolvedValue(Object.freeze([]));
|
||||
|
||||
let result!: { current: ReturnType<typeof useSlashCommandProcessor> };
|
||||
await act(async () => {
|
||||
const hook = renderHook(() =>
|
||||
useSlashCommandProcessor(
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockAddItem,
|
||||
mockClearItems,
|
||||
mockLoadHistory,
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
{
|
||||
openAuthDialog: vi.fn(),
|
||||
openThemeDialog: vi.fn(),
|
||||
openEditorDialog: vi.fn(),
|
||||
openPrivacyNotice: vi.fn(),
|
||||
openSettingsDialog: vi.fn(),
|
||||
openSessionBrowser: vi.fn(),
|
||||
openModelDialog: vi.fn(),
|
||||
openAgentConfigDialog: mockOpenAgentConfigDialog,
|
||||
openPermissionsDialog: vi.fn(),
|
||||
quit: vi.fn(),
|
||||
setDebugMessage: vi.fn(),
|
||||
toggleCorgiMode: vi.fn(),
|
||||
toggleDebugProfiler: vi.fn(),
|
||||
dispatchExtensionStateUpdate: vi.fn(),
|
||||
addConfirmUpdateExtensionRequest: vi.fn(),
|
||||
setText: vi.fn(),
|
||||
},
|
||||
new Map(),
|
||||
true,
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
),
|
||||
);
|
||||
result = hook.result;
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(result.current.slashCommands).toHaveLength(1),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleSlashCommand(`/${commandName}`);
|
||||
});
|
||||
|
||||
expect(mockOpenAgentConfigDialog).toHaveBeenCalledWith(
|
||||
'test-agent',
|
||||
'Test Agent',
|
||||
agentDefinition,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle "load_history" action', async () => {
|
||||
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
ExtensionsStartingEvent,
|
||||
ExtensionsStoppingEvent,
|
||||
ToolCallConfirmationDetails,
|
||||
AgentDefinition,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
GitService,
|
||||
@@ -69,6 +70,11 @@ interface SlashCommandProcessorActions {
|
||||
openSettingsDialog: () => void;
|
||||
openSessionBrowser: () => void;
|
||||
openModelDialog: () => void;
|
||||
openAgentConfigDialog: (
|
||||
name: string,
|
||||
displayName: string,
|
||||
definition: AgentDefinition,
|
||||
) => void;
|
||||
openPermissionsDialog: (props?: { targetDirectory?: string }) => void;
|
||||
quit: (messages: HistoryItem[]) => void;
|
||||
setDebugMessage: (message: string) => void;
|
||||
@@ -224,6 +230,7 @@ export const useSlashCommandProcessor = (
|
||||
toggleDebugProfiler: actions.toggleDebugProfiler,
|
||||
toggleVimEnabled,
|
||||
reloadCommands,
|
||||
openAgentConfigDialog: actions.openAgentConfigDialog,
|
||||
extensionsUpdateState,
|
||||
dispatchExtensionStateUpdate: actions.dispatchExtensionStateUpdate,
|
||||
addConfirmUpdateExtensionRequest:
|
||||
@@ -452,6 +459,26 @@ export const useSlashCommandProcessor = (
|
||||
case 'model':
|
||||
actions.openModelDialog();
|
||||
return { type: 'handled' };
|
||||
case 'agentConfig': {
|
||||
const props = result.props as Record<string, unknown>;
|
||||
if (
|
||||
!props ||
|
||||
typeof props['name'] !== 'string' ||
|
||||
typeof props['displayName'] !== 'string' ||
|
||||
!props['definition']
|
||||
) {
|
||||
throw new Error(
|
||||
'Received invalid properties for agentConfig dialog action.',
|
||||
);
|
||||
}
|
||||
|
||||
actions.openAgentConfigDialog(
|
||||
props['name'],
|
||||
props['displayName'],
|
||||
props['definition'] as AgentDefinition,
|
||||
);
|
||||
return { type: 'handled' };
|
||||
}
|
||||
case 'permissions':
|
||||
actions.openPermissionsDialog(
|
||||
result.props as { targetDirectory?: string },
|
||||
|
||||
@@ -24,6 +24,7 @@ export function createNonInteractiveUI(): CommandContext['ui'] {
|
||||
toggleDebugProfiler: () => {},
|
||||
toggleVimEnabled: async () => false,
|
||||
reloadCommands: () => {},
|
||||
openAgentConfigDialog: () => {},
|
||||
extensionsUpdateState: new Map(),
|
||||
dispatchExtensionStateUpdate: (_action: ExtensionUpdateAction) => {},
|
||||
addConfirmUpdateExtensionRequest: (_request) => {},
|
||||
|
||||
Reference in New Issue
Block a user