feat(pi-notifications): add PI_NOTIFICATION_DEBUG mode with visible steer signal
- Add PI_NOTIFICATION_DEBUG=true env var - When enabled, calls ctx.ui.steer() instead of desktop notification - Lets you verify trigger logic in the agent loop without actual notifications - Synced to both monorepo and auto-discovery extension paths
This commit is contained in:
parent
ce4d6c5971
commit
45a13fd08c
@ -11,3 +11,17 @@
|
||||
{"timestamp":"2026-04-28T09:40:15.439Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":97,"outputTokens":733,"totalTokens":830,"prefillTokensPerSec":63.19,"generationTokensPerSec":46.13,"combinedTokensPerSec":47.63,"totalDurationMs":17425,"timeToFirstTokenMs":1535,"rawTimestamps":{"ttftMs":1535,"allTtftMs":[1535,1095],"generationDurationMs":15890,"turns":[{"turnId":"turn-0","durationMs":8515,"ttftMs":1535},{"turnId":"turn-1","durationMs":8910,"ttftMs":1095}]}}
|
||||
{"timestamp":"2026-04-28T09:41:02.168Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":33,"outputTokens":386,"totalTokens":419,"prefillTokensPerSec":5.11,"generationTokensPerSec":135.87,"combinedTokensPerSec":45.06,"totalDurationMs":9298,"timeToFirstTokenMs":6457,"rawTimestamps":{"ttftMs":6457,"allTtftMs":[6457],"generationDurationMs":2841,"turns":[{"turnId":"turn-0","durationMs":9298,"ttftMs":6457}]}}
|
||||
{"timestamp":"2026-04-28T09:47:46.401Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":36,"inputTokens":15460,"outputTokens":12179,"totalTokens":27639,"prefillTokensPerSec":20104.03,"generationTokensPerSec":52.58,"combinedTokensPerSec":118.93,"totalDurationMs":232395,"timeToFirstTokenMs":769,"rawTimestamps":{"ttftMs":769,"allTtftMs":[769,3069,6075,3956,3658,3607,3823,3913,9797,0,0,1,13456],"generationDurationMs":231626,"turns":[{"turnId":"turn-0","durationMs":2786,"ttftMs":769},{"turnId":"turn-1","durationMs":2727},{"turnId":"turn-2","durationMs":2103},{"turnId":"turn-3","durationMs":2530},{"turnId":"turn-4","durationMs":2754},{"turnId":"turn-5","durationMs":3357},{"turnId":"turn-6","durationMs":10489,"ttftMs":3069},{"turnId":"turn-7","durationMs":2625},{"turnId":"turn-8","durationMs":15034,"ttftMs":6075},{"turnId":"turn-9","durationMs":2817},{"turnId":"turn-10","durationMs":14176,"ttftMs":3956},{"turnId":"turn-11","durationMs":2537},{"turnId":"turn-12","durationMs":16864,"ttftMs":3658},{"turnId":"turn-13","durationMs":3137},{"turnId":"turn-14","durationMs":18123,"ttftMs":3607},{"turnId":"turn-15","durationMs":2307},{"turnId":"turn-16","durationMs":17842,"ttftMs":3823},{"turnId":"turn-17","durationMs":2287},{"turnId":"turn-18","durationMs":10719,"ttftMs":3913},{"turnId":"turn-19","durationMs":24735},{"turnId":"turn-20","durationMs":2656},{"turnId":"turn-21","durationMs":15351,"ttftMs":9797},{"turnId":"turn-22","durationMs":22251},{"turnId":"turn-23","durationMs":4702},{"turnId":"turn-24","durationMs":2,"ttftMs":0},{"turnId":"turn-25","durationMs":0},{"turnId":"turn-26","durationMs":1},{"turnId":"turn-27","durationMs":0,"ttftMs":0},{"turnId":"turn-28","durationMs":1},{"turnId":"turn-29","durationMs":1},{"turnId":"turn-30","durationMs":0},{"turnId":"turn-31","durationMs":1},{"turnId":"turn-32","durationMs":1,"ttftMs":1},{"turnId":"turn-33","durationMs":1},{"turnId":"turn-34","durationMs":0},{"turnId":"turn-35","durationMs":27478,"ttftMs":13456}]}}
|
||||
{"timestamp":"2026-04-28T09:51:50.695Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":466,"outputTokens":858,"totalTokens":1324,"prefillTokensPerSec":52.59,"generationTokensPerSec":46.14,"combinedTokensPerSec":48.22,"totalDurationMs":27455,"timeToFirstTokenMs":8861,"rawTimestamps":{"ttftMs":8861,"allTtftMs":[8861,9050],"generationDurationMs":18594,"turns":[{"turnId":"turn-0","durationMs":11153,"ttftMs":8861},{"turnId":"turn-1","durationMs":16302,"ttftMs":9050}]}}
|
||||
{"timestamp":"2026-04-28T10:08:03.362Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":11,"inputTokens":1836,"outputTokens":2319,"totalTokens":4155,"prefillTokensPerSec":285.71,"generationTokensPerSec":31.6,"combinedTokensPerSec":52.06,"totalDurationMs":79806,"timeToFirstTokenMs":6426,"rawTimestamps":{"ttftMs":6426,"allTtftMs":[6426,4917],"generationDurationMs":73380,"turns":[{"turnId":"turn-0","durationMs":4780},{"turnId":"turn-1","durationMs":6008},{"turnId":"turn-2","durationMs":6163},{"turnId":"turn-3","durationMs":13885},{"turnId":"turn-4","durationMs":9190},{"turnId":"turn-5","durationMs":3312},{"turnId":"turn-6","durationMs":4928},{"turnId":"turn-7","durationMs":6900},{"turnId":"turn-8","durationMs":4513},{"turnId":"turn-9","durationMs":10233,"ttftMs":6426},{"turnId":"turn-10","durationMs":9894,"ttftMs":4917}]}}
|
||||
{"timestamp":"2026-04-28T10:19:08.786Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":16,"outputTokens":39,"totalTokens":55,"prefillTokensPerSec":14.55,"generationTokensPerSec":68.18,"combinedTokensPerSec":32.89,"totalDurationMs":1672,"timeToFirstTokenMs":1100,"rawTimestamps":{"ttftMs":1100,"allTtftMs":[1100],"generationDurationMs":572,"turns":[{"turnId":"turn-0","durationMs":1672,"ttftMs":1100}]}}
|
||||
{"timestamp":"2026-04-28T10:20:08.735Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":19,"outputTokens":49,"totalTokens":68,"prefillTokensPerSec":15.85,"generationTokensPerSec":74.13,"combinedTokensPerSec":36.56,"totalDurationMs":1860,"timeToFirstTokenMs":1199,"rawTimestamps":{"ttftMs":1199,"allTtftMs":[1199],"generationDurationMs":661,"turns":[{"turnId":"turn-0","durationMs":1860,"ttftMs":1199}]}}
|
||||
{"timestamp":"2026-04-28T10:23:13.758Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":6,"inputTokens":925,"outputTokens":1473,"totalTokens":2398,"prefillTokensPerSec":136.29,"generationTokensPerSec":35.16,"combinedTokensPerSec":49.26,"totalDurationMs":48677,"timeToFirstTokenMs":6787,"rawTimestamps":{"ttftMs":6787,"allTtftMs":[6787,5510,2924],"generationDurationMs":41890,"turns":[{"turnId":"turn-0","durationMs":8739,"ttftMs":6787},{"turnId":"turn-1","durationMs":14852,"ttftMs":5510},{"turnId":"turn-2","durationMs":5667},{"turnId":"turn-3","durationMs":6604},{"turnId":"turn-4","durationMs":4277},{"turnId":"turn-5","durationMs":8538,"ttftMs":2924}]}}
|
||||
{"timestamp":"2026-04-28T10:33:57.836Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":34,"inputTokens":18464,"outputTokens":8806,"totalTokens":27270,"prefillTokensPerSec":1285.88,"generationTokensPerSec":17.17,"combinedTokensPerSec":51.74,"totalDurationMs":527083,"timeToFirstTokenMs":14359,"rawTimestamps":{"ttftMs":14359,"allTtftMs":[14359,6019,5699,46350,7361,11610],"generationDurationMs":512724.00000000006,"turns":[{"turnId":"turn-0","durationMs":19887,"ttftMs":14359},{"turnId":"turn-1","durationMs":9752},{"turnId":"turn-2","durationMs":12545},{"turnId":"turn-3","durationMs":11989},{"turnId":"turn-4","durationMs":6672},{"turnId":"turn-5","durationMs":11330,"ttftMs":6019},{"turnId":"turn-6","durationMs":6916},{"turnId":"turn-7","durationMs":6962},{"turnId":"turn-8","durationMs":10087,"ttftMs":5699},{"turnId":"turn-9","durationMs":5183},{"turnId":"turn-10","durationMs":9006},{"turnId":"turn-11","durationMs":7101},{"turnId":"turn-12","durationMs":7657},{"turnId":"turn-13","durationMs":7486},{"turnId":"turn-14","durationMs":5186},{"turnId":"turn-15","durationMs":4095},{"turnId":"turn-16","durationMs":8195},{"turnId":"turn-17","durationMs":4141},{"turnId":"turn-18","durationMs":8813},{"turnId":"turn-19","durationMs":7467},{"turnId":"turn-20","durationMs":12261},{"turnId":"turn-21","durationMs":20658},{"turnId":"turn-22","durationMs":11466},{"turnId":"turn-23","durationMs":9632},{"turnId":"turn-24","durationMs":4},{"turnId":"turn-25","durationMs":137389},{"turnId":"turn-26","durationMs":50466,"ttftMs":46350},{"turnId":"turn-27","durationMs":20903},{"turnId":"turn-28","durationMs":23963},{"turnId":"turn-29","durationMs":25303},{"turnId":"turn-30","durationMs":15829,"ttftMs":7361},{"turnId":"turn-31","durationMs":11275},{"turnId":"turn-32","durationMs":3688},{"turnId":"turn-33","durationMs":13776,"ttftMs":11610}]}}
|
||||
{"timestamp":"2026-04-28T10:55:42.797Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":3,"inputTokens":166,"outputTokens":1127,"totalTokens":1293,"prefillTokensPerSec":7.05,"generationTokensPerSec":60.02,"combinedTokensPerSec":30.54,"totalDurationMs":42332,"timeToFirstTokenMs":23554,"rawTimestamps":{"ttftMs":23554,"allTtftMs":[23554,1590],"generationDurationMs":18778,"turns":[{"turnId":"turn-0","durationMs":10227},{"turnId":"turn-1","durationMs":27669,"ttftMs":23554},{"turnId":"turn-2","durationMs":4436,"ttftMs":1590}]}}
|
||||
{"timestamp":"2026-04-28T10:57:08.818Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":3,"inputTokens":182,"outputTokens":1366,"totalTokens":1548,"prefillTokensPerSec":4.65,"generationTokensPerSec":101.61,"combinedTokensPerSec":29.44,"totalDurationMs":52587,"timeToFirstTokenMs":39143,"rawTimestamps":{"ttftMs":39143,"allTtftMs":[39143,1730],"generationDurationMs":13444,"turns":[{"turnId":"turn-0","durationMs":43787,"ttftMs":39143},{"turnId":"turn-1","durationMs":5772},{"turnId":"turn-2","durationMs":3028,"ttftMs":1730}]}}
|
||||
{"timestamp":"2026-04-28T10:58:10.094Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":5,"inputTokens":553,"outputTokens":1115,"totalTokens":1668,"prefillTokensPerSec":31.66,"generationTokensPerSec":41.13,"combinedTokensPerSec":37.42,"totalDurationMs":44572,"timeToFirstTokenMs":17465,"rawTimestamps":{"ttftMs":17465,"allTtftMs":[17465,3182],"generationDurationMs":27107,"turns":[{"turnId":"turn-0","durationMs":28735,"ttftMs":17465},{"turnId":"turn-1","durationMs":2534},{"turnId":"turn-2","durationMs":7122},{"turnId":"turn-3","durationMs":2172},{"turnId":"turn-4","durationMs":4009,"ttftMs":3182}]}}
|
||||
{"timestamp":"2026-04-28T10:59:43.047Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":190,"outputTokens":891,"totalTokens":1081,"prefillTokensPerSec":16.33,"generationTokensPerSec":20.71,"combinedTokensPerSec":19.78,"totalDurationMs":54655,"timeToFirstTokenMs":11632,"rawTimestamps":{"ttftMs":11632,"allTtftMs":[11632,7835],"generationDurationMs":43023,"turns":[{"turnId":"turn-0","durationMs":34848,"ttftMs":11632},{"turnId":"turn-1","durationMs":19807,"ttftMs":7835}]}}
|
||||
{"timestamp":"2026-04-28T11:04:01.853Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":1334,"outputTokens":1358,"totalTokens":2692,"prefillTokensPerSec":183.87,"generationTokensPerSec":28.7,"combinedTokensPerSec":49.33,"totalDurationMs":54576,"timeToFirstTokenMs":7255,"rawTimestamps":{"ttftMs":7255,"allTtftMs":[7255],"generationDurationMs":47321,"turns":[{"turnId":"turn-0","durationMs":43344},{"turnId":"turn-1","durationMs":11232,"ttftMs":7255}]}}
|
||||
{"timestamp":"2026-04-28T11:04:07.153Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":14,"outputTokens":20,"totalTokens":34,"prefillTokensPerSec":11.77,"generationTokensPerSec":176.99,"combinedTokensPerSec":26.11,"totalDurationMs":1302,"timeToFirstTokenMs":1189,"rawTimestamps":{"ttftMs":1189,"allTtftMs":[1189],"generationDurationMs":113,"turns":[{"turnId":"turn-0","durationMs":1302,"ttftMs":1189}]}}
|
||||
{"timestamp":"2026-04-28T11:06:19.732Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":3955,"outputTokens":52,"totalTokens":4007,"prefillTokensPerSec":0,"generationTokensPerSec":0,"combinedTokensPerSec":823.13,"totalDurationMs":4868,"rawTimestamps":{"allTtftMs":[],"turns":[{"turnId":"turn-0","durationMs":4868}]}}
|
||||
{"timestamp":"2026-04-28T11:07:01.680Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":3,"inputTokens":1592,"outputTokens":977,"totalTokens":2569,"prefillTokensPerSec":486.11,"generationTokensPerSec":59.49,"combinedTokensPerSec":130.41,"totalDurationMs":19699,"timeToFirstTokenMs":3275,"rawTimestamps":{"ttftMs":3275,"allTtftMs":[3275,3442],"generationDurationMs":16424,"turns":[{"turnId":"turn-0","durationMs":1422},{"turnId":"turn-1","durationMs":5574,"ttftMs":3275},{"turnId":"turn-2","durationMs":12703,"ttftMs":3442}]}}
|
||||
|
||||
38
packages/pi-notifications/README.md
Normal file
38
packages/pi-notifications/README.md
Normal file
@ -0,0 +1,38 @@
|
||||
# pi-notifications
|
||||
|
||||
Desktop notifications for pi agent events via macOS Notification Center.
|
||||
|
||||
## What it does
|
||||
|
||||
Shows a system notification when the agent finishes a turn, so you can step away and get alerted when input is needed.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Env var | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `PI_NOTIFICATIONS_ENABLED` | `true` | Set to `false` to disable all notifications |
|
||||
| `PI_NOTIFICATION_AGENT_END` | `true` | Notification when agent finishes |
|
||||
| `PI_NOTIFICATION_TITLE` | `pi` | Notification title |
|
||||
| `PI_NOTIFICATION_SOUND` | `default` | macOS sound (Bottle, Ping, Pop, etc.) or `""` for silent |
|
||||
|
||||
## Usage
|
||||
|
||||
Add to `~/.pi/agent/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"packages": [
|
||||
"/path/to/packages/pi-notifications"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Then reload pi:
|
||||
|
||||
```bash
|
||||
/reload
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
17
packages/pi-notifications/package.json
Normal file
17
packages/pi-notifications/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "pi-notifications",
|
||||
"version": "0.1.0",
|
||||
"description": "Desktop notifications for pi agent events",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"keywords": ["pi-package"],
|
||||
"pi": {
|
||||
"extensions": ["src/index.ts"]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mariozechner/pi-coding-agent": "*"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
52
packages/pi-notifications/src/index.ts
Normal file
52
packages/pi-notifications/src/index.ts
Normal file
@ -0,0 +1,52 @@
|
||||
// Desktop notifications for pi agent events
|
||||
// Uses osascript (macOS) to trigger Notification Center alerts
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
// Configuration via environment variables
|
||||
const enabled = process.env.PI_NOTIFICATIONS_ENABLED !== "false";
|
||||
const agentEndEnabled = process.env.PI_NOTIFICATION_AGENT_END !== "false";
|
||||
const debug = process.env.PI_NOTIFICATION_DEBUG === "true";
|
||||
const title = process.env.PI_NOTIFICATION_TITLE || "pi";
|
||||
const sound = process.env.PI_NOTIFICATION_SOUND || "default";
|
||||
|
||||
function notify(body: string, subtitle?: string): void {
|
||||
if (!enabled) return;
|
||||
try {
|
||||
const sub = subtitle ? `subtitle "${subtitle}"` : "";
|
||||
const snd = sound ? `sound "${sound}"` : "";
|
||||
execSync(
|
||||
`osascript -e 'display notification "${body}" with title "${title}" ${sub} ${snd}'`.trim(),
|
||||
{ stdio: "ignore" }
|
||||
);
|
||||
} catch {
|
||||
// osascript not available (non-macOS) — silently fail
|
||||
}
|
||||
}
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
console.log("[pi-notifications] loaded (enabled=" + enabled + ", agentEnd=" + agentEndEnabled + ")");
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
if (debug) {
|
||||
ctx.ui.steer("[pi-notifications] session_start — debug mode, skipping actual notification");
|
||||
return;
|
||||
}
|
||||
if (enabled) {
|
||||
notify("pi-notifications active", "Listening for agent_end");
|
||||
}
|
||||
});
|
||||
|
||||
pi.on("agent_end", async (event, ctx) => {
|
||||
if (!agentEndEnabled) return;
|
||||
|
||||
if (debug) {
|
||||
ctx.ui.steer(`[pi-notifications] agent_end — debug mode, skipping actual notification (${event.messages?.length ?? 0} messages)`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[pi-notifications] agent_end: messages=${JSON.stringify(event.messages?.map((m: any) => m.type))}`);
|
||||
notify("Agent finished", `${event.messages?.length ?? 0} turns`);
|
||||
});
|
||||
}
|
||||
144
plans/pi-notifications-version-0.md
Normal file
144
plans/pi-notifications-version-0.md
Normal file
@ -0,0 +1,144 @@
|
||||
# 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 `/reload` extension list)
|
||||
- `console.log` from extensions is NOT visible in `/reload` output
|
||||
- `osascript` works when run directly in bash, but notification doesn't appear when called from the extension
|
||||
- The `session_start` handler fires on reload, `agent_end` fires 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:
|
||||
|
||||
```typescript
|
||||
// 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_process` is available in the extension sandbox
|
||||
|
||||
### 2. Check macOS notification settings
|
||||
|
||||
The notification might be suppressed by macOS settings:
|
||||
- System Settings → Notifications → check that "Terminal" or "pi" is allowed
|
||||
- Check if "Show Notifications on Lock Screen" is enabled
|
||||
- Check if "Focus Modes" are suppressing notifications
|
||||
|
||||
### 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:
|
||||
|
||||
```typescript
|
||||
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_end` handler: if `PI_NOTIFICATION_DEBUG=true`, call `ctx.ui.steer("[pi-notifications] notification triggered")` before `notify()`
|
||||
- In `session_start` handler: 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_SOUND` env var (already in design)
|
||||
- Add `PI_NOTIFICATIONS_ENABLED` toggle (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:
|
||||
|
||||
```typescript
|
||||
// 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` — default `true`, set to `false` to disable
|
||||
|
||||
### Step 3: Add sound option
|
||||
|
||||
Already designed in the extension:
|
||||
- `PI_NOTIFICATION_SOUND` env 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
|
||||
|
||||
1. ✅ Extension loads and appears in `/reload` output
|
||||
2. ✅ macOS Notification Center shows "pi-notifications active" on reload
|
||||
3. ✅ macOS Notification Center shows "Agent finished — N turns" when agent completes a prompt
|
||||
4. ✅ Turn-limit notification shows when turn limit is exceeded
|
||||
5. ✅ `PI_NOTIFICATIONS_ENABLED=false` disables all notifications
|
||||
6. ✅ README documents all configuration options
|
||||
7. ✅ `PI_NOTIFICATION_DEBUG=true` shows visible signal in TUI when handlers fire
|
||||
8. ✅ `test-notify.ts` fires a notification when run standalone
|
||||
Loading…
x
Reference in New Issue
Block a user