Refactor subagent delegation to be one tool per agent (#17346)

This commit is contained in:
Christian Gunderman
2026-01-23 02:18:31 +00:00
committed by GitHub
parent 07bd89399d
commit 2c6781d134
18 changed files with 247 additions and 720 deletions
+17 -14
View File
@@ -155,8 +155,8 @@ vi.mock('../agents/registry.js', () => {
return { AgentRegistry: AgentRegistryMock };
});
vi.mock('../agents/delegate-to-agent-tool.js', () => ({
DelegateToAgentTool: vi.fn(),
vi.mock('../agents/subagent-tool.js', () => ({
SubagentTool: vi.fn(),
}));
vi.mock('../resources/resource-registry.js', () => ({
@@ -966,12 +966,15 @@ describe('Server Config (config.ts)', () => {
AgentRegistryMock.prototype.getDefinition.mockReturnValue(
mockAgentDefinition,
);
AgentRegistryMock.prototype.getAllDefinitions.mockReturnValue([
mockAgentDefinition,
]);
const DelegateToAgentToolMock = (
(await vi.importMock('../agents/delegate-to-agent-tool.js')) as {
DelegateToAgentTool: Mock;
const SubAgentToolMock = (
(await vi.importMock('../agents/subagent-tool.js')) as {
SubagentTool: Mock;
}
).DelegateToAgentTool;
).SubagentTool;
await config.initialize();
@@ -981,8 +984,8 @@ describe('Server Config (config.ts)', () => {
}
).ToolRegistry.prototype.registerTool;
expect(DelegateToAgentToolMock).toHaveBeenCalledTimes(1);
expect(DelegateToAgentToolMock).toHaveBeenCalledWith(
expect(SubAgentToolMock).toHaveBeenCalledTimes(1);
expect(SubAgentToolMock).toHaveBeenCalledWith(
expect.anything(), // AgentRegistry
config,
expect.anything(), // MessageBus
@@ -990,7 +993,7 @@ describe('Server Config (config.ts)', () => {
const calls = registerToolMock.mock.calls;
const registeredWrappers = calls.filter(
(call) => call[0] instanceof DelegateToAgentToolMock,
(call) => call[0] instanceof SubAgentToolMock,
);
expect(registeredWrappers).toHaveLength(1);
});
@@ -1007,15 +1010,15 @@ describe('Server Config (config.ts)', () => {
};
const config = new Config(params);
const DelegateToAgentToolMock = (
(await vi.importMock('../agents/delegate-to-agent-tool.js')) as {
DelegateToAgentTool: Mock;
const SubAgentToolMock = (
(await vi.importMock('../agents/subagent-tool.js')) as {
SubagentTool: Mock;
}
).DelegateToAgentTool;
).SubagentTool;
await config.initialize();
expect(DelegateToAgentToolMock).not.toHaveBeenCalled();
expect(SubAgentToolMock).not.toHaveBeenCalled();
});
describe('with minified tool class names', () => {
+25 -17
View File
@@ -101,8 +101,7 @@ import { getCodeAssistServer } from '../code_assist/codeAssist.js';
import type { Experiments } from '../code_assist/experiments/experiments.js';
import { AgentRegistry } from '../agents/registry.js';
import { setGlobalProxy } from '../utils/fetch.js';
import { DelegateToAgentTool } from '../agents/delegate-to-agent-tool.js';
import { DELEGATE_TO_AGENT_TOOL_NAME } from '../tools/tool-names.js';
import { SubagentTool } from '../agents/subagent-tool.js';
import { getExperiments } from '../code_assist/experiments/experiments.js';
import { ExperimentFlags } from '../code_assist/experiments/flagNames.js';
import { debugLogger } from '../utils/debugLogger.js';
@@ -218,6 +217,7 @@ import {
} from '../utils/extensionLoader.js';
import { McpClientManager } from '../tools/mcp-client-manager.js';
import type { EnvironmentSanitizationConfig } from '../services/environmentSanitization.js';
import { getErrorMessage } from '../utils/errors.js';
export type { FileFilteringOptions };
export {
@@ -1967,8 +1967,7 @@ export class Config {
}
// Register Subagents as Tools
// Register DelegateToAgentTool if agents are enabled
this.registerDelegateToAgentTool(registry);
this.registerSubAgentTools(registry);
await registry.discoverAllTools();
registry.sortTools();
@@ -1976,27 +1975,36 @@ export class Config {
}
/**
* Registers the DelegateToAgentTool if agents or related features are enabled.
* Registers SubAgentTools for all available agents.
*/
private registerDelegateToAgentTool(registry: ToolRegistry): void {
private registerSubAgentTools(registry: ToolRegistry): void {
const agentsOverrides = this.getAgentsSettings().overrides ?? {};
if (
this.isAgentsEnabled() ||
agentsOverrides['codebase_investigator']?.enabled !== false ||
agentsOverrides['cli_help']?.enabled !== false
) {
// Check if the delegate tool itself is allowed (if allowedTools is set)
const allowedTools = this.getAllowedTools();
const isAllowed =
!allowedTools || allowedTools.includes(DELEGATE_TO_AGENT_TOOL_NAME);
const definitions = this.agentRegistry.getAllDefinitions();
if (isAllowed) {
const delegateTool = new DelegateToAgentTool(
this.agentRegistry,
this,
this.getMessageBus(),
);
registry.registerTool(delegateTool);
for (const definition of definitions) {
const isAllowed =
!allowedTools || allowedTools.includes(definition.name);
if (isAllowed) {
try {
const tool = new SubagentTool(
definition,
this,
this.getMessageBus(),
);
registry.registerTool(tool);
} catch (e: unknown) {
debugLogger.warn(
`Failed to register tool for agent ${definition.name}: ${getErrorMessage(e)}`,
);
}
}
}
}
}
@@ -2092,7 +2100,7 @@ export class Config {
private onAgentsRefreshed = async () => {
if (this.toolRegistry) {
this.registerDelegateToAgentTool(this.toolRegistry);
this.registerSubAgentTools(this.toolRegistry);
}
// Propagate updates to the active chat session
const client = this.getGeminiClient();