UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

634 lines (633 loc) 27.3 kB
import path from 'path'; import { FileUtils } from './file-utils.js'; import { ConfigurationError, ValidationError, createErrorContext } from './enhanced-errors.js'; import { ENVIRONMENT_VARIABLES, DEFAULT_PERFORMANCE_CONFIG, getEnvironmentValue, validateAllEnvironmentVariables } from './config-defaults.js'; import logger from '../../../logger.js'; import { getProjectRootSafe, logWorkingDirectorySafety } from '../../../utils/safe-path-resolver.js'; import { getProjectRoot } from '../../code-map-generator/utils/pathUtils.enhanced.js'; export class ConfigLoader { static instance; config = null; configCache = new Map(); llmConfigPath; mcpConfigPath; performanceConfig; initializationPromise = null; loadingStartTime = 0; cacheHits = 0; cacheRequests = 0; constructor() { const projectRoot = getProjectRoot(); this.llmConfigPath = path.join(projectRoot, 'llm_config.json'); this.mcpConfigPath = path.join(projectRoot, 'mcp-config.json'); this.performanceConfig = { ...DEFAULT_PERFORMANCE_CONFIG }; } static getInstance() { if (!ConfigLoader.instance) { ConfigLoader.instance = new ConfigLoader(); } return ConfigLoader.instance; } getVibeTaskManagerOutputDirectory() { const baseOutputDir = process.env.VIBE_CODER_OUTPUT_DIR ? path.resolve(process.env.VIBE_CODER_OUTPUT_DIR) : path.join(getProjectRoot(), 'VibeCoderOutput'); return path.join(baseOutputDir, 'vibe-task-manager'); } isCacheValid(cacheKey) { if (!this.performanceConfig.enableConfigCache) { return false; } const cached = this.configCache.get(cacheKey); if (!cached) { return false; } const now = Date.now(); return (now - cached.timestamp) < cached.ttl; } getCachedConfig(cacheKey) { this.cacheRequests++; if (!this.isCacheValid(cacheKey)) { this.configCache.delete(cacheKey); return null; } const cached = this.configCache.get(cacheKey); if (cached) { this.cacheHits++; return { ...cached.config }; } return null; } cacheConfig(cacheKey, config) { if (!this.performanceConfig.enableConfigCache) { return; } this.configCache.set(cacheKey, { config: { ...config }, timestamp: Date.now(), ttl: this.performanceConfig.configCacheTTL }); } async batchLoadConfigs() { const context = createErrorContext('ConfigLoader', 'batchLoadConfigs') .metadata({ llmConfigPath: this.llmConfigPath, mcpConfigPath: this.mcpConfigPath, batchLoading: this.performanceConfig.batchConfigLoading }) .build(); try { if (this.performanceConfig.batchConfigLoading) { const [llmResult, mcpResult] = await Promise.all([ FileUtils.readJsonFile(this.llmConfigPath), FileUtils.readJsonFile(this.mcpConfigPath) ]); if (!llmResult.success) { throw new ConfigurationError(`Failed to load LLM configuration file: ${llmResult.error}`, context, { configKey: 'llm_config', expectedValue: 'Valid JSON file with LLM mappings', actualValue: llmResult.error }); } if (!mcpResult.success) { throw new ConfigurationError(`Failed to load MCP configuration file: ${mcpResult.error}`, context, { configKey: 'mcp_config', expectedValue: 'Valid JSON file with MCP tool definitions', actualValue: mcpResult.error }); } return { llm: llmResult.data, mcp: mcpResult.data }; } else { const llmResult = await FileUtils.readJsonFile(this.llmConfigPath); if (!llmResult.success) { throw new ConfigurationError(`Failed to load LLM configuration file: ${llmResult.error}`, context, { configKey: 'llm_config', expectedValue: 'Valid JSON file with LLM mappings', actualValue: llmResult.error }); } const mcpResult = await FileUtils.readJsonFile(this.mcpConfigPath); if (!mcpResult.success) { throw new ConfigurationError(`Failed to load MCP configuration file: ${mcpResult.error}`, context, { configKey: 'mcp_config', expectedValue: 'Valid JSON file with MCP tool definitions', actualValue: mcpResult.error }); } return { llm: llmResult.data, mcp: mcpResult.data }; } } catch (error) { if (error instanceof ConfigurationError) { throw error; } throw new ConfigurationError(`Unexpected error during configuration loading: ${error instanceof Error ? error.message : String(error)}`, context, { cause: error instanceof Error ? error : undefined }); } } async loadConfig() { this.loadingStartTime = performance.now(); try { const cacheKey = 'main-config'; const cachedConfig = this.getCachedConfig(cacheKey); if (cachedConfig) { const loadTime = performance.now() - this.loadingStartTime; logger.debug({ loadTime }, 'Configuration loaded from cache'); this.config = cachedConfig; return { success: true, data: cachedConfig, metadata: { filePath: 'cached-config', operation: 'load_config_cached', timestamp: new Date(), loadTime } }; } logger.debug('Loading Vibe Task Manager configuration from files'); const { llm, mcp } = await this.batchLoadConfigs(); const envValidation = validateAllEnvironmentVariables(); if (!envValidation.valid) { const errorContext = createErrorContext('ConfigLoader', 'loadConfig') .metadata({ errors: envValidation.errors }) .build(); throw new ConfigurationError(`Environment variable validation failed: ${envValidation.errors.join(', ')}`, errorContext, { configKey: 'environment_variables', expectedValue: 'Valid environment configuration', actualValue: envValidation.errors.join(', ') }); } if (envValidation.warnings.length > 0) { logger.warn({ warnings: envValidation.warnings }, 'Environment variable warnings (using defaults)'); } this.config = { llm, mcp, taskManager: { maxConcurrentTasks: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_CONCURRENT_TASKS), defaultTaskTemplate: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DEFAULT_TASK_TEMPLATE), dataDirectory: this.getVibeTaskManagerOutputDirectory(), performanceTargets: { maxResponseTime: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_RESPONSE_TIME), maxMemoryUsage: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_MEMORY_USAGE), minTestCoverage: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MIN_TEST_COVERAGE) }, agentSettings: { maxAgents: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_AGENTS), defaultAgent: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DEFAULT_AGENT), coordinationStrategy: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_COORDINATION_STRATEGY), healthCheckInterval: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_HEALTH_CHECK_INTERVAL) }, nlpSettings: { primaryMethod: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_PRIMARY_NLP_METHOD), fallbackMethod: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_FALLBACK_NLP_METHOD), minConfidence: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MIN_CONFIDENCE), maxProcessingTime: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_NLP_PROCESSING_TIME) }, timeouts: { taskExecution: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_TASK_EXECUTION_TIMEOUT), taskDecomposition: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_TASK_DECOMPOSITION_TIMEOUT), recursiveTaskDecomposition: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RECURSIVE_TASK_DECOMPOSITION_TIMEOUT), taskRefinement: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_TASK_REFINEMENT_TIMEOUT), agentCommunication: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_AGENT_COMMUNICATION_TIMEOUT), llmRequest: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_LLM_REQUEST_TIMEOUT), fileOperations: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_FILE_OPERATIONS_TIMEOUT), databaseOperations: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DATABASE_OPERATIONS_TIMEOUT), networkOperations: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_NETWORK_OPERATIONS_TIMEOUT) }, retryPolicy: { maxRetries: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_RETRIES), backoffMultiplier: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_BACKOFF_MULTIPLIER), initialDelayMs: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_INITIAL_DELAY_MS), maxDelayMs: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_MAX_DELAY_MS), enableExponentialBackoff: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_ENABLE_EXPONENTIAL_BACKOFF) }, rddConfig: { maxDepth: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_MAX_DEPTH), maxSubTasks: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_MAX_SUB_TASKS), minConfidence: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_MIN_CONFIDENCE), enableParallelDecomposition: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_ENABLE_PARALLEL_DECOMPOSITION), epicTimeLimit: getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_RDD_EPIC_TIME_LIMIT) }, performance: { memoryManagement: { enabled: true, maxMemoryPercentage: 0.3, monitorInterval: 5000, autoManage: true, pruneThreshold: 0.6, prunePercentage: 0.4 }, fileSystem: { enableLazyLoading: true, batchSize: 50, enableCompression: false, indexingEnabled: true, concurrentOperations: 10 }, caching: { enabled: true, strategy: 'memory', maxCacheSize: 50 * 1024 * 1024, defaultTTL: 60000, enableWarmup: true }, monitoring: { enabled: true, metricsInterval: 1000, enableAlerts: true, performanceThresholds: { maxResponseTime: 50, maxMemoryUsage: 300, maxCpuUsage: 70 } } } } }; this.cacheConfig(cacheKey, this.config); const loadTime = performance.now() - this.loadingStartTime; if (loadTime > this.performanceConfig.maxStartupTime) { logger.warn({ loadTime, target: this.performanceConfig.maxStartupTime }, 'Configuration loading exceeded performance target'); } else { logger.debug({ loadTime }, 'Configuration loaded within performance target'); } logger.info({ loadTime }, 'Vibe Task Manager configuration loaded successfully'); return { success: true, data: this.config, metadata: { filePath: 'combined-config', operation: 'load_config', timestamp: new Date(), loadTime, fromCache: false } }; } catch (error) { const loadTime = performance.now() - this.loadingStartTime; if (error instanceof ConfigurationError || error instanceof ValidationError) { logger.error({ err: error, loadTime, category: error.category, severity: error.severity, retryable: error.retryable, recoveryActions: error.recoveryActions.length }, 'Configuration loading failed with enhanced error'); } else { logger.error({ err: error, loadTime, errorType: error instanceof Error ? error.constructor.name : 'Unknown' }, 'Configuration loading failed with unexpected error'); } return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'combined-config', operation: 'load_config', timestamp: new Date(), loadTime } }; } } getConfig() { return this.config ? { ...this.config } : null; } getLLMModel(operation) { const fallbackModel = getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DEFAULT_LLM_MODEL); if (!this.config) { return fallbackModel; } return this.config.llm.llm_mapping[operation] || this.config.llm.llm_mapping['default_generation'] || fallbackModel; } getMCPToolConfig(toolName) { if (!this.config) { return null; } return this.config.mcp.tools[toolName] || null; } getTaskManagerConfig() { return this.config?.taskManager || null; } validateLLMMappings() { if (!this.config) { return { valid: false, missing: ['Configuration not loaded'] }; } const requiredMappings = [ 'task_decomposition', 'atomic_task_detection', 'intent_recognition', 'task_refinement', 'dependency_graph_analysis', 'agent_coordination' ]; const missing = requiredMappings.filter(mapping => !this.config.llm.llm_mapping[mapping]); return { valid: missing.length === 0, missing }; } validateMCPRegistration() { if (!this.config) { return { valid: false, error: 'Configuration not loaded' }; } const taskManagerConfig = this.config.mcp.tools['vibe-task-manager']; if (!taskManagerConfig) { return { valid: false, error: 'vibe-task-manager not found in MCP config' }; } if (!taskManagerConfig.description || !taskManagerConfig.use_cases || !taskManagerConfig.input_patterns) { return { valid: false, error: 'vibe-task-manager MCP config is incomplete' }; } return { valid: true }; } getConfigSummary() { if (!this.config) { return { llmMappingsCount: 0, mcpToolsCount: 0, taskManagerConfigured: false, requiredLLMMappingsPresent: false }; } const llmValidation = this.validateLLMMappings(); const mcpValidation = this.validateMCPRegistration(); return { llmMappingsCount: Object.keys(this.config.llm.llm_mapping).length, mcpToolsCount: Object.keys(this.config.mcp.tools).length, taskManagerConfigured: mcpValidation.valid, requiredLLMMappingsPresent: llmValidation.valid }; } async reloadConfig() { this.config = null; return await this.loadConfig(); } isConfigValid() { if (!this.config) { return false; } const llmValidation = this.validateLLMMappings(); const mcpValidation = this.validateMCPRegistration(); return llmValidation.valid && mcpValidation.valid; } getPerformanceConfig() { return { ...this.performanceConfig }; } updatePerformanceConfig(updates) { this.performanceConfig = { ...this.performanceConfig, ...updates }; logger.debug({ updates }, 'Performance configuration updated'); } clearCache() { this.configCache.clear(); this.cacheHits = 0; this.cacheRequests = 0; logger.debug('Configuration cache and statistics cleared'); } resetCacheStats() { this.cacheHits = 0; this.cacheRequests = 0; logger.debug('Cache statistics reset'); } getCacheStats() { const entries = Array.from(this.configCache.keys()); const hitRate = this.cacheRequests > 0 ? (this.cacheHits / this.cacheRequests) : 0; return { size: this.configCache.size, entries, hitRate: Math.round(hitRate * 100) / 100, totalRequests: this.cacheRequests, totalHits: this.cacheHits }; } async warmupCache() { if (!this.performanceConfig.enableConfigCache) { return; } const startTime = performance.now(); await this.loadConfig(); this.getLLMModel('task_decomposition'); this.getLLMModel('atomic_task_detection'); this.getLLMModel('intent_recognition'); this.getMCPToolConfig('vibe-task-manager'); this.getTaskManagerConfig(); const warmupTime = performance.now() - startTime; logger.debug({ warmupTime }, 'Configuration cache warmed up'); } } export async function getVibeTaskManagerConfig() { const loader = ConfigLoader.getInstance(); if (!loader.getConfig()) { const result = await loader.loadConfig(); if (!result.success) { logger.error({ error: result.error }, 'Failed to load Vibe Task Manager configuration'); return null; } } return loader.getConfig(); } export async function getLLMModelForOperation(operation) { const loader = ConfigLoader.getInstance(); if (!loader.getConfig()) { await loader.loadConfig(); } return loader.getLLMModel(operation); } export function getBaseOutputDir() { return process.env.VIBE_CODER_OUTPUT_DIR ? path.resolve(process.env.VIBE_CODER_OUTPUT_DIR) : path.join(getProjectRoot(), 'VibeCoderOutput'); } export function getVibeTaskManagerOutputDir() { const baseOutputDir = getBaseOutputDir(); return path.join(baseOutputDir, 'vibe-task-manager'); } function validateExtractedSecurityConfig(extractedConfig) { const errors = []; const warnings = []; if (!extractedConfig.allowedReadDirectory) { errors.push('allowedReadDirectory is required'); } else if (typeof extractedConfig.allowedReadDirectory !== 'string') { errors.push('allowedReadDirectory must be a string'); } if (!extractedConfig.allowedWriteDirectory) { errors.push('allowedWriteDirectory is required'); } else if (typeof extractedConfig.allowedWriteDirectory !== 'string') { errors.push('allowedWriteDirectory must be a string'); } if (extractedConfig.securityMode && extractedConfig.securityMode !== 'strict' && extractedConfig.securityMode !== 'permissive') { errors.push('securityMode must be either "strict" or "permissive"'); } return { isValid: errors.length === 0, errors: errors, warnings: warnings, extractedConfig }; } export function extractVibeTaskManagerSecurityConfig(config) { let securityConfig = {}; if (config) { if (config.env) { logger.debug({ configEnv: config.env, hasReadDir: Boolean(config.env.VIBE_TASK_MANAGER_READ_DIR), hasWriteDir: Boolean(config.env.VIBE_CODER_OUTPUT_DIR), hasSecurityMode: Boolean(config.env.VIBE_TASK_MANAGER_SECURITY_MODE) }, 'Extracting security config from MCP client environment variables'); securityConfig = { allowedReadDirectory: config.env.VIBE_TASK_MANAGER_READ_DIR, allowedWriteDirectory: config.env.VIBE_CODER_OUTPUT_DIR, securityMode: (config.env.VIBE_TASK_MANAGER_SECURITY_MODE || 'strict') }; } if (!securityConfig.allowedReadDirectory || !securityConfig.allowedWriteDirectory) { const toolConfig = config.tools?.['vibe-task-manager']; const configSection = config.config?.['vibe-task-manager']; if (toolConfig || configSection) { logger.debug({ toolConfig, configSection }, 'Using legacy tool config extraction as fallback'); securityConfig = { ...configSection, ...toolConfig, ...securityConfig }; } } } logger.debug({ extractedSecurityConfig: securityConfig, hasConfig: Boolean(config), hasEnv: Boolean(config?.env), configKeys: config ? Object.keys(config) : [] }, 'Extracted vibe-task-manager security config'); logWorkingDirectorySafety(); const allowedReadDirectory = securityConfig.allowedReadDirectory || process.env.VIBE_TASK_MANAGER_READ_DIR || getProjectRootSafe(); const allowedWriteDirectory = securityConfig.allowedWriteDirectory || process.env.VIBE_CODER_OUTPUT_DIR || path.join(getProjectRootSafe(), 'VibeCoderOutput'); const securityMode = (securityConfig.securityMode || process.env.VIBE_TASK_MANAGER_SECURITY_MODE || 'strict'); const finalConfig = { allowedReadDirectory, allowedWriteDirectory, securityMode }; const validation = validateExtractedSecurityConfig(finalConfig); if (!validation.isValid) { const errorMessage = `Security configuration validation failed: ${validation.errors.join(', ')}`; logger.error({ errors: validation.errors, warnings: validation.warnings, config: finalConfig }, errorMessage); throw new Error(errorMessage); } if (validation.warnings.length > 0) { logger.warn({ warnings: validation.warnings }, 'Security configuration has warnings'); } let resolvedReadDir; let resolvedWriteDir; try { resolvedReadDir = path.resolve(allowedReadDirectory); resolvedWriteDir = path.resolve(allowedWriteDirectory); } catch (pathError) { const errorMessage = `Failed to resolve security configuration paths: ${pathError instanceof Error ? pathError.message : String(pathError)}`; logger.error({ allowedReadDirectory, allowedWriteDirectory, pathError }, errorMessage); throw new Error(errorMessage); } logger.info({ allowedReadDirectory: resolvedReadDir, allowedWriteDirectory: resolvedWriteDir, securityMode, extractionSource: config?.env ? 'MCP client env' : 'process env fallback' }, 'Vibe Task Manager security configuration extracted from MCP client config'); return { allowedReadDirectory: resolvedReadDir, allowedWriteDirectory: resolvedWriteDir, securityMode }; } export async function getOrchestratorConfig() { try { const config = await getVibeTaskManagerConfig(); if (!config) { logger.debug('No config found, using AgentOrchestrator defaults'); return null; } const agentSettings = config.taskManager?.agentSettings; const timeouts = config.taskManager?.timeouts; if (!agentSettings) { logger.debug('No agent settings found in config, using AgentOrchestrator defaults'); return null; } const orchestratorConfig = { heartbeatInterval: (agentSettings.healthCheckInterval || 30) * 1000, taskTimeout: timeouts?.taskExecution || 300000, maxRetries: 3, loadBalancingStrategy: mapCoordinationStrategy(agentSettings.coordinationStrategy), enableHealthChecks: true, conflictResolutionStrategy: 'queue', heartbeatTimeoutMultiplier: 3, enableAdaptiveTimeouts: true, maxHeartbeatMisses: 5 }; logger.debug({ orchestratorConfig }, 'Created OrchestratorConfig from centralized config'); return orchestratorConfig; } catch (error) { logger.warn({ err: error }, 'Failed to load centralized config for AgentOrchestrator, using defaults'); return null; } } function mapCoordinationStrategy(strategy) { switch (strategy) { case 'round_robin': return 'round_robin'; case 'capability_based': return 'capability_based'; case 'priority_based': case 'least_loaded': return 'performance_based'; default: return 'capability_based'; } }