Files
hermes-webui/api/agent_compat.py
Mike McHargueandClaude Opus 5 1f7e5e78da fix: resolve Hermes Agent names moved by the Sep 2026 decomposition
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>
2026-09-17 21:26:50 +00:00

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)