import type { Employee, ModelRegistry, WorkflowAttemptCompletion } from "../shared/types.js"; import { type DispatchResolutionDeps } from "./node-dispatch.js"; import type { JsonValue } from "./model.js"; import { type WorkflowLandingVerifier } from "./run-closure.js"; import { type WorkflowRepository } from "./repository.js"; import type { WorkflowRunDetail } from "./runtime.js"; import type { WorkflowSessionExecutor } from "./session-executor.js"; import type { WorkflowTodoApprovalMirror, WorkflowTodoDispatchOverride, WorkflowTodoLifecycle, WorkflowTodoSessionLink } from "./todo-ports.js"; export interface WorkflowRunnerOptions extends Pick { repository: WorkflowRepository; executor: WorkflowSessionExecutor; employees: () => ReadonlyMap; models: () => ModelRegistry; now?: () => string; onChange?: (change: { workflowId: string; runId: string; }) => void; callWorkflow: (input: { workflowId: string; caller: { workflowId: string; runId: string; nodeId: string; }; input: Record; idempotencyKey: string; itemIndex: number; todoId?: string; }) => Promise; /** Mirrors an Approval node's gate onto the run's bound Todo so the operator * decides it from Todos, not from Workflows. Absent = no Todo surface (the * gate still parks the run and is decidable through the workflow API). */ todoApprovals?: WorkflowTodoApprovalMirror; /** Links each phase session to the run's bound Todo so the run's spend rolls * up on that Todo. Absent = no attribution (the run still executes). */ todoSessions?: WorkflowTodoSessionLink; /** Reflects the run's own lifecycle onto its bound Todo, so no workflow author * has to write `update_work_item` into a phase prompt for the board to be * honest. Absent = no reflection (the run still executes). */ todoLifecycle?: WorkflowTodoLifecycle; /** Lets the run's bound Todo redirect the next attempt to another engine or * model. Absent = the node's own configuration decides. */ todoDispatch?: WorkflowTodoDispatchOverride; /** Proves that a commit a success End demanded really reached `main`, in the * checkout the run itself named. Absent = git in that checkout, which is the * answer everywhere but a test. */ landingEvidence?: WorkflowLandingVerifier; /** Engine sessions across the whole gateway that already hold the machine, * read fresh whenever a fan-out asks for room. Absent = no system ceiling: * the authored concurrency stands, bounded only by its schema maximum. */ activeEngineSessions?: () => number; } /** The Todo-facing ports live in todo-ports.ts; re-exported so the runner stays * the one import every implementer and caller already had. */ export type { WorkflowRearmTarget, WorkflowRevisionRequest, WorkflowRunReflection, WorkflowTodoApprovalMirror, WorkflowTodoDispatchOverride, WorkflowTodoLifecycle, WorkflowTodoSessionLink } from "./todo-ports.js"; export declare class WorkflowRunner { private readonly options; private readonly advances; constructor(options: WorkflowRunnerOptions); private now; private detail; private changed; private failRun; start(runId: string): Promise; private nextAction; private applyInline; /** Keep the node's account of its own fan-out width true: the ceiling is read again on every * reconcile, so a record frozen at the first wave would claim a width the run stopped using. */ private recordFanoutWidth; /** One round of a bounded loop. The node stays `running` across rounds — the * service refuses a child whose caller node is not live — and settles only * when `continueWhile` stops asking or the bound runs out. Exhaustion routes * through the `exhausted` port rather than failing the run, so what happens * after N is whatever the author wired there. */ private reconcileIteration; private reconcileFanout; /** Mirror a parked gate onto the run's bound Todo (Gap 2: the operator picks * and approves from Todos), then wake its routed employee when it has one. * Best-effort — neither a mirror nor a notification failure may fail a run * whose gate is already parked and decidable through the workflow API. * Reached only from the transition INTO parked, so an employee notification * fires once per gate rather than on every recovery sweep. */ private mirrorApproval; /** Reflect the run's own lifecycle onto its bound Todo. The platform owes the * board this: until now the ONLY reason a bound Todo moved was an author * hand-writing `update_work_item` into a phase prompt, so one forgotten * instruction left a merged Todo reading `assigned`. Best-effort — the Todo * may have been closed or deleted since the run started. */ private reflect; /** A run that settles failed leaves an honest trace: `blocked`, plus which * node died and why. Both halves so an author needs neither a status * instruction in a prompt nor a record-failure node in the graph. */ private reflectFailure; /** Hand a rejection's feedback to the bound Todo so the work goes round again. * Best-effort like every other Todo-side write from a run: the run has already * stopped, and a Todo closed or deleted mid-run must not throw from here. */ private requestRevision; private rearmTarget; /** Attribute a phase session to the run's bound Todo, so the Todo's derived * spend covers what the pipeline cost — it sums `total_cost` over its linked * sessions, and until now a run's phases were linked to nothing. Best-effort: * the attempt is already dispatched and the Todo may have been deleted since * the run started, so a failure is logged rather than failing the run. */ private attributeSession; private dispatch; /** Hand a created attempt to the executor, settling it as a dispatch failure if the executor refuses it. */ private sendAttempt; private finish; private advance; private advanceNow; advanceCaller(workflowId: string, runId: string, nodeId: string): Promise; resumeWait(workflowId: string, runId: string, nodeId: string, now: string): Promise; /** Resume a `todo-comment` Wait early, because the operator answered. The * comment is not claimed: every run parked on that Todo resumes from it, and * a re-sweep is a no-op because the node is no longer `waiting`. */ resumeCommentWait(workflowId: string, runId: string, nodeId: string, comment: { id: string; body: string; attachments: ReadonlyArray<{ id: string; mime: string; }>; }, now: string): Promise; recoverDispatching(workflowId: string, runId: string, nodeId: string, attemptNumber: number): Promise; timeoutAttempt(workflowId: string, runId: string, nodeId: string, attemptNumber: number, at: string): Promise; retryNode(workflowId: string, runId: string, nodeId: string, idempotencyKey: string): Promise; decideApproval(input: { workflowId: string; runId: string; nodeId: string; decision: "approve" | "reject"; decidedBy: string; reason?: string; choice?: string; expectedRevision: number; }): Promise; private settleFailure; private activeRunForAttempt; private alreadySubmitted; remindDueAttempts(now: string): Promise; /** * The immediate half of the same problem the ladder solves on a timer: a turn * that ended on narration decided nothing, and the first rung is five minutes * away. Nudging inline costs nothing and usually ends the step right here. * * The nudge is itself a turn, so its own end re-enters `complete()`; the * persisted count is what bounds the loop. A dispatch that cannot be claimed * (the session took a message in the meantime) is not a failure worth * propagating: the caller falls back to arming the ordinary rung. */ private stopNudge; submit(input: { sessionId: string; outcome?: "success" | "failure"; fields?: unknown; summary?: string; }): Promise; extend(input: { sessionId: string; reason?: string; }): Promise; complete(event: WorkflowAttemptCompletion): Promise; } //# sourceMappingURL=runner.d.ts.map