export type JobState = "working" | "completed" | "failed" | "cancelled"; export interface JobStatus { task: string; kind: string; status: JobState; progress?: number; total?: number; message?: string; result?: unknown; error?: { code: string; message: string; }; startedAt: number; finishedAt?: number; /** For mirrored jobs: the daemon's own task handle (e.g. "task_9"). */ daemonTask?: string; } export type ProgressFn = (p: number, total: number | undefined, msg: string) => void; export type JobRun = (signal: AbortSignal, progress: ProgressFn) => Promise; export interface DaemonLike { request(op: string, args: Record, opts?: { signal?: AbortSignal; timeoutMs?: number; }): Promise; } export declare const RETENTION_MS = 3600000; export declare const POLL_INTERVAL_MS = 500; /** Consecutive failed `task.status` polls tolerated before the mirror gives up. */ export declare const POLL_RETRIES = 3; /** How long a mirrored task may run before the mirror stops watching it. * Longer than the longest scenario anyone runs (`stability --duration-hours 8`) * by a wide margin — this is the backstop for a daemon task that will never * reach a terminal state, not a policy on how long work may take. */ export declare const DAEMON_TASK_DEADLINE_MS: number; export interface PumpOpts { /** Give up (and cancel the daemon side) after this long. */ deadlineMs?: number; /** Consecutive transient poll failures tolerated. */ retries?: number; } /** * Poll one daemon-side task ("task_N" from ota.flash / scenario.run) to its * terminal state, forwarding progress. Abort forwards `task.cancel` once and * keeps polling so the daemon's own `cancelled` state is the one observed. * Factored out of JobRegistry.mirror() so flash can compose it with a boot * wait inside a single job — there is exactly one poll loop in this code base. */ export declare function pumpDaemonTask(daemon: DaemonLike, daemonTask: string, signal: AbortSignal, progress: ProgressFn, pollMs?: number, opts?: PumpOpts): Promise; export declare class JobRegistry { private jobs; private seq; private readonly retentionMs; private readonly now; constructor(opts?: { retentionMs?: number; now?: () => number; }); /** Start `run` immediately; returns "task_N". */ create(kind: string, run: JobRun): string; /** Mirror a daemon task ("task_N" from scenario.run / ota.flash) as a local job. */ mirror(daemon: DaemonLike, daemonTask: string, kind: string, pollIntervalMs?: number, opts?: PumpOpts): string; status(id: string): JobStatus; /** Resolve when the job reaches a terminal state, or after timeoutMs with * whatever the status is then (the caller checks `.status`). */ wait(id: string, timeoutMs: number): Promise; /** Abort a working job. false when unknown or already terminal. */ cancel(id: string): boolean; list(): JobStatus[]; private finish; } export declare const jobs: JobRegistry;