/** * Notify Extension * * Sends a native notification when the pi agent finishes and is waiting for * input. Useful when you step away during a long task. * * Only fires when the agent ran for longer than --notify-min-secs (default 8). * Short responses are silently skipped to avoid notification noise. * * Terminal detection (in priority order): * KITTY_WINDOW_ID → OSC 99 (Kitty) * WT_SESSION → Windows toast (Windows Terminal / WSL) * otherwise → OSC 777 (iTerm2, WezTerm, Ghostty, rxvt-unicode) */ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; // ── Notification backends ───────────────────────────────────────────────────── function notifyKitty(title: string, body: string): void { // OSC 99: two-part protocol — title first, then body process.stdout.write(`\x1b]99;i=1:d=0;${title}\x1b\\`); process.stdout.write(`\x1b]99;i=1:p=body;${body}\x1b\\`); } function notifyOSC777(title: string, body: string): void { process.stdout.write(`\x1b]777;notify;${title};${body}\x07`); } function notifyWindows(title: string, body: string): void { const type = "Windows.UI.Notifications"; const script = [ `[${type}.ToastNotificationManager, ${type}, ContentType = WindowsRuntime] > $null`, `$xml = [${type}.ToastNotificationManager]::GetTemplateContent([${type}.ToastTemplateType]::ToastText01)`, `$xml.GetElementsByTagName('text')[0].AppendChild($xml.CreateTextNode('${body}')) > $null`, `[${type}.ToastNotificationManager]::CreateToastNotifier('${title}').Show([${type}.ToastNotification]::new($xml))`, ].join("; "); const { execFile } = require("node:child_process"); execFile("powershell.exe", ["-NoProfile", "-Command", script]); } function notify(title: string, body: string): void { if (process.env.KITTY_WINDOW_ID) { notifyKitty(title, body); } else if (process.env.WT_SESSION) { notifyWindows(title, body); } else { notifyOSC777(title, body); } } // ── Extension ───────────────────────────────────────────────────────────────── export default function (pi: ExtensionAPI) { pi.registerFlag("notify-min-secs", { description: "Minimum agent run time in seconds before a notification fires (default: 8)", type: "number", default: 8, }); let startedAt: number | undefined; pi.on("agent_start", async () => { startedAt = Date.now(); }); pi.on("agent_end", async () => { if (startedAt === undefined) return; const elapsed = (Date.now() - startedAt) / 1000; startedAt = undefined; const minSecs = (pi.getFlag("notify-min-secs") as number | undefined) ?? 8; if (elapsed < minSecs) return; notify("Pi", `Done (${Math.round(elapsed)}s)`); }); }