import { type MailboxCrossProcessVerdict, type MailboxStore } from "../core/mailbox-store.js";
import { type SessionBoxBinding, type SystemInjectionPriority, type TaskNotificationPayload } from "../core/task-notification.js";
import { type FrameSettlement } from "../core/frame-settlement.js";
import { type EngineNotice, type RunnerDeps } from "../core/types.js";
import { type PermissionModeClass } from "./cross-session-envelope.js";
import { type CrossSessionInboundSettingLayers } from "./cross-session-judge.js";
import { type PeerLaneConfig } from "./peer-admission.js";
import { type PeerDirectory } from "./peer-directory.js";
import { createListAgentsTool, type ListAgentsReadSeats } from "./list-agents-tool.js";
import { foldPermissionModeClass } from "./cross-session-judge.js";
import type { AskEffective } from "../core/wiring-manifest.js";
import type { SendMessageToolOptions } from "./send-message-tool.js";
import { type PeerClock, type PeerHeldQueue } from "./peer-held-queue.js";
import { type PeerIdleMachine } from "./peer-idle.js";
/** design/385 §1.2④ — the drain lease TTL. Same sizing law as the tier-3 revive lease: well under the
 *  store's stale-row floor, long enough to cover one turn (a longer turn renews on the same owner at
 *  its next boundary; an expired lease re-serves the batch — at-least-once). */
export declare const PEER_DRAIN_LEASE_TTL_MS: number;
/** The lane-mount refusal for a run whose own session id cannot be spelled as a peer address. */
export declare const PEER_SESSION_ID_UNGRAMMATICAL_CODE = "peer.session_id_ungrammatical";
/** The `admission_refused` cause of a principal-inbox record the delivery gate did not
 *  mint (no `peer_message` kind, or a missing `fromScope` / `gateReceiptId` anchor). */
export declare const PRINCIPAL_INBOX_UNMINTED_CAUSE = "not_minted_by_delivery_gate";
/** Does a terminal leg drain its scope's principal inbox? Exactly when the declared
 *  consumer is its terminals ({@link principalInboxConsumerOf}). The one predicate the drain binding and the
 *  `ReadInbox` origin share, so the face is external exactly where the lane exists. */
export declare function terminalsDrainPrincipalInbox(deps: Pick<RunnerDeps, "principalInboxConsumer">): boolean;
/** The lane's mount verdict: the mailbox declaration's verdict, or this leg's own address refusal. */
export type PeerLaneMountVerdict = MailboxCrossProcessVerdict | {
    ok: false;
    code: typeof PEER_SESSION_ID_UNGRAMMATICAL_CODE;
    reason: string;
};
/**
 * design/385 §1.2⑥ / §5.1 — may the cross-session lane mount on this deployment? `undefined` = no
 * directory seat (no lane, nothing to say). A directory with no mailbox, or over a mailbox that does not
 * declare cross-process safety, is REFUSED with a named reason (bad-value loudness: several terminals
 * sharing one box on luck is exactly what the declaration exists to forbid). With a `sessionId`, the
 * leg's OWN address is judged too: a host-minted session id outside the peer address grammar has no
 * session box to drain and no address a peer could send to, so the lane refuses by name — the
 * alternative was `peerSessionBoxHandle` throwing out of prepare and failing the whole run over an
 * optional feature.
 */
export declare function judgePeerLaneMount(deps: {
    peerDirectory?: PeerDirectory;
    mailboxStore?: MailboxStore;
}, sessionId?: string): PeerLaneMountVerdict | undefined;
/**
 * May THIS leg drain its own session box, and where is that box? The ONE mount judgment of the
 * session-box drain, read by two seats: the wiring station binds the drain on it, the notification
 * lane binds the park seat on it ({@link SessionBoxBinding}) — so a frame is parked into a box exactly
 * when a leg of that session drains it. Independent of the peer DIRECTORY (the directory adds the
 * discovery faces; the box is also where a child's `SendMessage("main")` waits while its host session
 * is idle). A DELEGATED CHILD never drains one: its addressable box is its `a*` handle (the tier-3
 * revival lane), and a drain per child on a box nobody writes would cost a lease claim per turn on
 * every background agent. `undefined` on every refusal — silently: no host asked for a box here.
 */
