import { type BrowserProfileWithDeclarations } from './profiles.js';
import { type ProfileName } from './types.js';
/**
 * How many devices declare this profile — the shipped model's two kinds.
 *
 * NOT the deleted `scope: local | fleet`, which described where the config was
 * stored. `fungible` means several devices declare the name and each uses its
 * own browser; `identity` means exactly one does, so the name is a singular
 * browser. Prune only ever sees profiles THIS device declares, so `fungible`
 * here means "declared here and elsewhere too".
 */
type PruneProfileClass = 'identity' | 'fungible';
/**
 * Detect an identity-bearing profile whose stored endpoint is loopback, viewed
 * from a machine that is NOT the declaring device.
 *
 * That is the original `comet-local` bug: five real logins on the declaring
 * box, a bare logged-out chromium answering to the same name on a worker
 * because `cdp://localhost:N` is evaluated on the machine running the command.
 * The daemon now tunnels that shape (`resolve-target.ts`); doctor still flags
 * it so a worker does not report a green local binary/port for someone else's
 * browser. Kind comes from the registry (exactly one declaring device), never
 * from a stored field.
 */
export declare function identityLoopbackMismatch(profile: BrowserProfileWithDeclarations): {
    misfiled: false;
} | {
    misfiled: true;
    why: string;
};
/**
 * Per-profile runtime files we persist under
 * `~/.agents/.cache/browser/<composite>/`:
 *
 *  - `pid`     — child process ID we spawned (or 0 if attached to an
 *                already-running browser)
 *  - `command` — basename of the executable so we can defend against pid
 *                reuse (`process.kill(pid, 0)` only proves *some* process
 *                with that id exists; if the OS recycled it for an
 *                unrelated daemon, we'd happily attach to garbage)
 *  - `meta.json` — richer record: which daemon spawned us, when, the
 *                user-data-dir we wrote into, optional tunnel PID. This
 *                is the file the orphan reaper reads on daemon startup.
 *  - `tasks.json` — open task state (managed elsewhere by service.ts)
 *
 * The one-value-per-file fields are kept for backward compat with older
 * builds; `meta.json` is additive and consulted preferentially.
 */
export interface ProfileRuntime {
    pid: number;
    port?: number;
    command?: string;
    /** Full path of the user-data-dir we passed to --user-data-dir, used by the reaper to confirm. */
    userDataDir?: string;
    /** PID of the daemon that spawned this. When the daemon dies, the next one reaps. */
    daemonPid?: number;
    /** Wall-clock time of spawn — useful for diagnostics and TTL-based cleanup. */
    spawnedAt?: number;
    /** What kind of process: 'browser' (Chrome-family), 'electron' (Notion etc.), or 'tunnel' (ssh -L). */
    kind?: 'browser' | 'electron' | 'tunnel';
    /** Local ssh -L PID, if this profile is SSH-backed. Distinct from `pid` (which is the remote browser, normally 0). */
    tunnelPid?: number;
}
/**
 * Save the runtime record atomically. We write the legacy one-value-per-
 * file fields plus a JSON meta blob so future code can read either.
 * The cache directory may not exist yet (first launch); we create it.
 */
export declare function writeProfileRuntime(profileName: string, runtime: ProfileRuntime): void;
/** Read just the JSON meta record. Returns null when absent or malformed. */
export declare function readProfileRuntimeMeta(profileName: string): ProfileRuntime | null;
/**
 * Read the runtime triple. Returns null when the files are missing OR when
 * the recorded pid no longer points at the same process we launched —
 * stale data is auto-cleaned to keep the next caller from acting on it.
 */
export declare function readProfileRuntime(profileName: string): ProfileRuntime | null;
/** Remove the pid/port/command/meta files. Leaves chrome-data + tasks.json intact. */
export declare function clearProfileRuntime(profileName: string): void;
/**
 * Recursively remove the whole profile cache (chrome-data, tasks.json,
 * everything). Used by `profiles remove` so an old profile name doesn't
 * leak its history into a freshly-recreated one.
 */
export declare function removeProfileCache(profileName: string): void;
/**
 * Find every cache directory belonging to a given profile. A runtime dir name
 * IS a connection key (`<name>@<endpoint>[.<fork>]`), so a single agents-cli
 * profile can have several side by side; this finds them all, plus the legacy
 * non-composite dir from older builds.
 *
 * Membership is decided by {@link keyBelongsToProfile} — the same single rule
 * `status`, `stopProfile`, and `findTask` use (RUSH-2709), so a fork dir is no
 * longer missed here while being matched there.
 */
export declare function listProfileCacheDirs(profileName: ProfileName): string[];
/**
 * `process.kill(pid, 0)` answers "is a process with this id alive?" — but
 * pid reuse is real on long-uptime machines, and a stale cache pointing
 * at a since-reassigned pid would happily call the imposter ours.
 *
 * Strategy: if we recorded the executable basename when we launched, ask
 * `ps` what command the live pid is running and compare. No command on
 * record means we fall back to the existence check (older cache entries
 * or `pid:0` for "attached to an externally-launched browser").
 */
export declare function isProcessAlive(pid: number, expectedCommand?: string): boolean;
/**
 * Snapshot of one tracked profile, suitable for `agents browser ps` output.
 * Combines the on-disk meta record with live-process probes so callers can
 * tell at a glance which entries are alive, stale, or have outright leaked.
 */
