import { type ArtifactReceipt } from "../artifacts/detect.js"; import type { DbExec } from "../db/client.js"; import { type ContinueRefusalCode, type ContinueTrigger } from "./auto-continue.js"; import { type ExecutionFence } from "./execution-fence.js"; import type { AgentChatEvent, RunEvent } from "./types.js"; export declare const RUN_STALE_MS = 15000; export declare const BACKGROUND_RUN_STALE_MS = 90000; export declare const BACKGROUND_PROCESSING_RUN_STALE_MS = 45000; export declare const STALE_RUN_ERROR_EVENT: { readonly type: "error"; readonly error: "The agent stopped before it could finish. It may have hit a server timeout or the worker may have been interrupted."; readonly errorCode: "stale_run"; readonly recoverable: true; readonly details: "The run heartbeat stopped while the run was still marked running. Partial output and tool calls were preserved when available."; }; export declare const STALE_RUN_TERMINAL_REASON: string; export declare const UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT: { readonly type: "error"; readonly error: "The agent run was handed off to a background worker that never started. It was recovered so you can try again."; readonly errorCode: "background_worker_never_started"; readonly recoverable: true; readonly details: "A background-dispatched run was acknowledged (HTTP 202) but its worker never claimed the run, so no progress was produced. The run was reaped early (it had no live worker to protect) so the turn can be retried."; }; export declare const CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT: { readonly type: "error"; readonly error: "The background agent worker stopped before it could start the turn. You can retry from the preserved chat context."; readonly errorCode: "background_worker_failed"; readonly recoverable: true; readonly details: "The durable background worker claimed the run but threw during setup before it could emit agent events."; }; export declare const RUN_RECORD_MISSING_ERROR_EVENT: { readonly type: "error"; readonly error: "The agent run record is no longer available, so this turn could not be confirmed as finished. Retry if the result is missing."; readonly errorCode: "run_record_missing"; readonly recoverable: true; readonly details: "No agent_runs row existed for this run when the live connection last checked, and no terminal event was persisted for it. The run may have completed before its record was pruned."; }; export declare const UNKNOWN_RUN_STATUS_ERROR_EVENT: { readonly type: "error"; readonly error: "The agent run ended in a state this app does not recognize, so the result could not be confirmed. Retry if the result is missing."; readonly errorCode: "unknown_run_status"; readonly recoverable: true; readonly details: "The run row left 'running' with a status the live connection has no terminal event for. Partial output and tool calls were preserved when available."; }; export declare const RUN_TERMINAL_LOOKUP_FAILED_ERROR_EVENT: { readonly type: "error"; readonly error: "The agent run's final state could not be read, so this turn could not be confirmed as finished. Retry if the result is missing."; readonly errorCode: "run_terminal_lookup_failed"; readonly recoverable: true; readonly details: "Reading the run's last persisted terminal event failed. The run may have completed; its outcome is unknown to this connection rather than known to be absent."; }; export declare const TERMINAL_EVENT_UNREADABLE_ERROR_EVENT: { readonly type: "error"; readonly error: "The agent run finished, but its final event could not be read, so the result could not be confirmed. Retry if the result is missing."; readonly errorCode: "terminal_event_unreadable"; readonly recoverable: true; readonly details: "Every persisted terminal event for this run failed to parse. Without this, the run stayed 'running' while the terminal-event exclusion kept the stale reaper away from it."; }; export declare const RUN_RECORD_MISSING_GRACE_MS = 15000; export declare const UNCLAIMED_BACKGROUND_RUN_GRACE_MS = 25000; export declare const UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS: number; /** * Tick interval for the DEDICATED fast redispatch sweep in * agent-chat-plugin.ts (distinct from that file's general-purpose 2-minute * orphan/reap sweep). Only attempts redispatch for rows still inside * `UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS` — it never reaps, so it * cannot race the loud-failure fallback onto an earlier trigger. * * This constant exists because the general sweep's 2-minute cadence puts the * FIRST redispatch attempt uncomfortably close to (and on a slow tick, past) * `BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS` (150s, agent-chat-adapter.ts) — the * client following a deferred successor would give up and report a fatal * error for a turn the server was silently about to recover. The whole * budget is a derived chain, each bound following from the one before it: * * UNCLAIMED_BACKGROUND_RUN_GRACE_MS (25s) row must look abandoned * + UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS (20s) worst-case tick latency * = ~45s worst-case time-to-first-redispatch-attempt, ~65s to a second * attempt if the first fails — both comfortably under the client's 150s * idle timeout, which additionally no longer counts a known-deferred row * against its idle window at all (see `awaitingRedispatch` surfaced by * `/runs/active` and consumed by the client follow loop). * < BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS (150s) client's own backstop * < UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS (300s) hard, unresettable * ceiling — untouched by this constant — past which the slow sweep's * existing loud reap (`background_worker_never_started`) still fires. */ export declare const UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS = 20000; export declare function __resetNoRunningRunsProbeForTests(): void; export declare function resolveTurnRunLedgerBudget(): number; export declare function turnRunLedgerExhausted(turnRunCount: number): boolean; export declare const STALE_RUN_RECOVERY_MAX_SUCCESSORS_PER_TURN = 3; export type AgentTurnInitiator = { email: string; authUserId?: string | null; orgId?: string | null; orgScope?: "personal" | null; anonymous: boolean; }; export declare class AgentTurnInitiatorMismatchError extends Error { constructor(threadId: string, turnId: string); } export declare class AgentTurnInitiatorUnavailableError extends Error { constructor(threadId: string, turnId: string); } export declare const agentRunsStore: import("../db/store-registry.js").Store; export declare function ensureRunTables(): Promise; export declare const TURN_EXECUTION_LEASE_MS = 15000; /** Claim a new physical worker attempt without changing its logical turn. */ export declare function claimTurnExecutionLease(threadId: string, turnId: string, runId: string): Promise; /** Claim a lease for a durable task whose lifecycle is not stored in agent_runs. */ export declare function claimTaskExecutionLease(threadId: string, turnId: string, taskAttemptId: string): Promise; export declare class TurnExecutionLeaseBusyError extends Error { readonly code = "turn_execution_lease_busy"; constructor(threadId: string, turnId: string); } export declare class TurnExecutionRunNotActiveError extends Error { readonly code = "turn_execution_run_not_active"; constructor(runId: string); } /** Renew the lease and its run liveness marker as one fenced transaction. */ export declare function renewTurnExecutionLease(fence: ExecutionFence): Promise; /** Renew a task-owned lease without expecting a corresponding agent_runs row. */ export declare function renewTaskExecutionLease(fence: ExecutionFence): Promise; /** Release an owned lease after its worker has committed its final writes. */ export declare function releaseTurnExecutionLease(fence: ExecutionFence): Promise; export type AgentExecutionStepReplayPolicy = "read-only" | "retry-safe" | "never"; export interface AgentExecutionStepIntent { threadId: string; turnId: string; runId: string; stepId: string; operationId: string; toolName: string; inputJson: string; replayPolicy: AgentExecutionStepReplayPolicy; generation: string; } export type AgentExecutionStepAdmission = { status: "execute"; wasDispatched: boolean; } | { status: "completed"; resultJson: string | null; errorJson: string | null; } | { status: "unknown"; }; /** Persist intent before dispatch and decide whether an interrupted call may run again. */ export declare function beginAgentExecutionStep(intent: AgentExecutionStepIntent): Promise; export declare function markAgentExecutionStepDispatched(intent: AgentExecutionStepIntent): Promise; export declare function resetAgentExecutionStepBeforeInvocation(intent: AgentExecutionStepIntent, options?: { preserveDispatchHistory?: boolean; }): Promise; export declare function completeAgentExecutionStep(intent: AgentExecutionStepIntent, resultJson: string, errorJson?: string): Promise; export declare function markAgentExecutionStepUnknown(intent: AgentExecutionStepIntent): Promise; /** A detached action may attach late evidence only while its lease is current. */ export declare function recordLateAgentExecutionStepOutcome(intent: AgentExecutionStepIntent, resultJson: string, errorJson?: string): Promise; export interface CompletedAgentExecutionStep { stepId: string; operationId: string; toolName: string; inputJson: string; resultJson: string; } /** Read completed outcomes only for the unfinished calls named by the ledger. */ export declare function getCompletedAgentExecutionSteps(threadId: string, turnId: string, stepIds: readonly string[]): Promise; /** * Persist a zombie tool-call completion to the ledger. Called by the detached * promise continuation after `Promise.race` abandons it. Best-effort — never * throws so a ledger write failure doesn't break any caller. `argumentsHash` * is the exact identity of the arguments the call ran with; only a call * prepared to the same identity may recover the result. */ export declare function writeLedgerEntry(threadId: string, toolKey: string, resultSummary: string, artifacts?: ArtifactReceipt[], resultIsString?: boolean, chatUIResultJson?: string, argumentsHash?: string): Promise; export declare function readLedgerEntry(threadId: string, toolKey: string): Promise<{ result: string; artifacts: ArtifactReceipt[]; resultIsString?: boolean; chatUIResult?: unknown; /** Absent for a result recorded before calls bound their arguments. */ argumentsHash?: string; } | null>; export declare function clearLedgerForThread(threadId: string): Promise; export declare function insertRun(id: string, threadId: string, turnId?: string, options?: { dispatchMode?: "foreground" | "foreground-self-chain" | "background"; dispatchPayload?: string; continuationOrder?: number; turnInitiator?: AgentTurnInitiator; afterInsert?: (tx: DbExec) => Promise; }): Promise; export declare function staleWindowMsForRow(row: { dispatchMode?: string | null; hasDispatchPayload?: boolean; maxStaleMs?: number; }): number; export declare function describeStaleReap(row: { startedAt?: number | null; heartbeatAt?: number | null; lastProgressAt?: number | null; inFlightSince?: number | null; dispatchMode?: string | null; hasDispatchPayload?: boolean; maxStaleMs?: number; now: number; }): string; export declare function claimBackgroundRun(runId: string): Promise; export declare function readBackgroundRunClaim(runId: string): Promise<{ dispatchMode: string | null; status: string | null; diagStage: string | null; workerStage: string | null; lastLivenessAt: number | null; } | null>; export declare function readRunDispatchPayload(runId: string): Promise; export declare function clearRunDispatchPayload(runId: string): Promise; export declare function listUnclaimedBackgroundRunIds(): Promise; export interface UnclaimedBackgroundRunRow { id: string; startedAt: number; /** * Whether the row still carries the `dispatch_payload` a redispatched worker * would rehydrate its request body from. Eligibility for this sweep does NOT * imply it: a background row can reach the grace window having never had a * payload at all. Redispatching one of those asserts `payloadRef: true` to a * worker that then cannot rehydrate, so it kills the run as * `dispatch_payload_missing` — a reason that reads like data loss for what is * really an un-redispatchable handoff. */ hasDispatchPayload: boolean; } export declare const UNCLAIMED_BACKGROUND_RUN_SWEEP_BATCH_LIMIT = 4; export declare function listUnclaimedBackgroundRunRows(options?: { limit?: number; }): Promise; export declare function shouldRedispatchUnclaimedBackgroundRun(row: { startedAt: number; }, now?: number): boolean; export declare function countRunsForTurn(threadId: string, turnId: string): Promise; /** * The newest run of the turn before `turnId` in a thread: how the previous * request ended. Null when the thread has no earlier turn. */ export declare function getPreviousTurnNewestRun(threadId: string, turnId: string): Promise<{ turnId: string; status: string; terminalReason: string | null; } | null>; export declare function getTurnInitiatorByRun(runId: string): Promise<(AgentTurnInitiator & { firstRunId: string; }) | null>; /** * The run currently holding the thread's run slot, read without claiming it, * so a request can refuse before work it would only repeat after a 409. */ export declare function getSlotHoldingRunId(threadId: string): Promise; export declare function tryClaimRunSlot(threadId: string, runId: string, maxStaleMs?: number, options?: { turnId?: string; replayCompletedTurn?: boolean; dispatchMode?: "foreground" | "foreground-self-chain" | "background"; dispatchPayload?: string; continuationOrder?: number; turnInitiator?: AgentTurnInitiator; /** The stopped run this run continues in the same turn, and who asked. */ continueOf?: { runId: string; trigger: ContinueTrigger; }; /** Defer a fresh turn while an earlier accepted inbox submission is queued. */ hasPendingQueuedSubmission?: (tx: DbExec) => Promise; /** Keep ordinary turns behind a pending tool approval on this thread. */ hasPendingApproval?: (tx: DbExec) => Promise; /** Cancel a completed approval pause before admitting an explicit interruption. */ interruptApprovalPause?: boolean; afterInsert?: (tx: DbExec) => Promise; }): Promise<{ claimed: boolean; activeRunId: string | null; queuedSubmissionPending?: boolean; approvalPending?: boolean; completedRunId?: string; turnAborted?: boolean; continueRefused?: ContinueRefusalCode; }>; export declare function setRunError(runId: string, errorCode: string | undefined, errorDetail: string | undefined): Promise; export declare function setRunTerminalReason(runId: string, terminalReason: string | undefined): Promise; export declare function setRunTerminalReasonIfStatus(runId: string, expectedStatus: "completed" | "truncated" | "errored" | "aborted", terminalReason: string): Promise; /** * Repair a run whose terminal event was durably appended but whose final * `agent_runs.status` write lost a race with reconnect/reaper code. * * The event ledger is the durable transcript users see. If its latest event is * terminal, the run is no longer alive and must not be converted into a stale * error later. This keeps `agent_runs` and `agent_run_events` from telling two * different stories after delayed DB writes or background function teardown. * This public path is for the active worker. The stale reaper uses a separate * transaction-side repair after excluding a live matching worker generation. */ export declare function reconcileTerminalRunFromEvents(runId: string, options?: { threadProjectionPending?: boolean; }): Promise; export declare const RUN_DIAG_STAGE: { readonly routeEntered: "route_entered"; readonly authFailed: "auth_failed"; readonly authPassed: "auth_passed"; readonly workerEntered: "worker_entered"; readonly workerClaimed: "worker_claimed"; readonly workerClaimLost: "worker_claim_lost"; readonly workerStarted: "worker_started"; readonly workerSetupStep: "worker_setup_step"; readonly setupTimings: "setup_timings"; readonly workerThrew: "worker_threw"; readonly routeThrew: "route_threw"; readonly foregroundInlineRecovery: "foreground_inline_recovery"; readonly staleRunRecoveryAttempted: "stale_run_recovery_attempted"; readonly runBoundaryReached: "run_boundary_reached"; readonly staleRunReaped: "stale_run_reaped"; }; export type RunDiagStage = (typeof RUN_DIAG_STAGE)[keyof typeof RUN_DIAG_STAGE]; export interface RouteFailureDiagnosticEvidence { format: "route_failure_v1"; errorMetadata?: "unreadable"; errorName?: string; errorCode?: string; httpStatus?: string; causeName?: string; } export declare function recordRunDiagnostic(runId: string, stage: RunDiagStage, detail?: string, evidence?: RouteFailureDiagnosticEvidence): Promise; export declare function updateRunHeartbeat(runId: string): Promise; export declare function bumpRunProgress(runId: string): Promise; /** * Mirror run-manager's in-memory `inFlightWorkCount` 0<->N transitions into * SQL so callers can report when a run is holding a tool call or A2A * `agent_call` delegation. * * `inFlight: true` only writes when the row is still `NULL` — a defense-in- * depth belt-and-suspenders against a nested 1->2 transition clobbering the * ORIGINAL start time with a later one (the caller's own counter already * dedupes 0->1 transitions; this WHERE just makes the write itself * idempotent/order-independent too). When `markerSince` is supplied for a * clear, the UPDATE is conditional on the marker still having that value. The * run manager serializes marker writes and supplies this token so a delayed * clear from one tool cannot erase a newer tool's marker. * * Best-effort: callers fire-and-forget so a write failure here never blocks * event emission or aborts the run. The run manager logs marker-write failures * while preserving transition order. Run liveness remains governed by the * execution lease and heartbeat. */ export declare function setRunInFlightMarker(runId: string, inFlight: boolean, markerSince?: number): Promise; export declare function reapIfStale(runId: string, maxStaleMs?: number): Promise; export declare function reapUnclaimedBackgroundRun(runId: string): Promise; export declare function updateRunStatus(runId: string, status: "completed" | "truncated" | "errored" | "aborted"): Promise; export declare function updateRunStatusIfRunning(runId: string, status: "completed" | "truncated" | "errored" | "aborted", options?: { threadProjectionPending?: boolean; terminalReason?: string; }): Promise; export declare function updateRunTerminalStateIfRunning(runId: string, terminal: { status: "completed" | "truncated" | "errored" | "aborted"; terminalReason: string; errorCode?: string; errorDetail?: string; }): Promise; /** A setup failure can precede the run-manager fence, so serialize it with reaping. */ export declare function finalizeClaimedBackgroundRunFailure(runId: string, failure: { errorCode: string; errorDetail: string; event: Extract; }): Promise; export declare function failRunBeforeDispatch(runId: string, errorCode: string, errorDetail: string, terminalReason: string): Promise; /** The caller owns this claim and has not called startRun yet. */ export declare function releaseBackgroundRunBeforeStart(runId: string, errorCode: string, errorDetail: string): Promise; export declare function getRunStatus(runId: string): Promise; export declare function terminalEventForAbortReason(reason: string | undefined): AgentChatEvent; export declare function markRunAborted(runId: string, reason?: string): Promise; /** Abort only when no model or tool work has crossed its dispatch boundary. */ export declare function markRunAbortedAtSafeBoundary(runId: string, reason?: string): Promise; /** Serialize a task lifecycle change with worker lease claims and writes. */ export declare function lockTaskExecutionLease(db: DbExec, threadId: string, turnId: string): Promise; /** Revoke a task worker while its lifecycle transaction holds the lease lock. */ export declare function revokeTaskExecutionLease(db: DbExec, threadId: string, turnId: string): Promise; export declare function markTurnAborted(threadId: string, turnId: string, reason?: string): Promise<"aborted" | "already_terminal">; export declare function getRunTurnRef(runId: string): Promise<{ threadId: string; turnId: string; } | null>; export declare function isTurnAborted(threadId: string, turnId: string): Promise; export declare function isRunAborted(runId: string): Promise; export declare function getRunAbortState(runId: string): Promise<{ aborted: boolean; reason?: string; }>; export declare class AgentRunEventNotPersistedError extends Error { readonly terminalRun: boolean; constructor(runId: string, seq: number | null, terminalRun?: boolean, cause?: unknown); } export declare function insertRunEvent(runId: string, seq: number, eventData: string, options?: { toolInputSource?: "execution"; requireInserted?: boolean; allowContinuationTerminal?: boolean; eventAt?: number; }): Promise; /** * Same guarantees as `insertRunEvent` without execution-input fingerprints, * so tool_start/tool_done must keep going through `insertRunEvent`. */ export declare function insertRunEvents(runId: string, events: ReadonlyArray<{ seq: number; eventAt: number; eventData: string; }>): Promise; export declare const CHECKPOINT_TERMINAL_EVENT_SEQ = 1000000000; export declare function persistRunCheckpointEvent(runId: string, event: AgentChatEvent, terminalReason: string): Promise; export declare function getRunEventsSince(runId: string, fromSeq: number, opts?: { limit?: number; }): Promise>; export declare function getRunById(runId: string): Promise<{ id: string; threadId: string; status: string; startedAt: number; errorCode: string | null; errorDetail: string | null; terminalReason: string | null; threadProjectionErrorCode: string | null; } | null>; /** * What saving a run the server ended on its behalf needs: the run's ledger, * and whether anything ran after it in its turn or in a later turn. */ export declare function readStoppedRunForThreadFold(runId: string): Promise<{ threadId: string; turnId: string; startedAt: number; workerGeneration: string | null; events: RunEvent[]; continuedInTurn: boolean; laterTurnStarted: boolean; } | null>; export declare function getLastTerminalRunEvent(runId: string): Promise<{ seq: number; event: Record; } | null>; export declare function resolveErroredRunTerminalEvent(run: { errorCode?: string | null; errorDetail?: string | null; }): { event: Record; shouldPersist: boolean; }; export declare function getRunByThread(threadId: string, options?: { includeTerminal?: boolean; turnId?: string; }): Promise<{ id: string; threadId: string; turnId?: string | null; status: string; startedAt: number; heartbeatAt: number | null; completedAt: number | null; lastProgressAt: number | null; dispatchMode: string | null; terminalReason: string | null; diagStage: string | null; inFlightSince: number | null; } | null>; /** The connection request that a restored client may still need to resolve. */ export declare function getRunPendingConnectionRequestId(runId: string): Promise; export interface AgentRunSummary { id: string; threadId: string; turnId: string | null; status: string; startedAt: number; heartbeatAt: number | null; completedAt: number | null; lastProgressAt: number | null; errorCode: string | null; abortReason: string | null; dispatchMode: string | null; terminalReason: string | null; diagStage: string | null; threadProjectionErrorCode: string | null; inFlightSince: number | null; } export declare function listRunsForThread(threadId: string, options?: { limit?: number; runIds?: readonly string[]; }): Promise; export interface CurrentTurnRunEvent { runId: string; seq: number; event: AgentChatEvent; } export declare class AgentRunJournalUnreadableError extends Error { readonly threadId: string; readonly turnId: string | undefined; readonly rowIndex: number; readonly reason: "invalid_turn" | "invalid_row" | "invalid_event_json" | "invalid_event" | "invalid_thread_data" | "invalid_execution_step"; readonly errorCode = "tool_call_journal_unreadable"; constructor(threadId: string, turnId: string | undefined, rowIndex: number, reason: "invalid_turn" | "invalid_row" | "invalid_event_json" | "invalid_event" | "invalid_thread_data" | "invalid_execution_step", options?: ErrorOptions); } export declare function getCurrentTurnRunEventsForThread(threadId: string, knownTurnId?: string): Promise; export declare function getCurrentTurnRunIdsForThread(threadId: string, turnId: string): Promise; export declare function getCurrentTurnEventsForThread(threadId: string, knownTurnId?: string): Promise; export interface StaleRunReapResult { reaped: number; failed: number; truncated: boolean; } export declare function reapAllStaleRuns(): Promise; export declare function getRunOutcomeCounters(options?: { sinceDay?: string; }): Promise>; export declare function cleanupOldRuns(olderThanMs: number, erroredOlderThanMs?: number): Promise; export declare function listErroredRuns(options?: { limit?: number; sinceMs?: number; }): Promise>; export declare function ensureTerminalRunEvent(runId: string, event: Record): Promise; /** Hands a terminal run's thread save to the stale-run projection retry. */ export declare function markRunThreadProjectionPending(runId: string): Promise; export type RunThreadProjectionRetry = { state: "untracked"; } | { state: "already_projected"; } | { state: "projected"; } | { state: "deferred"; }; /** Retry after repairing persisted source data; until success, the marker keeps later same-turn runs gated. */ export declare function retryRunThreadProjection(runId: string): Promise;