@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.
- package/README.md +401 -0
- package/__tests__/collection-manager.test.ts +332 -0
- package/__tests__/dependency-graph.test.ts +434 -0
- package/__tests__/enhanced-plugin-registry.test.ts +488 -0
- package/__tests__/plugin-registry.test.ts +368 -0
- package/__tests__/ruvector-bridge.test.ts +2429 -0
- package/__tests__/ruvector-integration.test.ts +1602 -0
- package/__tests__/ruvector-migrations.test.ts +1099 -0
- package/__tests__/ruvector-quantization.test.ts +846 -0
- package/__tests__/ruvector-streaming.test.ts +1088 -0
- package/__tests__/sdk.test.ts +325 -0
- package/__tests__/security.test.ts +348 -0
- package/__tests__/utils/ruvector-test-utils.ts +860 -0
- package/examples/plugin-creator/index.ts +636 -0
- package/examples/plugin-creator/plugin-creator.test.ts +312 -0
- package/examples/ruvector/README.md +288 -0
- package/examples/ruvector/attention-patterns.ts +394 -0
- package/examples/ruvector/basic-usage.ts +288 -0
- package/examples/ruvector/docker-compose.yml +75 -0
- package/examples/ruvector/gnn-analysis.ts +501 -0
- package/examples/ruvector/hyperbolic-hierarchies.ts +557 -0
- package/examples/ruvector/init-db.sql +119 -0
- package/examples/ruvector/quantization.ts +680 -0
- package/examples/ruvector/self-learning.ts +447 -0
- package/examples/ruvector/semantic-search.ts +576 -0
- package/examples/ruvector/streaming-large-data.ts +507 -0
- package/examples/ruvector/transactions.ts +594 -0
- package/examples/ruvector-plugins/hook-pattern-library.ts +486 -0
- package/examples/ruvector-plugins/index.ts +79 -0
- package/examples/ruvector-plugins/intent-router.ts +354 -0
- package/examples/ruvector-plugins/mcp-tool-optimizer.ts +424 -0
- package/examples/ruvector-plugins/reasoning-bank.ts +657 -0
- package/examples/ruvector-plugins/ruvector-plugins.test.ts +518 -0
- package/examples/ruvector-plugins/semantic-code-search.ts +498 -0
- package/examples/ruvector-plugins/shared/index.ts +20 -0
- package/examples/ruvector-plugins/shared/vector-utils.ts +257 -0
- package/examples/ruvector-plugins/sona-learning.ts +445 -0
- package/package.json +97 -0
- package/src/collections/collection-manager.ts +661 -0
- package/src/collections/index.ts +56 -0
- package/src/collections/official/index.ts +1040 -0
- package/src/core/base-plugin.ts +416 -0
- package/src/core/plugin-interface.ts +215 -0
- package/src/hooks/index.ts +685 -0
- package/src/index.ts +378 -0
- package/src/integrations/agentic-flow.ts +743 -0
- package/src/integrations/index.ts +88 -0
- package/src/integrations/ruvector/ARCHITECTURE.md +1245 -0
- package/src/integrations/ruvector/attention-advanced.ts +1040 -0
- package/src/integrations/ruvector/attention-executor.ts +782 -0
- package/src/integrations/ruvector/attention-mechanisms.ts +757 -0
- package/src/integrations/ruvector/attention.ts +1063 -0
- package/src/integrations/ruvector/gnn.ts +3050 -0
- package/src/integrations/ruvector/hyperbolic.ts +1948 -0
- package/src/integrations/ruvector/index.ts +394 -0
- package/src/integrations/ruvector/migrations/001_create_extension.sql +135 -0
- package/src/integrations/ruvector/migrations/002_create_vector_tables.sql +259 -0
- package/src/integrations/ruvector/migrations/003_create_indices.sql +328 -0
- package/src/integrations/ruvector/migrations/004_create_functions.sql +598 -0
- package/src/integrations/ruvector/migrations/005_create_attention_functions.sql +654 -0
- package/src/integrations/ruvector/migrations/006_create_gnn_functions.sql +728 -0
- package/src/integrations/ruvector/migrations/007_create_hyperbolic_functions.sql +762 -0
- package/src/integrations/ruvector/migrations/index.ts +35 -0
- package/src/integrations/ruvector/migrations/migrations.ts +647 -0
- package/src/integrations/ruvector/quantization.ts +2036 -0
- package/src/integrations/ruvector/ruvector-bridge.ts +2000 -0
- package/src/integrations/ruvector/self-learning.ts +2376 -0
- package/src/integrations/ruvector/streaming.ts +1737 -0
- package/src/integrations/ruvector/types.ts +1945 -0
- package/src/providers/index.ts +643 -0
- package/src/registry/dependency-graph.ts +568 -0
- package/src/registry/enhanced-plugin-registry.ts +994 -0
- package/src/registry/plugin-registry.ts +604 -0
- package/src/sdk/index.ts +563 -0
- package/src/security/index.ts +594 -0
- package/src/types/index.ts +446 -0
- package/src/workers/index.ts +700 -0
- package/tmp.json +0 -0
- package/tsconfig.json +25 -0
- 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
|
+
}
|