mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
only play sounds on errors, task completion, or when user intervention is needed
This commit is contained in:
@@ -64,7 +64,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
const [isAtBottom, setIsAtBottom] = useState(false)
|
const [isAtBottom, setIsAtBottom] = useState(false)
|
||||||
|
|
||||||
const [wasStreaming, setWasStreaming] = useState<boolean>(false)
|
const [wasStreaming, setWasStreaming] = useState<boolean>(false)
|
||||||
const [hasStarted, setHasStarted] = useState(false)
|
|
||||||
|
|
||||||
// UI layout depends on the last 2 messages
|
// UI layout depends on the last 2 messages
|
||||||
// (since it relies on the content of these messages, we are deep comparing. i.e. the button state after hitting button sets enableButtons to false, and this effect otherwise would have to true again even if messages didn't change
|
// (since it relies on the content of these messages, we are deep comparing. i.e. the button state after hitting button sets enableButtons to false, and this effect otherwise would have to true again even if messages didn't change
|
||||||
@@ -75,12 +74,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
vscode.postMessage({ type: "playSound", audioType })
|
vscode.postMessage({ type: "playSound", audioType })
|
||||||
}
|
}
|
||||||
|
|
||||||
function playSoundOnMessage(audioType: AudioType) {
|
|
||||||
if (hasStarted && !isStreaming) {
|
|
||||||
playSound(audioType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useDeepCompareEffect(() => {
|
useDeepCompareEffect(() => {
|
||||||
// if last message is an ask, show user ask UI
|
// if last message is an ask, show user ask UI
|
||||||
// if user finished a task, then start a new task with a new conversation history since in this moment that the extension is waiting for user response, the user could close the extension and the conversation history would be lost.
|
// if user finished a task, then start a new task with a new conversation history since in this moment that the extension is waiting for user response, the user could close the extension and the conversation history would be lost.
|
||||||
@@ -91,7 +84,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
const isPartial = lastMessage.partial === true
|
const isPartial = lastMessage.partial === true
|
||||||
switch (lastMessage.ask) {
|
switch (lastMessage.ask) {
|
||||||
case "api_req_failed":
|
case "api_req_failed":
|
||||||
playSoundOnMessage("progress_loop")
|
playSound("progress_loop")
|
||||||
setTextAreaDisabled(true)
|
setTextAreaDisabled(true)
|
||||||
setClineAsk("api_req_failed")
|
setClineAsk("api_req_failed")
|
||||||
setEnableButtons(true)
|
setEnableButtons(true)
|
||||||
@@ -99,7 +92,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
setSecondaryButtonText("Start New Task")
|
setSecondaryButtonText("Start New Task")
|
||||||
break
|
break
|
||||||
case "mistake_limit_reached":
|
case "mistake_limit_reached":
|
||||||
playSoundOnMessage("progress_loop")
|
playSound("progress_loop")
|
||||||
setTextAreaDisabled(false)
|
setTextAreaDisabled(false)
|
||||||
setClineAsk("mistake_limit_reached")
|
setClineAsk("mistake_limit_reached")
|
||||||
setEnableButtons(true)
|
setEnableButtons(true)
|
||||||
@@ -107,7 +100,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
setSecondaryButtonText("Start New Task")
|
setSecondaryButtonText("Start New Task")
|
||||||
break
|
break
|
||||||
case "followup":
|
case "followup":
|
||||||
playSoundOnMessage("notification")
|
playSound("notification")
|
||||||
setTextAreaDisabled(isPartial)
|
setTextAreaDisabled(isPartial)
|
||||||
setClineAsk("followup")
|
setClineAsk("followup")
|
||||||
setEnableButtons(isPartial)
|
setEnableButtons(isPartial)
|
||||||
@@ -115,7 +108,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
// setSecondaryButtonText(undefined)
|
// setSecondaryButtonText(undefined)
|
||||||
break
|
break
|
||||||
case "tool":
|
case "tool":
|
||||||
playSoundOnMessage("notification")
|
if (!isAutoApproved(lastMessage)) {
|
||||||
|
playSound("notification")
|
||||||
|
}
|
||||||
setTextAreaDisabled(isPartial)
|
setTextAreaDisabled(isPartial)
|
||||||
setClineAsk("tool")
|
setClineAsk("tool")
|
||||||
setEnableButtons(!isPartial)
|
setEnableButtons(!isPartial)
|
||||||
@@ -134,7 +129,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "browser_action_launch":
|
case "browser_action_launch":
|
||||||
playSoundOnMessage("notification")
|
if (!isAutoApproved(lastMessage)) {
|
||||||
|
playSound("notification")
|
||||||
|
}
|
||||||
setTextAreaDisabled(isPartial)
|
setTextAreaDisabled(isPartial)
|
||||||
setClineAsk("browser_action_launch")
|
setClineAsk("browser_action_launch")
|
||||||
setEnableButtons(!isPartial)
|
setEnableButtons(!isPartial)
|
||||||
@@ -142,7 +139,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
setSecondaryButtonText("Reject")
|
setSecondaryButtonText("Reject")
|
||||||
break
|
break
|
||||||
case "command":
|
case "command":
|
||||||
playSoundOnMessage("notification")
|
if (!isAutoApproved(lastMessage)) {
|
||||||
|
playSound("notification")
|
||||||
|
}
|
||||||
setTextAreaDisabled(isPartial)
|
setTextAreaDisabled(isPartial)
|
||||||
setClineAsk("command")
|
setClineAsk("command")
|
||||||
setEnableButtons(!isPartial)
|
setEnableButtons(!isPartial)
|
||||||
@@ -150,7 +149,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
setSecondaryButtonText("Reject")
|
setSecondaryButtonText("Reject")
|
||||||
break
|
break
|
||||||
case "command_output":
|
case "command_output":
|
||||||
playSoundOnMessage("notification")
|
|
||||||
setTextAreaDisabled(false)
|
setTextAreaDisabled(false)
|
||||||
setClineAsk("command_output")
|
setClineAsk("command_output")
|
||||||
setEnableButtons(true)
|
setEnableButtons(true)
|
||||||
@@ -166,7 +164,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
break
|
break
|
||||||
case "completion_result":
|
case "completion_result":
|
||||||
// extension waiting for feedback. but we can just present a new task button
|
// extension waiting for feedback. but we can just present a new task button
|
||||||
playSoundOnMessage("celebration")
|
playSound("celebration")
|
||||||
setTextAreaDisabled(isPartial)
|
setTextAreaDisabled(isPartial)
|
||||||
setClineAsk("completion_result")
|
setClineAsk("completion_result")
|
||||||
setEnableButtons(!isPartial)
|
setEnableButtons(!isPartial)
|
||||||
@@ -174,7 +172,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
setSecondaryButtonText(undefined)
|
setSecondaryButtonText(undefined)
|
||||||
break
|
break
|
||||||
case "resume_task":
|
case "resume_task":
|
||||||
playSoundOnMessage("notification")
|
|
||||||
setTextAreaDisabled(false)
|
setTextAreaDisabled(false)
|
||||||
setClineAsk("resume_task")
|
setClineAsk("resume_task")
|
||||||
setEnableButtons(true)
|
setEnableButtons(true)
|
||||||
@@ -183,7 +180,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
setDidClickCancel(false) // special case where we reset the cancel button state
|
setDidClickCancel(false) // special case where we reset the cancel button state
|
||||||
break
|
break
|
||||||
case "resume_completed_task":
|
case "resume_completed_task":
|
||||||
playSoundOnMessage("celebration")
|
playSound("celebration")
|
||||||
setTextAreaDisabled(false)
|
setTextAreaDisabled(false)
|
||||||
setClineAsk("resume_completed_task")
|
setClineAsk("resume_completed_task")
|
||||||
setEnableButtons(true)
|
setEnableButtons(true)
|
||||||
@@ -482,15 +479,70 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}, [modifiedMessages])
|
}, [modifiedMessages])
|
||||||
useEffect(() => {
|
|
||||||
if (isStreaming) {
|
const isReadOnlyToolAction = (message: ClineMessage | undefined) => {
|
||||||
// Set to true once any request has started
|
if (message?.type === "ask" && message.text) {
|
||||||
setHasStarted(true)
|
const tool = JSON.parse(message.text)
|
||||||
|
return ["readFile", "listFiles", "listFilesTopLevel", "listFilesRecursive", "listCodeDefinitionNames", "searchFiles"].includes(tool.tool)
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const isWriteToolAction = (message: ClineMessage | undefined) => {
|
||||||
|
if (message?.type === "ask" && message.text) {
|
||||||
|
const tool = JSON.parse(message.text)
|
||||||
|
return ["editedExistingFile", "appliedDiff", "newFileCreated"].includes(tool.tool)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMcpToolAlwaysAllowed = (message: ClineMessage | undefined) => {
|
||||||
|
if (message?.type === "ask" && message.ask === "use_mcp_server" && message.text) {
|
||||||
|
const mcpServerUse = JSON.parse(message.text) as { type: string; serverName: string; toolName: string }
|
||||||
|
if (mcpServerUse.type === "use_mcp_tool") {
|
||||||
|
const server = mcpServers?.find((s: McpServer) => s.name === mcpServerUse.serverName)
|
||||||
|
const tool = server?.tools?.find((t: McpTool) => t.name === mcpServerUse.toolName)
|
||||||
|
return tool?.alwaysAllow || false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAllowedCommand = (message: ClineMessage | undefined) => {
|
||||||
|
if (message?.type === "ask" && message.text) {
|
||||||
|
const command = message.text
|
||||||
|
|
||||||
|
// Split command by chaining operators
|
||||||
|
const commands = command.split(/&&|\|\||;|\||\$\(|`/).map(cmd => cmd.trim())
|
||||||
|
|
||||||
|
// Check if all individual commands are allowed
|
||||||
|
return commands.every((cmd) => {
|
||||||
|
const trimmedCommand = cmd.toLowerCase()
|
||||||
|
return allowedCommands?.some((prefix) => trimmedCommand.startsWith(prefix.toLowerCase()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAutoApproved = (message: ClineMessage | undefined) => {
|
||||||
|
if (!message || message.type !== "ask") return false
|
||||||
|
|
||||||
|
return (
|
||||||
|
(alwaysAllowBrowser && message.ask === "browser_action_launch") ||
|
||||||
|
(alwaysAllowReadOnly && message.ask === "tool" && isReadOnlyToolAction(message)) ||
|
||||||
|
(alwaysAllowWrite && message.ask === "tool" && isWriteToolAction(message)) ||
|
||||||
|
(alwaysAllowExecute && message.ask === "command" && isAllowedCommand(message)) ||
|
||||||
|
(alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
// Only execute when isStreaming changes from true to false
|
// Only execute when isStreaming changes from true to false
|
||||||
if (wasStreaming && !isStreaming && lastMessage) {
|
if (wasStreaming && !isStreaming && lastMessage) {
|
||||||
// Play appropriate sound based on lastMessage content
|
// Play appropriate sound based on lastMessage content
|
||||||
if (lastMessage.type === "ask") {
|
if (lastMessage.type === "ask") {
|
||||||
|
// Don't play sounds for auto-approved actions
|
||||||
|
if (!isAutoApproved(lastMessage)) {
|
||||||
switch (lastMessage.ask) {
|
switch (lastMessage.ask) {
|
||||||
case "api_req_failed":
|
case "api_req_failed":
|
||||||
case "mistake_limit_reached":
|
case "mistake_limit_reached":
|
||||||
@@ -509,6 +561,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Update previous value
|
// Update previous value
|
||||||
setWasStreaming(isStreaming)
|
setWasStreaming(isStreaming)
|
||||||
}, [isStreaming, lastMessage, wasStreaming])
|
}, [isStreaming, lastMessage, wasStreaming])
|
||||||
@@ -750,61 +803,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
|||||||
// Only proceed if we have an ask and buttons are enabled
|
// Only proceed if we have an ask and buttons are enabled
|
||||||
if (!clineAsk || !enableButtons) return
|
if (!clineAsk || !enableButtons) return
|
||||||
|
|
||||||
const isReadOnlyToolAction = () => {
|
if (isAutoApproved(lastMessage)) {
|
||||||
const lastMessage = messages.at(-1)
|
|
||||||
if (lastMessage?.type === "ask" && lastMessage.text) {
|
|
||||||
const tool = JSON.parse(lastMessage.text)
|
|
||||||
return ["readFile", "listFiles", "listFilesTopLevel", "listFilesRecursive", "listCodeDefinitionNames", "searchFiles"].includes(tool.tool)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const isWriteToolAction = () => {
|
|
||||||
const lastMessage = messages.at(-1)
|
|
||||||
if (lastMessage?.type === "ask" && lastMessage.text) {
|
|
||||||
const tool = JSON.parse(lastMessage.text)
|
|
||||||
return ["editedExistingFile", "appliedDiff", "newFileCreated"].includes(tool.tool)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMcpToolAlwaysAllowed = () => {
|
|
||||||
const lastMessage = messages.at(-1)
|
|
||||||
if (lastMessage?.type === "ask" && lastMessage.ask === "use_mcp_server" && lastMessage.text) {
|
|
||||||
const mcpServerUse = JSON.parse(lastMessage.text) as { type: string; serverName: string; toolName: string }
|
|
||||||
if (mcpServerUse.type === "use_mcp_tool") {
|
|
||||||
const server = mcpServers?.find((s: McpServer) => s.name === mcpServerUse.serverName)
|
|
||||||
const tool = server?.tools?.find((t: McpTool) => t.name === mcpServerUse.toolName)
|
|
||||||
return tool?.alwaysAllow || false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAllowedCommand = () => {
|
|
||||||
const lastMessage = messages.at(-1)
|
|
||||||
if (lastMessage?.type === "ask" && lastMessage.text) {
|
|
||||||
const command = lastMessage.text
|
|
||||||
|
|
||||||
// Split command by chaining operators
|
|
||||||
const commands = command.split(/&&|\|\||;|\||\$\(|`/).map(cmd => cmd.trim())
|
|
||||||
|
|
||||||
// Check if all individual commands are allowed
|
|
||||||
return commands.every((cmd) => {
|
|
||||||
const trimmedCommand = cmd.toLowerCase()
|
|
||||||
return allowedCommands?.some((prefix) => trimmedCommand.startsWith(prefix.toLowerCase()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(alwaysAllowBrowser && clineAsk === "browser_action_launch") ||
|
|
||||||
(alwaysAllowReadOnly && clineAsk === "tool" && isReadOnlyToolAction()) ||
|
|
||||||
(alwaysAllowWrite && clineAsk === "tool" && isWriteToolAction()) ||
|
|
||||||
(alwaysAllowExecute && clineAsk === "command" && isAllowedCommand()) ||
|
|
||||||
(alwaysAllowMcp && clineAsk === "use_mcp_server" && isMcpToolAlwaysAllowed())
|
|
||||||
) {
|
|
||||||
handlePrimaryButtonClick()
|
handlePrimaryButtonClick()
|
||||||
}
|
}
|
||||||
}, [clineAsk, enableButtons, handlePrimaryButtonClick, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, messages, allowedCommands, mcpServers])
|
}, [clineAsk, enableButtons, handlePrimaryButtonClick, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, messages, allowedCommands, mcpServers])
|
||||||
|
|||||||
Reference in New Issue
Block a user