100 lines
2.4 KiB
TypeScript
100 lines
2.4 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.
|
|
* 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();
|
|
};
|
|
}
|