import { type Command } from 'commander';
import { type ProjectDef } from '../lib/projects.js';
import type { SessionAgentId, SessionMeta, ViewMode } from '../lib/session/types.js';
import { type ActiveSession } from '../lib/session/active.js';
export { activeSessionProjectKey, backfillActiveRowsFromIndex, backfillActiveRowsFromMeta, isRunningLiveSession, serializeActiveSessionsForJson, serializeSessionsJson, type BackfillMeta } from '../lib/session/active.js';
import { loadLocalActiveSessions } from '../lib/session/session-cache.js';
import { gatherRemoteList, runOnPeer } from '../lib/session/remote-list.js';
import { type RemoteAgentsJsonParseResult } from '../lib/remote-agents-json.js';
import { type RunMeta } from '../lib/scheduling/routines.js';
import { type PickedSession } from './sessions-picker.js';
import { type ComputerRunRow } from '../lib/computer/sessions-list.js';
import { type ToolSearchEnvelope, type ToolProgramCountEnvelope } from '../lib/session/tool-index.js';
interface SessionFilterOptions {
    agent?: string;
    version?: string;
    /** Internal Commander spelling after index.ts disambiguates root --version. */
    sessionVersion?: string;
    project?: string;
    all?: boolean;
    teams?: boolean;
    inTeam?: string;
    routine?: boolean | string;
    since?: string;
    until?: string;
}
interface SessionsOptions extends SessionFilterOptions {
    /** Also list sessions from the user's own unmanaged ~/.<agent> installs. */
    unmanaged?: boolean;
    query?: string[];
    /** Resolve one historical selector to metadata only (requires --json). */
    resolve?: string;
    /** Versioned internal peer protocol; old/unsafe peers must reject it. */
    resolveSafeV1?: string;
    limit?: string;
    sort?: string;
    json?: boolean;
    markdown?: boolean;
    /** Commander populates this from `--no-redact`: true by default, false when the flag is passed. */
    redact?: boolean;
    include?: string;
    exclude?: string;
    first?: string;
    last?: string;
    artifacts?: boolean;
    artifact?: string;
    active?: boolean;
    /** Emit the on-disk session-scan directories (requires --json); for watchers. */
    roots?: boolean;
    cloud?: boolean;
    host?: string[];
    /** Group the listing by directory and drop the id/version columns. */
    tree?: boolean;
    /** Force the plain flat table instead of the grouped default overview. */
    flat?: boolean;
    /** With --active: show only sessions waiting on user input; exit 1 if any. */
    waiting?: boolean;
    /** Live-state shorthand filters. Any one implies --active; several compose as OR. */
    working?: boolean;
    idle?: boolean;
    orphan?: boolean;
    orphaned?: boolean;
    crashed?: boolean;
    closed?: boolean;
    abandoned?: boolean;
    queued?: boolean;
    unknown?: boolean;
    /** Show only bookmarked sessions — the `b` key's flag twin. */
    bookmarks?: boolean;
    /** Enrich the listing with live glyphs/preview for running rows. Default on;
     * `--no-live` sets this false. Commander's `--no-` convention. */
    live?: boolean;
    /** Force local-only: skip the cross-machine SSH fan-out (both the default
     * listing and --active). */
    local?: boolean;
    /** --device <target...> — values from the `--device` flag; resolves against the device registry. */
    device?: string[];
    /** --devices <target...> — plural alias for --device; a bare `all`/`fleet` value
     * means "search the whole fleet" (already the default), so it never errors. */
    devices?: string[];
    /** Query every registered online compute device and merge tool evidence. */
    fleet?: boolean;
    /** Aggregate static program sites instead of returning matching call evidence. */
    count?: boolean;
    /** Per-agent shorthands: aliases for `--agent <name>` (prioritized harnesses). */
    claude?: boolean;
    codex?: boolean;
    kimi?: boolean;
    antigravity?: boolean;
    grok?: boolean;
    opencode?: boolean;
    /** Force the printed listing even on a TTY. Commander's `--no-` convention:
     * `--no-interactive` sets this false, opting out of the interactive browser. */
    interactive?: boolean;
    /** Print the canonical `ag sessions …` command for the given flags and exit —
     * the non-interactive twin of the browser's `y` hotkey. */
    printCmd?: boolean;
    /** Print a compact preview of the matched session and exit (no pager). */
    preview?: boolean;
    /** Only sessions that invoked this skill (#12) — matches a bare name or a
     * namespaced plugin skill's short name (`--skill design` finds `rush:design`). */
    skill?: string;
    /** Only sessions that used a skill/command owned by this plugin (#12). */
    plugin?: string;
}
type InstalledVersionsForAgent = (agent: SessionAgentId) => string[];
/**
 * Treat a positional `agent@version` as a structured filter only when it names
 * a real installed version. Everything else remains ordinary free-text search.
 */
export declare function parseInstalledAgentVersionQuery(query: string | undefined, installedVersions?: InstalledVersionsForAgent): string | undefined;
/**
 * Strip terminal/harness noise from a preview so the column stays a single line
 * of plain prose: OSC title escapes, CSI/SGR ANSI, and the harness wrapper tags
 * (`<local-command-stdout>`, `<task-notification>`, `<command-*>`) that leak from
 * a captured transcript tail. Collapses runs of whitespace.
 */
export declare function cleanPreview(text: string): string;
/**
 * Build the live description for an active session: checklist progress (when
 * present) plus the state engine's preview (the latest turn), a user label, or
 * the first-prompt topic. Used by both the flat listing's `doing` cell and as
 * the snippet half of the --active row (identity is layered on in printActiveRow).
 *
 * Covers every ActiveSession context: terminal (interactive), headless, teams,
 * cloud, and sub-agent rows that share the same ActiveSession.todos field.
 */
export declare function buildSessionDescription(s: ActiveSession): string;
/**
 * Identity + checklist + live snippet for an --active / cross-machine row.
 * Surfaces agent-adjacent identity the flat table already has (label, project)
 * with a clickable project when a GitHub URL is resolvable, then the checklist
 * tally and the latest-turn snippet.
 *
 * Free-text fields are cleaned individually; OSC 8 hyperlinks are applied
 * *after* cleaning so `cleanPreview` does not strip the clickable targets
 * (RUSH-2045 review). Ticket is clickable via {@link signalBadges}, not here,
 * so the id is not printed twice.
 */
export declare function formatActiveRowDescription(s: ActiveSession): string;
/**
 * Index live sessions by their full session UUID so a historical `SessionMeta`
 * row (`meta.id`) can be matched to the session that is still running now.
 * Rows without a sessionId (some cloud/headless probes) are skipped — they
 * can't be correlated back to a transcript on disk.
 */
