/**
 * Account rotation across agent versions.
 *
 * Detects which installed versions have expired credentials and rotates
 * authentication tokens so users maintain active sessions across version switches.
 */
import type { AgentId, RunStrategy } from '../types.js';
import type { FallbackEntry } from '../exec.js';
import { PROJECTION_HORIZON_MIN, capacityWeight } from './capacity.js';
import { type AccountInfo, type CredentialPresence } from '../agents.js';
import { type EventPayload } from '../feed/events.js';
import { type UsageSnapshot } from './usage.js';
import { type AuthVerdict } from '../auth-health.js';
export interface RotateCandidate {
    agent: AgentId;
    version: string;
    accountKey: string | null;
    accountLabel: string;
    email: string | null;
    /**
     * Per-org usage/quota key (e.g. `claude:org=<orgUuid>`) — the unit rate
     * limits are actually measured in. Distinct orgs signed in under the same
     * email have distinct keys, so this is the correct dedup boundary; null when
     * no usage identity is available (then we fall back to email).
     */
    usageKey: string | null;
    usageStatus: AccountInfo['usageStatus'];
    usageSnapshot: UsageSnapshot | null;
    usageError: string | null;
    /**
     * Projected minutes until this account's 5-hour session window caps, as
     * computed by the daemon's burn-rate refresher and read from the headroom
     * cache. `null` when unknown (cold cache, idle, or not burning up). Balanced
     * routing deprioritizes an account projected to cap soon — see
     * {@link capacityWeight} — so a launch avoids an account racing toward its
     * limit, not just one already 100%-maxed.
     */
    usageMinutesToLimit: number | null;
    plan: string | null;
    signedIn: boolean;
    /**
     * Live auth-health verdict for this (agent, version) from the daemon's probe
     * cache (`auth-health.ts`), or null when no probe row exists (cold cache, or a
     * harness with no live-probe endpoint). `signedIn` only means "a credential
     * file is present and its email decodes" — it cannot tell a good token from a
     * revoked-but-unexpired one, so a server-rejected account reads
     * `signedIn: true` but `authVerdict: 'revoked'`. Eligibility excludes a
     * revoked account so rotation never launches into a doomed auth (see
     * {@link readinessFromCandidate}). Fail-open: any non-revoked or null verdict
     * does not gate — a stale/absent probe never blocks a launch.
     */
    authVerdict: AuthVerdict | null;
    /** Epoch milliseconds of the auth probe behind authVerdict, when present. */
    authCheckedAt?: number | null;
    lastActive: Date | null;
    /**
     * Set only for a candidate that comes from a provider account bundle rather
     * than a native version-home login (RUSH-3182): the account name to inject via
     * the `--account` spawn path (`resolveSpawnAccount` → `accountEnv`). Undefined
     * for a native login, whose credential already lives in `version`'s home. The
     * run path routes on this so a balanced pick of a setup-token / API-key account
     * authenticates through the existing provider-account injection.
     */
    providerAccount?: string;
}
export interface RotateResult {
    /** The version picked for this run. */
    picked: RotateCandidate;
    /** Candidates that were considered healthy (including the picked one). */
    healthy: RotateCandidate[];
    /** Candidates excluded (not signed in, or out of credits). */
    excluded: RotateCandidate[];
    /**
     * True when the PICKED candidate's usage could not be verified fresh, so the
     * route was decided on an unverified snapshot. Callers surface it — routing
     * blind is a fact the operator needs, not an internal detail. (Previously
     * this only reported the all-stale case; a stale pick out of a mixed pool
     * was silently reported as verified.)
     */
    usageUnverified?: boolean;
    /**
     * True when NO candidate carries a fresh usage snapshot AND at least one
     * carries a STALE-but-present one — the "entirely stale usage" case
     * (PHNX-2526). The INITIAL route MUST NOT be decided on a stale number that
     * looks plausible but is wrong (the yosemite-s1 incident: 26h–2.7d-old
     * snapshots read 48% while the account was at its weekly cap). `picked` is
     * still populated (a stale candidate) so `healthy` stays intact for BOUNDED
     * post-rejection failover, but a caller doing the initial selection MUST NOT
     * launch it — it diverts to the account picker (interactive) or fails loud
     * with NO_VERIFIED_USAGE (unattended). Distinct from a BLIND pool with no
     * snapshot at all (a worker box whose usage endpoint 403s, RUSH-2392): that
     * carries no misleading number, so it still draws a pick and this stays
     * false.
     */
    noVerifiedUsage?: boolean;
}
export declare const RUN_STRATEGIES: RunStrategy[];
/**
 * Return a run strategy when the input is valid, otherwise null.
 *
 * `'rotate'` is accepted as a deprecated alias for `'balanced'` so old yaml
 * configs and `--strategy rotate` invocations keep working. The legacy alias
 * normalizes to `'balanced'` and uses the weighted-random algorithm.
 */
