import type { AgentId, Mode, RunStrategy } from './types.js';
import { type EventPayload } from './feed/events.js';
import { type UsernsStatus } from './linux-userns.js';
/**
 * Agent execution modes. Canonical name `skip` (dangerously skip permissions);
 * `full` is accepted as a permanent silent alias via normalizeMode().
 */
export type ExecMode = Mode;
/**
 * Map a raw mode string (CLI flag, YAML field, env var) to the canonical Mode.
 *
 * Accepts the historical `full` spelling and rewrites it to `skip`. Throws on
 * anything outside the four canonical values so bad input fails loud at the
 * boundary rather than silently picking a wrong code path.
 */
export declare function normalizeMode(input: string | null | undefined): Mode;
/**
 * Detect the headless-plan stall footgun.
 *
 * A slash command (e.g. `/code:commit`) run headless under the IMPLICIT default
 * `plan` mode hangs forever: plan is read-only, so the agent calls ExitPlanMode
 * to start working, and in a headless run there is no TTY to approve it. The
 * process just sits there. Callers use this to fail fast with a fix instead.
 *
 * Returns the offending command token (e.g. `/code:commit`) when the run should
 * be blocked, else null. Guards are deliberately narrow:
 *   - interactive runs / no prompt        -> not headless, never blocks
 *   - explicit --mode (modeIsDefault false) -> respected; `--mode plan` is a
 *     legitimate read-only command run and must not be blocked
 *   - resolved mode is not `plan`          -> only plan stalls at ExitPlanMode
 *   - prompt is not a slash command        -> natural-language read-only prompts
 *     ("summarize commits") are a valid default-plan use and must not be blocked
 */
export declare function headlessPlanStallCommand(args: {
    prompt: string | undefined;
    interactive: boolean | undefined;
    mode: string;
    modeIsDefault: boolean;
}): string | null;
/**
 * Resolve a requested mode against an agent's capability table.
 *
 * - `auto` on an agent without auto support silently degrades to `edit`
 *   (every agent supports edit-like behavior as its default).
 * - `plan` on an agent without a read-only mode degrades to the agent's
 *   safest native mode (`capabilities.modes[0]`, typically `edit`). Agents
 *   like antigravity have no plan flag; hard-failing made
 *   multi-agent scripts (`--mode plan` for everyone) unusable and diverged
 *   from `agents teams add`, which already defaults to `edit`. Callers that
 *   care (the `agents run` CLI) must surface a warning when requested ≠
 *   resolved so the elevation is not silent.
 * - `skip` on an agent without skip support throws with a clear message
 *   naming the agent's supported modes. No silent fallback — the user
 *   explicitly asked to bypass permissions; pretending we did is unsafe.
 */
export declare function resolveMode(agent: AgentId, requested: Mode): Mode;
/**
 * Resolve a requested mode for a run, honoring whether the run is HEADLESS.
 *
 * Wraps resolveMode with one extra rule: an agent may list `plan` in its modes
 * (so interactive plan works) yet declare `capabilities.headlessPlan === false`
 * because plan is broken in a headless `--prompt`/`-p` run — kimi refuses
 * `--prompt` + `--plan`, and grok's `--permission-mode plan` silently stalls at
 * its ExitPlanMode gate. For those agents, a headless plan request degrades to
 * `auto` (kimi -p auto-runs; grok maps auto→edit via resolveMode) with a visible
 * one-line stderr warning, mirroring the graceful plan→edit degrade antigravity
 * get for having no plan flag at all. Interactive runs are never
 * downgraded. This is the single source of truth shared by buildExecCommand
 * (agents run / teams) and the routine runner.
 */
export declare function resolveHeadlessMode(agent: AgentId, requested: Mode, interactive: boolean, warningContext?: string, warningState?: ModeWarningState): Mode;
export interface ModeWarningState {
    /** Agents already warned about, so one run warns once per agent. A fallback
     *  chain degrades each agent independently and the agent that actually ran is
     *  usually not the first, so this cannot be a single boolean. */
    emitted?: Set<AgentId>;
    quiet?: boolean;
}
/**
 * The mode an agent should run in when the caller has no preference.
 *
 * Returns the first entry in the agent's `capabilities.modes` table — the
 * declaration order is the source of truth for "the safest mode this agent
 * supports." Agents that include `plan` list it first; agents like
 * antigravity that have no read-only mode list `edit` first.
 *
 * Prefer this over a hard-coded `'plan'` when the agent is known. `resolveMode`
 * also maps an unsupported `'plan'` request onto this same value.
 */
