/**
 * Hook management -- discovery, registration, and syncing of event hooks.
 *
 * Hooks are shell scripts in ~/.agents/hooks/ that fire on agent events
 * (tool calls, session start, etc.). Each hook directory contains a manifest
 * (agents.yaml) declaring events, matchers, and timeout. This module handles
 * parsing those manifests, registering hooks into agent-native settings files,
 * and syncing them across version switches.
 */
export declare function resolveHookScriptPath(script: string): string | null;
/**
 * Convert an absolute path under HOME to a portable ~/... form with forward
 * slashes. Hook commands stored this way work on both macOS and Windows:
 * absolute Windows paths break in bash because backslashes are stripped as
 * escape characters, whereas ~/... paths expand correctly via the ~/.claude
 * symlink/junction on both platforms.
 *
 * `home` and `sep` are injectable so the Windows behavior (backslash sep,
 * drive-letter home) is unit-testable on a POSIX CI host — pass sep='\\' to
 * simulate Windows. With the defaults this is byte-identical to reading
 * os.homedir()/path.sep at the call site.
 */
export declare function toPortableCommand(absPath: string, home?: string, sep?: string): string;
/**
 * Collapse version-scoped registrations by logical resource identity rather
 * than absolute command path. When the same resource is present in several
 * homes, the active home's command wins even if it appears later. Commands
 * outside version homes are user-owned and remain untouched.
 */
export declare function deduplicateVersionHookCommands(commands: string[], activeVersionHome: string): string[];
import type { AgentId, HookCacheConfig, HookMatches, InstalledHook, ManifestHook } from '../types.js';
export type HookEntry = {
    name: string;
    scriptPath: string;
    dataFile?: string;
};
export interface VersionHookCopy {
    agent: AgentId;
    version: string;
    name: string;
    path: string;
    hash: string;
    active: boolean;
}
export interface DuplicateVersionHook {
    agent: AgentId;
    name: string;
    kind: 'duplicate' | 'drift';
    authoritative: VersionHookCopy;
    copies: VersionHookCopy[];
}
/**
 * Inspect every installed hooks-capable harness without assuming a native
 * settings format. Same-name copies with one content hash are installation
 * noise; multiple hashes are drift. The active version is always selected as
 * the authoritative copy when it participates in the group.
 */
export declare function inspectDuplicateVersionHooks(cwd?: string): DuplicateVersionHook[];
/**
 * List hook entries in a single directory, grouping script + data files by
 * basename. Also discovers scripts in one-level group subdirs
 * (hooks/session-starts/). Exported so doctor-diff can reuse the same grouping
 * the sync path applies; without this, doctor would double-count `foo.sh` and
 * `foo.yaml`. On basename collision, the top-level file wins over a nested one.
 */
export declare function listHookEntriesFromDir(dir: string): HookEntry[];
/**
 * Check if a hook exists for an agent.
 */
export declare function hookExists(agentId: AgentId, hookName: string): boolean;
/**
 * Check if installed hook content matches source hook content.
 * Compares both script file and data file (if present).
 */
export declare function hookContentMatches(agentId: AgentId, hookName: string, sourceEntry: HookEntry): boolean;
/**
 * Hooks dir for an agent under an arbitrary home (version home or effective
 * home). An agent whose `hooksDir` is configured absolute under `$HOME` (grok,
 * kimi) must be translated to config-dir-relative first — a raw `path.join`
 * would embed the absolute path as a relative segment and produce a hybrid
 * path that never exists (RUSH-2237).
 */
export declare function getHooksDirInHome(agentId: AgentId, home: string): string;
export declare function listInstalledHooksWithScope(agentId: AgentId, cwd?: string, options?: {
    home?: string;
}): InstalledHook[];
export declare function installHooks(source: string, agents: AgentId[], options?: {
    scope?: 'user' | 'project';
}): Promise<{
    installed: string[];
    errors: string[];
}>;
/**
 * Path to the hooks dir of a specific version home (not the active one).
 */
export declare function getVersionHooksDir(agent: AgentId, version: string): string;
/**
 * List hook entries in a specific version home.
 */