export declare function judgeSessionBoxDrain(deps: Pick<RunnerDeps, "mailboxStore" | "onError" | "sessionBoxEngineFrames">, leg: {
    sessionId: string;
    scope: string;
    isDelegatedChild: boolean;
}): SessionBoxBinding | undefined;
/** What one pull of the box handed back: the records read (already rendered frames), and what is still waiting. */
export interface PulledInbox {
    delivered: TaskNotificationPayload[];
    /** Records still leased in the box after this pull (the next pull, or the next boundaries, read them). */
    pending: number;
    /** How many of the waiting records their sender marked urgent. */
    urgent: number;
}
/** The disposition arms of a `peer.inbound_disposition` notice (closed set; `detail.disposition`).
 *  #1036 adds `resupply_exhausted`: a record whose frame a compaction summarized away, was
 *  re-supplied once, and was summarized away AGAIN. It settles (leaves the box) — but as a DISCLOSED
 *  loss, never as "the model read it". The cap is a bound on delivery attempts; it does not
 *  manufacture a consumption proof. */
export type PeerInboundDisposition = "refused" | "held" | "admission_refused" | "notice_unrouted" | "notice_user_only" | "resupply_exhausted";
/**
 * #1036 — how many times a record whose frame was summarized away is handed over again before the
 * drain gives up and discloses. `1` = one repair, i.e. at most two occurrences of one record in one
 * run. A cap exists because repair that cannot converge is a loop; it is this small because the
 * second occurrence already says everything the first did. RUN-SCOPED (the ledger is in memory): a
 * crash or a takeover re-serves the record at `attempt: 1`, the at-least-once posture this lane has
 * always had, not a new hole.
 */