export declare function normalizeRunStrategy(value: unknown): RunStrategy | null;
/** Read project-local run strategy from the nearest agents.yaml, if present. */
export declare function getProjectRunStrategy(agent: AgentId, startPath: string): RunStrategy | null;
/**
 * Resolve the configured strategy. Lookup order:
 *   1. project-local agents.yaml (nearest to `startPath`)
 *   2. ~/.agents/.system/agents.yaml
 *   3. default: `balanced` (weighted-random across all healthy accounts by
 *      remaining headroom, skipping any that are currently rate-limited). A
 *      bare `agents run <agent>` — e.g. every new terminal the extension spawns
 *      — should spread load and never launch into a throttled account, rather
 *      than stick to the pinned default even when it's maxed.
 */
export declare function getConfiguredRunStrategy(agent: AgentId, startPath?: string): RunStrategy;
/** Persist the global run strategy used by bare `agents run <agent>`. */
export declare function setGlobalRunStrategy(agent: AgentId, strategy: RunStrategy): void;
/**
 * Whether a version home can actually authenticate a launch.
 *
 * `getAccountInfo` falls back to the active/global HOME when a version home has
 * no credential of its own, so `agents view` still shows who is logged in. Launch
 * paths isolate config (GROK_HOME, CODEX_HOME, KIMI_CODE_HOME, CLAUDE_CONFIG_DIR,
 * …) to the per-version home, so a home that only "inherits" the active login
 * cannot spawn a signed-in agent — balanced kept picking those empty homes and
 * the run died on "Not signed in".
 *
 * When we know where the credential lives (`knownLocation`), require it under
 * THIS version home. When we don't (keychain-only / unmapped agents), trust the
 * existing `signedIn` signal.
 */
export declare function isLaunchableSignedIn(signedIn: boolean, presence: Pick<CredentialPresence, 'knownLocation' | 'perVersion'>): boolean;
/** Launchable-signed-in verdict for ONE specific version on THIS device. */
export interface VersionLaunchState {
    /** True iff this exact version home can spawn a signed-in agent right now. */
    launchable: boolean;
    /** The version home's account email when launchable, else null. */
    email: string | null;
}
/**
 * Whether a SPECIFIC installed version is launchable-signed-in on THIS device,
 * plus the account email when it is. Mirrors EXACTLY the per-version gate
 * {@link collectRunCandidates} applies (getVersionHomePath -> getAccountInfo ->
 * {@link isLaunchableSignedIn} over {@link credentialPresence}), so the
 * pre-launch `run.launch` event can report the same signed-in verdict the
 * balanced router computes for that version.
 *
 * The point is to make a launch into a logged-out version VISIBLE at spawn time:
 * `--device auto` only guarantees SOME account is ready on the device, not that
 * the specific version launched is signed in there (the yosemite-m3 2.1.219
 * incident — 2.1.219 was logged out, the router correctly excluded it, yet it
 * launched). Non-fatal by construction: callers wrap it best-effort.
 */
export declare function isVersionLaunchableHere(agent: AgentId, version: string): Promise<VersionLaunchState>;
/**
 * How old a usage snapshot may be and still settle a routing DECISION.
 *
 * A display may show an older cached bar, but the router choosing an account
 * from one costs the whole run. Measured case: `yosemite-s1` held snapshots 26
 * hours to 2.7 days old
 * with a failing refresh, so balanced read `muqsit@getrush.ai` as 48% used and
 * launched into it while the account was actually at its weekly cap.
 */
