@@ -317,13 +317,14 @@ describe('AgentGroup main tool summary', () => {
317317
318318 afterEach ( ( ) => act ( ( ) => root . unmount ( ) ) )
319319
320- const render = ( items : AgentGroupItem [ ] , isStreaming = true ) => {
320+ const render = ( items : AgentGroupItem [ ] , isStreaming = true , completedGroupCount = 0 ) => {
321321 act ( ( ) => {
322322 root . render (
323323 createElement ( AgentGroup , {
324324 agentName : 'mothership' ,
325325 agentLabel : 'Sim' ,
326326 items,
327+ completedGroupCount,
327328 isStreaming,
328329 isLaneOpen : isStreaming ,
329330 } )
@@ -332,31 +333,187 @@ describe('AgentGroup main tool summary', () => {
332333 return container . querySelector < HTMLButtonElement > ( 'button[aria-expanded]' ) !
333334 }
334335
336+ const call = ( id : string , status : ToolCallStatus , activity ?: string ) : AgentGroupItem => ( {
337+ type : 'tool' ,
338+ data : {
339+ id,
340+ toolName : 'cli_blocks_get' ,
341+ displayTitle : `Reading ${ id } configuration` ,
342+ status,
343+ params : {
344+ args : [ 'blocks' , 'get' , id ] ,
345+ ...( activity
346+ ? {
347+ activity : {
348+ id : activity ,
349+ title : activity ,
350+ completedTitle : activity
351+ . replace ( / ^ C h e c k i n g / , 'Checked' )
352+ . replace ( / ^ T e s t i n g / , 'Tested' )
353+ . replace ( / ^ B u i l d i n g / , 'Built' ) ,
354+ } ,
355+ }
356+ : { } ) ,
357+ } ,
358+ } ,
359+ } )
360+
361+ it ( 'preserves intent through omitted updates and completion, and only renames on an explicit update' , ( ) => {
362+ const first = call ( 'Exa' , 'success' , 'Checking search input requirements' )
363+ const second = call ( 'Start' , 'executing' )
364+ const third = call ( 'Function' , 'executing' )
365+ const header = render ( [ first , second , third ] )
366+ expect ( header . textContent ) . toBe ( 'Checking search input requirements + 1' )
367+ expect ( header . querySelectorAll ( 'svg' ) ) . toHaveLength ( 1 )
368+ render ( [ first , call ( 'Start' , 'success' ) , third ] )
369+ expect ( header . textContent ) . toBe ( 'Checking search input requirements' )
370+ render ( [ first , call ( 'Start' , 'success' ) , call ( 'Function' , 'success' ) ] , false )
371+ expect ( header . textContent ) . toBe ( 'Checked search input requirements' )
372+ const update = call ( 'API' , 'executing' , 'Testing both API workflows' )
373+ render ( [ first , second , update ] )
374+ expect ( header . textContent ) . toBe ( 'Testing both API workflows + 1' )
375+ render ( [ call ( 'Next' , 'executing' ) ] )
376+ expect ( header . textContent ) . toBe ( 'Reading Next configuration' )
377+ } )
378+
379+ it ( 'summarizes a completed batch with the additional group count and a flat tool log' , ( ) => {
380+ const items = [
381+ call ( 'Exa' , 'success' , 'Checking search inputs' ) ,
382+ call ( 'Start' , 'success' , 'Checking start inputs' ) ,
383+ call ( 'Function' , 'success' ) ,
384+ ]
385+ const header = render ( items , true , 2 )
386+ expect ( header . textContent ) . toBe ( 'Checked start inputs + 1' )
387+ expect ( header . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' )
388+ act ( ( ) => header . click ( ) )
389+ expect ( header . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' )
390+ expect ( container . querySelectorAll ( 'button[aria-expanded]' ) ) . toHaveLength ( 1 )
391+ expect ( container . textContent ) . not . toContain ( 'Checked search inputs' )
392+ } )
393+
394+ it . each ( [ 'cancelled' , 'interrupted' , 'skipped' , 'rejected' ] as const ) (
395+ 'does not claim completion for %s activity calls' ,
396+ ( status ) => {
397+ const header = render ( [ call ( 'Start' , status , 'Checking start inputs' ) ] , false )
398+ expect ( header . textContent ) . toBe (
399+ status === 'rejected' ? 'Failed checking start inputs' : 'Stopped checking start inputs'
400+ )
401+ }
402+ )
403+
404+ it ( 'does not attribute another activity failure to the representative completed activity' , ( ) => {
405+ const header = render (
406+ [
407+ call ( 'Exa' , 'error' , 'Checking search inputs' ) ,
408+ call ( 'Start' , 'success' , 'Checking start inputs' ) ,
409+ ] ,
410+ false ,
411+ 2
412+ )
413+ expect ( header . textContent ) . toBe ( 'Checked start inputs + 1' )
414+ render (
415+ [
416+ call ( 'Exa' , 'success' , 'Checking search inputs' ) ,
417+ call ( 'Start' , 'error' , 'Checking start inputs' ) ,
418+ ] ,
419+ false ,
420+ 2
421+ )
422+ expect ( header . textContent ) . toBe ( 'Failed checking start inputs + 1' )
423+ } )
424+
425+ it ( 'waits for a complete streamed activity string and retains it when later calls omit activity' , ( ) => {
426+ const streaming = ( streamingArgs : string ) : AgentGroupItem => ( {
427+ type : 'tool' ,
428+ data : {
429+ id : 'streaming' ,
430+ toolName : 'sim_cli' ,
431+ displayTitle : 'Running CLI command' ,
432+ status : 'executing' ,
433+ streamingArgs,
434+ } ,
435+ } )
436+ const header = render ( [ streaming ( '{"activity":{"id":"inputs","title":"Checking' ) ] )
437+ expect ( header . textContent ) . toBe ( 'Working…' )
438+ render ( [
439+ streaming (
440+ '{"activity":{"id":"inputs","title":"Checking search inputs","completedTitle":"Checked search inputs"},"args":['
441+ ) ,
442+ ] )
443+ expect ( header . textContent ) . toBe ( 'Checking search inputs' )
444+ render ( [
445+ streaming (
446+ '{"activity":{"id":"inputs","title":"Checking search inputs","completedTitle":"Checked search inputs"},"args":['
447+ ) ,
448+ call ( 'Exa' , 'executing' ) ,
449+ ] )
450+ expect ( header . textContent ) . toBe ( 'Checking search inputs + 1' )
451+ } )
452+
453+ it ( 'uses current concrete calls and counts down across nested lanes when activity is absent' , ( ) => {
454+ const header = render ( [
455+ call ( 'Exa' , 'executing' ) ,
456+ group ( [ call ( 'Start' , 'executing' ) , call ( 'Function' , 'executing' ) ] ) ,
457+ ] )
458+ expect ( header . textContent ) . toBe ( 'Reading Function configuration + 2' )
459+ render ( [
460+ call ( 'Exa' , 'executing' ) ,
461+ group ( [ call ( 'Start' , 'executing' ) , call ( 'Function' , 'success' ) ] ) ,
462+ ] )
463+ expect ( header . textContent ) . toBe ( 'Reading Start configuration + 1' )
464+ render ( [
465+ call ( 'Exa' , 'executing' ) ,
466+ group ( [ call ( 'Start' , 'success' ) , call ( 'Function' , 'success' ) ] ) ,
467+ ] )
468+ expect ( header . textContent ) . toBe ( 'Reading Exa configuration' )
469+ } )
470+
471+ it ( 'does not let a nested agent rename the parent or treat a resource title as intent' , ( ) => {
472+ const header = render ( [
473+ call ( 'Exa' , 'executing' , 'Checking search inputs' ) ,
474+ group ( [ call ( 'Start' , 'executing' , 'Building an unrelated API' ) ] ) ,
475+ ] )
476+ expect ( header . textContent ) . toBe ( 'Checking search inputs + 1' )
477+ render ( [
478+ {
479+ type : 'tool' ,
480+ data : {
481+ id : 'file' ,
482+ toolName : 'cli_files_create' ,
483+ displayTitle : 'Creating Report' ,
484+ status : 'executing' ,
485+ params : { title : 'Report' } ,
486+ } ,
487+ } ,
488+ ] )
489+ expect ( header . textContent ) . toBe ( 'Creating Report' )
490+ } )
491+
335492 it ( 'starts collapsed while streaming, shows the tool and count, and preserves manual expansion' , ( ) => {
336493 const items = [ tool ( 'success' ) , tool ( 'executing' ) ]
337494 const header = render ( items )
338- expect ( header . textContent ) . toBe ( 'Searching + 1 ' )
495+ expect ( header . textContent ) . toBe ( 'Searching' )
339496 expect ( header . textContent ) . not . toContain ( 'Sim' )
340497 expect ( header . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' )
341498 act ( ( ) => header . click ( ) )
342499 expect ( header . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' )
343500 render ( [ ...items , tool ( 'executing' ) ] )
344- expect ( header . textContent ) . toBe ( 'Searching + 2 ' )
501+ expect ( header . textContent ) . toBe ( 'Searching + 1 ' )
345502 expect ( header . getAttribute ( 'aria-expanded' ) ) . toBe ( 'true' )
346503 act ( ( ) => header . click ( ) )
347504 render ( [ ...items , tool ( 'executing' ) ] )
348505 expect ( header . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' )
349506 } )
350507
351508 it . each ( [ 'success' , 'error' , 'cancelled' ] as const ) (
352- 'shows an honest terminal label for %s without losing the collapsed history count' ,
509+ 'shows an honest terminal label for %s with no remaining-call count' ,
353510 ( status ) => {
354511 const header = render ( [ tool ( 'success' ) , tool ( status ) ] , false )
355512 expect ( header . textContent ) . toBe (
356513 {
357- success : 'Searched + 1 ' ,
358- error : 'Failed searching + 1 ' ,
359- cancelled : 'Stopped searching + 1 ' ,
514+ success : 'Searched' ,
515+ error : 'Failed searching' ,
516+ cancelled : 'Stopped searching' ,
360517 } [ status ]
361518 )
362519 expect ( header . getAttribute ( 'aria-expanded' ) ) . toBe ( 'false' )
0 commit comments