/**
 * LLM stream-inactivity watchdog.
 *
 * Lives in its own module (mirroring `session-status.ts` and
 * `splice-inflight-message.ts`) so unit tests can exercise the behaviour
 * without dragging in the full `SessionManager` dependency graph (flexsearch,
 * sandbox manager, mcp, …).
 *
 * Problem (recap): the upstream model provider's streaming HTTP response can
 * go silent (TLS half-close, NAT keep-alive death, provider hiccup). The
 * agent's `await reader.read()` then never resolves and never rejects, leaving
 * the session wedged in `status: "streaming"` forever.
 *
 * Solution: a frame-inactivity timer per session. We arm the watchdog only
 * while the agent is waiting on the LLM (not while a tool is executing). On
 * stall we abort the turn and silently re-prompt up to `maxRetries` times;
 * after that we surface a stalled-stream error to the user.
 *
 * Design: docs/design — "LLM Stream Watchdog" (goal-llm-stream-305c45a1).
 */
export interface WatchdogConfig {
    /** Inactivity threshold in ms; <= 0 disables the watchdog. */
    timeoutMs: number;
    /** Number of silent retries before surfacing the stall to the user. */
    maxRetries: number;
}
/**
 * Resolve the watchdog config from env vars. Re-read on every call so tests
 * (and operators) can flip the threshold without a restart — the cost is a
 * couple of `Number()` calls per agent event, negligible vs. the agent loop.
 *
 * `BOBBIT_LLM_STREAM_TIMEOUT_MS` defaults to 30000; `0` disables the watchdog.
 * `BOBBIT_LLM_STREAM_MAX_RETRIES` defaults to 2.
 */
export declare function resolveWatchdogConfigFromEnv(): WatchdogConfig;
/** Subset of `SessionInfo` the watchdog touches. */
export interface WatchdogSession {
    id: string;
    rpcClient: {
        abort(): Promise<any>;
        prompt(text: string, images?: any): Promise<any>;
    };
    streamWatchdogTimer?: ReturnType<typeof setInterval>;
    lastLlmFrameAt?: number;
    awaitingLlmFrame?: boolean;
    streamStallRetries?: number;
    suppressNextDrainForStallRetry?: boolean;
    /** One-shot: tells the SessionManager `message_end` handler to skip its
     *  normal lastTurnErrored/consecutiveErrorTurns bookkeeping for the next
     *  assistant `message_end`. Set by the watchdog before initiating an
     *  abort, since the real agent emits a `message_end{stopReason:"error",
     *  errorMessage:"Request aborted"}` after every abort.
     *   - Silent retry: prevents the abort's error frame from advancing
     *     `consecutiveErrorTurns` (silent retries MUST NOT bump the counter).
     *   - Surfaced stall: the watchdog has already set state to its preferred
     *     values (and emitted a synthetic stalled-stream `message_end`); the
     *     abort's "Request aborted" frame must not clobber them. */
    suppressNextErrorMessageEnd?: boolean;
    /** One-shot: drop the next user-role `message_end` from the WS broadcast.
     *  Set by the watchdog before each silent-retry `rpcClient.prompt(...)` so
     *  the agent SDK's user-echo of the re-issued prompt does NOT render as a
     *  duplicate user message in the chat transcript. Consumed exactly once by
     *  the SessionManager's `handleAgentLifecycle` user-echo path. */
    suppressNextUserEcho?: boolean;
    /** One-shot: drop the next assistant `message_end{stopReason:"error"}`
     *  frame from the WS broadcast (the abort's "Request aborted" frame).
     *  Independent from `suppressNextErrorMessageEnd` (which only suppresses
     *  internal bookkeeping). Both flags are set together for silent retries
     *  AND for the surfaced-stall path (where the synthetic stalled-stream
     *  frame is emitted manually and the abort's real error frame would
     *  otherwise duplicate "Request aborted" on screen). Consumed exactly
     *  once by `handleAgentLifecycle`. */
    suppressNextAbortMessageEnd?: boolean;
    consecutiveErrorTurns?: number;
    lastTurnErrored?: boolean;
    lastTurnErrorMessage?: string;
    lastPromptText?: string;
    lastPromptImages?: any;
    /** Broadcast a synthetic agent event to the session's WS clients. Wired
     *  by `SessionManager.handleAgentLifecycle` (delegates to `emitSessionEvent`).
     *  Used for the surfaced-stall `message_end` so the UI's transcript renders
     *  the stalled-stream error text — the UI reads `errorMessage` from this
     *  frame, which is why we can't rely on `lastTurnErrorMessage` alone. */
    emitSyntheticEvent?: (event: any) => void;
}
/** Callback that returns true if the session is still alive (in the manager's map). */
export type IsAliveFn = (sessionId: string) => boolean;
/**
 * Per-event arming/disarming. Call from `handleAgentLifecycle` BEFORE any
 * other handling so an event mid-tear-down can't sneak past while the timer
 * is already firing.
 */