export declare const PEER_RESUPPLY_CAP = 1;
export interface PeerSessionDrainOptions {
    mailbox: MailboxStore;
    /** This session (the box owner) and this leg's cycle id (the lease owner). */
    sessionId: string;
    runId: string;
    /** The registry/mailbox scope this run mounts in. */
    scope: string;
    /**
     * Also drain this scope's PRINCIPAL INBOX ({@link PRINCIPAL_INBOX_HANDLE}) as a
     * second lane beside the session box: its own lease, ledger, ack prefix and held queue; the same pass
     * budget, injector, settlement seam and pull face. Set only when the scope's consumer is its terminals
     * ({@link principalInboxConsumerOf}); with an assistant configured the lane is never minted.
     */
    principalInbox?: boolean;
    /**
     * The leg's external-content accounting for the INJECTION path: called whenever a letter
     * from another principal (a record carrying the gate's `fromScope` anchor) is injected into the run, so the
     * session is marked as having taken external content up exactly as a web fetch marks it. The PULL path is
     * accounted by the `ReadInbox` tool's own declared class (its call is the mark), so a pulled letter is not
     * marked twice. Absent ⇒ nothing to mark (no memory session on this leg).
     */
    markExternalContent?: (reason: string) => void;
    /** The run's own notification injector (the task-notification lane; `next` tier). */
    inject: (notification: TaskNotificationPayload, opts?: {
        priority?: SystemInjectionPriority;
    }) => void;
    /** The peer lane's knob table (admission gate + delivery budgets + listing window; the same seat SendMessage reads). */
    lane?: Partial<PeerLaneConfig>;
    /** The recipient's self-token set for the hop check, read live (the run's peer identity ref). */
    ownTokens: () => readonly string[];
    /** The recipient's `crossSessionInbound` layers, read at every drain. */
    settingLayers: () => CrossSessionInboundSettingLayers | undefined;
    /** The recipient's own permission-mode class, read at every drain (the manifest's ask fold). */
    selfModeClass: () => PermissionModeClass | "unknown";
    /** This session's display name (its directory row's name), for receipts and idle notices it authors. */
    selfName?: () => string | undefined;
    /** Awaited ONCE before the first pass (a directory lookup that fills `selfName`, say); a rejection is swallowed. */
    warmup?: () => Promise<unknown>;
    /** The deployment's `dialogExpiry` (raw; resolved at bind — an out-of-vocabulary value is announced
     *  once through `onError` and the default applies). Absent ⇒ the default (5 m). */
    dialogExpiry?: () => unknown;
    /** The last turn's text, for the idle notice's one-line detail (CC `getLastTurnText`). */
    lastTurnText?: () => string | undefined;
    /** Whether the run is parked on a pending ask / review at its end (a parked run is `blocked`, not idle). */
    parked?: () => boolean;
    onNotice?: (notice: EngineNotice) => void;
    onError?: (error: unknown, ctx: {
        phase: "degraded";
        sessionId: string;
        classification: string;
    }) => void;
    /** #1036 — the LEG's settlement seam: the receipts the harness records when a frame reaches
     *  the transcript and the effects the compaction commit beat records. Absent ⇒ the drain mints a
     *  private one, which is honest but inert: nothing fills it, so every delivered record waits for a
     *  receipt that never comes and stays parked (at-least-once). Every production binding supplies it. */
    settlement?: FrameSettlement;
    leaseTtlMs?: number;
    /** Tests: a virtual clock for the held deadlines and idle timers (honored when this process first sees the session). */
    clock?: PeerClock;
}
export interface PeerSessionDrain {
    /**
     * One drain pass (serialized: a boundary never overlaps a still-running pass).
     *
     * @contract cross_session.settled_means_committed_and_not_summarized — a session-box record is acked
     * only after a frame carrying it has been committed to the transcript and no compaction applied up to
     * that settlement beat summarized that entry away. A summarized frame is re-supplied once (a new
     * occurrence of the SAME record: same `task_id`, same `seq`, `attempt: 2`, `resupplied: true`), and a
     * re-supplied occurrence summarized again settles as `resupply_exhausted`, disclosed — never as seen.
     * OBSERVABLE: a `compacted` frame counting a not-yet-acked delivered record
     * (`summarizedCrossSessionMessages >= 1`) is followed, in the same boundary's delivery half, by a
     * re-supply frame for it or by a `resupply_exhausted` disposition — never by silence; a record
     * committed and never summarized is never shown twice; no compaction implies no re-supply.
     */
    drain(): Promise<void>;
    /**
     * The PULL face (the `ReadInbox` tool): one pass over the same box, same cursor, same judgments —
     * but the records are RETURNED instead of injected, up to `limit` (default = the pass budget). A
     * record a boundary already handed over is never returned again, and a record returned here is
     * never injected later: one cursor (`settledUpTo`), two readers. Never acks (the next boundary does).
     */
    pull(limit?: number, toolCallId?: string, charBudget?: number): Promise<PulledInbox>;
    /**
     * #1036 — THE TURN BOUNDARY's pass, called by the boundary lane as its tail so it runs AFTER that
     * boundary's compaction (a hook of its own would have been registration-order luck). The settlement
     * half always runs; the delivery half only when `continues` — a boundary this leg will not follow
     * with another request cannot produce a transcript receipt for anything handed over there.
     */
    boundaryRound(o: {
        continues: boolean;
    }): Promise<void>;
    /** #1036 — "is this engine frame's record still parked in this box?" The terminal sweep's
     *  per-session pend must skip those: the box re-serves them itself, and a process-local copy beside
     *  a parked record is two sources of truth for one message. */
    isBoxBacked(payload: unknown): boolean;
    /** #1036 — "does this leg hold a record a landed compaction could still break?" The compaction
     *  commit beat asks before recording its effect, so a run that will never read one records none. */
    wantsCompactionEffects(): boolean;
    /** Run end: settle what reached the transcript, then release the lease so the session's next cycle
     *  claims at once. What did NOT reach it stays parked (at-least-once, next run re-serves it). */
    finish(): Promise<void>;
    /** The run began working (cancels a pending idle fire). */
    busy(): void;
    /** The run ended with nothing queued and no pending ask: the one-shot idle fire is scheduled (§5.2). */
    idle(finishedAt: number): void;
    /** This session's held queue (process-level; the host review face reads/settles through it). */
    readonly held: PeerHeldQueue;
    /** This session's held queue for the PRINCIPAL INBOX lane (present iff the drain
     *  has that lane): cross-principal letters a setting holds, for the same approval face. Its entries'
     *  `box` is {@link PRINCIPAL_INBOX_HANDLE}; their receipts reach no sender (another principal). */
    readonly principalHeld?: PeerHeldQueue;
    /** This session's idle machine (process-level). */
    readonly idleMachine: PeerIdleMachine;
}
export declare function createPeerSessionDrain(opts: PeerSessionDrainOptions): PeerSessionDrain;
/** The lane's late-bound facts: the ask fold resolves after the manifest derivation, the mount runs
 *  before it — both read this one cell. */
