diff --git a/apps/web/src/app/(sandbox)/sessions/[sessionId]/FastSessionTranscript.client.test.tsx b/apps/web/src/app/(sandbox)/sessions/[sessionId]/FastSessionTranscript.client.test.tsx index 5d8930f6d..2b73196cb 100644 --- a/apps/web/src/app/(sandbox)/sessions/[sessionId]/FastSessionTranscript.client.test.tsx +++ b/apps/web/src/app/(sandbox)/sessions/[sessionId]/FastSessionTranscript.client.test.tsx @@ -222,6 +222,109 @@ describe('FastSessionTranscript', () => { expect(screen.getAllByText('launch_task')).toHaveLength(1); }); + it('renders persisted Fast Brain entity and truncated query results as YAML', () => { + const entityOutput = JSON.stringify( + { + success: true, + result: { found: true, title: 'Roomote' }, + }, + null, + 2, + ); + const queryOutput = [ + '{', + ' "success": true,', + ' "result": [', + ' { "title": "First result" },', + '... [output truncated: kept 50000 of 72000 chars] ...', + ' { "title": "Last result" }', + ' ]', + '}', + ].join('\n'); + const buildBrainResult = ({ + eventId, + toolName, + args, + output, + truncated, + }: { + eventId: string; + toolName: 'entity' | 'query'; + args: Record; + output: string; + truncated: boolean; + }) => ({ + id: eventId, + eventId, + turnId: eventId, + turnSeq: 1, + ts: toolName === 'entity' ? 1 : 2, + eventType: ACP_ENVELOPE_EVENT_TYPES.ToolResult, + role: 'tool' as const, + contentBlocks: [{ type: 'text' as const, text: output }], + metadata: { visibleInTranscript: true, truncated }, + payload: { + toolCallId: eventId, + title: toolName, + kind: 'tool', + status: 'completed', + isExecute: false, + isMcp: true, + isRoomoteNativeTool: false, + mcpServerName: 'gbrain', + mcpToolName: toolName, + serverName: 'gbrain', + toolName, + command: null, + exitCode: null, + output, + rawInput: { arguments: args }, + }, + source: 'slack', + nativeSessionId: 'opencode-1', + nativeMessageId: null, + createdAt: new Date('2026-01-01T00:00:01.000Z'), + }); + + const { container } = render( + , + ); + + fireEvent.click(screen.getByText('Entity')); + fireEvent.click(screen.getByText('Query')); + + const details = Array.from(container.querySelectorAll('code')).map( + (element) => element.textContent, + ); + const entityDetails = details.find((text) => text?.includes('Roomote')); + expect(entityDetails).toContain('success: true'); + expect(entityDetails).not.toContain('"success"'); + + const queryDetails = details.find((text) => + text?.includes('[output truncated'), + ); + expect(queryDetails).toContain('query: Fast transcript payload shape'); + expect(queryDetails).toContain('output: |-'); + }); + it('renders trusted Fast show_widget results with the shared sandboxed preview', () => { render( | null, ): { code: string; isStructured: boolean } | undefined { if (!text) return undefined; + const hasVisibleToolInput = + visibleToolInput && Object.keys(visibleToolInput).length > 0; + let result: unknown = text; try { - const result = JSON.parse(text) as unknown; - if (!result || typeof result !== 'object') { - return { code: text, isStructured: false }; - } - - const details = - !Array.isArray(result) && - visibleToolInput && - Object.keys(visibleToolInput).length > 0 - ? { ...(result as Record), ...visibleToolInput } - : result; - - return { - code: YAML.stringify(details, { indent: 2, lineWidth: 0 }).trimEnd(), - isStructured: true, - }; + result = JSON.parse(text) as unknown; } catch { - return { code: text, isStructured: false }; + // Keep unparseable output intact below. } + + const details = + result && typeof result === 'object' + ? !Array.isArray(result) && hasVisibleToolInput + ? { ...(result as Record), ...visibleToolInput } + : result + : hasVisibleToolInput + ? { ...visibleToolInput, output: result } + : null; + + if (!details) return { code: text, isStructured: false }; + + return { + code: YAML.stringify(details, { indent: 2, lineWidth: 0 }).trimEnd(), + isStructured: true, + }; } function getVisibleToolInput(