From d78c34928fe9fd56c4506861a87b4134be20b448 Mon Sep 17 00:00:00 2001
From: Kshitij <82637225+kshitijk4poor@users.noreply.github.com>
Date: Wed, 6 May 2026 14:13:44 +0530
Subject: [PATCH] feat(tui): collapsible sections in startup banner (skills,
system prompt, MCP)
The TUI SessionPanel banner now uses collapsible \u25b8/\u25be toggle
sections matching the existing Chevron convention used for runtime
agent details. Skills, system prompt, and MCP server lists are
collapsed by default; tools remain expanded as the most actionable
info.
- tui_gateway/server.py: _session_info() now passes agent._cached_system_prompt
through to the TUI frontend
- ui-tui/src/types.ts: added system_prompt?: string to SessionInfo
- ui-tui/src/components/branding.tsx: rewrote SessionPanel with
CollapseToggle helper + per-section useState toggles
Default states: tools=open, skills=collapsed, system=collapsed,
mcp=collapsed. Clicking any \u25b8/\u25be header toggles that section.
---
tui_gateway/server.py | 4 +
ui-tui/src/components/branding.tsx | 219 ++++++++++++++++++++++-------
ui-tui/src/types.ts | 1 +
3 files changed, 177 insertions(+), 47 deletions(-)
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