mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-19 15:18:03 +00:00
feat(plugins): surface entry-point plugins in hermes plugins list
Salvaged from #40346; re-verified on main, tightened, tested. Co-authored-by: tjboudreaux <tjboudreaux@users.noreply.github.com>
This commit is contained in:
parent
91c68bf834
commit
94cdd56b82
2 changed files with 114 additions and 3 deletions
|
|
@ -10,6 +10,7 @@ rendered with Rich Markdown. Otherwise a default confirmation is shown.
|
|||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import importlib.metadata
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
|
@ -1015,11 +1016,11 @@ def _scan_level(
|
|||
|
||||
def _discover_all_plugins() -> list:
|
||||
"""Return a list of (name, version, description, source, dir_path, key) for
|
||||
every plugin the loader can see — user + bundled + project.
|
||||
every plugin the loader can see — user + bundled + project + entry point.
|
||||
|
||||
Matches the ordering/dedup of ``PluginManager.discover_and_load``:
|
||||
bundled first, then user, then project; user overrides bundled on
|
||||
key collision.
|
||||
bundled first, then user, then project, then entry points. Later sources
|
||||
override earlier ones on key collision.
|
||||
"""
|
||||
seen: dict = {} # key -> (name, version, description, source, path, key)
|
||||
|
||||
|
|
@ -1031,9 +1032,47 @@ def _discover_all_plugins() -> list:
|
|||
(_plugins_dir(), "user", set()),
|
||||
):
|
||||
_scan_level(base, source, skip, "", 0, seen)
|
||||
|
||||
# Entry-point plugins (installed as Python packages; no plugin directory).
|
||||
for name, version, description, path in _discover_entrypoint_plugins():
|
||||
seen[name] = (name, version, description, "entrypoint", path, name)
|
||||
return list(seen.values())
|
||||
|
||||
|
||||
def _discover_entrypoint_plugins() -> list[tuple[str, str, str, str]]:
|
||||
"""Return plugin entries advertised through ``hermes_agent.plugins``.
|
||||
|
||||
Entry-point plugins are installed as Python packages, so they do not have a
|
||||
plugin directory under ``~/.hermes/plugins``. Include package metadata here
|
||||
so ``hermes plugins list`` can show and enable them.
|
||||
"""
|
||||
from hermes_cli.plugins import ENTRY_POINTS_GROUP
|
||||
|
||||
try:
|
||||
eps = importlib.metadata.entry_points()
|
||||
if hasattr(eps, "select"):
|
||||
group_eps = eps.select(group=ENTRY_POINTS_GROUP)
|
||||
elif isinstance(eps, dict):
|
||||
group_eps = eps.get(ENTRY_POINTS_GROUP, [])
|
||||
else:
|
||||
group_eps = [ep for ep in eps if ep.group == ENTRY_POINTS_GROUP]
|
||||
except Exception as exc:
|
||||
logger.debug("Entry-point plugin discovery failed: %s", exc)
|
||||
return []
|
||||
|
||||
entries: list[tuple[str, str, str, str]] = []
|
||||
for ep in group_eps:
|
||||
version = ""
|
||||
description = ""
|
||||
dist = getattr(ep, "dist", None)
|
||||
metadata = getattr(dist, "metadata", None)
|
||||
if metadata is not None:
|
||||
version = str(getattr(dist, "version", "") or "")
|
||||
description = str(metadata.get("Summary", "") or "")
|
||||
entries.append((ep.name, version, description, ep.value))
|
||||
return entries
|
||||
|
||||
|
||||
def _plugin_status(name: str, enabled: set, disabled: set, key: str = "") -> str:
|
||||
"""Return the user-facing activation state for a plugin name or key."""
|
||||
if name in disabled or key in disabled:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import argparse
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
from hermes_cli import plugins_cmd
|
||||
|
||||
|
|
@ -86,3 +87,74 @@ def test_cmd_list_json_output(monkeypatch, capsys):
|
|||
"source": "git",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_discover_all_plugins_includes_entrypoint_plugins(monkeypatch, tmp_path):
|
||||
bundled_dir = tmp_path / "bundled"
|
||||
user_dir = tmp_path / "user"
|
||||
bundled_dir.mkdir()
|
||||
user_dir.mkdir()
|
||||
|
||||
dist = SimpleNamespace(
|
||||
version="0.1.0",
|
||||
metadata={"Summary": "Karpathy-style LLM Wikis for Hermes"},
|
||||
)
|
||||
entry_point = SimpleNamespace(
|
||||
name="wiki",
|
||||
value="adapters.hermes.cli_plugin",
|
||||
group="hermes_agent.plugins",
|
||||
dist=dist,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(plugins_cmd, "_plugins_dir", lambda: user_dir)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.plugins.get_bundled_plugins_dir",
|
||||
lambda: bundled_dir,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
plugins_cmd.importlib.metadata,
|
||||
"entry_points",
|
||||
lambda: [entry_point],
|
||||
)
|
||||
|
||||
entries = plugins_cmd._discover_all_plugins()
|
||||
|
||||
assert entries == [
|
||||
(
|
||||
"wiki",
|
||||
"0.1.0",
|
||||
"Karpathy-style LLM Wikis for Hermes",
|
||||
"entrypoint",
|
||||
"adapters.hermes.cli_plugin",
|
||||
"wiki",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_cmd_list_json_output_includes_entrypoint_source(monkeypatch, capsys):
|
||||
entries = [
|
||||
(
|
||||
"wiki",
|
||||
"0.1.0",
|
||||
"Karpathy-style LLM Wikis for Hermes",
|
||||
"entrypoint",
|
||||
"adapters.hermes.cli_plugin",
|
||||
"wiki",
|
||||
)
|
||||
]
|
||||
monkeypatch.setattr(plugins_cmd, "_discover_all_plugins", lambda: entries)
|
||||
monkeypatch.setattr(plugins_cmd, "_get_enabled_set", lambda: {"wiki"})
|
||||
monkeypatch.setattr(plugins_cmd, "_get_disabled_set", lambda: set())
|
||||
|
||||
plugins_cmd.cmd_list(_args(json=True))
|
||||
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload == [
|
||||
{
|
||||
"name": "wiki",
|
||||
"status": "enabled",
|
||||
"version": "0.1.0",
|
||||
"description": "Karpathy-style LLM Wikis for Hermes",
|
||||
"source": "entrypoint",
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue