import type { SessionStore } from '../types/session.js'; /** * Per-project lockfile used for crash detection. The CLI writes one of * these alongside the session JSONLs (`/active.json`) * when an interactive run starts, and deletes it on clean exit. If we * find one on the next launch whose owning PID is dead (or whose host * doesn't match), we know the previous run was killed mid-flight and * the session it was writing to is a recovery candidate. * * The lockfile is intentionally per-project (already isolated by * `wpaths.projectSessions`), so two TUIs in two different repos do not * fight each other. */ export interface RecoveryLockOptions { /** Directory the lockfile lives in. Usually `wpaths.projectSessions`. */ dir: string; /** This process's PID. Default: `process.pid`. */ pid?: number | undefined; /** Hostname recorded for the lock. Default: `os.hostname()`. */ hostname?: string | undefined; /** Locks older than this are considered orphaned (disk wiped, etc.). Default 24h. */ maxAgeMs?: number | undefined; /** Used to check whether the abandoned session was actually closed cleanly. */ sessionStore?: SessionStore | undefined; /** * Override the PID-liveness probe. Default: `process.kill(pid, 0)` — * succeeds (or throws EPERM) when the PID is alive, throws ESRCH when * it is gone. Tests inject a deterministic stub. */ isPidAlive?: (((pid: number) => boolean)) | undefined; } export interface AbandonedSession { sessionId: string; pid: number; startedAt: string; /** Lockfile age in ms at the time of the check. */ ageMs: number; /** Number of messages already on disk for this session. */ messageCount: number; } export declare class RecoveryLock { private readonly file; private readonly pid; private readonly hostname; private readonly maxAgeMs; private readonly sessionStore?; private readonly probe; constructor(opts: RecoveryLockOptions); /** * Examine the lockfile and decide whether it represents an abandoned * session. Returns `null` if the file is missing, points to a live * instance, references a clean-closed session, is too old, or is * malformed. Otherwise returns enough detail to prompt the user. * * Important: this is a read-only check. We never delete an active * lock from here — if another wstack instance is alive, the caller * should bail or run with a fresh session instead. */ checkAbandoned(): Promise; /** * Claim the lock for the given session. Uses exclusive-create (`O_EXCL`) * to detect whether another process acquired the lock between our * `checkAbandoned()` call and now. If the file already exists, it means * another process won the race and we throw instead of silently * overwriting their recovery record. * * The caller MUST have already called `checkAbandoned()` and handled its * null return before calling this. */ write(sessionId: string): Promise; /** * Release the lock. Idempotent — silently succeeds if the file is * already gone (e.g. someone else cleared it, or the directory was * wiped). */ clear(): Promise; private readLock; } //# sourceMappingURL=recovery-lock.d.ts.map