export declare function indexActiveBySessionId(active: ActiveSession[]): Map<string, ActiveSession>;
/**
 * The live decoration for a listing row: a status glyph and the latest-turn
 * preview, when the session is still running. `●` running / `◐` waiting on the
 * user / `○` idle, colored by the same `statusColor` the --active view uses.
 * Returns empty strings when there is no live match, so callers render the
 * plain historical row unchanged.
 */
export declare function liveGlyphAndPreview(a: ActiveSession | undefined): {
    glyph: string;
    preview: string;
};
/**
 * The one-word live status for a listing row — `working` / `waiting` / `idle`,
 * the same three states the `--active` column shows, so the default list is no
 * longer just a glyph. `waiting` is the actionable "needs you" case (a question /
 * permission / plan-review), kept distinct from `idle` (stopped) and `working`.
 * Empty for a not-live row, and for the rare no-signal `unknown`. Pure +
 * exported for the row tests.
 */
export declare function liveStatusWord(a: ActiveSession | undefined): string;
/**
 * True when a session is blocked on a human — the `--waiting` contract.
 *
 * NOT `status === 'input_required'`. `foldHostLink` rewrites that status to
 * `orphaned` when nothing is attached, and a session waiting on a question with
 * NOBODY watching is the most acute case `--waiting` exists to surface, not one
 * it should drop. The underlying `activity` is never rewritten, so it is the
 * honest signal here.
 *
 * But `activity` is never rewritten for a DEAD session either: one that died
 * mid-question keeps `waiting_input` forever, and answering it is not a thing a
 * human can do — it needs a relaunch. `--waiting` is a scriptable gate ("does
 * anything need me?"), so a corpse must not trip it.
 *
 * `closed` and `crashed` are unconditionally dead, so they are excluded outright.
 * `abandoned` is NOT: it fires on transcript staleness before the liveness check,
 * so it also covers the live-but-forgotten case — an interactive session that
 * asked a question and sat untouched over a long weekend is still answerable, and
 * is exactly what this gate exists for. It is excluded only when we positively
 * know its process is gone; unknown liveness (an older peer, a row with no pid)
 * stays excluded rather than inventing a human who can answer.
 */
export declare function isAwaitingUser(s: ActiveSession): boolean;
/**
 * The tracker/PR ref for a session's dedicated column: the ticket id when known,
 * else `PR#<n>`, else empty. Pulled out of the trailing badge blob so refs align
 * into a scannable column instead of jamming against a truncated topic.
 */
export declare function ticketLabel(s: Pick<SessionMeta, 'ticketId' | 'prNumber'>): string;
export declare function linkTicketCell(s: Pick<SessionMeta, 'ticketId' | 'prNumber' | 'prUrl'>, label: string): string;
export declare function linkCwdCell(s: Pick<SessionMeta, 'cwd' | '_remote'>, label: string): string;
/**
 * The single project/group key for an active session row (RUSH-2688). It is the
 * ONE derivation every project-grouped consumer of `--active`/the menubar snapshot
 * shares, so a session is bucketed the same way everywhere:
 *
 *   - A real working dir → its `basename` — the same key SessionMeta derives (see
 *     discover.ts), so the active view and the history view join identically on
 *     (ticketId + project).
 *   - A row with NO local cwd (a cloud task, a provider-run job) → an explicit
 *     bucket keyed by context: `cloud` for a cloud row, else `other`.
 *
 * It never falls back to `kind` (the harness — 'codex', 'claude') or `machine`
 * (the box it runs on): neither is a project, and letting either stand in for the
 * key is exactly what leaked a Codex cloud task under a 'codex' group and a bogus
 * machine label in the menubar. No fallback chain — one explicit bucket.
 */
/**
 * The row shape `agents sessions --active --json` emits. RUSH-1981: a watcher
 * joins active sessions on ticketId + project, but the raw ActiveSession nests
 * the ticket (`ticket.id`) and carries no `project` at all — so a naive join
 * silently drops every row. Emit both as flat, always-present top-level keys
 * alongside the raw fields, so every active row is joinable. `project` is the
 * canonical {@link activeSessionProjectKey} — basename(cwd), or an explicit
 * `cloud`/`other` bucket for a row with no local cwd, never the harness/machine.
 *
 * `viewingIn` flattens to the same display string the row renderer prints —
 * `'codium tab 3'` / `'detached'` / null — so a consumer can tell a watched
 * session from an orphaned one (its terminal died, the agent is still running)
 * without re-implementing the tmux client lookup.
 */
export interface SessionPickerJsonRow extends SessionMeta {
    state: ActiveSession['status'] | 'inactive';
    resumable: boolean;
    unwatched: boolean;
    viewingIn: string | null;
    sourceDevice: string;
    lastActivityMs: number;
    pid: number | null;
    recovery: {
        command: 'agents';
        args: string[];
        cwd?: string;
    } | null;
}
/** Canonical JSON row consumed by resume pickers and other session UIs. */
export declare function serializeSessionPickerRows(sessions: SessionMeta[], liveSessions: ActiveSession[], self?: string): SessionPickerJsonRow[];
/**
 * Build the (one or two) rendered lines for a single active-session row.
 * Indent is the leading whitespace (2 spaces for flat groups, 4 inside a window
 * sub-group). Sized to `termW` so no line ever wraps under tmux/SSH (RUSH-2205):
 *
 *   line 1: id · agent version · status · owner · created X · idle Y · ticket/PR
 *   line 2: └ label/topic (+ checklist) · jump locator (ssh/tmux/detached)
 *
 * The label/topic gets its own line so it is no longer buried in a truncated grey
 * snippet, and the actionable ticket/PR badges ride line 1. Version and the
 * ticket/PR/label are backfilled onto the {@link ActiveSession} from the indexed
 * SessionMeta before this renders (a live process reports none of them). Pure +
 * exported so the row layout is unit-tested for content and width without a
 * captured stdout.
 */
export declare function renderActiveRowLines(s: ActiveSession, indent: string, termW: number): string[];
/**
 * Compact owner display for the `--active` owner column: the local-part of a
 * resolved actor email/login (`muqsit@getrush.ai` -> `muqsit`), the id as-is
 * when it has no `@`, and `-` when the actor is unresolved (`UNRESOLVED@<host>`)
 * or absent (a launch predating actor stamping). Honest by design — an
 * unresolved local run shows no owner rather than inventing one.
 */