export declare function defaultModeFor(agent: AgentId): Mode;
/** Safe mode used when the user did not provide --mode or a configured default. */
export declare function implicitModeFor(agent: AgentId): ExecMode;
/**
 * Preflight for Codex's Linux sandbox. Codex ≥0.146 sandboxes `read-only` and
 * `workspace-write` runs with a bundled bubblewrap that needs an unprivileged
 * user namespace; on a box that restricts it (Ubuntu 24.04
 * `apparmor_restrict_unprivileged_userns=1`) bwrap dies with "setting up uid map:
 * Permission denied" and a HEADLESS codex run lands zero tools — no file writes,
 * no shell — while still reporting a completed turn. That silent under-delivery
 * is what breaks `agents teams` codex teammates (always headless + workspace-write)
 * and headless `agents run codex` alike on the fleet. Returns a loud, actionable
 * message to fail the launch with instead of spawning that doomed run; returns
 * null when the run is fine to proceed.
 *
 * Deliberately scoped: only `codex`, only Linux, only a HEADLESS run (an
 * interactive TUI surfaces the bwrap error to the operator itself), and only a
 * SANDBOXED mode — `skip` is codex `--dangerously-bypass-approvals-and-sandbox`,
 * which uses no bwrap and is unaffected. The intended auto=workspace-write config
 * is never weakened here; the run fails loud rather than silently downgrading.
 * PHNX-3285.
 */
export declare function codexSandboxPreflight(args: {
    agent: AgentId;
    platform: NodeJS.Platform;
    interactive: boolean;
    mode: Mode;
    userns: UsernsStatus;
    machine: string;
}): string | null;
/** Reasoning effort levels passed to agents that support them. 'auto' defers to the agent's default. */
export type ExecEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
/** Options for spawning an agent process. Omitting `prompt` launches the CLI interactively. */
export interface ExecOptions {
    agent: AgentId;
    /**
     * Custom harness / profile name when this run was launched via
     * `agents run <profile>` (e.g. `deepseek`). `agent` stays the HOST CLI
     * that actually executes. Stamped onto `AGENTS_AGENT_NAME`, the pid
     * registry, and the session-actor sidecar so listings can tell the
     * profile apart from a native host run (PHNX-2935).
     */
    harnessName?: string;
    version?: string;
    /** Version home whose native auth/config is overlaid onto this run's binary. */
    configVersion?: string;
    /** Omit to launch the CLI interactively -- no prompt, no --print, stdio fully inherited. */
    prompt?: string;
    /** Force interactive mode even when a prompt is provided. Wins over `headless`. */
    interactive?: boolean;
    mode: ExecMode;
    /** True when the caller omitted --mode; fallback agents resolve their own safe default. */
    modeWasImplicit?: boolean;
    effort: ExecEffort;
    cwd?: string;
    /** Force headless mode even when no prompt is provided (e.g. piping via stdin). */
    headless?: boolean;
    /** Prefix for mode-degradation warnings emitted by shared headless paths. */
    modeWarningContext?: string;
    /** Shared across command previews/spawns/loop iterations so degradation warns once. */
    modeWarningState?: ModeWarningState;
    json?: boolean;
    model?: string;
    addDirs?: string[];
    timeout?: string;
    sessionId?: string;
    /**
     * Durable `agents run --name <slug>` handle. Exported to the agent's env as
     * `AGENT_SESSION_NAME` (companion to `AGENT_SESSION_ID`) and, when a session
     * id is known at launch, recorded in the run-name index so `agents sessions
     * <name>` resolves the run. Absent for unnamed runs — no behavior change.
     */
    name?: string;
    /**
     * Resume the conversation named by `sessionId` using the agent's NATIVE resume
     * form (claude `--resume`, codex `resume`) instead of the default `--session-id`
     * create. Only set for agents where `nativeResume` returns true; other agents
     * resume via a `/continue <id>` first message (Tier 2), which needs no flag and
     * leaves this unset.
     */
    resume?: boolean;
    verbose?: boolean;
    env?: Record<string, string>;
    /**
     * Workflow capability scoping (Claude only). Sourced from WORKFLOW.md
     * frontmatter `tools:` / `mcpServers:` and translated to Claude headless
     * flags in buildExecCommand. Other agents ignore these.
     *
     * `toolsRestrict` is the AVAILABLE-tool allowlist: it maps to `--tools`, which
     * restricts the built-in tool set the run can use at all (NOT `--allowedTools`,
     * which only auto-approves without restricting availability). Declaring
     * `[Read, Grep]` makes Write/Bash/Edit unavailable for the whole run.
     */
    toolsRestrict?: string[];
    /**
     * Path to an ephemeral mcp-config JSON. Emitted as `--mcp-config <path>`
     * together with `--strict-mcp-config` so ONLY the named servers load (the
     * flag alone merely ADDS to the existing server set).
     */
    mcpConfigPath?: string;
    /** Raw args captured after `--` on the command line, forwarded verbatim to the underlying agent CLI. */
    passthroughArgs?: string[];
    /**
     * Tee-and-tail the child's stdout even when no budget cap is active, so the
     * caller can scan it for rate/usage-limit messages. Claude prints billing
     * refusals ("monthly spend limit", "out of usage credits") to STDOUT, not
     * stderr — a fallback chain that only inspects stderr never cascades on
     * them. Set by runWithFallback for every chain entry; harmless elsewhere
     * (output is mirrored to the parent's stdout exactly like stdio:'inherit').
     */
    captureStdoutTail?: boolean;
    /**
     * Print the run's resolved session id to stdout as a one-line sentinel once the
     * child exits (see hosts/session-marker.ts). Set by the `--device` dispatch so the
     * LAUNCHER can relate the remote-created session back to itself — Claude's id is
     * forced up front, but every other agent coins its own id on the remote box, and
     * this marker is how that id rides the followed log home. Headless-only and inert
     * for interactive runs (no combined log to parse).
     */
    emitSessionId?: boolean;
    /**
     * Escape hatch for the interactive tmux spawn-wrap (see shouldWrapInTmux):
     * when true, spawn the agent directly instead of inside a shared-socket tmux
     * session. Also forced off by AGENTS_NO_TMUX=1. No effect on headless runs.
     */
    raw?: boolean;
    /**
     * The run strategy that resolved this launch (pinned/available/balanced).
     * Observability-only: threaded from the `agents run` command purely so the
     * pre-launch `run.launch` event can record HOW the version was chosen. Never
     * read by the spawn itself.
     */
    strategy?: RunStrategy;
    /**
     * How the launched version was resolved (e.g. 'pinned-default', 'rotated',
     * 'explicit-pin'), when cheaply determinable. Observability-only, carried on
     * `run.launch`. Optional — omitted when the caller can't attribute it.
     */
    resolvedVia?: string;
    /**
     * Precomputed launchable-signed-in verdict for the launched version, supplied
     * by a caller that already computed it via the identical gate (a rotated pick
     * carries `rotationResult.picked.signedIn`). When present, `run.launch` uses it
     * instead of re-probing the version home — removing a double fs read on the hot
     * path and any disagreement window. `undefined` means "not precomputed" (the
     * pinned-default / shim paths), which makes `emitRunLaunch` fall back to
     * {@link isVersionLaunchableHere}. Observability-only.
     */
    launchSignedIn?: boolean | null;
    /** Precomputed account email companion to {@link launchSignedIn}. */
    launchEmail?: string | null;
}
/**
 * Identity a custom-harness run stamps on env / pid-registry / sidecars.
 * `agent` is the host CLI; `harnessName` is the profile the user launched.
 * Empty/whitespace harness names fall back to the host so a blank stamp
 * never hides a real agent.
 */
