Skip to content

fix(plugins): include entry-point plugins in hermes plugins list/enable/disable (#23802) - #25040

Closed
wesleysimplicio wants to merge 3 commits into
NousResearch:mainfrom
wesleysimplicio:fix/cx12-issue-23802-plugins-cmd-entrypoint-visibility
Closed

fix(plugins): include entry-point plugins in hermes plugins list/enable/disable (#23802)#25040
wesleysimplicio wants to merge 3 commits into
NousResearch:mainfrom
wesleysimplicio:fix/cx12-issue-23802-plugins-cmd-entrypoint-visibility

Conversation

@wesleysimplicio

@wesleysimplicio wesleysimplicio commented May 13, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

hermes plugins list, hermes plugins enable <name>, and hermes plugins disable <name> silently ignore plugins installed via pip install <package> that register through the hermes_agent.plugins entry-point group.

Root cause

hermes plugins list, hermes plugins enable <name>, and hermes plugins disable <name> silently ignore plugins installed via pip install <package> that register through the hermes_agent.plugins entry-point group.

A pip-installed plugin works (the PluginManager discovers and loads it), but it is invisible to every management surface.

Fix

Add the same importlib.metadata.entry_points() scan to both helpers.

# _discover_all_plugins() — after bundled/user loop
try:
    import importlib.metadata
    from hermes_cli.plugins import ENTRY_POINTS_GROUP
    eps = importlib.metadata.entry_points()
    ...
    for ep in group_eps:
        if ep.name not in seen:
            seen[ep.name] = (ep.name, "", "", "entrypoint", None)
except Exception:
    pass

# _plugin_exists() — before final return False
try:
    ...
    if any(ep.name == name for ep in group_eps):
        return True
except Exception:
    pass

Bundled/user directory plugins take precedence on name collision. Entry-point results carry source="entrypoint" and path=None (which all callers already handle with _path / _d unused positional names).

Why this shape

This shape mirrors #29640 so reviewers can quickly compare scope, root cause, fix, tests, and related context without having to decode a custom PR description.

Tests

  • Veja a descrição original preservada abaixo para detalhes de validação, testes e notas de verificação.
Original body

Related PRs / issues

Fixes #23802

Original body

Summary

hermes plugins list, hermes plugins enable <name>, and hermes plugins disable <name> silently ignore plugins installed via pip install <package> that register through the hermes_agent.plugins entry-point group.

What Changed

  • Standardized this PR body to the current Hermes Turbo template.
  • Preserved the original detailed description below for reference.

Fluxo

A mudança continua seguindo o fluxo original descrito na seção preservada abaixo, sem ampliar o escopo funcional deste PR.

Visão

A padronização melhora a revisão, reduz ruído e evita deriva de formatação entre PRs abertos.

Test Plan

  • Veja a descrição original preservada abaixo para detalhes de validação, testes e notas de verificação.
Original body

What does this PR do?

Problem

hermes plugins list, hermes plugins enable <name>, and hermes plugins disable <name> silently ignore plugins installed via pip install <package> that register through the hermes_agent.plugins entry-point group.

A pip-installed plugin works (the PluginManager discovers and loads it), but it is invisible to every management surface.

Root Cause

_discover_all_plugins() and _plugin_exists() in hermes_cli/plugins_cmd.py only scan on-disk manifest directories (bundled plugins/<name>/ and user ~/.hermes/plugins/<name>/). Neither helper calls importlib.metadata.entry_points(). The PluginManager._scan_entry_points() method has the correct logic, but the CLI management layer was never wired to it.

hermes plugins list
# → shows bundled + user-dir plugins only
# → entry-point plugin is NOT listed despite being loaded by PluginManager

hermes plugins enable my-ep-plugin
# → "Plugin 'my-ep-plugin' is not installed or bundled." ← wrong

Fix

Add the same importlib.metadata.entry_points() scan to both helpers.

# _discover_all_plugins() — after bundled/user loop
try:
    import importlib.metadata
    from hermes_cli.plugins import ENTRY_POINTS_GROUP
    eps = importlib.metadata.entry_points()
    ...
    for ep in group_eps:
        if ep.name not in seen:
            seen[ep.name] = (ep.name, "", "", "entrypoint", None)
except Exception:
    pass

# _plugin_exists() — before final return False
try:
    ...
    if any(ep.name == name for ep in group_eps):
        return True
except Exception:
    pass

Bundled/user directory plugins take precedence on name collision. Entry-point results carry source="entrypoint" and path=None (which all callers already handle with _path / _d unused positional names).

Flow

graph TD
    A[hermes plugins list] --> B[_discover_all_plugins]
    B --> C[scan bundled dir]
    B --> D[scan user dir]
    B --> E[NEW: scan entry_points]
    E --> F[ep.name not in seen → add with source=entrypoint]
    C & D --> G[user overrides bundled on collision]
    G & F --> H[return list]
Loading

Tests

  • test_discover_includes_entrypoint_plugin — entry-point plugin appears in results with source="entrypoint" and path=None
  • test_discover_entrypoint_does_not_override_bundled — bundled plugin with same name wins
  • test_plugin_exists_true_for_entrypoint_plugin_exists() returns True for entry-point plugin
  • test_plugin_exists_false_for_unknown_entrypoint — returns False for unknown name even when other entry-points exist

All 129 test_plugins_cmd.py + test_plugins.py tests green.

🤖 Generated with Claude Code

Solution Sketch

  • fix the root cause in the touched subsystem instead of layering a broad workaround around the symptom
  • keep surrounding behavior stable and avoid unrelated refactors while the area is under review
  • prove the change with focused checks on the exact path that regressed

Related Issue

Fixes #23802

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • preserved the existing technical rationale and validation notes inside the template body
  • scoped this PR description to the implementation already present on the branch
  • aligned the delivery format with .github/PULL_REQUEST_TEMPLATE.md

How to Test

  1. Review the existing validation notes preserved in this PR body.
  2. Run the focused checks for the touched area.
  3. Confirm the scoped change still behaves as described above.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform:

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

  • N/A.

Generated by Hermes Turbo


Generated by Hermes Turbo

wesleysimplicio and others added 3 commits May 12, 2026 23:04
…leton

Closes NousResearch#24714

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e source

When removing one of multiple credentials that share the same source tag
(e.g. manual:device_code from repeated `hermes auth add openai-codex`),
the previous unconditional suppress_credential_source() call would
silently mark that source as suppressed even though the surviving entries
still depended on it. On the next load_pool(), any code path gating on
is_source_suppressed() would drop those healthy survivors.

Fix: only suppress when pool.entries() shows no remaining entry with the
same source after the removal. Also strip misleading "will not be
re-seeded" hints when suppression is intentionally skipped.

Fixes NousResearch#24390

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…le/disable

`_discover_all_plugins()` and `_plugin_exists()` only scanned on-disk
manifest directories (bundled + user ~/.hermes/plugins/). Plugins
installed via `pip install <package>` that register via the
`hermes_agent.plugins` entry-point group were silently invisible to
both surfaces.

Root cause: neither helper consulted `importlib.metadata.entry_points()`.
The PluginManager itself scans entry points via `_scan_entry_points()`
and loads them correctly, but the management CLI layer had no equivalent
scan.

Fix: add entry-point scan to both helpers using the same
`importlib.metadata.entry_points()` logic as `_scan_entry_points()`.
Bundled/user directory plugins take precedence on name collision.
Entry-point results carry `source="entrypoint"` and `path=None`.

Fixes NousResearch#23802

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 13, 2026 14:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@daimon-nous daimon-nous Bot added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have labels May 13, 2026
@daimon-nous

daimon-nous Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Competing PR: #23814 also fixes #23802 with similar changes to hermes_cli/plugins_cmd.py. This PR has broader scope (touches plugins.py, auth_commands.py, and adds more tests). Maintainer should pick one and close the other.

@wesleysimplicio

Copy link
Copy Markdown
Contributor Author

Closing as a duplicate/competing PR with #23814, per review feedback. The competing PR already covers the entry-point plugin discovery behavior and is the clearer single source of truth for this fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hermes plugins enable/list filters out entry-point-discovered plugins

2 participants