/** * 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. * * Note: `pi.on()` returns void and has no unsubscribe mechanism — event * handlers are scoped to the extension lifetime, not to individual calls. * The returned function is a no-op kept for API compatibility. */ export function subscribeAgentEvents( pi: ExtensionAPI, onState: StateCallback, ): () => void { // agent_start → thinking pi.on("agent_start", () => { onState({ value: "thinking", ts: Date.now() }); }); // agent_end → idle pi.on("agent_end", () => { onState({ value: "idle", ts: Date.now() }); }); // tool_execution_start → tool (carries toolName directly on the event) pi.on("tool_execution_start", (event) => { onState({ value: "tool", tool: event.toolName, ts: Date.now() }); }); // tool_execution_end → thinking (agent loop continues after tool completes) pi.on("tool_execution_end", () => { onState({ value: "thinking", ts: Date.now() }); }); // input → awaiting-input (fired when pi pauses to wait for user input) pi.on("input", () => { onState({ value: "awaiting-input", ts: Date.now() }); }); return () => { // No-op: pi event subscriptions cannot be cancelled. }; }