export interface PeerLaneRefs {
    askEffective?: AskEffective;
    /** The session's EFFECTIVE permission mode, read live (it follows a mid-run mode change); absent on a leg
     *  that carries no mode fact. Paired with `askEffective` in the one mode-class fold. */
    permissionMode?: () => Parameters<typeof foldPermissionModeClass>[0];
    /** The bound drain's pull face (filled by {@link bindPeerLaneDrain}); the `ReadInbox` tool reads it late. */
    pull?: (limit?: number, toolCallId?: string, charBudget?: number) => Promise<PulledInbox>;
    /** #1036 — the bound drain's BOUNDARY pass (filled by {@link bindPeerLaneDrain}); the leg's
     *  prepared seat copies it out so the turn-boundary lane can tail-call it. */
    boundaryRound?: (o: {
        continues: boolean;
    }) => Promise<void>;
    /** #1036 — the bound drain's box-backed predicate (filled by {@link bindPeerLaneDrain}). */
    isBoxBacked?: (payload: unknown) => boolean;
}
/**
 * The mount verdict + its loud half: `true` = the lane mounts on this leg; `false` = no seat, or the
 * seat was refused — the refusal is announced ONCE per leg as `config.peer_lane_unmounted` naming the
 * reason (never mounted on luck, never silently inert).
 */
export declare function announcePeerLaneMount(deps: {
    peerDirectory?: PeerDirectory;
    mailboxStore?: MailboxStore;
    onNotice?: (n: EngineNotice) => void;
}, leg: {
    sessionId: string;
    runId: string;
}): boolean;
/** The SendMessage seats of the lane (design/385 §2): the directory (address arm + last name rung) and
 *  this session's identity/attestation, whose mode class folds LIVE off the manifest's ask derivation. */
export declare function peerLaneSendMessageSeats(args: {
    peerDirectory: PeerDirectory;
    sessionId: string;
    scope: string;
    name?: string;
    refs: PeerLaneRefs;
    listingMounted?: boolean;
}): Pick<SendMessageToolOptions, "peerDirectory" | "peerSelfSession" | "peerListingMounted">;
/**
 * design/385 §5.1 — THE ListAgents mount predicate, single source: the built-in mounts only when the
 * leg's tool valve leaves it (`leaves`: the valve's one reading, `valveLeaves`) and no caller tool owns either spelling
 * (`ListAgents` / `ListPeers`) as its name OR as an alias. Every reader of "is ListAgents on this roster?" — the mount itself and the
 * SendMessage face's listing phrase — calls this one predicate, so a face can never name a tool the
 * roster does not carry (a second, inline copy of the rule drifted on exactly the alias arms).
 */
export declare function listAgentsMountable(face: {
    leaves: (tool: {
        readonly name: string;
        readonly aliases?: readonly string[];
    }) => boolean;
    specTools: ReadonlyArray<{
        name: string;
        aliases?: readonly string[];
    }>;
}): boolean;
/** design/385 §5.1 — the ListAgents mount (alias ListPeers): read-only, concurrency-safe, on the peer
 *  seat's own arm beside SendMessage; a caller-supplied tool of the same name wins (skip, not throw). */
