diff --git a/MANIFEST.in b/MANIFEST.in index 876aeeb7d..a0296c377 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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] diff --git a/pyproject.toml b/pyproject.toml index f2164724e..ce1b8b3da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 + # " (#34034), web-search providers go missing (#28149), etc. + "**/plugin.yaml", + "**/plugin.yml", ] [tool.setuptools.packages.find] diff --git a/tests/test_packaging_metadata.py b/tests/test_packaging_metadata.py index ce6d4793f..b53355f1c 100644 --- a/tests/test_packaging_metadata.py +++ b/tests/test_packaging_metadata.py @@ -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 ". 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)" + )