import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { MESSAGE_TYPE_PROCESS_UPDATE, type ProcessInfo } from "../constants"; import type { ProcessManager } from "../manager"; import { formatRuntime, sanitizeLine, truncateCmd } from "../utils"; interface ProcessUpdateDetails { processId: string; processName: string; command: string; status: "exited" | "killed"; exitCode: number | null; success: boolean; runtime: string; } export function setupProcessEndHook(pi: ExtensionAPI, manager: ProcessManager) { manager.onEvent((event) => { if (event.type !== "process_ended") return; // Tool-initiated kills already return a tool result. Do not enqueue a // custom message while the agent is streaming; even triggerTurn=false would // otherwise be delivered as steering by Pi. if (!event.triggerAgentTurn) return; const info: ProcessInfo = event.info; const runtime = formatRuntime(info.startTime, info.endTime); // Build message let summary: string; const processName = sanitizeLine(info.name); if (info.status === "killed") { summary = `Process "${processName}" (${info.id}) was terminated after ${runtime}.`; } else if (info.success) { summary = `Process "${processName}" (${info.id}) completed successfully after ${runtime}.`; } else { summary = `Process "${processName}" (${info.id}) crashed with exit code ${info.exitCode ?? "?"} after ${runtime}.`; } const message = buildAgentMessage(summary, info, manager); // Send the message to the conversation - displayed via custom renderer in UI. const details: ProcessUpdateDetails = { processId: info.id, processName: info.name, command: info.command, status: info.status as "exited" | "killed", exitCode: info.exitCode, success: info.success ?? false, runtime, }; pi.sendMessage( { customType: MESSAGE_TYPE_PROCESS_UPDATE, content: message, display: true, details, }, { triggerTurn: true, deliverAs: "steer" }, ); }); } function buildAgentMessage( summary: string, info: ProcessInfo, manager: ProcessManager, ): string { const lines = [ summary, `Command: ${truncateCmd(sanitizeLine(info.command), 160)}`, ]; const recentOutput = manager.getCombinedOutput(info.id, 20) ?? []; if (recentOutput.length > 0) { lines.push("", "Recent output:"); for (const line of recentOutput) { const prefix = line.type === "stderr" ? "stderr" : "stdout"; lines.push(`${prefix}: ${truncateCmd(sanitizeLine(line.text), 500)}`); } } lines.push( "", "This is the automatic process-end notification. Do not call process list/output/logs just to check whether it finished; use process output once only if you need more logs for debugging.", ); return lines.join("\n"); }