mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
feat(core, ui): Add /agents refresh command. (#16204)
This commit is contained in:
@@ -82,4 +82,40 @@ describe('agentsCommand', () => {
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reload the agent registry when refresh subcommand is called', async () => {
|
||||
const reloadSpy = vi.fn().mockResolvedValue(undefined);
|
||||
mockConfig.getAgentRegistry = vi.fn().mockReturnValue({
|
||||
reload: reloadSpy,
|
||||
});
|
||||
|
||||
const refreshCommand = agentsCommand.subCommands?.find(
|
||||
(cmd) => cmd.name === 'refresh',
|
||||
);
|
||||
expect(refreshCommand).toBeDefined();
|
||||
|
||||
const result = await refreshCommand!.action!(mockContext, '');
|
||||
|
||||
expect(reloadSpy).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: 'Agents refreshed successfully.',
|
||||
});
|
||||
});
|
||||
|
||||
it('should show an error if agent registry is not available during refresh', async () => {
|
||||
mockConfig.getAgentRegistry = vi.fn().mockReturnValue(undefined);
|
||||
|
||||
const refreshCommand = agentsCommand.subCommands?.find(
|
||||
(cmd) => cmd.name === 'refresh',
|
||||
);
|
||||
const result = await refreshCommand!.action!(mockContext, '');
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Agent registry not found.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,8 +8,8 @@ import type { SlashCommand, CommandContext } from './types.js';
|
||||
import { CommandKind } from './types.js';
|
||||
import { MessageType, type HistoryItemAgentsList } from '../types.js';
|
||||
|
||||
export const agentsCommand: SlashCommand = {
|
||||
name: 'agents',
|
||||
const agentsListCommand: SlashCommand = {
|
||||
name: 'list',
|
||||
description: 'List available local and remote agents',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
autoExecute: true,
|
||||
@@ -49,3 +49,38 @@ export const agentsCommand: SlashCommand = {
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
const agentsRefreshCommand: SlashCommand = {
|
||||
name: 'refresh',
|
||||
description: 'Reload the agent registry',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
action: async (context: CommandContext) => {
|
||||
const { config } = context.services;
|
||||
const agentRegistry = config?.getAgentRegistry();
|
||||
if (!agentRegistry) {
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'error',
|
||||
content: 'Agent registry not found.',
|
||||
};
|
||||
}
|
||||
|
||||
await agentRegistry.reload();
|
||||
|
||||
return {
|
||||
type: 'message',
|
||||
messageType: 'info',
|
||||
content: 'Agents refreshed successfully.',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const agentsCommand: SlashCommand = {
|
||||
name: 'agents',
|
||||
description: 'Manage agents',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
subCommands: [agentsListCommand, agentsRefreshCommand],
|
||||
action: async (context: CommandContext, args) =>
|
||||
// Default to list if no subcommand is provided
|
||||
agentsListCommand.action!(context, args),
|
||||
};
|
||||
|
||||
@@ -162,6 +162,20 @@ describe('A2AClientManager', () => {
|
||||
"[A2AClientManager] Loaded agent 'TestAgent' from http://test.agent/card",
|
||||
);
|
||||
});
|
||||
|
||||
it('should clear the cache', async () => {
|
||||
await manager.loadAgent('TestAgent', 'http://test.agent/card');
|
||||
expect(manager.getAgentCard('TestAgent')).toBeDefined();
|
||||
expect(manager.getClient('TestAgent')).toBeDefined();
|
||||
|
||||
manager.clearCache();
|
||||
|
||||
expect(manager.getAgentCard('TestAgent')).toBeUndefined();
|
||||
expect(manager.getClient('TestAgent')).toBeUndefined();
|
||||
expect(debugLogger.debug).toHaveBeenCalledWith(
|
||||
'[A2AClientManager] Cache cleared.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendMessage', () => {
|
||||
|
||||
@@ -104,6 +104,15 @@ export class A2AClientManager {
|
||||
return agentCard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all cached clients and agent cards.
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.clients.clear();
|
||||
this.agentCards.clear();
|
||||
debugLogger.debug('[A2AClientManager] Cache cleared.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a loaded agent.
|
||||
* @param agentName The name of the agent to send the message to.
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('AgentRegistry', () => {
|
||||
|
||||
const agentCount = debugRegistry.getAllDefinitions().length;
|
||||
expect(debugLogSpy).toHaveBeenCalledWith(
|
||||
`[AgentRegistry] Initialized with ${agentCount} agents.`,
|
||||
`[AgentRegistry] Loaded with ${agentCount} agents.`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -444,6 +444,37 @@ describe('AgentRegistry', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('reload', () => {
|
||||
it('should clear existing agents and reload from directories', async () => {
|
||||
const config = makeFakeConfig({ enableAgents: true });
|
||||
const registry = new TestableAgentRegistry(config);
|
||||
|
||||
const initialAgent = { ...MOCK_AGENT_V1, name: 'InitialAgent' };
|
||||
await registry.testRegisterAgent(initialAgent);
|
||||
expect(registry.getDefinition('InitialAgent')).toBeDefined();
|
||||
|
||||
const newAgent = { ...MOCK_AGENT_V1, name: 'NewAgent' };
|
||||
vi.mocked(tomlLoader.loadAgentsFromDirectory).mockResolvedValue({
|
||||
agents: [newAgent],
|
||||
errors: [],
|
||||
});
|
||||
|
||||
const clearCacheSpy = vi.fn();
|
||||
vi.mocked(A2AClientManager.getInstance).mockReturnValue({
|
||||
clearCache: clearCacheSpy,
|
||||
} as unknown as A2AClientManager);
|
||||
|
||||
const emitSpy = vi.spyOn(coreEvents, 'emitAgentsRefreshed');
|
||||
|
||||
await registry.reload();
|
||||
|
||||
expect(clearCacheSpy).toHaveBeenCalled();
|
||||
expect(registry.getDefinition('InitialAgent')).toBeUndefined();
|
||||
expect(registry.getDefinition('NewAgent')).toBeDefined();
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('inheritance and refresh', () => {
|
||||
it('should resolve "inherit" to the current model from configuration', async () => {
|
||||
const config = makeFakeConfig({ model: 'current-model' });
|
||||
|
||||
@@ -46,8 +46,6 @@ export class AgentRegistry {
|
||||
* Discovers and loads agents.
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
this.loadBuiltInAgents();
|
||||
|
||||
coreEvents.on(CoreEvent.ModelChanged, () => {
|
||||
this.refreshAgents().catch((e) => {
|
||||
debugLogger.error(
|
||||
@@ -57,6 +55,22 @@ export class AgentRegistry {
|
||||
});
|
||||
});
|
||||
|
||||
await this.loadAgents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current registry and re-scans for agents.
|
||||
*/
|
||||
async reload(): Promise<void> {
|
||||
A2AClientManager.getInstance().clearCache();
|
||||
this.agents.clear();
|
||||
await this.loadAgents();
|
||||
coreEvents.emitAgentsRefreshed();
|
||||
}
|
||||
|
||||
private async loadAgents(): Promise<void> {
|
||||
this.loadBuiltInAgents();
|
||||
|
||||
if (!this.config.isAgentsEnabled()) {
|
||||
return;
|
||||
}
|
||||
@@ -99,7 +113,7 @@ export class AgentRegistry {
|
||||
|
||||
if (this.config.getDebugMode()) {
|
||||
debugLogger.log(
|
||||
`[AgentRegistry] Initialized with ${this.agents.size} agents.`,
|
||||
`[AgentRegistry] Loaded with ${this.agents.size} agents.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ export enum CoreEvent {
|
||||
SettingsChanged = 'settings-changed',
|
||||
HookStart = 'hook-start',
|
||||
HookEnd = 'hook-end',
|
||||
AgentsRefreshed = 'agents-refreshed',
|
||||
}
|
||||
|
||||
export interface CoreEvents {
|
||||
@@ -119,6 +120,7 @@ export interface CoreEvents {
|
||||
[CoreEvent.SettingsChanged]: never[];
|
||||
[CoreEvent.HookStart]: [HookStartPayload];
|
||||
[CoreEvent.HookEnd]: [HookEndPayload];
|
||||
[CoreEvent.AgentsRefreshed]: never[];
|
||||
}
|
||||
|
||||
type EventBacklogItem = {
|
||||
@@ -220,6 +222,13 @@ export class CoreEventEmitter extends EventEmitter<CoreEvents> {
|
||||
this.emit(CoreEvent.HookEnd, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies subscribers that agents have been refreshed.
|
||||
*/
|
||||
emitAgentsRefreshed(): void {
|
||||
this.emit(CoreEvent.AgentsRefreshed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes buffered messages. Call this immediately after primary UI listener
|
||||
* subscribes.
|
||||
|
||||
Reference in New Issue
Block a user