export declare const USAGE_DECISION_MAX_AGE_MS: number;
/**
 * How old a usage snapshot may be before routing REFUSES to run at all
 * (NO_VERIFIED_USAGE), as opposed to merely declining to *weight* by its number.
 *
 * These are two different risks and now two different bars. Weighting on a
 * slightly-old number is cheap to get wrong (a floored weight, {@link
 * USAGE_DECISION_MAX_AGE_MS} = 5 min); refusing to launch at all is expensive to
 * get wrong — it fails the user's `agents run` outright. The daemon's usage
 * refresher paces proactive fetches under a fixed per-provider budget
 * (`usage-refresh.ts`, PROVIDER_HOURLY_BUDGET), so on a multi-account fleet an
 * IDLE account is deliberately refreshed on a stretched round-robin cadence
 * (bounded at N × spacing — ~16 min at 8 accounts, ~32 min at 16) rather than
 * every 5 min, which would 429 the endpoint and park it for up to an hour. A
 * budget-paced idle reading of 10–30 min is NOT the failure this refusal exists
 * to catch. That failure is the `yosemite-s1` case: a box whose refresh is
 * genuinely BROKEN, holding readings 26 h – 2.7 d old. 40 min sits comfortably
 * above the worst-case budget cadence and still an order of magnitude below the
 * multi-hour staleness of a broken box — and actively-used accounts refresh for
 * free via the statusline ingest, so a *busy* account is never even this old.
 */
export declare const USAGE_STALE_REFUSAL_MAX_AGE_MS: number;
/**
 * Whether this candidate's usage number is recent enough to route on. A missing
 * snapshot is unverified by definition — there is no number to trust.
 *
 * A snapshot with NO windows is unverified for the same reason, however fresh
 * it is: it carries a subscription plan and no utilization, so there is still
 * no number. Freshness alone would make a meterless harness (Grok reports a
 * tier and no meters) verify against nothing — and since `preferVerified`
 * narrows the pool to verified candidates, the one account whose billing log
 * was touched most recently would win every draw, then win again because
 * running it refreshes that log. That self-reinforcing pin is exactly what the
 * narrowing rule below exists to prevent.
 */
export declare function isUsageVerified(candidate: RotateCandidate, nowMs?: number): boolean;
/**
 * Whether this candidate carries a GENUINELY-STALE usage number: a snapshot with
 * windows whose capture time is older than {@link USAGE_STALE_REFUSAL_MAX_AGE_MS}.
 *
 * This is the misleading case the initial route must refuse — the number reads
 * "48% used" with the same confidence whether captured a minute or three days
 * ago, and a box whose refresh is failing stays wrong indefinitely. Two things
 * make it NARROWER than "not verified":
 *   1. It uses the wider REFUSAL bar, not the 5-min weighting bar. A merely
 *      budget-paced idle account (10–30 min old) is not-verified — so it weights
 *      at the floor, conservatively — but it is NOT "stale" and must not, on its
 *      own, drive the whole provider to a NO_VERIFIED_USAGE refusal. Only a
 *      genuinely broken refresh (hours old) trips this.
 *   2. A BLIND candidate with no snapshot (or a plan-only meterless one with no
 *      windows) carries no number to be misled by — a worker box whose usage
 *      endpoint 403s (RUSH-2392), or a meterless Grok login — so it is not
 *      "stale", and an entirely-blind pool still draws a pick (PHNX-3392) rather
 *      than fail loud with NO_VERIFIED_USAGE.
 */
export declare function hasStaleUsage(candidate: RotateCandidate, nowMs?: number): boolean;
/**
 * Whether a specific account can serve a run right now, and — when it can't —
 * why. `signed_out` covers a missing usable credential; `revoked` is a token the
 * server has actually rejected (401/403, from the live auth-health probe);
 * `rate_limited` and `out_of_credits` name the throttle. Used to pre-warn on a
 * version-pinned teammate whose account rotation won't route around (a pin IS
 * the target).
 */
