import type { BackgroundJobRecord, BackgroundJobStore, ContextFile } from '../../utils'; export declare const STOP_CONFIRMATION_GRACE_MS = 5000; /** Deadline for a single transcript read inside the stop gate. A hung * read must not block the reconciler loop (or join other confirmations) * indefinitely; timeout degrades to an unknown-evidence verdict. */ export declare const DEFAULT_EVIDENCE_READ_TIMEOUT_MS = 5000; /** Race a promise against a deadline. On timeout the CONSUMER is * released with `undefined` (unknown evidence) while the underlying * operation may keep running — callers that must not pile up reads * layer a single-open policy on top (see createStopEvidenceGate). * `timeoutMs <= 0` disables the deadline (the promise still maps * rejection to `undefined`). */ export declare function raceEvidenceDeadline(promise: Promise, timeoutMs: number): Promise; export declare const STOPPED_WITHOUT_TERMINAL_RESULT = "Background session stopped before a terminal task result was received."; export declare const EVIDENCE_UNAVAILABLE_DIAGNOSTIC = "Terminal evidence could not be read after repeated attempts; task termination is unconfirmed (observation unavailable)."; export type StopConfirmationTracker = { pendingManagedTaskIds: Set; contextFilesForPrompt(taskId: string): ContextFile[]; prune(board: { taskIDs(): Set; }): void; }; export declare function applyConfirmedStop(options: { backgroundJobBoard: BackgroundJobStore; taskID: string; observedAt: number; generation: number; taskContextTracker: StopConfirmationTracker; }): BackgroundJobRecord | undefined; /** * Idle/absent/non-busy is only a stop candidate. The first observation * starts a grace clock; a later observation after the grace confirms * the stop. Live busy after the observation wins and leaves the job running. * * When `confirmStop` is provided, the post-grace confirmation is delegated * to it (terminal-evidence gate): quiescence alone proves the session is * not running, not that no result exists — the transcript must be * consulted before publishing `stopped` (false-stop incident: a fallback * re-prompt completed its answer while the grace timer was still armed). */ export declare function observeNonBusyRuntime(options: { backgroundJobBoard: BackgroundJobStore; taskID: string; observedAt: number; generation: number; graceMs: number; lastStatusError: string; taskContextTracker: StopConfirmationTracker; /** Idle timestamp the quiescence was first observed at (busy guard); * defaults to observedAt for periodic-poll callers. Stays IMMUTABLE * across retries — it is the real idle anchor that opened the * decision, never a synthetic grace-consuming timestamp. */ idleObservedAt?: number; /** Delegated post-grace confirmation. Returns the updated record. */ confirmStop?: (options: { taskID: string; observedAt: number; idleObservedAt: number; lastStatusError: string; onRetry?: () => void; }) => Promise; }): Promise; export type TerminalEvidenceVerdict = { verdict: 'completed'; text: string; } | { verdict: 'error'; text: string; } | { verdict: 'absent'; } | { verdict: 'retry'; reason: string; }; /** * Classify a child transcript response for the stop decision. The * distinction that matters: `absent` means the transcript was read * correctly and provably holds no result for THIS run; `retry` means * the evidence is unknown (unreadable, malformed, provenance * unverifiable, or the answer has not materialized) and must never be * treated as proof of no result. * * Provenance rules: * - With a baseline: only the post-baseline segment is considered. An * assistant turn is attributed to this run only when nothing newer * than it represents pending work — the backward scan from a * structural tail may NOT cross a real user message (that user * message is work whose answer has not arrived yet → retry). * - Without a baseline (untracked native jobs): only the ABSOLUTE * trailing message counts (strict semantics — no scan back through * history), and an assistant turn is only attributed when its * completion timestamp is at/after the run started. */ export declare function classifyTerminalEvidence(response: unknown, options?: { baselineMessageID?: string; runStartedAt?: number; }): TerminalEvidenceVerdict; export interface StopEvidenceGate { confirm(options: { taskID: string; generation: number; observedAt: number; idleObservedAt: number; lastStatusError: string; taskContextTracker: StopConfirmationTracker; /** Invoked (for EVERY joined caller) when the verdict is a bounded * retry — callers re-arm their own confirmation timers with their * own immutable idle anchor. */ onRetry?: () => void; }): Promise; dispose(): void; } /** * Shared post-grace stop confirmation backed by transcript evidence. * Coalesces concurrent confirmations per task AND generation (timer + * periodic poll share one in-flight read and every joined `onRetry` * fires), bounds each read with a deadline, revalidates state, busy, * generation and observation identity after every await, and keeps the * #1157 termination guarantee exclusively on the `absent` path (valid * read, provably no result). Unknown evidence never terminates into * `stopped`; after the episode budget it stays `running` + * `statusUncertain` with an explicit diagnostic. */ export declare function createStopEvidenceGate(options: { backgroundJobBoard: BackgroundJobStore; readTerminalEvidence: (taskID: string) => Promise; /** Baseline anchoring for tracker-registered runs (revive/fallback): * results from before the baseline belong to a substituted attempt. */ baselineFor?: (taskID: string, generation: number) => string | undefined; /** Observation-identity fence: two distinct observations can share * the same baseline (especially undefined), so the tracker also * exposes a monotonic revision per tracked run; a revision change * during the read invalidates the snapshot. */ observationRevisionFor?: (taskID: string, generation: number) => number | undefined; /** Fallback handoff deferral: while a fallback's admission await is * pending, terminal publication is deferred — the job may already * hold the re-prompted result but no delivery owner exists yet. */ isObservationPending?: (taskID: string, generation: number) => boolean; maxEvidenceRetries?: number; readTimeoutMs?: number; }): StopEvidenceGate;