claude-flow
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
202 lines (171 loc) • 5.8 kB
JavaScript
/**
* Wrapper for ruv-swarm MCP server to handle logger issues
* This wrapper ensures compatibility and handles known issues in ruv-swarm
*/
import { spawn } from 'child_process';
import { createInterface } from 'readline';
export class RuvSwarmWrapper {
constructor(options = {}) {
this.options = {
silent: options.silent || false,
autoRestart: options.autoRestart !== false,
maxRestarts: options.maxRestarts || 3,
restartDelay: options.restartDelay || 1000,
...options
};
this.process = null;
this.restartCount = 0;
this.isShuttingDown = false;
}
async start() {
if (this.process) {
throw new Error('RuvSwarm MCP server is already running');
}
return new Promise((resolve, reject) => {
try {
// Spawn ruv-swarm MCP server
this.process = spawn('npx', ['ruv-swarm', 'mcp', 'start'], {
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
// Ensure stdio mode for MCP
MCP_MODE: 'stdio',
// Set log level to reduce noise
LOG_LEVEL: 'WARN'
}
});
let initialized = false;
let initTimeout;
// Handle stdout (JSON-RPC messages)
const rlOut = createInterface({
input: this.process.stdout,
crlfDelay: Infinity
});
rlOut.on('line', (line) => {
try {
const message = JSON.parse(line);
// Check for initialization
if (message.method === 'server.initialized' && !initialized) {
initialized = true;
clearTimeout(initTimeout);
resolve({
process: this.process,
stdout: this.process.stdout,
stdin: this.process.stdin
});
}
// Forward JSON-RPC messages
process.stdout.write(line + '\n');
} catch (err) {
// Not JSON, ignore
}
});
// Handle stderr (logs and errors)
const rlErr = createInterface({
input: this.process.stderr,
crlfDelay: Infinity
});
rlErr.on('line', (line) => {
// Filter out known harmless errors
if (line.includes('logger.logMemoryUsage is not a function')) {
// This is a known issue in ruv-swarm v1.0.8
// The server continues to work despite this error
if (!this.options.silent) {
console.error('⚠️ Known ruv-swarm logger issue detected (continuing normally)');
}
return;
}
// Filter out initialization messages if silent
if (this.options.silent) {
if (line.includes('✅') || line.includes('🧠') || line.includes('📊')) {
return;
}
}
// Forward other stderr output
if (!this.options.silent) {
process.stderr.write(line + '\n');
}
});
// Handle process errors
this.process.on('error', (error) => {
if (!initialized) {
clearTimeout(initTimeout);
reject(new Error(`Failed to start ruv-swarm: ${error.message}`));
} else {
console.error('RuvSwarm process error:', error);
this.handleProcessExit(error.code || 1);
}
});
// Handle process exit
this.process.on('exit', (code, signal) => {
if (!initialized) {
clearTimeout(initTimeout);
reject(new Error(`RuvSwarm exited before initialization: code ${code}, signal ${signal}`));
} else {
this.handleProcessExit(code || 0);
}
});
// Set initialization timeout
initTimeout = setTimeout(() => {
if (!initialized) {
this.stop();
reject(new Error('RuvSwarm initialization timeout'));
}
}, 30000); // 30 second timeout
} catch (error) {
reject(error);
}
});
}
handleProcessExit(code) {
this.process = null;
if (this.isShuttingDown) {
return;
}
console.error(`RuvSwarm MCP server exited with code ${code}`);
// Auto-restart if enabled and under limit
if (this.options.autoRestart && this.restartCount < this.options.maxRestarts) {
this.restartCount++;
console.log(`Attempting to restart RuvSwarm (attempt ${this.restartCount}/${this.options.maxRestarts})...`);
setTimeout(() => {
this.start().catch(err => {
console.error('Failed to restart RuvSwarm:', err);
});
}, this.options.restartDelay);
}
}
async stop() {
this.isShuttingDown = true;
if (!this.process) {
return;
}
return new Promise((resolve) => {
const killTimeout = setTimeout(() => {
console.warn('RuvSwarm did not exit gracefully, forcing kill...');
this.process.kill('SIGKILL');
}, 5000);
this.process.on('exit', () => {
clearTimeout(killTimeout);
this.process = null;
resolve();
});
// Send graceful shutdown signal
this.process.kill('SIGTERM');
});
}
isRunning() {
return this.process !== null && !this.process.killed;
}
}
// Export a function to start ruv-swarm with error handling
export async function startRuvSwarmMCP(options = {}) {
const wrapper = new RuvSwarmWrapper(options);
try {
const result = await wrapper.start();
console.log('✅ RuvSwarm MCP server started successfully');
return { wrapper, ...result };
} catch (error) {
console.error('❌ Failed to start RuvSwarm MCP server:', error.message);
throw error;
}
}