sdd-mcp-server 1.0.0
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/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/__tests__/setup.d.ts +44 -0
- package/dist/__tests__/setup.js +178 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/test-helpers/mock-factories.d.ts +26 -0
- package/dist/__tests__/test-helpers/mock-factories.js +466 -0
- package/dist/__tests__/test-helpers/mock-factories.js.map +1 -0
- package/dist/adapters/cli/SDDToolAdapter.d.ts +26 -0
- package/dist/adapters/cli/SDDToolAdapter.js +273 -0
- package/dist/adapters/cli/SDDToolAdapter.js.map +1 -0
- package/dist/application/services/CodebaseAnalysisService.d.ts +38 -0
- package/dist/application/services/CodebaseAnalysisService.js +737 -0
- package/dist/application/services/CodebaseAnalysisService.js.map +1 -0
- package/dist/application/services/LocalizationService.d.ts +184 -0
- package/dist/application/services/LocalizationService.js +536 -0
- package/dist/application/services/LocalizationService.js.map +1 -0
- package/dist/application/services/ProjectContextService.d.ts +61 -0
- package/dist/application/services/ProjectContextService.js +550 -0
- package/dist/application/services/ProjectContextService.js.map +1 -0
- package/dist/application/services/ProjectInitializationService.d.ts +57 -0
- package/dist/application/services/ProjectInitializationService.js +485 -0
- package/dist/application/services/ProjectInitializationService.js.map +1 -0
- package/dist/application/services/ProjectService.d.ts +19 -0
- package/dist/application/services/ProjectService.js +159 -0
- package/dist/application/services/ProjectService.js.map +1 -0
- package/dist/application/services/QualityGateService.d.ts +62 -0
- package/dist/application/services/QualityGateService.js +428 -0
- package/dist/application/services/QualityGateService.js.map +1 -0
- package/dist/application/services/QualityService.d.ts +43 -0
- package/dist/application/services/QualityService.js +245 -0
- package/dist/application/services/QualityService.js.map +1 -0
- package/dist/application/services/SteeringDocumentService.d.ts +62 -0
- package/dist/application/services/SteeringDocumentService.js +694 -0
- package/dist/application/services/SteeringDocumentService.js.map +1 -0
- package/dist/application/services/TemplateService.d.ts +47 -0
- package/dist/application/services/TemplateService.js +438 -0
- package/dist/application/services/TemplateService.js.map +1 -0
- package/dist/application/services/WorkflowEngineService.d.ts +56 -0
- package/dist/application/services/WorkflowEngineService.js +348 -0
- package/dist/application/services/WorkflowEngineService.js.map +1 -0
- package/dist/application/services/WorkflowService.d.ts +22 -0
- package/dist/application/services/WorkflowService.js +147 -0
- package/dist/application/services/WorkflowService.js.map +1 -0
- package/dist/application/services/WorkflowValidationService.d.ts +51 -0
- package/dist/application/services/WorkflowValidationService.js +665 -0
- package/dist/application/services/WorkflowValidationService.js.map +1 -0
- package/dist/domain/context/ProjectContext.d.ts +350 -0
- package/dist/domain/context/ProjectContext.js +138 -0
- package/dist/domain/context/ProjectContext.js.map +1 -0
- package/dist/domain/i18n/index.d.ts +286 -0
- package/dist/domain/i18n/index.js +97 -0
- package/dist/domain/i18n/index.js.map +1 -0
- package/dist/domain/plugins/index.d.ts +498 -0
- package/dist/domain/plugins/index.js +157 -0
- package/dist/domain/plugins/index.js.map +1 -0
- package/dist/domain/ports.d.ts +54 -0
- package/dist/domain/ports.js +3 -0
- package/dist/domain/ports.js.map +1 -0
- package/dist/domain/quality/index.d.ts +361 -0
- package/dist/domain/quality/index.js +113 -0
- package/dist/domain/quality/index.js.map +1 -0
- package/dist/domain/services/DomainService.d.ts +18 -0
- package/dist/domain/services/DomainService.js +71 -0
- package/dist/domain/services/DomainService.js.map +1 -0
- package/dist/domain/templates/index.d.ts +158 -0
- package/dist/domain/templates/index.js +22 -0
- package/dist/domain/templates/index.js.map +1 -0
- package/dist/domain/types.d.ts +115 -0
- package/dist/domain/types.js +37 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/domain/workflow/WorkflowStateMachine.d.ts +62 -0
- package/dist/domain/workflow/WorkflowStateMachine.js +286 -0
- package/dist/domain/workflow/WorkflowStateMachine.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/adapters/AjvValidationAdapter.d.ts +7 -0
- package/dist/infrastructure/adapters/AjvValidationAdapter.js +37 -0
- package/dist/infrastructure/adapters/AjvValidationAdapter.js.map +1 -0
- package/dist/infrastructure/adapters/ConsoleLoggerAdapter.d.ts +8 -0
- package/dist/infrastructure/adapters/ConsoleLoggerAdapter.js +41 -0
- package/dist/infrastructure/adapters/ConsoleLoggerAdapter.js.map +1 -0
- package/dist/infrastructure/adapters/FileBasedTaskTracker.d.ts +9 -0
- package/dist/infrastructure/adapters/FileBasedTaskTracker.js +41 -0
- package/dist/infrastructure/adapters/FileBasedTaskTracker.js.map +1 -0
- package/dist/infrastructure/adapters/HandlebarsTemplateEngine.d.ts +8 -0
- package/dist/infrastructure/adapters/HandlebarsTemplateEngine.js +38 -0
- package/dist/infrastructure/adapters/HandlebarsTemplateEngine.js.map +1 -0
- package/dist/infrastructure/adapters/JsonConfigurationAdapter.d.ts +7 -0
- package/dist/infrastructure/adapters/JsonConfigurationAdapter.js +24 -0
- package/dist/infrastructure/adapters/JsonConfigurationAdapter.js.map +1 -0
- package/dist/infrastructure/adapters/LinusQualityAnalyzer.d.ts +9 -0
- package/dist/infrastructure/adapters/LinusQualityAnalyzer.js +75 -0
- package/dist/infrastructure/adapters/LinusQualityAnalyzer.js.map +1 -0
- package/dist/infrastructure/adapters/NodeFileSystemAdapter.d.ts +12 -0
- package/dist/infrastructure/adapters/NodeFileSystemAdapter.js +39 -0
- package/dist/infrastructure/adapters/NodeFileSystemAdapter.js.map +1 -0
- package/dist/infrastructure/di/container.d.ts +3 -0
- package/dist/infrastructure/di/container.js +98 -0
- package/dist/infrastructure/di/container.js.map +1 -0
- package/dist/infrastructure/di/types.d.ts +39 -0
- package/dist/infrastructure/di/types.js +45 -0
- package/dist/infrastructure/di/types.js.map +1 -0
- package/dist/infrastructure/i18n/I18nextService.d.ts +27 -0
- package/dist/infrastructure/i18n/I18nextService.js +357 -0
- package/dist/infrastructure/i18n/I18nextService.js.map +1 -0
- package/dist/infrastructure/mcp/CapabilityNegotiator.d.ts +21 -0
- package/dist/infrastructure/mcp/CapabilityNegotiator.js +75 -0
- package/dist/infrastructure/mcp/CapabilityNegotiator.js.map +1 -0
- package/dist/infrastructure/mcp/ErrorHandler.d.ts +29 -0
- package/dist/infrastructure/mcp/ErrorHandler.js +101 -0
- package/dist/infrastructure/mcp/ErrorHandler.js.map +1 -0
- package/dist/infrastructure/mcp/MCPServer.d.ts +25 -0
- package/dist/infrastructure/mcp/MCPServer.js +246 -0
- package/dist/infrastructure/mcp/MCPServer.js.map +1 -0
- package/dist/infrastructure/mcp/PromptManager.d.ts +18 -0
- package/dist/infrastructure/mcp/PromptManager.js +373 -0
- package/dist/infrastructure/mcp/PromptManager.js.map +1 -0
- package/dist/infrastructure/mcp/ResourceManager.d.ts +15 -0
- package/dist/infrastructure/mcp/ResourceManager.js +229 -0
- package/dist/infrastructure/mcp/ResourceManager.js.map +1 -0
- package/dist/infrastructure/mcp/SessionManager.d.ts +64 -0
- package/dist/infrastructure/mcp/SessionManager.js +221 -0
- package/dist/infrastructure/mcp/SessionManager.js.map +1 -0
- package/dist/infrastructure/mcp/ToolRegistry.d.ts +48 -0
- package/dist/infrastructure/mcp/ToolRegistry.js +235 -0
- package/dist/infrastructure/mcp/ToolRegistry.js.map +1 -0
- package/dist/infrastructure/platform/PlatformAdapter.d.ts +46 -0
- package/dist/infrastructure/platform/PlatformAdapter.js +355 -0
- package/dist/infrastructure/platform/PlatformAdapter.js.map +1 -0
- package/dist/infrastructure/plugins/HookSystem.d.ts +40 -0
- package/dist/infrastructure/plugins/HookSystem.js +415 -0
- package/dist/infrastructure/plugins/HookSystem.js.map +1 -0
- package/dist/infrastructure/plugins/PluginManager.d.ts +51 -0
- package/dist/infrastructure/plugins/PluginManager.js +650 -0
- package/dist/infrastructure/plugins/PluginManager.js.map +1 -0
- package/dist/infrastructure/plugins/PluginSteeringRegistry.d.ts +63 -0
- package/dist/infrastructure/plugins/PluginSteeringRegistry.js +439 -0
- package/dist/infrastructure/plugins/PluginSteeringRegistry.js.map +1 -0
- package/dist/infrastructure/plugins/PluginToolRegistry.d.ts +54 -0
- package/dist/infrastructure/plugins/PluginToolRegistry.js +490 -0
- package/dist/infrastructure/plugins/PluginToolRegistry.js.map +1 -0
- package/dist/infrastructure/quality/ASTAnalyzer.d.ts +65 -0
- package/dist/infrastructure/quality/ASTAnalyzer.js +439 -0
- package/dist/infrastructure/quality/ASTAnalyzer.js.map +1 -0
- package/dist/infrastructure/quality/LinusCodeReviewer.d.ts +52 -0
- package/dist/infrastructure/quality/LinusCodeReviewer.js +551 -0
- package/dist/infrastructure/quality/LinusCodeReviewer.js.map +1 -0
- package/dist/infrastructure/repositories/InMemoryProjectRepository.d.ts +10 -0
- package/dist/infrastructure/repositories/InMemoryProjectRepository.js +35 -0
- package/dist/infrastructure/repositories/InMemoryProjectRepository.js.map +1 -0
- package/dist/infrastructure/schemas/project.schema.d.ts +192 -0
- package/dist/infrastructure/schemas/project.schema.js +114 -0
- package/dist/infrastructure/schemas/project.schema.js.map +1 -0
- package/dist/infrastructure/templates/FileGenerator.d.ts +15 -0
- package/dist/infrastructure/templates/FileGenerator.js +385 -0
- package/dist/infrastructure/templates/FileGenerator.js.map +1 -0
- package/dist/infrastructure/templates/HandlebarsRenderer.d.ts +16 -0
- package/dist/infrastructure/templates/HandlebarsRenderer.js +252 -0
- package/dist/infrastructure/templates/HandlebarsRenderer.js.map +1 -0
- package/dist/infrastructure/templates/TemplateManager.d.ts +36 -0
- package/dist/infrastructure/templates/TemplateManager.js +777 -0
- package/dist/infrastructure/templates/TemplateManager.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
// Plugin manager implementation for discovery, loading, and lifecycle management
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
import { injectable, inject } from 'inversify';
|
|
15
|
+
import { promises as fs } from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { EventEmitter } from 'events';
|
|
18
|
+
import { TYPES } from '../di/types.js';
|
|
19
|
+
import { createHash, randomBytes, createCipher, createDecipher } from 'crypto';
|
|
20
|
+
const DEFAULT_CONFIG = {
|
|
21
|
+
pluginsDirectory: './plugins',
|
|
22
|
+
dataDirectory: './data/plugins',
|
|
23
|
+
maxPlugins: 100,
|
|
24
|
+
securityEnabled: true,
|
|
25
|
+
autoDiscovery: true,
|
|
26
|
+
autoLoad: false,
|
|
27
|
+
isolationMode: 'sandbox'
|
|
28
|
+
};
|
|
29
|
+
let PluginManager = class PluginManager {
|
|
30
|
+
logger;
|
|
31
|
+
fileSystem;
|
|
32
|
+
hookSystem;
|
|
33
|
+
toolRegistry;
|
|
34
|
+
steeringRegistry;
|
|
35
|
+
config;
|
|
36
|
+
plugins = new Map();
|
|
37
|
+
eventEmitter = new EventEmitter();
|
|
38
|
+
initialized = false;
|
|
39
|
+
constructor(logger, fileSystem, hookSystem, toolRegistry, steeringRegistry, config = {}) {
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
this.fileSystem = fileSystem;
|
|
42
|
+
this.hookSystem = hookSystem;
|
|
43
|
+
this.toolRegistry = toolRegistry;
|
|
44
|
+
this.steeringRegistry = steeringRegistry;
|
|
45
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
46
|
+
}
|
|
47
|
+
async initialize() {
|
|
48
|
+
if (this.initialized)
|
|
49
|
+
return;
|
|
50
|
+
try {
|
|
51
|
+
// Ensure plugin directories exist
|
|
52
|
+
await this.ensureDirectories();
|
|
53
|
+
// Auto-discovery if enabled
|
|
54
|
+
if (this.config.autoDiscovery) {
|
|
55
|
+
const discovered = await this.discover();
|
|
56
|
+
this.logger.info('Auto-discovery completed', {
|
|
57
|
+
pluginsFound: discovered.length,
|
|
58
|
+
validPlugins: discovered.filter(d => d.valid).length
|
|
59
|
+
});
|
|
60
|
+
// Auto-load valid plugins if enabled
|
|
61
|
+
if (this.config.autoLoad) {
|
|
62
|
+
for (const descriptor of discovered.filter(d => d.valid)) {
|
|
63
|
+
try {
|
|
64
|
+
await this.install(descriptor.path);
|
|
65
|
+
await this.load(descriptor.manifest.id);
|
|
66
|
+
await this.enable(descriptor.manifest.id);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
this.logger.warn('Failed to auto-load plugin', {
|
|
70
|
+
pluginId: descriptor.manifest.id,
|
|
71
|
+
error: error instanceof Error ? error.message : String(error)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this.initialized = true;
|
|
78
|
+
this.logger.info('Plugin manager initialized', {
|
|
79
|
+
pluginsDirectory: this.config.pluginsDirectory,
|
|
80
|
+
loadedPlugins: this.plugins.size
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
this.logger.error('Plugin manager initialization failed', {
|
|
85
|
+
error: error instanceof Error ? error.message : String(error)
|
|
86
|
+
});
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async discover(directory) {
|
|
91
|
+
const searchDirectory = directory || this.config.pluginsDirectory;
|
|
92
|
+
const descriptors = [];
|
|
93
|
+
try {
|
|
94
|
+
await fs.mkdir(searchDirectory, { recursive: true });
|
|
95
|
+
const entries = await fs.readdir(searchDirectory, { withFileTypes: true });
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
const pluginPath = path.join(searchDirectory, entry.name);
|
|
99
|
+
const manifestPath = path.join(pluginPath, 'plugin.json');
|
|
100
|
+
try {
|
|
101
|
+
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
102
|
+
const manifest = JSON.parse(manifestContent);
|
|
103
|
+
const validation = await this.validatePlugin(manifest);
|
|
104
|
+
descriptors.push({
|
|
105
|
+
path: pluginPath,
|
|
106
|
+
manifest,
|
|
107
|
+
valid: validation.valid,
|
|
108
|
+
errors: validation.errors.map(e => e.message),
|
|
109
|
+
warnings: validation.warnings.map(w => w.message)
|
|
110
|
+
});
|
|
111
|
+
this.logger.debug('Plugin discovered', {
|
|
112
|
+
id: manifest.id,
|
|
113
|
+
name: manifest.name,
|
|
114
|
+
valid: validation.valid,
|
|
115
|
+
path: pluginPath
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
descriptors.push({
|
|
120
|
+
path: pluginPath,
|
|
121
|
+
manifest: {},
|
|
122
|
+
valid: false,
|
|
123
|
+
errors: [`Failed to read manifest: ${error instanceof Error ? error.message : String(error)}`],
|
|
124
|
+
warnings: []
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.logger.info('Plugin discovery completed', {
|
|
130
|
+
directory: searchDirectory,
|
|
131
|
+
totalPlugins: descriptors.length,
|
|
132
|
+
validPlugins: descriptors.filter(d => d.valid).length
|
|
133
|
+
});
|
|
134
|
+
return descriptors;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
this.logger.error('Plugin discovery failed', {
|
|
138
|
+
directory: searchDirectory,
|
|
139
|
+
error: error instanceof Error ? error.message : String(error)
|
|
140
|
+
});
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async install(pluginPath) {
|
|
145
|
+
const manifestPath = path.join(pluginPath, 'plugin.json');
|
|
146
|
+
try {
|
|
147
|
+
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
148
|
+
const manifest = JSON.parse(manifestContent);
|
|
149
|
+
// Validate plugin
|
|
150
|
+
const validation = await this.validatePlugin(manifest);
|
|
151
|
+
if (!validation.valid) {
|
|
152
|
+
throw new Error(`Plugin validation failed: ${validation.errors.map(e => e.message).join(', ')}`);
|
|
153
|
+
}
|
|
154
|
+
// Check for conflicts
|
|
155
|
+
if (this.plugins.has(manifest.id)) {
|
|
156
|
+
throw new Error(`Plugin ${manifest.id} is already installed`);
|
|
157
|
+
}
|
|
158
|
+
// Check capacity
|
|
159
|
+
if (this.plugins.size >= this.config.maxPlugins) {
|
|
160
|
+
throw new Error(`Maximum number of plugins (${this.config.maxPlugins}) reached`);
|
|
161
|
+
}
|
|
162
|
+
// Resolve dependencies
|
|
163
|
+
const dependencies = await this.resolveDependencies(manifest);
|
|
164
|
+
this.logger.debug('Plugin dependencies resolved', {
|
|
165
|
+
pluginId: manifest.id,
|
|
166
|
+
dependencies: dependencies.length
|
|
167
|
+
});
|
|
168
|
+
// Create plugin context
|
|
169
|
+
const context = await this.createPluginContext(manifest, pluginPath);
|
|
170
|
+
// Load plugin implementation
|
|
171
|
+
const implementation = await this.loadPluginImplementation(pluginPath, context);
|
|
172
|
+
// Create plugin instance
|
|
173
|
+
const instance = {
|
|
174
|
+
plugin: manifest,
|
|
175
|
+
instance: implementation,
|
|
176
|
+
state: PluginState.INSTALLED,
|
|
177
|
+
loadedAt: new Date(),
|
|
178
|
+
configuration: { ...manifest.configuration.defaults },
|
|
179
|
+
hooks: [],
|
|
180
|
+
tools: []
|
|
181
|
+
};
|
|
182
|
+
this.plugins.set(manifest.id, instance);
|
|
183
|
+
this.eventEmitter.emit('plugin-installed', { pluginId: manifest.id, instance });
|
|
184
|
+
this.logger.info('Plugin installed successfully', {
|
|
185
|
+
id: manifest.id,
|
|
186
|
+
name: manifest.name,
|
|
187
|
+
version: manifest.version
|
|
188
|
+
});
|
|
189
|
+
return instance;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
this.logger.error('Plugin installation failed', {
|
|
193
|
+
pluginPath,
|
|
194
|
+
error: error instanceof Error ? error.message : String(error)
|
|
195
|
+
});
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async uninstall(pluginId) {
|
|
200
|
+
const instance = this.plugins.get(pluginId);
|
|
201
|
+
if (!instance) {
|
|
202
|
+
throw new Error(`Plugin ${pluginId} is not installed`);
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
// Disable plugin if active
|
|
206
|
+
if (instance.state === PluginState.ACTIVE) {
|
|
207
|
+
await this.disable(pluginId);
|
|
208
|
+
}
|
|
209
|
+
// Unload plugin if loaded
|
|
210
|
+
if (instance.state === PluginState.LOADED) {
|
|
211
|
+
await this.unload(pluginId);
|
|
212
|
+
}
|
|
213
|
+
// Clear all plugin registrations (defensive cleanup)
|
|
214
|
+
await this.hookSystem.clearHooks(pluginId);
|
|
215
|
+
await this.toolRegistry.clearTools(pluginId);
|
|
216
|
+
await this.steeringRegistry.clearSteeringDocuments(pluginId);
|
|
217
|
+
// Dispose plugin resources
|
|
218
|
+
await instance.instance.dispose();
|
|
219
|
+
// Remove from registry
|
|
220
|
+
this.plugins.delete(pluginId);
|
|
221
|
+
this.eventEmitter.emit('plugin-uninstalled', { pluginId });
|
|
222
|
+
this.logger.info('Plugin uninstalled successfully', { pluginId });
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
this.logger.error('Plugin uninstallation failed', {
|
|
226
|
+
pluginId,
|
|
227
|
+
error: error instanceof Error ? error.message : String(error)
|
|
228
|
+
});
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async enable(pluginId) {
|
|
233
|
+
const instance = this.plugins.get(pluginId);
|
|
234
|
+
if (!instance) {
|
|
235
|
+
throw new Error(`Plugin ${pluginId} is not installed`);
|
|
236
|
+
}
|
|
237
|
+
if (instance.state === PluginState.ACTIVE) {
|
|
238
|
+
this.logger.debug('Plugin is already enabled', { pluginId });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
instance.state = PluginState.LOADING;
|
|
243
|
+
// Initialize plugin if not already done
|
|
244
|
+
if (instance.state !== PluginState.LOADED) {
|
|
245
|
+
await instance.instance.initialize(await this.createPluginContext(instance.plugin, ''));
|
|
246
|
+
}
|
|
247
|
+
// Register plugin hooks
|
|
248
|
+
for (const hookDeclaration of instance.plugin.hooks) {
|
|
249
|
+
const hookRegistration = {
|
|
250
|
+
pluginId,
|
|
251
|
+
name: hookDeclaration.name,
|
|
252
|
+
type: hookDeclaration.type,
|
|
253
|
+
phase: hookDeclaration.phase,
|
|
254
|
+
priority: hookDeclaration.priority,
|
|
255
|
+
handler: async (context) => {
|
|
256
|
+
return await instance.instance.executeHook(hookDeclaration.name, context);
|
|
257
|
+
},
|
|
258
|
+
conditions: []
|
|
259
|
+
};
|
|
260
|
+
await this.hookSystem.register(pluginId, hookRegistration);
|
|
261
|
+
}
|
|
262
|
+
// Register plugin tools
|
|
263
|
+
for (const toolDeclaration of instance.plugin.tools) {
|
|
264
|
+
const toolRegistration = {
|
|
265
|
+
pluginId,
|
|
266
|
+
name: toolDeclaration.name,
|
|
267
|
+
description: toolDeclaration.description,
|
|
268
|
+
category: toolDeclaration.category,
|
|
269
|
+
handler: async (input, context) => {
|
|
270
|
+
return await instance.instance.executeTool(toolDeclaration.name, input);
|
|
271
|
+
},
|
|
272
|
+
inputSchema: toolDeclaration.inputSchema,
|
|
273
|
+
outputSchema: toolDeclaration.outputSchema,
|
|
274
|
+
permissions: toolDeclaration.permissions
|
|
275
|
+
};
|
|
276
|
+
await this.toolRegistry.register(pluginId, toolRegistration);
|
|
277
|
+
}
|
|
278
|
+
// Register plugin steering documents
|
|
279
|
+
for (const steeringDeclaration of instance.plugin.steeringDocuments) {
|
|
280
|
+
await this.steeringRegistry.registerSteeringDocument(pluginId, steeringDeclaration);
|
|
281
|
+
}
|
|
282
|
+
// Activate plugin
|
|
283
|
+
await instance.instance.activate();
|
|
284
|
+
instance.state = PluginState.ACTIVE;
|
|
285
|
+
this.eventEmitter.emit('plugin-enabled', { pluginId, instance });
|
|
286
|
+
this.logger.info('Plugin enabled successfully', {
|
|
287
|
+
pluginId,
|
|
288
|
+
hooks: instance.plugin.hooks.length,
|
|
289
|
+
tools: instance.plugin.tools.length,
|
|
290
|
+
steeringDocuments: instance.plugin.steeringDocuments.length
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
instance.state = PluginState.ERROR;
|
|
295
|
+
instance.lastError = error instanceof Error ? error : new Error(String(error));
|
|
296
|
+
this.logger.error('Plugin enable failed', {
|
|
297
|
+
pluginId,
|
|
298
|
+
error: error instanceof Error ? error.message : String(error)
|
|
299
|
+
});
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async disable(pluginId) {
|
|
304
|
+
const instance = this.plugins.get(pluginId);
|
|
305
|
+
if (!instance) {
|
|
306
|
+
throw new Error(`Plugin ${pluginId} is not installed`);
|
|
307
|
+
}
|
|
308
|
+
if (instance.state !== PluginState.ACTIVE) {
|
|
309
|
+
this.logger.debug('Plugin is not active', { pluginId, state: instance.state });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
// Deactivate plugin first
|
|
314
|
+
await instance.instance.deactivate();
|
|
315
|
+
// Unregister plugin hooks
|
|
316
|
+
for (const hookDeclaration of instance.plugin.hooks) {
|
|
317
|
+
await this.hookSystem.unregister(pluginId, hookDeclaration.name);
|
|
318
|
+
}
|
|
319
|
+
// Unregister plugin tools
|
|
320
|
+
for (const toolDeclaration of instance.plugin.tools) {
|
|
321
|
+
await this.toolRegistry.unregister(pluginId, toolDeclaration.name);
|
|
322
|
+
}
|
|
323
|
+
// Unregister plugin steering documents
|
|
324
|
+
for (const steeringDeclaration of instance.plugin.steeringDocuments) {
|
|
325
|
+
await this.steeringRegistry.unregisterSteeringDocument(pluginId, steeringDeclaration.name);
|
|
326
|
+
}
|
|
327
|
+
instance.state = PluginState.INACTIVE;
|
|
328
|
+
this.eventEmitter.emit('plugin-disabled', { pluginId, instance });
|
|
329
|
+
this.logger.info('Plugin disabled successfully', {
|
|
330
|
+
pluginId,
|
|
331
|
+
hooks: instance.plugin.hooks.length,
|
|
332
|
+
tools: instance.plugin.tools.length,
|
|
333
|
+
steeringDocuments: instance.plugin.steeringDocuments.length
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
instance.state = PluginState.ERROR;
|
|
338
|
+
instance.lastError = error instanceof Error ? error : new Error(String(error));
|
|
339
|
+
this.logger.error('Plugin disable failed', {
|
|
340
|
+
pluginId,
|
|
341
|
+
error: error instanceof Error ? error.message : String(error)
|
|
342
|
+
});
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async load(pluginId) {
|
|
347
|
+
const instance = this.plugins.get(pluginId);
|
|
348
|
+
if (!instance) {
|
|
349
|
+
throw new Error(`Plugin ${pluginId} is not installed`);
|
|
350
|
+
}
|
|
351
|
+
if (instance.state === PluginState.LOADED || instance.state === PluginState.ACTIVE) {
|
|
352
|
+
return instance;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
instance.state = PluginState.LOADING;
|
|
356
|
+
const context = await this.createPluginContext(instance.plugin, '');
|
|
357
|
+
await instance.instance.initialize(context);
|
|
358
|
+
instance.state = PluginState.LOADED;
|
|
359
|
+
this.eventEmitter.emit('plugin-loaded', { pluginId, instance });
|
|
360
|
+
this.logger.info('Plugin loaded successfully', { pluginId });
|
|
361
|
+
return instance;
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
instance.state = PluginState.ERROR;
|
|
365
|
+
instance.lastError = error instanceof Error ? error : new Error(String(error));
|
|
366
|
+
this.logger.error('Plugin load failed', {
|
|
367
|
+
pluginId,
|
|
368
|
+
error: error instanceof Error ? error.message : String(error)
|
|
369
|
+
});
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async unload(pluginId) {
|
|
374
|
+
const instance = this.plugins.get(pluginId);
|
|
375
|
+
if (!instance) {
|
|
376
|
+
throw new Error(`Plugin ${pluginId} is not installed`);
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
if (instance.state === PluginState.ACTIVE) {
|
|
380
|
+
await this.disable(pluginId);
|
|
381
|
+
}
|
|
382
|
+
await instance.instance.dispose();
|
|
383
|
+
instance.state = PluginState.INSTALLED;
|
|
384
|
+
this.eventEmitter.emit('plugin-unloaded', { pluginId, instance });
|
|
385
|
+
this.logger.info('Plugin unloaded successfully', { pluginId });
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
this.logger.error('Plugin unload failed', {
|
|
389
|
+
pluginId,
|
|
390
|
+
error: error instanceof Error ? error.message : String(error)
|
|
391
|
+
});
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async getPlugin(pluginId) {
|
|
396
|
+
return this.plugins.get(pluginId) || null;
|
|
397
|
+
}
|
|
398
|
+
async getAllPlugins() {
|
|
399
|
+
return Array.from(this.plugins.values());
|
|
400
|
+
}
|
|
401
|
+
async getEnabledPlugins() {
|
|
402
|
+
return Array.from(this.plugins.values()).filter(p => p.state === PluginState.ACTIVE);
|
|
403
|
+
}
|
|
404
|
+
async validatePlugin(plugin) {
|
|
405
|
+
const errors = [];
|
|
406
|
+
const warnings = [];
|
|
407
|
+
const securityIssues = [];
|
|
408
|
+
const compatibilityIssues = [];
|
|
409
|
+
// Basic validation
|
|
410
|
+
if (!plugin.id) {
|
|
411
|
+
errors.push({
|
|
412
|
+
type: 'missing-field',
|
|
413
|
+
message: 'Plugin ID is required',
|
|
414
|
+
field: 'id',
|
|
415
|
+
severity: ValidationSeverity.CRITICAL
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (!plugin.name) {
|
|
419
|
+
errors.push({
|
|
420
|
+
type: 'missing-field',
|
|
421
|
+
message: 'Plugin name is required',
|
|
422
|
+
field: 'name',
|
|
423
|
+
severity: ValidationSeverity.HIGH
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
if (!plugin.version) {
|
|
427
|
+
errors.push({
|
|
428
|
+
type: 'missing-field',
|
|
429
|
+
message: 'Plugin version is required',
|
|
430
|
+
field: 'version',
|
|
431
|
+
severity: ValidationSeverity.HIGH
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
// Security validation
|
|
435
|
+
if (this.config.securityEnabled) {
|
|
436
|
+
const security = await this.performSecurityValidation(plugin);
|
|
437
|
+
securityIssues.push(...security);
|
|
438
|
+
}
|
|
439
|
+
// Compatibility validation
|
|
440
|
+
const compatibility = await this.performCompatibilityValidation(plugin);
|
|
441
|
+
compatibilityIssues.push(...compatibility);
|
|
442
|
+
return {
|
|
443
|
+
valid: errors.filter(e => e.severity === ValidationSeverity.CRITICAL).length === 0,
|
|
444
|
+
errors,
|
|
445
|
+
warnings,
|
|
446
|
+
securityIssues,
|
|
447
|
+
compatibilityIssues
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
async resolveDependencies(plugin) {
|
|
451
|
+
const resolved = [];
|
|
452
|
+
for (const dep of plugin.dependencies) {
|
|
453
|
+
// Check if dependency is already installed
|
|
454
|
+
const installedPlugin = this.plugins.get(dep.name);
|
|
455
|
+
if (installedPlugin) {
|
|
456
|
+
// Version compatibility check would go here
|
|
457
|
+
resolved.push(dep);
|
|
458
|
+
}
|
|
459
|
+
else if (!dep.optional) {
|
|
460
|
+
throw new Error(`Required dependency ${dep.name} is not installed`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return resolved;
|
|
464
|
+
}
|
|
465
|
+
// Private helper methods
|
|
466
|
+
async ensureDirectories() {
|
|
467
|
+
await fs.mkdir(this.config.pluginsDirectory, { recursive: true });
|
|
468
|
+
await fs.mkdir(this.config.dataDirectory, { recursive: true });
|
|
469
|
+
}
|
|
470
|
+
async createPluginContext(plugin, pluginPath) {
|
|
471
|
+
const dataDirectory = path.join(this.config.dataDirectory, plugin.id);
|
|
472
|
+
await fs.mkdir(dataDirectory, { recursive: true });
|
|
473
|
+
return {
|
|
474
|
+
pluginId: plugin.id,
|
|
475
|
+
workingDirectory: pluginPath,
|
|
476
|
+
configurationDirectory: path.join(dataDirectory, 'config'),
|
|
477
|
+
dataDirectory,
|
|
478
|
+
logger: this.createPluginLogger(plugin.id),
|
|
479
|
+
services: this.createPluginServices(plugin.id),
|
|
480
|
+
events: this.createPluginEventEmitter(plugin.id)
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
async loadPluginImplementation(pluginPath, context) {
|
|
484
|
+
const indexPath = path.join(pluginPath, 'index.js');
|
|
485
|
+
try {
|
|
486
|
+
// In a production environment, this would use a more secure module loading system
|
|
487
|
+
const pluginModule = await import(`file://${indexPath}`);
|
|
488
|
+
if (!pluginModule.default) {
|
|
489
|
+
throw new Error('Plugin must export a default class implementing PluginImplementation');
|
|
490
|
+
}
|
|
491
|
+
const PluginClass = pluginModule.default;
|
|
492
|
+
return new PluginClass();
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
throw new Error(`Failed to load plugin implementation: ${error instanceof Error ? error.message : String(error)}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
createPluginLogger(pluginId) {
|
|
499
|
+
return {
|
|
500
|
+
debug: (message, data) => {
|
|
501
|
+
this.logger.debug(`[${pluginId}] ${message}`, data);
|
|
502
|
+
},
|
|
503
|
+
info: (message, data) => {
|
|
504
|
+
this.logger.info(`[${pluginId}] ${message}`, data);
|
|
505
|
+
},
|
|
506
|
+
warn: (message, data) => {
|
|
507
|
+
this.logger.warn(`[${pluginId}] ${message}`, data);
|
|
508
|
+
},
|
|
509
|
+
error: (message, error, data) => {
|
|
510
|
+
this.logger.error(`[${pluginId}] ${message}`, { error, ...data });
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
createPluginServices(pluginId) {
|
|
515
|
+
return {
|
|
516
|
+
fileSystem: this.createPluginFileSystem(pluginId),
|
|
517
|
+
http: this.createPluginHttpClient(pluginId),
|
|
518
|
+
crypto: this.createPluginCrypto(pluginId),
|
|
519
|
+
utils: this.createPluginUtils(pluginId)
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
createPluginEventEmitter(pluginId) {
|
|
523
|
+
const emitter = new EventEmitter();
|
|
524
|
+
return {
|
|
525
|
+
on: (event, listener) => {
|
|
526
|
+
emitter.on(event, listener);
|
|
527
|
+
},
|
|
528
|
+
off: (event, listener) => {
|
|
529
|
+
emitter.off(event, listener);
|
|
530
|
+
},
|
|
531
|
+
emit: (event, ...args) => {
|
|
532
|
+
emitter.emit(event, ...args);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
createPluginFileSystem(pluginId) {
|
|
537
|
+
// This would include security restrictions for plugin file access
|
|
538
|
+
return {
|
|
539
|
+
readFile: async (path) => this.fileSystem.readFile(path),
|
|
540
|
+
writeFile: async (path, content) => this.fileSystem.writeFile(path, content),
|
|
541
|
+
exists: async (path) => this.fileSystem.exists(path),
|
|
542
|
+
mkdir: async (path) => this.fileSystem.mkdir(path),
|
|
543
|
+
readdir: async (path) => [], // Simplified
|
|
544
|
+
stat: async (path) => ({ size: 0, mtime: new Date(), isFile: () => true, isDirectory: () => false })
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
createPluginHttpClient(pluginId) {
|
|
548
|
+
// Simplified HTTP client implementation
|
|
549
|
+
return {
|
|
550
|
+
get: async () => ({ status: 200, statusText: 'OK', headers: {}, data: {} }),
|
|
551
|
+
post: async () => ({ status: 200, statusText: 'OK', headers: {}, data: {} }),
|
|
552
|
+
put: async () => ({ status: 200, statusText: 'OK', headers: {}, data: {} }),
|
|
553
|
+
delete: async () => ({ status: 200, statusText: 'OK', headers: {}, data: {} })
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
createPluginCrypto(pluginId) {
|
|
557
|
+
return {
|
|
558
|
+
hash: (data, algorithm = 'sha256') => createHash(algorithm).update(data).digest('hex'),
|
|
559
|
+
encrypt: (data, key) => {
|
|
560
|
+
const cipher = createCipher('aes192', key);
|
|
561
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
562
|
+
encrypted += cipher.final('hex');
|
|
563
|
+
return encrypted;
|
|
564
|
+
},
|
|
565
|
+
decrypt: (data, key) => {
|
|
566
|
+
const decipher = createDecipher('aes192', key);
|
|
567
|
+
let decrypted = decipher.update(data, 'hex', 'utf8');
|
|
568
|
+
decrypted += decipher.final('utf8');
|
|
569
|
+
return decrypted;
|
|
570
|
+
},
|
|
571
|
+
generateId: () => randomBytes(16).toString('hex'),
|
|
572
|
+
generateSecret: (length = 32) => randomBytes(length).toString('hex')
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
createPluginUtils(pluginId) {
|
|
576
|
+
return {
|
|
577
|
+
validateSchema: () => ({ valid: true, errors: [], warnings: [] }),
|
|
578
|
+
formatDate: (date, format) => date.toISOString(),
|
|
579
|
+
parseTemplate: (template, data) => template,
|
|
580
|
+
debounce: (fn, delay) => {
|
|
581
|
+
let timeoutId;
|
|
582
|
+
return ((...args) => {
|
|
583
|
+
clearTimeout(timeoutId);
|
|
584
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
585
|
+
});
|
|
586
|
+
},
|
|
587
|
+
throttle: (fn, interval) => {
|
|
588
|
+
let lastCall = 0;
|
|
589
|
+
return ((...args) => {
|
|
590
|
+
const now = Date.now();
|
|
591
|
+
if (now - lastCall >= interval) {
|
|
592
|
+
lastCall = now;
|
|
593
|
+
return fn(...args);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async performSecurityValidation(plugin) {
|
|
600
|
+
const issues = [];
|
|
601
|
+
// Check for dangerous permissions
|
|
602
|
+
const dangerousPermissions = ['file:execute', 'system:info'];
|
|
603
|
+
for (const tool of plugin.tools) {
|
|
604
|
+
for (const permission of tool.permissions) {
|
|
605
|
+
if (dangerousPermissions.includes(permission.type)) {
|
|
606
|
+
issues.push({
|
|
607
|
+
type: SecurityIssueType.PRIVILEGE_ESCALATION,
|
|
608
|
+
severity: SecuritySeverity.HIGH,
|
|
609
|
+
message: `Tool '${tool.name}' requests dangerous permission '${permission.type}'`,
|
|
610
|
+
recommendation: 'Review if this permission is necessary and ensure proper validation'
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return issues;
|
|
616
|
+
}
|
|
617
|
+
async performCompatibilityValidation(plugin) {
|
|
618
|
+
const issues = [];
|
|
619
|
+
// Check engine requirements
|
|
620
|
+
for (const engine of plugin.engines) {
|
|
621
|
+
if (engine.name === 'node' && engine.version) {
|
|
622
|
+
const currentNodeVersion = process.version;
|
|
623
|
+
if (!this.isVersionCompatible(currentNodeVersion, engine.version)) {
|
|
624
|
+
issues.push({
|
|
625
|
+
type: 'engine-incompatible',
|
|
626
|
+
message: `Plugin requires Node.js ${engine.version}, but current version is ${currentNodeVersion}`,
|
|
627
|
+
affectedVersions: [currentNodeVersion],
|
|
628
|
+
workaround: `Upgrade Node.js to ${engine.version} or later`
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return issues;
|
|
634
|
+
}
|
|
635
|
+
isVersionCompatible(current, required) {
|
|
636
|
+
// Simplified version comparison - would use proper semver in production
|
|
637
|
+
return current >= required;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
PluginManager = __decorate([
|
|
641
|
+
injectable(),
|
|
642
|
+
__param(0, inject(TYPES.LoggerPort)),
|
|
643
|
+
__param(1, inject(TYPES.FileSystemPort)),
|
|
644
|
+
__param(2, inject(TYPES.HookSystem)),
|
|
645
|
+
__param(3, inject(TYPES.PluginToolRegistry)),
|
|
646
|
+
__param(4, inject(TYPES.PluginSteeringRegistry)),
|
|
647
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object, Function, Object])
|
|
648
|
+
], PluginManager);
|
|
649
|
+
export { PluginManager };
|
|
650
|
+
//# sourceMappingURL=PluginManager.js.map
|