Doctor pushed every plugin through PluginManager._load_plugin, which demands register(ctx);
model-provider plugins register a ProviderProfile at import through providers/ discovery (the
manager skips the kind on purpose), so every valid provider plugin failed with
'no register() function' while 'hermes plugins validate' passed it. Route the kind through
providers._import_plugin_dir, report the registered profiles, and restore the live registry
and module state afterwards.
Same diagnosis and shape as PR #86160 by @oliver-mee (Aug 14), which reached it first; this
version imports through providers._import_plugin_dir (the real discovery loader) instead of
the PluginManager namespace. Credit: #86160.
Co-authored-by: Oliver Mee <102673257+oliver-mee@users.noreply.github.com>
Follow-up to the salvaged manifest guard (#90859): the doctor's temporary
HERMES_HOME now enters the ExitStack before the staging copytree, so
ENOSPC / KeyboardInterrupt / any exception during staging deterministically
removes the hermes-plugin-doctor-* directory instead of relying on the
TemporaryDirectory GC finalizer (which cannot run while the traceback pins
the frame). Regression test proven via sabotage run against the old code.
`hermes plugins doctor` defaults its target to `.`, and
resolve_plugin_path accepted any directory that existed. Doctor then
copytree'd the resolved path into a temporary HERMES_HOME *before* the
runtime got to reject it, so running the command from a directory that
is not a plugin copied that whole tree.
Run from $HOME on macOS this copies the home directory, and because
`Library/CloudStorage` is not excluded it also materializes every
cloud-only Google Drive/iCloud placeholder. Observed locally: 461 GB
written to /private/var/folders and still growing when the process was
killed, on a machine with 49 GB free.
Resolve now requires a manifest before returning a path, mirroring
PluginManager._scan_directory: `plugin.yaml`/`plugin.yml`/`plugin.json`
in the directory itself, or in one immediate subdirectory for the
category layout. Plugin-id candidates are only tried for values that can
be an id, since joining `.` onto a plugins root resolves to the root and
would hand Doctor every installed plugin at once.
Non-plugin targets now fail with a clear message and no copy.