UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

255 lines (223 loc) 7.68 kB
/** * 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) => { // Parse structured error messages if available try { const errorData = JSON.parse(line); if (errorData.error && errorData.error.code) { // Handle specific error codes switch (errorData.error.code) { case 'LOGGER_METHOD_MISSING': case 'ERR_LOGGER_MEMORY_USAGE': // Known issue with logger.logMemoryUsage in ruv-swarm if (!this.options.silent) { console.error( '⚠️ Known ruv-swarm logger issue detected (continuing normally)', ); } return; case 'ERR_INITIALIZATION': console.error('❌ RuvSwarm initialization error:', errorData.error.message); return; default: // Unknown error code, log it if (!this.options.silent) { console.error( `RuvSwarm error [${errorData.error.code}]:`, errorData.error.message, ); } } return; } } catch (e) { // Not JSON, check for known text patterns as fallback const knownErrorPatterns = [ { pattern: /logger\.logMemoryUsage is not a function/, code: 'LOGGER_METHOD_MISSING', message: 'Known ruv-swarm logger issue detected (continuing normally)', }, { pattern: /Cannot find module/, code: 'MODULE_NOT_FOUND', message: 'Module not found error', }, { pattern: /ECONNREFUSED/, code: 'CONNECTION_REFUSED', message: 'Connection refused error', }, ]; for (const errorPattern of knownErrorPatterns) { if (errorPattern.pattern.test(line)) { if (!this.options.silent || errorPattern.code !== 'LOGGER_METHOD_MISSING') { console.error(`⚠️ ${errorPattern.message}`); } 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; } }