@@ -1375,6 +1375,98 @@ describe("grouped chat rendering", () => {
13751375 ) . toEqual ( { status : "error" } ) ;
13761376 } ) ;
13771377
1378+ describe ( "non-terminal internal tool failure de-promotion (#89683)" , ( ) => {
1379+ function failedToolMessage ( callId = "call-failed" ) {
1380+ return {
1381+ id : `tool-${ callId } ` ,
1382+ role : "toolResult" ,
1383+ toolCallId : callId ,
1384+ toolName : "shell" ,
1385+ content : JSON . stringify ( { status : "failed" , exitCode : 1 , stdout : "" } , null , 2 ) ,
1386+ isError : true ,
1387+ timestamp : Date . now ( ) ,
1388+ } ;
1389+ }
1390+
1391+ it ( "renders a failed tool collapsed (no error banner) when the turn succeeded" , ( ) => {
1392+ const container = document . createElement ( "div" ) ;
1393+ const group : MessageGroup = {
1394+ ...createMessageGroup ( failedToolMessage ( ) , "tool" ) ,
1395+ turnSucceeded : true ,
1396+ } ;
1397+ renderMessageGroups ( container , [ group ] , { isToolMessageExpanded : ( ) => false } ) ;
1398+
1399+ const summary = expectElement ( container , ".chat-tool-msg-summary" , HTMLButtonElement ) ;
1400+ expect ( summary . classList . contains ( "chat-tool-msg-summary--error" ) ) . toBe ( false ) ;
1401+ expect ( summary . querySelector ( ".chat-tool-msg-summary__label" ) ?. textContent ) . not . toBe (
1402+ "Tool error" ,
1403+ ) ;
1404+ expect ( summary . querySelector ( ".chat-tool-msg-summary__error-badge" ) ) . toBeNull ( ) ;
1405+ } ) ;
1406+
1407+ it ( "still surfaces a failed tool as an error when the turn did not produce a reply" , ( ) => {
1408+ const container = document . createElement ( "div" ) ;
1409+ // turnSucceeded undefined = terminal/in-progress failure; behavior unchanged.
1410+ const group = createMessageGroup ( failedToolMessage ( ) , "tool" ) ;
1411+ renderMessageGroups ( container , [ group ] , { isToolMessageExpanded : ( ) => false } ) ;
1412+
1413+ const summary = expectElement ( container , ".chat-tool-msg-summary" , HTMLButtonElement ) ;
1414+ expect ( summary . classList . contains ( "chat-tool-msg-summary--error" ) ) . toBe ( true ) ;
1415+ expect ( summary . querySelector ( ".chat-tool-msg-summary__label" ) ?. textContent ) . toBe (
1416+ "Tool error" ,
1417+ ) ;
1418+ expect ( summary . querySelector ( ".chat-tool-msg-summary__error-badge" ) ) . not . toBeNull ( ) ;
1419+ } ) ;
1420+
1421+ it ( "keeps the failed tool detail on expand even when de-promoted" , ( ) => {
1422+ const container = document . createElement ( "div" ) ;
1423+ const group : MessageGroup = {
1424+ ...createMessageGroup ( failedToolMessage ( ) , "tool" ) ,
1425+ turnSucceeded : true ,
1426+ } ;
1427+ renderMessageGroups ( container , [ group ] , { isToolMessageExpanded : ( ) => true } ) ;
1428+ // Info is not deleted: the expanded body still shows the failed tool output.
1429+ const body = container . querySelector ( ".chat-tool-msg-body" ) ;
1430+ expect ( body ) . not . toBeNull ( ) ;
1431+ expect ( body ?. textContent ) . toContain ( "failed" ) ;
1432+ } ) ;
1433+
1434+ it ( "de-promotes a multi-tool activity group when the turn succeeded" , ( ) => {
1435+ const container = document . createElement ( "div" ) ;
1436+ const group : MessageGroup = {
1437+ kind : "group" ,
1438+ key : "tool:multi" ,
1439+ role : "tool" ,
1440+ messages : [
1441+ { key : "t1" , message : failedToolMessage ( "call-1" ) } ,
1442+ { key : "t2" , message : failedToolMessage ( "call-2" ) } ,
1443+ ] ,
1444+ timestamp : Date . now ( ) ,
1445+ isStreaming : false ,
1446+ turnSucceeded : true ,
1447+ } ;
1448+ renderMessageGroups ( container , [ group ] , { isToolMessageExpanded : ( ) => false } ) ;
1449+
1450+ const summary = expectElement ( container , ".chat-activity-group__summary" , HTMLButtonElement ) ;
1451+ expect ( summary . classList . contains ( "chat-activity-group__summary--error" ) ) . toBe ( false ) ;
1452+ expect ( summary . querySelector ( ".chat-activity-group__badge" ) ) . toBeNull ( ) ;
1453+ expect ( summary . getAttribute ( "aria-expanded" ) ) . toBe ( "false" ) ;
1454+ } ) ;
1455+
1456+ it ( "keeps failed tools hidden when showToolCalls is false regardless of outcome" , ( ) => {
1457+ const container = document . createElement ( "div" ) ;
1458+ const group : MessageGroup = {
1459+ ...createMessageGroup ( failedToolMessage ( ) , "tool" ) ,
1460+ turnSucceeded : true ,
1461+ } ;
1462+ renderMessageGroups ( container , [ group ] , {
1463+ showToolCalls : false ,
1464+ isToolMessageExpanded : ( ) => false ,
1465+ } ) ;
1466+ expect ( container . querySelector ( ".chat-tool-msg-summary" ) ) . toBeNull ( ) ;
1467+ } ) ;
1468+ } ) ;
1469+
13781470 it ( "collapses an inline tool call while keeping matching tool output visible" , ( ) => {
13791471 const container = document . createElement ( "div" ) ;
13801472 const groups = [
0 commit comments