import { type Model } from "../../internal/llm.js";
import { type CheckpointToken, type ResumeOutcome } from "../checkpoint-store.js";
import { type TaskOutcome } from "../task-outcome.js";
import { type SwappableDeps } from "../swappable-deps.js";
import { type SideQuerySpec, type SideQueryResult } from "../side-query.js";
import type { SessionStore } from "../session.js";
import type { AgentDefinition, ModelRef, ModelRoles, RunnerDeps, TaskEvent, TaskResult, TaskSpec, TaskStream } from "../types.js";
import { type ContextSettings, type ContextWindowOverlay } from "../context-settings.js";
import type { ResumeRun, ResumeTaskConfig, RunInternals, RunnerSelfSeat } from "./contracts.js";
export type { ResumeTaskConfig } from "./contracts.js";
/**
 * A stateless task runner. Holds shared deps (the external brain, model catalog) and an
 * in-memory session store so that passing a `sessionId` continues a prior conversation.
 */
export declare class Runner implements RunnerSelfSeat {
    private deps;
    readonly sessions: SessionStore;
    /** Per-sessionId serialization so two tasks never mutate one session concurrently. */
    private sessionLocks;
    /** Per-session read-file-state seats (CC parity: readFileState is session-scoped;
     *  sema's map stays per-task, so the session scope lives here). A bounded CACHE, never an
     *  authority: a durable resume's checkpoint seed wins on its leg, and a miss (cross-process,
     *  evicted) falls back to the transcript-replay rung. See {@link SessionReadFileStates}. */
    private readonly sessionReadStates;
    private readonly sessionModeSeats;
    /** task notifications born BETWEEN turns (run torn down / harness already idle), parked
     *  per session and drained into the session's next run at its first boundary. Runner-lived (outlives any
     *  single run, like the registry handles that produce into it); bounded + drop-disclosing, see
     *  {@link PendingSessionNotifications}. Not checkpointed — the producing handles are process-local. */
    private readonly pendingSessionNotifications;
    /** Parent effective-policy inheritance (same-process resume convenience): checkpoint token → the OPAQUE
     *  `parentConstraints` the suspended leg ran under. Registered by `runTaskStream`'s result hook whenever a
     *  run spawned WITH `internals.inheritedGate.parentConstraints` ends `suspended` (process-local closures
     *  parked while the park is live — the same lifecycle posture as the subagent retain ledger's
     *  `internalsSnapshot`). `resumeStream` consults it when the caller supplied no constraints, so a
     *  same-process `runner.resume()` on a delegated child's checkpoint auto re-supplies the chain; a
     *  CROSS-process resume (registry miss — a different Runner/process) still requires an explicit
     *  `resumeStream(..., internals)` and fail-louds otherwise (`resume.parent_constraint_missing`).
     *  Retention: entries are kept across an `env_failed` reopen (the same token is retried), evicted on an
     *  observed `already_resolved` and by the FIFO cap — an evicted entry only ever degrades to the explicit
     *  re-supply requirement (fail-loud), never fail-open. */
    private readonly parentConstraintRegistry;
    /** Same-process suspended-env reap handles by token: the fence contract is
     *  "only the expire WINNER destroys the paused env", and the terminal resume-preflight arm
     *  (resumeStream) can now BE that winner — without this registry the losing stream-side
     *  reapSuspended returned early and the paused env this process holds leaked forever. Registered
     *  when a suspend publishes its reap handle; retired when the stream's own reap runs, when a
     *  resume wins the resolve CAS (the restore path owns the env from there — a later reopen keeps
     *  the ORIGINAL stream's closure as the reaping owner, deliberately), and when the terminal arm
     *  wins. Bounded by live suspended streams (the closures hold the same handles). */
    private readonly suspendedEnvReaps;
    /** SAME-RUNNER claim marks for {@link suspendedEnvReaps}: a token appears
     *  here from the moment an in-process actor ATTEMPTS its settling CAS (the terminal preflight's
     *  expire; a resume's resolve) until that actor's duty decision is done. The losing stream-side
     *  reap preserves a record ONLY while a local claim is in flight — a CROSS-Runner settlement
     *  leaves no local claim, so the loser retires the record (drops the strong ref; physical cleanup
     *  belongs to the winning owner, unprovable from here) instead of retaining env handles without
     *  bound in a long-lived Runner. A stale claim's failure direction is retention (the status quo),
     *  never a wrong destroy. */
    private readonly locallyClaimedTokens;
    private static readonly PARENT_CONSTRAINT_REGISTRY_CAP;
    /** The lanes' LIVE view of `deps` ({@link RunnerDepsSeat}): a getter, never a captured object, because
     *  `swapDeps` replaces the object and a rung reading after its own await must see the replacement. */
    private readonly depsSeat;
    constructor(deps: RunnerDeps);
    /** Fire `RunnerDeps.onTaskOutcome` through the single swallow-guarded chokepoint
     *  ({@link emitTaskOutcome}: mechanical-tier only, throwing sink never breaks the caller). Public so
     *  thin compositions OVER the Runner (`runGoal`) can emit at their terminal state without reaching
     *  into private deps. */
    emitTaskOutcome(outcome: TaskOutcome): void;
    /** The deployment agent catalog (RunnerDeps.agents/builtinAgents), exposed READ-ONLY
     *  so the Agent-tool lane (`createSubagentTool`) defaults to the SAME registry the workflow lane
     *  consumes (single source; the types.ts "pass the same array" follow-on made structural). The array
     *  is a fresh copy — a caller mutating it never rewrites this Runner's own deps; the definitions
     *  themselves are the shared objects (frozen by convention, same as gateBaseline's posture). */
    get agentCatalog(): {
        agents?: AgentDefinition[];
        builtinAgents?: boolean;
        models?: Record<string, Model>;
        roles?: ModelRoles;
    };
    /** The deployment's per-model context-window overlay as declared ({@link RunnerDeps.contextSettings}); a fresh
     *  read every call, the overlay objects frozen at their gate. */
    get contextSettings(): ContextSettings | undefined;
    /** The overlay a leg on `ref` reads RIGHT NOW — the one lookup ({@link contextOverlayFor}, tier aliases
     *  followed) against the seat and catalog in force, so a `swapDeps` reaches legs already running. */
    contextOverlayOf(ref: ModelRef | undefined): ContextWindowOverlay | undefined;
    /** The deployment-level gating baseline, exposed READ-ONLY so thin
     *  assemblers over the Runner (`runSpec`) can COMPOSE with it. The task-level engine semantic is a
     *  WHOLE-SLOT override (`spec.toolPolicy ?? deps.toolPolicy`, same for hooks — prepare-task), so any
     *  assembler that sets `spec.toolPolicy`/`spec.hooks` without folding these in silently shadows the
     *  deployment baseline. Getter only — nothing here is writable from outside. */
    get gateBaseline(): {
        toolPolicy?: RunnerDeps["toolPolicy"];
        hooks?: RunnerDeps["hooks"];
    };
    /** Acquire the lock for a sessionId; returns a release fn. The one lock every leg holds on its session, from the moment it
     *  knows the id to the leg's end (through `holdSessionLock` in `runTaskStream`, one per leg). */
    private acquireSessionLock;
    /**
     * Hot-swap the deployment seats a running Runner may change without a restart — the ONE door for every
     * hot-swappable seat ({@link SWAPPABLE_DEP_SEATS}: the model catalog generation `models` + its `tiers`
     * bindings, and the deployment's two containment-face declarations `readFace` / `writeFace`), and the ONLY sanctioned way to
     * change any of them after construction. It replaces the former `swapModels` verb (retired, no alias):
     * one verb per seat restated the same laws with a list of verbs as the only record of what is swappable.
     *
     * The laws, stated once (see the vocabulary module for the seat-by-seat meaning):
     * - **Screened first, written once — atomic.** The candidate is screened whole
     *   ({@link screenSwappableDeps}: the key set is the closed one, at least one seat, `tiers` rides with
     *   `models`, each seat's own value gate — both face seats through the very `assertContainmentFaceValue` every
     *   prepare door applies) and the catalog is tier-expanded on the candidate (an illegal binding throws
     *   exactly like the constructor's boot error) BEFORE the deps object is replaced. On any throw the
     *   current deps stay in force, byte-identical.
     * - **In-flight legs keep their snapshot.** A running task read `this.deps` at its prepare and holds
     *   that object: its `Model`, its resolved read face, everything. The swap changes what FUTURE prepares
     *   (and auto-sourced agents' string refs — {@link agentCatalog} copies per read) resolve. Same-name
     *   redirects never re-route or re-price a task mid-run; a read-face change never re-fences a mounted
     *   band; the divergence window is exactly the in-flight legs' lifetime, by design.
     * - **Presence is the instruction.** A key present on the candidate is replaced (an explicit
     *   `undefined` clears an optional seat: `tiers: undefined` ⇒ no bindings, a face seat `undefined` ⇒ the
     *   engine default); a key absent is kept. `tiers` omitted beside `models` ⇒ the current bindings are
     *   re-applied over the new catalog.
     * - **Announced per seat.** `config.models_swapped` (models/tiers counts — key material only, never the
     *   catalog itself; plus the advisory `route.base_url_changed_key_unchanged` for same-name entries
     *   whose URL moved while the Model-visible credential fingerprint did not) and
     *   `config.read_face_swapped` / `config.write_face_swapped` (`{ from, to, occurrenceId }`, the seat's
     *   previous and new value, `null` = no declaration) — the table {@link SWAP_SEAT_NOTICE} binds every
     *   seat to its line.
     */
    swapDeps(next: SwappableDeps): void;
    /** one-shot brain-routed utility query (see {@link runSideQuery} for the full contract):
     *  preserves system/multi-turn messages/tool DEFINITIONS, routes through the deployment's brain with
     *  the same model resolution as tasks, returns real usage/model. No session, no tool execution, no
     *  policy gate, no accounting — caller-trusted. */
    sideQuery(spec: SideQuerySpec): Promise<SideQueryResult>;
    /** Run a task and stream live events; await `.result()` for the final TaskResult. `resume` (internal)
     *  drives a durable resume through the same loop instead of a fresh objective prompt.
     *  `internals` (internal) is a TRUSTED run-scoped channel for live per-task state the
     *  Runner cannot see from `spec` (today: a `runRepairLoop` attempt's live `repairBundle`) — NOT a
     *  `TaskSpec` field; undefined on the public path.
     *  **Eager:** the task starts executing the moment this stream is constructed (fire-and-forget), NOT lazily
     *  on first iteration — you do NOT need to pull events for the run (and its `finish()`/env teardown) to
     *  happen. Iterating just observes events (the buffer is backpressure-free); `.result()` awaits completion. */
    runTaskStream(spec: TaskSpec, resume?: ResumeRun, internals?: RunInternals): TaskStream;
    /**
   * The run body as ONE driver method: `runLocked`'s signature, the lane order, and the two assemblies
     * the driver keeps (the harness handlers, the turn boundary). The lanes' products are threaded under the names they hand
     * back; the two continuations the S6 driver spelled as private methods (`runSeatLanes` / `runAssembliesAndLegs`, joined by
     * a `RunSeatsHandoff` re-spelling of two lanes' Results) are the `next` closures of the two async lanes they follow — the
     * notification lane's (R0) and the attachment seats' (R3) — because a closure sees what the lanes before it bound, so
     * nothing is re-spelled and nothing is re-destructured. Neither this method nor either closure is `async`: each lane's
     * own promise IS this method's, so a prepare that throws reaches the run IIFE's catch / finally (the failure backstop,
     * the session lock's release) on the tick it did, and each successor is entered on the tick its lane's last await
     * resolved on (the S6 TICKS pin holds unchanged: no first host read moved off the tick the one-function body read it on).
     */
    private runLocked;
    /** Run a task to completion and return a machine-readable result. */
    runTask(spec: TaskSpec, internals?: RunInternals): Promise<TaskResult>;
    /**
     * Resume a task suspended at a durable approval gate. The token came back as
     * `TaskResult.checkpointToken` from a `status:"suspended"` run; `outcome` carries the human/external
     * decision (`allow`/`deny`, with an optional `updatedInput` arg rewrite or `reason`); `taskConfig`
     * **re-supplies** the same tools / model / policy the task ran with (a token cannot reconstruct tool
     * implementations or the hand band — the session stores neither).
     *
     * Flow (§2.1): `get` the checkpoint → validate the outcome arm matches the gate (council #3) → **atomic
     * CAS `resolve`**. *Winning the CAS is the sole trigger to execute the pending action* — so a double /
     * concurrent resume of the same token loses the CAS and is rejected (`checkpoint.already_resolved`),
     * never re-running the side-effecting tool. The winner rewinds the session to the suspension point,
     * resolves the gated call (execute on `allow`, inject a denial on `deny`), closes the rest of the
     * suspended batch as deferred, and re-enters the run loop with a continuation — returning a normal
     * {@link TaskResult} (which may itself be `suspended` again with a fresh token).
     *
     * v1 serves only the `human`/`policy_ask` gate (F4). A `task_done` checkpoint (1C Path A) is not
     * resumable here — the caller orchestrates that one and reads its handle directly.
     *
     * **🔒 Authorization boundary — token-as-auth, scope is store-level
     * isolation, NOT caller authorization.** `resume` is TOKEN-AS-AUTH: whoever presents a valid, still-`pending`
     * checkpoint token resolves it. The scope passed to the store CAS is the checkpoint's OWN `cp.scope` (read
     * off the row), so the store's scope-WHERE is store-level multi-tenant DATA isolation (one tenant's reaper /
     * mis-scoped resolve can't touch another's row) — it does **not** verify that THIS CALLER is authorized for
     * that scope. Authorizing the operator's principal against the checkpoint (the "tenant-B holding tenant-A's
     * token must not resolve it" property) is the **caller's** responsibility: the BFF / supervisor service mints
     * the principal, scopes the approval inbox to it, and only calls `resume` for a checkpoint that principal
     * owns. The token's secrecy + single-use HMAC binding (D-G) is what keeps it from reaching the wrong tenant.
     * A direct (non-BFF) client is BLOCKED until D-G's cryptographic principal binding — never trust a
     * client-supplied principal/scope here.
     *
     * @throws {@link CheckpointError} `not_found` (no store / unknown token), `gate_mismatch` (outcome arm
     *   ≠ gate, or an unsupported gate), `invalid_outcome` (decision-action binding failed — boundCallId/
     *   boundInputHash mismatch, or a deny reason with a `</system-reminder>` tag), `reopen_revote` (an
     *   env_failed re-resume supplied a decision ≠ the persisted winner), `reopened_concurrently` (the
     *   optimistic-concurrency `rev` changed under a concurrent resolve/reopen — re-resume against current
     *   state), `unsupported_version` (checkpoint newer than this worker / remote handle with no factory),
     *   `already_resolved` (lost the CAS — idempotent no-op), `walltime_axis_retired` (pre-CAS: the
     *   persisted ledger carries the retired wall-clock budget axis — a worker of the previous release can
     *   still finish it), `resume_aborted` (the caller's signal was already aborted at entry, or aborted
     *   during the resume — pre-CAS/edit-re-adjudication legs leave the checkpoint pending and resumable,
     *   the post-claim leg leaves it consumed; the message says which), `reopen_failed` (an aborted resume
     *   needed to reopen the checkpoint and the store refused, or the reopen failed in flight — the message
     *   distinguishes terminally-consumed from state-unprovable), `config_refused` (pre-CAS: the re-supplied
     *   config or a mode the row recorded is one prepare would refuse — `detail.code` is prepare's own code,
     *   `detail.field` says whether the config or the row carries it; the checkpoint stays pending and
     *   unchanged). Each code's full contract is on {@link CheckpointError.code}.
     */
    resume(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, 
    /** Same TRUSTED run-internals seam as {@link resumeStream} — the convenience wrapper
     *  FORWARDS it (it used to silently drop a 4th arg, so a cross-process re-supply through resume()
     *  looked wired but always failed `resume.parent_constraint_missing`). */
    internals?: RunInternals): Promise<TaskResult>;
    /**
     * Streaming form of {@link resume}: runs the identical pre-CAS guards + atomic CAS, then returns the live
     * {@link TaskStream} of the resumed run (its events + `result()`) instead of draining it to a final
     * {@link TaskResult}. Use this when the resumed segment's tool/turn events must reach an event sink for
     * observability parity with the original `runInBackground` stream (service [15]); `resume()` is the
     * convenience wrapper that drains it. The guards + CAS run FIRST, so this rejects with the same
     * {@link CheckpointError}s as `resume` *before* any stream is returned (winning the CAS is still the sole
     * trigger to execute the pending action — a lost CAS rejects, never returns a re-running stream).
     *
     * **Eager (like {@link runTaskStream}):** once this resolves, the resumed run is already executing — the
     * pending action runs exactly once and the run-loop tail's `teardownOwnedEnv` tears down the
     * rebuilt remote env even if you never iterate the stream. So abandoning the returned stream is safe (no leak,
     * no hang); it only means you
     * don't observe the resumed segment's events.
     */
    resumeStream(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, 
    /** the TRUSTED run-internals seam, symmetric with {@link runTaskStream} — a resume leg
     *  otherwise has no `onForwardEvent`席位, so a subagent's task_progress ticks (child-isolated stream,
     *  only exit = the forward sink) were unreachable on resume. Deployment-owned, never a TaskSpec field.
     *  Parent effective-policy inheritance CONTRACT: a checkpoint whose `state.inheritedGate.
     *  requiresParentConstraint` is true was suspended under inherited OPAQUE parent-policy constraints
     *  (live ToolPolicy/onAsk closures — non-serializable); this is the ONLY seam that re-supplies them
     *  (`internals.inheritedGate.parentConstraints`). Omitting them for such a checkpoint rejects pre-CAS
     *  with `resume.parent_constraint_missing` (fail-loud; the checkpoint stays pending) — never a silent
     *  resume with the ancestors' gate dropped — unless this SAME Runner spawned the leg (the spawn-time
     *  registry auto re-supplies them; see `parentConstraintRegistry`). The re-supplied chain's LENGTH must
     *  match the checkpoint's recorded `parentConstraintCount` (`resume.parent_constraint_mismatch`
     *  otherwise). Most entries are policies the deployment authored and can hand straight back; a
     *  hook-wired ancestor also contributes a PreToolUse SCREENING entry (issue #33) whose policy the
     *  engine mints — rebuild that one with the exported `createPreToolUseConstraintPolicy(hook, env)`. The DATA half (ancestorRules/shellGate) rides the checkpoint itself and needs no
     *  re-supply; on the resumed leg the CHECKPOINT snapshot is the trust floor — live internals may only
     *  TIGHTEN it (per-entry rev-monotonic ancestorRules replacement, max-rank shellGate fold), never
     *  displace it wholesale. TRUST BOUNDARY: `internals` is the DEPLOYMENT trust channel (the same tier as
     *  `inheritedSkillGrants` and every other RunInternals field) — the library caller is trusted to
     *  re-supply the genuine chain; a caller able to invoke resumeStream already holds full Runner
     *  authority, so constraint substitution here is outside the enforced threat model (the count check
     *  catches shape MISTAKES, not a untrusted trusted caller). */
    internals?: RunInternals): Promise<TaskStream>;
    /**
     * The resume decision ladder after admission (E3–E10), driving the eight `resume-*` lanes in
     * the order the one-function ladder had: the review / park twins, the approval twin and its bindings, the
     * checkpoint screen, the internals-and-config rungs (whose one window await is taken HERE, where the ladder
     * took it), then the three async rungs after it — apply, preflight, claim — each entering the next in its own last
     * continuation, and the post-claim hand-off to the resumed leg. Every rung throws its typed refusal pre-CAS;
     * the first `return` is the leg's stream.
     */
    private resumeLadder;
    /**
     * The ladder's POST-CLAIM rung (E11), the driver's own: the post-consume hook, the answering
     * face minted from the decide's answer, the resumed leg's spec (identity continuity, the existing-session
     * requirement, the stripped pre-aborted preempt signal), the reopen compensation and the execution-record
     * verb bound to the row, the two post-CAS abort re-checks, and the hand-off to `runTaskStream` with the plain
     * twin. Entered in the claim rung's last continuation, so its first statement runs on the tick the win was read.
     */
    private resumeHandoff;
}
/** Convenience: one-shot run with explicit deps (creates a throwaway Runner). */
export declare function runTask(spec: TaskSpec, deps: RunnerDeps): Promise<TaskResult>;
export type { TaskEvent, TaskStream };