export declare function stampedAgentName(options: Pick<ExecOptions, 'agent' | 'harnessName'>): string;
/**
 * Profile name when it differs from the host agent. Undefined for a native
 * run, so pid-registry / sidecar records stay sparse.
 */
export declare function customHarnessName(options: Pick<ExecOptions, 'agent' | 'harnessName'>): string | undefined;
/**
 * Resolve interactive vs headless. Explicit flags are definitive and win over
 * inference: `--interactive` forces interactive, `--headless` forces headless.
 * With neither flag, prompt presence decides (prompt -> headless, none -> interactive).
 * `--interactive` takes precedence over `--headless`; the CLI layer rejects passing both.
 */
export declare function resolveInteractive(options: Pick<ExecOptions, 'interactive' | 'headless' | 'prompt'>): boolean;
/**
 * True when a run resolved to *inferred* interactive intent — no prompt and no
 * explicit `--interactive` — but there is no terminal to host the REPL. Launching
 * would attach a TUI to a dead stdin and hang forever, so the caller should fail
 * fast with the headless alternatives instead (RUSH-1829).
 *
 * An explicit `--interactive` is the caller's deliberate choice and is never
 * blocked (they may be driving a PTY we can't detect). Pure — the TTY state is a
 * parameter so this is unit-testable without touching `process.std*`.
 */
export declare function inferredInteractiveWithoutTty(options: Pick<ExecOptions, 'interactive' | 'headless' | 'prompt'>, isTty: boolean): boolean;
/**
 * Decide whether spawnAgent must capture (PIPE + tee) the child's stdout so the
 * live budget watcher can parse it (issue #346, FIX 3).
 *
 * The bug this fixes: stdout used to be PIPED only when downstream output was
 * piped (`piped = !isTTY`). For a normal headless run AT A TERMINAL, stdout was
 * 'inherit', so `child.stdout` was null and the watcher — hence the mid-run
 * hard-cap kill — was silently skipped. We now tap stdout for ALL
 * non-interactive runs when caps are active, regardless of TTY, and tee it back
 * so the user still sees output. Interactive REPLs are never tapped (the human
 * owns the TTY; they rely on the pre-flight gate).
 *
 * @param interactive  resolveInteractive() result for the run
 * @param piped        true when the parent's stdout is NOT a TTY (output piped)
 * @param capsActive   true when a budget watcher is attached (caps configured)
 */
