import type { AgentId } from '../types.js';
import { compareVersions } from '../agent-spec/primitives.js';
export * from './store.js';
import { type PrunableKind } from '../staleness/prune.js';
/** Resource selection for syncing to a version: 'all', a name list, or undefined (skip). */
export interface ResourceSelection {
    commands?: string[] | 'all';
    skills?: string[] | 'all';
    hooks?: string[] | 'all';
    memory?: string[] | 'all';
    mcp?: string[] | 'all';
    permissions?: string[] | 'all';
    subagents?: string[] | 'all';
    plugins?: string[] | 'all';
    workflows?: string[] | 'all';
}
/** Resources available in ~/.agents/ for syncing. `promptcuts` is a boolean because it is a single, version-unscoped file. */
export interface AvailableResources {
    commands: string[];
    skills: string[];
    hooks: string[];
    memory: string[];
    mcp: string[];
    permissions: string[];
    subagents: string[];
    plugins: string[];
    workflows: string[];
    promptcuts: boolean;
}
/** Discover all resources available for syncing from ~/.agents/. */
export declare function getAvailableResources(cwd?: string): AvailableResources;
/** Return what's actually synced to a version home (source of truth, not agents.yaml tracking). */
export declare function getActuallySyncedResources(agent: AgentId, version: string, options?: {
    cwd?: string;
}): AvailableResources;
/** Resource names that only exist in the project's `.agents/` layer, grouped by kind. */
export interface ProjectOnlyResources {
    commands: Set<string>;
    skills: Set<string>;
    hooks: Set<string>;
    subagents: Set<string>;
    plugins: Set<string>;
    workflows: Set<string>;
}
/** Names that exist only in the project's `.agents/` layer. Sync skips project-layer resources for security, so filter them out of the "new resources" diff. */
export declare function getProjectOnlyResources(cwd?: string): ProjectOnlyResources;
/** Return resources in `available` that are not yet synced to the version home. `projectOnly` filters project-layer resources that sync skips for security. */
export declare function getNewResources(available: AvailableResources, actuallySynced: AvailableResources, projectOnly?: ProjectOnlyResources): AvailableResources;
/** Return true when `diff` contains any resources the agent/version actually supports. */
export declare function hasNewResources(diff: AvailableResources, agent?: AgentId, version?: string): boolean;
/** Prompt the user to select which new resources to sync. */
export declare function promptNewResourceSelection(agent: AgentId, newResources: AvailableResources, version?: string): Promise<ResourceSelection | null>;
/** Prompt the user to select which resources to sync from ~/.agents/. */
export declare function promptResourceSelection(agent: AgentId): Promise<ResourceSelection | null>;
/** Parsed agent@version specification from CLI input. */
export interface AgentSpec {
    agent: AgentId;
    version: string;
}
/** Parse an `agent@version` spec; bare agent means `latest`. */
export declare function parseAgentSpec(spec: string): AgentSpec | null;
export declare function getLatestNpmVersion(agent: AgentId): Promise<string | null>;
export declare function getOldestNpmVersion(agent: AgentId): Promise<string | null>;
/** Check whether the npm `latest` version is installed. */
export declare function isLatestInstalled(agent: AgentId): Promise<{
    installed: boolean;
    version: string | null;
}>;
/** Check whether the npm `oldest` version is installed. */
export declare function isOldestInstalled(agent: AgentId): Promise<{
    installed: boolean;
    version: string | null;
}>;
/**
 * List every version directory for an agent, including home-only leftovers, for
 * `agents prune cleanup` only. Do NOT use elsewhere — every other call site
 * assumes a working binary.
 */