export declare function listHooksInVersionHome(agent: AgentId, version: string): HookEntry[];
export interface HookWiringIssue {
    /** Hook name (script basename minus extension). */
    name: string;
    /** Native lifecycle event the hook should be wired to (Stop, PreToolUse, …). */
    event: string;
    /** Matcher group the hook belongs to under `event` (`''` = the catch-all
     *  group). Real hooks scope by matcher — ask-user-question-guard=AskUserQuestion,
     *  user-message-guard=Bash — so wiring is verified per (event, matcher). */
    matcher: string;
    /** The command the harness-native config should reference under `event`. */
    command: string;
}
/** A generated, agents-managed hook wrapper and the inputs needed to recreate it. */
export interface ManagedHookRuntimeArtifact {
    agent: AgentId;
    version: string;
    name: string;
    scriptPath: string;
    shimPath: string;
    cache: HookCacheConfig | null;
    matches?: HookMatches;
}
/** A generated hook wrapper that is referenced by managed configuration but cannot run. */
export interface BrokenManagedHookRuntimeArtifact extends ManagedHookRuntimeArtifact {
    /** Stable, human-readable filesystem failure; the hook is never executed to find it. */
    reason: string;
}
/** Public, doctor-safe shape for a broken generated hook wrapper. */
export interface HookRuntimeIssue {
    name: string;
    path: string;
    reason: string;
}
export interface HookWiringReport {
    /** Whether this agent's hook config format is understood by the inspector. */
    supported: boolean;
    /** Absolute path of the settings file inspected (when supported). */
    settingsPath?: string;
    /** Number of hooks the manifest says should be wired for this version. */
    expected?: number;
    /** Native hook config does not exist — nothing declared can be wired. */
    settingsMissing?: boolean;
    /** Native hook config cannot be parsed — wiring can't be verified. */
    settingsUnparseable?: boolean;
    /** Hooks whose file is present/resolvable but that are NOT referenced in the
     *  native event group/entry should carry them in. */
    unwired: HookWiringIssue[];
    /** Expected hooks that ARE referenced in native config (expected − unwired).
     *  Empty whenever wiring cannot be verified (unsupported family, missing or
     *  unparseable settings). */
    wired: HookWiringIssue[];
    /** Generated agents-managed wrappers that are absent or unusable. This is
     * independent of whether the native config format itself is understood. */
    runtimeBroken: HookRuntimeIssue[];
}
/** Inspect managed generated hook wrappers without executing user hook code. */
export declare function inspectBrokenManagedHookRuntimeArtifacts(filter?: {
    agent?: AgentId;
    version?: string;
}, platform?: NodeJS.Platform): BrokenManagedHookRuntimeArtifact[];
/** Outcome of one generation attempt for a unique shim path. */
export interface HookRuntimeRepairAttempt {
    name: string;
    path: string;
    reasonBefore: string;
    /** True when generateHookShim ran for this path in this pass. */
    attempted: boolean;
    repaired: boolean;
    /** Stable failure text when not repaired after an attempt (or dry-run skip). */
    reason?: string;
}
/**
 * Result of one bounded repair pass over agents-managed generated hook shims.
 * Shared by self-heal and doctor --fix; additive types for the doctor track.
 */
export interface HookRuntimeRepairReport {
    /** Broken artifacts found before any write (inspect-only snapshot). */
    brokenBefore: BrokenManagedHookRuntimeArtifact[];
    /** Unique shim paths considered for generation in this pass. */
    attemptedPaths: string[];
    attempts: HookRuntimeRepairAttempt[];
    /** Human-readable lines for CheckResult.fixed (includes dry-run would-fix). */
    fixed: string[];
    /** Human-readable lines for CheckResult.needsAttention — stable wording. */
    needsAttention: string[];
}
export interface RepairManagedHookRuntimeOptions {
    /** Detect only — never write. Default false. */
    dryRun?: boolean;
    filter?: {
        agent?: AgentId;
        version?: string;
    };
    platform?: NodeJS.Platform;
}
/**
 * Regenerate one known-broken wrapper and prove the result is usable. Callers
 * provide a snapshot from inspectBrokenManagedHookRuntimeArtifacts; this never
 * loops or retries and returns a stable error for the current pass.
 *
 * Generation is delegated to generateHookShim (idempotent — preserves mtime when
 * content already matches). This path never calls registerHooksToSettings,
 * installHooks, or any sync routine.
 */
