/** * Workflow Automation API (mandate-scoped) * * Replaces instance-scoped /api/workflows/{instanceId}/... * with the unified /api/workflow-automation/... base path. * * Also unifies the former /api/system/workflow-runs/... and * /api/automations/runs/... endpoints under the same base. */ import type { ApiRequestOptions } from '../hooks/useApi'; const LOG = '[WorkflowAutomation]'; const BASE = '/api/workflow-automation'; // ============================================================================ // TYPES // ============================================================================ export interface NodeTypeParameter { name: string; type: string; required?: boolean; description?: string; default?: unknown; frontendType?: string; frontendOptions?: Record; options?: unknown[]; validation?: Record; } export interface PortField { name: string; type: string; /** Plain string or per-language map from the API catalog. */ description: string | Record; required: boolean; enumValues?: string[] | null; /** When true, surface at the top of the DataPicker as the most common/recommended pick. */ recommended?: boolean; /** Human label from portTypeCatalog (backend). Preferred over technical path in DataPicker. */ pickerLabel?: string | null; /** Backend: segment for one list element (between List field and nested field). */ pickerItemLabel?: string | null; } export interface PortSchema { name: string; fields: PortField[]; } /** One pickable binding — defined on ``outputPorts[n].dataPickOptions`` (authoritative list from gateway). */ export interface DataPickOption { path: (string | number)[]; pickerLabel: string; detail?: string; recommended?: boolean; iterable?: boolean; /** For display and optional strict compatibility (e.g. str, Any). */ type?: string; } /** @deprecated Prefer ``outputPorts[].dataPickOptions``; kept for older payloads. */ export type OutputPickHint = DataPickOption; export interface InputPortDef { accepts: string[]; } /** Graph-defined output schema (e.g. form fields from node parameters). */ export interface GraphDefinedSchemaRef { kind: 'fromGraph'; parameter: string; } export interface OutputPortDef { schema: string | GraphDefinedSchemaRef; dynamic?: boolean; deriveFrom?: string; /** * When set, DataPicker uses **only** this list for that port (no portTypeCatalog expansion). * Authoritative, like `parameters` for node configuration. */ dataPickOptions?: DataPickOption[]; } export interface NodeType { id: string; category: string; label: string; description: string; parameters: NodeTypeParameter[]; inputs: number; outputs: number; outputLabels?: string[]; executor: string; inputPorts?: Record; outputPorts?: Record; meta?: { icon?: string; color?: string; /** True if this node performs an LLM / AI call (credits). */ usesAi?: boolean; method?: string; action?: string; }; } export interface NodeTypeCategory { id: string; label: Record | string; } export interface SystemVariable { type: string; description: string; } /** Single form field type with its canonical port primitive. Delivered by GET /node-types. */ export interface FormFieldType { id: string; label: string; portType: string; } export interface ConditionOperatorDef { id: string; label: string; labelKey?: string; needsValue: boolean; valueInput?: { kind: string; options?: string[] }; } export interface NodeTypesResponse { nodeTypes: NodeType[]; categories: NodeTypeCategory[]; portTypeCatalog?: Record; conditionOperatorCatalog?: Record; systemVariables?: Record; formFieldTypes?: FormFieldType[]; } export interface Automation2GraphNode { id: string; type: string; parameters?: Record; inputPorts?: Array<{ name: string; schema: string; accepts?: string[] }>; outputPorts?: Array<{ name: string; schema: string | GraphDefinedSchemaRef }>; } export interface Automation2Connection { source: string; target: string; sourceOutput?: number; targetInput?: number; } export interface Automation2Graph { nodes: Automation2GraphNode[]; connections: Automation2Connection[]; } export interface ExecuteGraphResponse { success: boolean; nodeOutputs?: Record; error?: string; /** Soft, non-blocking message displayed alongside a successful response. */ warning?: string; stopped?: boolean; failedNode?: string; paused?: boolean; taskId?: string; runId?: string; nodeId?: string; } /** Entry point / start configured outside the canvas (manual, form, schedule, …) */ export interface WorkflowEntryPoint { id: string; kind: string; category: 'on_demand' | 'always_on'; enabled: boolean; title: Record | string; description?: Record; config: Record; } export interface Automation2Workflow { id: string; label: string; graph: Automation2Graph; active?: boolean; /** Target feature instance for execution data scope (NULL for templates) */ targetFeatureInstanceId?: string | null; /** Entry points (Starts) — how this workflow may be invoked */ invocations?: WorkflowEntryPoint[]; /** Enriched: run count */ runCount?: number; /** Enriched: has active (running/paused) run */ isRunning?: boolean; /** Enriched: status of active run */ runStatus?: string; /** Enriched: nodeId where workflow is stuck (paused) */ stuckAtNodeId?: string; /** Enriched: human-readable label for stuck node */ stuckAtNodeLabel?: string; /** From PowerOnModel base — record creation timestamp (seconds) */ sysCreatedAt?: number; /** Enriched: last run started timestamp (seconds) */ lastStartedAt?: number; } // ============================================================================ // AUTO-PREFIX TYPES (Greenfield) // ============================================================================ export type AutoWorkflowStatus = 'draft' | 'published' | 'archived'; export type AutoRunStatus = 'running' | 'paused' | 'completed' | 'failed' | 'cancelled'; export type AutoStepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped'; export type AutoTaskStatus = 'pending' | 'completed' | 'cancelled' | 'expired'; export type AutoTemplateScope = 'user' | 'instance' | 'mandate' | 'system'; export interface AutoVersion { id: string; workflowId: string; versionNumber: number; status: AutoWorkflowStatus; graph: Automation2Graph; invocations?: WorkflowEntryPoint[]; publishedAt?: number; publishedBy?: string; } export interface AutoRun { id: string; workflowId: string; versionId?: string; status: AutoRunStatus; trigger?: Record; startedAt?: number; completedAt?: number; nodeOutputs?: Record; currentNodeId?: string; resumeContext?: Record; error?: string; costTokens?: number; costCredits?: number; } export interface AutoWorkflow { id: string; mandateId: string; featureInstanceId: string; label: string; description?: string; tags?: string[]; isTemplate: boolean; templateSourceId?: string; templateScope?: AutoTemplateScope; sharedReadOnly?: boolean; currentVersionId?: string; active: boolean; eventId?: string; notifyOnFailure?: boolean; graph: Automation2Graph; invocations?: WorkflowEntryPoint[]; sysCreatedBy?: string; sysCreatedAt?: number; sysModifiedBy?: string; sysModifiedAt?: number; } export interface AutoTask { id: string; runId: string; workflowId: string; nodeId: string; nodeType: string; config: Record; assigneeId?: string; status: AutoTaskStatus; result?: Record; expiresAt?: number; sysCreatedAt?: number; } export interface AutoStepLog { id: string; runId: string; nodeId: string; nodeType: string; status: AutoStepStatus; inputSnapshot?: Record; output?: Record; error?: string; startedAt?: number; completedAt?: number; durationMs?: number; tokensUsed?: number; retryCount?: number; } // ============================================================================ // ADDITIONAL TYPES // ============================================================================ export interface UpstreamPathEntry { producerNodeId: string; producerLabel?: string; path: (string | number)[]; type: string; label: string; scopeOrigin: 'data' | 'loop' | 'system'; valueKind?: string; } export interface ConditionMetaResponse { valueKind: string; operators: ConditionOperatorDef[]; } export interface ConditionMetaRequest { graph: Automation2Graph; nodeId?: string; ref: { type: 'ref'; nodeId: string; path: (string | number)[] }; } /** Scope-aware data sources for the DataPicker. */ export interface GraphDataSources { /** Ancestor node IDs that are valid sources. */ availableSourceIds: string[]; /** Maps nodeId → output port index to use instead of 0. */ portIndexOverrides: Record; /** IDs of flow.loop nodes whose body the current node is inside. */ loopBodyContextIds: string[]; } export interface ExecuteGraphOptions { /** Use a configured start on the saved workflow */ entryPointId?: string; /** Full run envelope (overrides entry point mapping) */ runEnvelope?: Record; /** Merged into envelope.payload */ payload?: Record; } export interface Automation2Run { id: string; workflowId: string; status: string; nodeOutputs?: Record; currentNodeId?: string; } export interface CompletedRun extends Automation2Run { workflowLabel?: string; sysModifiedAt?: number; sysCreatedAt?: number; } export interface Automation2Task { id: string; runId: string; workflowId: string; nodeId: string; nodeType: string; config: Record; status: string; result?: Record; /** Workflow label (enriched by API) */ workflowLabel?: string; /** Unix timestamp ms (from sysCreatedAt) */ createdAt?: number; /** Optional due date — configurable in future */ dueAt?: number; } export interface AutoWorkflowTemplate extends Automation2Workflow { isTemplate: boolean; templateScope?: AutoTemplateScope; templateSourceId?: string; sharedReadOnly?: boolean; } export interface UserConnection { id: string; authority: string; externalUsername?: string; externalEmail?: string; status: string; } export interface ConnectionService { service: string; label: string; icon: string; } export interface BrowseEntry { name: string; path: string; isFolder: boolean; size?: number; mimeType?: string; metadata?: Record; } export interface WorkflowMetrics { workflowCount: number; activeWorkflows: number; totalRuns: number; runsByStatus: Record; totalTasks: number; tasksByStatus: Record; totalTokens: number; totalCredits: number; } // ------------------------------------------------------------------------- // Workflow file IO (envelopeVersioned, .workflow.json) // ------------------------------------------------------------------------- export const WORKFLOW_FILE_SCHEMA_VERSION = '1.0'; export const WORKFLOW_FILE_KIND = 'poweron.workflow'; export const WORKFLOW_FILE_EXTENSION = '.workflow.json'; export interface WorkflowFileEnvelope { $schemaVersion: string; $kind: string; $exportedAt?: string; $gatewayVersion?: string; label: string; description?: string; tags?: string[]; templateScope?: AutoTemplateScope; sharedReadOnly?: boolean; notifyOnFailure?: boolean; graph: Automation2Graph; invocations?: WorkflowEntryPoint[]; } export interface ImportWorkflowResponse { workflow: AutoWorkflow; warnings: string[]; created: boolean; } export interface ImportWorkflowOptions { /** Inline envelope payload (preferred for round-trip in the editor). */ envelope?: WorkflowFileEnvelope; /** UDB FileItem.id of a previously uploaded ``.workflow.json``. */ fileId?: string; /** When set, the existing workflow is replaced instead of a new one being created. */ existingWorkflowId?: string; } export interface ExportWorkflowResult { fileName: string; envelope: WorkflowFileEnvelope; } // ------------------------------------------------------------------------- // Workspace run types (user-facing run workspace) // ------------------------------------------------------------------------- export interface WorkspaceRun { id: string; workflowId: string; workflowLabel?: string; status: string; startedAt?: number; completedAt?: number; ownerId?: string; mandateId?: string; mandateLabel?: string; targetFeatureInstanceId?: string; targetInstanceLabel?: string; costTokens?: number; costCredits?: number; error?: string; } export interface WorkspaceRunDetail { run: WorkspaceRun & { nodeOutputs?: Record }; workflow: { id: string; label: string; targetFeatureInstanceId?: string; featureInstanceId?: string; tags?: string[]; } | null; steps: Array<{ id: string; runId: string; nodeId: string; nodeType: string; status: string; inputSnapshot?: Record; output?: Record; inputFiles?: Array<{ id: string; fileName?: string }>; outputFiles?: Array<{ id: string; fileName?: string }>; error?: string; startedAt?: number; completedAt?: number; durationMs?: number; tokensUsed?: number; retryCount?: number; }>; files: Array<{ id: string; fileName?: string; contentType?: string; sizeBytes?: number; }>; unassignedFiles?: Array<{ id: string; fileName?: string; }>; } // ============================================================================ // API FUNCTIONS // ============================================================================ export type ApiRequestFunction = (options: ApiRequestOptions) => Promise; // ------------------------------------------------------------------------- // Node types & graph helpers // ------------------------------------------------------------------------- /** * Fetch node types for the flow builder (backend-driven). * GET /api/workflow-automation/node-types?language=de */ export async function fetchNodeTypes( request: ApiRequestFunction, language = 'de' ): Promise { console.log(`${LOG} fetchNodeTypes: language=${language}`); const data = await request({ url: `${BASE}/node-types`, method: 'get', params: { language }, }); const nodeTypes = data?.nodeTypes ?? []; const categories = data?.categories ?? []; const portTypeCatalog = data?.portTypeCatalog ?? undefined; const conditionOperatorCatalog = data?.conditionOperatorCatalog ?? undefined; const systemVariables = data?.systemVariables ?? undefined; const formFieldTypes = data?.formFieldTypes ?? undefined; console.log( `${LOG} fetchNodeTypes response: ${nodeTypes.length} nodeTypes, ${categories.length} categories, ` + `${portTypeCatalog ? Object.keys(portTypeCatalog).length : 0} portTypes, ` + `${conditionOperatorCatalog ? Object.keys(conditionOperatorCatalog).length : 0} conditionKinds, ` + `${systemVariables ? Object.keys(systemVariables).length : 0} sysVars, ` + `${formFieldTypes ? formFieldTypes.length : 0} formFieldTypes` ); return { nodeTypes, categories, portTypeCatalog, conditionOperatorCatalog, systemVariables, formFieldTypes }; } /** * POST /api/workflow-automation/condition-meta — operators for a DataRef (If/Else). */ export async function fetchConditionMeta( request: ApiRequestFunction, body: ConditionMetaRequest, language = 'de' ): Promise { const data = await request({ url: `${BASE}/condition-meta`, method: 'post', params: { language }, data: body, }); return { valueKind: String(data?.valueKind ?? 'unknown'), operators: (data?.operators ?? []) as ConditionOperatorDef[], }; } /** * POST /api/workflow-automation/upstream-paths — pickable upstream paths for DataPicker / AI. */ export async function postUpstreamPaths( request: ApiRequestFunction, graph: Automation2Graph, nodeId: string ): Promise<{ paths: UpstreamPathEntry[] }> { const data = await request({ url: `${BASE}/upstream-paths`, method: 'post', data: { graph, nodeId }, }); return { paths: (data?.paths ?? []) as UpstreamPathEntry[] }; } /** * POST /api/workflow-automation/graph-data-sources * * Returns scope-aware source list so the DataPicker needs zero graph-traversal logic. */ export async function fetchGraphDataSources( request: ApiRequestFunction, nodeId: string, nodes: Array<{ id: string; type?: string }>, connections: Array<{ source: string; target: string; sourceOutput?: number; targetInput?: number }>, ): Promise { const data = await request({ url: `${BASE}/graph-data-sources`, method: 'post', data: { nodeId, graph: { nodes, connections } }, }); return { availableSourceIds: data?.availableSourceIds ?? [], portIndexOverrides: data?.portIndexOverrides ?? {}, loopBodyContextIds: data?.loopBodyContextIds ?? [], }; } /** GET saved workflow graph variant of upstream-paths (requires workflowId). */ export async function getUpstreamPathsSaved( request: ApiRequestFunction, workflowId: string, nodeId: string ): Promise<{ paths: UpstreamPathEntry[] }> { const data = await request({ url: `${BASE}/upstream-paths/${encodeURIComponent(nodeId)}`, method: 'get', params: { workflowId }, }); return { paths: (data?.paths ?? []) as UpstreamPathEntry[] }; } // ------------------------------------------------------------------------- // Options (dropdown data for node parameters) // ------------------------------------------------------------------------- /** GET /api/workflow-automation/options/user.connection */ export async function fetchUserConnectionOptions( request: ApiRequestFunction, ): Promise> { const data = await request({ url: `${BASE}/options/user.connection`, method: 'get', }); return data?.options ?? data ?? []; } /** GET /api/workflow-automation/options/feature.instance */ export async function fetchFeatureInstanceOptions( request: ApiRequestFunction, ): Promise> { const data = await request({ url: `${BASE}/options/feature.instance`, method: 'get', }); return data?.options ?? data ?? []; } // ------------------------------------------------------------------------- // Execute // ------------------------------------------------------------------------- /** * Execute an automation2 graph. * POST /api/workflow-automation/workflows/{workflowId}/execute */ export async function executeGraph( request: ApiRequestFunction, graph: Automation2Graph, workflowId?: string, options?: ExecuteGraphOptions ): Promise { console.log( `${LOG} executeGraph request: workflowId=${workflowId} nodes=${graph.nodes.length} connections=${graph.connections.length}`, { nodes: graph.nodes, connections: graph.connections, options } ); const start = performance.now(); try { const body: Record = { graph, workflowId }; if (options?.entryPointId) body.entryPointId = options.entryPointId; if (options?.runEnvelope) body.runEnvelope = options.runEnvelope; if (options?.payload && Object.keys(options.payload).length > 0) body.payload = options.payload; const url = workflowId ? `${BASE}/workflows/${workflowId}/execute` : `${BASE}/execute`; const result = await request({ url, method: 'post', data: body }); const ms = Math.round(performance.now() - start); console.log( `${LOG} executeGraph response (${ms}ms): success=${result?.success} error=${result?.error ?? 'none'} nodeOutputs_keys=${Object.keys(result?.nodeOutputs ?? {}).join(',')} failedNode=${result?.failedNode ?? '-'}`, result ); return result; } catch (err) { const ms = Math.round(performance.now() - start); console.error(`${LOG} executeGraph FAILED (${ms}ms):`, err); throw err; } } // ------------------------------------------------------------------------- // Workflows CRUD // ------------------------------------------------------------------------- export async function fetchWorkflows( request: ApiRequestFunction, params?: { active?: boolean; pagination?: any; mandateId?: string } ): Promise { const queryParams: Record = {}; if (params?.active !== undefined) queryParams.active = params.active; if (params?.pagination) queryParams.pagination = JSON.stringify(params.pagination); if (params?.mandateId) queryParams.mandateId = params.mandateId; const data = await request({ url: `${BASE}/workflows`, method: 'get', params: Object.keys(queryParams).length > 0 ? queryParams : undefined, }); if (data?.items && data?.pagination) return data; return data?.workflows ?? []; } export async function fetchWorkflow( request: ApiRequestFunction, workflowId: string ): Promise { return await request({ url: `${BASE}/workflows/${workflowId}`, method: 'get', }); } export async function createWorkflow( request: ApiRequestFunction, body: { label: string; graph: Automation2Graph; invocations?: WorkflowEntryPoint[]; targetFeatureInstanceId?: string | null; mandateId?: string; } ): Promise { return await request({ url: `${BASE}/workflows`, method: 'post', data: body, }); } export async function updateWorkflow( request: ApiRequestFunction, workflowId: string, body: { label?: string; graph?: Automation2Graph; invocations?: WorkflowEntryPoint[]; active?: boolean; notifyOnFailure?: boolean; targetFeatureInstanceId?: string | null; } ): Promise { return await request({ url: `${BASE}/workflows/${workflowId}`, method: 'put', data: body, }); } export async function deleteWorkflow( request: ApiRequestFunction, workflowId: string ): Promise { await request({ url: `${BASE}/workflows/${workflowId}`, method: 'delete', }); } // ------------------------------------------------------------------------- // Workflow file IO (envelopeVersioned, .workflow.json) // ------------------------------------------------------------------------- /** POST /api/workflow-automation/workflows/import */ export async function importWorkflowFromFile( request: ApiRequestFunction, options: ImportWorkflowOptions, ): Promise { if (!options.envelope && !options.fileId) { throw new Error('importWorkflowFromFile: either envelope or fileId is required'); } return await request({ url: `${BASE}/workflows/import`, method: 'post', data: options, }); } /** * GET /api/workflow-automation/workflows/{workflowId}/export * * Returns ``{ fileName, envelope }`` when ``download=false`` and a raw JSON * download (``Content-Disposition: attachment``) when ``download=true``. */ export async function exportWorkflowToFile( request: ApiRequestFunction, workflowId: string, download = false, ): Promise { return await request({ url: `${BASE}/workflows/${workflowId}/export`, method: 'get', params: { download }, }); } /** Quick content-sniffing — used by the UDB FilesTab to flag workflow files. */ export function isWorkflowFileContent(payload: unknown): boolean { if (!payload || typeof payload !== 'object') return false; const p = payload as Record; return ( typeof p.$schemaVersion === 'string' && p.$kind === WORKFLOW_FILE_KIND && !!p.graph && typeof p.graph === 'object' ); } /** Suggest a safe filename from the workflow label (mirrors gateway buildFileName). */ export function workflowFileNameFor(label: string): string { const slug = (label || 'workflow') .toLowerCase() .replace(/[^a-z0-9._-]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 80) || 'workflow'; return `${slug}${WORKFLOW_FILE_EXTENSION}`; } // ------------------------------------------------------------------------- // Runs // ------------------------------------------------------------------------- /** * Fetch runs for a specific workflow. * GET /api/workflow-automation/runs?workflowId={workflowId} * * Replaces both: * /api/workflows/{instanceId}/workflows/{workflowId}/runs * /api/system/workflow-runs (with workflowId filter) */ export async function fetchWorkflowRuns( request: ApiRequestFunction, workflowId: string ): Promise { const data = await request({ url: `${BASE}/runs`, method: 'get', params: { workflowId }, }); return data?.runs ?? []; } /** * Fetch completed runs across all workflows. * GET /api/workflow-automation/runs?status=completed */ export async function fetchCompletedRuns( request: ApiRequestFunction, limit = 20 ): Promise { const data = await request({ url: `${BASE}/runs`, method: 'get', params: { status: 'completed', limit }, }); return data?.runs ?? []; } /** * Fetch all runs (system-level / cross-workflow). * GET /api/workflow-automation/runs * * Replaces /api/system/workflow-runs and /api/automations/runs. */ export async function fetchRuns( request: ApiRequestFunction, params?: { status?: string; workflowId?: string; limit?: number; offset?: number; } ): Promise<{ runs: Automation2Run[]; total?: number }> { const data = await request({ url: `${BASE}/runs`, method: 'get', params, }); return { runs: data?.runs ?? [], total: data?.total }; } /** GET /api/workflow-automation/runs/{runId}/steps */ export async function fetchRunSteps( request: ApiRequestFunction, runId: string ): Promise { const data = await request({ url: `${BASE}/runs/${runId}/steps`, method: 'get', }); return data?.steps ?? []; } /** * Returns the SSE stream URL for a running workflow. * GET /api/workflow-automation/runs/{runId}/stream */ export async function fetchRunStream( request: ApiRequestFunction, runId: string ): Promise { return await request({ url: `${BASE}/runs/${runId}/stream`, method: 'get', }); } /** POST /api/workflow-automation/runs/{runId}/stop */ export async function stopRun( request: ApiRequestFunction, runId: string ): Promise<{ success: boolean }> { const data = await request({ url: `${BASE}/runs/${runId}/stop`, method: 'post', }); return { success: Boolean(data?.success) }; } /** GET /api/workflow-automation/runs/{runId}/detail */ export async function fetchRunDetail( request: ApiRequestFunction, runId: string, ): Promise { const resp = await request({ url: `${BASE}/runs/${runId}/detail`, method: 'get' }); return resp as WorkspaceRunDetail; } // ------------------------------------------------------------------------- // Workspace runs (user-facing, paginated) // ------------------------------------------------------------------------- /** * Paginated workspace runs (replaces /api/automations/runs). * GET /api/workflow-automation/runs */ export async function fetchWorkspaceRuns( request: ApiRequestFunction, params: { scope?: 'mine' | 'mandate'; status?: string; targetInstanceId?: string; workflowId?: string; limit?: number; offset?: number; } = {}, ): Promise<{ runs: WorkspaceRun[]; total: number }> { const query = new URLSearchParams(); if (params.scope) query.set('scope', params.scope); if (params.status) query.set('status', params.status); if (params.targetInstanceId) query.set('targetInstanceId', params.targetInstanceId); if (params.workflowId) query.set('workflowId', params.workflowId); if (params.limit) query.set('limit', String(params.limit)); if (params.offset) query.set('offset', String(params.offset)); const qs = query.toString(); const url = `${BASE}/runs${qs ? `?${qs}` : ''}`; const resp = await request({ url, method: 'get' }); return resp as { runs: WorkspaceRun[]; total: number }; } // ------------------------------------------------------------------------- // Tasks // ------------------------------------------------------------------------- export async function fetchTasks( request: ApiRequestFunction, params?: { workflowId?: string; status?: string } ): Promise { const data = await request({ url: `${BASE}/tasks`, method: 'get', params, }); return data?.tasks ?? []; } export async function completeTask( request: ApiRequestFunction, taskId: string, result: Record ): Promise { return await request({ url: `${BASE}/tasks/${taskId}/complete`, method: 'post', data: { result }, }); } /** Cancel a pending human task and stop its workflow run. */ export async function cancelPendingTaskStopRun( request: ApiRequestFunction, taskId: string ): Promise<{ success: boolean; runId?: string | null; taskId: string }> { const data = await request({ url: `${BASE}/tasks/${taskId}/cancel`, method: 'post', }); return { success: Boolean(data?.success), runId: data?.runId, taskId: data?.taskId ?? taskId, }; } // ------------------------------------------------------------------------- // Versions (AutoVersion Lifecycle) // ------------------------------------------------------------------------- export async function fetchVersions( request: ApiRequestFunction, workflowId: string ): Promise { const data = await request({ url: `${BASE}/workflows/${workflowId}/versions`, method: 'get', }); return data?.versions ?? []; } export async function createDraftVersion( request: ApiRequestFunction, workflowId: string ): Promise { return await request({ url: `${BASE}/workflows/${workflowId}/versions/draft`, method: 'post', }); } export async function publishVersion( request: ApiRequestFunction, versionId: string ): Promise { return await request({ url: `${BASE}/versions/${versionId}/publish`, method: 'post', }); } export async function unpublishVersion( request: ApiRequestFunction, versionId: string ): Promise { return await request({ url: `${BASE}/versions/${versionId}/unpublish`, method: 'post', }); } export async function archiveVersion( request: ApiRequestFunction, versionId: string ): Promise { return await request({ url: `${BASE}/versions/${versionId}/archive`, method: 'post', }); } // ------------------------------------------------------------------------- // Templates // ------------------------------------------------------------------------- export async function fetchTemplates( request: ApiRequestFunction, scope?: AutoTemplateScope, pagination?: any ): Promise { const queryParams: Record = {}; if (scope) queryParams.scope = scope; if (pagination) queryParams.pagination = JSON.stringify(pagination); const data = await request({ url: `${BASE}/templates`, method: 'get', params: Object.keys(queryParams).length > 0 ? queryParams : undefined, }); if (data?.items && data?.pagination) return data; return data?.templates ?? []; } export async function createTemplateFromWorkflow( request: ApiRequestFunction, workflowId: string, scope: AutoTemplateScope = 'user' ): Promise { return await request({ url: `${BASE}/templates/from-workflow`, method: 'post', data: { workflowId, scope }, }); } export async function copyTemplate( request: ApiRequestFunction, templateId: string ): Promise { return await request({ url: `${BASE}/templates/${templateId}/copy`, method: 'post', }); } export async function shareTemplate( request: ApiRequestFunction, templateId: string, scope: AutoTemplateScope ): Promise { return await request({ url: `${BASE}/templates/${templateId}/share`, method: 'post', data: { scope }, }); } // ------------------------------------------------------------------------- // Connections and Browse (for Email/SharePoint node config) // ------------------------------------------------------------------------- /** Encode connection id/reference for URL path segments (may contain spaces/colons). */ function _encodedConnectionId(connectionId: string): string { return encodeURIComponent(connectionId); } export async function fetchConnections( request: ApiRequestFunction, ): Promise { const data = await request({ url: `${BASE}/connections`, method: 'get', }); return data?.connections ?? []; } export async function fetchConnectionServices( request: ApiRequestFunction, connectionId: string ): Promise { const data = await request({ url: `${BASE}/connections/${_encodedConnectionId(connectionId)}/services`, method: 'get', }); return data?.services ?? []; } export async function fetchBrowse( request: ApiRequestFunction, connectionId: string, service: string, path = '/' ): Promise<{ items: BrowseEntry[]; path: string; service: string }> { const data = await request({ url: `${BASE}/connections/${_encodedConnectionId(connectionId)}/browse`, method: 'get', params: { service, path }, }); return { items: data?.items ?? [], path: data?.path ?? path, service: data?.service ?? service }; } // ------------------------------------------------------------------------- // Monitoring / Metrics // ------------------------------------------------------------------------- /** * GET /api/workflow-automation/metrics * * Replaces both /api/workflows/{instanceId}/metrics * and /api/system/workflow-runs/metrics. */ export async function fetchMetrics( request: ApiRequestFunction, ): Promise { return await request({ url: `${BASE}/metrics`, method: 'get', }); }