Plugins, die nur halb funktionierten: Hermes hat die Plugin-Namensauflösung endlich vereinheitlicht


Du öffnest den Plugin-Hub von Hermes Desktop und installierst ein Plugin, das ein Desktop-Panel mitbringt. Der Agent-Schalter leuchtet auf, das Panel daneben aber nie — du gräbst nach, und die Tools sind da, die Befehle sind da, nur die UI nicht. Oder etwas Einfacheres: hermes plugins install hermes-memory-wiki meldet Erfolg, du setzt mit hermes plugins enable hermes-memory-wiki nach, und die Antwort ist No plugin named hermes-memory-wiki. Du hast das Ding gerade installiert, und es behauptet, den Namen nicht zu kennen. Du hast ihn nicht falsch erinnert — Hermes hatte drei Namensauflöser, die sich uneinig waren. PR #135030, gemergt am 8. Oktober, hat das aufgeräumt.

Das Symptom: ein Plugin, drei Namen

Beginne mit den Symptomen, denn diese drei Fehler wirken zusammenhanglos und teilen eine Ursache:

  • Lässt sich sauber installieren, verweigert aber die Verwaltung. Der Name, unter dem eine Katalog-Installation landet, und der Name im eigenen Manifest des Plugins können differieren. Die Zahlen sind konkret: 17 von 484 Katalog-Einträgen installieren unter einem anderen Manifest-Namen — hermes-memory-wiki → memory-wiki, touchdesigner → td, hermes-subscription-meter → subscription-meter. install löst den Katalog-Namen auf, also gelingt es; aber enable, disable, show, remove und update lösten den Manifest-Namen oder -Key auf, also scheiterten alle an genau diesem Namen.
  • Ein Paket erscheint als zwei Zeilen. Klone es selbst nach ~/.hermes/plugins/hermes-subscription-meter, wie es das README des Plugins vorschlägt, und der Desktop-Hub zeigt zwei Zeilen: eine einsame Desktop-Hälfte, die „Install here“ für eine bereits installierte Agent-Hälfte anbietet, plus eine separate Agent-Zeile. Die Desktop-Seite gesellte sich über den Manifest-Namen zur Agent-Zeile und über den Ordnernamen zu ihrer eigenen Hälfte.
  • Der Schalter deckt nur eine Hälfte ab. Eine Desktop-Hälfte kommt opt-in, also schaltete das Umlegen des Agent-Schalters die Desktop-Hälfte nie mit ein.

Was diese drei gemeinsam haben: Der Name wurde an mehreren Stellen nach unterschiedlichen Regeln aufgelöst.

Die Ursache: fünf Auflöser, fünf Regelsätze

Vor der Änderung gab es fünf Stellen, die einen Plugin-Namen auflösten, verteilt über drei Pfade:

Wo Worauf abgeglichen wurde
hermes_cli/plugins_cmd.py _resolve_plugin_key_and_source, mit Kopien bei _find_plugin_entry, _require_installed_plugin und _user_installed_plugin_dir Manifest-Name / kanonischer Key / eindeutiges Blatt / Ordnername — jeweils mit einer anderen unterstützten Teilmenge
plugins_cmd_install.py _catalog_install_on_disk den Katalog-Namen, über den Installations-Record — nur zum Installationszeitpunkt
Desktop plugin-packages.ts vs. electron/desktop-plugins-root.ts einer verknüpfte über den Manifest-Namen, der andere über den Basename des Ordners

„Jeweils mit einer anderen unterstützten Teilmenge“ ist der Teil, der wirklich weh tat. Der Bug war nicht, dass ein Name nicht funktionierte; er war, dass zwei aufeinanderfolgende Befehle derselben Sequenz sich über die Regeln nicht einig waren, und so kommt ein erfolgreiches install gefolgt von einem fehlschlagenden enable zustande.

Jetzt: ein Auflöser, ein Schalter

Nach der Änderung:

  • _find_plugin_entry ist der einzige Auflöser, verwendet von jedem Verb (enable, disable, show, capabilities, remove, update und sowohl vom Dashboard als auch vom Desktop-Hub). Er versucht zuerst den kanonischen Key oder Manifest-Namen, dann ein eindeutiges nacktes Blatt (langfuse → observability/langfuse), dann den Katalog-Namen, unter dem das Plugin installiert wurde. Er gibt außerdem ein Versprechen: Er rät nie zwischen zwei Kandidaten — Mehrdeutigkeit schlägt fehl, statt einen zu wählen.
  • Der Katalog-Namen-Abgleich liest den eigenen Record des Installers (.install-metadata.json), nie eine Datei im Repository des Plugins. Das ist wichtig: Ein per URL installiertes Repo kann den Namen eines Katalog-Eintrags nicht mit etwas beanspruchen, das es selbst mitgeliefert hat.
  • Ein Schalter schaltet ein ganzes Paket ein. „Enable agent plugin after install“ im Installationsdialog (oder der Agent-Schalter der Zeile) schaltet jetzt auch die Desktop-Hälfte ein — es sei denn, du hast diese Hälfte selbst ausgeschaltet. Umgekehrt funktioniert es nicht: Ausschalten bleibt pro Hälfte, denn die Desktop-Hälfte ist von jedem Profil geteilt.

