import type { RuntimeStore } from '../runtime/store/index.js'; import type { RuntimeEventBus } from '../runtime/events/index.js'; import type { WatcherKind, WatcherRecord } from '../runtime/store/domains/watchers.js'; import type { AutomationSourceRecord } from '../automation/sources.js'; import type { FeatureFlagReader } from '../runtime/feature-flags/index.js'; export interface RegisterWatcherInput { readonly id: string; readonly label: string; readonly kind?: WatcherKind | undefined; readonly source: AutomationSourceRecord; readonly intervalMs?: number | undefined; readonly metadata?: Record | undefined; readonly run?: (() => Promise | string | void) | undefined; } export interface RegisterPollingWatcherInput { readonly id: string; readonly label: string; readonly source: AutomationSourceRecord; readonly intervalMs: number; readonly run: () => Promise | string | void; } export interface WatcherRegistryOptions { readonly storePath?: string | undefined; readonly featureFlags?: FeatureFlagReader | undefined; /** * `watchers.recoveryWindowMinutes`, read at each use. * * A getter rather than a number for the same reason `PairingTokenManager` * takes `maxPaired` as one: the registry outlives a config edit, and a * captured number would mean the operator's new window applied at the next * process start instead of the next restore. */ readonly recoveryWindowMinutes?: (() => number) | undefined; } export declare class WatcherRegistry { private readonly watchers; private readonly timers; private readonly inFlight; private readonly storePath; private runtimeDispatch; private runtimeBus; private readonly featureFlags; private readonly readRecoveryWindowMinutes; private loaded; constructor(options?: WatcherRegistryOptions); /** * How far back a restart will still try to catch up, in milliseconds. * * A non-finite or negative value, which only a hand-edited settings file * produces, since `ConfigManager.set()` holds the key to 0…1440, reads as the * shipped default rather than as "never catch up", because an unreadable number * should not silently switch a recovery behaviour off. */ private recoveryWindowMs; private isEnabled; private requireEnabled; private clearTimers; attachRuntime(config: { readonly runtimeStore?: RuntimeStore | null | undefined; readonly runtimeBus?: RuntimeEventBus | null | undefined; }): void; registerWatcher(input: RegisterWatcherInput): WatcherRecord; registerPollingWatcher(input: RegisterPollingWatcherInput): WatcherRecord; list(): WatcherRecord[]; getWatcher(id: string): WatcherRecord | null; startWatcher(id: string): WatcherRecord | null; /** * Stop every interval this registry started, without touching persisted state. * * Deliberately NOT `stopWatcher()` in a loop: that call runs * `requireEnabled()`, which throws when the watcher-framework gate is off, and * it rewrites each record to `state: 'stopped'`. Neither is wanted at * shutdown, a daemon whose watchers were running must find them running * again when it restarts, and a clean shutdown must not depend on a feature * gate being on. This releases process resources and nothing else. * * Idempotent. */ dispose(): void; stopWatcher(id: string, reason?: string): WatcherRecord | null; runWatcherNow(id: string): Promise; removeWatcher(id: string): boolean; private ensureLoaded; /** * `watchers.recoveryWindowMinutes`, the restart half of the recovery story. * * A watcher that was running when the process stopped is re-armed above, but * re-arming alone means it does nothing until its first interval tick: on a * 30-minute poller, a restart used to cost half an hour of blindness on top of * however long the daemon was down. `startWatcher` has always run once * immediately for exactly that reason; the restore path never did. * * The window bounds that immediate catch-up. When the gap since the last * heartbeat is inside it, the watcher runs at once and picks up what it missed. * When the gap is longer, an immediate run would be reading a source whose * events the checkpoint can no longer bracket, so the watcher waits for its * normal tick and the skipped catch-up is stated in the log rather than left * for someone to infer from a hole in the heartbeats. A window of 0 means * "never catch up on restart", which is the honest floor of the key's range. */ private catchUpAfterRestart; private normalizeRecord; private runWatcher; private buildRunStrategy; /** * Write the snapshot and refresh the runtime store's copy of every record. * * Every mutation here persists, and `list()` persists too, which the fleet * registry's coalesced tick calls on a timer, from a callback with nothing * above it. So this must not be able to throw: a failed snapshot write is * logged loudly with the path and errno by the store and then dropped, the * in-memory registry carries on as the source of truth, and the next persist * retries. Watcher state rebuilds from live registrations on any load, so * losing a snapshot costs nothing that killing the process would not cost a * great deal more. */ private persist; } //# sourceMappingURL=registry.d.ts.map