stigmergy 1.0.99 → 1.1.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/README.md +17 -0
- package/bin/stigmergy +1 -1
- package/bin/stigmergy.cmd +1 -1
- package/docs/NATIVE_INTEGRATION_GUIDE.md +1155 -1190
- package/docs/PROJECT_STRUCTURE.md +283 -303
- package/package.json +10 -7
- package/src/cli/router.js +460 -0
- package/src/core/coordination/index.js +16 -0
- package/src/core/coordination/nodejs/AdapterManager.js +89 -0
- package/src/core/coordination/nodejs/CLCommunication.js +59 -0
- package/src/core/coordination/nodejs/CLIIntegrationManager.js +236 -0
- package/src/core/coordination/nodejs/HealthChecker.js +77 -0
- package/src/core/coordination/nodejs/HookDeploymentManager.js +256 -0
- package/src/core/coordination/nodejs/StatisticsCollector.js +71 -0
- package/src/core/coordination/nodejs/index.js +72 -0
- package/src/core/coordination/nodejs/utils/Logger.js +29 -0
- package/src/core/installer.js +374 -0
- package/src/index.js +25 -4
- package/src/main.js +1068 -831
- package/src/utils/helpers.js +35 -0
- package/test/cross-cli-detection-test.js +33 -0
- package/test/python-plugins-test.js +259 -0
- package/test/remaining-adapters-test.js +256 -0
- package/test/system-compatibility-test.js +466 -446
- package/test/tool-selection-integration-test.js +157 -156
- package/src/main_english.js +0 -1338
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// src/core/coordination/nodejs/CLIIntegrationManager.js
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { spawn, spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
class CLIIntegrationManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.supportedCLIs = {
|
|
10
|
+
'claude': {
|
|
11
|
+
name: 'Claude CLI',
|
|
12
|
+
executable: 'claude',
|
|
13
|
+
hooks: ['user_prompt_submit', 'tool_use_pre', 'tool_use_post', 'response_generated']
|
|
14
|
+
},
|
|
15
|
+
'gemini': {
|
|
16
|
+
name: 'Gemini CLI',
|
|
17
|
+
executable: 'gemini',
|
|
18
|
+
extensions: ['on_user_input', 'on_response_generated', 'on_tool_execution']
|
|
19
|
+
},
|
|
20
|
+
'qwencode': {
|
|
21
|
+
name: 'QwenCode CLI',
|
|
22
|
+
executable: 'qwencode',
|
|
23
|
+
inheritance: ['on_code_generation', 'on_analysis_request', 'on_refactor_request']
|
|
24
|
+
},
|
|
25
|
+
'iflow': {
|
|
26
|
+
name: 'iFlow CLI',
|
|
27
|
+
executable: 'iflow',
|
|
28
|
+
workflows: ['on_workflow_start', 'on_stage_complete', 'on_workflow_success']
|
|
29
|
+
},
|
|
30
|
+
'qoder': {
|
|
31
|
+
name: 'Qoder CLI',
|
|
32
|
+
executable: 'qoder',
|
|
33
|
+
notifications: ['on_user_notification', 'on_system_alert', 'on_task_completion']
|
|
34
|
+
},
|
|
35
|
+
'codebuddy': {
|
|
36
|
+
name: 'CodeBuddy CLI',
|
|
37
|
+
executable: 'codebuddy',
|
|
38
|
+
skills: ['on_skill_invocation', 'on_buddy_request', 'on_cross_cli_task']
|
|
39
|
+
},
|
|
40
|
+
'codex': {
|
|
41
|
+
name: 'Codex CLI',
|
|
42
|
+
executable: 'codex',
|
|
43
|
+
slashCommands: ['/x', '/cross-cli', '/delegate']
|
|
44
|
+
},
|
|
45
|
+
'copilot': {
|
|
46
|
+
name: 'Copilot CLI',
|
|
47
|
+
executable: 'copilot',
|
|
48
|
+
mcp: ['cross_cli_execute']
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async checkCLIAvailability(cliName) {
|
|
54
|
+
console.log(`[CLI_INTEGRATION] Checking availability of ${cliName}...`);
|
|
55
|
+
|
|
56
|
+
const cliInfo = this.supportedCLIs[cliName.toLowerCase()];
|
|
57
|
+
if (!cliInfo) {
|
|
58
|
+
return { available: false, error: `Unsupported CLI: ${cliName}` };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Check if CLI executable is available
|
|
63
|
+
const result = spawnSync(cliInfo.executable, ['--version'], {
|
|
64
|
+
timeout: 5000,
|
|
65
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (result.status === 0) {
|
|
69
|
+
const version = result.stdout ? result.stdout.toString().trim() : 'Unknown';
|
|
70
|
+
return {
|
|
71
|
+
available: true,
|
|
72
|
+
version: version,
|
|
73
|
+
name: cliInfo.name
|
|
74
|
+
};
|
|
75
|
+
} else {
|
|
76
|
+
return {
|
|
77
|
+
available: false,
|
|
78
|
+
error: `CLI returned non-zero exit code: ${result.status}`,
|
|
79
|
+
stderr: result.stderr ? result.stderr.toString() : ''
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return {
|
|
84
|
+
available: false,
|
|
85
|
+
error: `Failed to execute CLI: ${error.message}`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async listAvailableCLIs() {
|
|
91
|
+
console.log('[CLI_INTEGRATION] Listing available CLIs...');
|
|
92
|
+
|
|
93
|
+
const results = {};
|
|
94
|
+
for (const [cliName, cliInfo] of Object.entries(this.supportedCLIs)) {
|
|
95
|
+
results[cliName] = await this.checkCLIAvailability(cliName);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async getNodeJsIntegrationScript(cliName) {
|
|
102
|
+
console.log(`[CLI_INTEGRATION] Generating Node.js integration script for ${cliName}...`);
|
|
103
|
+
|
|
104
|
+
const cliInfo = this.supportedCLIs[cliName.toLowerCase()];
|
|
105
|
+
if (!cliInfo) {
|
|
106
|
+
throw new Error(`Unsupported CLI: ${cliName}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Generate a Node.js integration script
|
|
110
|
+
const script = `#!/usr/bin/env node
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Node.js Integration Script for ${cliInfo.name}
|
|
114
|
+
* Auto-generated by Stigmergy CLI Integration Manager
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
const { spawn } = require('child_process');
|
|
118
|
+
|
|
119
|
+
class ${this.capitalize(cliName)}NodeJsIntegration {
|
|
120
|
+
constructor() {
|
|
121
|
+
this.cliName = '${cliName}';
|
|
122
|
+
this.executable = '${cliInfo.executable}';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async executeCommand(command, args = [], options = {}) {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const cliProcess = spawn(this.executable, [command, ...args], {
|
|
128
|
+
timeout: options.timeout || 30000,
|
|
129
|
+
...options
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
let stdout = '';
|
|
133
|
+
let stderr = '';
|
|
134
|
+
|
|
135
|
+
cliProcess.stdout.on('data', (data) => {
|
|
136
|
+
stdout += data.toString();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
cliProcess.stderr.on('data', (data) => {
|
|
140
|
+
stderr += data.toString();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
cliProcess.on('close', (code) => {
|
|
144
|
+
if (code === 0) {
|
|
145
|
+
resolve(stdout.trim());
|
|
146
|
+
} else {
|
|
147
|
+
reject(new Error(\`CLI process exited with code \${code}: \${stderr}\`));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
cliProcess.on('error', (error) => {
|
|
152
|
+
reject(new Error(\`Failed to start CLI process: \${error.message}\`));
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getVersion() {
|
|
158
|
+
try {
|
|
159
|
+
const version = await this.executeCommand('--version');
|
|
160
|
+
return version;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
throw new Error(\`Failed to get ${cliName} version: \${error.message}\`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async executeTask(task, context = {}) {
|
|
167
|
+
// This would depend on the specific CLI's interface
|
|
168
|
+
// For demonstration, we'll simulate execution
|
|
169
|
+
return \`[NODE.JS INTEGRATION] Simulated execution of task: \${task}\`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
capitalize(str) {
|
|
173
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = ${this.capitalize(cliName)}NodeJsIntegration;
|
|
178
|
+
|
|
179
|
+
// If run directly, test the integration
|
|
180
|
+
if (require.main === module) {
|
|
181
|
+
const integration = new ${this.capitalize(cliName)}NodeJsIntegration();
|
|
182
|
+
|
|
183
|
+
integration.getVersion()
|
|
184
|
+
.then(version => {
|
|
185
|
+
console.log('${cliInfo.name} version:', version);
|
|
186
|
+
})
|
|
187
|
+
.catch(error => {
|
|
188
|
+
console.error('Failed to get version:', error.message);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
|
|
193
|
+
return script;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
capitalize(str) {
|
|
197
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async deployNodeJsIntegration(cliName, targetDir) {
|
|
201
|
+
console.log(`[CLI_INTEGRATION] Deploying Node.js integration for ${cliName}...`);
|
|
202
|
+
|
|
203
|
+
if (!fs.existsSync(targetDir)) {
|
|
204
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const script = await this.getNodeJsIntegrationScript(cliName);
|
|
208
|
+
const scriptPath = path.join(targetDir, `${cliName}_nodejs_integration.js`);
|
|
209
|
+
|
|
210
|
+
fs.writeFileSync(scriptPath, script);
|
|
211
|
+
|
|
212
|
+
// Make executable
|
|
213
|
+
try {
|
|
214
|
+
fs.chmodSync(scriptPath, 0o755);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.warn(`[CLI_INTEGRATION] Failed to make script executable:`, error.message);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return scriptPath;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async getSupportedFeatures(cliName) {
|
|
223
|
+
const cliInfo = this.supportedCLIs[cliName.toLowerCase()];
|
|
224
|
+
if (!cliInfo) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
name: cliInfo.name,
|
|
230
|
+
executable: cliInfo.executable,
|
|
231
|
+
features: Object.keys(cliInfo).filter(key => key !== 'name' && key !== 'executable')
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = CLIIntegrationManager;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/core/coordination/nodejs/HealthChecker.js
|
|
2
|
+
const os = require('os');
|
|
3
|
+
|
|
4
|
+
class HealthChecker {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.checks = [
|
|
7
|
+
'AdapterAvailability',
|
|
8
|
+
'SystemResources',
|
|
9
|
+
'DiskSpace'
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async checkHealth() {
|
|
14
|
+
console.log('[HEALTH_CHECKER] Performing health check...');
|
|
15
|
+
|
|
16
|
+
const results = {};
|
|
17
|
+
let overallHealthy = true;
|
|
18
|
+
|
|
19
|
+
for (const check of this.checks) {
|
|
20
|
+
try {
|
|
21
|
+
const methodName = `check${check}`;
|
|
22
|
+
if (typeof this[methodName] === 'function') {
|
|
23
|
+
results[check.toLowerCase()] = await this[methodName]();
|
|
24
|
+
} else {
|
|
25
|
+
results[check.toLowerCase()] = { healthy: false, error: `Method ${methodName} not found` };
|
|
26
|
+
overallHealthy = false;
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
results[check.toLowerCase()] = { healthy: false, error: error.message };
|
|
30
|
+
overallHealthy = false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
healthy: overallHealthy,
|
|
36
|
+
timestamp: new Date().toISOString(),
|
|
37
|
+
checks: results
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async checkAdapterAvailability() {
|
|
42
|
+
// In a real implementation, this would check actual adapter availability
|
|
43
|
+
return {
|
|
44
|
+
healthy: true,
|
|
45
|
+
unavailableAdapters: []
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async checkSystemResources() {
|
|
50
|
+
const memUsage = process.memoryUsage();
|
|
51
|
+
const totalMem = os.totalmem();
|
|
52
|
+
const freeMem = os.freemem();
|
|
53
|
+
const usedMemPercent = ((totalMem - freeMem) / totalMem) * 100;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
healthy: usedMemPercent < 90, // Healthy if less than 90% memory used
|
|
57
|
+
memory: {
|
|
58
|
+
used: totalMem - freeMem,
|
|
59
|
+
free: freeMem,
|
|
60
|
+
total: totalMem,
|
|
61
|
+
percent: usedMemPercent
|
|
62
|
+
},
|
|
63
|
+
cpu: os.cpus().length
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async checkDiskSpace() {
|
|
68
|
+
// Node.js doesn't have built-in disk space checking
|
|
69
|
+
// This is a simplified check
|
|
70
|
+
return {
|
|
71
|
+
healthy: true,
|
|
72
|
+
message: 'Disk space check not implemented in Node.js layer'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = HealthChecker;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// src/core/coordination/nodejs/HookDeploymentManager.js
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { spawn, spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
class HookDeploymentManager {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.deploymentDir = path.join(os.homedir(), '.stigmergy', 'hooks');
|
|
10
|
+
this.supportedCLIs = [
|
|
11
|
+
'claude', 'gemini', 'qwencode', 'iflow', 'qoder', 'codebuddy', 'codex', 'copilot'
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async initialize() {
|
|
16
|
+
console.log('[HOOK_DEPLOYMENT] Initializing hook deployment manager...');
|
|
17
|
+
await this.ensureDeploymentDirectory();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async ensureDeploymentDirectory() {
|
|
21
|
+
if (!fs.existsSync(this.deploymentDir)) {
|
|
22
|
+
fs.mkdirSync(this.deploymentDir, { recursive: true });
|
|
23
|
+
console.log(`[HOOK_DEPLOYMENT] Created deployment directory: ${this.deploymentDir}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async deployHooksForCLI(cliName, options = {}) {
|
|
28
|
+
console.log(`[HOOK_DEPLOYMENT] Deploying hooks for ${cliName}...`);
|
|
29
|
+
|
|
30
|
+
if (!this.supportedCLIs.includes(cliName.toLowerCase())) {
|
|
31
|
+
throw new Error(`Unsupported CLI: ${cliName}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Create CLI-specific hook directory
|
|
36
|
+
const cliHookDir = path.join(this.deploymentDir, cliName);
|
|
37
|
+
if (!fs.existsSync(cliHookDir)) {
|
|
38
|
+
fs.mkdirSync(cliHookDir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Deploy Node.js specific hooks
|
|
42
|
+
await this.deployNodeJsHooks(cliName, cliHookDir, options);
|
|
43
|
+
|
|
44
|
+
console.log(`[HOOK_DEPLOYMENT] Hooks deployed successfully for ${cliName}`);
|
|
45
|
+
return true;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(`[HOOK_DEPLOYMENT] Failed to deploy hooks for ${cliName}:`, error);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async deployNodeJsHooks(cliName, hookDir, options) {
|
|
53
|
+
console.log(`[HOOK_DEPLOYMENT] Deploying Node.js hooks for ${cliName}...`);
|
|
54
|
+
|
|
55
|
+
// Create a basic hook template for Node.js
|
|
56
|
+
const hookTemplate = this.generateNodeJsHookTemplate(cliName);
|
|
57
|
+
const hookFilePath = path.join(hookDir, `${cliName}_nodejs_hook.js`);
|
|
58
|
+
|
|
59
|
+
fs.writeFileSync(hookFilePath, hookTemplate);
|
|
60
|
+
console.log(`[HOOK_DEPLOYMENT] Created Node.js hook: ${hookFilePath}`);
|
|
61
|
+
|
|
62
|
+
// Make the hook executable
|
|
63
|
+
try {
|
|
64
|
+
fs.chmodSync(hookFilePath, 0o755);
|
|
65
|
+
console.log(`[HOOK_DEPLOYMENT] Made hook executable: ${hookFilePath}`);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.warn(`[HOOK_DEPLOYMENT] Failed to make hook executable: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Create configuration file
|
|
71
|
+
const config = {
|
|
72
|
+
cli: cliName,
|
|
73
|
+
hookPath: hookFilePath,
|
|
74
|
+
deploymentTime: new Date().toISOString(),
|
|
75
|
+
version: '1.0.0-nodejs'
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const configPath = path.join(hookDir, 'config.json');
|
|
79
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
80
|
+
console.log(`[HOOK_DEPLOYMENT] Created hook configuration: ${configPath}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
generateNodeJsHookTemplate(cliName) {
|
|
84
|
+
return `#!/usr/bin/env node
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Node.js Hook for ${cliName.toUpperCase()}
|
|
88
|
+
* Auto-generated by Stigmergy CLI Hook Deployment Manager
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
const fs = require('fs');
|
|
92
|
+
const path = require('path');
|
|
93
|
+
|
|
94
|
+
class ${this.capitalize(cliName)}NodeJsHook {
|
|
95
|
+
constructor() {
|
|
96
|
+
this.cliName = '${cliName}';
|
|
97
|
+
this.hookDir = __dirname;
|
|
98
|
+
this.logFile = path.join(this.hookDir, '${cliName}_hook.log');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async onUserPrompt(prompt, context) {
|
|
102
|
+
this.log('INFO', \`User prompt received: \${prompt}\`);
|
|
103
|
+
|
|
104
|
+
// Check for cross-CLI requests
|
|
105
|
+
const crossCLIRequest = this.detectCrossCLIRequest(prompt);
|
|
106
|
+
if (crossCLIRequest) {
|
|
107
|
+
return await this.handleCrossCLIRequest(crossCLIRequest, context);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Default processing
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async onToolUse(toolName, toolArgs, context) {
|
|
115
|
+
this.log('INFO', \`Tool use detected: \${toolName}\`);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async onResponseGenerated(response, context) {
|
|
120
|
+
this.log('INFO', 'Response generated');
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
detectCrossCLIRequest(prompt) {
|
|
125
|
+
// Simple pattern matching for cross-CLI requests
|
|
126
|
+
const patterns = [
|
|
127
|
+
/(?:use|call|ask)\\s+(\\w+)\\s+(?:to|for)\\s+(.+)$/i,
|
|
128
|
+
/(?:use|call|ask)\\s+(\\w+)\\s+(?:to|for)\\s+(.+)$/i // English-only pattern
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
for (const pattern of patterns) {
|
|
132
|
+
const match = prompt.match(pattern);
|
|
133
|
+
if (match) {
|
|
134
|
+
return {
|
|
135
|
+
targetCLI: match[1].toLowerCase(),
|
|
136
|
+
task: match[2] || match[3],
|
|
137
|
+
source: this.cliName
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async handleCrossCLIRequest(request, context) {
|
|
146
|
+
this.log('INFO', \`Cross-CLI request detected: \${JSON.stringify(request)}\`);
|
|
147
|
+
|
|
148
|
+
// In a real implementation, this would communicate with the coordination layer
|
|
149
|
+
// For now, we simulate the response
|
|
150
|
+
return \`[NODE.JS HOOK] Simulated cross-CLI call to \${request.targetCLI}: \${request.task}\`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
log(level, message) {
|
|
154
|
+
const timestamp = new Date().toISOString();
|
|
155
|
+
const logEntry = \`[\${timestamp}] [\${level}] [\${this.cliName.toUpperCase()}] \${message}\\n\`;
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
fs.appendFileSync(this.logFile, logEntry);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('Failed to write to log file:', error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
capitalize(str) {
|
|
165
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Export the hook class
|
|
170
|
+
module.exports = ${this.capitalize(cliName)}NodeJsHook;
|
|
171
|
+
|
|
172
|
+
// If run directly, instantiate and test
|
|
173
|
+
if (require.main === module) {
|
|
174
|
+
const hook = new ${this.capitalize(cliName)}NodeJsHook();
|
|
175
|
+
console.log('${cliName.toUpperCase()} Node.js Hook initialized');
|
|
176
|
+
}
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
capitalize(str) {
|
|
181
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async undeployHooksForCLI(cliName) {
|
|
185
|
+
console.log(`[HOOK_DEPLOYMENT] Undeploying hooks for ${cliName}...`);
|
|
186
|
+
|
|
187
|
+
const cliHookDir = path.join(this.deploymentDir, cliName);
|
|
188
|
+
if (fs.existsSync(cliHookDir)) {
|
|
189
|
+
try {
|
|
190
|
+
fs.rmSync(cliHookDir, { recursive: true, force: true });
|
|
191
|
+
console.log(`[HOOK_DEPLOYMENT] Removed hook directory: ${cliHookDir}`);
|
|
192
|
+
return true;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error(`[HOOK_DEPLOYMENT] Failed to remove hook directory:`, error);
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(`[HOOK_DEPLOYMENT] No hooks found for ${cliName}`);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async listDeployedHooks() {
|
|
204
|
+
if (!fs.existsSync(this.deploymentDir)) {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const cliDirs = fs.readdirSync(this.deploymentDir, { withFileTypes: true })
|
|
209
|
+
.filter(dirent => dirent.isDirectory())
|
|
210
|
+
.map(dirent => dirent.name);
|
|
211
|
+
|
|
212
|
+
const hooks = [];
|
|
213
|
+
for (const cliName of cliDirs) {
|
|
214
|
+
const configPath = path.join(this.deploymentDir, cliName, 'config.json');
|
|
215
|
+
if (fs.existsSync(configPath)) {
|
|
216
|
+
try {
|
|
217
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
218
|
+
hooks.push(config);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.warn(`[HOOK_DEPLOYMENT] Failed to read config for ${cliName}:`, error.message);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return hooks;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async validateHookDeployment(cliName) {
|
|
229
|
+
console.log(`[HOOK_DEPLOYMENT] Validating hook deployment for ${cliName}...`);
|
|
230
|
+
|
|
231
|
+
const cliHookDir = path.join(this.deploymentDir, cliName);
|
|
232
|
+
if (!fs.existsSync(cliHookDir)) {
|
|
233
|
+
return { valid: false, error: 'Hook directory not found' };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const configPath = path.join(cliHookDir, 'config.json');
|
|
237
|
+
if (!fs.existsSync(configPath)) {
|
|
238
|
+
return { valid: false, error: 'Configuration file not found' };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const hookPath = path.join(cliHookDir, `${cliName}_nodejs_hook.js`);
|
|
242
|
+
if (!fs.existsSync(hookPath)) {
|
|
243
|
+
return { valid: false, error: 'Hook script not found' };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Try to load the hook
|
|
247
|
+
try {
|
|
248
|
+
require(hookPath);
|
|
249
|
+
return { valid: true, message: 'Hook deployment is valid' };
|
|
250
|
+
} catch (error) {
|
|
251
|
+
return { valid: false, error: `Failed to load hook: ${error.message}` };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = HookDeploymentManager;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/core/coordination/nodejs/StatisticsCollector.js
|
|
2
|
+
class StatisticsCollector {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.counters = {};
|
|
5
|
+
this.timings = {};
|
|
6
|
+
this.startTime = Date.now();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
initialize() {
|
|
10
|
+
console.log('[STATISTICS_COLLECTOR] Initializing statistics collector...');
|
|
11
|
+
this.reset();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
reset() {
|
|
15
|
+
this.counters = {
|
|
16
|
+
cross_cli_calls: 0,
|
|
17
|
+
successful_calls: 0,
|
|
18
|
+
failed_calls: 0,
|
|
19
|
+
adapter_loads: 0
|
|
20
|
+
};
|
|
21
|
+
this.timings = {
|
|
22
|
+
execution_times: [],
|
|
23
|
+
last_reset: Date.now()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
incrementCounter(name) {
|
|
28
|
+
this.counters[name] = (this.counters[name] || 0) + 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
recordExecutionTime(timeMs) {
|
|
32
|
+
this.timings.execution_times.push(timeMs);
|
|
33
|
+
// Keep only last 1000 timings to prevent memory bloat
|
|
34
|
+
if (this.timings.execution_times.length > 1000) {
|
|
35
|
+
this.timings.execution_times.shift();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getAdapterStats(cliName) {
|
|
40
|
+
return {
|
|
41
|
+
calls: this.counters.cross_cli_calls,
|
|
42
|
+
successRate: this.calculateSuccessRate(),
|
|
43
|
+
averageExecutionTime: this.calculateAverageExecutionTime(),
|
|
44
|
+
uptime: Date.now() - this.startTime
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getAllStats() {
|
|
49
|
+
return {
|
|
50
|
+
counters: this.counters,
|
|
51
|
+
timings: {
|
|
52
|
+
...this.timings,
|
|
53
|
+
averageExecutionTime: this.calculateAverageExecutionTime()
|
|
54
|
+
},
|
|
55
|
+
uptime: Date.now() - this.startTime
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
calculateSuccessRate() {
|
|
60
|
+
if (this.counters.cross_cli_calls === 0) return 1.0;
|
|
61
|
+
return this.counters.successful_calls / this.counters.cross_cli_calls;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
calculateAverageExecutionTime() {
|
|
65
|
+
if (this.timings.execution_times.length === 0) return 0;
|
|
66
|
+
const sum = this.timings.execution_times.reduce((a, b) => a + b, 0);
|
|
67
|
+
return sum / this.timings.execution_times.length;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = StatisticsCollector;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/core/coordination/nodejs/index.js
|
|
2
|
+
const AdapterManager = require('./AdapterManager');
|
|
3
|
+
const CLCommunication = require('./CLCommunication');
|
|
4
|
+
const StatisticsCollector = require('./StatisticsCollector');
|
|
5
|
+
const HealthChecker = require('./HealthChecker');
|
|
6
|
+
|
|
7
|
+
class NodeJsCoordinationLayer {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.adapterManager = new AdapterManager();
|
|
10
|
+
this.communication = new CLCommunication();
|
|
11
|
+
this.statistics = new StatisticsCollector();
|
|
12
|
+
this.healthChecker = new HealthChecker();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async initialize(options = {}) {
|
|
16
|
+
try {
|
|
17
|
+
console.log('[NODEJS_COORDINATION] Initializing Node.js coordination layer...');
|
|
18
|
+
|
|
19
|
+
// Initialize components
|
|
20
|
+
await this.adapterManager.initialize();
|
|
21
|
+
await this.communication.initialize();
|
|
22
|
+
this.statistics.initialize();
|
|
23
|
+
|
|
24
|
+
// Perform health check
|
|
25
|
+
const health = await this.healthChecker.checkHealth();
|
|
26
|
+
if (!health.healthy) {
|
|
27
|
+
console.warn('[NODEJS_COORDINATION] Health check issues detected:', health);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log('[NODEJS_COORDINATION] Node.js coordination layer initialized successfully');
|
|
31
|
+
return true;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('[NODEJS_COORDINATION] Failed to initialize Node.js coordination layer:', error);
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async executeCrossCLITask(sourceCLI, targetCLI, task, context = {}) {
|
|
39
|
+
this.statistics.incrementCounter('cross_cli_calls');
|
|
40
|
+
const startTime = Date.now();
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const result = await this.communication.executeTask(sourceCLI, targetCLI, task, context);
|
|
44
|
+
this.statistics.recordExecutionTime(Date.now() - startTime);
|
|
45
|
+
this.statistics.incrementCounter('successful_calls');
|
|
46
|
+
return result;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
this.statistics.incrementCounter('failed_calls');
|
|
49
|
+
console.error(`[NODEJS_COORDINATION] Cross-CLI task execution failed:`, error);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getAdapterStatistics(cliName) {
|
|
55
|
+
return this.statistics.getAdapterStats(cliName);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async getSystemStatus() {
|
|
59
|
+
return {
|
|
60
|
+
implementation: 'nodejs',
|
|
61
|
+
health: await this.healthChecker.checkHealth(),
|
|
62
|
+
statistics: this.statistics.getAllStats(),
|
|
63
|
+
adapters: await this.adapterManager.listAdapters()
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async healthCheck() {
|
|
68
|
+
return await this.healthChecker.checkHealth();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = NodeJsCoordinationLayer;
|