`_platform_plugin_manifests()` scanned only the repo's `plugins/platforms/*`, so a third-party platform plugin under `<HERMES_HOME>/plugins/` never reached `OPTIONAL_ENV_VARS`: the Desktop Gateway form and `hermes config` showed bare variable names with no prompt, description or password masking. It now also walks `<HERMES_HOME>/plugins/platforms/*` and flat `<HERMES_HOME>/plugins/*` manifests that declare `kind: platform`. Slim redo of #46964 by @LeonSGP43 onto the refactored helper (the original predates `_platform_plugin_manifests` and replaced `fast_safe_load`). Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
This commit is contained in:
@@ -3924,26 +3924,37 @@ _inject_profile_env_vars()
|
||||
|
||||
|
||||
def _platform_plugin_manifests():
|
||||
"""Yield ``(dir_name, manifest_dict)`` for every bundled ``plugins/platforms/*/plugin.y(a)ml``."""
|
||||
platforms_dir = get_project_root() / "plugins" / "platforms"
|
||||
if not platforms_dir.is_dir():
|
||||
return
|
||||
for child in platforms_dir.iterdir():
|
||||
manifest_path = next(
|
||||
(p for p in (child / "plugin.yaml", child / "plugin.yml") if child.is_dir() and p.exists()), None)
|
||||
if manifest_path is None:
|
||||
"""Yield ``(dir_name, manifest_dict)`` for every platform plugin manifest: bundled
|
||||
``plugins/platforms/*``, the user's ``<HERMES_HOME>/plugins/platforms/*`` category dir, and flat
|
||||
user installs ``<HERMES_HOME>/plugins/*`` that declare ``kind: platform`` (#46600)."""
|
||||
user_plugins = get_hermes_home() / "plugins"
|
||||
roots = (
|
||||
(get_project_root() / "plugins" / "platforms", False),
|
||||
(user_plugins / "platforms", False),
|
||||
(user_plugins, True), # flat layout: only manifests that say they are platforms
|
||||
)
|
||||
for root, require_kind in roots:
|
||||
if not root.is_dir():
|
||||
continue
|
||||
try:
|
||||
with open(manifest_path, "r", encoding="utf-8") as f:
|
||||
manifest = fast_safe_load(f) or {}
|
||||
except Exception:
|
||||
continue
|
||||
yield child.name, manifest
|
||||
for child in root.iterdir():
|
||||
manifest_path = next(
|
||||
(p for p in (child / "plugin.yaml", child / "plugin.yml") if child.is_dir() and p.exists()), None)
|
||||
if manifest_path is None:
|
||||
continue
|
||||
try:
|
||||
with open(manifest_path, "r", encoding="utf-8") as f:
|
||||
manifest = fast_safe_load(f) or {}
|
||||
except Exception:
|
||||
continue
|
||||
if not isinstance(manifest, dict) or (require_kind and manifest.get("kind") != "platform"):
|
||||
continue
|
||||
yield child.name, manifest
|
||||
|
||||
|
||||
def _inject_platform_plugin_env_vars() -> None:
|
||||
"""Populate OPTIONAL_ENV_VARS from bundled platform plugin manifests so Teams / IRC / Google
|
||||
Chat etc. are configurable in ``hermes config`` UI without the core knowing they exist.
|
||||
"""Populate OPTIONAL_ENV_VARS from platform plugin manifests (bundled AND user-installed) so
|
||||
Teams / IRC / Google Chat and third-party platforms are configurable in the ``hermes config`` /
|
||||
Desktop Gateway form without the core knowing they exist.
|
||||
|
||||
``requires_env`` / ``optional_env`` entries are a bare name or a dict with ``name`` plus
|
||||
optional ``description``/``url``/``password``/``prompt``/``category``. Failures are swallowed
|
||||
|
||||
42
tests/hermes_cli/test_platform_plugin_env_injection.py
Normal file
42
tests/hermes_cli/test_platform_plugin_env_injection.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""User-installed platform plugins feed OPTIONAL_ENV_VARS like bundled ones (#46600, redo of #46964).
|
||||
|
||||
The Desktop Gateway form and ``hermes config`` render env fields from ``OPTIONAL_ENV_VARS``; before
|
||||
this, only ``plugins/platforms/*`` in the repo was scanned, so a third-party platform's
|
||||
``requires_env`` prompts/descriptions/password flags never reached the UI.
|
||||
"""
|
||||
|
||||
import hermes_cli.config as config_mod
|
||||
|
||||
|
||||
def _manifest(path, text):
|
||||
path.mkdir(parents=True)
|
||||
(path / "plugin.yaml").write_text(text, encoding="utf-8")
|
||||
|
||||
|
||||
def test_user_platform_plugins_inject_env_metadata_but_non_platforms_do_not(monkeypatch, tmp_path):
|
||||
home = tmp_path / "hermes-home"
|
||||
_manifest(home / "plugins" / "demo-platform", (
|
||||
"name: demo-platform\nkind: platform\nlabel: Demo Platform\n"
|
||||
"requires_env:\n - name: DEMO_PLATFORM_TOKEN\n description: Token for the demo platform\n"
|
||||
" prompt: Demo token\n url: https://example.invalid/demo\n"
|
||||
"optional_env:\n - name: DEMO_PLATFORM_ROOM\n password: false\n"))
|
||||
_manifest(home / "plugins" / "platforms" / "nested-platform", (
|
||||
"name: nested-platform\nrequires_env:\n - NESTED_PLATFORM_SECRET\n"))
|
||||
_manifest(home / "plugins" / "ignore-me", "name: ignore-me\nkind: backend\nrequires_env:\n - IGNORE_ME_TOKEN\n")
|
||||
keys = ["DEMO_PLATFORM_TOKEN", "DEMO_PLATFORM_ROOM", "NESTED_PLATFORM_SECRET", "IGNORE_ME_TOKEN"]
|
||||
monkeypatch.setattr(config_mod, "get_hermes_home", lambda: home)
|
||||
for key in keys:
|
||||
monkeypatch.delitem(config_mod.OPTIONAL_ENV_VARS, key, raising=False)
|
||||
|
||||
config_mod._inject_platform_plugin_env_vars()
|
||||
|
||||
try:
|
||||
assert config_mod.OPTIONAL_ENV_VARS["DEMO_PLATFORM_TOKEN"] == {
|
||||
"description": "Token for the demo platform", "prompt": "Demo token",
|
||||
"url": "https://example.invalid/demo", "password": True, "category": "messaging"}
|
||||
assert config_mod.OPTIONAL_ENV_VARS["DEMO_PLATFORM_ROOM"]["password"] is False
|
||||
assert config_mod.OPTIONAL_ENV_VARS["NESTED_PLATFORM_SECRET"]["password"] is True # category dir needs no kind
|
||||
assert "IGNORE_ME_TOKEN" not in config_mod.OPTIONAL_ENV_VARS
|
||||
finally:
|
||||
for key in keys:
|
||||
config_mod.OPTIONAL_ENV_VARS.pop(key, None)
|
||||
Reference in New Issue
Block a user