import type { AgentEndEvent, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { sendNotification } from "../send.ts"; import type { Toggle } from "../config.ts"; interface AgentOutcome { stopReason: string; } function isAssistantMessage(value: unknown): value is { role: "assistant"; stopReason?: unknown } { return typeof value === "object" && value !== null && (value as { role?: unknown }).role === "assistant"; } export interface AgentLoopOptions { completion: Toggle; error: Toggle; getSessionName?: () => string | undefined; notify?: (message: unknown) => void; } export interface AgentLoopHandlers { onAgentEnd: (event: AgentEndEvent, context: ExtensionContext) => void; onAgentSettled: (_event: unknown, context: ExtensionContext) => void; } /** Capture intermediate results and emit only once at the final settlement. */ export function createAgentLoopHandlers(options: AgentLoopOptions): AgentLoopHandlers { let latest: AgentOutcome | undefined; const deliver = options.notify ?? sendNotification; const title = (): string => { try { return options.getSessionName?.()?.trim() || "pi"; } catch { return "pi"; } }; return { onAgentEnd(event) { try { latest = undefined; for (let index = event.messages.length - 1; index >= 0; index--) { const message = event.messages[index]; if (isAssistantMessage(message) && typeof message.stopReason === "string") { latest = { stopReason: message.stopReason }; break; } } } catch { // A malformed lifecycle event must not affect the agent loop. } }, onAgentSettled(_event, context) { try { const outcome = latest; latest = undefined; if (context.mode !== "tui" || !outcome) return; if (outcome.stopReason === "aborted") return; if (outcome.stopReason === "error") { if (options.error === "on") deliver({ title: title(), body: "Stopped with error", type: "error", urgency: "critical" }); return; } if (options.completion === "on") deliver({ title: title(), body: "Complete", type: "completion" }); } catch { latest = undefined; } }, }; } export function registerAgentLoopHandlers(pi: ExtensionAPI, options: AgentLoopOptions): AgentLoopHandlers { const handlers = createAgentLoopHandlers(options); pi.on("agent_end", handlers.onAgentEnd); pi.on("agent_settled", handlers.onAgentSettled); return handlers; }