import { type SessionRepo } from "../internal/harness.js"; import { type AcquiredSession, type SessionStore, type SessionSummary } from "./session.js"; /** What idle-eviction and `release` do to the underlying repo. */ export type EvictPolicy = "delete" | "forget"; export interface TtlSessionStoreOptions { /** Idle TTL before a cached session is evicted. Default 7 days. */ defaultTtlDays?: number; /** Optional background sweep interval (ms). Off by default (sweeps opportunistically on acquire). */ sweepIntervalMs?: number; /** * Backing session repo. Default `InMemorySessionRepo` (process-only, lost on restart). Supply a * **durable** repo (e.g. a TiDB-backed `SessionRepo`, or the vendored file repo) to persist * sessions: `acquire` then **resumes** an existing session via `repo.open`, so any stateless * runner instance — even after a restart or on another replica — continues the same conversation. */ repo?: SessionRepo; /** * What idle-eviction and `release` do to the repo: * - `"delete"` — remove the session from the repo too (correct for the throwaway in-memory repo). * - `"forget"` — only drop the in-memory cache; the **durable** backend keeps the history so it can * be resumed later (the backend runs its own retention/GC). * * Defaults to `"delete"` for the built-in in-memory repo and `"forget"` when a custom `repo` is * supplied — so a durable store never silently deletes history on an idle timer. */ evict?: EvictPolicy; } /** * TTL-cached {@link SessionStore} over a pluggable {@link SessionRepo}. * * Sessions are cached in process memory and evicted after `defaultTtlDays` of inactivity. With the * default in-memory repo they live only in this process (lost on restart). Supply a **durable** repo * and the same caching/concurrency logic becomes a "session center": on `acquire`, a session not in * the live cache is **resumed from the repo** (`repo.open`) rather than recreated — so a stateless * runner resumes a conversation after a restart or across replicas. The optimistic-lock seam * (`SessionWriteOptions.expectedLeafId` → `SessionError("conflict")`) keeps two replicas writing the * same session safe; the Runner reconciles a lost CAS and retries. * * See `design/10-会话持久化与会话中心.md` for the durable-backend (TiDB) contract. */ export declare class TtlSessionStore implements SessionStore { private repo; private entries; /** In-flight acquisitions keyed by id, so concurrent acquire(sameId) share one session. */ private pending; /** Sessions pinned by a design/45 checkpoint — skipped by idle sweep until unpinned (B6/§5). */ private pinned; private defaultTtlMs; private evictPolicy; private sweepTimer?; constructor(opts?: TtlSessionStoreOptions); /** Get an existing session by id (resuming from the repo if needed), or create one. */ acquire(sessionId?: string, opts?: { requireExisting?: boolean; }): Promise; /** Resume an existing session from the repo, or create it if the backend has no such id (unless * `requireExisting`, in which case a genuinely-missing id fails loud instead — design/114 Phase3). */ private openOrCreate; private createAndStore; /** Refresh the idle timer for a session. */ touch(sessionId: string): void; /** Record the most recent task run on a cached session (the `/resume` `lastRunId`); also bumps the idle * timer. Best-effort — a no-op if the session isn't cached here (the run will still be re-attachable via * its checkpoint). */ noteTaskRun(sessionId: string, taskId: string): void; /** List the live cache as {@link SessionSummary} projections, newest-first by `lastActiveAt` (so a shell's * `/resume` shows recent sessions + their last run first). The in-memory store lists only its live cache, * not evicted/durable history — a durable backend overrides this to list persisted sessions. */ list(): Promise; /** * design/110 (F3) — FORK a session: copy `sourceId`'s committed history into a NEW session and return its id * (or `null` when the source doesn't exist). Delegates to the backing repo's `fork`, so a durable repo persists the * branch; the in-memory repo holds it in-process. Registering the result in this cache makes `acquire(forkedId)` * hit immediately — the synchronous child run that `Agent(subagent_type:"fork")` launches resumes the fork without a repo round-trip. * * Adding this method is what makes {@link hasSessionFork} true for a `TtlSessionStore` wrapping ANY repo, so the * fork capability mounts (it was INERT before — the durable repo had `fork` but the store never exposed it). */ fork(sourceId: string, owner?: string | null): Promise; /** Drop a session from the live set. With a durable repo (`evict: "forget"`) the history is kept. */ release(sessionId: string): Promise; /** Drop only the cached view — the repo keeps the session regardless of `evictPolicy`, so a re-acquire * re-opens the SAME history (B-17: the reconcile retry / prepare-throw paths must never delete). Cost * on the default in-memory repo: ONE retained session object **per forgotten id** until process end * (invisible to `size`/`sweep`, which track the cache) — accumulation across many distinct forgotten * ids is the accepted trade-off vs silent history loss; deployments with churn use a durable repo, * where retention is the backend's normal job. A pinned id stays pinned (a forget must never expose a * suspended task's session to sweep eviction). */ forget(sessionId: string): void; /** Pin a session so idle sweep can't evict it while a design/45 checkpoint awaits resume (§5). */ pin(sessionId: string): void; /** Release a design/45 pin (on resume or checkpoint expiry); the session resumes normal idle eviction. */ unpin(sessionId: string): void; /** Evict idle-expired sessions from the cache. Deletes durable history only when `evict: "delete"`. */ sweep(now?: number): void; get size(): number; dispose(): void; } //# sourceMappingURL=session-store.d.ts.map