From 0c134079cc947a7153bb26c211c42c03eb70d194 Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Fri, 23 Jan 2026 16:10:51 -0800 Subject: [PATCH] feat: implement AgentConfigDialog for /agents config command (#17370) --- .../cli/src/ui/commands/agentsCommand.test.ts | 29 +- packages/cli/src/ui/commands/agentsCommand.ts | 12 +- .../ui/components/AgentConfigDialog.test.tsx | 309 +++++++++++++ .../src/ui/components/AgentConfigDialog.tsx | 435 ++++++++++++++++++ .../src/ui/components/DialogManager.test.tsx | 24 + .../cli/src/ui/components/DialogManager.tsx | 26 ++ 6 files changed, 821 insertions(+), 14 deletions(-) create mode 100644 packages/cli/src/ui/components/AgentConfigDialog.test.tsx create mode 100644 packages/cli/src/ui/components/AgentConfigDialog.tsx diff --git a/packages/cli/src/ui/commands/agentsCommand.test.ts b/packages/cli/src/ui/commands/agentsCommand.test.ts index a750888fb2..6b0a40ed5c 100644 --- a/packages/cli/src/ui/commands/agentsCommand.test.ts +++ b/packages/cli/src/ui/commands/agentsCommand.test.ts @@ -340,11 +340,12 @@ describe('agentsCommand', () => { }); describe('config sub-command', () => { - it('should open agent config dialog for a valid agent', async () => { + it('should return dialog action for a valid agent', async () => { const mockDefinition = { name: 'test-agent', displayName: 'Test Agent', description: 'test desc', + kind: 'local', }; mockConfig.getAgentRegistry = vi.fn().mockReturnValue({ getDiscoveredDefinition: vi.fn().mockReturnValue(mockDefinition), @@ -357,19 +358,22 @@ describe('agentsCommand', () => { const result = await configCommand!.action!(mockContext, 'test-agent'); - expect(mockContext.ui.openAgentConfigDialog).not.toHaveBeenCalled(); expect(result).toEqual({ - type: 'message', - messageType: 'info', - content: - "Configuration for 'test-agent' will be available in the next update.", + type: 'dialog', + dialog: 'agentConfig', + props: { + name: 'test-agent', + displayName: 'Test Agent', + definition: mockDefinition, + }, }); }); - it('should use name if displayName is missing', async () => { + it('should use name as displayName if displayName is missing', async () => { const mockDefinition = { name: 'test-agent', description: 'test desc', + kind: 'local', }; mockConfig.getAgentRegistry = vi.fn().mockReturnValue({ getDiscoveredDefinition: vi.fn().mockReturnValue(mockDefinition), @@ -381,10 +385,13 @@ describe('agentsCommand', () => { const result = await configCommand!.action!(mockContext, 'test-agent'); expect(result).toEqual({ - type: 'message', - messageType: 'info', - content: - "Configuration for 'test-agent' will be available in the next update.", + type: 'dialog', + dialog: 'agentConfig', + props: { + name: 'test-agent', + displayName: 'test-agent', // Falls back to name + definition: mockDefinition, + }, }); }); diff --git a/packages/cli/src/ui/commands/agentsCommand.ts b/packages/cli/src/ui/commands/agentsCommand.ts index fdfb329c21..32acbf69b7 100644 --- a/packages/cli/src/ui/commands/agentsCommand.ts +++ b/packages/cli/src/ui/commands/agentsCommand.ts @@ -252,10 +252,16 @@ async function configAction( }; } + const displayName = definition.displayName || agentName; + return { - type: 'message', - messageType: 'info', - content: `Configuration for '${agentName}' will be available in the next update.`, + type: 'dialog', + dialog: 'agentConfig', + props: { + name: agentName, + displayName, + definition, + }, }; } diff --git a/packages/cli/src/ui/components/AgentConfigDialog.test.tsx b/packages/cli/src/ui/components/AgentConfigDialog.test.tsx new file mode 100644 index 0000000000..6aa04cfecd --- /dev/null +++ b/packages/cli/src/ui/components/AgentConfigDialog.test.tsx @@ -0,0 +1,309 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from '../../test-utils/render.js'; +import { waitFor } from '../../test-utils/async.js'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { act } from 'react'; +import { AgentConfigDialog } from './AgentConfigDialog.js'; +import { LoadedSettings, SettingScope } from '../../config/settings.js'; +import { KeypressProvider } from '../contexts/KeypressContext.js'; +import type { AgentDefinition } from '@google/gemini-cli-core'; + +vi.mock('../contexts/UIStateContext.js', () => ({ + useUIState: () => ({ + mainAreaWidth: 100, + }), +})); + +enum TerminalKeys { + ENTER = '\u000D', + TAB = '\t', + UP_ARROW = '\u001B[A', + DOWN_ARROW = '\u001B[B', + ESCAPE = '\u001B', +} + +const createMockSettings = ( + userSettings = {}, + workspaceSettings = {}, +): LoadedSettings => { + const settings = new LoadedSettings( + { + settings: { ui: { customThemes: {} }, mcpServers: {}, agents: {} }, + originalSettings: { + ui: { customThemes: {} }, + mcpServers: {}, + agents: {}, + }, + path: '/system/settings.json', + }, + { + settings: {}, + originalSettings: {}, + path: '/system/system-defaults.json', + }, + { + settings: { + ui: { customThemes: {} }, + mcpServers: {}, + agents: { overrides: {} }, + ...userSettings, + }, + originalSettings: { + ui: { customThemes: {} }, + mcpServers: {}, + agents: { overrides: {} }, + ...userSettings, + }, + path: '/user/settings.json', + }, + { + settings: { + ui: { customThemes: {} }, + mcpServers: {}, + agents: { overrides: {} }, + ...workspaceSettings, + }, + originalSettings: { + ui: { customThemes: {} }, + mcpServers: {}, + agents: { overrides: {} }, + ...workspaceSettings, + }, + path: '/workspace/settings.json', + }, + true, + [], + ); + + // Mock setValue + settings.setValue = vi.fn(); + + return settings; +}; + +const createMockAgentDefinition = ( + overrides: Partial = {}, +): AgentDefinition => + ({ + name: 'test-agent', + displayName: 'Test Agent', + description: 'A test agent for testing', + kind: 'local', + modelConfig: { + model: 'inherit', + generateContentConfig: { + temperature: 1.0, + }, + }, + runConfig: { + maxTimeMinutes: 5, + maxTurns: 10, + }, + experimental: false, + ...overrides, + }) as AgentDefinition; + +describe('AgentConfigDialog', () => { + let mockOnClose: ReturnType; + let mockOnSave: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + mockOnClose = vi.fn(); + mockOnSave = vi.fn(); + }); + + const renderDialog = ( + settings: LoadedSettings, + definition: AgentDefinition = createMockAgentDefinition(), + ) => + render( + + + , + ); + + describe('rendering', () => { + it('should render the dialog with title', () => { + const settings = createMockSettings(); + const { lastFrame } = renderDialog(settings); + + expect(lastFrame()).toContain('Configure: Test Agent'); + }); + + it('should render all configuration fields', () => { + const settings = createMockSettings(); + const { lastFrame } = renderDialog(settings); + const frame = lastFrame(); + + expect(frame).toContain('Enabled'); + expect(frame).toContain('Model'); + expect(frame).toContain('Temperature'); + expect(frame).toContain('Top P'); + expect(frame).toContain('Top K'); + expect(frame).toContain('Max Output Tokens'); + expect(frame).toContain('Max Time (minutes)'); + expect(frame).toContain('Max Turns'); + }); + + it('should render scope selector', () => { + const settings = createMockSettings(); + const { lastFrame } = renderDialog(settings); + + expect(lastFrame()).toContain('Apply To'); + expect(lastFrame()).toContain('User Settings'); + expect(lastFrame()).toContain('Workspace Settings'); + }); + + it('should render help text', () => { + const settings = createMockSettings(); + const { lastFrame } = renderDialog(settings); + + expect(lastFrame()).toContain('Use Enter to select'); + expect(lastFrame()).toContain('Tab to change focus'); + expect(lastFrame()).toContain('Esc to close'); + }); + }); + + describe('keyboard navigation', () => { + it('should close dialog on Escape', async () => { + const settings = createMockSettings(); + const { stdin } = renderDialog(settings); + + await act(async () => { + stdin.write(TerminalKeys.ESCAPE); + }); + + await waitFor(() => { + expect(mockOnClose).toHaveBeenCalled(); + }); + }); + + it('should navigate down with arrow key', async () => { + const settings = createMockSettings(); + const { lastFrame, stdin } = renderDialog(settings); + + // Initially first item (Enabled) should be active + expect(lastFrame()).toContain('●'); + + // Press down arrow + await act(async () => { + stdin.write(TerminalKeys.DOWN_ARROW); + }); + + await waitFor(() => { + // Model field should now be highlighted + expect(lastFrame()).toContain('Model'); + }); + }); + + it('should switch focus with Tab', async () => { + const settings = createMockSettings(); + const { lastFrame, stdin } = renderDialog(settings); + + // Initially settings section is focused + expect(lastFrame()).toContain('> Configure: Test Agent'); + + // Press Tab to switch to scope selector + await act(async () => { + stdin.write(TerminalKeys.TAB); + }); + + await waitFor(() => { + expect(lastFrame()).toContain('> Apply To'); + }); + }); + }); + + describe('boolean toggle', () => { + it('should toggle enabled field on Enter', async () => { + const settings = createMockSettings(); + const { stdin } = renderDialog(settings); + + // Press Enter to toggle the first field (Enabled) + await act(async () => { + stdin.write(TerminalKeys.ENTER); + }); + + await waitFor(() => { + expect(settings.setValue).toHaveBeenCalledWith( + SettingScope.User, + 'agents.overrides.test-agent.enabled', + false, // Toggles from true (default) to false + ); + expect(mockOnSave).toHaveBeenCalled(); + }); + }); + }); + + describe('default values', () => { + it('should show values from agent definition as defaults', () => { + const definition = createMockAgentDefinition({ + modelConfig: { + model: 'gemini-2.0-flash', + generateContentConfig: { + temperature: 0.7, + }, + }, + runConfig: { + maxTimeMinutes: 10, + maxTurns: 20, + }, + }); + const settings = createMockSettings(); + const { lastFrame } = renderDialog(settings, definition); + const frame = lastFrame(); + + expect(frame).toContain('gemini-2.0-flash'); + expect(frame).toContain('0.7'); + expect(frame).toContain('10'); + expect(frame).toContain('20'); + }); + + it('should show experimental agents as disabled by default', () => { + const definition = createMockAgentDefinition({ + experimental: true, + }); + const settings = createMockSettings(); + const { lastFrame } = renderDialog(settings, definition); + + // Experimental agents default to disabled + expect(lastFrame()).toContain('false'); + }); + }); + + describe('existing overrides', () => { + it('should show existing override values with * indicator', () => { + const settings = createMockSettings({ + agents: { + overrides: { + 'test-agent': { + enabled: false, + modelConfig: { + model: 'custom-model', + }, + }, + }, + }, + }); + const { lastFrame } = renderDialog(settings); + const frame = lastFrame(); + + // Should show the overridden values + expect(frame).toContain('custom-model'); + expect(frame).toContain('false'); + }); + }); +}); diff --git a/packages/cli/src/ui/components/AgentConfigDialog.tsx b/packages/cli/src/ui/components/AgentConfigDialog.tsx new file mode 100644 index 0000000000..9226098bc7 --- /dev/null +++ b/packages/cli/src/ui/components/AgentConfigDialog.tsx @@ -0,0 +1,435 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type React from 'react'; +import { useState, useEffect, useMemo, useCallback } from 'react'; +import { Text } from 'ink'; +import { theme } from '../semantic-colors.js'; +import type { + LoadableSettingScope, + LoadedSettings, +} from '../../config/settings.js'; +import { SettingScope } from '../../config/settings.js'; +import type { AgentDefinition, AgentOverride } from '@google/gemini-cli-core'; +import { getCachedStringWidth } from '../utils/textUtils.js'; +import { + BaseSettingsDialog, + type SettingsDialogItem, +} from './shared/BaseSettingsDialog.js'; + +/** + * Configuration field definition for agent settings + */ +interface AgentConfigField { + key: string; + label: string; + description: string; + type: 'boolean' | 'number' | 'string'; + path: string[]; // Path within AgentOverride, e.g., ['modelConfig', 'generateContentConfig', 'temperature'] + defaultValue: boolean | number | string | undefined; +} + +/** + * Agent configuration fields + */ +const AGENT_CONFIG_FIELDS: AgentConfigField[] = [ + { + key: 'enabled', + label: 'Enabled', + description: 'Enable or disable this agent', + type: 'boolean', + path: ['enabled'], + defaultValue: true, + }, + { + key: 'model', + label: 'Model', + description: "Model to use (e.g., 'gemini-2.0-flash' or 'inherit')", + type: 'string', + path: ['modelConfig', 'model'], + defaultValue: 'inherit', + }, + { + key: 'temperature', + label: 'Temperature', + description: 'Sampling temperature (0.0 to 2.0)', + type: 'number', + path: ['modelConfig', 'generateContentConfig', 'temperature'], + defaultValue: undefined, + }, + { + key: 'topP', + label: 'Top P', + description: 'Nucleus sampling parameter (0.0 to 1.0)', + type: 'number', + path: ['modelConfig', 'generateContentConfig', 'topP'], + defaultValue: undefined, + }, + { + key: 'topK', + label: 'Top K', + description: 'Top-K sampling parameter', + type: 'number', + path: ['modelConfig', 'generateContentConfig', 'topK'], + defaultValue: undefined, + }, + { + key: 'maxOutputTokens', + label: 'Max Output Tokens', + description: 'Maximum number of tokens to generate', + type: 'number', + path: ['modelConfig', 'generateContentConfig', 'maxOutputTokens'], + defaultValue: undefined, + }, + { + key: 'maxTimeMinutes', + label: 'Max Time (minutes)', + description: 'Maximum execution time in minutes', + type: 'number', + path: ['runConfig', 'maxTimeMinutes'], + defaultValue: undefined, + }, + { + key: 'maxTurns', + label: 'Max Turns', + description: 'Maximum number of conversational turns', + type: 'number', + path: ['runConfig', 'maxTurns'], + defaultValue: undefined, + }, +]; + +interface AgentConfigDialogProps { + agentName: string; + displayName: string; + definition: AgentDefinition; + settings: LoadedSettings; + onClose: () => void; + onSave?: () => void; +} + +/** + * Get a nested value from an object using a path array + */ +function getNestedValue( + obj: Record | undefined, + path: string[], +): unknown { + if (!obj) return undefined; + let current: unknown = obj; + for (const key of path) { + if (current === null || current === undefined) return undefined; + if (typeof current !== 'object') return undefined; + current = (current as Record)[key]; + } + return current; +} + +/** + * Set a nested value in an object using a path array, creating intermediate objects as needed + */ +function setNestedValue( + obj: Record, + path: string[], + value: unknown, +): Record { + const result = { ...obj }; + let current = result; + + for (let i = 0; i < path.length - 1; i++) { + const key = path[i]; + if (current[key] === undefined || current[key] === null) { + current[key] = {}; + } else { + current[key] = { ...(current[key] as Record) }; + } + current = current[key] as Record; + } + + const finalKey = path[path.length - 1]; + if (value === undefined) { + delete current[finalKey]; + } else { + current[finalKey] = value; + } + + return result; +} + +/** + * Get the effective default value for a field from the agent definition + */ +function getFieldDefaultFromDefinition( + field: AgentConfigField, + definition: AgentDefinition, +): unknown { + if (definition.kind !== 'local') return field.defaultValue; + + if (field.key === 'enabled') { + return !definition.experimental; // Experimental agents default to disabled + } + if (field.key === 'model') { + return definition.modelConfig?.model ?? 'inherit'; + } + if (field.key === 'temperature') { + return definition.modelConfig?.generateContentConfig?.temperature; + } + if (field.key === 'topP') { + return definition.modelConfig?.generateContentConfig?.topP; + } + if (field.key === 'topK') { + return definition.modelConfig?.generateContentConfig?.topK; + } + if (field.key === 'maxOutputTokens') { + return definition.modelConfig?.generateContentConfig?.maxOutputTokens; + } + if (field.key === 'maxTimeMinutes') { + return definition.runConfig?.maxTimeMinutes; + } + if (field.key === 'maxTurns') { + return definition.runConfig?.maxTurns; + } + + return field.defaultValue; +} + +export function AgentConfigDialog({ + agentName, + displayName, + definition, + settings, + onClose, + onSave, +}: AgentConfigDialogProps): React.JSX.Element { + // Scope selector state (User by default) + const [selectedScope, setSelectedScope] = useState( + SettingScope.User, + ); + + // Pending override state for the selected scope + const [pendingOverride, setPendingOverride] = useState(() => { + const scopeSettings = settings.forScope(selectedScope).settings; + const existingOverride = scopeSettings.agents?.overrides?.[agentName]; + return existingOverride ? structuredClone(existingOverride) : {}; + }); + + // Track which fields have been modified + const [modifiedFields, setModifiedFields] = useState>(new Set()); + + // Update pending override when scope changes + useEffect(() => { + const scopeSettings = settings.forScope(selectedScope).settings; + const existingOverride = scopeSettings.agents?.overrides?.[agentName]; + setPendingOverride( + existingOverride ? structuredClone(existingOverride) : {}, + ); + setModifiedFields(new Set()); + }, [selectedScope, settings, agentName]); + + /** + * Save a specific field value to settings + */ + const saveFieldValue = useCallback( + (fieldKey: string, path: string[], value: unknown) => { + // Guard against prototype pollution + if (['__proto__', 'constructor', 'prototype'].includes(agentName)) { + return; + } + // Build the full settings path for agent override + // e.g., agents.overrides..modelConfig.generateContentConfig.temperature + const settingsPath = ['agents', 'overrides', agentName, ...path].join( + '.', + ); + settings.setValue(selectedScope, settingsPath, value); + onSave?.(); + }, + [settings, selectedScope, agentName, onSave], + ); + + // Calculate max label width + const maxLabelWidth = useMemo(() => { + let max = 0; + for (const field of AGENT_CONFIG_FIELDS) { + const lWidth = getCachedStringWidth(field.label); + const dWidth = getCachedStringWidth(field.description); + max = Math.max(max, lWidth, dWidth); + } + return max; + }, []); + + // Generate items for BaseSettingsDialog + const items: SettingsDialogItem[] = useMemo( + () => + AGENT_CONFIG_FIELDS.map((field) => { + const currentValue = getNestedValue( + pendingOverride as Record, + field.path, + ); + const defaultValue = getFieldDefaultFromDefinition(field, definition); + const effectiveValue = + currentValue !== undefined ? currentValue : defaultValue; + + let displayValue: string; + if (field.type === 'boolean') { + displayValue = effectiveValue ? 'true' : 'false'; + } else if (effectiveValue !== undefined && effectiveValue !== null) { + displayValue = String(effectiveValue); + } else { + displayValue = '(default)'; + } + + // Add * if modified + const isModified = + modifiedFields.has(field.key) || currentValue !== undefined; + if (isModified && currentValue !== undefined) { + displayValue += '*'; + } + + // Get raw value for edit mode + const rawValue = + currentValue !== undefined ? currentValue : effectiveValue; + + return { + key: field.key, + label: field.label, + description: field.description, + type: field.type, + displayValue, + isGreyedOut: currentValue === undefined, + scopeMessage: undefined, + rawValue: rawValue as string | number | boolean | undefined, + }; + }), + [pendingOverride, definition, modifiedFields], + ); + + const maxItemsToShow = 8; + + // Handle scope changes + const handleScopeChange = useCallback((scope: LoadableSettingScope) => { + setSelectedScope(scope); + }, []); + + // Handle toggle for boolean fields + const handleItemToggle = useCallback( + (key: string, _item: SettingsDialogItem) => { + const field = AGENT_CONFIG_FIELDS.find((f) => f.key === key); + if (!field || field.type !== 'boolean') return; + + const currentValue = getNestedValue( + pendingOverride as Record, + field.path, + ); + const defaultValue = getFieldDefaultFromDefinition(field, definition); + const effectiveValue = + currentValue !== undefined ? currentValue : defaultValue; + const newValue = !effectiveValue; + + const newOverride = setNestedValue( + pendingOverride as Record, + field.path, + newValue, + ) as AgentOverride; + + setPendingOverride(newOverride); + setModifiedFields((prev) => new Set(prev).add(key)); + + // Save the field value to settings + saveFieldValue(field.key, field.path, newValue); + }, + [pendingOverride, definition, saveFieldValue], + ); + + // Handle edit commit for string/number fields + const handleEditCommit = useCallback( + (key: string, newValue: string, _item: SettingsDialogItem) => { + const field = AGENT_CONFIG_FIELDS.find((f) => f.key === key); + if (!field) return; + + let parsed: string | number | undefined; + if (field.type === 'number') { + if (newValue.trim() === '') { + // Empty means clear the override + parsed = undefined; + } else { + const numParsed = Number(newValue.trim()); + if (Number.isNaN(numParsed)) { + // Invalid number; don't save + return; + } + parsed = numParsed; + } + } else { + // For strings, empty means clear the override + parsed = newValue.trim() === '' ? undefined : newValue; + } + + // Update pending override locally + const newOverride = setNestedValue( + pendingOverride as Record, + field.path, + parsed, + ) as AgentOverride; + + setPendingOverride(newOverride); + setModifiedFields((prev) => new Set(prev).add(key)); + + // Save the field value to settings + saveFieldValue(field.key, field.path, parsed); + }, + [pendingOverride, saveFieldValue], + ); + + // Handle clear/reset - reset to default value (removes override) + const handleItemClear = useCallback( + (key: string, _item: SettingsDialogItem) => { + const field = AGENT_CONFIG_FIELDS.find((f) => f.key === key); + if (!field) return; + + // Remove the override (set to undefined) + const newOverride = setNestedValue( + pendingOverride as Record, + field.path, + undefined, + ) as AgentOverride; + + setPendingOverride(newOverride); + setModifiedFields((prev) => { + const updated = new Set(prev); + updated.delete(key); + return updated; + }); + + // Save as undefined to remove the override + saveFieldValue(field.key, field.path, undefined); + }, + [pendingOverride, saveFieldValue], + ); + + // Footer content + const footerContent = + modifiedFields.size > 0 ? ( + Changes saved automatically. + ) : null; + + return ( + + ); +} diff --git a/packages/cli/src/ui/components/DialogManager.test.tsx b/packages/cli/src/ui/components/DialogManager.test.tsx index 196d0294b8..5ac33794cc 100644 --- a/packages/cli/src/ui/components/DialogManager.test.tsx +++ b/packages/cli/src/ui/components/DialogManager.test.tsx @@ -58,6 +58,9 @@ vi.mock('./ModelDialog.js', () => ({ vi.mock('./IdeTrustChangeDialog.js', () => ({ IdeTrustChangeDialog: () => IdeTrustChangeDialog, })); +vi.mock('./AgentConfigDialog.js', () => ({ + AgentConfigDialog: () => AgentConfigDialog, +})); describe('DialogManager', () => { const defaultProps = { @@ -86,6 +89,10 @@ describe('DialogManager', () => { isEditorDialogOpen: false, showPrivacyNotice: false, isPermissionsDialogOpen: false, + isAgentConfigDialogOpen: false, + selectedAgentName: undefined, + selectedAgentDisplayName: undefined, + selectedAgentDefinition: undefined, }; it('renders nothing by default', () => { @@ -148,6 +155,23 @@ describe('DialogManager', () => { [{ isEditorDialogOpen: true }, 'EditorSettingsDialog'], [{ showPrivacyNotice: true }, 'PrivacyNotice'], [{ isPermissionsDialogOpen: true }, 'PermissionsModifyTrustDialog'], + [ + { + isAgentConfigDialogOpen: true, + selectedAgentName: 'test-agent', + selectedAgentDisplayName: 'Test Agent', + selectedAgentDefinition: { + name: 'test-agent', + kind: 'local', + description: 'Test agent', + inputConfig: { inputSchema: {} }, + promptConfig: { systemPrompt: 'test' }, + modelConfig: { model: 'inherit' }, + runConfig: { maxTimeMinutes: 5 }, + }, + }, + 'AgentConfigDialog', + ], ]; it.each(testCases)( diff --git a/packages/cli/src/ui/components/DialogManager.tsx b/packages/cli/src/ui/components/DialogManager.tsx index badbfde75a..5d66927487 100644 --- a/packages/cli/src/ui/components/DialogManager.tsx +++ b/packages/cli/src/ui/components/DialogManager.tsx @@ -32,6 +32,7 @@ import process from 'node:process'; import { type UseHistoryManagerReturn } from '../hooks/useHistoryManager.js'; import { AdminSettingsChangedDialog } from './AdminSettingsChangedDialog.js'; import { IdeTrustChangeDialog } from './IdeTrustChangeDialog.js'; +import { AgentConfigDialog } from './AgentConfigDialog.js'; interface DialogManagerProps { addItem: UseHistoryManagerReturn['addItem']; @@ -161,6 +162,31 @@ export const DialogManager = ({ if (uiState.isModelDialogOpen) { return ; } + if ( + uiState.isAgentConfigDialogOpen && + uiState.selectedAgentName && + uiState.selectedAgentDisplayName && + uiState.selectedAgentDefinition + ) { + return ( + + { + // Reload agent registry to pick up changes + const agentRegistry = config?.getAgentRegistry(); + if (agentRegistry) { + await agentRegistry.reload(); + } + }} + /> + + ); + } if (uiState.isAuthenticating) { return (