mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-15 16:41:11 -07:00
Reload gemini memory on extension load/unload + memory refresh refactor (#12651)
This commit is contained in:
@@ -4,21 +4,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import yargs from 'yargs/yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import process from 'node:process';
|
||||
import { mcpCommand } from '../commands/mcp.js';
|
||||
import type {
|
||||
FileFilteringOptions,
|
||||
OutputFormat,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { OutputFormat } from '@google/gemini-cli-core';
|
||||
import { extensionsCommand } from '../commands/extensions.js';
|
||||
import {
|
||||
Config,
|
||||
loadServerHierarchicalMemory,
|
||||
setGeminiMdFilename as setServerGeminiMdFilename,
|
||||
getCurrentGeminiMdFilename,
|
||||
ApprovalMode,
|
||||
@@ -36,6 +29,7 @@ import {
|
||||
getPty,
|
||||
EDIT_TOOL_NAME,
|
||||
debugLogger,
|
||||
loadServerHierarchicalMemory,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Settings } from './settings.js';
|
||||
|
||||
@@ -47,10 +41,7 @@ import { appEvents } from '../utils/events.js';
|
||||
import { isWorkspaceTrusted } from './trustedFolders.js';
|
||||
import { createPolicyEngineConfig } from './policy.js';
|
||||
import { ExtensionManager } from './extension-manager.js';
|
||||
import type {
|
||||
ExtensionEvents,
|
||||
ExtensionLoader,
|
||||
} from '@google/gemini-cli-core/src/utils/extensionLoader.js';
|
||||
import type { ExtensionEvents } from '@google/gemini-cli-core/src/utils/extensionLoader.js';
|
||||
import { requestConsentNonInteractive } from './extensions/consent.js';
|
||||
import { promptForSetting } from './extensions/extensionSettings.js';
|
||||
import type { EventEmitter } from 'node:stream';
|
||||
@@ -297,49 +288,6 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
|
||||
return result as unknown as CliArgs;
|
||||
}
|
||||
|
||||
// This function is now a thin wrapper around the server's implementation.
|
||||
// It's kept in the CLI for now as App.tsx directly calls it for memory refresh.
|
||||
// TODO: Consider if App.tsx should get memory via a server call or if Config should refresh itself.
|
||||
export async function loadHierarchicalGeminiMemory(
|
||||
currentWorkingDirectory: string,
|
||||
includeDirectoriesToReadGemini: readonly string[] = [],
|
||||
debugMode: boolean,
|
||||
fileService: FileDiscoveryService,
|
||||
settings: Settings,
|
||||
extensionLoader: ExtensionLoader,
|
||||
folderTrust: boolean,
|
||||
memoryImportFormat: 'flat' | 'tree' = 'tree',
|
||||
fileFilteringOptions?: FileFilteringOptions,
|
||||
): Promise<{ memoryContent: string; fileCount: number; filePaths: string[] }> {
|
||||
// FIX: Use real, canonical paths for a reliable comparison to handle symlinks.
|
||||
const realCwd = fs.realpathSync(path.resolve(currentWorkingDirectory));
|
||||
const realHome = fs.realpathSync(path.resolve(homedir()));
|
||||
const isHomeDirectory = realCwd === realHome;
|
||||
|
||||
// If it is the home directory, pass an empty string to the core memory
|
||||
// function to signal that it should skip the workspace search.
|
||||
const effectiveCwd = isHomeDirectory ? '' : currentWorkingDirectory;
|
||||
|
||||
if (debugMode) {
|
||||
debugLogger.debug(
|
||||
`CLI: Delegating hierarchical memory load to server for CWD: ${currentWorkingDirectory} (memoryImportFormat: ${memoryImportFormat})`,
|
||||
);
|
||||
}
|
||||
|
||||
// Directly call the server function with the corrected path.
|
||||
return loadServerHierarchicalMemory(
|
||||
effectiveCwd,
|
||||
includeDirectoriesToReadGemini,
|
||||
debugMode,
|
||||
fileService,
|
||||
extensionLoader,
|
||||
folderTrust,
|
||||
memoryImportFormat,
|
||||
fileFilteringOptions,
|
||||
settings.context?.discoveryMaxDirs,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a filter function to determine if a tool should be excluded.
|
||||
*
|
||||
@@ -437,18 +385,18 @@ export async function loadCliConfig(
|
||||
|
||||
// Call the (now wrapper) loadHierarchicalGeminiMemory which calls the server's version
|
||||
const { memoryContent, fileCount, filePaths } =
|
||||
await loadHierarchicalGeminiMemory(
|
||||
await loadServerHierarchicalMemory(
|
||||
cwd,
|
||||
settings.context?.loadMemoryFromIncludeDirectories
|
||||
? includeDirectories
|
||||
: [],
|
||||
debugMode,
|
||||
fileService,
|
||||
settings,
|
||||
extensionManager,
|
||||
trustedFolder,
|
||||
memoryImportFormat,
|
||||
memoryFileFiltering,
|
||||
settings.context?.discoveryMaxDirs,
|
||||
);
|
||||
|
||||
const question = argv.promptInteractive || argv.prompt || '';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -164,7 +164,6 @@ describe('useSlashCommandProcessor', () => {
|
||||
vi.fn(), // refreshStatic
|
||||
vi.fn(), // toggleVimEnabled
|
||||
setIsProcessing,
|
||||
vi.fn(), // setGeminiMdFileCount
|
||||
{
|
||||
openAuthDialog: mockOpenAuthDialog,
|
||||
openThemeDialog: mockOpenThemeDialog,
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -23,7 +23,6 @@ export function createNonInteractiveUI(): CommandContext['ui'] {
|
||||
toggleCorgiMode: () => {},
|
||||
toggleDebugProfiler: () => {},
|
||||
toggleVimEnabled: async () => false,
|
||||
setGeminiMdFileCount: (_count) => {},
|
||||
reloadCommands: () => {},
|
||||
extensionsUpdateState: new Map(),
|
||||
dispatchExtensionStateUpdate: (_action: ExtensionUpdateAction) => {},
|
||||
|
||||
Reference in New Issue
Block a user