agentic-qe 2.6.0 → 2.6.1

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 (63) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +1 -1
  3. package/dist/agents/pool/AgentPool.d.ts +112 -0
  4. package/dist/agents/pool/AgentPool.d.ts.map +1 -0
  5. package/dist/agents/pool/AgentPool.js +573 -0
  6. package/dist/agents/pool/AgentPool.js.map +1 -0
  7. package/dist/agents/pool/QEAgentPoolFactory.d.ts +118 -0
  8. package/dist/agents/pool/QEAgentPoolFactory.d.ts.map +1 -0
  9. package/dist/agents/pool/QEAgentPoolFactory.js +251 -0
  10. package/dist/agents/pool/QEAgentPoolFactory.js.map +1 -0
  11. package/dist/agents/pool/index.d.ts +34 -0
  12. package/dist/agents/pool/index.d.ts.map +1 -0
  13. package/dist/agents/pool/index.js +44 -0
  14. package/dist/agents/pool/index.js.map +1 -0
  15. package/dist/agents/pool/types.d.ts +227 -0
  16. package/dist/agents/pool/types.d.ts.map +1 -0
  17. package/dist/agents/pool/types.js +28 -0
  18. package/dist/agents/pool/types.js.map +1 -0
  19. package/dist/mcp/handlers/agent-spawn.d.ts +71 -5
  20. package/dist/mcp/handlers/agent-spawn.d.ts.map +1 -1
  21. package/dist/mcp/handlers/agent-spawn.js +336 -110
  22. package/dist/mcp/handlers/agent-spawn.js.map +1 -1
  23. package/dist/mcp/handlers/fleet-init.d.ts +24 -0
  24. package/dist/mcp/handlers/fleet-init.d.ts.map +1 -1
  25. package/dist/mcp/handlers/fleet-init.js +56 -4
  26. package/dist/mcp/handlers/fleet-init.js.map +1 -1
  27. package/dist/mcp/server-instructions.d.ts +1 -1
  28. package/dist/mcp/server-instructions.js +1 -1
  29. package/dist/memory/HNSWPatternStore.d.ts.map +1 -1
  30. package/dist/memory/HNSWPatternStore.js.map +1 -1
  31. package/dist/plugins/BasePlugin.d.ts +111 -0
  32. package/dist/plugins/BasePlugin.d.ts.map +1 -0
  33. package/dist/plugins/BasePlugin.js +154 -0
  34. package/dist/plugins/BasePlugin.js.map +1 -0
  35. package/dist/plugins/PluginManager.d.ts +145 -0
  36. package/dist/plugins/PluginManager.d.ts.map +1 -0
  37. package/dist/plugins/PluginManager.js +862 -0
  38. package/dist/plugins/PluginManager.js.map +1 -0
  39. package/dist/plugins/adapters/McpToolsPlugin.d.ts +98 -0
  40. package/dist/plugins/adapters/McpToolsPlugin.d.ts.map +1 -0
  41. package/dist/plugins/adapters/McpToolsPlugin.js +518 -0
  42. package/dist/plugins/adapters/McpToolsPlugin.js.map +1 -0
  43. package/dist/plugins/adapters/PlaywrightPlugin.d.ts +63 -0
  44. package/dist/plugins/adapters/PlaywrightPlugin.d.ts.map +1 -0
  45. package/dist/plugins/adapters/PlaywrightPlugin.js +451 -0
  46. package/dist/plugins/adapters/PlaywrightPlugin.js.map +1 -0
  47. package/dist/plugins/adapters/VitestPlugin.d.ts +74 -0
  48. package/dist/plugins/adapters/VitestPlugin.d.ts.map +1 -0
  49. package/dist/plugins/adapters/VitestPlugin.js +589 -0
  50. package/dist/plugins/adapters/VitestPlugin.js.map +1 -0
  51. package/dist/plugins/adapters/index.d.ts +8 -0
  52. package/dist/plugins/adapters/index.d.ts.map +1 -0
  53. package/dist/plugins/adapters/index.js +17 -0
  54. package/dist/plugins/adapters/index.js.map +1 -0
  55. package/dist/plugins/index.d.ts +32 -0
  56. package/dist/plugins/index.d.ts.map +1 -0
  57. package/dist/plugins/index.js +48 -0
  58. package/dist/plugins/index.js.map +1 -0
  59. package/dist/plugins/types.d.ts +528 -0
  60. package/dist/plugins/types.d.ts.map +1 -0
  61. package/dist/plugins/types.js +61 -0
  62. package/dist/plugins/types.js.map +1 -0
  63. package/package.json +4 -1
