feat: add built-in agent(general-purpose)

This commit is contained in:
tanzhenxin
2025-09-09 20:53:53 +08:00
parent 3c67dc0c0b
commit 549f296eb5
19 changed files with 896 additions and 228 deletions

View File

@@ -8,26 +8,37 @@ import { useState } from 'react';
import { Box } from 'ink';
import { RadioButtonSelect } from '../shared/RadioButtonSelect.js';
import { MANAGEMENT_STEPS } from './types.js';
import { SubagentConfig } from '@qwen-code/qwen-code-core';
interface ActionSelectionStepProps {
selectedAgent: SubagentConfig | null;
onNavigateToStep: (step: string) => void;
onNavigateBack: () => void;
}
export const ActionSelectionStep = ({
selectedAgent,
onNavigateToStep,
onNavigateBack,
}: ActionSelectionStepProps) => {
const [selectedAction, setSelectedAction] = useState<
'view' | 'edit' | 'delete' | null
>(null);
const actions = [
// Filter actions based on whether the agent is built-in
const allActions = [
{ label: 'View Agent', value: 'view' as const },
{ label: 'Edit Agent', value: 'edit' as const },
{ label: 'Delete Agent', value: 'delete' as const },
{ label: 'Back', value: 'back' as const },
];
const actions = selectedAgent?.isBuiltin
? allActions.filter(
(action) => action.value === 'view' || action.value === 'back',
)
: allActions;
const handleActionSelect = (value: 'view' | 'edit' | 'delete' | 'back') => {
if (value === 'back') {
onNavigateBack();

View File

@@ -12,9 +12,10 @@ import { useKeypress } from '../../hooks/useKeypress.js';
import { SubagentConfig } from '@qwen-code/qwen-code-core';
interface NavigationState {
currentBlock: 'project' | 'user';
currentBlock: 'project' | 'user' | 'builtin';
projectIndex: number;
userIndex: number;
builtinIndex: number;
}
interface AgentSelectionStepProps {
@@ -30,6 +31,7 @@ export const AgentSelectionStep = ({
currentBlock: 'project',
projectIndex: 0,
userIndex: 0,
builtinIndex: 0,
});
// Group agents by level
@@ -41,6 +43,10 @@ export const AgentSelectionStep = ({
() => 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],
@@ -52,8 +58,10 @@ export const AgentSelectionStep = ({
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]);
}, [projectAgents, userAgents, builtinAgents]);
// Custom keyboard navigation
useKeypress(
@@ -65,6 +73,13 @@ export const AgentSelectionStep = ({
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 {
@@ -76,7 +91,7 @@ export const AgentSelectionStep = ({
// Wrap to last item in project block
return { ...prev, projectIndex: projectAgents.length - 1 };
}
} else {
} else if (prev.currentBlock === 'user') {
if (prev.userIndex > 0) {
return { ...prev, userIndex: prev.userIndex - 1 };
} else if (projectAgents.length > 0) {
@@ -86,10 +101,39 @@ export const AgentSelectionStep = ({
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') {
@@ -100,13 +144,19 @@ export const AgentSelectionStep = ({
} 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 {
} 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 };
@@ -114,6 +164,20 @@ export const AgentSelectionStep = ({
// 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') {
@@ -121,9 +185,14 @@ export const AgentSelectionStep = ({
let globalIndex: number;
if (navigation.currentBlock === 'project') {
globalIndex = navigation.projectIndex;
} else {
} 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) {
@@ -147,7 +216,11 @@ export const AgentSelectionStep = ({
// Render custom radio button items
const renderAgentItem = (
agent: { name: string; level: 'project' | 'user' },
agent: {
name: string;
level: 'project' | 'user' | 'builtin';
isBuiltin?: boolean;
},
index: number,
isSelected: boolean,
) => {
@@ -162,6 +235,12 @@ export const AgentSelectionStep = ({
</Box>
<Text color={textColor} wrap="truncate">
{agent.name}
{agent.isBuiltin && (
<Text color={isSelected ? theme.text.accent : theme.text.secondary}>
{' '}
(built-in)
</Text>
)}
{agent.level === 'user' && projectNames.has(agent.name) && (
<Text color={isSelected ? theme.status.warning : Colors.Gray}>
{' '}
@@ -176,7 +255,8 @@ export const AgentSelectionStep = ({
// Calculate enabled agents count (excluding conflicted user-level agents)
const enabledAgentsCount =
projectAgents.length +
userAgents.filter((agent) => !projectNames.has(agent.name)).length;
userAgents.filter((agent) => !projectNames.has(agent.name)).length +
builtinAgents.length;
return (
<Box flexDirection="column">
@@ -199,7 +279,10 @@ export const AgentSelectionStep = ({
{/* User Level Agents */}
{userAgents.length > 0 && (
<Box flexDirection="column">
<Box
flexDirection="column"
marginBottom={builtinAgents.length > 0 ? 1 : 0}
>
<Text color={theme.text.primary} bold>
User Level ({userAgents[0].filePath.replace(/\/[^/]+$/, '')})
</Text>
@@ -214,8 +297,27 @@ export const AgentSelectionStep = ({
</Box>
)}
{/* Built-in Agents */}
{builtinAgents.length > 0 && (
<Box flexDirection="column">
<Text color={theme.text.primary} bold>
Built-in Agents
</Text>
<Box marginTop={1} flexDirection="column">
{builtinAgents.map((agent, index) => {
const isSelected =
navigation.currentBlock === 'builtin' &&
navigation.builtinIndex === index;
return renderAgentItem(agent, index, isSelected);
})}
</Box>
</Box>
)}
{/* Agent count summary */}
{(projectAgents.length > 0 || userAgents.length > 0) && (
{(projectAgents.length > 0 ||
userAgents.length > 0 ||
builtinAgents.length > 0) && (
<Box marginTop={1}>
<Text color={theme.text.secondary}>
Using: {enabledAgentsCount} agents

View File

@@ -29,15 +29,6 @@ export const AgentViewerStep = ({ selectedAgent }: AgentViewerStepProps) => {
return (
<Box flexDirection="column" gap={1}>
<Box flexDirection="column">
<Box>
<Text bold>Location: </Text>
<Text>
{agent.level === 'project'
? 'Project Level (.qwen/agents/)'
: 'User Level (~/.qwen/agents/)'}
</Text>
</Box>
<Box>
<Text bold>File Path: </Text>
<Text>{agent.filePath}</Text>

View File

@@ -50,14 +50,19 @@ export function AgentsManagerDialog({
const manager = config.getSubagentManager();
// Load agents from both levels separately to show all agents including conflicts
const [projectAgents, userAgents] = await Promise.all([
// 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 and user level)
const allAgents = [...(projectAgents || []), ...(userAgents || [])];
// Combine all agents (project, user, and builtin level)
const allAgents = [
...(projectAgents || []),
...(userAgents || []),
...(builtinAgents || []),
];
setAvailableAgents(allAgents);
}, [config]);
@@ -208,7 +213,9 @@ export function AgentsManagerDialog({
/>
);
case MANAGEMENT_STEPS.ACTION_SELECTION:
return <ActionSelectionStep {...commonProps} />;
return (
<ActionSelectionStep selectedAgent={selectedAgent} {...commonProps} />
);
case MANAGEMENT_STEPS.AGENT_VIEWER:
return (
<AgentViewerStep selectedAgent={selectedAgent} {...commonProps} />

View File

@@ -289,9 +289,7 @@ const ToolCallItem: React.FC<{
<Box flexDirection="row">
<Box minWidth={STATUS_INDICATOR_WIDTH}>{statusIcon}</Box>
<Text wrap="truncate-end">
<Text>
{toolCall.name}
</Text>{' '}
<Text>{toolCall.name}</Text>{' '}
<Text color={Colors.Gray}>{description}</Text>
{toolCall.error && (
<Text color={theme.status.error}> - {toolCall.error}</Text>

View File

@@ -65,7 +65,8 @@ export function ToolSelector({
(tool) =>
tool.kind === Kind.Read ||
tool.kind === Kind.Search ||
tool.kind === Kind.Fetch,
tool.kind === Kind.Fetch ||
tool.kind === Kind.Think,
)
.map((tool) => tool.displayName)
.sort();
@@ -75,8 +76,7 @@ export function ToolSelector({
(tool) =>
tool.kind === Kind.Edit ||
tool.kind === Kind.Delete ||
tool.kind === Kind.Move ||
tool.kind === Kind.Think,
tool.kind === Kind.Move,
)
.map((tool) => tool.displayName)
.sort();