/** * stream-watchers.ts, watching the output of a long-lived command. * * The pipeline, in order: split into lines -> keep lines matching `match` and * drop lines matching `exclude` -> suppress repeats of a line already seen * inside the dedup TTL -> push onto a BOUNDED queue -> emit a batch once * `batchLines` have accumulated or `batchIntervalMs` has elapsed. * * Two properties matter more than the rest: * * An agent is invoked only after a match. A stream watcher tailing a busy log * must not start an agent turn per line, so batching and dedup are part of * the watcher rather than something the caller is trusted to add. * * The queue is bounded and every drop is counted. When a log outruns the * consumer the oldest entries go, and the drop count is reported on the * trigger record, a silent drop would make the watcher look healthy while it * was losing exactly the lines it exists to catch. * * The processor below is pure and synchronous: feed it text, ask it for * batches. That makes the whole matching/batching/dedup story testable without * spawning a process. */ import type { RegexExtract, StreamTriggerSpec } from './types.js'; export interface StreamBatch { readonly at: number; readonly lines: readonly string[]; /** Lines dropped by the bounded queue since the previous batch. */ readonly dropped: number; /** Lines suppressed by the dedup TTL since the previous batch. */ readonly deduped: number; } export interface StreamProcessorOptions { readonly match: RegexExtract; readonly exclude?: RegexExtract | undefined; readonly batchLines: number; readonly batchIntervalMs: number; readonly queueLimit: number; readonly dedupTtlMs?: number | undefined; } export declare const DEFAULT_STREAM_BATCH_LINES = 25; export declare const DEFAULT_STREAM_BATCH_INTERVAL_MS = 1000; export declare const DEFAULT_STREAM_QUEUE_LIMIT = 1000; export declare function resolveStreamOptions(spec: StreamTriggerSpec, defaults?: { readonly batchLines?: number | undefined; readonly batchIntervalMs?: number | undefined; readonly queueLimit?: number | undefined; }): StreamProcessorOptions; export declare class StreamLineProcessor { private readonly matchRegex; private readonly excludeRegex; private readonly options; private carry; private queue; private dedupMarks; private droppedSinceBatch; private dedupedSinceBatch; private totalDropped; private totalMatched; private firstQueuedAt; constructor(options: StreamProcessorOptions); /** Lines dropped by the bounded queue over the watcher's whole lifetime. */ get droppedTotal(): number; /** Lines that matched over the watcher's whole lifetime. */ get matchedTotal(): number; get pending(): number; /** * Feeds a chunk of stream output. Partial trailing lines are carried to the * next chunk, so a line split across two reads is still matched once, whole. */ push(chunk: string, now: number): void; /** Flushes any partial trailing line, call when the stream closes. */ finish(now: number): void; private acceptLine; private pruneDedup; /** * Returns a batch when one is ready, either the batch is full, or a partial * batch has been waiting longer than `batchIntervalMs`. Returns null * otherwise, so a caller can poll on a timer without emitting empty turns. */ takeBatch(now: number, force?: boolean): StreamBatch | null; } /** * The default agent prompt for a stream match. Says what matched and what was * lost, because a batch that silently omits dropped or deduplicated lines would * let an agent reason from a partial picture without knowing it. */ export declare function renderStreamPrompt(batch: StreamBatch, label: string, pattern: string): string; //# sourceMappingURL=stream-watchers.d.ts.map