export declare function shouldTapStdout(interactive: boolean, piped: boolean, capsActive: boolean, captureTail?: boolean): boolean;
/** Parse an array of KEY=VALUE strings into an env record. Returns undefined for empty input. */
export declare function parseExecEnv(entries: string[]): Record<string, string> | undefined;
/**
 * Resolve the launch id a run exports as `AGENT_LAUNCH_ID`.
 *
 * The launch id is the stable correlation key the SessionStart hook records
 * alongside the agent's real session id (terminals/sessions/<pid>.json), so it
 * is what maps a launch to its exact session even when the hook runs under a
 * different pid (tmux pane leaf / cmd.exe wrapper) — and, across an SSH hop, what
 * lets a `--device` launcher resolve the remote-coined id for agents that never
 * accept a forced `--session-id`.
 *
 * ADOPT a caller-supplied `AGENT_LAUNCH_ID` (a `--device` launcher forwards one via
 * `--env` so it controls the key end-to-end); MINT a fresh one otherwise (every
 * local run, which passes none). A malformed inbound value is ignored in favour
 * of a fresh mint — the key must be a real correlation id, never an empty string.
 */
export declare function resolveLaunchId(envLaunchId: string | undefined): string;
/**
 * Build the process environment for an agent invocation.
 * Pins CLAUDE_CONFIG_DIR for Claude, CODEX_HOME for Codex, and COPILOT_HOME
 * for GitHub Copilot; strips the other agents' env vars so they don't leak
 * into unrelated invocations.
 */
export declare function buildExecEnv(options: ExecOptions): NodeJS.ProcessEnv;
/** Materialize config roots for vendor CLIs that do not create parents recursively. */
export declare function ensureVendorHomeDir(agent: AgentId, versionHome: string): string | null;
/**
 * Describes how to translate ExecOptions into CLI arguments for a specific agent.
 *
 * `modeFlags` only declares modes this agent natively supports. Keys must agree
 * with AGENTS[agent].capabilities.modes — resolveMode() routes a request to a
 * supported mode (or throws), then buildExecCommand looks up the flags here.
 */
export interface AgentCommandTemplate {
    base: string[];
    promptFlag: 'positional' | string;
    modeFlags: Partial<Record<Mode, string[]>>;
    jsonFlags?: string[];
    modelFlag?: string;
    printFlags?: string[];
    verboseFlag?: string;
    /**
     * How this agent natively resumes a prior conversation. Presence here is the
     * single source of truth for `nativeResume(agent)` — agents without it fall
     * back to the universal `/continue <id>` replay (Tier 2). Two shapes:
     *   { flag }       — append `<flag> <id>` (e.g. claude `--resume <id>`)
     *   { subcommand } — replace the headless base subcommand with `<subcommand> <id>`
     *                    (codex: `codex exec` -> `codex exec resume <id>`)
     */
    resume?: ({
        flag: string;
        interactiveFlag?: string;
        headlessFlag?: string;
    } | {
        subcommand: string;
    }) & {
        since?: string;
    };
}
/**
 * CLI command templates for every supported agent.
 *
 * Each agent's `modeFlags` keys MUST match the modes listed in
 * AGENTS[agent].capabilities.modes. A test in exec.test.ts asserts this.
 */
export declare const AGENT_COMMANDS: Record<AgentId, AgentCommandTemplate>;
/**
 * Whether `agent` has a native resume form (Tier 1). Derived solely from the
 * command template's `resume` field — the single source of truth. Agents that
 * return false resume via the universal Tier-2 `/continue` replay instead.
 */
export declare function nativeResume(agent: AgentId, version?: string): boolean;
/**
 * Build the `-c` value that adds `dir` to codex's workspace-write writable
 * roots. Codex parses the value as TOML, so it's a single-element TOML array of
 * one quoted string. Used on codex resume forms, which reject `--add-dir` and
 * only accept `-c` config overrides.
 */
export declare function codexWritableRootsConfig(dir: string): string;
/**
 * Resolve the executable `buildExecCommand` will put in `cmd[0]`, or null when
 * that resolution finds nothing on disk.
 *
 * This is an EXISTENCE probe, not an "is it managed by us" check. A harness the
 * user installed themselves (Homebrew, a vendor `curl | sh`, a distro package)
 * has no version home at all, and running it is a supported state — so with no
 * version pinned we answer with a PATH lookup of the bare launch command, the
 * same thing `spawnAgent` would resolve.
 *
 * `findInPath` deliberately excludes our own shims dir, because a shim is a
 * dispatcher rather than an install. That exclusion alone is too strong here:
 * the shim DOES launch whenever agents-cli owns at least one version of the
 * agent — it resolves the version itself, and when no default is pinned it
 * prints its own accurate `no default set … agents use <agent> <version>`
 * guidance. Pre-empting that with "not installed" would name the wrong fix
 * (`agents add`) for a machine that already has the harness. So the shim counts
 * only when a managed version exists; with zero managed versions it is the dead
 * end this probe was written to catch (RUSH-2339).
 *
 * The version-pinned branch mirrors buildExecCommand exactly: versioned shim
 * first, then the version home's real binary. It deliberately does NOT fall back
 * to PATH — with a version pinned, buildExecCommand spawns the literal
 * `<cli>@<version>`, which is not on PATH, so a PATH hit here would be a lie
 * that still exits 127.
 */
