diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 3381c475cf..ebb56eb94d 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -941,10 +941,6 @@ export class Config implements McpContext, AgentLoopContext { private readonly adminSkillsEnabled: boolean; private readonly experimentalJitContext: boolean; private readonly experimentalMemoryManager: boolean; - private readonly experimentalAgentHistoryTruncation: boolean; - private readonly experimentalAgentHistoryTruncationThreshold: number; - private readonly experimentalAgentHistoryRetainedMessages: number; - private readonly experimentalAgentHistorySummarization: boolean; private readonly experimentalWatcher: boolean; private readonly experimentalWatcherInterval: number; private readonly memoryBoundaryMarkers: readonly string[]; @@ -1158,14 +1154,6 @@ export class Config implements McpContext, AgentLoopContext { this.experimentalJitContext = params.experimentalJitContext ?? false; this.experimentalMemoryManager = params.experimentalMemoryManager ?? false; - this.experimentalAgentHistoryTruncation = - params.experimentalAgentHistoryTruncation ?? false; - this.experimentalAgentHistoryTruncationThreshold = - params.experimentalAgentHistoryTruncationThreshold ?? 30; - this.experimentalAgentHistoryRetainedMessages = - params.experimentalAgentHistoryRetainedMessages ?? 15; - this.experimentalAgentHistorySummarization = - params.experimentalAgentHistorySummarization ?? false; this.experimentalWatcher = params.experimentalWatcher ?? false; this.experimentalWatcherInterval = params.experimentalWatcherInterval ?? 20; this.memoryBoundaryMarkers = params.memoryBoundaryMarkers ?? ['.git']; @@ -2452,6 +2440,14 @@ export class Config implements McpContext, AgentLoopContext { return this.experimentalMemoryManager; } + isExperimentalWatcherEnabled(): boolean { + return this.experimentalWatcher; + } + + getExperimentalWatcherInterval(): number { + return this.experimentalWatcherInterval; + } + getContextManagementConfig(): ContextManagementConfig { return this.contextManagement; } @@ -2468,30 +2464,6 @@ export class Config implements McpContext, AgentLoopContext { }; } - isExperimentalAgentHistoryTruncationEnabled(): boolean { - return this.experimentalAgentHistoryTruncation; - } - - getExperimentalAgentHistoryTruncationThreshold(): number { - return this.experimentalAgentHistoryTruncationThreshold; - } - - getExperimentalAgentHistoryRetainedMessages(): number { - return this.experimentalAgentHistoryRetainedMessages; - } - - isExperimentalAgentHistorySummarizationEnabled(): boolean { - return this.experimentalAgentHistorySummarization; - } - - isExperimentalWatcherEnabled(): boolean { - return this.experimentalWatcher; - } - - getExperimentalWatcherInterval(): number { - return this.experimentalWatcherInterval; - } - isTopicUpdateNarrationEnabled(): boolean { return this.topicUpdateNarration; } diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index d51c036cee..b57d028a0d 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -47,7 +47,7 @@ import type { ContentGenerator } from './contentGenerator.js'; import { LoopDetectionService } from '../services/loopDetectionService.js'; import { ChatCompressionService } from '../context/chatCompressionService.js'; import { AgentHistoryProvider } from '../context/agentHistoryProvider.js'; -import type { WatcherProgress } from '../agents/types.js'; +import { isSubagentProgress, type WatcherProgress } from '../agents/types.js'; import { WatcherReportSchema } from '../agents/watcher-agent.js'; import { ideContextStore } from '../ide/ideContext.js'; import { @@ -1390,10 +1390,13 @@ export class GeminiClient { const invocation = watcherTool.build({ recentHistory }); const result = await invocation.execute(signal); - if (result.llmContent) { + if ( + isSubagentProgress(result.returnDisplay) && + result.returnDisplay.result + ) { try { - const contentString = partListUnionToString(result.llmContent); - const parsed = WatcherReportSchema.parse(JSON.parse(contentString)); + const rawOutput = result.returnDisplay.result; + const parsed = WatcherReportSchema.parse(JSON.parse(rawOutput)); // Internally write the status report to avoid requiring user permission const projectTempDir = this.config.storage.getProjectTempDir(); diff --git a/packages/core/src/core/client_watcher.test.ts b/packages/core/src/core/client_watcher.test.ts index c6f4075591..edc54d5b0e 100644 --- a/packages/core/src/core/client_watcher.test.ts +++ b/packages/core/src/core/client_watcher.test.ts @@ -11,7 +11,6 @@ import { GeminiClient } from './client.js'; import { type AgentLoopContext } from '../config/agent-loop-context.js'; import { makeFakeConfig } from '../test-utils/config.js'; import { ApprovalMode } from '../policy/types.js'; -import type { WatcherProgress } from '../agents/types.js'; import type { Config } from '../config/config.js'; describe('GeminiClient Watcher Integration', () => { @@ -57,25 +56,17 @@ describe('GeminiClient Watcher Integration', () => { } }); - it('should trigger watcher periodically when enabled', async () => { - vi.spyOn(config, 'isExperimentalWatcherEnabled').mockReturnValue(true); - vi.spyOn(config, 'getExperimentalWatcherInterval').mockReturnValue(2); - vi.spyOn(config, 'getApprovalMode').mockReturnValue(ApprovalMode.DEFAULT); - - // Mock toolRegistry before initialize calls startChat - const mockWatcherTool = { + const createMockWatcherTool = (resultData: unknown) => ({ build: vi.fn().mockReturnValue({ execute: vi.fn().mockResolvedValue({ - llmContent: [ - { - text: JSON.stringify({ - userDirections: 'Keep testing', - progressSummary: 'Test in progress', - evaluation: 'Good', - feedback: 'Keep going', - } as WatcherProgress), - }, - ], + llmContent: [{ text: 'Subagent finished' }], + returnDisplay: { + isSubagentProgress: true, + agentName: 'watcher', + recentActivity: [], + state: 'completed', + result: resultData ? JSON.stringify(resultData) : undefined, + }, }), }), name: 'watcher', @@ -88,7 +79,19 @@ describe('GeminiClient Watcher Integration', () => { outputName: 'report', schema: {}, }, - }; + }); + + it('should trigger watcher periodically when enabled', async () => { + vi.spyOn(config, 'isExperimentalWatcherEnabled').mockReturnValue(true); + vi.spyOn(config, 'getExperimentalWatcherInterval').mockReturnValue(2); + vi.spyOn(config, 'getApprovalMode').mockReturnValue(ApprovalMode.DEFAULT); + + const mockWatcherTool = createMockWatcherTool({ + userDirections: 'Keep testing', + progressSummary: 'Test in progress', + evaluation: 'Good', + feedback: 'Keep going', + }); const mockToolRegistry = { getFunctionDeclarations: vi.fn().mockReturnValue([]), @@ -101,7 +104,6 @@ describe('GeminiClient Watcher Integration', () => { discoverAllTools: vi.fn(), }; - // Use type assertion for testing purposes to access protected members const clientAccess = client as unknown as { context: AgentLoopContext; }; @@ -130,7 +132,7 @@ describe('GeminiClient Watcher Integration', () => { promptId, ); for await (const _ of generator) { - // Intentionally consume + // consume } expect(mockWatcherTool.build).toHaveBeenCalled(); @@ -141,7 +143,6 @@ describe('GeminiClient Watcher Integration', () => { vi.spyOn(config, 'getExperimentalWatcherInterval').mockReturnValue(2); vi.spyOn(config, 'getApprovalMode').mockReturnValue(ApprovalMode.DEFAULT); - // Mock toolRegistry before initialize calls startChat const mockWatcherTool = { build: vi.fn(), name: 'watcher', @@ -158,7 +159,6 @@ describe('GeminiClient Watcher Integration', () => { discoverAllTools: vi.fn(), }; - // Use type assertion for testing purposes to access protected members const clientAccess = client as unknown as { context: AgentLoopContext; }; @@ -187,7 +187,7 @@ describe('GeminiClient Watcher Integration', () => { promptId, ); for await (const _ of generator) { - // Intentionally consume + // consume } expect(mockWatcherTool.build).not.toHaveBeenCalled(); @@ -201,35 +201,12 @@ describe('GeminiClient Watcher Integration', () => { ); vi.spyOn(config, 'getApprovalMode').mockReturnValue(ApprovalMode.DEFAULT); - const mockWatcherTool = { - build: vi.fn().mockReturnValue({ - execute: vi.fn().mockResolvedValue({ - llmContent: [ - { - text: JSON.stringify({ - userDirections: 'Keep testing', - progressSummary: 'Test in progress', - evaluation: 'Good', - feedback: 'Keep going', - } as WatcherProgress), - }, - ], - }), - }), - name: 'watcher', - displayName: 'Watcher', - description: 'Watcher tool', - inputConfig: { - inputName: 'history', - description: 'history', - schema: {}, - }, - outputConfig: { - outputName: 'report', - description: 'report', - schema: {}, - }, - }; + const mockWatcherTool = createMockWatcherTool({ + userDirections: 'Keep testing', + progressSummary: 'Test in progress', + evaluation: 'Good', + feedback: 'Keep going', + }); const mockToolRegistry = { getFunctionDeclarations: vi.fn().mockReturnValue([]), @@ -242,7 +219,6 @@ describe('GeminiClient Watcher Integration', () => { discoverAllTools: vi.fn(), }; - // Use type assertion for testing purposes to access protected members const clientAccess = client as unknown as { context: AgentLoopContext; };