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