diff --git a/hermes_cli/plugins_cmd.py b/hermes_cli/plugins_cmd.py index 9d1c744c53cf7..968b775191ea3 100644 --- a/hermes_cli/plugins_cmd.py +++ b/hermes_cli/plugins_cmd.py @@ -1076,6 +1076,32 @@ def _is_portable_plugin_dir(dir_path) -> bool: return False +# Manifest kinds that are active-by-default when bundled: backends auto-load, +# platforms register lazily but are available out of the box, model providers +# run through providers/ discovery (see PluginManager.discover_and_load). +_BUNDLED_DEFAULT_ON_KINDS = frozenset({"backend", "platform", "model-provider"}) + + +def _bundled_default_on(dir_path) -> bool: + """True when a bundled plugin at *dir_path* is active without an explicit + ``plugins.enabled`` entry. Standalone/exclusive kinds stay opt-in, and + portable packages (``plugin.json``) have no kind at all.""" + manifest_file = Path(dir_path) / "plugin.yaml" + if not manifest_file.exists(): + manifest_file = Path(dir_path) / "plugin.yml" + if not manifest_file.exists(): + return False + try: + import yaml + + with open(manifest_file, encoding="utf-8") as f: + manifest = yaml.safe_load(f) or {} + kind = str(manifest.get("kind", "standalone")).strip().lower() + return kind in _BUNDLED_DEFAULT_ON_KINDS + except Exception: + return False + + def _scan_level( base: Path, source: str, diff --git a/tui_gateway/methods_tools.py b/tui_gateway/methods_tools.py index 4012116b30941..dc12130c6fd30 100644 --- a/tui_gateway/methods_tools.py +++ b/tui_gateway/methods_tools.py @@ -1811,6 +1811,7 @@ def _(rid, params: dict) -> dict: action = params.get("action", "list") try: from hermes_cli.plugins_cmd import ( + _bundled_default_on, _discover_all_plugins, _get_disabled_set, _get_enabled_set, @@ -1825,6 +1826,17 @@ def _(rid, params: dict) -> dict: for name, version, desc, source, _dir, key in sorted( _discover_all_plugins() ): + status = _plugin_status(name, enabled, disabled, key=key) + # Bundled backends/platforms/providers are active without an + # explicit enable (they "just work" — plugins.py). Reporting + # them "not enabled" reads as OFF in clients when they are in + # fact running; surface the truthful default instead. + if ( + status == "not enabled" + and source == "bundled" + and _bundled_default_on(_dir) + ): + status = "enabled" out.append( { "name": name, @@ -1835,7 +1847,7 @@ def _(rid, params: dict) -> dict: "version": str(version or ""), "description": desc or "", "source": source, - "status": _plugin_status(name, enabled, disabled, key=key), + "status": status, # Agent Plugins v1 package (plugin.json — the portable # skills/MCP format) vs a native Hermes plugin. "portable": _is_portable_plugin_dir(_dir),