/** * ActiveRoleStore — per-workspace, session-keyed, versioned JSON sidecar for * the dsh active-role selection. * * sidecar remedy: active-role durability moves off the dsh session event log * (whose unknown `rolebox/active-role` envelope rc.6 persistence refuses to * reload — see `role-switcher.ts`) into a rolebox-owned file under * `.rolebox/state`. The file is named `activerole-.json`, matching the * canonical state-file naming in `src/utils/state-paths.ts` and the sibling * stores ({@link FunctionRuntimeStore} `fnstate-`, {@link LoopStore} `loops-`). * * Shape: `{ version: 1; sessions: [{ sessionId, roleId, updatedAt }] }`. * `roleId: null` records an explicit clear back to the base agent (distinct * from a session that was never written). * * Behaviour mirrors the house stores: * - atomic writes via `src/function/fs-util.ts` (no torn reads) * - `load()` fails soft — missing/corrupt/out-of-range files return `null` * - version-range migration: files older than the current schema version are * normalized forward instead of rejected; unknown future versions are * rejected (return `null`) * - `prune()` applies a TTL and a per-file session cap in memory * - `load()` prunes on every read (TTL + cap) so the file cannot grow without * bound; when a live-session census (`ctx.sessions.list()` mapped to ids) * is supplied, a still-live session is protected from TTL eviction even * when its entry is old — an entry is evicted only when it is BOTH absent * from the census AND older than the TTL * - **never delete on `session/disposed`** — disposal is memory eviction, not * permanent deletion (`dsh-session/lib/types/index.d.ts:54`); a disposed * session may be reloaded later. This store registers no session-lifecycle * handler and evicts only lazily via TTL/cap at load time. * * The store is workspace-scoped: the caller passes the workspace directory. * This module does NOT import `@deepseek-ai/*` (or `@opencode-ai/*`). * * @module */ /** Current on-disk schema version written by {@link ActiveRoleStore}. */ export declare const ACTIVE_ROLE_STORE_VERSION = 1; /** Oldest on-disk schema version this store can migrate forward from. */ export declare const ACTIVE_ROLE_STORE_MIN_VERSION = 0; /** Default prune TTL — sessions untouched for longer are dropped (30 days). */ export declare const DEFAULT_ACTIVE_ROLE_TTL_MS: number; /** Default prune cap — the newest N sessions are retained. */ export declare const DEFAULT_ACTIVE_ROLE_MAX_SESSIONS = 200; /** * One persisted active-role selection, keyed by `sessionId` in the in-memory * map and stored inline in the file's `sessions` array. */ export interface ActiveRoleEntry { /** The dsh session id this selection applies to. */ sessionId: string; /** Active role id, or `null` for an explicit clear back to the base agent. */ roleId: string | null; /** Epoch ms of the last write for this session (used by {@link ActiveRoleStore.prune}). */ updatedAt: number; } /** * Options controlling {@link ActiveRoleStore.prune} and * {@link ActiveRoleStore.load}. */ export interface ActiveRoleStoreOptions { /** TTL in ms; entries older than this are pruned. Defaults to 30 days. */ ttlMs?: number; /** Maximum sessions retained. Defaults to 200. */ maxSessions?: number; /** * Live-session census — the session ids that still exist in the harness * (`ctx.sessions.list()` mapped to ids). May be a snapshot or a provider so * a caller can read the census fresh at load time. When supplied, a stale * entry whose session is still present is retained (a disposed session that * is still reloadable is not deleted); an entry is evicted only when it is * absent from the census AND older than the TTL. Omit it to fall back to * TTL-only eviction. */ liveSessionIds?: Iterable | (() => Iterable); } /** * Workspace-scoped store for dsh active-role selections. * * Construct with the workspace directory; all instances for the same physical * directory resolve to the same state file. */ export declare class ActiveRoleStore { private readonly directory; private readonly dirHash; private readonly ttlMs; private readonly maxSessions; /** Census provider configured at construction; see {@link ActiveRoleStoreOptions.liveSessionIds}. */ private readonly liveSessionIds?; /** Serializes async saves so a later write never interleaves an earlier one. */ private _lock; /** * @param directory - Workspace directory (the `.rolebox/state` file lives under it). * @param options - Optional prune defaults (TTL, cap, live-session census). */ constructor(directory: string, options?: ActiveRoleStoreOptions); /** Absolute path to the workspace-scoped state file. */ private statePath; /** Serialize the in-memory map into the versioned envelope. */ private toFile; /** * Persist `sessions` atomically. Async writes are serialized through an * internal lock; a write failure is logged and swallowed (best-effort). */ save(sessions: Map): Promise; private _doSave; /** Synchronous variant of {@link save}. Best-effort; failures are logged. */ saveSync(sessions: Map): void; /** * Load the persisted selections, pruning as it reads. * * Fails soft: a missing file, unreadable file, malformed JSON, or a schema * version outside `[MIN, CURRENT]` returns `null` (never throws). A version * older than {@link ACTIVE_ROLE_STORE_VERSION} is migrated forward: a * missing `roleId` becomes `null` and a missing/invalid `updatedAt` becomes * `0`. * * Lifecycle/GC: the returned map is passed through {@link prune} (TTL + cap) * before it is returned, so a file cannot grow without bound across restarts. * Passing `liveSessionIds` (from `ctx.sessions.list()`) additionally protects * a still-live session from TTL eviction. Pruning is in-memory only — persist * the compacted map via {@link save}/{@link saveSync} if compaction on disk * is desired. * * @param options - Per-call prune overrides (TTL, cap, live-session census). * @returns A session-keyed map, or `null` when nothing usable was read. */ load(options?: ActiveRoleStoreOptions): Map | null; /** * Resolve the live-session census for a prune call: the per-call override * wins over the constructor default, and a provider is invoked fresh. * Returns `undefined` when no census is configured (TTL-only eviction). */ private resolveLiveIds; /** * Apply the TTL and session cap in place. * * An entry is dropped when it is older than `ttlMs` (strictly: kept when * `now - updatedAt <= ttlMs`). An `updatedAt` of `0` means "age unknown" (a * migrated row with no timestamp) and is never TTL-evicted — only the cap may * drop it, so migration stays meaningful. When a live-session census is * configured (constructor or per-call `liveSessionIds`), a stale entry whose * session is still present in the census is retained: eviction requires the * session to be ABSENT from the census AND older than the TTL. This is why a * `session/disposed` event must never delete state — a disposed session may * still be reloadable, so only the lazy census+TTL pass evicts it. * * If the survivors exceed `maxSessions`, only the newest `maxSessions` (by * `updatedAt`, ties broken by `sessionId`) are kept. Pruning is in-memory * only — call {@link save} to persist the result. * * @param sessions - Map to prune (mutated). * @param options - Per-call overrides for the constructor defaults. * @returns The same `sessions` reference, pruned. */ prune(sessions: Map, options?: ActiveRoleStoreOptions): Map; } //# sourceMappingURL=active-role-store.d.ts.map