pi-remote-control/extensions/remote-control/pi/events.ts

69 lines
1.9 KiB
TypeScript

/**
* 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.
};
}