export declare function resolveLaunchBinary(agent: AgentId, version?: string): string | null;
/** Assemble the full CLI argument array for an agent invocation. */
export declare function buildExecCommand(options: ExecOptions): string[];
/** Spawn an agent and return its exit code. Convenience wrapper over spawnAgent. */
export declare function execAgent(options: ExecOptions): Promise<number>;
/**
 * Resolve how to spawn a shim target for a platform. Pure — testable on any host.
 *
 * POSIX always execs the binary directly (no shell). On Windows a bare
 * (non-absolute) name or a `.cmd` companion goes through the shell so cmd.exe
 * resolves it via PATHEXT — the common, `.cmd`-present path; an absolute `.cmd`
 * or extensionless path is exec'd through the shell / directly. npm always ships
 * a `<cmd>.cmd` companion on Windows, so the runnable target `execShimPassthrough`
 * hands us is the `.cmd` (never a bare `.ps1`).
 */
export declare function resolveShimSpawn(platform: NodeJS.Platform, binary: string, extraArgs: string[]): {
    command: string;
    args: string[];
    shell: boolean;
};
/**
 * Transparent passthrough exec for generated shims — the node-side delegate that
 * Windows `.cmd` shims call. Resolves the active version (explicit pin, else
 * project/default) and execs the real binary with the user's RAW args and the
 * per-version env isolation, WITHOUT injecting mode/model/reasoning flags. This
 * mirrors what the POSIX bash shim does inline (`exec $BINARY $launchArgs "$@"`),
 * keeping version resolution in one place instead of reimplementing it in batch.
 */
export declare function execShimPassthrough(agent: AgentId, rawArgs: string[], cwd: string, pinnedVersion?: string): Promise<number>;
/**
 * Whether a dead pane's failure should be recapped to stderr (RUSH-2185 / EXEC-23a).
 *
 * For headless runs only a nonzero exit is a failure worth surfacing — a
 * clean 0 means the agent finished the task before we could attach.
 * For interactive runs ANY exit (including 0) is a failure: an instant clean
 * exit means the harness has no bare REPL and the user would see only a mute
 * `[detached]` with no explanation.
 */
export declare function shouldRecapDeadPane(status: number | undefined, interactive: boolean): boolean;
/**
 * The exit code a tmux-wrapped run resolves with (RUSH-2185 / EXEC-23b).
 *
 * Three inputs, one rule: report success ONLY for an outcome tmux actually told
 * us about. A clean user detach (`knownAlive`) is 0; a dead pane reports the
 * status tmux read off it; everything else — pane unreadable because the server
 * or session went away, or dead with no status — is UNKNOWN and resolves to
 * {@link UNKNOWN_OUTCOME_EXIT_CODE}, never 0.
 *
 * Why this is not `status ?? 0`: an interactive run whose tmux server died mid-
 * work landed on exactly those unknown branches and returned 0, so `agents run`
 * printed a failure banner while handing its caller a success code. The same
 * "1 if unknown" rule already governs the `--device` follow path
 * (`docs/specifications.md` §Agent execution, exit-code table).
 *
 * @param pane       What `paneExitStatus` read back for the agent pane.
 * @param knownAlive Positive proof the pane is still alive (see
 *                   {@link isPaneKnownAliveFromQueryResult}) — a clean detach.
 */
export declare function tmuxRunExitCode(pane: {
    dead: boolean;
    status?: number;
}, knownAlive: boolean): number;
/**
 * True only when a `display-message #{pane_dead}` tmux query explicitly returned
 * "0" (pane alive). Used to distinguish "pane alive" from "query failed" in
 * situations where `paneExitStatus` conservatively returns `{dead: false}` for
 * both (RUSH-2185 / EXEC-23a / F3).
 *
 * @param code   The exit code of the `tmux display-message` command.
 * @param stdout Its stdout (expected to be "0" when the pane is alive).
 */
