77 type DynamicWorkflowMissionControlOptions ,
88 dynamicWorkflowResultSummaryFromOutput ,
99} from '#/tui/components/messages/dynamic-workflow-mission-control' ;
10- import { BRAILLE_SPINNER_INTERVAL_MS } from '#/tui/constant/rendering' ;
10+ import { BRAILLE_SPINNER_INTERVAL_MS , DYNAMIC_WORKFLOW_RENDERING } from '#/tui/constant/rendering' ;
1111import { currentTheme , darkColors } from '#/tui/theme' ;
1212
1313const DESCRIPTION = 'Review the interface' ;
@@ -29,9 +29,15 @@ function memberLine(output: string, index: number): string {
2929 return line ;
3030}
3131
32+ function displayedPercent ( output : string , index : number ) : number {
33+ const match = / ( \d + ) % / u. exec ( memberLine ( output , index ) ) ;
34+ if ( match === null ) throw new Error ( `Missing percent for member ${ String ( index ) } ` ) ;
35+ return Number ( match [ 1 ] ) ;
36+ }
37+
3238function aggregateLine ( output : string ) : string {
3339 const line = output . split ( '\n' ) . find ( ( candidate ) =>
34- / \b (?: O r c h e s t r a t i n g | C o m p l e t e d | F a i l e d | C a n c e l l e d ) \b / u. test ( strip ( candidate ) )
40+ / \b (?: O r c h e s t r a t i n g | F i n a l i z i n g | C o m p l e t e d | F a i l e d | C a n c e l l e d ) \b / u. test ( strip ( candidate ) )
3541 ) ;
3642 if ( line === undefined ) throw new Error ( 'Missing Dynamic Workflow aggregate' ) ;
3743 return line ;
@@ -508,7 +514,7 @@ describe('DynamicWorkflowMissionControlComponent', () => {
508514 component . recordToolCall ( { agentId : 'agent-1' , name : 'Read' } ) ;
509515 expectProgress ( 75 , '⣶' ) ;
510516 component . appendModelDelta ( { agentId : 'agent-1' , delta : 'Summarizing' } ) ;
511- const activeOutput = expectProgress ( 90 , '⣷ ' ) ;
517+ const activeOutput = expectProgress ( 75 , '⣶ ' ) ;
512518 expect ( aggregateLine ( activeOutput ) ) . toContain ( '0/1 complete' ) ;
513519 expect ( aggregateLine ( activeOutput ) ) . not . toMatch ( / \b \d + % / u) ;
514520 expect ( aggregateLine ( activeOutput ) ) . not . toContain ( '━' ) ;
@@ -591,4 +597,116 @@ describe('DynamicWorkflowMissionControlComponent', () => {
591597 expect ( output . includes ( 'PROGRESS' ) ) . toBe ( showsProgress ) ;
592598 } ,
593599 ) ;
600+
601+ it ( 'creeps past 90 across streamed deltas and completes only on the terminal event' , ( ) => {
602+ const component = createComponent ( ) ;
603+ component . updateArgs ( { items : [ 'Long streaming work' ] } ) ;
604+ component . markInputComplete ( ) ;
605+ register ( component , 'agent-1' ) ;
606+ component . markStarted ( 'agent-1' ) ;
607+ component . recordToolCall ( { agentId : 'agent-1' , name : 'Read' } ) ;
608+
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+
617+ for ( let index = 0 ; index < 200 ; index += 1 ) {
618+ component . appendModelDelta ( { agentId : 'agent-1' , delta : 'more ' } ) ;
619+ }
620+ const late = displayedPercent ( renderText ( component , 100 ) , 1 ) ;
621+ expect ( late ) . toBeGreaterThan ( 90 ) ;
622+ expect ( late ) . toBeLessThan ( 100 ) ;
623+
624+ component . markCompleted ( 'agent-1' , 'Done' ) ;
625+ expect ( displayedPercent ( renderText ( component , 100 ) , 1 ) ) . toBe ( 100 ) ;
626+ } ) ;
627+
628+ it ( 'keeps mid-work delta creep under the tool-activity stage until a tool call lifts it' , ( ) => {
629+ const component = createComponent ( ) ;
630+ component . updateArgs ( { items : [ 'Chatty work' ] } ) ;
631+ component . markInputComplete ( ) ;
632+ register ( component , 'agent-1' ) ;
633+ component . markStarted ( 'agent-1' ) ;
634+
635+ for ( let index = 0 ; index < 300 ; index += 1 ) {
636+ component . appendModelDelta ( { agentId : 'agent-1' , delta : 'more ' } ) ;
637+ }
638+ const midwork = displayedPercent ( renderText ( component , 100 ) , 1 ) ;
639+ expect ( midwork ) . toBeGreaterThan ( 50 ) ;
640+ expect ( midwork ) . toBeLessThan ( DYNAMIC_WORKFLOW_RENDERING . toolActivityProgress ) ;
641+
642+ component . recordToolCall ( { agentId : 'agent-1' , name : 'Read' } ) ;
643+ expect ( displayedPercent ( renderText ( component , 100 ) , 1 ) )
644+ . toBeGreaterThanOrEqual ( DYNAMIC_WORKFLOW_RENDERING . toolActivityProgress ) ;
645+ } ) ;
646+
647+ it ( 'shimmers Finalizing once every member is terminal but the result has not arrived' , ( ) => {
648+ const component = createComponent ( ) ;
649+ component . updateArgs ( { items : [ 'One' , 'Two' ] } ) ;
650+ component . markInputComplete ( ) ;
651+ component . registerSubagent ( { agentId : 'agent-1' , dynamicWorkflowIndex : 1 } ) ;
652+ component . registerSubagent ( { agentId : 'agent-2' , dynamicWorkflowIndex : 2 } ) ;
653+ component . markStarted ( 'agent-1' ) ;
654+ component . markStarted ( 'agent-2' ) ;
655+ component . markCompleted ( 'agent-1' , 'Done' ) ;
656+
657+ const running = renderText ( component , 100 ) ;
658+ expect ( running ) . toContain ( 'Orchestrating' ) ;
659+ expect ( running ) . not . toContain ( 'Finalizing' ) ;
660+
661+ component . markCompleted ( 'agent-2' , 'Done' ) ;
662+ const finalizing = renderText ( component , 100 ) ;
663+ expect ( finalizing ) . toContain ( 'Finalizing' ) ;
664+ expect ( finalizing ) . not . toContain ( 'Orchestrating' ) ;
665+
666+ component . applyResult ( [
667+ '<dynamic_workflow_result>' ,
668+ '<subagent index="1" outcome="completed">Done</subagent>' ,
669+ '<subagent index="2" outcome="completed">Done</subagent>' ,
670+ '</dynamic_workflow_result>' ,
671+ ] . join ( '\n' ) ) ;
672+ const done = renderText ( component , 100 ) ;
673+ expect ( done ) . toContain ( '✓ Completed' ) ;
674+ expect ( done ) . not . toContain ( 'Finalizing' ) ;
675+ } ) ;
676+
677+ it ( 'keeps Orchestrating while an out-of-band member beyond knownTotal still runs' , ( ) => {
678+ const component = createComponent ( ) ;
679+ component . updateArgs ( { items : [ 'One' , 'Two' ] } ) ;
680+ component . markInputComplete ( ) ;
681+ component . registerSubagent ( { agentId : 'agent-1' , dynamicWorkflowIndex : 1 } ) ;
682+ component . registerSubagent ( { agentId : 'agent-2' , dynamicWorkflowIndex : 2 } ) ;
683+ component . registerSubagent ( { agentId : 'agent-3' , dynamicWorkflowIndex : 3 } ) ;
684+ component . markStarted ( 'agent-3' ) ;
685+ component . markCompleted ( 'agent-1' , 'Done' ) ;
686+ component . markCompleted ( 'agent-2' , 'Done' ) ;
687+
688+ const output = renderText ( component , 100 ) ;
689+ expect ( output ) . toContain ( '● RUN' ) ;
690+ expect ( output ) . toContain ( 'Orchestrating' ) ;
691+ expect ( output ) . not . toContain ( 'Finalizing' ) ;
692+
693+ component . markCompleted ( 'agent-3' , 'Done' ) ;
694+ expect ( renderText ( component , 100 ) ) . toContain ( 'Finalizing' ) ;
695+ } ) ;
696+
697+ it ( 'aligns narrow member rows and the header on the same task column' , ( ) => {
698+ const component = prepareObservedWorkflow ( ) ;
699+ const output = renderText ( component , 50 ) ;
700+ const unframe = ( line : string ) => line . replace ( / ^ │ / u, '' ) ;
701+ const running = unframe ( memberLine ( output , 1 ) ) ;
702+ const completed = unframe ( memberLine ( output , 2 ) ) ;
703+ const headerLine = output . split ( '\n' ) . find ( ( line ) => line . includes ( 'STATE' ) ) ;
704+ if ( headerLine === undefined ) throw new Error ( 'Missing Dynamic Workflow table header' ) ;
705+ const header = unframe ( headerLine ) ;
706+
707+ const taskColumn = running . indexOf ( 'Layout hierarchy' ) ;
708+ expect ( taskColumn ) . toBeGreaterThan ( 0 ) ;
709+ expect ( completed . indexOf ( 'Interaction audit' ) ) . toBe ( taskColumn ) ;
710+ expect ( header . indexOf ( 'TASK' ) ) . toBe ( taskColumn ) ;
711+ } ) ;
594712} ) ;
0 commit comments