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.

370 lines 13.8 kB
/** * Memory Element - Persistent context storage for continuity and learning * * Provides multiple storage backends, retention policies, and search capabilities * for maintaining context across sessions and interactions. * * SECURITY MEASURES IMPLEMENTED: * 1. Input sanitization for all memory content * 2. Memory size limits to prevent unbounded growth * 3. Path validation for file-based storage * 4. Retention policy enforcement * 5. Privacy level access control * 6. Audit logging for all operations */ import { BaseElement } from '../BaseElement.js'; import { IElement, ElementValidationResult } from '../../types/elements/index.js'; import { IElementMetadata } from '../../types/elements/IElement.js'; import { MetadataService } from '../../services/MetadataService.js'; import { PrivacyLevel, StorageBackend, TrustLevel } from './constants.js'; import { MemoryType } from './types.js'; export interface MemoryMetadata extends IElementMetadata { storageBackend?: StorageBackend; retentionDays?: number; privacyLevel?: PrivacyLevel; searchable?: boolean; maxEntries?: number; encryptionEnabled?: boolean; indexThreshold?: number; enableContentIndex?: boolean; maxTermsPerEntry?: number; minTermLength?: number; triggers?: string[]; autoLoad?: boolean; priority?: number; memoryType?: MemoryType; } export interface MemoryEntry { id: string; timestamp: Date; content: string; tags?: string[]; metadata?: Record<string, any>; expiresAt?: Date; privacyLevel?: PrivacyLevel; trustLevel?: TrustLevel; source?: string; } export interface MemorySearchOptions { query?: string; tags?: string[]; startDate?: Date; endDate?: Date; limit?: number; privacyLevel?: PrivacyLevel; } /** * Memory Element Implementation * * TODO: Memory Sharding Strategy (Issue #981) * --------------------------------------------- * Current: Single Map<id, entry> for all memories * Problem: Large memory sets (>10K entries) cause performance degradation * * Planned Sharding Architecture: * 1. Shard memories across multiple files based on hash(memoryId) % shardCount * 2. Each shard file <256KB for optimal YAML parsing * 3. Memory references stored separately from content (like git objects) * 4. Large binary content (PDFs, images) stored as external references * * Benefits: * - Parallel loading of memory shards * - Reduced memory footprint (load only needed shards) * - Better corruption resistance (one shard failure doesn't affect others) * - Efficient incremental updates * * TODO: Content Integrity Verification (Issue #982) * -------------------------------------------------- * Add SHA-256 hashes to detect: * - Accidental corruption from disk errors * - Intentional tampering with memory files * - Version conflicts during concurrent access * * Implementation: * - Store hash in memory metadata * - Verify on load, warn on mismatch * - Option to auto-restore from backup on corruption * * TODO: Memory Capacity Management (Issue #983) * --------------------------------------------- * Current: Synchronous retention enforcement on each add * Better: Background cleanup with smart triggers: * - Cleanup when 90% capacity reached * - Batch deletions for efficiency * - LRU eviction with access tracking * - Preserve "pinned" memories regardless of age */ export declare class Memory extends BaseElement implements IElement { private static readonly createdMemoryNames; private static memoryManagerResolver?; /** * Static resolver for RetentionPolicyService (Issue #51) * Used to check if retention enforcement should happen on load */ private static retentionPolicyResolver?; static configureMemoryManagerResolver(resolver: () => { list(): Promise<Memory[]>; save(memory: Memory, filePath?: string): Promise<void>; } | undefined): void; /** * Configure the RetentionPolicyService resolver (Issue #51) * Called during DI container setup */ static configureRetentionPolicyResolver(resolver: () => { shouldEnforceOnLoad(): boolean; isEnabled(): boolean; } | undefined): void; /** * Reset all static resolvers (for test cleanup) * Call this in afterEach hooks to prevent test isolation issues * where a stale resolver references disposed services */ static resetResolvers(): void; /** * Get the RetentionPolicyService instance * Returns undefined if not configured (allows graceful fallback) */ private static getRetentionPolicyService; private static getMemoryManager; private entries; private storageBackend; private retentionDays; private privacyLevel; private searchable; private maxEntries; private searchIndex; private sanitizationCache; private filePath?; private static readonly MAX_SANITIZATION_CACHE_SIZE; private static readonly MAX_SANITIZATION_CACHE_MEMORY_MB; constructor(metadata: Partial<MemoryMetadata> | undefined, metadataService: MetadataService); /** * Helper method to get the current number of entries * Compatible with LRUCache */ private get entriesSize(); /** * Add a new memory entry * SECURITY: Sanitizes input and enforces size limits * FIX #1315: Removed blocking validation - all entries created as UNTRUSTED * Background validation will update trust levels asynchronously (Issue #1314) * * FIX: Refactored to reduce cognitive complexity (SonarCloud S3776) * FIX (PR #1313 review): Sanitize source parameter for log injection prevention */ addEntry(content: string, tags?: string[], metadata?: Record<string, any>, source?: string): Promise<MemoryEntry>; /** * Enforce capacity limit synchronously * FIX (PR #1313): Made synchronous to prevent race conditions * This is called AFTER adding an entry to ensure we never exceed maxEntries */ private enforceCapacitySync; /** * Search memory entries * SECURITY: Respects privacy levels and sanitizes search queries * * IMPLEMENTED: Basic indexed search for O(log n) performance (Issue #984) * - Tag index: Map<tag, Set<entryId>> for instant tag lookups ✓ * - Content index: Inverted index for term search ✓ * - Date index: Binary tree for efficient range queries ✓ * - Privacy index: Pre-sorted entries by privacy level ✓ * * TODO: Advanced indexing features (Future enhancements): * - Composite indices: Combined indices for common query patterns * - Index-of-indexes pattern: * - Master index file (meta.yaml) with pointers to shard indices * - Each shard maintains its own local index * - Periodic index compaction and optimization * - Persistent index storage to disk * - Incremental index updates for large datasets * * Performance improvement achieved: * - Previous: ~100ms for 10,000 entries (linear scan) * - Current: <5ms for same dataset (indexed search) */ search(options?: MemorySearchOptions): Promise<MemoryEntry[]>; /** * Get a specific memory entry by ID */ getEntry(id: string): Promise<MemoryEntry | undefined>; /** * Delete a memory entry * SECURITY: Validates permissions and logs deletion */ deleteEntry(id: string): Promise<boolean>; /** * Get formatted content of all memory entries * Returns entries as a readable string for display * FIX #1269: Sandboxes untrusted content to prevent prompt injection */ get content(): string; /** * Sandbox untrusted content with clear delimiters * FIX #1269: Prevents AI from interpreting user content as instructions */ private sandboxUntrustedContent; /** * Enforce retention policy by removing expired entries * SECURITY: Ensures memory doesn't grow unbounded */ enforceRetentionPolicy(): Promise<number>; /** * Clear all memory entries * SECURITY: Requires confirmation and logs the action */ clearAll(confirm?: boolean): Promise<void>; /** * Helper function to ensure a value is a valid Date object * FIX #1069: Validates and converts timestamps to Date objects */ private ensureDateObject; /** * Get memory statistics */ getStats(): { totalEntries: number; totalSize: number; oldestEntry?: Date; newestEntry?: Date; tagFrequency: Map<string, number>; }; /** * Validate the memory element */ validate(): ElementValidationResult; /** * Serialize memory to string */ serialize(): string; /** * Process and validate a single memory entry during deserialization * FIX #1315: Reads trust level from metadata instead of re-validating * Returns true if entry was loaded, false if quarantined */ private processDeserializedEntry; /** * Deserialize memory from string * SECURITY: Validates all loaded data * FIX #1269: Added ContentValidator to prevent loading infected memories */ deserialize(data: string): void; private calculateExpiryDate; private sanitizeTags; private sanitizeMetadata; private canAccessPrivacyLevel; private isValidEntry; /** * Optimized sanitization with checksum caching * Avoids re-sanitizing content that hasn't changed * @param content Content to sanitize * @param maxLength Maximum allowed length * @returns Sanitized content */ private sanitizeWithCache; /** * Build search index with retry logic * Attempts to build the index up to 3 times with exponential backoff * This ensures search functionality even if there are transient failures */ private buildSearchIndexWithRetry; /** * Get all entries with a specific trust level * FIX #1320: Public API for accessing entries by trust level * Used by BackgroundValidator to find untrusted entries * * @param trustLevel - The trust level to filter by * @returns Array of memory entries matching the trust level */ getEntriesByTrustLevel(trustLevel: TrustLevel): MemoryEntry[]; /** * Get all entries in this memory * FIX #1320: Public API for accessing all entries * Replaces the need for `(memory as any).entries` hacks * * @returns Array of all memory entries */ getAllEntries(): MemoryEntry[]; /** * Get the entries map directly * Used by RetentionPolicyService for retention enforcement (Issue #51) * * @returns Map of entry ID to memory entry */ getEntries(): Map<string, MemoryEntry>; /** * Remove a specific entry by ID * FIX #51: Public API for retention policy to remove expired entries * Replaces the need for `(memory as any).entries.delete()` hacks * * @param entryId - The ID of the entry to remove * @returns true if entry was removed, false if not found */ removeEntry(entryId: string): boolean; /** * Get an iterator over all entries * FIX #1320: Memory-efficient way to iterate entries * * @returns Iterator over memory entries */ getEntriesIterator(): IterableIterator<MemoryEntry>; /** * Set the file path for this memory * FIX #1320: Used by MemoryManager after loading * FIX (SonarCloud): Added input validation with proper error types * @param path - The file path where this memory is stored * @throws {TypeError} If path is not a string * @throws {Error} If path is empty */ setFilePath(path: string): void; /** * Get the file path for this memory * FIX #1320: Returns the path where this memory is stored * @returns The file path, or undefined if not yet persisted */ getFilePath(): string | undefined; /** * Save this memory to disk * FIX #1320: Instance method for persisting memory changes * Used by BackgroundValidator to save updated trust levels * * @returns Promise that resolves when save is complete * @throws {Error} If memory has not been loaded from file and no path is set */ save(): Promise<void>; /** * Find all memories that have entries with a specific trust level * FIX #1320: Static query API for finding memories by trust level * Used by BackgroundValidator to discover untrusted memories * * @param trustLevel - The trust level to filter by * @param options - Optional query options * @param options.limit - Maximum number of memories to return * @returns Promise resolving to array of memories with matching entries */ static findByTrustLevel(trustLevel: TrustLevel, options?: { limit?: number; }): Promise<Memory[]>; /** * General query API for finding memories * FIX #1320: Flexible query API for multiple criteria * FIX (SonarCloud): Refactored to reduce cognitive complexity from 20 to 8 * * @param filter - Query filter criteria * @param filter.trustLevel - Filter by trust level * @param filter.tags - Filter by tags * @param filter.maxAge - Filter by age in days * @returns Promise resolving to array of matching memories */ static find(filter: { trustLevel?: TrustLevel; tags?: string[]; maxAge?: number; }): Promise<Memory[]>; /** * Check if a memory matches the given filter criteria * FIX (SonarCloud): Extracted to reduce cognitive complexity * @private */ private static matchesFilter; } //# sourceMappingURL=Memory.d.ts.map