// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii - ISTI-CNR // SPDX-License-Identifier: AGPL-3.0-or-later // Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM. import { FileInputConstraints, FlowBlockConnection, FlowData, FlowNode, FlowNodeDependency, FlowPort, LLMDescriptor } from './flow'; import { BiasExecutionContext } from './bias-impact'; export type TaskExecutionStatus = 'CREATED' | 'READY' | 'RUNNING' | 'WAITING' | 'SUSPENDED' | 'SUCCESS' | 'ERROR' | 'CANCELLED'; export type TaskExecutionStatusGroup = 'INIT' | 'RUNNING' | 'PAUSED' | 'FINAL'; export type StepStatus = | 'WAITING_FOR_INPUT' | 'WAITING_FOR_DEPENDENCY' | 'WAITING_FOR_INTERACTION' | 'WAITING_FOR_SUBFLOW' | 'READY' | 'RUNNING' | 'COMPLETED' | 'SKIPPED' | 'FAILED' | 'CANCELLED' | string; export type ContainerContinuationPhase = | 'CHILD_CREATED' | 'CHILD_RUNNING' | 'WAITING_FOR_SUBFLOW' | 'CHILD_COMPLETED' | string; export type ExecutionKind = 'TOP_LEVEL' | 'SUBFLOW' | string; export type SubflowRole = 'MAIN' | 'GUARD' | string; export type ExecutionEventLogEntry = { id: string; timestamp: number; stepId?: string | null; nodeId?: string | null; nodeName?: string | null; level?: string | null; type?: string | null; message?: string | null; details?: unknown; }; export type TaskExecution = { id: string; name: string; creationTime: number; flowId?: string | null; sourceFlowId?: string | null; /** The project the source flow belonged to when the run was created. */ projectId?: string | null; /** Ties together the executions started by one project run; null for a single-flow run. */ projectRunId?: string | null; runNumber?: number | null; rerunOfExecutionId?: string | null; executionKind?: ExecutionKind; parentExecutionId?: string | null; parentStepId?: string | null; parentIterationIndex?: number | null; subflowRole?: SubflowRole | null; biasExecutionContext?: BiasExecutionContext; context: TaskExecutionContext; interactionSimulationEnabled?: boolean; simulationAvailable?: boolean; interactionSimulationDescriptor?: LLMDescriptor; flowSnapshot?: FlowData; stepConnections?: FlowBlockConnection[]; stepDependencies?: FlowNodeDependency[]; requiredAuthorizations?: Record | TaskExecutionAuthorizationRequirement[]; providedAuthorizations?: Record; missingAuthorizationKeys?: string[]; missingGlobalInputKeys?: string[]; }; export type TaskExecutionGroup = { id: string; sourceFlowId: string; name: string; firstExecutionId: string; latestExecutionId: string; creationTime: number; lastExecutionTime: number; executionCount: number; executions: TaskExecution[]; }; export type TaskExecutionAuthorizationRequirement = { key: string; provider: string; fieldName: string; description: string; requiredBySteps: string[]; }; export type TaskExecutionContext = { inputs: Record; globalInputs?: Record; globalInputDescriptors?: Record; /** * Values inherited from the flow's project, frozen when the run was created. Read in prompts as * `${{project.}}`. */ projectContext?: Record; result: Record; partialResult?: Record; startTime?: number | null; endTime?: number | null; errors: Record; warnings: Record | unknown[]; steps: Record; status: TaskExecutionStatus; waitingSteps: string[]; authorizations?: Record; executionVariables?: Record; executionVariableDescriptors?: Record; errorCodes?: Record; /** * One entry per End node the run passed through. This is where a flow's answer lands when the * last block is wired into an End: the flow `result` is built only from *unconnected* outputs, so * connecting one into an End moves its value here, as the outcome payload. */ outcomes?: TaskExecutionOutcome[]; }; export type TaskExecutionOutcome = { stepId: string; code: string; label: string; payload: unknown; timestamp: number; }; export function normalizeExecutionOutcomes(raw: unknown): TaskExecutionOutcome[] { if (!Array.isArray(raw)) return []; return raw .filter((entry): entry is Record => !!entry && typeof entry === 'object' && !Array.isArray(entry)) .map((entry) => ({ stepId: String(entry['stepId'] ?? ''), code: String(entry['code'] ?? ''), label: String(entry['label'] ?? entry['code'] ?? ''), payload: entry['payload'] ?? null, timestamp: typeof entry['timestamp'] === 'number' ? entry['timestamp'] : 0 })) .filter((outcome) => outcome.code.length > 0 || outcome.payload !== null); } export type TaskExecutionGlobalInputDescriptor = { name: string; kind: string; value: unknown; description?: string | null; cleanupPolicy?: string | null; multiple?: boolean; fileConstraints?: FileInputConstraints | null; }; export type TaskExecutionStep = { node?: FlowNode; id: string; inputs?: TaskExecutionStepInput[]; outputs?: TaskExecutionStepOutput[]; result?: Record; status: StepStatus; started?: boolean; skipReason?: string | null; simulated: boolean; activeInnerExecutionId?: string | null; containerContinuationPhase?: ContainerContinuationPhase | null; containerIterationIndex?: number | null; activeBiasProbes?: Array<{ annotationId: string; direction: 'BIAS' | 'MITIGATION'; activationMode: string; instruction?: string; }>; }; export type TaskExecutionStepInput = { descriptor: FlowPort; value: unknown; registered: boolean; set: boolean; }; export type TaskExecutionStepOutput = { descriptor: FlowPort; connected: boolean; }; export function getExecutionStatusGroup(status: string | null | undefined): TaskExecutionStatusGroup | null { const normalized = String(status ?? '').toUpperCase(); if (!normalized) return null; if (normalized === 'CREATED' || normalized === 'READY') return 'INIT'; if ( normalized === 'RUNNING' || normalized === 'WAITING' ) { return 'RUNNING'; } if (normalized === 'SUSPENDED') { return 'PAUSED'; } if (normalized === 'SUCCESS' || normalized === 'ERROR' || normalized === 'CANCELLED') { return 'FINAL'; } return null; } export function normalizeExecutionStatus(status: string | null | undefined): TaskExecutionStatus { const normalized = String(status ?? '').toUpperCase(); if (normalized === 'CREATED') return 'CREATED'; if (normalized === 'READY') return 'READY'; if (normalized === 'RUNNING') return 'RUNNING'; if ( normalized === 'WAITING' || normalized === 'WAITING_FOR_INPUT' || normalized === 'WAITING_FOR_INTERACTION' || normalized === 'WAITING_FOR_SUBFLOW' || normalized === 'WAITING_FOR_DEPENDENCY' ) { return 'WAITING'; } if (normalized === 'SUSPENDED') return 'SUSPENDED'; if (normalized === 'CANCELLED') return 'CANCELLED'; if (normalized === 'SUCCESS' || normalized === 'COMPLETED') return 'SUCCESS'; if (normalized === 'ERROR' || normalized === 'FAILED') return 'ERROR'; return 'CREATED'; } export function getTaskExecutionStepNode(step: TaskExecutionStep | null | undefined): FlowNode | null { if (!step) return null; if (step.node) { return step.node.nodeFamily === 'container' ? { ...step.node, nodeFamily: 'container' } : { ...step.node, nodeFamily: 'block' }; } return null; }