Reload gemini memory on extension load/unload + memory refresh refactor (#12651)

This commit is contained in:
Jacob MacDonald
2025-11-07 09:07:25 -08:00
committed by GitHub
parent ef4030331a
commit 47603ef8e1
15 changed files with 276 additions and 259 deletions

View File

@@ -48,10 +48,11 @@ import {
debugLogger,
coreEvents,
CoreEvent,
refreshServerHierarchicalMemory,
type ModelChangedPayload,
type MemoryChangedPayload,
} from '@google/gemini-cli-core';
import { validateAuthMethod } from '../config/auth.js';
import { loadHierarchicalGeminiMemory } from '../config/config.js';
import process from 'node:process';
import { useHistory } from './hooks/useHistoryManager.js';
import { useMemoryMonitor } from './hooks/useMemoryMonitor.js';
@@ -160,9 +161,6 @@ export const AppContainer = (props: AppContainerProps) => {
const [showDebugProfiler, setShowDebugProfiler] = useState(false);
const [copyModeEnabled, setCopyModeEnabled] = useState(false);
const [geminiMdFileCount, setGeminiMdFileCount] = useState<number>(
initializationResult.geminiMdFileCount,
);
const [shellModeActive, setShellModeActive] = useState(false);
const [modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError] =
useState<boolean>(false);
@@ -569,7 +567,6 @@ Logging in with Google... Please restart Gemini CLI to continue.
refreshStatic,
toggleVimEnabled,
setIsProcessing,
setGeminiMdFileCount,
slashCommandActions,
extensionsUpdateStateInternal,
isConfigInitialized,
@@ -584,26 +581,8 @@ Logging in with Google... Please restart Gemini CLI to continue.
Date.now(),
);
try {
const { memoryContent, fileCount, filePaths } =
await loadHierarchicalGeminiMemory(
process.cwd(),
settings.merged.context?.loadMemoryFromIncludeDirectories
? config.getWorkspaceContext().getDirectories()
: [],
config.getDebugMode(),
config.getFileService(),
settings.merged,
config.getExtensionLoader(),
config.isTrustedFolder(),
settings.merged.context?.importFormat || 'tree', // Use setting or default to 'tree'
config.getFileFilteringOptions(),
);
config.setUserMemory(memoryContent);
config.setGeminiMdFileCount(fileCount);
config.setGeminiMdFilePaths(filePaths);
setGeminiMdFileCount(fileCount);
const { memoryContent, fileCount } =
await refreshServerHierarchicalMemory(config);
historyManager.addItem(
{
@@ -635,7 +614,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
);
debugLogger.warn('Error refreshing memory:', error);
}
}, [config, historyManager, settings.merged]);
}, [config, historyManager]);
const cancelHandlerRef = useRef<() => void>(() => {});
@@ -1248,6 +1227,19 @@ Logging in with Google... Please restart Gemini CLI to continue.
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
);
const [geminiMdFileCount, setGeminiMdFileCount] = useState<number>(
config.getGeminiMdFileCount(),
);
useEffect(() => {
const handleMemoryChanged = (result: MemoryChangedPayload) => {
setGeminiMdFileCount(result.fileCount);
};
coreEvents.on(CoreEvent.MemoryChanged, handleMemoryChanged);
return () => {
coreEvents.off(CoreEvent.MemoryChanged, handleMemoryChanged);
};
}, []);
const uiState: UIState = useMemo(
() => ({
history: historyManager.history,

View File

@@ -9,7 +9,7 @@ import { CommandKind } from './types.js';
import { MessageType } from '../types.js';
import * as os from 'node:os';
import * as path from 'node:path';
import { loadServerHierarchicalMemory } from '@google/gemini-cli-core';
import { refreshServerHierarchicalMemory } from '@google/gemini-cli-core';
export function expandHomeDir(p: string): string {
if (!p) {
@@ -94,25 +94,7 @@ export const directoryCommand: SlashCommand = {
try {
if (config.shouldLoadMemoryFromIncludeDirectories()) {
const { memoryContent, fileCount } =
await loadServerHierarchicalMemory(
config.getWorkingDir(),
[
...config.getWorkspaceContext().getDirectories(),
...pathsToAdd,
],
config.getDebugMode(),
config.getFileService(),
config.getExtensionLoader(),
config.getFolderTrust(),
context.services.settings.merged.context?.importFormat ||
'tree', // Use setting or default to 'tree'
config.getFileFilteringOptions(),
context.services.settings.merged.context?.discoveryMaxDirs,
);
config.setUserMemory(memoryContent);
config.setGeminiMdFileCount(fileCount);
context.ui.setGeminiMdFileCount(fileCount);
await refreshServerHierarchicalMemory(config);
}
addItem(
{

View File

@@ -13,11 +13,11 @@ import { MessageType } from '../types.js';
import type { LoadedSettings } from '../../config/settings.js';
import {
getErrorMessage,
refreshServerHierarchicalMemory,
SimpleExtensionLoader,
type FileDiscoveryService,
} from '@google/gemini-cli-core';
import type { LoadServerHierarchicalMemoryResponse } from '@google/gemini-cli-core/index.js';
import { loadHierarchicalGeminiMemory } from '../../config/config.js';
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const original =
@@ -28,19 +28,12 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
if (error instanceof Error) return error.message;
return String(error);
}),
refreshServerHierarchicalMemory: vi.fn(),
};
});
vi.mock('../../config/config.js', async (importOriginal) => {
const original =
await importOriginal<typeof import('../../config/config.js')>();
return {
...original,
loadHierarchicalGeminiMemory: vi.fn(),
};
});
const mockLoadHierarchicalGeminiMemory = loadHierarchicalGeminiMemory as Mock;
const mockRefreshServerHierarchicalMemory =
refreshServerHierarchicalMemory as Mock;
describe('memoryCommand', () => {
let mockContext: CommandContext;
@@ -203,11 +196,8 @@ describe('memoryCommand', () => {
},
} as unknown as LoadedSettings,
},
ui: {
setGeminiMdFileCount: vi.fn(),
},
});
mockLoadHierarchicalGeminiMemory.mockClear();
mockRefreshServerHierarchicalMemory.mockClear();
});
it('should display success message when memory is refreshed with content', async () => {
@@ -218,7 +208,7 @@ describe('memoryCommand', () => {
fileCount: 2,
filePaths: ['/path/one/GEMINI.md', '/path/two/GEMINI.md'],
};
mockLoadHierarchicalGeminiMemory.mockResolvedValue(refreshResult);
mockRefreshServerHierarchicalMemory.mockResolvedValue(refreshResult);
await refreshCommand.action(mockContext, '');
@@ -230,19 +220,7 @@ describe('memoryCommand', () => {
expect.any(Number),
);
expect(mockLoadHierarchicalGeminiMemory).toHaveBeenCalledOnce();
expect(mockSetUserMemory).toHaveBeenCalledWith(
refreshResult.memoryContent,
);
expect(mockSetGeminiMdFileCount).toHaveBeenCalledWith(
refreshResult.fileCount,
);
expect(mockSetGeminiMdFilePaths).toHaveBeenCalledWith(
refreshResult.filePaths,
);
expect(mockContext.ui.setGeminiMdFileCount).toHaveBeenCalledWith(
refreshResult.fileCount,
);
expect(mockRefreshServerHierarchicalMemory).toHaveBeenCalledOnce();
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
{
@@ -257,14 +235,11 @@ describe('memoryCommand', () => {
if (!refreshCommand.action) throw new Error('Command has no action');
const refreshResult = { memoryContent: '', fileCount: 0, filePaths: [] };
mockLoadHierarchicalGeminiMemory.mockResolvedValue(refreshResult);
mockRefreshServerHierarchicalMemory.mockResolvedValue(refreshResult);
await refreshCommand.action(mockContext, '');
expect(mockLoadHierarchicalGeminiMemory).toHaveBeenCalledOnce();
expect(mockSetUserMemory).toHaveBeenCalledWith('');
expect(mockSetGeminiMdFileCount).toHaveBeenCalledWith(0);
expect(mockSetGeminiMdFilePaths).toHaveBeenCalledWith([]);
expect(mockRefreshServerHierarchicalMemory).toHaveBeenCalledOnce();
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
{
@@ -279,11 +254,11 @@ describe('memoryCommand', () => {
if (!refreshCommand.action) throw new Error('Command has no action');
const error = new Error('Failed to read memory files.');
mockLoadHierarchicalGeminiMemory.mockRejectedValue(error);
mockRefreshServerHierarchicalMemory.mockRejectedValue(error);
await refreshCommand.action(mockContext, '');
expect(mockLoadHierarchicalGeminiMemory).toHaveBeenCalledOnce();
expect(mockRefreshServerHierarchicalMemory).toHaveBeenCalledOnce();
expect(mockSetUserMemory).not.toHaveBeenCalled();
expect(mockSetGeminiMdFileCount).not.toHaveBeenCalled();
expect(mockSetGeminiMdFilePaths).not.toHaveBeenCalled();
@@ -318,7 +293,7 @@ describe('memoryCommand', () => {
expect.any(Number),
);
expect(mockLoadHierarchicalGeminiMemory).not.toHaveBeenCalled();
expect(mockRefreshServerHierarchicalMemory).not.toHaveBeenCalled();
});
});

View File

@@ -4,9 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { getErrorMessage } from '@google/gemini-cli-core';
import {
getErrorMessage,
refreshServerHierarchicalMemory,
} from '@google/gemini-cli-core';
import { MessageType } from '../types.js';
import { loadHierarchicalGeminiMemory } from '../../config/config.js';
import type { SlashCommand, SlashCommandActionReturn } from './types.js';
import { CommandKind } from './types.js';
@@ -80,26 +82,9 @@ export const memoryCommand: SlashCommand = {
try {
const config = await context.services.config;
const settings = context.services.settings;
if (config) {
const { memoryContent, fileCount, filePaths } =
await loadHierarchicalGeminiMemory(
config.getWorkingDir(),
config.shouldLoadMemoryFromIncludeDirectories()
? config.getWorkspaceContext().getDirectories()
: [],
config.getDebugMode(),
config.getFileService(),
settings.merged,
config.getExtensionLoader(),
config.isTrustedFolder(),
settings.merged.context?.importFormat || 'tree',
config.getFileFilteringOptions(),
);
config.setUserMemory(memoryContent);
config.setGeminiMdFileCount(fileCount);
config.setGeminiMdFilePaths(filePaths);
context.ui.setGeminiMdFileCount(fileCount);
const { memoryContent, fileCount } =
await refreshServerHierarchicalMemory(config);
const successMessage =
memoryContent.length > 0

View File

@@ -68,7 +68,6 @@ export interface CommandContext {
toggleCorgiMode: () => void;
toggleDebugProfiler: () => void;
toggleVimEnabled: () => Promise<boolean>;
setGeminiMdFileCount: (count: number) => void;
reloadCommands: () => void;
extensionsUpdateState: Map<string, ExtensionUpdateStatus>;
dispatchExtensionStateUpdate: (action: ExtensionUpdateAction) => void;

View File

@@ -164,7 +164,6 @@ describe('useSlashCommandProcessor', () => {
vi.fn(), // refreshStatic
vi.fn(), // toggleVimEnabled
setIsProcessing,
vi.fn(), // setGeminiMdFileCount
{
openAuthDialog: mockOpenAuthDialog,
openThemeDialog: mockOpenThemeDialog,

View File

@@ -73,7 +73,6 @@ export const useSlashCommandProcessor = (
refreshStatic: () => void,
toggleVimEnabled: () => Promise<boolean>,
setIsProcessing: (isProcessing: boolean) => void,
setGeminiMdFileCount: (count: number) => void,
actions: SlashCommandProcessorActions,
extensionsUpdateState: Map<string, ExtensionUpdateStatus>,
isConfigInitialized: boolean,
@@ -207,7 +206,6 @@ export const useSlashCommandProcessor = (
toggleCorgiMode: actions.toggleCorgiMode,
toggleDebugProfiler: actions.toggleDebugProfiler,
toggleVimEnabled,
setGeminiMdFileCount,
reloadCommands,
extensionsUpdateState,
dispatchExtensionStateUpdate: actions.dispatchExtensionStateUpdate,
@@ -234,7 +232,6 @@ export const useSlashCommandProcessor = (
setPendingItem,
toggleVimEnabled,
sessionShellAllowlist,
setGeminiMdFileCount,
reloadCommands,
extensionsUpdateState,
],

View File

@@ -23,7 +23,6 @@ export function createNonInteractiveUI(): CommandContext['ui'] {
toggleCorgiMode: () => {},
toggleDebugProfiler: () => {},
toggleVimEnabled: async () => false,
setGeminiMdFileCount: (_count) => {},
reloadCommands: () => {},
extensionsUpdateState: new Map(),
dispatchExtensionStateUpdate: (_action: ExtensionUpdateAction) => {},