flow-nexus
Version:
🚀 AI-Powered Swarm Intelligence Platform - Gamified MCP Development with 70+ Tools
557 lines (476 loc) • 14.2 kB
JavaScript
/**
* Workflow Execution Engine
* Bridges workflow definitions with actual sandbox execution
*/
import supabaseClient from './supabase-client.js';
export class WorkflowExecutor {
constructor() {
this.activeExecutions = new Map();
}
/**
* Execute a workflow with its steps
*/
async executeWorkflow(workflowId, inputData = {}, userId = null) {
try {
// Get workflow definition
const { data: workflow, error: workflowError } = await supabaseClient.supabase
.from('workflows')
.select('*')
.eq('id', workflowId)
.single();
if (workflowError || !workflow) {
throw new Error(`Workflow not found: ${workflowId}`);
}
// Create execution record
const executionId = `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const { data: execution, error: execError } = await supabaseClient.supabase
.from('workflow_executions')
.insert({
id: executionId,
workflow_id: workflowId,
status: 'running',
started_at: new Date().toISOString(),
input_data: inputData,
user_id: userId
})
.select()
.single();
if (execError) {
throw new Error(`Failed to create execution: ${execError.message}`);
}
// Store active execution
this.activeExecutions.set(executionId, {
workflow,
execution,
currentStep: 0,
context: { ...inputData },
sandboxId: null
});
// Execute steps
const result = await this.executeSteps(executionId);
// Update execution status
await supabaseClient.supabase
.from('workflow_executions')
.update({
status: result.success ? 'completed' : 'failed',
completed_at: new Date().toISOString(),
output_data: result.output,
error: result.error
})
.eq('id', executionId);
// Clean up
this.activeExecutions.delete(executionId);
return {
success: result.success,
execution_id: executionId,
workflow_id: workflowId,
output: result.output,
error: result.error,
steps_executed: result.stepsExecuted
};
} catch (error) {
console.error('Workflow execution error:', error);
return {
success: false,
error: error.message
};
}
}
/**
* Execute workflow steps sequentially
*/
async executeSteps(executionId) {
const execution = this.activeExecutions.get(executionId);
if (!execution) {
return { success: false, error: 'Execution not found' };
}
const { workflow } = execution;
const steps = workflow.definition?.steps || [];
const results = [];
let lastOutput = execution.context;
try {
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
execution.currentStep = i;
// Record step start
await this.recordStepEvent(executionId, step.id, 'started', {
step_index: i,
step_name: step.name
});
// Execute step based on action type
const stepResult = await this.executeStep(step, lastOutput, execution);
results.push({
step_id: step.id,
name: step.name,
success: stepResult.success,
output: stepResult.output,
error: stepResult.error
});
if (!stepResult.success) {
// Record failure and stop execution
await this.recordStepEvent(executionId, step.id, 'failed', {
error: stepResult.error
});
return {
success: false,
error: `Step ${step.name} failed: ${stepResult.error}`,
stepsExecuted: results,
output: lastOutput
};
}
// Record success
await this.recordStepEvent(executionId, step.id, 'completed', {
output: stepResult.output
});
// Pass output to next step
lastOutput = { ...lastOutput, ...stepResult.output };
}
return {
success: true,
stepsExecuted: results,
output: lastOutput
};
} catch (error) {
return {
success: false,
error: error.message,
stepsExecuted: results,
output: lastOutput
};
}
}
/**
* Execute a single workflow step
*/
async executeStep(step, context, execution) {
try {
const { action, parameters = {} } = step;
// Resolve parameters with context
const resolvedParams = this.resolveParameters(parameters, context);
switch (action) {
case 'sandbox_execute':
return await this.executeSandboxCode(resolvedParams, execution);
case 'api_call':
return await this.executeApiCall(resolvedParams);
case 'database_query':
return await this.executeDatabaseQuery(resolvedParams);
case 'condition':
return await this.evaluateCondition(resolvedParams, context);
case 'transform':
return await this.transformData(resolvedParams, context);
case 'parallel':
return await this.executeParallelSteps(resolvedParams, context, execution);
case 'wait':
await new Promise(resolve => setTimeout(resolve, resolvedParams.duration || 1000));
return { success: true, output: {} };
default:
// For unknown actions, just pass through
return {
success: true,
output: {
action,
parameters: resolvedParams,
message: `Simulated ${action}`
}
};
}
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Execute code in a sandbox
*/
async executeSandboxCode(params, execution) {
try {
// Create or reuse sandbox
if (!execution.sandboxId) {
// Call Edge Function to create sandbox
const { data: createResult, error: createError } = await supabaseClient.supabase.functions
.invoke('mcp-tools-e2b', {
body: {
action: 'sandbox_create',
params: {
template: params.template || 'base',
name: `workflow-${execution.execution.id}`,
metadata: {
workflow_id: execution.workflow.id,
execution_id: execution.execution.id
}
}
}
});
if (createError || !createResult?.sandbox_id) {
throw new Error(`Failed to create sandbox: ${createError?.message || 'Unknown error'}`);
}
execution.sandboxId = createResult.sandbox_id;
}
// Execute code in sandbox
const { data: execResult, error: execError } = await supabaseClient.supabase.functions
.invoke('mcp-tools-e2b', {
body: {
action: 'sandbox_execute',
params: {
sandbox_id: execution.sandboxId,
code: params.code,
language: params.language || 'javascript'
}
}
});
if (execError) {
throw new Error(`Sandbox execution failed: ${execError.message}`);
}
return {
success: true,
output: {
sandbox_id: execution.sandboxId,
execution_result: execResult.output,
exit_code: execResult.exit_code
}
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Execute an API call
*/
async executeApiCall(params) {
try {
const { url, method = 'GET', headers = {}, body } = params;
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
...headers
},
body: body ? JSON.stringify(body) : undefined
});
const data = await response.json();
return {
success: response.ok,
output: {
status: response.status,
data
}
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Execute a database query
*/
async executeDatabaseQuery(params) {
try {
const { table, operation = 'select', filters = {}, data } = params;
let query = supabaseClient.supabase.from(table);
switch (operation) {
case 'select':
query = query.select(params.columns || '*');
break;
case 'insert':
query = query.insert(data);
break;
case 'update':
query = query.update(data);
break;
case 'delete':
query = query.delete();
break;
}
// Apply filters
Object.entries(filters).forEach(([key, value]) => {
query = query.eq(key, value);
});
const { data: result, error } = await query;
if (error) {
throw error;
}
return {
success: true,
output: { result }
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Evaluate a condition
*/
async evaluateCondition(params, context) {
try {
const { condition, then: thenAction, else: elseAction } = params;
// Simple condition evaluation (can be expanded)
const result = this.evaluateExpression(condition, context);
const actionToExecute = result ? thenAction : elseAction;
if (actionToExecute) {
return await this.executeStep(actionToExecute, context, {});
}
return {
success: true,
output: { condition_result: result }
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Transform data
*/
async transformData(params, context) {
try {
const { mapping } = params;
const output = {};
for (const [key, value] of Object.entries(mapping)) {
output[key] = this.resolveValue(value, context);
}
return {
success: true,
output
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Execute steps in parallel
*/
async executeParallelSteps(params, context, execution) {
try {
const { steps = [] } = params;
const promises = steps.map(step =>
this.executeStep(step, context, execution)
);
const results = await Promise.all(promises);
const allSuccess = results.every(r => r.success);
const outputs = results.map((r, i) => ({
step: steps[i].name || `Step ${i + 1}`,
...r
}));
return {
success: allSuccess,
output: { parallel_results: outputs }
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
/**
* Record workflow step events
*/
async recordStepEvent(executionId, stepId, event, metadata = {}) {
try {
await supabaseClient.supabase
.from('workflow_audit_log')
.insert({
workflow_execution_id: executionId,
event_type: `step_${event}`,
event_data: {
step_id: stepId,
...metadata
},
created_at: new Date().toISOString()
});
} catch (error) {
console.error('Failed to record step event:', error);
}
}
/**
* Resolve parameters with context values
*/
resolveParameters(params, context) {
const resolved = {};
for (const [key, value] of Object.entries(params)) {
resolved[key] = this.resolveValue(value, context);
}
return resolved;
}
/**
* Resolve a single value (supports template variables)
*/
resolveValue(value, context) {
if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) {
const path = value.slice(2, -2).trim();
return this.getValueByPath(context, path);
}
if (typeof value === 'object' && value !== null) {
if (Array.isArray(value)) {
return value.map(v => this.resolveValue(v, context));
}
const resolved = {};
for (const [k, v] of Object.entries(value)) {
resolved[k] = this.resolveValue(v, context);
}
return resolved;
}
return value;
}
/**
* Get value from object by path
*/
getValueByPath(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj);
}
/**
* Evaluate a simple expression
*/
evaluateExpression(expression, context) {
// Simple equality check (can be expanded)
if (typeof expression === 'object' && expression.operator) {
const { operator, left, right } = expression;
const leftValue = this.resolveValue(left, context);
const rightValue = this.resolveValue(right, context);
switch (operator) {
case '==': return leftValue == rightValue;
case '!=': return leftValue != rightValue;
case '>': return leftValue > rightValue;
case '<': return leftValue < rightValue;
case '>=': return leftValue >= rightValue;
case '<=': return leftValue <= rightValue;
default: return false;
}
}
return Boolean(this.resolveValue(expression, context));
}
/**
* Clean up sandbox when workflow completes
*/
async cleanupSandbox(sandboxId) {
if (!sandboxId) return;
try {
await supabaseClient.supabase.functions
.invoke('mcp-tools-e2b', {
body: {
action: 'sandbox_stop',
params: { sandbox_id: sandboxId }
}
});
} catch (error) {
console.error('Failed to stop sandbox:', error);
}
}
}
// Export singleton instance
export const workflowExecutor = new WorkflowExecutor();