mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-09 22:31:08 +00:00
fix: prevent queued messages from auto-approving unapproved commands
When a queued message was drained during a tool/command/mcp ask, the code unconditionally sent "yesButtonClicked", which approved the pending command regardless of auto-approval settings. This changes the response to "messageResponse" instead, matching the behavior when a user types a reply without clicking Approve (the command gets rejected). Fixes #12302
This commit is contained in:
parent
ad25634905
commit
839b63c1e7
2 changed files with 93 additions and 6 deletions
|
|
@ -1432,9 +1432,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
if (message) {
|
||||
// Check if this is a tool approval ask that needs to be handled.
|
||||
if (type === "tool" || type === "command" || type === "use_mcp_server") {
|
||||
// For tool approvals, we need to approve first, then send
|
||||
// the message if there's text/images.
|
||||
this.handleWebviewAskResponse("yesButtonClicked", message.text, message.images)
|
||||
// For tool/command approvals, reject the pending tool and
|
||||
// send the queued text as a plain message -- matching the
|
||||
// behaviour when a user types a reply without clicking
|
||||
// Approve. This prevents queued messages from
|
||||
// unconditionally auto-approving unapproved commands.
|
||||
this.handleWebviewAskResponse("messageResponse", message.text, message.images)
|
||||
} else {
|
||||
// For other ask types (like followup or command_output), fulfill the ask
|
||||
// directly.
|
||||
|
|
@ -1456,10 +1459,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
if (shouldDrainQueuedMessageForAsk && !this.messageQueueService.isEmpty()) {
|
||||
const message = this.messageQueueService.dequeueMessage()
|
||||
if (message) {
|
||||
// If this is a tool approval ask, we need to approve first (yesButtonClicked)
|
||||
// and include any queued text/images.
|
||||
// If this is a tool approval ask, reject the pending tool and
|
||||
// forward the queued text as a plain message so that
|
||||
// unapproved commands are never auto-executed.
|
||||
if (type === "tool" || type === "command" || type === "use_mcp_server") {
|
||||
this.handleWebviewAskResponse("yesButtonClicked", message.text, message.images)
|
||||
this.handleWebviewAskResponse("messageResponse", message.text, message.images)
|
||||
} else {
|
||||
this.handleWebviewAskResponse("messageResponse", message.text, message.images)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,89 @@ describe("Task.ask queued message drain", () => {
|
|||
expect(result.text).toBe("picked answer")
|
||||
})
|
||||
|
||||
it("rejects unapproved command asks with messageResponse when queued message is drained", async () => {
|
||||
const task = Object.create(Task.prototype) as Task
|
||||
;(task as any).abort = false
|
||||
;(task as any).clineMessages = []
|
||||
;(task as any).askResponse = undefined
|
||||
;(task as any).askResponseText = undefined
|
||||
;(task as any).askResponseImages = undefined
|
||||
;(task as any).lastMessageTs = undefined
|
||||
|
||||
const { MessageQueueService } = await import("../../message-queue/MessageQueueService")
|
||||
;(task as any).messageQueueService = new MessageQueueService()
|
||||
;(task as any).addToClineMessages = vi.fn(async () => {})
|
||||
;(task as any).saveClineMessages = vi.fn(async () => {})
|
||||
;(task as any).updateClineMessage = vi.fn(async () => {})
|
||||
;(task as any).cancelAutoApprovalTimeout = vi.fn(() => {})
|
||||
;(task as any).checkpointSave = vi.fn(async () => {})
|
||||
;(task as any).emit = vi.fn()
|
||||
;(task as any).providerRef = { deref: () => undefined }
|
||||
|
||||
// Queue a message before asking for command approval.
|
||||
;(task as any).messageQueueService.addMessage("continue please")
|
||||
|
||||
const result = await task.ask("command", "rm -rf /tmp/test", false)
|
||||
|
||||
// The queued message must NOT approve the command -- it should reject
|
||||
// with messageResponse, matching the behaviour of typing a reply
|
||||
// without clicking Approve.
|
||||
expect(result.response).toBe("messageResponse")
|
||||
expect(result.text).toBe("continue please")
|
||||
})
|
||||
|
||||
it("rejects unapproved tool asks with messageResponse when queued message is drained", async () => {
|
||||
const task = Object.create(Task.prototype) as Task
|
||||
;(task as any).abort = false
|
||||
;(task as any).clineMessages = []
|
||||
;(task as any).askResponse = undefined
|
||||
;(task as any).askResponseText = undefined
|
||||
;(task as any).askResponseImages = undefined
|
||||
;(task as any).lastMessageTs = undefined
|
||||
|
||||
const { MessageQueueService } = await import("../../message-queue/MessageQueueService")
|
||||
;(task as any).messageQueueService = new MessageQueueService()
|
||||
;(task as any).addToClineMessages = vi.fn(async () => {})
|
||||
;(task as any).saveClineMessages = vi.fn(async () => {})
|
||||
;(task as any).updateClineMessage = vi.fn(async () => {})
|
||||
;(task as any).cancelAutoApprovalTimeout = vi.fn(() => {})
|
||||
;(task as any).checkpointSave = vi.fn(async () => {})
|
||||
;(task as any).emit = vi.fn()
|
||||
;(task as any).providerRef = { deref: () => undefined }
|
||||
;(task as any).messageQueueService.addMessage("go ahead")
|
||||
|
||||
const result = await task.ask("tool", "write_to_file /etc/hosts", false)
|
||||
|
||||
expect(result.response).toBe("messageResponse")
|
||||
expect(result.text).toBe("go ahead")
|
||||
})
|
||||
|
||||
it("rejects unapproved use_mcp_server asks with messageResponse when queued message is drained", async () => {
|
||||
const task = Object.create(Task.prototype) as Task
|
||||
;(task as any).abort = false
|
||||
;(task as any).clineMessages = []
|
||||
;(task as any).askResponse = undefined
|
||||
;(task as any).askResponseText = undefined
|
||||
;(task as any).askResponseImages = undefined
|
||||
;(task as any).lastMessageTs = undefined
|
||||
|
||||
const { MessageQueueService } = await import("../../message-queue/MessageQueueService")
|
||||
;(task as any).messageQueueService = new MessageQueueService()
|
||||
;(task as any).addToClineMessages = vi.fn(async () => {})
|
||||
;(task as any).saveClineMessages = vi.fn(async () => {})
|
||||
;(task as any).updateClineMessage = vi.fn(async () => {})
|
||||
;(task as any).cancelAutoApprovalTimeout = vi.fn(() => {})
|
||||
;(task as any).checkpointSave = vi.fn(async () => {})
|
||||
;(task as any).emit = vi.fn()
|
||||
;(task as any).providerRef = { deref: () => undefined }
|
||||
;(task as any).messageQueueService.addMessage("next step")
|
||||
|
||||
const result = await task.ask("use_mcp_server", "mcp server call", false)
|
||||
|
||||
expect(result.response).toBe("messageResponse")
|
||||
expect(result.text).toBe("next step")
|
||||
})
|
||||
|
||||
it("does not consume queued messages for command_output asks", async () => {
|
||||
const task = Object.create(Task.prototype) as Task
|
||||
;(task as any).abort = false
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue