From 4ec272758cfddf65dcaa8f427f0e974f89adf96f Mon Sep 17 00:00:00 2001 From: Cesar Augusto Fonseca <38327047+cesarafonseca@users.noreply.github.com> Date: Thu, 19 Feb 2026 02:07:30 -0300 Subject: [PATCH] fix: collect tool results from subagent messages with absent isMeta field (#23) User messages in subagent JSONLs lack the isMeta field, defaulting to false. An unconditional `continue` in the !isMeta branch skipped tool result collection for these messages, causing all subagent tools to show "No result received". Now we check for tool_result blocks before continuing, allowing them to fall through to the result collection logic. --- src/renderer/utils/displayItemBuilder.ts | 10 +- .../renderer/utils/displayItemBuilder.test.ts | 99 +++++++++++++++++++ 2 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 test/renderer/utils/displayItemBuilder.test.ts diff --git a/src/renderer/utils/displayItemBuilder.ts b/src/renderer/utils/displayItemBuilder.ts index 953dc645..07b4e786 100644 --- a/src/renderer/utils/displayItemBuilder.ts +++ b/src/renderer/utils/displayItemBuilder.ts @@ -423,16 +423,20 @@ export function buildDisplayItemsFromMessages( } continue; } - // Plain-text user message (subagent input prompt) - if (rawText.trim()) { + // Only treat as subagent input if there are NO tool_result blocks in this message + const hasToolResults = + Array.isArray(msg.content) && + msg.content.some((b) => b.type === 'tool_result'); + if (rawText.trim() && !hasToolResults) { displayItems.push({ type: 'subagent_input', content: rawText.trim(), timestamp: msgTimestamp, tokenCount: estimateTokens(rawText), }); + continue; } - continue; + // Fall through to tool result processing below if message has tool_results } if (msg.type === 'assistant' && Array.isArray(msg.content)) { diff --git a/test/renderer/utils/displayItemBuilder.test.ts b/test/renderer/utils/displayItemBuilder.test.ts new file mode 100644 index 00000000..1a843713 --- /dev/null +++ b/test/renderer/utils/displayItemBuilder.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, it } from 'vitest'; +import { buildDisplayItemsFromMessages } from '../../../src/renderer/utils/displayItemBuilder'; +import type { ParsedMessage } from '../../../src/main/types/messages'; + +/** + * 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'); + }); + }); +});