export declare function listInstalledVersionDirs(agent: AgentId): Array<{
    version: string;
    hasBinary: boolean;
}>;
/** Set (or clear) the global default version for an agent. */
export declare function setGlobalDefault(agent: AgentId, version: string | undefined): void;
/** Set (or clear) the preferred isolated version without touching the launcher, shim, or global default. */
export declare function setIsolatedDefault(agent: AgentId, version: string | undefined): void;
/** Install a specific version of an agent. */
export declare function installVersion(agent: AgentId, version: string, onProgress?: (message: string) => void, opts?: {
    clean?: boolean;
}): Promise<{
    success: boolean;
    installedVersion: string;
    error?: string;
}>;
/**
 * Fold a stale literal `latest` version dir into the real resolved version.
 *
 * Script-installed agents (droid, grok) have no npm package to read a version
 * from, so the installer resolves the version by probing `<cli> --version`
 * after the install script runs. When that probe failed (3s timeout, or the
 * freshly-dropped binary not yet resolvable on PATH) the installer fell back to
 * the literal string `latest`, creating a `versions/<agent>/latest/` dir. A
 * later install where the probe succeeded then created a SECOND dir at the real
 * semver, orphaning `latest` — and because these agents' getBinaryPath points
 * at a single global binary regardless of version dir, `latest` keeps showing
 * up in `agents view` next to the real version forever.
 *
 * Call this once the install path has resolved a real version: if a stale
 * `latest` dir exists, rename it onto the real version (preserving `home/`), or
 * if the real dir already exists, soft-delete the `latest` dir to trash. No-op
 * when nothing was resolved or no stale dir is present, so it is safe to call
 * on every script-based install. Returns the action taken (for tests/logging).
 */
/**
 * Proactively fold a stale `latest` version-home into its concrete version,
 * WITHOUT needing a fresh install (RUSH-1320). `reconcileStaleLatestDir` only
 * fires at install time, so a `latest` dir left by an old probe-failed install
 * lingers in `agents view` indefinitely. This resolves the live CLI version and
 * reconciles — cheap no-op when there's no `latest` dir (the common case, so
 * `agents view` pays a `--version` shell-out only the once, until it's folded).
 * Skipped when the active config symlink still points at `latest`, since
 * renaming that dir would dangle the live symlink.
 */
export declare function reconcileStaleLatestForAgent(agent: AgentId): Promise<void>;
export declare function reconcileStaleLatestDir(agent: AgentId, installedVersion: string): Promise<'none' | 'renamed' | 'trashed'>;
/**
 * Soft-delete a version directory by moving it to ~/.agents/.system/trash/versions/.
 * Returns the trash path on success or null on failure / no source.
 *
 * Trash layout: ~/.agents/.system/trash/versions/<agent>/<version>/<timestamp>/
 * The timestamp suffix lets a user soft-delete the same version twice (after
 * re-install) without collision and gives a chronological audit trail.
 *
 * The whole versionDir moves — including `home/` (transcripts, sessions). The
 * user can recover everything via `agents restore <agent>@<version>`.
 * Nothing is ever hard-deleted.
 */
export declare function softDeleteVersionDir(agent: AgentId, version: string): string | null;
/**
 * Remove a specific version of an agent.
 *
 * Soft-delete only: moves the entire version directory (including `home/`)
 * to ~/.agents/.system/trash/versions/. Recoverable via `agents restore`.
 * Nothing is hard-deleted.
 */
export declare function removeVersion(agent: AgentId, version: string): boolean;
/**
 * Print the standard footer after one or more versions were soft-deleted to
 * trash. Reminds the user that sessions stay readable and how to restore.
 */
export declare function printTrashFooter(moved: Array<{
    agent: AgentId;
    version: string;
}>): void;
/**
 * Remove all versions of an agent. Preserves each version's `home/` directory
 * so conversation history is never deleted; the per-version folders (now
 * containing only `home/`) remain under the agent dir.
 */
