export { Session } from "../internal/harness.js"; export type { SessionApi } from "../internal/harness.js"; export { InMemorySessionRepo, InMemorySessionStorage, uuidv7, validateEntriesForImport, StreamingImportValidator, } from "../internal/harness.js"; export { BaseSessionStorage, leafIdAfterEntry, } from "../internal/harness.js"; export type { SessionStorage, SessionRepo, SessionMetadata, SessionTreeEntry, } from "../internal/harness.js"; export { SessionError } from "../internal/harness.js"; export type { SessionWriteOptions } from "../internal/harness.js"; import type { SessionApi } from "../internal/harness.js"; import type { SessionTreeEntry } from "../internal/harness.js"; /** True when `err` is a session optimistic-lock conflict (a concurrent writer won the branch leaf). */ export declare function isSessionConflict(err: unknown): boolean; /** A session handed back by a {@link SessionStore}: the harness `Session` plus its resolved id. */ export interface AcquiredSession { session: SessionApi; sessionId: string; } /** * A lightweight, read-only projection of one session for a `/resume` list (shell K-5 — re-attach without an * N+1 walk). Returned in bulk by {@link SessionStore.list}, newest-first by `lastActiveAt` by convention. * `lastTaskId` is the engine's term for what the service/wire surfaces as the session's **`lastRunId`** — the * most recent task run on this session, so a shell re-attaches in ONE hop. */ export interface SessionSummary { /** The session id. */ sessionId: string; /** When the session was created ({@link SessionMetadata.createdAt}, ISO string); absent when the backend's * cache view doesn't carry it. */ createdAt?: string; /** Epoch ms the session was last acquired / touched / ran — the newest-first sort key for "recent sessions". */ lastActiveAt: number; /** The most recent task run on this session (core's `taskId`; the service projects it as `lastRunId`). * Absent until a task has run on the session (recorded via {@link SessionStore.noteTaskRun}). */ lastTaskId?: string; /** F7 fork-GC seam: the parent session this one was forked from ({@link SessionMetadata.forkedFrom}). * Lets a consumer render/reap fork lineage; absent on non-forked sessions and on cache views that * predate the fork. The durable source of truth is the repo's persisted metadata. */ forkedFrom?: string; } /** * The session lifecycle/caching layer the {@link Runner} depends on. * * `TtlSessionStore` is the default (in-memory, 7-day idle TTL). Provide a custom implementation * to back sessions with durable, external storage (the "Session center"): `acquire` reconstructs * a session from the persisted event log so any stateless runner instance can resume it. * * Implementations must be safe under concurrent `acquire` of the same id (return one session). */ export interface SessionStore { /** Get an existing session by id, or create one (optionally with a caller-supplied id). design/114 Phase3: * `opts.requireExisting` ⇒ a store MUST fail loud (throw a `not_found` {@link SessionError}) on a genuinely * missing id rather than create-on-miss — so a reuse-style warm-resume of a gone session errors instead of * silently starting a fresh empty run. A store that cannot honor it MUST still not silently create (either * implement the check or reject the option). */ acquire(sessionId?: string, opts?: { requireExisting?: boolean; }): Promise; /** Mark a session recently active (resets idle TTL where applicable). May be async for durable stores. */ touch(sessionId: string): void | Promise; /** Drop a session from the live set (e.g. a throwaway task). Durable history may be retained by the backend. */ release(sessionId: string): Promise; /** * Drop only the CACHED view of a session — **never** deletes history (unlike `release`, which with the * in-memory `TtlSessionStore`'s default `evict: "delete"` policy removes the session from the repo too). * The Runner uses this where the intent is "discard a stale view and re-wake", and on a prepare-phase * throw after acquire (audit B-17 + the 1.93.0 throw-cleanup contract gap: calling `release` there * silently DELETED an existing session's whole history on the default store — the re-acquire then * continued on an empty same-id session). **Optional, but custom stores SHOULD implement it**: when * absent the Runner performs NO fallback (it never substitutes `release`, whose contract permits * deletion — B-17 council) — the cached view is simply not dropped, so a persistent reconcile conflict * exhausts its retry budget and propagates instead of risking history. A store whose `release` is * already history-safe can implement `forget = release`. */ forget?(sessionId: string): void | Promise; /** * Protect a session from idle eviction while a design/45 checkpoint references it (a suspended task * must still be resumable). `pin` before suspend, `unpin` on resume/expiry. **Optional**: a durable * backend that never idle-GCs sessions (service's `tidb-session-store`) implements both as no-ops and * is still safe; only the in-memory `TtlSessionStore` needs a real pin (its `sweep` skips pinned ids). * See design/45 §5 / M3. */ pin?(sessionId: string): void | Promise; unpin?(sessionId: string): void | Promise; /** * Record that a task run STARTED on a session (the Runner calls this at task start, when both ids are * known) so the store can surface it as {@link SessionSummary.lastTaskId} — letting a `/resume` list * re-attach to the most recent run, even one still in-flight or suspended. **Optional + best-effort**: a * store that surfaces no session list may ignore it; it MUST NOT throw. The engine's `taskId` is the * service's `lastRunId`. */ noteTaskRun?(sessionId: string, taskId: string): void | Promise; /** * Enumerate known sessions as lightweight {@link SessionSummary} projections (newest-first by * `lastActiveAt`) so a shell's `/resume` lists sessions + their last run in ONE call. **Optional**: the * in-memory `TtlSessionStore` lists its live cache; a durable backend lists persisted sessions. A store * that cannot enumerate omits this (the shell then has no session-list affordance — honest degrade). */ list?(): Promise; /** * design/110 — FORK a session: copy `sourceId`'s durable history (root→leaf) into a NEW session and return its * id (or `null` if the source doesn't exist). The forked session is independent (writes to it don't touch the * source). Backs CC `/fork` (continue with the parent's full context) AND the inheriting-context subagent fork * (run a child on the fork so it inherits the parent context + shares the prompt-cache prefix). **Optional**: * a store implements it only if it can replay history. `TtlSessionStore` DOES — it delegates to its * {@link SessionRepo} (so {@link hasSessionFork} is true) — BUT the fork is only as durable as that repo: * an in-memory repo forks IN-PROCESS yet loses the copy on restart, while a durable repo (file/pg) makes the * fork survive restart/replica. **Capability presence ≠ durability** — a cross-replica consumer (design/114 * warm-resume) MUST wire a durable repo; core cannot verify durability, only fail-loud on a missing source at * fork time (returns `null`). `owner` (if given) scopes the new session for multi-tenant stores. MUST copy the COMMITTED leaf (an in-flight * uncommitted turn of an active source is not included — a fork is a snapshot at the fork point). */ fork?(sourceId: string, owner?: string | null): Promise; /** Number of live/cached sessions (best-effort). */ readonly size: number; /** Release resources (timers, connections). Best-effort; must not throw. */ dispose(): void | Promise; } /** Structural detection: does this store expose the optional {@link SessionStore.fork}? Mounts the fork * capability only when true (INERT otherwise — mirrors `hasScheduler`/`hasBackgroundShell`). */ export declare function hasSessionFork(store: SessionStore): store is SessionStore & { fork: NonNullable; }; /** * F3 bounded-wake recipe, packaged. Given the **path-ordered** entries of a branch * (root→leaf, e.g. `await session.getBranch(leafId)` or a durable store's leaf-walk), * returns the smallest self-contained tail a stateless runner needs to resume: * * - `floorEntryId` = the **latest** compaction's `firstKeptEntryId` (null if never compacted), * - `tail` = entries from that floor to the leaf — i.e. the surviving compaction summary * plus every message kept after it. Everything before the floor is already folded into * that summary, so it can stay cold in durable storage. * * Hand the result straight to a floored storage: * `new InMemorySessionStorage({ entries: tail, floorEntryId })` (or your durable equivalent). * `buildContext()` on that is identical to waking from the full tree — see `session-floor.test.ts`. * * Pure and allocation-light: it scans for the last compaction and slices. If the tree was never * compacted (or the floor isn't on this path — shouldn't happen for a real branch) it returns the * full input unchanged with `floorEntryId: null`, so callers can always use the result verbatim. */ export declare function boundedTail(pathEntries: readonly SessionTreeEntry[]): { tail: SessionTreeEntry[]; floorEntryId: string | null; }; //# sourceMappingURL=session.d.ts.map