mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix(tui): guard personality overlay when personalities is null
TUI auto-resolves `display.personality` at session init, unlike the base CLI.
If config contains `agent.personalities: null`, `_resolve_personality_prompt`
called `.get()` on None and failed before model/provider selection.
Normalize null personalities to `{}` and surface a targeted config warning.
This commit is contained in:
parent
bfa60234c8
commit
e3940f9807
2 changed files with 77 additions and 12 deletions
|
|
@ -67,11 +67,25 @@ def test_probe_config_health_flags_null_sections():
|
||||||
assert "model" not in msg
|
assert "model" not in msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_probe_config_health_flags_null_personalities_with_active_personality():
|
||||||
|
from tui_gateway.server import _probe_config_health
|
||||||
|
|
||||||
|
msg = _probe_config_health(
|
||||||
|
{
|
||||||
|
"agent": {"personalities": None},
|
||||||
|
"display": {"personality": "kawaii"},
|
||||||
|
"model": {},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert "display.personality" in msg
|
||||||
|
assert "agent.personalities" in msg
|
||||||
|
|
||||||
|
|
||||||
def test_make_agent_tolerates_null_config_sections():
|
def test_make_agent_tolerates_null_config_sections():
|
||||||
"""Bare `agent:` / `display:` keys in ~/.hermes/config.yaml parse as
|
"""Bare `agent:` / `display:` keys in ~/.hermes/config.yaml parse as
|
||||||
None. cfg.get("agent", {}) returns None (default only fires on missing
|
None. cfg.get("agent", {}) returns None (default only fires on missing
|
||||||
key), so downstream .get() chains must be guarded. Reported via Twitter
|
key), so downstream .get() chains must be guarded. Reported via Twitter
|
||||||
against the new TUI; CLI path is unaffected."""
|
against the new TUI."""
|
||||||
|
|
||||||
fake_runtime = {
|
fake_runtime = {
|
||||||
"provider": "openrouter",
|
"provider": "openrouter",
|
||||||
|
|
@ -99,3 +113,37 @@ def test_make_agent_tolerates_null_config_sections():
|
||||||
_make_agent("sid-null", "key-null")
|
_make_agent("sid-null", "key-null")
|
||||||
|
|
||||||
assert mock_agent.called
|
assert mock_agent.called
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_agent_tolerates_null_personalities_with_active_personality():
|
||||||
|
fake_runtime = {
|
||||||
|
"provider": "openrouter",
|
||||||
|
"base_url": "https://api.synthetic.new/v1",
|
||||||
|
"api_key": "sk-test",
|
||||||
|
"api_mode": "chat_completions",
|
||||||
|
"command": None,
|
||||||
|
"args": None,
|
||||||
|
"credential_pool": None,
|
||||||
|
}
|
||||||
|
cfg = {
|
||||||
|
"agent": {"personalities": None},
|
||||||
|
"display": {"personality": "kawaii"},
|
||||||
|
"model": {"default": "glm-5"},
|
||||||
|
}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("tui_gateway.server._load_cfg", return_value=cfg),
|
||||||
|
patch("tui_gateway.server._get_db", return_value=MagicMock()),
|
||||||
|
patch("cli.load_cli_config", return_value={"agent": {"personalities": None}}),
|
||||||
|
patch(
|
||||||
|
"hermes_cli.runtime_provider.resolve_runtime_provider",
|
||||||
|
return_value=fake_runtime,
|
||||||
|
),
|
||||||
|
patch("run_agent.AIAgent") as mock_agent,
|
||||||
|
):
|
||||||
|
from tui_gateway.server import _make_agent
|
||||||
|
|
||||||
|
_make_agent("sid-null-personality", "key-null-personality")
|
||||||
|
|
||||||
|
assert mock_agent.called
|
||||||
|
assert mock_agent.call_args.kwargs["ephemeral_system_prompt"] is None
|
||||||
|
|
|
||||||
|
|
@ -829,15 +829,32 @@ def _probe_config_health(cfg: dict) -> str:
|
||||||
drop nested settings. Returns warning or ''."""
|
drop nested settings. Returns warning or ''."""
|
||||||
if not isinstance(cfg, dict):
|
if not isinstance(cfg, dict):
|
||||||
return ""
|
return ""
|
||||||
|
warnings: list[str] = []
|
||||||
null_keys = sorted(k for k, v in cfg.items() if v is None)
|
null_keys = sorted(k for k, v in cfg.items() if v is None)
|
||||||
if not null_keys:
|
if not null_keys:
|
||||||
return ""
|
pass
|
||||||
keys = ", ".join(f"`{k}`" for k in null_keys)
|
else:
|
||||||
return (
|
keys = ", ".join(f"`{k}`" for k in null_keys)
|
||||||
f"config.yaml has empty section(s): {keys}. "
|
warnings.append(
|
||||||
f"Remove the line(s) or set them to `{{}}` — "
|
f"config.yaml has empty section(s): {keys}. "
|
||||||
f"empty sections silently drop nested settings."
|
f"Remove the line(s) or set them to `{{}}` — "
|
||||||
)
|
f"empty sections silently drop nested settings."
|
||||||
|
)
|
||||||
|
display_cfg = cfg.get("display")
|
||||||
|
agent_cfg = cfg.get("agent")
|
||||||
|
if isinstance(display_cfg, dict):
|
||||||
|
personality = str(display_cfg.get("personality", "") or "").strip().lower()
|
||||||
|
if (
|
||||||
|
personality
|
||||||
|
and personality not in {"default", "none", "neutral"}
|
||||||
|
and isinstance(agent_cfg, dict)
|
||||||
|
and agent_cfg.get("personalities") is None
|
||||||
|
):
|
||||||
|
warnings.append(
|
||||||
|
"`display.personality` is set but `agent.personalities` is empty/null; "
|
||||||
|
"personality overlay will be skipped."
|
||||||
|
)
|
||||||
|
return " ".join(warnings).strip()
|
||||||
|
|
||||||
|
|
||||||
def _session_info(agent) -> dict:
|
def _session_info(agent) -> dict:
|
||||||
|
|
@ -1134,16 +1151,16 @@ def _resolve_personality_prompt(cfg: dict) -> str:
|
||||||
try:
|
try:
|
||||||
from cli import load_cli_config
|
from cli import load_cli_config
|
||||||
|
|
||||||
personalities = (load_cli_config().get("agent") or {}).get("personalities", {})
|
personalities = (load_cli_config().get("agent") or {}).get("personalities", {}) or {}
|
||||||
except Exception:
|
except Exception:
|
||||||
try:
|
try:
|
||||||
from hermes_cli.config import load_config as _load_full_cfg
|
from hermes_cli.config import load_config as _load_full_cfg
|
||||||
|
|
||||||
personalities = (_load_full_cfg().get("agent") or {}).get(
|
personalities = (
|
||||||
"personalities", {}
|
(_load_full_cfg().get("agent") or {}).get("personalities", {}) or {}
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
personalities = (cfg.get("agent") or {}).get("personalities", {})
|
personalities = (cfg.get("agent") or {}).get("personalities", {}) or {}
|
||||||
pval = personalities.get(name)
|
pval = personalities.get(name)
|
||||||
if pval is None:
|
if pval is None:
|
||||||
return ""
|
return ""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue