import type { Layer } from "effect"; import type { AgentToolUpdateCallback, ExtensionAPI, ExtensionContext, } from "@earendil-works/pi-coding-agent"; import { ArtifactStore, loadPreviousResult } from "../artifacts/index.ts"; import { compact } from "../shared/compact.ts"; import { buildWorkflowResultMessage } from "../presentation/result-text.ts"; import { SandboxRunner } from "../sandbox/index.ts"; import { prepareWorkflowScript } from "../scripting/index.ts"; import type { WorkflowInput } from "./input.ts"; import { prepareRun } from "./prepare.ts"; import { compactToolDetails, parseArgs } from "./result.ts"; import { settleRun } from "./settle.ts"; import type { ActiveRun, WorkflowDetails } from "./types.ts"; const errorText = (error: unknown) => (error instanceof Error ? error.message : String(error)).slice(0, 16 * 1024); export interface ExecuteHooks { settled(status: "completed" | "failed" | "aborted"): void; changed(): void; } export async function executeWorkflow( pi: ExtensionAPI, input: WorkflowInput, signal: AbortSignal, update: AgentToolUpdateCallback | undefined, context: ExtensionContext, active: Map, layer: Layer.Layer, hooks: ExecuteHooks, ) { let prepared; try { prepared = prepareWorkflowScript(input.script); } catch (error) { throw new Error(`Workflow script failed to parse: ${errorText(error)}`); } // Read before anything is created, like model preflight: an unreadable resume // target must not leave a half-started run behind. const previousJson = input.resume === undefined ? undefined : loadPreviousResult(input.resume); const run = await prepareRun(pi, input, prepared, signal, update, context, layer, active.keys()); const activeRun: ActiveRun = { get details() { return run.runtime.state.snapshot(); }, controller: run.runtime.controller, }; active.set(run.runId, activeRun); const inputs = compact({ args: parseArgs(input.args), previousJson }); const completion = settleRun(run.runtime, prepared, inputs, layer).finally(() => { run.progress.flush(); }); activeRun.completion = completion; hooks.changed(); try { await completion; } finally { const details = run.runtime.state.snapshot(); active.delete(run.runId); hooks.settled(details.status === "running" ? "failed" : details.status); hooks.changed(); } const details = run.runtime.state.snapshot(); const message = buildWorkflowResultMessage(details, run.runDir); if (details.status !== "completed") throw new Error(message); return { content: [{ type: "text" as const, text: message }], details: compactToolDetails(details), }; }