UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

1,041 lines (884 loc) 31.2 kB
/** * Enhanced Plugin Registry * * Extended plugin registry with: * - Version constraint enforcement * - Safe unload with dependency checking * - Parallel initialization * - Enhanced service container * - Hot reload support * - Conflict resolution */ import { EventEmitter } from 'events'; import type { PluginContext, PluginConfig, PluginMetadata, IEventBus, ILogger, ServiceContainer, AgentTypeDefinition, TaskTypeDefinition, MCPToolDefinition, CLICommandDefinition, MemoryBackendFactory, HookDefinition, WorkerDefinition, LLMProviderDefinition, HealthCheckResult, } from '../types/index.js'; import type { IPlugin, PluginFactory } from '../core/plugin-interface.js'; import { validatePlugin, PLUGIN_EVENTS } from '../core/plugin-interface.js'; import { DependencyGraph, type PluginDependency, type DependencyError, satisfiesVersion, } from './dependency-graph.js'; // ============================================================================ // Types // ============================================================================ export type InitializationStrategy = 'sequential' | 'parallel' | 'parallel-safe'; export type ConflictStrategy = 'first' | 'last' | 'error' | 'namespace'; export interface ConflictResolution { strategy: ConflictStrategy; namespaceTemplate?: string; // e.g., "{plugin}:{name}" } export interface EnhancedPluginRegistryConfig { coreVersion: string; dataDir: string; logger?: ILogger; eventBus?: IEventBus; defaultConfig?: Partial<PluginConfig>; maxPlugins?: number; loadTimeout?: number; initializationStrategy?: InitializationStrategy; maxParallelInit?: number; conflictResolution?: { mcpTools?: ConflictResolution; cliCommands?: ConflictResolution; agentTypes?: ConflictResolution; taskTypes?: ConflictResolution; }; } export interface PluginEntry { plugin: IPlugin; config: PluginConfig; loadTime: Date; initTime?: Date; error?: string; } export interface UnregisterOptions { cascade?: boolean; // Unload dependents first force?: boolean; // Ignore dependency errors } export interface HotReloadOptions { preserveState?: boolean; migrateState?: (oldState: unknown, newVersion: string) => unknown; timeout?: number; } export interface RegistryStats { total: number; initialized: number; failed: number; agentTypes: number; taskTypes: number; mcpTools: number; cliCommands: number; hooks: number; workers: number; providers: number; } export interface ServiceMetadata { description?: string; provider: string; version?: string; deprecated?: boolean; replacement?: string; } // ============================================================================ // Enhanced Service Container // ============================================================================ class EnhancedServiceContainer implements ServiceContainer { private services = new Map<string, unknown>(); private metadata = new Map<string, ServiceMetadata>(); get<T>(key: string): T | undefined { return this.services.get(key) as T | undefined; } set<T>(key: string, value: T): void { this.services.set(key, value); } setWithMetadata<T>(key: string, value: T, metadata: ServiceMetadata): void { this.services.set(key, value); this.metadata.set(key, metadata); } has(key: string): boolean { return this.services.has(key); } delete(key: string): boolean { this.metadata.delete(key); return this.services.delete(key); } list(): string[] { return Array.from(this.services.keys()); } listByPrefix(prefix: string): string[] { return this.list().filter(key => key.startsWith(prefix)); } getMetadata(key: string): ServiceMetadata | undefined { return this.metadata.get(key); } } // ============================================================================ // Default Implementations // ============================================================================ class DefaultEventBus implements IEventBus { private emitter = new EventEmitter(); emit(event: string, data?: unknown): void { this.emitter.emit(event, data); } on(event: string, handler: (data?: unknown) => void | Promise<void>): () => void { this.emitter.on(event, handler); return () => this.off(event, handler); } off(event: string, handler: (data?: unknown) => void | Promise<void>): void { this.emitter.off(event, handler); } once(event: string, handler: (data?: unknown) => void | Promise<void>): () => void { this.emitter.once(event, handler); return () => this.off(event, handler); } } class DefaultLogger implements ILogger { private context: Record<string, unknown> = {}; constructor(context?: Record<string, unknown>) { if (context) this.context = context; } debug(message: string, ...args: unknown[]): void { console.debug(`[DEBUG]`, message, ...args, this.context); } info(message: string, ...args: unknown[]): void { console.info(`[INFO]`, message, ...args, this.context); } warn(message: string, ...args: unknown[]): void { console.warn(`[WARN]`, message, ...args, this.context); } error(message: string, ...args: unknown[]): void { console.error(`[ERROR]`, message, ...args, this.context); } child(context: Record<string, unknown>): ILogger { return new DefaultLogger({ ...this.context, ...context }); } } // ============================================================================ // Enhanced Plugin Registry // ============================================================================ /** * Enhanced plugin registry with advanced features. * * Features: * - Version constraint enforcement * - Dependency graph with safe unload * - Parallel initialization * - Enhanced service container * - Hot reload support * - Conflict resolution */ export class EnhancedPluginRegistry extends EventEmitter { private readonly plugins = new Map<string, PluginEntry>(); private readonly config: EnhancedPluginRegistryConfig; private readonly logger: ILogger; private readonly eventBus: IEventBus; private readonly services: EnhancedServiceContainer; private readonly dependencyGraph: DependencyGraph; private initialized = false; // Extension point caches private agentTypesCache: AgentTypeDefinition[] = []; private taskTypesCache: TaskTypeDefinition[] = []; private mcpToolsCache: MCPToolDefinition[] = []; private cliCommandsCache: CLICommandDefinition[] = []; private memoryBackendsCache: MemoryBackendFactory[] = []; private hooksCache: HookDefinition[] = []; private workersCache: WorkerDefinition[] = []; private providersCache: LLMProviderDefinition[] = []; // Track extension ownership for conflict resolution private toolOwners = new Map<string, string>(); private commandOwners = new Map<string, string>(); private agentTypeOwners = new Map<string, string>(); private taskTypeOwners = new Map<string, string>(); constructor(config: EnhancedPluginRegistryConfig) { super(); this.config = { initializationStrategy: 'sequential', maxParallelInit: 5, ...config, }; this.logger = config.logger ?? new DefaultLogger({ component: 'EnhancedPluginRegistry' }); this.eventBus = config.eventBus ?? new DefaultEventBus(); this.services = new EnhancedServiceContainer(); this.dependencyGraph = new DependencyGraph(); // Register self in services this.services.setWithMetadata('pluginRegistry', this, { provider: 'core', description: 'Plugin registry instance', version: config.coreVersion, }); } // ========================================================================= // Plugin Loading // ========================================================================= /** * Register a plugin with version constraint validation. */ async register( plugin: IPlugin | PluginFactory, config?: Partial<PluginConfig> ): Promise<void> { // Resolve factory if needed const resolvedPlugin = typeof plugin === 'function' ? await plugin() : plugin; // Validate plugin if (!validatePlugin(resolvedPlugin)) { throw new Error('Invalid plugin: does not implement IPlugin interface'); } const name = resolvedPlugin.metadata.name; const version = resolvedPlugin.metadata.version; // Check for duplicates if (this.plugins.has(name)) { throw new Error(`Plugin ${name} already registered`); } // Check max plugins if (this.config.maxPlugins && this.plugins.size >= this.config.maxPlugins) { throw new Error(`Maximum plugin limit (${this.config.maxPlugins}) reached`); } // Check core version compatibility if (resolvedPlugin.metadata.minCoreVersion) { if (!satisfiesVersion(`>=${resolvedPlugin.metadata.minCoreVersion}`, this.config.coreVersion)) { throw new Error( `Plugin ${name} requires core version >= ${resolvedPlugin.metadata.minCoreVersion}, ` + `but current version is ${this.config.coreVersion}` ); } } if (resolvedPlugin.metadata.maxCoreVersion) { if (!satisfiesVersion(`<=${resolvedPlugin.metadata.maxCoreVersion}`, this.config.coreVersion)) { throw new Error( `Plugin ${name} requires core version <= ${resolvedPlugin.metadata.maxCoreVersion}, ` + `but current version is ${this.config.coreVersion}` ); } } // Parse dependencies const dependencies = this.parseDependencies(resolvedPlugin.metadata.dependencies); // Add to dependency graph this.dependencyGraph.addPlugin(name, version, dependencies); // Create config const pluginConfig: PluginConfig = { enabled: true, priority: 50, settings: {}, ...this.config.defaultConfig, ...config, }; // Store entry const entry: PluginEntry = { plugin: resolvedPlugin, config: pluginConfig, loadTime: new Date(), }; this.plugins.set(name, entry); this.eventBus.emit(PLUGIN_EVENTS.LOADED, { plugin: name }); this.logger.info(`Plugin registered: ${name} v${version}`); } /** * Unregister a plugin with dependency checking. */ async unregister(name: string, options?: UnregisterOptions): Promise<void> { const entry = this.plugins.get(name); if (!entry) { throw new Error(`Plugin ${name} not found`); } // Check dependents const dependents = this.dependencyGraph.getDependents(name); if (dependents.length > 0) { if (options?.cascade) { // Unload dependents first (in reverse order) const order = this.dependencyGraph.getRemovalOrder(name); for (const dep of order) { if (dep !== name) { await this.shutdownPlugin(dep); this.removePluginFromGraph(dep); } } } else if (options?.force) { this.logger.warn(`Force removing ${name}, breaking: ${dependents.join(', ')}`); } else { throw new Error(`Cannot remove ${name}: required by ${dependents.join(', ')}`); } } // Shutdown and remove await this.shutdownPlugin(name); this.removePluginFromGraph(name); this.logger.info(`Plugin unregistered: ${name}`); } // ========================================================================= // Initialization // ========================================================================= /** * Initialize all registered plugins. */ async initialize(): Promise<void> { if (this.initialized) { throw new Error('Registry already initialized'); } // Validate dependencies const errors = this.dependencyGraph.validate(); const criticalErrors = errors.filter(e => e.type !== 'missing' || !this.isOptionalDependency(e)); if (criticalErrors.length > 0) { const errorMessages = criticalErrors.map(e => e.message).join('\n'); throw new Error(`Dependency validation failed:\n${errorMessages}`); } // Initialize based on strategy const strategy = this.config.initializationStrategy ?? 'sequential'; switch (strategy) { case 'sequential': await this.initializeSequential(); break; case 'parallel': await this.initializeParallel(); break; case 'parallel-safe': await this.initializeParallelSafe(); break; } // Check for initialization errors (including conflicts) const initErrors = Array.from(this.plugins.values()) .filter(e => e.error) .map(e => `${e.plugin.metadata.name}: ${e.error}`); if (initErrors.length > 0) { throw new Error(`Plugin initialization failed:\n${initErrors.join('\n')}`); } this.initialized = true; this.logger.info(`Registry initialized with ${this.plugins.size} plugins (${strategy})`); } private async initializeSequential(): Promise<void> { const loadOrder = this.dependencyGraph.getLoadOrder(); for (const name of loadOrder) { const entry = this.plugins.get(name); if (!entry) continue; if (!entry.config.enabled) { this.logger.info(`Plugin ${name} is disabled, skipping initialization`); continue; } try { await this.initializePlugin(entry); this.logger.info(`Plugin initialized: ${name}`); } catch (error) { entry.error = error instanceof Error ? error.message : String(error); this.logger.error(`Failed to initialize plugin ${name}: ${entry.error}`); } } } private async initializeParallel(): Promise<void> { const entries = Array.from(this.plugins.values()).filter(e => e.config.enabled); const maxParallel = this.config.maxParallelInit ?? 5; // Initialize in batches for (let i = 0; i < entries.length; i += maxParallel) { const batch = entries.slice(i, i + maxParallel); const promises = batch.map(entry => this.initializePlugin(entry).catch(err => { entry.error = err instanceof Error ? err.message : String(err); this.logger.error(`Failed to initialize plugin ${entry.plugin.metadata.name}: ${entry.error}`); })); await Promise.all(promises); } } private async initializeParallelSafe(): Promise<void> { const levels = this.dependencyGraph.getDepthLevels(); const maxParallel = this.config.maxParallelInit ?? 5; for (const level of levels) { // Initialize each level in parallel, but levels are sequential for (let i = 0; i < level.length; i += maxParallel) { const batch = level.slice(i, i + maxParallel); const promises = batch.map(async name => { const entry = this.plugins.get(name); if (!entry || !entry.config.enabled) return; try { await this.initializePlugin(entry); this.logger.info(`Plugin initialized: ${name}`); } catch (error) { entry.error = error instanceof Error ? error.message : String(error); this.logger.error(`Failed to initialize plugin ${name}: ${entry.error}`); } }); await Promise.all(promises); } } } private async initializePlugin(entry: PluginEntry): Promise<void> { const context = this.createPluginContext(entry); const timeout = this.config.loadTimeout ?? 30000; await Promise.race([ entry.plugin.initialize(context), new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Initialization timeout')), timeout) ), ]); entry.initTime = new Date(); this.collectExtensionPoints(entry.plugin); } /** * Shutdown all plugins. */ async shutdown(): Promise<void> { // Shutdown in reverse order const names = Array.from(this.plugins.keys()).reverse(); for (const name of names) { await this.shutdownPlugin(name); } this.invalidateCaches(); this.initialized = false; } // ========================================================================= // Hot Reload // ========================================================================= /** * Hot reload a plugin without full restart. */ async reload( name: string, newPlugin: IPlugin | PluginFactory, options?: HotReloadOptions ): Promise<void> { const entry = this.plugins.get(name); if (!entry) { throw new Error(`Plugin ${name} not found`); } // Capture state if preserving let state: unknown; if (options?.preserveState && (entry.plugin as any).getState) { state = await (entry.plugin as any).getState(); } // Shutdown old plugin if (entry.plugin.state === 'initialized') { await entry.plugin.shutdown(); } // Resolve and validate new plugin const resolved = typeof newPlugin === 'function' ? await newPlugin() : newPlugin; if (!validatePlugin(resolved)) { throw new Error('Invalid plugin replacement'); } // Verify same name if (resolved.metadata.name !== name) { throw new Error(`Plugin name mismatch: expected ${name}, got ${resolved.metadata.name}`); } // Update dependency graph const dependencies = this.parseDependencies(resolved.metadata.dependencies); this.dependencyGraph.removePlugin(name); this.dependencyGraph.addPlugin(name, resolved.metadata.version, dependencies); // Initialize new plugin const context = this.createPluginContext(entry); const timeout = options?.timeout ?? this.config.loadTimeout ?? 30000; await Promise.race([ resolved.initialize(context), new Promise<never>((_, reject) => setTimeout(() => reject(new Error('Hot reload timeout')), timeout) ), ]); // Restore state if applicable if (state && options?.migrateState) { state = options.migrateState(state, resolved.metadata.version); } if (state && (resolved as any).restoreState) { await (resolved as any).restoreState(state); } // Update entry entry.plugin = resolved; entry.initTime = new Date(); entry.error = undefined; // Recollect extension points this.invalidateCaches(); this.logger.info(`Plugin hot reloaded: ${name} -> v${resolved.metadata.version}`); this.eventBus.emit(PLUGIN_EVENTS.INITIALIZED, { plugin: name, reloaded: true }); } // ========================================================================= // Extension Points // ========================================================================= private collectExtensionPoints(plugin: IPlugin): void { const name = plugin.metadata.name; const resolution = this.config.conflictResolution; // Collect agent types if (plugin.registerAgentTypes) { const types = plugin.registerAgentTypes(); if (types) { for (const type of types) { const resolvedType = this.resolveConflict( 'agentTypes', type.type, type, name, this.agentTypeOwners, resolution?.agentTypes ); if (resolvedType) { this.agentTypesCache.push(resolvedType); } } } } // Collect task types if (plugin.registerTaskTypes) { const types = plugin.registerTaskTypes(); if (types) { for (const type of types) { const resolvedType = this.resolveConflict( 'taskTypes', type.type, type, name, this.taskTypeOwners, resolution?.taskTypes ); if (resolvedType) { this.taskTypesCache.push(resolvedType); } } } } // Collect MCP tools if (plugin.registerMCPTools) { const tools = plugin.registerMCPTools(); if (tools) { for (const tool of tools) { const resolvedTool = this.resolveConflict( 'mcpTools', tool.name, tool, name, this.toolOwners, resolution?.mcpTools ); if (resolvedTool) { this.mcpToolsCache.push(resolvedTool); } } } } // Collect CLI commands if (plugin.registerCLICommands) { const commands = plugin.registerCLICommands(); if (commands) { for (const command of commands) { const resolvedCommand = this.resolveConflict( 'cliCommands', command.name, command, name, this.commandOwners, resolution?.cliCommands ); if (resolvedCommand) { this.cliCommandsCache.push(resolvedCommand); } } } } // Collect other extension points (no conflict resolution needed) if (plugin.registerMemoryBackends) { const backends = plugin.registerMemoryBackends(); if (backends) this.memoryBackendsCache.push(...backends); } if (plugin.registerHooks) { const hooks = plugin.registerHooks(); if (hooks) this.hooksCache.push(...hooks); } if (plugin.registerWorkers) { const workers = plugin.registerWorkers(); if (workers) this.workersCache.push(...workers); } if (plugin.registerProviders) { const providers = plugin.registerProviders(); if (providers) this.providersCache.push(...providers); } } private resolveConflict<T extends { name?: string; type?: string }>( category: string, identifier: string, item: T, pluginName: string, owners: Map<string, string>, resolution?: ConflictResolution ): T | null { const existing = owners.get(identifier); if (!existing) { owners.set(identifier, pluginName); return item; } const strategy = resolution?.strategy ?? 'error'; switch (strategy) { case 'first': this.logger.warn(`${category}: ${identifier} already registered by ${existing}, ignoring from ${pluginName}`); return null; case 'last': this.logger.warn(`${category}: ${identifier} replacing ${existing}'s version with ${pluginName}'s`); owners.set(identifier, pluginName); // Remove existing from cache this.removeFromCache(category, identifier); return item; case 'namespace': { const template = resolution?.namespaceTemplate ?? '{plugin}:{name}'; // Namespace the existing entry too (on first conflict) const existingNs = template .replace('{plugin}', existing) .replace('{name}', identifier); if (!owners.has(existingNs)) { this.renameInCache(category, identifier, existingNs); owners.delete(identifier); owners.set(existingNs, existing); } const newName = template .replace('{plugin}', pluginName) .replace('{name}', identifier); owners.set(newName, pluginName); return { ...item, name: newName, type: newName } as T; } case 'error': default: throw new Error(`${category}: ${identifier} conflict between ${existing} and ${pluginName}`); } } private renameInCache(category: string, oldName: string, newName: string): void { switch (category) { case 'agentTypes': this.agentTypesCache = this.agentTypesCache.map(t => t.type === oldName ? { ...t, type: newName } : t ); break; case 'taskTypes': this.taskTypesCache = this.taskTypesCache.map(t => t.type === oldName ? { ...t, type: newName } : t ); break; case 'mcpTools': this.mcpToolsCache = this.mcpToolsCache.map(t => t.name === oldName ? { ...t, name: newName } : t ); break; case 'cliCommands': this.cliCommandsCache = this.cliCommandsCache.map(c => c.name === oldName ? { ...c, name: newName } : c ); break; } } private removeFromCache(category: string, identifier: string): void { switch (category) { case 'agentTypes': this.agentTypesCache = this.agentTypesCache.filter(t => t.type !== identifier); break; case 'taskTypes': this.taskTypesCache = this.taskTypesCache.filter(t => t.type !== identifier); break; case 'mcpTools': this.mcpToolsCache = this.mcpToolsCache.filter(t => t.name !== identifier); break; case 'cliCommands': this.cliCommandsCache = this.cliCommandsCache.filter(c => c.name !== identifier); break; } } private invalidateCaches(): void { this.agentTypesCache = []; this.taskTypesCache = []; this.mcpToolsCache = []; this.cliCommandsCache = []; this.memoryBackendsCache = []; this.hooksCache = []; this.workersCache = []; this.providersCache = []; this.toolOwners.clear(); this.commandOwners.clear(); this.agentTypeOwners.clear(); this.taskTypeOwners.clear(); // Recollect from initialized plugins for (const entry of this.plugins.values()) { if (entry.plugin.state === 'initialized') { this.collectExtensionPoints(entry.plugin); } } } // ========================================================================= // Getters // ========================================================================= getAgentTypes(): AgentTypeDefinition[] { return [...this.agentTypesCache]; } getTaskTypes(): TaskTypeDefinition[] { return [...this.taskTypesCache]; } getMCPTools(): MCPToolDefinition[] { return [...this.mcpToolsCache]; } getCLICommands(): CLICommandDefinition[] { return [...this.cliCommandsCache]; } getMemoryBackends(): MemoryBackendFactory[] { return [...this.memoryBackendsCache]; } getHooks(): HookDefinition[] { return [...this.hooksCache]; } getWorkers(): WorkerDefinition[] { return [...this.workersCache]; } getProviders(): LLMProviderDefinition[] { return [...this.providersCache]; } getPlugin(name: string): IPlugin | undefined { return this.plugins.get(name)?.plugin; } getPluginEntry(name: string): PluginEntry | undefined { return this.plugins.get(name); } listPlugins(): PluginMetadata[] { return Array.from(this.plugins.values()).map(e => e.plugin.metadata); } getDependencyGraph(): DependencyGraph { return this.dependencyGraph; } getServices(): EnhancedServiceContainer { return this.services; } // ========================================================================= // Health Check // ========================================================================= async healthCheck(): Promise<Map<string, HealthCheckResult>> { const results = new Map<string, HealthCheckResult>(); for (const [name, entry] of this.plugins) { if (entry.plugin.state !== 'initialized') { results.set(name, { healthy: false, status: 'unhealthy', message: `Plugin not initialized: ${entry.plugin.state}`, checks: {}, timestamp: new Date(), }); continue; } try { if (entry.plugin.healthCheck) { results.set(name, await entry.plugin.healthCheck()); } else { results.set(name, { healthy: true, status: 'healthy', checks: {}, timestamp: new Date(), }); } } catch (error) { results.set(name, { healthy: false, status: 'unhealthy', message: error instanceof Error ? error.message : String(error), checks: {}, timestamp: new Date(), }); } } return results; } // ========================================================================= // Stats // ========================================================================= getStats(): RegistryStats { let initialized = 0; let failed = 0; for (const entry of this.plugins.values()) { if (entry.plugin.state === 'initialized') initialized++; if (entry.plugin.state === 'error' || entry.error) failed++; } return { total: this.plugins.size, initialized, failed, agentTypes: this.agentTypesCache.length, taskTypes: this.taskTypesCache.length, mcpTools: this.mcpToolsCache.length, cliCommands: this.cliCommandsCache.length, hooks: this.hooksCache.length, workers: this.workersCache.length, providers: this.providersCache.length, }; } // ========================================================================= // Private Helpers // ========================================================================= private createPluginContext(entry: PluginEntry): PluginContext { return { config: entry.config, eventBus: this.eventBus, logger: this.logger.child({ plugin: entry.plugin.metadata.name }), services: this.services, coreVersion: this.config.coreVersion, dataDir: this.config.dataDir, }; } private async shutdownPlugin(name: string): Promise<void> { const entry = this.plugins.get(name); if (!entry) return; if (entry.plugin.state === 'initialized') { try { this.eventBus.emit(PLUGIN_EVENTS.SHUTTING_DOWN, { plugin: name }); await entry.plugin.shutdown(); this.eventBus.emit(PLUGIN_EVENTS.SHUTDOWN, { plugin: name }); this.logger.info(`Plugin shutdown: ${name}`); } catch (error) { this.logger.error(`Error shutting down plugin ${name}: ${error}`); } } } private removePluginFromGraph(name: string): void { this.dependencyGraph.removePlugin(name); this.plugins.delete(name); this.invalidateCaches(); } private parseDependencies(deps?: string[] | PluginDependency[]): PluginDependency[] { if (!deps) return []; return deps.map(dep => { if (typeof dep === 'string') { // Parse "name@version" format or just "name" const match = dep.match(/^([^@]+)(?:@(.+))?$/); if (match) { return { name: match[1], version: match[2] ?? '*', }; } return { name: dep, version: '*' }; } return dep; }); } private isOptionalDependency(error: DependencyError): boolean { const plugin = this.plugins.get(error.plugin); if (!plugin) return false; const deps = this.parseDependencies(plugin.plugin.metadata.dependencies); const dep = deps.find(d => d.name === error.dependency); return dep?.optional ?? false; } } // ============================================================================ // Default Registry Instance // ============================================================================ let defaultRegistry: EnhancedPluginRegistry | null = null; export function getDefaultEnhancedRegistry(): EnhancedPluginRegistry { if (!defaultRegistry) { defaultRegistry = new EnhancedPluginRegistry({ coreVersion: '3.0.0', dataDir: process.cwd(), }); } return defaultRegistry; } export function setDefaultEnhancedRegistry(registry: EnhancedPluginRegistry): void { defaultRegistry = registry; }