export declare function isPaneKnownAliveFromQueryResult(code: number, stdout: string): boolean;
/** Inputs that decide whether an interactive spawn is wrapped in a shared-socket tmux session. */
export interface TmuxWrapContext {
    /** resolveInteractive() result — only interactive REPL launches are wrapped. */
    interactive: boolean;
    /** process.platform — Windows has no tmux path, always spawns bare. */
    platform: NodeJS.Platform;
    /** True when the launcher itself already runs inside tmux ($TMUX set) — never double-wrap. */
    inTmux: boolean;
    /** The `--raw` escape hatch. */
    raw: boolean;
    /** The AGENTS_NO_TMUX=1 escape hatch. */
    noTmuxEnv: boolean;
    /** This device's `tmux.enabled` config — true opts every eligible launch on this box into the wrap. */
    configEnabled: boolean;
    /**
     * True when this run was dispatched onto this box over SSH by `--device`
     * (the launcher exports {@link REMOTE_INTERACTIVE_ENV}).
     *
     * Since PHNX-3316 this flag no longer forces the wrap: `tmux.enabled` gates
     * local and remote runs alike, because the operator reading "tmux disabled"
     * expects NO tmux anywhere. A followed remote run left bare is protected by
     * reconnect-and-resume (lib/hosts/reconnect.ts): a dropped link costs the
     * in-flight turn, and the harness session resumes from disk.
     *
     * The flag still matters for one case: a followed remote run whose LAUNCHER
     * has no TTY (CI, a script, another agent driving the CLI — `sshStream`
     * allocates the peer's TTY from `process.stdin.isTTY`, dispatch.ts) gets no
     * TTY on the peer either, so there is nothing to attach to and the detached
     * pane is the run's only interface — it wraps regardless of `configEnabled`
     * (and is refused as `undurable` when tmux is missing).
     */
    remoteDispatch: boolean;
    /** Whether a tmux binary is on PATH. */
    tmuxAvailable: boolean;
    /**
     * True when this process has a real TTY to attach (`stdout.isTTY`).
     * A piped `agents run --interactive` (session-tracker tests, CI) has none:
     * wrapping then treating the failed attach as Ctrl-b d leaked live panes
     * for a week on yosemite-s0 (PHNX-3293). A remote dispatch still wraps
     * without a TTY — a launcher that has none (CI, scripts, another agent)
     * gives the peer nothing to attach to, so the detached pane is the run's
     * only interface.
     */
    hasTty: boolean;
}
/**
 * What to do with an interactive spawn. Three outcomes, not two: a remote run
 * that WOULD wrap (the operator opted in, or a `--no-follow` run whose only
 * interface is the detached pane) on a box with no tmux is neither "wrap" nor
 * "spawn bare" — it is a launch that should not happen, because a bare remote
 * spawn looks fine right up until the link blinks and the agent dies with it.
 */
export type TmuxWrapDecision = 
/** Run the agent in a detached tmux session and attach this TTY. */
{
    kind: 'wrap';
}
/** Spawn directly — nothing about this run needs a pane. */
 | {
    kind: 'bare';
}
/** Remote-dispatched, wants the wrap, and tmux is missing: refuse, don't pretend. */
 | {
    kind: 'undurable';
};
/**
 * Tmux is opt-in except when a TTY-less remote launch would otherwise have no
 * interface. Explicit per-run opt-outs always win.
 */
export declare function resolveTmuxWrap(ctx: TmuxWrapContext): TmuxWrapDecision;
/**
 * True when `options.sessionId` is an id the HARNESS actually received, and so a
 * real, resumable handle — rather than one the launcher generated for its own
 * bookkeeping and the agent never saw.
 *
 * Mirrors exactly the two branches in buildExecCommand that put the id on the
 * command line: a native resume (any harness with a `resume` spec) and claude's
 * create-with-`--session-id`. Everything else — `agents run codex --session-id X`
 * without `--resume`, or a fresh non-claude launch whose real id only shows up
 * later via the SessionStart hook — leaves the agent with an id of its own
 * choosing, so `options.sessionId` must not be published as if it were that id.
 *
 * Keep in lockstep with buildExecCommand's resume/create branches: a harness
 * that gains a forced-session-id flag must be reflected here in the SAME change,
 * or this reports a genuine id as fabricated.
 */
export declare function isHarnessKnownSessionId(agent: AgentId, sessionId: string | undefined, resume: boolean | undefined): boolean;
/**
 * Build the pane command without inheriting stale tmux-server env. Redacted
 * copies keep secret values out of persisted SessionMeta commands.
 */
export declare function buildTmuxAgentCommand(executable: string, args: string[], env: NodeJS.ProcessEnv, opts?: {
    redactEnvValues?: boolean;
    envFile?: string;
}): string;
/**
 * Serialize the pane env to a shell-sourceable file, created 0600 and exclusively
 * (`wx`) so it can never adopt a pre-existing file's mode or content.
 *
 * EVERY key goes in the file, not a curated "secret-bearing" subset: a denylist
 * has to be updated for each new credential and is wrong the moment someone
 * forgets, whereas routing all of it through the file makes the guarantee hold by
 * construction. Keys are filtered to valid shell identifiers for the same reason
 * `env` needed it — an exported function (`BASH_FUNC_x%%`) is not assignable.
 */
