fix(pi-notify): default Kitty visibility to always

This commit is contained in:
云服务部-叶林立
2026-08-26 10:36:03 +08:00
parent dc925e1eeb
commit fcdaa25b8f
7 changed files with 13 additions and 11 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ Kitty-first completion notifications for Pi. This repository maintains a source
- Waits for Pi's `agent_settled` event, so retries, automatic compaction, and queued follow-ups do not notify early.
- Uses Kitty OSC 99 with Base64 payloads, an ID unique to each Pi session, and explicit `a=focus`.
- Defaults to `o=unfocused`: no notification while the originating Kitty window/pane has keyboard focus; clicking a notification returns to that exact source window.
- Defaults to `o=always`: notifications remain visible while the originating Kitty window/pane has keyboard focus; clicking a notification returns to that exact source window.
- Measures the entire run across automatic retries and ignores runs shorter than three seconds by default.
- Emits terminal sequences only in interactive TUI mode, never into JSON, print, or RPC output.
- Uses macOS AppleScript outside Kitty and a sanitized OSC 777 fallback outside macOS.
@@ -37,7 +37,7 @@ Command overrides are session-local. Use environment variables for persistent de
- `PI_NOTIFY_MESSAGE_SOURCE`: `assistant`, `user`, or privacy mode `none`; default `assistant`.
- `PI_NOTIFY_MESSAGE_MAX`: maximum message characters before ellipsis; default `80`, maximum `500`.
- `PI_NOTIFY_MIN_SECONDS`: minimum complete run duration; default `3`, range `0``3600`.
- `PI_NOTIFY_VISIBILITY`: Kitty policy `unfocused`, `invisible`, or `always`; default `unfocused`.
- `PI_NOTIFY_VISIBILITY`: Kitty policy `unfocused`, `invisible`, or `always`; default `always`.
- `PI_NOTIFY_ACTION`: Kitty click action `focus` or `none`; default `focus`.
- `PI_NOTIFY_SOUND_CMD`: optional shell command run after the notification. This is trusted local configuration and executes through a shell.
+1 -1
View File
@@ -14,7 +14,7 @@ The source is maintained directly in this repository. It is not a submodule and
- Notify on `agent_settled` rather than `agent_end`, preserving elapsed time and the latest messages across retries.
- Restrict terminal output to TUI mode so OSC sequences cannot corrupt RPC, JSON, or print output.
- Give each Pi session a stable unique OSC 99 notification ID.
- Make Kitty click-to-focus explicit and default visibility to the source-window-aware `unfocused` policy.
- Make Kitty click-to-focus explicit and default visibility to `always`, while retaining `unfocused` and `invisible` as opt-in policies.
- Add duration filtering, enable/privacy/action settings, status template fields, and `/notify` controls.
- Keep Base64 Kitty payloads, harden OSC metadata/fallback sanitization, and ignore detached-process spawn errors.
- Add lifecycle, multi-window metadata, fallback, parser, and configuration tests.
+2 -2
View File
@@ -22,8 +22,8 @@ export function parseEnabled(value: string | undefined): boolean {
export function parseKittyVisibility(value: string | undefined): KittyVisibility {
const normalized = value?.trim().toLowerCase();
if (normalized === "always" || normalized === "invisible") return normalized;
return "unfocused";
if (normalized === "always" || normalized === "unfocused" || normalized === "invisible") return normalized;
return "always";
}
export function parseNotificationAction(value: string | undefined): NotificationAction {
+1 -1
View File
@@ -54,7 +54,7 @@ describe("config", () => {
durationMs: 4_200,
minDurationMs: 3_000,
notificationId: "pi-session",
visibility: "unfocused",
visibility: "always",
action: "focus",
soundCommand: undefined,
});
+4 -2
View File
@@ -33,9 +33,11 @@ describe("parsers", () => {
assert.equal(parseEnabled(undefined), true);
assert.equal(parseEnabled("off"), false);
assert.equal(parseEnabled("yes"), true);
assert.equal(parseKittyVisibility(undefined), "unfocused");
assert.equal(parseKittyVisibility(undefined), "always");
assert.equal(parseKittyVisibility("always"), "always");
assert.equal(parseKittyVisibility("invalid"), "unfocused");
assert.equal(parseKittyVisibility("unfocused"), "unfocused");
assert.equal(parseKittyVisibility("invisible"), "invisible");
assert.equal(parseKittyVisibility("invalid"), "always");
assert.equal(parseNotificationAction("none"), "none");
assert.equal(parseNotificationAction("invalid"), "focus");
});