diff options
| -rw-r--r-- | config/pi/extensions/herdr-session-name.ts | 84 |
1 files changed, 84 insertions, 0 deletions
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<void> { + 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<void> { + 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); + } + }); +} |
