@@ -34,6 +34,12 @@ const theme = {
3434 bold : ( text : string ) => text ,
3535} as unknown as Theme ;
3636
37+ const ansiTheme = {
38+ fg : ( _name : string , text : string ) => `\u001b[38;5;245m${ text } \u001b[39m` ,
39+ bg : ( _name : string , text : string ) => `\u001b[48;5;236m${ text } \u001b[49m` ,
40+ bold : ( text : string ) => text ,
41+ } as unknown as Theme ;
42+
3743function createRenderContext ( overrides : Record < string , unknown > = { } ) : Record < string , unknown > {
3844 return {
3945 expanded : false ,
@@ -59,6 +65,23 @@ function stripAnsi(text: string): string {
5965 return text . replace ( / \u001b \[ [ 0 - 9 ; ] * m / g, "" ) . replace ( / \u001b \] [ ^ \u0007 ] * \u0007 / g, "" ) ;
6066}
6167
68+ function getRenderedLine ( lines : string [ ] , match : ( plain : string ) => boolean ) : string {
69+ const line = lines . find ( candidate => match ( stripAnsi ( candidate ) ) ) ;
70+ assert . ok ( line ) ;
71+ return line ;
72+ }
73+
74+ function getLineContaining ( lines : string [ ] , text : string ) : string {
75+ const line = lines . find ( candidate => candidate . includes ( text ) ) ;
76+ assert . ok ( line ) ;
77+ return line ;
78+ }
79+
80+ function assertShellBackgroundPreserved ( line : string ) : void {
81+ assert . equal ( line . includes ( "\u001b[0m" ) , false ) ;
82+ assert . match ( line , / \u001b \[ 4 8 ; / ) ;
83+ }
84+
6285function createDeferred ( ) {
6386 let resolve ! : ( ) => void ;
6487 const promise = new Promise < void > ( ( r ) => { resolve = r ; } ) ;
@@ -580,6 +603,71 @@ test("collapsed nested spawn render keeps all text blocks from the last assistan
580603 assert . ok ( lines . some ( ( l : string ) => l . includes ( "second" ) ) ) ;
581604} ) ;
582605
606+ test ( "collapsed nested spawn truncation preserves shell background across preview and stats lines" , ( ) => {
607+ const state = createState ( ) ;
608+ const childSpawnTool = createChildSpawnTool ( state ) ;
609+ const session = createSession ( [
610+ { role : "assistant" , content : [ { type : "text" , text : "Research the nudge on toggle off TODO from the readonly mode plan." } ] } ,
611+ ] ) ;
612+ state . childSessions . set ( "tool-call-1" , session ) ;
613+
614+ const component = childSpawnTool . renderResult (
615+ {
616+ content : [ { type : "text" , text : "ignored" } ] ,
617+ details : {
618+ model : "mock-model" ,
619+ thinking : "medium" ,
620+ truncated : true ,
621+ stats : { inputTokens : 12 , outputTokens : 34 , turns : 2 , cost : 0.125 } ,
622+ } ,
623+ } ,
624+ { expanded : false } ,
625+ ansiTheme ,
626+ createRenderContext ( ) ,
627+ ) as any ;
628+
629+ const lines = component . render ( 24 ) ;
630+ const previewLine = getRenderedLine ( lines , plain => plain . includes ( "Research" ) ) ;
631+ const statsLine = getRenderedLine ( lines , plain => plain . includes ( "tok 12/34" ) ) ;
632+ assertShellBackgroundPreserved ( previewLine ) ;
633+ assertShellBackgroundPreserved ( statsLine ) ;
634+ assert . match ( stripAnsi ( statsLine ) , / t o k 1 2 \/ 3 4 / ) ;
635+ } ) ;
636+
637+ test ( "collapsed nested spawn keeps truncated stats line calm" , ( ) => {
638+ const markerTheme = {
639+ fg : ( name : string , text : string ) => `<${ name } >${ text } </${ name } >` ,
640+ bg : ( _name : string , text : string ) => text ,
641+ bold : ( text : string ) => text ,
642+ } as unknown as Theme ;
643+ const state = createState ( ) ;
644+ const childSpawnTool = createChildSpawnTool ( state ) ;
645+ const session = createSession ( [
646+ { role : "assistant" , content : [ { type : "text" , text : "short preview" } ] } ,
647+ ] ) ;
648+ state . childSessions . set ( "tool-call-1" , session ) ;
649+
650+ const component = childSpawnTool . renderResult (
651+ {
652+ content : [ { type : "text" , text : "ignored" } ] ,
653+ details : {
654+ model : "mock-model" ,
655+ thinking : "medium" ,
656+ truncated : true ,
657+ stats : { inputTokens : 12 , outputTokens : 34 , turns : 2 , cost : 0.125 } ,
658+ } ,
659+ } ,
660+ { expanded : false } ,
661+ markerTheme ,
662+ createRenderContext ( ) ,
663+ ) as any ;
664+
665+ const lines = component . render ( 120 ) ;
666+ const statsLine = getLineContaining ( lines , "tok 12/34" ) ;
667+ assert . match ( statsLine , / < d i m > .* t o k 1 2 \/ 3 4 .* t r u n c .* < \/ d i m > / ) ;
668+ assert . equal ( statsLine . includes ( "<warning>" ) , false ) ;
669+ } ) ;
670+
583671test ( "nested spawn render is safe without details" , ( ) => {
584672 const state = createState ( ) ;
585673 const childSpawnTool = createChildSpawnTool ( state ) ;
0 commit comments