@@ -165,16 +165,21 @@ describe("live subagent observe", () => {
165165 expect ( shell . observe ) . toBeNull ( )
166166 expect ( shell . parentStreamLog ) . toBeNull ( )
167167 expect ( focusOwner ( shell . focus ) ) . not . toBe ( "observe" )
168- expect ( shell . streamLog . length ) . toBeGreaterThanOrEqual ( parentLen )
168+ // Parent gained exactly the row appended while away plus the
169+ // "left observe" system row — never a doubled copy of either.
170+ expect ( shell . streamLog . length ) . toBe ( parentLen + 2 )
169171 expect (
170- shell . streamLog . some ( ( r ) => r . text === "parent user line" ) ,
171- ) . toBe ( true )
172+ shell . streamLog . filter ( ( r ) => r . text === "parent user line" )
173+ . length ,
174+ ) . toBe ( 1 )
172175 expect (
173- shell . streamLog . some ( ( r ) => r . text === "parent while away" ) ,
174- ) . toBe ( true )
176+ shell . streamLog . filter ( ( r ) => r . text === "parent while away" )
177+ . length ,
178+ ) . toBe ( 1 )
175179 expect (
176- shell . streamLog . some ( ( r ) => r . text . includes ( "left observe" ) ) ,
177- ) . toBe ( true )
180+ shell . streamLog . filter ( ( r ) => r . text . includes ( "left observe" ) )
181+ . length ,
182+ ) . toBe ( 1 )
178183 // Child rows must not leak into the restored parent transcript.
179184 expect (
180185 shell . streamLog . some ( ( r ) => r . text === "child only" ) ,
@@ -190,6 +195,51 @@ describe("live subagent observe", () => {
190195 )
191196 } )
192197
198+ test ( "leave does not duplicate rows across repeated enter/observe/leave cycles" , async ( ) => {
199+ await withTestRenderer (
200+ async ( h ) => {
201+ const shell = createAppShell ( h . renderer , {
202+ terminal : { columns : 80 , rows : 24 } ,
203+ run : "idle" ,
204+ } )
205+ try {
206+ appendStreamRow ( shell , { role : "user" , text : "start" } )
207+
208+ for ( let cycle = 0 ; cycle < 3 ; cycle ++ ) {
209+ enterSubagentObserve (
210+ shell ,
211+ liveChildSession ( [ { role : "assistant" , text : `cycle ${ cycle } ` } ] ) ,
212+ )
213+ appendObserveStreamRow ( shell , {
214+ role : "tool" ,
215+ text : `child tool ${ cycle } ` ,
216+ meta : "tool.done" ,
217+ } )
218+ leaveSubagentObserve ( shell )
219+ }
220+
221+ // One "start" row and one "left observe" row per cycle; no
222+ // child row and no doubled parent row from any cycle.
223+ expect (
224+ shell . streamLog . filter ( ( r ) => r . text === "start" ) . length ,
225+ ) . toBe ( 1 )
226+ expect (
227+ shell . streamLog . filter ( ( r ) => r . text . includes ( "left observe" ) )
228+ . length ,
229+ ) . toBe ( 3 )
230+ for ( let cycle = 0 ; cycle < 3 ; cycle ++ ) {
231+ expect (
232+ shell . streamLog . some ( ( r ) => r . text === `child tool ${ cycle } ` ) ,
233+ ) . toBe ( false )
234+ }
235+ } finally {
236+ shell . dispose ( )
237+ }
238+ } ,
239+ { width : 80 , height : 24 } ,
240+ )
241+ } )
242+
193243 test ( "appendObserveStreamRow is no-op when not observing" , async ( ) => {
194244 await withTestRenderer (
195245 async ( h ) => {
0 commit comments