c600fd46bd
Builds on the three salvaged commits: adds the sources and integration points they leave out, so a pip-installed memory provider is not a second-class citizen next to a directory install. Discovery - Project-local providers (./.hermes/plugins/<name>/), gated on HERMES_ENABLE_PROJECT_PLUGINS exactly as PluginManager gates its own project scan. Completes the four sources CONTRIBUTING.md and AGENTS.md already promised; memory was the only discovery system missing two of them. - find_provider_dir() now resolves a package entry point to its directory. This is load-bearing: config_schema.py (the dashboard panel) and cli.py (the `hermes <provider>` subcommands) are read from disk rather than imported, so without a directory a pip-installed provider silently lost both. - list_memory_provider_names() includes entry-point providers, so they appear in the dashboard's memory.provider dropdown. Resolution stays import-free. hermes_cli.plugins.resolve_module_origin() is extracted from _resolve_module_source() (added by the salvaged #76567) and shared, so discovery walks a module's file layout instead of importing it. find_provider_dir() is called from the dashboard and from argparse setup, long before the operator has chosen a provider — importing every installed candidate would execute third-party code on the strength of a package being present. A test asserts the resolution leaves no side effects and no sys.modules entry. Registration - PluginContext gains register_memory_provider(). Memory was the only provider category without one; context engine, image gen, video gen, web search, browser, TTS, transcription, secret source, dashboard auth and platform all have one. - _ProviderCollector delegates unknown register_* calls to a real PluginContext instead of carrying three hand-written no-ops. It silently dropped register_tool/register_hook, and had no register_auxiliary_task at all — despite PluginContext.register_auxiliary_task documenting a memory provider (hindsight's pre-retain dedup) as its worked example. It can no longer drift behind PluginContext. - A raise after register_memory_provider() no longer costs the provider. The loader caught it into a debug log, discarded the registered instance, and fell through to "instantiate any MemoryProvider subclass" — returning a different, unconfigured provider. A silent downgrade that looked like success, and the exact outcome of calling register_auxiliary_task. Activation is unchanged: still gated on memory.provider naming the plugin, and covered by a test so the real PluginContext cannot start requiring plugins.enabled — that would break every existing user-installed provider. Verified end to end against a real third-party provider (kainappsinc/elephant) installed by pip alone, with no directory copy: it appears in the dropdown, resolves its directory, loads with its tools, and renders its dashboard panel. Closes #40101.