From c8a646ba3ec4f4c7eac629cb68c1e2d4aafef6b3 Mon Sep 17 00:00:00 2001 From: "Thomas G. Lopes" Date: Wed, 4 Mar 2026 21:14:36 +0000 Subject: [PATCH] better notifs --- .../agent/extensions/confirm-destructive.ts | 4 ++++ .../{pi-done-notify.ts => notify.ts} | 22 +++++++++---------- pi/files/agent/extensions/permission-gate.ts | 2 ++ 3 files changed, 16 insertions(+), 12 deletions(-) rename pi/files/agent/extensions/{pi-done-notify.ts => notify.ts} (76%) diff --git a/pi/files/agent/extensions/confirm-destructive.ts b/pi/files/agent/extensions/confirm-destructive.ts index 7a32df8..caadf13 100644 --- a/pi/files/agent/extensions/confirm-destructive.ts +++ b/pi/files/agent/extensions/confirm-destructive.ts @@ -6,12 +6,14 @@ */ import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@mariozechner/pi-coding-agent"; +import { sendNotification } from "./notify.js"; export default function (pi: ExtensionAPI) { pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => { if (!ctx.hasUI) return; if (event.reason === "new") { + sendNotification("Clear session confirmation"); const confirmed = await ctx.ui.confirm( "Clear session?", "This will delete all messages in the current session.", @@ -31,6 +33,7 @@ export default function (pi: ExtensionAPI) { ); if (hasUnsavedWork) { + sendNotification("Switch session confirmation"); const confirmed = await ctx.ui.confirm( "Switch session?", "You have messages in the current session. Switch anyway?", @@ -46,6 +49,7 @@ export default function (pi: ExtensionAPI) { pi.on("session_before_fork", async (event, ctx) => { if (!ctx.hasUI) return; + sendNotification("Fork session confirmation"); const choice = await ctx.ui.select(`Fork from entry ${event.entryId.slice(0, 8)}?`, [ "Yes, create fork", "No, stay in current session", diff --git a/pi/files/agent/extensions/pi-done-notify.ts b/pi/files/agent/extensions/notify.ts similarity index 76% rename from pi/files/agent/extensions/pi-done-notify.ts rename to pi/files/agent/extensions/notify.ts index 26ab8bd..9248c35 100644 --- a/pi/files/agent/extensions/pi-done-notify.ts +++ b/pi/files/agent/extensions/notify.ts @@ -1,7 +1,7 @@ /** - * Pi Done Notify Extension + * Notify Extension * - * Sends a notification when Pi finishes a prompt. + * Sends a notification when Pi finishes a prompt or when interactive tools are called. * If on SSH/remote, SSHs back to Linux PC to play sound + show notification. */ @@ -9,8 +9,6 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; // ============ CONFIGURATION ============ const REMOTE_HOST = "linux-pc"; // SSH host for remote notifications -const REMOTE_COMMAND = - "paplay /usr/share/sounds/freedesktop/stereo/window-attention.oga & notify-send -i ~/.pi/agent/extensions/assets/pi-logo.svg 'Pi' 'Done. Ready for input.'"; const LOCAL_SOUND_MAC = "/System/Library/Sounds/Glass.aiff"; const LOCAL_SOUND_LINUX = "/usr/share/sounds/freedesktop/stereo/complete.oga"; // ======================================= @@ -36,15 +34,15 @@ function isSSH(): boolean { } } -function notifyRemote(): void { +function notifyRemote(message: string): void { const { exec } = require("child_process"); exec( - `ssh -o ConnectTimeout=2 -o BatchMode=yes ${REMOTE_HOST} "${REMOTE_COMMAND}"`, + `ssh -o ConnectTimeout=2 -o BatchMode=yes ${REMOTE_HOST} "paplay /usr/share/sounds/freedesktop/stereo/window-attention.oga & notify-send -i ~/.pi/agent/extensions/assets/pi-logo.svg 'Pi' '${message}'"`, { timeout: 5000 }, ); } -function notifyLocal(): void { +function notifyLocal(message: string): void { const { execFile, exec } = require("child_process"); if (process.platform === "darwin") { execFile("afplay", [LOCAL_SOUND_MAC]); @@ -57,7 +55,7 @@ function notifyLocal(): void { }, ); exec( - "notify-send -i ~/.pi/agent/extensions/assets/pi-logo.svg 'Pi' 'Done. Ready for input.'", + `notify-send -i ~/.pi/agent/extensions/assets/pi-logo.svg 'Pi' '${message}'`, (err: any) => { if (err) console.error("notify-send error:", err); }, @@ -65,13 +63,13 @@ function notifyLocal(): void { } } -function sendNotification(): void { +export function sendNotification(message: string = "Done. Ready for input."): void { if (process.platform === "darwin" || isSSH()) { - notifyRemote(); + notifyRemote(message); } // always notify locally too - notifyLocal(); + notifyLocal(message); } const ACTION_NOTIFY_TOOLS = new Set(["question", "questionnaire"]); @@ -86,7 +84,7 @@ export default function (pi: ExtensionAPI) { pi.on("tool_call", async (event, ctx) => { if (!ctx.hasUI) return; if (!ACTION_NOTIFY_TOOLS.has(event.toolName)) return; - sendNotification(); + sendNotification("Question requires input"); }); // Simple test command diff --git a/pi/files/agent/extensions/permission-gate.ts b/pi/files/agent/extensions/permission-gate.ts index 0fc97c4..b77d5d9 100644 --- a/pi/files/agent/extensions/permission-gate.ts +++ b/pi/files/agent/extensions/permission-gate.ts @@ -6,6 +6,7 @@ */ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; +import { sendNotification } from "./notify.js"; export default function (pi: ExtensionAPI) { const dangerousPatterns = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i]; @@ -22,6 +23,7 @@ export default function (pi: ExtensionAPI) { return { block: true, reason: "Dangerous command blocked (no UI for confirmation)" }; } + sendNotification("Destructive command pending"); const choice = await ctx.ui.select(`⚠️ Dangerous command:\n\n ${command}\n\nAllow?`, ["Yes", "No"]); if (choice !== "Yes") {