/**
 * Daemon lifecycle management — spawn, verify, stop, coordinate OwlCoda background daemons.
 * Extracted from cli-core.ts for modularity.
 */
import type { OwlCodaConfig } from './config.js';
import { type HealthzResponse } from './healthz-client.js';
export interface RuntimeMeta {
    pid: number;
    runtimeToken: string;
    host: string;
    port: number;
    routerUrl: string;
    version: string;
    startedAt: string;
}
export interface StaleDaemonVersionInfo {
    daemonVersion: string;
    cliVersion: string;
    activeClientCount: number;
}
export interface EnsureProxyRunningResult {
    pid: number;
    reused: boolean;
    staleDaemon?: StaleDaemonVersionInfo;
}
export type DaemonVersionPolicyAction = 'reuse_current' | 'restart_idle_stale' | 'reuse_active_stale';
export interface DaemonVersionPolicyDecision {
    action: DaemonVersionPolicyAction;
    stale: boolean;
    daemonVersion?: string;
    cliVersion: string;
    activeClientCount: number;
}
export declare function ensureOwlcodaDir(): void;
export declare function writeRuntimeMeta(meta: RuntimeMeta): void;
export declare function readRuntimeMeta(): RuntimeMeta | null;
export declare function removeRuntimeMeta(): void;
export declare function isPidAlive(pid: number): boolean;
export declare function readPid(): number | null;
export declare function writePid(pid: number): void;
export declare function removePid(): void;
export declare function getMetaBaseUrl(meta: Pick<RuntimeMeta, 'host' | 'port'>): string;
export declare function verifyManagedDaemon(meta: RuntimeMeta): Promise<HealthzResponse | null>;
export declare function decideDaemonVersionPolicy(daemonVersion: string | undefined, activeClientCount: number, cliVersion?: string): DaemonVersionPolicyDecision;
export declare function safeSendSignal(pid: number, signal: NodeJS.Signals): boolean;
/** Build the port-in-use error. When `holderPid` is non-null the port is held
 *  by an OwlCoda daemon (identified via /healthz, an OwlCoda-only endpoint) —
 *  surface it as a stale daemon with a stop hint instead of mislabeling it a
 *  "non-OwlCoda process" (the Windows orphan-daemon symptom). */
export declare function buildPortInUseMessage(port: number, baseUrl: string, holderPid: number | null): string;
/** Injectable IO for forceStopOrphanDaemon (real impls by default; fakes in tests). */
export interface OrphanStopIO {
    fetchHealthz: (baseUrl: string, timeoutMs?: number) => Promise<{
        pid: number;
    } | null>;
    signal: (pid: number, sig: NodeJS.Signals) => boolean;
    waitGone: (baseUrl: string, timeoutMs?: number) => Promise<boolean>;
    isAlive: (pid: number) => boolean;
}
/** Stop an orphan OwlCoda daemon that has no PID file by recovering its PID
 *  from /healthz on `baseUrl`. Returns the stopped PID, or null when no live
 *  OwlCoda daemon answers there. SIGTERM first, SIGKILL fallback. This is what
 *  makes `owlcoda stop --force` work without relying on the PID file alone. */
export declare function forceStopOrphanDaemon(baseUrl: string, io?: Partial<OrphanStopIO>): Promise<number | null>;
export declare function stopAndWait(pid: number, baseUrl: string): Promise<void>;
export declare function buildDaemonArgs(configPath?: string, port?: number, routerUrl?: string): string[];
/**
 * True when this server was launched directly by launchd (the plist sets
 * OWLCODA_LAUNCHD=1) rather than spawned by spawnDaemon (which writes the
 * pid-file from the parent). Only the launchd case must self-register.
 */
export declare function shouldSelfRegisterDaemon(env?: NodeJS.ProcessEnv): boolean;
/**
 * Register this process's pid + runtime-meta so clients can discover a
 * launchd-managed daemon (there is no spawnDaemon parent to do it). The meta's
 * runtimeToken MUST equal the token the daemon serves via /healthz
 * (process.env.OWLCODA_RUNTIME_TOKEN, set by the plist) or
 * healthzMatchesRuntimeMeta rejects it. No-ops (returns false) when not
 * launchd-launched, or when the token is absent — so we never write a
 * mismatched meta that would get the daemon needlessly restarted.
 */
