feat: improve agent status and thinking indicator UX

- Show agent activity label in chat header (Thinking…, Using bash…, etc.)
  instead of an anonymous pulsing dot
- Fix ThinkingProcess elapsed timer to tick live every second using
  useElapsedTimer hook instead of stale useMemo computation
- Show completed/failed subagents for 3s grace period with fade-out
  transition instead of instantly hiding them
- Transition activity labels to Completed state for 1.5s grace period
  on session idle instead of abrupt removal
- Eliminate message flash during thinking reclassification by folding
  trailing pending assistant messages into the thinking group
  optimistically when they follow existing thinking messages
- Stabilize ThinkingProcess isActive flag by checking for pending
  messages in the group rather than relying solely on array position
- Skip rendering empty pending messages inside ThinkingProcess steps

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-07 08:44:18 +02:00
co-authored by Copilot
parent 4c3198a550
commit e79cbe8df9
7 changed files with 366 additions and 41 deletions
+100
View File
@@ -13,6 +13,8 @@ import {
isAgentActivityCompleted,
pruneSessionActivities,
pruneSessionRequestUsage,
purgeCompletedActivity,
summarizeSessionActivity,
type SessionActivityMap,
type SessionRequestUsageMap,
} from '@renderer/lib/sessionActivity';
@@ -202,6 +204,7 @@ describe('session activity helpers', () => {
},
};
// Active agents transition to 'completed' on idle (grace period before purge)
expect(
applySessionEventActivity(current, {
sessionId: 'session-1',
@@ -216,6 +219,11 @@ describe('session activity helpers', () => {
agentName: 'Architect',
activityType: 'completed',
},
reviewer: {
agentId: 'reviewer',
agentName: 'Reviewer',
activityType: 'completed',
},
},
});
});
@@ -236,6 +244,7 @@ describe('session activity helpers', () => {
},
};
// Active agents transition to 'completed' on cancel (grace period before purge)
expect(
applySessionEventActivity(current, {
sessionId: 'session-1',
@@ -265,6 +274,11 @@ describe('session activity helpers', () => {
agentName: 'Architect',
activityType: 'completed',
},
reviewer: {
agentId: 'reviewer',
agentName: 'Reviewer',
activityType: 'completed',
},
},
});
});
@@ -385,6 +399,92 @@ describe('session activity helpers', () => {
'session-2': current['session-2'],
});
});
test('purgeCompletedActivity removes completed entries after grace period', () => {
const current: SessionActivityMap = {
'session-1': {
architect: {
agentId: 'architect',
agentName: 'Architect',
activityType: 'completed',
},
reviewer: {
agentId: 'reviewer',
agentName: 'Reviewer',
activityType: 'completed',
},
},
};
const result = purgeCompletedActivity(current, 'session-1');
expect(result['session-1']).toBeUndefined();
});
test('purgeCompletedActivity is a no-op when nothing is completed', () => {
const current: SessionActivityMap = {
'session-1': {
architect: {
agentId: 'architect',
agentName: 'Architect',
activityType: 'thinking',
},
},
};
expect(purgeCompletedActivity(current, 'session-1')).toBe(current);
});
test('summarizeSessionActivity returns the most relevant label', () => {
expect(summarizeSessionActivity(undefined)).toBeUndefined();
expect(summarizeSessionActivity({})).toBeUndefined();
expect(
summarizeSessionActivity({
architect: {
agentId: 'architect',
agentName: 'Architect',
activityType: 'thinking',
},
}),
).toBe('Thinking…');
expect(
summarizeSessionActivity({
architect: {
agentId: 'architect',
agentName: 'Architect',
activityType: 'tool-calling',
toolName: 'bash',
},
}),
).toBe('Using bash…');
// Thinking takes priority over tool-calling
expect(
summarizeSessionActivity({
architect: {
agentId: 'architect',
agentName: 'Architect',
activityType: 'thinking',
},
reviewer: {
agentId: 'reviewer',
agentName: 'Reviewer',
activityType: 'tool-calling',
toolName: 'read_file',
},
}),
).toBe('Thinking…');
expect(
summarizeSessionActivity({
architect: {
agentId: 'architect',
agentName: 'Architect',
activityType: 'completed',
},
}),
).toBe('Completed');
});
});
describe('assistant usage accumulator', () => {