import { describe, expect, it } from 'vitest'; import { buildDisplayItems, buildDisplayItemsFromMessages, } from '../../../src/renderer/utils/displayItemBuilder'; import type { ParsedMessage } from '../../../src/main/types/messages'; import type { SemanticStep } from '../../../src/main/types/chunks'; import type { AIGroupLastOutput } from '../../../src/renderer/types/groups'; /** * Helper to create a minimal ParsedMessage for testing. */ function makeMessage(overrides: Partial & Pick): ParsedMessage { return { uuid: `msg-${Math.random().toString(36).slice(2, 8)}`, parentUuid: null, timestamp: new Date('2025-01-01T00:00:00Z'), isMeta: false, isSidechain: false, toolCalls: [], toolResults: [], ...overrides, } as ParsedMessage; } describe('buildDisplayItemsFromMessages', () => { describe('subagent tool results with isMeta=false', () => { it('should collect tool results from user messages without isMeta field', () => { // Simulates real subagent JSONL where user messages with tool_result // blocks have isMeta absent (defaults to false after parsing). const toolUseId = 'toolu_test123'; const assistantMsg = makeMessage({ uuid: 'assistant-1', type: 'assistant', content: [ { type: 'tool_use', id: toolUseId, name: 'Bash', input: { command: 'echo hello' }, }, ], timestamp: new Date('2025-01-01T00:00:00Z'), }); // This is the key scenario: user message with tool_result but isMeta: false // (simulating subagent JSONL where isMeta field is absent) const toolResultMsg = makeMessage({ uuid: 'user-result-1', type: 'user', isMeta: false, content: [ { type: 'tool_result', tool_use_id: toolUseId, content: 'hello\n', is_error: false, }, ], toolResults: [ { toolUseId: toolUseId, content: 'hello\n', isError: false, }, ], timestamp: new Date('2025-01-01T00:00:01Z'), }); const items = buildDisplayItemsFromMessages([assistantMsg, toolResultMsg], []); const toolItems = items.filter((item) => item.type === 'tool'); expect(toolItems).toHaveLength(1); const tool = toolItems[0]; if (tool.type !== 'tool') throw new Error('Expected tool item'); // The critical assertion: result must be present, not orphaned expect(tool.tool.isOrphaned).toBe(false); expect(tool.tool.result).toBeDefined(); expect(tool.tool.result?.content).toBe('hello\n'); expect(tool.tool.name).toBe('Bash'); }); it('should still render subagent_input for plain text user messages without tool results', () => { const userMsg = makeMessage({ uuid: 'user-input-1', type: 'user', isMeta: false, content: 'Please run the tests', toolResults: [], timestamp: new Date('2025-01-01T00:00:00Z'), }); const items = buildDisplayItemsFromMessages([userMsg], []); const inputItems = items.filter((item) => item.type === 'subagent_input'); expect(inputItems).toHaveLength(1); if (inputItems[0].type !== 'subagent_input') throw new Error('Expected subagent_input'); expect(inputItems[0].content).toBe('Please run the tests'); }); }); }); describe('buildDisplayItems', () => { it('keeps the linked tool item when the last output is the paired tool_result', () => { const steps: SemanticStep[] = [ { id: 'tool-1', type: 'tool_call', startTime: new Date('2025-01-01T00:00:00Z'), durationMs: 0, content: { toolName: 'mcp__agent-teams__task_add_comment', toolInput: { text: 'hello' }, }, context: 'main', } as SemanticStep, { id: 'tool-1', type: 'tool_result', startTime: new Date('2025-01-01T00:00:01Z'), durationMs: 0, content: { toolResultContent: 'comment posted', isError: false, }, context: 'main', } as SemanticStep, ]; const lastOutput: AIGroupLastOutput = { type: 'tool_result', toolResult: 'comment posted', isError: false, timestamp: new Date('2025-01-01T00:00:01Z'), }; const items = buildDisplayItems(steps, lastOutput, []); expect(items).toEqual( expect.arrayContaining([ expect.objectContaining({ type: 'tool', tool: expect.objectContaining({ id: 'tool-1', name: 'mcp__agent-teams__task_add_comment', }), }), ]) ); }); });