feat: sdk subagent support

This commit is contained in:
mingholy.lmh
2025-11-25 10:03:15 +08:00
parent e1ffaec499
commit d76cdf1076
11 changed files with 705 additions and 41 deletions

View File

@@ -613,6 +613,12 @@ export class Config {
}
this.promptRegistry = new PromptRegistry();
this.subagentManager = new SubagentManager(this);
// Load session subagents if they were provided before initialization
if (this.sessionSubagents.length > 0) {
this.subagentManager.loadSessionSubagents(this.sessionSubagents);
}
this.toolRegistry = await this.createToolRegistry();
await this.geminiClient.initialize();
@@ -874,13 +880,6 @@ export class Config {
this.sessionSubagents = subagents;
}
addSessionSubagents(subagents: SubagentConfig[]): void {
if (this.initialized) {
throw new Error('Cannot modify sessionSubagents after initialization');
}
this.sessionSubagents = [...this.sessionSubagents, ...subagents];
}
getSdkMode(): boolean {
return this.sdkMode;
}

View File

@@ -182,7 +182,7 @@ You are a helpful assistant.
it('should parse valid markdown content', () => {
const config = manager.parseSubagentContent(
validMarkdown,
validConfig.filePath,
validConfig.filePath!,
'project',
);
@@ -207,7 +207,7 @@ You are a helpful assistant.
const config = manager.parseSubagentContent(
markdownWithTools,
validConfig.filePath,
validConfig.filePath!,
'project',
);
@@ -228,7 +228,7 @@ You are a helpful assistant.
const config = manager.parseSubagentContent(
markdownWithModel,
validConfig.filePath,
validConfig.filePath!,
'project',
);
@@ -249,7 +249,7 @@ You are a helpful assistant.
const config = manager.parseSubagentContent(
markdownWithRun,
validConfig.filePath,
validConfig.filePath!,
'project',
);
@@ -267,7 +267,7 @@ You are a helpful assistant.
const config = manager.parseSubagentContent(
markdownWithNumeric,
validConfig.filePath,
validConfig.filePath!,
'project',
);
@@ -288,7 +288,7 @@ You are a helpful assistant.
const config = manager.parseSubagentContent(
markdownWithBoolean,
validConfig.filePath,
validConfig.filePath!,
'project',
);
@@ -324,7 +324,7 @@ Just content`;
expect(() =>
manager.parseSubagentContent(
invalidMarkdown,
validConfig.filePath,
validConfig.filePath!,
'project',
),
).toThrow(SubagentError);
@@ -341,7 +341,7 @@ You are a helpful assistant.
expect(() =>
manager.parseSubagentContent(
markdownWithoutName,
validConfig.filePath,
validConfig.filePath!,
'project',
),
).toThrow(SubagentError);
@@ -358,7 +358,7 @@ You are a helpful assistant.
expect(() =>
manager.parseSubagentContent(
markdownWithoutDescription,
validConfig.filePath,
validConfig.filePath!,
'project',
),
).toThrow(SubagentError);
@@ -438,7 +438,7 @@ You are a helpful assistant.
await manager.createSubagent(validConfig, { level: 'project' });
expect(fs.mkdir).toHaveBeenCalledWith(
path.normalize(path.dirname(validConfig.filePath)),
path.normalize(path.dirname(validConfig.filePath!)),
{ recursive: true },
);
expect(fs.writeFile).toHaveBeenCalledWith(

View File

@@ -159,7 +159,14 @@ export class SubagentManager {
return this.findSubagentByNameAtLevel(name, level);
}
// Try project level first
// Try session level first (highest priority for runtime)
const sessionSubagents = this.subagentsCache?.get('session') || [];
const sessionConfig = sessionSubagents.find((agent) => agent.name === name);
if (sessionConfig) {
return sessionConfig;
}
// Try project level
const projectConfig = await this.findSubagentByNameAtLevel(name, 'project');
if (projectConfig) {
return projectConfig;
@@ -220,6 +227,15 @@ export class SubagentManager {
// Validate the updated configuration
this.validator.validateOrThrow(updatedConfig);
// Ensure filePath exists for file-based agents
if (!existing.filePath) {
throw new SubagentError(
`Cannot update subagent "${name}": no file path available`,
SubagentErrorCode.FILE_ERROR,
name,
);
}
// Write the updated configuration
const content = this.serializeSubagent(updatedConfig);
@@ -302,11 +318,6 @@ export class SubagentManager {
// In SDK mode, only load session-level subagents
if (this.config.getSdkMode()) {
const sessionSubagents = this.config.getSessionSubagents();
if (sessionSubagents && sessionSubagents.length > 0) {
this.loadSessionSubagents(sessionSubagents);
}
const levelsToCheck: SubagentLevel[] = options.level
? [options.level]
: ['session'];

View File

@@ -42,8 +42,8 @@ export interface SubagentConfig {
/** Storage level - determines where the configuration file is stored */
level: SubagentLevel;
/** Absolute path to the configuration file */
filePath: string;
/** Absolute path to the configuration file. Optional for session subagents. */
filePath?: string;
/**
* Optional model configuration. If not provided, uses defaults.