154 lines
4.7 KiB
TypeScript
154 lines
4.7 KiB
TypeScript
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<ParsedMessage> & Pick<ParsedMessage, 'type' | 'content'>): 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',
|
|
}),
|
|
}),
|
|
])
|
|
);
|
|
});
|
|
});
|