fix(tui): collapse long system messages in transcript with expand toggle

System messages over 400 chars (system prompt, AGENTS.md, etc.) now
render as a collapsed \u25b8/\u25be toggle line in the transcript, matching
the Chevron convention used for runtime details. The summary shows
the first line + char count; clicking expands to full content.
This commit is contained in:
Kshitij 2026-05-06 14:38:48 +05:30 committed by kshitij
parent d78c34928f
commit 68162eb18f

View file

@ -1,5 +1,5 @@
import { Ansi, Box, NoSelect, Text } from '@hermes/ink'
import { memo } from 'react'
import { memo, useState } from 'react'
import { LONG_MSG } from '../config/limits.js'
import { sectionMode } from '../domain/details.js'
@ -22,6 +22,9 @@ import { StreamingMd } from './streamingMarkdown.js'
import { ToolTrail } from './thinking.js'
import { TodoPanel } from './todoPanel.js'
// Collapse threshold for long system messages (system prompt etc.)
const SYSTEM_COLLAPSE_CHARS = 400
export const MessageLine = memo(function MessageLine({
cols,
compact,
@ -46,6 +49,10 @@ export const MessageLine = memo(function MessageLine({
const activityMode = sectionMode('activity', detailsMode, sections, detailsModeCommandOverride)
const thinking = msg.thinking?.trim() ?? ''
// Collapse toggle for long system messages
const systemIsLong = msg.role === 'system' && msg.text.length > SYSTEM_COLLAPSE_CHARS
const [systemOpen, setSystemOpen] = useState(false)
if (msg.kind === 'trail' && msg.todos?.length) {
return (
<TodoPanel
@ -106,6 +113,27 @@ export const MessageLine = memo(function MessageLine({
return <Text color={t.color.muted}>{msg.text}</Text>
}
// ── Collapsible long system message (system prompt, AGENTS.md, etc.) ──
// MUST come before the hasAnsi check — system messages from the backend
// contain Rich markup escape codes that would otherwise hit <Ansi> full render.
if (systemIsLong) {
const firstLine = (msg.text.split('\n')[0] ?? '').trim().slice(0, 120) || '(system message)'
return (
<Box flexDirection="column">
<Box onClick={() => setSystemOpen(v => !v)}>
<Text color={t.color.accent}>{systemOpen ? '▾ ' : '▸ '}</Text>
<Text color={t.color.muted}>{firstLine}</Text>
<Text color={t.color.muted} dimColor>
{' — '}
{msg.text.length.toLocaleString()} chars
</Text>
</Box>
{systemOpen && <Ansi>{msg.text}</Ansi>}
</Box>
)
}
if (msg.role !== 'user' && hasAnsi(msg.text)) {
return <Ansi>{msg.text}</Ansi>
}