UNPKG

sf-agent-framework

Version:

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

407 lines (348 loc) • 10.9 kB
/** * Interactive Workflow Planner * Provides conditional workflow branching with user choices and validation gates */ const fs = require('fs').promises; const path = require('path'); const yaml = require('js-yaml'); const inquirer = require('inquirer'); class InteractiveWorkflowPlanner { constructor(rootDir = process.cwd()) { this.rootDir = rootDir; this.workflowsDir = path.join(rootDir, 'sf-core', 'workflows'); this.currentWorkflow = null; this.workflowState = { currentStep: null, completedSteps: [], decisions: {}, artifacts: [], validationResults: {}, }; } /** * Load a workflow definition */ async loadWorkflow(workflowName) { const workflowPath = path.join(this.workflowsDir, `${workflowName}.yaml`); try { const content = await fs.readFile(workflowPath, 'utf8'); this.currentWorkflow = yaml.load(content); this.currentWorkflow.name = workflowName; return this.currentWorkflow; } catch (error) { throw new Error(`Failed to load workflow ${workflowName}: ${error.message}`); } } /** * Start interactive workflow execution */ async startInteractiveWorkflow(workflowName) { await this.loadWorkflow(workflowName); console.log( `\nšŸš€ Starting Interactive Workflow: ${this.currentWorkflow.title || workflowName}` ); console.log(`${this.currentWorkflow.description || ''}\n`); // Check if workflow has interactive mode if (this.currentWorkflow.interactive) { return await this.executeInteractiveFlow(); } else { return await this.executeLinearFlow(); } } /** * Execute interactive workflow with branching */ async executeInteractiveFlow() { const workflow = this.currentWorkflow; let currentPhase = workflow.phases ? workflow.phases[0] : null; while (currentPhase) { console.log(`\nšŸ“‹ Phase: ${currentPhase.name}`); console.log(`${currentPhase.description || ''}`); // Check for user choices if (currentPhase.user_choices) { const choice = await this.presentUserChoices(currentPhase); this.workflowState.decisions[currentPhase.name] = choice; // Execute based on choice if (currentPhase.branches && currentPhase.branches[choice]) { await this.executeBranch(currentPhase.branches[choice]); } } else { // Execute phase steps await this.executePhaseSteps(currentPhase); } // Validation gate if (currentPhase.validation_gate) { const passed = await this.executeValidationGate(currentPhase.validation_gate); if (!passed) { console.log('āš ļø Validation failed. Please address issues before continuing.'); const retry = await this.askRetry(); if (!retry) break; continue; } } // Move to next phase currentPhase = await this.determineNextPhase(currentPhase); } return this.generateWorkflowSummary(); } /** * Present user choices and get selection */ async presentUserChoices(phase) { const choices = phase.user_choices.map((choice) => { if (typeof choice === 'string') { return { name: choice, value: choice }; } return { name: choice.label, value: choice.value }; }); const { selection } = await inquirer.prompt([ { type: 'list', name: 'selection', message: phase.choice_prompt || 'Please select your preferred approach:', choices, }, ]); return selection; } /** * Execute a workflow branch */ async executeBranch(branch) { console.log(`\n→ Executing branch: ${branch.name || 'Custom Branch'}`); for (const step of branch.steps || []) { await this.executeStep(step); } // Record artifacts created in this branch if (branch.creates) { this.workflowState.artifacts.push(...branch.creates); } } /** * Execute phase steps */ async executePhaseSteps(phase) { for (const step of phase.steps || []) { await this.executeStep(step); } } /** * Execute a single workflow step */ async executeStep(step) { console.log(` āœ“ ${step.name || step}`); // Record step completion this.workflowState.completedSteps.push({ name: step.name || step, timestamp: new Date().toISOString(), agent: step.agent || 'default', }); // Handle step-specific actions if (step.action) { await this.executeAction(step.action); } // Create artifacts if specified if (step.creates) { this.workflowState.artifacts.push(step.creates); } } /** * Execute validation gate */ async executeValidationGate(gate) { console.log(`\nšŸ” Validation Gate: ${gate.name || 'Validation'}`); const validations = gate.validations || []; let allPassed = true; for (const validation of validations) { const result = await this.runValidation(validation); this.workflowState.validationResults[validation.name] = result; if (!result.passed) { console.log(` āŒ ${validation.name}: ${result.message}`); allPassed = false; } else { console.log(` āœ… ${validation.name}`); } } return allPassed; } /** * Run a single validation */ async runValidation(validation) { // Check different validation types if (validation.type === 'artifacts_exist') { const missing = validation.required.filter( (artifact) => !this.workflowState.artifacts.includes(artifact) ); return { passed: missing.length === 0, message: missing.length > 0 ? `Missing artifacts: ${missing.join(', ')}` : 'All artifacts present', }; } if (validation.type === 'steps_completed') { const completedNames = this.workflowState.completedSteps.map((s) => s.name); const missing = validation.required.filter((step) => !completedNames.includes(step)); return { passed: missing.length === 0, message: missing.length > 0 ? `Missing steps: ${missing.join(', ')}` : 'All steps completed', }; } // Custom validation if (validation.type === 'custom') { // Would call custom validation function return { passed: true, message: 'Custom validation passed' }; } return { passed: true, message: 'Validation passed' }; } /** * Determine next phase based on current state */ async determineNextPhase(currentPhase) { if (!this.currentWorkflow.phases) return null; const currentIndex = this.currentWorkflow.phases.findIndex((p) => p.name === currentPhase.name); // Check for conditional next phase if (currentPhase.next_conditions) { for (const condition of currentPhase.next_conditions) { if (this.evaluateCondition(condition)) { const nextPhase = this.currentWorkflow.phases.find((p) => p.name === condition.go_to); return nextPhase; } } } // Default to next phase in sequence if (currentIndex < this.currentWorkflow.phases.length - 1) { return this.currentWorkflow.phases[currentIndex + 1]; } return null; } /** * Evaluate a condition */ evaluateCondition(condition) { if (condition.if_choice_was) { const decision = this.workflowState.decisions[condition.phase]; return decision === condition.if_choice_was; } if (condition.if_artifacts_exist) { return condition.if_artifacts_exist.every((artifact) => this.workflowState.artifacts.includes(artifact) ); } return false; } /** * Execute a linear workflow without branching */ async executeLinearFlow() { const workflow = this.currentWorkflow; for (const phase of workflow.phases || []) { console.log(`\nšŸ“‹ Phase: ${phase.name}`); await this.executePhaseSteps(phase); } return this.generateWorkflowSummary(); } /** * Execute an action */ async executeAction(action) { // This would integrate with actual tools console.log(` → Action: ${action}`); } /** * Ask user if they want to retry */ async askRetry() { const { retry } = await inquirer.prompt([ { type: 'confirm', name: 'retry', message: 'Would you like to retry this phase?', default: true, }, ]); return retry; } /** * Generate workflow execution summary */ generateWorkflowSummary() { return { workflow: this.currentWorkflow.name, completed: true, steps_completed: this.workflowState.completedSteps.length, artifacts_created: this.workflowState.artifacts, decisions_made: this.workflowState.decisions, validation_results: this.workflowState.validationResults, timestamp: new Date().toISOString(), }; } /** * Create a new workflow definition */ async createWorkflow(config) { const workflow = { title: config.title, description: config.description, interactive: true, phases: [], }; // Add phases with branching for (const phase of config.phases || []) { const phaseConfig = { name: phase.name, description: phase.description, user_choices: phase.choices, branches: {}, }; // Add branches for each choice if (phase.choices) { for (const choice of phase.choices) { phaseConfig.branches[choice] = { name: `${choice} branch`, steps: phase.branches?.[choice]?.steps || [], creates: phase.branches?.[choice]?.creates || [], }; } } // Add validation gate if specified if (phase.validation) { phaseConfig.validation_gate = { name: phase.validation.name, validations: phase.validation.checks, }; } workflow.phases.push(phaseConfig); } // Save workflow const workflowPath = path.join(this.workflowsDir, `${config.name}.yaml`); await fs.writeFile(workflowPath, yaml.dump(workflow), 'utf8'); return workflow; } /** * List available workflows */ async listWorkflows() { try { const files = await fs.readdir(this.workflowsDir); const workflows = files.filter((f) => f.endsWith('.yaml')).map((f) => f.replace('.yaml', '')); return workflows; } catch (error) { return []; } } /** * Get workflow status */ getWorkflowStatus() { return { current_workflow: this.currentWorkflow?.name, current_step: this.workflowState.currentStep, completed_steps: this.workflowState.completedSteps.length, artifacts: this.workflowState.artifacts.length, decisions: Object.keys(this.workflowState.decisions).length, }; } } module.exports = InteractiveWorkflowPlanner;