/**
 * Typed wrapper over the external `crabbox` binary (github.com/openclaw/crabbox).
 *
 * crabbox leases ephemeral cloud boxes (Hetzner/DO/EC2/…), syncs the dirty
 * checkout, and runs commands on them. We use it as the transport for
 * `agents run --lease`: warm a box → run the agent on it via `crabbox run` →
 * stop it. crabbox owns the SSH connection, so agents-cli never needs a direct
 * ssh target (unlike the host-dispatch model).
 *
 * crabbox talks to its cloud provider's API for list/status/warmup/stop, which
 * needs a provider token (e.g. HCLOUD_TOKEN) in the environment. We inject it
 * from a secrets bundle when one is configured (see `crabboxEnv`).
 */
import { type SecretsBundle } from '../secrets/bundles.js';
/** A crabbox machine as reported by `crabbox list --json`. */
export interface CrabboxBox {
    /** Provider machine name, e.g. `crabbox-blue-hermit-1039689b`. */
    name: string;
    /** Provider run state, e.g. `running`. */
    status: string;
    /** Friendly slug used with `--id`, e.g. `blue-hermit`. */
    slug: string;
    /** Lease id, e.g. `cbx_9968746bb15c`. */
    lease: string;
    /** crabbox bootstrap state; `ready` once the box is usable. */
    state: string;
    /** Public IPv4, when the provider exposes one. */
    ip?: string;
    /** Tailnet IPv4 (100.x), when the box was leased with `--network tailscale`. */
    tailscaleIPv4?: string;
    /** Tailnet MagicDNS FQDN, when the box joined the tailnet. */
    tailscaleFQDN?: string;
    profile?: string;
    class?: string;
    /** True when running + bootstrap-complete. */
    ready: boolean;
    /** crabbox `keep` label — a kept box survives `crabbox cleanup` past its TTL. */
    keep: boolean;
    /** Unix seconds the box was created, or null when the label is absent. */
    createdAt: number | null;
    /** Unix seconds the lease expires, or null. */
    expiresAt: number | null;
    /** Unix seconds the box was last touched (reused / run against), or null. */
    lastTouchedAt: number | null;
    /** Idle-timeout window in seconds, or null. */
    idleTimeoutSecs: number | null;
}
export interface CrabboxOptions {
    /**
     * Name of a secrets bundle whose env (e.g. `HCLOUD_TOKEN`) crabbox needs to
     * reach its cloud provider. Resolved via agents-cli's own keychain-backed
     * secrets. When unset, crabbox runs with the ambient environment / its own
     * `crabbox login` credentials.
     */
    secretsBundle?: string;
    /**
     * Hard cap (ms) on a single crabbox invocation that hits the provider API
     * (`list`). Prevents a slow/unreachable provider from hanging an ambient
     * command like `agents devices`. Defaults to 8s; callers on a fast/interactive
     * path (devices list, `agents ssh` fall-through) pass a shorter bound.
     */
    timeoutMs?: number;
}
/** Locate the crabbox binary, or throw an actionable error. */
export declare function findCrabbox(): string;
/**
 * Env keys that mark a secrets bundle as usable for `--lease` — the provider
 * tokens crabbox reads to reach a cloud API. Matching a bundle needs only its
 * declared key NAMES; only the matched key's VALUE is ever injected (see
 * `crabboxEnv`), so an auto-detected bundle can't leak its other secrets.
 */
export declare const LEASE_PROVIDER_TOKEN_KEYS: string[];
/** The first bundle that declares a provider token key, or undefined. Pure over `bundles`. */
export declare function pickLeaseBundleFromList(bundles: SecretsBundle[]): string | undefined;
/**
 * Env keys a bundle may use for a Tailscale auth key. crabbox reads
 * `CRABBOX_TAILSCALE_AUTH_KEY` to join a leased box to the tailnet; we accept the
 * common alternate names too and rename to that canonical key on injection.
 */
