Skip to main content
Glama
config-loader.ts36.8 kB
import path from 'path'; import { FileUtils, FileOperationResult } from './file-utils.js'; import { OpenRouterConfig } from '../../../types/workflow.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 { logWorkingDirectorySafety } from '../../../utils/safe-path-resolver.js'; import { getProjectRoot } from '../../code-map-generator/utils/pathUtils.enhanced.js'; /** * LLM configuration interface */ export interface LLMConfig { llm_mapping: Record<string, string>; } /** * MCP tool configuration interface */ export interface MCPToolConfig { description: string; use_cases: string[]; input_patterns: string[]; } /** * MCP configuration interface */ export interface MCPConfig { tools: Record<string, MCPToolConfig>; } /** * Vibe Task Manager security configuration interface */ export interface VibeTaskManagerSecurityConfig { allowedReadDirectory: string; allowedWriteDirectory: string; securityMode: 'strict' | 'permissive'; } /** * Performance configuration for startup optimization */ export interface PerformanceConfig { enableConfigCache: boolean; configCacheTTL: number; lazyLoadServices: boolean; preloadCriticalServices: string[]; connectionPoolSize: number; maxStartupTime: number; asyncInitialization: boolean; batchConfigLoading: boolean; } /** * Configuration cache entry */ interface ConfigCacheEntry { config: VibeTaskManagerConfig; timestamp: number; ttl: number; } /** * Combined configuration for Vibe Task Manager */ export interface VibeTaskManagerConfig { llm: LLMConfig; mcp: MCPConfig; taskManager: { // Task manager specific settings maxConcurrentTasks: number; defaultTaskTemplate: string; dataDirectory: string; performanceTargets: { maxResponseTime: number; // ms maxMemoryUsage: number; // MB minTestCoverage: number; // percentage }; agentSettings: { maxAgents: number; defaultAgent: string; coordinationStrategy: 'round_robin' | 'least_loaded' | 'capability_based' | 'priority_based'; healthCheckInterval: number; // seconds }; nlpSettings: { primaryMethod: 'pattern' | 'llm' | 'hybrid'; fallbackMethod: 'pattern' | 'llm' | 'none'; minConfidence: number; maxProcessingTime: number; // ms }; // Timeout and retry configuration timeouts: { taskExecution: number; // ms taskDecomposition: number; // ms recursiveTaskDecomposition: number; // ms taskRefinement: number; // ms agentCommunication: number; // ms llmRequest: number; // ms fileOperations: number; // ms databaseOperations: number; // ms networkOperations: number; // ms }; retryPolicy: { maxRetries: number; backoffMultiplier: number; initialDelayMs: number; maxDelayMs: number; enableExponentialBackoff: boolean; }; // RDD Engine Configuration rddConfig: { maxDepth: number; maxSubTasks: number; minConfidence: number; enableParallelDecomposition: boolean; epicTimeLimit: number; }; // Performance optimization settings performance: { memoryManagement: { enabled: boolean; maxMemoryPercentage: number; monitorInterval: number; autoManage: boolean; pruneThreshold: number; prunePercentage: number; }; fileSystem: { enableLazyLoading: boolean; batchSize: number; enableCompression: boolean; indexingEnabled: boolean; concurrentOperations: number; }; caching: { enabled: boolean; strategy: 'memory' | 'disk' | 'hybrid'; maxCacheSize: number; defaultTTL: number; enableWarmup: boolean; }; monitoring: { enabled: boolean; metricsInterval: number; enableAlerts: boolean; performanceThresholds: { maxResponseTime: number; maxMemoryUsage: number; maxCpuUsage: number; }; }; }; }; } /** * Enhanced configuration loader with performance optimizations * - Configuration caching with TTL * - Async initialization * - Batch loading * - Connection pooling preparation */ export class ConfigLoader { private static instance: ConfigLoader; private config: VibeTaskManagerConfig | null = null; private configCache: Map<string, ConfigCacheEntry> = new Map(); private llmConfigPath: string; private mcpConfigPath: string; private performanceConfig: PerformanceConfig; private initializationPromise: Promise<void> | null = null; private loadingStartTime: number = 0; // Cache hit rate tracking private cacheHits: number = 0; private cacheRequests: number = 0; private constructor() { const projectRoot = getProjectRoot(); this.llmConfigPath = path.join(projectRoot, 'llm_config.json'); this.mcpConfigPath = path.join(projectRoot, 'mcp-config.json'); // Performance configuration from defaults this.performanceConfig = { ...DEFAULT_PERFORMANCE_CONFIG }; } /** * Get singleton instance */ static getInstance(): ConfigLoader { if (!ConfigLoader.instance) { ConfigLoader.instance = new ConfigLoader(); } return ConfigLoader.instance; } /** * Get the Vibe Task Manager output directory following the established convention */ private getVibeTaskManagerOutputDirectory(): string { 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'); } /** * Check if cached configuration is valid */ private isCacheValid(cacheKey: string): boolean { 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; } /** * Get configuration from cache */ private getCachedConfig(cacheKey: string): VibeTaskManagerConfig | null { 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; } /** * Cache configuration */ private cacheConfig(cacheKey: string, config: VibeTaskManagerConfig): void { if (!this.performanceConfig.enableConfigCache) { return; } this.configCache.set(cacheKey, { config: { ...config }, timestamp: Date.now(), ttl: this.performanceConfig.configCacheTTL }); } /** * Load configuration files in batch for better performance */ private async batchLoadConfigs(): Promise<{ llm: LLMConfig; mcp: MCPConfig }> { const context = createErrorContext('ConfigLoader', 'batchLoadConfigs') .metadata({ llmConfigPath: this.llmConfigPath, mcpConfigPath: this.mcpConfigPath, batchLoading: this.performanceConfig.batchConfigLoading }) .build(); try { if (this.performanceConfig.batchConfigLoading) { // Load both files concurrently const [llmResult, mcpResult] = await Promise.all([ FileUtils.readJsonFile<LLMConfig>(this.llmConfigPath), FileUtils.readJsonFile<MCPConfig>(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 { // Sequential loading (fallback) const llmResult = await FileUtils.readJsonFile<LLMConfig>(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<MCPConfig>(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 } ); } } /** * Load configuration from existing files with performance optimizations */ async loadConfig(): Promise<FileOperationResult<VibeTaskManagerConfig>> { this.loadingStartTime = performance.now(); try { const cacheKey = 'main-config'; // Check cache first 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'); // Batch load configuration files const { llm, mcp } = await this.batchLoadConfigs(); // Validate environment variables first 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(', ') } ); } // Log warnings for non-critical environment variable issues if (envValidation.warnings.length > 0) { logger.warn({ warnings: envValidation.warnings }, 'Environment variable warnings (using defaults)'); } // Combine configurations with environment-based task manager settings 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) }, // Environment-based timeout and retry settings 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) }, // RDD Engine Configuration 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) }, // Enhanced performance optimization for <50ms target performance: { memoryManagement: { enabled: true, maxMemoryPercentage: 0.3, // Reduced to 30% for better performance monitorInterval: 5000, // 5 seconds for faster response autoManage: true, pruneThreshold: 0.6, // More aggressive pruning prunePercentage: 0.4 // Prune 40% of entries }, fileSystem: { enableLazyLoading: true, batchSize: 50, // Smaller batches for faster processing enableCompression: false, // Disabled for speed indexingEnabled: true, concurrentOperations: 10 // Increased concurrency }, caching: { enabled: true, strategy: 'memory', // Memory-only for speed maxCacheSize: 50 * 1024 * 1024, // 50MB for faster access defaultTTL: 60000, // 1 minute for faster refresh enableWarmup: true }, monitoring: { enabled: true, metricsInterval: 1000, // 1 second for real-time monitoring enableAlerts: true, performanceThresholds: { maxResponseTime: 50, // <50ms target maxMemoryUsage: 300, // 300MB maxCpuUsage: 70 // 70% } } } } }; // Cache the configuration this.cacheConfig(cacheKey, this.config); const loadTime = performance.now() - this.loadingStartTime; // Check if we met the performance target 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; // Enhanced error logging with context 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 } }; } } /** * Get current configuration */ getConfig(): VibeTaskManagerConfig | null { return this.config ? { ...this.config } : null; } /** * Get LLM model for specific operation */ getLLMModel(operation: string): string { const fallbackModel = getEnvironmentValue(ENVIRONMENT_VARIABLES.VIBE_DEFAULT_LLM_MODEL) as string; if (!this.config) { return fallbackModel; } return this.config.llm.llm_mapping[operation] || this.config.llm.llm_mapping['default_generation'] || fallbackModel; } /** * Get MCP tool configuration */ getMCPToolConfig(toolName: string): MCPToolConfig | null { if (!this.config) { return null; } return this.config.mcp.tools[toolName] || null; } /** * Get task manager specific configuration */ getTaskManagerConfig(): VibeTaskManagerConfig['taskManager'] | null { return this.config?.taskManager || null; } /** * Validate that required LLM mappings exist for task manager */ validateLLMMappings(): { valid: boolean; missing: string[] } { 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 }; } /** * Validate that task manager is registered in MCP config */ validateMCPRegistration(): { valid: boolean; error?: string } { 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' }; } // Validate required fields if (!taskManagerConfig.description || !taskManagerConfig.use_cases || !taskManagerConfig.input_patterns) { return { valid: false, error: 'vibe-task-manager MCP config is incomplete' }; } return { valid: true }; } /** * Get configuration summary for logging/debugging */ getConfigSummary(): { llmMappingsCount: number; mcpToolsCount: number; taskManagerConfigured: boolean; requiredLLMMappingsPresent: boolean; } { 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 }; } /** * Reload configuration from files */ async reloadConfig(): Promise<FileOperationResult<VibeTaskManagerConfig>> { this.config = null; return await this.loadConfig(); } /** * Check if configuration is loaded and valid */ isConfigValid(): boolean { if (!this.config) { return false; } const llmValidation = this.validateLLMMappings(); const mcpValidation = this.validateMCPRegistration(); return llmValidation.valid && mcpValidation.valid; } /** * Get performance configuration */ getPerformanceConfig(): PerformanceConfig { return { ...this.performanceConfig }; } /** * Update performance configuration */ updatePerformanceConfig(updates: Partial<PerformanceConfig>): void { this.performanceConfig = { ...this.performanceConfig, ...updates }; logger.debug({ updates }, 'Performance configuration updated'); } /** * Clear configuration cache */ clearCache(): void { this.configCache.clear(); this.cacheHits = 0; this.cacheRequests = 0; logger.debug('Configuration cache and statistics cleared'); } /** * Reset cache statistics without clearing cache */ resetCacheStats(): void { this.cacheHits = 0; this.cacheRequests = 0; logger.debug('Cache statistics reset'); } /** * Get cache statistics */ getCacheStats(): { size: number; entries: string[]; hitRate: number; totalRequests: number; totalHits: number; } { 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, // Round to 2 decimal places totalRequests: this.cacheRequests, totalHits: this.cacheHits }; } /** * Warm up configuration cache */ async warmupCache(): Promise<void> { if (!this.performanceConfig.enableConfigCache) { return; } const startTime = performance.now(); await this.loadConfig(); // Pre-load frequently accessed configurations 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'); } } /** * Convenience function to get configured instance */ export async function getVibeTaskManagerConfig(): Promise<VibeTaskManagerConfig | null> { 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(); } /** * Convenience function to get LLM model for operation */ export async function getLLMModelForOperation(operation: string): Promise<string> { const loader = ConfigLoader.getInstance(); if (!loader.getConfig()) { await loader.loadConfig(); } return loader.getLLMModel(operation); } /** * Get the base output directory following the established Vibe Coder MCP convention */ export function getBaseOutputDir(): string { return process.env.VIBE_CODER_OUTPUT_DIR ? path.resolve(process.env.VIBE_CODER_OUTPUT_DIR) : path.join(getProjectRoot(), 'VibeCoderOutput'); } /** * Get the Vibe Task Manager specific output directory */ export function getVibeTaskManagerOutputDir(): string { const baseOutputDir = getBaseOutputDir(); return path.join(baseOutputDir, 'vibe-task-manager'); } /** * Runtime validation result for security configuration */ interface SecurityConfigValidationResult { readonly isValid: boolean; readonly errors: readonly string[]; readonly warnings: readonly string[]; readonly extractedConfig: Partial<VibeTaskManagerSecurityConfig>; } /** * Validates extracted security configuration with strict type checking */ function validateExtractedSecurityConfig( extractedConfig: Partial<VibeTaskManagerSecurityConfig> ): SecurityConfigValidationResult { const errors: string[] = []; const warnings: string[] = []; // Validate allowedReadDirectory if (!extractedConfig.allowedReadDirectory) { errors.push('allowedReadDirectory is required'); } else if (typeof extractedConfig.allowedReadDirectory !== 'string') { errors.push('allowedReadDirectory must be a string'); } // Validate allowedWriteDirectory if (!extractedConfig.allowedWriteDirectory) { errors.push('allowedWriteDirectory is required'); } else if (typeof extractedConfig.allowedWriteDirectory !== 'string') { errors.push('allowedWriteDirectory must be a string'); } // Validate securityMode 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 as readonly string[], warnings: warnings as readonly string[], extractedConfig }; } /** * Resolves the unified project root directory following the same priority chain as UnifiedSecurityConfig * but without requiring the security config to be initialized (for use in config-loader). * @param config Optional OpenRouter configuration object * @returns The resolved project root directory */ function resolveUnifiedProjectRootForConfig(config?: OpenRouterConfig): string { try { // Priority 1: VIBE_PROJECT_ROOT environment variable const unifiedProjectRoot = process.env.VIBE_PROJECT_ROOT; if (unifiedProjectRoot?.trim()) { logger.debug({ unifiedProjectRoot, priorityUsed: 'env-var' }, 'Config-loader using VIBE_PROJECT_ROOT environment variable'); return unifiedProjectRoot.trim(); } // Priority 2: MCP client config (if provided) if (config?.env?.VIBE_PROJECT_ROOT) { const mcpProjectRoot = config.env.VIBE_PROJECT_ROOT; logger.debug({ mcpProjectRoot, priorityUsed: 'mcp-config' }, 'Config-loader using VIBE_PROJECT_ROOT from MCP client config'); return mcpProjectRoot; } // Priority 3: Legacy environment variables const legacyTaskManagerDir = process.env.VIBE_TASK_MANAGER_READ_DIR; if (legacyTaskManagerDir?.trim()) { logger.debug({ legacyTaskManagerDir, priorityUsed: 'legacy-task-manager' }, 'Config-loader using legacy VIBE_TASK_MANAGER_READ_DIR'); return legacyTaskManagerDir.trim(); } const legacyCodeMapDir = process.env.CODE_MAP_ALLOWED_DIR; if (legacyCodeMapDir?.trim()) { logger.debug({ legacyCodeMapDir, priorityUsed: 'legacy-code-map' }, 'Config-loader using legacy CODE_MAP_ALLOWED_DIR'); return legacyCodeMapDir.trim(); } // Priority 4: Fallback to current working directory const fallbackDir = process.cwd(); logger.debug({ fallbackDir, priorityUsed: 'cwd-fallback' }, 'Config-loader using process.cwd() as fallback'); return fallbackDir; } catch (error) { logger.error({ err: error }, 'Error resolving unified project root in config-loader, using process.cwd()'); return process.cwd(); } } /** * Extracts and validates the Vibe Task Manager security configuration from the MCP client config. * This follows the same pattern as the Code Map Generator's extractCodeMapConfig function. * @param config The OpenRouter configuration object from MCP client * @returns The validated Vibe Task Manager security configuration * @throws Error if the configuration is invalid */ export function extractVibeTaskManagerSecurityConfig(config?: OpenRouterConfig): VibeTaskManagerSecurityConfig { // Create a base security configuration object let securityConfig: Partial<VibeTaskManagerSecurityConfig> = {}; // Enhanced extraction logic to handle actual MCP client config structure if (config) { // First try to extract from config.env (MCP client environment variables) 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') as 'strict' | 'permissive' }; } // Fallback: Try to extract from tools['vibe-task-manager'] (legacy support) if (!securityConfig.allowedReadDirectory || !securityConfig.allowedWriteDirectory) { const toolConfig = config.tools?.['vibe-task-manager'] as Partial<VibeTaskManagerSecurityConfig> | undefined; const configSection = config.config?.['vibe-task-manager'] as Partial<VibeTaskManagerSecurityConfig> | undefined; if (toolConfig || configSection) { logger.debug({ toolConfig, configSection }, 'Using legacy tool config extraction as fallback'); securityConfig = { ...configSection, ...toolConfig, ...securityConfig // Preserve any values from env extraction }; } } } logger.debug({ extractedSecurityConfig: securityConfig, hasConfig: Boolean(config), hasEnv: Boolean(config?.env), configKeys: config ? Object.keys(config) : [] }, 'Extracted vibe-task-manager security config'); // SECURITY CHECK: Log working directory safety before path resolution logWorkingDirectorySafety(); // Apply environment variable fallbacks with UNIFIED path resolution const allowedReadDirectory: string = securityConfig.allowedReadDirectory || process.env.VIBE_TASK_MANAGER_READ_DIR || resolveUnifiedProjectRootForConfig(config); // UNIFIED FIX: Use unified project root const allowedWriteDirectory: string = securityConfig.allowedWriteDirectory || process.env.VIBE_CODER_OUTPUT_DIR || path.join(resolveUnifiedProjectRootForConfig(config), 'VibeCoderOutput'); // UNIFIED FIX: Use unified project root const securityMode: 'strict' | 'permissive' = (securityConfig.securityMode || process.env.VIBE_TASK_MANAGER_SECURITY_MODE || 'strict') as 'strict' | 'permissive'; // Create final configuration for validation const finalConfig: VibeTaskManagerSecurityConfig = { allowedReadDirectory, allowedWriteDirectory, securityMode }; // Runtime validation 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); } // Log warnings if any if (validation.warnings.length > 0) { logger.warn({ warnings: validation.warnings }, 'Security configuration has warnings'); } // Resolve paths to absolute paths with proper error handling let resolvedReadDir: string; let resolvedWriteDir: string; 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 }; } /** * Create AgentOrchestrator configuration from centralized config */ export async function getOrchestratorConfig(): Promise<unknown> { 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; } // Map centralized config to OrchestratorConfig const orchestratorConfig = { heartbeatInterval: (agentSettings.healthCheckInterval || 30) * 1000, // Convert seconds to ms taskTimeout: timeouts?.taskExecution || 300000, // 5 minutes default maxRetries: 3, // Default, could be made configurable loadBalancingStrategy: mapCoordinationStrategy(agentSettings.coordinationStrategy), enableHealthChecks: true, conflictResolutionStrategy: 'queue' as const, // Could be made configurable heartbeatTimeoutMultiplier: 3, // Could be made configurable enableAdaptiveTimeouts: true, // Could be made configurable maxHeartbeatMisses: 5 // Could be made configurable }; 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; } } /** * Map coordination strategy from centralized config to orchestrator strategy */ function mapCoordinationStrategy(strategy?: string): 'round_robin' | 'capability_based' | 'performance_based' { 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'; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freshtechbro/vibe-coder-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server