mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
fix: duplicate subagents config if qwen-code runs in home dir
This commit is contained in:
@@ -227,7 +227,7 @@ export const AgentSelectionStep = ({
|
|||||||
const textColor = isSelected ? theme.text.accent : theme.text.primary;
|
const textColor = isSelected ? theme.text.accent : theme.text.primary;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box key={agent.name} alignItems="center">
|
<Box key={`${agent.name}-${agent.level}`} alignItems="center">
|
||||||
<Box minWidth={2} flexShrink={0}>
|
<Box minWidth={2} flexShrink={0}>
|
||||||
<Text color={isSelected ? theme.text.accent : theme.text.primary}>
|
<Text color={isSelected ? theme.text.accent : theme.text.primary}>
|
||||||
{isSelected ? '●' : ' '}
|
{isSelected ? '●' : ' '}
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ You are a helpful assistant.
|
|||||||
const config = manager.parseSubagentContent(
|
const config = manager.parseSubagentContent(
|
||||||
validMarkdown,
|
validMarkdown,
|
||||||
validConfig.filePath,
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(config.name).toBe('test-agent');
|
expect(config.name).toBe('test-agent');
|
||||||
@@ -209,6 +210,7 @@ You are a helpful assistant.
|
|||||||
const config = manager.parseSubagentContent(
|
const config = manager.parseSubagentContent(
|
||||||
markdownWithTools,
|
markdownWithTools,
|
||||||
validConfig.filePath,
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(config.tools).toEqual(['read_file', 'write_file']);
|
expect(config.tools).toEqual(['read_file', 'write_file']);
|
||||||
@@ -229,6 +231,7 @@ You are a helpful assistant.
|
|||||||
const config = manager.parseSubagentContent(
|
const config = manager.parseSubagentContent(
|
||||||
markdownWithModel,
|
markdownWithModel,
|
||||||
validConfig.filePath,
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(config.modelConfig).toEqual({ model: 'custom-model', temp: 0.5 });
|
expect(config.modelConfig).toEqual({ model: 'custom-model', temp: 0.5 });
|
||||||
@@ -249,6 +252,7 @@ You are a helpful assistant.
|
|||||||
const config = manager.parseSubagentContent(
|
const config = manager.parseSubagentContent(
|
||||||
markdownWithRun,
|
markdownWithRun,
|
||||||
validConfig.filePath,
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(config.runConfig).toEqual({ max_time_minutes: 5, max_turns: 10 });
|
expect(config.runConfig).toEqual({ max_time_minutes: 5, max_turns: 10 });
|
||||||
@@ -266,6 +270,7 @@ You are a helpful assistant.
|
|||||||
const config = manager.parseSubagentContent(
|
const config = manager.parseSubagentContent(
|
||||||
markdownWithNumeric,
|
markdownWithNumeric,
|
||||||
validConfig.filePath,
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(config.name).toBe('11');
|
expect(config.name).toBe('11');
|
||||||
@@ -286,6 +291,7 @@ You are a helpful assistant.
|
|||||||
const config = manager.parseSubagentContent(
|
const config = manager.parseSubagentContent(
|
||||||
markdownWithBoolean,
|
markdownWithBoolean,
|
||||||
validConfig.filePath,
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(config.name).toBe('true');
|
expect(config.name).toBe('true');
|
||||||
@@ -301,8 +307,13 @@ You are a helpful assistant.
|
|||||||
const projectConfig = manager.parseSubagentContent(
|
const projectConfig = manager.parseSubagentContent(
|
||||||
validMarkdown,
|
validMarkdown,
|
||||||
projectPath,
|
projectPath,
|
||||||
|
'project',
|
||||||
|
);
|
||||||
|
const userConfig = manager.parseSubagentContent(
|
||||||
|
validMarkdown,
|
||||||
|
userPath,
|
||||||
|
'user',
|
||||||
);
|
);
|
||||||
const userConfig = manager.parseSubagentContent(validMarkdown, userPath);
|
|
||||||
|
|
||||||
expect(projectConfig.level).toBe('project');
|
expect(projectConfig.level).toBe('project');
|
||||||
expect(userConfig.level).toBe('user');
|
expect(userConfig.level).toBe('user');
|
||||||
@@ -313,7 +324,11 @@ You are a helpful assistant.
|
|||||||
Just content`;
|
Just content`;
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
manager.parseSubagentContent(invalidMarkdown, validConfig.filePath),
|
manager.parseSubagentContent(
|
||||||
|
invalidMarkdown,
|
||||||
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
|
),
|
||||||
).toThrow(SubagentError);
|
).toThrow(SubagentError);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -326,7 +341,11 @@ You are a helpful assistant.
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
manager.parseSubagentContent(markdownWithoutName, validConfig.filePath),
|
manager.parseSubagentContent(
|
||||||
|
markdownWithoutName,
|
||||||
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
|
),
|
||||||
).toThrow(SubagentError);
|
).toThrow(SubagentError);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -342,39 +361,20 @@ You are a helpful assistant.
|
|||||||
manager.parseSubagentContent(
|
manager.parseSubagentContent(
|
||||||
markdownWithoutDescription,
|
markdownWithoutDescription,
|
||||||
validConfig.filePath,
|
validConfig.filePath,
|
||||||
|
'project',
|
||||||
),
|
),
|
||||||
).toThrow(SubagentError);
|
).toThrow(SubagentError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should warn when filename does not match subagent name', () => {
|
|
||||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
||||||
const mismatchedPath = '/test/project/.qwen/agents/wrong-filename.md';
|
|
||||||
|
|
||||||
const config = manager.parseSubagentContent(
|
|
||||||
validMarkdown,
|
|
||||||
mismatchedPath,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(config.name).toBe('test-agent');
|
|
||||||
expect(consoleSpy).toHaveBeenCalledWith(
|
|
||||||
expect.stringContaining(
|
|
||||||
'Warning: Subagent file "wrong-filename.md" contains name "test-agent"',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
expect(consoleSpy).toHaveBeenCalledWith(
|
|
||||||
expect.stringContaining(
|
|
||||||
'Consider renaming the file to "test-agent.md"',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
consoleSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not warn when filename matches subagent name', () => {
|
it('should not warn when filename matches subagent name', () => {
|
||||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
const matchingPath = '/test/project/.qwen/agents/test-agent.md';
|
const matchingPath = '/test/project/.qwen/agents/test-agent.md';
|
||||||
|
|
||||||
const config = manager.parseSubagentContent(validMarkdown, matchingPath);
|
const config = manager.parseSubagentContent(
|
||||||
|
validMarkdown,
|
||||||
|
matchingPath,
|
||||||
|
'project',
|
||||||
|
);
|
||||||
|
|
||||||
expect(config.name).toBe('test-agent');
|
expect(config.name).toBe('test-agent');
|
||||||
expect(consoleSpy).not.toHaveBeenCalled();
|
expect(consoleSpy).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -330,7 +330,10 @@ export class SubagentManager {
|
|||||||
* @returns SubagentConfig
|
* @returns SubagentConfig
|
||||||
* @throws SubagentError if parsing fails
|
* @throws SubagentError if parsing fails
|
||||||
*/
|
*/
|
||||||
async parseSubagentFile(filePath: string): Promise<SubagentConfig> {
|
async parseSubagentFile(
|
||||||
|
filePath: string,
|
||||||
|
level: SubagentLevel,
|
||||||
|
): Promise<SubagentConfig> {
|
||||||
let content: string;
|
let content: string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -342,7 +345,7 @@ export class SubagentManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.parseSubagentContent(content, filePath);
|
return this.parseSubagentContent(content, filePath, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -353,7 +356,11 @@ export class SubagentManager {
|
|||||||
* @returns SubagentConfig
|
* @returns SubagentConfig
|
||||||
* @throws SubagentError if parsing fails
|
* @throws SubagentError if parsing fails
|
||||||
*/
|
*/
|
||||||
parseSubagentContent(content: string, filePath: string): SubagentConfig {
|
parseSubagentContent(
|
||||||
|
content: string,
|
||||||
|
filePath: string,
|
||||||
|
level: SubagentLevel,
|
||||||
|
): SubagentConfig {
|
||||||
try {
|
try {
|
||||||
// Split frontmatter and content
|
// Split frontmatter and content
|
||||||
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
||||||
@@ -394,31 +401,16 @@ export class SubagentManager {
|
|||||||
| undefined;
|
| undefined;
|
||||||
const color = frontmatter['color'] as string | undefined;
|
const color = frontmatter['color'] as string | undefined;
|
||||||
|
|
||||||
// Determine level from file path using robust, cross-platform check
|
|
||||||
// A project-level agent lives under <projectRoot>/.qwen/agents
|
|
||||||
const projectAgentsDir = path.join(
|
|
||||||
this.config.getProjectRoot(),
|
|
||||||
QWEN_CONFIG_DIR,
|
|
||||||
AGENT_CONFIG_DIR,
|
|
||||||
);
|
|
||||||
const rel = path.relative(
|
|
||||||
path.normalize(projectAgentsDir),
|
|
||||||
path.normalize(filePath),
|
|
||||||
);
|
|
||||||
const isProjectLevel =
|
|
||||||
rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
||||||
const level: SubagentLevel = isProjectLevel ? 'project' : 'user';
|
|
||||||
|
|
||||||
const config: SubagentConfig = {
|
const config: SubagentConfig = {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
tools,
|
tools,
|
||||||
systemPrompt: systemPrompt.trim(),
|
systemPrompt: systemPrompt.trim(),
|
||||||
level,
|
|
||||||
filePath,
|
filePath,
|
||||||
modelConfig: modelConfig as Partial<ModelConfig>,
|
modelConfig: modelConfig as Partial<ModelConfig>,
|
||||||
runConfig: runConfig as Partial<RunConfig>,
|
runConfig: runConfig as Partial<RunConfig>,
|
||||||
color,
|
color,
|
||||||
|
level,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate the parsed configuration
|
// Validate the parsed configuration
|
||||||
@@ -427,16 +419,6 @@ export class SubagentManager {
|
|||||||
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
|
throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn if filename doesn't match subagent name (potential issue)
|
|
||||||
const expectedFilename = `${config.name}.md`;
|
|
||||||
const actualFilename = path.basename(filePath);
|
|
||||||
if (actualFilename !== expectedFilename) {
|
|
||||||
console.warn(
|
|
||||||
`Warning: Subagent file "${actualFilename}" contains name "${config.name}" but filename suggests "${path.basename(actualFilename, '.md')}". ` +
|
|
||||||
`Consider renaming the file to "${expectedFilename}" for consistency.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new SubagentError(
|
throw new SubagentError(
|
||||||
@@ -679,14 +661,18 @@ export class SubagentManager {
|
|||||||
return BuiltinAgentRegistry.getBuiltinAgents();
|
return BuiltinAgentRegistry.getBuiltinAgents();
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseDir =
|
const projectRoot = this.config.getProjectRoot();
|
||||||
level === 'project'
|
const homeDir = os.homedir();
|
||||||
? path.join(
|
const isHomeDirectory = path.resolve(projectRoot) === path.resolve(homeDir);
|
||||||
this.config.getProjectRoot(),
|
|
||||||
QWEN_CONFIG_DIR,
|
// If project level is requested but project root is same as home directory,
|
||||||
AGENT_CONFIG_DIR,
|
// return empty array to avoid conflicts between project and global agents
|
||||||
)
|
if (level === 'project' && isHomeDirectory) {
|
||||||
: path.join(os.homedir(), QWEN_CONFIG_DIR, AGENT_CONFIG_DIR);
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseDir = level === 'project' ? projectRoot : homeDir;
|
||||||
|
baseDir = path.join(baseDir, QWEN_CONFIG_DIR, AGENT_CONFIG_DIR);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = await fs.readdir(baseDir);
|
const files = await fs.readdir(baseDir);
|
||||||
@@ -698,7 +684,7 @@ export class SubagentManager {
|
|||||||
const filePath = path.join(baseDir, file);
|
const filePath = path.join(baseDir, file);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const config = await this.parseSubagentFile(filePath);
|
const config = await this.parseSubagentFile(filePath, level);
|
||||||
subagents.push(config);
|
subagents.push(config);
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
// Ignore invalid files
|
// Ignore invalid files
|
||||||
|
|||||||
Reference in New Issue
Block a user