From 51b5dbd51da93d7893192124a6f1b906c89447ab Mon Sep 17 00:00:00 2001 From: rain Date: Sat, 22 Aug 2026 22:36:23 -0400 Subject: [PATCH] pi: drop herdr-managed extension from source --- .chezmoiignore.tmpl | 4 + dot_pi/agent/extensions/herdr-agent-state.ts | 257 ------------------- 2 files changed, 4 insertions(+), 257 deletions(-) delete mode 100644 dot_pi/agent/extensions/herdr-agent-state.ts diff --git a/.chezmoiignore.tmpl b/.chezmoiignore.tmpl index c4119a8..3f7411e 100644 --- a/.chezmoiignore.tmpl +++ b/.chezmoiignore.tmpl @@ -23,3 +23,7 @@ README.md .config/herdr/*.log .config/herdr/.plugins.lock .config/herdr/*.sock + +# herdr installs and updates its agent integrations itself — never manage via chezmoi +.pi/agent/extensions/herdr-* +.omp/agent/extensions/herdr-* diff --git a/dot_pi/agent/extensions/herdr-agent-state.ts b/dot_pi/agent/extensions/herdr-agent-state.ts deleted file mode 100644 index 67bc418..0000000 --- a/dot_pi/agent/extensions/herdr-agent-state.ts +++ /dev/null @@ -1,257 +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=8 -// @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, - }), - }); -} - -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) => { - // TUI only: RPC/JSON/print modes are headless (no PTY herdr can display), - // and RPC still reports hasUI=true, so mode is the reliable gate. - if (ctx?.mode !== "tui") { - 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(); - }); -}