type LogFn = (msg: string) => void; export interface CodexRpcEngineOpts { /** Absolute path to the codex-family CLI binary (codex / traex / …). */ cliBin: string; /** Working directory / agent root for the session. */ cwd: string; /** Child env (must carry CODEX_HOME + proxy vars + BOTMUX_SESSION_ID). */ env: NodeJS.ProcessEnv; /** botmux session id — used to name the app-server orphan-cleanup marker so a * new incarnation of this session can reap a prior app-server (P0 teardown). */ sessionId?: string; log?: LogFn; /** Optional model + reasoning effort forwarded to thread config (P1). */ model?: string; reasoningEffort?: string; /** Feature gates owned by the app-server process (the viewer TUI does not * execute model tools in RPC mode). */ appServerFeatures?: string[]; /** Bridge a native request_user_input server request to the host UI. */ onRequestUserInput?: (params: unknown) => Promise; /** Override the per-request JSON-RPC timeout (default REQUEST_TIMEOUT_MS). * Mainly for tests that assert the wedged-app-server recovery path. */ requestTimeoutMs?: number; /** Called once if the app-server dies unexpectedly (not via stop()). The * worker uses it to kill the now-orphaned `codex --remote` pane so the normal * exit→daemon-refork→resume path re-engages RPC on a fresh app-server (P1). */ onDead?: () => void; /** Authoritative native turn terminal. `turn/start` returns a native Codex * turn id which this engine binds to the exact Botmux delivery attempt before * resolving the ack. The worker uses that identity to release only the * matching rpcActive bridge entry. */ onTurnTerminal?: (terminal: CodexRpcTurnTerminal) => void; } export interface CodexRpcTurnIdentity { turnId: string; dispatchAttempt?: number; } export type CodexRpcTurnTerminalStatus = 'completed' | 'failed' | 'aborted' | 'engine-dead' | 'stopped'; export interface CodexRpcTurnTerminal { identity: CodexRpcTurnIdentity; nativeTurnId: string; status: CodexRpcTurnTerminalStatus; errorCode?: string; } export declare class CodexRpcEngine { private readonly opts; private child?; private ws?; private nextId; private pending; private readonly turnOwners; private readonly nativeTurnByOwner; private readonly terminalNativeTurns; private readonly deferredUnownedTerminals; private port; private threadId?; private closed; private deadNotified; private lastStderr; private readonly log; constructor(opts: CodexRpcEngineOpts); get wsUrl(): string; get activeThreadId(): string | undefined; get appServerPid(): number | undefined; private ownerKey; private takeNativeTurnId; private bindNativeTurn; private emitTurnTerminal; private emitAllTurnTerminals; /** Spawn the app-server, connect, and complete the initialize handshake. */ start(): Promise; /** Create a fresh session thread. Its id (== codex rollout session id) is what * the TUI resumes and what botmux persists for future resume. */ startThread(): Promise; /** Resume the persisted thread after a botmux reconnect (P0 resume-survival), * so RPC mode stays engaged across daemon restarts instead of reverting to * the paste path. */ resumeThread(threadId: string): Promise; private threadParams; /** Inject one user message as a turn. Resolves when the app-server acks the * turn start (fast); the turn itself streams to the attached TUI. * `clientUserMessageId` (a stable botmux turn id) is forwarded so codex can * CORRELATE the message — NOT relied on for dedupe (the 0.144.1 schema carries * it but promises no idempotency). Correctness comes from the caller never * auto-resending an accepted turn (P1-1). * opts.fatalOnTimeout=false makes a timeout reject only THIS request instead of * tearing the engine down — used for the fresh first turn, whose ambiguity is * then resolved against rollout persistence (see sendFirstTurn). */ sendTurn(content: string, identity: CodexRpcTurnIdentity, opts?: { timeoutMs?: number; fatalOnTimeout?: boolean; }): Promise<{ nativeTurnId: string; }>; /** 首条用户消息落盘后设置线程名;失败不得拖垮仍在执行的模型 turn。 */ setThreadName(name: string): Promise; /** 等待 Codex 的首条消息预览落盘;超时后由调用方继续设置标题。 */ waitForThreadPreview(timeoutMs?: number): Promise; /** 等待 resume 后首次 append 的元数据补丁落库;超时后由调用方继续做最终覆盖。 */ waitForThreadUpdatedAfter(baseline: number, timeoutMs?: number): Promise; readThreadMetadata(timeoutMs?: number): Promise<{ name?: string; preview?: string; updatedAt?: number; }>; /** Deliver the FRESH first turn and resolve its outcome as one of THREE states, * prioritising exactly-once over never-lost (P1-1). An empty thread can't be * resumed by the TUI, so the first turn must persist the rollout before the * pane spawns — but a lost/late ack must NOT be blindly re-pasted (that would * double-execute, the failure users care about most): * - 'accepted' — ack received, OR (ack lost) the rollout already contains * THIS turn's user message → engaged, never resend. * - 'not-sent' — the turn/start FRAME was never dispatched (ws not open / * send threw) → the turn cannot have run → safe paste once. * - 'ambiguous' — the frame WAS dispatched but no ack AND no positive rollout * evidence (timeout / transport / server / unknown error) → * it may have executed → NEVER auto-paste; the caller notifies * the user and lets the viewer resume (recovers if it landed). * Only "frame not dispatched" is treated as safe; every dispatched-then-failed * case is ambiguous, and a timeout is non-fatal so the engine survives to serve * the accepted/ambiguous cases. `rolloutProbe` is the ground-truth positive * check (matches this turn's user_message in the persisted rollout). */ sendFirstTurn(content: string, identity: CodexRpcTurnIdentity, rolloutProbe: (threadId: string) => Promise): Promise<{ outcome: 'accepted' | 'not-sent' | 'ambiguous'; nativeTurnId?: string; }>; stop(): void; private markerPath; /** Verify a pid is actually OUR app-server before signalling it — the marker * can outlive a SIGKILLed worker and its pid may be REUSED by an unrelated * process (daemon runs as root → mis-kill would be severe). Match the process * argv against `app-server` AND, when recorded, the exact `--listen ` a * reused pid could not carry (P1-2). */ private processIsOurAppServer; /** Kill an app-server left behind by a prior incarnation of this session * (e.g. the worker was SIGKILLed so its exit hooks never ran). Identity-checked * so a reused pid is never mis-killed. */ private reapStaleAppServer; private writeMarker; /** Remove the marker ONLY if it still names THIS engine's app-server (pid + * wsUrl). Prevents an ABA race: a same-session engine B may have already * reaped + rewritten the marker with its own pid/url by the time this (old) * engine's child exits late — an unconditional delete would orphan B's live * app-server (no marker → next incarnation can't reap it). P1-2. */ private removeMarkerIfOwned; private waitReady; private connect; private request; private notify; private respond; /** Fail a native user-input request by INTERRUPTING its turn instead of * replying. Verified on real traex 0.200.19: replying with either empty * answers or a JSON-RPC error is normalized to `{answers:{}}` and the turn * still completes (the ask is silently skipped). `turn/interrupt` is the only * path that actually stops the turn (status → 'interrupted'); the pending * server request is cancelled along with it, so we do NOT also respond. */ private interruptTurnFor; /** Reply to a server→client request with a JSON-RPC error. Used only as a * last resort when a failed requestUserInput has no turn to interrupt. */ private respondError; private send; private onMessage; private failAll; } export {}; //# sourceMappingURL=codex-rpc-engine.d.ts.map