better notifs
This commit is contained in:
@@ -6,12 +6,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI, SessionBeforeSwitchEvent, SessionMessageEntry } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { sendNotification } from "./notify.js";
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
export default function (pi: ExtensionAPI) {
|
||||||
pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => {
|
pi.on("session_before_switch", async (event: SessionBeforeSwitchEvent, ctx) => {
|
||||||
if (!ctx.hasUI) return;
|
if (!ctx.hasUI) return;
|
||||||
|
|
||||||
if (event.reason === "new") {
|
if (event.reason === "new") {
|
||||||
|
sendNotification("Clear session confirmation");
|
||||||
const confirmed = await ctx.ui.confirm(
|
const confirmed = await ctx.ui.confirm(
|
||||||
"Clear session?",
|
"Clear session?",
|
||||||
"This will delete all messages in the current session.",
|
"This will delete all messages in the current session.",
|
||||||
@@ -31,6 +33,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (hasUnsavedWork) {
|
if (hasUnsavedWork) {
|
||||||
|
sendNotification("Switch session confirmation");
|
||||||
const confirmed = await ctx.ui.confirm(
|
const confirmed = await ctx.ui.confirm(
|
||||||
"Switch session?",
|
"Switch session?",
|
||||||
"You have messages in the current session. Switch anyway?",
|
"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) => {
|
pi.on("session_before_fork", async (event, ctx) => {
|
||||||
if (!ctx.hasUI) return;
|
if (!ctx.hasUI) return;
|
||||||
|
|
||||||
|
sendNotification("Fork session confirmation");
|
||||||
const choice = await ctx.ui.select(`Fork from entry ${event.entryId.slice(0, 8)}?`, [
|
const choice = await ctx.ui.select(`Fork from entry ${event.entryId.slice(0, 8)}?`, [
|
||||||
"Yes, create fork",
|
"Yes, create fork",
|
||||||
"No, stay in current session",
|
"No, stay in current session",
|
||||||
|
|||||||
@@ -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.
|
* 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 ============
|
// ============ CONFIGURATION ============
|
||||||
const REMOTE_HOST = "linux-pc"; // SSH host for remote notifications
|
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_MAC = "/System/Library/Sounds/Glass.aiff";
|
||||||
const LOCAL_SOUND_LINUX = "/usr/share/sounds/freedesktop/stereo/complete.oga";
|
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");
|
const { exec } = require("child_process");
|
||||||
exec(
|
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 },
|
{ timeout: 5000 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyLocal(): void {
|
function notifyLocal(message: string): void {
|
||||||
const { execFile, exec } = require("child_process");
|
const { execFile, exec } = require("child_process");
|
||||||
if (process.platform === "darwin") {
|
if (process.platform === "darwin") {
|
||||||
execFile("afplay", [LOCAL_SOUND_MAC]);
|
execFile("afplay", [LOCAL_SOUND_MAC]);
|
||||||
@@ -57,7 +55,7 @@ function notifyLocal(): void {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
exec(
|
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) => {
|
(err: any) => {
|
||||||
if (err) console.error("notify-send error:", err);
|
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()) {
|
if (process.platform === "darwin" || isSSH()) {
|
||||||
notifyRemote();
|
notifyRemote(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// always notify locally too
|
// always notify locally too
|
||||||
notifyLocal();
|
notifyLocal(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ACTION_NOTIFY_TOOLS = new Set(["question", "questionnaire"]);
|
const ACTION_NOTIFY_TOOLS = new Set(["question", "questionnaire"]);
|
||||||
@@ -86,7 +84,7 @@ export default function (pi: ExtensionAPI) {
|
|||||||
pi.on("tool_call", async (event, ctx) => {
|
pi.on("tool_call", async (event, ctx) => {
|
||||||
if (!ctx.hasUI) return;
|
if (!ctx.hasUI) return;
|
||||||
if (!ACTION_NOTIFY_TOOLS.has(event.toolName)) return;
|
if (!ACTION_NOTIFY_TOOLS.has(event.toolName)) return;
|
||||||
sendNotification();
|
sendNotification("Question requires input");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Simple test command
|
// Simple test command
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { sendNotification } from "./notify.js";
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
export default function (pi: ExtensionAPI) {
|
||||||
const dangerousPatterns = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
|
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)" };
|
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"]);
|
const choice = await ctx.ui.select(`⚠️ Dangerous command:\n\n ${command}\n\nAllow?`, ["Yes", "No"]);
|
||||||
|
|
||||||
if (choice !== "Yes") {
|
if (choice !== "Yes") {
|
||||||
|
|||||||
Reference in New Issue
Block a user