/** * src/engine/runs.ts — delegation run lifecycle (pure, node-only). * * Ported/adapted from the ZOB harness `delegation-monitor.ts` types + * `startDelegationRun`/`updateDelegationRun`/`finishDelegationRun`. This is * the engine-pure lifecycle: a run moves queued -> running -> complete | * failed | aborted | preflight_failed. The monitor (bounded state, trimming, * liveness, list/sort) and ledger (hash-only persistence) live in sibling * engine modules and consume these types. * * Zero @earendil-works/* imports, zero fs side effects (state is in-memory). */ import type { ChildChangedPathRef, ChildResult, DelegationFailureKind } from "../core/types.js"; export type DelegationRunSource = "delegate_agent" | "delegate_task"; export type DelegationRunMode = "single" | "parallel" | "chain"; export type DelegationRunStatus = "queued" | "running" | "steered" | "preflight_failed" | "complete" | "failed" | "aborted" | "escalated"; /** * B5 escalated runs are TERMINAL (the child exited) but actionable: the master * holds the consumed ask_master message and may `continueRun` with an answer. */ export declare function terminalRank(status: DelegationRunStatus): number; /** True for the five terminal (non-retryable) statuses. */ export declare function isTerminalStatus(status: DelegationRunStatus): boolean; /** True while a run is active and can still accept a steering message. */ export declare function isSteerableStatus(status: DelegationRunStatus): boolean; /** * A read-consistent snapshot of one delegation run. `agent`/`model`/`exitCode` * identify the child; `outputHash` is the sha-256 of the full child output * (hash-only ledger posture); `durationMs` is captured on finish. */ export interface DelegationRunView { id: string; parentToolCallId: string; source: DelegationRunSource; mode: DelegationRunMode; index?: number; agent: string; taskPreview: string; status: DelegationRunStatus; startedAtMs: number; endedAtMs?: number; outputPreview: string; stderrPreview: string; cwd?: string; sessionPath?: string; exitCode?: number; gatePassed?: boolean; gateErrors?: string[]; failureKind?: DelegationFailureKind; stopReason?: string; stopCondition?: string; errorMessage?: string; childChangedPaths?: ChildChangedPathRef[]; usage?: ChildResult["usage"]; model?: string; outputHash?: string; durationMs?: number; background?: boolean; /** Run id this run continues from (continue). Links the continuation chain. */ continuedFromRunId?: string; /** 1-based continuation turn count (1 for a fresh run, incremented on continue). */ turnCount?: number; /** * B5: sha-256 of the consumed ask_master escalation message (hash-only — * the body is NEVER stored on the run view, ledger, or events; it lives * only in the in-memory ChildResult.escalationMessage held by the caller). */ escalationHash?: string; /** Session-local authority marker. Restored ledger projections omit it. */ authoritativeCurrentRuntime?: true; /** * LIVE mid-run usage snapshot streamed from child `kind=turn` events * (cumulative turns, current contextTokens, model). Written by the engine * while the run is ACTIVE so widgets can show tokens before settle; the * settle path stays authoritative (`usage` overwrites the display at the * terminal transition). In-memory monitor view only — never persisted to * the ledger or attestations (explicit-field posture keeps it out). */ liveUsage?: { turns: number; contextTokens?: number; model?: string; atMs: number; }; /** * C3 model-scope warnings (non-blocking preflight notices, e.g. an * agent/class/inherited model outside the enabledModels allowlist). * Recorded on the run view only; never flips a status or gate. */ warnings?: string[]; /** * C4: path of the hash-only attestation sidecar * (`/attestations/.json`, schema * `pi-subagents.attestation.v1`) written best-effort at settle. Undefined * when ledger persistence is off or the write failed (never blocking). */ attestationRef?: string; } /** The engine's run collection: bounded in-memory state. */ export interface DelegationMonitorState { runs: DelegationRunView[]; maxRuns: number; } /** Head/tail-capped preview helper (copied from the harness). */ export declare function capPreview(text: string | undefined, limit?: number): string; /** Whitespace-compacted short task preview (copied from the harness). */ export declare function taskPreview(task: string, limit?: number): string; /** Elapsed duration of a run (ends now if not yet finished). */ export declare function delegationDurationMs(run: DelegationRunView, nowMs?: number): number; /** Generate a run id with a prefix (delegate by default). */ export declare function makeRunId(prefix?: string): string; /** Input accepted by `startDelegationRun`. */ export interface StartRunInput { id: string; parentToolCallId: string; source: DelegationRunSource; mode: DelegationRunMode; index?: number; agent: string; task: string; startedAtMs: number; cwd?: string; sessionPath?: string; background?: boolean; continuedFromRunId?: string; turnCount?: number; } /** * Create a run in the QUEUED state and register it in the collection. A run * id that already exists is replaced (latest wins), matching the harness. */ export declare function startDelegationRun(state: DelegationMonitorState, input: StartRunInput): DelegationRunView; /** Advance a queued run to the running state (no-op unless queued). */ export declare function markRunRunning(state: DelegationMonitorState, id: string): DelegationRunView | undefined; /** Patchable fields (id/startedAtMs are immutable after creation). */ export type DelegationRunPatch = Partial>; /** Apply a partial patch to an existing run; returns undefined when missing. */ export declare function updateDelegationRun(state: DelegationMonitorState, id: string, patch: DelegationRunPatch): DelegationRunView | undefined; /** * Finish a run with a terminal status. Requires an `endedAtMs` timestamp and a * status; `durationMs` is derived from `endedAtMs` when not supplied. */ export declare function finishDelegationRun(state: DelegationMonitorState, id: string, patch: DelegationRunPatch & { endedAtMs: number; status: DelegationRunStatus; }): DelegationRunView | undefined; /** Abort a run: marks it aborted with an optional message. */ export declare function abortDelegationRun(state: DelegationMonitorState, id: string, message?: string, endedAtMs?: number): DelegationRunView | undefined; /** sha-256 of the full child output (hash-only ledger / view posture). */ export declare function outputHashOf(output: string | undefined): string | undefined; /** * F4: detect a provider quota/rate-limit failure on an ALREADY-FAILED child * (non-zero exit, failed output gate, or runtime error). A successful child * whose output merely mentions quotas is never classified — the failure gate * keeps honest runs honest. Matches stderr, output, and errorMessage * case-insensitively against `usage limit` / `rate limit` / `quota`. */ export declare function detectProviderQuotaFailure(result: ChildResult): boolean; /** Compute an honest child failure kind from a settled child result. */ export declare function classifyChildFailure(result: ChildResult): DelegationFailureKind | undefined;