From 6a6c5821a0f368a53548f36a0bd63b035b8e1344 Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Mon, 5 Oct 2026 16:29:50 +0300 Subject: pi: the session name in herdr's Agent sidebar herdr labels every pi pane "pi", so with several open the rows cannot be told apart. herdr 0.9.3's socket API has pane.report_metadata, whose display_agent replaces that label without touching lifecycle state; this reports the session name there on start and on every rename, auto-named titles included. Interactive sessions only: a pi -p or subagent run from inside a pane inherits its HERDR_* variables and would otherwise rename the pane after itself. Checked against a fake herdr socket: a print-mode run sends nothing, while the socket accepted a control message. Outside herdr it returns at once. The pi APIs it uses -- ctx.mode "tui", getSessionName, session_info_changed's name -- are as in pi 1.0.0, and link/pi.sh links it in like any tracked extension. From change request e053eb. --- config/pi/extensions/herdr-session-name.ts | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 config/pi/extensions/herdr-session-name.ts diff --git a/config/pi/extensions/herdr-session-name.ts b/config/pi/extensions/herdr-session-name.ts new file mode 100644 index 0000000..63663f1 --- /dev/null +++ b/config/pi/extensions/herdr-session-name.ts @@ -0,0 +1,84 @@ +// Shows the pi session name in herdr's (or tmuxrdr's) Agent sidebar, in place of +// the "pi" label. +// +// herdr's own pi integration (herdr-agent-state.ts, which herdr installs and +// overwrites) reports lifecycle state only. This sits beside it and reports +// display-only pane metadata: `display_agent` replaces the `agent` token in +// sidebar rows. `agent: "pi"` guards it, so it stops applying once the pane no +// longer runs pi. With no session name, the label is cleared back to "pi". +// +// Reported on session start (including /resume, /new and reloads) and whenever +// the name changes (/name, --name, or an extension's pi.setSessionName()). +// TUI sessions only: print, JSON and RPC runs launched from inside a pane (a +// review panel, a subagent) inherit the pane's HERDR_* variables and would +// otherwise rename the pane after themselves. +// +// API: pane.report_metadata, herdr socket API (herdr 0.9.3). + +import net from "node:net"; + +const socketPath = process.env.HERDR_SOCKET_PATH; +const paneId = process.env.HERDR_PANE_ID; +const source = "user:pi-session-name"; + +function enabled(): boolean { + return process.env.HERDR_ENV === "1" && !!socketPath && !!paneId; +} + +function send(request: unknown, timeoutMs = 1000): Promise { + return new Promise((resolve) => { + const endpoint = + process.platform === "win32" ? `\\\\.\\pipe\\${socketPath}` : socketPath!; + const socket = net.createConnection(endpoint); + const finish = () => { + clearTimeout(timer); + socket.destroy(); + resolve(); + }; + const timer = setTimeout(finish, timeoutMs); + timer.unref?.(); + socket.on("error", finish); + socket.on("end", finish); + socket.on("data", finish); + socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`)); + }); +} + +let seq = Date.now() * 1000; + +function report(name: string | undefined): Promise { + const label = name?.trim(); + seq += 1; + return send({ + id: `${source}:${seq}`, + method: "pane.report_metadata", + params: { + pane_id: paneId, + source, + agent: "pi", + seq, + ...(label ? { display_agent: label } : { clear_display_agent: true }), + }, + }); +} + +export default function (pi: any) { + if (!enabled()) { + return; + } + + let active = false; + + pi.on("session_start", async (_event: unknown, ctx: any) => { + active = ctx?.mode === "tui"; + if (active) { + await report(pi.getSessionName()); + } + }); + + pi.on("session_info_changed", async (event: { name?: string }) => { + if (active) { + await report(event?.name); + } + }); +} -- cgit v1.2.3