| |
| |
| |
| |
| |
|
|
| import { |
| loadJitSubdirectoryMemory, |
| concatenateInstructions, |
| getGlobalMemoryPaths, |
| getUserProjectMemoryPaths, |
| getExtensionMemoryPaths, |
| getEnvironmentMemoryPaths, |
| readGeminiMdFiles, |
| categorizeAndConcatenate, |
| type GeminiFileContent, |
| deduplicatePathsByFileIdentity, |
| } from '../utils/memoryDiscovery.js'; |
| import type { Config } from '../config/config.js'; |
| import { coreEvents, CoreEvent } from '../utils/events.js'; |
|
|
| export class MemoryContextManager { |
| private readonly loadedPaths: Set<string> = new Set(); |
| private readonly loadedFileIdentities: Set<string> = new Set(); |
| private readonly config: Config; |
| private globalMemory: string = ''; |
| private extensionMemory: string = ''; |
| private projectMemory: string = ''; |
| private userProjectMemoryContent: string = ''; |
|
|
| constructor(config: Config) { |
| this.config = config; |
| } |
|
|
| |
| |
| |
| async refresh(): Promise<void> { |
| this.loadedPaths.clear(); |
| this.loadedFileIdentities.clear(); |
|
|
| const paths = await this.discoverMemoryPaths(); |
| const contentsMap = await this.loadMemoryContents(paths); |
|
|
| this.categorizeMemoryContents(paths, contentsMap); |
| this.emitMemoryChanged(); |
| } |
|
|
| private async discoverMemoryPaths() { |
| const [global, extension, project, userProjectMemory] = await Promise.all([ |
| getGlobalMemoryPaths(), |
| Promise.resolve( |
| getExtensionMemoryPaths(this.config.getExtensionLoader()), |
| ), |
| this.config.isTrustedFolder() |
| ? getEnvironmentMemoryPaths( |
| [...this.config.getWorkspaceContext().getDirectories()], |
| this.config.getMemoryBoundaryMarkers(), |
| ) |
| : Promise.resolve([]), |
| getUserProjectMemoryPaths(this.config.storage.getProjectMemoryDir()), |
| ]); |
|
|
| return { global, extension, project, userProjectMemory }; |
| } |
|
|
| private async loadMemoryContents(paths: { |
| global: string[]; |
| extension: string[]; |
| project: string[]; |
| userProjectMemory: string[]; |
| }) { |
| const allPathsStringDeduped = Array.from( |
| new Set([ |
| ...paths.global, |
| ...paths.extension, |
| ...paths.project, |
| ...paths.userProjectMemory, |
| ]), |
| ); |
|
|
| |
| const { paths: allPaths, identityMap: pathIdentityMap } = |
| await deduplicatePathsByFileIdentity(allPathsStringDeduped); |
|
|
| const allContents = await readGeminiMdFiles( |
| allPaths, |
| this.config.getImportFormat(), |
| this.config.getMemoryBoundaryMarkers(), |
| ); |
|
|
| const loadedFilePaths = allContents |
| .filter((c) => c.content !== null) |
| .map((c) => c.filePath); |
| this.markAsLoaded(loadedFilePaths); |
|
|
| |
| for (const filePath of loadedFilePaths) { |
| const identity = pathIdentityMap.get(filePath); |
| if (identity) { |
| this.loadedFileIdentities.add(identity); |
| } |
| } |
|
|
| return new Map(allContents.map((c) => [c.filePath, c])); |
| } |
|
|
| private categorizeMemoryContents( |
| paths: { |
| global: string[]; |
| extension: string[]; |
| project: string[]; |
| userProjectMemory: string[]; |
| }, |
| contentsMap: Map<string, GeminiFileContent>, |
| ) { |
| const hierarchicalMemory = categorizeAndConcatenate(paths, contentsMap); |
|
|
| this.globalMemory = hierarchicalMemory.global || ''; |
| this.extensionMemory = hierarchicalMemory.extension || ''; |
| this.userProjectMemoryContent = hierarchicalMemory.userProjectMemory || ''; |
|
|
| const mcpInstructions = |
| this.config.getMcpClientManager()?.getMcpInstructions() || ''; |
| const projectMemoryWithMcp = [ |
| hierarchicalMemory.project, |
| mcpInstructions.trimStart(), |
| ] |
| .filter(Boolean) |
| .join('\n\n'); |
|
|
| this.projectMemory = this.config.isTrustedFolder() |
| ? projectMemoryWithMcp |
| : ''; |
| } |
|
|
| |
| |
| |
| |
| async discoverContext( |
| accessedPath: string, |
| trustedRoots: string[], |
| ): Promise<string> { |
| if (!this.config.isTrustedFolder()) { |
| return ''; |
| } |
| const result = await loadJitSubdirectoryMemory( |
| accessedPath, |
| trustedRoots, |
| this.loadedPaths, |
| this.loadedFileIdentities, |
| this.config.getMemoryBoundaryMarkers(), |
| ); |
|
|
| if (result.files.length === 0) { |
| return ''; |
| } |
|
|
| const newFilePaths = result.files.map((f) => f.path); |
| this.markAsLoaded(newFilePaths); |
|
|
| |
| if (result.fileIdentities) { |
| for (const identity of result.fileIdentities) { |
| this.loadedFileIdentities.add(identity); |
| } |
| } |
| return concatenateInstructions( |
| result.files.map((f) => ({ filePath: f.path, content: f.content })), |
| ); |
| } |
|
|
| private emitMemoryChanged(): void { |
| coreEvents.emit(CoreEvent.MemoryChanged, { |
| fileCount: this.loadedPaths.size, |
| }); |
| } |
|
|
| getGlobalMemory(): string { |
| return this.globalMemory; |
| } |
|
|
| getExtensionMemory(): string { |
| return this.extensionMemory; |
| } |
|
|
| getEnvironmentMemory(): string { |
| return this.projectMemory; |
| } |
|
|
| getUserProjectMemory(): string { |
| return this.userProjectMemoryContent; |
| } |
|
|
| private markAsLoaded(paths: string[]): void { |
| paths.forEach((p) => this.loadedPaths.add(p)); |
| } |
|
|
| getLoadedPaths(): ReadonlySet<string> { |
| return this.loadedPaths; |
| } |
| } |
|
|