feat(pi-notifications): use 'tell application' for notifications to suppress Show button
- 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
This commit is contained in:
parent
383cb46fe7
commit
040513e1d6
@ -25,3 +25,12 @@
|
||||
{"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}]}}
|
||||
{"timestamp":"2026-04-28T11:10:37.283Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":32,"inputTokens":6935,"outputTokens":7163,"totalTokens":14098,"prefillTokensPerSec":10428.57,"generationTokensPerSec":50.87,"combinedTokensPerSec":99.66,"totalDurationMs":141464,"timeToFirstTokenMs":665,"rawTimestamps":{"ttftMs":665,"allTtftMs":[665,2503,870,1306,941,835,963,4510,903,972,803],"generationDurationMs":140799,"turns":[{"turnId":"turn-0","durationMs":2679},{"turnId":"turn-1","durationMs":6615},{"turnId":"turn-2","durationMs":6542,"ttftMs":665},{"turnId":"turn-3","durationMs":4622},{"turnId":"turn-4","durationMs":13602,"ttftMs":2503},{"turnId":"turn-5","durationMs":1926,"ttftMs":870},{"turnId":"turn-6","durationMs":8320},{"turnId":"turn-7","durationMs":2635,"ttftMs":1306},{"turnId":"turn-8","durationMs":3534,"ttftMs":941},{"turnId":"turn-9","durationMs":7422,"ttftMs":835},{"turnId":"turn-10","durationMs":2988,"ttftMs":963},{"turnId":"turn-11","durationMs":2253},{"turnId":"turn-12","durationMs":2253},{"turnId":"turn-13","durationMs":2162},{"turnId":"turn-14","durationMs":2460},{"turnId":"turn-15","durationMs":1826},{"turnId":"turn-16","durationMs":1966},{"turnId":"turn-17","durationMs":2357},{"turnId":"turn-18","durationMs":2036},{"turnId":"turn-19","durationMs":3039},{"turnId":"turn-20","durationMs":3115},{"turnId":"turn-21","durationMs":2912},{"turnId":"turn-22","durationMs":3526},{"turnId":"turn-23","durationMs":11560,"ttftMs":4510},{"turnId":"turn-24","durationMs":9433},{"turnId":"turn-25","durationMs":1627},{"turnId":"turn-26","durationMs":5835},{"turnId":"turn-27","durationMs":2414},{"turnId":"turn-28","durationMs":2242,"ttftMs":903},{"turnId":"turn-29","durationMs":5854},{"turnId":"turn-30","durationMs":4553,"ttftMs":972},{"turnId":"turn-31","durationMs":7156,"ttftMs":803}]}}
|
||||
{"timestamp":"2026-04-28T11:16:13.226Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":21,"outputTokens":44,"totalTokens":65,"prefillTokensPerSec":24.91,"generationTokensPerSec":102.8,"combinedTokensPerSec":51.14,"totalDurationMs":1271,"timeToFirstTokenMs":843,"rawTimestamps":{"ttftMs":843,"allTtftMs":[843],"generationDurationMs":428,"turns":[{"turnId":"turn-0","durationMs":1271,"ttftMs":843}]}}
|
||||
{"timestamp":"2026-04-28T11:18:27.997Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":94,"outputTokens":699,"totalTokens":793,"prefillTokensPerSec":17.94,"generationTokensPerSec":77.6,"combinedTokensPerSec":55.66,"totalDurationMs":14248,"timeToFirstTokenMs":5240,"rawTimestamps":{"ttftMs":5240,"allTtftMs":[5240,1223],"generationDurationMs":9008,"turns":[{"turnId":"turn-0","durationMs":11689,"ttftMs":5240},{"turnId":"turn-1","durationMs":2559,"ttftMs":1223}]}}
|
||||
{"timestamp":"2026-04-28T11:19:48.781Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":513,"outputTokens":501,"totalTokens":1014,"prefillTokensPerSec":335.08,"generationTokensPerSec":64.65,"combinedTokensPerSec":109.26,"totalDurationMs":9281,"timeToFirstTokenMs":1531,"rawTimestamps":{"ttftMs":1531,"allTtftMs":[1531,1219],"generationDurationMs":7750,"turns":[{"turnId":"turn-0","durationMs":7110,"ttftMs":1531},{"turnId":"turn-1","durationMs":2171,"ttftMs":1219}]}}
|
||||
{"timestamp":"2026-04-28T11:20:48.239Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":19,"outputTokens":50,"totalTokens":69,"prefillTokensPerSec":26.24,"generationTokensPerSec":124.38,"combinedTokensPerSec":61.28,"totalDurationMs":1126,"timeToFirstTokenMs":724,"rawTimestamps":{"ttftMs":724,"allTtftMs":[724],"generationDurationMs":402,"turns":[{"turnId":"turn-0","durationMs":1126,"ttftMs":724}]}}
|
||||
{"timestamp":"2026-04-28T11:24:12.631Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":4,"inputTokens":3761,"outputTokens":1175,"totalTokens":4936,"prefillTokensPerSec":495.13,"generationTokensPerSec":62.4,"combinedTokensPerSec":186.79,"totalDurationMs":26425,"timeToFirstTokenMs":7596,"rawTimestamps":{"ttftMs":7596,"allTtftMs":[7596,724],"generationDurationMs":18829,"turns":[{"turnId":"turn-0","durationMs":2202},{"turnId":"turn-1","durationMs":6207},{"turnId":"turn-2","durationMs":15923,"ttftMs":7596},{"turnId":"turn-3","durationMs":2093,"ttftMs":724}]}}
|
||||
{"timestamp":"2026-04-28T11:26:02.822Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":2,"inputTokens":260,"outputTokens":299,"totalTokens":559,"prefillTokensPerSec":349.46,"generationTokensPerSec":46.53,"combinedTokensPerSec":77.96,"totalDurationMs":7170,"timeToFirstTokenMs":744,"rawTimestamps":{"ttftMs":744,"allTtftMs":[744],"generationDurationMs":6426,"turns":[{"turnId":"turn-0","durationMs":1741},{"turnId":"turn-1","durationMs":5429,"ttftMs":744}]}}
|
||||
{"timestamp":"2026-04-28T11:28:33.270Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":23,"outputTokens":510,"totalTokens":533,"prefillTokensPerSec":3.54,"generationTokensPerSec":123.76,"combinedTokensPerSec":50.19,"totalDurationMs":10619,"timeToFirstTokenMs":6498,"rawTimestamps":{"ttftMs":6498,"allTtftMs":[6498],"generationDurationMs":4121,"turns":[{"turnId":"turn-0","durationMs":10619,"ttftMs":6498}]}}
|
||||
{"timestamp":"2026-04-28T11:29:17.677Z","provider":"llama.cpp","model":"Qwen3.6-35B-A3B-MXFP4_MOE.gguf","turnCount":1,"inputTokens":33,"outputTokens":628,"totalTokens":661,"prefillTokensPerSec":3.86,"generationTokensPerSec":135.23,"combinedTokensPerSec":50.12,"totalDurationMs":13188,"timeToFirstTokenMs":8544,"rawTimestamps":{"ttftMs":8544,"allTtftMs":[8544],"generationDurationMs":4644,"turns":[{"turnId":"turn-0","durationMs":13188,"ttftMs":8544}]}}
|
||||
|
||||
@ -10,6 +10,7 @@ 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";
|
||||
const targetApp = process.env.PI_NOTIFICATION_APP || "Ghostty";
|
||||
|
||||
function notify(body: string, subtitle?: string): void {
|
||||
if (!enabled) return;
|
||||
@ -17,8 +18,10 @@ function notify(body: string, subtitle?: string): void {
|
||||
const sub = subtitle ? `subtitle "${subtitle}"` : "";
|
||||
// "default" is a reserved word in AppleScript, so only add sound param if it's a custom sound
|
||||
const snd = sound && sound !== "default" ? `sound "${sound}"` : "";
|
||||
// Tell the target app to display the notification — avoids the "Show" button
|
||||
// that appears when osascript is the attributed app
|
||||
execSync(
|
||||
`osascript -e 'display notification "${body}" with title "${title}" ${sub} ${snd}'`.trim(),
|
||||
`osascript -e 'tell application "${targetApp}" to display notification "${body}" with title "${title}" ${sub} ${snd}'`.trim(),
|
||||
{ stdio: "ignore" }
|
||||
);
|
||||
} catch {
|
||||
|
||||
@ -9,17 +9,44 @@ import { execSync } from "node:child_process";
|
||||
|
||||
const title = process.env.PI_NOTIFICATION_TITLE || "pi";
|
||||
const sound = process.env.PI_NOTIFICATION_SOUND || "default";
|
||||
const targetApp = process.env.PI_NOTIFICATION_APP || "Ghostty";
|
||||
|
||||
// "default" is a reserved word in AppleScript, so only add sound param if it's a custom sound
|
||||
const soundArg = sound && sound !== "default" ? `sound "${sound}"` : "";
|
||||
|
||||
try {
|
||||
function tryNotify(tellApp: string): string {
|
||||
const cmd = `osascript -e 'tell application "${tellApp}" to display notification "Test notification from pi-notifications" with title "${title}" ${soundArg}'`.trim();
|
||||
return cmd;
|
||||
}
|
||||
|
||||
function tryPlainNotify(): string {
|
||||
const cmd = `osascript -e 'display notification "Test notification from pi-notifications" with title "${title}" ${soundArg}'`.trim();
|
||||
console.log("[test-notify] running:", cmd);
|
||||
execSync(cmd, { stdio: ["ignore", "pipe", "pipe"] });
|
||||
return cmd;
|
||||
}
|
||||
|
||||
let cmd = tryNotify(targetApp);
|
||||
let success = false;
|
||||
let lastError: string | undefined;
|
||||
|
||||
// Try telling the target app first, fall back to plain display notification
|
||||
for (const attempt of [
|
||||
{ cmd: tryNotify(targetApp), label: `tell "${targetApp}"` },
|
||||
{ cmd: tryPlainNotify(), label: "plain" },
|
||||
]) {
|
||||
try {
|
||||
console.log(`[test-notify] trying ${attempt.label}:`, attempt.cmd);
|
||||
execSync(attempt.cmd, { stdio: ["ignore", "pipe", "pipe"] });
|
||||
success = true;
|
||||
break;
|
||||
} catch (e: any) {
|
||||
lastError = e.message;
|
||||
console.log(`[test-notify] ${attempt.label} failed:`, e.message.split("\n")[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
console.log("[test-notify] ✅ Notification sent — check your Notification Center");
|
||||
} catch (e: any) {
|
||||
console.error("[test-notify] ❌ Failed:", e.message);
|
||||
console.error("[test-notify] Try running the command directly in bash to verify osascript works.");
|
||||
} else {
|
||||
console.error("[test-notify] ❌ Failed:", lastError);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -42,10 +42,20 @@ If `execSync` fails silently, try:
|
||||
|
||||
### 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
|
||||
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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user