From 2134c0389352de1dc74c6f948c141a74ab0076b6 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 3 Apr 2026 02:21:28 +0000 Subject: [PATCH] feat: play notification sound immediately on followup ask Add immediate notification sound when the AI asks a follow-up question (followup ask type), so users get audio feedback right when the AI needs their attention rather than waiting for the 2-second interactionRequired timeout. Also adds tests to verify the sound plays for non-partial followup asks and does not play for partial (streaming) followup asks. Closes #12047 --- webview-ui/src/components/chat/ChatView.tsx | 3 + .../ChatView.notification-sound.spec.tsx | 101 ++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index fd0aca66cb7..862646e6b9b 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -296,6 +296,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction { }) }) +describe("ChatView - Followup Notification Sound", () => { + beforeEach(() => vi.clearAllMocks()) + + it("plays notification sound when a non-partial followup ask is received", async () => { + renderChatView() + + // First hydrate state with initial task + mockPostMessage({ + soundEnabled: true, + messageQueue: [], + clineMessages: [ + { + type: "say", + say: "task", + ts: Date.now() - 2000, + text: "Initial task", + }, + ], + }) + + // Clear any initial calls + mockPlayFunction.mockClear() + + // Add a followup ask message + mockPostMessage({ + soundEnabled: true, + messageQueue: [], + clineMessages: [ + { + type: "say", + say: "task", + ts: Date.now() - 2000, + text: "Initial task", + }, + { + type: "ask", + ask: "followup", + ts: Date.now(), + text: "Could you clarify which file you want me to edit?", + partial: false, + }, + ], + }) + + // Wait for sound to be played + await waitFor(() => { + expect(mockPlayFunction).toHaveBeenCalled() + }) + }) + + it("does not play notification sound when followup ask is partial", async () => { + renderChatView() + + // First hydrate state with initial task + mockPostMessage({ + soundEnabled: true, + messageQueue: [], + clineMessages: [ + { + type: "say", + say: "task", + ts: Date.now() - 2000, + text: "Initial task", + }, + ], + }) + + // Clear any initial calls + mockPlayFunction.mockClear() + + // Add a partial followup ask message + mockPostMessage({ + soundEnabled: true, + messageQueue: [], + clineMessages: [ + { + type: "say", + say: "task", + ts: Date.now() - 2000, + text: "Initial task", + }, + { + type: "ask", + ask: "followup", + ts: Date.now(), + text: "Could you clarify...", + partial: true, + }, + ], + }) + + // Wait a bit to ensure the effect would have run + await waitFor( + () => { + expect(mockPlayFunction).not.toHaveBeenCalled() + }, + { timeout: 1000 }, + ) + }) +}) + describe("ChatView - Sound Debounce", () => { beforeEach(() => vi.clearAllMocks())