/** * AgentManager - Refactored to extend BaseElementManager * Manages agent CRUD operations, metadata sanitization, and state persistence. */ import { FileOperationsService } from '../../services/FileOperationsService.js'; import { Agent } from './Agent.js'; import { AgentMetadata, AgentState, ExecuteAgentResult, AgentMetadataV2, AutonomyDirective } from './types.js'; import { type GatheredData } from './gatheredData.js'; import { BaseElementManager } from '../base/BaseElementManager.js'; import { FileLockManager } from '../../security/fileLockManager.js'; import { PortfolioManager } from '../../portfolio/PortfolioManager.js'; import { ValidationRegistry } from '../../services/validation/ValidationRegistry.js'; import { SerializationService } from '../../services/SerializationService.js'; import { MetadataService } from '../../services/MetadataService.js'; import { FileWatchService } from '../../services/FileWatchService.js'; /** Match the sanitized storage identity used by agent definition lookup. */ export declare function normalizeAgentExecutionIdentity(name: string): string; interface ElementCreationResult { success: boolean; message: string; element?: Agent; } export interface ExecutionGenerationObservation { token: object; release: () => void; } type AgentCreateMetadata = (Partial & Partial) & { content?: string; }; export declare class AgentManager extends BaseElementManager { private readonly stateDir; private readonly stateCache; private readonly triggerValidationService; private readonly validationService; private readonly serializationService; private readonly metadataService; /** Per-agent tokens retained only while execution or recovery is in flight. */ private readonly executionGenerations; private readonly activeAgentNames; private static elementManagerResolver?; private static dangerZoneEnforcerResolver?; private static verificationStoreResolver?; constructor(portfolioManager: PortfolioManager, fileLockManager: FileLockManager, baseDir: string, fileOperationsService: FileOperationsService, validationRegistry: ValidationRegistry, serializationService: SerializationService, metadataService: MetadataService, fileWatchService?: FileWatchService, memoryBudget?: import('../../cache/CacheMemoryBudget.js').CacheMemoryBudget, backupService?: import('../../services/BackupService.js').BackupService); protected getElementLabel(): string; /** * Configure the element manager resolver for element-agnostic activation * This is called by the DI container during initialization * Follows the same pattern as Memory.configureMemoryManagerResolver * * @param resolver Function that takes a manager name and returns the manager instance */ static setElementManagerResolver(resolver: (managerName: string) => any): void; /** * Issue #402: Set DangerZoneEnforcer resolver for DI injection. * Called by the DI container during initialization. */ static setDangerZoneEnforcerResolver(resolver: () => import('./types.js').DangerZoneBlocker): void; /** * Issue #142: Set VerificationStore resolver for DI injection. * Called by the DI container during initialization. */ static setVerificationStoreResolver(resolver: () => { set: (id: string, challenge: { code: string; expiresAt: number; reason: string; }) => void; }): void; /** * Get the element manager resolver * @private */ private static getElementManagerResolver; /** * Reset static resolvers (for test cleanup) * Call this in afterEach hooks to prevent test isolation issues */ static resetResolvers(): void; /** * Prepare directory structure for agents and state files. */ initialize(): Promise; /** * Create a new agent on disk. */ create(name: string, description: string, content: string, metadata?: AgentCreateMetadata): Promise; /** * Read an agent by name (without extension). * * @param name - Agent name (without extension) * @returns Agent instance or null if not found */ read(name: string): Promise; /** * Read an agent while selecting whether state-storage failures are fatal. * Security-sensitive callers use strict state reads so unavailable durable * state cannot be mistaken for an agent with no active goals. */ private readWithStatePolicy; /** * Fallback for read() when direct file lookup fails. * Searches loaded agents by metadata name using case-insensitive and slug matching. * Logs a warning when a match is found (indicates filename/name mismatch needing cleanup). * * @example * // File on disk: "legacy-poster-agent.md" * // Metadata name: "legacy-poster" * // Direct lookup for "legacy-poster.md" fails (ENOENT) * // Flexible fallback matches via metadata name: * const agent = await read("legacy-poster"); // resolves via fallback */ private readFlexibly; /** * Enumerate fallback candidates without creating missing storage. Strict * recovery propagates listing failures; ordinary reads retain the portfolio * API's empty-directory and unavailable-directory compatibility behavior. */ private listForFlexibleRead; /** * Update metadata/content for an existing agent. */ update(name: string, updates: Partial, content?: string): Promise; /** * Validate a provided agent name. */ validateName(name: string): { valid: boolean; error?: string; }; /** * Import an agent from serialized content. */ importElement(data: string, format?: 'json' | 'yaml' | 'markdown'): Promise; /** * Export an agent to JSON or markdown (default). */ exportElement(agent: Agent, format?: 'json' | 'yaml' | 'markdown'): Promise; /** * Load an agent file, enforcing size and format checks. */ load(filePath: string): Promise; private loadAgentFile; /** * Override BaseElementManager.save to persist state when required. */ save(agent: Agent, filePath: string): Promise; /** * Persist agent state to disk (public API for external callers). * * This method is the proper way for external code (strategies, handlers) to * trigger state persistence. It implements the Option C pattern from Issue #123: * stateVersion is only incremented on successful save. * * @param name - Agent name * @returns Promise - True if state was persisted, false if not needed * @throws Error if agent not found or save fails */ persistState(name: string): Promise; /** * Override delete to remove associated state file. * * FIX: Uses normalizeFilename() to ensure state file deletion matches * the normalized filename used for state file creation/loading. */ delete(filePath: string): Promise; exists(filePath: string): Promise; validatePath(targetPath: string): boolean; getFileExtension(): string; /** * Override list to apply active status based on activeAgentNames set */ list(): Promise; /** * Activate an agent by name or identifier * * 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 */ activateAgent(identifier: string): Promise<{ success: boolean; message: string; agent?: Agent; }>; /** * Deactivate an agent by name or identifier * * Issue #24 (LOW PRIORITY): Performance optimization using findByName() * Issue #24 (LOW PRIORITY): Consistent error messages using ElementMessages */ deactivateAgent(identifier: string): Promise<{ success: boolean; message: string; }>; /** * Get all active agents */ getActiveAgents(): Promise; /** * Execute an agent with goal parameters * * Returns context for LLM to drive the agentic loop. * This method: * 1. Loads the agent configuration * 2. Validates and renders the goal template with parameters * 3. Activates configured elements (element-agnostic) * 4. Evaluates programmatic constraints and risk * 5. Returns structured context for LLM * * The LLM then drives the agentic loop using this context. * * @since v2.0.0 - Agentic Loop Redesign */ executeAgent(name: string, parameters: Record, context?: { operationName?: 'execute_agent' | 'continue_execution'; }): Promise; /** Hold a bounded observation lease while stale-policy recovery is in flight. */ observeExecutionGeneration(name: string): ExecutionGenerationObservation; hasExecutionGenerationChanged(name: string, observedToken: object): boolean; private beginExecutionAttempt; private endExecutionAttempt; private getOrCreateExecutionGeneration; private cleanupExecutionGeneration; /** * Security validation for template parameters (Issue #103). * Checks for prototype pollution, Unicode injection, and oversized payloads. * Must be called BEFORE template rendering. * @private */ private validateParameterSecurity; /** * Validate parameters against goal parameter schema * @private */ private validateParameters; /** * Build an actionable missing-parameter error for execute/continue calls. * @private */ private formatMissingRequiredParametersError; /** * Render goal template by replacing {parameter} placeholders * @private */ private renderGoalTemplate; /** * Detect unmatched {placeholder} patterns remaining after template rendering. * Returns array of placeholder names found in the rendered string. * Issue #126: Warn when template parameters are missing. * @private */ private detectUnmatchedPlaceholders; /** * Formats a consistent error message for circular activation detection. * Used by both the static pre-flight check and the runtime chain check. */ private static formatCircularActivationError; /** Safety limits for activation graph traversal */ private static readonly MAX_ACTIVATION_DEPTH; private static readonly MAX_NODES_VISITED; /** * Static activation cycle detection — Layer 1 of dual-detection strategy (Issue #374) * * Performs async DFS on the activation graph to detect cycles BEFORE execution begins. * This is a pre-flight check that prevents circular activation chains from being attempted. * * Layer 1 (this method): Static graph analysis — walks the activation graph from the root * agent, loading each agent's `activates.agents` metadata. Catches all cycles reachable * from the root without executing any agents. Bounded by MAX_ACTIVATION_DEPTH and * MAX_NODES_VISITED to prevent resource exhaustion from large acyclic graphs. * * Layer 2 (getElementContent runtime check): Defense-in-depth — guards against edge cases * like dynamically-constructed activation chains or agents modified between the static * check and actual execution. See getElementContent for details. * * @param rootName - The root agent name initiating the activation chain * @param activatedAgents - The immediate agents activated by the root * @returns The cycle path array if a cycle exists, null otherwise * @private */ private detectActivationCycles; /** * Get content from an element (element-agnostic) * @private */ private getElementContent; /** * 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 agents set * Issue #24 (LOW PRIORITY): Memory leak prevention * @private */ private cleanupStaleActiveAgents; /** * Persist an agent state file (YAML). * * IMPROVEMENT: Optimistic locking with version checking (Issue #24) * Prevents state corruption from concurrent updates by comparing versions * * FIX: Uses normalizeFilename() for consistent state file naming * This ensures state files use kebab-case (e.g., "crudv-agent-delta.state.yaml") * regardless of input name format (e.g., "CRUDV-Agent-Delta") * * FIX (Issue #107 - CRIT-2): Wrap read-compare-write sequence in file lock * to prevent TOCTOU race condition from concurrent agent executions * * FIX (Issue #123): Version increments on successful save, not during operations * * @returns The new state version after successful save * @protected Only accessible by subclasses (e.g., TestableAgentManager for testing) */ protected saveAgentState(name: string, state: AgentState, strictExistingStateErrors?: boolean): Promise; /** * Utility: ensure `.md` extension on requested path. */ private stripExtension; private hydrateAgentState; private loadAgentState; /** * Distinguish a legitimately absent per-agent state file from unavailable * state storage. Strict recovery may treat only the former as empty state. */ private assertStateDirectoryAvailable; private ensureStateDirectory; protected parseMetadata(data: any): Promise; protected createElement(metadata: AgentMetadata, bodyContent: string): Agent; protected serializeElement(agent: Agent): Promise; private buildDefaultInstructions; private buildDefaultBody; /** * Select the text that should satisfy create-time content validation. * Behavioral instructions remain the primary source, while reference content * acts as the fallback for content-only agent creation. */ private getPrimaryValidationText; /** * Issue #727: Validate V2 agent fields at write time. * * Normalizes snake_case → camelCase (same as parseMetadata) and then validates * structural constraints. Unlike parseMetadata (which silently strips for * backward compat), this returns errors so the caller can reject the create. * * Mutates the metadata in place (normalization), returns validation errors. */ private validateV2FieldsForCreate; /** * Normalize goal input to V2 format. * LLMs may pass goal as a simple string or as a V2 config object. * This ensures we always have a proper V2 structure before validation. * * @param goal - Either a string goal or a V2 goal config object * @returns Normalized V2 goal config, or undefined if no goal provided */ private normalizeGoalInput; private parseAgentFile; /** * Get the filename for an agent element. * * Uses inherited normalizeFilename() from BaseElementManager for consistent * filename formatting across all element managers. * * Examples: * "Creative Writer" -> "creative-writer.md" * "CRUDV-Agent-Delta" -> "crudv-agent-delta.md" * "Multi_Goal_Agent" -> "multi-goal-agent.md" * "CamelCaseName" -> "camel-case-name.md" */ private getFilename; private validateElementName; /** * Get current user for attribution * REFACTORED: Delegates to MetadataService for consistent user attribution across all managers */ private getCurrentUserForAttribution; private prepareStateForSerialization; private normalizeLoadedState; /** * Record a step in agent execution * Wraps Agent.recordDecision() for the MCP tool interface * * @since v2.0.0 - Agentic Loop Redesign */ recordAgentStep(params: { agentName: string; stepDescription: string; outcome: "success" | "failure" | "partial"; /** Optional findings or results from this step */ findings?: string; confidence?: number; /** Optional hint about what the LLM plans to do next (for proactive risk evaluation) */ nextActionHint?: string; /** Optional risk score for the current step (0-100) */ riskScore?: number; /** Runtime override for maxAutonomousSteps (Issue #447) */ maxStepsOverride?: number; }): Promise<{ success: boolean; message: string; decision: { id: string; goalId: string; timestamp: string; decision: string; reasoning: string; framework: string; confidence: number; outcome: "success" | "failure" | "partial"; }; state: { goalCount: number; decisionCount: number; lastActive: string; stateVersion: number; }; /** Autonomy directive indicating whether to continue or pause */ autonomy: AutonomyDirective; }>; /** * Complete an agent goal * Wraps Agent.completeGoal() for the MCP tool interface * * @since v2.0.0 - Agentic Loop Redesign */ completeAgentGoal(params: { agentName: string; goalId?: string; outcome: "success" | "failure" | "partial"; summary: string; }): Promise<{ success: boolean; message: string; goal: { id: string; description: string; status: "completed" | "failed"; createdAt: string; completedAt: string; estimatedEffort?: number; actualEffort?: number; }; metrics: { successRate: number; goalsCompleted: number; goalsInProgress: number; decisionAccuracy: number; averageCompletionTime: number; }; state: { goalCount: number; decisionCount: number; lastActive: string; stateVersion: number; }; }>; /** Complete an exact durable goal without consulting ordinary state caches. */ completeAgentGoalForRecovery(params: { agentName: string; goalId: string; outcome: "success" | "failure" | "partial"; summary: string; }): Promise<{ success: boolean; message: string; goal: { id: string; description: string; status: "completed" | "failed"; createdAt: string; completedAt: string; estimatedEffort?: number; actualEffort?: number; }; metrics: { successRate: number; goalsCompleted: number; goalsInProgress: number; decisionAccuracy: number; averageCompletionTime: number; }; state: { goalCount: number; decisionCount: number; lastActive: string; stateVersion: number; }; }>; private completeAgentGoalWithStatePolicy; /** * Get agent state * Wraps Agent.getState() for the MCP tool interface * * @since v2.0.0 - Agentic Loop Redesign */ getAgentState(params: { agentName: string; includeDecisionHistory?: boolean; includeContext?: boolean; }): Promise<{ success: boolean; agentName: string; state: { goals: Array<{ id: string; description: string; priority: string; status: string; importance: number; urgency: number; eisenhowerQuadrant?: string; createdAt: string; updatedAt: string; completedAt?: string; dependencies?: string[]; riskLevel?: string; estimatedEffort?: number; actualEffort?: number; notes?: string; }>; decisions: Array<{ id: string; goalId: string; timestamp: string; decision: string; reasoning: string; framework: string; confidence: number; outcome?: string; }>; context?: Record; contextSummary: { keys: string[]; size: number; }; lastActive: string; sessionCount: number; stateVersion: number; }; metrics: { successRate: number; goalsCompleted: number; goalsInProgress: number; decisionAccuracy: number; averageCompletionTime: number; }; }>; /** Strict durable-state variant reserved for stale-policy recovery. */ getAgentStateForRecovery(params: { agentName: string; includeDecisionHistory?: boolean; includeContext?: boolean; }): Promise<{ success: boolean; agentName: string; state: { goals: Array<{ id: string; description: string; priority: string; status: string; importance: number; urgency: number; eisenhowerQuadrant?: string; createdAt: string; updatedAt: string; completedAt?: string; dependencies?: string[]; riskLevel?: string; estimatedEffort?: number; actualEffort?: number; notes?: string; }>; decisions: Array<{ id: string; goalId: string; timestamp: string; decision: string; reasoning: string; framework: string; confidence: number; outcome?: string; }>; context?: Record; contextSummary: { keys: string[]; size: number; }; lastActive: string; sessionCount: number; stateVersion: number; }; metrics: { successRate: number; goalsCompleted: number; goalsInProgress: number; decisionAccuracy: number; averageCompletionTime: number; }; }>; private getAgentStateWithPolicy; /** * Get gathered data for a specific goal execution. * * Aggregates decision history, goal state, and summary statistics * into a structured GatheredData object. This is a read-side view * over existing agent state data. * * Issue #68: GatheredDataEntry for state recording * @since v2.0.0 - Agentic Loop Completion (Epic #380) */ getGatheredData(params: { agentName: string; goalId: string; }): Promise; /** * Continue agent execution from previous state * Combines executeAgent with state context * * @since v2.0.0 - Agentic Loop Redesign */ continueAgentExecution(params: { agentName: string; parameters?: Record; previousStepResult?: string; }): Promise; recentDecisions: Array<{ decision: string; reasoning: string; outcome?: string; timestamp: string; }>; sessionCount: number; lastActive: string; stateVersion: number; }; continuation: { isResuming: boolean; previousStepResult?: string; suggestedNextSteps?: string[]; }; }>; /** * Generate suggested next steps based on current state * @private */ private generateNextSteps; } export {}; //# sourceMappingURL=AgentManager.d.ts.map