diff --git a/packages/core/src/agents/generalist-agent.ts b/packages/core/src/agents/generalist-agent.ts index 6e2cd90c48..26eb2aa8d5 100644 --- a/packages/core/src/agents/generalist-agent.ts +++ b/packages/core/src/agents/generalist-agent.ts @@ -55,8 +55,9 @@ export const GeneralistAgent = ( return { systemPrompt: getCoreSystemPrompt( context.config, - /*useMemory=*/ undefined, + /*userMemory=*/ undefined, /*interactiveOverride=*/ false, + /*topicUpdateNarrationOverride=*/ false, ), query: '${request}', }; diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts index e7d8078579..478521cd9e 100644 --- a/packages/core/src/agents/local-executor.ts +++ b/packages/core/src/agents/local-executor.ts @@ -82,6 +82,7 @@ import { CompleteTaskTool } from '../tools/complete-task.js'; import { COMPLETE_TASK_TOOL_NAME, ACTIVATE_SKILL_TOOL_NAME, + UPDATE_TOPIC_TOOL_NAME, } from '../tools/definitions/base-declarations.js'; /** A callback function to report on agent activity. */ @@ -189,6 +190,10 @@ export class LocalAgentExecutor { return; } + if (tool.name === UPDATE_TOPIC_TOOL_NAME) { + return; + } + // Clone the tool, so it gets its own state and subagent messageBus const clonedTool = tool.clone(subagentMessageBus); agentToolRegistry.registerTool(clonedTool); diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index b85c29494d..48e70e4cf4 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -24,11 +24,13 @@ export function getCoreSystemPrompt( config: Config, userMemory?: string | HierarchicalMemory, interactiveOverride?: boolean, + topicUpdateNarrationOverride?: boolean, ): string { return new PromptProvider().getCoreSystemPrompt( config, userMemory, interactiveOverride, + topicUpdateNarrationOverride, ); } diff --git a/packages/core/src/prompts/promptProvider.test.ts b/packages/core/src/prompts/promptProvider.test.ts index 27a0e7844d..17845200ae 100644 --- a/packages/core/src/prompts/promptProvider.test.ts +++ b/packages/core/src/prompts/promptProvider.test.ts @@ -277,6 +277,56 @@ describe('PromptProvider', () => { }); }); + describe('topicUpdateNarrationOverride', () => { + let provider: PromptProvider; + + beforeEach(() => { + provider = new PromptProvider(); + mockConfig.topicState.reset(); + (mockConfig.getToolRegistry as ReturnType).mockReturnValue({ + getAllToolNames: vi.fn().mockReturnValue([UPDATE_TOPIC_TOOL_NAME]), + }); + (mockConfig.getAgentRegistry as ReturnType).mockReturnValue( + { + getAllDefinitions: vi.fn().mockReturnValue([]), + getDefinition: vi.fn().mockReturnValue(undefined), + }, + ); + }); + + it('should disable topic update narration when override is false, even if config is true', () => { + vi.mocked(mockConfig.isTopicUpdateNarrationEnabled).mockReturnValue(true); + + const prompt = provider.getCoreSystemPrompt( + mockConfig as unknown as Config, + /*userMemory=*/ undefined, + /*interactiveOverride=*/ undefined, + /*topicUpdateNarrationOverride=*/ false, + ); + + expect(prompt).not.toContain( + `As you work, the user follows along by reading topic updates that you publish with ${UPDATE_TOPIC_TOOL_NAME}.`, + ); + }); + + it('should enable topic update narration when override is true, even if config is false', () => { + vi.mocked(mockConfig.isTopicUpdateNarrationEnabled).mockReturnValue( + false, + ); + + const prompt = provider.getCoreSystemPrompt( + mockConfig as unknown as Config, + /*userMemory=*/ undefined, + /*interactiveOverride=*/ undefined, + /*topicUpdateNarrationOverride=*/ true, + ); + + expect(prompt).toContain( + `As you work, the user follows along by reading topic updates that you publish with ${UPDATE_TOPIC_TOOL_NAME}.`, + ); + }); + }); + describe('Topic & Update Narration', () => { beforeEach(() => { mockConfig.topicState.reset(); diff --git a/packages/core/src/prompts/promptProvider.ts b/packages/core/src/prompts/promptProvider.ts index 36d08c7e74..335f6cf784 100644 --- a/packages/core/src/prompts/promptProvider.ts +++ b/packages/core/src/prompts/promptProvider.ts @@ -44,6 +44,7 @@ export class PromptProvider { context: AgentLoopContext, userMemory?: string | HierarchicalMemory, interactiveOverride?: boolean, + topicUpdateNarrationOverride?: boolean, ): string { const systemMdResolution = resolvePathFromEnv( process.env['GEMINI_SYSTEM_MD'], @@ -57,6 +58,10 @@ export class PromptProvider { const isYoloMode = approvalMode === ApprovalMode.YOLO; const skills = context.config.getSkillManager().getSkills(); const toolNames = context.toolRegistry.getAllToolNames(); + const isTopicUpdateNarrationEnabled = + topicUpdateNarrationOverride ?? + context.config.isTopicUpdateNarrationEnabled(); + const enabledToolNames = new Set(toolNames); const approvedPlanPath = context.config.getApprovedPlanPath(); @@ -139,7 +144,7 @@ export class PromptProvider { hasSkills: skills.length > 0, hasHierarchicalMemory, contextFilenames, - topicUpdateNarration: context.config.isTopicUpdateNarrationEnabled(), + topicUpdateNarration: isTopicUpdateNarrationEnabled, })), subAgents: this.withSection( 'agentContexts', @@ -184,8 +189,7 @@ export class PromptProvider { ? { path: approvedPlanPath } : undefined, taskTracker: trackerDir, - topicUpdateNarration: - context.config.isTopicUpdateNarrationEnabled(), + topicUpdateNarration: isTopicUpdateNarrationEnabled, }; }, !isPlanMode, @@ -207,8 +211,7 @@ export class PromptProvider { enableShellEfficiency: context.config.getEnableShellOutputEfficiency(), interactiveShellEnabled: context.config.isInteractiveShellEnabled(), - topicUpdateNarration: - context.config.isTopicUpdateNarrationEnabled(), + topicUpdateNarration: isTopicUpdateNarrationEnabled, memoryManagerEnabled: context.config.isMemoryManagerEnabled(), }), ), @@ -251,7 +254,7 @@ export class PromptProvider { let sanitizedPrompt = finalPrompt.replace(/\n{3,}/g, '\n\n'); // Context Reinjection (Active Topic) - if (context.config.isTopicUpdateNarrationEnabled()) { + if (isTopicUpdateNarrationEnabled) { const activeTopic = context.config.topicState.getTopic(); if (activeTopic) { const sanitizedTopic = activeTopic