import type { CliId } from '../adapters/cli/types.js';
export interface AdoptableSession {
    source: 'tmux' | 'herdr';
    tmuxTarget?: string;
    panePid?: number;
    cliPid?: number;
    herdrSessionName?: string;
    herdrTarget?: string;
    herdrPaneId?: string;
    herdrAgentName?: string;
    herdrTerminalId?: string;
    cliId: CliId;
    sessionId?: string;
    cwd: string;
    startedAt?: number;
    paneCols: number;
    paneRows: number;
}
export declare function cliIdForComm(comm: string, filterCliId?: CliId): CliId | undefined;
/** /proc/<pid>/cmdline → argv (Linux fast path; ps fallback for macOS). */
export declare function readCmdline(pid: number): string[];
/**
 * Resolve a CliId from a process's comm + argv. Checks comm first; if the comm
 * belongs to a generic launcher, scan argv for the CLI executable basename.
 */
export declare function cliIdFromCommArgv(comm: string | undefined, argv: string[], filterCliId?: CliId): CliId | undefined;
/**
 * 读取进程的 comm 名（不含路径）。Linux 走 /proc/<pid>/comm 快路径；
 * macOS / 其它 Unix 走 `ps -o comm=` 兜底。
 *
 * 注意 macOS 的 `ps -o comm=` 返回完整可执行路径（如 `/usr/local/bin/claude`），
 * 所以这里统一做一次 basename，让上层匹配 CLI_COMM_MAP 的逻辑保持不变。
 *
 * 返回 undefined 表示进程不存在或读不到。
 */
export declare function readComm(pid: number): string | undefined;
/**
 * 读取进程的工作目录。Linux 走 /proc/<pid>/cwd 软链；
 * macOS / 其它 Unix 走 `lsof -a -d cwd -p <pid> -Fn` 兜底。
 *
 * lsof -Fn 的输出格式：
 *   p<pid>
 *   fcwd
 *   n<path>
 * 这里只解析以 n 开头的那一行。
 *
 * 返回 undefined 表示读不到。
 */
export declare function readCwd(pid: number): string | undefined;
/**
 * 进程启动时间（epoch ms），best-effort。让 /adopt 选择卡片能为**任意** CLI 显示
 * 真实运行时长，而不是只有 Claude（Claude 另有 ~/.claude/sessions/<pid>.json 带
 * startedAt）。其它 CLI（cursor/codex/coco/gemini…）之前一律落 "未知" 就是因为
 * 这里没有兜底。
 *
 * Linux 走 /proc/<pid>/stat（字段 22 = starttime，单位时钟滴答，自开机起算）+
 * /proc/stat 的 btime；其它 Unix 走 `ps -o lstart=` 解析。读不到返回 undefined。
 */
export declare function readProcessStartTime(pid: number): number | undefined;
/**
 * 获取一个进程的直接子进程 PID。
 *
 * 既不能用 GNU `ps --ppid`（BSD ps 不支持长选项），也不能用 `pgrep -P`
 * （macOS BSD pgrep 把 `-P` 当过滤器，要求**必须**搭配一个 pattern 位置参数，
 * 不传 pattern 返回空）。
 *
 * 改成一次 `ps -A -o pid= -o ppid=` 把全表拿回来 JS 端过滤 —— 两个平台
 * 的 ps 都接受这个写法。fork-exec 一次代价可接受，因为 discovery 本身是
 * 低频操作（只在用户 /adopt 时跑一遍）。
 */
export declare function getChildPids(pid: number): number[];
/**
 * Resolve the REAL CLI pid spawned underneath a wrapperCli launcher
 * (e.g. `aiden x claude`, where the launcher forks real Claude Code as a child).
 *
 * The worker's `backend.getChildPid()` returns the LAUNCHER's pid, but it's the
 * forked child — not the launcher — that writes `~/.claude/sessions/<pid>.json`
 * and owns the transcript jsonl. Tracking the launcher pid therefore breaks
 * session-id discovery and leaves the JSONL bridge watching a path the real CLI
 * never writes. This walks the launcher's DESCENDANTS to find the actual CLI.
 *
 * Matching is by process `comm` ONLY — deliberately NOT argv. The launcher's own
 * argv carries the target name as a literal token (`aiden x claude` → "claude"
 * is in argv), so argv-scanning (cliIdFromCommArgv) would misidentify the
 * launcher itself as the CLI. The real CLI process has the binary as its comm
 * (`claude`, `codex`, …); the launcher's comm is its own (`node`/`aiden`).
 *
 * BFS starts at the launcher's children (never the launcher node) and returns
 * the shallowest descendant recognized as `targetCliId`, or null if none exists
 * yet — the launcher may not have forked the CLI at call time, so callers retry.
 */
