@@ -148,6 +148,18 @@ vi.mock("../../environment/getEnvironmentDetails", () => ({
148148
149149vi . 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.
152164vi . 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