import type { Role, WorkflowStageCreateRequest, WorkflowStagePart, WorkflowStageReorderRequest, WorkflowStageSummary, WorkflowStageUpdateRequest, WorkflowTemplateAuditEventSummary, WorkflowTemplateCreateRequest, WorkflowTemplateSummary, WorkflowTemplateUpdateRequest, } from "@pipeline/shared"; export type WorkflowDraft = { name: string; slug: string; description: string; }; export type WorkflowStageDraft = { stableKey: string; displayName: string; description: string; position: number; ownerRole: Role; runnerProfileKey: string; requiredInputs: string[]; expectedOutputs: string[]; acceptanceCriteria: string[]; requiresHumanApproval: boolean; retryPolicy: Record; parts: WorkflowStagePart[]; }; export type WorkflowStageFormPart = { key: string; type: string; title: string; payloadJson: string; acceptanceCriteriaText: string; }; export type WorkflowStageFormDraft = Omit & { requiredInputsText: string; expectedOutputsText: string; acceptanceCriteriaText: string; retryPolicyJson: string; parts: WorkflowStageFormPart[]; }; export type WorkflowStagePositionItem = { id: string; position: number; }; export type AdminWorkflowRow = { id: string; name: string; slug: string; status: string; version: number; updatedAt: string; stageCount: number; isDraft: boolean; isActive: boolean; isArchived: boolean; }; export type AdminWorkflowAuditRow = { id: string; eventType: string; createdAt: string; actorUserId: string | null; payload: string; }; export type StageDraftValidationErrors = Partial< Record< | "stableKey" | "displayName" | "description" | "runnerProfileKey" | "retryPolicyJson" | "parts", string > >; export function defaultWorkflowDraft(): WorkflowDraft { return { name: "", slug: "", description: "", }; } export function buildCreateWorkflowPayload( draft: WorkflowDraft, ): WorkflowTemplateCreateRequest { return { name: draft.name, slug: draft.slug, description: draft.description, }; } export function buildUpdateWorkflowPayload( draft: WorkflowDraft, ): WorkflowTemplateUpdateRequest { return { name: draft.name, slug: draft.slug, description: draft.description, }; } export function defaultWorkflowStagePart(): WorkflowStagePart { return { key: "stage-part", type: "instruction", title: "Stage instruction", payload: { prompt: "", config: {}, }, acceptance_criteria: [], }; } export function defaultWorkflowStageDraft(position = 1): WorkflowStageDraft { return { stableKey: "", displayName: "", description: "", position, ownerRole: "EDITOR", runnerProfileKey: "", requiredInputs: [], expectedOutputs: [], acceptanceCriteria: [], requiresHumanApproval: false, retryPolicy: { max_attempts: 1, backoff_seconds: 0, }, parts: [], }; } export function buildCreateWorkflowStagePayload( draft: WorkflowStageDraft, ): WorkflowStageCreateRequest { return { stable_key: draft.stableKey, display_name: draft.displayName, description: draft.description, position: draft.position, owner_role: draft.ownerRole, runner_profile_key: draft.runnerProfileKey, required_inputs: draft.requiredInputs, expected_outputs: draft.expectedOutputs, acceptance_criteria: draft.acceptanceCriteria, requires_human_approval: draft.requiresHumanApproval, retry_policy: draft.retryPolicy, parts: draft.parts, }; } export function buildUpdateWorkflowStagePayload( draft: WorkflowStageDraft, ): WorkflowStageUpdateRequest { return buildCreateWorkflowStagePayload(draft); } export function moveWorkflowStage( stages: readonly TStage[], stageId: string, targetIndex: number, ): TStage[] { const currentIndex = stages.findIndex((stage) => stage.id === stageId); if (currentIndex === -1) { return stages.map((stage, index) => ({ ...stage, position: index + 1, })); } const nextStages = [...stages]; const [stage] = nextStages.splice(currentIndex, 1); const clampedIndex = Math.max(0, Math.min(targetIndex, nextStages.length)); nextStages.splice(clampedIndex, 0, stage); return nextStages.map((nextStage, index) => ({ ...nextStage, position: index + 1, })); } export function buildReorderWorkflowStagesPayload( stages: readonly WorkflowStagePositionItem[], ): WorkflowStageReorderRequest { return { stage_ids: stages.map((stage) => stage.id), }; } export function buildAdminWorkflowRows( workflows: readonly WorkflowTemplateSummary[], ): AdminWorkflowRow[] { return workflows.map((workflow) => ({ id: workflow.id, name: workflow.name, slug: workflow.slug, status: workflow.status, version: workflow.version, updatedAt: workflow.updated_at, stageCount: workflow.stages?.length ?? 0, isDraft: workflow.status === "DRAFT", isActive: workflow.status === "ACTIVE", isArchived: workflow.status === "ARCHIVED", })); } export function buildAdminWorkflowAuditRows( events: readonly WorkflowTemplateAuditEventSummary[], ): AdminWorkflowAuditRow[] { return events.map((event) => ({ id: event.id, eventType: event.event_type, createdAt: event.created_at, actorUserId: event.actor_user_id ?? null, payload: JSON.stringify(event.payload ?? {}), })); } export function workflowDraftFromSummary( workflow: WorkflowTemplateSummary, ): WorkflowDraft { return { name: workflow.name, slug: workflow.slug, description: workflow.description, }; } export function workflowStageDraftFromSummary( stage: WorkflowStageSummary, ): WorkflowStageDraft { return { stableKey: stage.stable_key, displayName: stage.display_name, description: stage.description, position: stage.position, ownerRole: stage.owner_role, runnerProfileKey: stage.runner_profile_key, requiredInputs: stage.required_inputs ?? [], expectedOutputs: stage.expected_outputs ?? [], acceptanceCriteria: stage.acceptance_criteria ?? [], requiresHumanApproval: stage.requires_human_approval ?? false, retryPolicy: stage.retry_policy ?? {}, parts: stage.parts ?? [], }; } export function workflowStageFormDraftFromSummary( stage: WorkflowStageSummary, ): WorkflowStageFormDraft { const draft = workflowStageDraftFromSummary(stage); return workflowStageDraftToFormDraft(draft); } export function workflowStageDraftToFormDraft( draft: WorkflowStageDraft, ): WorkflowStageFormDraft { return { ...draft, requiredInputsText: draft.requiredInputs.join("\n"), expectedOutputsText: draft.expectedOutputs.join("\n"), acceptanceCriteriaText: draft.acceptanceCriteria.join("\n"), retryPolicyJson: JSON.stringify(draft.retryPolicy, null, 2), parts: draft.parts.map((part) => ({ key: part.key, type: part.type, title: part.title, payloadJson: JSON.stringify(part.payload ?? {}, null, 2), acceptanceCriteriaText: (part.acceptance_criteria ?? []).join("\n"), })), }; } export function defaultWorkflowStageFormDraft( position = 1, ): WorkflowStageFormDraft { return workflowStageDraftToFormDraft(defaultWorkflowStageDraft(position)); } export function workflowStageFormDraftToStageDraft( draft: WorkflowStageFormDraft, ): WorkflowStageDraft | null { const retryPolicy = parseJsonObject(draft.retryPolicyJson); if (retryPolicy === null) { return null; } const parts: WorkflowStagePart[] = []; for (const part of draft.parts) { const payload = parseJsonObject(part.payloadJson); if (payload === null) { return null; } parts.push({ key: part.key, type: part.type, title: part.title, payload, acceptance_criteria: splitLines(part.acceptanceCriteriaText), }); } return { stableKey: draft.stableKey, displayName: draft.displayName, description: draft.description, position: draft.position, ownerRole: draft.ownerRole, runnerProfileKey: draft.runnerProfileKey, requiredInputs: splitLines(draft.requiredInputsText), expectedOutputs: splitLines(draft.expectedOutputsText), acceptanceCriteria: splitLines(draft.acceptanceCriteriaText), requiresHumanApproval: draft.requiresHumanApproval, retryPolicy, parts, }; } export function splitLines(value: string): string[] { return value .split(/\r?\n/) .map((item) => item.trim()) .filter(Boolean); } export function parseJsonObject(value: string): Record | null { if (!value.trim()) { return {}; } try { const parsed = JSON.parse(value); if ( parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed !== null ) { return parsed as Record; } } catch { return null; } return null; }