/** * Every seat's installed version (DES-seats-page A1): `RosterSeat.version`. * * `version_pin` (core#581, `diagnostics.ts`) exists only for a seat whose ACP config pins a build * (opencode). Every other seat read "Not reported" on the Agents page, although the registry record * already carries the argv that answers it (`version_probe`, e.g. `["claude", "--version"]`). * * The reading, per seat: * - `installed`: the first non-empty line the seat's `version_probe` prints (stdout, then stderr), * bounded and with the home directory written `~`; `null` when it did not answer; * - `proven` / `status`: from the pin reading when the seat pins a build (`proven` = the pinned * build; `status` = `proven` when the pin holds, else `unproven`), `no_pin` otherwise. A pinned * seat whose pin has not been probed yet carries no `version` at all, never a guessed status; * - `why` (crew#1004, `unproven` only): `no_answer` when the pin's own `--version` probe got no * answer; `different_build` when it answered naming another build, which `observed` carries; * `invalid_answer` when it answered but the engine's first-line, byte-for-byte check rejected it * without naming another build (the pinned build on a later line, in brackets, coloured, after a * `v`, or no version at all). A build is named by its id alone, pre-release and build suffixes * included, and only as a whole token ({@link buildsNamed}): `1.17.18-beta` is another build, * `1.18.31.4` names none; `opencode (1.17.18)`, a coloured `1.17.18` and `v1.17.18,` only * present the pinned one; * - `install_source`: how the binary on PATH was installed, read off its resolved path (no * spawn): under `node_modules` = `npm`, under a Homebrew prefix = `brew`, else `other`; `null` * when the binary is not on PATH. It lets the Agents page offer a real update command instead of * guessing one. The path itself never reaches the wire. * * crew#1004, a deliberate choice: `status` comes from the pin reading alone, never from `installed`. * The pin reading mirrors the engine's spawn-time check (`probe_resolved_binary_version`: the pin's * own binary, its first line byte for byte), and the engine switches the seat's safety checks off * when that check gets no answer. So a pin probe with no answer stays `unproven` (fail closed) even * when `installed` reads exactly the pinned build: `why: 'no_answer'` says the build was not shown * to differ, only not confirmed. Likewise a probe the engine's check rejects although it names the * pinned build (`"\n1.17.18\n"`: the check reads the blank first line) stays `unproven` with * `why: 'invalid_answer'`, never `different_build`. * * `installed` and the pin reading are two snapshots from two caches with their own TTLs * ({@link SEAT_VERSION_TTL_MS} here, the pin cache's in `diagnostics.ts`), so after an upgrade they * disagree for up to one TTL: the pin probe already saw the new build while `installed` still reads * the pinned one. So `different_build` carries `observed`, the build the pin's check saw, and the * `version_unproven` message names that build, never `installed`. * * Cost: one bounded `--version` per seat per {@link SEAT_VERSION_TTL_MS}, in the background; the * roster read never waits on it. A test-built server spawns nothing (no source under a runner). */ import type { SeatVersionPin } from './diagnostics.js'; /** How long a version reading is trusted before a background refresh. */ export declare const SEAT_VERSION_TTL_MS: number; /** A read where some seat's `--version` did not answer (a slow start on a loaded host) is retried * this soon, not after the full TTL. */ export declare const SEAT_VERSION_RETRY_MS: number; /** How long a seat's last answered version outlives later probes that did not answer. */ export declare const SEAT_VERSION_KEEP_MS: number; export type SeatInstallSource = 'npm' | 'brew' | 'other'; /** One seat's probed version (what the cache keeps). */ export interface SeatVersionReading { cli: string; installed: string | null; checked_at: string; install_source: SeatInstallSource | null; } /** Why a pinned seat reads `unproven` (crew#1004; see the module doc). */ export type SeatVersionUnprovenWhy = 'no_answer' | 'different_build' | 'invalid_answer'; /** The builds a `--version` answer names, in order, presentation stripped; `[]` when it names none. */ export declare function buildsNamed(answer: string): string[]; /** `RosterSeat.version` (api-types 0.115.0; `why` and `observed` 0.131.0): `why` exactly when * `unproven`; `observed` (the build the pin's check saw) exactly when `why` is `different_build`; * `proven` is the pinned build, `null` only on `no_pin`. */ export type SeatVersion = { installed: string | null; checked_at: string; install_source: SeatInstallSource | null; } & ({ status: 'no_pin'; proven: null; } | { status: 'proven'; proven: string; } | { status: 'unproven'; proven: string; why: 'no_answer' | 'invalid_answer'; } | { status: 'unproven'; proven: string; why: 'different_build'; observed: string; }); /** Runs one version probe; resolves to its combined output, or `null` when it did not answer. */ export type VersionExec = (cmd: string, args: string[]) => Promise; /** Resolves a binary name to its real path on PATH, or `null`. */ export type BinaryResolver = (binary: string) => string | null; /** The binary's real path on PATH (`which` + realpath), or `null` when it is not there. */ export declare function resolveOnPath(binary: string, env?: NodeJS.ProcessEnv): string | null; /** How a binary was installed, from its resolved path (see the module doc). */ export declare function installSourceOf(realPath: string | null): SeatInstallSource | null; /** The first non-empty line of a probe's output, bounded and home-free; `null` for none. */ export declare function firstVersionLine(output: string | null): string | null; /** Probe every seat's version (in parallel, each bounded). */ export declare function probeSeatVersions(roster: unknown[], exec?: VersionExec, resolve?: BinaryResolver, now?: () => number): Promise; /** A TTL cache of {@link probeSeatVersions}: read synchronously, refreshed in the background. */ export declare class SeatVersionCache { private readonly source; private readonly ttlMs; private readonly now; /** `missing` — some installed seat's probe did not answer: the read is retried after * {@link SEAT_VERSION_RETRY_MS}. `silent` — per seat whose last answer was kept, what this probe * said, served once the kept answer is older than {@link SEAT_VERSION_KEEP_MS}. */ private value; private inFlight; private failedAt; constructor(source?: (() => Promise) | null, ttlMs?: number, now?: () => number); private stale; /** Probe now unless a probe is running; resolves when the answer is in. */ refresh(): Promise; /** The last reading for `cli`, without waiting; a stale or empty cache starts a refresh. */ read(cli: string): SeatVersionReading | undefined; } /** The daemon's source: the engine's production roster. `null` under a test runner (no spawns). */ export declare function defaultSeatVersionSource(): (() => Promise) | null; /** The daemon's one version cache. */ export declare const seatVersions: SeatVersionCache; /** * `RosterSeat.version` from a seat's reading and its pin reading (see the module doc). `undefined` * when there is no reading yet, or when the seat pins a build whose pin has not been probed. */ export declare function seatVersion(seat: { acp?: { verified_version?: unknown; } | null; }, reading: SeatVersionReading | undefined, pin: SeatVersionPin | undefined): SeatVersion | undefined; //# sourceMappingURL=seat-version.d.ts.map