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.
- package/CHANGELOG.md +85 -0
- package/README.md +1 -1
- package/dist/agents/pool/AgentPool.d.ts +112 -0
- package/dist/agents/pool/AgentPool.d.ts.map +1 -0
- package/dist/agents/pool/AgentPool.js +573 -0
- package/dist/agents/pool/AgentPool.js.map +1 -0
- package/dist/agents/pool/QEAgentPoolFactory.d.ts +118 -0
- package/dist/agents/pool/QEAgentPoolFactory.d.ts.map +1 -0
- package/dist/agents/pool/QEAgentPoolFactory.js +251 -0
- package/dist/agents/pool/QEAgentPoolFactory.js.map +1 -0
- package/dist/agents/pool/index.d.ts +34 -0
- package/dist/agents/pool/index.d.ts.map +1 -0
- package/dist/agents/pool/index.js +44 -0
- package/dist/agents/pool/index.js.map +1 -0
- package/dist/agents/pool/types.d.ts +227 -0
- package/dist/agents/pool/types.d.ts.map +1 -0
- package/dist/agents/pool/types.js +28 -0
- package/dist/agents/pool/types.js.map +1 -0
- package/dist/mcp/handlers/agent-spawn.d.ts +71 -5
- package/dist/mcp/handlers/agent-spawn.d.ts.map +1 -1
- package/dist/mcp/handlers/agent-spawn.js +336 -110
- package/dist/mcp/handlers/agent-spawn.js.map +1 -1
- package/dist/mcp/handlers/fleet-init.d.ts +24 -0
- package/dist/mcp/handlers/fleet-init.d.ts.map +1 -1
- package/dist/mcp/handlers/fleet-init.js +56 -4
- package/dist/mcp/handlers/fleet-init.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +1 -1
- package/dist/mcp/server-instructions.js +1 -1
- package/dist/memory/HNSWPatternStore.d.ts.map +1 -1
- package/dist/memory/HNSWPatternStore.js.map +1 -1
- package/dist/plugins/BasePlugin.d.ts +111 -0
- package/dist/plugins/BasePlugin.d.ts.map +1 -0
- package/dist/plugins/BasePlugin.js +154 -0
- package/dist/plugins/BasePlugin.js.map +1 -0
- package/dist/plugins/PluginManager.d.ts +145 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/PluginManager.js +862 -0
- package/dist/plugins/PluginManager.js.map +1 -0
- package/dist/plugins/adapters/McpToolsPlugin.d.ts +98 -0
- package/dist/plugins/adapters/McpToolsPlugin.d.ts.map +1 -0
- package/dist/plugins/adapters/McpToolsPlugin.js +518 -0
- package/dist/plugins/adapters/McpToolsPlugin.js.map +1 -0
- package/dist/plugins/adapters/PlaywrightPlugin.d.ts +63 -0
- package/dist/plugins/adapters/PlaywrightPlugin.d.ts.map +1 -0
- package/dist/plugins/adapters/PlaywrightPlugin.js +451 -0
- package/dist/plugins/adapters/PlaywrightPlugin.js.map +1 -0
- package/dist/plugins/adapters/VitestPlugin.d.ts +74 -0
- package/dist/plugins/adapters/VitestPlugin.d.ts.map +1 -0
- package/dist/plugins/adapters/VitestPlugin.js +589 -0
- package/dist/plugins/adapters/VitestPlugin.js.map +1 -0
- package/dist/plugins/adapters/index.d.ts +8 -0
- package/dist/plugins/adapters/index.d.ts.map +1 -0
- package/dist/plugins/adapters/index.js +17 -0
- package/dist/plugins/adapters/index.js.map +1 -0
- package/dist/plugins/index.d.ts +32 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +48 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/types.d.ts +528 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +61 -0
- package/dist/plugins/types.js.map +1 -0
- 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
|