export declare function removeAllVersions(agent: AgentId): number;
export interface HealedVersionPointers {
    /** Global default reassigned off a not-installed version (`to: null` = cleared). */
    globalDefault?: {
        from: string;
        to: string | null;
    };
    /** Isolated default (bare `agents run <agent>`) reassigned off a not-installed version. */
    isolatedDefault?: {
        from: string;
        to: string | null;
    };
    /** `~/.<agent>` config symlink repointed off a not-installed version. */
    configSymlink?: {
        from: string;
        to: string;
    };
}
/**
 * Repoint an agent's version pointers — the global/isolated default and the
 * `~/.<agent>` config symlink — off any version that is no longer installed, so
 * neither `agents sync`/`agents run` resolution nor the conventional-path home
 * can land on a dead version (RUSH-2471).
 *
 * How a pointer goes dangling: {@link removeVersion} reassigns the defaults and
 * clears the config symlink only when IT removes the pointed-at version. A
 * version-home whose launch binary disappears by any other route — grok
 * self-updated its per-version binary out from under the old dir, a manual
 * delete, a half-finished install that seeded the home but never landed the
 * binary — leaves the default and/or the symlink pointing at something
 * {@link isVersionInstalled} reports as gone.
 *
 * Called from the sync resolve path so the invariant self-heals. Mirrors
 * {@link removeVersion}'s reassignment: the global default prefers the newest
 * NON-isolated survivor else clears; the isolated default prefers the newest
 * remaining isolated copy else clears. The symlink is repointed at the now-healed
 * resolved default, else the newest installed non-isolated version. A symlink
 * already on an installed version, a real (non-symlink) config dir, and an
 * isolated-only agent are left untouched.
 */
export declare function healDanglingVersionPointers(agent: AgentId, cwd: string): Promise<HealedVersionPointers>;
/** Normalize a user-supplied `@version` token. `default`/`pinned`/`any` → undefined; `latest`/`oldest` → extreme installed version; concrete versions must be installed. */
export declare function resolveVersionAlias(agent: AgentId, raw: string | undefined | null): string | undefined;
/**
 * Loose variant of resolveVersionAlias for record-filter contexts (sessions,
 * team history). Same `default`/`pinned`/`latest`/`oldest` semantics, but explicit
 * versions pass through unchanged so historical records of uninstalled versions
 * remain queryable.
 */
export declare function resolveVersionAliasLoose(agent: AgentId, raw: string | undefined | null): string | undefined;
export { compareVersions };
/**
 * Get actual version from an installed 'latest' directory.
 */
export declare function getInstalledVersion(agent: AgentId, version: string): Promise<string | null>;
/**
 * True when a probe's combined output/error looks like the runnable binary (or a
 * native sub-binary it execs) is MISSING — the "gutted install" signature. This
 * is the exact class of failure behind the ENOENT crash: an npm package whose JS
 * wrapper landed at node_modules/.bin/<cli> while its native platform binary
 * (shipped via an optional per-arch dependency, e.g. @openai/codex-<platform>)
 * did not — so the wrapper spawns and immediately dies with `spawn … ENOENT`.
 *
 * Deliberately narrow: only the missing-file signature counts. An agent that
 * merely dislikes `--version` (nonzero exit, ordinary error text) or ignores it
 * (times out) must NOT match, so a healthy install is never falsely condemned.
 *
 * The trailing phrases catch a gutted install that reports its own breakage
 * *politely* rather than with a raw ENOENT: @anthropic-ai/claude-code's stub
 * (run when its postinstall never copied the native binary in) prints "native
 * binary not installed / postinstall did not run / … optional dependency was not
 * downloaded" and exits nonzero — which the generic patterns above miss. Anchored
 * tightly so a healthy `--version` can never emit them.
 */
export declare function isMissingBinarySignature(output: string): boolean;
/**
 * Verify a freshly-installed agent can actually LAUNCH — not merely that its JS
 * wrapper exists. getBinaryPath()/isVersionInstalled() only check the wrapper, so
 * a gutted install (wrapper present, native binary missing) reads as healthy,
 * gets pinned as the default, and gets picked to run — then dies with ENOENT the
 * instant it spawns (which, wrapped in tmux, showed up as a silent `[detached]`).
 *
 * We probe `<binary> --version` under the version's isolated HOME so config
 * resolution matches a real launch. Because the ENOENT originates in the child
 * (the wrapper spawns fine, then fails to exec the absent native binary), we
 * inspect the child's OUTPUT, not just whether our spawn succeeded. Only the
 * missing-binary signature (see isMissingBinarySignature) fails the check; a
 * plain nonzero exit or a timeout is treated as healthy so we never false-fail.
 */
