fix(agents): default to all tools when tool list is omitted in subagents (#17422)

This commit is contained in:
Christian Gunderman
2026-01-24 01:30:18 +00:00
committed by GitHub
parent 0c134079cc
commit 77aef861fe
2 changed files with 77 additions and 17 deletions
@@ -117,6 +117,23 @@ vi.mock('../telemetry/loggers.js', () => ({
logRecoveryAttempt: vi.fn(), logRecoveryAttempt: vi.fn(),
})); }));
vi.mock('../utils/schemaValidator.js', () => ({
SchemaValidator: {
validate: vi.fn().mockReturnValue(null),
validateSchema: vi.fn().mockReturnValue(null),
},
}));
vi.mock('../utils/filesearch/crawler.js', () => ({
crawl: vi.fn().mockResolvedValue([]),
}));
vi.mock('../telemetry/clearcut-logger/clearcut-logger.js', () => ({
ClearcutLogger: class {
log() {}
},
}));
vi.mock('../utils/promptIdContext.js', async (importOriginal) => { vi.mock('../utils/promptIdContext.js', async (importOriginal) => {
const actual = const actual =
await importOriginal<typeof import('../utils/promptIdContext.js')>(); await importOriginal<typeof import('../utils/promptIdContext.js')>();
@@ -441,6 +458,40 @@ describe('LocalAgentExecutor', () => {
// Subagent should be filtered out // Subagent should be filtered out
expect(agentRegistry.getTool(subAgentName)).toBeUndefined(); expect(agentRegistry.getTool(subAgentName)).toBeUndefined();
}); });
it('should default to ALL tools (except subagents) when toolConfig is undefined', async () => {
const subAgentName = 'recursive-agent';
// Register tools in parent registry
// LS_TOOL_NAME is already registered in beforeEach
const otherTool = new MockTool({ name: 'other-tool' });
parentToolRegistry.registerTool(otherTool);
parentToolRegistry.registerTool(new MockTool({ name: subAgentName }));
// Mock the agent registry to return the subagent name
vi.spyOn(
mockConfig.getAgentRegistry(),
'getAllAgentNames',
).mockReturnValue([subAgentName]);
// Create definition and force toolConfig to be undefined
const definition = createTestDefinition();
definition.toolConfig = undefined;
const executor = await LocalAgentExecutor.create(
definition,
mockConfig,
onActivity,
);
const agentRegistry = executor['toolRegistry'];
// Should include standard tools
expect(agentRegistry.getTool(LS_TOOL_NAME)).toBeDefined();
expect(agentRegistry.getTool('other-tool')).toBeDefined();
// Should exclude subagent
expect(agentRegistry.getTool(subAgentName)).toBeUndefined();
});
}); });
describe('run (Execution Loop and Logic)', () => { describe('run (Execution Loop and Logic)', () => {
+19 -10
View File
@@ -114,24 +114,28 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
runtimeContext.getAgentRegistry().getAllAgentNames(), runtimeContext.getAgentRegistry().getAllAgentNames(),
); );
if (definition.toolConfig) { const registerToolByName = (toolName: string) => {
for (const toolRef of definition.toolConfig.tools) {
if (typeof toolRef === 'string') {
// Check if the tool is a subagent to prevent recursion. // Check if the tool is a subagent to prevent recursion.
// We do not allow agents to call other agents. // We do not allow agents to call other agents.
if (allAgentNames.has(toolRef)) { if (allAgentNames.has(toolName)) {
debugLogger.warn( debugLogger.warn(
`[LocalAgentExecutor] Skipping subagent tool '${toolRef}' for agent '${definition.name}' to prevent recursion.`, `[LocalAgentExecutor] Skipping subagent tool '${toolName}' for agent '${definition.name}' to prevent recursion.`,
); );
continue; return;
} }
// If the tool is referenced by name, retrieve it from the parent // If the tool is referenced by name, retrieve it from the parent
// registry and register it with the agent's isolated registry. // registry and register it with the agent's isolated registry.
const toolFromParent = parentToolRegistry.getTool(toolRef); const tool = parentToolRegistry.getTool(toolName);
if (toolFromParent) { if (tool) {
agentToolRegistry.registerTool(toolFromParent); agentToolRegistry.registerTool(tool);
} }
};
if (definition.toolConfig) {
for (const toolRef of definition.toolConfig.tools) {
if (typeof toolRef === 'string') {
registerToolByName(toolRef);
} else if ( } else if (
typeof toolRef === 'object' && typeof toolRef === 'object' &&
'name' in toolRef && 'name' in toolRef &&
@@ -142,9 +146,14 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
// Note: Raw `FunctionDeclaration` objects in the config don't need to be // Note: Raw `FunctionDeclaration` objects in the config don't need to be
// registered; their schemas are passed directly to the model later. // registered; their schemas are passed directly to the model later.
} }
} else {
// If no tools are explicitly configured, default to all available tools.
for (const toolName of parentToolRegistry.getAllToolNames()) {
registerToolByName(toolName);
}
}
agentToolRegistry.sortTools(); agentToolRegistry.sortTools();
}
// Get the parent prompt ID from context // Get the parent prompt ID from context
const parentPromptId = promptIdContext.getStore(); const parentPromptId = promptIdContext.getStore();