Add notification ding when agent needs attention#2373
Add notification ding when agent needs attention#2373D3OXY wants to merge 3 commits intopingdotgg:mainfrom
Conversation
Opt-in audible ding (not a desktop/OS notification or popup) that plays when an agent finishes a turn, requests approval, or asks a question. Per-device settings with focus-aware playback and a 5s throttle. New "Notifications" section in Settings with master toggle, three event sub-toggles, focus rule, and a Play Sound preview button.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR introduces a complete new notification sound feature with settings UI, audio playback logic, and event-processing integration. While well-tested and disabled by default, the scope of new user-facing capability and the author being new to this codebase warrants human review. You can customize Macroscope's approvability policy. Learn more. |
…ture ClientSettings shape now requires the 5 notification fields; the desktop test fixture was missed in the initial change.
`latestTurn.state` is not a reliable end-of-turn signal: the projector flips it to "completed" on every `thread.turn-diff-completed` event, which fires mid-turn whenever a checkpoint is captured for a git repo (see ProviderRuntimeIngestion turn.diff.updated handling). The next session-set running event then flips it back, producing running -> completed -> running oscillations through a single turn and spurious notification dings on the first mid-turn diff capture. Switch turn-end detection to `session.orchestrationStatus`, which the provider keeps as "running" continuously through tool calls and only transitions out at actual turn end. "starting" is treated as still active so session restarts/resumes mid-turn don't trigger.
What Changed
Adds an opt-in audible ding that plays when an agent needs the user's attention.
Important
This is strictly an in-app sound effect — not a desktop notification, not a native OS notification, not a popup or toast. There's no permission flow, no system tray icon, no notification center entry. It just plays a short bell through the page's audio output.
New per-device (
ClientSettings) controls in a new Notifications section in Settings → General:Play soundpreview buttoncompleted/error/interruptedAlways/Window not focused/Window not focused or viewing a different threadBehaviour:
console.warnif browser autoplay is blocked or the audio asset fails to load. The Play sound preview button surfaces errors via toast since it's an explicit user action.Why
When an agent runs a long task, it's common to switch to another window or another thread while waiting. Without an audible cue, you keep glancing back to check if it's finished or needs an approval. A short ding closes that loop without hijacking attention the way a popup or system notification would.
Keeping it as an in-app sound (rather than the Notifications API) avoids the permission prompt, works identically across browser and Electron, and stays scoped to the app — the user is always in T3 Code when they hear it.
UI Changes
New Notifications section in Settings → General, between General and Providers:
The bell sound that plays (a short servant-bell ring):
notification.mp3
Implementation notes
apps/web/src/notificationSound.ts:deriveNotificationTriggers(prevShells, nextShells, events)— rising-edge detection onlatestTurn.state,hasPendingApprovals || hasActionableProposedPlan,hasPendingUserInput. Skips archived threads. Requiresprevto exist (no spurious dings on initial bootstrap).shouldPlay(triggers, settings, focusContext, nowMs, lastPlayAtMs)— applies sub-toggle filters, focus rule, and 5s throttle.notificationSound.test.ts).notificationSoundManagersingleton owns a singleHTMLAudioElementfor/sounds/notification.mp3(~54 KB MP3 bundled inapps/web/public/sounds/). Lazy-initialized; SSR-safe guards ondocument/windowaccess.apps/web/src/environments/runtime/service.tsat bothapplyRecoveredEventBatchandapplyShellEventpaths so server-pushed shell upserts (the common case for fresh approvals / turn-end) reliably trigger.useLocation().pathnamein__root.tsx; the manager itself is non-React and exposes asetCurrentThreadAccessorinjection point.packages/contracts/src/settings.tsuse the existingSchema.withDecodingDefaultpattern;ClientSettingsPatchupdated in lockstep. Backward-compatible — missing fields decode to defaults.useSettingsRestoreso "Restore defaults" wipes them.Verification
bun fmt— cleanbun lint— 0 errors (pre-existing warnings unrelated)bun typecheck— clean for@t3tools/contractsand@t3tools/webbun run test— 998/998 pass (28 new innotificationSound.test.ts)Checklist
Note
Add notification sound when agent needs attention
NotificationSoundManagerinnotificationSound.ts.thread-upsertedevents and after recovered event batches inruntime/service.ts.ClientSettingsSchemainsettings.ts: a master toggle, per-event toggles (turn end, approval, question), and a focus rule (always,unfocused-only,unfocused-or-different-thread).NotificationSoundBootstrapcomponent in__root.tsx.notificationSoundEnabled: false); the focus rule defaults tounfocused-or-different-thread.Macroscope summarized a274cfc.