mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-29 06:25:16 -07:00
feat(core, ui): Add /agents refresh command. (#16204)
This commit is contained in:
@@ -82,4 +82,40 @@ describe('agentsCommand', () => {
|
|||||||
expect.any(Number),
|
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 { CommandKind } from './types.js';
|
||||||
import { MessageType, type HistoryItemAgentsList } from '../types.js';
|
import { MessageType, type HistoryItemAgentsList } from '../types.js';
|
||||||
|
|
||||||
export const agentsCommand: SlashCommand = {
|
const agentsListCommand: SlashCommand = {
|
||||||
name: 'agents',
|
name: 'list',
|
||||||
description: 'List available local and remote agents',
|
description: 'List available local and remote agents',
|
||||||
kind: CommandKind.BUILT_IN,
|
kind: CommandKind.BUILT_IN,
|
||||||
autoExecute: true,
|
autoExecute: true,
|
||||||
@@ -49,3 +49,38 @@ export const agentsCommand: SlashCommand = {
|
|||||||
return;
|
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",
|
"[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', () => {
|
describe('sendMessage', () => {
|
||||||
|
|||||||
@@ -104,6 +104,15 @@ export class A2AClientManager {
|
|||||||
return agentCard;
|
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.
|
* Sends a message to a loaded agent.
|
||||||
* @param agentName The name of the agent to send the message to.
|
* @param agentName The name of the agent to send the message to.
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ describe('AgentRegistry', () => {
|
|||||||
|
|
||||||
const agentCount = debugRegistry.getAllDefinitions().length;
|
const agentCount = debugRegistry.getAllDefinitions().length;
|
||||||
expect(debugLogSpy).toHaveBeenCalledWith(
|
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', () => {
|
describe('inheritance and refresh', () => {
|
||||||
it('should resolve "inherit" to the current model from configuration', async () => {
|
it('should resolve "inherit" to the current model from configuration', async () => {
|
||||||
const config = makeFakeConfig({ model: 'current-model' });
|
const config = makeFakeConfig({ model: 'current-model' });
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ export class AgentRegistry {
|
|||||||
* Discovers and loads agents.
|
* Discovers and loads agents.
|
||||||
*/
|
*/
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
this.loadBuiltInAgents();
|
|
||||||
|
|
||||||
coreEvents.on(CoreEvent.ModelChanged, () => {
|
coreEvents.on(CoreEvent.ModelChanged, () => {
|
||||||
this.refreshAgents().catch((e) => {
|
this.refreshAgents().catch((e) => {
|
||||||
debugLogger.error(
|
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()) {
|
if (!this.config.isAgentsEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -99,7 +113,7 @@ export class AgentRegistry {
|
|||||||
|
|
||||||
if (this.config.getDebugMode()) {
|
if (this.config.getDebugMode()) {
|
||||||
debugLogger.log(
|
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',
|
SettingsChanged = 'settings-changed',
|
||||||
HookStart = 'hook-start',
|
HookStart = 'hook-start',
|
||||||
HookEnd = 'hook-end',
|
HookEnd = 'hook-end',
|
||||||
|
AgentsRefreshed = 'agents-refreshed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CoreEvents {
|
export interface CoreEvents {
|
||||||
@@ -119,6 +120,7 @@ export interface CoreEvents {
|
|||||||
[CoreEvent.SettingsChanged]: never[];
|
[CoreEvent.SettingsChanged]: never[];
|
||||||
[CoreEvent.HookStart]: [HookStartPayload];
|
[CoreEvent.HookStart]: [HookStartPayload];
|
||||||
[CoreEvent.HookEnd]: [HookEndPayload];
|
[CoreEvent.HookEnd]: [HookEndPayload];
|
||||||
|
[CoreEvent.AgentsRefreshed]: never[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventBacklogItem = {
|
type EventBacklogItem = {
|
||||||
@@ -220,6 +222,13 @@ export class CoreEventEmitter extends EventEmitter<CoreEvents> {
|
|||||||
this.emit(CoreEvent.HookEnd, payload);
|
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
|
* Flushes buffered messages. Call this immediately after primary UI listener
|
||||||
* subscribes.
|
* subscribes.
|
||||||
|
|||||||
Reference in New Issue
Block a user