- Tell target app (default: Ghostty) to display notification instead of raw osascript - This attributes notification to the app, avoiding the 'Show' button that opens Script Editor - Configurable via PI_NOTIFICATION_APP env var - test-notify.ts falls back to plain display notification if target app isn't running - Synced to auto-discovery extension path
7.3 KiB
Plan: pi-notifications v0 — Desktop Notifications for Agent Events
Goal
Make the pi-notifications extension reliably show macOS Notification Center alerts when the agent finishes a turn, so the user gets alerted without needing to watch the screen.
Current State
- Extension exists at
packages/pi-notifications/src/index.ts(monorepo) and~/.pi/agent/extensions/pi-notifications.ts(auto-discovery) - Extension loads correctly (appears in
/reloadextension list) console.logfrom extensions is NOT visible in/reloadoutputosascriptworks when run directly in bash, but notification doesn't appear when called from the extension- The
session_starthandler fires on reload,agent_endfires when prompts complete
Debugging Strategy (split into two orthogonal problems)
Problem A: "Does the trigger fire?" — visible debug signal
console.log from extensions is invisible in pi's TUI output. To debug the trigger logic in a fast loop, add a debug mode (PI_NOTIFICATION_DEBUG=true) that emits a visible signal via ctx.ui.steer() (or similar) right before calling notify(). This surfaces in the chat/TUI so you can verify the handler fires without needing actual desktop notifications.
Problem B: "Does osascript actually deliver?" — isolated tester
Create a standalone script (test-notify.ts) that you run from bash independently of the agent loop. This verifies osascript works in the extension's import context, decoupled from event handlers.
1. Verify osascript works in extension context
The extension uses execSync from node:child_process. Test that it works inside the extension:
// In the extension, add this to session_start handler:
try {
const output = execSync('osascript -e "display notification \\"test\\" with title \\"test\\""').toString();
console.log("[pi-notifications] osascript output:", output);
} catch (e: any) {
console.log("[pi-notifications] osascript error:", e.message, e.stderr?.toString());
}
If execSync fails silently, try:
- Using
{ stdio: ["pipe", "pipe", "pipe"] }to capture stderr - Checking if
node:child_processis available in the extension sandbox
2. Check macOS notification settings
Notifications may be delivered but not shown as banners:
- System Settings → Notifications → Ghostty → Notification Style — must be "Banners" or "Alerts", not "None" (osascript fires from the Ghostty process, so macOS attributes notifications to Ghostty, not "pi")
- System Settings → Focus → [active focus] → Apps — ensure "Ghostty" is not excluded
- System Settings → Notifications → Show Notifications on Lock Screen — enable if needed
Known symptom: Notifications appear in Notification Center when pulled down, but never pop up as banners. This is a macOS style setting, not a code issue.
3. Ghostty suppresses banners when focused
Ghostty intentionally silences banner notifications (no pop-up, no sound) when the Ghostty window is active/focused. The notification is still delivered to Notification Center. Banners only appear when Ghostty is not the active window.
Workarounds:
- System Settings → Notifications → Ghostty → Alert Style → "Persistent" — macOS shows these as banners regardless of Ghostty's silencing
- Switch to another app (e.g. leave your browser open) when you want to see the banner
3. Try alternative notification methods
If osascript doesn't work from the extension, try:
notify-send(Linux-only, not relevant for macOS)- A custom TUI widget that shows a persistent banner
- Using
ctx.ui.notify()(but this only shows in pi's TUI, not system notification)
4. Verify event handlers fire
Add a session_start handler that definitely fires:
pi.on("session_start", async (_event, ctx) => {
console.log("[pi-notifications] session_start fired");
ctx.ui.notify("pi-notifications active", "info"); // Shows in TUI
});
If ctx.ui.notify() works but osascript doesn't, the issue is macOS notification permissions, not the extension.
Implementation Plan
Step 0A: Add debug mode with visible signal (PI_NOTIFICATION_DEBUG)
Add a PI_NOTIFICATION_DEBUG=true env var. When enabled, the extension calls ctx.ui.steer() (or a visible TUI signal) right before each notification, so you see "notification triggered" in the chat output during the agent loop. This lets you verify trigger logic without needing actual desktop notifications.
- In
agent_endhandler: ifPI_NOTIFICATION_DEBUG=true, callctx.ui.steer("[pi-notifications] notification triggered")beforenotify() - In
session_starthandler: same pattern - This is purely for debugging — no desktop notification shown when debug is on (or both are shown)
Step 0B: Create isolated notification tester
Create packages/pi-notifications/src/test-notify.ts — a standalone script runnable via npx jiti that fires a test notification. Run it from bash to verify osascript works in the extension's context, completely separate from the agent loop.
Step 1: Fix notification delivery (priority)
Once the root cause is identified:
If osascript works but notification is suppressed:
- Add a
PI_NOTIFICATION_SOUNDenv var (already in design) - Add
PI_NOTIFICATIONS_ENABLEDtoggle (already in design) - Consider adding a "first-run" notification that asks user to enable notifications
If osascript doesn't work from extension:
- Fall back to
ctx.ui.notify()which shows in pi's TUI - Or use a different approach (e.g., write to a file that a separate process monitors)
Step 2: Add turn-limit notification
In packages/pi-turn-limit/src/turn-limit.ts, add notification when the limit is reached:
// In the turn-limit extension, when the limit fires:
if (shouldNotify) {
execSync('osascript -e \'display notification "Turn limit reached" with title "pi" subtitle "Turns: ' + turnCount + '/' + maxTurns + '"\'');
}
Configuration via env var:
PI_NOTIFICATION_TURN_LIMIT— defaulttrue, set tofalseto disable
Step 3: Add sound option
Already designed in the extension:
PI_NOTIFICATION_SOUNDenv var (default:default)- macOS sounds:
Bottle,Ping,Pop,Submarine,Sosumi,Tink - Set to
""for silent
Step 4: Update README
Document the extension with:
- What it does
- Configuration options
- How to enable macOS notifications
- Troubleshooting tips
Files to Modify
| File | Action |
|---|---|
~/.pi/agent/extensions/pi-notifications.ts |
Debug and fix notification delivery |
packages/pi-notifications/src/index.ts |
Sync fixes from auto-discovery version |
packages/pi-turn-limit/src/turn-limit.ts |
Add turn-limit notification |
packages/pi-notifications/README.md |
Update with notification docs |
Success Criteria
- ✅ Extension loads and appears in
/reloadoutput - ✅ macOS Notification Center shows "pi-notifications active" on reload
- ✅ macOS Notification Center shows "Agent finished — N turns" when agent completes a prompt
- ✅ Turn-limit notification shows when turn limit is exceeded
- ✅
PI_NOTIFICATIONS_ENABLED=falsedisables all notifications - ✅ README documents all configuration options
- ✅
PI_NOTIFICATION_DEBUG=trueshows visible signal in TUI when handlers fire - ✅
test-notify.tsfires a notification when run standalone