Skip to content

Commit 3b83ea9

Browse files
committed
fix: prevent completion sound from replaying when reopening completed tasks (#4885)
- Add completionSoundPlayed flag to HistoryItem type - Modify ChatView to prevent sound on task resumption - Update Task.ts to persist the completion sound flag - Update tests to match new behavior Fixes #4885
1 parent b45252d commit 3b83ea9

File tree

4 files changed

+74
-36
lines changed

4 files changed

+74
-36
lines changed

packages/types/src/history.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const historyItemSchema = z.object({
1616
totalCost: z.number(),
1717
size: z.number().optional(),
1818
workspace: z.string().optional(),
19+
completionSoundPlayed: z.boolean().optional(),
1920
})
2021

2122
export type HistoryItem = z.infer<typeof historyItemSchema>

src/core/task/Task.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,16 @@ export class Task extends EventEmitter<ClineEvents> {
403403

404404
this.emit("taskTokenUsageUpdated", this.taskId, tokenUsage)
405405

406+
// Check if this is a completion save by looking for completion-related messages
407+
const hasCompletionMessage = this.clineMessages.some(
408+
(msg) => msg.ask === "completion_result" || msg.ask === "resume_completed_task",
409+
)
410+
411+
// If we have a completion message, set the completionSoundPlayed flag
412+
if (hasCompletionMessage) {
413+
historyItem.completionSoundPlayed = true
414+
}
415+
406416
await this.providerRef.deref()?.updateTaskHistory(historyItem)
407417
} catch (error) {
408418
console.error("Failed to save Roo messages:", error)

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
333333
break
334334
case "completion_result":
335335
// extension waiting for feedback. but we can just present a new task button
336-
if (!isPartial) {
336+
if (!isPartial && soundEnabled && !currentTaskItem?.completionSoundPlayed) {
337337
playSound("celebration")
338338
}
339339
setSendingDisabled(isPartial)
@@ -354,9 +354,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
354354
setDidClickCancel(false) // special case where we reset the cancel button state
355355
break
356356
case "resume_completed_task":
357-
if (!isPartial) {
358-
playSound("celebration")
359-
}
360357
setSendingDisabled(false)
361358
setClineAsk("resume_completed_task")
362359
setEnableButtons(true)

webview-ui/src/components/chat/__tests__/ChatView.spec.tsx

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -884,51 +884,81 @@ describe("ChatView - Sound Playing Tests", () => {
884884
})
885885
})
886886

887-
it("plays celebration sound for completion results", async () => {
887+
it("plays celebration sound for completion results when completionSoundPlayed is not set", async () => {
888888
renderChatView()
889889

890-
// First hydrate state with initial task and streaming
890+
const taskItem = {
891+
id: "task-1",
892+
number: 1,
893+
ts: Date.now() - 2000,
894+
task: "Initial task",
895+
tokensIn: 100,
896+
tokensOut: 50,
897+
totalCost: 0.01,
898+
}
899+
900+
// Send completion result without completionSoundPlayed flag
891901
mockPostMessage({
892902
clineMessages: [
893-
{
894-
type: "say",
895-
say: "task",
896-
ts: Date.now() - 2000,
897-
text: "Initial task",
898-
},
899-
{
900-
type: "say",
901-
say: "api_req_started",
902-
ts: Date.now() - 1000,
903-
text: JSON.stringify({}),
904-
partial: true,
905-
},
903+
{ type: "say", say: "task", ts: taskItem.ts, text: taskItem.task },
904+
{ type: "ask", ask: "completion_result", ts: Date.now(), text: "Task completed", partial: false },
906905
],
906+
currentTaskItem: taskItem,
907+
soundEnabled: true,
907908
})
908909

909-
// Then send the completion result message (streaming finished)
910+
await waitFor(() => expect(mockPlayFunction).toHaveBeenCalled())
911+
})
912+
913+
it("does not play celebration sound when completionSoundPlayed is true", async () => {
914+
renderChatView()
915+
916+
const taskItem = {
917+
id: "task-1",
918+
number: 1,
919+
ts: Date.now() - 2000,
920+
task: "Initial task",
921+
tokensIn: 100,
922+
tokensOut: 50,
923+
totalCost: 0.01,
924+
completionSoundPlayed: true,
925+
}
926+
927+
// Send completion result with completionSoundPlayed flag
910928
mockPostMessage({
911929
clineMessages: [
912-
{
913-
type: "say",
914-
say: "task",
915-
ts: Date.now() - 2000,
916-
text: "Initial task",
917-
},
918-
{
919-
type: "ask",
920-
ask: "completion_result",
921-
ts: Date.now(),
922-
text: "Task completed successfully",
923-
partial: false,
924-
},
930+
{ type: "say", say: "task", ts: taskItem.ts, text: taskItem.task },
931+
{ type: "ask", ask: "completion_result", ts: Date.now(), text: "Task completed", partial: false },
925932
],
933+
currentTaskItem: taskItem,
934+
soundEnabled: true,
926935
})
927936

928-
// Verify celebration sound was played
929-
await waitFor(() => {
930-
expect(mockPlayFunction).toHaveBeenCalled()
937+
expect(mockPlayFunction).not.toHaveBeenCalled()
938+
})
939+
940+
it("does not play sound when resuming a completed task", async () => {
941+
renderChatView()
942+
943+
mockPostMessage({
944+
clineMessages: [
945+
{ type: "say", say: "task", ts: Date.now() - 2000, text: "Initial task" },
946+
{ type: "ask", ask: "resume_completed_task", ts: Date.now(), text: "Resume", partial: false },
947+
],
948+
currentTaskItem: {
949+
id: "task-1",
950+
number: 1,
951+
ts: Date.now() - 2000,
952+
task: "Initial task",
953+
tokensIn: 100,
954+
tokensOut: 50,
955+
totalCost: 0.01,
956+
completionSoundPlayed: true,
957+
},
958+
soundEnabled: true,
931959
})
960+
961+
expect(mockPlayFunction).not.toHaveBeenCalled()
932962
})
933963

934964
it("plays progress_loop sound for api failures", async () => {

0 commit comments

Comments
 (0)