@@ -29,6 +29,12 @@ function memberLine(output: string, index: number): string {
2929 return line ;
3030}
3131
32+ function memberRowCount ( output : string ) : number {
33+ return output . split ( '\n' ) . filter (
34+ ( candidate ) => / ^ \d { 3 } \s / u. test ( candidate . replace ( / ^ │ \s * / u, '' ) ) ,
35+ ) . length ;
36+ }
37+
3238function displayedPercent ( output : string , index : number ) : number {
3339 const match = / ( \d + ) % / u. exec ( memberLine ( output , index ) ) ;
3440 if ( match === null ) throw new Error ( `Missing percent for member ${ String ( index ) } ` ) ;
@@ -598,34 +604,26 @@ describe('DynamicWorkflowMissionControlComponent', () => {
598604 } ,
599605 ) ;
600606
601- it ( 'creeps past 90 across streamed deltas and completes only on the terminal event' , ( ) => {
607+ it ( 'holds the observed stage across streamed deltas and completes only on the terminal event' , ( ) => {
602608 const component = createComponent ( ) ;
603609 component . updateArgs ( { items : [ 'Long streaming work' ] } ) ;
604610 component . markInputComplete ( ) ;
605611 register ( component , 'agent-1' ) ;
606612 component . markStarted ( 'agent-1' ) ;
607613 component . recordToolCall ( { agentId : 'agent-1' , name : 'Read' } ) ;
608614
609- for ( let index = 0 ; index < 10 ; index += 1 ) {
610- component . appendModelDelta ( { agentId : 'agent-1' , delta : `chunk ${ String ( index ) } ` } ) ;
611- }
612- const early = displayedPercent ( renderText ( component , 100 ) , 1 ) ;
613- // No snap to 90: the finalizing phase climbs from 75 instead of jumping.
614- expect ( early ) . toBeGreaterThan ( 75 ) ;
615- expect ( early ) . toBeLessThan ( 90 ) ;
616-
617615 for ( let index = 0 ; index < 200 ; index += 1 ) {
618- component . appendModelDelta ( { agentId : 'agent-1' , delta : 'more ' } ) ;
616+ component . appendModelDelta ( { agentId : 'agent-1' , delta : `chunk ${ String ( index ) } ` } ) ;
619617 }
620- const late = displayedPercent ( renderText ( component , 100 ) , 1 ) ;
621- expect ( late ) . toBeGreaterThan ( 90 ) ;
622- expect ( late ) . toBeLessThan ( 100 ) ;
618+ // No invented progress: text after a tool call never climbs toward 100.
619+ expect ( displayedPercent ( renderText ( component , 100 ) , 1 ) )
620+ . toBe ( DYNAMIC_WORKFLOW_RENDERING . toolActivityProgress ) ;
623621
624622 component . markCompleted ( 'agent-1' , 'Done' ) ;
625623 expect ( displayedPercent ( renderText ( component , 100 ) , 1 ) ) . toBe ( 100 ) ;
626624 } ) ;
627625
628- it ( 'keeps mid-work delta creep under the tool-activity stage until a tool call lifts it' , ( ) => {
626+ it ( 'keeps streamed text at the model stage until a tool call lifts it' , ( ) => {
629627 const component = createComponent ( ) ;
630628 component . updateArgs ( { items : [ 'Chatty work' ] } ) ;
631629 component . markInputComplete ( ) ;
@@ -635,13 +633,50 @@ describe('DynamicWorkflowMissionControlComponent', () => {
635633 for ( let index = 0 ; index < 300 ; index += 1 ) {
636634 component . appendModelDelta ( { agentId : 'agent-1' , delta : 'more ' } ) ;
637635 }
638- const midwork = displayedPercent ( renderText ( component , 100 ) , 1 ) ;
639- expect ( midwork ) . toBeGreaterThan ( 50 ) ;
640- expect ( midwork ) . toBeLessThan ( DYNAMIC_WORKFLOW_RENDERING . toolActivityProgress ) ;
636+ expect ( displayedPercent ( renderText ( component , 100 ) , 1 ) )
637+ . toBe ( DYNAMIC_WORKFLOW_RENDERING . modelActivityProgress ) ;
641638
642639 component . recordToolCall ( { agentId : 'agent-1' , name : 'Read' } ) ;
643640 expect ( displayedPercent ( renderText ( component , 100 ) , 1 ) )
644- . toBeGreaterThanOrEqual ( DYNAMIC_WORKFLOW_RENDERING . toolActivityProgress ) ;
641+ . toBe ( DYNAMIC_WORKFLOW_RENDERING . toolActivityProgress ) ;
642+ } ) ;
643+
644+ it ( 'starts a new line for model text after a tool label instead of fusing them' , ( ) => {
645+ const component = createComponent ( ) ;
646+ component . updateArgs ( { items : [ 'Work' ] } ) ;
647+ component . markInputComplete ( ) ;
648+ register ( component , 'agent-1' ) ;
649+ component . markStarted ( 'agent-1' ) ;
650+ component . recordToolCall ( { agentId : 'agent-1' , name : 'Read' } ) ;
651+ component . appendModelDelta ( { agentId : 'agent-1' , delta : "I've read the files" } ) ;
652+
653+ const line = memberLine ( renderText ( component , 200 ) , 1 ) ;
654+ expect ( line ) . not . toContain ( "Using ReadI've" ) ;
655+ expect ( line ) . toContain ( "I've read the files" ) ;
656+ } ) ;
657+
658+ it ( 'renders object items by their prompt field and drops streamed phantom rows' , ( ) => {
659+ const component = createComponent ( ) ;
660+ const streamingArguments =
661+ '{"items": [{"prompt": "Explore records", "description": "Records"},'
662+ + ' {"prompt": "Explore events", "description": "Events"}' ;
663+ component . updateArgs ( { } , { streamingArguments } ) ;
664+ // Object keys and nested values are not items: two members, not eight.
665+ expect ( memberRowCount ( renderText ( component , 200 ) ) ) . toBe ( 2 ) ;
666+
667+ component . updateArgs ( {
668+ items : [
669+ { prompt : 'Explore records' , description : 'Records' } ,
670+ { prompt : 'Explore events' , description : 'Events' } ,
671+ ] ,
672+ } ) ;
673+ component . markInputComplete ( ) ;
674+
675+ const output = renderText ( component , 200 ) ;
676+ expect ( memberRowCount ( output ) ) . toBe ( 2 ) ;
677+ expect ( memberLine ( output , 1 ) ) . toContain ( 'Explore records' ) ;
678+ expect ( memberLine ( output , 1 ) ) . not . toContain ( '[object Object]' ) ;
679+ expect ( memberLine ( output , 2 ) ) . toContain ( 'Explore events' ) ;
645680 } ) ;
646681
647682 it ( 'shimmers Finalizing once every member is terminal but the result has not arrived' , ( ) => {
0 commit comments