import type { RewindAnchor, RewindConversationOutcome, RewindConversationPort, RewindConversationPreview } from './types.js'; /** What a host is asked to do. */ export type ConversationRewindRequestKind = 'preview' | 'rewind'; /** A surface's live claim on one session's conversation. */ export interface ConversationRewindHost { readonly hostId: string; readonly sessionId: string; /** How the surface names itself, for the refusal messages a person reads. */ readonly label: string; readonly registeredAt: number; readonly leaseExpiresAt: number; } /** One question put to a host, as the host sees it. */ export interface ConversationRewindRequest { readonly requestId: string; readonly sessionId: string; readonly turnId: string | null; readonly kind: ConversationRewindRequestKind; /** When this question stops waiting; an answer after it is refused. */ readonly expiresAt: number; } /** What a host answers with. */ export type ConversationRewindAnswer = { readonly kind: 'preview'; readonly messagesToDrop: number; readonly messagesRemaining: number; } | { readonly kind: 'rewind'; readonly droppedMessages: number; readonly undoSnapshotId: string; } | { readonly kind: 'unavailable'; readonly reason: string; }; /** Why a host-facing call was refused. Each maps to one honest message. */ export type ConversationRewindRefusal = 'not-the-host' | 'host-unknown' | 'request-unknown' | 'request-expired' | 'too-many-hosts'; export declare class ConversationRewindHostError extends Error { readonly refusal: ConversationRewindRefusal; /** The input field the refusal is about, for the caller's error attribution. */ readonly field: string; constructor(message: string, refusal: ConversationRewindRefusal, field: string); } /** Lease bounds. A lease is a promise to keep polling, not a reservation. */ export declare const CONVERSATION_HOST_MIN_LEASE_MS = 5000; export declare const CONVERSATION_HOST_MAX_LEASE_MS: number; export declare const CONVERSATION_HOST_DEFAULT_LEASE_MS: number; /** How long a host may hold a `take` call open waiting for work. */ export declare const CONVERSATION_HOST_MAX_WAIT_MS = 25000; /** How long the daemon waits for a host's answer before reporting unavailable. */ export declare const CONVERSATION_ANSWER_TIMEOUT_MS = 20000; /** * Ceilings. Both bound memory held on behalf of processes that may already be * gone, which is the only kind of state this broker keeps. */ export declare const CONVERSATION_HOST_MAX_HOSTS = 256; export declare const CONVERSATION_HOST_MAX_PENDING = 8; export interface ConversationRewindHostBrokerOptions { /** * A port for sessions no surface has registered, the daemon's own in-process * conversation store, when it has one. Consulted only after the registry * misses, because a surface that says it is holding the conversation is a * better authority on it than a store that merely might be. */ readonly fallback?: RewindConversationPort | null | undefined; readonly answerTimeoutMs?: number | undefined; readonly defaultLeaseMs?: number | undefined; readonly now?: (() => number) | undefined; } /** * The registry of surfaces hosting conversations, and the port the rewind * service asks through. */ export declare class ConversationRewindHostBroker implements RewindConversationPort { private readonly hosts; private readonly pending; /** Resolvers for `take` calls waiting on work, keyed by host id. */ private readonly waiters; private readonly fallback; private readonly answerTimeoutMs; private readonly defaultLeaseMs; private readonly now; constructor(options?: ConversationRewindHostBrokerOptions); /** * Offer this surface's live conversation for a session, or renew the offer. * * Passing the `hostId` a previous call returned renews that registration. * Passing none claims the session: any earlier host for it is replaced and * its outstanding questions are answered unavailable, because they were * addressed to a surface that is no longer the one holding the messages. * Passing a `hostId` that is not the session's current host is refused rather * than treated as a fresh claim, a surface that believes it is the host and * is not should learn so, not silently take over. */ registerHost(input: { readonly sessionId: string; readonly hostId?: string | undefined; readonly label?: string | undefined; readonly leaseMs?: number | undefined; }): ConversationRewindHost; /** Withdraw an offer. Outstanding questions for it are answered unavailable. */ releaseHost(input: { readonly sessionId: string; readonly hostId: string; }): ConversationRewindHost; /** Every live registration, for a status surface. */ listHosts(): readonly ConversationRewindHost[]; /** * Collect the questions waiting for this host, renewing its lease, a surface * that is polling is a surface that is alive, so a separate keepalive would * be ceremony over the same fact. * * With nothing waiting and a `waitMs`, the call holds open until a question * arrives or the wait runs out, and an empty return is a normal answer rather * than an error: nothing needed doing. */ takeRequests(input: { readonly hostId: string; readonly waitMs?: number | undefined; readonly limit?: number | undefined; }): Promise<{ readonly host: ConversationRewindHost; readonly requests: readonly ConversationRewindRequest[]; }>; /** * The question behind a request id, for a caller that needs to know which * kind of answer it owes before it builds one. Refused when the request is no * longer waiting, or was put to a different surface. */ describeRequest(input: { readonly hostId: string; readonly requestId: string; }): ConversationRewindRequest; /** Answer one question. Refused when it is not this host's, or has expired. */ answerRequest(input: { readonly hostId: string; readonly requestId: string; readonly answer: ConversationRewindAnswer; }): ConversationRewindRequest; /** Answer everything outstanding and forget every registration. */ shutdown(): void; preview(anchor: RewindAnchor): Promise; rewind(anchor: RewindAnchor): Promise; private noHostReason; private wrongKindReason; /** Put one question to a host and wait, bounded, for the answer. */ private ask; /** Resolve one question exactly once, however it ended. */ private settle; private findHostForSession; private renewByPolling; private collect; private waitForWork; private wakeWaiters; /** Answer this host's outstanding questions and forget it. */ private evictHost; /** * Drop registrations whose lease has lapsed, on the way into every access. * A surface that stopped polling stopped being able to answer, whether it * crashed, disconnected, or simply moved on, and a stale claim is worse than * no claim, because it turns "nobody is hosting this" into a timeout. */ private dropExpiredHosts; } //# sourceMappingURL=conversation-host-broker.d.ts.map