mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
Merge pull request #17638 from NousResearch/bb/tui-details-persist
fix(tui): persist global details mode sections
This commit is contained in:
commit
d9bf093728
4 changed files with 56 additions and 10 deletions
|
|
@ -879,6 +879,36 @@ def test_config_set_statusbar_survives_non_dict_display(tmp_path, monkeypatch):
|
||||||
assert saved["display"]["tui_statusbar"] == "bottom"
|
assert saved["display"]["tui_statusbar"] == "bottom"
|
||||||
|
|
||||||
|
|
||||||
|
def test_config_set_details_mode_pins_all_sections(tmp_path, monkeypatch):
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
cfg_path = tmp_path / "config.yaml"
|
||||||
|
cfg_path.write_text(
|
||||||
|
yaml.safe_dump(
|
||||||
|
{"display": {"sections": {"tools": "expanded", "activity": "hidden"}}}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(server, "_hermes_home", tmp_path)
|
||||||
|
|
||||||
|
resp = server.handle_request(
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"method": "config.set",
|
||||||
|
"params": {"key": "details_mode", "value": "collapsed"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp["result"] == {"key": "details_mode", "value": "collapsed"}
|
||||||
|
saved = yaml.safe_load(cfg_path.read_text())
|
||||||
|
assert saved["display"]["details_mode"] == "collapsed"
|
||||||
|
assert saved["display"]["sections"] == {
|
||||||
|
"thinking": "collapsed",
|
||||||
|
"tools": "collapsed",
|
||||||
|
"subagents": "collapsed",
|
||||||
|
"activity": "collapsed",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_config_set_section_writes_per_section_override(tmp_path, monkeypatch):
|
def test_config_set_section_writes_per_section_override(tmp_path, monkeypatch):
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,8 @@ _cfg_path = None
|
||||||
_SLASH_WORKER_TIMEOUT_S = max(
|
_SLASH_WORKER_TIMEOUT_S = max(
|
||||||
5.0, float(os.environ.get("HERMES_TUI_SLASH_TIMEOUT_S", "45") or 45)
|
5.0, float(os.environ.get("HERMES_TUI_SLASH_TIMEOUT_S", "45") or 45)
|
||||||
)
|
)
|
||||||
|
_DETAIL_SECTION_NAMES = ("thinking", "tools", "subagents", "activity")
|
||||||
|
_DETAIL_MODES = frozenset({"hidden", "collapsed", "expanded"})
|
||||||
|
|
||||||
# ── Async RPC dispatch (#12546) ──────────────────────────────────────
|
# ── Async RPC dispatch (#12546) ──────────────────────────────────────
|
||||||
# A handful of handlers block the dispatcher loop in entry.py for seconds
|
# A handful of handlers block the dispatcher loop in entry.py for seconds
|
||||||
|
|
@ -3149,19 +3151,26 @@ def _(rid, params: dict) -> dict:
|
||||||
|
|
||||||
if key == "details_mode":
|
if key == "details_mode":
|
||||||
nv = str(value or "").strip().lower()
|
nv = str(value or "").strip().lower()
|
||||||
allowed_dm = frozenset({"hidden", "collapsed", "expanded"})
|
if nv not in _DETAIL_MODES:
|
||||||
if nv not in allowed_dm:
|
|
||||||
return _err(rid, 4002, f"unknown details_mode: {value}")
|
return _err(rid, 4002, f"unknown details_mode: {value}")
|
||||||
_write_config_key("display.details_mode", nv)
|
cfg = _load_cfg()
|
||||||
|
display = cfg.get("display") if isinstance(cfg.get("display"), dict) else {}
|
||||||
|
sections = display.get("sections") if isinstance(display.get("sections"), dict) else {}
|
||||||
|
display["details_mode"] = nv
|
||||||
|
for section in _DETAIL_SECTION_NAMES:
|
||||||
|
sections[section] = nv
|
||||||
|
display["sections"] = sections
|
||||||
|
cfg["display"] = display
|
||||||
|
_save_cfg(cfg)
|
||||||
return _ok(rid, {"key": key, "value": nv})
|
return _ok(rid, {"key": key, "value": nv})
|
||||||
|
|
||||||
if key.startswith("details_mode."):
|
if key.startswith("details_mode."):
|
||||||
# Per-section override: `details_mode.<section>` writes to
|
# Per-section override: `details_mode.<section>` writes to
|
||||||
# `display.sections.<section>`. Empty value clears the override
|
# `display.sections.<section>`. Empty value clears the explicit
|
||||||
# and lets the section fall back to the global details_mode.
|
# override and lets frontend resolution apply built-in section defaults
|
||||||
|
# before the global details_mode.
|
||||||
section = key.split(".", 1)[1]
|
section = key.split(".", 1)[1]
|
||||||
allowed_sections = frozenset({"thinking", "tools", "subagents", "activity"})
|
if section not in _DETAIL_SECTION_NAMES:
|
||||||
if section not in allowed_sections:
|
|
||||||
return _err(rid, 4002, f"unknown section: {section}")
|
return _err(rid, 4002, f"unknown section: {section}")
|
||||||
|
|
||||||
cfg = _load_cfg()
|
cfg = _load_cfg()
|
||||||
|
|
@ -3178,8 +3187,7 @@ def _(rid, params: dict) -> dict:
|
||||||
_save_cfg(cfg)
|
_save_cfg(cfg)
|
||||||
return _ok(rid, {"key": key, "value": ""})
|
return _ok(rid, {"key": key, "value": ""})
|
||||||
|
|
||||||
allowed_dm = frozenset({"hidden", "collapsed", "expanded"})
|
if nv not in _DETAIL_MODES:
|
||||||
if nv not in allowed_dm:
|
|
||||||
return _err(rid, 4002, f"unknown details_mode: {value}")
|
return _err(rid, 4002, f"unknown details_mode: {value}")
|
||||||
|
|
||||||
sections_cfg[section] = nv
|
sections_cfg[section] = nv
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,12 @@ describe('createSlashHandler', () => {
|
||||||
expect(createSlashHandler(ctx)('/details toggle')).toBe(true)
|
expect(createSlashHandler(ctx)('/details toggle')).toBe(true)
|
||||||
expect(getUiState().detailsMode).toBe('expanded')
|
expect(getUiState().detailsMode).toBe('expanded')
|
||||||
expect(getUiState().detailsModeCommandOverride).toBe(true)
|
expect(getUiState().detailsModeCommandOverride).toBe(true)
|
||||||
|
expect(getUiState().sections).toEqual({
|
||||||
|
thinking: 'expanded',
|
||||||
|
tools: 'expanded',
|
||||||
|
subagents: 'expanded',
|
||||||
|
activity: 'expanded'
|
||||||
|
})
|
||||||
expect(ctx.gateway.rpc).toHaveBeenCalledWith('config.set', {
|
expect(ctx.gateway.rpc).toHaveBeenCalledWith('config.set', {
|
||||||
key: 'details_mode',
|
key: 'details_mode',
|
||||||
value: 'expanded'
|
value: 'expanded'
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,9 @@ export const coreCommands: SlashCommand[] = [
|
||||||
return transcript.sys(DETAILS_USAGE)
|
return transcript.sys(DETAILS_USAGE)
|
||||||
}
|
}
|
||||||
|
|
||||||
patchUiState({ detailsMode: next, detailsModeCommandOverride: true })
|
const sections = Object.fromEntries(SECTION_NAMES.map(section => [section, next]))
|
||||||
|
|
||||||
|
patchUiState({ detailsMode: next, detailsModeCommandOverride: true, sections })
|
||||||
gateway.rpc<ConfigSetResponse>('config.set', { key: 'details_mode', value: next }).catch(() => {})
|
gateway.rpc<ConfigSetResponse>('config.set', { key: 'details_mode', value: next }).catch(() => {})
|
||||||
transcript.sys(`details: ${next}`)
|
transcript.sys(`details: ${next}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue