fix(task): drain queued messages while waiting for ask (#10315)

This commit is contained in:
Hannes Rudolph 2025-12-23 15:40:41 -07:00 committed by GitHub
parent 53e1ff0058
commit dfa7c357c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 69 additions and 1 deletions

View file

@ -1241,7 +1241,37 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
// Wait for askResponse to be set
await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 })
await pWaitFor(
() => {
if (this.askResponse !== undefined || this.lastMessageTs !== askTs) {
return true
}
// If a queued message arrives while we're blocked on an ask (e.g. a follow-up
// suggestion click that was incorrectly queued due to UI state), consume it
// immediately so the task doesn't hang.
if (!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 (
type === "tool" ||
type === "command" ||
type === "browser_action_launch" ||
type === "use_mcp_server"
) {
this.handleWebviewAskResponse("yesButtonClicked", message.text, message.images)
} else {
this.handleWebviewAskResponse("messageResponse", message.text, message.images)
}
}
}
return false
},
{ interval: 100 },
)
if (this.lastMessageTs !== askTs) {
// Could happen if we send multiple asks in a row i.e. with

View file

@ -0,0 +1,38 @@
import { Task } from "../Task"
// Keep this test focused: if a queued message arrives while Task.ask() is blocked,
// it should be consumed and used to fulfill the ask.
describe("Task.ask queued message drain", () => {
it("consumes queued message while blocked on followup ask", 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
// Message queue service exists in constructor; for unit test we can attach a real one.
const { MessageQueueService } = await import("../../message-queue/MessageQueueService")
;(task as any).messageQueueService = new MessageQueueService()
// Minimal stubs used by ask()
;(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 }
const askPromise = task.ask("followup", "Q?", false)
// Simulate webview queuing the user's selection text while the ask is pending.
;(task as any).messageQueueService.addMessage("picked answer")
const result = await askPromise
expect(result.response).toBe("messageResponse")
expect(result.text).toBe("picked answer")
})
})