/**
 * Compose the spawn spec for a `<binary> --version` launch probe. On Windows the
 * `.cmd` wrapper runs through cmd.exe, so the path is fully quoted into ONE
 * command line and the args array is emptied (composeWin32CommandLine) — the
 * DEP0190-safe pattern the real launch uses. Critically this keeps a spaced
 * Windows profile path (`C:\Users\John Doe\…\claude.cmd`) intact; passing the raw
 * path to a shell would split it at the space and false-fail a HEALTHY install.
 * On POSIX no shell is involved and the binary is exec'd directly. Pure/exported
 * so the quoting is unit-testable without spawning.
 */
export declare function probeSpawnSpec(binary: string, isWin: boolean): {
    command: string;
    args: string[];
    shell: boolean;
};
export declare function verifyInstalledBinaryLaunches(agent: AgentId, version: string): Promise<{
    ok: boolean;
    detail?: string;
}>;
/**
 * The launch probe itself, addressed by PATH rather than by installed version.
 *
 * `verifyInstalledBinaryLaunches` is this function applied to a version dir that
 * is already live. `agents update` needs the identical check applied to a release
 * that is still STAGED — probing it before the swap is what makes the update
 * transactional, since a staged release that cannot launch is discarded instead
 * of replacing a working one. Both callers must agree byte-for-byte on what
 * "launches" means, hence one implementation.
 *
 * `posixBinary` is the extensionless launch target. The real launch target
 * differs by platform, so we probe whatever `agents run` actually execs: on
 * Windows that is the npm `.cmd` wrapper beside it (exec.ts uses
 * `absPath + '.cmd'`), which chains to the native `.exe`; a gutted install
 * (renamed/missing `.exe`) makes that wrapper emit "is not recognized" — the
 * exact win-mini failure a vendor auto-update leaves behind. Probing the
 * extensionless `.bin/<cli>` instead would ENOENT even on a HEALTHY Windows
 * install, so we DON'T. On POSIX the `.bin/<cli>` binary is the launch target
 * and is probed directly.
 */
export declare function verifyBinaryLaunches(posixBinary: string, home: string): Promise<{
    ok: boolean;
    detail?: string;
}>;
/**
 * Launch-path self-heal. Given the concrete version `agents run` is about to
 * spawn, make sure it will actually run — and if not, repair it instead of
 * letting the agent die with a raw `ENOENT` deep inside its own wrapper.
 *
 * Steps, cheapest first:
 *   1. Probe the version (verifyInstalledBinaryLaunches). Healthy → return it.
 *   2. Broken → **clean** reinstall in place (wipes the partial node_modules so
 *      npm actually re-fetches the platform binary). Re-probe; good → return it.
 *   3. Still broken → fall back to another INSTALLED version that launches,
 *      re-pinning it as the default so the shim path heals too. Return it.
 *   4. Nothing runnable installed → install `latest`, pin it, return it.
 *   5. Give up → return null (caller surfaces a clear error).
 *
 * Isolation boundary (both directions — steps 3/4 are the only mutating ones):
 *   - An ISOLATED target gets step 2 and nothing else. Falling back would let
 *     `agents run <agent>@<isolated>` silently repoint the user's NORMAL default,
 *     and installing `latest` would materialize a version they never asked for.
 *     A failed repair returns null so the caller says so plainly.
 *   - An isolated version is never a fallback CANDIDATE either, whatever the
 *     target is: promoting one to the global default is exactly what `agents use`
 *     refuses and what removeVersion's promotion filter excludes, so it must not
 *     happen here by the back door (the daemon's launch-health pass calls this
 *     unattended every ~6h).
 *
 * Gated to npm-package agents: their native binary ships as an optional per-arch
 * dependency whose tarball can extract partially (interrupted/raced install) —
 * the exact failure this repairs. Agents with a global/native binary (grok,
 * droid) have no such tarball and are returned unchanged.
 */
