UNPKG

veas

Version:

Veas CLI - Command-line interface for Veas platform

310 lines 12.5 kB
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