compat(plugins): discovery itself refreshes the Desktop's report file
Live Desktop E2E (real Electron, worktree backend, demo plugin on old paths) found the modal never fired: compat_report() was only called from the CLI banner / doctor / update / plugins-compat surfaces, none of which run inside the Desktop's `serve` backend, so .plugin-compat-report.json was never written. PluginManager.discover_and_load now refreshes the report from the manifests it just discovered (fail-open). E2E after the fix, all five acceptance steps green on the real seat: report written and names the plugin; native dialog 'Plugins need an update' with plugin, date, and `hermes plugins compat`; OK persists the dismissal; relaunch with the same userData shows nothing and logs no 'compat notice shown'; fixing the plugin's imports deletes the report and shows nothing.
This commit is contained in:
@@ -1307,6 +1307,20 @@ class PluginManager(PluginLoaderMixin, PluginDispatchMixin, PluginLedgerMixin):
|
||||
if manifests:
|
||||
logger.info("Plugin discovery complete: %d found, %d enabled", len(self._plugins),
|
||||
sum(1 for p in self._plugins.values() if p.enabled))
|
||||
self._refresh_plugin_compat_report(list(to_load.values()))
|
||||
|
||||
def _refresh_plugin_compat_report(self, manifests: List[PluginManifest]) -> None:
|
||||
"""Refresh HERMES_HOME/.plugin-compat-report.json from this discovery pass (hermes_cli.plugin_compat).
|
||||
|
||||
The Desktop boot modal has no Python runtime of its own and reads that file after the ``serve``
|
||||
backend is up, so the scan must run wherever plugins are discovered — not only under the CLI
|
||||
banner / doctor / update, which never run inside the Desktop's backend. Fail-open: never raises.
|
||||
"""
|
||||
try:
|
||||
from hermes_cli.plugin_compat import compat_report
|
||||
compat_report(manifests, force=True)
|
||||
except Exception as exc:
|
||||
logger.debug("plugin compat report refresh skipped: %s", exc)
|
||||
|
||||
def _gate_manifest(
|
||||
self, manifest: PluginManifest, disabled: Set[str], enabled: Optional[Set[str]],
|
||||
|
||||
@@ -115,3 +115,24 @@ def test_loader_skips_hitting_plugin_after_date(tmp_path, monkeypatch):
|
||||
mgr._load_plugin(real)
|
||||
loaded = next(lp for lp in mgr._plugins.values() if lp.manifest.name == "oldpaths")
|
||||
assert not loaded.enabled and loaded.error and pc.COMPAT_REMOVAL in loaded.error
|
||||
|
||||
|
||||
def test_discovery_refreshes_report_file(tmp_path, monkeypatch):
|
||||
"""The Desktop modal reads the report the `serve` backend's discovery wrote — discovery itself must
|
||||
write it (not only the CLI banner / doctor / update paths), and clear it once the plugin is fixed."""
|
||||
from hermes_cli.plugins import PluginManager
|
||||
monkeypatch.setattr(pc, "load_manifest", lambda: MANIFEST)
|
||||
monkeypatch.setattr(pc, "removal_in_effect", lambda today=None: False)
|
||||
monkeypatch.setattr(pc, "report_file_path", lambda: tmp_path / "r.json")
|
||||
plugin = tmp_path / "plugins" / "oldpaths"; plugin.mkdir(parents=True)
|
||||
(plugin / "plugin.yaml").write_text("name: oldpaths\nversion: 0.1\ndescription: t\n")
|
||||
(plugin / "__init__.py").write_text("from tools.web_tools import prefers_gateway\ndef register(ctx):\n pass\n")
|
||||
from hermes_cli.plugins_manifest import PluginManifest
|
||||
real = PluginManifest(name="oldpaths", version="0.1", description="t", source="user", path=str(plugin))
|
||||
mgr = PluginManager(scope_key=str(tmp_path))
|
||||
mgr._refresh_plugin_compat_report([real])
|
||||
data = json.loads((tmp_path / "r.json").read_text())
|
||||
assert list(data["plugins"]) == ["oldpaths"] and data["in_effect"] is False
|
||||
(plugin / "__init__.py").write_text("from tools.tool_backend_helpers import prefers_gateway\ndef register(ctx):\n pass\n")
|
||||
mgr._refresh_plugin_compat_report([real])
|
||||
assert not (tmp_path / "r.json").exists()
|
||||
|
||||
Reference in New Issue
Block a user