Skip to content

Commit 9d9bfa3

Browse files
fix(webview): flush unanswered asks before message events
Ensure unanswered ask state reaches the webview before Message listeners can respond. Keep already answered asks on the throttled path and cover both ordering cases with regression tests. Signed-off-by: JunyongParkDev <jun94.park@samsung.com>
1 parent fb369bb commit 9d9bfa3

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

src/core/task/Task.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1044,8 +1044,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
10441044
private async addToClineMessages(message: ClineMessage) {
10451045
this.clineMessages.push(message)
10461046
const provider = this.providerRef.deref()
1047+
// Unanswered asks must reach the webview before Message listeners can respond against its state.
1048+
const requiresImmediateState =
1049+
message.partial === true || (message.type === "ask" && message.isAnswered !== true)
10471050
await provider?.postStateToWebviewThrottled()
1048-
if (message.partial === true) {
1051+
if (requiresImmediateState) {
10491052
await provider?.flushPostStateToWebviewThrottled()
10501053
}
10511054
this.emit(RooCodeEventName.Message, { action: "created", message })

src/core/task/__tests__/Task.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
88

99
import {
1010
providerIdentifiers,
11+
RooCodeEventName,
1112
type GlobalState,
1213
type ProviderSettings,
1314
type ModelInfo,
@@ -1694,6 +1695,65 @@ describe("Cline", () => {
16941695
expect(mockProvider.postStateToWebviewWithoutTaskHistory).not.toHaveBeenCalled()
16951696
})
16961697

1698+
it("waits for an unanswered ask flush before emitting the message", async () => {
1699+
const task = new Task({
1700+
provider: mockProvider,
1701+
apiConfiguration: mockApiConfig,
1702+
task: "test task",
1703+
startTask: false,
1704+
})
1705+
const taskAccess = getTaskTestAccess(task)
1706+
vi.spyOn(taskAccess, "saveClineMessages").mockResolvedValue(true)
1707+
let releaseFlush!: () => void
1708+
const pendingFlush = new Promise<void>((resolve) => {
1709+
releaseFlush = resolve
1710+
})
1711+
const flushSpy = vi.mocked(mockProvider.flushPostStateToWebviewThrottled).mockReturnValueOnce(pendingFlush)
1712+
const messageListener = vi.fn()
1713+
task.on(RooCodeEventName.Message, messageListener)
1714+
const message = {
1715+
ts: 1,
1716+
type: "ask" as const,
1717+
ask: "resume_task" as const,
1718+
}
1719+
1720+
const addPromise = taskAccess.addToClineMessages(message)
1721+
1722+
await Promise.resolve()
1723+
expect(mockProvider.postStateToWebviewThrottled).toHaveBeenCalledOnce()
1724+
expect(mockProvider.postStateToWebviewThrottled).toHaveBeenCalledWith()
1725+
expect(flushSpy).toHaveBeenCalledOnce()
1726+
expect(flushSpy).toHaveBeenCalledWith()
1727+
expect(messageListener).not.toHaveBeenCalled()
1728+
1729+
releaseFlush()
1730+
await addPromise
1731+
1732+
expect(flushSpy.mock.invocationCallOrder[0]).toBeLessThan(messageListener.mock.invocationCallOrder[0])
1733+
expect(messageListener).toHaveBeenCalledWith({ action: "created", message })
1734+
})
1735+
1736+
it("keeps an already answered ask on the throttled path", async () => {
1737+
const task = new Task({
1738+
provider: mockProvider,
1739+
apiConfiguration: mockApiConfig,
1740+
task: "test task",
1741+
startTask: false,
1742+
})
1743+
vi.spyOn(getTaskTestAccess(task), "saveClineMessages").mockResolvedValue(true)
1744+
1745+
await getTaskTestAccess(task).addToClineMessages({
1746+
ts: 1,
1747+
type: "ask",
1748+
ask: "tool",
1749+
isAnswered: true,
1750+
})
1751+
1752+
expect(mockProvider.postStateToWebviewThrottled).toHaveBeenCalledOnce()
1753+
expect(mockProvider.postStateToWebviewThrottled).toHaveBeenCalledWith()
1754+
expect(mockProvider.flushPostStateToWebviewThrottled).not.toHaveBeenCalled()
1755+
})
1756+
16971757
it("waits for a new partial message flush before a following message update", async () => {
16981758
const task = new Task({
16991759
provider: mockProvider,

0 commit comments

Comments
 (0)