export declare function findLaunchedCliPid(launcherPid: number, targetCliId: CliId, maxDepth?: number, probes?: {
    childrenOf?: (pid: number) => number[];
    commOf?: (pid: number) => string | undefined;
}): number | null;
/**
 * Guard for the async wrapperCli pid-resolver retry. The retry closure starts
 * for ONE spawn and captures that spawn's `backendAtSpawn` instance + the
 * launcher pid it observed. A worker restart (CLI crash → in-worker respawn)
 * during the ~6s retry window tears down the backend and forks a NEW launcher,
 * so a stale tick must NOT apply its result — doing so would overwrite the NEW
 * session's `backend.cliPid` / global `bridgeCliPid` with a pid resolved from
 * the OLD launcher tree, mis-pointing the new session's bridge + discovery.
 *
 * A tick may apply only when (a) the backend instance is still the very one the
 * retry was scheduled for (respawn replaces it: `backend = null` then a fresh
 * object), and (b) that backend's current child pid is still the captured
 * launcher pid (defends against a same-instance pane-child change / pid reuse).
 */
export declare function launcherRetryStillValid(currentBackend: unknown, backendAtSpawn: unknown, currentChildPid: number | null | undefined, launcherPid: number): boolean;
export interface WrapperRealPidResolveDeps {
    /** Find the real CLI descendant pid under the launcher (null until forked). */
    findRealPid: (launcherPid: number) => number | null;
    /** Current backend instance (identity-compared against the spawn snapshot). */
    getBackend: () => unknown;
    /** Backend's current child pid (the launcher while unchanged). */
    getChildPid: () => number | null | undefined;
    /** Apply the resolved real pid: rewire backend.cliPid + bridgeCliPid. */
    applyRealPid: (realPid: number) => void;
    /** Timer scheduler (injectable for tests). */
    schedule: (fn: () => void, ms: number) => void;
    intervalMs?: number;
    maxAttempts?: number;
}
/**
 * Drive the wrapperCli real-CLI-pid resolution as a bounded retry loop. Shared
 * by BOTH worker spawn paths — the synchronous one (tmux/pty, where
 * getChildPid() is the launcher immediately) and the late-pid fallback (zellij,
 * where getChildPid() is null at spawn and only resolves to the launcher
 * asynchronously). Either way the launcher forks the real CLI a beat later, so
 * we poll until findRealPid returns a descendant, then rewire.
 *
 * Every tick is gated by launcherRetryStillValid: a worker restart that swapped
 * the backend (or changed its child pid) aborts the loop so a stale resolution
 * can't clobber the new session's bridge/discovery.
 */
export declare function scheduleWrapperRealCliPid(launcherPid: number, deps: WrapperRealPidResolveDeps): void;
/**
 * Try to read Claude Code session metadata from ~/.claude/sessions/<PID>.json.
 * Returns { sessionId, cwd, startedAt } or undefined.
 */
export declare function readClaudeSessionMeta(pid: number): {
    sessionId?: string;
    cwd?: string;
    startedAt?: number;
} | undefined;
export declare function findUniqueClaudeSessionByCwd(cwd: string): {
    sessionId?: string;
    startedAt?: number;
} | undefined;
export declare function adoptTargetLabel(target: AdoptableSession | NonNullable<import('./types.js').DaemonSession['adoptedFrom']>): string;
export declare function adoptTargetKey(target: AdoptableSession): string;
/**
 * Scan all tmux panes for running CLI processes that can be adopted by Botmux.
 *
 * Skips `bmx-*` prefixed sessions (already managed by Botmux).
 * For each remaining pane, recursively searches the process tree (up to 3 levels)
 * for known CLI binaries.
 *
 * @param filterCliId - If provided, only return sessions matching this CLI type.
 */
export declare function discoverAdoptableSessions(filterCliId?: CliId): AdoptableSession[];
/**
 * Re-check that a specific pane still has the expected CLI process running.
 * Used to validate an adopt target right before the actual adoption.
 *
 * `filterCliId` MUST mirror the filter discovery used. A Cursor agent installed
 * under the generic name `agent` is only recognized as a CLI when filtered to
 * 'cursor' (see cliIdForComm); without the same filter here, discovery surfaces
 * the session but validation re-identifies nothing and wrongly reports it exited.
 */
export declare function validateTmuxAdoptTarget(tmuxTarget: string, expectedPid: number, filterCliId?: CliId): boolean;
export type AdoptValidationResult = 'alive' | 'missing' | 'unknown';
export declare function validateHerdrAdoptTarget(sessionName: string | undefined, paneId: string | undefined): AdoptValidationResult;
export declare function validateAdoptTarget(target: AdoptableSession | NonNullable<import('./types.js').DaemonSession['adoptedFrom']>): boolean;
export declare function validateAdoptTargetState(target: AdoptableSession | NonNullable<import('./types.js').DaemonSession['adoptedFrom']>): AdoptValidationResult;
export declare const __testOnly_readComm: typeof readComm;
export declare const __testOnly_readCwd: typeof readCwd;
export declare const __testOnly_getChildPids: typeof getChildPids;
//# sourceMappingURL=session-discovery.d.ts.map