/** * @fileoverview TTL cache for parsed task operations envelopes. * * Two responsibilities: * 1. Don't spawn the host CLI on every page request. * 2. Coalesce concurrent requests for the same task id. * * Errors from the source are NOT cached — a transient CLI failure * shouldn't poison the entry for the rest of the TTL window. * * v0.8.10+: the cache now also enforces a hard `maxEntries` cap. When * the cache exceeds the cap, the eviction sweep drops the *oldest* * entries that are also past their TTL. This keeps the heap footprint * bounded in long-lived hosts (see Task #2537 — roy-agent 4 GiB OOM). */ import type { CachedEnvelope, TaskOperationsEnvelope, TaskOperationsSource } from "./cli-tasks-adapter.js"; export interface OperationsCacheSource { (taskId: number): Promise; } export interface OperationsCacheOptions { source: OperationsCacheSource | TaskOperationsSource; /** Time-to-live in milliseconds. Default 5000. */ ttlMs?: number; /** * v0.8.10+: hard cap on the number of cached entries. Past this limit, * the cache sweeps the oldest entries that are *also* past their TTL * before inserting a new one. Default 256. Set to `Infinity` to keep * the unbounded behavior (not recommended in long-lived hosts). */ maxEntries?: number; /** Optional: clock for tests. */ now?: () => number; } export declare class OperationsCache { private readonly entries; private readonly ttlMs; private readonly source; private readonly now; private readonly maxEntries; constructor(opts: OperationsCacheOptions); /** * Fetch (or read from cache). Concurrent calls for the same id coalesce. * `allowStale: true` returns the cached value past TTL but triggers a * background refresh — used for active task polling. */ get(taskId: number, opts?: { allowStale?: boolean; }): Promise; /** Force the next call to refetch. */ invalidate(taskId: number): void; /** Clear every entry. */ clear(): void; /** Number of cached entries (test introspection). */ size(): number; /** * Sweep entries that are past their TTL once we exceed `maxEntries`. * * Insertion order is preserved by `Map`, so the first keys we iterate * are the oldest. We drop only stale entries — fresh ones survive even * under heavy churn so a hot task id never loses its cache hit. * * v2.0.5 (Task #2700): the previous threshold `size <= maxEntries` * leaked one extra entry per cycle (oscillation between `maxEntries` * and `maxEntries + 1`). The fix: evict until `size < maxEntries` * so the upcoming `entries.set()` never pushes the cache past the * cap. We prefer stale entries; if all entries are fresh we drop the * oldest one (Map insertion order). */ private evictStale; private refresh; } //# sourceMappingURL=operations-cache.d.ts.map