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

View File

@@ -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<typeof import('../utils/promptIdContext.js')>();
@@ -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)', () => {

View File

@@ -114,24 +114,28 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
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<TOutput extends z.ZodTypeAny> {
// 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();