mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-07 02:51:50 +00:00
feat(tui): per-section visibility for the details accordion
Adds optional per-section overrides on top of the existing global
details_mode (hidden | collapsed | expanded). Lets users keep the
accordion collapsed by default while auto-expanding tools, or hide the
activity panel entirely without touching thinking/tools/subagents.
Config (~/.hermes/config.yaml):
display:
details_mode: collapsed
sections:
thinking: expanded
tools: expanded
activity: hidden
Slash command:
/details show current global + overrides
/details [hidden|collapsed|expanded] set global mode (existing)
/details <section> <mode|reset> per-section override (new)
/details <section> reset clear override
Sections: thinking, tools, subagents, activity.
Implementation:
- ui-tui/src/types.ts SectionName + SectionVisibility
- ui-tui/src/domain/details.ts parseSectionMode / resolveSections /
sectionMode + SECTION_NAMES
- ui-tui/src/app/uiStore.ts +
app/interfaces.ts +
app/useConfigSync.ts sections threaded into UiState
- ui-tui/src/components/
thinking.tsx ToolTrail consults per-section mode for
hidden/expanded behaviour; expandAll
skips hidden sections; floating-alert
fallback respects activity:hidden
- ui-tui/src/components/
messageLine.tsx + appLayout.tsx pass sections through render tree
- ui-tui/src/app/slash/
commands/core.ts /details <section> <mode|reset> syntax
- tui_gateway/server.py config.set details_mode.<section>
writes to display.sections.<section>
(empty value clears the override)
- website/docs/user-guide/tui.md documented
Tests: 14 new (4 domain, 4 useConfigSync, 3 slash, 3 gateway).
Total: 269/269 vitest, all gateway tests pass.
This commit is contained in:
parent
6051fba9dc
commit
78481ac124
16 changed files with 478 additions and 70 deletions
|
|
@ -16,6 +16,7 @@ import type {
|
|||
Msg,
|
||||
PanelSection,
|
||||
SecretReq,
|
||||
SectionVisibility,
|
||||
SessionInfo,
|
||||
SlashCatalog,
|
||||
SubagentProgress,
|
||||
|
|
@ -87,6 +88,7 @@ export interface UiState {
|
|||
detailsMode: DetailsMode
|
||||
info: null | SessionInfo
|
||||
inlineDiffs: boolean
|
||||
sections: SectionVisibility
|
||||
showCost: boolean
|
||||
showReasoning: boolean
|
||||
sid: null | string
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { NO_CONFIRM_DESTRUCTIVE } from '../../../config/env.js'
|
||||
import { dailyFortune, randomFortune } from '../../../content/fortunes.js'
|
||||
import { HOTKEYS } from '../../../content/hotkeys.js'
|
||||
import { nextDetailsMode, parseDetailsMode } from '../../../domain/details.js'
|
||||
import { isSectionName, nextDetailsMode, parseDetailsMode, SECTION_NAMES } from '../../../domain/details.js'
|
||||
import type {
|
||||
ConfigGetValueResponse,
|
||||
ConfigSetResponse,
|
||||
|
|
@ -10,7 +10,7 @@ import type {
|
|||
} from '../../../gatewayTypes.js'
|
||||
import { writeOsc52Clipboard } from '../../../lib/osc52.js'
|
||||
import { configureDetectedTerminalKeybindings, configureTerminalKeybindings } from '../../../lib/terminalSetup.js'
|
||||
import type { DetailsMode, Msg, PanelSection } from '../../../types.js'
|
||||
import type { DetailsMode, Msg, PanelSection, SectionName } from '../../../types.js'
|
||||
import type { StatusBarMode } from '../../interfaces.js'
|
||||
import { patchOverlayState } from '../../overlayStore.js'
|
||||
import { patchUiState } from '../../uiStore.js'
|
||||
|
|
@ -57,7 +57,8 @@ export const coreCommands: SlashCommand[] = [
|
|||
sections.push(
|
||||
{
|
||||
rows: [
|
||||
['/details [hidden|collapsed|expanded|cycle]', 'set agent detail visibility mode'],
|
||||
['/details [hidden|collapsed|expanded|cycle]', 'set global agent detail visibility mode'],
|
||||
['/details <section> [hidden|collapsed|expanded|reset]', 'override one section (thinking/tools/subagents/activity)'],
|
||||
['/fortune [random|daily]', 'show a random or daily local fortune']
|
||||
],
|
||||
title: 'TUI'
|
||||
|
|
@ -140,7 +141,7 @@ export const coreCommands: SlashCommand[] = [
|
|||
|
||||
{
|
||||
aliases: ['detail'],
|
||||
help: 'control agent detail visibility',
|
||||
help: 'control agent detail visibility (global or per-section)',
|
||||
name: 'details',
|
||||
run: (arg, ctx) => {
|
||||
const { gateway, transcript, ui } = ctx
|
||||
|
|
@ -154,9 +155,14 @@ export const coreCommands: SlashCommand[] = [
|
|||
}
|
||||
|
||||
const mode = parseDetailsMode(r?.value) ?? ui.detailsMode
|
||||
|
||||
patchUiState({ detailsMode: mode })
|
||||
transcript.sys(`details: ${mode}`)
|
||||
|
||||
const overrides = SECTION_NAMES
|
||||
.filter(s => ui.sections[s])
|
||||
.map(s => `${s}=${ui.sections[s]}`)
|
||||
.join(' ')
|
||||
|
||||
transcript.sys(`details: ${mode}${overrides ? ` (${overrides})` : ''}`)
|
||||
})
|
||||
.catch(() => {
|
||||
if (!ctx.stale()) {
|
||||
|
|
@ -167,10 +173,46 @@ export const coreCommands: SlashCommand[] = [
|
|||
return
|
||||
}
|
||||
|
||||
const mode = arg.trim().toLowerCase()
|
||||
const tokens = arg.trim().toLowerCase().split(/\s+/)
|
||||
|
||||
// Per-section override: `/details <section> <mode>`
|
||||
if (tokens.length >= 2 && isSectionName(tokens[0])) {
|
||||
const section = tokens[0] as SectionName
|
||||
const action = tokens[1] ?? ''
|
||||
|
||||
if (action === 'reset' || action === 'clear' || action === 'default') {
|
||||
const { [section]: _drop, ...rest } = ui.sections
|
||||
patchUiState({ sections: rest })
|
||||
gateway
|
||||
.rpc<ConfigSetResponse>('config.set', { key: `details_mode.${section}`, value: '' })
|
||||
.catch(() => {})
|
||||
transcript.sys(`details ${section}: reset`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const sectionMode = parseDetailsMode(action)
|
||||
|
||||
if (!sectionMode) {
|
||||
return transcript.sys('usage: /details <section> [hidden|collapsed|expanded|reset]')
|
||||
}
|
||||
|
||||
patchUiState({ sections: { ...ui.sections, [section]: sectionMode } })
|
||||
gateway
|
||||
.rpc<ConfigSetResponse>('config.set', { key: `details_mode.${section}`, value: sectionMode })
|
||||
.catch(() => {})
|
||||
transcript.sys(`details ${section}: ${sectionMode}`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Global mode (existing behavior).
|
||||
const mode = tokens[0] ?? ''
|
||||
|
||||
if (!DETAIL_MODES.has(mode)) {
|
||||
return transcript.sys('usage: /details [hidden|collapsed|expanded|cycle]')
|
||||
return transcript.sys(
|
||||
'usage: /details [hidden|collapsed|expanded|cycle] or /details <section> [hidden|collapsed|expanded|reset]'
|
||||
)
|
||||
}
|
||||
|
||||
const next = mode === 'cycle' || mode === 'toggle' ? nextDetailsMode(ui.detailsMode) : (mode as DetailsMode)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const buildUiState = (): UiState => ({
|
|||
detailsMode: 'collapsed',
|
||||
info: null,
|
||||
inlineDiffs: true,
|
||||
sections: {},
|
||||
showCost: false,
|
||||
showReasoning: false,
|
||||
sid: null,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
|
||||
import { resolveDetailsMode } from '../domain/details.js'
|
||||
import { resolveDetailsMode, resolveSections } from '../domain/details.js'
|
||||
import type { GatewayClient } from '../gatewayClient.js'
|
||||
import type {
|
||||
ConfigFullResponse,
|
||||
|
|
@ -46,6 +46,7 @@ export const applyDisplay = (cfg: ConfigFullResponse | null, setBell: (v: boolea
|
|||
compact: !!d.tui_compact,
|
||||
detailsMode: resolveDetailsMode(d),
|
||||
inlineDiffs: d.inline_diffs !== false,
|
||||
sections: resolveSections(d.sections),
|
||||
showCost: !!d.show_cost,
|
||||
showReasoning: !!d.show_reasoning,
|
||||
statusBar: normalizeStatusBar(d.tui_statusbar),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue