import type { SessionSurface } from './session-surface.js'; export interface LivenessMarker { readonly sessionId: string; readonly pid: number; readonly updatedAt: number; } /** * A marker older than this is treated as stale even if its pid happens to * still resolve to a running process (pid reuse after a crash), the marker * is refreshed on the host's recovery cadence (60s), so anything past * ~2.5x that cadence means the writer stopped refreshing it. */ export declare const LIVENESS_STALE_AFTER_MS = 150000; /** Absolute path to the directory holding every session's liveness marker. */ export declare function livenessMarkerDirFor(surface: SessionSurface): string; /** Absolute path to a session's liveness marker file. */ export declare function livenessMarkerPathFor(surface: SessionSurface, sessionId: string): string; /** Refresh (creating if needed) the liveness marker for this session/pid. Best-effort, never throws. */ export declare function writeLivenessMarker(surface: SessionSurface, sessionId: string, pid?: number): void; /** Best-effort removal on exit. Never throws. */ export declare function removeLivenessMarker(surface: SessionSurface, sessionId: string): void; /** Best-effort check that a pid still refers to a running process. Injectable for tests. */ export declare function isPidAlive(pid: number, kill?: (pid: number, signal: 0) => void): boolean; export interface SessionLivenessCheck { readonly live: boolean; readonly pid: number | null; } /** * Best-effort: is `sessionId` apparently open in another still-running * process right now? A missing, stale (older than LIVENESS_STALE_AFTER_MS), * or unreadable marker all resolve to `{ live: false, pid: null }`, never a * throw, never a block on the caller. */ export declare function checkSessionLiveness(surface: SessionSurface, sessionId: string, opts?: { readonly now?: () => number; readonly isPidAliveFn?: typeof isPidAlive; }): SessionLivenessCheck; /** * Hard ceiling on marker files kept after a sweep, oldest dropped first. * * The liveness rule below already reclaims ordinary crash residue, but it can * be defeated: `isPidAlive` deliberately reports EPERM (a pid this user cannot * signal) as alive, so on a busy host a reused pid can keep a dead session's * marker "live" indefinitely. This cap is the second, unconditional bound so * the directory can never grow without limit whatever the pid probe says. */ export declare const LIVENESS_MARKER_MAX_FILES = 200; export interface LivenessReapResult { /** Marker files examined this sweep. */ readonly scanned: number; /** Marker files deleted this sweep. */ readonly reaped: number; } export interface LivenessReapOptions { readonly now?: () => number; readonly isPidAliveFn?: typeof isPidAlive; /** * Session ids that must never be reaped regardless of what the marker says *, normally just the current session's id. Belt-and-braces: the current * session refreshes its own marker every 60s, so the liveness rule already * keeps it. */ readonly keepSessionIds?: readonly string[]; /** Override the count cap (tests). */ readonly maxFiles?: number; } /** * Delete liveness markers that no longer describe a running session. * * A marker is reaped when it is either: * - unreadable, empty, or not shaped like a marker (crash residue that can * never resolve to a live session again, validated by parsing the * content, not by the file merely existing); or * - definitively not live: older than `LIVENESS_STALE_AFTER_MS` AND its pid * no longer resolves to a running process. * * Both halves of that second rule are required on purpose. Deleting is * destructive while a liveness answer is only advisory, so a marker that is * stale-but-pid-alive (a wedged owner still holding the session) or * fresh-but-pid-dead (a process that died seconds ago, or a pid probe that * failed transiently) is left for a later sweep. A genuinely crashed session * satisfies both conditions within `LIVENESS_STALE_AFTER_MS` and is reclaimed * on the next sweep after that. * * Idempotent and safe to run concurrently from several processes: a marker * another sweeper unlinked between the directory listing and this unlink * (ENOENT) counts as reaped, never as an error. */ export declare function reapStaleLivenessMarkers(surface: SessionSurface, opts?: LivenessReapOptions): LivenessReapResult; //# sourceMappingURL=session-liveness-marker.d.ts.map