export declare function ownerLabel(s: ActiveSession): string;
/** Grouped + sorted view of active sessions for the --active renderer. */
export interface ActiveSessionsLayout {
    workspaces: Array<{
        /** Internal grouping key — `__cloud__`, `__unknown__`, or the cwd. */
        key: string;
        /** Sessions in this workspace, both windowed and flat (preserves total count). */
        total: number;
        /** Terminals grouped by IDE window (sorted by oldest startedAtMs). */
        windows: Array<{
            windowId: string;
            sessions: ActiveSession[];
        }>;
        /** Everything else in this workspace: cloud, teams, headless, terminals without a windowId. */
        flat: ActiveSession[];
    }>;
}
/**
 * Group sessions by workspace, then split each workspace into IDE-window
 * sub-groups + a flat bucket. Pure function — no I/O — so the renderer's
 * grouping rules can be tested without mocking the session scanner.
 *
 * Sort order:
 *   - workspaces: by session count descending, then key ascending
 *   - windows within a workspace: by oldest startedAtMs ascending
 *   - sessions within a window/flat bucket: input order preserved
 */
export declare function groupActiveSessions(sessions: ActiveSession[]): ActiveSessionsLayout;
/** One machine's active sessions, keeping the within-machine workspace layout. */
export interface MachineGroup {
    /** Normalized device id (machineId() form). */
    machine: string;
    /** The machine this command is running on — pinned first and marked. */
    isLocal: boolean;
    total: number;
    layout: ActiveSessionsLayout;
}
/** Active sessions grouped by the machine they run on. */
export interface MachineGroupedLayout {
    machines: MachineGroup[];
}
/**
 * Group active sessions by machine, then delegate each machine's sessions to the
 * existing workspace/window grouping. Local machine is pinned first and flagged;
 * the rest sort by session count descending, then name. Pure — `localMachine` is
 * injected so the function stays testable without reading os.hostname().
 */
export declare function groupSessionsByMachine(sessions: ActiveSession[], localMachine: string): MachineGroupedLayout;
/**
 * Collapse duplicate sessions after a cross-machine merge. Two rows collapse
 * only when they share both machine and session UUID (the same host listed
 * twice, or a local/remote overlap); rows without a sessionId can't be
 * correlated, so they're all kept.
 */
export declare function dedupeByMachineSession(sessions: ActiveSession[]): ActiveSession[];
/**
 * Narrow a gathered live set to the machines an explicit `--device`
 * scope named. The gather picks which boxes to ASK; this asserts what the answer
 * may contain, and the two are not the same question: a host-dispatched run is
 * reported by the box that dispatched it while executing somewhere else, so
 * `--device <dispatcher>` used to list sessions that were not running there at
 * all (RUSH-2479). No scope → unchanged. Pure; exported for unit testing.
 */
export declare function filterActiveSessionsByHostScope(sessions: ActiveSession[], hosts: string[] | undefined, self: string): ActiveSession[];
/**
 * Order a merged listing so the local machine's sessions come first, then each
 * remote machine as a contiguous block (more sessions first, then name), with
 * every machine keeping its incoming order (timestamp) within the block. Also
 * dedupes: a session present both locally (a synced mirror copy) and via live
 * fan-out collapses to one, keyed by machine + session id. Rows are keyed by
 * `machine` (discover tags local rows with the local id; fan-out tags remote
 * rows with the peer id) falling back to `localMachine` when untagged. Pure —
 * `localMachine` is injected so the ordering is testable without os.hostname().
 */
export declare function mergeLocalFirst(sessions: SessionMeta[], localMachine: string): SessionMeta[];
/**
 * Serialize a `SessionMeta[]` to the clean JSON shape the `--json` listing
 * emits: strip the internal-only scoring/provenance fields (`_matchedTerms`,
 * `_bm25Score`, `_remote`) that are search/fan-out bookkeeping, never part of
 * the public record, then pretty-print as a 2-space array with a trailing
 * newline. The single seam shared by the local `--json` path and the
 * `--json --host` remote fan-out so both emit byte-identical row shapes.
 */
/** The intentionally small metadata contract emitted by `sessions --resolve`.
 * It includes only launch identity needed to route/resume. Transcript paths,
 * extracted plans, costs, and content stay on the machine that owns them. */
export declare function serializeResolvedSessionsJson(sessions: SessionMeta[]): string;
/** Parse one peer's computer-run array without accepting banners or partial
 * JSON. The fan-out boundary supplies the machine name when an older peer row
 * omitted it. */
export declare function parseRemoteComputerSessionRows(stdout: string, machine: string): RemoteAgentsJsonParseResult<ComputerRunRow>;
/** Collect computer history from named peers, or every automatic peer when
 * hosts is omitted. Every peer is forced to JSON and receives the recursion
 * guard through the shared fleet transport. */
export declare function gatherRemoteComputerSessionRows(hosts?: string[]): Promise<ComputerRunRow[]>;
/**
 * Whether the local machine's sessions belong in an `--active` view. Local is
 * included by default; an explicit `--device` list scopes the view to
 * exactly those machines, so local is dropped unless it is itself named (by
 * alias or `user@host`, matched on the normalized machine id). Exported for
 * unit testing without touching SSH or the live process table.
 */
export declare function shouldIncludeLocal(hosts: string[] | undefined, self: string): boolean;
/**
 * The peers to dial for an `--active` view. No `--device` → `undefined`, which
 * tells `gatherRemoteActive` to sweep the registered online devices. An
 * explicit list → exactly those, minus this machine (its sessions come from the
 * local seed, so dialing self would be a wasted SSH and a spurious "unreachable"
 * note). Returns `[]` when the only named host is self — the caller then skips
 * the remote fan-out entirely rather than letting `[]` trigger the sweep.
 * Exported for unit testing.
 */
export declare function remoteHostsToDial(hosts: string[] | undefined, self: string): string[] | undefined;
/**
 * The fleet-wide live-session set behind every `--active` surface. Local sessions
 * come from `getActiveSessions()` and (unless `--local`) the registered online
 * devices from `ag devices` are folded in over SSH. An explicit `--device`
 * list SCOPES the sweep to exactly those machines — the local machine is included
 * only when it is itself named — so `--device` is a filter, not an addition.
 *
 * This is the single gather: the static renderer AND the interactive browser both
 * call it, so the browser can never disagree with `--active --json` about which
 * sessions are live (it used to call the local-only `getActiveSessions()` directly
 * and silently hid every remote session).
 *
 * RUSH-2062: default path is cache-first against the daemon-warmed shared
 * snapshot (`session-cache.ts`). Menubar / Factory / watchdog / CLI share one
 * warm result instead of each re-running the full SSH fan-out. `forceRefresh`
 * (or `AGENTS_SESSIONS_FORCE_REFRESH=1`) re-gathers live; scoped `--device` lists
 * always gather live so a filter never returns a wrong unscoped snapshot.
 */
export declare function gatherActiveSessions(opts?: {
    local?: boolean;
    hosts?: string[];
    forceRefresh?: boolean;
}): Promise<{
    sessions: ActiveSession[];
    remoteDeviceCount: number;
    /** Peer names that went unheard on this gather (RUSH-2507). Undefined when unknown (e.g. a cache hit). */
    remoteSkipped?: string[];
    remoteDiscoveryFailed?: boolean;
}>;
/** Apply the routine selector to enriched live rows for every non-browser active view. */
export declare function filterActiveSessionsByRoutine(sessions: ActiveSession[], routine: boolean | string | undefined): ActiveSession[];
export type LiveStatusFilter = 'working' | 'idle' | 'waiting' | 'orphaned' | 'crashed' | 'closed' | 'abandoned' | 'queued' | 'unknown';
/** Match the status words users see, preserving activity's richer working signal. */
export declare function matchesLiveStatus(session: ActiveSession, status: LiveStatusFilter): boolean;
/** Resolve convenience flags once. Multiple flags intentionally form a union. */
export declare function requestedLiveStatuses(options: SessionsOptions): LiveStatusFilter[];
/**
 * A bare interactive fleet listing — no query, no render/filter flag — that the
 * `runSessionBrowser` picker can represent. The single predicate shared by the
 * bare-browser branch and the `--device` early-return guard so they can't drift:
 * when this holds, an explicit `--device` scope is folded into the
 * browser (preview-rich, selectable) instead of the legacy per-host raw stream.
 */
export declare function isBareBrowserListing(options: SessionsOptions, query: string | undefined): boolean;
/**
 * Pure flag-gate half of {@link isBareBrowserListing} (TTY-independent, so it is
 * unit-testable): true when no query, render, or filter flag is present that the
 * `runSessionBrowser` picker cannot represent.
 */
export declare function hasNoBrowserDisqualifyingFlags(options: SessionsOptions, query: string | undefined): boolean;
/** Resolve a typed routine selector by exact name, substring, or one unambiguous typo. */
export declare function resolveRoutineName(query: string, names: readonly string[]): string | null;
export interface RoutineRunGroup {
    runId: string;
    timestamp: string;
    sessions: SessionMeta[];
}
export interface RoutineChoice {
    name: string;
    lastRunAt: string;
    runCount: number;
    latestRunSessionCount: number;
}
export declare function buildRoutineRunGroups(sessions: SessionMeta[]): RoutineRunGroup[];
export declare function buildRoutineChoices(sessions: SessionMeta[], runOnlyNames?: string[]): RoutineChoice[];
/**
 * Resolve which routine the user is targeting for `--routine`. Returns `null` to
 * ABORT the command (a bad `--routine <name>` printed an error, or the user
 * cancelled the interactive pick). `{ name: null }` means "no routine was
 * selected, don't filter" — the degenerate bare `--routine` piped to a
 * non-interactive sink. `{ name }` is a chosen routine.
 *
 * The name universe is transcripts UNION every routine with a definition or a
 * run record (`safeListJobsWithRuns`), so a command-only or never-ran routine —
 * which produces no `SessionMeta` — still resolves and drills down.
 */
export declare function selectRoutineTarget(sessions: SessionMeta[], routine: boolean | string, interactive: boolean): Promise<{
    name: string | null;
} | null>;
export declare function filterSessionsByRoutine(sessions: SessionMeta[], routine: boolean | string, interactive: boolean): Promise<SessionMeta[] | null>;
/** How a run executed — decides whether an agent session is even expected. */
export type RoutineExecutionKind = 'agent' | 'command' | 'workflow';
export declare function executionKind(meta: RunMeta): RoutineExecutionKind;
/** One canonical run record plus the indexed sessions linked to it by run id. */
export interface RoutineRunEntry {
    meta: RunMeta;
    sessions: SessionMeta[];
}
export interface RoutineDrilldown {
    name: string;
    /** Canonical run records (newest first), each with its linked sessions. */
    runs: RoutineRunEntry[];
    /** Sessions whose `routineRunId` matches no local run record — e.g. archived
     *  on another host whose run dir this box has not synced. Grouped by run id so
     *  they are still shown, honestly separated from the canonical run history. */
    orphanSessions: RoutineRunGroup[];
    /** Count of canonical run records (any status, incl. missed/blocked/skipped). */
    runRecordCount: number;
    /** Count of linked indexed sessions across all runs (+ orphans). */
    linkedSessionCount: number;
    /** True when any run ran an agent or any session is linked — i.e. an agent
     *  routine. False for a pure command routine, where no session is expected. */
    isAgentRoutine: boolean;
}
/**
 * Reconcile a routine's canonical run records (`listRuns`, local disk) with the
 * indexed sessions for that routine (fleet-wide, keyed by `routineRunId`).
 */
export declare function buildRoutineDrilldown(name: string, sessions: SessionMeta[]): RoutineDrilldown;
/**
 * Render the routine drilldown: canonical run history first, each run linked to
 * its indexed agent session(s). This is the run/session seam made visible — a
 * command routine shows its runs and states plainly that no session is expected;
 * an agent routine shows the same runs plus each run's session metadata.
 */
export declare function printRoutineDrilldown(drill: RoutineDrilldown, liveIndex?: Map<string, ActiveSession>, opts?: {
    hiddenCount?: number;
    hiddenUnmanaged?: number;
}): void;
/** Resolve a session by id/query globally and print its compact preview (no pager).
 * Backs `--preview` — the fast path for the "peek before resume" hot loop. */
export declare function renderSessionPreview(query: string, scope: {
    agent?: string;
    project?: string;
    local?: boolean;
    hosts?: string[];
    json?: boolean;
}): Promise<void>;
/**
 * The one-line live status banner shown above a session preview: the glyph, the
 * status word, and — when the session needs a human or has LOST one — a plain
 * sentence saying so. Shared by `--preview` and the interactive browser's preview
 * pane so both explain a state the same way.
 *
 * `crashed` and `orphaned` are the states a glyph alone cannot carry: nobody
 * reads "orphan" and knows it means "still running in tmux with no window
 * attached", so those two spell it out.
 */
export declare function formatLiveStatusHeadline(live: ActiveSession | undefined, bookmarked?: boolean): string;
/** Merge local and peer envelopes without changing the versioned JSON shape. */
export declare function mergeToolSearchEnvelopes(local: ToolSearchEnvelope, remotes: ToolSearchEnvelope[]): ToolSearchEnvelope;
export declare function mergeToolProgramCountEnvelopes(local: ToolProgramCountEnvelope, remotes: ToolProgramCountEnvelope[]): ToolProgramCountEnvelope;
/** Partition a fleet query by transcript origin so synced mirrors cannot duplicate results. */
export declare function toolOriginSessions(sessions: SessionMeta[], machine: string, originOnly: boolean): SessionMeta[];
export declare function printToolProgramCount(envelope: ToolProgramCountEnvelope): void;
export declare function toolSearchFleetSortError(sort: string | undefined, spansDevices: boolean): string | undefined;
/** Compact grouped evidence for humans; JSON retains every bounded field. */
export declare function printToolSearch(envelope: ToolSearchEnvelope): void;
/**
 * Whether a session belongs to `team`, from either end: it spawned the team, or
 * it is one of the team's teammates. Case-insensitive, matching the SQL
 * predicate behind `querySessions({ spawnedTeam })`.
 */
export declare function matchesTeam(session: SessionMeta, team: string): boolean;
/**
 * The `team:<name>` badge for a session that SPAWNED a team — the orchestrator
 * end of the lineage, from the scan-derived `spawnedTeam`. Returned as a plain
 * (uncolored) string plus its display width so callers can reserve the width
 * from the topic budget and color it as their own segment: folding it into the
 * topic string would lose the color, since renderTopicCell strips ANSI and
 * re-whitens every slice.
 */
export declare function teamBadge(session: SessionMeta): {
    plain: string;
    width: number;
};
/** One flat table row:
 *   shortId · agent · version · model · project · [glyph] label·doing · [ticket] · [wt] · time
 * `doing` is the live preview when running, else the topic. The `ticket` column
 * (tracker/PR ref, pulled out of the badge blob so refs align) is only rendered
 * when `showTicket` — otherwise a listing with no refs would waste a column of
 * dashes and needlessly truncate the topic. Worktree stays a trailing badge. */
export declare function flatSessionRow(session: SessionMeta, live?: ActiveSession, showTicket?: boolean, cols?: PickerColumns, bookmarked?: boolean): string;
/**
 * Live-session index for enriching the default listing, or undefined when
 * enrichment is off (`--no-live`) or irrelevant (`--json`, which serializes
 * SessionMeta). Full detection (incl. the headless `ps` scan) is deliberate:
 * bare-CLI and tmux agents are the common case here, and skipping them would
 * leave the glyph almost never showing. The listing is a one-shot user action,
 * not a hot loop, so the `ps`/`lsof` cost is acceptable; `--no-live` is the
 * escape hatch. Never throws — a probe failure just yields a plain listing.
 */
export declare function maybeLiveIndex(options: SessionsOptions): Promise<Map<string, ActiveSession> | undefined>;
/**
 * Group key for the overview: resolve the cwd through the same canonical
 * resolver the `agents feed` timeline groups by — a defined project's name
 * when `defs` contains the cwd (multi-repo projects read as one group), else
 * the repo-level key, so a monorepo subdir (`<repo>/apps/cli`) reads as
 * `<repo>` in both views. Falls back to the indexed project name (stamped at
 * scan time) when the cwd carries nothing usable, e.g. a remote path this
 * machine cannot see still folds by basename through the same resolver.
 */
export declare function overviewProjectKey(s: Pick<SessionMeta, 'project' | 'cwd'>, defs?: ProjectDef[]): string;
export interface OverviewGroup {
    key: string;
    total: number;
    shown: SessionMeta[];
    more: number;
    maxTs: string;
}
/**
 * Turn a recency-descending pool into project groups: each group shows its
 * `perProjectCap` most-recent sessions (the rest become `· N more`), and groups
 * are ordered by their most-recent session so the newest-active project leads.
 * `perProjectCap = Infinity` expands every group. Pure — unit-tested.
 */
export declare function buildOverviewGroups(pool: SessionMeta[], perProjectCap: number, defs?: ProjectDef[]): {
    groups: OverviewGroup[];
    projectCount: number;
};
/**
 * Render a resolved session to stdout — the non-follow view behind
 * `agents logs <sessionId>`. Defaults to the concise `summary` digest (same as
 * `agents sessions <id>`); pass `'markdown'` for the full transcript
 * (`agents logs <id> --full`). Reuses the shared `renderSession` renderer.
 */
export declare function renderSessionLog(session: SessionMeta, mode?: ViewMode): Promise<void>;
/**
 * Emit one session exactly as `agents sessions <id> --json` does — the same
 * redact-by-default `{ session, events }` shape — so `agents logs <id> --json`
 * shares one machine-readable session contract instead of inventing another.
 */
export declare function renderSessionLogJson(session: SessionMeta): Promise<void>;
/** Column-visibility flags for the picker row, computed once over the whole pool. */
/** The SSH-launch origin for a picker row, resolved from the live session's
 * provenance (transport 'ssh'). `device` is set when the client IP matched a
 * registered device; absent for an unresolved IP (renders a bare `ssh`). */
export interface SshOriginTag {
    device?: string;
}
export interface PickerColumns {
    /** Render the machine column (only when the pool spans more than one machine). */
    showMachine?: boolean;
    /** Map a full machine id to its compact display form (shared prefix stripped). */
    machineLabel?: (m: string) => string;
    /** Total width of the machine column, sized to the widest compacted hostname
     * in the pool (capped). Falls back to PICKER_MACHINE_W when absent. */
    machineWidth?: number;
    /** Render the model column only when at least one row carries a model. */
    showModel?: boolean;
    /** Pool-sized model column width, including one trailing separator cell. */
    modelWidth?: number;
    /** Render the ticket/PR column (only when at least one row carries a ref). */
    showTicket?: boolean;
    /**
     * Render the host-program column — which terminal/editor the session is running
     * in (`ghostty`, `codium`, `tmux→ghostty`, …). Live-only: it comes from the
     * active-session scan, so it is set by the running-filtered browser and stays
     * off for a plain transcript listing, where no row has a host.
     */
    showHost?: boolean;
    /**
     * Render the bookmark marker column. Like every other conditional column here,
     * it earns its 2 cells only when some row in the pool is actually starred — a
     * user who has never bookmarked anything pays nothing for the feature.
     */
    showBookmark?: boolean;
    /**
     * Render the live status column (`working` / `waiting` / `orphan` / `crashed`).
     * Live-only, gated the same way as {@link showHost}: it comes from the
     * active-session scan, so the running-filtered browser sets it and a plain
     * transcript listing — where no row has a status — leaves it off.
     */
    showStatus?: boolean;
    /**
     * Cells the picker prepends before each row: 2 for the single-select cursor
     * ('> '), 6 for the multi-select cursor + checkbox ('> [x] '). Reserved from
     * the topic width so rows never wrap. Defaults to 2.
     */
    gutter?: number;
}
/**
 * Compact display form for machine ids: strip the longest shared dash-delimited
 * prefix so "yosemite-s0"/"yosemite-s1" read as "s0"/"s1" while unrelated ids
 * ("zion", "mac-mini") stay whole. Stripping a *common* prefix can't collide,
 * and at least one segment is always kept.
 */
