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
JavaScript
/**
* 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;