@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
218 lines • 9.17 kB
JavaScript
import { EventEmitter } from 'events';
import { ActivityExecutor } from './activity-executor.js';
import { VariableResolver } from './variable-resolver.js';
// import { AgentOrchestrator } from '../agent-orchestration/orchestrator.js';
export class ProcessEngine extends EventEmitter {
executions = new Map();
activityExecutor;
variableResolver;
store;
// private agentOrchestrator: AgentOrchestrator;
constructor(store, agentOrchestrator /*AgentOrchestrator*/) {
super();
this.store = store;
// this.agentOrchestrator = agentOrchestrator;
this.activityExecutor = new ActivityExecutor(this, agentOrchestrator);
this.variableResolver = new VariableResolver();
}
async executeProcess(process, triggerId = 'manual', initialVariables = {}) {
const execution = {
id: this.generateExecutionId(),
processId: process.id,
processVersion: process.version,
status: 'pending',
triggeredBy: triggerId,
startedAt: new Date().toISOString(),
variables: { ...process.variables, ...initialVariables },
activityResults: [],
logs: []
};
this.executions.set(execution.id, execution);
await this.store.saveExecution(execution);
this.log(execution.id, 'info', `Starting process: ${process.name}`);
this.emit('execution:started', execution);
try {
execution.status = 'running';
await this.store.saveExecution(execution);
// Execute activities sequentially
for (const activity of process.activities) {
if (execution.status !== 'running') {
break; // Process was paused or cancelled
}
await this.executeActivity(execution, activity);
}
if (execution.status === 'running') {
execution.status = 'completed';
execution.completedAt = new Date().toISOString();
execution.duration = Date.now() - new Date(execution.startedAt).getTime();
// Execute success handlers if any
if (process.onSuccess) {
for (const activity of process.onSuccess) {
await this.executeActivity(execution, activity);
}
}
}
}
catch (error) {
execution.status = 'failed';
execution.error = this.createProcessError(error);
execution.completedAt = new Date().toISOString();
execution.duration = Date.now() - new Date(execution.startedAt).getTime();
this.log(execution.id, 'error', `Process failed: ${execution.error.message}`);
// Execute failure handlers if any
if (process.onFailure) {
for (const activity of process.onFailure) {
try {
await this.executeActivity(execution, activity);
}
catch (handlerError) {
this.log(execution.id, 'error', `Failure handler error: ${handlerError}`);
}
}
}
}
await this.store.saveExecution(execution);
this.emit('execution:completed', execution);
return execution;
}
async executeActivity(execution, activity) {
// Check condition if specified
if (activity.condition) {
const shouldRun = await this.variableResolver.evaluateCondition(activity.condition, execution.variables);
if (!shouldRun) {
const result = {
activityId: activity.id,
status: 'skipped',
startedAt: new Date().toISOString(),
completedAt: new Date().toISOString(),
duration: 0
};
execution.activityResults.push(result);
this.log(execution.id, 'info', `Skipped activity: ${activity.name} (condition not met)`);
return result;
}
}
this.log(execution.id, 'info', `Executing activity: ${activity.name}`, activity.id);
this.emit('activity:started', { execution, activity });
const startTime = Date.now();
const result = {
activityId: activity.id,
status: 'success',
startedAt: new Date().toISOString(),
completedAt: '',
duration: 0
};
try {
// Resolve input variables
const resolvedInputs = await this.variableResolver.resolveVariables(activity.inputs || {}, execution.variables);
// Execute based on activity type
const outputs = await this.activityExecutor.execute(activity, resolvedInputs, execution);
result.outputs = outputs;
result.status = 'success';
// Store outputs in execution variables
if (activity.outputs && outputs) {
for (const outputVar of activity.outputs) {
execution.variables[outputVar] = outputs[outputVar];
}
}
}
catch (error) {
result.status = 'failed';
result.error = error instanceof Error ? error.message : String(error);
// Handle error based on error handler configuration
if (activity.errorHandler) {
await this.handleActivityError(execution, activity, error);
}
else {
throw error; // Propagate to process level
}
}
finally {
result.completedAt = new Date().toISOString();
result.duration = Date.now() - startTime;
execution.activityResults.push(result);
this.emit('activity:completed', { execution, activity, result });
}
return result;
}
async handleActivityError(execution, activity, error) {
const handler = activity.errorHandler;
switch (handler.type) {
case 'retry':
// Implement retry logic
if (handler.retryPolicy) {
// TODO: Implement retry with backoff
this.log(execution.id, 'warn', `Retrying activity: ${activity.name}`);
}
break;
case 'skip':
this.log(execution.id, 'warn', `Skipping failed activity: ${activity.name}`);
break;
case 'alternate':
if (handler.alternateActivities) {
for (const altActivity of handler.alternateActivities) {
await this.executeActivity(execution, altActivity);
}
}
break;
case 'fail':
default:
throw error;
}
}
async pauseExecution(executionId) {
const execution = this.executions.get(executionId);
if (execution && execution.status === 'running') {
execution.status = 'paused';
await this.store.saveExecution(execution);
this.emit('execution:paused', execution);
}
}
async resumeExecution(executionId) {
const execution = this.executions.get(executionId);
if (execution && execution.status === 'paused') {
execution.status = 'running';
await this.store.saveExecution(execution);
this.emit('execution:resumed', execution);
// TODO: Continue execution from where it left off
}
}
async cancelExecution(executionId) {
const execution = this.executions.get(executionId);
if (execution && ['running', 'paused', 'waiting'].includes(execution.status)) {
execution.status = 'cancelled';
execution.completedAt = new Date().toISOString();
execution.duration = Date.now() - new Date(execution.startedAt).getTime();
await this.store.saveExecution(execution);
this.emit('execution:cancelled', execution);
}
}
getExecution(executionId) {
return this.executions.get(executionId);
}
log(executionId, level, message, activityId) {
const execution = this.executions.get(executionId);
if (execution) {
const entry = {
timestamp: new Date().toISOString(),
level,
message,
activityId
};
execution.logs.push(entry);
this.emit('execution:log', { executionId, entry });
}
}
createProcessError(error) {
return {
message: error instanceof Error ? error.message : String(error),
code: error.code,
stack: error instanceof Error ? error.stack : undefined,
retryable: false // TODO: Determine based on error type
};
}
generateExecutionId() {
return `exec-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
}
}
//# sourceMappingURL=process-engine.js.map