Add opt-in terminal notifications for Studio Code (Proof of Concept) - #4179
Add opt-in terminal notifications for Studio Code (Proof of Concept)#4179SumoTTo wants to merge 5 commits into
Conversation
Adds notifyTerminal(), a small best-effort helper that writes BEL + OSC 9 to stderr so terminals that support it (Warp, iTerm2, etc.) can flash a tab or show a system notification. Off by default via a new notificationsEnabled cli.json flag so existing users see no behavior change. Suppressed when forked by the Studio desktop app over IPC (it has its own native OS notification path) or when stderr isn't a TTY (headless spawns, e.g. the remote-session daemon). Uses stderr rather than stdout because stdout is the documented NDJSON event stream for `studio code --json`, matching the existing split in tos-notice.ts. Not yet wired into any call sites.
Calls notifyTerminal() from both AiOutputAdapter implementations when a turn finishes or pauses to ask the user a question: - JsonAdapter (studio code --json, headless NDJSON mode): on emitTurnCompleted's 'success' and 'paused' statuses. - AiChatUI (interactive TUI): on askUser() and the agent_end success branch. JsonAdapter is also used by non-interactive callers (the desktop app via fork+IPC, the remote-session/Telegram daemon via spawn+pipes), but notifyTerminal's own guards (process.send, stderr.isTTY) make sure those never see raw escape codes — only a human running `studio code --json` directly in their own terminal does.
Lets users enable or disable terminal notifications from inside studio code, alongside /model and /provider. Off by default, so this is the only way to opt in.
|
Thanks a lot for this PR — and for the thorough description and cross-terminal testing notes. The implementation itself is in good shape: the stderr/TTY/IPC guards are right, the config write goes through the proper locking helpers, and it's nicely scoped as an opt-in PoC. One ask before this moves forward: could you trim the lower-value tests? The coverage is currently optimized for volume rather than for what can actually break. Specifically:
Definitely keep the double-notification regression test in Thanks again for dogfooding Studio Code on its own source! |
|
One more thought on the opt-in/opt-out question — after looking at how Claude Code handles the same problem, I'd suggest inverting the default, with a guard: Proposal: on by default, but only in terminals known to support OSC 9 well — and drop the bell entirely. Claude Code's default ( Applied here, that would mean:
The upside is that the feature actually gets used — most users will never discover an off-by-default flag buried behind a slash command, and on iTerm2/Warp/Ghostty the notification is unobtrusive enough that on-by-default is the right call. The detection guard is what earns that default. Curious what you think — happy to keep it opt-in for the PoC and treat this as the graduation criteria instead. |
sejas
left a comment
There was a problem hiding this comment.
@SumoTTo , thanks for adding this feature to Studio. I like the idea of receiving notifications and that it's an opt-in feature.
I tested the PR on Mac by running /notifications in Studio Code CLI and telling the agent to ask a question. It worked well. The PR is in good shape. I triggered the CI tests to confirm nothing is broken.
I see in the future we could consider playing a different sound to differentiate from other alerts, but that's just my personal preference and something we can discuss after merging the PR as a follow-up.
@SumoTTo, would you like to keep pushing forward the PR and address Riad's feedback #4179 (comment)?
Let me know if you run into any issues, Thanks!
Per @youknowriad's review on PR Automattic#4179: remove tests that only assert an object literal exists or that a one-line wrapper calls the function it wraps, and drop the redundant half of duplicate true/false and enable/disable pairs. Keep the guard tests (silent under desktop IPC, silent on non-TTY stderr, never throws on config read failure) and the double-notification regression test, which encode the feature's real contract.
Per @youknowriad's suggestion on PR Automattic#4179: default to on for terminals known to render OSC 9 well (Warp, iTerm2, WezTerm, Ghostty, Kitty), rather than requiring users to discover the /notifications command. The command cycles a three-way override on top of detection: unset (auto-detect) -> true (always on) -> false (always off) -> back to unset, so users can always return to auto-detection without editing the config file by hand. Also drops the bare BEL write. It was the riskier half of the escape sequence for an on-by-default feature, since unsupported terminals may render it as an audible beep rather than ignoring it silently like OSC 9. The BEL terminator inside the OSC 9 sequence itself is unchanged -- it's part of the protocol, not a standalone beep.
@youknowriad Thank you for the review and for such a detailed comment. I have removed/kept the tests as you requested.
Took this feedback and reworked the detection model:
|
|
@sejas Thank you for the review, and it's great that this works on Mac! I've made the changes requested by @youknowriad, and I will continue working on this PR. Regarding the audio signal setting, as far as I understand, unfortunately we cannot do that in this implementation. |
Related issues
How AI was used in this PR
This entire PR — code, tests, and this description — was written in an AI pair-programming session (Studio Code itself, dogfooding the agent on its own CLI source). I reviewed
every file change myself: read each diff, questioned the design decisions (why two output adapters need separate hooks, why stderr over stdout, why the
--jsonguard checks bothprocess.sendandisTTY), and manually verified the terminal behavior across Warp/Git Bash/PowerShell/CMD myself rather than trusting the agent's claims. I did not write theimplementation by hand.
This is a Proof of Concept (see
AGENTS.md's "Large & Exploratory Contributions" section) — opened as a draft since I couldn't apply theProof of Conceptlabel myself asan external contributor. Please add it if a maintainer agrees this is worth exploring further.
Proposed Changes
Adds a terminal notification (OSC 9 written to stderr) when a
studio codeagent turn finishes or pauses on anAskUserQuestion, so users don't have to keep watching theterminal during long-running turns.
Auto-detected: on by default for terminals confirmed to render OSC 9 as a system notification — Warp, iTerm2, WezTerm, Ghostty, Kitty — and silent everywhere else. The
/notificationsslash command cycles a manual override on top of detection: auto-detect → always on → always off → back to auto-detect.No bare BEL: earlier versions of this PR also wrote a bare BEL as a fallback. Dropped per review feedback.
Testing Instructions
npm run cli:build && node apps/cli/dist/cli/main.mjs code/notificationsa few times — it should cycle through "auto-detect", "always on", and "always off" messages.AskUserQuestion— you shouldsee a system notification.
/notificationsto "always off" and confirm no signal on the next turn, even on a capable terminal.npm test -- apps/cli/lib/tests/notify.test.ts apps/cli/ai/tests/output-adapter.test.ts apps/cli/ai/tests/ui.test.ts apps/cli/ai/tests/slash-commands.test.tsPre-merge Checklist