/** * Background Job Manager for Long-Running Operations * * Operations that may take >30s support async execution. This module * provides a job manager backed by a SQLite DurableJobStore so that job * state survives process restart. Any job recorded as `running` in the * database at construction time is immediately transitioned to `orphaned` * so that humans/agents can triage it — no silent retry. * * @remarks * Relocated from `packages/cleo/src/dispatch/lib/background-jobs.ts` into * `@cleocode/core/store` (R3-K1 · T11455 · SG-RUNTIME-UNIFICATION). This is * Drizzle DB-access logic, so it belongs in `@cleocode/core` — the package that * owns the consolidated SQLite schema, the single Drizzle instance, and the DB * handle types. Hosting it here means there is exactly ONE `drizzle-orm` * instance backing the query builders (no dual peer-hashed instance / `tsc -b` * `SQL` nominal mismatch). `@cleocode/runtime/gateway` imports the * accessor from here and declares no `drizzle-orm` dependency of its own. * * @task T641 * @task T11455 */ import type { NodeSQLiteDatabase } from './sqlite.js'; import type * as schema from './tasks-schema.js'; import { type BackgroundJobStatus } from './tasks-schema.js'; export type { BackgroundJobStatus }; /** * Background job representation returned by public API methods. * * Timestamps are ISO-8601 strings so callers do not need to know that the * database stores them as integer milliseconds. */ export interface BackgroundJob { /** Unique job identifier (UUID v4). */ id: string; /** Operation name, e.g. "nexus.analyze". */ operation: string; /** Current lifecycle status. */ status: BackgroundJobStatus; /** ISO-8601 timestamp of when the job started. */ startedAt: string; /** ISO-8601 timestamp of when the job finished; undefined while running. */ completedAt?: string; /** JSON-serialised result; undefined on failure or while running. */ result?: unknown; /** Error message; undefined on success or while running. */ error?: string; /** Progress 0-100; undefined until reported. */ progress?: number; /** Agent or session that claimed this job; undefined if unclaimed. */ claimedBy?: string; } /** * Configuration for {@link BackgroundJobManager}. */ export interface BackgroundJobManagerConfig { /** Maximum number of concurrently *running* jobs. Default: 10. */ maxJobs?: number; /** How long (ms) to retain completed/failed/cancelled jobs. Default: 3 600 000 (1 h). */ retentionMs?: number; } type TasksDb = NodeSQLiteDatabase; /** * Drizzle-backed persistence layer for background jobs. * * On construction the store scans for `running` rows left by a prior * process and marks them `orphaned`. All reads and writes go through * Drizzle — no raw SQL strings. * * @task T641 */ export declare class DurableJobStore { #private; constructor(db: TasksDb); /** * Insert a new job row with `running` status. * * @param id - UUID for the job * @param operation - Operation name * @param now - Current timestamp in ms */ insert(id: string, operation: string, now: number): void; /** * Retrieve a single job by ID. Returns `undefined` if not found. */ get(id: string): BackgroundJob | undefined; /** * Retrieve all jobs, optionally filtered by status. */ list(status?: string): BackgroundJob[]; /** * Mark a job as `complete` with an optional result payload. */ complete(id: string, result: unknown, now: number): void; /** * Mark a job as `failed` with an error message. */ fail(id: string, error: string, now: number): void; /** * Mark a job as `cancelled`. */ cancel(id: string, now: number): void; /** * Update the progress (0-100) and heartbeat of a running job. */ progress(id: string, progress: number, now: number): void; /** * Delete terminal (complete/failed/cancelled/orphaned) jobs whose * `completedAt` is older than `cutoffMs`. * * @returns Number of rows deleted. */ purgeOlderThan(cutoffMs: number): number; } /** * Manages background jobs for long-running operations. * * Backed by a {@link DurableJobStore} so job state persists across process * restarts. The public interface is identical to the previous in-memory * implementation so the job-manager accessor does not change. * * @task T641 */ export declare class BackgroundJobManager { #private; constructor(db: TasksDb, config?: BackgroundJobManagerConfig); /** * Start a new background job. * * @param operation - The operation identifier (e.g. "nexus.analyze") * @param executor - Async function to execute in the background * @returns The job ID * @throws Error if the maximum number of concurrent running jobs is reached */ startJob(operation: string, executor: () => Promise): Promise; /** * Get a specific job by ID. * * @returns The job or `undefined` if not found. */ getJob(jobId: string): BackgroundJob | undefined; /** * List all jobs, optionally filtered by status. * * @param status - Optional status filter string. * @returns Array of matching jobs. */ listJobs(status?: string): BackgroundJob[]; /** * Cancel a running job. * * @returns `true` if the job was cancelled; `false` if not found or not running. */ cancelJob(jobId: string): boolean; /** * Update the progress of a running job (0-100). * * @returns `true` if updated; `false` if the job is not found or not running. */ updateProgress(jobId: string, progress: number): boolean; /** * Delete completed/failed/cancelled/orphaned jobs past the retention window. * * @returns Number of jobs removed. */ cleanup(): number; /** * Destroy the manager: cancel all in-process running jobs and stop the * cleanup timer. Does NOT purge DB rows — orphaned jobs must be reviewed. */ destroy(): void; } //# sourceMappingURL=background-jobs.d.ts.map