export declare function repairManagedHookRuntimeArtifact(artifact: ManagedHookRuntimeArtifact, platform?: NodeJS.Platform): {
    repaired: boolean;
    reason?: string;
};
/**
 * Bounded repair of all broken agents-managed generated hook shims.
 *
 * - Inspect first (read-only, no hook execution).
 * - One generation attempt per unique shim path per call (no retry, no timer).
 * - Canonical owner per shared path: global default, else newest non-isolated.
 * - Post-repair reinspection; unresolved findings become stable needsAttention.
 * - Never recurses into resource sync / registerHooksToSettings.
 *
 * This is the shared routine used by the self-heal `hook-runtime` check and
 * exported for the doctor track.
 */
export declare function repairManagedHookRuntimeArtifacts(opts?: RepairManagedHookRuntimeOptions): HookRuntimeRepairReport;
/**
 * Verify that every hook the manifest says should be wired is actually
 * referenced in that version's harness-native config, not merely present as a
 * file on disk.
 *
 * `agents doctor` compares hook FILES against source (see diffHooks in
 * doctor-diff.ts) but never checks the wiring, so a hook whose script is
 * byte-identical to source yet missing from settings.json's PreToolUse/Stop/…
 * array reads as "ok" while it never fires. This closes that blind spot.
 *
 * Read-only by construction: it mirrors registerHooksForClaude's command
 * resolution WITHOUT the shim-generation / chmod side effects that
 * resolveHookCommand performs, so it never mutates the version home.
 */
export declare function checkVersionHookWiring(agent: AgentId, version: string): HookWiringReport;
export interface VersionHookDiff {
    agent: AgentId;
    version: string;
    toAdd: string[];
    toUpdate: string[];
    matched: string[];
    orphans: string[];
}
/**
 * Compare a version home's hooks against central. Returns the reconciliation diff.
 */
export declare function diffVersionHooks(agent: AgentId, version: string): VersionHookDiff;
/**
 * Install a single hook from central into a specific version home.
 */
export declare function installHookToVersion(agent: AgentId, version: string, hookName: string): {
    success: boolean;
    error?: string;
};
/**
 * Remove a single hook (script + data file) from a specific version home.
 * Soft-deletes to ~/.agents/.trash/hooks/.
 */
export declare function removeHookFromVersion(agent: AgentId, version: string, hookName: string): {
    success: boolean;
    error?: string;
};
/**
 * Iterate all (agent, version) pairs that support hooks and are installed,
 * optionally scoped to a single agent/version.
 */
export declare function iterHooksCapableVersions(filter?: {
    agent?: AgentId;
    version?: string;
}): Array<{
    agent: AgentId;
    version: string;
}>;
export declare function removeHook(name: string, agents: AgentId[]): Promise<{
    removed: string[];
    errors: string[];
}>;
/**
 * Get detailed info about a hook from central storage.
 */
export declare function getHookInfo(name: string): {
    name: string;
    path: string;
    content: string;
} | null;
export declare function discoverHooksFromRepo(repoPath: string): string[];
/**
 * Get the source hook entry from repo.
 */
export declare function getSourceHookEntry(repoPath: string, hookName: string): HookEntry | null;
/**
 * Install hooks to central ~/.agents/hooks/ directory.
 * Shims will symlink this to per-agent directories for synced agents.
 */
export declare function installHooksCentrally(source: string): Promise<{
    installed: string[];
    errors: string[];
}>;
/**
 * List hooks from user (~/.agents/hooks/) and system (~/.agents/.system/hooks/) dirs.
 * User dir takes priority; deduplication preserves first occurrence.
 */
