/** Shape the SDK expects for streaming-input user messages. */ export interface OrgUserMessage { type: 'user'; message: { role: 'user'; content: string; }; parent_tool_use_id: null; session_id: string; } export declare function isRecoverableCloseReason(reason: string | undefined): boolean; /** ADR-O001 D3 — optional stream boundaries. Omitted = the pre-D3 stream. */ export interface StreamOptions { /** End the stream before yielding a message this rejects (not the first). */ stopBefore?: (next: string) => boolean; /** End the stream after this long with an empty queue. */ idleExitMs?: number; } /** * Async message queue feeding one persistent SDK session. * push() from the daemon (deliveries from other agents / the user); * stream() is passed as the `prompt` of query() to keep the session open. */ export declare class Mailbox { private static readonly MAX_QUEUE; /** Prefix tagging self-continuation pushes (turn-limit resume) so stream() can * tell them apart from real deliveries and the restart loop can detect a role * that is spinning on its own continuations without making progress. */ static readonly CONTINUE_PREFIX = "[system:turn-continue]"; /** The daemon's own requests to this session (the compaction summary request), by exact text. * Each carries an unguessable id, so ordinary mail is never mistaken for one; a request belongs * to the session it was sent to and a replacement never inherits it (see isInternal). */ private internalRequests; private queue; private wake; private closed; /** Why close() was called — e.g. 'token-budget' / 'usd-budget' — distinct * from a crash/terminal close (undefined). Lets callers like the idle * watchdog tell a recoverable budget pause from genuine unreachability. */ private closeReasonValue?; /** Bumped when a new stream() starts; stale generators see the mismatch and exit. */ private generation; /** Monotonic count of REAL (non-continuation) messages consumed by stream(). * The restart loop snapshots this around a session to tell progress from spin. */ private consumedReal; /** The most recently shift()ed-but-not-yet-confirmed-processed message: set * right before stream() yields it, cleared once the generator is resumed * for the next pull (proof the runner asked for more, i.e. the prior turn * finished). If a session dies with this still set, the generator was * abandoned mid-yield — see reclaimInFlight(). */ private inFlight; /** Set by beginDrain(): stream() stops yielding further queued messages * (letting only the current in-flight one finish) instead of pulling the * next one or parking on wake. Sticky for the life of this Mailbox * instance — a draining mailbox is being retired, not reused. */ private draining; private lastStreamEndValue?; /** True while stream() or waitForMessage() is parked on an empty queue. */ private parked; /** #331: the runner is inside a turn — from a yield (or runner activity) * until its result. See observeTurn(). */ private turnLive; /** The role has nothing to process: it is parked waiting for mail, either * inside stream() between turns or in waitForMessage() with its process * down (run_config.session_idle_exit_ms). Read by the no-progress alarm — * a role waiting for work is not stalled on it. */ get awaitingMail(): boolean; /** Queue a daemon request that only this session may see. */ pushInternal(text: string): void; isInternal(text: string): boolean; /** Take one still-queued message back out (a request nobody consumed). True when it was queued. */ removeQueued(text: string): boolean; /** The runner is inside a turn (a message was yielded and its result has not arrived). */ get turnActive(): boolean; /** Number of messages waiting to be yielded. */ get queuedCount(): number; /** Number of real (non-continuation) messages consumed so far across all sessions. */ get consumedRealCount(): number; push(text: string): void; close(reason?: string): void; get isClosed(): boolean; /** Why close() was called, if given a reason — undefined for a plain crash/ * terminal close. See the closeReasonValue field doc for intent. */ get closeReason(): string | undefined; /** * Detach the current waker WITHOUT resolving it. Called between sessions * (maxTurns restart, crash backoff): the dead session's generator may still * be parked on `wake` inside an abandoned next() — if a push() during that * window resolved it, the stale generator would shift() the message and * yield it into a promise nobody reads (silent loss, after deliver() * already returned a "delivered" receipt). With the waker dropped, such a * push only queues; the replacement session's stream() drains it. The * parked stale generator is never resumed and becomes garbage with its * session. */ detach(): void; get isDraining(): boolean; /** Begin a graceful drain: the current in-flight message (already yielded, * awaiting the consumer's next pull) is allowed to finish, but no further * queued message will ever be yielded. Synchronously sweeps and returns * everything NOT currently in flight so the caller can hand it to the * replacement incarnation without a race — anything still queued when * this returns will never be seen by this mailbox's stream() again. * Wakes a parked stream() (empty queue, nothing in flight) so it can * observe `draining` and return immediately instead of waiting forever. */ beginDrain(): string[]; /** * One live generator at a time: each stream() call bumps `generation`, and * a stale generator that ever resumes exits immediately without touching * the queue. Values are shift()ed at yield time — once the consumer's * next() resolves with a message it counts as delivered (matching SDK * behavior: a session may consume a message and end without ever resuming * the generator; redelivering would duplicate work and can livelock the * restart loop). */ stream(sessionId?: string, opts?: StreamOptions): AsyncGenerator; /** What the runner is doing, from the type of each message it emits: a * 'result' settles the turn; 'assistant'/'tool_result' mean one is running * (also one the runner started from a queued message after a result). * * #331: stream() must not END during a turn. The Claude SDK writes each * yielded message to the CLI's stdin at once, pulls for the next one right * away, and closes stdin when the stream ends — and tool permission * requests and in-process MCP tool calls travel over that same channel, so * every one of them then fails with "Stream closed". The idle exit and the * task boundary therefore wait for the turn to settle. */ observeTurn(type: string): void; /** Why the most recent stream() returned early, if it did: 'boundary' * (stopBefore rejected the next message) or 'idle' (idleExitMs elapsed). */ get lastStreamEnd(): 'boundary' | 'idle' | undefined; /** The next queued message, without consuming it. */ peek(): string | undefined; /** Resolves true once a message is queued, false if the mailbox closes or * drains first. Used to keep a role's process down while it has no mail. */ waitForMessage(): Promise; /** Called by the crash-retry path right after a session dies abnormally. If * a message was mid-flight — shifted for the crashed generator's yield but * never confirmed processed, because the generator was abandoned at that * yield rather than resumed for another pull — put it back at the front of * the queue so the replacement session (built by the next stream() call) * gets it first instead of the queue staying empty and the new session * parking on the mailbox forever. A no-op when nothing was in flight * (clean session end, or nothing was ever pulled). */ reclaimInFlight(): void; /** Serialize mailbox state for checkpoint/resume - Pattern 3 */ serialize(): { queue: string[]; closed: boolean; consumedReal: number; closeReason?: string; }; /** Deserialize mailbox state from checkpoint - Pattern 3 */ deserialize(state: { queue: string[]; closed: boolean; consumedReal: number; closeReason?: string; }): void; } //# sourceMappingURL=mailbox.d.ts.map