Auf der Kommandozeile lösen jetzt beide Namen auf:

# der klassische Mismatch: der touchdesigner-Eintrag installiert sich als td
hermes plugins install touchdesigner
hermes plugins enable touchdesigner   # dasselbe wie: hermes plugins enable td

# den Schalter schon beim Installieren umlegen und den zweiten Schritt sparen
hermes plugins install touchdesigner --enable

Vorher nahm nur hermes plugins install <name> den Katalog-Namen, und die anderen Verben brachen ab; jetzt akzeptieren enable / disable / show / capabilities / update / remove beide. Unsere Kommando-Referenz zu hermes plugins ist die Flag-Liste, und der Beitrag zum Plugin-Katalog behandelt das Vertrauensmodell hinter den Einträgen.

Außerdem behoben: „Ich habe den Code bearbeitet, und nichts hat sich geändert“

Der gleiche Batch enthält PR #135017, der dieselbe Problemklasse aus Entwicklersicht löst: Du bearbeitest desktop/plugin.js, und der Desktop führt weiterhin eine Kopie aus.

Electron kopiert plugins/<name>/desktop/ nach desktop-plugins/<name>/, und der Renderer lädt diese Kopie. Er kopiert nur dann neu, wenn die mtime der Quell-plugin.js neuer ist und etwas es explizit anfordert; der Hot-Reload-Watcher beobachtet die Kopie, nie die Quelle. Schlimmer: Der Python-Loader folgt einem Symlink in plugins/, aber der Reconcile-Code listete Pakete mit isDirectory() auf — und ein Symlink ist kein Verzeichnis —, also bekam das natürlichste Entwicklungssetup, ein per Symlink verknüpfter Checkout, seine Desktop-Hälfte nie kopiert.

Was es jetzt tut:

# das hier als Dev-Modus behandeln
ln -s ~/src/my-plugin ~/.hermes/plugins/my-plugin

# installierte Kopie an Ort und Stelle bearbeitet? lass doctor dir sagen, ob du eine veraltete ausführst
hermes plugins doctor my-plugin

Per Symlink verknüpfte Pakete werden erkannt (der Marker trägt linked: true), die Kopie synchronisiert sich bei jedem Byte-Unterschied neu statt über ein mtime-Gate (sodass ein git stash pop funktioniert), und die Quelldatei wird beobachtet, sodass ein Speichern das Plugin per Hot-Reload neu lädt. Normale installierte Pakete behalten die mtime-Regel. hermes plugins doctor <id> sagt jetzt klar, dass der Desktop eine veraltete Kopie ausführt, benennt sie und sagt dir, wie du sie aktualisierst (Rescan oder Neustart).

Status: auf main, noch in keinem Tag

Um nicht missverstanden zu werden: #135030, #135017 und #135033 sind alle am 8. Oktober in main gemergt und nicht im Tag v0.21.6 — der Tag wurde vor ihnen geschnitten. Eine Git-Installation, die main trackt, hat den neuen Auflöser heute; alles, was das Tag fährt (Docker-Images eingeschlossen), noch nicht. Der Check ist der Befehl von oben: Wenn hermes plugins enable touchdesigner durchläuft, hast du den neuen Code; wenn er No plugin named sagt, den alten.

Die Kurzfassung

  • Die Symptome waren „installiert sauber, verweigert aber die Verwaltung“ und „die Desktop-Hälfte leuchtet nie auf“; die Ursache waren fünf Auflöser mit unterschiedlichen Regeln.
  • Es gibt jetzt einen Auflöser, _find_plugin_entry, mit der Reihenfolge Key/Manifest-Name → eindeutiges Blatt → Katalog-Name aus dem Installations-Record, und er schlägt bei Mehrdeutigkeit fehl, statt zu raten.
  • Katalog-Namen lösen über den eigenen Record des Installers auf, sodass ein fremdes Repo einen Katalog-Eintrag nicht imitieren kann.
  • Ein Schalter pro Paket; Ausschalten bleibt pro Hälfte, denn die Desktop-Hälfte wird über Profile hinweg geteilt.
  • Die Desktop-Plugin-Entwicklung über einen Symlink funktioniert jetzt, und hermes plugins doctor weist auf eine veraltete Kopie hin.

Wenn deine Plugins sauber installiert wurden und du keine Desktop-Plugins baust, ist diese Änderung „mehr vorhersehbares Verhalten“, keine Migration. Für mehr Tiefe lies Plugin-Isolation in einen Hostprozess und das MCP-Trust-Gate für portable Pakete — diese beiden drehen sich darum, wo der Code läuft und was er anfassen darf; dieser hier darum, wie er heißt.