/** * MCPAQLHandler - Unified handler for all MCP-AQL operations * * ARCHITECTURE: * - Thin resolver pattern: validates → routes → dispatches * - SOLID principles: depends on abstractions (HandlerRegistry interface) * - Defense in depth: Gatekeeper validates endpoint/operation matching and enforces policies * - Dispatch pattern: routes handler references to actual method calls * * OPERATION FLOW: * 1. Validate input structure (type guards) * 2. Validate permissions (Gatekeeper) * 3. Route operation (OperationRouter) * 4. Dispatch to handler (resolveHandlerReference) * 5. Return standardized result (OperationResult) * * ERROR HANDLING: * All errors are caught and returned as OperationFailure with: * - success: false * - error: human-readable message * - data: never (discriminated union enforces this) */ import { Gatekeeper } from './Gatekeeper.js'; import { OperationResult, BatchResult } from './types.js'; import type { DangerZoneEnforcer } from '../../security/DangerZoneEnforcer.js'; import type { VerificationStore } from '@dollhousemcp/safety'; import type { IVerificationNotifier } from '../../services/VerificationNotifier.js'; import type { ElementCRUDHandler } from '../ElementCRUDHandler.js'; import type { MemoryManager } from '../../elements/memories/MemoryManager.js'; import { type AgentManager } from '../../elements/agents/AgentManager.js'; import type { TemplateRenderer } from '../../utils/TemplateRenderer.js'; import type { ElementQueryService } from '../../services/query/ElementQueryService.js'; import type { CollectionHandler } from '../CollectionHandler.js'; import type { PortfolioHandler } from '../PortfolioHandler.js'; import type { GitHubAuthHandler } from '../GitHubAuthHandler.js'; import type { ConfigHandler } from '../ConfigHandler.js'; import type { EnhancedIndexHandler } from '../EnhancedIndexHandler.js'; import type { PersonaHandler } from '../PersonaHandler.js'; import type { SyncHandler } from '../SyncHandlerV2.js'; import type { BuildInfoService } from '../../services/BuildInfoService.js'; import type { MemoryLogSink } from '../../logging/sinks/MemoryLogSink.js'; import type { PerformanceMonitor } from '../../utils/PerformanceMonitor.js'; import type { OperationMetricsTracker } from '../../metrics/OperationMetricsTracker.js'; import type { GatekeeperMetricsTracker } from '../../metrics/GatekeeperMetricsTracker.js'; import type { AutonomyMetricsSnapshot } from '../../elements/agents/autonomyEvaluator.js'; /** * Verification metrics tracker following DangerZoneEnforcer.getMetrics() pattern. * Tracks success/failure/expiry rates and time-to-verify. */ export interface VerificationMetrics { /** Total verify_challenge calls since startup */ totalAttempts: number; /** Successful verifications */ totalSuccesses: number; /** Failed verifications (wrong code) */ totalFailures: number; /** Expired challenge attempts */ totalExpired: number; /** Invalid format attempts (rejected before store lookup) */ totalInvalidFormat: number; /** Rate-limited attempts */ totalRateLimited: number; /** Average time from challenge creation to successful verify (ms) */ averageTimeToVerifyMs: number; /** Current failures in rate-limit window */ failuresInCurrentWindow: number; } /** * Handler registry interface for dependency injection. * Abstracts the concrete handler types for better testability and decoupling. */ export interface HandlerRegistry { elementCRUD: ElementCRUDHandler; memoryManager: MemoryManager; agentManager: AgentManager; templateRenderer: TemplateRenderer; elementQueryService: ElementQueryService; collectionHandler?: CollectionHandler; portfolioHandler?: PortfolioHandler; authHandler?: GitHubAuthHandler; configHandler?: ConfigHandler; enhancedIndexHandler?: EnhancedIndexHandler; personaHandler?: PersonaHandler; syncHandler?: SyncHandler; buildInfoService?: BuildInfoService; cacheMemoryBudget?: import('../../cache/CacheMemoryBudget.js').CacheMemoryBudget; gatekeeper: Gatekeeper; dangerZoneEnforcer?: DangerZoneEnforcer; verificationStore?: VerificationStore; verificationNotifier?: IVerificationNotifier; memorySink?: MemoryLogSink; metricsSink?: import('../../metrics/sinks/MemoryMetricsSink.js').MemoryMetricsSink; performanceMonitor?: PerformanceMonitor; operationMetricsTracker?: OperationMetricsTracker; gatekeeperMetricsTracker?: GatekeeperMetricsTracker; } /** * MCPAQLHandler - Unified entry point for all MCP-AQL operations * * This handler implements the thin resolver pattern: * 1. Validates that operations are called via correct CRUD endpoints * 2. Routes operations to their handler references * 3. Dispatches to actual handler methods * 4. Returns standardized OperationResult * * DESIGN RATIONALE: * - Single Responsibility: Only orchestrates, delegates actual work * - Dependency Inversion: Depends on HandlerRegistry interface * - Interface Segregation: HandlerRegistry exposes only what's needed * - Open/Closed: New operations added to router, not this class */ /** * Minimal interface for correlation ID retrieval. * Keeps coupling loose — only requires what MCPAQLHandler actually needs. * Issue #301: Request correlation support. */ export interface CorrelationIdProvider { getCorrelationId(): string | undefined; } export declare class MCPAQLHandler { private readonly handlers; private readonly contextTracker?; private readonly gatekeeper; /** * Issue #656: Per-memory save debounce timers. * When addEntry is called rapidly, we coalesce saves — only the latest * state is written to disk after the debounce window expires. * Key: normalized memory name, Value: { timer, memory, manager } */ private readonly pendingSaves; /** Issue #656: Debounce metrics — tracks saves coalesced vs actually written. */ private readonly debounceMetrics; /** * Issue #2329: Memories whose most recent save attempt failed. * Key: normalized memory name (memorySaveKey). Holds the failing Memory * instance and its manager — the unpersisted entries exist only in that * instance, so recovery must retry it (a freshly loaded instance would lack * them), and holding the reference keeps it alive across cache eviction. * The next addEntry retries the save synchronously and reports the error to * the caller instead of silently accepting more entries; dispose() flushes * these as a last resort. */ private readonly failedMemorySaves; /** * Issue #2329: monotonically increasing save-attempt counter per memory key. * Guards the failure ledger against reordering: an older in-flight save that * resolves after a newer one started must not overwrite the newer attempt's * outcome. */ private readonly memorySaveAttempts; /** * Issue #657: Per-memory save frequency tracker. * Sliding window counter: tracks addEntry calls per memory within the monitor window. * Logs warnings at configurable thresholds to catch runaway loops early. */ private readonly saveFrequencyCounters; /** Issue #625 Phase 4: Rate limiter for permission_prompt evaluations (configurable via env) */ private readonly permissionPromptLimiter; /** Issue #625 Phase 4: Rate limiter for CLI approval record creation (configurable via env) */ private readonly cliApprovalLimiter; /** * Build a standardized rate-limit deny response for permission_prompt. */ private buildRateLimitDeny; /** Issue #142: Rate limiter for verify_challenge attempts (max 10 failures per 60s window) */ private readonly verificationRateLimiter; /** Issue #142: Metrics tracker for verification operations */ private readonly verificationMetrics; /** * Tracks agents currently in an execution loop for Gatekeeper policy enforcement. * * **Lifecycle:** Entries are added in `dispatchExecute()` on `execute_agent` and * removed on `complete_execution`. Only agents with a gatekeeper policy (explicit * or synthesized from `tools` config) are tracked. * * **Policy resolution:** Entries are included in `getActiveElements()` so the * Gatekeeper evaluates agent policies alongside persona/skill/ensemble policies. * The standard priority applies: deny > scope_restriction > confirm > allow. * * **Memory safety:** The Map is bounded by concurrently executing agents. If a * session ends without `complete_execution`, the Map is garbage collected with * the MCPAQLHandler instance. * * Issue #449 */ private readonly executingAgents; /** * Set of aborted goalIds. Once a goalId is aborted, all further execution * operations (record_execution_step, complete_execution, continue_execution) * for that goalId are rejected at the dispatch layer. * * Issue #249: Abort/cancellation infrastructure. */ private readonly abortedGoals; constructor(handlers: HandlerRegistry, contextTracker?: CorrelationIdProvider | undefined); /** * Get verification metrics for monitoring/diagnostics. * Follows the same pattern as DangerZoneEnforcer.getMetrics(). */ getVerificationMetrics(): VerificationMetrics; /** * Get autonomy evaluation metrics for monitoring/diagnostics. * Issue #391: Follows the same pattern as getVerificationMetrics(). */ getAutonomyMetrics(): AutonomyMetricsSnapshot; /** * Gather currently active elements for Gatekeeper policy evaluation. * * Queries PersonaManager, SkillManager, and EnsembleManager for active elements, * then appends any currently executing agents with gatekeeper policies. All * elements are mapped to the {@link ActiveElement} interface expected by * `Gatekeeper.enforce()`. * * Issue #452: Provides element context for Layer 2 (element policy resolution). * Issue #449: Includes executing agents alongside personas/skills/ensembles. * * @returns Array of active elements with their gatekeeper policies, or empty * array if gathering fails (fail-open: only route policies apply). */ private getActiveElements; private getPolicyReportElements; private copyGatekeeperDiagnostics; /** * Handle CREATE operations (additive, non-destructive) * * CREATE endpoint operations: * - create_element: Create new elements * - import_element: Import elements from exported data * - addEntry: Add entries to memory elements * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleCreate(input: unknown): Promise; /** * Handle READ operations (read-only, safe) * * READ endpoint operations: * - list_elements: List elements with filtering * - get_element: Get element by name * - get_element_details: Get detailed element information * - search_elements: Full-text search * - query_elements: Query with pagination * - get_active_elements: Get active elements * - validate_element: Validate element * - render: Render template * - export_element: Export element * - activate_element: Activate elements for use * - deactivate_element: Deactivate element * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleRead(input: unknown): Promise; /** * Handle UPDATE operations (modifying existing state) * * UPDATE endpoint operations: * - edit_element: Modify existing elements * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleUpdate(input: unknown): Promise; /** * Handle DELETE operations (destructive actions) * * DELETE endpoint operations: * - delete_element: Delete elements * - clear: Clear memory entries * - clear_github_auth: Remove GitHub authentication * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleDelete(input: unknown): Promise; /** * Handle EXECUTE operations (runtime execution lifecycle) * * EXECUTE endpoint operations: * - execute_agent: Start execution of an agent or executable element * - get_execution_state: Query current execution state * - record_execution_step: Record execution progress or findings * - complete_execution: Signal execution finished successfully * - continue_execution: Resume execution from saved state * - abort_execution: Cancel an ongoing execution * * Unlike CRUD operations (which are idempotent), EXECUTE operations manage * runtime state and are inherently non-idempotent. Calling execute twice * creates two separate executions. * * Supports batch operations when input contains `operations` array. * * @param input - Operation input with operation name and params, or BatchRequest * @returns OperationResult with success/failure status, or BatchResult for batch operations */ handleExecute(input: unknown): Promise; /** * Core execution logic shared by all CRUD endpoints. * Implements the thin resolver pattern: validate → route → dispatch → return. * * OPERATION FLOW: * 1. Validate input structure * 2. Validate permissions (PermissionGuard) * 3. Route operation (OperationRouter) * 4. Dispatch to handler * 5. Return standardized result * * @param input - Raw input to validate and process * @param endpoint - CRUD endpoint being called * @returns Standardized OperationResult */ private executeOperation; /** * Execute a batch of operations sequentially. * Operations are executed in order, and failures do not stop execution. * * EXECUTION SEMANTICS: * - Operations run sequentially (in order) * - Each operation is validated independently * - Failed operations don't stop the batch * - All results are collected and returned * * @param batch - BatchRequest with array of operations * @param endpoint - CRUD endpoint being called * @returns BatchResult with all operation results and summary */ private executeBatch; /** * Dispatch a handler reference to the actual handler method. * * Handler reference format: "Module.method" * Examples: * - "ElementCRUD.create" → this.handlers.elementCRUD.createElement(...) * - "Memory.addEntry" → this.handlers.memoryManager.addEntry(...) * - "Agent.execute" → this.handlers.agentManager.executeAgent(...) * * DISPATCH STRATEGY (Issue #247): * 1. Check if operation is schema-driven → use SchemaDispatcher * 2. Otherwise fall through to legacy module-based dispatch * * Schema-driven operations benefit from: * - Declarative configuration (no switch statements) * - Auto-generated parameter validation * - Single source of truth for operation metadata * * @param handlerRef - Handler reference in "Module.method" format * @param input - Validated operation input * @returns Promise resolving to operation-specific data * @throws Error if handler reference is unknown or method fails */ private dispatch; /** * Dispatch ElementCRUD operations to ElementCRUDHandler */ private dispatchElementCRUD; /** * Handle element import operation. * Parses export package, deserializes element data, and creates the element. * * @param params - Operation parameters containing data and optional overwrite flag * @returns Promise resolving to created element * @throws Error if export package is invalid or element already exists (when overwrite is false) */ private handleImportElement; /** * Export an element to JSON or YAML format * * @param name - Element name to export * @param type - Element type (persona, skill, template, agent, memory, ensemble) * @param format - Export format: 'json' or 'yaml' (default: 'json') * @returns ExportPackage with serialized element data */ private handleExportElement; /** * Dispatch Memory operations to MemoryManager * * Memory operations work on individual Memory instances: * 1. Get the Memory by name from MemoryManager * 2. Call the operation on the Memory instance */ /** * Normalized key shared by pendingSaves, failedMemorySaves, memorySaveAttempts, * and saveFrequencyCounters. All memory-save bookkeeping must use this — a * mismatched key silently disconnects the failure ledger from recovery (#2329). */ private memorySaveKey; /** * Issue #2329 (Codex review): a successfully deleted memory must drop its * save bookkeeping — a retained failure-ledger instance or pending debounce * timer would otherwise re-save the in-RAM state and resurrect the deleted * file. Called after dispatch in executeOperation so the schema-dispatch, * legacy, and batch paths are all covered. */ private cleanupDeletedMemoryBookkeeping; /** * Issue #2329 (Codex review): drop all save bookkeeping for a deleted memory. * Without this, the failure ledger keeps the deleted memory's in-RAM instance * and the flush/retry paths re-save it, resurrecting the deleted file. A save * already in flight when the delete lands can still race the file back — that * narrow window is inherent to fire-and-forget writes and unchanged here. */ private clearMemorySaveBookkeeping; /** * Issue #2329: save a memory with failure-ledger bookkeeping. On failure the * ledger records the error AND the failing instance (its unpersisted entries * exist nowhere else); on success the record clears. The attempt counter makes * the newest-started save win: an older in-flight save resolving late cannot * erase a newer failure. Rethrows the save error. */ private saveMemoryTracked; /** * Issue #656: Debounce memory saves to prevent file descriptor exhaustion. * Coalesces rapid addEntry calls — only writes the latest state after the window expires. */ private debouncedMemorySave; /** * Issue #657: Track addEntry call frequency per memory and log warnings at thresholds. * * Uses a sliding window counter to detect runaway save loops before they exhaust resources. * Counters are bounded: max 500 tracked memories, oldest-first eviction when exceeded. * Warning/critical flags auto-reset when frequency drops below thresholds. * * @param memoryName - Name of the memory being written to */ private trackSaveFrequency; /** * Issue #656: Flush all pending debounced saves on shutdown. * Called by DI container dispose chain to ensure no data is lost. */ dispose(): Promise; /** * Issue #656: Flush all pending debounced saves immediately. * Issue #2329: also retries memories whose earlier deferred save failed and * that have no pending timer — their dirty state exists only in RAM and this * is the last chance to persist it before shutdown. */ flushPendingSaves(): Promise; private dispatchMemory; /** * Fix #387: resolve the addEntry 'content' parameter, accepting 'entry' as an * alias and throwing a contextual error when the text portion is missing. */ private extractAddEntryContent; /** * addEntry with the #2329 persistence guarantees: recover from a prior failed * save, mutate the authoritative instance, verify persistability before * reporting success (rolling back on failure), then schedule the debounced save. */ private handleMemoryAddEntry; /** * Dispatch Agent operations to AgentManager */ private dispatchAgent; /** * Dispatch Template operations to TemplateRenderer */ private dispatchTemplate; /** * Dispatch Activation operations (cross-cutting concern) */ private dispatchActivation; /** * Dispatch Search operations to ElementQueryService */ private dispatchSearch; /** * Dispatch Introspection operations to IntrospectionResolver */ private dispatchIntrospection; /** * Search elements across one or all element types * * Implements simple case-insensitive substring matching on: * - metadata.name * - metadata.description * - content (if available) * * @param input - Operation input with optional elementType and search params * @returns Search results with matched elements and relevance info */ private handleSearchElements; /** * Dispatch Collection operations to CollectionHandler */ private dispatchCollection; /** * Dispatch Portfolio operations to PortfolioHandler */ private dispatchPortfolio; /** * Dispatch Auth operations to GitHubAuthHandler */ private dispatchAuth; /** * Dispatch Config operations to ConfigHandler and BuildInfoService */ private dispatchConfig; /** * Dispatch EnhancedIndex operations to EnhancedIndexHandler */ private dispatchEnhancedIndex; /** * Dispatch Persona operations to PersonaHandler */ private dispatchPersona; private challengeIsForDeadlockRelief; private issueDeadlockReliefChallenge; private completeDeadlockRelief; /** * Dispatch Gatekeeper operations for confirmation management. * * Issue #452: Handles the confirm_operation flow where users approve * operations that require confirmation per Gatekeeper policy. * * Flow: * 1. Gatekeeper.enforce() returns confirmationPending: true * 2. LLM calls confirm_operation with the operation name * 3. This method records the confirmation in the session * 4. LLM retries the original operation, which now passes via Layer 3 */ private dispatchGatekeeper; /** * Dispatch Logging operations (Issue #528 - CRUDE migration) * * Routes query_logs through the unified CRUDE pipeline instead of * a standalone MCP tool, providing operation routing, gatekeeper * policy enforcement, and structured response format. */ private dispatchLogging; /** * Dispatch Metrics operations. * * Routes query_metrics through the unified CRUDE pipeline, providing * operation routing, gatekeeper policy enforcement, and structured response format. */ private dispatchMetrics; /** * Dispatch Browser operations (Issue #774: open portfolio browser) */ /** * Dispatch Browser operations. * * Starts the portfolio web server (if not running) and opens the * system browser. Returns the URL and any warnings if the browser * could not be opened automatically. * * @param method - The browser method to dispatch ('open') * @returns MCP response with URL and optional warning * @see Issue #774 */ /** * Extract URL query parameters from operation params. * Serializes all params except 'tab' as string key-value pairs. * @see Issue #1765 - URL parameter support for portfolio browser */ private static extractUrlParams; /** Serialize a param value to a URL-safe string. */ private static serializeParamValue; private dispatchBrowser; /** * Look up the CRUDE endpoint for a given operation name. * Used by dispatchGatekeeper to determine the correct endpoint context. */ private getEndpointForOperation; /** * Build a human-readable summary of an operation for confirmation prompts. * Issue #748: Natural language confirmation summaries. * * Translates raw operation names and params into plain language so users * understand what they're approving without parsing machine-readable payloads. */ private buildOperationSummary; /** * Score the risk of an MCP-AQL operation for auto-confirm audit trails. * * Returns a score from 0-100 based on operation characteristics: * - DELETE endpoint operations: base 80 (destructive, data loss) * - EXECUTE endpoint operations: base 60 (unpredictable side effects) * - UPDATE endpoint operations: base 40 (modifies existing data) * - CREATE endpoint operations: base 20 (additive, low risk) * * Modifiers: * - Operations with canBeElevated: false get +10 (structurally dangerous) * - Operations targeting gatekeeper fields get +10 (privilege escalation vector) * * @param operation - The operation name * @param endpoint - The CRUDE endpoint * @param params - Operation parameters (for field inspection) * @returns Risk score 0-100 */ private scoreOperationRisk; /** * Dispatch Execute operations for execution lifecycle management (Issue #244) * * Execute operations handle runtime state of executable elements: * - execute: Start a new execution * - getState: Query current execution state * - updateState: Record progress, findings, step completion * - complete: Signal successful completion * - continue: Resume from saved state * - abort: Cancel an ongoing execution * * These operations are non-idempotent by nature (unlike CRUD operations). * Currently routes to AgentManager, but designed for future extensibility * to workflows, pipelines, and other executable element types. */ private dispatchExecute; /** Maximum recent blocks tracked per agent to prevent unbounded growth */ private static readonly MAX_RECENT_BLOCKS; /** * Record a gatekeeper block against all currently executing agents. * Called when Gatekeeper.enforce() denies an operation, so that * record_execution_step can report it as a notification. * * @since v2.1.0 - Agent Notification System * @private */ private recordGatekeeperBlockForAgents; /** * Collect notifications for an executing agent from all sources: * 1. Unreported gatekeeper blocks → permission_pending * 2. Autonomy pause (continue=false) → autonomy_pause * 3. DangerZone blocked agents (system-wide broadcast) → danger_zone * * Gatekeeper blocks are marked as "reported" after first collection * so they don't repeat on subsequent calls. DangerZone and autonomy * notifications are always emitted while the condition persists. * * @since v2.1.0 - Agent Notification System * @private */ private collectNotifications; /** * Evaluate resilience policy after a step result and potentially override * the autonomy directive to enable auto-continuation or retry. * * Returns a modified step result if resilience kicks in, or null to use * the original result (default behavior). * * @since v2.1.0 - Agent Execution Resilience (Issue #526) * @private */ private evaluateResilience; /** * Get active goal IDs for an agent (Issue #249). * Queries the agent's state to find in-progress goals. * @private */ private getActiveGoalIds; /** * Get the current correlation ID from the underlying ContextTracker. * Exposed for use by UnifiedEndpoint and other wrappers. * Issue #301. */ getCorrelationId(): string | undefined; /** * Build response metadata with correlation ID and timing. * Issue #301: Request IDs and distributed tracing support. */ private buildMeta; /** * Create a successful operation result */ private success; /** * Create a failed operation result */ private failure; /** * Apply field selection and name transformation to handler response. * * Processes the `fields` parameter to filter response data: * - If fields is an array, filter to those fields only * - If fields is a preset string ('minimal', 'standard', 'full'), use preset * - If fields is not provided, transform names only (name → element_name) * * For responses with `results` or `items` arrays, field selection is applied * to each item in the array rather than filtering the container object. * * @param result - The raw handler result * @param params - Original params containing optional `fields` * @returns Transformed result with field selection applied */ private applyFieldSelection; /** * Transform response with awareness of nested arrays. * Applies name transformation to items within `results` or `items` arrays. */ private transformWithArrayAwareness; /** * Filter response with awareness of nested arrays. * Applies field filtering to items within `results` or `items` arrays. */ private filterWithArrayAwareness; /** * Transform top-level fields of an object, excluding specified keys. * Used to preserve container metadata (total, query, etc.) while filtering nested arrays. */ private transformTopLevel; /** * Check if result is an MCP response format (with content array). * These responses contain formatted text and shouldn't be transformed. */ private isMCPResponse; } //# sourceMappingURL=MCPAQLHandler.d.ts.map