import { type Clock } from "./clock.js"; import { type Relation } from "./comms.js"; import type { RoostEvent } from "./events.js"; import type { JobStateName } from "./protocol.js"; /** A watch: tell `watcher` (a session key) once when job `id` is next idle, or has ended. */ export interface Watch { watcher: string; watcherName: string; /** The watching job, when a job watches (null: a foreground session). */ watcherId?: string | null; id: string; when: "idle" | "ended"; at: number; } /** The parts of a job's row a notice is made of. */ export interface WatchedRow { id: string; name: string; state: JobStateName; waitingFor: string | null; question: string | null; summary: string | null; error: string | null; } /** The sessions: the peer that asks (`P`: whatever the daemon calls a connected session) and its tree. */ export interface WatchesSessionsPort

{ /** The peer takes no agent items (a watch is told as one). */ refusesItems(peer: P): boolean; /** The peer's session key (throws what the daemon throws for a session-less peer). */ keyOf(peer: P): string; name(peer: P): string; /** The peer's job, when a job asks (null: a foreground session). */ jobId(peer: P): string | null; /** The session `to` (an id or a name) names, among those the peer reaches (throws when none, or many). */ find(peer: P, to: string): { key: string; id: string; role: string; }; } export interface WatchesJobsPort { /** The job still has a record. */ exists(id: string): boolean; row(id: string): WatchedRow | undefined; /** What the watched job's session is to the watcher's. */ relation(watcherKey: string, jobId: string): Relation; /** Hand a notice to the session `key`; `reached`: it got it (a gone watcher is not told). */ deliver(key: string, text: string, meta: { from: string; relation: Relation; }): Promise<{ outcome: string; reached: boolean; }>; } /** The file the watches live in (the daemon's writes atomically under the roost's home). */ export interface WatchesStore { read(): unknown; write(v: unknown): void; } export interface WatchesDeps

{ sessions: WatchesSessionsPort

; jobs: WatchesJobsPort; store: WatchesStore; record(e: Omit): void; log(level: "info" | "warn" | "error", msg: string, extra?: Record): void; clock?: Clock; /** A watch that never fires expires after this (default WATCH_TTL_MS). */ ttlMs?: number; /** Watches one session keeps at a time (default MAX_WATCHES). */ maxPerWatcher?: number; } export interface WatchResult { id: string; name: string; when: Watch["when"]; fired: boolean; } export interface Watches

{ /** Loads the file: expired watches and ones on jobs that are gone are dropped. Call once the jobs are known. */ load(): void; /** The `watch` op of `peer`. */ add(peer: P, args: { id?: unknown; when?: unknown; } | undefined): Promise; /** A job's row went out: if it is in a state watches fire on, tell each watcher once. */ stateChanged(row: WatchedRow): Promise; /** A job is being removed (call before its record goes): its watchers are told it ended. */ removed(id: string, name: string): Promise; /** The session `key` is held by nothing any more: its own watches go. */ watcherGone(key: string): void; /** Expires watches that never fired and drops ones on jobs that are gone (the daemon's reaper calls it). */ sweep(): void; } /** idle: it stopped working (done, waiting, failed, stopped); ended: it failed, was stopped, or is gone. */ export declare function watchFires(when: Watch["when"], state: JobStateName | "removed"): boolean; export declare function createWatches

(deps: WatchesDeps

): Watches

;