mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
docs: describe prompt notifications
A prompt that waits on the human is announced after a grace period, and the notification can carry the answer: allow / deny / review buttons for a confirmation, a reply field for an `ask_user_line`. `doc/notifications.md` lays out the two channels (the optional `desktop-notifier` extra, and OSC 777 / OSC 9 written to `/dev/tty` with no dependency), what each prompt kind offers, why the reason line stays out by default, and the platform limits — no buttons where a server does not render them, a reply field only under KDE, kitty left with the bell. The example config and the README config block gain the `[notify]` section, and AGENTS.md documents the three prompt hooks and the rule that only the outermost prompt of a nested read is announced.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# Prompt notifications
|
||||
|
||||
A turn stops while a prompt waits on the human. If you switched to another
|
||||
window, the wait is invisible until you come back — so the CLI announces it.
|
||||
|
||||
A prompt is announced only after `grace_seconds` of waiting (5s by default), so
|
||||
answering at the terminal stays silent. Bursts are throttled by
|
||||
`min_interval_seconds`, and the announcement says what is being asked, not what
|
||||
it would run: the one-line reason can name a command, argv or URL, and a
|
||||
notification outlives the prompt (and may be mirrored to a lock screen), so it
|
||||
travels only with `include_reason = true`.
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
[notify]
|
||||
enabled = true
|
||||
grace_seconds = 5.0
|
||||
min_interval_seconds = 10.0
|
||||
channels = ["auto"] # auto | desktop | terminal
|
||||
actions = true # offer the answer, not just the news
|
||||
include_reason = false
|
||||
protocol = "" # terminal protocol override: osc777 / osc9 / bell
|
||||
```
|
||||
|
||||
`channels = ["auto"]` tries the desktop first and falls back to the terminal;
|
||||
`["desktop"]` and `["terminal"]` pin one of them. Notifications are only sent
|
||||
from an interactive run — a one-shot `plyngent "…"` never waits on a human, and
|
||||
a prompt answered inside the grace period is never announced at all.
|
||||
|
||||
## What is answered where
|
||||
|
||||
| Prompt | Notification offers |
|
||||
|---|---|
|
||||
| tool confirm (single / a round's batch) | Allow once / Deny / Go to terminal |
|
||||
| command denylist, `fetch_local` private host | same three |
|
||||
| directory-access request, skill write | same three |
|
||||
| `ask_user_line` | a reply field — the answer *is* the text you type |
|
||||
| `ask_user_choice`, `ask_user_form`, PTY secrets | nothing (terminal only) |
|
||||
| limit-continue | nothing (terminal only) |
|
||||
|
||||
An answer read from the notification is treated exactly like a typed one —
|
||||
`allow` is the `y` the prompt accepts, `deny` is `n` — and it never records a
|
||||
session grant: the button means "this once". "Go to terminal" withdraws the
|
||||
notification and leaves the prompt waiting. A secret is never asked for in a
|
||||
notification, and never echoed into one.
|
||||
|
||||
## Desktop channel
|
||||
|
||||
The desktop channel needs the optional extra:
|
||||
|
||||
```console
|
||||
pip install 'plyngent[notify]'
|
||||
```
|
||||
|
||||
It wraps `desktop-notifier`, which talks to `org.freedesktop.Notifications` on
|
||||
Linux, Notification Center on macOS and WinRT on Windows. Buttons need a
|
||||
notification server that renders them (KDE Plasma and GNOME Shell do; a bare
|
||||
`dunst` may not, and then the notification is text-only without any error). The
|
||||
reply field is KDE-only in practice: it is the `inline-reply` capability plus the
|
||||
`x-kde-reply-submit-button-text` hint, which only KNotifications implements.
|
||||
|
||||
Notifications are shown from the library's async API on a loop that runs in a
|
||||
daemon thread, because callbacks only fire while the loop that owns the
|
||||
notification is running.
|
||||
|
||||
## Terminal channel
|
||||
|
||||
No dependency and no extra: the CLI writes an escape sequence to `/dev/tty`
|
||||
(never stdout, so a redirect cannot swallow it) and the terminal turns it into a
|
||||
native notification.
|
||||
|
||||
| Protocol | Sequence | Terminals |
|
||||
|---|---|---|
|
||||
| OSC 777 | `ESC ] 777 ; notify ; title ; body` | Ghostty, WezTerm, Warp, Contour, VTE (GNOME Terminal, Tilix, Terminator), rxvt, Windows Terminal |
|
||||
| OSC 9 | `ESC ] 9 ; title: body` | iTerm2, Apple Terminal, ConEmu |
|
||||
| bell | `BEL` | everything else |
|
||||
|
||||
Detection reads the environment only (`TERM_PROGRAM`, `TERM`, `WT_SESSION`,
|
||||
`VTE_VERSION`), so it never blocks and never touches the input stream;
|
||||
`protocol` pins it. Inside tmux the sequence is wrapped in DCS passthrough —
|
||||
tmux needs `set -g allow-passthrough on` — and inside GNU Screen in a Screen
|
||||
DCS. kitty speaks OSC 99 only, which is not implemented, so it keeps the bell;
|
||||
install the extra to reach it through the desktop instead.
|
||||
|
||||
## Failure is always silent
|
||||
|
||||
A missing extra, a desktop without a notification server, a refused write: each
|
||||
falls through to the next channel and never raises into the prompt. There is no
|
||||
sound, no remote push and no notification when a turn merely ends — only when a
|
||||
prompt is waiting for you.
|
||||
Reference in New Issue
Block a user