import type { AgentChatEvent, RunEvent, RunStatus } from "./types.js"; export interface ActiveRun { runId: string; threadId: string; /** Logical-turn identity (see StartRunOptions.turnId). Defaults to runId. */ turnId: string; events: RunEvent[]; status: RunStatus; subscribers: Set<(event: RunEvent) => void>; abort: AbortController; abortReason?: string; /** * Terminal event to emit when a server-driven continuation has been handed * off successfully. The continuation runs outside this process, so the * normal loop-level auto_continue event is not sent through this run's * `send` callback. */ continuationTerminalEvent?: Extract; startedAt: number; } export interface StartedRun extends ActiveRun { /** * Resolves after the terminal event and final SQL status have been persisted. * Serverless workers must await this before returning or the runtime can * freeze the isolate between onComplete and terminalization. */ finalized: Promise; } /** * Default run chunk budget for hosted/serverless deploys. * * This MUST fire before the two upstream hard walls that otherwise kill a run * mid-turn with no chance to hand off: * 1. The Builder model gateway keeps a 45s cap only for hosted foreground * runs; local and proven background-function runs use longer caps. * 2. Serverless functions are hard-killed around 60-65s (the heartbeat then * reaps the row as a stale_run). * Production data showed every cutoff landing in the 44-70s window with ZERO * auto_continue events ever emitted — i.e. the old 45s default raced the 45s * gateway and lost, and per-template overrides (e.g. 240_000) pushed it past * BOTH walls so it could never fire. 40s leaves ~5s of headroom under the * gateway wall to abort, persist the partial turn, write the terminal event, * and emit a clean auto_continue so the client resumes seamlessly. */ export declare const DEFAULT_HOSTED_RUN_SOFT_TIMEOUT_MS = 40000; /** * Hard ceiling for the hosted soft timeout. On a hosted runtime the * foreground auto_continue soft timeout can never usefully exceed this — the * synchronous function (~60s) wall kills the run first, so a larger configured * or env value just guarantees the cutoff is a hard error instead of a graceful * hand-off. Any resolved value above this is clamped down for hosted foreground * runs. Local dev (non-hosted) is left alone so long-running local turns aren't * chunked. * * IMPORTANT: this clamp is for the INTERACTIVE / foreground path and must NOT * be raised. The foreground POST still rides a synchronous serverless function * (~60-65s wall), so 40s remains correct there. The only sanctioned exception * is the opt-in `backgroundFunction` mode (see * `BACKGROUND_SOFT_TIMEOUT_CEILING_MS`), which runs inside a Netlify background * function (no ~60s wall, 15-min budget) and therefore can safely outlast 40s. */ export declare const HOSTED_SOFT_TIMEOUT_CEILING_MS = 40000; /** * Hard ceiling for the soft timeout when a run executes inside a Netlify * background function (any deployed function whose name ends in `-background`). * Background functions return 202 immediately and run detached for up to 15 * minutes, so the ~60s synchronous function wall that 40s defends against does * NOT apply. 13 minutes leaves ~2 min of headroom under Netlify's 15-min hard * kill to abort, persist the partial turn, write the terminal event, and (for * the rare >13-min turn) self-fire another background continuation. * * This ceiling is used ONLY when a caller explicitly opts in with * `backgroundFunction: true`. It does not change the foreground/interactive * ceiling and does not fire unless the durable-background path dispatched the * run into a background function. Per the design doc Guardrail, the 40s * interactive clamp stays correct for every non-background run. */ export declare const BACKGROUND_SOFT_TIMEOUT_CEILING_MS: number; /** * Default soft-timeout budget for a background-function run when the caller * does not pass an explicit `softTimeoutMs`. Same value as the ceiling — we * want a background turn to use nearly its whole 15-min budget before handing * off to a chained background continuation. */ export declare const DEFAULT_BACKGROUND_RUN_SOFT_TIMEOUT_MS: number; /** * Default no-progress window for a run executing inside a proven durable * background function. Keep this below the 13-minute soft timeout so a truly * wedged background turn can still checkpoint, persist, and continue before * the function budget expires, but far above the foreground 150s window so * large Design/Plan/Assets generations are not chopped up while the model is * legitimately planning a big tool payload. */ export declare const DEFAULT_BACKGROUND_NO_PROGRESS_TIMEOUT_MS: number; /** * AUTHORITATIVE no-progress backstop for a run, enforced by the run manager * itself (timer-driven, independent of any layer below). * * The finer-grained watchdogs inside the agent loop (model-stream and * action-preparation no-progress, both 90s) only guard the model event stream * — a stall in any segment OUTSIDE that guarded loop (engine-call * establishment, worker setup between continuation chunks, a wedged transport * that emits keepalives while the loop never runs) previously hung forever * with the client watching keepalives. This backstop covers every segment by * construction: if no REAL progress event (see `shouldBumpProgressForEvent`; * keepalives and zero-byte prep activity don't count) lands for this long — * and no tool call is in flight (tool execution legitimately emits nothing * for minutes and has its own 12-min timeout) — the run manager emits * `auto_continue { reason: "no_progress" }` and aborts the chunk, exactly * like the soft timeout, so the normal continuation machinery recovers it. * * This is now only the CEILING, not the value: `resolveRunNoProgressTimeoutMs` * clamps the foreground backstop to a fraction of the chunk's soft timeout * (~30s at a 40s chunk), which is BELOW the 90s in-loop watchdogs rather than * above them. That ordering is deliberate — the in-loop watchdogs could never * fire inside a hosted foreground chunk anyway, since the serverless wall * (~57-59s) arrives first. Proven durable-background chunks keep the full * `DEFAULT_BACKGROUND_NO_PROGRESS_TIMEOUT_MS` so large outputs can use the * background budget. Only armed when a soft-timeout regime is active (hosted * runs); local dev stays unbounded. */ export declare const RUN_NO_PROGRESS_HARD_TIMEOUT_MS = 150000; /** * Largest per-tool timeout that can actually fire inside this run's chunk * budget. `0` means "no run-imposed ceiling" (local dev / unbounded runs), in * which case the caller keeps its own default. */ export declare function resolveRunToolTimeoutCeilingMs(softTimeoutMs: number): number; /** * Resolve the no-progress backstop for a run. * * Foreground values are clamped to a fraction of the chunk's soft timeout so a * template cannot configure a background-sized window (templates/analytics * passed 3min unconditionally) that outlives the serverless wall AND the * client-side watchdog — which is how the server's whole recovery ladder came * to never run. Background-function runs keep the full background budget and * take `backgroundOverrideMs` when a caller wants to tune only that regime. */ export declare function resolveRunNoProgressTimeoutMs(params: { softTimeoutMs: number; backgroundFunction?: boolean; overrideMs?: number; backgroundOverrideMs?: number; }): number; /** * Default SQL retention for completed run event logs (24 hours). * * Deliberately SHORTER than the errored retention below. Reading outcome rates * straight off `agent_runs` over any wider window therefore undercounts * successes — `cleanupOldRuns` rolls each pruned row into * `agent_run_outcome_daily` (see `getRunOutcomeCounters`) so rates stay * correct; use counters plus live rows, not live rows alone. */ export declare const DEFAULT_COMPLETED_RUN_RETENTION_MS: number; /** * Default SQL retention for unsuccessful run event logs — errored, aborted, AND * truncated (7 days). Kept longer than completed runs so cut-off / failed chats * survive for pattern analysis (listErroredRuns): they are exactly the runs we * need to study to keep hardening reliability. Truncations only reach this * window because they are no longer filed as `completed`; while they were, the * most-reported failures were also the fastest-deleted evidence. */ export declare const DEFAULT_ERRORED_RUN_RETENTION_MS: number; /** * How recently a terminal run must have started for `/runs/active` to surface * it. Reconnect after this window won't replay the run — typical real-world * disconnects resolve in seconds, so 10 minutes is generous while keeping us * from resurrecting ancient turns when the user reopens an old thread. */ export declare const TERMINAL_RUN_RECONNECT_WINDOW_MS: number; /** Fast poll cadence while a SQL-backed SSE subscription is actively receiving rows. */ export declare const SQL_SUBSCRIPTION_ACTIVE_POLL_MS = 125; /** Baseline SQL-backed SSE poll cadence when the run is idle. */ export declare const SQL_SUBSCRIPTION_IDLE_POLL_MS = 500; /** * Keep briefly polling quickly after rows arrive so token streams stay smooth, * then back off to the idle cadence if the producer goes quiet. */ export declare const SQL_SUBSCRIPTION_ACTIVE_GRACE_MS = 2000; /** Keep terminal/status probes at the historical cadence to bound DB work. */ export declare const SQL_SUBSCRIPTION_STATUS_POLL_MS = 500; export declare function resolveSqlSubscriptionPollMs(now: number, activePollUntil: number): number; export interface StartRunOptions { /** Keep a request-scoped serverless invocation alive for this run. */ waitUntil?: (promise: Promise) => void; /** Optional internal run chunk budget. When reached, the framework emits an * auto-continuation signal instead of a user-facing timeout. Leave unset for * no framework-imposed run timeout. */ softTimeoutMs?: number; /** Opt into the hosted/serverless default chunk budget. Only callers with * automatic continuation support should enable this. */ useHostedSoftTimeoutDefault?: boolean; /** Stable identity for the logical assistant turn this run belongs to. A * turn may span several continuation runs (each chunk is its own run); they * share one `turnId` so the durable assistant message can be folded across * them instead of dropped per-run. Defaults to the runId (turn == run). */ turnId?: string; /** * Opt into the durable-background-function soft-timeout regime for THIS run * only. When true, `resolveRunSoftTimeoutMs` lifts the hosted ceiling from * 40s to ~13min (`BACKGROUND_SOFT_TIMEOUT_CEILING_MS`) because the run is * executing inside a Netlify background function (no ~60s wall). Off by * default — the foreground/interactive path never sets this, so its 40s * clamp is unchanged. See the design doc + the durable-background dispatch * decision in production-agent.ts. */ backgroundFunction?: boolean; /** * Override the run-manager-level no-progress backstop * (`RUN_NO_PROGRESS_HARD_TIMEOUT_MS`). `0` disables it. Defaults to the * backstop constant whenever a soft-timeout regime is active (hosted runs) * and to disabled otherwise (local dev stays unbounded). */ noProgressTimeoutMs?: number; /** * Override the no-progress backstop for a `backgroundFunction` run only. * Exists so a template can raise the background window without also raising * the foreground one — `noProgressTimeoutMs` is clamped to a fraction of the * foreground chunk budget precisely so it can never outlive the serverless * wall. See `resolveRunNoProgressTimeoutMs`. */ backgroundNoProgressTimeoutMs?: number; /** * Lifecycle metadata persisted to `agent_runs.dispatch_mode` and surfaced to * clients through `/runs/active`. This does not change run-manager behavior; * callers use it to describe who owns continuation at hosted chunk boundaries. */ dispatchMode?: "foreground" | "foreground-self-chain" | "background"; } export interface ResolveRunSoftTimeoutOptions { useHostedDefault?: boolean; /** * Resolve the soft timeout for a run executing inside a Netlify background * function. Lifts the hosted clamp to `BACKGROUND_SOFT_TIMEOUT_CEILING_MS` * (~13min) for this invocation only and, when no override/env is supplied, * defaults to `DEFAULT_BACKGROUND_RUN_SOFT_TIMEOUT_MS`. Does NOT change the * foreground ceiling. Off by default. */ backgroundFunction?: boolean; } /** * True on hosted/serverless runtimes where the soft-timeout regime applies * (see `resolveRunSoftTimeoutMs`, which resolves to 0 — disabled — off these * runtimes). Exported so production-agent.ts can gate its foreground * first-model-event cap (`FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS`) on the * SAME predicate that selects the 40s clamp: the cap only makes sense where * that clamp (and the platform wall behind it) exists. */ export declare function isHostedRuntime(): boolean; export declare function resolveRunSoftTimeoutMs(overrideMs?: number, options?: ResolveRunSoftTimeoutOptions): number; export declare function resolveCompletedRunRetentionMs(): number; export declare function resolveErroredRunRetentionMs(): number; /** * Start a new agent run in the background. * `runFn` receives a `send` callback and an `AbortSignal`. * The run continues even if all SSE subscribers disconnect. * * Events are persisted to SQL for cross-isolate access (Cloudflare Workers). */ export declare function startRun(runId: string, threadId: string, runFn: (send: (event: AgentChatEvent) => void, signal: AbortSignal) => Promise, onComplete?: (run: ActiveRun) => void | Promise, options?: StartRunOptions): StartedRun; /** * Subscribe to a run's events starting from `fromSeq`. * Returns a ReadableStream that replays buffered events then live-tails. * Cancelling the stream only unsubscribes — does NOT abort the agent. * * Falls back to SQL polling when the run is not in local memory * (cross-isolate reconnection on Workers). */ export declare function subscribeToRun(runId: string, fromSeq: number): ReadableStream | null; /** Get the active run for a thread (if any) — checks memory then SQL */ export declare function getActiveRunForThread(threadId: string): ActiveRun | null; /** * Async version that also checks SQL — for cross-isolate access. * Used by the /runs/active endpoint. * * Returns `heartbeatAt` so the client can independently decide a run is * dead even before the server-side stale reap has fired. Returns * `lastProgressAt` so the client-side stuck-detector can show a * user-visible "this chat looks stuck" affordance when a run is alive * (heartbeating) but not actually emitting events. Returns * `awaitingRedispatch` so the client's background follow loop can tell a * legitimately-deferred `chainServerDrivenContinuation` successor (recovery * in progress server-side) apart from a genuinely dead run — see this * field's own doc comment below. */ export declare function getActiveRunForThreadAsync(threadId: string): Promise<{ runId: string; threadId: string; turnId: string; status: string; heartbeatAt: number; lastProgressAt: number | null; /** How the run was dispatched/continued (foreground, foreground-self-chain, background...). */ dispatchMode?: string | null; /** Compact terminal classification, e.g. done, run_timeout, stale_run. */ terminalReason?: string | null; /** * Last reached `_process-run` worker stage as a JSON string * `{stage,detail?,at}`. Surfaced so a silent background-worker death is * diagnosable from the client WITHOUT the unreadable bg-fn logs. */ diagStage?: string | null; /** * True exactly when this run is a `chainServerDrivenContinuation` deferral * (dispatch_mode === 'background', never claimed) still inside * `UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS` — the same condition this * function already uses below to skip its own `reapUnclaimedBackgroundRun`. * Surfaced on `/runs/active` (agent-chat-plugin.ts) so * `agent-chat-adapter.ts`'s follow loop can tell "silently deferred, * server-side recovery in progress" apart from "dead" and stop counting the * quiet gap against its idle timeout — see the THREE-SITE INVARIANT comment * below and in agent-chat-plugin.ts / production-agent.ts. Always false for * an in-memory run (that isolate IS the live producer) and for any run that * isn't an unclaimed background dispatch. */ awaitingRedispatch: boolean; /** * True exactly when this run's `in_flight_since` marker is set — a tool * call or A2A `agent_call` delegation is open and has not yet resolved * (see `setRunInFlightMarker` / `IN_FLIGHT_RUN_STALE_GRACE_MS` in * run-store.ts). This is the SAME signal `reapIfStale` reads to grant its * bounded stale-reap grace — computed here from the identical * `in_flight_since` column via `getRunByThread`, never re-derived, so the * client and the reaper cannot disagree about what "in flight" means. * * Surfaced on `/runs/active` (agent-chat-plugin.ts) as the * server-authoritative alternative to the client-side proxy * `RunStuckBanner` currently infers from unresolved `tool-call` content * parts in the local message list — that proxy can go stale after a * reconnect or reader-mode replay; this cannot, because it is read fresh * from SQL on every poll. */ hasInFlightWork: boolean; } | null>; /** Get a run by ID */ export declare function getRun(runId: string): ActiveRun | null; /** Explicitly abort a run (e.g. Stop button). */ export declare function abortRun(runId: string, reason?: string): boolean; /** * Abort a run and wait until the cross-isolate SQL state and terminal event * are durable. Request handlers that start recovery immediately after aborting * must use this path; otherwise the recovery POST can race the old row while * it is still marked running. */ export declare function abortRunDurably(runId: string, reason?: string): Promise; export { tryClaimRunSlot } from "./run-store.js"; //# sourceMappingURL=run-manager.d.ts.map