import { Type } from "typebox"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; const MAX_SCOPE_NAME_LENGTH = 60; interface CommandResult { readonly code: number; readonly stdout: string; readonly stderr: string; } function normalizeScopeName(rawName: string): string { const name = rawName.trim(); if (!name) { throw new Error("Scope name must not be empty."); } if (name.length > MAX_SCOPE_NAME_LENGTH) { throw new Error(`Scope name must be at most ${MAX_SCOPE_NAME_LENGTH} characters.`); } if (/\p{Cc}/u.test(name)) { throw new Error("Scope name must be a single printable line."); } return name; } function parseTabId(stdout: string): string { let payload: unknown; try { payload = JSON.parse(stdout); } catch { throw new Error("Herdr returned invalid JSON for the current pane."); } const tabId = (payload as { result?: { pane?: { tab_id?: unknown } }; })?.result?.pane?.tab_id; if (typeof tabId !== "string" || !tabId.trim()) { throw new Error("Herdr did not return a tab ID for the current pane."); } return tabId; } function parseTabLabel(stdout: string): string { let payload: unknown; try { payload = JSON.parse(stdout); } catch { throw new Error("Herdr returned invalid JSON for the current tab."); } const label = (payload as { result?: { tab?: { label?: unknown } }; })?.result?.tab?.label; if (typeof label !== "string") { throw new Error("Herdr did not return a label for the current tab."); } return label; } function assertCommandSucceeded(command: string, result: CommandResult): void { if (result.code === 0) return; const detail = result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`; throw new Error(`${command} failed: ${detail}`); } function hasHerdrEnvironment(env: NodeJS.ProcessEnv = process.env): boolean { return ( env.HERDR_ENV === "1" && Boolean(env.HERDR_SOCKET_PATH?.trim()) && Boolean(env.HERDR_PANE_ID?.trim()) ); } export default function herdrRenameExtension(pi: ExtensionAPI): void { if (!hasHerdrEnvironment()) return; let tabNeedsRename = false; pi.on("session_start", async () => { if (!hasHerdrEnvironment()) return; const paneId = process.env.HERDR_PANE_ID!.trim(); try { const paneResult = await pi.exec("herdr", ["pane", "get", paneId]); assertCommandSucceeded("herdr pane get", paneResult); const tabId = parseTabId(paneResult.stdout); const tabResult = await pi.exec("herdr", ["tab", "get", tabId]); assertCommandSucceeded("herdr tab get", tabResult); tabNeedsRename = /^\d+$/.test(parseTabLabel(tabResult.stdout).trim()); } catch { tabNeedsRename = false; } }); pi.on("before_agent_start", async () => { if (!tabNeedsRename) return; return { message: { customType: "pi-herdr-rename", content: "Current Herdr tab still has a default numeric name. Call rename_herdr_tab with a concise primary-work-scope name before doing other work.", display: false, }, }; }); pi.registerTool({ name: "rename_herdr_tab", label: "Rename Herdr Tab", description: "Rename the current Herdr tab to the agent's primary work scope.", promptSnippet: "Rename the current Herdr tab when primary work scope changes", promptGuidelines: [ "Call rename_herdr_tab once before starting work when the primary task scope changes. Do not call rename_herdr_tab for follow-ups, subtasks, status updates, or continuations.", ], parameters: Type.Object({ name: Type.String({ description: `Concise primary work scope, at most ${MAX_SCOPE_NAME_LENGTH} characters`, minLength: 1, maxLength: MAX_SCOPE_NAME_LENGTH, }), }), async execute(_toolCallId, params, signal) { const name = normalizeScopeName(params.name); if (!hasHerdrEnvironment()) { throw new Error("Herdr environment is no longer active; cannot rename the current tab."); } const paneId = process.env.HERDR_PANE_ID!.trim(); const paneResult = await pi.exec("herdr", ["pane", "get", paneId], { signal }); assertCommandSucceeded("herdr pane get", paneResult); const tabId = parseTabId(paneResult.stdout); const renameResult = await pi.exec("herdr", ["tab", "rename", tabId, name], { signal }); assertCommandSucceeded("herdr tab rename", renameResult); tabNeedsRename = false; return { content: [{ type: "text" as const, text: `Herdr tab renamed to ${name}.` }], details: { name, paneId, tabId }, }; }, }); }