import { PROMPT_SCHEMA_VERSION, type Prompt, type PromptAnswer, type PromptOption, type PromptQuestion } from "../../schemas/prompt.schema"; export declare const PROMPT_TERMINAL_RETENTION_MS: number; export declare const PROMPT_MAX_RECORDS_PER_SESSION = 200; type PromptTerminalState = Extract; export type PromptOptionDraft = Omit; export type PromptQuestionDraft = Omit & { options: PromptOptionDraft[]; }; export type PromptDraft = Omit & { questions: PromptQuestionDraft[]; }; export type PromptAnswerErrorCode = "prompt_not_found" | "prompt_revision_mismatch" | "already_resolved" | "prompt_expired" | "prompt_cancelled" | "prompt_unavailable" | "unknown_question" | "unknown_option" | "incomplete_answer" | "unsupported_prompt_shape" | "provider_error"; export type PromptAnswerOutcome = { ok: true; prompt: Prompt; } | { ok: false; code: PromptAnswerErrorCode; currentRevision?: number; }; export type PromptAdapterResult = { ok: true; } | { ok: false; code: PromptAnswerErrorCode; terminal?: { state: PromptTerminalState; reason: string; }; }; export type PromptAnswerAdapter = (context: { prompt: Prompt; answer: PromptAnswer; }) => PromptAdapterResult | Promise; export interface PromptEvent { type: "prompt_event"; sessionId: string; sequence: number; prompt: Prompt; } /** * Everything the registry still RETAINS for a session, not just what is * actionable: terminal records stay for PROMPT_TERMINAL_RETENTION_MS (capped at * PROMPT_MAX_RECORDS_PER_SESSION) so a reconnecting client can see how a prompt * it was showing ended. A subscriber therefore filters on `state` — presence in * a snapshot is not an open prompt. * * Retention also bounds idempotency: once a record is pruned, an answer retry * that would have replayed its recorded outcome gets `prompt_not_found` * instead (HTTP 404 on the answer route). */ export interface PromptSnapshot { type: "prompt_snapshot"; schemaVersion: typeof PROMPT_SCHEMA_VERSION; sessionId: string; sequence: number; prompts: Prompt[]; } export interface PromptRegistryOptions { createId?: () => string; emit?: (event: PromptEvent) => void; onExpire?: (prompt: Prompt) => void; now?: () => number; terminalRetentionMs?: number; maxRecordsPerSession?: number; } export declare class PromptRegistry { private readonly bySession; private readonly byId; private readonly sequences; private readonly createId; private readonly emit?; private readonly onExpire?; private readonly now; private readonly terminalRetentionMs; private readonly maxRecordsPerSession; constructor(options?: PromptRegistryOptions); open(draft: PromptDraft, adapter?: PromptAnswerAdapter, promptId?: string): Prompt; update(promptId: string, draft: PromptDraft, adapter?: PromptAnswerAdapter): Prompt; transition(promptId: string, state: PromptTerminalState, reason: string): Prompt; /** * The provider's prompt left the screen. * * Distinct from `transition` because the detector cannot tell a teardown it * observed on its own from one OUR OWN write caused. On a screen-scraped gate * the answer keys remove the box, and pty-manager's sendKeys fires the close * synchronously from inside the write — so the close always arrives before * the answer that caused it has settled, and reporting it as a cancel failed * every answer whose bytes had already landed (#720). * * While an answer is writing, the close is deferred and the answer decides: * `resolved` if the write landed, the deferred close if it did not. Success * is the adapter's own result, never the prompt's absence from the screen — * a prompt that does not vanish when answered (a multi-select form) simply * never reaches here, and is settled by the same adapter result (#721). * * ONLY this path defers. `replaced` from a different gate taking the screen, * `unavailable` from invalidateSession and `expired` all still go through * `transition` and still win, because none of them was caused by our write. */ providerClosed(promptId: string, reason: string): Prompt | null; /** * Run a provider write that may close the prompt as a side effect, so the * close it causes is deferred rather than applied. For callers outside this * class that write without going through `answer()` — the legacy permission * route. `performAnswer` manages the same two fields directly, because it * needs the deferred value in its own control flow. * * The write's own failure needs no unwinding here: pty-manager fires the * close as the last statement of a successful sendKeys, so a throw means no * close was ever deferred and the record is still open for the caller. */ whileAnswering(promptId: string, write: () => T): T; invalidateSession(sessionId: string, reason?: string): Prompt[]; get(promptId: string): Prompt | null; snapshot(sessionId: string): PromptSnapshot; dispose(): void; answer(sessionId: string, answer: PromptAnswer): Promise; private performAnswer; private validateResponses; private publish; private scheduleExpiration; private clearExpiration; private expireIfDue; private requireEntry; sweepExpired(sessionId: string): void; private enforceCap; private pruneOutcomes; } export {}; //# sourceMappingURL=promptRegistry.d.ts.map