Bladeren bron

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

Hannes Rudolph 5 dagen geleden
bovenliggende
commit
dfa7c357c2
2 gewijzigde bestanden met toevoegingen van 69 en 1 verwijderingen
  1. 31 1
      src/core/task/Task.ts
  2. 38 0
      src/core/task/__tests__/ask-queued-message-drain.spec.ts

+ 31 - 1
src/core/task/Task.ts

@@ -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

+ 38 - 0
src/core/task/__tests__/ask-queued-message-drain.spec.ts

@@ -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")
+	})
+})