export declare function ensureAgentRunnable(agent: AgentId, version: string, log?: (message: string) => void, opts?: {
    allowDefaultSwitch?: boolean;
}): Promise<string | null>;
export declare function healBrokenDefaultLaunches(log?: (m: string) => void, opts?: {
    allowDefaultSwitch?: boolean;
}): Promise<{
    repaired: string[];
    unhealed: string[];
}>;
/** Outcome of syncing resources to a version home, keyed by resource type. */
export interface SyncResult {
    commands: boolean;
    skills: boolean;
    hooks: boolean;
    memory: string[];
    permissions: boolean;
    mcp: string[];
    subagents: string[];
    plugins: string[];
    workflows: string[];
    /**
     * Project files the sync left alone because the workspace already has them
     * (repo-relative). Reported once, grouped, by the command that rendered the
     * sync — never one line per file from down here.
     */
    projectSkipped: string[];
    /**
     * Resources removed from the version home because they were deleted from
     * source since the last sync (RUSH-2438), per kind. Populated only on a
     * repo-scope reconcile that ran the prune pass; empty otherwise.
     */
    pruned: Record<PrunableKind, string[]>;
    /**
     * Resources this sync refused to write, as user-facing sentences (RUSH-2677).
     *
     * An empty `mcp`/`subagents`/… list means "nothing to write"; it cannot also
     * mean "declined and here is why", which is how a harness with no config
     * writer reported a clean sync while writing nothing. The rendering command
     * MUST surface these — a decline nobody prints is the silent no-op again.
     */
    declined: string[];
}
/**
 * Enumerate the DotAgent repo names that resources can be scoped to:
 * the fixed `project` / `user` / `system` layers plus every enabled extra
 * repo alias. Used to validate `agents sync <agent> --repo <name>`.
 */
export declare function listRepoNames(): string[];
/**
 * Build a ResourceSelection from source-pattern expansion and an optional
 * per-kind name filter. Both dimensions are optional and compose:
 *
 *   patterns only   → resources from those source layers (all matching names)
 *   kindFilter only → only those specific kinds / names, no source restriction
 *   both combined   → patterns scope the source layer; kindFilter further
 *                     limits which names within each kind are included
 *
 * Typical callers:
 *   `--repo system`   → buildSelection(['system:*'])
 *   `--plugin`        → buildSelection([], { plugins: 'all' })
 *   `--plugin fleet`  → buildSelection([], { plugins: ['fleet'] })
 *   no flags          → buildSelection([]) → full selection
 *
 * Memory (`kindFilter.memory`):
 *   - Always `'all'` when patterns are active (repo scope must recompile the
 *     composed file from all layers, RUSH-1354).
 *   - `'all'` when `kindFilter.memory` is set (--rule / --memory flag).
 *   - Absent (skipped) when kindFilter is given but does not include memory,
 *     so `--plugin fleet` does NOT trigger a memory recompile.
 *   - `'all'` when no kindFilter is given (full sync includes everything).
 */
export declare function buildSelection(patterns: string[], kindFilter?: ResourceSelection, cwd?: string): ResourceSelection;
/**
 * Build a ResourceSelection scoped to a single DotAgent repo (`system`,
 * `user`, `project`, or an extra-repo alias). Delegates to `buildSelection`
 * so the source-map and pattern-expansion logic stays in one place.
 *
 * `memory` is always `'all'`: the composed rules-memory file spans all layers
 * and must be recompiled in the same pass (RUSH-1354).
 */
export declare function buildRepoScopedSelection(repo: string, cwd?: string): ResourceSelection;
/**
 * Union several ResourceSelections into one, deduping names per kind. Pure — no
 * FS — so the merge logic is unit-tested without a DotAgents layout. `memory` is
 * a whole-file merge of the user+system layers, not a per-repo artifact: pass
 * `includeMemory` to request a full memory write (`'all'`), else the skip
 * sentinel (`[]`) is kept.
 */
