mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
fix(core): ensure sub-agent schema and prompt refresh during runtime (#16409)
Co-authored-by: Sehoon Shon <sshon@google.com>
This commit is contained in:
@@ -65,6 +65,14 @@ export async function runExitCleanup() {
|
||||
}
|
||||
cleanupFunctions.length = 0; // Clear the array
|
||||
|
||||
if (configForTelemetry) {
|
||||
try {
|
||||
await configForTelemetry.dispose();
|
||||
} catch (_) {
|
||||
// Ignore errors during disposal
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: Shutdown telemetry AFTER all other cleanup functions have run
|
||||
// This ensures SessionEnd hooks and other telemetry are properly flushed
|
||||
if (configForTelemetry && isTelemetrySdkInitialized()) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import type { Config } from '../config/config.js';
|
||||
describe('CliHelpAgent', () => {
|
||||
const fakeConfig = {
|
||||
getMessageBus: () => ({}),
|
||||
isAgentsEnabled: () => false,
|
||||
} as unknown as Config;
|
||||
const localAgent = CliHelpAgent(fakeConfig) as LocalAgentDefinition;
|
||||
|
||||
@@ -52,6 +53,22 @@ describe('CliHelpAgent', () => {
|
||||
expect(query).toContain('${question}');
|
||||
});
|
||||
|
||||
it('should include sub-agent information when agents are enabled', () => {
|
||||
const enabledConfig = {
|
||||
getMessageBus: () => ({}),
|
||||
isAgentsEnabled: () => true,
|
||||
getAgentRegistry: () => ({
|
||||
getDirectoryContext: () => 'Mock Agent Directory',
|
||||
}),
|
||||
} as unknown as Config;
|
||||
const agent = CliHelpAgent(enabledConfig) as LocalAgentDefinition;
|
||||
const systemPrompt = agent.promptConfig.systemPrompt || '';
|
||||
|
||||
expect(systemPrompt).toContain('### Sub-Agents (Local & Remote)');
|
||||
expect(systemPrompt).toContain('Remote Agent (A2A)');
|
||||
expect(systemPrompt).toContain('Agent2Agent functionality');
|
||||
});
|
||||
|
||||
it('should process output to a formatted JSON string', () => {
|
||||
const mockOutput = {
|
||||
answer: 'This is the answer.',
|
||||
|
||||
@@ -76,6 +76,12 @@ export const CliHelpAgent = (
|
||||
'- **CLI Version:** ${cliVersion}\n' +
|
||||
'- **Active Model:** ${activeModel}\n' +
|
||||
"- **Today's Date:** ${today}\n\n" +
|
||||
(config.isAgentsEnabled()
|
||||
? '### Sub-Agents (Local & Remote)\n' +
|
||||
'User defined sub-agents are defined in `.gemini/agents/` or `~/.gemini/agents/` using YAML frontmatter for metadata and Markdown for instructions (system_prompt). Always reference the types and properties outlined here directly when answering questions about sub-agents.\n' +
|
||||
'- **Local Agent:** `kind = "local"`, `name`, `description`, `prompts.system_prompt`, and optional `tools`, `model`, `run`.\n' +
|
||||
'- **Remote Agent (A2A):** `kind = "remote"`, `name`, `agent_card_url`. Multiple remotes can be defined using a `remote_agents` array. **Note:** When users ask about "remote agents", they are referring to this Agent2Agent functionality, which is completely distinct from MCP servers.\n\n'
|
||||
: '') +
|
||||
'### Instructions\n' +
|
||||
"1. **Explore Documentation**: Use the `get_internal_docs` tool to find answers. If you don't know where to start, call `get_internal_docs()` without arguments to see the full list of available documentation files.\n" +
|
||||
'2. **Be Precise**: Use the provided runtime context and documentation to give exact answers.\n' +
|
||||
|
||||
@@ -46,18 +46,20 @@ export class AgentRegistry {
|
||||
* Discovers and loads agents.
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
coreEvents.on(CoreEvent.ModelChanged, () => {
|
||||
this.refreshAgents().catch((e) => {
|
||||
debugLogger.error(
|
||||
'[AgentRegistry] Failed to refresh agents on model change:',
|
||||
e,
|
||||
);
|
||||
});
|
||||
});
|
||||
coreEvents.on(CoreEvent.ModelChanged, this.onModelChanged);
|
||||
|
||||
await this.loadAgents();
|
||||
}
|
||||
|
||||
private onModelChanged = () => {
|
||||
this.refreshAgents().catch((e) => {
|
||||
debugLogger.error(
|
||||
'[AgentRegistry] Failed to refresh agents on model change:',
|
||||
e,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the current registry and re-scans for agents.
|
||||
*/
|
||||
@@ -68,6 +70,13 @@ export class AgentRegistry {
|
||||
coreEvents.emitAgentsRefreshed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of resources and removes event listeners.
|
||||
*/
|
||||
dispose(): void {
|
||||
coreEvents.off(CoreEvent.ModelChanged, this.onModelChanged);
|
||||
}
|
||||
|
||||
private async loadAgents(): Promise<void> {
|
||||
this.loadBuiltInAgents();
|
||||
|
||||
|
||||
@@ -167,13 +167,18 @@ const mockCoreEvents = vi.hoisted(() => ({
|
||||
emitFeedback: vi.fn(),
|
||||
emitModelChanged: vi.fn(),
|
||||
emitConsoleLog: vi.fn(),
|
||||
on: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockSetGlobalProxy = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('../utils/events.js', () => ({
|
||||
coreEvents: mockCoreEvents,
|
||||
}));
|
||||
vi.mock('../utils/events.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('../utils/events.js')>();
|
||||
return {
|
||||
...actual,
|
||||
coreEvents: mockCoreEvents,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('../utils/fetch.js', () => ({
|
||||
setGlobalProxy: mockSetGlobalProxy,
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
DEFAULT_OTLP_ENDPOINT,
|
||||
uiTelemetryService,
|
||||
} from '../telemetry/index.js';
|
||||
import { coreEvents } from '../utils/events.js';
|
||||
import { coreEvents, CoreEvent } from '../utils/events.js';
|
||||
import { tokenLimit } from '../core/tokenLimits.js';
|
||||
import {
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
@@ -735,6 +735,8 @@ export class Config {
|
||||
this.agentRegistry = new AgentRegistry(this);
|
||||
await this.agentRegistry.initialize();
|
||||
|
||||
coreEvents.on(CoreEvent.AgentsRefreshed, this.onAgentsRefreshed);
|
||||
|
||||
this.toolRegistry = await this.createToolRegistry();
|
||||
discoverToolsHandle?.end();
|
||||
this.mcpClientManager = new McpClientManager(
|
||||
@@ -1764,6 +1766,17 @@ export class Config {
|
||||
|
||||
// Register Subagents as Tools
|
||||
// Register DelegateToAgentTool if agents are enabled
|
||||
this.registerDelegateToAgentTool(registry);
|
||||
|
||||
await registry.discoverAllTools();
|
||||
registry.sortTools();
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the DelegateToAgentTool if agents or related features are enabled.
|
||||
*/
|
||||
private registerDelegateToAgentTool(registry: ToolRegistry): void {
|
||||
if (
|
||||
this.isAgentsEnabled() ||
|
||||
this.getCodebaseInvestigatorSettings().enabled ||
|
||||
@@ -1783,10 +1796,6 @@ export class Config {
|
||||
registry.registerTool(delegateTool);
|
||||
}
|
||||
}
|
||||
|
||||
await registry.discoverAllTools();
|
||||
registry.sortTools();
|
||||
return registry;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1870,6 +1879,35 @@ export class Config {
|
||||
});
|
||||
debugLogger.debug('Experiments loaded', summaryString);
|
||||
}
|
||||
|
||||
private onAgentsRefreshed = async () => {
|
||||
if (this.toolRegistry) {
|
||||
this.registerDelegateToAgentTool(this.toolRegistry);
|
||||
}
|
||||
// Propagate updates to the active chat session
|
||||
const client = this.getGeminiClient();
|
||||
if (client?.isInitialized()) {
|
||||
await client.setTools();
|
||||
await client.updateSystemInstruction();
|
||||
} else {
|
||||
debugLogger.debug(
|
||||
'[Config] GeminiClient not initialized; skipping live prompt/tool refresh.',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disposes of resources and removes event listeners.
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
coreEvents.off(CoreEvent.AgentsRefreshed, this.onAgentsRefreshed);
|
||||
if (this.agentRegistry) {
|
||||
this.agentRegistry.dispose();
|
||||
}
|
||||
if (this.mcpClientManager) {
|
||||
await this.mcpClientManager.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Export model constants for use in CLI
|
||||
export { DEFAULT_GEMINI_FLASH_MODEL };
|
||||
|
||||
@@ -66,6 +66,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||
},
|
||||
isInteractive: vi.fn().mockReturnValue(true),
|
||||
isInteractiveShellEnabled: vi.fn().mockReturnValue(true),
|
||||
isAgentsEnabled: vi.fn().mockReturnValue(false),
|
||||
getModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL_AUTO),
|
||||
getActiveModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL),
|
||||
getPreviewFeatures: vi.fn().mockReturnValue(false),
|
||||
@@ -214,6 +215,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||
},
|
||||
isInteractive: vi.fn().mockReturnValue(false),
|
||||
isInteractiveShellEnabled: vi.fn().mockReturnValue(false),
|
||||
isAgentsEnabled: vi.fn().mockReturnValue(false),
|
||||
getModel: vi.fn().mockReturnValue('auto'),
|
||||
getActiveModel: vi.fn().mockReturnValue(DEFAULT_GEMINI_MODEL),
|
||||
getPreviewFeatures: vi.fn().mockReturnValue(false),
|
||||
|
||||
Reference in New Issue
Block a user