Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -1742,7 +1742,10 @@ def _load_show_reasoning() -> bool:
if cfg_path.exists():
with open(cfg_path, encoding="utf-8") as _f:
cfg = _y.safe_load(_f) or {}
return bool(cfg_get(cfg, "display", "show_reasoning", default=False))
return is_truthy_value(
cfg_get(cfg, "display", "show_reasoning"),
default=False,
)
except Exception:
pass
return False
Expand Down Expand Up @@ -8351,7 +8354,10 @@ async def _handle_verbose_command(self, event: MessageEvent) -> str:
# --- check config gate ------------------------------------------------
try:
user_config = _load_gateway_config()
gate_enabled = cfg_get(user_config, "display", "tool_progress_command", default=False)
gate_enabled = is_truthy_value(
cfg_get(user_config, "display", "tool_progress_command"),
default=False,
)
except Exception:
gate_enabled = False

Expand Down Expand Up @@ -11298,7 +11304,10 @@ def progress_callback(event_type: str, tool_name: str = None, preview: str = Non
tool_progress_hint_gateway,
)
_cfg = _load_gateway_config()
gate_on = bool(cfg_get(_cfg, "display", "tool_progress_command", default=False))
gate_on = is_truthy_value(
cfg_get(_cfg, "display", "tool_progress_command"),
default=False,
)
if gate_on and not is_seen(_cfg, TOOL_PROGRESS_FLAG):
long_tool_hint_fired[0] = True
progress_queue.put(tool_progress_hint_gateway())
Expand Down
4 changes: 3 additions & 1 deletion hermes_cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from dataclasses import dataclass
from typing import Any

from utils import is_truthy_value

# prompt_toolkit is an optional CLI dependency — only needed for
# SlashCommandCompleter and SlashCommandAutoSuggest. Gateway and test
# environments that lack it must still be able to import this module
Expand Down Expand Up @@ -371,7 +373,7 @@ def _resolve_config_gates() -> set[str]:
else:
val = None
break
if val:
if is_truthy_value(val, default=False):
result.add(cmd.name)
return result

Expand Down
41 changes: 41 additions & 0 deletions tests/gateway/test_reasoning_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,44 @@ def test_run_agent_homeassistant_uses_default_platform_toolset(self, tmp_path, m
assert result["final_response"] == "ok"
assert _CapturingAgent.last_init is not None
assert "homeassistant" in set(_CapturingAgent.last_init["enabled_toolsets"])


class TestLoadShowReasoningCoercion:
"""Regression: display.show_reasoning must be coerced, not bool()'d."""

def _load_with_config(self, tmp_path, monkeypatch, yaml_body: str) -> bool:
hermes_home = tmp_path / "hermes"
hermes_home.mkdir()
(hermes_home / "config.yaml").write_text(yaml_body, encoding="utf-8")
monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home)
return gateway_run.GatewayRunner._load_show_reasoning()

def test_quoted_false_is_false(self, tmp_path, monkeypatch):
assert self._load_with_config(
tmp_path, monkeypatch,
'display:\n show_reasoning: "false"\n',
) is False

def test_quoted_off_is_false(self, tmp_path, monkeypatch):
assert self._load_with_config(
tmp_path, monkeypatch,
'display:\n show_reasoning: "off"\n',
) is False

def test_quoted_true_is_true(self, tmp_path, monkeypatch):
assert self._load_with_config(
tmp_path, monkeypatch,
'display:\n show_reasoning: "true"\n',
) is True

def test_bare_true_is_true(self, tmp_path, monkeypatch):
assert self._load_with_config(
tmp_path, monkeypatch,
'display:\n show_reasoning: true\n',
) is True

def test_missing_is_false(self, tmp_path, monkeypatch):
assert self._load_with_config(
tmp_path, monkeypatch,
'display: {}\n',
) is False
19 changes: 19 additions & 0 deletions tests/gateway/test_verbose_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,25 @@ async def test_enabled_cycles_mode(self, tmp_path, monkeypatch):
saved = yaml.safe_load(config_path.read_text(encoding="utf-8"))
assert saved["display"]["platforms"]["telegram"]["tool_progress"] == "verbose"

