/** * pi ExtensionAPI event subscriptions. * * Bridges pi's lifecycle events into the sidecar's state model. * Emits structured state updates that the WebSocket broadcaster (T-1.5) * can forward as IC-1 `{ type: "state"; value: ... }` frames. * * Subscribes to: * - agent_start / agent_end → "thinking" / "idle" * - tool_start / tool_end → "tool" (with tool name) * - awaiting_input → "awaiting-input" * * Owner: T-1.4 */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; /** IC-1 state values */ export type AgentState = "thinking" | "tool" | "idle" | "awaiting-input"; export interface StateEvent { value: AgentState; tool?: string; ts: number; } export type StateCallback = (event: StateEvent) => void; /** * Subscribe to pi agent lifecycle events. * Returns an unsubscribe function. */ export function subscribeAgentEvents( pi: ExtensionAPI, onState: StateCallback, ): () => void { const unsubs: Array<() => void> = []; // agent_start → thinking try { const off = pi.on("agent_start", () => { onState({ value: "thinking", ts: Date.now() }); }); if (off) unsubs.push(off); } catch { // event may not exist in this pi version } // agent_end → idle try { const off = pi.on("agent_end", () => { onState({ value: "idle", ts: Date.now() }); }); if (off) unsubs.push(off); } catch { // event may not exist } // tool_start → tool try { const off = pi.on("tool_start", (data: unknown) => { const toolName = data && typeof data === "object" && "name" in data && typeof (data as { name: unknown }).name === "string" ? (data as { name: string }).name : undefined; onState({ value: "tool", tool: toolName, ts: Date.now() }); }); if (off) unsubs.push(off); } catch { // event may not exist } // tool_end → thinking (agent is still running after tool) try { const off = pi.on("tool_end", () => { onState({ value: "thinking", ts: Date.now() }); }); if (off) unsubs.push(off); } catch { // event may not exist } // awaiting_input → awaiting-input try { const off = pi.on("awaiting_input", () => { onState({ value: "awaiting-input", ts: Date.now() }); }); if (off) unsubs.push(off); } catch { // event may not exist } return () => { for (const off of unsubs) off(); }; }