export declare function machineLabeler(machines: string[]): (m: string) => string;
/**
 * Column flags for a picker, computed once over the whole pool so every row
 * aligns: the machine column only earns its width when the listing spans more
 * than one box, the ticket column only when some row carries a PR/ticket ref.
 */
export declare function pickerColumnsFor(sessions: SessionMeta[]): PickerColumns;
/**
 * Which program a live session is running in, for the picker's host column: the
 * immediate host app (`codium`, `ghostty`, `tmux`, `iterm`, …) and — when a tmux
 * session is currently being watched through a different app — the app it is
 * viewed in, as `tmux→ghostty`. A tmux session with no attached client stays a
 * bare `tmux`, which is exactly "running detached". Empty when the session has no
 * resolvable host (cloud rows, an unreadable process env).
 */
export declare function liveHostLabel(a: ActiveSession | undefined): string;
export declare function formatPickerLabel(s: SessionMeta, query: string, cols?: PickerColumns, ssh?: SshOriginTag, host?: string, bookmarked?: boolean, live?: ActiveSession): string;
/**
 * Pick a hint to show above the picker. Deterministic (keys off the pool size)
 * so it stays fixed across the picker's re-renders within a single run.
 */
export declare function formatPickerTip(sessions: SessionMeta[]): string;
export declare function pickSessionInteractive(sessions: SessionMeta[], message?: string, initialSearch?: string, hiddenCount?: number, enterHint?: string, scope?: SessionSearchScope): Promise<PickedSession | null>;
/**
 * Row-id prefix for a live session whose agent has not reported a session id yet
 * (a booting harness, a queued teammate). The browser lists these so nothing live
 * is hidden, but they address no transcript — {@link isIdlessLiveRow} is what the
 * pick handler checks before trying to read or resume one.
 */
export declare const LIVE_ROW_PREFIX = "live:";
export declare function isIdlessLiveRow(s: SessionMeta): boolean;
export declare function handlePickedSession(picked: PickedSession): Promise<void>;
/**
 * Resume `session` on the machine that owns it, when that is not this one.
 * Returns true when it handled the resume (hopped, or reported an unreachable
 * owner), false when the session is local and the caller should take over here.
 *
 * The one hop for a foreground, one-session-at-a-time resume — shared by the
 * `agents sessions` picker and by `sessions resume`'s no-tab-backend path, which
 * would otherwise reach `resumeSessionInPlace` and be refused (RUSH-2022).
 */
export declare function resumeOnOwnerIfRemote(session: SessionMeta): Promise<boolean>;
/**
 * Resume a session in the current terminal — a foreground takeover of this
 * process. Used by the single-select picker and by `sessions resume` when the
 * chosen destination is "in place" (unknown emulator / off-macOS, single pick).
 * Falls back to the same resume invocation against the current version when the
 * version-pinned launcher is genuinely missing.
 */
export declare function resumeSessionInPlace(session: SessionMeta): Promise<void>;
/**
 * Relaunch this CLI's one recovery path. The owning device resolves account
 * health and either performs exact-home native resume or same-harness
 * `/continue`; callers never guess which version home can see the transcript.
 * `portable` is for an SSH/terminal-engine command that executes on a peer.
 */
export declare function buildSessionRecoveryCommand(session: Pick<SessionMeta, 'id'>, portable?: boolean): string[];
/**
 * Map a resume argv to the spawn(command, args, {shell}) triple.
 *
 * On Windows the agent launcher is a `.cmd`/PATHEXT shim and needs shell:true.
 * With shell:true, Node concatenates args into the cmd.exe line unescaped
 * (DEP0190 + injection). A session.id derived from a filename can carry
 * metacharacters (`&|<>`); compose a fully-quoted line and pass an EMPTY args
 * array so cmd.exe cannot reparse them (RUSH-1753). See composeWin32CommandLine.
 *
 * `platform` is injectable so the win32 shell path is unit-testable on any host.
 */
export declare function resumeSpawnInvocation(cmd: string[], platform?: NodeJS.Platform): {
    command: string;
    args: string[];
    shell: boolean;
};
export declare function buildResumeCommand(session: SessionMeta): string[] | null;
interface AgentFilter {
    agent?: SessionAgentId;
    version?: string;
}
/** Resolve the harness portion of a sessions selector with the CLI's canonical
 * alias and single-typo rules. Kept pure so focus and the browser cannot drift. */
export declare function resolveSessionAgentName(name: string): SessionAgentId | null;
export declare function parseAgentFilter(agentName?: string): AgentFilter;
/**
 * Explicit `--agent` / `--project` / `--routine` flags. Distinct from the
 * listing pool (cwd-scoped, default-capped) that PHNX-2767 hydrates past:
 * these flags MUST still exclude FTS hits after that union.
 */
export type SessionSearchScope = {
    agent?: string;
    project?: string;
    routine?: boolean | string;
};
/**
 * How a `sessions <query>` argument was resolved against the pool.
 *
 * `byId` records that the rows came from an id lookup, so only then does an
 * ambiguous result mean "your id prefix is too short". `completeId` records that
 * the query was a whole session id: it is unique by construction, so a miss is
 * final and must NOT widen into a text/content search.
 */
export interface SessionQueryResolution {
    matches: SessionMeta[];
    byId: boolean;
    completeId: boolean;
}
/**
 * The single entry point for turning a `sessions <query>` argument into rows.
 *
 * An id-shaped query — a complete id OR a hex short-id/prefix (looksLikeSessionId)
 * — resolves by id alone, through the index, and never falls back to content
 * search (a bare id must not surface every transcript that merely mentions it).
 * A genuine search phrase keeps the ranked metadata+content search.
 */
export declare function resolveSessionQuery(pool: SessionMeta[], query: string, options?: {
    indexFallback?: boolean;
    scope?: SessionSearchScope;
}): SessionQueryResolution;
/** Honest result of a fleet sweep that found nothing: it says what the sweep
 * actually did — how many peers answered, which were unreachable — instead of
 * dead-ending on "search with --device <host>" after the search already ran. */
export declare function fleetNotFoundMessage(query: string, deviceCount: number, unreachable: string[]): string[];
/** Filter and rank sessions by a multi-term search query across metadata and content. */
export declare function filterSessionsByQuery(sessions: SessionMeta[], query: string | undefined, scope?: SessionSearchScope): SessionMeta[];
/**
 * Narrow a session list by --project and --agent before search resolution.
 * Without this, a query like "scoped search" could match sessions in BOTH
 * the project you specified AND elsewhere, producing an ambiguity error
 * even though the user already pointed at the correct scope.
 *
 * PHNX-2767 hydrates FTS hits missing from the listing pool *after* this
 * runs, so callers that search must also pass the same scope into
 * `filterSessionsByQuery` / `scopedContentIndex` or the union
 * reintroduces the rows this function just dropped.
 */
