/** * Per-thread chat run health, derived from the durable `last_progress_at` * timestamp on the server. Drives the user-visible "this chat looks stuck" * affordance — distinct from the silent reconnect logic in * `agent-chat-adapter.ts`, which keeps trying in the background. When * automatic recovery isn't making progress (for whatever reason), this * hook surfaces a Retry / Cancel button to the user instead of leaving * them staring at a frozen spinner. */ export interface RunStuckState { /** True when an active run hasn't emitted an event for `stuckThresholdMs`. */ isStuck: boolean; /** ID of the active run, or null when nothing is in flight. */ runId: string | null; /** Server-side run status ("running" / "completed" / "errored" / etc.). */ status: string | null; /** Server timestamp (ms) of the last emitted event, or null if none yet. */ lastProgressAt: number | null; /** Milliseconds since `lastProgressAt`, or null. */ stuckSinceMs: number | null; /** Server timestamp (ms) of the last process-alive heartbeat. */ heartbeatAt: number | null; /** Milliseconds since `heartbeatAt`, computed against the server clock. */ heartbeatSinceMs: number | null; /** How the run was dispatched/continued, e.g. foreground-self-chain or background-processing. */ dispatchMode: string | null; /** * Server-authoritative: true when the run holds an open tool call or A2A * `agent_call` delegation (`in_flight_since` marker set). Preferred over the * client-side proxy for deciding whether Retry (which aborts the run) is * safe to offer. Null when the server bundle predates this field. */ hasInFlightWork: boolean | null; } export interface UseRunStuckDetectionOptions { /** The thread to monitor. Pass null/undefined to disable polling. */ threadId: string | null | undefined; /** * Set false to skip scheduling the poll loop entirely — used to gate * polling to only the active chat tab when multiple tabs are mounted * (inactive tabs are kept alive via display:none, not unmounted). * Defaults to true. */ enabled?: boolean; /** * Threshold above which an in-flight FOREGROUND run is considered stuck. * The default sits comfortably above the adapter's 75s no-progress * reconnect — by then automatic recovery has already had its chance. */ stuckThresholdMs?: number; /** * Threshold for BACKGROUND-dispatched runs (dispatchMode starts with * "background"). The server owns recovery for these — its run-manager * no-progress backstop (150s) and unclaimed-run sweep act first — so the * user-facing "stuck" affordance is a late fallback, not a race against * them. Selected inside the hook because the dispatch mode is only known * from the same poll response that computes the elapsed time. */ backgroundStuckThresholdMs?: number; /** * Legacy upper bound for a claimed durable background worker that is still * sending fresh process heartbeats. Informational quiet-run UI is never * delayed past `backgroundStuckThresholdMs`; this option can only request an * earlier notice for live workers. Default 13 minutes. */ liveBackgroundStuckThresholdMs?: number; /** Poll interval. Default 5_000ms. */ pollIntervalMs?: number; /** API base path. Default `/_agent-native/agent-chat`. */ apiUrl?: string; } export declare const DEFAULT_BACKGROUND_STUCK_THRESHOLD_MS = 180000; export declare const DEFAULT_LIVE_BACKGROUND_STUCK_THRESHOLD_MS: number; export declare function useRunStuckDetection({ threadId, enabled, stuckThresholdMs, backgroundStuckThresholdMs, liveBackgroundStuckThresholdMs, pollIntervalMs, apiUrl, }: UseRunStuckDetectionOptions): RunStuckState; /** * POST `/runs/:id/abort` so the server flips the run to "aborted" and the * adapter's reconnect loop exits cleanly. Returns the run id that was * aborted (or null on failure) so callers can correlate observability * events. Best-effort — failures are swallowed, since the user's intent * is already captured locally. */ export declare function useAbortRun(apiUrl?: string): (runId: string, reason?: string) => Promise; //# sourceMappingURL=use-run-stuck-detection.d.ts.map