/** Workflow authoring UI, builder, run, and result contract types. */ import type { AgentSessionEvent, KeybindingsManager, ModelRouterOutput, Theme } from "@bastani/atomic"; import type { Component, OverlayHandle, OverlayOptions, TUI } from "@earendil-works/pi-tui"; import type { TSchema } from "typebox"; import type { RunJournalOption } from "../durable/journal/resolution.js"; import type { RunStatus, StageAdapters, StageContext, StageOptions, WorkflowAction, WorkflowActor, WorkflowArtifact, WorkflowChainOptions, WorkflowChildResult, WorkflowContextMode, WorkflowDetailsMode, WorkflowDetailsStatus, WorkflowExecutionMode, WorkflowExitOptions, WorkflowInputSchemaMap, WorkflowInputValues, WorkflowMcpPort, WorkflowModelCatalogPort, WorkflowOutputSchemaMap, WorkflowOutputValues, WorkflowParallelOptions, WorkflowPersistencePort, WorkflowRunChildArgs, WorkflowSerializableObject, WorkflowSerializableValue, WorkflowTaskOptions, WorkflowTaskResult, WorkflowTaskStep } from "./authoring-contract-stage.js"; import type { EffectiveBudget, WorkflowBudget } from "./budget.js"; export type WorkflowCustomUiComponent = Component & { dispose?(): void; }; export type WorkflowCustomUiTui = TUI; export type WorkflowCustomUiTheme = Theme; export type WorkflowCustomUiKeybindings = KeybindingsManager; export type WorkflowCustomUiOverlayOptions = OverlayOptions; export type WorkflowCustomUiOverlayHandle = OverlayHandle; export type WorkflowCustomUiFactory = (tui: TUI, theme: Theme, keybindings: KeybindingsManager, done: (value: T) => void) => WorkflowCustomUiComponent | Promise; export interface WorkflowCustomUiOptions { /** * Ask the host to render as an overlay. Workflow graph hosts accept this: the * attached stage chat mounts the widget on its ordinary custom-UI slot, which * already keeps the stage transcript visible behind it, instead of opening a * nested overlay above the graph chrome. */ readonly overlay?: boolean; /** AbortSignal to programmatically dismiss the custom UI. */ readonly signal?: AbortSignal; /** Overlay positioning/sizing options. Can be static or a function for dynamic updates. */ readonly overlayOptions?: OverlayOptions | (() => OverlayOptions); /** Called with the real overlay handle after an overlay is shown. */ readonly onHandle?: (handle: OverlayHandle) => void; /** * Workflow-only replay identity. Recommended whenever widget state or * semantics can change without the callsite changing. Do not include secrets; * the runtime stores only a hash. */ readonly replayIdentity?: string; /** Safe display-only label for graph/status surfaces. Defaults to "Custom TUI prompt". Not part of replay identity. */ readonly label?: string; } export interface WorkflowUIContext { input(prompt: string): Promise; confirm(message: string): Promise; select(message: string, options: readonly T[]): Promise; editor(initial?: string): Promise; custom(factory: WorkflowCustomUiFactory, options?: WorkflowCustomUiOptions): Promise; } export interface WorkflowUIAdapter { input(prompt: string): Promise; confirm(message: string): Promise; select(message: string, options: readonly T[]): Promise; editor(initial?: string): Promise; custom(factory: WorkflowCustomUiFactory, options?: WorkflowCustomUiOptions): Promise; } export interface WorkflowRunContext, TOutputs extends WorkflowOutputValues = WorkflowOutputValues> { readonly inputs: Readonly; /** Stable owning workflow-run id for durable run-scoped artifacts. */ readonly runId?: string; readonly cwd?: string; exit(options?: WorkflowExitOptions): never; stage(name: string, options: StageOptions & { readonly schema: TSchemaDef; }): StageContext; stage(name: string, options?: StageOptions): StageContext; task(name: string, options: WorkflowTaskOptions): Promise; chain(steps: readonly WorkflowTaskStep[], options?: WorkflowChainOptions): Promise; parallel(steps: readonly WorkflowTaskStep[], options?: WorkflowParallelOptions): Promise; workflow(definition: WorkflowDefinition & TDefinitionBrand, ...args: WorkflowRunChildArgs): Promise>; readonly ui: WorkflowUIContext; /** * Durable cached tool execution. Runs arbitrary TypeScript code and caches * the result durably so completed side effects are not repeated on resume. * Only `ctx.*` blocks (tool, ui, stage, task, chain, parallel, workflow) * produce durable checkpoints. * */ tool: WorkflowToolPrimitive; } /** Serializable process details copied from an exhausted `ctx.tool` callback error. */ export interface WorkflowToolError extends WorkflowSerializableObject { readonly name: string; readonly message: string; readonly exitCode?: number; readonly stdout?: string; readonly stderr?: string; } export interface WorkflowToolSuccess extends WorkflowSerializableObject { readonly ok: true; readonly value: TValue; readonly attempts: number; readonly cached: boolean; } export interface WorkflowToolFailure extends WorkflowSerializableObject { readonly ok: false; readonly error: WorkflowToolError; readonly attempts: number; readonly cached: boolean; } /** Typed result returned when `failureMode: "return"` is selected. */ export type WorkflowToolOutcome = WorkflowToolSuccess | WorkflowToolFailure; /** Options for `ctx.tool`. Throwing after exhausted retries remains the default. */ export interface WorkflowToolOptions { readonly failureMode?: "throw" | "return"; readonly retriesAllowed?: boolean; readonly maxAttempts?: number; readonly intervalMs?: number; readonly backoffRate?: number; /** Maximum duration in milliseconds for each callback attempt. */ readonly timeoutMs?: number; } export type WorkflowToolReturnOptions = WorkflowToolOptions & { readonly failureMode: "return"; }; export type WorkflowToolThrowOptions = WorkflowToolOptions & { readonly failureMode?: "throw"; }; /** * Cancellation handle handed to every `ctx.tool` callback. * * The signal aborts when the run is cancelled, when the run is gracefully quit, * or when this single tool node is aborted through the workflow quit/pause action * with the node's id or name. Callbacks that forward it to `fetch`, a child * process, or a network client unblock promptly; callbacks that ignore it are * abandoned after a bounded wait. */ export interface WorkflowToolContext { readonly signal: AbortSignal; } /** `ctx.tool` runs an async function and durably caches its serializable result. */ export interface WorkflowToolPrimitive { (name: string, args: Readonly>, fn: (toolCtx: WorkflowToolContext) => Promise, options: WorkflowToolReturnOptions): Promise>; (name: string, args: Readonly>, fn: (toolCtx: WorkflowToolContext) => Promise, options?: WorkflowToolThrowOptions): Promise; (name: string, args: Readonly>, fn: (toolCtx: WorkflowToolContext) => Promise, options?: WorkflowToolOptions): Promise>; } export type WorkflowRunFn> = (ctx: WorkflowRunContext) => Promise | TOutputs; export interface WorkflowRuntimeConfig { readonly maxDepth: number; readonly defaultConcurrency: number; readonly persistRuns: boolean; readonly statusFile: boolean; readonly statusFilePath?: string; readonly resumeInFlight: "ask" | "auto" | "never"; /** Resolved default budget for workflow runs. */ readonly budget?: EffectiveBudget; readonly worktree?: { readonly symlinkDirectories: readonly string[]; }; } export interface WorkflowWorktreeInputBinding { readonly gitWorktreeDir: string; readonly baseBranch?: string; } export interface WorkflowInputBindings { readonly worktree?: WorkflowWorktreeInputBinding; } export interface WorkflowDefinition> { readonly __piWorkflow: true; readonly __runInputs?: TRunInputs; readonly name: string; readonly normalizedName: string; readonly description: string; readonly autoAttach?: true; readonly heartbeatIntervalMinutes: number; /** Optional budget declaration resolved when this workflow runs. */ readonly budget?: WorkflowBudget; readonly inputs: WorkflowInputSchemaMap; readonly outputs?: WorkflowOutputSchemaMap; readonly inputBindings?: WorkflowInputBindings; run(ctx: WorkflowRunContext): Promise | TOutputs; } export type NoExtraOutputs = TActual & Record, never>; export interface WorkflowOverlayAdapter extends WorkflowSerializableObject { } export interface RunSnapshot extends WorkflowSerializableObject { } export interface ActiveRunEntry { readonly controller: AbortController; readonly children: readonly AbortController[]; } export interface CancellationRegistry { register(runId: string, controller: AbortController): void; registerChild(runId: string, controller: AbortController): void; abort(runId: string, reason?: unknown): boolean; abortAll(reason?: unknown): number; unregister(runId: string): void; isAborted(runId: string): boolean; } export interface RunContinuationOpts { readonly source: RunSnapshot; readonly resumeFromStageId: string; } export interface WorkflowParentRunLink { readonly runId: string; readonly stageId: string; readonly rootRunId: string; } export interface RunOpts { readonly adapters?: StageAdapters; readonly cwd?: string; readonly ui?: WorkflowUIAdapter; readonly executionMode?: WorkflowExecutionMode; readonly usePromptNodesForUi?: boolean; readonly confirmStageReadiness?: (request: { readonly runId: string; readonly stageId: string; readonly stageName: string; readonly signal: AbortSignal; }) => Promise; readonly store?: object; readonly persistence?: WorkflowPersistencePort; readonly mcp?: WorkflowMcpPort; readonly cancellation?: CancellationRegistry; readonly overlay?: WorkflowOverlayAdapter; readonly signal?: AbortSignal; readonly deferWorkflowStart?: boolean; readonly config?: WorkflowRuntimeConfig; /** Per-run budget override. Each field resolves over definition and config values. */ readonly budget?: WorkflowBudget; readonly journal?: RunJournalOption; readonly localWorkers?: import("../workers/public-options.js").LocalWorkerOptions; readonly admission?: import("../workers/public-options.js").WorkflowAdmissionOptions; readonly models?: WorkflowModelCatalogPort; readonly registry?: object; readonly depth?: number; readonly stageControlRegistry?: object; readonly toolControlRegistry?: object; readonly toolAdmissionBoundary?: object; readonly runId?: string; readonly continuation?: RunContinuationOpts; /** Who launched this run. A continuation inherits its source run's origin instead. */ readonly origin?: WorkflowActor; readonly modelOwner?: string; /** Who requested the resume that produced this continuation run. */ readonly resumeActor?: WorkflowActor; readonly parentRun?: WorkflowParentRunLink; readonly onRunStart?: (snapshot: RunSnapshot) => void; readonly onStageStart?: (runId: string, snapshot: StageSnapshot) => void; readonly onStageEnd?: (runId: string, snapshot: StageSnapshot) => unknown; /** * Receives every event from each stage's agent session, tagged with the stage's run and stage ids. * The stream continues across fallback-model replacement sessions and includes nested child * workflow stages, which report their child run id. Listener exceptions and rejections are ignored. */ readonly onStageSessionEvent?: (runId: string, stageId: string, event: AgentSessionEvent) => void; readonly onRunEnd?: (runId: string, status: RunStatus, result?: WorkflowOutputValues, error?: string, exitReason?: string) => void; } export interface WorkflowProgressSummary extends WorkflowSerializableObject { readonly completed?: number; readonly total?: number; } export interface WorkflowControlEvent extends WorkflowSerializableObject { readonly type?: "notify" | "needs_attention" | "interrupted" | "resumed"; readonly message?: string; } export interface WorkflowIntercomSummary extends WorkflowSerializableObject { readonly enabled?: boolean; readonly delivery?: "off" | "notify" | "result" | "control-and-result"; readonly parentSession?: string; } export interface WorkflowDetails extends WorkflowSerializableObject { readonly mode: WorkflowDetailsMode; readonly action?: WorkflowAction; readonly runId?: string; readonly status: WorkflowDetailsStatus; readonly context?: WorkflowContextMode; readonly results?: readonly WorkflowTaskResult[]; readonly output?: WorkflowOutputValues; readonly progress?: WorkflowProgressSummary; readonly artifacts?: readonly WorkflowArtifact[]; readonly controlEvents?: readonly WorkflowControlEvent[]; readonly intercom?: WorkflowIntercomSummary; readonly warnings?: readonly string[]; /** Actionable user guidance for accepted background execution. */ readonly message?: string; readonly error?: string; /** True when the run reached its terminal status through ctx.exit(). */ readonly exited?: boolean; readonly exitReason?: string; } export type StageStatus = RunStatus | "skipped" | "awaiting_input" | "blocked"; export interface StageSnapshot extends WorkflowSerializableObject { readonly id: string; readonly name: string; readonly status: StageStatus; /** Parent stage ids. Do not cache this array across store updates; the runtime may replace it before a stage starts. */ readonly parentIds: readonly string[]; /** Shared admission order with tool nodes in this run. */ readonly executionOrder?: number; /** Effective model id selected for this stage after fallback resolution. */ readonly model?: string; readonly startedAt?: number; readonly endedAt?: number; readonly durationMs?: number; readonly result?: WorkflowSerializableValue; readonly error?: string; /** Immutable automatic model selection, separate from the actual execution model. */ readonly routerSelection?: ModelRouterOutput; } export interface ToolNodeSnapshot extends WorkflowSerializableObject { readonly kind: "tool"; /** Stable durable node identity. */ readonly id: string; readonly name: string; /** Exact invocation arguments retained for read-only graph inspection. */ readonly args?: Readonly>; readonly argsHash: string; readonly ordinal: number; readonly parentIds: readonly string[]; readonly status: "pending" | "running" | "completed" | "failed" | "cached" | "cancelled"; readonly replayed?: boolean; readonly executionOrder?: number; readonly startedAt?: number; readonly endedAt?: number; readonly durationMs?: number; /** Full serializable callback result, when one was retained. */ readonly result?: WorkflowSerializableValue; /** Callback source captured from `fn.toString()` at registration. */ readonly source?: string; readonly resultSummary?: string; readonly error?: string; readonly attachable: false; } export interface RunResult extends WorkflowSerializableObject { readonly runId: string; readonly status: RunStatus; readonly result?: Partial; readonly error?: string; /** Tool node whose rejection supplied the selected terminal failure. */ readonly failedToolNodeId?: string; /** True when the run reached its terminal status through ctx.exit(). */ readonly exited?: boolean; readonly exitReason?: string; readonly stages: readonly StageSnapshot[]; readonly toolNodes: readonly ToolNodeSnapshot[]; } export type ResolvedInputs = Readonly & WorkflowSerializableObject; export interface GitWorktreeSetupOptions extends WorkflowSerializableObject { readonly gitWorktreeDir: string; readonly baseBranch?: string; readonly cwd: string; } export interface GitWorktreeSetupResult extends WorkflowSerializableObject { readonly worktreeRoot: string; readonly cwd: string; readonly repositoryRoot: string; readonly created: boolean; }