Skip to content

Commit b400af6

Browse files
committed
perf(tui): streamline workflow progress rendering
1 parent 287bb23 commit b400af6

2 files changed

Lines changed: 24 additions & 24 deletions

File tree

apps/pythinker-code/src/tui/components/messages/dynamic-workflow-mission-control.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -261,21 +261,14 @@ export class DynamicWorkflowMissionControlComponent implements Component {
261261
this.recordActivity(member.index, 'Started');
262262
}
263263

264-
private markStartedFromActivity(member: DynamicWorkflowMember): void {
265-
if (member.phase !== 'pending' && member.phase !== 'queued') return;
266-
member.phase = 'running';
267-
member.startedAtMs ??= Date.now();
268-
delete member.statusDetail;
269-
this.recordActivity(member.index, 'Started');
270-
}
271264

272265
recordToolCall(input: {
273266
readonly agentId: string;
274267
readonly name?: string;
275268
}): void {
276269
const member = this.findMemberByAgentId(input.agentId);
277270
if (member === undefined || isTerminalPhase(member.phase)) return;
278-
this.markStartedFromActivity(member);
271+
if (member.phase === 'pending' || member.phase === 'queued') this.markStarted(input.agentId);
279272
const latest = input.name === undefined ? 'Using a tool' : `Using ${input.name}`;
280273
this.setLatest(member, latest, true);
281274
// Streamed text that follows starts a new line, never continues this label.
@@ -285,7 +278,7 @@ export class DynamicWorkflowMissionControlComponent implements Component {
285278
appendModelDelta(input: { readonly agentId: string; readonly delta: string }): void {
286279
const member = this.findMemberByAgentId(input.agentId);
287280
if (member === undefined || isTerminalPhase(member.phase) || input.delta.length === 0) return;
288-
this.markStartedFromActivity(member);
281+
if (member.phase === 'pending' || member.phase === 'queued') this.markStarted(input.agentId);
289282
const combined = `${member.carry}${input.delta}`;
290283
// Only the text after the last newline is still being written. A delta that
291284
// ends exactly at a newline leaves nothing pending, so carrying the closed
@@ -604,15 +597,14 @@ export class DynamicWorkflowMissionControlComponent implements Component {
604597
Math.max(0, nowMs - this.model.startedAtMs) / DYNAMIC_WORKFLOW_PROGRESS_FRAME_MS,
605598
);
606599
const showProgress = width >= DYNAMIC_WORKFLOW_RENDERING.memberProgressMinWidth;
607-
const progressColumn = centerToWidth(
608-
renderProgressGlyph(member.phase, frame),
609-
DYNAMIC_WORKFLOW_RENDERING.memberProgressWidth,
610-
);
611-
const stateColumn = padToWidth(renderStateLabel(member.phase), STATE_COLUMN_WIDTH);
612-
const compactStatus = padToWidth(renderCompactStatus(member.phase, frame), STATE_COLUMN_WIDTH);
613600
const prefix = showProgress
614-
? `${id} ${progressColumn} ${stateColumn} `
615-
: `${id} ${compactStatus} `;
601+
? `${id} ${
602+
centerToWidth(
603+
renderProgressGlyph(member.phase, frame),
604+
DYNAMIC_WORKFLOW_RENDERING.memberProgressWidth,
605+
)
606+
} ${padToWidth(renderStateLabel(member.phase), STATE_COLUMN_WIDTH)} `
607+
: `${id} ${padToWidth(renderCompactStatus(member.phase, frame), STATE_COLUMN_WIDTH)} `;
616608
const task = member.item || 'Delegated agent';
617609
// The elision is display-only: the dedup below still compares whole items,
618610
// so a streamed line that merely repeats the task is still suppressed.

apps/pythinker-code/test/tui/components/messages/dynamic-workflow-mission-control.test.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -866,17 +866,25 @@ describe('DynamicWorkflowMissionControlComponent', () => {
866866
expect(output).not.toContain('001 +1s Agent spawned');
867867
});
868868

869-
it.each([20, 40, 63, 64, 79, 80, 100])(
869+
it.each([
870+
[20, false, false],
871+
[40, false, true],
872+
[63, false, true],
873+
[64, true, false],
874+
[79, true, false],
875+
[80, true, false],
876+
[100, true, false],
877+
] as const)(
870878
'keeps progress and task columns aligned at width %i',
871-
(width) => {
879+
(width, expectedProgress, expectedStatus) => {
872880
const component = prepareObservedWorkflow();
873881
const rendered = component.render(width);
874882
const output = strip(rendered.join('\n'));
875883

876884
expect(rendered.every((line) => visibleWidth(line) <= width)).toBe(true);
877885
expect(memberLine(output, 1)).toMatch(/[]\s+RUN/u);
878-
expect(output.includes('PROGRESS')).toBe(width >= 64);
879-
expect(output.includes('STATUS')).toBe(width >= 21 && width < 64);
886+
expect(output.includes('PROGRESS')).toBe(expectedProgress);
887+
expect(output.includes('STATUS')).toBe(expectedStatus);
880888
expect(output).not.toContain('WORK IDLE');
881889
},
882890
);
@@ -892,16 +900,16 @@ describe('DynamicWorkflowMissionControlComponent', () => {
892900

893901
vi.setSystemTime(30_000);
894902
const before = memberLine(renderText(component, 100), 1).match(/[]/u)?.[0];
895-
if (before === undefined) throw new Error('Missing lifecycle progress glyph');
896903
component.recordToolCall({ agentId: 'agent-1', name: 'Read' });
897904
for (let index = 0; index < 200; index += 1) {
898905
component.appendModelDelta({ agentId: 'agent-1', delta: `chunk ${String(index)} ` });
899906
}
900907
component.recordToolCall({ agentId: 'agent-1', name: 'Bash' });
901908

902909
const output = renderText(component, 100);
903-
expect(before).toMatch(/[]/u);
904-
expect(memberLine(output, 1)).toContain(before);
910+
const after = memberLine(output, 1).match(/[]/u)?.[0];
911+
expect(before).toBeDefined();
912+
expect(after).toBe(before);
905913
expect(output).not.toMatch(/\b\d+%|/u);
906914
});
907915

0 commit comments

Comments
 (0)