export type AccountReadiness = {
    ready: true;
} | {
    ready: false;
    reason: 'rate_limited' | 'out_of_credits' | 'signed_out' | 'revoked';
    email: string | null;
};
/**
 * Pure decision reusing the router's own eligibility gate (`hasUsageAvailable`
 * + canonical signed-in state, i.e. `isRotationEligible`), so a pre-flight warning can NEVER
 * disagree with what rotation would actually do. The `reason` combines the two
 * signals `hasUsageAvailable` reads: the live snapshot (session-inclusive
 * rate-limit) and the coarse cached `usageStatus` (out-of-credits, which a
 * snapshot never carries). When a live snapshot exists it wins over the cached
 * status — matching the gate — so a stale `out_of_credits` cache is not
 * reported while the account is actually serving requests.
 */
export declare function readinessFromCandidate(candidate: RotateCandidate, now?: number): AccountReadiness;
/**
 * Whether a human sitting at a terminal can clear this exclusion by launching
 * the agent and signing in. The two unhealthy classes are opposites, and the
 * zero-healthy callers MUST NOT treat them alike:
 *
 * - `signed_out` / `revoked` — recoverable. There is no credential (or the
 *   server rejected it), and the harness's own TUI is the login surface, so
 *   launching it is exactly the fix. Refusing to launch strands the user with
 *   no way to authenticate through agents-cli at all (RUSH-2334).
 * - `rate_limited` / `out_of_credits` — NOT recoverable. The account is signed
 *   in and throttled; launching it just hammers an exhausted account, which is
 *   precisely the loop RUSH-2132's fail-loud guard exists to stop. Only a
 *   window reset clears these.
 */
export declare function isSignInRecoverable(readiness: AccountReadiness): boolean;
/**
 * The subset of an `exhausted` set whose exclusion a sign-in would clear, so an
 * interactive caller can offer the login instead of dead-ending. Empty means
 * every account is throttled — nothing a human can fix right now, so the caller
 * keeps failing loud.
 */
export declare function signInRecoverableCandidates(candidates: RotateCandidate[]): RotateCandidate[];
/**
 * Readiness for a specific installed (agent, version). Returns `{ ready: true }`
 * when the version isn't among the collected candidates — absence is the
 * caller's `isVersionInstalled` concern, not ours; don't cry wolf. Only
 * meaningful for a version-pinned target: a bare target rotates to a healthy
 * account on its own, and a profile injects its own auth (a different account
 * than the version home carries), so neither is checkable here.
 */
export declare function checkRunAccountReadiness(agent: AgentId, version: string): Promise<AccountReadiness>;
/**
 * Pick a healthy candidate using weighted random by remaining capacity.
 *
 * Each healthy candidate gets weight = max(1, 100 - usedPercent) where
 * usedPercent is the highest-utilized non-session window (week / sonnet_week
 * for Claude). An account at 10% used gets weight 90; one at 90% used gets
 * weight 10 — so the fresher account is 9× more likely to be picked. Over N
 * calls, traffic distributes across healthy accounts proportional to their
 * headroom, with no stampede on the lowest-usage one. Stateless — parallel
 * callers naturally fan out via the random roll.
 *
 * Eligibility: signed in according to AccountInfo and not currently
 * rate-limited — no blocking window (session OR weekly) at 100%, matching the
 * `agents view` badge; or the local cached status is usable when no live
 * snapshot exists. Note the split: eligibility considers the session window
 * (a session-maxed account can't run now), but the capacity *weight* above is
 * driven by weekly headroom so a brief session spike doesn't distort routing.
 *
 * Dedupe: when multiple versions share a usage/account identity, collapse to
 * one candidate (the least-recently-active version). The org-scoped usage key
 * wins over email so same-email personal and Team accounts remain distinct.
 *
 * Returns null if no candidate is eligible — callers fall back to the pinned
 * version so behavior stays predictable.
 */
export declare function pickBalancedCandidate(candidates: RotateCandidate[], nowMs?: number): RotateResult | null;
export { PROJECTION_HORIZON_MIN, capacityWeight };
/**
 * Pick an available candidate. Prefers the configured pinned version when that
 * version has usage available; otherwise routes to the candidate with the most
 * usage headroom.
 */
export declare function pickAvailableCandidate(candidates: RotateCandidate[], preferredVersion?: string | null, nowMs?: number): RotateResult | null;
/**
 * Per-harness routing summary for `agents run auto` — the cross-harness layer
 * that sits above `pickBalancedCandidate` (which is strictly per-harness).
 */