export declare const TAILSCALE_AUTH_KEY_NAMES: string[];
/** The first bundle + key that declares a Tailscale auth key, or undefined. Pure over `bundles`. */
export declare function pickTailscaleBundleFromList(bundles: SecretsBundle[]): {
    name: string;
    key: string;
} | undefined;
/** Test seam: drop every crabboxEnv secrets memo so each test resolves fresh. */
export declare function resetCrabboxSecretsMemosForTest(): void;
/** A resolved lease bundle: its name, plus (auto-detect only) the exact keys to inject. */
export interface ResolvedLeaseBundle {
    name: string;
    /** When set (auto-detect), inject ONLY these keys — not the whole bundle. */
    keys?: string[];
}
/**
 * The secrets bundle to feed crabbox, resolved in priority order:
 *   1. `AGENTS_LEASE_SECRETS_BUNDLE` env var          — explicit, no keychain
 *   2. `lease.secretsBundle` config (set by `lease setup`) — explicit, no keychain
 *   3. auto-detect: the first keychain bundle DECLARING a provider token key
 *
 * Tiers 1–2 are the frictionless steady state (env + config are plain, no keychain
 * read). Tier 3 is a fallback that DOES read bundle metadata via `listBundles()`
 * (one batched keychain unlock, ~7-day broker cache) — so it only runs when
 * neither env nor config is set, and `crabboxEnv` memoizes the result for the
 * process (it is called several times per lease, and we don't want a scan each
 * time). Once `lease setup` persists the choice (tier 2), tier 3 never runs.
 * Returns undefined when nothing matches — crabbox then falls back to `crabbox login`.
 */
export declare function resolveLeaseBundle(): ResolvedLeaseBundle | undefined;
/** Persist `lease.secretsBundle` in agents config so `--lease` needs no env var. */
export declare function setLeaseSecretsBundle(name: string): void;
/** Build the child env for crabbox, injecting a secrets bundle when configured. */
export declare function crabboxEnv(opts: CrabboxOptions): NodeJS.ProcessEnv;
/** All crabbox machines the broker knows about. */
export declare function crabboxList(opts?: CrabboxOptions): CrabboxBox[];
/** Find one box by slug, or null. */
export declare function crabboxFind(slug: string, opts?: CrabboxOptions): CrabboxBox | null;
/**
 * Whether `crabbox status` reports the box SSH-ready (`ready=true`). A box whose
 * cloud-init bootstrap failed still LISTS as `running` but never becomes ready —
 * selecting it burns the full SSH wait before hard-failing. `crabbox status`
 * flips ready=true only once sshd answers, so warm-pool reuse gates on it
 * (mirrors scripts/sandbox.sh's `box_ready`).
 */
export declare function crabboxStatusReady(slug: string, opts?: CrabboxOptions): boolean;
export interface PoolMatchOptions {
    /**
     * Lease pool label (shared default, or `.crabbox.yaml leaseProfile:` opt-in;
     * see config.ts). Both sides normalize an unset profile to
     * DEFAULT_CRABBOX_PROFILE, so profile-less runs match unlabeled boxes.
     */
    profile?: string;
    /**
     * Network mode of the run (default 'public'). A tailnet-joined box is never
     * handed to a public run, nor a public box to a tailnet run — reachability and
     * exposure differ, so the pool is partitioned by it.
     */
    netMode?: 'public' | 'tailscale';
    /** Injectable clock (unix seconds) for the expiry check. */
    nowSecs?: number;
}
/**
 * Warm boxes in the profile pool this run could reuse: `running`, same profile
 * label, same network mode, lease unexpired — most-recently-touched first.
 *
 * Readiness is NOT required here (deliberately mirrors sandbox.sh's
 * `running_slugs_for_profile`, which filters on `status` only): the list `state`
 * label can lag, so the caller gates each candidate on `crabboxStatusReady`
 * before committing. A not-ready box is skipped, never stopped — a concurrent
 * run may be mid-boot on it, and crabbox's idle timeout reaps genuine duds.
 */
export declare function poolReusableBoxes(boxes: CrabboxBox[], opts?: PoolMatchOptions): CrabboxBox[];
export interface WarmupOptions extends CrabboxOptions {
    class?: string;
    profile?: string;
    /** Provision web code-server capability on the box. */
    code?: boolean;
    /** Cloud backend override (crabbox provider id, e.g. hetzner/aws/do). */
    provider?: string;
    /**
     * Network mode for the leased box. `'public'` (default) leases with a public
     * IP; `'tailscale'` joins the box to the tailnet (`--network tailscale`,
     * tagged `tag:crabbox`) so it is reachable only over Tailscale. The caller
     * (command layer) decides WHEN to enable this — F5 here is plumbing only.
     */
    netMode?: 'public' | 'tailscale';
}
/**
 * Lease a box and block until it is ready. Returns the leased box.
 *
 * We diff `crabbox list` before/after so we reliably identify the box this call
 * created even if warmup's stdout format changes — the new lease id is the one
 * that wasn't present before.
 */
