import type { ActorAssertion, AssistantMessage, ImageContent, Model, TextContent, UserMessage, UserMessageAttachment } from "../llm/index.js";
import { type ToolTurnRepair } from "../llm/index.js";
import { type ExecutedToolCallOutcome } from "../loop/agent-loop.js";
import type { HaltRemainingDirective } from "../loop/types.js";
import type { AgentMessage, AgentTool, AgentToolCall, LoopMalformedToolUseRecovery, LoopThinkingOnlyRecovery, LoopTruncatedOutputRecovery, QueueMode, ThinkingLevel } from "../loop/types.js";
import { type EngineSegment } from "../../core/untrusted-text.js";
import type { CommittedFrameCarrier } from "../../core/frame-settlement.js";
import type { AbortResult, AgentHarnessEvent, AgentHarnessEventResultMap, AgentHarnessOptions, AgentHarnessOwnEvent, AgentHarnessResources, AgentHarnessStreamOptions, ExecutionEnv, PromptTemplate, Skill } from "./types.js";
/** [c209-C] 收口 C1/C2 — engine-provenance metadata a caller can stamp on the user message it
 *  mints (see the field docs on {@link UserMessage}). Threaded from `prompt`/`steer`/`followUp`/
 *  `nextTurn` options into the persisted message so derived views (auto-mode window, compaction
 *  serializer) work from METADATA, never from reminder-shape guessing. */
/** Who aborted a turn seat: the bare user halt or the steer-now accelerator. A run-level abort reaches the seat
 *  through the loop's composition listener and stamps nothing. */
export type SeatCutSource = "halt" | "steer";
/** Who asked the clean-stop latch to end the run: the bare user halt, or a soft stop ({@link AgentHarness.requestStopAfterTurn}). */
export type StopRequestSource = "halt" | "soft";
/** The harness's STATEMENT of what a run's halt (and its stop siblings) did — the result assembly reads this in
 *  place of any inference from message shape: `haltedByUser` ⇔ `turnCut === "halt"` ∨ `"halt" ∈ boundaryStop.sources`.
 *  Per prompt: every seat is cleared at the next `prompt()`, so a read after the prompt settled describes that run. */
export interface HaltConsumption {
    /** The loop's interrupt settlement consumed the cut of the LAST-published seat, stamped with its aborter. Absent: no
     *  seat cut was consumed on the final turn (never cut / a later seat cleared it / a run abort owned the ending / no stamp). */
    readonly turnCut?: SeatCutSource;
    /** The first boundary consult that stopped a non-unwinding loop, with the latch's sources at that instant. */
    readonly boundaryStop?: {
        readonly face: "pre_turn" | "post_turn";
        readonly sources: ReadonlyArray<StopRequestSource>;
    };
}
/** `prompt()` option: settle the transcript's trailing UNANSWERED tool batch through the loop's own batch pipeline
 *  BEFORE the prompt is delivered (a resumed run whose suspended batch still holds unanswered calls). `halt` settles
 *  every unanswered call as not-executed instead (the batch-halt form). No unanswered tail ⇒ a no-op. */
export interface TrailingToolBatchOptions {
    readonly halt?: HaltRemainingDirective;
}
/** `prompt()` option: called when the prompt and next-turn queue's messages enter the loop transcript, before
 *  their frames are emitted and committed. Not called when a trailing-batch settlement ends the run first — the
 *  caller then still owns whatever the prompt was carrying. */
export type PromptDeliveredCallback = () => void;
/** An engine-minted message that TRAILS the prompt's human message in the same request (a user-attachment frame):
 *  minted from its content blocks, always an engine note, carrying its delivery record. */