export declare function selfRegisterDaemon(config: OwlCodaConfig, pid?: number, env?: NodeJS.ProcessEnv): boolean;
/**
 * If the daemon log has grown past maxBytes, move it aside to `<path>.1`
 * (single-generation rotation) so the next open('a') starts fresh. No-op when
 * the log is missing or under the cap. Best-effort — never blocks daemon start.
 */
export declare function rotateDaemonLogIfOversized(logPath: string, maxBytes?: number): void;
/**
 * Resolve the stdio for spawning the daemon so its stdout/stderr land in
 * ~/.owlcoda/daemon.log — giving an uncaughtException/OOM crash a forensic
 * trail (today's stdio:'ignore' sends it to /dev/null). Returns the open fd so
 * the caller closes its copy after spawn (the detached child inherits its own).
 * Degrades to 'ignore' (today's behavior) if the log can't be opened — the
 * daemon must still start.
 */
export declare function openDaemonLogStdio(): {
    stdio: 'ignore' | ['ignore', number, number];
    logFd?: number;
};
export declare function spawnDaemon(config: OwlCodaConfig, configPath?: string, port?: number, routerUrl?: string): {
    pid: number;
    runtimeToken: string;
    exitPromise: Promise<{
        code: number | null;
        signal: NodeJS.Signals | null;
    }>;
};
export declare function getBaseUrl(config: {
    host: string;
    port: number;
}): string;
export interface LaunchdProxyDeps {
    fetchHealthz: (baseUrl: string) => Promise<HealthzResponse | null>;
    matchesConfig: (healthz: HealthzResponse, config: OwlCodaConfig) => boolean;
    readMeta: () => RuntimeMeta | null;
    activeClients: (meta: RuntimeMeta) => number;
    decide: (daemonVersion: string, activeClientCount: number) => DaemonVersionPolicyDecision;
    kickstart: () => void;
    waitReady: (baseUrl: string, matcher: (healthz: HealthzResponse) => boolean) => Promise<HealthzResponse | null>;
}
/**
 * Coordinate the daemon when launchd owns its lifecycle (service installed).
 * launchd's KeepAlive keeps the daemon up and W4a self-registration makes it
 * discoverable, so we must NOT spawnDaemon a second detached process racing the
 * KeepAlive respawn for the port. Reuse a healthy+current daemon; otherwise
 * kickstart via launchctl (which also picks up a rebuilt binary) and wait.
 * IO is injected so the decision flow is unit-testable without a live daemon.
 */
export declare function ensureLaunchdProxyRunning(config: OwlCodaConfig, baseUrl: string, log: (message: string) => void, deps?: LaunchdProxyDeps): Promise<EnsureProxyRunningResult>;
/**
 * Unified daemon coordination — used by both start and launch.
 * 1. Check existing daemon via PID + healthz identity
 * 2. If matching config + matching version → reuse
 * 3. If matching config but stale version → restart only when no live REPL clients are attached
 * 4. If config mismatch → safe stop + restart
 * 5. If nothing running → start fresh
 * 6. Wait for healthz ready before returning
 */
export declare function ensureProxyRunning(config: OwlCodaConfig, configPath?: string, port?: number, routerUrl?: string, options?: {
    quiet?: boolean;
}): Promise<EnsureProxyRunningResult>;
/**
 * 2026-05-28: version-drift hard-block.
 *
 * Previously this was a warn-and-continue path: the CLI would emit the
 * stale-daemon warning and proceed to reuse the mismatched daemon. In
 * practice that produced subtle inconsistencies — the CLI runs the
 * latest agent / classifier / wrapper code, but HTTP error
 * classification (e.g. detailLooksLikeRateLimit from a66383e) lives in
 * the daemon process, so a stale daemon silently disabled retry logic
 * even though the CLI thought it was active. Dogfood across the
 * 0.14.36 → 0.14.39 release window made this drift visible only in
 * post-mortem audit-log inspection.
 *
 * New behaviour: refuse to proceed. The user must either close the
 * active REPL clients (daemon auto-restarts on next start) or
 * `owlcoda stop --force && owlcoda` to align. An escape hatch
 * `OWLCODA_ALLOW_VERSION_DRIFT=1` is provided for emergency cases
 * (e.g. interactive long-running session a user explicitly wants to
 * keep going on a frozen daemon).
 */
export declare function applyActiveStaleDaemonPolicy(pid: number, baseUrl: string, decision: DaemonVersionPolicyDecision): void;