export declare function onAgentEvent(session: WatchdogSession, event: any, cfg: WatchdogConfig, isAlive: IsAliveFn): void;
/**
 * Stall handler. Aborts the in-flight turn, then either silently re-prompts
 * (`attempt <= maxRetries`) or surfaces a stalled-stream error to the user
 * (final attempt). Silent retries do NOT advance `consecutiveErrorTurns`;
 * only the surfaced failure does, by exactly 1.
 *
 * Exported for unit tests. Production callers reach this via the `setInterval`
 * tick set up in `onAgentEvent`.
 */
export declare function handleStreamStall(session: WatchdogSession, cfg: WatchdogConfig, isAlive: IsAliveFn): Promise<void>;
/**
 * Helper for the `agent_end` branch in `SessionManager.handleAgentLifecycle`.
 * Decides whether the silent-retry suppression should short-circuit the
 * idle-broadcast/drain. A concurrent user Stop must always win — when the
 * session has already transitioned to `aborting`, we clear the flag and let
 * the standard `wasAborting` cleanup proceed (which broadcasts idle).
 *
 * Returns `true` iff the caller should short-circuit. The flag is consumed
 * (cleared) on every call regardless.
 */
export declare function shouldSuppressDrainForStallRetry(session: WatchdogSession, isAborting: boolean): boolean;
/**
 * Helper for the `message_end` branch in `SessionManager.handleAgentLifecycle`.
 * Returns `true` iff the next assistant `message_end` should skip the standard
 * lastTurnErrored / consecutiveErrorTurns bookkeeping. Consumed exactly once.
 */
export declare function shouldSkipErrorMessageEnd(session: WatchdogSession): boolean;
/**
 * Helper for the WS-broadcast gate in `SessionManager.handleAgentLifecycle`.
 * Returns `true` iff the next user-role `message_end` should be dropped from
 * the broadcast (silent-retry user echo). Consumed exactly once.
 *
 * The flag is one-shot regardless of body match: `lastPromptText` is set
 * right before each silent-retry `prompt()` call, so by construction the
 * very next user `message_end` IS the re-issued prompt's echo.
 */
export declare function shouldSuppressUserEchoBroadcast(session: WatchdogSession): boolean;
/**
 * Helper for the WS-broadcast gate in `SessionManager.handleAgentLifecycle`.
 * Returns `true` iff the next assistant error `message_end` should be
 * dropped from the broadcast (the abort's "Request aborted" frame).
 * Consumed exactly once.
 */
export declare function shouldSuppressAbortBroadcast(session: WatchdogSession): boolean;
/** Clear the timer and reset the awaiting flag. Idempotent. */
export declare function disposeStreamWatchdog(session: WatchdogSession): void;
//# sourceMappingURL=stream-watchdog.d.ts.map