/** * @license * Copyright 2025 Qwen * SPDX-License-Identifier: Apache-2.0 */ import { useState, useEffect, useMemo } from 'react'; import { Box, Text } from 'ink'; import { theme } from '../../../semantic-colors.js'; import { useKeypress } from '../../../hooks/useKeypress.js'; import { type SubagentConfig } from '@qwen-code/qwen-code-core'; import { t } from '../../../../i18n/index.js'; interface NavigationState { currentBlock: 'project' | 'user' | 'builtin'; projectIndex: number; userIndex: number; builtinIndex: number; } interface AgentSelectionStepProps { availableAgents: SubagentConfig[]; onAgentSelect: (agentIndex: number) => void; } export const AgentSelectionStep = ({ availableAgents, onAgentSelect, }: AgentSelectionStepProps) => { const [navigation, setNavigation] = useState({ currentBlock: 'project', projectIndex: 0, userIndex: 0, builtinIndex: 0, }); // Group agents by level const projectAgents = useMemo( () => availableAgents.filter((agent) => agent.level === 'project'), [availableAgents], ); const userAgents = useMemo( () => availableAgents.filter((agent) => agent.level === 'user'), [availableAgents], ); const builtinAgents = useMemo( () => availableAgents.filter((agent) => agent.level === 'builtin'), [availableAgents], ); const projectNames = useMemo( () => new Set(projectAgents.map((agent) => agent.name)), [projectAgents], ); // Initialize navigation state when agents are loaded (only once) useEffect(() => { if (projectAgents.length > 0) { setNavigation((prev) => ({ ...prev, currentBlock: 'project' })); } else if (userAgents.length > 0) { setNavigation((prev) => ({ ...prev, currentBlock: 'user' })); } else if (builtinAgents.length > 0) { setNavigation((prev) => ({ ...prev, currentBlock: 'builtin' })); } }, [projectAgents, userAgents, builtinAgents]); // Custom keyboard navigation useKeypress( (key) => { const { name } = key; if (name === 'up' || name === 'k') { setNavigation((prev) => { if (prev.currentBlock === 'project') { if (prev.projectIndex > 0) { return { ...prev, projectIndex: prev.projectIndex - 1 }; } else if (builtinAgents.length > 0) { // Move to last item in builtin block return { ...prev, currentBlock: 'builtin', builtinIndex: builtinAgents.length - 1, }; } else if (userAgents.length > 0) { // Move to last item in user block return { ...prev, currentBlock: 'user', userIndex: userAgents.length - 1, }; } else { // Wrap to last item in project block return { ...prev, projectIndex: projectAgents.length - 1 }; } } else if (prev.currentBlock === 'user') { if (prev.userIndex > 0) { return { ...prev, userIndex: prev.userIndex - 1 }; } else if (projectAgents.length > 0) { // Move to last item in project block return { ...prev, currentBlock: 'project', projectIndex: projectAgents.length - 1, }; } else if (builtinAgents.length > 0) { // Move to last item in builtin block return { ...prev, currentBlock: 'builtin', builtinIndex: builtinAgents.length - 1, }; } else { // Wrap to last item in user block return { ...prev, userIndex: userAgents.length - 1 }; } } else { // builtin block if (prev.builtinIndex > 0) { return { ...prev, builtinIndex: prev.builtinIndex - 1 }; } else if (userAgents.length > 0) { // Move to last item in user block return { ...prev, currentBlock: 'user', userIndex: userAgents.length - 1, }; } else if (projectAgents.length > 0) { // Move to last item in project block return { ...prev, currentBlock: 'project', projectIndex: projectAgents.length - 1, }; } else { // Wrap to last item in builtin block return { ...prev, builtinIndex: builtinAgents.length - 1 }; } } }); } else if (name === 'down' || name === 'j') { setNavigation((prev) => { if (prev.currentBlock === 'project') { if (prev.projectIndex < projectAgents.length - 1) { return { ...prev, projectIndex: prev.projectIndex + 1 }; } else if (userAgents.length > 0) { // Move to first item in user block return { ...prev, currentBlock: 'user', userIndex: 0 }; } else if (builtinAgents.length > 0) { // Move to first item in builtin block return { ...prev, currentBlock: 'builtin', builtinIndex: 0 }; } else { // Wrap to first item in project block return { ...prev, projectIndex: 0 }; } } else if (prev.currentBlock === 'user') { if (prev.userIndex < userAgents.length - 1) { return { ...prev, userIndex: prev.userIndex + 1 }; } else if (builtinAgents.length > 0) { // Move to first item in builtin block return { ...prev, currentBlock: 'builtin', builtinIndex: 0 }; } else if (projectAgents.length > 0) { // Move to first item in project block return { ...prev, currentBlock: 'project', projectIndex: 0 }; } else { // Wrap to first item in user block return { ...prev, userIndex: 0 }; } } else { // builtin block if (prev.builtinIndex < builtinAgents.length - 1) { return { ...prev, builtinIndex: prev.builtinIndex + 1 }; } else if (projectAgents.length > 0) { // Move to first item in project block return { ...prev, currentBlock: 'project', projectIndex: 0 }; } else if (userAgents.length > 0) { // Move to first item in user block return { ...prev, currentBlock: 'user', userIndex: 0 }; } else { // Wrap to first item in builtin block return { ...prev, builtinIndex: 0 }; } } }); } else if (name === 'return' || name === 'space') { // Calculate global index and select current item let globalIndex: number; if (navigation.currentBlock === 'project') { globalIndex = navigation.projectIndex; } else if (navigation.currentBlock === 'user') { // User agents come after project agents in the availableAgents array globalIndex = projectAgents.length + navigation.userIndex; } else { // builtin block // Builtin agents come after project and user agents in the availableAgents array globalIndex = projectAgents.length + userAgents.length + navigation.builtinIndex; } if (globalIndex >= 0 && globalIndex < availableAgents.length) { onAgentSelect(globalIndex); } } }, { isActive: true }, ); if (availableAgents.length === 0) { return ( {t('No subagents found.')} {t("Use '/agents create' to create your first subagent.")} ); } // Render custom radio button items const renderAgentItem = ( agent: { name: string; level: 'project' | 'user' | 'builtin' | 'session'; isBuiltin?: boolean; }, index: number, isSelected: boolean, ) => { const textColor = isSelected ? theme.text.accent : theme.text.primary; return ( {isSelected ? '●' : ' '} {agent.name} {agent.isBuiltin && ( {' '} {t('(built-in)')} )} {agent.level === 'user' && projectNames.has(agent.name) && ( {' '} {t('(overridden by project level agent)')} )} ); }; // Calculate enabled agents count (excluding conflicted user-level agents) const enabledAgentsCount = projectAgents.length + userAgents.filter((agent) => !projectNames.has(agent.name)).length + builtinAgents.length; return ( {/* Project Level Agents */} {projectAgents.length > 0 && ( {t('Project Level ({{path}})', { path: projectAgents[0].filePath?.replace(/\/[^/]+$/, '') || '', })} {projectAgents.map((agent, index) => { const isSelected = navigation.currentBlock === 'project' && navigation.projectIndex === index; return renderAgentItem(agent, index, isSelected); })} )} {/* User Level Agents */} {userAgents.length > 0 && ( 0 ? 1 : 0} > {t('User Level ({{path}})', { path: userAgents[0].filePath?.replace(/\/[^/]+$/, '') || '', })} {userAgents.map((agent, index) => { const isSelected = navigation.currentBlock === 'user' && navigation.userIndex === index; return renderAgentItem(agent, index, isSelected); })} )} {/* Built-in Agents */} {builtinAgents.length > 0 && ( {t('Built-in Agents')} {builtinAgents.map((agent, index) => { const isSelected = navigation.currentBlock === 'builtin' && navigation.builtinIndex === index; return renderAgentItem(agent, index, isSelected); })} )} {/* Agent count summary */} {(projectAgents.length > 0 || userAgents.length > 0 || builtinAgents.length > 0) && ( {t('Using: {{count}} agents', { count: enabledAgentsCount.toString(), })} )} ); };