export interface HarnessSummary {
    agent: AgentId;
    /** Every installed account slot probed for this harness. */
    candidates: RotateCandidate[];
    /** Healthy accounts after identity dedupe, sorted by headroom. */
    healthy: RotateCandidate[];
    /** The account this harness would route to (best verified headroom). Null when the harness is excluded. */
    best: RotateCandidate | null;
    /** Routing used% of `best` (max across non-session windows); null when unknown. */
    bestUsedPercent: number | null;
    /** Why the harness was excluded, e.g. ['2 rate_limited', '1 signed_out']. Empty when healthy. */
    exclusionReasons: string[];
}
export interface HarnessPickResult {
    /** The harness picked for this run. */
    picked: HarnessSummary;
    /** Harnesses with ≥1 healthy account (including the picked one). */
    healthy: HarnessSummary[];
    /** Harnesses with zero healthy accounts — excluded, not down-weighted. */
    excluded: HarnessSummary[];
}
/**
 * Classify every harness's candidates into healthy (with a representative
 * best account) vs excluded (with per-reason counts). Pure — the pick and the
 * zero-healthy error message both read this, so they can never disagree.
 *
 * Health uses the exact account-layer gate (`isRotationEligible`: signed in
 * AND not maxed on ANY blocking window, weekly included). The representative
 * best account honors `preferVerified`: confirmed headroom beats apparent
 * headroom, the same freshness rule the account layer routes on.
 */
export declare function classifyHarnessCandidates(byHarness: ReadonlyMap<AgentId, RotateCandidate[]>, nowMs?: number): HarnessSummary[];
/**
 * Pick a harness for `agents run auto` using weighted random by best-account
 * headroom (RUSH-2132).
 *
 * A harness's capacity is `100 − min(routingUsed% across its healthy accounts)`
 * — its best account's headroom. The pick reuses `weightedRandomByCapacity` on
 * the representative best accounts, so host/harness/account layers all share
 * one sampling behavior. Harnesses with zero healthy accounts are EXCLUDED,
 * not down-weighted. Returns null when no harness has any healthy account;
 * call `classifyHarnessCandidates` for the exclusion detail to message with.
 */
export declare function pickHarnessWeighted(byHarness: ReadonlyMap<AgentId, RotateCandidate[]>, nowMs?: number): HarnessPickResult | null;
/** One-line banner naming the auto-picked harness and why (headroom). */
export declare function formatHarnessPickBanner(result: HarnessPickResult): string;
/**
 * The earliest FUTURE window reset across these candidates' usage snapshots —
 * when the first exhausted account becomes usable again. Null when no snapshot
 * carries a reset timestamp.
 */
export declare function earliestResetAcross(candidates: RotateCandidate[], nowMs?: number): Date | null;
/**
 * The zero-healthy-account error (RUSH-2132). EXACT contract — the Factory
 * watchdog tail-detects this text: it must contain the literal `no healthy`
 * and `resets <time>` (parsed for the rotate cooldown). Do not deviate.
 */
export declare function formatNoHealthyAccountError(agent: AgentId, strategy: RunStrategy, excluded: RotateCandidate[], nowMs?: number): string;
/**
 * The all-stale-usage error (PHNX-2526) an UNATTENDED `balanced`/`available`
 * run fails loud with when no account's usage is fresh enough to route on. EXACT
 * contract — it MUST contain the literal `NO_VERIFIED_USAGE` so a machine caller
 * (and the Factory watchdog) can tail-detect it distinctly from the
 * `no healthy` throttle error, which is a different condition (throttled vs
 * merely stale). Names each candidate with how stale its snapshot is, so the
 * operator can see the failing-refresh box rather than guess.
 */
export declare function formatNoVerifiedUsageError(agent: AgentId, strategy: RunStrategy, candidates: RotateCandidate[], nowMs?: number): string;
/**
 * The zero-healthy-harness error for `agents run auto` — names each harness's
 * exclusion reason plus the earliest reset across all snapshots.
 */
