import { type PathCtx } from '../config/paths.js'; /** * Which accounts are being used by a running session right now. * * This exists because renewing a login ROTATES it: the token that was valid a * moment ago stops working as soon as the new one is issued. A running Claude * session holds its own copy of the login (ccx copies it into the shared session * folder so accounts can be swapped underneath a live process), so when ccx * renews that same login behind the session's back, the session is left holding a * token the server has already retired. What the operator sees is * "Login expired - please run /login" in the middle of working, having done * nothing. That is the bug this prevents. * * A session announces the account it is using by writing a small file, and keeps * it warm while it runs. Anything that renews logins skips the accounts named by * a live file, and reads usage from the session's own copy instead, which is the * fresher one anyway. * * Crash-safe on purpose: a session that dies without cleaning up leaves its file * behind, so a file only counts as live while its process still exists AND it has * been touched recently. Either test failing makes it ignorable, so a stale file * can never freeze renewals forever. */ /** A file older than this is ignored even if some process still has its pid. */ export declare const LEASE_STALE_MS = 120000; export interface SessionLease { account: string; pid: number; /** The config folder the session is actually reading its login from. */ configDir: string; /** * The working directory the session was launched in. Absent on leases written * before this existed. Used so `ccx sessions` can name a session by its * project and `ccx use --here` can target the session running in this folder. */ cwd?: string; /** Last time the session said it was still going. */ at: number; } export interface LeaseOptions { now?: () => number; /** Injected in tests; defaults to a real liveness check on the pid. */ isAlive?: (pid: number) => boolean; /** Injected in tests; defaults to `process.cwd()`. */ cwd?: string; } /** * Where THIS process's file for a given account lives. * * Named after the account AND the pid: several sessions can run one account at * the same time, and when the file was per-account the last session to start * silently took the only slot. The other sessions could not refresh the * announcement (not their pid), so their protection lapsed while they ran, * which is one of the ways a renewal killed a live session's login. Reading is * by CONTENT, so files written before the pid suffix existed still count. */ export declare function leasePath(account: string, c?: PathCtx, pid?: number): string; /** Announce that this process is now using `account`, reading from `configDir`. */ export declare function takeLease(account: string, configDir: string, c?: PathCtx, options?: LeaseOptions): void; /** * Say the session is still going. * * Only refreshes our OWN file. Touching another process's would keep its account * protected after it died, which is the failure this design is built to avoid. */ export declare function touchLease(account: string, c?: PathCtx, options?: LeaseOptions): void; /** Give up the announcement for `account`, if it is ours. */ export declare function releaseLease(account: string, c?: PathCtx): void; /** * Every account a running session is using right now. * * Files whose process is gone, or that have not been touched recently, are * ignored and cleaned up, so a crashed session cannot block renewals forever. */ export declare function liveLeases(c?: PathCtx, options?: LeaseOptions): SessionLease[]; /** * The live announcement for one account, or null when nothing is using it. * With several sessions on one account, the most recently refreshed one: that * is the session whose copy of the login is most plausibly the freshest. */ export declare function leaseFor(account: string, c?: PathCtx, options?: LeaseOptions): SessionLease | null;