55f9e472a0
Cold CLI time-to-banner was ~1.8s (hermes) / ~2.8s (hermes -w). The banner path was paying for work the session doesn't need before first input: - aux availability probes built REAL OpenAI/httpx clients (openai import ~0.3s + SSL context) just to answer check_fns. New aux_probe_mode() returns a cache-excluded stub; resolution policy unchanged. - tools/mcp_tool imported the mcp SDK (~260ms, mcp.types pydantic model construction) at module import even with zero MCP servers configured. SDK import is now lazy behind _ensure_mcp_sdk(); _MCP_AVAILABLE is a find_spec probe so every existing gate/test keeps its semantics. - banner blocked 500ms on the update-check prefetch; now waits 50ms and defers the warning line to a daemon thread (prints above the prompt). - banner recomputed get_tool_definitions + skills scan + git state every launch; now snapshotted to ~/.hermes/cache/banner_snapshot.json keyed on (config.yaml, .env, checkout rev, toolsets) and replayed on warm launches with a background refresh. Agent tool list is still computed fresh. - _resolve_active_context_length probed the Nous portal /models (~200ms network) per launch; the tool-search gate now prefers the on-disk context cache when present. - schema reconciliation re-executed SCHEMA_SQL in a scratch SQLite DB (~85ms) per SessionDB(); the reference parse is now disk-memoized by DDL hash (live-DB diffing still runs every startup). - bundled-skills sync (~120-170ms rglob/hash) moved off the startup path to a daemon thread; plugin discovery starts in the background and every synchronous consumer joins via discover_plugins(). - hermes_cli.auth imported httpx eagerly (~30ms); now a lazy proxy that test monkeypatching still reaches (setattr forwards to the real module). - fast chat launch: unambiguous 'hermes'/'hermes chat' invocations skip building all ~40 subcommand parsers (bails to full dispatch on anything else, incl. container mode). - -w path: git worktree add runs with checkout.workers=8 (0.6s→0.2s) and overlaps HermesCLI construction; --skills preload runs in the background and is folded in at agent init (finalize_preloaded_skills, same fail-loud contract for fully-unknown skill lists); stale-worktree prune moved off the banner path. Warm results (PTY time-to-banner, 5-run): hermes 1.80s → 0.38-0.40s; hermes -w -s hermes-agent-dev --yolo 2.82s → 0.57-0.69s.
63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
"""Tests for banner get_available_skills() — disabled and platform filtering."""
|
|
|
|
import pytest
|
|
from unittest.mock import patch
|
|
|
|
|
|
_MOCK_SKILLS = [
|
|
{"name": "skill-a", "description": "A skill", "category": "tools"},
|
|
{"name": "skill-b", "description": "B skill", "category": "tools"},
|
|
{"name": "skill-c", "description": "C skill", "category": "creative"},
|
|
]
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _reset_skills_cache():
|
|
"""get_available_skills is memoized per-process (startup perf) — reset
|
|
the cache around each test so patched _find_all_skills results are
|
|
actually observed."""
|
|
import hermes_cli.banner as banner
|
|
banner._available_skills_cache = None
|
|
yield
|
|
banner._available_skills_cache = None
|
|
|
|
|
|
def test_get_available_skills_delegates_to_find_all_skills():
|
|
"""get_available_skills should call _find_all_skills (which handles filtering)."""
|
|
with patch("tools.skills_tool._find_all_skills", return_value=list(_MOCK_SKILLS)):
|
|
from hermes_cli.banner import get_available_skills
|
|
result = get_available_skills()
|
|
|
|
assert "tools" in result
|
|
assert "creative" in result
|
|
assert sorted(result["tools"]) == ["skill-a", "skill-b"]
|
|
assert result["creative"] == ["skill-c"]
|
|
|
|
|
|
def test_get_available_skills_null_category_becomes_general():
|
|
"""Skills with None category should be grouped under 'general'."""
|
|
skills = [{"name": "orphan-skill", "description": "No cat", "category": None}]
|
|
with patch("tools.skills_tool._find_all_skills", return_value=skills):
|
|
from hermes_cli.banner import get_available_skills
|
|
result = get_available_skills()
|
|
|
|
assert "general" in result
|
|
assert result["general"] == ["orphan-skill"]
|
|
|
|
|
|
def test_get_available_skills_is_memoized():
|
|
"""Second call must not re-walk the skills tree (startup perf contract)."""
|
|
import hermes_cli.banner as banner
|
|
calls = []
|
|
|
|
def fake_find(**kwargs):
|
|
calls.append(1)
|
|
return list(_MOCK_SKILLS)
|
|
|
|
with patch("tools.skills_tool._find_all_skills", side_effect=fake_find):
|
|
first = banner.get_available_skills()
|
|
second = banner.get_available_skills()
|
|
|
|
assert first == second
|
|
assert len(calls) == 1
|