export declare function formatNoHealthyHarnessError(summaries: HarnessSummary[], nowMs?: number): string;
export declare function collectRunCandidates(agent: AgentId): Promise<RotateCandidate[]>;
/**
 * Collect run candidates for every harness with ≥1 installed version — the
 * probe `agents run auto` routes on (the same per-harness account probe
 * `agents view` aggregates). Harnesses with nothing installed are absent from
 * the map: not a candidate at all, rather than an excluded one.
 */
export declare function collectHarnessCandidates(agentIds?: AgentId[]): Promise<Map<AgentId, RotateCandidate[]>>;
/**
 * Resolve an account identity to the installed version slot that holds it, over
 * an already-collected candidate list. Pure — no I/O — so it is unit-tested
 * directly. Matches, case-insensitively, against a candidate's login `email`
 * (the usual form) or its `accountKey`, and only ever returns a signed-in slot.
 * Returns null when nothing matches, so the caller can fall back and warn.
 */
export declare function matchAccountVersion(candidates: RotateCandidate[], account: string): string | null;
/**
 * Resolve a routine's `account:` pin (login email or account key) to the
 * installed version currently holding that account. Thin I/O wrapper over
 * {@link collectRunCandidates} + {@link matchAccountVersion}; returns null when
 * no signed-in version matches. Pinning a routine to a distinct account is the
 * durable cure for the shared-single-use-refresh-token revocation storm
 * (RUSH-1957): the pinned run never rotates and never lands on another
 * routine's credential.
 */
export declare function resolveAccountVersion(agent: AgentId, account: string): Promise<string | null>;
/**
 * Pick a healthy version for `agent` using weighted random by remaining
 * capacity. See `pickBalancedCandidate` for algorithm details.
 *
 * No external state — health and capacity are both read off per-version
 * AccountInfo (same data `agents view` surfaces). The weighted random roll
 * keeps parallel callers fanned out without rotation files or locks.
 *
 * Returns null if no installed version is eligible. Callers fall back to the
 * global default so behavior stays predictable — we never refuse to run.
 */
export declare function selectBalancedVersion(agent: AgentId): Promise<RotateResult | null>;
/** Select the configured version if available, otherwise another available version. */
export declare function selectAvailableVersion(agent: AgentId, preferredVersion?: string | null): Promise<RotateResult | null>;
/**
 * Build the enriched `rotation.resolved`/`rotation.unresolved` event payload:
 * the full candidate pool as the router saw it, the pick and WHY, and a
 * freshness tally. Replaces the old `{ version, healthy: <n>, excluded: <n> }`
 * shape, which recorded only counts and a device-local version and so could not
 * tell a blind-pool draw from a skewed-verified pick from a refused-stale route
 * — the exact ambiguity that keeps a bad pick undebuggable from the log. All
 * candidates share ONE `nowMs` so their `tier`/`ageMs` are mutually consistent.
 */
export declare function buildRotationDecisionEvent(rotation: RotateResult, agent: AgentId, strategy: RunStrategy): EventPayload;
/**
 * Resolve the version `agents run` should use when the caller did not pin
 * one with `@version`. The caller supplies the effective strategy.
 *
 * `pinned` still prefers the workspace/global default, but it MUST NOT launch
 * a logged-out (or revoked) default when the same device holds a signed-in
 * version that can run — that was a silent 1-second death on fleet workers
 * whose default home had no credential (PHNX-2685). A rate-limited pin is
 * still honoured: `--strategy pinned` remains the escape hatch to force the
 * default through a throttle. When the default is auth-blocked and nothing
 * else is healthy, `exhausted` is set so the caller fails loud instead of
 * spawning into a credential-less home.
 */