export interface TrailingMessageSeed {
    readonly content: ReadonlyArray<TextContent | ImageContent>;
    readonly attachment: UserMessageAttachment;
}
/** `prompt()` option: the messages that follow the human message, in order (see {@link TrailingMessageSeed}). */
export interface PromptTrailing {
    trailing?: readonly TrailingMessageSeed[];
}
export interface UserMessageProvenance {
    /** Exact char length of the engine-prepended prefix on `text` (first-frame reminders etc.).
     *  Sugar for the single segment `[0, chars)` — when `engineSegments` is also supplied, segments win. */
    enginePrefixChars?: number;
    /** [c209-C] R4: engine-trusted `[start, end)` UTF-16 segments of `text` — the general form of
     *  `enginePrefixChars` for engine frames that are NOT a pure head (a resume continuation keeps
     *  issue order, so a trusted frame may follow an untrusted fenced one). See UserMessage.engineSegments. */
    engineSegments?: ReadonlyArray<EngineSegment>;
    /** The ENTIRE message is engine-minted guidance (nudges/finalize/attachment bundles), not user speech.
     *  [c209-C] R4: mutually exclusive with `provenance` — on a double-stamp, `provenance` wins (see below). */
    engineMinted?: true;
    /** [c209-C] R3: engine-injected note whose content must stay VISIBLE in derived views (diagnostics,
     *  task notifications, steering, recall) but never under user authority — see UserMessage.provenance. */
    provenance?: "engine-note";
    /** The frame's CONTENT is caller/supervisor speech relayed by the engine (a TaskStream steer), not
     *  engine-authored guidance. Rides only WITH `provenance:"engine-note"` (the projection semantics are
     *  identical — visible, never `[user]` authority); what it changes is the run-control judgment: the
     *  human-halt hold below treats a caller-authored frame as USER-provenance input (always passes),
     *  while an engine-authored one is held. Minter-stated at the single relay seam that mints such
     *  frames — never inferred from text. Metadata only; not persisted on the message. */
    callerAuthored?: true;
    /** #1036 — the frame's injection-queue dedup key, persisted on the message it becomes
     *  ({@link UserMessage.engineNoteKey}) so a reader of the TRANSCRIPT alone can join an entry back
     *  to its frame. Rides only WITH `provenance:"engine-note"`. */
    engineNoteKey?: string;
    /** RB-30 terminal fix — an OPAQUE payload the engine rides on this queued message (the runner's
     *  task-notification frame). Never serialized, never interpreted by the harness: if the message is
     *  still undrained at agent_end, the payload is handed back through `onUndrainedEngineNotes` so
     *  the runner can pend it per session (the final-drain stranding window closes losslessly). */
    enginePayload?: unknown;
    /** design/171 §5.2 — WHO spoke this message (shared-session multiplayer attribution). Threaded
     *  verbatim onto {@link UserMessage.actor}: metadata only, never authority, never on the provider
     *  wire. The model-facing speaker envelope is rendered INTO `text` by the caller (the single
     *  projection point `projectHumanInput`) BEFORE the frame wrapping — the harness never rewrites
     *  text, so annotation stays adjacent to the frame it attributes. */
    actor?: ActorAssertion;
    /** backlog #389 — an OPAQUE record the caller rides on a CALLER-AUTHORED queued frame so a durable
     *  park can carry the instruction across the suspend instead of losing it. Same sidecar posture as
     *  {@link UserMessageProvenance.enginePayload} (a WeakMap entry, never a message field, never
     *  serialized, never interpreted here) but a different lane and a different fate: an engine payload
     *  is handed back for per-session redelivery, a park record is handed to the checkpoint mint through
     *  {@link AgentHarness.readParkableUserInputs}. The harness holds the FRAMED text; the record is what
     *  the parked-steer queue needs (pre-framing text + trust + inputId), which only the minting seam
     *  knows — hence opaque. */
    parkRecord?: unknown;
    /** design/373 — an IMMEDIATE-class ("now") frame: the steer-lane enqueue inserts it ahead of the
     *  first non-immediate frame (FIFO among immediates — the dequeue-priority form), the loop's
     *  pre-request re-check sweeps it into the imminent turn, and {@link AgentHarness.interruptTurn}
     *  may force the boundary for it. Class metadata only (a sidecar, judged at the mint) — it never
     *  rides the message or the wire, and it grants NOTHING by itself: the interrupt is a separate,
     *  caller-provenance verb. */
    immediate?: true;
    /** design/373 (#445 saturation prerequisite) — terminal-preference at the engine-note backlog
     *  cap: when the target queue's engine-note count is AT the cap, a frame carrying this mark
     *  displaces the OLDEST non-preferred engine-note frame instead of being refused — the displaced
     *  payload is handed to the undrained sink (the runner's lossless per-session park lane), so a
     *  watcher's event storm can delay its own batches but never crowd a terminal completion out of
     *  the run that is waiting on it (the pend store's terminal-preferred eviction, mirrored at the
     *  queue mouth). Only meaningful WITH `enginePayload`; a preferred frame among only preferred
     *  frames still gets the cap refusal (nothing displaceable). */
    capPreferred?: true;
}
/**
 * Harness-level recovery wiring (design/118 ④b/⑤). The loop's prompt-too-long seam wants a
 * replacement TRANSCRIPT; at the harness level the correct policy is a SESSION-persisted reduction
 * (e.g. a forced compaction) — otherwise the next turn's `createTurnState()` rebuilds the context
 * from the session and the in-memory shrink evaporates. So the harness contract is: `recover`
 * persists the reduction to the session and returns whether anything shrank; the harness then
 * rebuilds the turn state FROM the session and hands the loop the rebuilt context — transcript AND
 * system prompt (#474 ①), because the reduction pass may commit a prompt-epoch change of its own.
 */
