diff --git a/tui_gateway/server.py b/tui_gateway/server.py index ade8d57ebb..b618c5bd56 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -1413,6 +1413,10 @@ def _session_info(agent) -> dict: info["mcp_servers"] = get_mcp_status() except Exception: info["mcp_servers"] = [] + try: + info["system_prompt"] = getattr(agent, "_cached_system_prompt", "") or "" + except Exception: + pass try: from hermes_cli.banner import get_update_result from hermes_cli.config import recommended_update_command diff --git a/ui-tui/src/components/branding.tsx b/ui-tui/src/components/branding.tsx index 84e502aada..b7590f695e 100644 --- a/ui-tui/src/components/branding.tsx +++ b/ui-tui/src/components/branding.tsx @@ -58,6 +58,44 @@ export function Banner({ t }: { t: Theme }) { ) } +// ── Collapsible helpers ────────────────────────────────────────────── + +function CollapseToggle({ + count, + open, + suffix, + t, + title, + onToggle +}: { + count?: number + open: boolean + suffix?: string + t: Theme + title: string + onToggle: () => void +}) { + return ( + + {open ? '▾ ' : '▸ '} + + {title} + + {typeof count === 'number' ? ( + ({count}) + ) : null} + {suffix ? ( + {suffix} + ) : null} + + ) +} + +// ── SessionPanel ───────────────────────────────────────────────────── + +const SKILLS_MAX = 8 +const TOOLSETS_MAX = 8 + export function SessionPanel({ info, sid, t }: SessionPanelProps) { const cols = useStdout().stdout?.columns ?? 100 const heroLines = caduceus(t.color, t.bannerHero || undefined) @@ -67,6 +105,12 @@ export function SessionPanel({ info, sid, t }: SessionPanelProps) { const lineBudget = Math.max(12, w - 2) const strip = (s: string) => (s.endsWith('_tools') ? s.slice(0, -6) : s) + // ── Local collapse state for each section ── + const [toolsOpen, setToolsOpen] = useState(true) + const [skillsOpen, setSkillsOpen] = useState(false) + const [systemOpen, setSystemOpen] = useState(false) + const [mcpOpen, setMcpOpen] = useState(false) + const truncLine = (pfx: string, items: string[]) => { let line = '' let shown = 0 @@ -85,35 +129,89 @@ export function SessionPanel({ info, sid, t }: SessionPanelProps) { return line } - const section = (title: string, data: Record, max = 8, overflowLabel = 'more…') => { - const entries = Object.entries(data).sort() - const shown = entries.slice(0, max) - const overflow = entries.length - max - const skeleton = info.lazy && entries.length === 0 + // ── Collapsible skills section ── + const skillEntries = Object.entries(info.skills).sort() + const skillsTotal = flat(info.skills).length + const skillsCatCount = skillEntries.length + + const skillsBody = () => { + if (info.lazy && skillEntries.length === 0) { + return + } + + const shown = skillEntries.slice(0, SKILLS_MAX) + const overflow = skillEntries.length - SKILLS_MAX return ( - - - Available {title} - - - {skeleton ? ( - - ) : ( - shown.map(([k, vs]) => ( - - {strip(k)}: - {truncLine(strip(k) + ': ', vs)} - - )) - )} - - {overflow > 0 && ( - - (and {overflow} {overflowLabel}) + <> + {shown.map(([k, vs]) => ( + + {strip(k)}: + {truncLine(strip(k) + ': ', vs)} + ))} + {overflow > 0 && ( + (and {overflow} more categories…) )} - + + ) + } + + // ── Collapsible tools section ── + const toolEntries = Object.entries(info.tools).sort() + const toolsTotal = flat(info.tools).length + + const toolsBody = () => { + const shown = toolEntries.slice(0, TOOLSETS_MAX) + const overflow = toolEntries.length - TOOLSETS_MAX + + return ( + <> + {shown.map(([k, vs]) => ( + + {strip(k)}: + {truncLine(strip(k) + ': ', vs)} + + ))} + {overflow > 0 && ( + (and {overflow} more toolsets…) + )} + + ) + } + + // ── Collapsible MCP section ── + const mcpBody = () => ( + <> + {(info.mcp_servers ?? []).map(s => ( + + {` ${s.name} `} + {`[${s.transport}]`} + : + {s.connected ? ( + + {s.tools} tool{s.tools === 1 ? '' : 's'} + + ) : ( + failed + )} + + ))} + + ) + + // ── System prompt body ── + const sysPromptLen = (info.system_prompt ?? '').length + + const systemBody = () => { + if (sysPromptLen === 0) { + return No system prompt loaded. + } + + return ( + + {info.system_prompt} + ) } @@ -151,37 +249,64 @@ export function SessionPanel({ info, sid, t }: SessionPanelProps) { - {section('Tools', info.tools, 8, 'more toolsets…')} - {section('Skills', info.skills)} + {/* ── Tools (expanded by default) ── */} + + setToolsOpen(v => !v)} + open={toolsOpen} + t={t} + title="Available Tools" + /> + {toolsOpen && toolsBody()} + + {/* ── Skills (collapsed by default) ── */} + + setSkillsOpen(v => !v)} + open={skillsOpen} + suffix={skillsCatCount > 0 ? `in ${skillsCatCount} categor${skillsCatCount === 1 ? 'y' : 'ies'}` : undefined} + t={t} + title="Available Skills" + /> + {skillsOpen && skillsBody()} + + + {/* ── System Prompt (collapsed by default) ── */} + {sysPromptLen > 0 && ( + + setSystemOpen(v => !v)} + open={systemOpen} + suffix={`— ${sysPromptLen.toLocaleString()} chars`} + t={t} + title="System Prompt" + /> + {systemOpen && systemBody()} + + )} + + {/* ── MCP Servers (collapsed by default) ── */} {info.mcp_servers && info.mcp_servers.length > 0 && ( - - MCP Servers - - - {info.mcp_servers.map(s => ( - - {` ${s.name} `} - {`[${s.transport}]`} - : - {s.connected ? ( - - {s.tools} tool{s.tools === 1 ? '' : 's'} - - ) : ( - failed - )} - - ))} + setMcpOpen(v => !v)} + open={mcpOpen} + suffix="connected" + t={t} + title="MCP Servers" + /> + {mcpOpen && mcpBody()} )} - {flat(info.tools).length} tools{' · '} - {flat(info.skills).length} skills + {toolsTotal} tools{' · '} + {skillsTotal} skills {info.mcp_servers?.length ? ` · ${info.mcp_servers.length} MCP` : ''} {' · '} /help for commands diff --git a/ui-tui/src/types.ts b/ui-tui/src/types.ts index b3ecc8fbb6..9153cfb297 100644 --- a/ui-tui/src/types.ts +++ b/ui-tui/src/types.ts @@ -150,6 +150,7 @@ export interface SessionInfo { release_date?: string service_tier?: string skills: Record + system_prompt?: string tools: Record update_behind?: number | null update_command?: string