reporters/herdr

Source: src/reporters/herdr.ts

The herdr reporter: one split per subagent, showing it work.

A herdr pane cannot host an in-process subagent - there is no process and no TTY to attach. So the pane hosts a client of the mirror instead: the pane runs pane/main.ts, which attaches to the subagent by id, draws the session with pi’s own components and sends the keyboard back. The pane is then ours, which is also why we can report agent state on it: the main pane’s state already belongs to herdr’s own pi integration, and two sources cannot own one pane.

The board is the one pane that still displays a stream we write: nobody works in it, nothing is typed to it, and its lines are traffic.ts’s so the console and the pane read the same. It gets a file and tail -f.

Opening either takes three calls, because herdr has none that does all three: pane.split makes the pane and hands back its id, pane.rename puts the name on it, and pane.send_input types the command into the shell the split started. agent.start sounds like the call that opens one and is not: it puts a recognised agent into a pane that already exists.

createHerdrReporter

function

export function createHerdrReporter(options: HerdrOptions = {}): EventListener | undefined { /* … */ }

Builds the herdr reporter, or undefined when herdr is not there.

undefined is the whole fallback protocol: the caller drops to another reporter without an error and without a warning. Nobody wants a message telling them herdr is not running when they never asked for herdr.

createHerdrReporterWith

function

export function createHerdrReporterWith(send: HerdrSend, options: HerdrOptions = {}): EventListener { /* … */ }

The reporter proper, with the transport already chosen. Exported for tests.

HerdrOptions

type

export type HerdrOptions = {
	/** Where the split opens. Defaults to `"right"`. */
	split?: "right" | "down";
	/**
	 * The pane ours open beside. Defaults to the one pi was launched in.
	 *
	 * Named rather than left out: with no target herdr splits whichever pane is
	 * focused, and that can belong to another client, or to the user reading
	 * something else in the next tab.
	 */
	pane?: string;
	/** Steal focus when a split opens. Defaults to `false` - you are still typing. */
	focus?: boolean;
	/** Transport. Injection point for tests; defaults to the real socket. */
	send?: HerdrSend;
	/** Directory for the board's live log. Defaults to a per-run temp directory. */
	dir?: string;
	/**
	 * The mirror's socket, which a subagent's pane attaches to. Defaults to this
	 * process's, started on the first pane. Tests name one that nothing listens
	 * on, so a reporter test opens no socket.
	 */
	mirror?: string;
	/**
	 * Open a split for **every** subagent, whatever each one asked for.
	 *
	 * `openInHerdr` is opt-in per subagent so a fan-out of twenty branches
	 * cannot carpet the screen by accident. This is the other regime, asked for
	 * explicitly: watch everything. It belongs to the reporter and not to the
	 * core, because "who gets a pane" is a display decision - the workflow runs
	 * identically either way.
	 *
	 * Off unless asked for. `/herdr on` is the session-wide switch inside pi.
	 */
	all?: boolean;
};

How the herdr reporter behaves: where splits open, and for whom.