export declare function resolveRunVersion(agent: AgentId, strategy: RunStrategy, cwd?: string, collect?: (agent: AgentId) => Promise<RotateCandidate[]>): Promise<{
    version: string | null;
    rotation: RotateResult | null;
    /**
     * Set when a strategy found ZERO healthy candidates among the installed
     * versions: the full excluded set, so callers fail loud with per-account
     * reasons instead of launching the exhausted pinned default (RUSH-2132).
     * Also set for `pinned` when the default is logged out / revoked and no
     * signed-in alternative exists (PHNX-2685). Undefined for successful picks
     * and when no version is installed at all (the pre-existing not-installed
     * path — there is no account to be "unhealthy").
     */
    exhausted?: RotateCandidate[];
    /**
     * Set (with `version: null`) for a `balanced`/`available` route when EVERY
     * eligible account's usage is stale and none is verified (PHNX-2526). The
     * initial selection MUST NOT auto-launch on a stale number: an interactive
     * caller diverts to the account picker, an unattended one fails loud with
     * NO_VERIFIED_USAGE (`formatNoVerifiedUsageError`). `rotation` is still
     * returned — its `healthy` set (the stale candidates) is preserved ONLY for
     * bounded post-rejection failover, never the initial pick. Undefined when a
     * verified account exists, when the pool is entirely blind (no snapshots —
     * the worker-box case still draws a pick), for `pinned`, and for the
     * zero-healthy `exhausted` case.
     */
    noVerifiedUsage?: boolean;
}>;
/**
 * Cap on the number of healthy accounts a single run will re-dispatch through
 * after a mid-run rate limit. Bounds the synthesized chain so a machine signed
 * into many accounts can't turn one 429 into an unbounded cascade of retries.
 */
export declare const DEFAULT_ROTATION_FAILOVER_LIMIT = 3;
/**
 * Synthesize a same-agent, cross-account fallback chain from a pre-flight
 * rotation result (issue #348: mid-run rate-limit failover).
 *
 * The account rotation picks ONE version pre-spawn; today a 429 mid-run kills
 * the run with no recovery. `runWithFallback` + `detectRateLimit` already
 * re-dispatch to the NEXT chain entry on a rate limit and hand off the session
 * via `/continue <id>` — but only for explicit `--fallback` chains. This turns
 * the OTHER healthy rotation candidates (every account except the one already
 * picked as the primary) into `FallbackEntry`s so that SAME machinery re-runs
 * the task on the next healthy account of the same agent when the primary 429s.
 *
 * Each account is a distinct installed version (its own home/auth), so the
 * entries are same-agent, different-version — exactly what runWithFallback
 * spawns and what buildFallbackPrompt continues (claude→claude via `/continue`).
 * Candidates are consumed in `rotation.healthy` order, which is sorted by
 * remaining capacity (most headroom first, see compareCandidates), so failover
 * prefers the freshest account.
 *
 * Returns `[]` when there is no rotation (pinned strategy) or the picked account
 * is the only healthy one — so single-account users and non-rotation runs are
 * completely unchanged.
 */
export declare function rotationFailoverChain(rotation: RotateResult | null, pickedVersion: string, limit?: number): FallbackEntry[];
/**
 * Whether a run is eligible to have a mid-run rate-limit failover chain armed
 * (issue #348). Failover injects synthesized `FallbackEntry`s into the same
 * `fallback` array that `--fallback` uses — so it must NOT arm for run shapes
 * that reject a non-empty fallback chain, or the run hard-exits on a flag the
 * user never passed. Specifically:
 *
 * - `acp` and `loop` runs bail with "not compatible with --fallback yet" the
 *   moment `fallback.length > 0` (src/commands/exec.ts), so arming failover
 *   would break a previously-working `agents run … --loop` / `--acp`.
 * - `resumeCheckpoint` runs take the loop path (same guard).
 * - `interactive` / no-prompt runs can't be re-dispatched headlessly.
 * - `hasRotation`/`hasVersion` gate on an actual pre-flight rotation having
 *   picked an account, so pinned and non-rotation runs are untouched.
 *
 * An explicit `--fallback` chain does NOT disarm rotation failover: the
 * synthesized same-agent entries are unshifted AHEAD of the user's cross-agent
 * entries, so a rate limit exhausts the other accounts of the same agent
 * before cascading to a different CLI. Profile fallbacks never reach here —
 * strategy resolution is skipped for profiles, so hasRotation is false.
 *
 * Pure so the arming matrix is unit-testable without invoking the run command.
 */
export interface FailoverArmingContext {
    hasRotation: boolean;
    hasVersion: boolean;
    hasPrompt: boolean;
    interactive: boolean;
    acp: boolean;
    loop: boolean;
    resumeCheckpoint: boolean;
}
export declare function shouldArmRotationFailover(ctx: FailoverArmingContext): boolean;
