mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-09 04:02:35 +00:00
The persistence seam is now create/open/stat/list returning per-session SessionHandles (read/append/flush/close); every log read and write flows through the owning handle. The seam package exports only the service and handle contracts, consumer-visible errors, and pure durable-data validation helpers; each backend owns its complete storage runtime, and the shared contract suites pin equivalent observable behavior. The backend routes published sessions' live events by id into the active write handle; agent-loop only acquires, seeds, and closes the handle. Resume appends interruptedTurnClosers through its write handle; session-query owns the revision-keyed cold cache. Legacy-only surfaces are removed in the same swap: locate/readRaw/supportsRawArtifacts, the legacy event-shape read migration, zstd torn-frame salvage, DSH_SESSION_JSONL, and hook transcript_path population; a torn final zstd frame is discarded whole; the session-list cold blank probe returns on stat metadata (eventCount derived from the last physical row, sizeBytes). The WebUI ZIP export serializes the logical log from a read handle, so both backends export identically. Refs #3245
107 lines
4.7 KiB
TypeScript
107 lines
4.7 KiB
TypeScript
/**
|
|
* The per-session storage handle: one open channel onto a stored session's
|
|
* append-only event log, returned by `SessionPersistence.create`/`open`.
|
|
* @module @deepseek-ai/dsh-session-persistence/handle
|
|
*/
|
|
|
|
import type { SessionEvent, SessionHeader, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session'
|
|
|
|
/**
|
|
* Log access granted by an open. `write` is read-write: the session's single
|
|
* mutator, which also reads its own log. `read` only observes — it never
|
|
* takes ownership and works while another handle or process holds `write`.
|
|
*/
|
|
export type SessionAccess = 'read' | 'write'
|
|
|
|
/** Options for {@link SessionHandle.read}. */
|
|
export interface SessionHandleReadOptions {
|
|
/** Optional cancellation for backend read work. */
|
|
readonly signal?: AbortSignal
|
|
}
|
|
|
|
/** Options for {@link SessionHandle.append}. */
|
|
export interface SessionHandleAppendOptions {
|
|
/** Optional cancellation observed before the write starts. */
|
|
readonly signal?: AbortSignal
|
|
}
|
|
|
|
/** Options for {@link SessionHandle.flush}. */
|
|
export interface SessionHandleFlushOptions {
|
|
/** Optional cancellation observed before the barrier starts. */
|
|
readonly signal?: AbortSignal
|
|
}
|
|
|
|
/**
|
|
* One open channel onto a stored session. A handle is single-owner state, not
|
|
* a shared service: `read` never backtracks below what this handle already
|
|
* observed, a `write` handle reads its own successful appends, and `close()`
|
|
* is the one teardown (idempotent, uncancellable; `Symbol.asyncDispose`
|
|
* delegates to it). Every operation on a closed handle rejects with
|
|
* `SessionHandleClosedError`.
|
|
*
|
|
* Freshness across handles: once an `append` or `flush` resolves on a write
|
|
* handle, every read STARTED afterwards on the same backend instance — on any
|
|
* handle, or through `stat`/`list` — observes at least that prefix.
|
|
* Reads concurrent with a mutation carry no ordering promise beyond the valid
|
|
* contiguous prefix.
|
|
*/
|
|
export interface SessionHandle extends AsyncDisposable {
|
|
/** The stored session this handle addresses. */
|
|
readonly id: SessionId
|
|
/** The immutable stored header, fixed at `create`/`open`. */
|
|
readonly header: SessionHeader
|
|
/**
|
|
* Exact fork-inherited prefix length stored with the log; `0` when
|
|
* `header.isSeeded` is false. Storage metadata paired with the header for
|
|
* every body read, never part of the replayable event log.
|
|
*/
|
|
readonly inheritedEventCount: SessionLogOffset
|
|
/** Whether this handle may mutate the log. */
|
|
readonly access: SessionAccess
|
|
|
|
/**
|
|
* Read a slice of the valid contiguous logical log. The slice is a legal log
|
|
* prefix segment: a torn physical tail is never returned, and repeated reads
|
|
* on this handle never observe an older state than a prior read.
|
|
* @param offset - first logical event seq to include; defaults to `0`.
|
|
* @param length - maximum number of events to return; defaults to the rest
|
|
* of the log. An offset at or past the end returns an empty list.
|
|
* @param options - optional cancellation.
|
|
* @returns the events with `seq >= offset`, at most `length` of them.
|
|
*/
|
|
read(offset?: number, length?: number, options?: SessionHandleReadOptions): Promise<readonly SessionEvent[]>
|
|
|
|
/**
|
|
* Append a contiguous batch continuing the current logical end. The first
|
|
* event's `seq` MUST equal the stored next-seq; committed events are never
|
|
* rewritten. Persistence is best-effort: on resolution the batch is
|
|
* accepted, ordered, and visible to reads on this backend instance, but
|
|
* only a resolved {@link flush} promises it survives a crash — a backend
|
|
* may buffer or batch physical writes behind append. Rejects with
|
|
* `SessionReadOnlyError` on a read handle and `SessionOwnershipLostError`
|
|
* when write ownership is gone.
|
|
* @param events - the contiguous batch, in seq order.
|
|
* @param options - optional cancellation observed before the write starts.
|
|
*/
|
|
append(events: readonly SessionEvent[], options?: SessionHandleAppendOptions): Promise<void>
|
|
|
|
/**
|
|
* The durability barrier — the one operation that promises storage: on
|
|
* resolution every acknowledged append is durable and the session is
|
|
* materialized for other processes; an empty created session becomes
|
|
* durably listable here. Callers that must survive a crash flush; a backend
|
|
* whose `append` already persists on resolution treats this as
|
|
* materialize-if-needed. Rejects with `SessionReadOnlyError` on a read
|
|
* handle.
|
|
* @param options - optional cancellation observed before the barrier starts.
|
|
*/
|
|
flush(options?: SessionHandleFlushOptions): Promise<void>
|
|
|
|
/**
|
|
* Release the handle: a read handle frees local resources; a write handle
|
|
* completes pending durability and releases write ownership. Idempotent,
|
|
* asynchronous, and deliberately not cancellable.
|
|
*/
|
|
close(): Promise<void>
|
|
}
|