/** * Scripted-flow decision logic for the echo driver. * * Pure, deterministic policy that maps a live {@link FlowExecution} snapshot * (plus an optional per-node script) to the single next flow-adapter mutation * the driver should emit this iteration. The echo driver calls this in a loop * per turn (see `echo.ts`), executing each returned action against the * runner-injected {@link FlowTestDriverHook} until it yields (`null`) or emits * a turn-ending action. * * The policy mirrors what a real LLM orchestrator would do, but without an * LLM: it reacts to state each turn, so a host can drive a flow to `complete`, * park it at an approval/input gate, and exercise the reject/recover path * simply by applying host mutations (`applyApproval`, `applyInput`, …) between * turns. No new protocol, no flow-contract change — it only calls the existing * FlowAdapter agent-tool operations. * * @docLink packages/bridge/drivers#scripted-flow * @since 3.6.0 */ import type { FlowExecution } from "@skaile/workspaces/types"; /** * What the scripted driver should do when a node reaches its decision point. * * - `auto` (default): follow the flow's `autonomousMode` — `complete_node` * when autonomous, else `request_approval`. A mandatory approval gate always * resolves to `request_approval`, even when autonomous (it cannot be skipped). * - `complete`: always `complete_node` (autonomous-style, even if the flow's * autonomousMode is false — the adapter allows it only when autonomous, so * use with autonomous flows). * - `approve`: always `request_approval` (park at an approval gate). * - `input`: `request_input` once; after the host applies input, fall back to * `auto` behaviour. * - `skip`: `skip_node` (optional nodes only). * - `fail`: `fail_node`. With `recoverable: false` (default) this is terminal. * With `recoverable: true` the node returns to `available`; the driver * re-emits the recoverable failure `failCount` times (default 1) and then * proceeds as `auto` — it never deadlocks on a recoverable fail. */ export type OnRun = "auto" | "complete" | "approve" | "input" | "skip" | "fail"; /** * What to do when a node returns to `available` after a rejection. * * - `retry` (default): re-drive it — `start_node` again, then its `onRun`. * - `fail`: `fail_node(recoverable: false)` — exercises the terminal path. */ export type OnReject = "retry" | "fail"; /** Per-node scripting directive. All fields optional. */ export interface FlowNodeDirective { onRun?: OnRun; onReject?: OnReject; /** For `onRun: "input"` — the input request shown to the host. */ input?: { prompt?: string; schema?: unknown; }; /** For `onRun: "fail"` — whether the failure is recoverable. Default false. */ recoverable?: boolean; /** * For `onRun: "fail"` with `recoverable: true` — how many recoverable * failures to emit before the node proceeds as `auto`. Default 1. Counted * from the node's own `errorHistory`, so the policy stays a pure function of * state (no driver-instance bookkeeping) and never deadlocks. */ failCount?: number; /** Summary text used for `complete_node` / `request_approval` / failures. */ summary?: string; } /** * A scripted-flow program. Empty (`{}`) means "canonical policy": start each * available node, then complete (autonomous) or request approval (interactive) * — enough to drive the autonomous-complete, approval-gate, and reject/recover * scenarios with no per-node scripting at all. */ export interface FlowScript { /** Per-node overrides, keyed by node id. */ nodes?: Record; /** Fallback directive for any node without its own entry. */ default?: FlowNodeDirective; } /** A single flow-adapter mutation the driver will execute. */ export type FlowAction = { op: "start_node"; nodeId: string; } | { op: "complete_node"; nodeId: string; summary: string; } | { op: "request_approval"; nodeId: string; summary: string; } | { op: "request_input"; nodeId: string; prompt: string; schema: string; } | { op: "skip_node"; nodeId: string; } | { op: "fail_node"; nodeId: string; message: string; recoverable: boolean; }; /** Whether an action ends the turn (driver stops looping after emitting it). */ export declare function isTurnEnding(action: FlowAction): boolean; /** Map a {@link FlowAction} to the `executeOp` argument bag the adapter expects. */ export declare function actionArgs(action: FlowAction): Record; /** * Decide the single next flow action for the current state, or `null` to yield * (nothing to do this iteration — wait for a host mutation). * * Priority order: resolve approved gates → advance running nodes → start (or * skip / terminally-fail) available nodes. Terminal flow states and gates with * no decision / input yet yield. * * Pure — the fail bookkeeping (how many recoverable failures a node has already * emitted) is read from the node's own `errorHistory`, so no external counter is * needed and a recoverable `fail` directive can never deadlock. */ export declare function decideFlowAction(state: FlowExecution, script: FlowScript, mandatoryGates?: ReadonlySet): FlowAction | null; /** * Parse a `[test-flow]` sentinel body into a {@link FlowScript}. An empty body * yields the canonical (empty) script. Malformed JSON is a hard error so tests * fail loudly rather than silently running the wrong policy. */ export declare function parseFlowScript(body: string): FlowScript; //# sourceMappingURL=flow-script.d.ts.map