export declare function listCentralHooks(): HookEntry[];
/**
 * Normalize a hook `timeout` from agents.yaml into a whole number of seconds.
 *
 * A bare number stays seconds (`timeout: 30` → 30) for backward compatibility.
 * A Go-style duration string is parsed into seconds: `5s`, `2m`, `1h30m`,
 * `90s`, `1h`. Suffixed durations longer than 24 hours are rejected as likely
 * typos, while bare seconds stay uncapped for backward compatibility. This
 * intentionally does NOT reuse {@link parseTimeout} from routines.ts — that one
 * returns milliseconds, has no seconds (`s`) unit, and floors at one minute,
 * none of which fit hook timeouts (typically 5–600s).
 *
 * Returns the seconds value, or `null` when the input is not a positive number,
 * is not parseable, or is a suffixed duration longer than 24 hours — the caller
 * decides how to surface that.
 */
export declare function normalizeHookTimeoutSeconds(value: unknown): number | null;
/**
 * Parse hook manifests. Reads system hooks from ~/.agents/.system/hooks.yaml
 * (npm-shipped defaults) and user hooks from the `hooks:` section of
 * ~/.agents/agents.yaml. Merges with user-wins-on-key-collision precedence.
 * A user entry with `enabled: false` disables the system-shipped hook of
 * the same name without forking the system file.
 *
 * Hooks marked `enabled: false` are dropped from the returned map. A hook
 * `timeout` written as a duration string (`5s`, `2m`) is normalized to a
 * seconds number here, so every downstream serializer keeps reading a number.
 */
export declare function parseHookManifest(opts?: {
    warn?: boolean;
}): Record<string, ManifestHook>;
export declare function selectHookManifest(manifest: Record<string, ManifestHook>, selected: string[]): Record<string, ManifestHook>;
/**
 * Hook files present in a version home but absent from every configured
 * SOURCE — genuine orphans left behind by a removed/renamed source hook.
 *
 * The definition is deliberately source-based, not manifest-based (PHNX-2693).
 * Sync copies EVERY source hook file into a version home — registered hooks AND
 * the helper / test / benchmark scripts that sit alongside them — but only the
 * registered ones appear in `parseHookManifest`. Diffing installed names against
 * the manifest therefore flagged every source-present-but-unregistered file
 * (e.g. `permission-handler`, `verify-work-state`, `*_test`, `benchmark_*`) as
 * an orphan, so `agents prune cleanup` offered to trash ~1000 in-use files. An
 * installed hook is only truly orphaned when NO configured source still carries
 * a file of that name — which is exactly what the `prune`/`doctor` help already
 * promised ("present in a version home but missing from every configured
 * source").
 *
 * Pure on purpose (no disk reads) so it is trivially testable; callers pass the
 * installed hook names and the source hook script paths.
 */
export declare function unmanagedHookNames(installedHookNames: string[], sourceHookScripts: string[]): string[];
/**
 * Every hook script path across the resolved SOURCE roots — user
 * (`~/.agents/hooks`), system (`~/.agents/.system/hooks`), and each enabled
 * extra repo's `hooks/` — grouped the same way sync materializes them
 * ({@link listHookEntriesFromDir}, which also descends one-level event-group
 * dirs). This is the set an installed hook must be absent from to count as an
 * orphan.
 */
export declare function listResolvedSourceHookScripts(): string[];
/**
 * The orphan hooks (see {@link unmanagedHookNames}) sitting in one version home:
 * installed hook files whose name matches no file in any configured source root.
 */
export declare function listUnmanagedHooksInVersionHome(agent: AgentId, version: string): string[];
/**
 * Compute the trust hash Codex expects for a single command hook handler, so
 * agents-cli can pre-trust the hooks it registers. Without a matching
 * trusted_hash in [hooks.state], Codex classifies the hook Untrusted and
 * silently drops it in non-interactive (`codex exec`) mode where there is no
 * TUI prompt to approve it.
 *
 * Mirrors command_hook_hash() in codex-rs/hooks/src/engine/discovery.rs +
 * version_for_toml() in codex-rs/config/src/fingerprint.rs:
 *   sha256( canonicalJson( NormalizedHookIdentity ) ) prefixed with "sha256:".
 *
 * The identity passes through TOML on the Codex side, which drops None fields
 * (commandWindows, statusMessage, and matcher when absent). `async` is always
 * false (async hooks are not yet supported) and is always present. `timeout`
 * is normalized to >= 1 (Codex: unwrap_or(600).max(1)).
 */