export declare function applyScopeFilters(sessions: SessionMeta[], scope: SessionSearchScope): SessionMeta[];
export declare function artifactLookupScope(agent?: string, project?: string, routine?: boolean | string): SessionSearchScope;
/**
 * Whether a missed local id lookup should widen to a cross-machine sweep.
 *
 * Gate (all must hold):
 *   - the query is id-shaped (`looksLikeSessionId`) — only an identifier resolves
 *     across the fleet; a search phrase never does.
 *   - not `--local` — the caller opted out of cross-machine lookup (deterministic
 *     local behavior for scripts, RUSH-2024 acceptance: "--local still restricts").
 *   - `AGENTS_SESSIONS_LOCAL` is unset — we are not ourselves a peer answering a
 *     parent's sweep, so a locate can never recurse (RUSH-2024: avoid double-fan-out).
 *
 * Pure + exported so the gate is unit-tested without driving discovery / SSH.
 */
export declare function shouldFanOutForId(query: string, local: boolean | undefined): boolean;
/** Injectable SSH/peer boundary so the fleet-resolve logic is unit-testable
 * without a live tailnet. Production wires these to the real remote-list infra. */
export interface FleetResolveDeps {
    gatherRemoteList: typeof gatherRemoteList;
    runOnPeer: typeof runOnPeer;
}
/** One distinct machine that reported a logical session, plus its winning row. */
interface FleetHit {
    machine: string;
    session: SessionMeta;
}
/** One logical session returned by the fleet, including every machine holding a copy. */
export interface FleetSessionCandidate {
    id: string;
    hits: FleetHit[];
}
export type MetadataResolveOutcome = {
    kind: 'resolved';
    session: SessionMeta;
} | {
    kind: 'not-found';
} | {
    kind: 'ambiguous';
    candidates: FleetSessionCandidate[];
} | {
    kind: 'partial';
    failedPeers: string[];
};
/**
 * A match unique enough to resolve on the FIRST peer that returns it and cancel
 * the rest of the fleet sweep: a full session UUID (globally unique), a live
 * tmux alias, or a bare 8-hex short id — all three name at most one session PER
 * ANSWERING peer, so the first reachable hit is enough (PHNX-3292: the alias/
 * short-id case is exactly what `agents tmux ls` prints, and it used to stay
 * all-settle right alongside a genuine keyword search).
 *
 * A session **label** is deliberately NOT definitive here even on an exact
 * match: labels are free-form and can collide, so a distinct session may carry
 * the same label on a peer that has not answered yet — early-exiting on the
 * first would silently, and nondeterministically, resume the wrong session
 * (whichever peer replied first). Labels therefore stay all-settle so a
 * cross-machine label conflict surfaces as an ambiguity, and a short-id PREFIX
 * *shorter* than the full 8-hex width stays all-settle for the same reason
 * (RUSH-2203).
 *
 * This is about cancelling a sweep that is still IN FLIGHT, where a peer that
 * has not answered yet is still expected to. It is deliberately stricter than
 * `isUniqueEnoughSelector`, which decides what to do once the sweep is OVER and
 * a peer has definitively not answered — see SES-9a. A collision between two
 * peers that BOTH answer before the abort lands is still caught by the ordinary
 * uniqueness gate in `metadataResolveOutcome`/`fleetCandidatesByQuery`; only a
 * peer that has not answered yet, at the moment of the FIRST hit, is the
 * accepted risk (documented in the PHNX-3292 plan).
 */
export declare function isDefinitiveMatch(session: SessionMeta, selector: string): boolean;
/**
 * Whether a selector may enable early-exit on the cancellable fan-out: a full
 * UUID, a tmux alias, or an exact 8-hex short id — every shape that is globally
 * unique enough for the first hit to be the only hit worth waiting for. Labels,
 * keywords, and short-id prefixes narrower than 8 hex stay all-settle: their
 * uniqueness (or conflict) is only knowable once every peer has answered. See
 * {@link isDefinitiveMatch}.
 */
export declare function selectorAllowsEarlyExit(selector: string): boolean;
/** Resolve a fleet sweep through the same canonical full-id / prefix resolver as
 * local lookups, then group copies by logical session id. Synced mirrors of one
 * session therefore stay one candidate even when several machines report them;
 * distinct ids sharing a prefix remain distinct ambiguity candidates. */
export declare function fleetCandidatesByQuery(rows: SessionMeta[], query: string, trustResolvedRows?: boolean): FleetSessionCandidate[];
/** Fixed peer argv for the metadata resolver. Scope flags compose identically on
 * every host; `--all` removes the SSH login cwd/time window, not agent/project filters. */
export declare function metadataResolveForwardedArgs(selector: string, scope: Pick<SessionFilterOptions, 'agent' | 'project'>): string[];
/** Build the local-only peer argv for a distributed tool search. */
export declare function toolSearchForwardedArgs(argv: string[], hosts: string[]): string[];
/**
 * Whether a selector names ONE session precisely enough to resolve from the
 * reachable fleet alone when some peer did not answer (SES-9a).
 *
 * Deliberately stricter than `looksLikeSessionId`, which accepts any 6+ char
 * `[0-9a-f-]` run and so matches ordinary words (`facade`, `decade`, `beaded`).
 * Those are keywords a user typed as a search, not identifiers, and they must
 * keep waiting for every peer.
 */
export declare function isUniqueEnoughSelector(selector: string): boolean;
/** Resolution fails closed for keywords and labels when a peer did not answer.
 * An id-shaped selector at least `SHORT_SESSION_ID_WIDTH` hex chars wide (or a
 * complete id) resolves from a single reachable hit even when an unrelated
 * registered device is offline — see SES-9a for the accepted collision risk. */
export declare function metadataResolveOutcome(localMatches: SessionMeta[], remote: {
    sessions: SessionMeta[];
    unreachable: string[];
}, selector: string): MetadataResolveOutcome;
/**
 * Whether a local candidate answers a full-UUID selector **definitively** — i.e.
 * whether finding it on this box is the whole answer, so the fleet fan-out can be
 * skipped (PHNX-3890).
 *
 * Two shapes qualify, and both are claims this box can actually back:
 *
 * - **A real transcript on this disk** (`filePath`). It renders locally, whoever
 *   owns it — a local session or a fleet-synced mirror — so no SSH hop is needed.
 * - **A genuine peer attribution** (`machine` names another box). The row already
 *   routes the read to its owner through `transcriptOnPeerOf`, so the peer that
 *   would answer the fan-out is the peer the render already dials.
 *
 * What does NOT qualify is the launcher-shim shape: a transcript-less live row
 * whose `machine` is this box only because it DEFAULTED there
 * (`activeSessionToSessionMeta`'s `active.machine ?? self`,
 * `computeLocalMetadataMatches`'s `machine || localMachine`). A dispatcher holds
 * exactly that row for a session whose agent and transcript live on a peer: the
 * launch process is here, the conversation is not. Treating it as definitive is
 * what dead-ended `agents sessions preview <full-uuid>` on the local
 * "Live session — full transcript not indexed here." stub while a passive peer —
 * which has no local row at all, so it fans out — rendered the real digest.
 * Process locality is not transcript locality.
 */
