@sparkleideas/plugins 3.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +401 -0
  2. package/__tests__/collection-manager.test.ts +332 -0
  3. package/__tests__/dependency-graph.test.ts +434 -0
  4. package/__tests__/enhanced-plugin-registry.test.ts +488 -0
  5. package/__tests__/plugin-registry.test.ts +368 -0
  6. package/__tests__/ruvector-bridge.test.ts +2429 -0
  7. package/__tests__/ruvector-integration.test.ts +1602 -0
  8. package/__tests__/ruvector-migrations.test.ts +1099 -0
  9. package/__tests__/ruvector-quantization.test.ts +846 -0
  10. package/__tests__/ruvector-streaming.test.ts +1088 -0
  11. package/__tests__/sdk.test.ts +325 -0
  12. package/__tests__/security.test.ts +348 -0
  13. package/__tests__/utils/ruvector-test-utils.ts +860 -0
  14. package/examples/plugin-creator/index.ts +636 -0
  15. package/examples/plugin-creator/plugin-creator.test.ts +312 -0
  16. package/examples/ruvector/README.md +288 -0
  17. package/examples/ruvector/attention-patterns.ts +394 -0
  18. package/examples/ruvector/basic-usage.ts +288 -0
  19. package/examples/ruvector/docker-compose.yml +75 -0
  20. package/examples/ruvector/gnn-analysis.ts +501 -0
  21. package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
  22. package/examples/ruvector/init-db.sql +119 -0
  23. package/examples/ruvector/quantization.ts +680 -0
  24. package/examples/ruvector/self-learning.ts +447 -0
  25. package/examples/ruvector/semantic-search.ts +576 -0
  26. package/examples/ruvector/streaming-large-data.ts +507 -0
  27. package/examples/ruvector/transactions.ts +594 -0
  28. package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
  29. package/examples/ruvector-plugins/index.ts +79 -0
  30. package/examples/ruvector-plugins/intent-router.ts +354 -0
  31. package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
  32. package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
  33. package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
  34. package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
  35. package/examples/ruvector-plugins/shared/index.ts +20 -0
  36. package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
  37. package/examples/ruvector-plugins/sona-learning.ts +445 -0
  38. package/package.json +97 -0
  39. package/src/collections/collection-manager.ts +661 -0
  40. package/src/collections/index.ts +56 -0
  41. package/src/collections/official/index.ts +1040 -0
  42. package/src/core/base-plugin.ts +416 -0
  43. package/src/core/plugin-interface.ts +215 -0
  44. package/src/hooks/index.ts +685 -0
  45. package/src/index.ts +378 -0
  46. package/src/integrations/agentic-flow.ts +743 -0
  47. package/src/integrations/index.ts +88 -0
  48. package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
  49. package/src/integrations/ruvector/attention-advanced.ts +1040 -0
  50. package/src/integrations/ruvector/attention-executor.ts +782 -0
  51. package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
  52. package/src/integrations/ruvector/attention.ts +1063 -0
  53. package/src/integrations/ruvector/gnn.ts +3050 -0
  54. package/src/integrations/ruvector/hyperbolic.ts +1948 -0
  55. package/src/integrations/ruvector/index.ts +394 -0
  56. package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
  57. package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
  58. package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
  59. package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
  60. package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
  61. package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
  62. package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
  63. package/src/integrations/ruvector/migrations/index.ts +35 -0
  64. package/src/integrations/ruvector/migrations/migrations.ts +647 -0
  65. package/src/integrations/ruvector/quantization.ts +2036 -0
  66. package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
  67. package/src/integrations/ruvector/self-learning.ts +2376 -0
  68. package/src/integrations/ruvector/streaming.ts +1737 -0
  69. package/src/integrations/ruvector/types.ts +1945 -0
  70. package/src/providers/index.ts +643 -0
  71. package/src/registry/dependency-graph.ts +568 -0
  72. package/src/registry/enhanced-plugin-registry.ts +994 -0
  73. package/src/registry/plugin-registry.ts +604 -0
  74. package/src/sdk/index.ts +563 -0
  75. package/src/security/index.ts +594 -0
  76. package/src/types/index.ts +446 -0
  77. package/src/workers/index.ts +700 -0
  78. package/tmp.json +0 -0
  79. package/tsconfig.json +25 -0
  80. package/vitest.config.ts +23 -0