export declare function computeCodexHookTrustHash(eventKeyLabel: string, command: string, timeout: number, matcher: string | undefined): string;
/**
 * Options that narrow `registerHooksToSettings`'s process-global side effects.
 *
 * `skipGlobalShimSweep` — do NOT run {@link sweepOrphanShims}. The sweep deletes
 * every `.sh` in the ONE process-global shim dir (`getHookShimsDir()`,
 * `~/.agents/.cache/shims/hooks/`) whose name is absent from the manifest it is
 * handed. That is correct for a normal install/sync (the manifest is the
 * operator's complete hook set), but catastrophic when the materializer registers
 * a portable PACKAGE's hooks into an isolated output home: that manifest carries
 * only the package's hooks, so the sweep would wipe the operator's unrelated
 * global shims. The materializer sets this so materialization stays isolated
 * (PHNX-3838); every normal caller leaves it unset and keeps the sweep.
 */
export interface RegisterHooksOptions {
    skipGlobalShimSweep?: boolean;
}
export declare function registerHooksToSettings(agentId: AgentId, versionHome: string, hookManifest?: Record<string, ManifestHook>, agentsDirOverride?: string, options?: RegisterHooksOptions): {
    registered: string[];
    errors: string[];
};
/**
 * The concrete config file(s) `registerHooksToSettings` writes for `agentId` in
 * `home` — the settings/registrar leaves, NOT the per-hook script copies. A
 * caller materializing into an UNTRUSTED home (the portable-agent materializer)
 * uses this to verify none of those leaves is a preplanted symlink before the
 * registrar follows it and overwrites a file outside the home.
 *
 * This MUST mirror the `registerHooksToSettings` dispatch above — each arm's
 * write target is reproduced here, so a new registrar branch adds its leaf here
 * too. It intentionally returns only the primary settings/registrar file(s); the
 * per-event/script files a registrar also writes live under the hooks dir the
 * caller already guards. An agent with no hooks registrar returns `[]`.
 */
export declare function hookRegistrationTargets(agentId: AgentId, home: string): string[];
/**
 * Prune every Claude-family (`settings.json`) hook entry whose command lives
 * under a removed version's home
 * (`~/.agents/.history/versions/<agent>/<removedVersion>/home/…`).
 *
 * `agents remove <agent>@<version>` soft-deletes the version's files but leaves
 * the hook entries other version homes registered against it — dead hooks that
 * error on every tool call ("No such file or directory") until the next sync.
 * This clears them from a remaining version's settings immediately. Only the
 * removed version's entries are touched; the current version's entries, system
 * hooks, and the user's own custom hooks are left intact. Returns the number of
 * entries removed.
 */
export declare function pruneVersionHomeHookEntriesFromSettings(settingsPath: string, agent: AgentId, removedVersion: string): number;
export interface InstallSessionTrackerHookResult {
    installed: boolean;
    error?: string;
}
/**
 * Install the shared SessionStart state-writer hook (`packages/session-tracker`)
 * into the harness's native config. Gated by `supports(agent, 'hooks', version)`;
 * unsupported agents return `installed: false` with a reason instead of throwing.
 *
 * This is the bridge that makes bare `claude` / `codex` / `kimi` launches write
 * `~/.agents/.cache/terminals/sessions/<pid>.json`, not just `agents run` launches.
 */
export declare function installSessionTrackerHook(agent: AgentId, version?: string, home?: string): Promise<InstallSessionTrackerHookResult>;
/**
 * Synchronous variant for callers that cannot be made async (e.g.
 * `syncResourcesToVersion`). Best-effort: failures are returned, not thrown.
 */
export declare function installSessionTrackerHookSync(agent: AgentId, version?: string, home?: string): InstallSessionTrackerHookResult;
