import { createLogger, runWithRequestContext } from '@sim/logger' import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { task } from '@trigger.dev/sdk' import type { AsyncExecutionCorrelation } from '@/lib/core/async-jobs/types' import { createTimeoutAbortController, getTimeoutErrorMessage } from '@/lib/core/execution-limits' import { preprocessExecution } from '@/lib/execution/preprocessing' import { LoggingSession } from '@/lib/logs/execution/logging-session' import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans' import { cleanupExecutionBase64Cache } from '@/lib/uploads/utils/user-file-base64.server' import { executeWorkflowCore, wasExecutionFinalizedByCore, } from '@/lib/workflows/executor/execution-core' import { handlePostExecutionPauseState } from '@/lib/workflows/executor/pause-persistence' import { WORKFLOW_EXECUTION_CONCURRENCY_LIMIT } from '@/background/concurrency-limits' import { ExecutionSnapshot } from '@/executor/execution/snapshot' import type { ExecutionMetadata } from '@/executor/execution/types' import { hasExecutionResult } from '@/executor/utils/errors' import type { CoreTriggerType } from '@/stores/logs/filters/types' const logger = createLogger('TriggerWorkflowExecution') export function buildWorkflowCorrelation( payload: WorkflowExecutionPayload ): AsyncExecutionCorrelation { const executionId = payload.executionId || generateId() const requestId = payload.requestId || payload.correlation?.requestId || executionId.slice(0, 8) return { executionId, requestId, source: 'workflow', workflowId: payload.workflowId, triggerType: payload.triggerType || payload.correlation?.triggerType || 'api', } } export type WorkflowExecutionPayload = { workflowId: string userId: string workspaceId?: string input?: any triggerType?: CoreTriggerType executionId?: string requestId?: string correlation?: AsyncExecutionCorrelation metadata?: Record callChain?: string[] executionMode?: 'sync' | 'stream' | 'async' } /** * Background workflow execution job * @see preprocessExecution For detailed information on preprocessing checks * @see executeWorkflowCore For the core workflow execution logic */ export async function executeWorkflowJob(payload: WorkflowExecutionPayload) { const workflowId = payload.workflowId const correlation = buildWorkflowCorrelation(payload) const executionId = correlation.executionId const requestId = correlation.requestId return runWithRequestContext({ requestId }, async () => { logger.info(`[${requestId}] Starting workflow execution job: ${workflowId}`, { userId: payload.userId, triggerType: payload.triggerType, executionId, }) const triggerType = (correlation.triggerType || 'api') as CoreTriggerType const loggingSession = new LoggingSession(workflowId, executionId, triggerType, requestId) try { const preprocessResult = await preprocessExecution({ workflowId: payload.workflowId, userId: payload.userId, triggerType: triggerType, executionId: executionId, requestId: requestId, checkRateLimit: true, checkDeployment: true, loggingSession: loggingSession, triggerData: { correlation }, }) if (!preprocessResult.success) { logger.error(`[${requestId}] Preprocessing failed: ${preprocessResult.error?.message}`, { workflowId, statusCode: preprocessResult.error?.statusCode, }) throw new Error(preprocessResult.error?.message || 'Preprocessing failed') } const actorUserId = preprocessResult.actorUserId! const workspaceId = preprocessResult.workflowRecord?.workspaceId if (!workspaceId) { throw new Error(`Workflow ${workflowId} has no associated workspace`) } logger.info(`[${requestId}] Preprocessing passed. Using actor: ${actorUserId}`) const workflow = preprocessResult.workflowRecord! const metadata: ExecutionMetadata = { requestId, executionId, workflowId, workspaceId, userId: actorUserId, sessionUserId: undefined, workflowUserId: workflow.userId, triggerType: payload.triggerType || 'api', useDraftState: false, startTime: new Date().toISOString(), isClientSession: false, callChain: payload.callChain, correlation, executionMode: payload.executionMode ?? 'async', } const snapshot = new ExecutionSnapshot( metadata, workflow, payload.input, workflow.variables || {}, [] ) const timeoutController = createTimeoutAbortController( preprocessResult.executionTimeout?.async ) let result try { result = await executeWorkflowCore({ snapshot, callbacks: {}, loggingSession, includeFileBase64: true, base64MaxBytes: undefined, abortSignal: timeoutController.signal, }) } finally { timeoutController.cleanup() } if ( result.status === 'cancelled' && timeoutController.isTimedOut() && timeoutController.timeoutMs ) { const timeoutErrorMessage = getTimeoutErrorMessage(null, timeoutController.timeoutMs) logger.info(`[${requestId}] Workflow execution timed out`, { timeoutMs: timeoutController.timeoutMs, }) await loggingSession.markAsFailed(timeoutErrorMessage) } else { await handlePostExecutionPauseState({ result, workflowId, executionId, loggingSession }) } await loggingSession.waitForPostExecution() logger.info(`[${requestId}] Workflow execution completed: ${workflowId}`, { success: result.success, executionTime: result.metadata?.duration, executionId, }) return { success: result.success, workflowId: payload.workflowId, executionId, output: result.output, executedAt: new Date().toISOString(), metadata: payload.metadata, } } catch (error: unknown) { logger.error(`[${requestId}] Workflow execution failed: ${workflowId}`, { error: toError(error).message, executionId, }) if (wasExecutionFinalizedByCore(error, executionId)) { throw error } const executionResult = hasExecutionResult(error) ? error.executionResult : undefined const { traceSpans } = executionResult ? buildTraceSpans(executionResult) : { traceSpans: [] } await loggingSession.safeCompleteWithError({ error: { message: toError(error).message, stackTrace: error instanceof Error ? error.stack : undefined, }, traceSpans, }) throw error } finally { void cleanupExecutionBase64Cache(executionId) } }) } export const workflowExecutionTask = task({ id: 'workflow-execution', machine: 'medium-1x', queue: { concurrencyLimit: WORKFLOW_EXECUTION_CONCURRENCY_LIMIT, }, run: executeWorkflowJob, })