import { type ClaudeInvoker } from '../invoker.js'; import { type BlockedWatchOptions } from './blocked-watch.js'; import { type TerminalInput } from './terminal-input.js'; import type { SessionOutcome } from './hot-swap.js'; export interface PtySessionOptions { claude: ClaudeInvoker; args: string[]; /** CLAUDE_CONFIG_DIR for the session (kept constant across swaps so the resume works). */ configDir: string; /** Extra env for this launch (e.g. CLAUDE_CODE_OAUTH_TOKEN for the active account). */ env?: Record; /** If set, write the session's raw output here for debugging cap detection. */ debugLog?: string; /** * Polled periodically; return an account name when the operator has picked a * different account mid-session, and the child is ended so the swap loop * relaunches, resuming this conversation on it. Return null to keep running. */ switchWatch?: () => string | null; /** * Run on every poll, before anything can short-circuit it. For work that must * keep happening for as long as the session is alive, whatever else is going * on: saying the account is still in use, and copying a refreshed login back. */ onTick?: () => void; /** * Called when cap-looking text renders, with that text; resolves true ONLY if * the account is actually limited (verified against the API). Rendered text * alone is untrustworthy: resuming a conversation REPLAYS history, * including old cap messages, and code on screen can mention rate limits. * When absent, a text match is trusted as-is (legacy behavior). */ verifyCap?: (renderedText: string) => Promise; /** * Do not watch for usage limits during this run. Used for the deliberate * "run anyway" case, where the limit is already known and the operator needs * the session to start so they can switch models. */ ignoreLimits?: boolean; /** * The run's terminal input. Sessions borrow the operator's keyboard from this * owner rather than taking the terminal into raw mode themselves, so a swap * never toggles global terminal state mid-teardown. */ input?: TerminalInput; /** * Given a CONFIRMED account limit while the child is still alive, try to move * the session to a healthy account IN PLACE (swap the credential under the live * process) instead of ending it. Returns 'relieved' when it did (the child * keeps running on the new account, and its next request goes there), or * 'restart' when no in-place move is possible and the session should end so the * swap loop can relaunch on the next account, resuming the conversation. * * This is what stops an account cap from clobbering a running session: real * Claude stays on screen after a usage limit, so the account underneath it can * be swapped and the very next message succeeds on the new one. When absent * (or when it returns 'restart'), the historical end-and-relaunch path runs. * * `relieve` says whether it may move the account in place; `switching` tells it * a manual switch is already taking over. It records the cap to the ledger * itself ONLY when there will be no `capped` outcome to do so (a completed * in-place relief, or a preempting switch), which it decides from what actually * happened; otherwise the caller confirms the cap and the swap loop records it, * so the ledger is written exactly once. */ onCapConfirmed?: (hit: { reason?: string; resetAt?: number; }, opts: { relieve: boolean; switching: boolean; }) => 'relieved' | 'restart'; /** * Thresholds for deciding the session is blocked. Injected in tests so the * pattern can be reached in seconds instead of minutes; production uses the * defaults in blocked-watch. */ blockedWatch?: BlockedWatchOptions; /** * How long a REFUTED match backs off before another probe. Injected in tests * so the case where a wall recurs AFTER the backoff has expired can be * reached in seconds; production uses 20s. */ refuteBackoffMs?: number; } /** * Run a claude session inside a pseudo-terminal, relaying it transparently to * the operator's terminal (so the TUI still sees a real terminal) while watching * the output stream for the rate-limit signal. Resolves 'capped' (and ends the * child) when the cap appears, or 'ok' on a normal exit. */ export declare function runPtySession(options: PtySessionOptions): Promise;