export declare function writeTmuxEnvFile(env: NodeJS.ProcessEnv, filePath: string): void;
/**
 * Trim a raw `tmux capture-pane` dump to its last `maxLines` non-empty lines
 * (right-stripping each). Used by runInTmux to recap a fast-failed agent's
 * output into the caller's shell so a launch crash (e.g. a gutted install that
 * dies with ENOENT the instant it spawns) isn't swallowed by the bare
 * `[detached]` the pane-died hook otherwise leaves behind.
 */
export declare function formatPaneTail(raw: string, maxLines?: number): string;
/**
 * Spawn an agent process and return its exit code plus a tee'd copy of stderr.
 *
 * Stderr is always piped so the caller can inspect it (e.g., for rate-limit
 * detection) while also forwarding every chunk to process.stderr in real time --
 * the user sees the same output they would with stdio: 'inherit'. Stdout keeps
 * the original behavior: 'pipe' when downstream output is piped (so `agents
 * run ... | ...` composes cleanly), otherwise 'inherit' so TTY output is
 * unbuffered.
 */
/** Inputs the pre-launch `run.launch` payload is built from. */
export interface RunLaunchInput {
    agent: AgentId;
    harnessName?: string;
    /** The version being launched, or undefined when none could be resolved. */
    version?: string;
    strategy?: RunStrategy;
    /**
     * Whether the launched version is launchable-signed-in on THIS device
     * ({@link isVersionLaunchableHere}). `null` when the verdict is unknown — a
     * missing verdict must NOT be read as logged out.
     */
    signedIn: boolean | null;
    /** Account email of the version home when signed in, else null. */
    email: string | null;
    /** How the version was resolved (explicit-pin / rotated / pinned-default). */
    resolvedVia?: string;
}
/**
 * Build the `run.launch` event payload. Pure and exported so the signedIn ->
 * launchedLoggedOut mapping is unit-testable without spawning. Mirrors the
 * buildRotationDecisionEvent / emitRotationDecision split in rotate.ts.
 *
 * `launchedLoggedOut` is the headline flag — true ONLY when `signedIn` was
 * resolved to false (a launch into a logged-out version, the yosemite-m3 2.1.219
 * incident). An UNKNOWN verdict (`signedIn === null`) is never treated as
 * logged out.
 */
export declare function buildRunLaunchPayload(input: RunLaunchInput): EventPayload;
/** Exit code spawnAgent resolves with when a run is killed for crossing a budget cap. */
export declare const BUDGET_KILL_EXIT_CODE = 7;
/**
 * Exit code a tmux-wrapped run resolves with when tmux cannot tell us how the
 * agent finished — the pane is unreadable, or it is dead with no reported status
 * (EXEC-23b). Never 0: an unknown outcome reported as success is how a run killed
 * mid-work gets counted as a clean finish by whatever scripted it.
 */
export declare const UNKNOWN_OUTCOME_EXIT_CODE = 1;
/**
 * Patterns that indicate a rate/usage limit. Matching is intentionally broad
 * because providers phrase these differently -- Anthropic uses "5-hour limit"
 * and "rate limit", OpenAI surfaces 429s, Google says "quota exceeded".
 * False positives here just trigger a fallback attempt; false negatives leave
 * the original error unhandled, which is worse.
 */
export declare const RATE_LIMIT_PATTERNS: RegExp[];
/** Return true if the text contains any known rate-limit or overload indicator. */
export declare function detectRateLimit(text: string): boolean;
export declare function detectOutOfCredits(text: string): boolean;
/**
 * Classify what a Claude run's output + exit code means for the account's
 * persisted refusal marker. Pure and exported so the persist/clear decision is
 * unit-tested on the real path (runWithFallback can't be driven with a real
 * `claude` spawn in tests). Precedence: a session-limit reset wins (it carries a
 * clock), then a clock-less billing exhaustion, then a clean success clears any
 * stale marker; anything else leaves the marker untouched.
 */
export type ClaudeRefusalAction = {
    action: 'note_session';
    resetsAt: Date;
} | {
    action: 'note_out_of_credits';
} | {
    action: 'clear';
} | {
    action: 'none';
};
export declare function classifyClaudeRunRefusal(output: string, exitCode: number): ClaudeRefusalAction;
/**
 * Patterns that indicate an authentication failure — the agent is logged out,
 * its token was revoked, or the session expired. These are the user-visible
 * strings a logged-out agent surfaces (observed across the routine-run corpus).
 * Unlike a rate limit, an auth failure is NOT self-healing by failover — every
 * chain entry on the same account fails identically — so it is classified
 * separately and never triggers a fallback attempt.
 *
 * The bare `401` is deliberately paired with an auth keyword: a plain "401" can
 * appear in legitimate output (an HTTP-status table, a log line), so it only
 * counts when it co-occurs with OAuth/authentication/credentials/Unauthorized.
 */