export interface HarnessLoopRecovery {
    truncatedOutput?: LoopTruncatedOutputRecovery;
    malformedToolUse?: LoopMalformedToolUseRecovery;
    thinkingOnly?: LoopThinkingOnlyRecovery;
    /** design/128-follow-up (TB 尸检 T1-4): recover from a DEGENERATE-REPETITION cutoff instead of ending the
     *  run — the brain already cut the stream and stamped the turn errored; when `detect` matches, the loop
     *  nudges "don't repeat that, take a different approach" and runs another turn, bounded to `maxContinues`
     *  CONSECUTIVE times (default 2). The cut-off turn stays in the transcript (its salvaged text is real
     *  context). `detect` is injected by the caller (core owns the brain's DEGENERATE_MESSAGE constant). */
    degenerateOutput?: {
        detect: (message: AssistantMessage) => boolean;
        maxContinues?: number;
    };
    promptTooLong?: {
        /** Persist a context reduction to the SESSION; return true if anything shrank. Must not throw.
         *  `turnSignal` is the TURN-scoped abort (#474 ②): a policy whose work is expensive (a forced
         *  compaction's summary call) must ride it so a turn interrupt cuts the pass short instead of
         *  being waited out — and a policy that declines under it leaves the loop settling the turn as
         *  an interrupted one, never as the provider error's terminal. */
        recover: (attempt: number, turnSignal?: AbortSignal) => Promise<boolean>;
        /** Override the loop's default prompt-too-long classifier. */
        detect?: (message: AssistantMessage) => boolean;
        /** Max recovery retries per turn (loop default: 2). */
        maxRetries?: number;
        /** M1 CC-withhold parity: hold the failure's stream events until the recovery outcome is
         *  known (loop default: true). See LoopPromptTooLongRecovery.withholdErrorEvents. */
        withholdErrorEvents?: boolean;
    };
}
export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate, TTool extends AgentTool = AgentTool> {
    readonly env: ExecutionEnv;
    private session;
    private phase;
    /** Can an engine injection land at a turn boundary RIGHT NOW? A producer choosing a destination
     *  BEFORE it commits (the session notification route) needs this as a fact, not as a rejected
     *  promise afterwards. Three conjuncts, each a condition a later stage enforces: a turn must EXIST
     *  (`steer`/`followUp` throw while idle); the run must not be UNWINDING; it must not be STOPPING
     *  (`halt()` / `requestStopAfterTurn()` latch a source and the loop exits at the next boundary) —
     *  the phase reads `"turn"` through all of that. Nothing awaits between this read and the injection
     *  it gates, so a `true` cannot go stale under its caller. */
    get acceptsInjections(): boolean;
    /** design/74 R3-A: the one-way clean-stop latch — SET iff this set is non-empty. When set, the boundary consults
     *  answer true so the loop exits CLEANLY (emit `agent_end` + return) WITHOUT an abort/interrupt — the path a
     *  resource-slice suspend needs (an abort would trip the orphan-[INTERRUPTED] reconcile = corrupt resume). It is
     *  the SET of its requesters (`halt()` adds `"halt"`, `requestStopAfterTurn()` adds `"soft"`; both may be present,
     *  no first-writer arbitration) so the consumption record can say WHOSE stop the loop stopped for. */
    private readonly stopRequestSources;
    private get stopRequested();
    private boundaryStopConsumed?;
    /** The seat the loop published LAST — retract does not clear it, only the next publish replaces it (a fresh controller
     *  per turn, so identity carries the generation); the loop's cut-consumption report is accepted for THIS seat only. */
    private lastPublishedSeat?;
    private readonly seatCutSources;
    private trailingBatchEndedOn?;
    private turnCutConsumed?;
    /** backlog #389 — one-way latch set by {@link abort}. Read by `drainQueuedMessages`: no boundary may
     *  hand a queued frame to the model once the run is unwinding, and — crucially — none may SPLICE one
     *  out of the queue either (that would delete the terminal undrained account the abort path owes it). */
    private aborting;
    /** backlog #389 (HIGH-1) — frames a boundary has REMOVED from a queue but that
     *  have not yet entered the model's context.
     *
     *  The abort latch alone leaves a real window: `drainQueuedMessages` splices first and then awaits
     *  `emitQueueUpdate`, and even after it returns the loop has one more decision point before it injects
     *  anything. An `abort()` landing in either gap finds both queues empty, so the terminal account —
     *  which reads the queues — reports zero for a frame that will never reach the model. That is the very
     *  silent loss this ticket exists to close, one layer deeper.
     *
     *  A frame sits here from the splice until its `message_end` (the moment it is in the transcript);
     *  the terminal account, the abort snapshot and the durable-park migration all read it alongside the
     *  queues, so "accepted" is covered end to end rather than only while it happens to be in an array. */
    private drainedPendingInjection;
    private runAbortController?;
    private runPromise?;
    private pendingSessionWrites;
    private model;
    /** The ref that chose {@link model}, moved by the SAME assignment (one value, never two answers). */
    private modelRef;
    /** The run's thinking level; `undefined` = undeclared (provider default, no wire key) — distinct from "off". */
    private thinkingLevel;
    /** RB-30 terminal fix — runner-set sink for engine-note payloads left undrained at agent_end. */
    onUndrainedEngineNotes?: (payloads: unknown[]) => void;
    /** Runner-set sink for the provider boundary's tool-turn repairs ({@link ensureToolTurnInvariants}) — called
     *  with every request view that needed one (the transcript is never rewritten, so a broken turn is repaired, and
     *  reported, on each request; the sink owns its dedup). Advisory: a throwing sink never breaks the request. */
    onToolTurnRepaired?: (repairs: readonly ToolTurnRepair[]) => void;
    /** The assistant messages standing in the transcript when the current run began — what the request view reads as
     *  "before this run" (a message is the run's own unless it is in here). */
    private runHistory?;
    /** backlog #259 — USER-authored queue remnants at agent_end: steers/follow-ups whose receipts said
     *  "queued" but that no turn will ever drain (the run ended first). The engine-note sweep above
     *  hands ENGINE payloads back for redelivery; user inputs have no redelivery semantics (a steer
     *  aimed at a finished run must not silently fire at the next one), so their loss is ANNOUNCED
     *  instead — the runner surfaces it as an operator notice, closing the "accepted then silently
     *  dropped" window the #257 contract could only document. */
    onUndrainedUserInputs?: (counts: {
        steer: number;
        followUp: number;
    }) => void;
    /** design/176 — runner-set sink fired at the CONSUMPTION boundary, once per engine-note payload,
     *  in consumption order (steer/followUp drain and the turn-open nextTurn splice — the two points
     *  where a queued frame actually enters the model's input). The runner uses it to record the
     *  inbound peer hop chain of the message the model is about to see; writing at ENQUEUE time
     *  instead would let an outbound send inherit the chain of a message the model has not read yet.
     *  Advisory (swallow-guarded at the call sites via the runner's own closure); never re-entered
     *  for the same payload — consumption deletes the sidecar entry in the same step. */
    onEngineNoteConsumed?: (payload: unknown) => void;
    /** backlog #1273 — runner-set sink fired ONCE per CALLER frame at the moment the frame is in the transcript (its
     *  `message_end` append — the next request carries it), with the opaque park record the frame rode in on
     *  ({@link UserMessageProvenance.parkRecord}). The consumption point of a caller input, deliberately NOT the drain:
     *  a drained frame can still be dropped before its turn starts (a halt latched or an abort landing after the drain)
     *  and goes back into the park / undrained account, so a drain-time report would announce an input the model never
     *  saw. Advisory (swallow-guarded); the record is released in the same step, so a delivered frame reports once. */
    onUserInputConsumed?: (parkRecord: unknown) => void;
    /** #1036 — THE RECEIPT SINK: a frame reached the TRANSCRIPT, reported at the `message_end` append
     *  that proves it, with its carrier and entry id. Advisory. */
    onFrameCommitted?: (carrier: CommittedFrameCarrier, entryId: string) => void;
    /** Runner-set HOLD — the single gate for every engine-authored injection against the run's live
     *  human-halt fact. When it reports true (a person's bare rejection halted the run at this
     *  boundary, and only that person's own input may continue it):
     *   · a NEW engine-authored steer/followUp is refused at entry (the caller's existing rejection
     *     path parks the payload for the session's next run — same lane an idle-race refusal takes);
     *   · engine-authored frames ALREADY queued are not drained — they stay queued, delayed past the
     *     halted boundary: delivered at the next un-halted boundary if the user's own queued input
     *     legitimately continues the run, otherwise handed to the terminal sweep (payload frames park
     *     losslessly; payload-less boundary advisories end with the run, the same best-effort fate
     *     their mint sites already accept for a final turn).
     *  Caller/user-provenance input always passes — it is the awaited direction. One predicate here
     *  instead of a per-lane check at every injection site: a future engine lane that never heard of
     *  the halt is held by construction rather than by remembering. `nextTurn` is deliberately NOT
     *  gated: its splice opens a NEW run (idle-park redelivery), and a halt belongs to the run that
     *  minted it. */
    engineInjectionsHeld?: () => boolean;
    /** The provider boundary's tool-turn law: the repaired view goes to the brain, the repairs to the runner's sink. */
    private holdToolTurnLaw;
    /** RB-30 R2 belt-and-braces: runner-callable idempotent recovery for exit paths that reach
     *  neither agent_end nor abort() (a pre-prompt throw after prepare). Swept entries are removed,
     *  so a double call is a no-op. */
    recoverUndrainedEngineNotes(): void;
    /** RB-30: shared recovery sweep — collects engine-note payloads from the given queues
     *  in DELIVERY order (steer before followUp, each queue forward — the live loop serves steering
     *  first, so the recovered redelivery must not present "later" frames ahead of "now/next"),
     *  removes those entries, and hands the payloads to the runner sink. Called from BOTH terminal
     *  paths: the natural agent_end AND abort() — which does NOT clear the queues: #389 retired that
     *  clearing, so they are LEFT STANDING (the durable-park migration reads them AFTER abort()
     *  returns) and the abort-side pass is the belt for an exit that never reached agent_end. */
    private sweepUndrainedEngineNotes;
    /**
       * How much of a PERSON's input is currently accepted-but-unserved, per lane — the one predicate behind
       * both readers below, so "a person's input is waiting" cannot come to mean two things. Engine-AUTHORED
       * frames are excluded, and the IN-FLIGHT slot counts (a frame spliced out of a queue and never injected
       * is as pending as one that never left it).
     */
    private pendingUserInputsByLane;
    /**
     * Is a PERSON's input waiting for this run's NEXT TURN BOUNDARY? The live read face over
     * {@link pendingUserInputsByLane}, for the producer that must decide whether to YIELD that boundary
     * sooner — the shell's auto-backgrounding leg.
     *
       * THE STEER LANE ONLY: the follow-up lane's contract is delivery at the run's natural
       * would-otherwise-stop seat, which backgrounding a command cannot bring forward.
       * `announceUndrainedUserInputs` still reads both lanes.
     *
       * Synchronous and side-effect-free by contract, the same posture as {@link pendingInjectionCount}:
       * nothing is awaited between the read and the action it gates. An UNWINDING run answers `false`.
     */
    hasPendingUserInput(): boolean;
    /** backlog #259 — what remains in the steer/follow-up queues AFTER the engine-note sweep is USER
     *  input that was accepted ("queued") and will never be consumed: the run reached agent_end first.
     *  User inputs have no redelivery semantics (unlike engine notes — a steer aimed at a finished run
     *  must not fire at the next one), so the loss is ANNOUNCED, never silent. The window is a narrow
     *  race (an injection landing after the loop's final queue check), which is exactly why it needs a
     *  loud terminal account rather than an e2e reproduction. Returns the counts for the settled frame. */
    private announceUndrainedUserInputs;
    private systemPrompt;
    /** S4: physical system blocks (static per leg, additive — see AgentHarnessOptions.systemBlocks). */
    private systemBlocks;
    private streamOptions;
    private getApiKeyAndHeaders?;
    private runtime?;
    private resources;
    private tools;
    private activeToolNames;
    private steerQueue;
    private steeringQueueMode;
    private maxOutputTokens?;
    private maxOutputTokensPerCall?;
    private stallTimeoutsPerCall?;
    private abortResultDetails?;
    private loopTrace?;
    private onToolInputValidationFault?;
    private onToolMediaRefused?;
    private resilience?;
    private maxToolConcurrency?;
    private streamingToolExecution?;
    private followUpQueue;
    private followUpQueueMode;
    private nextTurnQueue;
    private loopRecovery?;
    /** roadmap #5 (CC Stop hook): the runner-wired stop gate — see {@link setStopGate}. */
    private stopGate?;
    /** design/373 — the loop-published turn-scoped AbortController (live exactly while a main turn's
     *  work phase is in flight; `undefined` between turns). Read by {@link interruptTurn} only. */
    private turnInterruptSeat?;
    private handlers;
    constructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>);
    private getHandlers;
    private emitOwn;
    private emitAny;
    /**
     * Emit a hook event to its registered handlers and return a single result.
     *
     * **Last-write-wins:** handlers run in registration order; each non-`undefined` result overwrites
     * the previous one, and the LAST non-`undefined` result is returned (`undefined` = abstain, keeps
     * the prior winner). Handlers do NOT compose — registering two handlers that both return a result
     * for the same event silently drops all but the last. A handler that throws aborts the whole emit
     * (errors are not swallowed). Callers that need ordered, composable semantics for one event (e.g.
     * the design/37 two-phase tool gate: collect arg rewrites, then adjudicate policy) must funnel that
     * logic through a SINGLE handler rather than relying on multiple handlers + this fold.
     */
    private emitHook;
    private emitBeforeProviderRequest;
    private emitBeforeProviderPayload;
    private emitQueueUpdate;
    private startRunPromise;
    private createTurnState;
    private createContext;
    private createStreamFn;
    private drainQueuedMessages;
    /** #389 — drop these frames from the in-flight account (they went back on a queue, or they landed). */
    private unbookInFlight;
    /** #389 — a queued frame has reached the transcript: it is delivered, so it leaves the in-flight
     *  account and gives up its park record (a suspend must never re-park what the model has read). */
    private settleInjectedFrame;
    private createLoopConfig;
    /**
     * design/118 ④b/⑤: wire (or clear) the loop's recovery chains. The runner calls this after
     * construction — truncated-output auto-continue and the prompt-too-long recover policy are
     * PROFILE decisions (the harness only relays them to the loop).
     */
    setLoopRecovery(recovery: HarnessLoopRecovery | undefined): void;
    /**
     * roadmap #5 (CC Stop hook): wire (or clear) the stop gate — consulted ONLY when the run would
     * otherwise end (no tool calls, steer + followUp queues dry). Non-empty return = the run
     * continues with those messages injected; empty = the run ends. Policy (the `classic.Stop` dispatch,
     * consecutive-block cap, reason formatting) lives in the runner; the harness only supplies the
     * drain-point timing.
     */
    setStopGate(gate: (() => Promise<AgentMessage[]>) | undefined): void;
    private validateToolNames;
    private flushPendingSessionWrites;
    /**
     * #506 ㋐ — write the transcript's record of a TERMINAL provider failure (CC 2.1.250 parity, `Oo`/`CIt`).
     *
     * A SEPARATE object, never the live frame: `failure` is the engine's terminal error shell and every
     * other consumer of it (the run result, failover, telemetry, usage accounting) reads its real
     * `model`, so re-stamping that field in place would corrupt all of them. The record is minted here
     * with the reserved {@link SYNTHETIC_MESSAGE_MODEL} instead, which is what makes it recognizable — and
     * therefore droppable — at the request boundary and at the derived-state walks.
     *
     * Two CC fields are deliberately NOT copied, because this tree already states the same facts more
     * honestly: CC writes `stop_reason: "stop_sequence"` on an entry that stopped for no such reason (its
     * own consumers must read `isApiErrorMessage` to know better), and CC writes a zero `usage` with
     * nothing marking it synthetic. Here the stop reason stays `"error"` and `usageMissing` says the zeros
     * are a shell rather than a measurement — the same discipline the live frame already carries.
     *
     * BEST-EFFORT: a record of a failure must never become a second failure. The append can lose the
     * session's optimistic lock (another writer moved the leaf) or hit a storage fault, and the run is
     * already over — swallowing that leaves the pre-#506 behavior (no record) rather than converting a
     * provider outage into a harness throw on the teardown path.
     */
    private recordApiFailure;
    private handleAgentEvent;
    private emitRunFailure;
    private executeTurn;
    /** Returns the run's final assistant message — or `undefined` on exactly one arm (#504): a bare
     *  user halt ({@link halt}) stopped the run before any assistant output existed. Every other
     *  zero-assistant ending still throws (see the executeTurn tail). */
    prompt(text: string, options?: {
        images?: ImageContent[];
        settleUnansweredToolCalls?: TrailingToolBatchOptions;
        onDelivered?: PromptDeliveredCallback;
    } & UserMessageProvenance & PromptTrailing): Promise<AssistantMessage | undefined>;
    /** R2-③: an empty/whitespace-only injection with no images carries ZERO information — it
     *  must not mint a user frame (strict endpoints reject empty user content, and an empty follow-up would
     *  pointlessly extend the run by one turn). No-op, not a throw: injection callers are fire-and-
     *  forget lanes (wakeups, reminders) and a shell bug upstream must not crash the run. */
    private static emptyInjection;
    /** REF-B6 — the ONE injection enqueue behind steer/followUp/nextTurn: the R2-③ empty-injection
     *  short circuit, the user frame, the RB-30 engine-payload SIDECAR (a WeakMap entry, never a message field —
     *  pre-prompt exits recover these too), the queue push, and the broadcast. All three lanes ship in ONE
     *  `queue_update` event (see emitQueueUpdate), so they are one protocol contract and must stay one
     *  implementation: R2-③ and RB-30 each had to be patched in three places.
     *
     *  The idle check is deliberately NOT here: its message differs per lane, and nextTurn() legitimately has
     *  none (queueing the NEXT prompt while idle is exactly what it is for). */
    private enqueueInjection;
    /** The human-halt hold's ENTRY half (see {@link engineInjectionsHeld}): a new engine-authored
     *  injection during a halt is refused before it can queue — the refusal is the caller's park
     *  signal (the same posture as the backlog-cap refusal above: pend per session, redeliver on the
     *  session's next run; nothing is lost). Belt-and-braces beside the drain-side hold: today's
     *  engine lanes each check the halt fact before injecting, so this arm exists for the lane that
     *  does not — including the one not written yet. */
    private refuseHeldEngineInjection;
    /** design/373 — returns the minted queue frame (or `undefined` on the empty-injection no-op), so
     *  a caller-provenance immediate steer can hand the SAME frame to {@link interruptTurn} (the
     *  consumption guard is identity-keyed). Additive: every existing caller awaited `void`. */
    steer(text: string, options?: {
        images?: ImageContent[];
    } & UserMessageProvenance): Promise<UserMessage | undefined>;
    followUp(text: string, options?: {
        images?: ImageContent[];
    } & UserMessageProvenance): Promise<void>;
    /**
     * design/373 §3.4 — force the running turn's boundary FOR a queued immediate-class frame:
     * abort the CURRENT main turn (provider stream + tool batch — never the run, never boundary
     * housekeeping), whose loop-side settlement reconciles the cut and continues the run with the
     * frame at the queue head. Best-effort accelerator by contract: `false` means no interruption
     * happened AND none was needed — the frame is already consumed or already rides the imminent
     * boundary — so delivery semantics are exactly the non-interrupting ones either way.
     *
     * Guards, in order:
     *  - an unwinding run (`abort()` latched) owns its ending — never contest it;
     *  - CONSUMPTION guard (r4/R3-1): the frame must still sit in the steer queue. Drained/consumed ⇒
     *    it is already part of a turn's input — aborting THAT turn would cut the very delivery this
     *    verb exists to speed up. Same-tick check-then-abort (single-threaded: no window);
     *  - seat liveness: no published turn (idle tail / between turns / boundary housekeeping) ⇒ no-op,
     *    the frame rides the next boundary; an already-fired seat ⇒ idempotent no-op (one boundary per
     *    turn — later immediates join the same boundary batch).
     */
    interruptTurn(frame: AgentMessage): boolean;
    /**
     * design/373 (#504) — the BARE user interrupt, the CC Esc form: CUT the in-flight turn (when one
     * is in flight) and STOP the run at that manufactured boundary — no new model turn starts, the
     * run collects to a clean, resumable ending (never the orphan-[INTERRUPTED] run-abort reconcile).
     * The run-model equivalent of "the session stays alive waiting for the user's next input".
     *
     * Two halves, one call:
     *  - STOP — the one-way `requestStopAfterTurn` latch, now consulted at BOTH boundary faces
     *    (post-turn `shouldStopAfterTurn` and #504's pre-turn `shouldHaltBeforeTurn`), so the loop
     *    ends at whichever boundary comes first and never starts another provider request;
     *  - CUT — the design/373 S1 turn seat, aborted WITHOUT the frame-consumption guard: unlike
     *    {@link interruptTurn} (an accelerator FOR a queued immediate frame, guarded so it can never
     *    cut the delivery it exists to speed up), a bare halt is not delivering anything — there is
     *    no frame whose consumption could make the cut self-defeating, so the seat's own liveness is
     *    the only guard. The cut settles exactly as every S1 cut does: finished tool calls keep
     *    their REAL results, never-started ones settle as paired interrupted results, the
     *    CC-verbatim interruption marker lands.
     *
     * Returns `{ turnCut, accepted }` — `turnCut` `true` iff THIS call aborted a live turn seat (an
     * in-flight provider stream / tool batch will settle as interrupted; the completion-race arm is
     * honest: a turn whose work already finished when the abort landed ends normally and the run
     * still stops at its boundary). `false` = nothing was in flight (between turns / boundary
     * housekeeping / a boundary already being manufactured) — the stop half alone answers, at the
     * imminent boundary. `accepted` (#504 codex r3) is the ATOMIC ownership bit: `true` iff this
     * call latched the stop on a run whose harness-channel abort had NOT already fired — captured
     * BEFORE the seat abort dispatches, so a re-entrant abort fired by the cut's own listeners can
     * never rewrite the answer. The unwinding arm answers `{ turnCut:false, accepted:false }`: an
     * abort-owned ending is never this verb's claim (the runner's attribution seat keys on it).
     *
     * Guards, in order (the interruptTurn discipline, minus the frame guard):
     *  - idle ⇒ typed refusal (the steer posture — misuse must be loud; the runner's birth-window
     *    poll owns the not-yet-started arm);
     *  - an unwinding run (`abort()` latched) owns its ending — never contest it, and never latch a
     *    stale stop onto a harness a later `prompt()` would inherit... (the latch is per-prompt
     *    reset, but the honest answer while unwinding is still "nothing for this verb to do");
     *  - stop latch, THEN seat: latch-before-abort ordering is load-bearing — the cut's settlement
     *    consults the latch at its own boundary, so the stop is provably in place when the forced
     *    boundary arrives (the enqueue-then-abort discipline, one lane over).
     *
     * Queued injection frames are deliberately NOT touched (CC 2.1.223 `control_request:"interrupt"`
     * keeps the queue and answers `still_queued` — see {@link abort}'s recorded ruling): whatever
     * the queues hold settles by the run's ordinary terminal contract for accepted-but-undelivered
     * input (durable park when a suspend seat exists, else the loud undrained accounts).
     */
    halt(): {
        turnCut: boolean;
        accepted: boolean;
    };
    /** The ONE seat-cut dispatch behind both verbs: stamp the aborter FIRST (a seat is never aborted twice), then abort.
     *  A run abort fired INSIDE this dispatch (a listener on the cut signal tearing the run down) was fired BY the cut —
     *  the loop takes the run-terminal path and never reports that settlement, so the consumption is recorded here, the
     *  only point that can see the causation (synchronous, single-threaded). A LATER abort is the run's own ending. */
    private cutSeat;
    /** The harness's statement of what this run's halt (and its stop siblings) did — {@link HaltConsumption}. Readable
     *  during the run and after `prompt()` settled (cleared by the NEXT prompt). */
    haltConsumption(): HaltConsumption;
    /** The ONE consult behind both boundary faces: a set latch answers true, and the FIRST true answer on a run that is
     *  NOT unwinding is the consumption (the loop exits on it), recorded with the latch's sources at that instant. The run
     *  signal always wins (the loop's cut arms' rule): an unwinding run ends FOR its abort — the latch's true answer is
     *  incidental (the next turn's abort guard would have exited anyway), so the attribution is withheld, not the exit. */
    private consultStopLatch;
    /** The loop's report that a seat's cut settled its turn (the loop-config seam). Identity is checked against the
     *  LAST-published seat, never the live one (the arms report around retraction); any other seat is refused loudly. */
    private noteTurnCutConsumed;
    /**
     * design/373 §3.3 (the final-commit-point double check) — how many queued injection frames a
     * boundary drain could deliver RIGHT NOW: both lanes, minus frames the current state refuses to
     * drain (an unwinding run drains nothing — those frames belong to the terminal account; a live
     * human-halt hold skips engine-authored frames). Synchronous and side-effect-free by contract:
     * the loop reads it between its last drain and the terminal commit, with zero awaits.
     */
    pendingInjectionCount(): number;
    /** design/373 §3.4-3 — the pre-request re-check's drain half (see the loop-config seam): sweep
     *  queued immediate-class frames into the imminent turn's batch, merged at the class position
     *  (after immediates already pending, ahead of everything else — FIFO within the class holds
     *  across the drain/re-check split). Same booking discipline as {@link drainQueuedMessages}
     *  (in-flight account, queue_update broadcast, consumption sink, throw-rollback); the hold skips
     *  engine-authored immediates exactly as the boundary drain would (a theoretical lane today —
     *  no engine mint issues immediates — held by construction rather than by remembering). */
    private absorbImmediateSteering;
    nextTurn(text: string, options?: {
        images?: ImageContent[];
    } & UserMessageProvenance): Promise<void>;
    appendMessage(message: AgentMessage): Promise<void>;
    /** The loop's `afterToolCall`: one `tool_result` hook emit per executed call (every registered handler, one fold). */
    private afterToolCallHook;
    /**
     * Finalize a call this harness's caller executed OUTSIDE a turn (a resumed, already-approved call) exactly as the
     * loop finalizes its own: the same {@link finalizeToolOutcome} over the same `tool_result` emit, so the call meets the
     * same handlers, the same patch application, the same throwing-hook posture and the same media admission as a live one.
     */
    finalizeToolCall(toolCall: AgentToolCall, args: unknown, executed: ExecutedToolCallOutcome): Promise<ExecutedToolCallOutcome & {
        toolCall: AgentToolCall;
    }>;
    getModel(): Model;
    /** The ref that chose the CURRENT model — {@link getModel}'s companion (a word re-resolves, an object pins). */
    getModelRef(): import("../../core/model-seat.js").ModelRef;
    /** The current level, or `undefined` when none was declared (provider default — no wire key). */
    getThinkingLevel(): ThinkingLevel | undefined;
    /** `ref` = the word that chose `model`; omitted ⇒ the object pins itself. The pair moves together. */
    setModel(model: Model, ref?: import("../../core/model-seat.js").ModelRef): Promise<void>;
    setThinkingLevel(level: ThinkingLevel): Promise<void>;
    setActiveTools(toolNames: string[]): Promise<void>;
    getSteeringMode(): QueueMode;
    setSteeringMode(mode: QueueMode): Promise<void>;
    getFollowUpMode(): QueueMode;
    setFollowUpMode(mode: QueueMode): Promise<void>;
    getResources(): AgentHarnessResources<TSkill, TPromptTemplate>;
    setResources(resources: AgentHarnessResources<TSkill, TPromptTemplate>): Promise<void>;
    getStreamOptions(): AgentHarnessStreamOptions;
    setStreamOptions(streamOptions: AgentHarnessStreamOptions): Promise<void>;
    /** RB-31 (design/148 S2) — swap the system prompt (+ physical blocks) for the NEXT turn snapshot,
     *  the setTools-mirror seam: a compaction-boundary center adoption changes the stable prefix at a
     *  legal boundary only (the running turn keeps its snapshot; createTurnState picks this up).
     *  Blocks and prompt swap ATOMICALLY here; the existing blocksSafe guard still verifies equality
     *  per snapshot, so a mismatched pair degrades to the single-block face, never a divergent wire. */
    setSystemPrompt(prompt: string, blocks?: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>["systemBlocks"]): void;
    setTools(tools: TTool[], activeToolNames?: string[]): Promise<void>;
    /**
     * design/74 R3-A: request a CLEAN stop at the next turn boundary. Unlike {@link abort} (a hard
     * AbortController cancel that — with no committed pause — drives the orphan-[INTERRUPTED] session reconcile),
     * this sets a one-way flag the loop reads via its `shouldStopAfterTurn` callback: the current turn finishes
     * its tool calls, the loop emits `agent_end`, and returns. The runner uses it for a resource-slice suspend
     * so the suspended session is left clean (resumable), never reconciled as interrupted. Idempotent.
     */
    requestStopAfterTurn(): void;
    /**
     * VENDOR.md mod #5 (fixed at de-vendoring, design/118 ⑤): drain session writes queued during an
     * ABORTED run. The harness intentionally does NOT flush on the abort path — the caller's interrupt
     * reconcile must append its synthetic tool_results first (closing the open tool_call batch), then
     * call this, so queued writes always land on a legal transcript. Safe to call anytime; no-op when
     * the queue is empty.
     */
    flushQueuedSessionWrites(): Promise<void>;
    /**
     * backlog #389 — the accepted-but-undelivered CALLER frames a durable suspend can carry forward,
     * in delivery order (steer before followUp, each queue forward — the order the live loop serves).
     * NON-DESTRUCTIVE by design: the mint that reads this may still fail to commit, and a frame dropped
     * for a checkpoint that never landed would be lost twice over. {@link dropParkedUserInputs} is the
     * second half, called only once the checkpoint is durably committed.
     *
     * Only frames carrying a park record are returned — engine-authored notes have their own lossless
     * lane (`onUndrainedEngineNotes` → pend per session) and must not be parked twice.
     */
    readParkableUserInputs(): unknown[];
    /**
     * backlog #389 — the COMMIT half of {@link readParkableUserInputs}: the named records are now durable
     * on a checkpoint, so their live frames leave the queues (identity-matched on the sidecar). Dropping
     * them here is what keeps the terminal `#259` account honest — a migrated instruction is not "accepted
     * then lost", and one this is never called for (a failed commit) still gets its loud undrained notice.
     * Returns the per-lane drop counts so the caller can subtract them from an account already taken.
     */
    dropParkedUserInputs(records: readonly unknown[]): {
        steer: number;
        followUp: number;
    };
    /**
     * backlog #389 (CC 2.1.223 `control_request:"interrupt"`, `CC:1064478-1064513`) — an interrupt does
     * NOT destroy accepted user input. CC's default keeps the queue and answers `still_queued`; only an
     * explicit `cancel_queued` cancels, with a per-uuid `cancelled` receipt. This harness had the opposite
     * polarity: both queues were emptied unconditionally and the contents handed back on a result object
     * with ZERO readers in the repo — so the #259 "accepted then never consumed must be LOUD" account (run
     * at `agent_end`) counted the emptied queues and found nothing to announce. An input accepted one
     * millisecond before `interrupt()` therefore vanished behind its own `queued` receipt.
     *
     * The queues are now LEFT STANDING. The abort latch above stops any surviving boundary from draining
     * (or silently splicing) them, and the terminal `agent_end` account — the one disclosure leg, unchanged
     * — reports them through `onUndrainedUserInputs` + the `settled` frame, exactly as it does for a run
     * that ended naturally with input still queued. Engine notes keep their own lossless recovery sweep,
     * which must still run HERE: it is the one path `agent_end` cannot cover on its own (this sweep also
     * serves a `waitForIdle` that never reaches `agent_end`).
     *
     * A sema `interrupt()` ends the RUN (CC's ends a turn inside a living session), so there is no state in
     * which "still queued" means "will run next" — hence no `cancel_queued` opt-in: its two settings would
     * be indistinguishable at the package face, which is the dead-knob shape this same ticket family flags
     * elsewhere. The CC-shaped "survives the pause" seat is the DURABLE PARK, served by
     * {@link readParkableUserInputs}.
     *
     * RECORDED, because the two halves must not be read as contradicting each other (adversarial r2
     * MEDIUM-6): the #259 notice says a stranded input is "NOT redelivered", and that is a statement about
     * the RUNNER's contract — a `TaskStream.steer` is never served to a later run, because the Runner
     * builds one harness per run and prompts it exactly once. A host driving `AgentHarness` DIRECTLY (it is
     * not on the package export surface) and re-prompting the same instance WILL see the leftovers served,
     * both the queued ones (true before this ticket too) and the in-flight ones the next `prompt()`
     * restores. Clearing them here instead is not available: the durable-park migration reads them AFTER
     * this method returns, so an abort that empties the queues would take the park's payload with it.
     */
    abort(): Promise<AbortResult>;
    waitForIdle(): Promise<void>;
    subscribe(listener: (event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal) => Promise<void> | void): () => void;
    on<TType extends keyof AgentHarnessEventResultMap>(type: TType, handler: (event: Extract<AgentHarnessOwnEvent, {
        type: TType;
    }>) => Promise<AgentHarnessEventResultMap[TType]> | AgentHarnessEventResultMap[TType]): () => void;
}