export declare function mountListAgents(args: {
    /** The directory when the peer lane is up; absent on a delegation-only mount (agent lanes list, no peer rows). */
    peerDirectory?: PeerDirectory;
    /** The resolved listing freshness window (the peer lane's knob table). */
    freshWindowMs?: number;
    sessionId: string;
    scope: string;
    hostTaskId: string;
    /** The delegated child's trusted parent pair (RunInternals), when this run is a child. */
    parentTaskId?: string;
    parentSessionId?: string;
    registry: ListAgentsToolRegistry;
    roster?: NonNullable<Parameters<typeof createListAgentsTool>[0]["roster"]>;
    /** The continuation reach of the SendMessage beside it — what a recorded agent's label may promise (see the tool option). */
    continuationTier?: NonNullable<Parameters<typeof createListAgentsTool>[0]["continuationTier"]>;
    specTools: ReadonlyArray<{
        name: string;
        aliases?: readonly string[];
    }>;
    toolEffects: Map<string, "read" | "write" | "idempotent">;
    /** The read faces' store seats, handed to the tool unchanged (see {@link ListAgentsReadSeats}). */
    readSeats?: ListAgentsReadSeats;
}): ReturnType<typeof createListAgentsTool> | undefined;
type ListAgentsToolRegistry = NonNullable<Parameters<typeof createListAgentsTool>[0]["registry"]>;
/** The harness surface the binding needs (structural — the machine itself never sees the harness). */
export interface PeerDrainHarness {
    on(type: "turn_boundary" | "before_agent_start", handler: () => Promise<undefined>): unknown;
    subscribe(listener: (event: {
        type: string;
        nextTurnCount?: number;
        undrainedSteerCount?: number;
        undrainedFollowUpCount?: number;
    }) => void | Promise<void>): unknown;
}
/**
 * Bind the drain to a leg: the run's own injector at the `next` tier, run open (the loop's first
 * steering drain follows the `before_agent_start` hook) + every turn boundary, and the run-end
 * ack/release on `agent_end`. Slice 4: the harness's `settled` account (the frame after `agent_end`)
 * is the idle judgment's seat — "finished its turn with nothing queued" = no next-turn frames, no
 * undrained user inputs, and (through `opts.parked`) no pending ask/review; the held queue's own
 * emptiness is the machine's to check.
 */
export declare function bindPeerSessionDrain(harness: PeerDrainHarness, opts: PeerSessionDrainOptions): PeerSessionDrain;
/**
 * The leg-side spelling of {@link bindPeerSessionDrain}: the deployment seats the drain reads
 * (`mailboxStore` / `peerLane` / `crossSessionInbound` / `crossSessionDialogExpiry` / the two
 * sinks) come straight off deps, the recipient's mode class folds off the lane refs — one binding call
 * per leg, no per-seat plumbing.
 */
export declare function bindPeerLaneDrain(harness: PeerDrainHarness, args: {
    deps: Pick<RunnerDeps, "mailboxStore" | "peerDirectory" | "peerLane" | "crossSessionInbound" | "crossSessionDialogExpiry" | "principalInboxConsumer" | "onNotice" | "onError">;
    sessionId: string;
    runId: string;
    scope: string;
    inject: PeerSessionDrainOptions["inject"];
    ownTokens: () => readonly string[];
    refs: PeerLaneRefs;
    /** #1036 — the leg's settlement seam (receipts + compaction effects), minted by the
     *  orchestrator and shared with the harness sink and the compaction commit beat. */
    settlement?: FrameSettlement;
    selfName?: () => string | undefined;
    parked?: () => boolean;
    lastTurnText?: () => string | undefined;
    /** The leg's external-content mark for an injected letter from another principal ({@link PeerSessionDrainOptions.markExternalContent}). */
    markExternalContent?: (reason: string) => void;
}): PeerSessionDrain;
export {};
