diff --git a/packages/cli/src/acp/acpClient.test.ts b/packages/cli/src/acp/acpClient.test.ts index 470ff38351..10c90824f9 100644 --- a/packages/cli/src/acp/acpClient.test.ts +++ b/packages/cli/src/acp/acpClient.test.ts @@ -41,6 +41,8 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import { ApprovalMode } from '@google/gemini-cli-core/src/policy/types.js'; +const startMemoryServiceMock = vi.hoisted(() => vi.fn()); + vi.mock('../config/config.js', () => ({ loadCliConfig: vi.fn(), })); @@ -101,6 +103,7 @@ vi.mock( const actual = await importOriginal(); return { ...actual, + startMemoryService: startMemoryServiceMock, updatePolicy: vi.fn(), createPolicyUpdater: vi.fn(), ReadManyFilesTool: vi.fn(), @@ -148,6 +151,8 @@ describe('GeminiAgent', () => { let agent: GeminiAgent; beforeEach(() => { + vi.clearAllMocks(); + startMemoryServiceMock.mockResolvedValue(undefined); mockConfig = { refreshAuth: vi.fn(), initialize: vi.fn(), @@ -155,6 +160,7 @@ describe('GeminiAgent', () => { getFileSystemService: vi.fn(), setFileSystemService: vi.fn(), getContentGeneratorConfig: vi.fn(), + isAutoMemoryEnabled: vi.fn().mockReturnValue(false), getActiveModel: vi.fn().mockReturnValue('gemini-pro'), getModel: vi.fn().mockReturnValue('gemini-pro'), getGeminiClient: vi.fn().mockReturnValue({ @@ -354,6 +360,34 @@ describe('GeminiAgent', () => { vi.useRealTimers(); }); + it('should start auto memory for new ACP sessions when enabled', async () => { + mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({ + apiKey: 'test-key', + }); + mockConfig.isAutoMemoryEnabled = vi.fn().mockReturnValue(true); + + await agent.newSession({ + cwd: '/tmp', + mcpServers: [], + }); + + expect(startMemoryServiceMock).toHaveBeenCalledWith(mockConfig); + }); + + it('should not start auto memory for new ACP sessions when disabled', async () => { + mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({ + apiKey: 'test-key', + }); + mockConfig.isAutoMemoryEnabled = vi.fn().mockReturnValue(false); + + await agent.newSession({ + cwd: '/tmp', + mcpServers: [], + }); + + expect(startMemoryServiceMock).not.toHaveBeenCalled(); + }); + it('should return modes without plan mode when plan is disabled', async () => { mockConfig.getContentGeneratorConfig = vi.fn().mockReturnValue({ apiKey: 'test-key', diff --git a/packages/cli/src/acp/acpClient.ts b/packages/cli/src/acp/acpClient.ts index ed83417d56..57c7790b05 100644 --- a/packages/cli/src/acp/acpClient.ts +++ b/packages/cli/src/acp/acpClient.ts @@ -76,6 +76,7 @@ import { randomUUID } from 'node:crypto'; import { loadCliConfig, type CliArgs } from '../config/config.js'; import { runExitCleanup } from '../utils/cleanup.js'; import { SessionSelector } from '../utils/sessionUtils.js'; +import { startAutoMemoryIfEnabled } from '../utils/autoMemory.js'; import { CommandHandler } from './commandHandler.js'; @@ -324,6 +325,7 @@ export class GeminiAgent { await config.initialize(); startupProfiler.flush(config); + startAutoMemoryIfEnabled(config); const geminiClient = config.getGeminiClient(); const chat = await geminiClient.startChat(); @@ -465,6 +467,7 @@ export class GeminiAgent { // which starts the MCP servers and other heavy resources. await config.initialize(); startupProfiler.flush(config); + startAutoMemoryIfEnabled(config); return config; } diff --git a/packages/cli/src/acp/acpResume.test.ts b/packages/cli/src/acp/acpResume.test.ts index 3f75119d0b..6a92d68814 100644 --- a/packages/cli/src/acp/acpResume.test.ts +++ b/packages/cli/src/acp/acpResume.test.ts @@ -100,6 +100,7 @@ describe('GeminiAgent Session Resume', () => { unsubscribe: vi.fn(), }, getApprovalMode: vi.fn().mockReturnValue('default'), + isAutoMemoryEnabled: vi.fn().mockReturnValue(false), isPlanEnabled: vi.fn().mockReturnValue(true), getModel: vi.fn().mockReturnValue('gemini-pro'), getHasAccessToPreviewModel: vi.fn().mockReturnValue(false), diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index f9906f6fb5..fdbaf57fbe 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -92,7 +92,6 @@ import { ApiKeyUpdatedEvent, LegacyAgentProtocol, type InjectionSource, - startMemoryService, } from '@google/gemini-cli-core'; import { validateAuthMethod } from '../config/auth.js'; import process from 'node:process'; @@ -125,6 +124,7 @@ import { type BackgroundTask } from './hooks/useExecutionLifecycle.js'; import { useVim } from './hooks/vim.js'; import { type LoadableSettingScope, SettingScope } from '../config/settings.js'; import { type InitializationResult } from '../core/initializer.js'; +import { startAutoMemoryIfEnabled } from '../utils/autoMemory.js'; import { useFocus } from './hooks/useFocus.js'; import { useKeypress, type Key } from './hooks/useKeypress.js'; import { KeypressPriority } from './contexts/KeypressContext.js'; @@ -486,12 +486,7 @@ export const AppContainer = (props: AppContainerProps) => { setConfigInitialized(true); startupProfiler.flush(config); - // Fire-and-forget Auto Memory service (skill extraction from past sessions) - if (config.isAutoMemoryEnabled()) { - startMemoryService(config).catch((e) => { - debugLogger.error('Failed to start memory service:', e); - }); - } + startAutoMemoryIfEnabled(config); const sessionStartSource = resumedSessionData ? SessionStartSource.Resume diff --git a/packages/cli/src/utils/autoMemory.ts b/packages/cli/src/utils/autoMemory.ts new file mode 100644 index 0000000000..9d4a04f632 --- /dev/null +++ b/packages/cli/src/utils/autoMemory.ts @@ -0,0 +1,21 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + debugLogger, + startMemoryService, + type Config, +} from '@google/gemini-cli-core'; + +export function startAutoMemoryIfEnabled(config: Config): void { + if (!config.isAutoMemoryEnabled()) { + return; + } + + startMemoryService(config).catch((e) => { + debugLogger.error('Failed to start memory service:', e); + }); +}