5 Commits

Author SHA1 Message Date
NCBM b7044bbba7 core/cli: keep a prompt notification on screen
A prompt notification went out with the library defaults: the notification
server's own expiry. A desktop server drops such a notification after a few
seconds, so the announcement of a blocked turn had usually slid into the
notification tray by the time the human looked back.

It now goes out without expiry (`timeout=0`, the freedesktop value for "until
dismissed"). Urgency is left alone: measured on KDE Plasma, `critical` keeps the
notification up just the same, but paints it as urgent and asks the platform's
Focus mode to let it through — decoration a prompt does not need. Dismissing is
already the prompt's own job once it is answered, so nothing lingers.
2026-10-24 01:48:04 +08:00
NCBM 7b7c90b8ee core/tools: send a deleted path to the system trash
`delete_path` is the only destructive file entry point the agent has, and it
carries `ToolTag.YOLO` — under `--yes` / `/yolo` it runs with no confirmation at
all. A delete is now a move into the desktop's own trash (freedesktop trash on
Linux, Finder trash on macOS, Recycle Bin on Windows) through `send2trash`, so
the human can put the file back with the file manager they already have. The new
`permanent=true` argument is the old behaviour, chosen on purpose.

A trashing that fails is reported, never downgraded to a deletion: the human was
told the path is recoverable, so a volume with no writable trash directory
leaves the file where it is and the error names `permanent=true` as the way to
delete it anyway. A broken install (the library is a required dependency) is
reported the same way.

Scratch paths under the OS temporary directory are still deleted for good —
nobody restores them, and a trash on the `/tmp` volume would only collect them.
The rule now lives in `tools/trash.py` (`under_system_temp`) and the temporary
workspace cleanup uses it too, instead of keeping a second copy of the check.
2026-10-21 20:18:35 +08:00
NCBM 036e0b40ad cli/notify: notify a waiting prompt through the desktop or the terminal
A prompt that has waited `grace_seconds` is now announced, and where the
platform allows it the notification carries the answer: allow / deny / review
for a confirmation, a reply field for an `ask_user_*` question. Picking
"review" withdraws the notification and keeps waiting at the terminal, and a
notification is dismissed as soon as the prompt returns — answered or not.

Two channels. The desktop one is the optional `notify` extra, run on its own
event loop in a daemon thread because a notification's callbacks only fire
while the loop that owns it is running (the library's synchronous wrapper does
not provide that). The terminal one needs nothing: it writes OSC 777 / OSC 9
to `/dev/tty` — so a redirect cannot swallow it — picking the protocol from the
environment and wrapping it for tmux / screen, falling back to a plain bell.
kitty speaks OSC 99 only, which is not implemented, so it keeps the bell.

Nothing here can break a prompt: a missing extra, a desktop that refuses, a
failed write all fall through to the next channel and never raise. Titles are
flattened and stripped of control characters, and the reason line (which can
name a command or a URL) only travels with `include_reason`.
2026-10-21 19:57:17 +08:00
NCBM fbba2c2a14 core/tools+test: make process and path checks Windows-portable
Use sys.executable for process tests; normalize denylist/glob paths; skip readline when missing.
2026-07-15 20:20:25 +08:00
NCBM c466d09173 core/process: cross-platform PTY via backend and pywinpty
POSIX openpty/fork and Windows ConPTY; win32 marker dep plus typings stub.
2026-07-15 20:01:43 +08:00