@pytest.mark.asyncio
async def test_quoted_false_keeps_command_disabled(self, tmp_path, monkeypatch):
"""Quoted false must not enable the /verbose gateway command."""
hermes_home = tmp_path / "hermes"
hermes_home.mkdir()
config_path = hermes_home / "config.yaml"
config_path.write_text(
'display:\n tool_progress_command: "false"\n tool_progress: all\n',
encoding="utf-8",
)

monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home)

runner = _make_runner()
result = await runner._handle_verbose_command(_make_event())

assert "not enabled" in result.lower()
assert "tool_progress_command" in result

@pytest.mark.asyncio
async def test_cycles_through_all_modes(self, tmp_path, monkeypatch):
"""Calling /verbose repeatedly cycles through all four modes."""
Expand Down
15 changes: 15 additions & 0 deletions tests/hermes_cli/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,21 @@ def test_config_gate_included_in_help_when_on(self, tmp_path, monkeypatch):
joined = "\n".join(lines)
assert "`/verbose" in joined

def test_config_gate_quoted_false_stays_disabled_everywhere(self, tmp_path, monkeypatch):
"""Quoted false must not enable config-gated gateway commands."""
config_file = tmp_path / "config.yaml"
config_file.write_text('display:\n tool_progress_command: "false"\n')
monkeypatch.setenv("HERMES_HOME", str(tmp_path))

lines = gateway_help_lines()
joined = "\n".join(lines)
names = {name for name, _ in telegram_bot_commands()}
mapping = slack_subcommand_map()

assert "`/verbose" not in joined
assert "verbose" not in names
assert "verbose" not in mapping

def test_config_gate_excluded_from_telegram_when_off(self, tmp_path, monkeypatch):
config_file = tmp_path / "config.yaml"
config_file.write_text("display:\n tool_progress_command: false\n")
Expand Down
20 changes: 20 additions & 0 deletions tests/tools/test_skill_manager_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,26 @@ def test_guard_flag_handles_config_error(self):
with patch("hermes_cli.config.load_config", side_effect=RuntimeError("boom")):
assert _guard_agent_created_enabled() is False

def test_guard_flag_quoted_false_stays_disabled(self):
"""Quoted 'false' from YAML edits must not enable the guard."""
from tools.skill_manager_tool import _guard_agent_created_enabled

for quoted in ("false", "False", "0", "no", "off"):
with patch("hermes_cli.config.load_config",
return_value={"skills": {"guard_agent_created": quoted}}):
assert _guard_agent_created_enabled() is False, \
f"guard_agent_created={quoted!r} must coerce to False"

def test_guard_flag_quoted_true_enables(self):
"""Quoted truthy strings must enable the guard."""
from tools.skill_manager_tool import _guard_agent_created_enabled

for quoted in ("true", "True", "1", "yes", "on"):
with patch("hermes_cli.config.load_config",
return_value={"skills": {"guard_agent_created": quoted}}):
assert _guard_agent_created_enabled() is True, \
f"guard_agent_created={quoted!r} must coerce to True"


# ---------------------------------------------------------------------------
# External skills directories (skills.external_dirs) — mutations in place
Expand Down
7 changes: 5 additions & 2 deletions tools/skill_manager_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from hermes_constants import get_hermes_home, display_hermes_home
from typing import Dict, Any, Optional, Tuple

from utils import atomic_replace
from utils import atomic_replace, is_truthy_value
from hermes_cli.config import cfg_get

logger = logging.getLogger(__name__)
Expand All @@ -67,7 +67,10 @@ def _guard_agent_created_enabled() -> bool:
try:
from hermes_cli.config import load_config
cfg = load_config()
return bool(cfg_get(cfg, "skills", "guard_agent_created", default=False))
return is_truthy_value(
cfg_get(cfg, "skills", "guard_agent_created"),
default=False,
)
except Exception:
return False

Expand Down
Loading