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