fix: process queued messages after context condensing completes (#8478)

Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
roomote[bot] 2025-10-27 16:57:34 -04:00 • committed by GitHub
parent cff65b4f5e
commit 13d20bbe8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 136 additions and 0 deletions

View file

@ -1075,6 +1075,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
{ isNonInteractive: true } /* options */,
contextCondense,
)
// Process any queued messages after condensing completes
this.processQueuedMessages()
}
async say(

View file

@ -148,6 +148,18 @@ vi.mock("../../environment/getEnvironmentDetails", () => ({
vi.mock("../../ignore/RooIgnoreController")
vi.mock("../../condense", async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
summarizeConversation: vi.fn().mockResolvedValue({
messages: [{ role: "user", content: [{ type: "text", text: "continued" }], ts: Date.now() }],
summary: "summary",
cost: 0,
newContextTokens: 1,
}),
}
})
// Mock storagePathManager to prevent dynamic import issues.
vi.mock("../../../utils/storage", () => ({
getTaskDirectoryPath: vi
@ -1823,3 +1835,124 @@ describe("Cline", () => {
})
})
})
describe("Queued message processing after condense", () => {
function createProvider(): any {
const storageUri = { fsPath: path.join(os.tmpdir(), "test-storage") }
const ctx = {
globalState: {
get: vi.fn().mockImplementation((_key: keyof GlobalState) => undefined),
update: vi.fn().mockResolvedValue(undefined),
keys: vi.fn().mockReturnValue([]),
},
globalStorageUri: storageUri,
workspaceState: {
get: vi.fn().mockImplementation((_key) => undefined),
update: vi.fn().mockResolvedValue(undefined),
keys: vi.fn().mockReturnValue([]),
},
secrets: {
get: vi.fn().mockResolvedValue(undefined),
store: vi.fn().mockResolvedValue(undefined),
delete: vi.fn().mockResolvedValue(undefined),
},
extensionUri: { fsPath: "/mock/extension/path" },
extension: { packageJSON: { version: "1.0.0" } },
} as unknown as vscode.ExtensionContext
const output = {
appendLine: vi.fn(),
append: vi.fn(),
clear: vi.fn(),
show: vi.fn(),
hide: vi.fn(),
dispose: vi.fn(),
}
const provider = new ClineProvider(ctx, output as any, "sidebar", new ContextProxy(ctx)) as any
provider.postMessageToWebview = vi.fn().mockResolvedValue(undefined)
provider.postStateToWebview = vi.fn().mockResolvedValue(undefined)
provider.getState = vi.fn().mockResolvedValue({})
return provider
}
const apiConfig: ProviderSettings = {
apiProvider: "anthropic",
apiModelId: "claude-3-5-sonnet-20241022",
apiKey: "test-api-key",
} as any
it("processes queued message after condense completes", async () => {
const provider = createProvider()
const task = new Task({
provider,
apiConfiguration: apiConfig,
task: "initial task",
startTask: false,
})
// Make condense fast + deterministic
vi.spyOn(task as any, "getSystemPrompt").mockResolvedValue("system")
const submitSpy = vi.spyOn(task, "submitUserMessage").mockResolvedValue(undefined)
// Queue a message during condensing
task.messageQueueService.addMessage("queued text", ["img1.png"])
// Use fake timers to capture setTimeout(0) in processQueuedMessages
vi.useFakeTimers()
await task.condenseContext()
// Flush the microtask that submits the queued message
vi.runAllTimers()
vi.useRealTimers()
expect(submitSpy).toHaveBeenCalledWith("queued text", ["img1.png"])
expect(task.messageQueueService.isEmpty()).toBe(true)
})
it("does not cross-drain queues between separate tasks", async () => {
const providerA = createProvider()
const providerB = createProvider()
const taskA = new Task({
provider: providerA,
apiConfiguration: apiConfig,
task: "task A",
startTask: false,
})
const taskB = new Task({
provider: providerB,
apiConfiguration: apiConfig,
task: "task B",
startTask: false,
})
vi.spyOn(taskA as any, "getSystemPrompt").mockResolvedValue("system")
vi.spyOn(taskB as any, "getSystemPrompt").mockResolvedValue("system")
const spyA = vi.spyOn(taskA, "submitUserMessage").mockResolvedValue(undefined)
const spyB = vi.spyOn(taskB, "submitUserMessage").mockResolvedValue(undefined)
taskA.messageQueueService.addMessage("A message")
taskB.messageQueueService.addMessage("B message")
// Condense in task A should only drain A's queue
vi.useFakeTimers()
await taskA.condenseContext()
vi.runAllTimers()
vi.useRealTimers()
expect(spyA).toHaveBeenCalledWith("A message", undefined)
expect(spyB).not.toHaveBeenCalled()
expect(taskB.messageQueueService.isEmpty()).toBe(false)
// Now condense in task B should drain B's queue
vi.useFakeTimers()
await taskB.condenseContext()
vi.runAllTimers()
vi.useRealTimers()
expect(spyB).toHaveBeenCalledWith("B message", undefined)
expect(taskB.messageQueueService.isEmpty()).toBe(true)
})
})