veas
Version:
Veas CLI - Command-line interface for Veas platform
310 lines • 12.5 kB
JavaScript
import { createClient } from '@supabase/supabase-js';
import { logger } from '../utils/logger.js';
export class TaskExecutor {
realtimeService;
mcpClient;
supabase;
activeExecutions = new Map();
maxConcurrentTasks;
constructor(realtimeService, mcpClient, supabaseUrl, supabaseAnonKey, maxConcurrentTasks = 1) {
this.realtimeService = realtimeService;
this.mcpClient = mcpClient;
this.maxConcurrentTasks = maxConcurrentTasks;
this.supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
persistSession: false,
autoRefreshToken: false,
},
});
}
canAcceptTask() {
return this.activeExecutions.size < this.maxConcurrentTasks;
}
async executeTask(execution) {
if (!this.canAcceptTask()) {
logger.warn(`Cannot accept task ${execution.id}, at capacity`);
return;
}
logger.info(`Starting execution of task ${execution.id}`);
this.activeExecutions.set(execution.id, execution);
try {
await this.realtimeService.updateExecutionStatus(execution.id, 'running', {
startedAt: new Date().toISOString(),
});
await this.realtimeService.addExecutionLog(execution.id, 'info', 'Task execution started', {
taskId: execution.taskId,
});
const task = await this.fetchTask(execution.taskId);
if (!task) {
throw new Error(`Task ${execution.taskId} not found`);
}
const result = await this.executeTaskWorkflow(execution, task);
await this.realtimeService.updateExecutionStatus(execution.id, 'completed', {
outputResult: result,
completedAt: new Date().toISOString(),
durationMs: Date.now() - new Date(execution.startedAt || execution.queuedAt).getTime(),
});
await this.realtimeService.addExecutionLog(execution.id, 'info', 'Task execution completed successfully', {
result,
});
logger.info(`Task ${execution.id} completed successfully`);
}
catch (error) {
logger.error(`Task ${execution.id} failed:`, error);
await this.realtimeService.updateExecutionStatus(execution.id, 'failed', {
errorMessage: error instanceof Error ? error.message : String(error),
errorDetails: {
stack: error instanceof Error ? error.stack : undefined,
code: error instanceof Error ? error.code : undefined,
},
completedAt: new Date().toISOString(),
durationMs: Date.now() - new Date(execution.startedAt || execution.queuedAt).getTime(),
});
await this.realtimeService.addExecutionLog(execution.id, 'error', 'Task execution failed', {
error: error instanceof Error ? error.message : String(error),
});
}
finally {
this.activeExecutions.delete(execution.id);
}
}
async fetchTask(taskId) {
try {
const { data, error } = await this.supabase.from('tasks').select('*').eq('id', taskId).single();
if (error) {
logger.error(`Failed to fetch task ${taskId}:`, error);
return null;
}
return data;
}
catch (error) {
logger.error(`Error fetching task ${taskId}:`, error);
return null;
}
}
async executeTaskWorkflow(execution, task) {
const workflow = task.workflow || [];
const context = {
...execution.inputParams,
executionId: execution.id,
taskId: task.id,
};
let result = null;
for (const step of workflow) {
try {
logger.debug(`Executing workflow step: ${step.name}`);
await this.realtimeService.addExecutionLog(execution.id, 'debug', `Starting workflow step: ${step.name}`, {
step,
});
result = await this.executeWorkflowStep(execution, step, context);
context[step.id] = result;
await this.realtimeService.addExecutionLog(execution.id, 'debug', `Completed workflow step: ${step.name}`, {
result,
});
if (step.onSuccess) {
const nextStepIndex = workflow.findIndex(s => s.id === step.onSuccess);
if (nextStepIndex >= 0) {
}
}
}
catch (error) {
logger.error(`Workflow step ${step.name} failed:`, error);
await this.realtimeService.addExecutionLog(execution.id, 'error', `Workflow step failed: ${step.name}`, {
error: error instanceof Error ? error.message : String(error),
});
if (step.retryOnFailure) {
logger.info(`Retrying step ${step.name}`);
try {
result = await this.executeWorkflowStep(execution, step, context);
context[step.id] = result;
}
catch (retryError) {
if (!step.continueOnError) {
throw retryError;
}
}
}
else if (!step.continueOnError) {
throw error;
}
if (step.onFailure) {
const nextStepIndex = workflow.findIndex(s => s.id === step.onFailure);
if (nextStepIndex >= 0) {
}
}
}
}
return result;
}
async executeWorkflowStep(execution, step, context) {
switch (step.type) {
case 'tool':
return await this.executeToolStep(execution, step, context);
case 'condition':
return await this.executeConditionStep(step, context);
case 'loop':
return await this.executeLoopStep(execution, step, context);
case 'parallel':
return await this.executeParallelStep(execution, step, context);
case 'transform':
return await this.executeTransformStep(step, context);
default:
throw new Error(`Unknown step type: ${step.type}`);
}
}
async executeToolStep(execution, step, context) {
if (!step.tool) {
throw new Error('Tool step missing tool name');
}
const params = this.resolveParams(step.params || {}, context);
const toolCall = {
id: `${execution.id}-${step.id}-${Date.now()}`,
tool: step.tool,
params,
startedAt: new Date().toISOString(),
};
try {
const result = await this.mcpClient.callTool(step.tool, params);
toolCall.completedAt = new Date().toISOString();
toolCall.durationMs = Date.now() - new Date(toolCall.startedAt).getTime();
toolCall.result = result;
await this.recordToolCall(execution.id, toolCall);
return result;
}
catch (error) {
toolCall.completedAt = new Date().toISOString();
toolCall.durationMs = Date.now() - new Date(toolCall.startedAt).getTime();
toolCall.error = error instanceof Error ? error.message : String(error);
await this.recordToolCall(execution.id, toolCall);
throw error;
}
}
async executeConditionStep(step, context) {
if (!step.condition) {
throw new Error('Condition step missing condition');
}
const { type, expression, left, operator, right } = step.condition;
if (type === 'expression') {
try {
const func = new Function('context', `return ${expression}`);
return func(context);
}
catch (_error) {
throw new Error(`Invalid condition expression: ${expression}`);
}
}
else if (type === 'comparison') {
const leftValue = this.resolveValue(left, context);
const rightValue = this.resolveValue(right, context);
switch (operator) {
case '==':
case '===':
return leftValue === rightValue;
case '!=':
case '!==':
return leftValue !== rightValue;
case '>':
return leftValue > rightValue;
case '>=':
return leftValue >= rightValue;
case '<':
return leftValue < rightValue;
case '<=':
return leftValue <= rightValue;
case 'contains':
return String(leftValue).includes(String(rightValue));
case 'startsWith':
return String(leftValue).startsWith(String(rightValue));
case 'endsWith':
return String(leftValue).endsWith(String(rightValue));
default:
throw new Error(`Unknown operator: ${operator}`);
}
}
return false;
}
async executeLoopStep(_execution, _step, _context) {
logger.warn('Loop steps not yet implemented');
return [];
}
async executeParallelStep(_execution, _step, _context) {
logger.warn('Parallel steps not yet implemented');
return [];
}
async executeTransformStep(step, context) {
const params = this.resolveParams(step.params || {}, context);
return params;
}
resolveParams(params, context) {
const resolved = {};
for (const [key, value] of Object.entries(params)) {
resolved[key] = this.resolveValue(value, context);
}
return resolved;
}
resolveValue(value, context) {
if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) {
const varName = value.slice(2, -2).trim();
return this.getNestedValue(context, varName);
}
else if (typeof value === 'object' && value !== null) {
if (Array.isArray(value)) {
return value.map(v => this.resolveValue(v, context));
}
else {
const resolved = {};
for (const [k, v] of Object.entries(value)) {
resolved[k] = this.resolveValue(v, context);
}
return resolved;
}
}
return value;
}
getNestedValue(obj, path) {
const parts = path.split('.');
let current = obj;
for (const part of parts) {
if (current === null || current === undefined) {
return undefined;
}
current = current[part];
}
return current;
}
async recordToolCall(executionId, toolCall) {
try {
const { data: execution, error: fetchError } = await this.supabase
.from('executions')
.select('tool_calls')
.eq('id', executionId)
.single();
if (fetchError) {
logger.error(`Failed to fetch execution: ${fetchError.message}`);
return;
}
const toolCalls = execution?.tool_calls || [];
toolCalls.push(toolCall);
const { error: updateError } = await this.supabase
.from('executions')
.update({
tool_calls: toolCalls,
tool_calls_count: toolCalls.length,
})
.eq('id', executionId);
if (updateError) {
logger.error(`Failed to record tool call: ${updateError.message}`);
}
}
catch (error) {
logger.error('Error recording tool call:', error);
}
}
getActiveExecutionsCount() {
return this.activeExecutions.size;
}
getActiveExecutionIds() {
return Array.from(this.activeExecutions.keys());
}
}
//# sourceMappingURL=task-executor.js.map