import { CliExecResult } from "../types.js";
import { type Config } from "../config.js";
/**
 * The `app-server` codex real-time-wake rail.
 *
 * codex >=0.145 has a SINGLE-WRITER thread-store: a codex agent open in an
 * interactive TUI holds an exclusive writer on its thread, so the `exec-resume`
 * rail (`codex exec resume <same id>`) fast-fails `-32600 already has an active
 * writer`. Here we inject the peer's message as a TURN through codex's
 * app-server daemon instead — the daemon is the single writer; the human's
 * `codex --remote` TUI and this bridge client are both just RPC clients, so
 * there is no conflict (proven end-to-end in scratchpad/appserver-ws-proof.mjs).
 *
 * Transport (all verified against codex 0.147): a WebSocket on the daemon's
 * unix socket at path `/rpc`, permessage-deflate DISABLED and `Host: localhost`
 * (the real client sends no extensions; otherwise the upgrade hangs up). No auth
 * token for the LOCAL unix socket. JSON-RPC messages are one WS text frame each
 * (no LSP/newline framing).
 */
/** Minimal surface of a `ws` WebSocket we use — injectable for tests. */
export interface WsLike {
    on(event: string, cb: (...args: any[]) => void): void;
    send(data: string): void;
    close(): void;
}
export type WsFactory = (url: string, opts: Record<string, unknown>) => WsLike;
/** Control socket for the codex app-server daemon under a given codex home. */
export declare function appServerSocketPath(codexHome?: string): string;
/**
 * Is an app-server daemon ALREADY listening on this codex home's control socket?
 *
 * 🔴 WHY ANCESTRY WAS NOT ENOUGH. {@link runningUnderAppServerDaemon} answers "was I spawned
 * under a daemon?" — but the daemon is a SHARED, MACHINE-WIDE SINGLETON (see ensureDaemon), so a
 * process that is not its descendant can still use it. A codex agent whose bridge runs outside a
 * `cogent-codex`-launched codex — a standalone monitor script, a systemd unit, an MCP server under
 * a plain codex — always answered "no", fell back to `exec-resume`, and `codex exec resume
 * <thread>` then took the EXCLUSIVE WRITER on that thread. On 2026-09-02 that locked the operator
 * out of their own live session (`already has an active writer (code -32600)`) while a perfectly
 * good daemon was running on the same machine.
 *
 * Deliberately narrow: this reports only whether a daemon is ALREADY up. It never starts one, so a
 * machine without a daemon keeps its previous behaviour exactly. A stale socket file costs one
 * failed attempt and falls through to `exec-resume`, which is the pre-existing path anyway.
 *
 * `isSocket()` rather than `existsSync` on purpose: a leftover REGULAR FILE at the socket path
 * must not be mistaken for a live daemon.
 */
export declare function appServerDaemonRunning(codexHome?: string): boolean;
/** The `ws+unix://<sock>:/rpc` URL the daemon speaks JSON-RPC over. */
export declare function rpcUrl(sock: string): string;
/**
 * When codex runs `--remote` (attached to the daemon, e.g. launched via
 * cogent-codex), codex's app-server DAEMON — not the codex TUI — is the process
 * that spawns the plugin's MCP server. So the wake decision cannot rely on an env
 * flag reaching the bridge (the daemon doesn't carry it). Instead we detect the
 * situation structurally: the bridge is a descendant of `codex … app-server`
 * exactly when codex is `--remote`. Then the app-server rail is the RIGHT one and
 * we use it automatically — no `COGENT_CODEX_WAKE` needed anywhere.
 *
 * Pure + unit-testable: walk an ancestry via the injected `getProc`, return true
 * if any ancestor's command line is the codex app-server. `codex-cli` (Homebrew/npm)
 * and other names never match; only `codex … app-server`.
 */
export declare function ancestryHasAppServer(startPid: number, getProc: (pid: number) => {
    cmd: string;
    ppid: number;
} | null, maxDepth?: number): boolean;
/**
 * Memoized: is THIS bridge process running under a `codex … app-server` daemon
 * (i.e. codex is `--remote`)? Cached for the process lifetime — ancestry is fixed.
 */
