UNPKG

sf-agent-framework

Version:

AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction

583 lines (501 loc) 16.4 kB
/** * Update-Safe Configuration Manager * * Purpose: Preserve user customizations through framework version updates * Ensures user settings, agent overrides, and preferences persist across upgrades * * @module UpdateSafeConfig * @version 1.0.0 * @date 2025-11-25 */ const fs = require('fs-extra'); const path = require('path'); const yaml = require('js-yaml'); class UpdateSafeConfig { constructor(rootDir = process.cwd()) { this.rootDir = rootDir; this.userConfigDir = path.join(rootDir, '.sf-agent'); this.frameworkConfigDir = path.join(rootDir, 'sf-core', 'config'); // User-specific paths (never overwritten) this.userConfigPath = path.join(this.userConfigDir, 'user-config.json'); this.userPreferencesPath = path.join(this.userConfigDir, 'preferences.json'); this.agentOverridesDir = path.join(this.userConfigDir, 'agent-overrides'); this.workflowOverridesDir = path.join(this.userConfigDir, 'workflow-overrides'); } /** * Initialize user configuration directory structure */ async initialize() { try { // Create .sf-agent directory structure await fs.ensureDir(this.userConfigDir); await fs.ensureDir(this.agentOverridesDir); await fs.ensureDir(this.workflowOverridesDir); // Create default user config if doesn't exist if (!(await fs.pathExists(this.userConfigPath))) { await this.createDefaultUserConfig(); } // Create default preferences if doesn't exist if (!(await fs.pathExists(this.userPreferencesPath))) { await this.createDefaultPreferences(); } console.log('✓ Update-safe configuration initialized'); return true; } catch (error) { console.error('✗ Failed to initialize update-safe config:', error.message); return false; } } /** * Create default user configuration */ async createDefaultUserConfig() { const defaultConfig = { version: '1.0.0', created: new Date().toISOString(), lastUpdated: new Date().toISOString(), customizations: { agents: {}, workflows: {}, templates: {}, phases: {}, }, overrides: { agentDirectory: this.agentOverridesDir, workflowDirectory: this.workflowOverridesDir, enabled: true, }, metadata: { frameworkVersion: '4.5.0', compatibilityVersion: '4.5.0', }, }; await fs.writeJson(this.userConfigPath, defaultConfig, { spaces: 2 }); console.log('✓ Created default user configuration'); } /** * Create default user preferences */ async createDefaultPreferences() { const defaultPreferences = { version: '1.0.0', display: { verboseOutput: false, showBenchmarks: true, coloredOutput: true, showProgress: true, }, workflow: { autoSelectComplexity: true, confirmBeforeExecution: false, saveWorkflowHistory: true, defaultTrack: 'balanced', // quick | balanced | enterprise }, agents: { defaultCommunicationStyle: 'balanced', // concise | balanced | detailed defaultExpertiseLevel: 'senior', // junior | mid | senior autoReflection: true, cachePersonas: true, }, models: { preferredProvider: 'auto', // auto | anthropic | openai | google preferredModelCoding: 'claude_opus_4_5', preferredModelArchitecture: 'claude_opus_4_5', preferredModelDaily: 'gpt_5_1_instant', enablePromptCaching: true, costOptimization: true, }, context: { enableDocumentSharding: true, maxContextUsage: 0.8, // 80% of available context priorityLoadingEnabled: true, }, notifications: { showCompletionNotifications: true, showErrorNotifications: true, soundEnabled: false, }, }; await fs.writeJson(this.userPreferencesPath, defaultPreferences, { spaces: 2 }); console.log('✓ Created default user preferences'); } /** * Load user configuration */ async loadUserConfig() { try { if (await fs.pathExists(this.userConfigPath)) { return await fs.readJson(this.userConfigPath); } return null; } catch (error) { console.error('Error loading user config:', error.message); return null; } } /** * Load user preferences */ async loadPreferences() { try { if (await fs.pathExists(this.userPreferencesPath)) { return await fs.readJson(this.userPreferencesPath); } return null; } catch (error) { console.error('Error loading preferences:', error.message); return null; } } /** * Save user configuration */ async saveUserConfig(config) { try { config.lastUpdated = new Date().toISOString(); await fs.writeJson(this.userConfigPath, config, { spaces: 2 }); return true; } catch (error) { console.error('Error saving user config:', error.message); return false; } } /** * Save user preferences */ async savePreferences(preferences) { try { await fs.writeJson(this.userPreferencesPath, preferences, { spaces: 2 }); return true; } catch (error) { console.error('Error saving preferences:', error.message); return false; } } /** * Load framework configuration */ async loadFrameworkConfig(configName = 'core-config.yaml') { try { const configPath = path.join(this.frameworkConfigDir, configName); if (await fs.pathExists(configPath)) { const content = await fs.readFile(configPath, 'utf8'); return yaml.load(content); } return null; } catch (error) { console.error('Error loading framework config:', error.message); return null; } } /** * Merge user config with framework config * User settings always take precedence */ async mergeConfigs() { try { const userConfig = await this.loadUserConfig(); const frameworkConfig = await this.loadFrameworkConfig(); const preferences = await this.loadPreferences(); if (!frameworkConfig) { throw new Error('Framework configuration not found'); } // Start with framework config as base const merged = { ...frameworkConfig }; // Apply user customizations if they exist if (userConfig && userConfig.customizations) { // Merge agent customizations if (userConfig.customizations.agents) { merged.agents = this.mergeAgentConfigs( frameworkConfig.agents, userConfig.customizations.agents ); } // Merge workflow customizations if (userConfig.customizations.workflows) { merged.workflows = { ...frameworkConfig.workflows, ...userConfig.customizations.workflows, }; } // Merge phase customizations if (userConfig.customizations.phases) { merged.phases = this.deepMerge(frameworkConfig.phases, userConfig.customizations.phases); } } // Apply preferences if (preferences) { merged.preferences = preferences; } // Load agent overrides merged.agentOverrides = await this.loadAgentOverrides(); return merged; } catch (error) { console.error('Error merging configs:', error.message); throw error; } } /** * Merge agent configurations */ mergeAgentConfigs(frameworkAgents, userAgents) { const merged = { ...frameworkAgents }; for (const [agentId, userSettings] of Object.entries(userAgents)) { if (merged[agentId]) { merged[agentId] = { ...merged[agentId], ...userSettings, customized: true, customizedAt: new Date().toISOString(), }; } } return merged; } /** * Load agent override files */ async loadAgentOverrides() { try { const overrides = {}; if (await fs.pathExists(this.agentOverridesDir)) { const files = await fs.readdir(this.agentOverridesDir); for (const file of files) { if (file.endsWith('.md') || file.endsWith('.yaml')) { const agentId = path.basename(file, path.extname(file)); const filePath = path.join(this.agentOverridesDir, file); const content = await fs.readFile(filePath, 'utf8'); overrides[agentId] = { source: 'user-override', path: filePath, content: content, lastModified: (await fs.stat(filePath)).mtime, }; } } } return overrides; } catch (error) { console.error('Error loading agent overrides:', error.message); return {}; } } /** * Deep merge objects */ deepMerge(target, source) { const output = { ...target }; if (this.isObject(target) && this.isObject(source)) { Object.keys(source).forEach((key) => { if (this.isObject(source[key])) { if (!(key in target)) { output[key] = source[key]; } else { output[key] = this.deepMerge(target[key], source[key]); } } else { output[key] = source[key]; } }); } return output; } /** * Check if value is an object */ isObject(item) { return item && typeof item === 'object' && !Array.isArray(item); } /** * Backup user configuration before upgrade */ async backupUserConfig() { try { const timestamp = new Date().toISOString().replace(/:/g, '-'); const backupDir = path.join(this.userConfigDir, 'backups', timestamp); await fs.ensureDir(backupDir); // Backup user config if (await fs.pathExists(this.userConfigPath)) { await fs.copy(this.userConfigPath, path.join(backupDir, 'user-config.json')); } // Backup preferences if (await fs.pathExists(this.userPreferencesPath)) { await fs.copy(this.userPreferencesPath, path.join(backupDir, 'preferences.json')); } // Backup agent overrides if (await fs.pathExists(this.agentOverridesDir)) { await fs.copy(this.agentOverridesDir, path.join(backupDir, 'agent-overrides')); } console.log(`✓ User configuration backed up to: ${backupDir}`); return backupDir; } catch (error) { console.error('Error backing up user config:', error.message); throw error; } } /** * Preserve user settings during framework upgrade */ async preserveUserSettings(newFrameworkVersion) { try { console.log('\n🔄 Preserving user settings during upgrade...'); // 1. Backup current settings const backupDir = await this.backupUserConfig(); console.log(' ✓ User settings backed up'); // 2. Load current user settings const currentUserConfig = await this.loadUserConfig(); const currentPreferences = await this.loadPreferences(); // 3. Perform framework upgrade (this would be called externally) console.log(' ⏳ Framework upgrade in progress...'); // 4. Update metadata if (currentUserConfig) { currentUserConfig.metadata.previousFrameworkVersion = currentUserConfig.metadata.frameworkVersion; currentUserConfig.metadata.frameworkVersion = newFrameworkVersion; currentUserConfig.metadata.upgradedAt = new Date().toISOString(); await this.saveUserConfig(currentUserConfig); } console.log(' ✓ User settings preserved'); console.log(` ✓ Backup available at: ${backupDir}`); return { success: true, backupLocation: backupDir, version: newFrameworkVersion, }; } catch (error) { console.error('✗ Error preserving user settings:', error.message); throw error; } } /** * Create agent override file */ async createAgentOverride(agentId, content) { try { const filePath = path.join(this.agentOverridesDir, `${agentId}.md`); await fs.writeFile(filePath, content, 'utf8'); // Update user config const userConfig = await this.loadUserConfig(); if (!userConfig.customizations.agents[agentId]) { userConfig.customizations.agents[agentId] = {}; } userConfig.customizations.agents[agentId].hasOverride = true; userConfig.customizations.agents[agentId].overridePath = filePath; await this.saveUserConfig(userConfig); console.log(`✓ Created agent override: ${agentId}`); return true; } catch (error) { console.error('Error creating agent override:', error.message); return false; } } /** * Update preference value */ async updatePreference(category, key, value) { try { const preferences = await this.loadPreferences(); if (preferences[category]) { preferences[category][key] = value; await this.savePreferences(preferences); console.log(`✓ Updated preference: ${category}.${key} = ${value}`); return true; } else { console.error(`✗ Invalid preference category: ${category}`); return false; } } catch (error) { console.error('Error updating preference:', error.message); return false; } } /** * Get effective configuration (merged) */ async getEffectiveConfig() { return await this.mergeConfigs(); } /** * Validate user configuration */ async validateUserConfig() { try { const userConfig = await this.loadUserConfig(); const preferences = await this.loadPreferences(); const validation = { valid: true, issues: [], warnings: [], }; // Check version compatibility if (userConfig && userConfig.metadata.frameworkVersion) { const frameworkVersion = userConfig.metadata.frameworkVersion; const currentVersion = '4.5.0'; if (frameworkVersion !== currentVersion) { validation.warnings.push( `Configuration was created for framework v${frameworkVersion}, ` + `current version is v${currentVersion}` ); } } // Validate agent overrides exist if (userConfig && userConfig.customizations.agents) { for (const [agentId, settings] of Object.entries(userConfig.customizations.agents)) { if (settings.hasOverride) { const overridePath = path.join(this.agentOverridesDir, `${agentId}.md`); if (!(await fs.pathExists(overridePath))) { validation.issues.push(`Agent override file missing: ${agentId}.md`); validation.valid = false; } } } } // Validate preferences structure if (preferences) { const requiredCategories = ['display', 'workflow', 'agents', 'models', 'context']; for (const category of requiredCategories) { if (!preferences[category]) { validation.warnings.push(`Missing preference category: ${category}`); } } } return validation; } catch (error) { return { valid: false, issues: [error.message], warnings: [], }; } } /** * Reset user configuration to defaults */ async resetToDefaults(options = { backupFirst: true }) { try { console.log('\n⚠️ Resetting user configuration to defaults...'); // Backup if requested if (options.backupFirst) { await this.backupUserConfig(); console.log(' ✓ Current configuration backed up'); } // Remove current config files if (await fs.pathExists(this.userConfigPath)) { await fs.remove(this.userConfigPath); } if (await fs.pathExists(this.userPreferencesPath)) { await fs.remove(this.userPreferencesPath); } // Recreate defaults await this.createDefaultUserConfig(); await this.createDefaultPreferences(); console.log(' ✓ Configuration reset to defaults'); console.log('\n💡 Tip: Agent overrides were NOT removed'); return true; } catch (error) { console.error('✗ Error resetting configuration:', error.message); return false; } } } module.exports = UpdateSafeConfig;