From 77aef861fef4b5443b026196a947ebb292045614 Mon Sep 17 00:00:00 2001 From: Christian Gunderman Date: Sat, 24 Jan 2026 01:30:18 +0000 Subject: [PATCH] fix(agents): default to all tools when tool list is omitted in subagents (#17422) --- .../core/src/agents/local-executor.test.ts | 51 +++++++++++++++++++ packages/core/src/agents/local-executor.ts | 43 +++++++++------- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/packages/core/src/agents/local-executor.test.ts b/packages/core/src/agents/local-executor.test.ts index 2cd04a4a6e..b9e6488c1e 100644 --- a/packages/core/src/agents/local-executor.test.ts +++ b/packages/core/src/agents/local-executor.test.ts @@ -117,6 +117,23 @@ vi.mock('../telemetry/loggers.js', () => ({ 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) => { const actual = await importOriginal(); @@ -441,6 +458,40 @@ describe('LocalAgentExecutor', () => { // Subagent should be filtered out 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)', () => { diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts index 506f333684..a75a92a4ec 100644 --- a/packages/core/src/agents/local-executor.ts +++ b/packages/core/src/agents/local-executor.ts @@ -114,24 +114,28 @@ export class LocalAgentExecutor { runtimeContext.getAgentRegistry().getAllAgentNames(), ); + const registerToolByName = (toolName: string) => { + // Check if the tool is a subagent to prevent recursion. + // We do not allow agents to call other agents. + if (allAgentNames.has(toolName)) { + debugLogger.warn( + `[LocalAgentExecutor] Skipping subagent tool '${toolName}' for agent '${definition.name}' to prevent recursion.`, + ); + return; + } + + // If the tool is referenced by name, retrieve it from the parent + // registry and register it with the agent's isolated registry. + const tool = parentToolRegistry.getTool(toolName); + if (tool) { + agentToolRegistry.registerTool(tool); + } + }; + if (definition.toolConfig) { for (const toolRef of definition.toolConfig.tools) { if (typeof toolRef === 'string') { - // Check if the tool is a subagent to prevent recursion. - // We do not allow agents to call other agents. - if (allAgentNames.has(toolRef)) { - debugLogger.warn( - `[LocalAgentExecutor] Skipping subagent tool '${toolRef}' for agent '${definition.name}' to prevent recursion.`, - ); - continue; - } - - // If the tool is referenced by name, retrieve it from the parent - // registry and register it with the agent's isolated registry. - const toolFromParent = parentToolRegistry.getTool(toolRef); - if (toolFromParent) { - agentToolRegistry.registerTool(toolFromParent); - } + registerToolByName(toolRef); } else if ( typeof toolRef === 'object' && 'name' in toolRef && @@ -142,10 +146,15 @@ export class LocalAgentExecutor { // Note: Raw `FunctionDeclaration` objects in the config don't need to be // registered; their schemas are passed directly to the model later. } - - agentToolRegistry.sortTools(); + } else { + // If no tools are explicitly configured, default to all available tools. + for (const toolName of parentToolRegistry.getAllToolNames()) { + registerToolByName(toolName); + } } + agentToolRegistry.sortTools(); + // Get the parent prompt ID from context const parentPromptId = promptIdContext.getStore();