export declare function runningUnderAppServerDaemon(): boolean;
export declare function resetAppServerAncestryCache(): void;
export interface EnsureDaemonDeps {
    /** Run a command; injectable for tests. Returns collected stdout/stderr/code. */
    run?: (cmd: string, args: string[], env: NodeJS.ProcessEnv) => Promise<{
        stdout: string;
        stderr: string;
        code: number | null;
    }>;
    /** Does the control socket exist yet? Injectable for tests. */
    socketExists?: (p: string) => Promise<boolean>;
    /** Total budget to wait for the socket to appear (ms). */
    waitMs?: number;
    /** Poll interval while waiting (ms). */
    pollMs?: number;
}
export declare function resetDaemonCache(): void;
/**
 * Ensure the codex app-server daemon is running for `codexHome` and return its
 * control socket. Idempotent (`codex app-server daemon start` is a no-op when the
 * daemon is already up). Throws if the socket never appears — the caller
 * (`execRemote`) catches this and falls back to the `exec-resume` rail.
 */
export declare function ensureDaemon(codexHome?: string, deps?: EnsureDaemonDeps): Promise<string>;
interface JsonRpcMessage {
    jsonrpc?: string;
    id?: number;
    method?: string;
    params?: any;
    result?: any;
    error?: {
        code?: number;
        message?: string;
    };
}
/**
 * Extract the agent's reply text from the collected turn notifications. Prefers
 * the final `turn/completed` items; falls back to streamed `item/completed`
 * agent messages when the completed turn carries none. Tolerant of the exact
 * shape (experimental API) — the live test pins the real wire format.
 */
export declare function extractAgentMessageText(notes: JsonRpcMessage[]): string;
/**
 * A thin JSON-RPC-over-WebSocket client for the codex app-server daemon.
 * `connect()` resolves once the socket is open + `initialize` succeeds.
 */
export declare class AppServerClient {
    private ws;
    private nextId;
    private pending;
    private notifyListeners;
    private opened;
    private openResolved;
    constructor(ws: WsLike);
    /** Await the open handshake. Rejects (before open) on connect/upgrade errors. */
    waitOpen(): Promise<void>;
    private _failAll;
    private _onMessage;
    /** Issue a JSON-RPC request and resolve with its result (rejects on rpc error). */
    request(method: string, params?: unknown): Promise<any>;
    /** Subscribe to server notifications; returns an unsubscribe fn. */
    onNotification(cb: (msg: JsonRpcMessage) => void): () => void;
    close(): void;
}
export interface AppServerDeps {
    /** Injectable WebSocket factory (tests supply a fake). */
    wsFactory?: WsFactory;
    /** Override the daemon socket path (default derives from codex home). */
    socketPath?: string;
    /**
     * Stable id of the inbound message (relay MessageRecord.id). When set, a
     * duplicate injection of the SAME message into the SAME thread is coalesced
     * (still in-flight) or short-circuited (recently completed) so the daemon
     * never runs the peer's turn twice — the async-daemon-turn duplicate risk
     * (the daemon keeps running a turn after our client times out/disconnects).
     * Also sent as turn/start `clientUserMessageId` for daemon-side correlation.
     */
    idempotencyKey?: string;
}
/**
 * codex 0.147 SandboxPolicy (shape from `codex app-server generate-ts`).
 * `workspaceWrite` carries the exclude flags; `readOnly` carries networkAccess.
 */
type SandboxPolicy = {
    type: "dangerFullAccess";
} | {
    type: "readOnly";
    networkAccess: boolean;
} | {
    type: "workspaceWrite";
    writableRoots: string[];
    networkAccess: boolean;
    excludeTmpdirEnvVar: boolean;
    excludeSlashTmp: boolean;
};
/**
 * Map COGENT_CODEX_SANDBOX to an explicit turn/start `sandboxPolicy`, so a PEER's
 * injected turn is bounded by the CONFIGURED sandbox — not by whatever privilege
 * the human happened to launch codex with (a live thread may be
 * danger-full-access). Keeps the app-server rail's sandbox consistent with the
 * exec-resume rail. Default (workspace-write) allows edits under the cwd, no net.
 */
export declare function sandboxPolicyFor(mode: Config["COGENT_CODEX_SANDBOX"], cwd: string): SandboxPolicy;
/** Clear the injection idempotency cache (test isolation). */
export declare function resetAppServerIdempotency(): void;
/**
 * Inject `message` as a turn into the codex thread `threadId` via the app-server
 * daemon, and return the agent's reply as a {@link CliExecResult} (parallel to
 * `execCodex`). When `deps.idempotencyKey` is set, a duplicate injection of the
 * same message into the same thread is coalesced/short-circuited (never a second
 * daemon turn). Closing our client afterward only detaches THIS client — the
 * daemon + thread (and the human's TUI) persist.
 */
export declare function execCodexViaAppServer(threadId: string, message: string, cwd: string, timeoutMs?: number, deps?: AppServerDeps): Promise<CliExecResult>;
export {};
//# sourceMappingURL=codex-app-server.d.ts.map