/**
 * The run's CONTROL surface: `TaskStream` — the async-iterable handle `runTaskStream` returns, with
 * the steer / halt / approve / compact methods a caller drives a live run through. Separate from the
 * frames it yields (`task-event.ts`): a change to the handle's methods is not a change to the wire.
 * Layer 0 vocabulary; `types.ts` re-exports the name, so no consumer's import changes.
 */
import type { ActorAssertion } from "../internal/llm.js";
import type { CompactOutcome, TaskEvent } from "./task-event.js";
import type { TaskResult } from "./task-result.js";
/** A running task you can iterate for live events and await for the final result. */
export interface TaskStream extends AsyncIterable<TaskEvent> {
    result(): Promise<TaskResult>;
    /**
     * design/100 §E12 — resolves to the post-completion prompt **suggestions** (the shell's follow-up prompts),
     * or `[]` when `suggestNextPrompts` was off / the task did not complete / the pass produced nothing / it
     * failed. The pass is **fire-and-forget**: `result()` never waits for it, so a
     * shell does `await stream.result()` THEN `await stream.suggestions()`. **Never rejects.** The strings are
     * UNTRUSTED model output for display ONLY — never re-feed them to a model.
     */
    suggestions(): Promise<string[]>;
    /**
     * Inject a mid-task **steering** message (design/47) that the running task sees at the start of its
     * next turn (delivered via the harness steering queue). Resolves once queued; **throws** (code
     * `steering.not_running`) once the task has finished (teardown included) — never silently dropped. A
     * steer issued BEFORE the run has ISSUED ITS FIRST PROMPT is not refused: it is HELD for the birth
     * window and enters the queue as soon as the loop goes live, so the model sees it at the next turn
     * boundary like any other steer — it is refused if the loop ends first, or if that BOUNDED wait runs
     * out while the run still has not started (ruled 2026-08-05; a retry under the same `inputId` is then
     * clean — a refusal reserves nothing). By default the text enters as a normal **user** message (in an orchestration the caller IS
     * the task's user). Pass `trusted: true` ONLY for operator/system-level guidance: it is wrapped as a
     * `<system-reminder>` (elevated authority) — do NOT use it for caller/third-party content that could
     * carry a prompt injection. The seam gives the channel; the caller owns the judgement (design/43 §6).
     *
     * design/171 §5.2 — `actor` attributes WHO steered (a shared session's second voice): the text gets
     * the speaker envelope from the single projection point and the queued message carries the
     * metadata seat. Attribution only, never authority; absent = anonymous (bytes unchanged).
     *
     * design/171 §6.3 (additive) — `inputId` is the caller's correlation/idempotency key, the SAME key
     * space as the parked queue's `PendingSteerEntry.inputId` and the `human_input` event's `inputId`
     * (an ingress that already minted a message id passes it here, and the emitted frame carries it
     * VERBATIM instead of a fresh uuidv7). It exists because the two legs of one steering ingress must be
     * equally replay-safe: the parked leg has taken this key since the queue landed, so a retried request
     * that arrives while the task is LIVE was the only one that injected twice.
     *  - **Absent ⇒ nothing changes**: a uuidv7 is minted for the event, nothing is recorded for the call,
     *    and the delivered bytes are what they always were. Every pre-existing caller is on this arm.
     *  - **Replay ⇒ idempotent no-op**: re-steering an id this stream already ACCEPTED, with the same
     *    steer (same text, `trusted`, tier — absent reads `next` — and `actor`), injects nothing and emits no second
     *    `human_input` frame. What is remembered is exactly what QUEUED: a delivery that was refused
     *    (not running) can be retried under its own id, and a call whose DELIVERED payload is
     *    whitespace-only — which the harness discards without minting a frame; note a `trusted` steer is
     *    wrapped first, so it queues even for blank text — reserves nothing, leaving that id usable.
     *  - **Same id, not the same steer ⇒ typed throw** `steering.duplicate_input_id`: a key is not
     *    evidence of a replay, and two callers colliding on one id must not silently lose the second
     *    (the parked leg's `appendPendingSteer` judges and refuses it identically). The error carries
     *    `detail.differs` — the facets that differ (closed `STEER_REPLAY_FACETS`: `text`, `trusted`,
     *    `priority`, `actor`), compared on what the caller SENT, never on the framed bytes. With `text`
     *    among them it is another instruction: re-issue it under a fresh id. Without, it is the accepted one
     *    re-sent at another tier / trust / attribution, which acceptance does not change: a fresh id would
     *    queue it twice, so replay it with its original values to confirm the receipt.
     *  - **Bad value ⇒ typed throw** `steering.invalid_content`, never a silent fallback to "no id": the
     *    value domain is the parked leg's (a non-empty string of at most `MAX_STEER_INPUT_ID_CHARS`
     *    characters), so one key is accepted or
     *    refused the same way on both legs. Validated before the liveness check, like the parked leg's.
     *  - **Liveness outranks the key**: once the run's loop-liveness latch has flipped (the same signal
     *    the injection path stops polling on) a replay is refused `steering.not_running` like any other
     *    steer, never answered "already accepted" — the parked leg's row CAS answers `false` for a
     *    resolved checkpoint on a replayed id for the same reason. The one asymmetry, stated rather than
     *    papered over: in the sub-window where the harness has gone idle but the latch has not yet
     *    flipped, a FRESH steer polls (and is refused when the latch flips) while a replay answers
     *    immediately with the SAME outcome its original call reported — a key whose answer depended on
     *    microsecond timing would defeat its own purpose. Note what that outcome has always meant on this
     *    verb: ACCEPTED INTO THE QUEUE, not consumed by the model. A steer accepted in the last moments of
     *    a run can be stranded by the run ending before the next boundary drains it (true of every steer,
     *    keyed or not); a caller that needs delivery evidence reads the run's own events, not this receipt.
     *  - **The receipt does NOT distinguish the two**: a fresh accept and a replay both resolve `void`,
     *    exactly as `setPendingSteer` answers `true` for both. The observable difference is on the event
     *    stream (a fresh accept emits the `human_input` frame; a replay emits none), which is also where
     *    the parked leg's difference shows (a replay adds no queue entry, so the resume drains one frame).
     *  - **Honest window** (weaker than the parked leg's, deliberately stated): the live dedup domain is
     *    THIS stream object — one run leg, in this process. It is not persisted, so it does not span a
     *    restart, a replica, or a second `runTaskStream`/`resumeStream` call on the same session; and it
     *    holds only what the LIVE verb accepted, so an id already delivered by the parked leg (drained
     *    into this run's resume prompt) is NOT in it and WOULD inject again. A deployment that needs
     *    cross-leg or cross-process idempotency owns that half (its own key ledger), the same division
     *    of labor `notify`'s park-window dedup states.
     *
     * design/373 (additive) — `priority` is the steer face's injection tier, default `"next"`
     * (= the pre-373 behavior, byte-identical):
     *  - `"next"` — the running turn's next boundary (unchanged);
     *  - `"later"` — never folded into work in progress: delivered at the run's natural
     *    would-otherwise-stop seat (its own closing turn); a run that ends before that seat re-parks
     *    the input via the existing durable/undrained lanes (receipt semantics unchanged — "queued"
     *    still means accepted-at-this-moment, never "consumed"); a `"later"` steer rides the followUp
     *    lane, so its stranded account is `task.user_followup_undrained` (the accounts are LANE-keyed
     *    — same two-key split the settled frame uses);
     *  - `"now"` — `"next"`'s delivery guarantee PLUS the boundary is manufactured early: the frame
     *    takes the queue's class head and, when a turn is in flight, the engine CUTS that turn at a
     *    reconciled boundary (finished tool calls keep their real results; never-started ones settle
     *    as paired interrupted results; the CC-verbatim interruption marker lands) and the run
     *    CONTINUES with this input — never the run-level `interrupt()`. Best-effort accelerator:
     *    with nothing in flight (idle tail, between turns) delivery is exactly `"next"`'s. Every
     *    real cut is announced (`task.turn_interrupted` notice) and counts toward `maxTurns` —
     *    deliberately (the hard cap IS the interrupt storm's bound), which means a `now` issued at
     *    the run's FINAL allowed turn spends the remaining budget on the cut and the run then ends
     *    at the limit before the steer's own turn: the input takes the terminal lanes like any
     *    accepted-but-undelivered steer (durable park when a resource-suspend seat is eligible —
     *    redeemed on resume; otherwise the loud `task.user_steer_undrained` account). Same for the
     *    other terminal gates (budget/walltime) firing at the cut's own `turn_end`. The receipt
     *    never promised consumption; no budget is reserved for the interruptor.
     *  ⚠️ POWER FACE (§5-4b): passing `priority` through to a third-party caller GRANTS it the power
     *    to cut this run's in-flight turns (cancelling that turn's in-flight tool work). A deployment
     *    relaying steer must gate this option at its own face — relay IS authorization.
     *  An unknown value throws typed `steering.invalid_content` (bad-value loudness). The replay
     *  identity (`inputId` idempotency) includes the NORMALIZED tier: the same text replayed under
     *  the same id at a different tier refuses `steering.duplicate_input_id` (`detail.differs:
     *  ["priority"]`) — an "idempotent success" that silently skipped the interrupt would be a
     *  disposition lie.
     *
     * design/373 §4.3 (D2, ruling of 2026-08-24) — a deployment `userPromptSubmit` hook SCREENS this verb:
     * the steer face is a SERVICE entrance (third-party callers reach a running run through it), so
     * the deployment's prompt filter sits at the entrance, `ctx.source:"steer"` + `ctx.inputId` +
     * `ctx.actor` discriminated. Chain position: domain validation → liveness → `inputId` replay
     * short-circuit → screen → accept/enqueue → (now) interrupt — an idempotent replay of an ACCEPTED
     * id answers success WITHOUT re-running the hook (a re-run could answer differently and
     * retro-falsify the standing receipt). Hook `block` ⇒ **typed throw `steering.blocked_by_hook`**
     * (message carries the hook's own bounded reason); a hook timeout / cancellation / crash refuses
     * with the SAME code, fail-closed (a screen that did not answer has not cleared the input —
     * message discriminates the cause; the crash also reaches `onError` phase:"hook"). A blocked call
     * was never accepted: no `human_input` frame, no undrained account, and the `inputId` stays
     * UNBOOKED (retry freely, changed content included). `additionalContext` ⇒ prepended to the
     * delivered frame as the engine's own reminder (never inside the untrusted mid-turn frame). The
     * replay identity is over the CALLER's bytes — hook output never enters it. Engine-authored
     * frames (task notifications, diagnostics) and external `notify()` text are OUT of the screen's
     * domain (notifications are sanitized DATA, not prompts — the notify contract's ruling).
     */
    steer(text: string, options?: {
        trusted?: boolean;
        actor?: ActorAssertion;
        inputId?: string;
        priority?: import("./task-notification.js").SystemInjectionPriority;
    }): Promise<void>;
    /**
     * design/144 §2 — inject an EXTERNAL structured event into this run's task-notification lane, as a
     * `<task-notification type="external">` frame the model sees like any background completion. Core MINTS
     * the payload (`task_type` is cast to `"external"` — a caller can never impersonate an internal lane)
     * and every field is sanitized as UNTRUSTED data on the model face. Delivery rides the EXISTING
     * notification machinery, so the semantics are identical to internal producers:
     *  - design/373 — the injection LADDER is live on this lane (BREAKING vs the flat era, named):
     *    `"next"` = the running turn's next boundary (mid-work included — the previous behavior of
     *    every frame, now this tier's); `"later"` = never folded into work in progress — delivered at
     *    the run's natural would-otherwise-stop seat (its own closing turn), and a run that ends
     *    before that seat parks the frame per session for the NEXT run's turn-open (delivery then
     *    depends on a next run happening — a recorded structural honesty note, this engine owns no
     *    idle process); `"now"` is REFUSED typed (`notify.invalid_priority`) — turn-interrupt
     *    authority belongs to the caller-provenance steer face, never to the notification lane
     *    (callers wanting earliest-boundary delivery say `"next"`). ⚠️ The parameterless DEFAULT is
     *    `"later"` (the CC pending-notification default): an omitted priority moved from
     *    next-boundary to the closing seat with this design — callers that need boundary delivery
     *    must say `"next"` explicitly. NOTE for the flat era's callers: `"later"` was silently inert
     *    then (delivered at the boundary with no runtime signal saying otherwise) — the version is
     *    the ONLY discriminator of which behavior a build has. Delivery order within a tier is
     *    ARRIVAL order; consecutive buffered notifications drain as one boundary batch. `priority`
     *    rides the park path (pend records carry it; the turn-open batch is priority-major) and the
     *    uplink path;
     *  - duplicate events dedup on `task_id:status[:seq]` — pass a fresh `seq` per repeat event. The
     *    external lane keys in its OWN dedup domain (design/144 X1): an external event can never collide
     *    with (or pre-occupy) an internal lane's key, and vice versa. The key deliberately excludes
     *    `priority` (tier = delivery metadata, not event identity): a repeat under an in-flight key
     *    folds WHATEVER tier it names — the standing entry keeps the FIRST accept's tier — so
     *    re-tiering requires a fresh `seq` (a new occurrence), never a same-key resend;
     *  - a notify landing AFTER the run ended PARKS per session and the session's NEXT run delivers it
     *    (unlike {@link steer}, a finished task does not reject — the park lane is the contract). The park
     *    leg dedups on the same key (design/144 X4) with an HONEST WINDOW LIMIT: the dedup set spans ONE
     *    parked batch — it does not reach back across the live-queue/park boundary, and it resets when the
     *    next run drains the batch (a repeat sent after that drain is deliverable again);
     * Throws typed `notify.invalid_payload` on a malformed input, `notify.invalid_priority` on
     * `priority: "now"` (see above — a KNOWN value this lane refuses, distinct from an unknown one),
     * and `notify.not_running` when the run
     * never built its notification lane (prepare failed). Everything upstream of the event reaching this
     * process — transport, webhook/daemon wiring, inbound authn, durable parking for idle sessions — is
     * the DEPLOYMENT half (design/43: zero daemon semantics in core).
     */
    notify(payload: import("./task-notification.js").ExternalNotificationInput, opts?: {
        priority?: import("./task-notification.js").SystemInjectionPriority;
    }): Promise<void>;
    /**
     * Request a manual context **compaction** (design/99 MF-18 — the shell `/compact` button). Forces a
     * compaction at the NEXT safe turn boundary regardless of the auto-threshold (the only safe point — never
     * mid-turn, which would rewrite the session under an in-flight request); a `compacted` event with
     * `trigger:"manual"` is emitted when it actually compacts. Resolves once the request has been PROCESSED at
     * a boundary, or when the task ends first (whichever comes first — it never hangs). **Throws**
     * (`steering.not_running`) if the task hasn't started or has already finished. Idempotent-ish: concurrent
     * calls coalesce onto the next boundary (all waiters get the same outcome).
     *
     * ⚠️ BREAKING (MF-18: "processed" without "compacted"): resolves with a {@link CompactOutcome}
     * instead of `void` — "processed" alone told a consumer nothing when the attempt failed or was mooted
     * (the 202-then-silence fingerprint). Every outcome also has a stream/trace counterpart:
     * `"compacted"` → the `compacted` event; `"failed"` → `compaction.failed` trace (+ `onError(phase:
     * "compaction")` when an attempt was burned); `"blocked"` → `compaction.blocked` trace; `"noop"` →
     * `compaction.noop` trace; `"disabled"` → `compaction.disabled` trace; `"mooted"` →
     * `compaction.mooted` trace — or the task simply settled before any boundary processed the request
     * (the run-end backstop resolves "mooted" without a frame; the task's terminal events are the signal).
     *
     * design/145 §4 (additive): `opts.instructions` — the CC `/compact <instructions>` parity face.
     * Injected into the summarization prompt in the CC-verbatim shape (`\n\nAdditional Instructions:`),
     * REPLACING the spec-level `compaction.instructions` / default instructions for THIS pass (a
     * one-shot directive should out-rank standing configuration — sema judgment inside the superset;
     * CC has no spec level). A `preCompact` hook's `additionalInstructions` still APPENDS (CC 207
     * :348007 merge shape). Sanitized like every instructions channel (break-out tags defused, fence
     * sentinels capped, ≤2048 code points). Concurrent `compact()` calls coalesce onto one boundary:
     * the LAST caller's instructions win that pass (all waiters share its outcome — same coalescing
     * contract as before).
     *
     * `opts.signal` (additive) — CANCEL this compact request. Aborting resolves the caller's promise
     * `"mooted"` (honest: the request was withdrawn by the caller, no boundary served it):
     * - **before the pass starts** (still parked for a boundary): the request is simply un-parked — no
     *   summary call is made; when this was the only pending caller the whole manual request (and its
     *   pending instructions override) is withdrawn.
     * - **while the summary call is in flight**: the signal is composed into that pass's summary-call
     *   abort chain, so the in-flight model call is aborted. The task itself is unaffected (compaction
     *   is best-effort; the run continues on the un-compacted session). A caller-cancel abort is an
     *   EXTERNAL cause, not summarizer evidence — it is never counted toward the §17.4 consecutive-
     *   failure breaker and never fired through `onError` (same discrimination posture as the walltime
     *   soft-deadline abort; the trace counterpart is `compaction.mooted` with `reason:"cancelled"`).
     *   Coalescing caveat: coalesced callers share ONE pass, so one caller's cancel aborts the shared
     *   pass — every waiter of that pass then resolves `"mooted"`.
     * An already-aborted signal resolves `"mooted"` immediately without arming a request.
     */
    compact(opts?: {
        instructions?: string;
        signal?: AbortSignal;
    }): Promise<CompactOutcome>;
    /**
     * design/116 detach(CC mid-flight ctrl+b)— move a RUNNING tool call to the background.
     * Today only a detach-capable Bash honors it (the env adopts the child as a background shell; the tool
     * settles early with "moved to background; task_id=b*", and the G2b completion notification takes over).
     * Fire-and-forget and race-safe: a request landing before the tool reads its signal still detaches; one
     * landing after the tool finished is a no-op. Unknown toolCallIds are a no-op (nothing to detach).
     */
    detach(toolCallId: string): void;
    /**
     * design/383 §2.1 — the MID-SESSION memory-capture opt-out verb ("聊着聊着发现不该记" — the
     * design's primary scenario): from this call on, the running session commits NOTHING to the
     * long-term memory store, its already-committed contributions leave the consolidation candidate
     * set (the retroactive A2 arm), and the session window's residue on the writable memory root is
     * boundary-swept into control-plane quarantine (per-path named; a sweep failure REFUSES the call
     * loudly — the one-way record stands either way, so the failure mode is "opt-out on, residue
     * named", never a silent partial).
     *
     * ONE-WAY: no reverse verb exists at any layer; a repeat call is an idempotent `"existed"`. The
     * way back to capture is a NEW session (a deliberate product cost — "which turns count" has no
     * honest answer for a retroactive re-enable).
     *
     * AUTHORITY (steer-lane law): core does not authenticate — holding this stream IS the capability
     * handle, exactly as with {@link steer}; a service exposing this verb over the wire owns the
     * verified-principal / session-ownership check at its own face (a bare sessionId relay would let
     * anyone switch off anyone's memory: an availability attack). The call re-adjudicates the
     * per-principal entitlement AT FLIP TIME (`RuntimeCaps.allowMemoryOptOut` under
     * {@link RunnerDeps.memoryCapturePolicy}, fresh resolve — a denied flip refuses typed
     * `memory.capture_optout_denied` and leaves ZERO record residue). MODEL-UNREACHABLE by
     * construction (§2.6): no tool face, no MCP face, and governed workflow specs cannot spell it —
     * a model that could declare an opt-out could silently disable a user's memory; one that could
     * revoke it would pierce the privacy control.
     *
     * Typed refusals (the closed set, one per arm — rescan post-6.0.0-RC completed the roster):
     * `memory.capture_optout_denied` (entitlement), `memory.capture_optout_unpersisted` (the record
     * could not be durably written — a store-mark failure OR a failing §2.7 consolidation-epoch
     * bump, both refused rather than held in-process, which the first resume would silently break),
     * `memory.capture_optout_unavailable` (this run mounted no memory session — nothing to opt out
     * of), `memory.capture_optout_sweep_failed` (the record STANDS but the §2.3 boundary sweep could
     * not contain named residue paths — a repeat call is the retry lane),
     * `config.memory_capture_unsupported` (remote/per-run deployment shape with no
     * {@link RunnerDeps.memoryCaptureRecordStore} — the record would not survive to a resume
     * replica), `steering.invalid_content` (a non-string `options.reason`), plus the steer-family
     * `steering.not_running` once the task has finished. Resolves `{ outcome }`: `"created"` = this
     * call made the crossing (the `memory.capture_opted_out` notice minted); `"existed"` = already
     * recorded (idempotent; no second notice).
     */
    optOutMemoryCapture(options?: {
        reason?: string;
    }): Promise<{
        outcome: "created" | "existed";
    }>;
    /**
     * Change the running leg's PERMISSION MODE (`TaskSpec.permissionMode`'s live half — a shell's mode toggle, a serving
     * layer's `permission_mode` control frame). The requested word is rewritten on the leg's mode seat; the deployment's
     * fold is re-judged (`bypassPermissions` where `RuntimeCaps.bypassPermissions` is `false`, or `auto` with no
     * classifier face / a `false` deny bit, runs `default` and is announced `config.permission_mode_unavailable`); a leg
     * that became `auto` arms its classifier now — an arming that fails rolls the steer back (the word it replaced stands)
     * and the verb rejects with the arming's code; the NEXT tool call reads the new mode at the gate's arbitration point
     * (a tightening and a loosening both take effect one call later, on one seat), and a child leg derives its own
     * effective mode from this seat on its next read. Resolves the same three-member receipt `wiring_manifest.permissionMode`
     * carries — `{ requested, effective, foldedBy? }` — on EVERY call, moved or not, so a caller renders what the engine
     * says and never what it sent. Typed refusals: `steering.invalid_content` (a word outside the closed set) and the
     * steer-family `steering.not_running` once the task has finished. AUTHORITY (steer-lane law): holding this stream IS
     * the capability handle; a service exposing the verb owns the principal check at its own face — and a deployment that
     * must never let a caller reach `bypassPermissions` says so with the deny bit, which this verb honours.
     */
    setPermissionMode(mode: import("./ask-origin.js").PermissionMode): Promise<import("./mode-policy.js").PermissionModeReceipt>;
    /**
     * **RE-DIAL one MCP server** on the running task, the action `RefreshMcpTools` deliberately
     * is not (its contract is "never dials or re-dials"). Core holds no MCP connection across task
     * legs, so once a server's transport closes this leg is stuck with it: every one of its tools fails
     * fast and a refresh can only keep reporting `not_connected`. This is the verb a host hangs a
     * "Reconnect" control on. ONE NAMED SERVER, required — CC 2.1.259's control-channel request carries
     * a required `serverName` and has no all-servers form, and an all-servers form would CLOSE HEALTHY
     * connections to re-dial them (the stdio server owning a device or a port — the case this verb
     * exists for — is the one that cannot be re-dialed while its predecessor still holds the device).
     *
     * ⚠️ **A FAILED RE-DIAL LEAVES THE SERVER DOWN.** The old connection is closed BEFORE the new one is
     * dialed (no other order works for a device-owning server), so a dial that fails has spent a
     * working connection: the server stays disconnected, with its tools WITHDRAWN from the roster,
     * until another re-dial or the next leg's materialize — and calls in flight on the replaced
     * connection fail with it. Both are inherent to re-dialing; CC behaves the same way.
     *
     * Resolves {@link import("./mcp.js").McpReconnectResult} — `outcome` is one of three words, so a
     * consumer mapping it onto a three-state control answer needs no table: `reconnected` (the only
     * success), `refused` (the engine declined, or the dial failed — `reason` says which and
     * `status.errorCode` keys the next action), `not_declared` (no server of that name on this leg).
     * Throws, never silently no-ops: `mcp.reconnect_unavailable` when this run declared NO MCP servers
     * (a host maps that to "unsupported", not "refused"), `steering.invalid_content` on a missing or
     * non-string name, and the steer-family `steering.not_running` once the task has finished.
     * LEG-LOCAL: the result rides no checkpoint, and a durable resume takes a fresh first listing.
     */
    reconnectMcpServer(server: string): Promise<import("./mcp.js").McpReconnectResult>;
    /**
     * Hard-**interrupt** the running task — a real abort (not a best-effort hint), equivalent to firing the
     * task's `signal` (design/47). Refused typed (`steering.not_running`, the steer-family liveness code —
     * `cannot interrupt: …`) once the task's result is out, never silently dropped. Until then it is
     * answered — the teardown window after the loop's end included, where the consumer still sees a running
     * task: the run's ONE caller-stop gate (this verb, {@link halt}, {@link destroy}, the task's `signal`)
     * cuts any compaction pass still in flight — end-of-task, turn-boundary or prompt-too-long recovery,
     * reported `compaction_outcome{outcome:"mooted", reason:"cancelled"}` with that pass's own `trigger` — and
     * no further pass starts; a stop after the loop's end changes nothing else. **A run that has already given its answer
     * is not cancellable**: once its last turn ended `stopReason:"stop"` with no queued steer / follow-up
     * left to serve (a continuation the person had already asked for makes that "stop" work in progress,
     * not an answer), a stop landing on the run's
     * tail (this verb, the task's `signal`, the walltime or turns limit — one law) still aborts what is
     * left of that tail (a final-boundary compaction is mooted) but the run ends `completed` with the
     * answer as `result`, writes no interruption marker, mints no `limits.*` code, and says so once with
     * the `task.interrupt_unconsumed` notice. For a SOFT redirect that lets the
     * task keep running and adjust, use {@link steer} instead. For the CC-Esc "stop and wait for my
     * input" form — cut the turn, keep the ending clean and resumable — use {@link halt}.
     */
    interrupt(): Promise<void>;
    /**
     * design/373 (#504) — the BARE user interrupt, the CC Esc true form ("cut + stop"): CUT the
     * in-flight turn and STOP the run at that manufactured boundary, collecting to a clean,
     * resumable ending. The run-model equivalent of CC's session staying alive waiting for the
     * user's next input. Three verbs, three powers: {@link interrupt} hard-aborts the run (the
     * orphan-reconcile ending), {@link steer} with `priority:"now"` cuts the turn AND CONTINUES the
     * run with the steer text, `halt` cuts the turn and lets the run END awaiting the user.
     *
     * The CUT half is the design/373 S1 turn-scoped settle, verbatim: finished tool calls keep their
     * REAL results; never-started ones settle as paired interrupted results
     * (`errorKind:"interrupted_never_started"`); the CC-verbatim interruption marker lands in the
     * session as an engine-note user frame. Every REAL cut is announced (`task.turn_interrupted`
     * notice, `detail.cause:"user_halt"` — the steer-now landing keeps its own message and no
     * `cause` key). The STOP half: from acceptance on, NO new model turn starts — the loop ends at
     * whichever boundary comes first (both boundary faces consult the latch, the pre-turn one
     * included, so a halt landing between turns cannot buy one more turn). With nothing in flight
     * the cut half is a no-op (`turnCut:false`) and the stop half alone answers — no false marker.
     *
     * **Terminal form (#504 detail ①, ruled completed-with-marker over a new status/stop-reason
     * member):** the run ends `status:"completed"` with the additive result seat
     * {@link TaskResult.haltedByUser} `: true` as the discriminator. CC anchor: 250 has NO typed
     * "interrupted" terminal — Esc leaves a clean continuable session with the transcript marker as
     * the record, and the SDK result taxonomy (`error_during_execution`/`error_max_turns`/…) gains
     * no interrupt member; a new closed-set status/stop-reason row would therefore be a non-anchored
     * invention, while completed-plus-marker is also this repo's own precedent seat
     * ({@link TaskResult.haltedOnUserRejection} — "the model did not finish; the person stopped it
     * and the run awaits their direction"). Consumers discriminating "user stopped it" vs "ran to
     * completion" (billing, continuation copy) read the one boolean.
     *
     * **Queued-but-undelivered frames (#504 detail ②, ruled keep-don't-clear):** a halt clears
     * NOTHING. CC anchor: 250's `control_request:"interrupt"` handler keeps the command queue by
     * default and answers `still_queued`; clearing is the explicit `cancel_queued:true` opt-in
     * (`cleared_on_cancel`) — destruction is never the interrupt's default meaning. The run-model
     * translation: queued steer/follow-up frames stay queued through the cut, are NOT drained into a
     * turn that will never start, and settle by the run's EXISTING terminal contract for
     * accepted-but-undelivered input — durable park when a suspend seat exists, else the loud
     * per-run accounts (`task.user_steer_undrained` / `task.user_followup_undrained`). The receipt
     * semantics were always "queued = accepted, never consumed": the caller holds the bytes, the
     * account tells it what to re-send, and `inputId` idempotency makes host-side re-delivery on the
     * next run replay-safe. (A per-session durable park for non-suspend endings would be new storage
     * machinery — out of this slice, recorded.)
     *
     * **Resume hand-off (#504 detail ③, ruled the ordinary user-lane front door):** the next input
     * enters as the NEXT RUN's objective on the same session (`runTask`/`runTaskStream` with this
     * result's `sessionId`) — no special lane, no checkpoint. The halted transcript ends with the
     * interruption marker exactly where CC's session shows it (marker, then the user's next
     * message), so the resumed model reads the same shape a CC session shows after Esc. A
     * checkpoint-resume is neither minted nor needed: `"completed"` + `sessionId` IS the
     * continuation contract, and a marker-less halted transcript (nothing was in flight to cut) is
     * an ordinary clean boundary.
     *
     * Receipt: resolves `{ turnCut }` — `true` iff THIS call cut a live turn (the completion-race
     * arm is stated, not hidden: a turn whose work finished as the abort landed ends normally and
     * the run still stops at its boundary). Repeat calls are accepted and answer honestly
     * (`turnCut:false` once the stop is already latched); the notice is per-cut, the result seat
     * per-run. AUTHORITY (steer-lane law): holding this stream IS the capability — a deployment
     * relaying this verb owns its own gate (relay IS authorization; it cancels in-flight tool work
     * and ends the run's forward progress). No text ⇒ the `userPromptSubmit` screen has no domain
     * here (nothing enters the model). The late window, stated: a halt accepted
     * in the run's last moments (past the loop's final commit point) finds the run completing
     * naturally — nothing is cut and no boundary stops for it, so the result reports the natural
     * completion WITHOUT the `haltedByUser` seat and the engine mints `task.halt_unconsumed` (the
     * verb receipt and that notice are the halt's whole trace; the seat is signed from consumption,
     * never from acceptance — see {@link TaskResult.haltedByUser}). A halt after the loop's end but before
     * the result is out finds nothing to cut or stop and is answered the same way (`{turnCut:false}` +
     * `task.halt_unconsumed`). Every answered halt trips the run's caller-stop gate: no compaction pass
     * starts after it (the halted boundary's and the end-of-task pass included — the session's next run
     * compacts at its own boundary) and a pass in flight is cut (see {@link interrupt}). Typed refusals: the
     * steer-family `steering.not_running` once the task's result is out; a halt
     * issued BEFORE the run's first prompt polls the same bounded birth window as {@link steer} and
     * then stops the run before its first model turn (an empty, cleanly-halted completed run).
     *
     * **Receipt tension, stated:** `{turnCut:true}` and the
     * `task.turn_interrupted` notice assert facts about the CUT — a seat was cut, no new model turn
     * starts — and both stay true even when the gate's durable leg still collects to `suspended`:
     * an approval park whose store commit was already in flight (or committed) when the cut landed
     * WINS the fence race, the row is redeemable, the park's own abort then owns the ending, and the
     * result reads `status:"suspended"` WITHOUT {@link TaskResult.haltedByUser} — the halt cut a seat
     * but consumed nothing (no interrupt settlement took its cut, no boundary stopped for it), which
     * `task.halt_unconsumed` discloses. A cut observed BEFORE the commit makes the park concede
     * instead — no row, no card, and the ordinary halted ending. A BOUNDARY park (review / resource
     * slice) beside a consumed halt is the other way round: the terminal is the park's, and the seat
     * IS signed — a person also stopped it.
     */
    halt(): Promise<{
        turnCut: boolean;
    }>;
    /**
     * Reap this task's resources — **callable after it finished**, idempotent, never throws (design/50 §3,
     * design/51). The fan-out (v2) cancel primitive: an orchestrator's `cancelAll` is the shared
     * `AbortController.abort()` then `Promise.all(streams.map((s) => s.destroy()))`.
     *
     *  - **still running** → hard-interrupts it; the run's own `finish()` tears down its execution env.
     *    It is a caller stop like {@link interrupt}: a compaction pass in flight (the run's tail included)
     *    is cut, so the reap never waits out a summary call's retry schedule.
     *  - **suspended** (`status:"suspended"`) → `finish()` SKIPPED env teardown (the remote env was
     *    `suspendVM`-paused for a durable resume) and the checkpoint is committed → `destroy()` reaps BOTH:
     *    it CAS-**expires** the checkpoint (`CheckpointStore.expire`, fencing any concurrent resume) and —
     *    **only if it won that CAS** — destroys the paused env. So a cancelled fan-out worker never leaks a
     *    paused container + orphan checkpoint, and is never resumed onto an env `destroy()` already tore down
     *    (the expire/resolve CAS on the one `pending` row is mutually exclusive: exactly one of reap/resume
     *    wins). If `expire` itself fails, the env is left intact (checkpoint + paused env stay a consistent,
     *    still-resumable pair; the failure surfaces via `RunnerDeps.onError`, not a throw).
     *  - **already completed / failed / already destroyed** → no-op.
     *
     * Idempotent at the promise level (repeat/concurrent calls await the same settled work).
     */
    destroy(): Promise<void>;
}