export declare function crabboxWarmup(opts?: WarmupOptions): Promise<CrabboxBox>;
/**
 * Poll until the box reports ready, or throw after timeoutMs.
 * `sleep` is injectable so tests don't wall-clock wait.
 */
export declare function crabboxWaitReady(slug: string, opts?: CrabboxOptions & {
    timeoutMs?: number;
    intervalMs?: number;
    sleep?: (ms: number) => Promise<void>;
}): Promise<CrabboxBox>;
export interface CrabboxRunOptions extends CrabboxOptions {
    /** Called with each chunk of combined stdout/stderr as it streams. */
    onData?: (chunk: string) => void;
    /** Force a full remote resync before running. */
    fullResync?: boolean;
    /** Refresh an existing lease with this idle window before running. */
    renewIdleTimeoutSecs?: number;
}
/**
 * Run `remoteCmd` on the leased box via `crabbox run` (crabbox syncs the dirty
 * checkout and owns the SSH). Streams combined output; resolves with the remote
 * exit code (or null if crabbox itself failed to dispatch).
 */
export declare function crabboxRun(slug: string, remoteCmd: string, opts?: CrabboxRunOptions): Promise<number | null>;
/**
 * Upload `script` to the box via `crabbox run --script-stdin` and run it.
 *
 * The script body travels over stdin and is written to a file on the box before
 * execution — it never appears in argv / `ps` / shell history, which is why this
 * is the transport for credential provisioning (the token contents live only in
 * the uploaded script, then the file is removed by the script itself).
 * Streams combined output; resolves with the remote exit code (null on dispatch failure).
 */
export declare function crabboxRunScript(slug: string, script: string, opts?: CrabboxRunOptions): Promise<number | null>;
/**
 * Parse crabbox's shell-quoted `ssh` command (`'ssh' '-i' '<key>' … 'crabbox@ip'`)
 * into an argv array. Exported for testing.
 */
export declare function parseCrabboxSshArgv(stdout: string): string[] | null;
/**
 * The concrete `ssh` argv crabbox uses to reach box `slug` — including the
 * per-lease identity key and known_hosts crabbox provisions under its own config
 * dir. A raw `ssh crabbox@ip` fails `publickey`; this is the only auth that works.
 * `crabbox ssh --id` PRINTS the command (it doesn't connect — that's `crabbox
 * connect`), and `--reclaim` makes it resolve regardless of which repo holds the
 * lease claim (matching `crabboxRunScript`). Returns null when crabbox can't
 * resolve the box (caller treats setup-copy / `agents ssh` as best-effort).
 */
export declare function crabboxSshArgv(slug: string, opts?: CrabboxOptions): string[] | null;
/** Release the lease / delete the box. Best-effort; never throws. */
export declare function crabboxStop(slug: string, opts?: CrabboxOptions): boolean;
/** Never reap a box touched within this many seconds, regardless of idle-timeout. */
export declare const REAP_MIN_IDLE_SECS = 3600;
/**
 * Whether a box is a genuine orphan that is safe to reap.
 *
 * Reap-safe ONLY when BOTH hold: the lease has already expired (`expiresAt` in the
 * past) AND the box has not been touched for a safety window of
 * `max(2 × idleTimeout, 1h)`. The freshness guard is what makes this safe against
 * a TOCTOU race: a box a concurrent run just reused (`cbx_acquire_box`) has a
 * recent `lastTouchedAt` and is never eligible. Reaping by `profile`/`ready` alone
 * — as `crabbox cleanup` cannot (it skips `keep=true`, which every real orphan is)
 * — would kill in-use boxes. Boxes with unknown age (`expiresAt`/`lastTouchedAt`
 * null) are never reaped. `nowSecs` is injected so tests don't wall-clock.
 */
export declare function isReapSafe(box: CrabboxBox, nowSecs: number): boolean;
/** The reap-safe orphans among `boxes`, most-stale (oldest touch) first. */
export declare function reapSafeOrphans(boxes: CrabboxBox[], nowSecs: number): CrabboxBox[];
/**
 * List reap-safe orphans and (unless `dryRun`) stop them. Returns the candidates
 * considered and the slugs actually stopped. Best-effort per box — a stop failure
 * is skipped, never thrown. Backs `agents devices lease prune` and the 403 auto-reap opt-in.
 */
export declare function reapOrphans(opts?: CrabboxOptions & {
    nowSecs?: number;
    dryRun?: boolean;
}): {
    candidates: CrabboxBox[];
    reaped: string[];
};
