Hermes Agent moved several names the WebUI uses into sibling modules (tools.approval_context, tools.mcp_tool_discovery/_lifecycle, hermes_cli.kanban_db_connect/_dispatch, hermes_cli.models_local, agent.anthropic_credentials). The old paths only resolved through PLUGIN-COMPAT __getattr__ pointers that warn on every use and are removed on schedule by reverting one commit. With the pointers removed, every affected WebUI call site is wrapped in a broad except, so nothing crashes; features silently degrade instead: per-turn approval session-key binding is skipped, MCP discovery/status return nothing, /reload-mcp reports "MCP runtime unavailable", Claude Code credential linking returns None, every kanban connection raises AttributeError, and the dispatcher reports "unavailable". Add api.agent_compat.agent_attr, which resolves a moved name from the original module's own namespace first (pre-split Agents and existing test stubs), then from its new home (split Agents, with or without pointers), then plain attribute access (non-module doubles). Importing only the new modules would have broken pre-split Agents and bypassed the sys.modules stubs the test suite installs on the original modules. Names still native to their original module (_servers, _lock, has_blocking_approval, is_session_yolo_enabled, register/unregister gateway notify, the rest of kanban_db) are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
55 lines
2.2 KiB
Python
55 lines
2.2 KiB
Python
"""Resolve Hermes Agent names that moved to a sibling module.
|
|
|
|
Hermes Agent's September 2026 decomposition moved many names out of their
|
|
original modules (``tools.approval``, ``tools.mcp_tool``,
|
|
``hermes_cli.kanban_db``, ...) into ``<stem>_<topic>`` siblings. The old paths
|
|
kept resolving for a while only through PEP 562 ``__getattr__`` pointers that
|
|
emit ``HermesPluginCompatWarning`` and are removed on schedule, so WebUI code
|
|
must not rely on them. Importing only the new module would instead break Agent
|
|
installs that predate the split.
|
|
|
|
``agent_attr`` is compatibility-only and resolves a moved name in this order:
|
|
|
|
1. the original module's own namespace -- pre-split Agents, and tests that stub
|
|
the original module in ``sys.modules`` or patch the name onto it;
|
|
2. the new home module -- split Agents, with or without the old-path pointers;
|
|
3. plain attribute access on the original object -- non-module test doubles,
|
|
or an Agent whose new home is not importable.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
from types import ModuleType
|
|
from typing import Any
|
|
|
|
_MISSING = object()
|
|
|
|
|
|
def agent_attr(owner: Any, name: str, home: str, default: Any = _MISSING) -> Any:
|
|
"""Return ``owner.name`` without going through a removed/deprecated pointer.
|
|
|
|
``owner`` is the original module (or its dotted name); ``home`` is the
|
|
dotted name of the module the Agent moved ``name`` to. Raises
|
|
``ImportError``/``AttributeError`` like the plain import it replaces, unless
|
|
``default`` is given.
|
|
"""
|
|
if isinstance(owner, str):
|
|
try:
|
|
owner = importlib.import_module(owner)
|
|
except ImportError:
|
|
if default is _MISSING:
|
|
return getattr(importlib.import_module(home), name)
|
|
try:
|
|
return getattr(importlib.import_module(home), name, default)
|
|
except ImportError:
|
|
return default
|
|
if isinstance(owner, ModuleType) and name not in vars(owner):
|
|
try:
|
|
return getattr(importlib.import_module(home), name)
|
|
except (ImportError, AttributeError):
|
|
pass
|
|
if default is _MISSING:
|
|
return getattr(owner, name)
|
|
return getattr(owner, name, default)
|