/** * @license * Copyright 2025 Qwen * SPDX-License-Identifier: Apache-2.0 */ import { useState, useCallback, useMemo, useEffect } from 'react'; import { Box, Text } from 'ink'; import { AgentSelectionStep } from './AgentSelectionStep.js'; import { ActionSelectionStep } from './ActionSelectionStep.js'; import { AgentViewerStep } from './AgentViewerStep.js'; import { EditOptionsStep } from './AgentEditStep.js'; import { AgentDeleteStep } from './AgentDeleteStep.js'; import { ToolSelector } from '../create/ToolSelector.js'; import { ColorSelector } from '../create/ColorSelector.js'; import { MANAGEMENT_STEPS } from '../types.js'; import { Colors } from '../../../colors.js'; import { theme } from '../../../semantic-colors.js'; import { getColorForDisplay, shouldShowColor } from '../utils.js'; import { Config, SubagentConfig } from '@qwen-code/qwen-code-core'; import { useKeypress } from '../../../hooks/useKeypress.js'; interface AgentsManagerDialogProps { onClose: () => void; config: Config | null; } /** * Main orchestrator component for the agents management dialog. */ export function AgentsManagerDialog({ onClose, config, }: AgentsManagerDialogProps) { // Simple state management with useState hooks const [availableAgents, setAvailableAgents] = useState([]); const [selectedAgentIndex, setSelectedAgentIndex] = useState(-1); const [navigationStack, setNavigationStack] = useState([ MANAGEMENT_STEPS.AGENT_SELECTION, ]); // Memoized selectedAgent based on index const selectedAgent = useMemo( () => selectedAgentIndex >= 0 ? availableAgents[selectedAgentIndex] : null, [availableAgents, selectedAgentIndex], ); // Function to load agents const loadAgents = useCallback(async () => { if (!config) return; const manager = config.getSubagentManager(); // Load agents from all levels separately to show all agents including conflicts const [projectAgents, userAgents, builtinAgents] = await Promise.all([ manager.listSubagents({ level: 'project' }), manager.listSubagents({ level: 'user' }), manager.listSubagents({ level: 'builtin' }), ]); // Combine all agents (project, user, and builtin level) const allAgents = [ ...(projectAgents || []), ...(userAgents || []), ...(builtinAgents || []), ]; setAvailableAgents(allAgents); }, [config]); // Load agents when component mounts or config changes useEffect(() => { loadAgents(); }, [loadAgents]); // Helper to get current step const getCurrentStep = useCallback( () => navigationStack[navigationStack.length - 1] || MANAGEMENT_STEPS.AGENT_SELECTION, [navigationStack], ); const handleSelectAgent = useCallback((agentIndex: number) => { setSelectedAgentIndex(agentIndex); setNavigationStack((prev) => [...prev, MANAGEMENT_STEPS.ACTION_SELECTION]); }, []); const handleNavigateToStep = useCallback((step: string) => { setNavigationStack((prev) => [...prev, step]); }, []); const handleNavigateBack = useCallback(() => { setNavigationStack((prev) => { if (prev.length <= 1) { return prev; // Can't go back from root step } return prev.slice(0, -1); }); }, []); const handleDeleteAgent = useCallback( async (agent: SubagentConfig) => { if (!config) return; try { const subagentManager = config.getSubagentManager(); await subagentManager.deleteSubagent(agent.name, agent.level); // Reload agents to get updated state await loadAgents(); // Navigate back to agent selection after successful deletion setNavigationStack([MANAGEMENT_STEPS.AGENT_SELECTION]); setSelectedAgentIndex(-1); } catch (error) { console.error('Failed to delete agent:', error); throw error; // Re-throw to let the component handle the error state } }, [config, loadAgents], ); // Centralized ESC key handling for the entire dialog useKeypress( (key) => { if (key.name !== 'escape') { return; } const currentStep = getCurrentStep(); if (currentStep === MANAGEMENT_STEPS.AGENT_SELECTION) { // On first step, ESC cancels the entire dialog onClose(); } else { // On other steps, ESC goes back to previous step in navigation stack handleNavigateBack(); } }, { isActive: true }, ); // Props for child components - now using direct state and callbacks const commonProps = useMemo( () => ({ onNavigateToStep: handleNavigateToStep, onNavigateBack: handleNavigateBack, }), [handleNavigateToStep, handleNavigateBack], ); const renderStepHeader = useCallback(() => { const currentStep = getCurrentStep(); const getStepHeaderText = () => { switch (currentStep) { case MANAGEMENT_STEPS.AGENT_SELECTION: return 'Agents'; case MANAGEMENT_STEPS.ACTION_SELECTION: return 'Choose Action'; case MANAGEMENT_STEPS.AGENT_VIEWER: return selectedAgent?.name; case MANAGEMENT_STEPS.EDIT_OPTIONS: return `Edit ${selectedAgent?.name}`; case MANAGEMENT_STEPS.EDIT_TOOLS: return `Edit Tools: ${selectedAgent?.name}`; case MANAGEMENT_STEPS.EDIT_COLOR: return `Edit Color: ${selectedAgent?.name}`; case MANAGEMENT_STEPS.DELETE_CONFIRMATION: return `Delete ${selectedAgent?.name}`; default: return 'Unknown Step'; } }; // Use agent color for the Agent Viewer header const headerColor = currentStep === MANAGEMENT_STEPS.AGENT_VIEWER && selectedAgent && shouldShowColor(selectedAgent.color) ? getColorForDisplay(selectedAgent.color) : undefined; return ( {getStepHeaderText()} ); }, [getCurrentStep, selectedAgent]); const renderStepFooter = useCallback(() => { const currentStep = getCurrentStep(); const getNavigationInstructions = () => { if (currentStep === MANAGEMENT_STEPS.AGENT_SELECTION) { if (availableAgents.length === 0) { return 'Esc to close'; } return 'Enter to select, ↑↓ to navigate, Esc to close'; } if (currentStep === MANAGEMENT_STEPS.AGENT_VIEWER) { return 'Esc to go back'; } if (currentStep === MANAGEMENT_STEPS.DELETE_CONFIRMATION) { return 'Enter to confirm, Esc to cancel'; } return 'Enter to select, ↑↓ to navigate, Esc to go back'; }; return ( {getNavigationInstructions()} ); }, [getCurrentStep, availableAgents]); const renderStepContent = useCallback(() => { const currentStep = getCurrentStep(); switch (currentStep) { case MANAGEMENT_STEPS.AGENT_SELECTION: return ( ); case MANAGEMENT_STEPS.ACTION_SELECTION: return ( ); case MANAGEMENT_STEPS.AGENT_VIEWER: return ( ); case MANAGEMENT_STEPS.EDIT_OPTIONS: return ( ); case MANAGEMENT_STEPS.EDIT_TOOLS: return ( { if (selectedAgent && config) { try { // Save the changes using SubagentManager const subagentManager = config.getSubagentManager(); await subagentManager.updateSubagent( selectedAgent.name, { tools }, selectedAgent.level, ); // Reload agents to get updated state await loadAgents(); handleNavigateBack(); } catch (error) { console.error('Failed to save agent changes:', error); } } }} config={config} /> ); case MANAGEMENT_STEPS.EDIT_COLOR: return ( { // Save changes and reload agents if (selectedAgent && config) { try { // Save the changes using SubagentManager const subagentManager = config.getSubagentManager(); await subagentManager.updateSubagent( selectedAgent.name, { color }, selectedAgent.level, ); // Reload agents to get updated state await loadAgents(); handleNavigateBack(); } catch (error) { console.error('Failed to save color changes:', error); } } }} /> ); case MANAGEMENT_STEPS.DELETE_CONFIRMATION: return ( ); default: return ( Invalid step: {currentStep} ); } }, [ getCurrentStep, availableAgents, selectedAgent, commonProps, config, loadAgents, handleNavigateBack, handleSelectAgent, handleDeleteAgent, ]); return ( {/* Main content wrapped in bounding box */} {renderStepHeader()} {renderStepContent()} {renderStepFooter()} ); }