From be3def4ab84df18d12bf206f30dc41017e0a132f Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 13 Sep 2026 07:13:58 -0400 Subject: [PATCH] fix(pi): avoid duplicate herdr state extension --- .pi/extensions/herdr-agent-state.ts | 287 ---------------------------- 1 file changed, 287 deletions(-) delete mode 100644 .pi/extensions/herdr-agent-state.ts diff --git a/.pi/extensions/herdr-agent-state.ts b/.pi/extensions/herdr-agent-state.ts deleted file mode 100644 index caa50ed..0000000 --- a/.pi/extensions/herdr-agent-state.ts +++ /dev/null @@ -1,287 +0,0 @@ -// installed by herdr -// managed by herdr; reinstalling or updating the integration overwrites this file. -// add custom hooks/plugins beside this file instead of editing it. -// HERDR_INTEGRATION_ID=pi -// HERDR_INTEGRATION_VERSION=6 -// @ts-nocheck - -import net from "node:net"; - -const HERDR_ENV = process.env.HERDR_ENV; -const socketPath = process.env.HERDR_SOCKET_PATH; -const socketEndpoint = - process.platform === "win32" && socketPath ? `\\\\.\\pipe\\${socketPath}` : socketPath; -const paneId = process.env.HERDR_PANE_ID; -const source = "herdr:pi"; - -function enabled() { - return HERDR_ENV === "1" && !!socketPath && !!paneId; -} - -function sendRequestAttempt(request: unknown, timeoutMs: number): Promise { - if (!enabled()) { - return Promise.resolve(true); - } - - return new Promise((resolve) => { - let done = false; - let timeout: ReturnType | undefined; - const finish = (delivered: boolean) => { - if (done) return; - done = true; - if (timeout) { - clearTimeout(timeout); - } - socket.destroy(); - resolve(delivered); - }; - - const socket = net.createConnection(socketEndpoint!); - socket.on("error", () => finish(false)); - socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`)); - socket.on("data", () => finish(true)); - socket.on("end", () => finish(false)); - timeout = setTimeout(() => finish(false), timeoutMs); - timeout.unref?.(); - }); -} - -async function sendRequest(request: unknown): Promise { - if (await sendRequestAttempt(request, 500)) { - return; - } - await sendRequestAttempt(request, 1500); -} - -type AgentState = "working" | "blocked" | "idle"; - -type QueuedState = { - state: AgentState; - message?: string; - seq: number; -}; - -let reportSeq = Date.now() * 1000; -let currentAgentSessionId: string | undefined; -let currentAgentSessionPath: string | undefined; - -function nextReportSeq(): number { - reportSeq += 1; - return reportSeq; -} - -function updateSessionRef(ctx: any): void { - try { - const file = ctx?.sessionManager?.getSessionFile?.(); - currentAgentSessionPath = - typeof file === "string" && file.startsWith("/") ? file : undefined; - } catch { - currentAgentSessionPath = undefined; - } - - try { - const id = ctx?.sessionManager?.getSessionId?.(); - currentAgentSessionId = typeof id === "string" && id.length > 0 ? id : undefined; - } catch { - currentAgentSessionId = undefined; - } -} - -function withSessionRef(params: Record): Record { - if (currentAgentSessionPath) { - return { ...params, agent_session_path: currentAgentSessionPath }; - } - if (currentAgentSessionId) { - return { ...params, agent_session_id: currentAgentSessionId }; - } - return params; -} - -function currentSessionRef(): Record | undefined { - if (currentAgentSessionPath) { - return { agent_session_path: currentAgentSessionPath }; - } - if (currentAgentSessionId) { - return { agent_session_id: currentAgentSessionId }; - } - return undefined; -} - -function reportSession(sessionStartSource?: string): Promise { - const sessionRef = currentSessionRef(); - if (!sessionRef) { - return Promise.resolve(); - } - - return sendRequest({ - id: `${source}:session:${Date.now()}:${Math.random().toString(36).slice(2)}`, - method: "pane.report_agent_session", - params: { - pane_id: paneId, - source, - agent: "pi", - seq: nextReportSeq(), - session_start_source: sessionStartSource, - ...sessionRef, - }, - }); -} - -function sendState(state: AgentState, message?: string, seq = nextReportSeq()): Promise { - return sendRequest({ - id: `${source}:${Date.now()}:${Math.random().toString(36).slice(2)}`, - method: "pane.report_agent", - params: withSessionRef({ - pane_id: paneId, - source, - agent: "pi", - state, - message, - seq, - }), - }); -} - -function releaseAgent(): Promise { - return sendRequest({ - id: `${source}:release:${Date.now()}:${Math.random().toString(36).slice(2)}`, - method: "pane.release_agent", - params: { - pane_id: paneId, - source, - agent: "pi", - seq: nextReportSeq(), - }, - }); -} - -function shouldReleaseOnSessionShutdown(event: any): boolean { - // Pi tears down and rebinds extension runtimes for internal lifecycle actions - // such as /reload, /new, /resume, and /fork. Those do not mean the pane's - // agent process has exited, and releasing hook authority there can suppress - // legitimate reports from the replacement runtime. Only a user/process quit - // should release Herdr's full-lifecycle authority. - const reason = event?.reason; - return reason === "quit"; -} - -let sendInFlight = false; -let queuedState: QueuedState | undefined; - -function queueState(state: AgentState, message?: string): void { - queuedState = { state, message, seq: nextReportSeq() }; - if (!sendInFlight) { - void drainStateQueue(); - } -} - -async function drainStateQueue(): Promise { - if (sendInFlight) { - return; - } - - sendInFlight = true; - try { - while (queuedState) { - const next = queuedState; - queuedState = undefined; - await sendState(next.state, next.message, next.seq); - } - } finally { - sendInFlight = false; - if (queuedState) { - void drainStateQueue(); - } - } -} - -export default function (pi) { - if (!enabled()) { - return; - } - - let agentActive = false; - let blockedCount = 0; - let blockedMessage: string | undefined; - let lastState: AgentState | undefined; - let lastMessage: string | undefined; - let rootSession = false; - - function desiredState() { - if (blockedCount > 0) { - return { state: "blocked" as const, message: blockedMessage }; - } - if (agentActive) { - return { state: "working" as const, message: undefined }; - } - return { state: "idle" as const, message: undefined }; - } - - function publishState(force = false) { - const next = desiredState(); - if (!force && next.state === lastState && next.message === lastMessage) { - return; - } - lastState = next.state; - lastMessage = next.message; - queueState(next.state, next.message); - } - - pi.events.on("herdr:blocked", (data) => { - if (!rootSession) { - return; - } - if (!data?.active) { - blockedCount = Math.max(0, blockedCount - 1); - if (blockedCount === 0) { - blockedMessage = undefined; - } - publishState(); - return; - } - - blockedCount += 1; - blockedMessage = data.label; - publishState(); - }); - - pi.on("session_start", async (event, ctx) => { - if (ctx?.hasUI !== true) { - return; - } - rootSession = true; - updateSessionRef(ctx); - await reportSession(event?.reason); - // A reload can replace this extension mid-run without emitting another agent_start. - agentActive = ctx?.isIdle?.() === false; - publishState(true); - }); - - pi.on("agent_start", (_event, ctx) => { - if (!rootSession) { - return; - } - updateSessionRef(ctx); - void reportSession(); - agentActive = true; - publishState(); - }); - - pi.on("agent_settled", (_event, ctx) => { - if (!rootSession || ctx?.isIdle?.() !== true) { - return; - } - - agentActive = false; - publishState(); - }); - - pi.on("session_shutdown", async (event) => { - if (!rootSession) { - return; - } - if (shouldReleaseOnSessionShutdown(event)) { - await releaseAgent(); - } - }); -}