export type SessionLockState = 'attached' | 'paused' | 'running-detached'; export type DisconnectPolicy = 'pause' | 'continue'; export type SessionLockAgentStatus = 'idle' | 'running' | 'cancelling'; export interface SessionLockInfo { pid: number; sessionId: string; startedAt: string; /** `/proc/sys/kernel/random/boot_id` when the lock was taken (POSIX only). */ bootId?: string; /** `/proc//stat` starttime (field 22) of `pid` when the lock was taken. */ pidStart?: string; tty?: string; sock?: string; state?: SessionLockState; disconnectPolicy?: DisconnectPolicy; agentStatus?: SessionLockAgentStatus; } export declare class SessionLockHeldError extends Error { readonly lock: SessionLockInfo; readonly path: string; constructor(lock: SessionLockInfo, path: string); } export declare function sessionLockPath(sessionId: string, dshHome?: string): string; /** * Pre-digest lock path (`tui-locks/.json`). * * 0.7.1 Hosts still write here. Lookup must find that file; a new Host must * not bind a second lock beside a live one just because the filename changed. */ export declare function legacySessionLockPath(sessionId: string, dshHome?: string): string; /** Digested lock first, then the 0.7.1 name when it is different. */ export declare function sessionLockLookupPaths(sessionId: string, dshHome?: string): string[]; export declare function parseSessionLock(raw: string): SessionLockInfo | undefined; /** True when `pid` still exists on this machine (best-effort). */ export declare function processIsAlive(pid: number): boolean; /** Slice of a Windows process identity needed to rule out pid reuse. */ export interface WindowsProcessIdentity { /** Executable name without `.exe`, lowercased (`node`). */ name: string; /** Process creation time in epoch milliseconds, when readable. */ startedAt?: number; } /** Clock/resolution slack when comparing a creation time with the lock write. */ export declare const PID_REUSE_SLACK_MS = 5000; /** * Parse the probe's stdout. Everything after the last non-empty line is * ignored (a stray warning banner must not masquerade as the image name), and * an unexpected shape is reported as "unverifiable" rather than as a * mismatch: mistaking noise for a foreign process would steal a live lock. */ export declare function parseWindowsProcessIdentity(stdout: string): WindowsProcessIdentity | undefined; /** Image name this Host runs as (`node` / `dsh`), without `.exe`. */ export declare function hostImageName(execPath?: string): string; /** * Decide whether a live Windows pid can still be the lock's Host. * * Windows recycles pids aggressively, and without procfs an unrelated process * inheriting the recorded pid used to look like a permanent live-but-silent * Host ("zombie"), which blocked `--resume` until the lock was deleted by * hand. Two facts rule reuse out: * - the Host always runs this same executable, so a different image name is * someone else's process; * - the Host existed before it wrote the lock, so a process created after the * lock was written cannot be its owner. * An unverifiable process keeps the legacy best-effort answer (alive). */ export declare function windowsProcessMatchesLock(lock: SessionLockInfo, identity: WindowsProcessIdentity | undefined, expectedName?: string, slackMs?: number): boolean; /** * True when `pid` is genuinely the Host process that wrote `lock`. * * `processIsAlive` alone is not enough: the pid may have been recorded inside * a different pid namespace (sandbox/container) and then be recycled by an * unrelated host process — e.g. a lock written as pid 5 in a sandbox matches * the forever-alive pid 5 kernel thread on the host, which used to produce a * permanent false "zombie" that blocked `--resume`. We therefore verify the * process identity: * - new locks carry `bootId` + `pidStart` (boot_id + /proc//stat * starttime): a matching pair can only be the same process on the same * boot, so a recycled or cross-namespace pid fails the check; * - older locks fall back to `/proc//cmdline`: the detached Host is * always launched with `--resume=` in argv, so any other * process (kernel threads have an empty cmdline) is proven stale; * - Windows has neither: a `Get-Process` probe supplies the image name and * creation time instead (see {@link windowsProcessMatchesLock}). * On platforms where none of this is available the legacy kill(pid, 0) * behavior is kept. * * Async because the Windows probe spawns PowerShell, and this runs on the * history-listing path (the picker inspects every lock); a synchronous spawn * would freeze painting and keystrokes for the duration of the probe. */ export declare function lockOwnerIsAlive(lock: SessionLockInfo): Promise; export declare function formatLockHeldMessage(lock: SessionLockInfo): string; export declare function sessionLockDisabled(env?: NodeJS.ProcessEnv): boolean; export declare function readSessionLock(sessionId: string, dshHome?: string): Promise<{ path: string; info: SessionLockInfo; } | undefined>; export declare function writeSessionLock(path: string, info: SessionLockInfo): Promise; export declare function acquireSessionLock(sessionId: string, options?: { pid?: number; tty?: string | null; dshHome?: string; sock?: string; state?: SessionLockState; disconnectPolicy?: DisconnectPolicy; agentStatus?: SessionLockAgentStatus; }): Promise<{ path: string; info: SessionLockInfo; }>; export declare function releaseSessionLock(path: string, pid?: number): Promise; export type LiveHostKind = 'attachable' | 'zombie'; export declare function inspectLiveHost(sessionId: string, dshHome?: string): Promise<{ kind: LiveHostKind; lock: SessionLockInfo; path: string; sock: string; } | undefined>; /** Every lock file under `$DSH_HOME/tui-locks` whose Host pid is still alive. */ export declare function listAttachableHosts(dshHome?: string): Promise>;