import type { HexisMcpConfig } from './config.js'; /** * The least time between two checks. A burst of activity — a `call_tool_chain` * fanning out into a dozen calls, a client re-listing right after a * notification — collapses onto one digest read; the second and later calls in * the window see the answer the first one got. * * It is also a CEILING ON STALENESS for a connection in use, which is what * sets its size. A check that runs just before a commit reads the old catalog * and opens a fresh window, so the change cannot be noticed until the window * expires; add the digest read and the re-registration and that is the whole * delay a person working on that connection experiences. At five seconds this * alone put the first sighting at ~7.6s against a deployment that had the * manual in ~1s. Two seconds keeps a busy connection to one small read per * window while a person who commits a manual and keeps calling tools sees it * on the next call after the window. * * The promise this window has to fit inside is ten seconds from the commit, * and it is not the only term spending it: the deployment needs its own * 2-3.5s to land the commit and drop its registry before there is anything * here to notice. `catalog-watch.test.ts` keeps that arithmetic, all three * terms of it, so shrinking the budget is a conversation with a failing test. */ export declare const CATALOG_CHECK_MIN_INTERVAL_MS = 2000; /** A running checker. `stop()` is idempotent and never throws. */ export interface CatalogCheck { /** * Compare the deployment's catalog to the one this process serves, and * refresh when they differ. Resolves when the check — and any refresh it * owed — has settled, so a caller that wants a FRESH answer (a `tools/list`) * can await it, and a caller that does not (a finished tool call) can let it * run. Never rejects: every failure is logged and carried to the next check. * * Throttled from the last check's START, not its finish: a call within * {@link CATALOG_CHECK_MIN_INTERVAL_MS} of the last check STARTING, or while * one is still running, joins that check instead of asking again. Counting * from the start keeps the window a cadence a budget can be computed from — * a refresh runs inside a check, so counting from the finish would add the * re-registration's seconds to every window. */ check(): Promise; stop(): void; } export interface CatalogCheckOptions { config: HexisMcpConfig; /** * The revision this process's current toolset was built from, if it is * known. When absent — a startup whose revision read failed — the first * successful check is treated as a CHANGE rather than as a baseline: nobody * can tell whether a commit landed between discovery and that check, and * one refresh of a catalog that turns out to be unchanged is much cheaper * than a tool that stays invisible until someone restarts the server. */ initialRevision?: string | null; /** The least time between two checks; `0` means every call checks. */ minIntervalMs?: number; /** * Called once per observed change, never concurrently with itself. A * rejection is logged and the checker continues, but the revision is NOT * recorded as applied — the change is still owed, so the next check attempts * it again until one succeeds. */ onChanged: (revision: string) => void | Promise; /** Where a notice goes. Defaults to stderr — the only stream this process may write. */ log?: (message: string) => void; /** * The clock the throttle reads. Monotonic by default (`performance.now`): * the wall clock can be set back — a sleep, an NTP correction, a VM * migration — and a throttle stamped from it would then refuse every check * until the wall caught up with a timestamp from the future. Tests pass * their own. */ now?: () => number; } /** * Create the checker. Nothing is asked until the first `check()`. */ export declare function createCatalogCheck(options: CatalogCheckOptions): CatalogCheck; //# sourceMappingURL=catalog-watch.d.ts.map