UNPKG

task-engine-ai-core

Version:

Revolutionary AI-driven task management system with complete transformation trilogy: Frontend v0.1.0, Backend v0.2.0, CLI v0.3.0 - Enterprise-grade performance with 95% improvements

514 lines (444 loc) 18.1 kB
#!/usr/bin/env node /** * Bridge Server Startup Script * Starts the WebSocket bridge for IDE integration */ import WebSocketBridge from './websocket-bridge.js'; import BridgeConfig from './bridge-config.js'; import logger from '../../mcp-server/src/logger.js'; import { fileURLToPath } from 'url'; import path from 'path'; import fs from 'fs/promises'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); class BridgeServer { constructor() { this.bridge = null; this.config = null; this.running = false; this.startTime = null; this.pidFile = '.taskmaster/bridge.pid'; this.logFile = '.taskmaster/bridge.log'; } /** * Initialize and start the bridge server */ async start(options = {}) { try { this.startTime = Date.now(); // Load configuration this.config = new BridgeConfig(options.configPath); await this.config.load(); // Auto-detect IDE if needed if (this.config.get('ide.type') === 'auto-detect') { logger.info('Auto-detecting IDE...'); const detection = await this.config.detectIDE(); if (detection.detected) { logger.info(`Auto-detected IDE: ${detection.type} (confidence: ${Math.round(detection.confidence * 100)}%)`); } else { logger.warn('Could not auto-detect IDE, using Cursor as default'); await this.config.set('ide.type', 'cursor'); } } // Validate configuration const validation = this.config.validate(); if (!validation.valid) { throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`); } // Check if bridge is enabled if (!this.config.get('bridge.enabled') && !options.force) { logger.warn('Bridge is disabled in configuration. Use --force to start anyway.'); logger.info('To enable bridge: npm run bridge-enable'); return; } // Check for existing instance if (await this.isRunning()) { throw new Error('Bridge server is already running. Use stop command first.'); } // Create bridge instance this.bridge = new WebSocketBridge({ port: this.config.get('bridge.port'), host: this.config.get('bridge.host'), mcpServerPath: path.join(__dirname, '../../mcp-server/server.js'), maxConnections: this.config.get('bridge.maxConnections'), timeout: this.config.get('bridge.timeout') }); // Set up event handlers this.setupEventHandlers(); // Start the bridge await this.bridge.start(); this.running = true; // Write PID file await this.writePidFile(); // Log startup information this.logStartupInfo(); return this; } catch (error) { logger.error('Failed to start bridge server:', error); await this.cleanup(); throw error; } } /** * Set up event handlers for bridge */ setupEventHandlers() { // Bridge events this.bridge.on('connection', (connectionId, connectionInfo) => { logger.info(`New IDE connection: ${connectionId} from ${connectionInfo.clientIP}`); this.logConnectionEvent('connect', connectionId, connectionInfo); }); this.bridge.on('disconnection', (connectionId) => { logger.info(`IDE connection closed: ${connectionId}`); this.logConnectionEvent('disconnect', connectionId); }); this.bridge.on('error', (error) => { logger.error('Bridge error:', error); }); this.bridge.on('started', (info) => { logger.info(`WebSocket server listening on ${info.host}:${info.port}`); }); // Process signals process.on('SIGINT', () => { logger.info('Received SIGINT, shutting down bridge server...'); this.stop().then(() => process.exit(0)); }); process.on('SIGTERM', () => { logger.info('Received SIGTERM, shutting down bridge server...'); this.stop().then(() => process.exit(0)); }); // Handle uncaught exceptions process.on('uncaughtException', (error) => { logger.error('Uncaught exception in bridge server:', error); this.stop().then(() => process.exit(1)); }); process.on('unhandledRejection', (reason, promise) => { logger.error('Unhandled rejection in bridge server:', reason); }); } /** * Log startup information */ logStartupInfo() { const status = this.getStatus(); logger.info('Bridge server started successfully'); logger.info(`Configuration:`); logger.info(` - Host: ${status.config.host}`); logger.info(` - Port: ${status.config.port}`); logger.info(` - IDE Type: ${status.config.ideType}`); logger.info(` - Max Connections: ${status.config.maxConnections}`); logger.info(` - Migration Mode: ${status.config.migrationMode}`); logger.info(` - Fallback Enabled: ${status.config.fallbackEnabled}`); logger.info(` - PID: ${process.pid}`); } /** * Log connection events */ logConnectionEvent(type, connectionId, connectionInfo = null) { const event = { timestamp: new Date().toISOString(), type, connectionId, ...(connectionInfo && { clientIP: connectionInfo.clientIP, userAgent: connectionInfo.userAgent }) }; // Log to file if configured if (this.config?.get('logging.bridgeEvents')) { this.writeLogFile('connection', event); } } /** * Write to log file */ async writeLogFile(category, data) { try { const logEntry = `${new Date().toISOString()} [${category.toUpperCase()}] ${JSON.stringify(data)}\n`; await fs.appendFile(this.logFile, logEntry); } catch (error) { logger.debug('Failed to write to log file:', error); } } /** * Write PID file */ async writePidFile() { try { const pidDir = path.dirname(this.pidFile); await fs.mkdir(pidDir, { recursive: true }); await fs.writeFile(this.pidFile, process.pid.toString()); } catch (error) { logger.warn('Failed to write PID file:', error); } } /** * Check if bridge server is running */ async isRunning() { try { const pidData = await fs.readFile(this.pidFile, 'utf8'); const pid = parseInt(pidData.trim()); // Check if process exists process.kill(pid, 0); return true; } catch (error) { // PID file doesn't exist or process is not running return false; } } /** * Stop the bridge server */ async stop() { logger.info('Stopping bridge server...'); try { if (this.bridge && this.running) { await this.bridge.stop(); } await this.cleanup(); this.running = false; logger.info('Bridge server stopped successfully'); } catch (error) { logger.error('Error stopping bridge server:', error); throw error; } } /** * Clean up resources */ async cleanup() { try { // Remove PID file await fs.unlink(this.pidFile).catch(() => {}); // Log shutdown if (this.startTime) { const uptime = Date.now() - this.startTime; logger.info(`Bridge server uptime: ${Math.round(uptime / 1000)}s`); } } catch (error) { logger.debug('Error during cleanup:', error); } } /** * Get server status */ getStatus() { const bridgeStatus = this.bridge?.getStatus() || null; const configStatus = this.config?.getStatus() || null; return { running: this.running, pid: process.pid, uptime: this.startTime ? Date.now() - this.startTime : 0, startTime: this.startTime ? new Date(this.startTime).toISOString() : null, bridge: bridgeStatus, config: configStatus, health: this.bridge?.healthCheck() || { healthy: false } }; } /** * Restart the bridge server */ async restart(options = {}) { logger.info('Restarting bridge server...'); await this.stop(); // Wait a moment before restarting await new Promise(resolve => setTimeout(resolve, 1000)); await this.start(options); logger.info('Bridge server restarted successfully'); } /** * Reload configuration */ async reload() { logger.info('Reloading bridge configuration...'); if (this.config) { await this.config.load(); logger.info('Configuration reloaded successfully'); } else { throw new Error('Configuration not initialized'); } } /** * Get detailed status for monitoring */ getDetailedStatus() { const status = this.getStatus(); const memUsage = process.memoryUsage(); return { ...status, memory: { rss: Math.round(memUsage.rss / 1024 / 1024), // MB heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), // MB heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), // MB external: Math.round(memUsage.external / 1024 / 1024) // MB }, cpu: process.cpuUsage(), platform: process.platform, nodeVersion: process.version }; } } /** * CLI interface for bridge server */ async function main() { const args = process.argv.slice(2); const command = args[0] || 'start'; const server = new BridgeServer(); try { switch (command) { case 'start': const startOptions = { force: args.includes('--force') || args.includes('-f'), configPath: getArgValue(args, '--config') || '.taskmaster/bridge-config.json' }; logger.info('Starting bridge server...'); await server.start(startOptions); // Keep the process running process.on('SIGINT', () => { logger.info('Received interrupt signal, stopping...'); server.stop().then(() => process.exit(0)); }); break; case 'stop': logger.info('Stopping bridge server...'); if (await server.isRunning()) { // Send signal to running process try { const pidData = await fs.readFile('.taskmaster/bridge.pid', 'utf8'); const pid = parseInt(pidData.trim()); process.kill(pid, 'SIGTERM'); logger.info('Stop signal sent to bridge server'); } catch (error) { logger.error('Failed to stop bridge server:', error); process.exit(1); } } else { logger.info('Bridge server is not running'); } break; case 'restart': const restartOptions = { force: args.includes('--force') || args.includes('-f'), configPath: getArgValue(args, '--config') || '.taskmaster/bridge-config.json' }; await server.restart(restartOptions); break; case 'status': if (await server.isRunning()) { // For running server, we'd need to implement a status endpoint // For now, just show basic status logger.info('Bridge server is running'); try { const pidData = await fs.readFile('.taskmaster/bridge.pid', 'utf8'); logger.info(`PID: ${pidData.trim()}`); } catch (error) { logger.debug('Could not read PID file'); } } else { logger.info('Bridge server is not running'); } // Show configuration status const config = new BridgeConfig(); await config.load(); const summary = config.getSummary(); console.log('\nConfiguration:'); console.log(JSON.stringify(summary, null, 2)); break; case 'enable': const configEnable = new BridgeConfig(); await configEnable.load(); await configEnable.enableBridge(); logger.info('Bridge enabled in configuration'); break; case 'disable': const configDisable = new BridgeConfig(); await configDisable.load(); await configDisable.disableBridge(); logger.info('Bridge disabled in configuration'); break; case 'detect-ide': const configDetect = new BridgeConfig(); await configDetect.load(); logger.info('Detecting IDE...'); const detection = await configDetect.detectIDE(); if (detection.detected) { logger.info(`Detected IDE: ${detection.type}`); logger.info(`Capabilities: ${detection.capabilities.join(', ')}`); logger.info(`Confidence: ${Math.round(detection.confidence * 100)}%`); if (detection.version) { logger.info(`Version: ${detection.version}`); } } else { logger.warn('Could not detect IDE type'); } break; case 'health': // Basic health check const healthConfig = new BridgeConfig(); await healthConfig.load(); const validation = healthConfig.validate(); const health = { config: validation.valid, running: await server.isRunning(), timestamp: new Date().toISOString() }; console.log(JSON.stringify(health, null, 2)); process.exit(health.config && health.running ? 0 : 1); break; case 'logs': // Show recent logs try { const logData = await fs.readFile('.taskmaster/bridge.log', 'utf8'); const lines = logData.split('\n').filter(line => line.trim()); const recentLines = lines.slice(-50); // Last 50 lines console.log(recentLines.join('\n')); } catch (error) { logger.info('No log file found or unable to read logs'); } break; default: console.log(` Usage: node bridge-server.js <command> [options] Commands: start [--force] [--config <path>] Start the bridge server stop Stop the bridge server restart [--force] [--config <path>] Restart the bridge server status Show server and configuration status enable Enable bridge in configuration disable Disable bridge in configuration detect-ide Auto-detect IDE type and capabilities health Check bridge health (exit code 0=healthy) logs Show recent bridge logs Options: --force, -f Start even if disabled in config --config <path> Path to configuration file Examples: node bridge-server.js start node bridge-server.js start --force node bridge-server.js status node bridge-server.js detect-ide `); process.exit(command === 'help' ? 0 : 1); } } catch (error) { logger.error(`Command '${command}' failed:`, error); process.exit(1); } } /** * Get argument value from command line args */ function getArgValue(args, flag) { const index = args.indexOf(flag); return index !== -1 && index + 1 < args.length ? args[index + 1] : null; } // Run if this script is executed directly if (import.meta.url === `file://${process.argv[1]}`) { main().catch(error => { logger.error('Bridge server startup failed:', error); process.exit(1); }); } export default BridgeServer;