diff --git a/tests/tools/test_honcho_tools.py b/tests/tools/test_honcho_tools.py index 16e144541..0651eb52c 100644 --- a/tests/tools/test_honcho_tools.py +++ b/tests/tools/test_honcho_tools.py @@ -1,11 +1,86 @@ """Regression tests for per-call Honcho tool session routing.""" import json -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch +from dataclasses import dataclass from tools import honcho_tools +class TestCheckHonchoAvailable: + """Tests for _check_honcho_available (banner + runtime gating).""" + + def setup_method(self): + self.orig_manager = honcho_tools._session_manager + self.orig_key = honcho_tools._session_key + + def teardown_method(self): + honcho_tools._session_manager = self.orig_manager + honcho_tools._session_key = self.orig_key + + def test_returns_true_when_session_active(self): + """Fast path: session context already injected (mid-conversation).""" + honcho_tools._session_manager = MagicMock() + honcho_tools._session_key = "test-key" + assert honcho_tools._check_honcho_available() is True + + def test_returns_true_when_configured_but_no_session(self): + """Slow path: honcho configured but agent not started yet (banner time).""" + honcho_tools._session_manager = None + honcho_tools._session_key = None + + @dataclass + class FakeConfig: + enabled: bool = True + api_key: str = "test-key" + base_url: str = None + + with patch("tools.honcho_tools.HonchoClientConfig", create=True): + with patch( + "honcho_integration.client.HonchoClientConfig" + ) as mock_cls: + mock_cls.from_global_config.return_value = FakeConfig() + assert honcho_tools._check_honcho_available() is True + + def test_returns_false_when_not_configured(self): + """No session, no config: tool genuinely unavailable.""" + honcho_tools._session_manager = None + honcho_tools._session_key = None + + @dataclass + class FakeConfig: + enabled: bool = False + api_key: str = None + base_url: str = None + + with patch( + "honcho_integration.client.HonchoClientConfig" + ) as mock_cls: + mock_cls.from_global_config.return_value = FakeConfig() + assert honcho_tools._check_honcho_available() is False + + def test_returns_false_when_import_fails(self): + """Graceful fallback when honcho_integration not installed.""" + import sys + + honcho_tools._session_manager = None + honcho_tools._session_key = None + + # Hide honcho_integration from the import system to simulate + # an environment where the package is not installed. + hidden = { + k: sys.modules.pop(k) + for k in list(sys.modules) + if k.startswith("honcho_integration") + } + try: + with patch.dict(sys.modules, {"honcho_integration": None, + "honcho_integration.client": None}): + assert honcho_tools._check_honcho_available() is False + finally: + sys.modules.update(hidden) + + class TestHonchoToolSessionContext: def setup_method(self): self.orig_manager = honcho_tools._session_manager diff --git a/tools/honcho_tools.py b/tools/honcho_tools.py index 4aa86d57a..c3a1ac59c 100644 --- a/tools/honcho_tools.py +++ b/tools/honcho_tools.py @@ -45,8 +45,23 @@ def clear_session_context() -> None: # ── Availability check ── def _check_honcho_available() -> bool: - """Tool is only available when Honcho is active.""" - return _session_manager is not None and _session_key is not None + """Tool is available when Honcho is active OR configured. + + At banner time the session context hasn't been injected yet, but if + a valid config exists the tools *will* activate once the agent starts. + Returning True for "configured" prevents the banner from marking + honcho tools as red/disabled when they're actually going to work. + """ + # Fast path: session already active (mid-conversation) + if _session_manager is not None and _session_key is not None: + return True + # Slow path: check if Honcho is configured (banner time) + try: + from honcho_integration.client import HonchoClientConfig + cfg = HonchoClientConfig.from_global_config() + return cfg.enabled and bool(cfg.api_key or cfg.base_url) + except Exception: + return False def _resolve_session_context(**kwargs):