/** * @module infra/narration-governor * * ONE budget for the koi's spoken status. "Never work in silence" used to be * implemented as several independent speakers — the model's own progress * lines, the synthetic tool-beat narrator, the silence heartbeat, executor * hands-on-screen lines — each with its own throttle (or none), all sharing * one voice. On a long task the person heard a content-free reassurance every * few seconds, over and over, until the answer finally landed. * * The governor is the single admission gate they all pass through: * * - A DECAYING cadence: early in a run updates may come every few seconds * (that's when reassurance is worth something); the longer the run goes, * the wider the mandatory quiet between lines. * - PRIORITIES: lines that carry real information (the model's own progress * milestones, executor actions) get a short flat floor; synthetic * reassurance rides the decaying gap. * - INFORMATION dedup: a candidate too similar to something recently spoken * is dropped no matter who produced it — paraphrased "working on it" * counts as a repeat even when the words differ. * * The device voice uses the shared singleton; a web voice call creates its * own instance so the two audio channels never eat each other's budget. */ export type NarrationPriority = "milestone" | "hands" | "synthetic"; /** * Content-word Jaccard similarity — cheap, language-dumb, and good enough to * catch "still putting the report together" vs "putting that report together". */ export declare function narrationSimilarity(a: string, b: string): number; export declare function isNearDuplicate(a: string, b: string): boolean; export interface NarrationGovernor { /** Arm the decay clock (idempotent while runs remain active). */ noteRunStarted(): void; noteRunEnded(): void; /** Remember the person's latest ask, for context-rich status lines. */ noteTask(text: string): void; /** The latest ask, trimmed for prompt use ("" when none). */ taskHint(): string; /** Current mandatory quiet for SYNTHETIC lines (decays over the run). */ syntheticGapMs(): number; /** Time since the last ADMITTED line — lets producers pre-check cheaply * (skip a status-model call that the gate would reject anyway). */ msSinceSpoken(): number; /** Admit (and record) a spoken line, or reject it. */ admit(priority: NarrationPriority, text: string): boolean; } export declare function createNarrationGovernor(now?: () => number): NarrationGovernor; /** The device voice's shared budget — every narration-bus producer uses it. */ export declare const deviceNarrationGovernor: NarrationGovernor;