import type { EventBus } from "@earendil-works/pi-coding-agent"; export type PixAgentState = "working" | "blocked" | "idle"; export type UnattendedMode = "off" | "afk" | "yolo"; export type PixAgentStateEvent = { state: PixAgentState; message?: string; activities: number; blocks: number; }; type Entry = { source: string; message?: string }; type Coordinator = { activities: Map; blocks: Map; unattendedMode: UnattendedMode; yoloConsent: boolean; }; function coordinators(): WeakMap { const global = globalThis as { __pixAgentState?: WeakMap }; global.__pixAgentState ??= new WeakMap(); return global.__pixAgentState; } function coordinator(events: EventBus): Coordinator { const registry = coordinators(); let state = registry.get(events); if (!state) { state = { activities: new Map(), blocks: new Map(), unattendedMode: "off", yoloConsent: false, }; registry.set(events, state); } return state; } function snapshot(state: Coordinator): PixAgentStateEvent { const blocked = [...state.blocks.values()].at(-1); if (blocked) { return { state: "blocked", ...(blocked.message ? { message: blocked.message } : {}), activities: state.activities.size, blocks: state.blocks.size, }; } const active = [...state.activities.values()].at(-1); if (active) { return { state: "working", ...(active.message ? { message: active.message } : {}), activities: state.activities.size, blocks: 0, }; } return { state: "idle", activities: 0, blocks: 0 }; } function publish(events: EventBus): void { events.emit("pix:agent-state", snapshot(coordinator(events))); } function begin( events: EventBus, kind: "activities" | "blocks", source: string, message?: string, ): () => void { const state = coordinator(events); const token = Symbol(source); state[kind].set(token, { source, message }); publish(events); let active = true; return () => { if (!active) return; active = false; state[kind].delete(token); publish(events); }; } /** Keep external status integrations working while asynchronous Pix work remains. */ export function beginAgentActivity(events: EventBus, source: string, message?: string): () => void { return begin(events, "activities", source, message); } /** Internal primitive behind {@link withAgentBlock}; exported only for same-package tests. Blocks take priority over activity. */ export function beginAgentBlock(events: EventBus, source: string, message?: string): () => void { return begin(events, "blocks", source, message); } /** Hold blocked state for one prompt and always release it. */ export async function withAgentBlock( events: EventBus, source: string, message: string | undefined, prompt: () => Promise, ): Promise { const release = beginAgentBlock(events, source, message); try { return await prompt(); } finally { release(); } } export function getUnattendedMode(events: EventBus): UnattendedMode { return coordinator(events).unattendedMode; } export function setUnattendedMode(events: EventBus, mode: UnattendedMode): void { coordinator(events).unattendedMode = mode; } export function hasYoloConsent(events: EventBus): boolean { return coordinator(events).yoloConsent; } export function setYoloConsent(events: EventBus, consent: boolean): void { coordinator(events).yoloConsent = consent; } export function resetUnattendedState(events: EventBus): void { const state = coordinator(events); state.unattendedMode = "off"; state.yoloConsent = false; } /** Bind state replay/reset to one Pi session lifecycle. */ export function bindAgentStateEvents(events: EventBus): () => void { const off = events.on("pix:agent-state:request", () => publish(events)); publish(events); return off; } /** Clear stale leases when a session shuts down or an extension reloads. */ export function resetAgentState(events: EventBus): void { const state = coordinator(events); state.activities.clear(); state.blocks.clear(); publish(events); }