Skip to content

Commit 13d20bb

Browse files
fix: process queued messages after context condensing completes (#8478)
Co-authored-by: Roo Code <[email protected]>
1 parent cff65b4 commit 13d20bb

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

src/core/task/Task.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
10751075
{ isNonInteractive: true } /* options */,
10761076
contextCondense,
10771077
)
1078+
1079+
// Process any queued messages after condensing completes
1080+
this.processQueuedMessages()
10781081
}
10791082

10801083
async say(

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

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ vi.mock("../../environment/getEnvironmentDetails", () => ({
148148

149149
vi.mock("../../ignore/RooIgnoreController")
150150

151+
vi.mock("../../condense", async (importOriginal) => {
152+
const actual = (await importOriginal()) as any
153+
return {
154+
...actual,
155+
summarizeConversation: vi.fn().mockResolvedValue({
156+
messages: [{ role: "user", content: [{ type: "text", text: "continued" }], ts: Date.now() }],
157+
summary: "summary",
158+
cost: 0,
159+
newContextTokens: 1,
160+
}),
161+
}
162+
})
151163
// Mock storagePathManager to prevent dynamic import issues.
152164
vi.mock("../../../utils/storage", () => ({
153165
getTaskDirectoryPath: vi
@@ -1823,3 +1835,124 @@ describe("Cline", () => {
18231835
})
18241836
})
18251837
})
1838+
1839+
describe("Queued message processing after condense", () => {
1840+
function createProvider(): any {
1841+
const storageUri = { fsPath: path.join(os.tmpdir(), "test-storage") }
1842+
const ctx = {
1843+
globalState: {
1844+
get: vi.fn().mockImplementation((_key: keyof GlobalState) => undefined),
1845+
update: vi.fn().mockResolvedValue(undefined),
1846+
keys: vi.fn().mockReturnValue([]),
1847+
},
1848+
globalStorageUri: storageUri,
1849+
workspaceState: {
1850+
get: vi.fn().mockImplementation((_key) => undefined),
1851+
update: vi.fn().mockResolvedValue(undefined),
1852+
keys: vi.fn().mockReturnValue([]),
1853+
},
1854+
secrets: {
1855+
get: vi.fn().mockResolvedValue(undefined),
1856+
store: vi.fn().mockResolvedValue(undefined),
1857+
delete: vi.fn().mockResolvedValue(undefined),
1858+
},
1859+
extensionUri: { fsPath: "/mock/extension/path" },
1860+
extension: { packageJSON: { version: "1.0.0" } },
1861+
} as unknown as vscode.ExtensionContext
1862+
1863+
const output = {
1864+
appendLine: vi.fn(),
1865+
append: vi.fn(),
1866+
clear: vi.fn(),
1867+
show: vi.fn(),
1868+
hide: vi.fn(),
1869+
dispose: vi.fn(),
1870+
}
1871+
1872+
const provider = new ClineProvider(ctx, output as any, "sidebar", new ContextProxy(ctx)) as any
1873+
provider.postMessageToWebview = vi.fn().mockResolvedValue(undefined)
1874+
provider.postStateToWebview = vi.fn().mockResolvedValue(undefined)
1875+
provider.getState = vi.fn().mockResolvedValue({})
1876+
return provider
1877+
}
1878+
1879+
const apiConfig: ProviderSettings = {
1880+
apiProvider: "anthropic",
1881+
apiModelId: "claude-3-5-sonnet-20241022",
1882+
apiKey: "test-api-key",
1883+
} as any
1884+
1885+
it("processes queued message after condense completes", async () => {
1886+
const provider = createProvider()
1887+
const task = new Task({
1888+
provider,
1889+
apiConfiguration: apiConfig,
1890+
task: "initial task",
1891+
startTask: false,
1892+
})
1893+
1894+
// Make condense fast + deterministic
1895+
vi.spyOn(task as any, "getSystemPrompt").mockResolvedValue("system")
1896+
const submitSpy = vi.spyOn(task, "submitUserMessage").mockResolvedValue(undefined)
1897+
1898+
// Queue a message during condensing
1899+
task.messageQueueService.addMessage("queued text", ["img1.png"])
1900+
1901+
// Use fake timers to capture setTimeout(0) in processQueuedMessages
1902+
vi.useFakeTimers()
1903+
await task.condenseContext()
1904+
1905+
// Flush the microtask that submits the queued message
1906+
vi.runAllTimers()
1907+
vi.useRealTimers()
1908+
1909+
expect(submitSpy).toHaveBeenCalledWith("queued text", ["img1.png"])
1910+
expect(task.messageQueueService.isEmpty()).toBe(true)
1911+
})
1912+
1913+
it("does not cross-drain queues between separate tasks", async () => {
1914+
const providerA = createProvider()
1915+
const providerB = createProvider()
1916+
1917+
const taskA = new Task({
1918+
provider: providerA,
1919+
apiConfiguration: apiConfig,
1920+
task: "task A",
1921+
startTask: false,
1922+
})
1923+
const taskB = new Task({
1924+
provider: providerB,
1925+
apiConfiguration: apiConfig,
1926+
task: "task B",
1927+
startTask: false,
1928+
})
1929+
1930+
vi.spyOn(taskA as any, "getSystemPrompt").mockResolvedValue("system")
1931+
vi.spyOn(taskB as any, "getSystemPrompt").mockResolvedValue("system")
1932+
1933+
const spyA = vi.spyOn(taskA, "submitUserMessage").mockResolvedValue(undefined)
1934+
const spyB = vi.spyOn(taskB, "submitUserMessage").mockResolvedValue(undefined)
1935+
1936+
taskA.messageQueueService.addMessage("A message")
1937+
taskB.messageQueueService.addMessage("B message")
1938+
1939+
// Condense in task A should only drain A's queue
1940+
vi.useFakeTimers()
1941+
await taskA.condenseContext()
1942+
vi.runAllTimers()
1943+
vi.useRealTimers()
1944+
1945+
expect(spyA).toHaveBeenCalledWith("A message", undefined)
1946+
expect(spyB).not.toHaveBeenCalled()
1947+
expect(taskB.messageQueueService.isEmpty()).toBe(false)
1948+
1949+
// Now condense in task B should drain B's queue
1950+
vi.useFakeTimers()
1951+
await taskB.condenseContext()
1952+
vi.runAllTimers()
1953+
vi.useRealTimers()
1954+
1955+
expect(spyB).toHaveBeenCalledWith("B message", undefined)
1956+
expect(taskB.messageQueueService.isEmpty()).toBe(true)
1957+
})
1958+
})

0 commit comments

Comments
 (0)