/** * Waiting for a herdr agent to settle, and reading what it printed on the way. * * Split out of `src/executors/run-herdr.ts` when that file hit **404 of the 400-line ceiling** adding ADR-0032's output * polling. The seam was named in the plan before it was needed, and it is a real one: this module is about * *observing* an agent, `run-herdr.ts` is about *starting and cleaning up after* one. Nothing here creates or * destroys anything. * * The two facts it is built on were measured against real herdr 0.7.5 (probe `g16-herdr`) and both are * counter-intuitive enough to be worth the module comment: `agent wait --until idle` matches the state the * agent was **already** in, and `agent read` is the one command that does **not** return a JSON envelope. */ import { type HerdrExec } from "./herdr-cli.ts"; import { herdrSessionReference } from "../governance/native-session.ts"; /** * What `waitForSettled` needs from a run request. * * Declared here rather than importing `HerdrRunRequest`, which would make the two modules mutually dependent * for no benefit. `HerdrRunRequest` satisfies it structurally, so the call site needs no adapter. */ export interface PollTarget { /** herdr agent name. */ name: string; nativePaneId?: string; onSessionReference?: (reference: NonNullable>) => void; signal?: AbortSignal; /** * The pane's last few lines, re-reported on every poll — a SNAPSHOT, not a stream (ADR-0032). * * The consumer must **replace** what it holds rather than append. `agent read` returns a snapshot of a * bounded terminal, and treating it as append-only is what produced an 89,000× amplification; see * `tailLines`. */ onSnapshot?: (lines: string[]) => void; /** Reuses already-requested pane bytes; never adds an observation-only RPC. */ onObservation?: (bytes: Uint8Array) => void; /** How many lines the display wants. Bounds the per-poll cost regardless of how big the pane is. */ snapshotLines?: number; /** Poll cadence override. Exists so tests do not wait `POLL_INTERVAL_MS` per state transition. */ pollIntervalMs?: number; /** * Inactivity bound (PR 3e). Activity on this executor is a change in the pane's text or in what `activityProbe` * returns (the child's pi session file). Undefined means the wall-clock deadline alone applies. */ idleTimeoutMs?: number; activityProbe?: () => Promise | string | number | undefined; } /** The pre-prompt sequence from the native start reply; absent is unknown, never zero. */ export declare function observeHerdrSession(agent: unknown, pane: string | undefined, sink: PollTarget["onSessionReference"]): void; export declare function seqOf(result: Record | undefined): number; /** How often to poll `agent get` while waiting for the child to settle. */ export declare const POLL_INTERVAL_MS = 750; /** * A prompt accepted from a terminal state must produce an observable lifecycle change promptly. * * Herdr 0.8's `agent prompt --wait` documents this same five-second bound. We retain polling because it * supplies the bounded live pane snapshots, but refuse rather than holding a parent for the whole child timeout when * the only reported state remains the pre-prompt terminal state. That is a detector/integration failure, not * evidence that a child settled. */ export declare const FRESH_LIFECYCLE_TIMEOUT_MS = 5000; /** Lines of pane tail reported per poll. Matches the status block's own tail, so nothing is fetched unused. */ export declare const DEFAULT_SNAPSHOT_LINES = 3; /** * Wait for the child to settle, without accepting the state it was already in. * * **R-33, measured.** `herdr agent wait --until idle` called right after `agent prompt` returned * *immediately*, matching the agent's **pre-existing** idle state with `state_change_seq` unchanged — a * reply indistinguishable from a completed run. For fan-out that is not an inconvenience but a * correctness bug: an orchestrator would "collect" N children that never ran and merge N empty results * into a confident summary (R-03 with a new cause). * * So this polls `agent get` and requires **both** that the status is terminal **and** that * `state_change_seq` has advanced past the value observed before prompting. `agent wait` is deliberately * not used at all: its contract cannot express "settled *after* this point". */ /** * Establish the bundled Pi lifecycle reporter's idle baseline before prompt dispatch. * * `screen_detection_skipped` is Herdr's reported fact that a full lifecycle authority owns this pane. * Waiting for it prevents the reporter's asynchronously queued initial idle report from becoming a * post-prompt sequence advance. This is not completion: no prompt has been sent at this point. */ export declare function waitForLifecycleBaseline(exec: HerdrExec, request: PollTarget, deadline: number): Promise<{ before?: number; aborted?: boolean; spawnError?: string; }>; export declare function waitForSettled(exec: HerdrExec, request: PollTarget, before: number, deadline: number, maxOutputBytes: number): Promise<{ status?: string; timedOut?: boolean; idle?: true; aborted?: boolean; spawnError?: string; }>; /** * The last `keep` non-blank lines of a pane snapshot — what the display actually needs. * * **This replaces a `newSuffix` diff, and the replacement is a correction rather than a tune-up.** The old * design treated `agent read` as a *stream* and tried to report only what was new, by testing whether the new * text extended the old. That is wrong about the substrate: `agent read` returns a **snapshot of a bounded * terminal**, and a snapshot is not an append-only log. Two ordinary things break the prefix test forever — * the pane **scrolling** (its top lines are gone, so the new text is not an extension of the old) and * `readPane` **truncating to the tail** past `maxOutputBytes` (each read is a different window of a growing * buffer). Once either happens, every poll reported the whole buffer. * * Measured before the fix: **51 MiB streamed for ~600 bytes of real output — 89,000× amplification** in 37 * seconds, per child, with a scrolling pane also delivering the same real lines three times each. The old * docstring named that exact failure as the thing it prevented. * * So the herdr path now reports a **bounded snapshot** and the consumer *replaces* rather than appends. There * is no diff to get wrong, the per-poll cost is `keep` lines regardless of buffer size, and a scrolling pane * simply shows its current tail — which is what a human looking at that pane would see. */ export declare function tailLines(snapshot: string, keep: number): string[]; /** * Read the pane's contents. * * `agent read` is the ONE command that does not return herdr's JSON envelope — it writes the terminal's * text straight to stdout. Running it through `parseReply` turned every successful read into * "unparseable herdr reply", i.e. reported the child's actual answer as a failure to read it. Found by the * end-to-end run; the unit fake had been written to the envelope shape and so agreed with the bug. * * A JSON envelope is still accepted first, because an `error` reply here IS JSON and must not be mistaken * for terminal output. * * **`readFailed` is separate from `text`, and that separation is the fix for an R-03 defect.** A failed read * used to return its own diagnostic *as* `text` — so `runHerdrPane` returned * `[grants] could not read the agent pane: pane is gone` **as the child's answer, with `code: 0`**, and the * orchestrator read a failure message as a completed sub-agent's report. Measured. It mattered little when this * ran once per child; ADR-0032 made it run on every poll, up to 800 times for a ten-minute child, so a * transient failure went from unlikely to expected. The caller must now decide, and it cannot do so by * inspecting a string. */ export declare function readPane(exec: HerdrExec, name: string, maxOutputBytes: number): Promise<{ text: string; truncated: boolean; readFailed?: string; }>; //# sourceMappingURL=herdr-poll.d.ts.map