fix(packaging): ship bundled plugin.yaml manifests in wheel and sdist

The v0.15.0 PyPI wheel shipped every plugin's Python code but none of its
plugin.yaml manifests, so plugin discovery (hermes_cli/plugins.py) found zero
plugins and ALL gateway platforms failed with "No adapter available for
<platform>" (discord, slack, mattermost, ...). Same gap also dropped the
web-search provider manifests (#28149).

Declare manifest coverage in both packaging channels:
- wheel: [tool.setuptools.package-data] plugins += **/plugin.yaml, **/plugin.yml
- sdist: MANIFEST.in recursive-include plugins plugin.yaml plugin.yml
  (Homebrew and other downstream packagers build from the sdist)

Verified by building the wheel before/after: plugin.yaml count went 0 -> 69,
discord's manifest now ships. Adds a regression test asserting both channels
cover manifests.

Fixes #34034

Co-authored-by: outsourc-e <201563152+outsourc-e@users.noreply.github.com>
Co-authored-by: Dhruvil Parikh <41384593+dparikh79@users.noreply.github.com>
Co-authored-by: ousiaresearch <261687298+ousiaresearch@users.noreply.github.com>
Co-authored-by: libre-7 <6366424+libre-7@users.noreply.github.com>
This commit is contained in:
teknium1 2026-05-29 01:10:46 -07:00 committed by Teknium
parent c01a2df0a3
commit 2765b02021
3 changed files with 52 additions and 0 deletions

View file

@ -1,4 +1,9 @@
graft skills
graft optional-skills
# Bundled plugin manifests (plugin.yaml / plugin.yml). Without these the
# PluginManager scan (hermes_cli/plugins.py) finds zero plugins on installs
# built from the sdist (e.g. Homebrew, downstream packagers). package-data
# below covers the wheel; this covers the sdist. See #34034 / #28149.
recursive-include plugins plugin.yaml plugin.yml
global-exclude __pycache__
global-exclude *.py[cod]

View file

@ -226,6 +226,14 @@ plugins = [
"*/dashboard/manifest.json",
"*/dashboard/dist/*",
"*/dashboard/dist/**/*",
# Plugin discovery (hermes_cli/plugins.py) reads a plugin.yaml/plugin.yml
# manifest from each bundled plugin directory to register it. Wheels only
# carry files declared here, so without this glob the wheel ships every
# plugin's Python code but none of its manifests — the scan finds zero
# plugins and all gateway platforms fail with "No adapter available for
# <platform>" (#34034), web-search providers go missing (#28149), etc.
"**/plugin.yaml",
"**/plugin.yml",
]
[tool.setuptools.packages.find]

View file

@ -20,3 +20,42 @@ def test_manifest_includes_bundled_skills():
assert "graft skills" in manifest
assert "graft optional-skills" in manifest
def test_bundled_plugin_manifests_ship_in_both_wheel_and_sdist():
"""Regression test for #34034 / #28149.
Plugin discovery (hermes_cli/plugins.py) registers each bundled plugin by
reading its ``plugin.yaml`` / ``plugin.yml`` manifest. Those manifests are
data files, not Python modules, so they only reach installed packages when
declared explicitly:
- wheel -> ``[tool.setuptools.package-data]`` ``plugins`` glob
- sdist -> ``MANIFEST.in`` (Homebrew and other downstream packagers build
from the sdist)
v0.15.0 declared neither, so the wheel shipped every adapter's Python code
but none of its manifests, and *every* gateway platform failed with
"No adapter available for <platform>". Both channels must cover manifests.
"""
# There must actually be manifests on disk for the globs to match.
on_disk = list((REPO_ROOT / "plugins").rglob("plugin.yaml")) + list(
(REPO_ROOT / "plugins").rglob("plugin.yml")
)
assert on_disk, "expected bundled plugin manifests under plugins/"
# Wheel channel: package-data must declare a glob that matches plugin
# manifests anywhere under the plugins package.
data = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8"))
plugins_pkg_data = data["tool"]["setuptools"]["package-data"].get("plugins", [])
assert any(
g.endswith("plugin.yaml") or g.endswith("plugin.yml")
for g in plugins_pkg_data
), "pyproject package-data 'plugins' must ship plugin.yaml/plugin.yml (wheel)"
# Sdist channel: MANIFEST.in must recursively include the manifests so
# downstream packagers building from the sdist also get them.
manifest = (REPO_ROOT / "MANIFEST.in").read_text(encoding="utf-8")
assert "recursive-include plugins" in manifest and "plugin.yaml" in manifest, (
"MANIFEST.in must recursive-include plugins plugin.yaml/plugin.yml (sdist)"
)