import type { ConversationHandlePage, ExitRecord, InterruptOutcome, PromptOrigin, QueuedPrompt, SessionEvent, AgentSession, SessionSnapshot, SubmitPromptOptions, TurnCancellationSource, TurnResult } from './types.js'; import type { ConversationEventV1, ConversationSnapshot, PromptReceipt, SubmitPromptCommand } from './conversation-types.js'; export type ScheduledAttempt = { state: 'started'; queued: QueuedPrompt; } | { state: 'skipped_busy'; } | { state: 'unavailable'; error: string; }; /** * One in-process admission boundary for every producer targeting a role. * Scheduled callers get an atomic idle recheck plus submission; ordinary * producers retain ACP queue semantics while making their unsettled claim * visible before another producer can inspect idle state. */ export declare class RoleTurnArbiter implements AgentSession { private readonly session; readonly backend: import("../config.js").SessionBackendId; readonly pid: number; readonly capabilities: import("./types.js").AgentSessionCapabilities | undefined; private tail; private unsettled; private stopping; /** Bumped by `retireStalledAdmission`; every claim carries the one it was made under. */ private generation; /** Resolves the instant the CURRENT generation is retired. */ private retirement; constructor(session: AgentSession); /** * Waiters race the tail against their own generation's retirement, so a * single operation that never settles cannot own the boundary forever. A * woken waiter from a retired generation never runs its operation: the * ordering it was promised no longer exists, and admitting it silently would * be the one thing worse than telling the caller it was not admitted. */ private exclusive; private track; /** * Release the admission boundary after a cancellation that never settled. * Nothing can make a hung ACP call return, so the stuck operation keeps its * own promise — it just stops owning `tail` and stops holding a claim. * * Without this, one unsettled `interrupt` left every later producer queued * behind it forever: a scheduled poll never returned, so the loop was never * rescheduled and reported neither `started` nor `skipped_busy`, and an owner * prompt stayed pending with nothing to resolve it. * * One-active-run safety is unchanged. The arbiter only orders admission; * `tryScheduled` still asks the session itself whether it is idle, and the * session still refuses to run two turns at once. */ retireStalledAdmission(): void; queuePrompt(text: string, options?: SubmitPromptOptions): Promise; submitPrompt(text: string, options?: SubmitPromptOptions): Promise; /** * Do not hold `exclusive` while ACP waits for a tool boundary: permission * answers and explicit interrupts must remain able to pass immediately. */ submitPromptAfterTool(text: string, options?: SubmitPromptOptions): Promise; tryScheduled(text: string, origin: Extract, beforeQueue?: () => void | Promise): Promise; stopScheduledAdmission(): void; isAlive(): boolean; snapshot(): SessionSnapshot; conversationPage(request?: { after?: string; limit?: number; }): ConversationHandlePage; conversationSnapshot(): ConversationSnapshot; subscribeConversation(listener: (event: ConversationEventV1) => void): () => void; submitPromptBrowser(command: SubmitPromptCommand): Promise; interrupt(source?: TurnCancellationSource): Promise; respondPermission(permissionId: string, optionId: string): boolean; respondPermissionV2(permissionId: string, optionId: string, sessionGeneration: string): 'accepted' | 'stale'; eventsSince(seq: number): SessionEvent[]; subscribe(listener: (event: SessionEvent) => void): () => void; setControllerAttached(attached: boolean): void; exitResult(): ExitRecord | null; close(): Promise; }