/** * `SessionFold` — the SS4 client fold, bound to the generated wire types * (spec 14990 FR-007, tasks.md T019). * * `ItemStore` and `SessionStateStore` are generic because they were written * before the session-view plane entered the generated declarations. This * module is where the generic parameters are BOUND: items are `@muse-code/msp`'s * `Item`, state-family values are the generated `session/*` params objects, * and every event this fold accepts is typed as the params object the wire * actually carries. Nothing here restates a wire shape (INV-001). * * Beyond the two-store composition, the fold owns the parts of the view that * are neither an item nor a state family: * * - the TURN LIFECYCLE — `turn/started`, `turn/completed`, `turn/retracted`, * `turn/unqueued`, and the non-terminal `turn/retryScheduled`, * `turn/modelRetryScheduled`, and `turn/foregroundCompleted`; * - the APPROVAL and USER-INPUT view events as fold INPUTS — the pending * sets and their first durable terminal. The decision flow (choosing and * sending a resolution) is the facade's, not the fold's; * - the DELIVERY marker `view/gap` — it seeds no store, it moves the fold's * CURRENCY (`pendingGap`/`current`, FM-003). The recovery that fills the * hole needs client→server I/O and is therefore the facade's (T032). * * INV-006 governs all of it: the fold never invents a terminal. A retract and * a reclaim are not `TurnTerminal` values and are never rendered as one, an * unrecognized `TurnTerminal` is kept verbatim, and a second resolution never * displaces the first durable one. */ import type { ApprovalRequestParams, ApprovalResolvedParams, ApprovalUpdatedParams, Item, ItemCompletedParams, ItemDeltaParams, ItemStartedParams, ItemUpdatedParams, MonitorInboxItemAdmittedParams, MonitorInboxItemDrainedParams, MonitorInboxItemVoidedParams, MspNotification, SessionApprovalModeChangedParams, SessionBranchChangedParams, SessionContextUsageParams, SessionGoalChangedParams, SessionModelChangedParams, SessionModelRouteUnservedParams, SessionNameChangedParams, SessionReasoningEffortChangedParams, SessionTodoListChangedParams, SessionTokenUsageParams, TurnCompletedParams, TurnError, TurnForegroundCompletedParams, TurnModelRetryScheduledParams, TurnRetractedParams, TurnRetryScheduledParams, TurnStartedParams, TurnTerminal, TurnUnqueuedParams, UserInputRequestParams, UserInputSettledParams, ViewGapParams } from "../msp.js"; import type { DeltaApplyOutcome, ItemApplyOutcome, TerminalUnknownItemAnnotation } from "./item-store.js"; import type { StateApplyOutcome, StateValue } from "./state-store.js"; /** The session-state families, each keyed in the store by its method name. */ export type SessionStateParams = SessionApprovalModeChangedParams | SessionBranchChangedParams | SessionContextUsageParams | SessionGoalChangedParams | SessionModelChangedParams | SessionNameChangedParams | SessionReasoningEffortChangedParams | SessionTodoListChangedParams | SessionTokenUsageParams; /** A view notification, paired with the generated params object it carries. */ export type ViewEvent = { readonly method: "item/started"; readonly params: ItemStartedParams; } | { readonly method: "item/updated"; readonly params: ItemUpdatedParams; } | { readonly method: "item/completed"; readonly params: ItemCompletedParams; } | { readonly method: "item/delta"; readonly params: ItemDeltaParams; } | { readonly method: "turn/started"; readonly params: TurnStartedParams; } | { readonly method: "turn/completed"; readonly params: TurnCompletedParams; } | { readonly method: "turn/foregroundCompleted"; readonly params: TurnForegroundCompletedParams; } | { readonly method: "turn/retracted"; readonly params: TurnRetractedParams; } | { readonly method: "turn/retryScheduled"; readonly params: TurnRetryScheduledParams; } | { readonly method: "turn/modelRetryScheduled"; readonly params: TurnModelRetryScheduledParams; } | { readonly method: "turn/unqueued"; readonly params: TurnUnqueuedParams; } | { readonly method: "approval/requested"; readonly params: ApprovalRequestParams; } | { readonly method: "approval/reviewing"; readonly params: ApprovalRequestParams; } | { readonly method: "approval/updated"; readonly params: ApprovalUpdatedParams; } | { readonly method: "approval/resolved"; readonly params: ApprovalResolvedParams; } | { readonly method: "userInput/requested"; readonly params: UserInputRequestParams; } | { readonly method: "userInput/settled"; readonly params: UserInputSettledParams; } | { readonly method: "session/modelChanged"; readonly params: SessionModelChangedParams; } | { readonly method: "session/reasoningEffortChanged"; readonly params: SessionReasoningEffortChangedParams; } | { readonly method: "session/goalChanged"; readonly params: SessionGoalChangedParams; } | { readonly method: "session/todoListChanged"; readonly params: SessionTodoListChangedParams; } | { readonly method: "session/branchChanged"; readonly params: SessionBranchChangedParams; } | { readonly method: "session/tokenUsage"; readonly params: SessionTokenUsageParams; } | { readonly method: "session/contextUsage"; readonly params: SessionContextUsageParams; } | { readonly method: "session/approvalModeChanged"; readonly params: SessionApprovalModeChangedParams; } | { readonly method: "session/nameChanged"; readonly params: SessionNameChangedParams; } /** * The one-shot login-swap route disclosure (#25603, tdd SS4.6.8): routed to * its own non-storing outcome — a notice, never sticky session state. */ | { readonly method: "session/modelRouteUnserved"; readonly params: SessionModelRouteUnservedParams; } /** * The delivery-plane marker (tdd SS4.8). A `ViewEvent` since spec 14990 * T032, and the ONE arm that changes no session state: it names a hole in * DELIVERY rather than a change in the view, so what it moves is the fold's * CURRENCY — see `pendingGap` below. * * D-24021-1 excluded it deliberately and left the flip to this lane * ("that lane owns removing the exclusion"). The flip is recorded in * `specs/14990-muse-sdk/decisions/23980-fold-the-view-gap-marker.md`. What * changed is not the structural fact D-24021-1 named — the marker still * carries no `viewCursor` and no `sourceRange`, and still seeds nothing into * either store — but the SDK's answer to it: a frame this package fully * recognizes may not be reported as `ignoredUnrecognizedMethod`, because * that is the report reserved for a NEWER host's method this SDK has never * heard of (SS1.5.4), and a consumer told that cannot tell a tolerated * unknown from an unfilled hole. */ | { readonly method: "view/gap"; readonly params: ViewGapParams; } /** * The monitor-inbox trio: per-monitor inbox admissions, drains, and * wake-missed voids. A NOTICE, never sticky state — the SDK holds no * inbox store, so the per-task projection (tails, badges, counts, * retirement) is consumer-folded from the facts on the outcome. The * params ride the outcome verbatim for the consumer to fold once. */ | { readonly method: "monitor/inboxItemAdmitted"; readonly params: MonitorInboxItemAdmittedParams; } | { readonly method: "monitor/inboxItemDrained"; readonly params: MonitorInboxItemDrainedParams; } | { readonly method: "monitor/inboxItemVoided"; readonly params: MonitorInboxItemVoidedParams; }; /** * Compile-time coverage: every `MspNotification` except the handshake's * `initialized` and the live host-state projections is a fold input. A #206 * enrollment that adds a view notification fails HERE rather than being * silently ignored at runtime. * * The exclusions, each excluded because it is NOT a view event: * - `initialized` is a HANDSHAKE notification, answered by `MspHandshake` * before this fold exists. (The `view/gap` exclusion D-24021-1 recorded * was removed by spec 14990 T032 when the marker became the `ViewEvent` * arm above.) * - `session/started` and `session/closed` (#33065's enrollment) are SS2.6 * control-plane lifecycle broadcasts, not view events — they carry no * foldable view envelope (no `sourceRange`; `closed`'s `viewCursor` is a * final session head, not view state), and a fold over session-view state * has nothing to apply. Today a lifecycle frame keeps the pre-enrollment * runtime answer (`ignoredUnrecognizedMethod`, pinned by the session-fold * test); #33102 owns consciously surfacing the pair, the same sanctioned * initial-drop shape D-24021-1 used for `view/gap` before T032 flipped it. * - `session/viewHealthChanged` (#32557, ADR 32557) is a COMMAND-PLANE * health push, not a view-fold event: it reports that the live view * stream stopped, so it carries no cursor and folds into no transcript * state. A consumer handles it out of band (mark the session view * unhealthy), never through this fold. * - `usage/changed` (ADR 32563 D3, tdd SS3.23) is a live HOST-STATE push * outside the view altitude: no `viewCursor`, no `sourceRange`, nothing * seeded into either store; it reaches consumers through the ordinary * notification hook, not this fold. * - `skill/changed` (ADR 32471 D3, tdd SS3.22.2) is a live host-state * projection carrying `sessionId` only — no `viewCursor`, no * `sourceRange`, nothing durable to fold. The SDK consumer's reaction is * an ACTION (re-issue `skill/list`), not fold state, so it rides the * client's notification callback rather than this fold. */ type AssertNever = T; type LiveHostStateProjection = "skill/changed" | "usage/changed" | "session/statusChanged" | "session/listChanged" | "plugin/changed" | "plugin/marketplaceChanged" | "computerUseSetup/changed"; type SessionLifecycleBroadcast = "session/started" | "session/closed"; type NonFoldNotification = "initialized" | "userInput/engaged" | "session/viewHealthChanged" | "session/leaveCompleted" | "recap/completed" | SessionLifecycleBroadcast | LiveHostStateProjection; type UnfoldedViewNotification = Exclude; export type EveryViewNotificationIsFolded = AssertNever; /** * The reverse direction: every `ViewEvent` arm names a REAL generated * notification. `Exclude` silently ignores excluder members outside the * union, so without this a typo'd or later-retired arm would compile clean * as dead code (PR #23087 review round). */ type StaleViewArm = Exclude; export type NoStaleViewArm = AssertNever; /** * The same reverse pin for the hand-typed exclusion literals above: * `Exclude` silently ignores excluder members outside the union, so a * retired or renamed `initialized` — or a lifecycle name that leaves the * union — would otherwise compile clean as dead vocabulary (the PR #23087 * rule, applied to this file's only other hand-typed method set). */ type StaleExclusion = Exclude; export type NoStaleExclusion = AssertNever; /** * A recursively read-only view of a value. * * The fold's snapshot getters hand back its OWN stored objects — copying * every read would cost more than the fold saves — and the generated wire * types carry no `readonly`, so without this a consumer could write through * a snapshot into fold state. This is composition over the generated types, * never a restatement of one (INV-001). * * `readonly` is compile-time only; this is a type-level seal against * accidental writes, not a runtime freeze. The alternative — deep-freezing * or cloning on every read — would be a real cost on the hot path for a * hazard the compiler can price at zero (PR #23087 review round). */ export type DeepReadonly = T extends (infer U)[] ? readonly DeepReadonly[] : T extends ReadonlyArray ? readonly DeepReadonly[] : T extends object ? { readonly [K in keyof T]: DeepReadonly; } : T; /** The session-state family keys: the `session/*` half of `ViewEvent`. */ export type SessionStateMethod = ViewEvent["method"] & `session/${string}`; /** * The methods whose arms can drop a frame as stale. Narrow rather than the * whole `ViewEvent["method"]`: a consumer switching on a stale drop would * otherwise have to "handle" cases like `item/delta` that no arm produces, * and could never match exhaustively. A future arm that starts dropping * widens this deliberately (PR #23087 review round). */ export type StaleDroppableMethod = "turn/started" | "turn/completed" | "turn/retracted" | "turn/foregroundCompleted" | "turn/retryScheduled" | "turn/modelRetryScheduled" | "approval/requested" | "approval/updated" | "userInput/requested" | "view/gap"; /** * The `NoStaleViewArm` sibling for the union above: these are hand-typed * literals, so without this pin a typo'd or later-retired member compiles * clean and a consumer told to match `ignoredStaleFrame` exhaustively has to * "handle" a case no arm can produce (PR #23087 review round). */ type StrayStaleDroppable = Exclude; export type EveryStaleDroppableIsWireVocabulary = AssertNever; /** Where a turn stands. Client fold state — not a wire vocabulary. */ export type TurnState = "running" | "settled" | "retracted" | "unqueued"; /** A turn as the fold has observed it. */ export interface TurnEntry { readonly turnId: string; /** The submitting command, when an event carried it. */ readonly commandId?: string; readonly state: TurnState; /** The server's terminal, verbatim. Absent unless `turn/completed` folded. */ readonly terminal?: TurnTerminal; /** Present iff the terminal was `"failed"` (tdd SS4.5.1). */ readonly error?: TurnError; /** The latest scheduled model retry; non-terminal (tdd SS4.5.1, D-026). */ readonly retryScheduled?: TurnRetryScheduledParams; /** * The latest scheduled provider-backoff retry; non-terminal * (tdd SS4.5.1, ADR 50047 D1/D2). The model-attempt twin of * `retryScheduled`: a failing model attempt's backoff is waiting out * its delay. Clears on the turn's terminal. */ readonly modelRetryScheduled?: TurnModelRetryScheduledParams; /** * The latest foreground-completed notice; non-terminal (tdd SS4.5.12). * Tells a renderer the answer is done while `blockingAgents` hold the * turn; clears on the turn's terminal. */ readonly foregroundCompleted?: TurnForegroundCompletedParams; } /** An approval awaiting its first durable terminal. */ export interface PendingApproval { readonly approvalId: string; readonly requested: ApprovalRequestParams; /** The most recent `approval/updated` refresh, if any. */ readonly latestUpdate?: ApprovalUpdatedParams; } /** What one `apply` did — enough to drive a render without re-diffing. */ export type FoldOutcome = { readonly kind: "item"; readonly outcome: ItemApplyOutcome; } | { readonly kind: "itemDelta"; readonly outcome: DeltaApplyOutcome; } | { readonly kind: "turn"; readonly turnId: string; readonly state: TurnState; } /** * `outcome` is sealed for the same reason every getter is (D-14): the * store hands back the very object it just stored, so an unsealed * `current` is a live handle into fold state — `out.outcome.current * .modelId = "..."` would mutate the fold with no cast and break INV-002 * replay equality. This and `approvalPending.requested` (#36949) are the * two `FoldOutcome` arms that carry a params object; every other arm * carries only ids, numbers, and booleans, which is why D-14 originally * missed this one (#23556 item 2). Both seals are pinned by * `@ts-expect-error` write probes in `session-fold.test.ts`. */ | { readonly kind: "sessionState"; readonly outcome: DeepReadonly>; } /** * `approval/reviewing` (tdd SS5.5.1, spec 209 FR-52080-1): the native * reviewer holds this approval. A typed-activity NOTICE, never a pending * entry — the approval is absent from `pendingRequests` and no decide is * open until `approval/requested` releases it (or `approval/resolved` * settles it) on the same id. Ids only, so nothing here needs sealing. */ | { readonly kind: "approvalReviewing"; readonly approvalId: string; } | { readonly kind: "approvalPending"; readonly approvalId: string; /** * The pending entry's retained request (#36949): the facade's * `approval/updated` re-route needs the request-shape members, and * carrying them on the verdict itself makes "the fold vouched for this * entry" and "here is that entry" one fact instead of a re-lookup a * contract drift could silently miss. */ readonly requested: DeepReadonly; } | { readonly kind: "approvalResolved"; readonly approvalId: string; /** False when a durable terminal had already landed; the first wins. */ readonly firstTerminal: boolean; } | { readonly kind: "userInputPending"; readonly userInputId: string; } | { readonly kind: "userInputSettled"; readonly userInputId: string; readonly firstSettlement: boolean; } | { /** * A `view/gap` folded: push delivery dropped everything in the open * interval `(after, next)`, so this fold is no longer current until a * sanctioned recovery fills it (tdd SS4.8, FM-003). * * The bounds are the FRAME's own, verbatim — the coalesced outstanding * hole is `pendingGap`, which is what a recovery walks. Reported rather * than dropped because the SDK recognizes this frame completely: an * `ignoredUnrecognizedMethod` here would be indistinguishable from a * newer host's genuinely unknown method (SS1.5.4). */ readonly kind: "deliveryGap"; readonly after: string; readonly next: string; } /** * The one-shot login-swap route disclosure (#25603, tdd SS4.6.8): a NOTICE, * not a state family — it seeds no store (a repair via `session/setModel` * would never clear a stored copy, and a snapshot reseed would wipe it), so * the params ride the outcome verbatim for the consumer to render once. */ | { readonly kind: "modelRouteUnserved"; readonly params: SessionModelRouteUnservedParams; } /** * One monitor-inbox trio fact, surfaced verbatim (see the `ViewEvent` * arms above): the method names which of the three arrived, and the * params carry that event's fact members for the consumer's own fold. * Fresh wire objects, never store handles, so no D-14 seal applies. */ | { readonly kind: "monitorInbox"; readonly method: "monitor/inboxItemAdmitted" | "monitor/inboxItemDrained" | "monitor/inboxItemVoided"; readonly params: MonitorInboxItemAdmittedParams | MonitorInboxItemDrainedParams | MonitorInboxItemVoidedParams; } | { readonly kind: "ignoredUnrecognizedMethod"; readonly method: string; } | { /** * A frame that carried no `params`. The generated `Notification` * declares `params` optional ("omitted entirely when empty"), so the * wire can legally deliver one — but no `ViewEvent` arm is foldable * without its params object, every one of them reads at least a * `sessionId`. Dropped rather than thrown: SS1.5.4's posture is that a * frame this SDK cannot fold leaves the rest of the fold intact, and * throwing here would kill the consumer's notification pump * (PR #23087 review round). */ readonly kind: "ignoredMissingParams"; readonly method: string; } | { /** * A recognized frame the fold deliberately dropped, leaving state * unchanged: a `turn/started`, `turn/retryScheduled`, * `turn/modelRetryScheduled` or `turn/foregroundCompleted` for a turn * that already left `running`; a `turn/completed` for a `retracted` or * `unqueued` turn; a declined `turn/retracted` for a turn the fold has * not seen; an `approval/updated` for an approval it never saw * requested (or whose entry a resolution already deleted); or a * redelivered `approval/requested` / `userInput/requested` for an * approval/prompt that already has its durable terminal; or a * `view/gap` whose `next` an earlier frame already named while the * hole is open (`id` is that `next`, #62032). Truthful by * contract: a "pending" kind here would make a renderer show an entry * the fold does not hold (PR #23087 review round). * * SCOPE. "Dropped" means only that THIS fold's state is unchanged; it * does not mean the frame carried nothing. The one known post-terminal * frame that carries a first-delivery fact — an `approval/updated` * whose `policyPersistence` reports a FAILED `localPersistent` store * write, which the server sends AFTER `approval/resolved` (tdd * SS5.5/SS6.8) — is dropped here with no accessor holding the reason. * Surfacing it is deferred to the facade slice: issue #23379. */ readonly kind: "ignoredStaleFrame"; readonly method: StaleDroppableMethod; readonly id: string; }; /** * The fold's read-only view of its `ItemStore`. Every mutation goes through * `apply()`; a directly reachable `seed`/`applyDelta` would let a consumer * desync the composite view (items cleared, turn/pending maps not), breaking * INV-002 replay equality (PR #23087 review round). * * An ALLOWLIST, stated explicitly: TypeScript never checks `Omit`'s keys * against the type, so a renamed or newly added `ItemStore` mutator would * land on this exported surface silently. An explicit member list admits * only what is written here, in both drift directions. * * Hiding the mutator METHODS is only half the job. The values these * accessors return ARE the fold's stored objects and the generated wire * types carry no `readonly`, so `fold.items.get(id).revision = 0` used to * typecheck AND stick — defeating the INV-003 revision guard and breaking * INV-002 replay equality through the read surface the mutator allowlist * was supposed to seal. Every accessor therefore returns `DeepReadonly` * (PR #23087 review round). */ export interface FoldItems { accumulated(itemId: string, field?: string): string | undefined; accumulatedFields(itemId: string): readonly string[]; readonly ephemeralSessionDiscarded: boolean; get(itemId: string): DeepReadonly | undefined; has(itemId: string): boolean; isTerminalUnknown(itemId: string): boolean; lastOpenedItemId(): string | undefined; list(): readonly DeepReadonly[]; readonly size: number; } /** * The fold's read-only view of its `SessionStateStore`, stated explicitly * rather than derived: the store's `get`/`has` take a bare `string`, so * `get("session/modelchanged")` would typecheck and silently read * `undefined`. The family key IS protocol vocabulary, so name it — this * surface goes public in this PR, and narrowing it later is a break. */ export interface FoldSessionState { /** * GENERIC in the family, so the return type is that family's params * object rather than the 7-way union. With the union, a wrong-family cast * (`get("session/goalChanged") as SessionTokenUsageParams`) was a legal * narrowing and read the wrong shape silently — the same invisible-miss * class the key narrowing closed on the argument side. The method→params * map already exists in `ViewEvent`; this just reuses it. */ get(family: M): DeepReadonly["params"]>> | undefined; has(family: SessionStateMethod): boolean; /** * Every family that holds a value, in insertion order. Narrowed in the * RETURN position too, so the obvious consumer loop — * `for (const f of families()) get(f)` — round-trips against this same * interface instead of forcing the bare-string cast the parameter * narrowing exists to forbid. */ families(): readonly SessionStateMethod[]; } /** * The whole client-side session view: items, session state, turns, and the * pending approval/user-input sets. */ export declare class SessionFold { #private; /** Items, snapshot-only: the mutators are `apply()`'s, not the consumer's. */ get items(): FoldItems; /** * Discharge the SS2.13.3b ephemeral host-death obligation over the item * half: every item still in progress becomes terminal-unknown and the fold * refuses further events (spec FM-002; slice 3's T030b). * * A FOLD-level method rather than a `FoldItems` entry on purpose. `items` is * snapshot-only — an allowlist whose whole job is that a store mutator can * never leak onto the consumer surface (PR #23087 review round) — and this * IS a mutator. Widening the allowlist to reach it would undo that; routing * it here keeps exactly one caller (`Session.hostExited`) and leaves the * read surface untouched. The in-progress probe stays the caller's, so the * store remains wire-shape-blind (INV-001). */ markEphemeralHostDeath(isInProgress: (item: Item) => boolean): readonly TerminalUnknownItemAnnotation[]; /** * The session-state families, keyed by notification method name. Named * `sessionState` rather than `state` because the surface contract reserves * `state` for the FR-008 aggregate that arrives with snapshot ingestion. * Snapshot-only, like `items`. */ get sessionState(): FoldSessionState; /** The turn currently running, if one is. */ get activeTurnId(): string | undefined; /** * The outstanding delivery hole, or `undefined` when there is none (tdd * SS4.8, FM-003). * * COALESCED, keeping the FIRST `after`: consecutive overflows are one hole * from a filler's point of view, which is the client mirror of the server's * own D-16487-1 bracket rule. A recovery walks `(after, next)` and calls * `gapFilled(next)` with the target it actually reached, so a hole that grew * while the walk was in flight cannot be reported filled. * * A later frame moves `next` to its own, but one repeating a `next` already * named while the hole is open is stale and changes nothing (#62032). */ get pendingGap(): DeepReadonly | undefined; /** * Is this fold current with the session view? * * FM-003's "only then reports current": false from the moment a `view/gap` * folds until its hole is filled. Live events keep folding meanwhile — they * are real facts and dropping them would add a second hole — so this flag is * the ONLY statement that the transcript has a hole in it. */ get current(): boolean; /** * A recovery filled the hole up to `next`. Returns whether it cleared. * * EQUALITY, never a relational compare (tdd SS4.1): the argument is the * target the walk reached, and it clears only when that is still the * outstanding target. A gap that arrived mid-walk left a newer `next`, so * this answers `false` and the fold stays not-current — the truthful answer, * since the second hole is unfilled. * * A fold-level mutator with the same rationale as `markEphemeralHostDeath`: * `items`/`sessionState` are snapshot-only allowlists, and this belongs to * neither store. */ gapFilled(next: string): boolean; /** * Fold one view event. * * The `default` arm is not defensive padding: the wire is an external * boundary and SS1.5.4 makes a NEW notification additive evolution, so a * host newer than this SDK really does reach it. Ignoring the frame keeps * the rest of the fold intact, which is what tolerance means. The second * overload is how such a frame gets in: the wire's actual shape is * `{ method: string, params }`, and the ONE tolerance cast lives here * rather than at every call site — a hand-written literal for a KNOWN * method should use the `ViewEvent` overload and keep its typo checking * (PR #23087 review round). * * `params` is OPTIONAL on the wide overload because the generated * `Notification` — the exact type this package's own `NotificationHandler` * hands consumers — declares it optional. Requiring it here would make * `fold.apply(notification)` a TS2769 and force every wiring site to * re-wrap the frame, which is the ceremony this overload exists to remove. */ apply(event: ViewEvent): FoldOutcome; apply(event: { readonly method: string; readonly params?: unknown; }): FoldOutcome; /** Turns in first-observed order. */ turns(): readonly DeepReadonly[]; turn(turnId: string): DeepReadonly | undefined; /** Approvals awaiting a durable terminal, in first-observed order. */ pendingApprovals(): readonly DeepReadonly[]; /** The winning durable terminal per approval, in first-observed order. */ resolvedApprovals(): readonly DeepReadonly[]; /** * Prompts awaiting a durable settlement, in first-observed order. The * entries ARE the generated request params — the wire shape already carries * `userInputId`, so a wrapper would only duplicate it (unlike * `PendingApproval`, which earns its wrapper with `latestUpdate`). */ pendingUserInputs(): readonly DeepReadonly[]; /** The winning durable settlement per prompt, in first-observed order. */ settledUserInputs(): readonly DeepReadonly[]; } export {};