export interface ProfileSnapshot {
    /** Composite name as the cache dir is keyed: `<profile>` or `<profile>@<endpoint>`. */
    name: string;
    /** Absolute path of the cache dir. */
    dir: string;
    meta: ProfileRuntime | null;
    /** Live-process probe: does the recorded pid still exist + match command? */
    pidAlive: boolean;
    /** Live-process probe for the tunnel pid (SSH profiles). */
    tunnelAlive: boolean;
    /** True iff the daemon that started this is still alive. False == orphaned. */
    daemonAlive: boolean;
    /** Number of tasks recorded in tasks.json (open browser tabs). */
    taskCount: number;
}
/**
 * Read every profile cache directory and produce a structured snapshot.
 * Works without the daemon — `agents browser ps` uses this to render a
 * complete state view even when the IPC server is down. The caller can
 * post-process to detect conflicts (e.g. two profiles with the same port,
 * or a port someone else is listening on).
 */
export declare function listAllProfileSnapshots(): ProfileSnapshot[];
/**
 * Reap browser + tunnel processes spawned by daemons that no longer exist.
 * Call once on daemon startup. The idea: every process we spawn records
 * its daemonPid in meta.json. If that daemon is dead (crashed, SIGKILL),
 * its children were left rootless — kill them now so they don't hijack
 * the next session's local ports.
 *
 * We're conservative: a record with no daemonPid (older builds) is left
 * alone — we'd rather leak than wrongly kill a user-owned process that
 * happens to share metadata.
 */
export declare function reapOrphanedProcesses(): {
    reaped: number;
    details: string[];
};
/**
 * True when ANY cache dir belonging to this profile still has a live browser or
 * SSH tunnel, or an open task. The composite naming (`<name>@<endpoint>`) means
 * one profile can own several runtime dirs, so checking only
 * `getProfileRuntimeDir(name)` would miss a running composite — and prune must
 * never remove a profile that is in use.
 *
 * A recorded `pid: 0` means "attached to a browser someone else launched", so it
 * is not our process to protect; an open task on that dir still counts as in use.
 */
export declare function isProfileInUse(profileName: string): boolean;
/** Why a profile is considered dead. */
export type PruneReason = 'binary-missing' | 'never-used';
export interface PruneCandidate {
    name: string;
    scope: PruneProfileClass;
    reason: PruneReason;
    /** Cache dirs that would be removed along with the config entry. */
    cacheDirs: string[];
}
export interface PrunePlan {
    /** Profiles that would be (or were) removed. */
    candidates: PruneCandidate[];
    /** Profiles deliberately left alone, with the guard that kept them. */
    kept: Array<{
        name: string;
        scope: PruneProfileClass;
        why: string;
        /**
         * True for an identity-bearing profile whose loopback endpoint only makes
         * sense on the declaring device. A structured flag, not a substring of
         * `why`: both the CLI's human output and `--json` consumers branch on it,
         * and sniffing the reason text would couple them to wording.
         */
        misfiled?: boolean;
    }>;
}
export declare const PRUNE_REASON_TEXT: Record<PruneReason, string>;
/**
 * Decide which browser profiles are dead, WITHOUT touching anything.
 * `pruneProfiles` executes exactly this plan, so `--dry-run` and the real run
 * can never disagree.
 *
 * Dead means one of {@link PruneReason}: the configured browser cannot launch
 * here at all, or the profile has never been started so nothing on this disk
 * refers to it. That second reason is what actually reclaims agent-minted
 * throwaway profiles (RUSH-2716) — they are created for an installed browser, so
 * only "never started" identifies them.
 *
 * Four guards, and each exists because removing that profile would be wrong
 * rather than merely tidy:
 *   - **in use** — a live browser, SSH tunnel, or open task on any of its dirs.
 *   - **the configured default** — this machine resolves a bare
 *     `agents browser start` to it; removing it breaks that command.
 *   - **the auto-detected profile** (`auto-chrome`, or the `default` an older
 *     build wrote) — still recognized and resolved by
 *     `ensureDefaultBrowserProfile` as this machine's default; since PHNX-3296
 *     it is no longer re-created on demand, so pruning it forces the user
 *     back through `agents setup` to get a default browser again.
 *
 * Known limitation: `BrowserProfileConfig` records no creation time, so a
 * profile created seconds ago and not yet started is indistinguishable from an
 * abandoned one and is reported `never-used`. That is why the verb is explicit
 * and `--dry-run` exists.
 */
export declare function planProfilePrune(profiles: Array<{
    name: string;
    scope: PruneProfileClass;
    launchableHere: boolean;
    /** Set when this identity-bearing profile's endpoint only makes sense on one machine. */
    misfiledWhy?: string;
}>, opts?: {
    configuredDefault?: string;
}): PrunePlan;
/**
 * Build the prune plan from live config. Split from {@link planProfilePrune} so
 * the decision rules stay unit-testable without a configured machine.
 */
export declare function buildProfilePrunePlan(): Promise<PrunePlan>;
/**
 * Execute a prune plan: drop each candidate's config entry and wipe its cache
 * dirs. Returns the plan that was applied so the caller can report exactly what
 * went, matching what `--dry-run` printed.
 */
export declare function pruneProfiles(plan: PrunePlan): Promise<PrunePlan>;
export {};
