diff --git a/cli.py b/cli.py index a3c534365a..2c4715d86c 100644 --- a/cli.py +++ b/cli.py @@ -9175,6 +9175,50 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): model = getattr(self, "model", None) or "(unknown)" is_running = bool(getattr(self, "_agent_running", False)) + # Reasoning level (C-02): resolve the effective effort for display. + reasoning_label = None + try: + rc = getattr(agent, "reasoning_config", None) or getattr(self, "reasoning_config", None) + if isinstance(rc, dict): + if rc.get("enabled") is False: + reasoning_label = "off" + elif rc.get("effort"): + reasoning_label = str(rc.get("effort")) + show_r = getattr(self, "show_reasoning", None) + if reasoning_label: + reasoning_label += f" (display: {'on' if show_r else 'off'})" if show_r is not None else "" + except Exception: + reasoning_label = None + + # Approval mode (C-02). + approval_label = None + try: + from tools.approval import _get_approval_mode, is_approval_bypass_active_for_session + approval_label = _get_approval_mode() + try: + if is_approval_bypass_active_for_session(getattr(self, "session_key", "") or ""): + approval_label += " (YOLO bypass active)" + except Exception: + pass + except Exception: + approval_label = None + + # Context window usage (C-02): reuse the status-bar snapshot which + # already computes tokens / max / percent. + ctx_label = None + try: + snap = self._get_status_bar_snapshot() + ctx_tokens = snap.get("context_tokens") or 0 + ctx_max = snap.get("context_length") + ctx_pct = snap.get("context_percent") + if ctx_max: + left = "" + if isinstance(ctx_pct, (int, float)): + left = f"{max(0, 100 - int(ctx_pct))}% left · " + ctx_label = f"{left}{ctx_tokens:,} / {ctx_max:,} tokens used" + except Exception: + ctx_label = None + lines = [ "Hermes CLI Status", "", @@ -9183,8 +9227,14 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): ] if title: lines.append(f"Title: {title}") + lines.append(f"Model: {model} ({provider})") + if reasoning_label: + lines.append(f"Reasoning: {reasoning_label}") + if approval_label: + lines.append(f"Approvals: {approval_label}") + if ctx_label: + lines.append(f"Context: {ctx_label}") lines.extend([ - f"Model: {model} ({provider})", f"Created: {created_at.strftime('%Y-%m-%d %H:%M')}", f"Last Activity: {updated_at.strftime('%Y-%m-%d %H:%M')}", f"Tokens: {total_tokens:,}", diff --git a/tests/cli/test_cli_status_command.py b/tests/cli/test_cli_status_command.py index 84297d7b15..99518e2deb 100644 --- a/tests/cli/test_cli_status_command.py +++ b/tests/cli/test_cli_status_command.py @@ -94,6 +94,30 @@ def test_show_session_status_prints_gateway_style_summary(): assert kwargs.get("markup") is False +def test_show_session_status_includes_reasoning_approvals_context(): + """C-02: /status surfaces reasoning level, approval mode, and context %.""" + cli_obj = _make_cli() + cli_obj.agent = SimpleNamespace(session_total_tokens=1000, session_api_calls=2, + reasoning_config={"enabled": True, "effort": "high"}) + cli_obj.reasoning_config = {"enabled": True, "effort": "high"} + cli_obj.show_reasoning = True + cli_obj.session_key = "" + cli_obj._session_db.get_session.return_value = {"started_at": 1775791440} + cli_obj._get_status_bar_snapshot = lambda: { + "context_tokens": 50000, "context_length": 200000, "context_percent": 25, + } + + with patch("cli.display_hermes_home", return_value="~/.hermes"), \ + patch("tools.approval._get_approval_mode", return_value="manual"), \ + patch("tools.approval.is_approval_bypass_active_for_session", return_value=False): + cli_obj._show_session_status() + + printed = "\n".join(str(call.args[0]) for call in cli_obj.console.print.call_args_list) + assert "Reasoning: high (display: on)" in printed + assert "Approvals: manual" in printed + assert "Context: 75% left · 50,000 / 200,000 tokens used" in printed + + def test_profile_command_reports_custom_root_profile(monkeypatch, tmp_path, capsys): """Profile detection works for custom-root deployments (not under ~/.hermes).""" cli_obj = _make_cli()