// @generated by scripts/build-runtime.mjs; do not edit. // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader. // src/herdr-agent-state.ts import path from "node:path"; // src/herdr-client.ts import net from "node:net"; var MAX_HERDR_FRAME_BYTES = 1024 * 1024; var MAX_PENDING_SUBSCRIPTION_EVENTS = 1024; var NdjsonFrames = class { buffer = Buffer.alloc(0); push(chunk) { this.buffer = Buffer.concat([this.buffer, chunk]); if (this.buffer.length > MAX_HERDR_FRAME_BYTES && this.buffer.indexOf(10) < 0) { throw new Error("Herdr response frame exceeded the size limit"); } const frames = []; while (true) { const newline = this.buffer.indexOf(10); if (newline < 0) break; if (newline > MAX_HERDR_FRAME_BYTES) { throw new Error("Herdr response frame exceeded the size limit"); } const line = this.buffer.subarray(0, newline); this.buffer = this.buffer.subarray(newline + 1); if (line.length === 0) continue; try { frames.push(JSON.parse(line.toString("utf8"))); } catch { throw new Error("Herdr returned invalid JSON"); } } if (this.buffer.length > MAX_HERDR_FRAME_BYTES) { throw new Error("Herdr response frame exceeded the size limit"); } return frames; } }; function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function abortReason(signal) { return signal.reason instanceof Error ? signal.reason : new DOMException("Herdr request aborted", "AbortError"); } var HerdrApiError = class extends Error { code; constructor(error) { super(`Herdr ${typeof error.message === "string" ? error.message : "request failed"}`); this.code = typeof error.code === "string" ? error.code : void 0; } }; function responseError(frame) { if (!("error" in frame)) return void 0; if (!isRecord(frame.error)) return new Error("Herdr returned an invalid error response"); return new HerdrApiError(frame.error); } function createBestEffortSender(socketEndpoint) { return async (request, signal) => { for (const timeoutMs of [500, 1500]) { if (signal.aborted) return; try { const result = await requestHerdr(socketEndpoint, request, signal, timeoutMs); if (isRecord(result) && result.type === "ok") return; } catch (error) { if (signal.aborted || error instanceof HerdrApiError) return; } } }; } function requestHerdr(socketEndpoint, request, signal, timeoutMs = 1500) { if (signal.aborted) return Promise.reject(abortReason(signal)); return new Promise((resolve, reject) => { const payload = `${JSON.stringify(request)} `; const socket = net.createConnection(socketEndpoint); const parser = new NdjsonFrames(); let finished = false; const timeout = setTimeout(() => finish(new Error("Herdr request timed out")), timeoutMs); timeout.unref?.(); const cleanup = () => { clearTimeout(timeout); signal.removeEventListener("abort", abort); socket.destroy(); }; const finish = (error, result) => { if (finished) return; finished = true; cleanup(); if (error) reject(error); else resolve(result); }; const abort = () => finish(abortReason(signal)); signal.addEventListener("abort", abort, { once: true }); socket.on("connect", () => { if (!finished) socket.write(payload); }); socket.on("error", (error) => finish(error)); socket.on("end", () => finish(new Error("Herdr closed the request before responding"))); socket.on("close", () => finish(new Error("Herdr request socket closed"))); socket.on("data", (chunk) => { if (finished) return; try { for (const value of parser.push(chunk)) { if (!isRecord(value)) continue; const uncorrelatedError = responseError(value); if (uncorrelatedError && (value.id === "" || value.id === void 0)) { finish(uncorrelatedError); return; } if (value.id !== request.id) continue; const error = responseError(value); if (error) { finish(error); return; } if (!("result" in value)) { finish(new Error("Herdr response did not include a result")); return; } finish(void 0, value.result); return; } } catch (error) { finish(error instanceof Error ? error : new Error(String(error))); } }); }); } async function openHerdrSubscription(socketEndpoint, request, signal, onEvent, timeoutMs = 1500) { if (signal.aborted) throw abortReason(signal); const payload = `${JSON.stringify(request)} `; const socket = net.createConnection(socketEndpoint); const parser = new NdjsonFrames(); const pendingEvents = []; let ready = false; let finished = false; let intentionalClose = false; let resolveReady; let rejectReady; let resolveClosed; let rejectClosed; const readyPromise = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; }); const closed = new Promise((resolve, reject) => { resolveClosed = resolve; rejectClosed = reject; }); void closed.catch(() => void 0); const timeout = setTimeout(() => finish(new Error("Herdr subscription timed out")), timeoutMs); timeout.unref?.(); const cleanup = () => { clearTimeout(timeout); signal.removeEventListener("abort", abort); socket.destroy(); }; const finish = (error) => { if (finished) return; finished = true; cleanup(); if (!ready) { if (error && !intentionalClose) rejectReady(error); else rejectReady(abortReason(signal)); resolveClosed(); return; } if (error && !intentionalClose) rejectClosed(error); else resolveClosed(); }; const deliver = (frame) => { if (finished) return; try { onEvent(frame); } catch (error) { finish(error instanceof Error ? error : new Error(String(error))); } }; const abort = () => { intentionalClose = true; finish(); }; signal.addEventListener("abort", abort, { once: true }); socket.on("connect", () => { if (!finished) socket.write(payload); }); socket.on("error", (error) => finish(error)); socket.on("end", () => finish(new Error("Herdr subscription closed"))); socket.on("close", () => finish(new Error("Herdr subscription socket closed"))); socket.on("data", (chunk) => { if (finished) return; try { for (const value of parser.push(chunk)) { if (finished) return; if (isRecord(value)) { const error = responseError(value); if (error && (value.id === request.id || value.id === "" || value.id === void 0)) { finish(error); return; } if ("id" in value && value.id !== request.id) continue; } if (!ready) { if (isRecord(value) && value.id === request.id) { if (!isRecord(value.result) || value.result.type !== "subscription_started") { finish(new Error("Herdr returned an invalid subscription acknowledgement")); return; } ready = true; clearTimeout(timeout); resolveReady(); for (const event of pendingEvents.splice(0)) deliver(event); continue; } if (pendingEvents.length >= MAX_PENDING_SUBSCRIPTION_EVENTS) { finish(new Error("Too many Herdr events arrived before subscription acknowledgement")); return; } pendingEvents.push(value); continue; } deliver(value); } } catch (error) { finish(error instanceof Error ? error : new Error(String(error))); } }); await readyPromise; if (signal.aborted) { intentionalClose = true; finish(); throw abortReason(signal); } return { closed, close() { intentionalClose = true; finish(); } }; } // src/herdr-metadata.ts import { sanitizeTerminalText } from "@narumitw/pi-tui-kit/terminal-text"; var HERDR_METADATA_TOKEN_KEYS = ["model", "provider", "thinking", "session", "context_usage"]; var HERDR_METADATA_MAX_CHARACTERS = 80; var HERDR_METADATA_TTL_MS = 60 * 60 * 1e3; var HERDR_METADATA_REFRESH_MS = 30 * 60 * 1e3; function normalizeHerdrMetadataValue(value) { if (typeof value !== "string") return null; const normalized = sanitizeTerminalText(value).trim(); if (normalized.length === 0) return null; return [...normalized].slice(0, HERDR_METADATA_MAX_CHARACTERS).join(""); } function formatContextUsagePercent(value) { if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return null; return `${Math.round(value)}%`; } function createHerdrMetadataSnapshot(inputs) { return Object.freeze({ model: normalizeHerdrMetadataValue(inputs.model), provider: normalizeHerdrMetadataValue(inputs.provider), thinking: normalizeHerdrMetadataValue(inputs.thinking), session: normalizeHerdrMetadataValue(inputs.session), context_usage: formatContextUsagePercent(inputs.contextUsagePercent) }); } function createHerdrMetadataClearSnapshot() { return Object.freeze({ model: null, provider: null, thinking: null, session: null, context_usage: null }); } function herdrMetadataSnapshotsEqual(left, right) { return left !== void 0 && HERDR_METADATA_TOKEN_KEYS.every((key) => left[key] === right[key]); } function createHerdrMetadataRequest(options) { return { id: options.id, method: "pane.report_metadata", params: { pane_id: options.paneId, source: options.source, seq: options.seq, tokens: { ...options.snapshot }, ttl_ms: options.ttlMs ?? HERDR_METADATA_TTL_MS } }; } // src/herdr-protocol.ts var HERDR_AGENT_STATUSES = ["idle", "working", "blocked", "done", "unknown"]; var HERDR_WIDGET_TOPOLOGY_SUBSCRIPTIONS = [ { type: "pane.created" }, { type: "pane.updated" }, { type: "workspace.renamed" }, { type: "pane.closed" }, { type: "pane.moved" }, { type: "pane.exited" }, { type: "pane.agent_detected" } ]; function herdrWidgetSubscriptions(panes) { return [ ...HERDR_WIDGET_TOPOLOGY_SUBSCRIPTIONS.map((entry) => ({ ...entry })), ...panes.filter((pane) => pane.agent !== void 0).map((pane) => ({ type: "pane.agent_status_changed", pane_id: pane.paneId })) ]; } function isRecord2(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function requiredString(value) { return typeof value === "string" && value.length > 0 ? value : void 0; } function optionalString(value) { return typeof value === "string" ? value : void 0; } function agentStatus(value) { return typeof value === "string" && HERDR_AGENT_STATUSES.includes(value) ? value : void 0; } function stringRecord(value) { if (!isRecord2(value)) return void 0; const entries = Object.entries(value); if (!entries.every(([, entry]) => typeof entry === "string")) return void 0; return Object.fromEntries(entries); } function parseHerdrPane(value) { if (!isRecord2(value)) return void 0; const paneId = requiredString(value.pane_id); const workspaceId = requiredString(value.workspace_id); const tabId = requiredString(value.tab_id); const terminalId = requiredString(value.terminal_id); const status = agentStatus(value.agent_status); const revision = value.revision; if (!paneId || !workspaceId || !tabId || !terminalId || !status || typeof value.focused !== "boolean" || typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 0) { return void 0; } return { paneId, workspaceId, tabId, terminalId, focused: value.focused, revision, agent: optionalString(value.agent), agentName: optionalString(value.name), agentStatus: status, displayAgent: optionalString(value.display_agent), label: optionalString(value.label), stateLabels: stringRecord(value.state_labels) ?? {}, terminalTitle: optionalString(value.terminal_title_stripped) ?? optionalString(value.terminal_title), title: optionalString(value.title) }; } function parsePaneCurrentResult(value) { if (!isRecord2(value) || value.type !== "pane_current") { throw new Error("Herdr returned an invalid current-pane response"); } const pane = parseHerdrPane(value.pane); if (!pane) throw new Error("Herdr returned an invalid current pane"); return pane; } function parsePaneListResult(value) { if (!isRecord2(value) || value.type !== "pane_list" || !Array.isArray(value.panes)) { throw new Error("Herdr returned an invalid pane-list response"); } const panes = value.panes.map(parseHerdrPane); if (panes.some((pane) => pane === void 0)) { throw new Error("Herdr returned an invalid pane in the pane list"); } return panes; } function parseAgentListResult(value) { if (!isRecord2(value) || value.type !== "agent_list" || !Array.isArray(value.agents)) { throw new Error("Herdr returned an invalid agent-list response"); } return value.agents.flatMap((entry) => { const pane = parseHerdrPane(entry); return pane ? [pane] : []; }); } function parseWorkspaceGetResult(value) { if (!isRecord2(value) || value.type !== "workspace_info" || !isRecord2(value.workspace)) { throw new Error("Herdr returned an invalid workspace response"); } const workspaceId = requiredString(value.workspace.workspace_id); if (!workspaceId) throw new Error("Herdr returned an invalid workspace identity"); return { workspaceId, label: optionalString(value.workspace.label) }; } function parseHerdrPaneEvent(value) { if (!isRecord2(value) || typeof value.event !== "string" || !isRecord2(value.data)) { return void 0; } const data = value.data; if (value.event === "workspace_renamed") { const workspaceId = requiredString(data.workspace_id); if (!workspaceId || typeof data.label !== "string") return void 0; return { type: "workspace_renamed", workspaceId }; } if (value.event === "pane.agent_status_changed" || value.event === "pane_agent_status_changed") { const paneId = requiredString(data.pane_id); const workspaceId = requiredString(data.workspace_id); const status = agentStatus(data.agent_status); if (!paneId || !workspaceId || !status) return void 0; return { type: "agent_status_changed", paneId, workspaceId }; } if (value.event === "pane_created" || value.event === "pane_updated") { const pane = parseHerdrPane(data.pane); return pane ? { type: value.event === "pane_created" ? "created" : "updated", pane } : void 0; } if (value.event === "pane_closed" || value.event === "pane_exited") { const paneId = requiredString(data.pane_id); const workspaceId = requiredString(data.workspace_id); if (!paneId || !workspaceId) return void 0; return { type: value.event === "pane_closed" ? "closed" : "exited", paneId, workspaceId }; } if (value.event === "pane_moved") { const pane = parseHerdrPane(data.pane); const previousPaneId = requiredString(data.previous_pane_id); const previousWorkspaceId = requiredString(data.previous_workspace_id); if (!pane || !previousPaneId || !previousWorkspaceId) return void 0; return { type: "moved", pane, previousPaneId, previousWorkspaceId }; } if (value.event === "pane_agent_detected") { const paneId = requiredString(data.pane_id); const workspaceId = requiredString(data.workspace_id); if (!paneId || !workspaceId || data.released !== void 0 && typeof data.released !== "boolean") { return void 0; } const finalStatus = data.final_status === null ? void 0 : agentStatus(data.final_status); if (data.final_status !== void 0 && data.final_status !== null && !finalStatus) return void 0; return { type: "agent_detected", paneId, workspaceId }; } return void 0; } // src/herdr-widget.ts import { EditorStatusWidget } from "@narumitw/pi-tui-kit/editor-status-widget"; import { sanitizeTerminalText as sanitizeTerminalText2 } from "@narumitw/pi-tui-kit/terminal-text"; var HERDR_WIDGET_KEY = "herdr:agents"; var MAX_HERDR_WIDGET_AGENTS = 5; var STATE_ORDER = { blocked: 0, done: 1, working: 2, idle: 3, unknown: 4 }; function observedPane(pane) { return { paneId: pane.paneId, workspaceId: pane.workspaceId, agent: pane.agent, agentName: pane.agentName, agentStatus: pane.agentStatus, displayAgent: pane.displayAgent, label: pane.label, stateLabels: { ...pane.stateLabels }, title: pane.title }; } function safeDisplay(value) { if (value === void 0) return void 0; const safe = sanitizeTerminalText2(value).trim(); return safe.length > 0 ? safe : void 0; } function shortIdentity(value, fallback) { const safe = safeDisplay(value); if (!safe) return fallback; return safe.split(":").at(-1) || fallback; } function agentDisplay(pane) { return safeDisplay(pane.agentName) ?? safeDisplay(pane.displayAgent) ?? safeDisplay(pane.agent) ?? "agent"; } function paneDisplay(pane) { const id = shortIdentity(pane.paneId, "pane"); const label = safeDisplay(pane.label) ?? safeDisplay(pane.title); return label ? `${label}/${id}` : id; } function spaceDisplay(workspace) { const id = shortIdentity(workspace.workspaceId, "space"); const label = safeDisplay(workspace.label); return label ? `${label}/${id}` : id; } function rowForPane(pane, workspace) { return { agent: agentDisplay(pane), pane: paneDisplay(pane), paneId: safeDisplay(pane.paneId) ?? "pane", space: spaceDisplay(workspace), state: pane.agentStatus, stateLabel: safeDisplay(pane.stateLabels[pane.agentStatus]) ?? safeDisplay(pane.agentStatus) ?? "unknown" }; } function immutableSnapshot(rows) { if (rows.length === 0) return void 0; const visible = rows.slice(0, MAX_HERDR_WIDGET_AGENTS).map((row) => Object.freeze({ ...row })); return Object.freeze({ totalAgents: rows.length, hiddenAgents: Math.max(0, rows.length - visible.length), rows: Object.freeze(visible) }); } var HerdrWidgetModel = class { currentPaneId; currentWorkspaceId; workspace; panes = /* @__PURE__ */ new Map(); reset(currentPane, panes, workspace = { workspaceId: currentPane.workspaceId }) { this.currentPaneId = currentPane.paneId; this.currentWorkspaceId = currentPane.workspaceId; this.workspace = { ...workspace }; this.panes = new Map(panes.map((pane) => [pane.paneId, observedPane(pane)])); } clear() { this.currentPaneId = void 0; this.currentWorkspaceId = void 0; this.workspace = void 0; this.panes.clear(); } isCurrentPane(paneId) { return this.currentPaneId === paneId; } isCurrentWorkspace(workspaceId) { return this.currentWorkspaceId === workspaceId; } snapshot() { if (!this.currentPaneId || !this.currentWorkspaceId || !this.workspace) return void 0; const rows = [...this.panes.values()].filter( (pane) => pane.workspaceId === this.currentWorkspaceId && pane.paneId !== this.currentPaneId && pane.agent !== void 0 ).map((pane) => rowForPane(pane, this.workspace)); rows.sort( (left, right) => STATE_ORDER[left.state] - STATE_ORDER[right.state] || left.agent.localeCompare(right.agent) || left.pane.localeCompare(right.pane) || left.paneId.localeCompare(right.paneId) ); return immutableSnapshot(rows); } }; function stateStyle(theme, state, text) { switch (state) { case "blocked": return theme.fg("error", text); case "done": return theme.fg("accent", text); case "working": return theme.fg("warning", text); case "idle": return theme.fg("success", text); case "unknown": return theme.fg("dim", text); } } function stateSymbol(state) { switch (state) { case "blocked": return "\xD7"; case "done": return "\u2713"; case "working": return "\u25D0"; case "idle": return "\u25CB"; case "unknown": return "\xB7"; } } function createHerdrWidget(snapshot, theme) { return new EditorStatusWidget({ theme, renderBody() { const noun = snapshot.totalAgents === 1 ? "agent" : "agents"; const lines = [theme.fg("muted", `Herdr \xB7 ${snapshot.totalAgents} sibling ${noun}`)]; const separator = theme.fg("dim", " \xB7 "); for (const row of snapshot.rows) { const state = stateStyle(theme, row.state, `${stateSymbol(row.state)} ${row.stateLabel}`); const agent = theme.bold(theme.fg("text", row.agent)); const pane = theme.fg("muted", row.pane); const space = theme.fg("dim", row.space); lines.push(`${state} ${agent}${separator}${pane}${separator}${space}`); } if (snapshot.hiddenAgents > 0) { lines.push(theme.fg("dim", `+${snapshot.hiddenAgents} more`)); } return lines; } }); } function herdrWidgetFingerprint(snapshot) { return snapshot ? JSON.stringify(snapshot) : void 0; } // src/herdr-observer.ts var HerdrTopologyChangedError = class extends Error { }; function isRelevantEvent(model, event) { if (event.type === "created" || event.type === "updated") { return model.isCurrentWorkspace(event.pane.workspaceId); } if (event.type === "moved") { return model.isCurrentPane(event.previousPaneId) || model.isCurrentWorkspace(event.previousWorkspaceId) || model.isCurrentWorkspace(event.pane.workspaceId); } return "workspaceId" in event && model.isCurrentWorkspace(event.workspaceId); } function subscriptionFingerprint(currentPane, panes) { const agentPaneIds = panes.filter((pane) => pane.agent !== void 0).map((pane) => pane.paneId).sort(); return JSON.stringify([currentPane.paneId, currentPane.workspaceId, agentPaneIds]); } function waitForRetry(delayMs, signal) { if (signal.aborted) return Promise.reject(signal.reason); if (delayMs <= 0) return Promise.resolve(); return new Promise((resolve, reject) => { const timer = setTimeout(() => { signal.removeEventListener("abort", abort); resolve(); }, delayMs); timer.unref?.(); const abort = () => { clearTimeout(timer); reject(signal.reason); }; signal.addEventListener("abort", abort, { once: true }); }); } function createHerdrWidgetObserver(options) { const now = options.now ?? Date.now; const random = options.random ?? Math.random; const request = options.request ?? requestHerdr; const subscribe = options.subscribe ?? openHerdrSubscription; const retryDelayMs = options.retryDelayMs ?? 200; let generation = 0; let activeContext; let activeSession; let controller; const ownedTasks = /* @__PURE__ */ new Set(); let publishedFingerprint; let hasPublished = false; const owns = (ctx, expectedGeneration = generation) => ctx.sessionManager === activeSession && expectedGeneration === generation; const requestId = (kind) => `herdr:pi:widget:${kind}:${now()}:${random().toString(36).slice(2)}`; const clearWidget = (ctx) => { if (ctx.mode === "tui") ctx.ui.setWidget(HERDR_WIDGET_KEY, void 0); publishedFingerprint = void 0; hasPublished = false; }; const publish = (ctx, model, expectedGeneration) => { if (!owns(ctx, expectedGeneration) || ctx.mode !== "tui") return; const snapshot = model.snapshot(); const fingerprint = herdrWidgetFingerprint(snapshot); if (hasPublished && fingerprint === publishedFingerprint) return; if (!snapshot) { ctx.ui.setWidget(HERDR_WIDGET_KEY, void 0); } else { ctx.ui.setWidget(HERDR_WIDGET_KEY, (_tui, theme) => createHerdrWidget(snapshot, theme), { placement: "aboveEditor" }); } publishedFingerprint = fingerprint; hasPublished = true; }; const loadPaneSet = async (ctx, expectedGeneration, signal) => { const currentResult = await request( options.environment.socketEndpoint, { id: requestId("current"), method: "pane.current", params: { caller_pane_id: options.environment.paneId } }, signal ); if (!owns(ctx, expectedGeneration) || signal.aborted) return void 0; const currentPane = parsePaneCurrentResult(currentResult); const [listResult, agentResult, workspaceResult] = await Promise.all([ request( options.environment.socketEndpoint, { id: requestId("list"), method: "pane.list", params: { workspace_id: currentPane.workspaceId } }, signal ), request( options.environment.socketEndpoint, { id: requestId("agents"), method: "agent.list", params: {} }, signal ), request( options.environment.socketEndpoint, { id: requestId("workspace"), method: "workspace.get", params: { workspace_id: currentPane.workspaceId } }, signal ) ]); if (!owns(ctx, expectedGeneration) || signal.aborted) return void 0; const panes = parsePaneListResult(listResult); const paneIds = new Set(panes.map((pane) => pane.paneId)); const namesByPane = new Map( parseAgentListResult(agentResult).filter((pane) => pane.workspaceId === currentPane.workspaceId && paneIds.has(pane.paneId)).map((pane) => [pane.paneId, pane.agentName]) ); const withAgentName = (pane) => ({ ...pane, agentName: namesByPane.get(pane.paneId) ?? pane.agentName }); const workspace = parseWorkspaceGetResult(workspaceResult); if (workspace.workspaceId !== currentPane.workspaceId) { throw new Error("Herdr returned a mismatched workspace"); } return { currentPane: withAgentName(currentPane), panes: panes.map(withAgentName), workspace }; }; const runAttempt = async (ctx, expectedGeneration, sessionSignal) => { const attemptController = new AbortController(); const abortAttempt = () => attemptController.abort(sessionSignal.reason); if (sessionSignal.aborted) abortAttempt(); else sessionSignal.addEventListener("abort", abortAttempt, { once: true }); const signal = attemptController.signal; let attemptActive = true; let subscription; let failure; let wake; const wakeRefresh = () => { const resolve = wake; wake = void 0; resolve?.(); }; signal.addEventListener("abort", wakeRefresh); const failAttempt = (error) => { if (!attemptActive || signal.aborted) return; failure = error; if (owns(ctx, expectedGeneration)) clearWidget(ctx); attemptController.abort(error); }; try { const discovered = await loadPaneSet(ctx, expectedGeneration, signal); if (!discovered) return; const subscribedFingerprint = subscriptionFingerprint(discovered.currentPane, discovered.panes); const model = new HerdrWidgetModel(); model.reset(discovered.currentPane, discovered.panes, discovered.workspace); let dirty = true; subscription = await subscribe( options.environment.socketEndpoint, { id: requestId("subscribe"), method: "events.subscribe", params: { subscriptions: herdrWidgetSubscriptions(discovered.panes) } }, signal, (frame) => { if (!attemptActive || !owns(ctx, expectedGeneration) || signal.aborted) return; const event = parseHerdrPaneEvent(frame); if (!event || !isRelevantEvent(model, event)) return; dirty = true; wakeRefresh(); } ).catch(async (error) => { if (!owns(ctx, expectedGeneration) || signal.aborted) throw error; const latest = await loadPaneSet(ctx, expectedGeneration, signal); if (latest && subscriptionFingerprint(latest.currentPane, latest.panes) !== subscribedFingerprint) { throw new HerdrTopologyChangedError(); } throw error; }); void subscription.closed.then( () => failAttempt(new Error("Herdr subscription ended")), (error) => failAttempt(error instanceof Error ? error : new Error(String(error))) ); while (owns(ctx, expectedGeneration) && !signal.aborted) { dirty = false; const refreshed = await loadPaneSet(ctx, expectedGeneration, signal); if (!refreshed || signal.aborted) break; if (dirty) continue; if (subscriptionFingerprint(refreshed.currentPane, refreshed.panes) !== subscribedFingerprint) { throw new HerdrTopologyChangedError(); } model.reset(refreshed.currentPane, refreshed.panes, refreshed.workspace); publish(ctx, model, expectedGeneration); await new Promise((resolve) => { wake = resolve; if (dirty || signal.aborted) wakeRefresh(); }); } if (failure) throw failure; } finally { attemptActive = false; attemptController.abort(new DOMException("Herdr widget attempt ended", "AbortError")); signal.removeEventListener("abort", wakeRefresh); sessionSignal.removeEventListener("abort", abortAttempt); subscription?.close(); } }; const run = async (ctx, expectedGeneration, signal) => { let failures = 0; while (owns(ctx, expectedGeneration) && !signal.aborted) { try { await runAttempt(ctx, expectedGeneration, signal); return; } catch (error) { if (!owns(ctx, expectedGeneration) || signal.aborted) return; clearWidget(ctx); if (error instanceof HerdrTopologyChangedError) continue; failures += 1; if (failures >= 2) return; try { await waitForRetry(retryDelayMs, signal); } catch { return; } if (!owns(ctx, expectedGeneration) || signal.aborted) return; } } }; return { start(ctx) { generation += 1; controller?.abort(new DOMException("Herdr widget session replaced", "AbortError")); if (activeContext) clearWidget(activeContext); activeContext = void 0; activeSession = void 0; controller = void 0; if (ctx.mode !== "tui") return; activeContext = ctx; activeSession = ctx.sessionManager; controller = new AbortController(); const expectedGeneration = generation; clearWidget(ctx); const task = run(ctx, expectedGeneration, controller.signal).finally(() => { ownedTasks.delete(task); }); ownedTasks.add(task); }, async shutdown(ctx) { if (!owns(ctx)) return; const shutdownGeneration = ++generation; controller?.abort(new DOMException("Herdr widget session shut down", "AbortError")); clearWidget(ctx); await Promise.allSettled([...ownedTasks]); if (generation !== shutdownGeneration) return; activeContext = void 0; activeSession = void 0; controller = void 0; } }; } // src/settings.ts import { randomUUID } from "node:crypto"; import { constants } from "node:fs"; import { lstat, mkdir, open, rename, rm, writeFile } from "node:fs/promises"; import { basename, dirname, join } from "node:path"; import { getAgentDir } from "@earendil-works/pi-coding-agent"; var HERDR_SETTINGS_FILE = "pi-herdr.json"; var MAX_SETTINGS_BYTES = 64 * 1024; var DEFAULT_SETTINGS = { widget: true }; var queues = /* @__PURE__ */ new Map(); function herdrSettingsPath() { return join(getAgentDir(), HERDR_SETTINGS_FILE); } function normalizeHerdrSettings(value) { if (!isRecord3(value)) return void 0; if (Object.hasOwn(value, "widget") && typeof value.widget !== "boolean") { return void 0; } return { widget: typeof value.widget === "boolean" ? value.widget : DEFAULT_SETTINGS.widget }; } function readHerdrSettings(settingsPath = herdrSettingsPath()) { return enqueue(settingsPath, async () => { let document; try { document = await readSettingsDocument(settingsPath); } catch (error) { if (isNodeError(error) && error.code === "ENOENT") { return { kind: "missing", settings: { ...DEFAULT_SETTINGS } }; } return { kind: "invalid", reason: `${settingsPath}: ${safeError(error)}`, settings: { ...DEFAULT_SETTINGS } }; } const settings = normalizeHerdrSettings(document); return settings ? { kind: "loaded", settings } : { kind: "invalid", reason: `${settingsPath}: invalid settings shape`, settings: { ...DEFAULT_SETTINGS } }; }); } function updateHerdrSettings(patch, options = {}) { const settingsPath = options.settingsPath ?? herdrSettingsPath(); return enqueue(settingsPath, async () => { const current = await readDocumentForUpdate(settingsPath); const updated = { ...current, ...patch }; const settings = normalizeHerdrSettings(updated); if (!settings) throw new Error(`Pi Herdr settings at ${settingsPath} have an invalid shape.`); await publishSettings(settingsPath, updated, options); return settings; }); } async function awaitHerdrSettingsWrites(settingsPath = herdrSettingsPath()) { await queues.get(settingsPath); } function enqueue(path2, operation) { const previous = queues.get(path2) ?? Promise.resolve(); const result = previous.then(operation, operation); const settled = result.then( () => void 0, () => void 0 ); queues.set(path2, settled); void settled.finally(() => { if (queues.get(path2) === settled) queues.delete(path2); }); return result; } async function readDocumentForUpdate(path2) { try { const document = await readSettingsDocument(path2); if (!normalizeHerdrSettings(document)) throw new Error("invalid settings shape"); return document; } catch (error) { if (isNodeError(error) && error.code === "ENOENT") return {}; throw new Error(`Pi Herdr settings at ${path2} are invalid: ${safeError(error)}`); } } async function readSettingsDocument(path2) { const pathStats = await lstat(path2); if (pathStats.isSymbolicLink() || !pathStats.isFile()) { throw new Error("settings path is not a regular file"); } if (pathStats.size > MAX_SETTINGS_BYTES) { throw new Error(`settings file exceeds ${MAX_SETTINGS_BYTES} bytes`); } const noFollow = constants.O_NOFOLLOW ?? 0; const handle = await open(path2, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0) | noFollow); try { const stats = await handle.stat(); if (!stats.isFile() || stats.dev !== pathStats.dev || stats.ino !== pathStats.ino) { throw new Error("settings path changed while opening"); } const buffer = Buffer.alloc(MAX_SETTINGS_BYTES + 1); let offset = 0; while (offset < buffer.length) { const { bytesRead } = await handle.read(buffer, offset, buffer.length - offset, offset); if (bytesRead === 0) break; offset += bytesRead; } if (offset > MAX_SETTINGS_BYTES) { throw new Error(`settings file exceeds ${MAX_SETTINGS_BYTES} bytes`); } let contents; try { contents = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(buffer.subarray(0, offset)); } catch { throw new Error("settings file is not valid UTF-8"); } let parsed; try { parsed = JSON.parse(contents); } catch { throw new Error("invalid JSON"); } if (!isRecord3(parsed)) throw new Error("settings document must be a JSON object"); return parsed; } finally { await handle.close(); } } async function publishSettings(path2, document, options) { const contents = `${JSON.stringify(document, null, 2)} `; if (Buffer.byteLength(contents) > MAX_SETTINGS_BYTES) { throw new Error(`Pi Herdr settings document exceeds ${MAX_SETTINGS_BYTES} bytes.`); } const directory = dirname(path2); await mkdir(directory, { recursive: true }); const temporaryPath = join(directory, `.${basename(path2)}.${process.pid}.${randomUUID()}.tmp`); try { await writeFile(temporaryPath, contents, { encoding: "utf8", flag: "wx", mode: 384 }); await options.beforeRename?.(temporaryPath, path2); await rename(temporaryPath, path2); } catch (error) { await rm(temporaryPath, { force: true }).catch(() => void 0); throw error; } } function isRecord3(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function isNodeError(error) { return error instanceof Error && "code" in error; } function safeError(error) { return error instanceof Error ? error.message : String(error); } // src/herdr-settings-controller.ts function createHerdrSettingsController(pi, observer, options = {}) { const settingsPath = options.settingsPath ?? herdrSettingsPath(); let generation = 0; let owner; let controller = new AbortController(); let enabled = true; let changes = Promise.resolve(); let menuOpen = false; const current = (expected) => expected === generation && !controller.signal.aborted; pi.registerCommand("herdr", { description: "Configure the Herdr agent widget", handler: async (args, ctx) => { if (args.trim()) throw new Error("/herdr does not accept arguments."); if (!ctx.hasUI || ctx.mode !== "tui" && ctx.mode !== "rpc") { throw new Error("/herdr requires TUI or RPC mode."); } if (!owner || ctx.sessionManager !== owner.sessionManager || menuOpen) return; const expected = generation; const signal = controller.signal; menuOpen = true; try { const [{ runMenu }, { createHerdrMenu }] = await Promise.all([ import("@narumitw/pi-tui-kit"), import("./chunks/herdr-menu-IUPFF5YK.ts") ]); if (!current(expected)) return; await runMenu( ctx, createHerdrMenu({ settingsPath, isEnabled: () => enabled, toggle: (actionSignal) => { const result = changes.then(async () => { if (actionSignal.aborted || !current(expected) || !owner) return false; const previous = enabled; const actionOwner = owner; try { enabled = !previous; if (enabled && actionOwner.mode === "tui") observer.start(actionOwner); else await observer.shutdown(actionOwner); if (!current(expected)) return false; await updateHerdrSettings({ widget: enabled }, { settingsPath }); return current(expected); } catch { if (!current(expected)) return false; enabled = previous; try { if (previous && actionOwner.mode === "tui") observer.start(actionOwner); else await observer.shutdown(actionOwner); } finally { if (current(expected)) ctx.ui.notify( "Herdr settings were not saved; the previous widget setting was restored. Check pi-herdr.json and its permissions.", "warning" ); } return false; } }); changes = result.then( () => void 0, () => void 0 ); return result; } }), { getState: () => void 0, signal, isCurrent: () => current(expected), onError: () => { if (current(expected)) ctx.ui.notify("The Herdr menu could not be displayed.", "error"); } } ); } finally { if (expected === generation) menuOpen = false; } } }); return { async start(ctx) { const expected = ++generation; controller.abort(); controller = new AbortController(); menuOpen = false; const previous = owner; owner = ctx; if (previous) await observer.shutdown(previous); if (!current(expected)) return; await changes; if (!current(expected)) return; const loaded = await readHerdrSettings(settingsPath); if (!current(expected)) return; enabled = loaded.settings.widget; if (loaded.kind === "invalid" && ctx.hasUI) ctx.ui.notify( "Herdr ignored invalid pi-herdr.json settings and kept the widget enabled. Fix the file before saving.", "warning" ); if (enabled && ctx.mode === "tui") observer.start(ctx); }, async shutdown(ctx) { if (ctx.sessionManager !== owner?.sessionManager) return; generation += 1; controller.abort(); owner = void 0; menuOpen = false; await Promise.all([observer.shutdown(ctx), changes]); await awaitHerdrSettingsWrites(settingsPath); } }; } // src/herdr-agent-state.ts var SOURCE = "herdr:pi"; var reportSeq = Date.now() * 1e3; function nextReportSeq() { reportSeq += 1; return reportSeq; } function resolveSocketEndpoint(socketPath, platform = process.platform) { if (!socketPath || platform !== "win32") return socketPath ?? ""; const normalized = socketPath.toLowerCase(); if (normalized.startsWith("\\\\.\\pipe\\") || normalized.startsWith("\\\\?\\pipe\\")) { return socketPath; } return `\\\\.\\pipe\\${socketPath}`; } function readEnvironment(environment = process.env) { const socketPath = environment.HERDR_SOCKET_PATH; const paneId = environment.HERDR_PANE_ID; return { enabled: environment.HERDR_ENV === "1" && !!socketPath && !!paneId, paneId: paneId ?? "", socketEndpoint: resolveSocketEndpoint(socketPath) }; } function createHerdrAgentStateExtension(options = {}) { const environment = options.environment ?? readEnvironment(); const now = options.now ?? Date.now; const random = options.random ?? Math.random; const sendRequest = options.sendRequest ?? createBestEffortSender(environment.socketEndpoint); const widgetObserver = options.widgetObserver ?? createHerdrWidgetObserver({ environment, now, random }); return function herdrAgentState2(pi) { if (!environment.enabled) return; const widget = createHerdrSettingsController(pi, widgetObserver, options); let sessionGeneration = 0; let sessionController = new AbortController(); let activeSession; let currentAgentSessionId; let currentAgentSessionPath; let agentActive = false; let blockedCount = 0; let blockedMessage; let lastState; let lastMessage; let rootSession = false; let queuedState; let drainTask; let queuedMetadata; let metadataDrainTask; let lastMetadataSnapshot; let metadataRefreshTimer; const sessionReportTasks = /* @__PURE__ */ new Set(); let shutdownController; let shutdownSession; let shutdownTask; function requestId(kind) { const segment = kind ? `${kind}:` : ""; return `${SOURCE}:${segment}${now()}:${random().toString(36).slice(2)}`; } function updateSessionRef(ctx) { try { const file = ctx.sessionManager.getSessionFile(); currentAgentSessionPath = typeof file === "string" && path.isAbsolute(file) ? file : void 0; } catch { currentAgentSessionPath = void 0; } try { const id = ctx.sessionManager.getSessionId(); currentAgentSessionId = typeof id === "string" && id.length > 0 ? id : void 0; } catch { currentAgentSessionId = void 0; } } function currentSessionRef() { if (currentAgentSessionPath) return { agent_session_path: currentAgentSessionPath }; if (currentAgentSessionId) return { agent_session_id: currentAgentSessionId }; return void 0; } function withSessionRef(params) { return { ...params, ...currentSessionRef() }; } async function safeSend(request, signal) { try { await sendRequest(request, signal); } catch { } } function reportSession(sessionStartSource) { const sessionRef = currentSessionRef(); if (!sessionRef) return Promise.resolve(); const task = safeSend( { id: requestId("session"), method: "pane.report_agent_session", params: { pane_id: environment.paneId, source: SOURCE, agent: "pi", seq: nextReportSeq(), session_start_source: sessionStartSource, ...sessionRef } }, sessionController.signal ); sessionReportTasks.add(task); void task.finally(() => sessionReportTasks.delete(task)); return task; } function stateRequest(state) { return { id: requestId(), method: "pane.report_agent", params: withSessionRef({ pane_id: environment.paneId, source: SOURCE, agent: "pi", state: state.state, message: state.message, seq: state.seq }) }; } function readMetadataSnapshot(ctx) { let session; let contextUsagePercent; try { session = pi.getSessionName(); } catch { } try { contextUsagePercent = ctx.getContextUsage()?.percent; } catch { } return createHerdrMetadataSnapshot({ model: ctx.model?.id, provider: ctx.model?.provider, thinking: ctx.thinkingLevel, session, contextUsagePercent }); } function metadataRequest(metadata) { return createHerdrMetadataRequest({ id: requestId(), paneId: environment.paneId, source: SOURCE, seq: metadata.seq, snapshot: metadata.snapshot }); } async function drainStateQueue() { while (queuedState) { const next = queuedState; queuedState = void 0; if (!next.signal.aborted) await safeSend(stateRequest(next), next.signal); } } function queueState(state, message) { queuedState = { state, message, seq: nextReportSeq(), signal: sessionController.signal }; if (drainTask) return; drainTask = drainStateQueue().finally(() => { drainTask = void 0; if (queuedState) queueState(queuedState.state, queuedState.message); }); } async function drainMetadataQueue() { while (queuedMetadata) { const next = queuedMetadata; queuedMetadata = void 0; if (next.signal.aborted || next.generation !== sessionGeneration || !rootSession) { continue; } await safeSend(metadataRequest(next), next.signal); } } function startMetadataDrain() { if (metadataDrainTask) return; metadataDrainTask = drainMetadataQueue().finally(() => { metadataDrainTask = void 0; if (queuedMetadata) startMetadataDrain(); }); } function scheduleMetadataRefresh(generation) { if (metadataRefreshTimer) clearTimeout(metadataRefreshTimer); metadataRefreshTimer = setTimeout(() => { metadataRefreshTimer = void 0; if (generation !== sessionGeneration || sessionController.signal.aborted || !rootSession || !lastMetadataSnapshot) { return; } queueMetadata(lastMetadataSnapshot, true); }, HERDR_METADATA_REFRESH_MS); metadataRefreshTimer.unref?.(); } function queueMetadata(snapshot, force = false) { if (!rootSession || sessionController.signal.aborted) return; if (!force && herdrMetadataSnapshotsEqual(lastMetadataSnapshot, snapshot)) return; lastMetadataSnapshot = snapshot; queuedMetadata = { snapshot, seq: nextReportSeq(), signal: sessionController.signal, generation: sessionGeneration }; scheduleMetadataRefresh(sessionGeneration); startMetadataDrain(); } function owns(ctx) { return rootSession && ctx.sessionManager === activeSession; } function publishMetadata(ctx) { if (!owns(ctx)) return; queueMetadata(readMetadataSnapshot(ctx)); } function desiredState() { if (blockedCount > 0) return { state: "blocked", message: blockedMessage }; if (agentActive) return { state: "working" }; return { state: "idle" }; } 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.on("ui_prompt_start", (event, ctx) => { if (!owns(ctx)) return; blockedCount += 1; blockedMessage = event.title?.trim() || event.kind; publishState(); }); pi.on("ui_prompt_end", (_event, ctx) => { if (!owns(ctx)) return; blockedCount = Math.max(0, blockedCount - 1); if (blockedCount === 0) blockedMessage = void 0; publishState(); }); pi.on("session_start", async (event, ctx) => { const generation = ++sessionGeneration; const replaced = new DOMException("Herdr session replaced", "AbortError"); sessionController.abort(replaced); shutdownController?.abort(replaced); shutdownController = void 0; shutdownTask = void 0; shutdownSession = void 0; if (metadataRefreshTimer) clearTimeout(metadataRefreshTimer); metadataRefreshTimer = void 0; queuedMetadata = void 0; lastMetadataSnapshot = void 0; sessionController = new AbortController(); activeSession = ctx.sessionManager; rootSession = ctx.mode === "tui"; const widgetStart = widget.start(ctx); agentActive = false; blockedCount = 0; blockedMessage = void 0; lastState = void 0; lastMessage = void 0; if (!rootSession) { await widgetStart; return; } updateSessionRef(ctx); const sessionReport = reportSession(event.reason); publishMetadata(ctx); await Promise.all([sessionReport, widgetStart]); if (generation !== sessionGeneration || activeSession !== ctx.sessionManager || sessionController.signal.aborted || !rootSession) { return; } agentActive = !ctx.isIdle(); publishState(true); }); pi.on("agent_start", (_event, ctx) => { if (!owns(ctx)) return; updateSessionRef(ctx); void reportSession(); agentActive = true; publishState(); }); pi.on("agent_settled", (_event, ctx) => { if (!owns(ctx)) return; publishMetadata(ctx); if (!ctx.isIdle()) return; agentActive = false; publishState(); }); pi.on("session_info_changed", (_event, ctx) => publishMetadata(ctx)); pi.on("model_select", (_event, ctx) => publishMetadata(ctx)); pi.on("thinking_level_select", (_event, ctx) => publishMetadata(ctx)); pi.on("session_compact", (_event, ctx) => publishMetadata(ctx)); pi.on("session_shutdown", async (_event, ctx) => { if (ctx.sessionManager !== activeSession) { await Promise.allSettled([widget.shutdown(ctx)]); return; } if (shutdownTask && shutdownSession === ctx.sessionManager) { await shutdownTask; return; } const session = ctx.sessionManager; const task = (async () => { const shouldClearMetadata = rootSession; const shutdownGeneration = ++sessionGeneration; rootSession = false; if (metadataRefreshTimer) clearTimeout(metadataRefreshTimer); metadataRefreshTimer = void 0; sessionController.abort(new DOMException("Herdr session shut down", "AbortError")); queuedState = void 0; queuedMetadata = void 0; await Promise.allSettled([...sessionReportTasks, drainTask, metadataDrainTask, widget.shutdown(ctx)]); const stillOwnsShutdown = () => shutdownGeneration === sessionGeneration && activeSession === session && !rootSession; if (shouldClearMetadata && stillOwnsShutdown()) { const controller = new AbortController(); shutdownController = controller; try { await safeSend( createHerdrMetadataRequest({ id: requestId(), paneId: environment.paneId, source: SOURCE, seq: nextReportSeq(), snapshot: createHerdrMetadataClearSnapshot() }), controller.signal ); } finally { if (shutdownController === controller) shutdownController = void 0; } } if (!stillOwnsShutdown()) { return; } lastMetadataSnapshot = void 0; currentAgentSessionId = void 0; currentAgentSessionPath = void 0; activeSession = void 0; })(); shutdownSession = session; shutdownTask = task; await task; if (shutdownTask === task) { shutdownTask = void 0; shutdownSession = void 0; } }); }; } function herdrAgentState(pi) { createHerdrAgentStateExtension()(pi); } export { herdrAgentState as default }; //# sourceMappingURL=index.ts.map