/** * Close herdr panes this process opened but never got to close. * * **As of ADR-0032 a pane belongs to the AGENT RUN, not to the tool call.** `runHerdrPane` closes a tab only for * a child that did *not* settle — closing the tab is the only kill herdr offers, so a child still working must * lose its pane. A child that answered keeps its pane so a human can read it, and this module reaps those: at * `agent_settled` (async) and at process `exit` (sync backstop). * * The gap that remains is the one this module was created for. A pi session **killed outright** runs no `exit` * handler, so it leaves one pane per tracked child, and probe `g16-herdr` records that an orphaned pane is * not trivially closable afterwards. R-62 was re-rated L×L → M×L when ADR-0031 made panes the default path. * * **Registered on `exit` only, deliberately — not on SIGINT or SIGTERM.** That is the part worth reading, * because the obvious fix is the dangerous one. Adding a signal listener *suppresses Node's default * termination*, so a library that adds one takes over an application-level decision it has no standing to * make: pi uses SIGINT to interrupt a turn, and a listener here that re-raised would turn "cancel this * delegation" into "exit pi". A governance package quietly changing the host's interrupt semantics is a * worse defect than the leak it fixes, and it would land on **every** session rather than the opt-in ones. * * So the coverage is exact and stated rather than implied: * * - **Covered:** normal exit, `process.exit()`, an uncaught exception that unwinds to the default handler. * - **NOT covered:** SIGKILL, and SIGTERM/SIGINT where nothing else in the process has installed a * listener. Node terminates without running `exit` handlers in those cases, by design. A pane can still * be orphaned there, and `herdr tab close ` is the manual remedy. * * Everything here is **synchronous**, because an `exit` handler is: a promise scheduled there never runs. */ import { type HerdrExec } from "./herdr-cli.ts"; /** * How many panes may be open at once — ADR-0032. * * **Derived from `MAX_CHILDREN_PER_CALL` rather than declared, so the two cannot drift.** The bound is not * decoration: `delegate_all` is capped per call and the fan-out budget bounds a subtree, but a plain blocking * `delegate` spends **nothing** from that budget by design — so thirty sequential `delegate` calls in one agent * run would otherwise hold thirty panes open until it settled. */ export declare const MAX_OPEN_PANES = 8; export interface OpenPane { /** herdr tab id — what `tab close` takes. */ tab: string; /** herdr agent name, for diagnostics. There is no `agent stop`, so nothing acts on it — see `closePane`. */ name: string; /** Staged system-prompt directory, removed with the pane it belonged to. */ promptDir?: string; /** * True once the child has answered and stopped working. * * **The trim may only close a pane with this set**, and that is a correctness rule rather than politeness. pi * executes tool calls in **parallel** by default, and a plain `delegate` spends nothing from the fan-out * budget — so one assistant message can hold `delegate_all(8)` *and* a `delegate`, and the ninth pane's trim * used to close the oldest **live sibling**. Measured: two `delegate_all(8)` in one message killed **8 of 16 * children mid-work**, each reported as "could not be started" with its partial output discarded, while the * ledger recorded all sixteen as provisioned. ADR-0032 argued the cap from *sequential* delegates only. */ settled?: boolean; /** * The operator asked to keep this tab (`PI_DADDY_HERDR_KEEP_PANE=1`), so no sweep may close it. * * It is registered anyway, and only for its `promptDir`: that temp dir was otherwise unreachable by either * sweep and leaked one directory per kept pane, forever. So `exit` removes the staged prompt and leaves the * tab, which is exactly what the flag promises. */ keepTab?: boolean; } /** A run has finished with its pane: the trim may now reclaim it. */ export declare function markPaneSettled(tab: string): void; /** A run has opened a pane. Idempotent per tab, and installs the exit hook on first use only. */ export declare function trackPane(pane: OpenPane): void; /** A run closed its own pane the normal way. */ export declare function untrackPane(tab: string): void; /** How many panes are currently outstanding — for tests and for `/grants`. */ export declare function openPaneCount(): number; /** * Close every outstanding pane and return the tab ids closed. * * Exported and parameterised so it can be tested without herdr installed: the exit hook is unreachable from * a test (registering a real `exit` handler would run during the test runner's own shutdown), so the hook is * one line and *this* is where the behaviour lives. * * Failures are swallowed per pane rather than per call — one pane herdr will not close must not strand the * other seven, and nothing at exit has anywhere to report to anyway. */ export declare function reapOpenPanes(syncExec?: (args: string[]) => void, now?: () => number): string[]; /** * Close every outstanding pane — the `agent_settled` path (ADR-0032). * * **A separate function from `reapOpenPanes` rather than a shared implementation**, and the reason is not * style. The sync one is `execFileSync` with a six-second total budget *by necessity*: an `exit` handler cannot * await. Running that at `agent_settled` would freeze pi for up to six seconds **every time the operator gets * their prompt back** — turning a feature that exists to make work visible into a stall. * * Both drain the same `Map`, keyed by tab id, so a double close is impossible and a pane herdr refused stays * registered for whichever sweep runs next. * * Sequential rather than concurrent: a fan-out's panes are at most `MAX_OPEN_PANES`, and eight `tab close` * calls in parallel against one herdr server buys nothing worth the burst. */ export declare function reapOpenPanesAsync(exec?: HerdrExec): Promise; /** * Keep at most `MAX_OPEN_PANES` open, closing the oldest first. * * The `Map`'s insertion order **is** the age order, so no timestamp is needed — and that is why `trackPane` * must never re-`set` an existing tab, which would move it to the back of the queue and make an old pane look * new. * * Whatever it closes is returned so the caller can **say so** rather than silently dropping a pane the operator * was reading (R-48's rule, applied to a display). */ export declare function trimOpenPanes(exec?: HerdrExec): Promise; //# sourceMappingURL=pane-reaper.d.ts.map