mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat: add system-reminder to help model use subagent
This commit is contained in:
@@ -53,18 +53,7 @@ export function AgentsManagerDialog({
|
||||
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 || []),
|
||||
];
|
||||
const allAgents = await manager.listSubagents();
|
||||
|
||||
setAvailableAgents(allAgents);
|
||||
}, [config]);
|
||||
|
||||
@@ -199,6 +199,9 @@ describe('Gemini Client (client.ts)', () => {
|
||||
vertexai: false,
|
||||
authType: AuthType.USE_GEMINI,
|
||||
};
|
||||
const mockSubagentManager = {
|
||||
listSubagents: vi.fn().mockResolvedValue([]),
|
||||
};
|
||||
const mockConfigObject = {
|
||||
getContentGeneratorConfig: vi
|
||||
.fn()
|
||||
@@ -233,6 +236,7 @@ describe('Gemini Client (client.ts)', () => {
|
||||
getCliVersion: vi.fn().mockReturnValue('1.0.0'),
|
||||
getChatCompression: vi.fn().mockReturnValue(undefined),
|
||||
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
|
||||
getSubagentManager: vi.fn().mockReturnValue(mockSubagentManager),
|
||||
};
|
||||
const MockedConfig = vi.mocked(Config, true);
|
||||
MockedConfig.mockImplementation(
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
NextSpeakerCheckEvent,
|
||||
} from '../telemetry/types.js';
|
||||
import { IdeContext, File } from '../ide/ideContext.js';
|
||||
import { TaskTool } from '../tools/task.js';
|
||||
|
||||
function isThinkingSupported(model: string) {
|
||||
if (model.startsWith('gemini-2.5')) return true;
|
||||
@@ -452,7 +453,8 @@ export class GeminiClient {
|
||||
turns: number = MAX_TURNS,
|
||||
originalModel?: string,
|
||||
): AsyncGenerator<ServerGeminiStreamEvent, Turn> {
|
||||
if (this.lastPromptId !== prompt_id) {
|
||||
const isNewPrompt = this.lastPromptId !== prompt_id;
|
||||
if (isNewPrompt) {
|
||||
this.loopDetector.reset(prompt_id);
|
||||
this.lastPromptId = prompt_id;
|
||||
}
|
||||
@@ -549,6 +551,24 @@ export class GeminiClient {
|
||||
this.forceFullIdeContext = false;
|
||||
}
|
||||
|
||||
if (isNewPrompt) {
|
||||
const taskTool = this.config.getToolRegistry().getTool(TaskTool.Name);
|
||||
const subagents = (
|
||||
await this.config.getSubagentManager().listSubagents()
|
||||
).filter((subagent) => subagent.level !== 'builtin');
|
||||
|
||||
if (taskTool && subagents.length > 0) {
|
||||
this.getChat().addHistory({
|
||||
role: 'user',
|
||||
parts: [
|
||||
{
|
||||
text: `<system-reminder>You have powerful specialized agents at your disposal, available agent types are: ${subagents.map((subagent) => subagent.name).join(', ')}. PROACTIVELY use the ${TaskTool.Name} tool to delegate user's task to appropriate agent when user's task matches agent capabilities. Ignore this message if user's task is not relevant to any agent. This message is for internal use only. Do not mention this to user in your response.</system-reminder>`,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const turn = new Turn(this.getChat(), prompt_id);
|
||||
|
||||
const loopDetected = await this.loopDetector.turnStarted(signal);
|
||||
|
||||
@@ -40,6 +40,7 @@ const AGENT_CONFIG_DIR = 'agents';
|
||||
*/
|
||||
export class SubagentManager {
|
||||
private readonly validator: SubagentValidator;
|
||||
private subagentsCache: Map<SubagentLevel, SubagentConfig[]> | null = null;
|
||||
|
||||
constructor(private readonly config: Config) {
|
||||
this.validator = new SubagentValidator();
|
||||
@@ -93,6 +94,8 @@ export class SubagentManager {
|
||||
|
||||
try {
|
||||
await fs.writeFile(filePath, content, 'utf8');
|
||||
// Clear cache after successful creation
|
||||
this.clearCache();
|
||||
} catch (error) {
|
||||
throw new SubagentError(
|
||||
`Failed to write subagent file: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
@@ -181,6 +184,8 @@ export class SubagentManager {
|
||||
|
||||
try {
|
||||
await fs.writeFile(existing.filePath, content, 'utf8');
|
||||
// Clear cache after successful update
|
||||
this.clearCache();
|
||||
} catch (error) {
|
||||
throw new SubagentError(
|
||||
`Failed to update subagent file: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
@@ -237,6 +242,9 @@ export class SubagentManager {
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
// Clear cache after successful deletion
|
||||
this.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,9 +263,17 @@ export class SubagentManager {
|
||||
? [options.level]
|
||||
: ['project', 'user', 'builtin'];
|
||||
|
||||
// Check if we should use cache or force refresh
|
||||
const shouldUseCache = !options.force && this.subagentsCache !== null;
|
||||
|
||||
// Initialize cache if it doesn't exist or we're forcing a refresh
|
||||
if (!shouldUseCache) {
|
||||
await this.refreshCache();
|
||||
}
|
||||
|
||||
// Collect subagents from each level (project takes precedence over user, user takes precedence over builtin)
|
||||
for (const level of levelsToCheck) {
|
||||
const levelSubagents = await this.listSubagentsAtLevel(level);
|
||||
const levelSubagents = this.subagentsCache?.get(level) || [];
|
||||
|
||||
for (const subagent of levelSubagents) {
|
||||
// Skip if we've already seen this name (precedence: project > user > builtin)
|
||||
@@ -305,6 +321,30 @@ export class SubagentManager {
|
||||
return subagents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the subagents cache by loading all subagents from disk.
|
||||
* This method is called automatically when cache is null or when force=true.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private async refreshCache(): Promise<void> {
|
||||
this.subagentsCache = new Map();
|
||||
|
||||
const levels: SubagentLevel[] = ['project', 'user', 'builtin'];
|
||||
|
||||
for (const level of levels) {
|
||||
const levelSubagents = await this.listSubagentsAtLevel(level);
|
||||
this.subagentsCache.set(level, levelSubagents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the subagents cache, forcing the next listSubagents call to reload from disk.
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.subagentsCache = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a subagent by name and returns its metadata.
|
||||
*
|
||||
|
||||
@@ -116,6 +116,9 @@ export interface ListSubagentsOptions {
|
||||
|
||||
/** Sort direction */
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
|
||||
/** Force refresh from disk, bypassing cache. Defaults to false. */
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user