export declare function unionResourceSelections(selections: ResourceSelection[], includeMemory: boolean): ResourceSelection;
/**
 * Build one selection spanning several DotAgent repos — the source set an
 * interactive `agents sync` picker collects. Each repo is scoped via
 * `buildRepoScopedSelection`, then unioned. Memory is written whole when the
 * `user` or `system` layer is among the selected repos (that's where the memory
 * file's content comes from); a project-only / alias-only pick leaves it
 * untouched.
 */
export declare function mergeRepoScopedSelections(repos: string[], cwd?: string): ResourceSelection;
/**
 * Sync central resources (~/.agents/) into a specific version's config directory.
 * Copies selected resources from central storage into {versionHome}/.{agent}/.
 *
 * @param agent - The agent ID
 * @param version - The version string
 * @param selection - Optional resource selection. If not provided, syncs all resources.
 *
 * For Gemini: commands are converted from markdown to TOML.
 */
/**
 * Resolve a caller's hook selection against the available hook set, matching a
 * selection entry by exact name OR by its extensionless basename, and returning
 * the AVAILABLE (extensioned) name in every case.
 *
 * `available.hooks` carries the source filename WITH its extension
 * (`git-guard.sh`) — the shape the hooks writer's source resolver requires — but
 * the doctor/heal resource diff identifies a hook by its extensionless basename
 * (`git-guard`). A heal pass feeds those diff names straight back as the
 * selection, so a plain exact-set filter matched NOTHING and `agents doctor
 * --fix` could never reconcile a flagged hook (PHNX-3187). Basename tolerance
 * closes that gap without changing the extensioned names the writer needs.
 *
 * Order-stable and de-duplicated; a selection entry that matches nothing is
 * dropped (mirrors resolveSelection).
 */
export declare function resolveHookSelection(sel: string[] | 'all' | undefined, available: string[]): string[];
export declare function syncResourcesToVersion(agent: AgentId, version: string, selection?: ResourceSelection, options?: {
    projectDir?: string;
    cwd?: string;
    force?: boolean;
    available?: AvailableResources;
    prune?: boolean;
    allowExecSurfaces?: boolean;
}): SyncResult;
/** Result of resolving agent/version targets from CLI input or interactive selection. */
export interface VersionSelectionResult {
    selectedAgents: AgentId[];
    versionSelections: Map<AgentId, string[]>;
}
/** Extended target result that distinguishes managed versions from direct (unmanaged) agent homes. */
export interface InstalledAgentTargetResult {
    selectedAgents: AgentId[];
    directAgents: AgentId[];
    versionSelections: Map<AgentId, string[]>;
}
/** Thrown when an `agent@version` target is not installed; carries the parsed ids so callers can react without parsing the message. */
export declare class VersionNotInstalledError extends Error {
    readonly agentId: AgentId;
    readonly version: string;
    readonly installedVersions: readonly string[];
    constructor(agentId: AgentId, version: string, installedVersions: readonly string[]);
}
/** Resolve a comma-separated `--agents` list into concrete installed version selections. */
export declare function resolveAgentVersionTargets(value: string, availableAgents: readonly AgentId[], options?: {
    allVersions?: boolean;
}): VersionSelectionResult;
/** Resolve a comma-separated `--agents` list into install/apply targets, distinguishing managed versions from direct homes. */
export declare function resolveInstalledAgentTargets(value: string, availableAgents: readonly AgentId[], options?: {
    allVersions?: boolean;
}): InstalledAgentTargetResult;
/** Resolve configured manifest targets into direct homes and managed versions. */
export declare function resolveConfiguredAgentTargets(agents: readonly AgentId[] | undefined, agentVersions: Partial<Record<AgentId, string[]>> | undefined, availableAgents: readonly AgentId[], options?: {
    allVersions?: boolean;
}): InstalledAgentTargetResult;
/** Prompt the user to select agents and versions for resource installation. */
export declare function promptAgentVersionSelection(availableAgents: AgentId[], options?: {
    skipPrompts?: boolean;
}): Promise<VersionSelectionResult>;
