UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

382 lines 15.7 kB
/** * MemoryManager - Implementation of IElementManager for Memory elements * Handles CRUD operations and lifecycle management for memories implementing IElement * * FIXES IMPLEMENTED: * 1. CRITICAL: Fixed race conditions in file operations by using FileLockManager for atomic reads/writes * 2. HIGH: Fixed unvalidated YAML parsing vulnerability by using SecureYamlParser * 3. MEDIUM: All user inputs are now validated and sanitized * 4. MEDIUM: Audit logging added for security operations * 5. MEDIUM: Path validation prevents directory traversal attacks */ import { Memory, MemoryMetadata } from './Memory.js'; import { ElementValidationResult } from '../../types/elements/IElement.js'; import { ElementType } from '../../portfolio/types.js'; import { BaseElementManager } from '../base/BaseElementManager.js'; import type { IStorageLayer } from '../../storage/IStorageLayer.js'; import { FileLockManager } from '../../security/fileLockManager.js'; import { ValidationRegistry } from '../../services/validation/ValidationRegistry.js'; import { SerializationService } from '../../services/SerializationService.js'; import { MetadataService } from '../../services/MetadataService.js'; import { FileOperationsService } from '../../services/FileOperationsService.js'; import { FileWatchService } from '../../services/FileWatchService.js'; import { PortfolioManager } from '../../portfolio/PortfolioManager.js'; export declare class MemoryManager extends BaseElementManager<Memory> { private metadataService; private readonly memoriesDir; private contentHashIndex; private contentHashByPath; private triggerValidationService; private validationService; private serializationService; private activeMemoryNames; constructor(portfolioManager: PortfolioManager, fileLockManager: FileLockManager, fileOperationsService: FileOperationsService, validationRegistry: ValidationRegistry, serializationService: SerializationService, metadataService: MetadataService, fileWatchService?: FileWatchService, memoryBudget?: import('../../cache/CacheMemoryBudget.js').CacheMemoryBudget, backupService?: import('../../services/BackupService.js').BackupService); /** * Phase 2: Override factory to use MemoryStorageLayer for multi-directory scanning. */ protected createStorageLayer(fileOperationsService: FileOperationsService): IStorageLayer; protected getElementLabel(): string; protected createBackupBeforeSave(): Promise<void>; protected createBackupBeforeDelete(): Promise<boolean>; /** * Override clearCache to also clear memory-specific caches. * Phase 2: Removed unbounded memoryCache and dateFoldersCache. */ clearCache(): void; /** * Override dispose to flush MemoryStorageLayer _index.json and clear caches. * Prevents resource leaks and "Jest did not exit" warnings in tests. */ dispose(): void; /** * Resolve memory file path across different storage locations * Searches in this order: system/, adapters/, date folders, root (legacy) * Extracted method to reduce cognitive complexity (PR #7) * @private */ private resolveMemoryPath; /** * Load a memory from file * SECURITY FIX #1: Uses FileLockManager.atomicReadFile() instead of fs.readFile() * to prevent race conditions and ensure atomic file operations * @param filePath Path to the memory file to load * @returns Promise resolving to the loaded Memory instance * @throws {Error} When file cannot be found or path validation fails * @throws {Error} When YAML parsing fails or content is malformed * @throws {Error} When memory validation fails after loading */ load(filePath: string): Promise<Memory>; /** * Move existing memory file to user backup directory * Issue #49: Prevents duplicate files by backing up instead of versioning */ private moveToUserBackup; /** * Prune excess backups for a specific memory in a date folder. * Keeps only the N most recent backups (by timestamp in filename). * Issue #654: Prevents unbounded backup growth (was 197k+ files). */ private pruneBackupsForMemory; /** * Generate path for memory storage based on memory type * Routes memories to appropriate folders: * - SYSTEM: system/ * - ADAPTER: adapters/ * - USER: YYYY-MM-DD/ (date-based folders) * @param element Memory element to save * @param memoryType Type of memory (defaults to USER) * @param fileName Optional custom filename * @returns Full path to memory file */ private generateMemoryPath; /** * Determine memory type from file path * @param filePath Relative path within memories directory * @returns Memory type based on path */ private getMemoryTypeFromPath; /** * Ensure all required folder structure exists * Creates system/, backups/system/, backups/user/, adapters/ folders */ private ensureFolderStructure; /** * Calculate SHA-256 hash of memory content for deduplication * Implements Issue #994 - Content-based deduplication */ private calculateContentHash; /** * Get all date folders in memories directory. * Phase 2: Removed local cache — MemoryStorageLayer scan cooldown handles the hot path. * This method is now only used by resolveMemoryPath() as a fallback. * @returns Array of date folder names */ private getDateFolders; /** * Save a memory to file * SECURITY FIX #1: Uses FileLockManager.atomicWriteFile() for atomic operations * @param element Memory element to save * @param filePath Optional custom file path, defaults to type-based path * @returns Promise that resolves when save is complete * @throws {Error} When memory validation fails before saving * @throws {Error} When path validation fails or file system errors occur * @throws {Error} When atomic write operation fails */ save(element: Memory, filePath?: string): Promise<void>; /** * Handle memory load failure * FIX (SonarCloud): Extract duplicated error handling to reduce code duplication * @private */ private handleLoadFailure; /** * List all available memories from all locations. * Phase 2: Delegates multi-directory scanning to MemoryStorageLayer. * Storage layer handles: system/, adapters/, date folders, root, backup filtering, cooldown. * * Issue #18 Phase 4: Apply active status to memories that are in the active set. */ list(): Promise<Memory[]>; /** * Activate a memory by name or identifier * Issue #18 Phase 4: Context-loading activation strategy * Issue #24 (LOW PRIORITY): Performance optimization using findByName() * Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages * Issue #24 (LOW PRIORITY): Cleanup trigger for memory leak prevention */ activateMemory(identifier: string): Promise<{ success: boolean; message: string; memory?: Memory; }>; /** * Deactivate a memory by name or identifier * Issue #18 Phase 4: Remove from active set * Issue #24 (LOW PRIORITY): Performance optimization using findByName() * Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages */ deactivateMemory(identifier: string): Promise<{ success: boolean; message: string; }>; /** * Get all active memories * Issue #18 Phase 4: Return memories that are in the active set */ getActiveMemories(): Promise<Memory[]>; /** * Issue #39: Scan all memories and repair corrupted backup names * This method loads all memories, checks for corrupted names, repairs them, * and saves the corrected files back to disk. * * @param onProgress Optional callback for progress updates during long operations * @returns Object with repair statistics and list of repaired memories */ repairCorruptedNames(onProgress?: (processed: number, total: number, current?: string) => void): Promise<{ scanned: number; repaired: number; errors: number; repairedMemories: Array<{ original: string; repaired: string; path: string; }>; errorDetails: Array<{ name: string; error: string; }>; }>; /** * Issue #39: Clean up excessive backup files in a directory * Removes versioned backup files (e.g., name.backup-...-v2.yaml, -v3.yaml, etc.) * keeping only the most recent backup (without version suffix) * * @param targetDir Directory to clean up (defaults to system/ folder) * @param dryRun If true, only reports what would be deleted without actually deleting * @param onProgress Optional callback for progress updates during long operations * @returns Object with cleanup statistics */ cleanupExcessiveBackups(targetDir?: string, dryRun?: boolean, onProgress?: (processed: number, total: number, current?: string) => void): Promise<{ scanned: number; deleted: number; errors: number; deletedFiles: string[]; keptFiles: string[]; errorDetails: Array<{ file: string; error: string; }>; }>; /** * Check if active set cleanup is needed and perform cleanup if necessary * Issue #24 (LOW PRIORITY): Memory leak prevention * @private */ private checkAndCleanupActiveSet; /** * Clean up stale entries from active memories set * Issue #24 (LOW PRIORITY): Memory leak prevention * @private */ private cleanupStaleActiveMemories; /** * Check root files for load failures * FIX (SonarCloud S3776): Extract to reduce cognitive complexity * @private */ private checkRootFiles; /** * Check date folder files for load failures * FIX (SonarCloud S3776): Extract to reduce cognitive complexity * @private */ private checkDateFolderFiles; /** * Get diagnostic information about memory loading status * FIX (#1206): New method to expose failed loads to users */ getLoadStatus(): Promise<{ total: number; loaded: number; failed: number; failures: Array<{ file: string; error: string; }>; }>; /** * Get memories marked for auto-loading on server initialization. * Phase 2: Queries index for autoLoad entries instead of loading all memories. * Drops from ~70s to <30ms for typical portfolios with 14k memories. * Issue #1430: Auto-load baseline memories feature * * @returns Promise resolving to array of auto-load memories sorted by priority */ getAutoLoadMemories(): Promise<Memory[]>; /** * Install seed memories from the seed-elements directory * Issue #1430: Copy baseline memory to user portfolio on first run * * Copies seed files from src/seed-elements/memories/ to the user's portfolio * if they don't already exist. This allows users to have baseline knowledge * available immediately without manual installation. * * @returns Promise resolving when installation is complete */ installSeedMemories(): Promise<void>; /** * Find memories matching a predicate */ find(predicate: (element: Memory) => boolean): Promise<Memory | undefined>; /** * Find multiple memories matching a predicate */ findMany(predicate: (element: Memory) => boolean): Promise<Memory[]>; /** * Delete a memory file * SECURITY: Validates path and logs deletion */ delete(filePath: string): Promise<void>; /** * Check if a memory file exists */ exists(filePath: string): Promise<boolean>; /** * Create a new memory with metadata */ create(metadata: Partial<MemoryMetadata> & { content?: string; instructions?: string; }): Promise<Memory>; /** * Parse YAML data for import * Extracted to reduce cognitive complexity (SonarCloud) * @private */ private parseYamlForImport; /** * Create memory from parsed data * Extracted to reduce cognitive complexity (SonarCloud) * @private */ private createMemoryFromParsed; /** * Import a memory from JSON/YAML string * SECURITY: Full validation of imported content * @param data JSON or YAML string containing memory data * @param format Format of the input data ('json' or 'yaml') * @returns Promise resolving to the imported Memory instance * @throws {Error} When JSON/YAML parsing fails * @throws {Error} When imported data is missing required fields * @throws {Error} When YAML content exceeds maximum allowed size * @throws {Error} When imported memory fails validation */ importElement(data: string, format?: 'json' | 'yaml' | 'markdown'): Promise<Memory>; /** * Export a memory to YAML string */ exportElement(element: Memory, format?: 'json' | 'yaml' | 'markdown'): Promise<string>; protected parseMetadata(data: any): Promise<MemoryMetadata>; protected createElement(metadata: MemoryMetadata, _content: string): Memory; protected serializeElement(element: Memory): Promise<string>; /** * Validate a memory element */ validate(element: Memory): ElementValidationResult; /** * Validate and resolve a file path * SECURITY: Prevents directory traversal attacks */ validatePath(filePath: string): boolean; /** * Get the element type this manager handles */ getElementType(): ElementType; /** * Get the file extension for memory files */ getFileExtension(): string; /** * Validate and resolve a file path to prevent security issues * @param filePath Path to validate and resolve * @returns Promise resolving to the validated full path * @throws {Error} When path contains traversal attempts (../) * @throws {Error} When path is absolute or invalid * @throws {Error} When file extension is not allowed (.md, .yaml, .yml) * @throws {Error} When resolved path would be outside memories directory */ private validateAndResolvePath; private parseMemoryFile; /** * Helper to parse retention days from various formats */ private parseRetentionDays; /** * Estimate tokens in memory content (rough approximation) * Uses 1 token ≈ 0.75 words for English text * @param content Memory content to estimate * @returns Estimated token count */ estimateTokens(content: string): number; /** * Load and activate auto-load memories during server initialization * Issue #1430: Auto-load baseline memories feature * * This method is called by Container.preparePortfolio() during server startup * to automatically load memories marked with autoLoad: true * * Architecture Note: * This implementation follows the DI-aligned pattern where managers own all operations * for their element type. Auto-load logic lives in MemoryManager (not ServerStartup) * because MemoryManager already owns all memory operations (CRUD, activation, etc.) * * @returns Promise resolving to auto-load statistics */ loadAndActivateAutoLoadMemories(): Promise<{ loaded: number; skipped: number; totalTokens: number; errors: string[]; }>; } //# sourceMappingURL=MemoryManager.d.ts.map