/** * Core persona management operations */ import { Persona } from '../types/persona.js'; import { PersonaElement, PersonaElementMetadata } from './PersonaElement.js'; import { FileLockManager } from '../security/fileLockManager.js'; import { IndicatorConfig } from '../config/indicator-config.js'; import { PortfolioManager } from '../portfolio/PortfolioManager.js'; import { StateChangeNotifier } from '../services/StateChangeNotifier.js'; import { PersonaImporter, ImportResult } from './export-import/PersonaImporter.js'; import { BaseElementManager, type BaseElementManagerOptions } from '../elements/base/BaseElementManager.js'; import { ValidationRegistry } from '../services/validation/ValidationRegistry.js'; import { MetadataService } from '../services/MetadataService.js'; import { FileOperationsService } from '../services/FileOperationsService.js'; interface PersonaManagerOptions extends BaseElementManagerOptions { personaImporter?: PersonaImporter; notifier?: StateChangeNotifier; } export declare class PersonaManager extends BaseElementManager { /** * Track active personas by filename (stable identifier) * Issue #281: Changed from single active persona to Set for multiple active */ private activePersonas; private currentUser; private indicatorConfig; protected portfolioManager: PortfolioManager; protected fileLockManager: FileLockManager; private personaImporter?; private notifier?; private readonly personasDir; private pathValidatorInitialized; private triggerValidationService; private validationService; private metadataService; private readonly serializationService; constructor(portfolioManager: PortfolioManager, indicatorConfig: IndicatorConfig, fileLockManager: FileLockManager, fileOperationsService: FileOperationsService, validationRegistry: ValidationRegistry, metadataService: MetadataService, personaImporter?: PersonaImporter, notifier?: StateChangeNotifier, baseOptions?: PersonaManagerOptions); /** * Override to return singular form for element type labeling. */ protected getElementLabel(): string; /** * Initialize and load all personas */ initialize(): Promise; /** * Reload personas using BaseElementManager caches */ reload(): Promise; private deriveFilename; private clonePersona; private normalizePersonaForSave; /** * Get all loaded personas as a Map (used by import operations) */ getAllPersonas(): Promise>; /** * Get personas from cache as a Map (synchronous, for DI consumers) */ getPersonas(): Map; /** * List all personas as an array * Ensures all personas have filenames set */ list(): Promise; /** * Reload personas and return MCP-formatted response */ reloadPersonas(): Promise<{ content: { type: string; text: string; }[]; }>; /** * Find a persona by identifier (filename, name, or unique_id) * Uses cached elements from BaseElementManager for synchronous access * Multi-strategy search: filename (with/without .md), name (case-insensitive), unique_id */ /** Cache miss metrics for monitoring persona lookup health (Issue #843) */ private cacheMissMetrics; /** Get cache miss metrics for monitoring and diagnostics */ getCacheMissMetrics(): Readonly; /** * Check if a persona matches an identifier by filename, name, or ID. * Shared matching logic used by both findPersona() and findPersonaAsync(). */ private matchesIdentifier; findPersona(identifier: string): PersonaElement | undefined; /** In-flight disk lookups to prevent duplicate reads for the same identifier */ private pendingLookups; /** * Find a persona with disk fallback when cache misses. * * Issue #843: findPersona() is synchronous and cache-only. After LRU eviction * or direct filesystem edits followed by scan(), personas become invisible even * though the file exists on disk. This async method tries the fast cache path * first, then falls back to BaseElementManager.findByName() which has the full * cache → storage index → direct file load → list scan fallback chain. * * Includes request deduplication: concurrent lookups for the same identifier * share a single disk read to avoid redundant I/O in bridge/swarm scenarios. */ findPersonaAsync(identifier: string): Promise; /** * Perform the actual disk lookup for findPersonaAsync. * Separated to support request deduplication. */ private performDiskLookup; /** * Activate a persona * Issue #281: Now supports multiple active personas (adds to set instead of replacing) * Issue #843: Now async — uses findPersonaAsync() to recover from cache eviction */ activatePersona(identifier: string): Promise<{ success: boolean; message: string; persona?: Persona; }>; /** * Deactivate a specific persona by name * Issue #281: Now requires a name parameter to deactivate specific persona from the set */ deactivatePersona(identifier?: string): { success: boolean; message: string; }; /** * Get the first active persona (for backward compatibility) * Issue #281: With multiple active personas, returns the first one */ getActivePersona(): PersonaElement | null; /** * Get all active personas * Issue #281: New method to get all active personas */ getActivePersonas(): PersonaElement[]; /** * Get identifier for the first active persona (filename). * Issue #281: For backward compatibility, returns first active persona ID */ getActivePersonaId(): string | null; /** * Get all active persona IDs (filenames) * Issue #281: New method to get all active persona IDs */ getActivePersonaIds(): string[]; /** * Check if a specific persona is active * Issue #281: New method to check if a persona is active */ isPersonaActive(identifier: string): boolean; /** * Get persona indicator for responses * Issue #281: Now supports multiple active personas - concatenates all indicators */ getPersonaIndicator(): string; /** * Build persona metadata with default values and optional overrides * * @param validatedInputs - Validated input data * @param metadataOverrides - Optional metadata overrides from user * @returns Complete PersonaMetadata object */ private buildPersonaMetadata; /** * Validate and sanitize all persona creation inputs * SECURITY: Phase 4.3 - Added Unicode validation to all text inputs * SECURITY: Phase 4.1 - Block ALL invalid content, not just critical * SECURITY: Phase 4.7 - Accept normalized content even if Unicode issues were detected and fixed * * @param name - Raw persona name * @param description - Raw persona description * @param instructions - Raw persona instructions * @param triggers - Optional comma-separated trigger words * @returns Validated and sanitized inputs ready for metadata construction * @throws Error if validation fails */ private validatePersonaInputs; /** * Validates metadata keys to prevent prototype pollution * @throws Error if dangerous keys are detected * @private */ private validateMetadataKeys; /** * Create a new persona following the unified element manager pattern. * * This is the primary API for persona creation, replacing the deprecated * `createPersona()` and `createNewPersona()` methods (v2 breaking change). * * @param data - Persona creation options * @param data.name - Display name for the persona (required) * @param data.description - Short description of the persona's purpose * @param data.instructions - Behavioral instructions (alias: content) * @param data.category - Optional category for organization * @param data.triggers - Optional keywords that activate this persona * @returns The created PersonaElement * @throws {Error} If validation fails or persona already exists * * @example * // Basic persona creation * const persona = await personaManager.create({ * name: 'Code Reviewer', * description: 'Expert at reviewing code for quality', * instructions: 'You are a meticulous code reviewer...' * }); * * @example * // With category and triggers * const persona = await personaManager.create({ * name: 'Technical Writer', * description: 'Specializes in clear documentation', * instructions: 'You write clear, concise documentation...', * category: 'professional', * triggers: ['docs', 'documentation', 'readme'] * }); * * @example * // Minimal creation (defaults applied) * const persona = await personaManager.create({ * name: 'Quick Helper' * }); * * @since v2.0.0 - Replaces createPersona() and createNewPersona() */ create(data: Partial & { content?: string; instructions?: string; }): Promise; /** * Edit an existing persona */ editPersona(personaIdentifier: string, field: string, value: string): Promise<{ success: boolean; message: string; isDefault: boolean; newName: string; version: string | undefined; newId: string | undefined; }>; /** * Edit existing persona and return the updated persona (for PersonaHandler compatibility) */ editExistingPersona(persona: PersonaElement, field: string, value: string): Promise; /** * Validate a persona and return a formatted report */ validatePersona(personaIdentifier: string): { success: boolean; message: string; report: import("../index.barrel.js").ElementValidationResult; }; /** * Set current user identity * SECURITY: Phase 4.6 - Added username and email validation */ setUserIdentity(username: string | null, email?: string): void; /** * Get current user identity */ getUserIdentity(): { username: string | null; email: string | null; }; /** * Clear user identity */ clearUserIdentity(): void; /** * Update indicator configuration */ updateIndicatorConfig(config: IndicatorConfig): void; /** * Get current indicator configuration */ getIndicatorConfig(): IndicatorConfig; /** * Helper to get current user for attribution * REFACTORED: Now delegates to MetadataService for consistent user attribution */ getCurrentUserForAttribution(): string; /** * Reset internal state; aligns PersonaManager with DI lifecycle hooks. * CRITICAL: Must call super.dispose() to clean up file watchers and prevent open handles */ dispose(): void; /** * Check if active set cleanup is needed and perform cleanup if necessary * Issue #83: Active element limit enforcement for personas * @private */ private checkAndCleanupActiveSet; /** * Clean up stale entries from active personas set * Issue #83: Validates that all active personas still exist and removes orphaned references * @private */ private cleanupStaleActivePersonas; private notifyPersonaChange; private initializePathValidator; /** * Override delete to handle auto-deactivation before deletion. * FIX: Issue #281 - Unified delete pattern for standard element-crud flow * * @param filePath - The filename of the persona to delete */ delete(filePath: string): Promise; /** * Delete persona by name or identifier (legacy API) * @deprecated Use delete(filename) via standard element-crud flow */ deletePersona(personaIdentifier: string): Promise<{ success: boolean; message: string; }>; importPersona(source: string, overwrite?: boolean): Promise; /** * Convert PersonaMetadata (legacy) to PersonaElementMetadata (new standard) * Ensures strict typing for age_rating and generation_method * NOTE: Filename is infrastructure data, not business metadata - it's excluded here */ private toPersonaElementMetadata; /** * Type guard to check if metadata is PersonaElementMetadata */ private isPersonaElementMetadata; protected parseMetadata(data: any): Promise; protected createElement(metadata: PersonaElementMetadata, bodyText: string): PersonaElement; protected serializeElement(element: PersonaElement): Promise; getFileExtension(): string; importElement(data: string, format?: 'json' | 'yaml' | 'markdown'): Promise; exportElement(persona: PersonaElement, _format?: 'json' | 'yaml' | 'markdown'): Promise; protected canDelete(element: PersonaElement): Promise<{ allowed: boolean; reason?: string; }>; protected beforeSave(persona: PersonaElement): Promise; } export {}; //# sourceMappingURL=PersonaManager.d.ts.map