mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-22 07:41:23 -07:00
feat(core): add project-level memory scope to save_memory tool (#24161)
This commit is contained in:
@@ -227,6 +227,7 @@ vi.mock('../services/contextManager.js', () => ({
|
||||
getGlobalMemory: vi.fn().mockReturnValue(''),
|
||||
getExtensionMemory: vi.fn().mockReturnValue(''),
|
||||
getEnvironmentMemory: vi.fn().mockReturnValue(''),
|
||||
getUserProjectMemory: vi.fn().mockReturnValue(''),
|
||||
getLoadedPaths: vi.fn().mockReturnValue(new Set()),
|
||||
})),
|
||||
}));
|
||||
@@ -2948,6 +2949,7 @@ describe('Config JIT Initialization', () => {
|
||||
getEnvironmentMemory: vi
|
||||
.fn()
|
||||
.mockReturnValue('Environment Memory\n\nMCP Instructions'),
|
||||
getUserProjectMemory: vi.fn().mockReturnValue(''),
|
||||
getLoadedPaths: vi.fn().mockReturnValue(new Set(['/path/to/GEMINI.md'])),
|
||||
} as unknown as ContextManager;
|
||||
(ContextManager as unknown as Mock).mockImplementation(
|
||||
@@ -2975,6 +2977,7 @@ describe('Config JIT Initialization', () => {
|
||||
global: 'Global Memory',
|
||||
extension: 'Extension Memory',
|
||||
project: 'Environment Memory\n\nMCP Instructions',
|
||||
userProjectMemory: '',
|
||||
});
|
||||
|
||||
// Tier 1: system instruction gets only global memory
|
||||
|
||||
@@ -2282,6 +2282,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
global: this.contextManager.getGlobalMemory(),
|
||||
extension: this.contextManager.getExtensionMemory(),
|
||||
project: this.contextManager.getEnvironmentMemory(),
|
||||
userProjectMemory: this.contextManager.getUserProjectMemory(),
|
||||
};
|
||||
}
|
||||
return this.userMemory;
|
||||
@@ -2311,13 +2312,20 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
|
||||
/**
|
||||
* Returns memory for the system instruction.
|
||||
* When JIT is enabled, only global memory (Tier 1) goes in the system
|
||||
* instruction. Extension and project memory (Tier 2) are placed in the
|
||||
* first user message instead, per the tiered context model.
|
||||
* When JIT is enabled, global memory and user project memory (Tier 1) go
|
||||
* in the system instruction. Extension and project memory (Tier 2) are
|
||||
* placed in the first user message instead, per the tiered context model.
|
||||
* User project memory is in Tier 1 so mid-session saves are reflected
|
||||
* via system instruction updates.
|
||||
*/
|
||||
getSystemInstructionMemory(): string | HierarchicalMemory {
|
||||
if (this.experimentalJitContext && this.contextManager) {
|
||||
return this.contextManager.getGlobalMemory();
|
||||
const global = this.contextManager.getGlobalMemory();
|
||||
const userProjectMemory = this.contextManager.getUserProjectMemory();
|
||||
if (userProjectMemory?.trim()) {
|
||||
return { global, userProjectMemory };
|
||||
}
|
||||
return global;
|
||||
}
|
||||
return this.userMemory;
|
||||
}
|
||||
@@ -3476,7 +3484,7 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
);
|
||||
if (!this.isMemoryManagerEnabled()) {
|
||||
maybeRegister(MemoryTool, () =>
|
||||
registry.registerTool(new MemoryTool(this.messageBus)),
|
||||
registry.registerTool(new MemoryTool(this.messageBus, this.storage)),
|
||||
);
|
||||
}
|
||||
maybeRegister(WebSearchTool, () =>
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface HierarchicalMemory {
|
||||
global?: string;
|
||||
extension?: string;
|
||||
project?: string;
|
||||
userProjectMemory?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,6 +22,12 @@ export function flattenMemory(memory?: string | HierarchicalMemory): string {
|
||||
if (memory.global?.trim()) {
|
||||
sections.push({ name: 'Global', content: memory.global.trim() });
|
||||
}
|
||||
if (memory.userProjectMemory?.trim()) {
|
||||
sections.push({
|
||||
name: 'User Project Memory',
|
||||
content: memory.userProjectMemory.trim(),
|
||||
});
|
||||
}
|
||||
if (memory.extension?.trim()) {
|
||||
sections.push({ name: 'Extension', content: memory.extension.trim() });
|
||||
}
|
||||
|
||||
@@ -147,6 +147,17 @@ describe('Storage – additional helpers', () => {
|
||||
expect(storage.getProjectAgentsDir()).toBe(expected);
|
||||
});
|
||||
|
||||
it('getProjectMemoryDir returns ~/.gemini/memory/<identifier>', async () => {
|
||||
await storage.initialize();
|
||||
const expected = path.join(
|
||||
os.homedir(),
|
||||
GEMINI_DIR,
|
||||
'memory',
|
||||
PROJECT_SLUG,
|
||||
);
|
||||
expect(storage.getProjectMemoryDir()).toBe(expected);
|
||||
});
|
||||
|
||||
it('getMcpOAuthTokensPath returns ~/.gemini/mcp-oauth-tokens.json', () => {
|
||||
const expected = path.join(
|
||||
os.homedir(),
|
||||
|
||||
@@ -266,6 +266,11 @@ export class Storage {
|
||||
return path.join(historyDir, identifier);
|
||||
}
|
||||
|
||||
getProjectMemoryDir(): string {
|
||||
const identifier = this.getProjectIdentifier();
|
||||
return path.join(Storage.getGlobalGeminiDir(), 'memory', identifier);
|
||||
}
|
||||
|
||||
getWorkspaceSettingsPath(): string {
|
||||
return path.join(this.getGeminiDir(), 'settings.json');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user