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

792 lines (684 loc) 24.4 kB
/** * Bridge Configuration Management * Handles configuration for WebSocket bridge and IDE integration */ import fs from 'fs/promises'; import path from 'path'; import { exec } from 'child_process'; import { promisify } from 'util'; import os from 'os'; import logger from '../../mcp-server/src/logger.js'; import IDEDetection from './ide-detection.js'; const execAsync = promisify(exec); export class BridgeConfig { constructor(configPath = '.taskmaster/bridge-config.json') { this.configPath = configPath; this.config = null; this.detectionCache = new Map(); this.cacheTimeout = 30000; // 30 seconds this.ideDetection = new IDEDetection(); this.defaults = { bridge: { enabled: false, port: 8765, host: 'localhost', maxConnections: 10, timeout: 30000, heartbeatInterval: 10000, autoRestart: true, logLevel: 'info' }, ide: { type: 'auto-detect', // cursor, vscode, windsurf, auto-detect agentEndpoint: null, capabilities: [], fallbackToExternal: true, preferredModel: null, autoDetectModel: true, connectionTimeout: 5000 }, security: { allowedOrigins: ['localhost', '127.0.0.1'], requireAuth: false, authToken: null, enableCORS: true, maxPayloadSize: 10485760 // 10MB }, logging: { level: 'info', bridgeEvents: true, agentCommunication: false, performance: false, logFile: null, maxLogSize: 52428800 // 50MB }, migration: { preserveExternalProviders: true, migrationMode: 'gradual', // immediate, gradual, manual fallbackBehavior: 'external-api', // external-api, error, queue backupConfig: true, rollbackEnabled: true }, monitoring: { enabled: false, metricsPort: 8766, healthCheckInterval: 30000, alertThresholds: { connectionFailures: 5, responseTime: 5000, memoryUsage: 512 // MB } } }; } /** * Load configuration from file */ async load() { try { const configData = await fs.readFile(this.configPath, 'utf8'); const loadedConfig = JSON.parse(configData); // Merge with defaults to ensure all properties exist this.config = this.mergeWithDefaults(loadedConfig); logger.debug('Bridge configuration loaded successfully'); return this.config; } catch (error) { if (error.code === 'ENOENT') { logger.info('Bridge config not found, creating with defaults'); this.config = { ...this.defaults }; await this.save(); } else { logger.error('Error loading bridge config:', error); throw error; } } return this.config; } /** * Merge loaded config with defaults */ mergeWithDefaults(loadedConfig) { const merged = JSON.parse(JSON.stringify(this.defaults)); for (const [section, values] of Object.entries(loadedConfig)) { if (merged[section] && typeof merged[section] === 'object') { merged[section] = { ...merged[section], ...values }; } else { merged[section] = values; } } return merged; } /** * Save configuration to file */ async save() { try { const configDir = path.dirname(this.configPath); await fs.mkdir(configDir, { recursive: true }); // Add metadata const configWithMeta = { ...this.config, _metadata: { version: '1.0.0', lastModified: new Date().toISOString(), platform: os.platform(), nodeVersion: process.version } }; await fs.writeFile(this.configPath, JSON.stringify(configWithMeta, null, 2)); logger.debug('Bridge configuration saved successfully'); } catch (error) { logger.error('Error saving bridge config:', error); throw error; } } /** * Get configuration value using dot notation */ get(keyPath) { if (!this.config) { throw new Error('Configuration not loaded. Call load() first.'); } const keys = keyPath.split('.'); let value = this.config; for (const key of keys) { if (value && typeof value === 'object' && key in value) { value = value[key]; } else { return undefined; } } return value; } /** * Set configuration value using dot notation */ async set(keyPath, value) { if (!this.config) { await this.load(); } const keys = keyPath.split('.'); const lastKey = keys.pop(); let target = this.config; // Navigate to parent object for (const key of keys) { if (!target[key] || typeof target[key] !== 'object') { target[key] = {}; } target = target[key]; } target[lastKey] = value; await this.save(); logger.debug(`Bridge config updated: ${keyPath} = ${JSON.stringify(value)}`); } /** * Enable bridge mode */ async enableBridge(options = {}) { await this.set('bridge.enabled', true); if (options.port) { await this.set('bridge.port', options.port); } if (options.ideType) { await this.set('ide.type', options.ideType); } if (options.host) { await this.set('bridge.host', options.host); } logger.info('Bridge mode enabled'); return this.config; } /** * Disable bridge mode */ async disableBridge() { await this.set('bridge.enabled', false); logger.info('Bridge mode disabled'); return this.config; } /** * Auto-detect IDE type using enhanced detection */ async detectIDE() { const cacheKey = 'ide_detection'; const cached = this.detectionCache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { logger.debug('Using cached IDE detection result'); return cached.result; } logger.info('Auto-detecting IDE type...'); try { // Use the enhanced IDE detection const bestIDE = await this.ideDetection.getBestIDE(); if (bestIDE.type) { const result = { detected: true, type: bestIDE.type, confidence: bestIDE.info.confidence, capabilities: this.getIDECapabilities(bestIDE.type), info: bestIDE.info }; // Update configuration await this.set('ide.type', result.type); await this.set('ide.capabilities', result.capabilities); // Cache the result this.detectionCache.set(cacheKey, { result, timestamp: Date.now() }); logger.info(`Detected IDE: ${result.type} (confidence: ${Math.round(result.confidence * 100)}%)`); return result; } // Fallback to legacy detection methods logger.debug('Enhanced detection failed, trying legacy methods...'); const legacyResult = await this.legacyDetectIDE(); if (legacyResult.detected) { return legacyResult; } } catch (error) { logger.warn('Enhanced IDE detection failed:', error.message); // Fallback to legacy detection const legacyResult = await this.legacyDetectIDE(); if (legacyResult.detected) { return legacyResult; } } const fallbackResult = { detected: false, type: 'unknown', confidence: 0 }; logger.warn('Could not auto-detect IDE type'); // Cache negative result for shorter time this.detectionCache.set(cacheKey, { result: fallbackResult, timestamp: Date.now() - (this.cacheTimeout - 5000) // Cache for 5 seconds only }); return fallbackResult; } /** * Get IDE capabilities based on type */ getIDECapabilities(ideType) { const capabilityMap = { cursor: [ 'text-generation', 'code-completion', 'code-analysis', 'file-operations', 'project-context', 'streaming', 'multi-turn-conversation', 'context-aware-completion' ], vscode: [ 'text-generation', 'code-completion', 'extensions-api', 'workspace-integration', 'github-copilot' ], windsurf: [ 'text-generation', 'code-completion', 'multi-agent', 'workflow-automation', 'advanced-reasoning', 'cascade-ai' ] }; return capabilityMap[ideType] || []; } /** * Legacy IDE detection (fallback) */ async legacyDetectIDE() { const detectionMethods = [ this.detectCursor.bind(this), this.detectVSCode.bind(this), this.detectWindsurf.bind(this) ]; for (const detect of detectionMethods) { try { const result = await detect(); if (result.detected) { await this.set('ide.type', result.type); await this.set('ide.capabilities', result.capabilities); logger.info(`Detected IDE (legacy): ${result.type}`); return result; } } catch (error) { logger.debug(`Error in ${detect.name}:`, error); } } return { detected: false, type: 'unknown' }; } /** * Detect Cursor IDE */ async detectCursor() { logger.debug('Detecting Cursor IDE...'); const indicators = [ // Environment variables !!process.env.CURSOR_USER_DATA_DIR, !!process.env.CURSOR_EXTENSIONS_DIR, !!process.env.CURSOR_SESSION_ID, // Process detection await this.checkProcess('Cursor'), await this.checkProcess('cursor'), // File system checks await this.checkCursorInstallation() ]; if (indicators.some(Boolean)) { const capabilities = [ 'text-generation', 'code-completion', 'code-analysis', 'file-operations', 'project-context', 'streaming', 'multi-turn-conversation' ]; // Check for advanced features if (await this.checkCursorAIFeatures()) { capabilities.push('advanced-ai-features', 'context-aware-completion'); } return { detected: true, type: 'cursor', capabilities, confidence: indicators.filter(Boolean).length / indicators.length, version: await this.getCursorVersion() }; } return { detected: false }; } /** * Check Cursor installation */ async checkCursorInstallation() { const possiblePaths = { darwin: [ '/Applications/Cursor.app', `${os.homedir()}/Applications/Cursor.app` ], win32: [ 'C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Cursor', 'C:\\Program Files\\Cursor' ], linux: [ '/usr/bin/cursor', '/usr/local/bin/cursor', `${os.homedir()}/.local/bin/cursor` ] }; const paths = possiblePaths[os.platform()] || []; for (const path of paths) { try { const expandedPath = path.replace('%USERNAME%', os.userInfo().username); await fs.access(expandedPath); return true; } catch { continue; } } return false; } /** * Check Cursor AI features */ async checkCursorAIFeatures() { try { // Check if Cursor has AI features enabled // This would involve checking Cursor's configuration return true; // Placeholder } catch { return false; } } /** * Get Cursor version */ async getCursorVersion() { try { const { stdout } = await execAsync('cursor --version'); return stdout.trim(); } catch { return 'unknown'; } } /** * Detect VS Code */ async detectVSCode() { logger.debug('Detecting VS Code...'); const indicators = [ // Environment variables !!process.env.VSCODE_PID, !!process.env.VSCODE_IPC_HOOK, !!process.env.VSCODE_CWD, // Process detection await this.checkProcess('Code'), await this.checkProcess('code'), // Installation check await this.checkVSCodeInstallation() ]; if (indicators.some(Boolean)) { const capabilities = [ 'text-generation', 'code-completion', 'extensions-api', 'workspace-integration' ]; // Check for AI extensions const extensions = await this.getVSCodeExtensions(); if (extensions.includes('github.copilot')) { capabilities.push('github-copilot', 'ai-chat'); } if (extensions.includes('ms-vscode.vscode-ai')) { capabilities.push('vscode-ai'); } return { detected: true, type: 'vscode', capabilities, confidence: indicators.filter(Boolean).length / indicators.length, version: await this.getVSCodeVersion(), extensions: extensions.slice(0, 10) // Limit to first 10 extensions }; } return { detected: false }; } /** * Check VS Code installation */ async checkVSCodeInstallation() { try { await execAsync('code --version'); return true; } catch { return false; } } /** * Get VS Code extensions */ async getVSCodeExtensions() { try { const { stdout } = await execAsync('code --list-extensions'); return stdout.split('\n').filter(ext => ext.trim()); } catch { return []; } } /** * Get VS Code version */ async getVSCodeVersion() { try { const { stdout } = await execAsync('code --version'); return stdout.split('\n')[0]; } catch { return 'unknown'; } } /** * Detect Windsurf IDE */ async detectWindsurf() { logger.debug('Detecting Windsurf IDE...'); const indicators = [ // Environment variables !!process.env.WINDSURF_USER_DATA_DIR, !!process.env.WINDSURF_SESSION_ID, // Process detection await this.checkProcess('Windsurf'), await this.checkProcess('windsurf'), // Installation check await this.checkWindsurfInstallation() ]; if (indicators.some(Boolean)) { return { detected: true, type: 'windsurf', capabilities: [ 'text-generation', 'code-completion', 'multi-agent', 'workflow-automation', 'advanced-reasoning', 'cascade-ai' ], confidence: indicators.filter(Boolean).length / indicators.length, version: await this.getWindsurfVersion() }; } return { detected: false }; } /** * Check Windsurf installation */ async checkWindsurfInstallation() { const possiblePaths = { darwin: ['/Applications/Windsurf.app'], win32: ['C:\\Program Files\\Windsurf'], linux: ['/usr/bin/windsurf', '/usr/local/bin/windsurf'] }; const paths = possiblePaths[os.platform()] || []; for (const path of paths) { try { await fs.access(path); return true; } catch { continue; } } return false; } /** * Get Windsurf version */ async getWindsurfVersion() { try { const { stdout } = await execAsync('windsurf --version'); return stdout.trim(); } catch { return 'unknown'; } } /** * Check if process is running */ async checkProcess(processName) { try { let command; if (os.platform() === 'win32') { command = `tasklist /FI "IMAGENAME eq ${processName}.exe" /FO CSV | find /I "${processName}"`; } else { command = `pgrep -f "${processName}"`; } const { stdout } = await execAsync(command); return stdout.trim().length > 0; } catch { return false; } } /** * Get bridge status */ getStatus() { if (!this.config) { return { loaded: false }; } return { loaded: true, enabled: this.get('bridge.enabled'), ideType: this.get('ide.type'), port: this.get('bridge.port'), host: this.get('bridge.host'), migrationMode: this.get('migration.migrationMode'), fallbackEnabled: this.get('ide.fallbackToExternal'), configPath: this.configPath, lastModified: this.config._metadata?.lastModified, version: this.config._metadata?.version }; } /** * Validate configuration */ validate() { if (!this.config) { return { valid: false, errors: ['Configuration not loaded'] }; } const errors = []; // Validate bridge settings const port = this.get('bridge.port'); if (!Number.isInteger(port) || port < 1024 || port > 65535) { errors.push('Bridge port must be an integer between 1024 and 65535'); } const host = this.get('bridge.host'); if (!host || typeof host !== 'string') { errors.push('Bridge host must be a valid string'); } const maxConnections = this.get('bridge.maxConnections'); if (!Number.isInteger(maxConnections) || maxConnections < 1) { errors.push('Max connections must be a positive integer'); } // Validate IDE type const ideType = this.get('ide.type'); const validIdeTypes = ['cursor', 'vscode', 'windsurf', 'auto-detect']; if (!validIdeTypes.includes(ideType)) { errors.push(`Invalid IDE type: ${ideType}. Must be one of: ${validIdeTypes.join(', ')}`); } // Validate migration mode const migrationMode = this.get('migration.migrationMode'); const validMigrationModes = ['immediate', 'gradual', 'manual']; if (!validMigrationModes.includes(migrationMode)) { errors.push(`Invalid migration mode: ${migrationMode}. Must be one of: ${validMigrationModes.join(', ')}`); } // Validate fallback behavior const fallbackBehavior = this.get('migration.fallbackBehavior'); const validFallbackBehaviors = ['external-api', 'error', 'queue']; if (!validFallbackBehaviors.includes(fallbackBehavior)) { errors.push(`Invalid fallback behavior: ${fallbackBehavior}. Must be one of: ${validFallbackBehaviors.join(', ')}`); } return { valid: errors.length === 0, errors }; } /** * Reset configuration to defaults */ async reset() { logger.info('Resetting bridge configuration to defaults'); this.config = JSON.parse(JSON.stringify(this.defaults)); await this.save(); return this.config; } /** * Backup current configuration */ async backup(backupPath) { if (!this.config) { throw new Error('No configuration to backup'); } const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const defaultBackupPath = `${this.configPath}.backup.${timestamp}`; const finalBackupPath = backupPath || defaultBackupPath; await fs.writeFile(finalBackupPath, JSON.stringify(this.config, null, 2)); logger.info(`Configuration backed up to: ${finalBackupPath}`); return finalBackupPath; } /** * Restore configuration from backup */ async restore(backupPath) { try { const backupData = await fs.readFile(backupPath, 'utf8'); this.config = JSON.parse(backupData); await this.save(); logger.info(`Configuration restored from: ${backupPath}`); return this.config; } catch (error) { logger.error(`Failed to restore configuration from ${backupPath}:`, error); throw error; } } /** * Get configuration summary for display */ getSummary() { if (!this.config) { return 'Configuration not loaded'; } const status = this.getStatus(); const validation = this.validate(); return { status: status.enabled ? 'enabled' : 'disabled', ide: status.ideType, endpoint: `${status.host}:${status.port}`, migration: status.migrationMode, valid: validation.valid, errors: validation.errors, lastModified: status.lastModified }; } } export default BridgeConfig;