export declare function isLocallyDefinitiveMatch(session: SessionMeta, self: string): boolean;
/**
 * Drop the launcher-shim local rows that a peer has since answered for
 * (PHNX-3890). `fleetCandidatesByQuery` groups a logical session's copies per
 * machine and every consumer reads `hits[0]`, with local rows passed first — so
 * simply fanning out is not enough: the self-defaulted shim would still win the
 * attribution and route the read back to a box with no transcript. When the peer
 * that actually owns the session has answered the sweep, its row is the strictly
 * better one, and the shim carries no information the candidate loses (same
 * logical id, so the candidate — and any ambiguity it is part of — survives
 * through the remote hit).
 *
 * A row that is {@link isLocallyDefinitiveMatch} is never dropped, so a locally
 * readable transcript or a synced mirror still renders here with no SSH hop. When
 * no peer answered for that id, the shim is kept: with no fleet evidence, a
 * transcript-less self-attributed row is indistinguishable from a session THIS box
 * just started, and dropping it would re-break the cold-index lookup RUSH-2682
 * fixed.
 */
export declare function preferOwnerAttribution(localMatches: SessionMeta[], remoteSessions: SessionMeta[], self: string): SessionMeta[];
/** Injectable dependencies for the live-registry cold-miss fallback (RUSH-2682)
 * and the fleet-attribution reconciliation (PHNX-3890). */
export type LiveMetadataDeps = {
    loadActive?: typeof loadLocalActiveSessions;
    /** The fleet-active snapshot rows used to attribute a self-defaulted launcher
     * shim to its true execution host. Defaults to the cached fleet snapshot; the
     * cache is warmed by any fleet-wide `agents sessions --active`. */
    loadFleetActive?: () => ActiveSession[];
};
/**
 * Local metadata candidates for a selector: the indexed SQLite rows, plus — when
 * an id-shaped selector misses the index entirely — the live-session registry
 * (RUSH-2682).
 *
 * Indexing is lazy (it only runs inside `discoverSessions`), so a session THIS
 * box just started is "running" in `agents sessions --active` minutes before its
 * transcript reaches the local index. Every id resolver (`preview`, `resume`,
 * `focus`, and the fan-out peer that answers `--resolve-safe-v1`) therefore
 * consults the SAME live registry `--active` reads before declaring a running
 * session non-existent. It reshapes process state the registry already computed;
 * it parses no transcript and renders nothing. The live path is entered ONLY on a
 * cold id miss, so an indexed hit keeps its richer row and keyword resolution is
 * unchanged.
 */
export declare function computeLocalMetadataMatches(selector: string, scope: {
    agent?: string;
    project?: string;
    local?: boolean;
    hosts?: string[];
}, deps?: LiveMetadataDeps): Promise<SessionMeta[]>;
/**
 * Resolve an id-shaped selector against the local live-session registry
 * (RUSH-2682). Reads the daemon-warmed active snapshot first (cheap); if that
 * snapshot predates a just-started session and misses, it forces ONE fresh
 * gather before conceding, so a session started seconds ago resolves
 * synchronously. Never throws — a degraded registry read yields no candidates,
 * so the caller falls through to the ordinary not-found path.
 */
export declare function liveMetadataMatches(selector: string, scope: {
    agent?: string;
    project?: string;
}, self: string, deps?: LiveMetadataDeps): Promise<SessionMeta[]>;
/** Reusable local-first resolver for `agents run --resume` and `agents sessions resume`.
 * Full UUIDs hit the local SQLite index without any SSH fan-out. */
export declare function resolveSessionMetadataValue(selector: string, scope?: {
    agent?: string;
    project?: string;
    local?: boolean;
    hosts?: string[];
}, deps?: Pick<FleetResolveDeps, 'gatherRemoteList'> & LiveMetadataDeps): Promise<MetadataResolveOutcome>;
/** Resolve one selector to indexed metadata across the fleet without reading or
 * rendering transcript events. A peer answering the parent sweep returns every
 * local candidate; the parent performs the one logical-session uniqueness gate.
 * Backs `agents sessions --resolve <selector> --json`. Exported (deps already
 * injectable) so tests can drive this real call site against a fake fan-out
 * instead of only the pure resolver it wraps. */
export declare function resolveSessionMetadata(selector: string, scope: {
    agent?: string;
    project?: string;
    local?: boolean;
    hosts?: string[];
}, deps?: Pick<FleetResolveDeps, 'gatherRemoteList'> & LiveMetadataDeps): Promise<void>;
/**
 * Locate a full session id or short id prefix across the online fleet and render it from the machine
 * that holds it. The local disk already missed; this fans `sessions <id> --json
 * --all` out to every registered online peer (or the explicit `hosts` set),
 * groups the rows to distinct machines, then:
 *
 *   - exactly one logical session → delegate rendering to one peer via `runOnPeer`
 *     (its transcript and agent binary live there — a local `--device` hop would
 *     re-discover locally and dead-end), returning `'rendered'`.
 *   - more than one logical session → print every full-id candidate with its
 *     machine labels, returning `{ kind: 'conflict' }`.
 *   - none                 → `{ kind: 'not-found', deviceCount, unreachable }`,
 *     letting the caller print an honest sweep-aware message.
 *
 * A full-UUID query opts into early-exit: the first peer holding it resolves the
 * sweep and cancels the rest, so a fast peer is not bounded by the slowest one.
 * A short-id prefix stays all-settle — its ambiguity is only known once every
 * peer has answered, so it must wait to surface a conflict.
 *
 * No fuzzy/content fallback: the sweep forwards the id selector and every result
 * is resolved through `resolveSessionQuery`, the same id-only resolver used locally.
 */
export type FleetResolveResult = {
    kind: 'rendered';
} | {
    kind: 'conflict';
} | {
    kind: 'not-found';
    deviceCount: number;
    unreachable: string[];
};
export declare function resolveSessionAcrossFleet(query: string, mode: ViewMode, hosts?: string[], deps?: FleetResolveDeps): Promise<FleetResolveResult>;
/** Register the `agents sessions` command with all its options and help text. */
export declare function registerSessionsCommands(program: Command): void;
