@@ -1845,6 +1845,107 @@ describe("Cline", () => {
18451845 } )
18461846 } )
18471847 } )
1848+
1849+ describe ( "Partial tool_use blocks at stream end" , ( ) => {
1850+ /**
1851+ * These tests verify that presentAssistantMessage is called at stream end
1852+ * even when only tool_use blocks are partial. This is critical because:
1853+ * - For XML protocol: tool_use blocks come from text parsing, not native events
1854+ * - If presentAssistantMessage isn't called, tools never execute and the task hangs
1855+ *
1856+ * A previous regression was caused by adding the condition:
1857+ * `partialBlocks.some((block) => block.type !== "tool_use")`
1858+ * which prevented presentAssistantMessage from being called when only
1859+ * tool_use blocks were partial.
1860+ */
1861+ it ( "should call presentAssistantMessage when only tool_use blocks are partial" , ( ) => {
1862+ const cline = new Task ( {
1863+ provider : mockProvider ,
1864+ apiConfiguration : mockApiConfig ,
1865+ task : "test task" ,
1866+ startTask : false ,
1867+ } )
1868+
1869+ // Simulate the state at stream end with only a tool_use block that's partial
1870+ cline . assistantMessageContent = [
1871+ {
1872+ type : "tool_use" as const ,
1873+ name : "read_file" as any ,
1874+ params : { path : "test.txt" } ,
1875+ partial : true ,
1876+ } ,
1877+ ]
1878+
1879+ // Get partial blocks (simulating what happens at stream end)
1880+ const partialBlocks = cline . assistantMessageContent . filter ( ( block ) => block . partial )
1881+
1882+ // This is what the FIXED condition should evaluate to
1883+ const shouldPresentMessage = partialBlocks . length > 0
1884+ expect ( shouldPresentMessage ) . toBe ( true )
1885+
1886+ // This is what the BROKEN condition (PR #9542) would evaluate to
1887+ const brokenCondition = partialBlocks . length > 0 && partialBlocks . some ( ( block ) => block . type !== "tool_use" )
1888+ expect ( brokenCondition ) . toBe ( false ) // This was the bug - it evaluated to false!
1889+ } )
1890+
1891+ it ( "should call presentAssistantMessage when text and tool_use blocks are partial" , ( ) => {
1892+ const cline = new Task ( {
1893+ provider : mockProvider ,
1894+ apiConfiguration : mockApiConfig ,
1895+ task : "test task" ,
1896+ startTask : false ,
1897+ } )
1898+
1899+ // Simulate the state at stream end with both text and tool_use blocks partial
1900+ cline . assistantMessageContent = [
1901+ {
1902+ type : "text" as const ,
1903+ content : "Some text" ,
1904+ partial : true ,
1905+ } ,
1906+ {
1907+ type : "tool_use" as const ,
1908+ name : "read_file" as any ,
1909+ params : { path : "test.txt" } ,
1910+ partial : true ,
1911+ } ,
1912+ ]
1913+
1914+ const partialBlocks = cline . assistantMessageContent . filter ( ( block ) => block . partial )
1915+
1916+ // Both conditions should be true in this case
1917+ const shouldPresentMessage = partialBlocks . length > 0
1918+ expect ( shouldPresentMessage ) . toBe ( true )
1919+
1920+ const brokenCondition = partialBlocks . length > 0 && partialBlocks . some ( ( block ) => block . type !== "tool_use" )
1921+ expect ( brokenCondition ) . toBe ( true ) // The old condition worked when text was also partial
1922+ } )
1923+
1924+ it ( "should not call presentAssistantMessage when no blocks are partial" , ( ) => {
1925+ const cline = new Task ( {
1926+ provider : mockProvider ,
1927+ apiConfiguration : mockApiConfig ,
1928+ task : "test task" ,
1929+ startTask : false ,
1930+ } )
1931+
1932+ // Simulate completed blocks (no partial blocks)
1933+ cline . assistantMessageContent = [
1934+ {
1935+ type : "tool_use" as const ,
1936+ name : "read_file" as any ,
1937+ params : { path : "test.txt" } ,
1938+ partial : false ,
1939+ } ,
1940+ ]
1941+
1942+ const partialBlocks = cline . assistantMessageContent . filter ( ( block ) => block . partial )
1943+
1944+ // Neither condition should trigger presentAssistantMessage
1945+ const shouldPresentMessage = partialBlocks . length > 0
1946+ expect ( shouldPresentMessage ) . toBe ( false )
1947+ } )
1948+ } )
18481949} )
18491950
18501951describe ( "Queued message processing after condense" , ( ) => {
0 commit comments