export declare const AUTH_FAILURE_PATTERNS: RegExp[];
/**
 * Return true if the text contains any known authentication-failure indicator.
 * Agent-agnostic: matches the user-visible error string wherever it surfaces
 * (stdout tail, a captured error message, a plain-text agent's output).
 */
export declare function detectAuthFailure(text: string): boolean;
/**
 * Return true if a stream-json log carries the structural markers of an auth
 * failure. This is the authoritative signal for Claude: a logged-out run emits
 *   {"type":"system","subtype":"api_retry","error":"authentication_failed",…}
 *   {"type":"assistant",…,"error":"authentication_failed"}
 *   {"type":"result","is_error":true,"result":"Failed to authenticate…"}
 * Note `terminal_reason` is "completed" on such a run, so exit-code / terminal-
 * reason logic can never catch it — the `error:"authentication_failed"` marker
 * and the `result`+`is_error` text are the reliable signals.
 *
 * Gated on the Claude-compatible stream-json shape emitted by Claude and Cursor;
 * callers pass their agent so unrelated stream formats cannot match by accident.
 */
export declare function detectAuthFailureEvent(logText: string, agent: AgentId): boolean;
/**
 * Return the first human-readable auth-failure phrase found in the text, for use
 * as a stable, short run `errorMessage` reason. Falls back to null when the only
 * signal was the structural `error:"authentication_failed"` marker with no
 * user-visible string.
 */
export declare function authFailureReason(text: string): string | null;
/**
 * Decide whether a run's stream-json log is an authentication failure. The
 * single source of truth for both the foreground and detached run paths.
 *
 * The structural marker (`detectAuthFailureEvent`, gated on `is_error:true`) is
 * authoritative and safe on ANY exit — it catches a logged-out Claude that exits
 * 0 (its `result` event carries `is_error:true` while `terminal_reason` is
 * "completed"). The raw user-visible string (`detectAuthFailure`) is only
 * consulted when the process actually FAILED, as a fallback for a run that died
 * mid-stream before emitting a `result` event. That gate is what prevents a
 * genuinely-completed run whose output merely *mentions* an auth phrase (e.g. a
 * routine documenting a login flow) from being misclassified and having its
 * legitimate report suppressed.
 */
export declare function isAuthFailureFromLog(logText: string, agent: AgentId, opts: {
    processFailed: boolean;
}): boolean;
/** An agent (with optional pinned version) in a fallback chain. */
export interface FallbackEntry {
    agent: AgentId;
    /** Optional pinned version (e.g. '0.116.0'). When set, takes precedence over the active default. */
    version?: string;
    /**
     * Env vars merged over options.env for THIS attempt only. Used by profiles
     * with `fallback_model` to swap the model env key (e.g. ANTHROPIC_MODEL) on
     * a same-agent retry without touching auth or base URL.
     */
    envOverride?: Record<string, string>;
}
/** ExecOptions extended with a fallback chain for rate-limit cascading. */
export interface FallbackOptions extends ExecOptions {
    /** Ordered list of agents to try if the primary (options.agent) hits a rate limit. */
    fallback: FallbackEntry[];
    /** Fallback requires a prompt -- chain handoff doesn't apply to interactive sessions. */
    prompt: string;
    /**
     * Optional out-param the caller reads AFTER the call to learn which chain
     * entry actually executed — updated to each entry as it is attempted, so on
     * return it holds the agent+version whose exit code is returned. Lets the
     * audit log record the fallback that really ran, not always the primary
     * (issue #347).
     */
    dispatchSink?: {
        agent?: AgentId;
        version?: string;
    };
}
/**
 * Build the prompt handed to the fallback agent when the primary was stopped
 * mid-task by a rate limit.
 *
 * When the prior agent was Claude we pin its session ID via `--session-id` so
 * `prevSessionId` is always defined; for other primaries we pass undefined and
 * get a simpler retry-with-context prompt. Claude understands `/continue <id>`
 * via its shipped skill -- other agents fall through to an explicit instruction
 * that points at the version-agnostic `agents sessions <id>` reader.
 */
export declare function buildFallbackPrompt(prevAgent: AgentId, prevSessionId: string | undefined, nextAgent: AgentId, originalPrompt: string): string;
/**
 * Run an agent and, on rate-limit failure, cascade through the fallback chain.
 *
 * The primary agent gets the original prompt. Subsequent agents get a
 * `/continue <id>`-style handoff (see buildFallbackPrompt) when we can pin a
 * session ID -- which today means Claude as primary (supports `--session-id`).
 * For other primaries, fallbacks run with the original prompt plus a
 * retry-with-context note, since we can't deterministically resolve their
 * auto-generated session IDs.
 *
 * Only rate-limit failures cascade. Other errors (missing flag, auth failure,
 * compile error) bubble up from the primary so the caller sees the real cause
 * instead of an opaque "all agents failed" message.
 */
export declare function runWithFallback(options: FallbackOptions): Promise<number>;
