feat: render thinking process UI for intermediate agent messages

Wire message-reclassified sidecar events through the main process to
the renderer, where reclassified messages are filtered out of the main
transcript and collected into a collapsible ThinkingProcess component.

Main process changes:
- Route message-reclassified via dedicated onMessageReclassified callback
- Add applyMessageReclassified handler that sets messageKind and emits
  the session event for the renderer
- Forward assistant-intent and reasoning-delta as turn-scoped events

Renderer changes:
- Split session.messages into visibleMessages and thinkingMessages
- Render ThinkingProcess above the last assistant message
- ThinkingProcess auto-expands during active turns, collapses on finish
- Update messagePhase to skip thinking-kind messages for final detection

Tests:
- 3 new sessionWorkspace tests for reclassification apply/dedup/ignore
- 2 new messagePhase tests for thinking-kind handling
- Updated runTurnPending test fixtures for new callback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-30 16:16:37 +01:00
co-authored by Copilot
parent 56de8b7bd6
commit deb5c96d58
11 changed files with 464 additions and 90 deletions
+55
View File
@@ -85,4 +85,59 @@ describe('assistant message phase', () => {
expect(getAssistantMessagePhase(session, session.messages[0], 0)).toBe('default');
});
test('returns default for thinking-kind messages regardless of other state', () => {
const session = createSession([
{
id: 'msg-1',
role: 'assistant',
authorName: 'Primary Agent',
content: 'Let me search...',
createdAt: '2026-03-23T00:00:00.000Z',
messageKind: 'thinking',
},
{
id: 'msg-2',
role: 'assistant',
authorName: 'Primary Agent',
content: 'Here is the result.',
createdAt: '2026-03-23T00:00:01.000Z',
},
]);
expect(getAssistantMessagePhase(session, session.messages[0], 0)).toBe('default');
expect(getAssistantMessagePhase(session, session.messages[1], 1)).toBe('final');
});
test('skips thinking messages when determining the last completed assistant', () => {
const session = createSession([
{
id: 'msg-1',
role: 'assistant',
authorName: 'Primary Agent',
content: 'Let me search...',
createdAt: '2026-03-23T00:00:00.000Z',
messageKind: 'thinking',
},
{
id: 'msg-2',
role: 'assistant',
authorName: 'Primary Agent',
content: 'More searching...',
createdAt: '2026-03-23T00:00:01.000Z',
messageKind: 'thinking',
},
{
id: 'msg-3',
role: 'assistant',
authorName: 'Primary Agent',
content: 'Final answer.',
createdAt: '2026-03-23T00:00:02.000Z',
},
]);
expect(getAssistantMessagePhase(session, session.messages[0], 0)).toBe('default');
expect(getAssistantMessagePhase(session, session.messages[1], 1)).toBe('default');
expect(getAssistantMessagePhase(session, session.messages[2], 2)).toBe('final');
});
});