// adapted from herdr's pi integration (herdr/src/integration/assets/pi/herdr-agent-state.ts) // reports lifecycle state for selesai instead of pi: agent=selesai, source=herdr:selesai // HERDR_INTEGRATION_ID=selesai // HERDR_INTEGRATION_VERSION=2 // @ts-nocheck // // Instance state lives inside the factory, not at module scope. Selesai caches // the extension module per cwd and re-invokes the factory for every session, so // a module-scoped `released`/`queuedState`/`sendInFlight` from a previous // session (handoff-new, auto-handoff, /new, resume, fork, reload) would make // every successor instance silently drop its state reports and leave the pane // stuck on the last reported state. Only the monotonic reportSeq stays // module-level: Herdr guards pane.report_agent per source by seq and drops // lower-seq reports, so the successor must never restart the counter below the // previous instance's high-water mark. 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:selesai"; 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; }; // Monotonic across all extension instances in this process. A per-session // restart below the previous instance's high-water mark would be dropped by // Herdr's per-source seq guard. let reportSeq = Date.now() * 1000; function nextReportSeq(): number { reportSeq = Math.max(reportSeq + 1, Date.now() * 1000); return reportSeq; } export default function (pi) { if (!enabled()) { return; } let currentAgentSessionId: string | undefined; let currentAgentSessionPath: string | undefined; let sendInFlight = false; let queuedState: QueuedState | undefined; let activeDrain: Promise = Promise.resolve(); let released = false; let agentActive = false; let blockedCount = 0; let blockedMessage: string | undefined; let lastState: AgentState | undefined; let lastMessage: string | undefined; let rootSession = false; 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: "selesai", 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: "selesai", state, message, seq, }), }); } function queueState(state: AgentState, message?: string): void { if (released) { // The pane was released on quit; a late report would reclaim it and // leave Herdr showing an agent that already exited. return; } queuedState = { state, message, seq: nextReportSeq() }; if (!sendInFlight) { activeDrain = 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) { activeDrain = drainStateQueue(); } } } async function releaseAgent(): Promise { // Stop new reports, drop anything still queued, and wait for the in-flight // send to finish so the release is the last write on the wire; a report // landing after the release would reclaim the pane. released = true; queuedState = undefined; await activeDrain.catch(() => undefined); return sendRequest({ id: `${source}:release:${Date.now()}:${Math.random().toString(36).slice(2)}`, method: "pane.release_agent", params: { pane_id: paneId, source, agent: "selesai", seq: nextReportSeq(), }, }); } 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(); }); pi.on("session_shutdown", async (event) => { if (!rootSession) { return; } if (event?.reason !== "quit") { // new/resume/fork/reload: a successor instance in this same pane // re-reports immediately. Releasing here races that report, and a // release landing after the successor's report clears the pane. // Silence this instance so no stale queued report lands around it. released = true; queuedState = undefined; return; } await releaseAgent(); }); }