@@ -0,0 +1,994 @@
1
+ /**
2
+ * Enhanced Plugin Registry
3
+ *
4
+ * Extended plugin registry with:
5
+ * - Version constraint enforcement
6
+ * - Safe unload with dependency checking
7
+ * - Parallel initialization
8
+ * - Enhanced service container
9
+ * - Hot reload support
10
+ * - Conflict resolution
11
+ */
12
+
13
+ import { EventEmitter } from 'events';
14
+ import type {
15
+ PluginContext,
16
+ PluginConfig,
17
+ PluginMetadata,
18
+ IEventBus,
19
+ ILogger,
20
+ ServiceContainer,
21
+ AgentTypeDefinition,
22
+ TaskTypeDefinition,
23
+ MCPToolDefinition,
24
+ CLICommandDefinition,
25
+ MemoryBackendFactory,
26
+ HookDefinition,
27
+ WorkerDefinition,
28
+ LLMProviderDefinition,
29
+ HealthCheckResult,
30
+ } from '../types/index.js';
31
+ import type { IPlugin, PluginFactory } from '../core/plugin-interface.js';
32
+ import { validatePlugin, PLUGIN_EVENTS } from '../core/plugin-interface.js';
33
+ import {
34
+ DependencyGraph,
35
+ type PluginDependency,
36
+ type DependencyError,
37
+ satisfiesVersion,
38
+ } from './dependency-graph.js';
39
+
40
+ // ============================================================================
41
+ // Types
42
+ // ============================================================================
43
+
44
+ export type InitializationStrategy = 'sequential' | 'parallel' | 'parallel-safe';
45
+
46
+ export type ConflictStrategy = 'first' | 'last' | 'error' | 'namespace';
47
+
48
+ export interface ConflictResolution {
49
+ strategy: ConflictStrategy;
50
+ namespaceTemplate?: string; // e.g., "{plugin}:{name}"
51
+ }
52
+
53
+ export interface EnhancedPluginRegistryConfig {
54
+ coreVersion: string;
55
+ dataDir: string;
56
+ logger?: ILogger;
57
+ eventBus?: IEventBus;
58
+ defaultConfig?: Partial<PluginConfig>;
59
+ maxPlugins?: number;
60
+ loadTimeout?: number;
61
+ initializationStrategy?: InitializationStrategy;
62
+ maxParallelInit?: number;
63
+ conflictResolution?: {
64
+ mcpTools?: ConflictResolution;
65
+ cliCommands?: ConflictResolution;
66
+ agentTypes?: ConflictResolution;
67
+ taskTypes?: ConflictResolution;
68
+ };
69
+ }
70
+
71
+ export interface PluginEntry {
72
+ plugin: IPlugin;
73
+ config: PluginConfig;
74
+ loadTime: Date;
75
+ initTime?: Date;
76
+ error?: string;
77
+ }
78
+
79
+ export interface UnregisterOptions {
80
+ cascade?: boolean; // Unload dependents first
81
+ force?: boolean; // Ignore dependency errors
82
+ }
83
+
84
+ export interface HotReloadOptions {
85
+ preserveState?: boolean;
86
+ migrateState?: (oldState: unknown, newVersion: string) => unknown;
87
+ timeout?: number;
88
+ }
89
+
90
+ export interface RegistryStats {
91
+ total: number;
92
+ initialized: number;
93
+ failed: number;
94
+ agentTypes: number;
95
+ taskTypes: number;
96
+ mcpTools: number;
97
+ cliCommands: number;
98
+ hooks: number;
99
+ workers: number;
100
+ providers: number;
101
+ }
102
+
103
+ export interface ServiceMetadata {
104
+ description?: string;
105
+ provider: string;
106
+ version?: string;
107
+ deprecated?: boolean;
108
+ replacement?: string;
109
+ }
110
+
111
+ // ============================================================================
112
+ // Enhanced Service Container
113
+ // ============================================================================
114
+
115
+ class EnhancedServiceContainer implements ServiceContainer {
116
+ private services = new Map<string, unknown>();
117
+ private metadata = new Map<string, ServiceMetadata>();
118
+
119
+ get<T>(key: string): T | undefined {
120
+ return this.services.get(key) as T | undefined;
121
+ }
122
+
123
+ set<T>(key: string, value: T): void {
124
+ this.services.set(key, value);
125
+ }
126
+
127
+ setWithMetadata<T>(key: string, value: T, metadata: ServiceMetadata): void {
128
+ this.services.set(key, value);
129
+ this.metadata.set(key, metadata);
130
+ }
131
+
132
+ has(key: string): boolean {
133
+ return this.services.has(key);
134
+ }
135
+
136
+ delete(key: string): boolean {
137
+ this.metadata.delete(key);
138
+ return this.services.delete(key);
139
+ }
140
+
141
+ list(): string[] {
142
+ return Array.from(this.services.keys());
143
+ }
144
+
145
+ listByPrefix(prefix: string): string[] {
146
+ return this.list().filter(key => key.startsWith(prefix));
147
+ }
148
+
149
+ getMetadata(key: string): ServiceMetadata | undefined {
150
+ return this.metadata.get(key);
151
+ }
152
+ }
153
+
154
+ // ============================================================================
155
+ // Default Implementations
156
+ // ============================================================================
157
+
158
+ class DefaultEventBus implements IEventBus {
159
+ private emitter = new EventEmitter();
160
+
161
+ emit(event: string, data?: unknown): void {
162
+ this.emitter.emit(event, data);
163
+ }
164
+
165
+ on(event: string, handler: (data?: unknown) => void | Promise<void>): () => void {
166
+ this.emitter.on(event, handler);
167
+ return () => this.off(event, handler);
168
+ }
169
+
170
+ off(event: string, handler: (data?: unknown) => void | Promise<void>): void {
171
+ this.emitter.off(event, handler);
172
+ }
173
+
174
+ once(event: string, handler: (data?: unknown) => void | Promise<void>): () => void {
175
+ this.emitter.once(event, handler);
176
+ return () => this.off(event, handler);
177
+ }
178
+ }
179
+
180
+ class DefaultLogger implements ILogger {
181
+ private context: Record<string, unknown> = {};
182
+
183
+ constructor(context?: Record<string, unknown>) {
184
+ if (context) this.context = context;
185
+ }
186
+
187
+ debug(message: string, ...args: unknown[]): void {
188
+ console.debug(`[DEBUG]`, message, ...args, this.context);
189
+ }
190
+
191
+ info(message: string, ...args: unknown[]): void {
192
+ console.info(`[INFO]`, message, ...args, this.context);
193
+ }
194
+
195
+ warn(message: string, ...args: unknown[]): void {
196
+ console.warn(`[WARN]`, message, ...args, this.context);
197
+ }
198
+
199
+ error(message: string, ...args: unknown[]): void {
200
+ console.error(`[ERROR]`, message, ...args, this.context);
201
+ }
202
+
203
+ child(context: Record<string, unknown>): ILogger {
204
+ return new DefaultLogger({ ...this.context, ...context });
205
+ }
206
+ }
207
+
208
+ // ============================================================================
209
+ // Enhanced Plugin Registry
210
+ // ============================================================================
211
+
212
+ /**
213
+ * Enhanced plugin registry with advanced features.
214
+ *
215
+ * Features:
216
+ * - Version constraint enforcement
217
+ * - Dependency graph with safe unload
218
+ * - Parallel initialization
219
+ * - Enhanced service container
220
+ * - Hot reload support
221
+ * - Conflict resolution
222
+ */
223
+ export class EnhancedPluginRegistry extends EventEmitter {
224
+ private readonly plugins = new Map<string, PluginEntry>();
225
+ private readonly config: EnhancedPluginRegistryConfig;
226
+ private readonly logger: ILogger;
227
+ private readonly eventBus: IEventBus;
228
+ private readonly services: EnhancedServiceContainer;
229
+ private readonly dependencyGraph: DependencyGraph;
230
+ private initialized = false;
231
+
232
+ // Extension point caches
233
+ private agentTypesCache: AgentTypeDefinition[] = [];
234
+ private taskTypesCache: TaskTypeDefinition[] = [];
235
+ private mcpToolsCache: MCPToolDefinition[] = [];
236
+ private cliCommandsCache: CLICommandDefinition[] = [];
237
+ private memoryBackendsCache: MemoryBackendFactory[] = [];
238
+ private hooksCache: HookDefinition[] = [];
239
+ private workersCache: WorkerDefinition[] = [];
240
+ private providersCache: LLMProviderDefinition[] = [];
241
+
242
+ // Track extension ownership for conflict resolution
243
+ private toolOwners = new Map<string, string>();
244
+ private commandOwners = new Map<string, string>();
245
+ private agentTypeOwners = new Map<string, string>();
246
+ private taskTypeOwners = new Map<string, string>();
247
+
248
+ constructor(config: EnhancedPluginRegistryConfig) {
249
+ super();
250
+ this.config = {
251
+ initializationStrategy: 'sequential',
252
+ maxParallelInit: 5,
253
+ ...config,
254
+ };
255
+ this.logger = config.logger ?? new DefaultLogger({ component: 'EnhancedPluginRegistry' });
256
+ this.eventBus = config.eventBus ?? new DefaultEventBus();
257
+ this.services = new EnhancedServiceContainer();
258
+ this.dependencyGraph = new DependencyGraph();
259
+
260
+ // Register self in services
261
+ this.services.setWithMetadata('pluginRegistry', this, {
262
+ provider: 'core',
263
+ description: 'Plugin registry instance',
264
+ version: config.coreVersion,
265
+ });
266
+ }
267
+
268
+ // =========================================================================
269
+ // Plugin Loading
270
+ // =========================================================================
271
+
272
+ /**
273
+ * Register a plugin with version constraint validation.
274
+ */
275
+ async register(
276
+ plugin: IPlugin | PluginFactory,
277
+ config?: Partial<PluginConfig>
278
+ ): Promise<void> {
279
+ // Resolve factory if needed
280
+ const resolvedPlugin = typeof plugin === 'function' ? await plugin() : plugin;
281
+
282
+ // Validate plugin
283
+ if (!validatePlugin(resolvedPlugin)) {
284
+ throw new Error('Invalid plugin: does not implement IPlugin interface');
285
+ }
286
+
287
+ const name = resolvedPlugin.metadata.name;
288
+ const version = resolvedPlugin.metadata.version;
289
+
290
+ // Check for duplicates
291
+ if (this.plugins.has(name)) {
292
+ throw new Error(`Plugin ${name} already registered`);
293
+ }
294
+
295
+ // Check max plugins
296
+ if (this.config.maxPlugins && this.plugins.size >= this.config.maxPlugins) {
297
+ throw new Error(`Maximum plugin limit (${this.config.maxPlugins}) reached`);
298
+ }
299
+
300
+ // Check core version compatibility
301
+ if (resolvedPlugin.metadata.minCoreVersion) {
302
+ if (!satisfiesVersion(`>=${resolvedPlugin.metadata.minCoreVersion}`, this.config.coreVersion)) {
303
+ throw new Error(
304
+ `Plugin ${name} requires core version >= ${resolvedPlugin.metadata.minCoreVersion}, ` +
305
+ `but current version is ${this.config.coreVersion}`
306
+ );
307
+ }
308
+ }
309
+ if (resolvedPlugin.metadata.maxCoreVersion) {
310
+ if (!satisfiesVersion(`<=${resolvedPlugin.metadata.maxCoreVersion}`, this.config.coreVersion)) {
311
+ throw new Error(
312
+ `Plugin ${name} requires core version <= ${resolvedPlugin.metadata.maxCoreVersion}, ` +
313
+ `but current version is ${this.config.coreVersion}`
314
+ );
315
+ }
316
+ }
317
+
318
+ // Parse dependencies
319
+ const dependencies = this.parseDependencies(resolvedPlugin.metadata.dependencies);
320
+
321
+ // Add to dependency graph
322
+ this.dependencyGraph.addPlugin(name, version, dependencies);
323
+
324
+ // Create config
325
+ const pluginConfig: PluginConfig = {
326
+ enabled: true,
327
+ priority: 50,
328
+ settings: {},
329
+ ...this.config.defaultConfig,
330
+ ...config,
331
+ };
332
+
333
+ // Store entry
334
+ const entry: PluginEntry = {
335
+ plugin: resolvedPlugin,
336
+ config: pluginConfig,
337
+ loadTime: new Date(),
338
+ };
339
+
340
+ this.plugins.set(name, entry);
341
+ this.eventBus.emit(PLUGIN_EVENTS.LOADED, { plugin: name });
342
+ this.logger.info(`Plugin registered: ${name} v${version}`);
343
+ }
344
+
345
+ /**
346
+ * Unregister a plugin with dependency checking.
347
+ */
348
+ async unregister(name: string, options?: UnregisterOptions): Promise<void> {
349
+ const entry = this.plugins.get(name);
350
+ if (!entry) {
351
+ throw new Error(`Plugin ${name} not found`);
352
+ }
353
+
354
+ // Check dependents
355
+ const dependents = this.dependencyGraph.getDependents(name);
356
+
357
+ if (dependents.length > 0) {
358
+ if (options?.cascade) {
359
+ // Unload dependents first (in reverse order)
360
+ const order = this.dependencyGraph.getRemovalOrder(name);
361
+ for (const dep of order) {
362
+ if (dep !== name) {
363
+ await this.shutdownPlugin(dep);
364
+ this.removePluginFromGraph(dep);
365
+ }
366
+ }
367
+ } else if (options?.force) {
368
+ this.logger.warn(`Force removing ${name}, breaking: ${dependents.join(', ')}`);
369
+ } else {
370
+ throw new Error(`Cannot remove ${name}: required by ${dependents.join(', ')}`);
371
+ }
372
+ }
373
+
374
+ // Shutdown and remove
375
+ await this.shutdownPlugin(name);
376
+ this.removePluginFromGraph(name);
377
+
378
+ this.logger.info(`Plugin unregistered: ${name}`);
379
+ }
380
+
381
+ // =========================================================================
382
+ // Initialization
383
+ // =========================================================================
384
+
385
+ /**
386
+ * Initialize all registered plugins.
387
+ */
388
+ async initialize(): Promise<void> {
389
+ if (this.initialized) {
390
+ throw new Error('Registry already initialized');
391
+ }
392
+
393
+ // Validate dependencies
394
+ const errors = this.dependencyGraph.validate();
395
+ const criticalErrors = errors.filter(e => e.type !== 'missing' || !this.isOptionalDependency(e));
396
+
397
+ if (criticalErrors.length > 0) {
398
+ const errorMessages = criticalErrors.map(e => e.message).join('\n');
399
+ throw new Error(`Dependency validation failed:\n${errorMessages}`);
400
+ }
401
+
402
+ // Initialize based on strategy
403
+ const strategy = this.config.initializationStrategy ?? 'sequential';
404
+
405
+ switch (strategy) {
406
+ case 'sequential':
407
+ await this.initializeSequential();
408
+ break;
409
+ case 'parallel':
410
+ await this.initializeParallel();
411
+ break;
412
+ case 'parallel-safe':
413
+ await this.initializeParallelSafe();
414
+ break;
415
+ }
416
+
417
+ this.initialized = true;
418
+ this.logger.info(`Registry initialized with ${this.plugins.size} plugins (${strategy})`);
419
+ }
420
+
421
+ private async initializeSequential(): Promise<void> {
422
+ const loadOrder = this.dependencyGraph.getLoadOrder();
423
+
424
+ for (const name of loadOrder) {
425
+ const entry = this.plugins.get(name);
426
+ if (!entry) continue;
427
+
428
+ if (!entry.config.enabled) {
429
+ this.logger.info(`Plugin ${name} is disabled, skipping initialization`);
430
+ continue;
431
+ }
432
+
433
+ try {
434
+ await this.initializePlugin(entry);
435
+ this.logger.info(`Plugin initialized: ${name}`);
436
+ } catch (error) {
437
+ entry.error = error instanceof Error ? error.message : String(error);
438
+ this.logger.error(`Failed to initialize plugin ${name}: ${entry.error}`);
439
+ }
440
+ }
441
+ }
442
+
443
+ private async initializeParallel(): Promise<void> {
444
+ const entries = Array.from(this.plugins.values()).filter(e => e.config.enabled);
445
+ const maxParallel = this.config.maxParallelInit ?? 5;
446
+
447
+ // Initialize in batches
448
+ for (let i = 0; i < entries.length; i += maxParallel) {
449
+ const batch = entries.slice(i, i + maxParallel);
450
+ const promises = batch.map(entry => this.initializePlugin(entry).catch(err => {
451
+ entry.error = err instanceof Error ? err.message : String(err);
452
+ this.logger.error(`Failed to initialize plugin ${entry.plugin.metadata.name}: ${entry.error}`);
453
+ }));
454
+
455
+ await Promise.all(promises);
456
+ }
457
+ }
458
+
459
+ private async initializeParallelSafe(): Promise<void> {
460
+ const levels = this.dependencyGraph.getDepthLevels();
461
+ const maxParallel = this.config.maxParallelInit ?? 5;
462
+
463
+ for (const level of levels) {
464
+ // Initialize each level in parallel, but levels are sequential
465
+ for (let i = 0; i < level.length; i += maxParallel) {
466
+ const batch = level.slice(i, i + maxParallel);
467
+ const promises = batch.map(async name => {
468
+ const entry = this.plugins.get(name);
469
+ if (!entry || !entry.config.enabled) return;
470
+
471
+ try {
472
+ await this.initializePlugin(entry);
473
+ this.logger.info(`Plugin initialized: ${name}`);
474
+ } catch (error) {
475
+ entry.error = error instanceof Error ? error.message : String(error);
476
+ this.logger.error(`Failed to initialize plugin ${name}: ${entry.error}`);
477
+ }
478
+ });
479
+
480
+ await Promise.all(promises);
481
+ }
482
+ }
483
+ }
484
+
485
+ private async initializePlugin(entry: PluginEntry): Promise<void> {
486
+ const context = this.createPluginContext(entry);
487
+ const timeout = this.config.loadTimeout ?? 30000;
488
+
489
+ await Promise.race([
490
+ entry.plugin.initialize(context),
491
+ new Promise<never>((_, reject) =>
492
+ setTimeout(() => reject(new Error('Initialization timeout')), timeout)
493
+ ),
494
+ ]);
495
+
496
+ entry.initTime = new Date();
497
+ this.collectExtensionPoints(entry.plugin);
498
+ }
499
+
500
+ /**
501
+ * Shutdown all plugins.
502
+ */
503
+ async shutdown(): Promise<void> {
504
+ // Shutdown in reverse order
505
+ const names = Array.from(this.plugins.keys()).reverse();
506
+
507
+ for (const name of names) {
508
+ await this.shutdownPlugin(name);
509
+ }
510
+
511
+ this.invalidateCaches();
512
+ this.initialized = false;
513
+ }
514
+
515
+ // =========================================================================
516
+ // Hot Reload
517
+ // =========================================================================
518
+
519
+ /**
520
+ * Hot reload a plugin without full restart.
521
+ */
522
+ async reload(
523
+ name: string,
524
+ newPlugin: IPlugin | PluginFactory,
525
+ options?: HotReloadOptions
526
+ ): Promise<void> {
527
+ const entry = this.plugins.get(name);
528
+ if (!entry) {
529
+ throw new Error(`Plugin ${name} not found`);
530
+ }
531
+
532
+ // Capture state if preserving
533
+ let state: unknown;
534
+ if (options?.preserveState && (entry.plugin as any).getState) {
535
+ state = await (entry.plugin as any).getState();
536
+ }
537
+
538
+ // Shutdown old plugin
539
+ if (entry.plugin.state === 'initialized') {
540
+ await entry.plugin.shutdown();
541
+ }
542
+
543
+ // Resolve and validate new plugin
544
+ const resolved = typeof newPlugin === 'function' ? await newPlugin() : newPlugin;
545
+ if (!validatePlugin(resolved)) {
546
+ throw new Error('Invalid plugin replacement');
547
+ }
548
+
549
+ // Verify same name
550
+ if (resolved.metadata.name !== name) {
551
+ throw new Error(`Plugin name mismatch: expected ${name}, got ${resolved.metadata.name}`);
552
+ }
553
+
554
+ // Update dependency graph
555
+ const dependencies = this.parseDependencies(resolved.metadata.dependencies);
556
+ this.dependencyGraph.removePlugin(name);
557
+ this.dependencyGraph.addPlugin(name, resolved.metadata.version, dependencies);
558
+
559
+ // Initialize new plugin
560
+ const context = this.createPluginContext(entry);
561
+ const timeout = options?.timeout ?? this.config.loadTimeout ?? 30000;
562
+
563
+ await Promise.race([
564
+ resolved.initialize(context),
565
+ new Promise<never>((_, reject) =>
566
+ setTimeout(() => reject(new Error('Hot reload timeout')), timeout)
567
+ ),
568
+ ]);
569
+
570
+ // Restore state if applicable
571
+ if (state && options?.migrateState) {
572
+ state = options.migrateState(state, resolved.metadata.version);
573
+ }
574
+ if (state && (resolved as any).setState) {
575
+ await (resolved as any).setState(state);
576
+ }
577
+
578
+ // Update entry
579
+ entry.plugin = resolved;
580
+ entry.initTime = new Date();
581
+ entry.error = undefined;
582
+
583
+ // Recollect extension points
584
+ this.invalidateCaches();
585
+
586
+ this.logger.info(`Plugin hot reloaded: ${name} -> v${resolved.metadata.version}`);
587
+ this.eventBus.emit(PLUGIN_EVENTS.INITIALIZED, { plugin: name, reloaded: true });
588
+ }
589
+
590
+ // =========================================================================
591
+ // Extension Points
592
+ // =========================================================================
593
+
594
+ private collectExtensionPoints(plugin: IPlugin): void {
595
+ const name = plugin.metadata.name;
596
+ const resolution = this.config.conflictResolution;
597
+
598
+ // Collect agent types
599
+ if (plugin.registerAgentTypes) {
600
+ const types = plugin.registerAgentTypes();
601
+ if (types) {
602
+ for (const type of types) {
603
+ const resolvedType = this.resolveConflict(
604
+ 'agentTypes',
605
+ type.type,
606
+ type,
607
+ name,
608
+ this.agentTypeOwners,
609
+ resolution?.agentTypes
610
+ );
611
+ if (resolvedType) {
612
+ this.agentTypesCache.push(resolvedType);
613
+ }
614
+ }
615
+ }
616
+ }
617
+
618
+ // Collect task types
619
+ if (plugin.registerTaskTypes) {
620
+ const types = plugin.registerTaskTypes();
621
+ if (types) {
622
+ for (const type of types) {
623
+ const resolvedType = this.resolveConflict(
624
+ 'taskTypes',
625
+ type.type,
626
+ type,
627
+ name,
628
+ this.taskTypeOwners,
629
+ resolution?.taskTypes
630
+ );
631
+ if (resolvedType) {
632
+ this.taskTypesCache.push(resolvedType);
633
+ }
634
+ }
635
+ }
636
+ }
637
+
638
+ // Collect MCP tools
639
+ if (plugin.registerMCPTools) {
640
+ const tools = plugin.registerMCPTools();
641
+ if (tools) {
642
+ for (const tool of tools) {
643
+ const resolvedTool = this.resolveConflict(
644
+ 'mcpTools',
645
+ tool.name,
646
+ tool,
647
+ name,
648
+ this.toolOwners,
649
+ resolution?.mcpTools
650
+ );
651
+ if (resolvedTool) {
652
+ this.mcpToolsCache.push(resolvedTool);
653
+ }
654
+ }
655
+ }
656
+ }
657
+
658
+ // Collect CLI commands
659
+ if (plugin.registerCLICommands) {
660
+ const commands = plugin.registerCLICommands();
661
+ if (commands) {
662
+ for (const command of commands) {
663
+ const resolvedCommand = this.resolveConflict(
664
+ 'cliCommands',
665
+ command.name,
666
+ command,
667
+ name,
668
+ this.commandOwners,
669
+ resolution?.cliCommands
670
+ );
671
+ if (resolvedCommand) {
672
+ this.cliCommandsCache.push(resolvedCommand);
673
+ }
674
+ }
675
+ }
676
+ }
677
+
678
+ // Collect other extension points (no conflict resolution needed)
679
+ if (plugin.registerMemoryBackends) {
680
+ const backends = plugin.registerMemoryBackends();
681
+ if (backends) this.memoryBackendsCache.push(...backends);
682
+ }
683
+
684
+ if (plugin.registerHooks) {
685
+ const hooks = plugin.registerHooks();
686
+ if (hooks) this.hooksCache.push(...hooks);
687
+ }
688
+
689
+ if (plugin.registerWorkers) {
690
+ const workers = plugin.registerWorkers();
691
+ if (workers) this.workersCache.push(...workers);
692
+ }
693
+
694
+ if (plugin.registerProviders) {
695
+ const providers = plugin.registerProviders();
696
+ if (providers) this.providersCache.push(...providers);
697
+ }
698
+ }
699
+
700
+ private resolveConflict<T extends { name?: string; type?: string }>(
701
+ category: string,
702
+ identifier: string,
703
+ item: T,
704
+ pluginName: string,
705
+ owners: Map<string, string>,
706
+ resolution?: ConflictResolution
707
+ ): T | null {
708
+ const existing = owners.get(identifier);
709
+
710
+ if (!existing) {
711
+ owners.set(identifier, pluginName);
712
+ return item;
713
+ }
714
+
715
+ const strategy = resolution?.strategy ?? 'error';
716
+
717
+ switch (strategy) {
718
+ case 'first':
719
+ this.logger.warn(`${category}: ${identifier} already registered by ${existing}, ignoring from ${pluginName}`);
720
+ return null;
721
+
722
+ case 'last':
723
+ this.logger.warn(`${category}: ${identifier} replacing ${existing}'s version with ${pluginName}'s`);
724
+ owners.set(identifier, pluginName);
725
+ // Remove existing from cache
726
+ this.removeFromCache(category, identifier);
727
+ return item;
728
+
729
+ case 'namespace':
730
+ const template = resolution?.namespaceTemplate ?? '{plugin}:{name}';
731
+ const newName = template
732
+ .replace('{plugin}', pluginName)
733
+ .replace('{name}', identifier);
734
+ owners.set(newName, pluginName);
735
+ return { ...item, name: newName, type: newName } as T;
736
+
737
+ case 'error':
738
+ default:
739
+ throw new Error(`${category}: ${identifier} conflict between ${existing} and ${pluginName}`);
740
+ }
741
+ }
742
+
743
+ private removeFromCache(category: string, identifier: string): void {
744
+ switch (category) {
745
+ case 'agentTypes':
746
+ this.agentTypesCache = this.agentTypesCache.filter(t => t.type !== identifier);
747
+ break;
748
+ case 'taskTypes':
749
+ this.taskTypesCache = this.taskTypesCache.filter(t => t.type !== identifier);
750
+ break;
751
+ case 'mcpTools':
752
+ this.mcpToolsCache = this.mcpToolsCache.filter(t => t.name !== identifier);
753
+ break;
754
+ case 'cliCommands':
755
+ this.cliCommandsCache = this.cliCommandsCache.filter(c => c.name !== identifier);
756
+ break;
757
+ }
758
+ }
759
+
760
+ private invalidateCaches(): void {
761
+ this.agentTypesCache = [];
762
+ this.taskTypesCache = [];
763
+ this.mcpToolsCache = [];
764
+ this.cliCommandsCache = [];
765
+ this.memoryBackendsCache = [];
766
+ this.hooksCache = [];
767
+ this.workersCache = [];
768
+ this.providersCache = [];
769
+
770
+ this.toolOwners.clear();
771
+ this.commandOwners.clear();
772
+ this.agentTypeOwners.clear();
773
+ this.taskTypeOwners.clear();
774
+
775
+ // Recollect from initialized plugins
776
+ for (const entry of this.plugins.values()) {
777
+ if (entry.plugin.state === 'initialized') {
778
+ this.collectExtensionPoints(entry.plugin);
779
+ }
780
+ }
781
+ }
782
+
783
+ // =========================================================================
784
+ // Getters
785
+ // =========================================================================
786
+
787
+ getAgentTypes(): AgentTypeDefinition[] {
788
+ return [...this.agentTypesCache];
789
+ }
790
+
791
+ getTaskTypes(): TaskTypeDefinition[] {
792
+ return [...this.taskTypesCache];
793
+ }
794
+
795
+ getMCPTools(): MCPToolDefinition[] {
796
+ return [...this.mcpToolsCache];
797
+ }
798
+
799
+ getCLICommands(): CLICommandDefinition[] {
800
+ return [...this.cliCommandsCache];
801
+ }
802
+
803
+ getMemoryBackends(): MemoryBackendFactory[] {
804
+ return [...this.memoryBackendsCache];
805
+ }
806
+
807
+ getHooks(): HookDefinition[] {
808
+ return [...this.hooksCache];
809
+ }
810
+
811
+ getWorkers(): WorkerDefinition[] {
812
+ return [...this.workersCache];
813
+ }
814
+
815
+ getProviders(): LLMProviderDefinition[] {
816
+ return [...this.providersCache];
817
+ }
818
+
819
+ getPlugin(name: string): IPlugin | undefined {
820
+ return this.plugins.get(name)?.plugin;
821
+ }
822
+
823
+ getPluginEntry(name: string): PluginEntry | undefined {
824
+ return this.plugins.get(name);
825
+ }
826
+
827
+ listPlugins(): PluginMetadata[] {
828
+ return Array.from(this.plugins.values()).map(e => e.plugin.metadata);
829
+ }
830
+
831
+ getDependencyGraph(): DependencyGraph {
832
+ return this.dependencyGraph;
833
+ }
834
+
835
+ getServices(): EnhancedServiceContainer {
836
+ return this.services;
837
+ }
838
+
839
+ // =========================================================================
840
+ // Health Check
841
+ // =========================================================================
842
+
843
+ async healthCheck(): Promise<Map<string, HealthCheckResult>> {
844
+ const results = new Map<string, HealthCheckResult>();
845
+
846
+ for (const [name, entry] of this.plugins) {
847
+ if (entry.plugin.state !== 'initialized') {
848
+ results.set(name, {
849
+ healthy: false,
850
+ status: 'unhealthy',
851
+ message: `Plugin not initialized: ${entry.plugin.state}`,
852
+ checks: {},
853
+ timestamp: new Date(),
854
+ });
855
+ continue;
856
+ }
857
+
858
+ try {
859
+ if (entry.plugin.healthCheck) {
860
+ results.set(name, await entry.plugin.healthCheck());
861
+ } else {
862
+ results.set(name, {
863
+ healthy: true,
864
+ status: 'healthy',
865
+ checks: {},
866
+ timestamp: new Date(),
867
+ });
868
+ }
869
+ } catch (error) {
870
+ results.set(name, {
871
+ healthy: false,
872
+ status: 'unhealthy',
873
+ message: error instanceof Error ? error.message : String(error),
874
+ checks: {},
875
+ timestamp: new Date(),
876
+ });
877
+ }
878
+ }
879
+
880
+ return results;
881
+ }
882
+
883
+ // =========================================================================
884
+ // Stats
885
+ // =========================================================================
886
+
887
+ getStats(): RegistryStats {
888
+ let initialized = 0;
889
+ let failed = 0;
890
+
891
+ for (const entry of this.plugins.values()) {
892
+ if (entry.plugin.state === 'initialized') initialized++;
893
+ if (entry.plugin.state === 'error' || entry.error) failed++;
894
+ }
895
+
896
+ return {
897
+ total: this.plugins.size,
898
+ initialized,
899
+ failed,
900
+ agentTypes: this.agentTypesCache.length,
901
+ taskTypes: this.taskTypesCache.length,
902
+ mcpTools: this.mcpToolsCache.length,
903
+ cliCommands: this.cliCommandsCache.length,
904
+ hooks: this.hooksCache.length,
905
+ workers: this.workersCache.length,
906
+ providers: this.providersCache.length,
907
+ };
908
+ }
909
+
910
+ // =========================================================================
911
+ // Private Helpers
912
+ // =========================================================================
913
+
914
+ private createPluginContext(entry: PluginEntry): PluginContext {
915
+ return {
916
+ config: entry.config,
917
+ eventBus: this.eventBus,
918
+ logger: this.logger.child({ plugin: entry.plugin.metadata.name }),
919
+ services: this.services,
920
+ coreVersion: this.config.coreVersion,
921
+ dataDir: this.config.dataDir,
922
+ };
923
+ }
924
+
925
+ private async shutdownPlugin(name: string): Promise<void> {
926
+ const entry = this.plugins.get(name);
927
+ if (!entry) return;
928
+
929
+ if (entry.plugin.state === 'initialized') {
930
+ try {
931
+ this.eventBus.emit(PLUGIN_EVENTS.SHUTTING_DOWN, { plugin: name });
932
+ await entry.plugin.shutdown();
933
+ this.eventBus.emit(PLUGIN_EVENTS.SHUTDOWN, { plugin: name });
934
+ this.logger.info(`Plugin shutdown: ${name}`);
935
+ } catch (error) {
936
+ this.logger.error(`Error shutting down plugin ${name}: ${error}`);
937
+ }
938
+ }
939
+ }
940
+
941
+ private removePluginFromGraph(name: string): void {
942
+ this.dependencyGraph.removePlugin(name);
943
+ this.plugins.delete(name);
944
+ this.invalidateCaches();
945
+ }
946
+
947
+ private parseDependencies(deps?: string[] | PluginDependency[]): PluginDependency[] {
948
+ if (!deps) return [];
949
+
950
+ return deps.map(dep => {
951
+ if (typeof dep === 'string') {
952
+ // Parse "name@version" format or just "name"
953
+ const match = dep.match(/^([^@]+)(?:@(.+))?$/);
954
+ if (match) {
955
+ return {
956
+ name: match[1],
957
+ version: match[2] ?? '*',
958
+ };
959
+ }
960
+ return { name: dep, version: '*' };
961
+ }
962
+ return dep;
963
+ });
964
+ }
965
+
966
+ private isOptionalDependency(error: DependencyError): boolean {
967
+ const plugin = this.plugins.get(error.plugin);
968
+ if (!plugin) return false;
969
+
970
+ const deps = this.parseDependencies(plugin.plugin.metadata.dependencies);
971
+ const dep = deps.find(d => d.name === error.dependency);
972
+ return dep?.optional ?? false;
973
+ }
974
+ }
975
+
976
+ // ============================================================================
977
+ // Default Registry Instance
978
+ // ============================================================================
979
+
980
+ let defaultRegistry: EnhancedPluginRegistry | null = null;
981
+
982
+ export function getDefaultEnhancedRegistry(): EnhancedPluginRegistry {
983
+ if (!defaultRegistry) {
984
+ defaultRegistry = new EnhancedPluginRegistry({
985
+ coreVersion: '3.0.0',
986
+ dataDir: process.cwd(),
987
+ });
988
+ }
989
+ return defaultRegistry;
990
+ }
991
+
992
+ export function setDefaultEnhancedRegistry(registry: EnhancedPluginRegistry): void {
993
+ defaultRegistry = registry;
994
+ }