@@ -0,0 +1,862 @@
1
+ "use strict";
2
+ /**
3
+ * Plugin Manager Implementation
4
+ * Phase 3 B2: Extensible Test Framework Adapters
5
+ *
6
+ * Handles plugin lifecycle:
7
+ * - Discovery and registration
8
+ * - Loading with lazy/eager strategies
9
+ * - Activation and deactivation
10
+ * - Hot-reload for development
11
+ * - Dependency resolution
12
+ * - Error isolation
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.PluginManager = void 0;
49
+ exports.getPluginManager = getPluginManager;
50
+ exports.resetPluginManager = resetPluginManager;
51
+ const events_1 = require("events");
52
+ const path = __importStar(require("path"));
53
+ const fs = __importStar(require("fs/promises"));
54
+ const fs_1 = require("fs");
55
+ const semver = __importStar(require("semver"));
56
+ const types_1 = require("./types");
57
+ // Package version for compatibility checking
58
+ const AGENTIC_QE_VERSION = '2.6.0';
59
+ /**
60
+ * Default plugin manager configuration
61
+ */
62
+ const DEFAULT_CONFIG = {
63
+ pluginDirs: ['./plugins', './node_modules/@agentic-qe'],
64
+ hotReload: process.env.NODE_ENV === 'development',
65
+ loadTimeout: 5000,
66
+ activationTimeout: 10000,
67
+ sandboxing: true,
68
+ autoActivate: false,
69
+ pluginConfigs: {},
70
+ };
71
+ /**
72
+ * Plugin Manager - Central hub for plugin lifecycle management
73
+ */
74
+ class PluginManager extends events_1.EventEmitter {
75
+ constructor(config = {}) {
76
+ super();
77
+ this.plugins = new Map();
78
+ this.services = new Map();
79
+ this.pluginStorage = new Map();
80
+ this.initialized = false;
81
+ this.fileWatchers = new Map();
82
+ this.pluginPathMap = new Map(); // path -> pluginId mapping
83
+ this.config = { ...DEFAULT_CONFIG, ...config };
84
+ }
85
+ /**
86
+ * Initialize the plugin manager
87
+ */
88
+ async initialize() {
89
+ if (this.initialized) {
90
+ return;
91
+ }
92
+ this.log('info', 'Initializing Plugin Manager');
93
+ // Discover plugins
94
+ const discovery = await this.discoverPlugins();
95
+ this.log('info', `Discovered ${discovery.plugins.length} plugins`);
96
+ // Load discovered plugins
97
+ for (const metadata of discovery.plugins) {
98
+ try {
99
+ await this.loadPlugin(metadata.id);
100
+ }
101
+ catch (error) {
102
+ this.log('error', `Failed to load plugin ${metadata.id}`, error);
103
+ }
104
+ }
105
+ // Auto-activate if configured
106
+ if (this.config.autoActivate) {
107
+ await this.activateAll();
108
+ }
109
+ // Setup hot reload if enabled
110
+ if (this.config.hotReload) {
111
+ await this.setupHotReload();
112
+ }
113
+ this.initialized = true;
114
+ this.log('info', 'Plugin Manager initialized');
115
+ }
116
+ /**
117
+ * Discover plugins in configured directories
118
+ * This method actually loads plugins from disk using dynamic import
119
+ */
120
+ async discoverPlugins() {
121
+ const startTime = Date.now();
122
+ const plugins = [];
123
+ const errors = [];
124
+ for (const dir of this.config.pluginDirs) {
125
+ try {
126
+ const resolvedDir = path.resolve(dir);
127
+ const exists = await fs.access(resolvedDir).then(() => true).catch(() => false);
128
+ if (!exists) {
129
+ continue;
130
+ }
131
+ const entries = await fs.readdir(resolvedDir, { withFileTypes: true });
132
+ for (const entry of entries) {
133
+ if (!entry.isDirectory())
134
+ continue;
135
+ const pluginPath = path.join(resolvedDir, entry.name);
136
+ const packageJsonPath = path.join(pluginPath, 'package.json');
137
+ try {
138
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
139
+ // Check if it's an agentic-qe plugin
140
+ if (packageJson.agenticQEPlugin) {
141
+ const metadata = this.extractMetadata(packageJson, pluginPath);
142
+ if (this.isCompatible(metadata)) {
143
+ // Actually load the plugin from disk
144
+ const plugin = await this.loadPluginFromPath(pluginPath);
145
+ if (plugin) {
146
+ // Register the loaded plugin
147
+ if (!this.plugins.has(plugin.metadata.id)) {
148
+ await this.registerPlugin(plugin);
149
+ plugins.push(plugin.metadata);
150
+ this.log('info', `Discovered and loaded plugin: ${plugin.metadata.id}`);
151
+ }
152
+ }
153
+ else {
154
+ // Fallback: just report metadata if loading failed
155
+ plugins.push(metadata);
156
+ this.emit('plugin:discovered', { metadata });
157
+ }
158
+ }
159
+ }
160
+ }
161
+ catch (error) {
162
+ errors.push({
163
+ path: pluginPath,
164
+ error: error instanceof Error ? error.message : String(error),
165
+ });
166
+ }
167
+ }
168
+ }
169
+ catch (error) {
170
+ errors.push({
171
+ path: dir,
172
+ error: error instanceof Error ? error.message : String(error),
173
+ });
174
+ }
175
+ }
176
+ return {
177
+ plugins,
178
+ errors,
179
+ duration: Date.now() - startTime,
180
+ };
181
+ }
182
+ /**
183
+ * Register a plugin programmatically
184
+ */
185
+ async registerPlugin(plugin) {
186
+ const { id } = plugin.metadata;
187
+ if (this.plugins.has(id)) {
188
+ throw new Error(`Plugin ${id} is already registered`);
189
+ }
190
+ if (!this.isCompatible(plugin.metadata)) {
191
+ throw new Error(`Plugin ${id} is not compatible with agentic-qe ${AGENTIC_QE_VERSION}`);
192
+ }
193
+ const registration = {
194
+ plugin,
195
+ state: types_1.PluginState.DISCOVERED,
196
+ dependenciesResolved: false,
197
+ };
198
+ this.plugins.set(id, registration);
199
+ this.emit('plugin:discovered', { metadata: plugin.metadata });
200
+ this.log('info', `Registered plugin: ${id}`);
201
+ }
202
+ /**
203
+ * Load a plugin by ID
204
+ */
205
+ async loadPlugin(pluginId) {
206
+ const registration = this.plugins.get(pluginId);
207
+ if (!registration) {
208
+ throw new Error(`Plugin ${pluginId} not found`);
209
+ }
210
+ if (registration.state !== types_1.PluginState.DISCOVERED) {
211
+ return; // Already loaded or loading
212
+ }
213
+ this.emit('plugin:loading', { pluginId });
214
+ registration.state = types_1.PluginState.LOADING;
215
+ try {
216
+ // Resolve dependencies first
217
+ await this.resolveDependencies(pluginId);
218
+ // Create plugin context
219
+ const context = this.createPluginContext(registration.plugin);
220
+ registration.context = context;
221
+ // Call onLoad if defined
222
+ if (registration.plugin.onLoad) {
223
+ await this.withTimeout(Promise.resolve(registration.plugin.onLoad(context)), this.config.loadTimeout, `Plugin ${pluginId} load timeout`);
224
+ }
225
+ registration.state = types_1.PluginState.LOADED;
226
+ registration.loadedAt = new Date();
227
+ this.emit('plugin:loaded', { pluginId });
228
+ this.log('info', `Loaded plugin: ${pluginId}`);
229
+ }
230
+ catch (error) {
231
+ registration.state = types_1.PluginState.ERROR;
232
+ registration.lastError = error instanceof Error ? error : new Error(String(error));
233
+ this.emit('plugin:error', { pluginId, error: registration.lastError });
234
+ throw error;
235
+ }
236
+ }
237
+ /**
238
+ * Activate a plugin
239
+ */
240
+ async activatePlugin(pluginId) {
241
+ const registration = this.plugins.get(pluginId);
242
+ if (!registration) {
243
+ throw new Error(`Plugin ${pluginId} not found`);
244
+ }
245
+ // Load first if needed
246
+ if (registration.state === types_1.PluginState.DISCOVERED) {
247
+ await this.loadPlugin(pluginId);
248
+ }
249
+ if (registration.state !== types_1.PluginState.LOADED &&
250
+ registration.state !== types_1.PluginState.INACTIVE) {
251
+ return; // Already active or activating
252
+ }
253
+ this.emit('plugin:activating', { pluginId });
254
+ registration.state = types_1.PluginState.ACTIVATING;
255
+ try {
256
+ // Ensure dependencies are activated first
257
+ for (const dep of registration.plugin.metadata.dependencies || []) {
258
+ if (!dep.optional) {
259
+ await this.activatePlugin(dep.pluginId);
260
+ }
261
+ }
262
+ // Call onActivate if defined
263
+ if (registration.plugin.onActivate && registration.context) {
264
+ await this.withTimeout(registration.plugin.onActivate(registration.context), this.config.activationTimeout, `Plugin ${pluginId} activation timeout`);
265
+ }
266
+ registration.state = types_1.PluginState.ACTIVE;
267
+ registration.activatedAt = new Date();
268
+ this.emit('plugin:activated', { pluginId });
269
+ this.log('info', `Activated plugin: ${pluginId}`);
270
+ }
271
+ catch (error) {
272
+ registration.state = types_1.PluginState.ERROR;
273
+ registration.lastError = error instanceof Error ? error : new Error(String(error));
274
+ this.emit('plugin:error', { pluginId, error: registration.lastError });
275
+ throw error;
276
+ }
277
+ }
278
+ /**
279
+ * Deactivate a plugin
280
+ */
281
+ async deactivatePlugin(pluginId) {
282
+ const registration = this.plugins.get(pluginId);
283
+ if (!registration) {
284
+ throw new Error(`Plugin ${pluginId} not found`);
285
+ }
286
+ if (registration.state !== types_1.PluginState.ACTIVE) {
287
+ return;
288
+ }
289
+ this.emit('plugin:deactivating', { pluginId });
290
+ registration.state = types_1.PluginState.DEACTIVATING;
291
+ try {
292
+ // Deactivate dependents first
293
+ for (const [id, reg] of this.plugins) {
294
+ const deps = reg.plugin.metadata.dependencies || [];
295
+ if (deps.some(d => d.pluginId === pluginId)) {
296
+ await this.deactivatePlugin(id);
297
+ }
298
+ }
299
+ // Call onDeactivate if defined
300
+ if (registration.plugin.onDeactivate && registration.context) {
301
+ await registration.plugin.onDeactivate(registration.context);
302
+ }
303
+ registration.state = types_1.PluginState.INACTIVE;
304
+ this.emit('plugin:deactivated', { pluginId });
305
+ this.log('info', `Deactivated plugin: ${pluginId}`);
306
+ }
307
+ catch (error) {
308
+ registration.state = types_1.PluginState.ERROR;
309
+ registration.lastError = error instanceof Error ? error : new Error(String(error));
310
+ this.emit('plugin:error', { pluginId, error: registration.lastError });
311
+ throw error;
312
+ }
313
+ }
314
+ /**
315
+ * Unload a plugin
316
+ */
317
+ async unloadPlugin(pluginId) {
318
+ const registration = this.plugins.get(pluginId);
319
+ if (!registration) {
320
+ return;
321
+ }
322
+ // Deactivate first if active
323
+ if (registration.state === types_1.PluginState.ACTIVE) {
324
+ await this.deactivatePlugin(pluginId);
325
+ }
326
+ // Call onUnload if defined
327
+ if (registration.plugin.onUnload && registration.context) {
328
+ registration.plugin.onUnload(registration.context);
329
+ }
330
+ // Clear storage
331
+ this.pluginStorage.delete(pluginId);
332
+ this.plugins.delete(pluginId);
333
+ this.emit('plugin:unloaded', { pluginId });
334
+ this.log('info', `Unloaded plugin: ${pluginId}`);
335
+ }
336
+ /**
337
+ * Activate all loaded plugins
338
+ */
339
+ async activateAll() {
340
+ for (const [pluginId, registration] of this.plugins) {
341
+ if (registration.state === types_1.PluginState.LOADED ||
342
+ registration.state === types_1.PluginState.INACTIVE) {
343
+ try {
344
+ await this.activatePlugin(pluginId);
345
+ }
346
+ catch (error) {
347
+ this.log('error', `Failed to activate plugin ${pluginId}`, error);
348
+ }
349
+ }
350
+ }
351
+ }
352
+ /**
353
+ * Deactivate all plugins
354
+ */
355
+ async deactivateAll() {
356
+ for (const [pluginId, registration] of this.plugins) {
357
+ if (registration.state === types_1.PluginState.ACTIVE) {
358
+ try {
359
+ await this.deactivatePlugin(pluginId);
360
+ }
361
+ catch (error) {
362
+ this.log('error', `Failed to deactivate plugin ${pluginId}`, error);
363
+ }
364
+ }
365
+ }
366
+ }
367
+ /**
368
+ * Get a plugin by ID
369
+ */
370
+ getPlugin(pluginId) {
371
+ const registration = this.plugins.get(pluginId);
372
+ return registration?.plugin;
373
+ }
374
+ /**
375
+ * Check if plugin exists
376
+ */
377
+ hasPlugin(pluginId) {
378
+ return this.plugins.has(pluginId);
379
+ }
380
+ /**
381
+ * Get plugins by category
382
+ */
383
+ getPluginsByCategory(category) {
384
+ const result = [];
385
+ for (const registration of this.plugins.values()) {
386
+ if (registration.plugin.metadata.category === category) {
387
+ result.push(registration.plugin);
388
+ }
389
+ }
390
+ return result;
391
+ }
392
+ /**
393
+ * Get all active plugins
394
+ */
395
+ getActivePlugins() {
396
+ const result = [];
397
+ for (const registration of this.plugins.values()) {
398
+ if (registration.state === types_1.PluginState.ACTIVE) {
399
+ result.push(registration.plugin);
400
+ }
401
+ }
402
+ return result;
403
+ }
404
+ /**
405
+ * Get all registered plugins
406
+ */
407
+ getAllPlugins() {
408
+ return Array.from(this.plugins.values()).map(r => r.plugin);
409
+ }
410
+ /**
411
+ * Check if plugin manager is initialized
412
+ */
413
+ isInitialized() {
414
+ return this.initialized;
415
+ }
416
+ /**
417
+ * Get plugin state
418
+ */
419
+ getPluginState(pluginId) {
420
+ return this.plugins.get(pluginId)?.state;
421
+ }
422
+ /**
423
+ * Register a service
424
+ */
425
+ registerService(name, service) {
426
+ this.services.set(name, service);
427
+ this.log('debug', `Registered service: ${name}`);
428
+ }
429
+ /**
430
+ * Get a service
431
+ */
432
+ getService(name) {
433
+ return this.services.get(name);
434
+ }
435
+ /**
436
+ * Shutdown the plugin manager
437
+ */
438
+ async shutdown() {
439
+ this.log('info', 'Shutting down Plugin Manager');
440
+ // Stop file watchers
441
+ for (const [dir, watcher] of this.fileWatchers) {
442
+ this.log('debug', `Closing file watcher for: ${dir}`);
443
+ watcher.close();
444
+ }
445
+ this.fileWatchers.clear();
446
+ this.pluginPathMap.clear();
447
+ // Deactivate all plugins
448
+ await this.deactivateAll();
449
+ // Unload all plugins
450
+ for (const pluginId of this.plugins.keys()) {
451
+ await this.unloadPlugin(pluginId);
452
+ }
453
+ this.services.clear();
454
+ this.pluginStorage.clear();
455
+ this.initialized = false;
456
+ this.log('info', 'Plugin Manager shutdown complete');
457
+ }
458
+ // === Private Methods ===
459
+ extractMetadata(packageJson, pluginPath) {
460
+ const pluginConfig = packageJson.agenticQEPlugin || {};
461
+ return {
462
+ id: packageJson.name,
463
+ name: pluginConfig.displayName || packageJson.name,
464
+ version: packageJson.version,
465
+ description: packageJson.description || '',
466
+ author: typeof packageJson.author === 'string'
467
+ ? packageJson.author
468
+ : packageJson.author?.name || 'Unknown',
469
+ homepage: packageJson.homepage,
470
+ license: packageJson.license,
471
+ keywords: packageJson.keywords,
472
+ minAgenticQEVersion: pluginConfig.minVersion || '2.0.0',
473
+ maxAgenticQEVersion: pluginConfig.maxVersion,
474
+ dependencies: pluginConfig.dependencies,
475
+ category: pluginConfig.category || types_1.PluginCategory.UTILITY,
476
+ enabledByDefault: pluginConfig.enabledByDefault ?? false,
477
+ };
478
+ }
479
+ isCompatible(metadata) {
480
+ const { minAgenticQEVersion, maxAgenticQEVersion } = metadata;
481
+ if (!semver.valid(AGENTIC_QE_VERSION)) {
482
+ return true; // Skip check if version is invalid
483
+ }
484
+ if (minAgenticQEVersion && !semver.gte(AGENTIC_QE_VERSION, minAgenticQEVersion)) {
485
+ return false;
486
+ }
487
+ if (maxAgenticQEVersion && !semver.lte(AGENTIC_QE_VERSION, maxAgenticQEVersion)) {
488
+ return false;
489
+ }
490
+ return true;
491
+ }
492
+ async resolveDependencies(pluginId) {
493
+ const registration = this.plugins.get(pluginId);
494
+ if (!registration || registration.dependenciesResolved) {
495
+ return;
496
+ }
497
+ const dependencies = registration.plugin.metadata.dependencies || [];
498
+ for (const dep of dependencies) {
499
+ const depRegistration = this.plugins.get(dep.pluginId);
500
+ if (!depRegistration) {
501
+ if (dep.optional) {
502
+ this.log('warn', `Optional dependency ${dep.pluginId} not found for ${pluginId}`);
503
+ continue;
504
+ }
505
+ throw new Error(`Required dependency ${dep.pluginId} not found for ${pluginId}`);
506
+ }
507
+ // Check version
508
+ if (!semver.satisfies(depRegistration.plugin.metadata.version, dep.versionRange)) {
509
+ if (dep.optional) {
510
+ this.log('warn', `Optional dependency ${dep.pluginId} version mismatch for ${pluginId}`);
511
+ continue;
512
+ }
513
+ throw new Error(`Dependency ${dep.pluginId} version ${depRegistration.plugin.metadata.version} ` +
514
+ `does not satisfy ${dep.versionRange} for ${pluginId}`);
515
+ }
516
+ // Load dependency first
517
+ await this.loadPlugin(dep.pluginId);
518
+ }
519
+ registration.dependenciesResolved = true;
520
+ }
521
+ createPluginContext(plugin) {
522
+ const pluginId = plugin.metadata.id;
523
+ // Create plugin-specific storage
524
+ if (!this.pluginStorage.has(pluginId)) {
525
+ this.pluginStorage.set(pluginId, new Map());
526
+ }
527
+ const storage = this.pluginStorage.get(pluginId);
528
+ const context = {
529
+ metadata: plugin.metadata,
530
+ agenticQEVersion: AGENTIC_QE_VERSION,
531
+ pluginManager: this.createPluginManagerAPI(),
532
+ logger: this.createPluginLogger(pluginId),
533
+ config: this.createPluginConfigStore(pluginId),
534
+ events: this.createPluginEventBus(pluginId),
535
+ storage: this.createPluginStorage(pluginId, storage),
536
+ };
537
+ return context;
538
+ }
539
+ createPluginManagerAPI() {
540
+ return {
541
+ getPlugin: (id) => this.getPlugin(id),
542
+ hasPlugin: (id) => this.hasPlugin(id),
543
+ getPluginsByCategory: (category) => this.getPluginsByCategory(category),
544
+ registerService: (name, service) => this.registerService(name, service),
545
+ getService: (name) => this.getService(name),
546
+ requestActivation: (pluginId) => this.activatePlugin(pluginId),
547
+ };
548
+ }
549
+ createPluginLogger(pluginId) {
550
+ const prefix = `[Plugin:${pluginId}]`;
551
+ return {
552
+ debug: (msg, ...args) => this.log('debug', `${prefix} ${msg}`, ...args),
553
+ info: (msg, ...args) => this.log('info', `${prefix} ${msg}`, ...args),
554
+ warn: (msg, ...args) => this.log('warn', `${prefix} ${msg}`, ...args),
555
+ error: (msg, ...args) => this.log('error', `${prefix} ${msg}`, ...args),
556
+ };
557
+ }
558
+ createPluginConfigStore(pluginId) {
559
+ const config = this.config.pluginConfigs[pluginId] || {};
560
+ const store = new Map(Object.entries(config));
561
+ return {
562
+ get: (key, defaultValue) => store.get(key) ?? defaultValue,
563
+ set: (key, value) => {
564
+ store.set(key, value);
565
+ this.emit('plugin:configChanged', { pluginId, changes: { [key]: value } });
566
+ },
567
+ has: (key) => store.has(key),
568
+ getAll: () => Object.fromEntries(store),
569
+ reset: () => {
570
+ store.clear();
571
+ Object.entries(config).forEach(([k, v]) => store.set(k, v));
572
+ },
573
+ };
574
+ }
575
+ createPluginEventBus(pluginId) {
576
+ const prefix = `plugin:${pluginId}:`;
577
+ const handlers = new Map();
578
+ return {
579
+ emit: (event, data) => {
580
+ this.emit(`${prefix}${event}`, data);
581
+ },
582
+ on: (event, handler) => {
583
+ const fullEvent = `${prefix}${event}`;
584
+ if (!handlers.has(fullEvent)) {
585
+ handlers.set(fullEvent, new Set());
586
+ }
587
+ handlers.get(fullEvent).add(handler);
588
+ this.on(fullEvent, handler);
589
+ return () => {
590
+ handlers.get(fullEvent)?.delete(handler);
591
+ this.off(fullEvent, handler);
592
+ };
593
+ },
594
+ once: (event, handler) => {
595
+ const fullEvent = `${prefix}${event}`;
596
+ this.once(fullEvent, handler);
597
+ return () => this.off(fullEvent, handler);
598
+ },
599
+ off: (event) => {
600
+ const fullEvent = `${prefix}${event}`;
601
+ const eventHandlers = handlers.get(fullEvent);
602
+ if (eventHandlers) {
603
+ for (const handler of eventHandlers) {
604
+ this.off(fullEvent, handler);
605
+ }
606
+ handlers.delete(fullEvent);
607
+ }
608
+ },
609
+ };
610
+ }
611
+ createPluginStorage(pluginId, storage) {
612
+ return {
613
+ get: async (key) => storage.get(key),
614
+ set: async (key, value) => { storage.set(key, value); },
615
+ delete: async (key) => { storage.delete(key); },
616
+ keys: async () => Array.from(storage.keys()),
617
+ clear: async () => { storage.clear(); },
618
+ };
619
+ }
620
+ async setupHotReload() {
621
+ this.log('info', 'Setting up hot reload for plugin development');
622
+ // Track reload debouncing per plugin
623
+ const reloadDebounce = new Map();
624
+ const DEBOUNCE_MS = 300; // Debounce rapid file changes
625
+ for (const dir of this.config.pluginDirs) {
626
+ try {
627
+ const resolvedDir = path.resolve(dir);
628
+ const exists = await fs.access(resolvedDir).then(() => true).catch(() => false);
629
+ if (!exists) {
630
+ continue;
631
+ }
632
+ // Create watcher for this directory
633
+ const watcher = (0, fs_1.watch)(resolvedDir, { recursive: true }, async (eventType, filename) => {
634
+ if (!filename)
635
+ return;
636
+ const fullPath = path.join(resolvedDir, filename);
637
+ const pluginId = this.getPluginIdFromPath(fullPath);
638
+ if (!pluginId) {
639
+ // Not a tracked plugin, skip
640
+ return;
641
+ }
642
+ // Debounce rapid changes (editors often save multiple times)
643
+ const existingTimeout = reloadDebounce.get(pluginId);
644
+ if (existingTimeout) {
645
+ clearTimeout(existingTimeout);
646
+ }
647
+ reloadDebounce.set(pluginId, setTimeout(async () => {
648
+ reloadDebounce.delete(pluginId);
649
+ try {
650
+ this.log('info', `Hot reload triggered for ${pluginId} (${eventType}: ${filename})`);
651
+ await this.reloadPlugin(pluginId);
652
+ this.emit('plugin:hotReloaded', { pluginId, filename, eventType });
653
+ }
654
+ catch (error) {
655
+ this.log('error', `Hot reload failed for ${pluginId}`, error);
656
+ this.emit('plugin:hotReloadError', { pluginId, error });
657
+ }
658
+ }, DEBOUNCE_MS));
659
+ });
660
+ this.fileWatchers.set(resolvedDir, watcher);
661
+ this.log('debug', `Watching plugin directory: ${resolvedDir}`);
662
+ }
663
+ catch (error) {
664
+ this.log('warn', `Failed to setup hot reload for ${dir}`, error);
665
+ }
666
+ }
667
+ this.log('info', `Hot reload enabled, watching ${this.fileWatchers.size} directories`);
668
+ }
669
+ /**
670
+ * Reload a plugin (deactivate, unload, rediscover, load, reactivate)
671
+ */
672
+ async reloadPlugin(pluginId) {
673
+ const registration = this.plugins.get(pluginId);
674
+ if (!registration) {
675
+ throw new Error(`Plugin ${pluginId} not found`);
676
+ }
677
+ const wasActive = registration.state === types_1.PluginState.ACTIVE;
678
+ const pluginPath = this.getPluginPath(pluginId);
679
+ this.log('info', `Reloading plugin: ${pluginId}`);
680
+ this.emit('plugin:reloading', { pluginId });
681
+ // Step 1: Deactivate if active
682
+ if (wasActive) {
683
+ await this.deactivatePlugin(pluginId);
684
+ }
685
+ // Step 2: Unload the plugin
686
+ await this.unloadPlugin(pluginId);
687
+ // Step 3: Re-discover and load from disk
688
+ if (pluginPath) {
689
+ try {
690
+ const plugin = await this.loadPluginFromPath(pluginPath);
691
+ if (plugin) {
692
+ await this.registerPlugin(plugin);
693
+ await this.loadPlugin(pluginId);
694
+ // Step 4: Reactivate if it was active before
695
+ if (wasActive) {
696
+ await this.activatePlugin(pluginId);
697
+ }
698
+ this.log('info', `Successfully reloaded plugin: ${pluginId}`);
699
+ this.emit('plugin:reloaded', { pluginId });
700
+ }
701
+ }
702
+ catch (error) {
703
+ this.log('error', `Failed to reload plugin from ${pluginPath}`, error);
704
+ throw error;
705
+ }
706
+ }
707
+ }
708
+ /**
709
+ * Get plugin ID from a file path within plugin directories
710
+ */
711
+ getPluginIdFromPath(filePath) {
712
+ // Check if we already have a mapping
713
+ for (const [mappedPath, pluginId] of this.pluginPathMap) {
714
+ if (filePath.startsWith(mappedPath)) {
715
+ return pluginId;
716
+ }
717
+ }
718
+ // Try to determine plugin from package.json in parent directories
719
+ let currentDir = path.dirname(filePath);
720
+ const roots = this.config.pluginDirs.map(d => path.resolve(d));
721
+ while (currentDir && !roots.some(r => currentDir === r || currentDir === path.dirname(r))) {
722
+ const packageJsonPath = path.join(currentDir, 'package.json');
723
+ // Check synchronously if we have a registered plugin at this path
724
+ for (const [pluginId, registration] of this.plugins) {
725
+ if (registration.plugin.metadata.id === pluginId) {
726
+ // Check if this plugin's path matches
727
+ const existingPath = this.getPluginPath(pluginId);
728
+ if (existingPath && currentDir.startsWith(existingPath)) {
729
+ this.pluginPathMap.set(currentDir, pluginId);
730
+ return pluginId;
731
+ }
732
+ }
733
+ }
734
+ currentDir = path.dirname(currentDir);
735
+ }
736
+ return undefined;
737
+ }
738
+ /**
739
+ * Get the filesystem path for a plugin
740
+ */
741
+ getPluginPath(pluginId) {
742
+ // Reverse lookup from pluginPathMap
743
+ for (const [pluginPath, id] of this.pluginPathMap) {
744
+ if (id === pluginId) {
745
+ return pluginPath;
746
+ }
747
+ }
748
+ return undefined;
749
+ }
750
+ /**
751
+ * Load a plugin from a filesystem path
752
+ */
753
+ async loadPluginFromPath(pluginPath) {
754
+ try {
755
+ const packageJsonPath = path.join(pluginPath, 'package.json');
756
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
757
+ if (!packageJson.agenticQEPlugin) {
758
+ return null;
759
+ }
760
+ // Determine the entry point
761
+ const mainFile = packageJson.main || 'index.js';
762
+ const entryPoint = path.join(pluginPath, mainFile);
763
+ // Clear require cache for hot reload
764
+ this.clearRequireCache(entryPoint);
765
+ // Dynamic import the plugin
766
+ const pluginModule = await Promise.resolve(`${entryPoint}`).then(s => __importStar(require(s)));
767
+ // Get the plugin instance from factory or direct export
768
+ let plugin = null;
769
+ if (typeof pluginModule.createPlugin === 'function') {
770
+ plugin = pluginModule.createPlugin();
771
+ }
772
+ else if (pluginModule.default && typeof pluginModule.default === 'object') {
773
+ plugin = pluginModule.default;
774
+ }
775
+ else if (pluginModule.plugin && typeof pluginModule.plugin === 'object') {
776
+ plugin = pluginModule.plugin;
777
+ }
778
+ if (plugin) {
779
+ // Store the path mapping
780
+ this.pluginPathMap.set(pluginPath, plugin.metadata.id);
781
+ }
782
+ return plugin;
783
+ }
784
+ catch (error) {
785
+ this.log('error', `Failed to load plugin from path: ${pluginPath}`, error);
786
+ return null;
787
+ }
788
+ }
789
+ /**
790
+ * Clear require cache for a module and its dependencies
791
+ */
792
+ clearRequireCache(modulePath) {
793
+ try {
794
+ const resolved = require.resolve(modulePath);
795
+ const cached = require.cache[resolved];
796
+ if (cached) {
797
+ // Clear children first (dependencies)
798
+ if (cached.children) {
799
+ for (const child of cached.children) {
800
+ // Only clear cache for files within the same plugin
801
+ if (child.filename.startsWith(path.dirname(modulePath))) {
802
+ this.clearRequireCache(child.filename);
803
+ }
804
+ }
805
+ }
806
+ delete require.cache[resolved];
807
+ }
808
+ }
809
+ catch {
810
+ // Module not in require cache, ignore
811
+ }
812
+ }
813
+ async withTimeout(promise, timeoutMs, message) {
814
+ return Promise.race([
815
+ promise,
816
+ new Promise((_, reject) => setTimeout(() => reject(new Error(message)), timeoutMs)),
817
+ ]);
818
+ }
819
+ log(level, message, ...args) {
820
+ const timestamp = new Date().toISOString();
821
+ const formattedMessage = `[${timestamp}] [PluginManager] [${level.toUpperCase()}] ${message}`;
822
+ switch (level) {
823
+ case 'error':
824
+ console.error(formattedMessage, ...args);
825
+ break;
826
+ case 'warn':
827
+ console.warn(formattedMessage, ...args);
828
+ break;
829
+ case 'debug':
830
+ if (process.env.DEBUG) {
831
+ console.debug(formattedMessage, ...args);
832
+ }
833
+ break;
834
+ default:
835
+ console.log(formattedMessage, ...args);
836
+ }
837
+ }
838
+ }
839
+ exports.PluginManager = PluginManager;
840
+ /**
841
+ * Global plugin manager instance
842
+ */
843
+ let globalPluginManager = null;
844
+ /**
845
+ * Get or create the global plugin manager instance
846
+ */
847
+ function getPluginManager(config) {
848
+ if (!globalPluginManager) {
849
+ globalPluginManager = new PluginManager(config);
850
+ }
851
+ return globalPluginManager;
852
+ }
853
+ /**
854
+ * Reset the global plugin manager (for testing)
855
+ */
856
+ async function resetPluginManager() {
857
+ if (globalPluginManager) {
858
+ await globalPluginManager.shutdown();
859
+ globalPluginManager = null;
860
+ }
861
+ }
862
+ //# sourceMappingURL=PluginManager.js.map