Plugins que solo funcionaban a medias: Hermes por fin unificó la resolución de nombres de plugins

Abres el hub de plugins de Hermes Desktop e instalas un plugin que trae un panel de escritorio. El interruptor del Agent se enciende, pero el panel de al lado nunca lo hace — te pones a investigar y las herramientas están, los comandos están, la interfaz sencillamente no. O algo más simple: hermes plugins install hermes-memory-wiki dice que ha funcionado, continúas con hermes plugins enable hermes-memory-wiki, y responde No plugin named hermes-memory-wiki. Acabas de instalar la cosa y asegura que no conoce el nombre. No lo recordabas mal — Hermes tenía tres resolutores de nombres que no coincidían entre sí. El PR #135030, fusionado el 8 de octubre, lo dejó limpio.
El síntoma: un plugin, tres nombres
Empecemos por los síntomas, porque estos tres errores parecen no tener relación y comparten una misma causa raíz:
- Se instala bien, se niega a dejarse gestionar. El nombre con el que queda una instalación de catálogo y el nombre del propio manifest del plugin pueden diferir. Las cifras son concretas: 17 de 484 entradas del catálogo se instalan con un nombre de manifest distinto —
hermes-memory-wiki→memory-wiki,touchdesigner→td,hermes-subscription-meter→subscription-meter.installresuelve el nombre de catálogo, así que funciona; peroenable,disable,show,removeyupdateresolvían el nombre de manifest o la key, así que todos fallaban con ese mismo nombre. - Un paquete se muestra como dos filas. Clónalo tú mismo en
~/.hermes/plugins/hermes-subscription-meter, como sugiere el propio README del plugin, y el hub de escritorio muestra dos filas: una mitad de escritorio suelta que ofrece «Instalar aquí» para una mitad de agent que ya está instalada, más una fila de agent aparte. El lado de escritorio unía la fila del agent por nombre de manifest y su propia mitad por nombre de carpeta. - El interruptor solo cubre una mitad. La mitad de escritorio se entrega como opt-in, así que encender el interruptor del Agent nunca encendía con él la mitad de escritorio.
Lo que tienen en común esos tres: el nombre se resolvía en varios sitios, con reglas distintas.
La causa: cinco resolutores, cinco conjuntos de reglas
Antes del cambio había cinco sitios que resolvían el nombre de un plugin, repartidos en tres rutas:
| Dónde | Con qué coincidía |
|---|---|
hermes_cli/plugins_cmd.py _resolve_plugin_key_and_source, con copias en _find_plugin_entry, _require_installed_plugin y _user_installed_plugin_dir |
nombre de manifest / key canónica / leaf único / nombre de carpeta — cada uno con un subconjunto admitido distinto |
plugins_cmd_install.py _catalog_install_on_disk |
el nombre de catálogo, a través del registro de instalación — solo en el momento de la instalación |
Escritorio plugin-packages.ts vs electron/desktop-plugins-root.ts |
uno unía por nombre de manifest, el otro por el basename de la carpeta |
«Cada uno con un subconjunto admitido distinto» es la parte que de verdad dolía. El bug no era que un nombre no funcionara; era que dos comandos consecutivos de la misma secuencia no coincidían en las reglas, y así es como acabas con un install correcto seguido de un enable que falla.
Ahora: un solo resolutor, un solo interruptor
Después del cambio:
_find_plugin_entryes el único resolutor, y lo usan todos los verbos (enable, disable, show, capabilities, remove, update, y tanto el dashboard como el hub de escritorio). Prueba primero la key canónica o el nombre de manifest, luego un leaf desnudo único (langfuse→observability/langfuse), y después el nombre de catálogo con el que se instaló el plugin. Además hace una promesa: nunca elige a ciegas entre dos candidatos — ante la ambigüedad falla en lugar de escoger uno.- La coincidencia por nombre de catálogo lee el propio registro del instalador (
.install-metadata.json), nunca un archivo dentro del repositorio del plugin. Eso importa: un repo instalado por URL no puede reclamar el nombre de una entrada del catálogo usando algo que él mismo ha traído. - Un interruptor enciende todo un paquete. «Habilitar el plugin del agent después de instalar» en el diálogo de instalación (o el interruptor del Agent de la fila) ahora enciende también la mitad de escritorio — salvo que hayas apagado tú esa mitad. No funciona al revés: apagar sigue siendo por mitad, porque la mitad de escritorio es compartida por todos los profiles.
En la línea de comandos, ahora se resuelven los dos nombres:
# el desajuste clásico: la entrada touchdesigner se instala como td
hermes plugins install touchdesigner
hermes plugins enable touchdesigner # igual que: hermes plugins enable td
# enciende el interruptor en el momento de instalar y sáltate el segundo paso
hermes plugins install touchdesigner --enable
Antes, solo hermes plugins install <name> aceptaba el nombre de catálogo y los demás verbos daban error; ahora enable / disable / show / capabilities / update / remove aceptan cualquiera de los dos. Nuestra referencia del comando hermes plugins es la lista de flags, y el artículo del catálogo de plugins cubre el modelo de confianza que hay detrás de las entradas.
También arreglado: «edité el código y no cambió nada»
El mismo lote incluye el PR #135017, que resuelve la misma clase de problema desde el punto de vista de quien desarrolla: editas desktop/plugin.js y el Desktop sigue ejecutando una copia.
Electron copia plugins/<name>/desktop/ a desktop-plugins/<name>/, y el renderer carga esa copia. Solo vuelve a copiar cuando el mtime del plugin.js de origen es más reciente y algo lo pide explícitamente; el watcher de recarga en caliente vigila la copia, nunca el origen. Peor aún: el loader de Python sigue un symlink dentro de plugins/, pero el código de reconciliación listaba los paquetes con isDirectory() — y un symlink no es un directorio — así que la configuración de desarrollo más natural, un checkout con symlink, nunca llegaba a copiar su mitad de escritorio.
Lo que hace ahora:
# trata esto como modo dev
ln -s ~/src/my-plugin ~/.hermes/plugins/my-plugin
# ¿copia instalada editada in situ? deja que doctor te diga si ejecutas una obsoleta
hermes plugins doctor my-plugin
Los paquetes con symlink se reconocen (el marcador lleva linked: true), la copia se resincroniza ante cualquier diferencia de bytes en lugar de por un umbral de mtime (así git stash pop funciona), y se vigila el archivo de origen para que guardar recargue el plugin en caliente. Los paquetes instalados de forma normal mantienen la regla del mtime. hermes plugins doctor <id> ahora dice claramente que el Desktop ejecuta una copia obsoleta, la nombra y te indica cómo refrescarla (reescanear o reiniciar).
Estado: en main, todavía en ningún tag
Para que no haya dudas: el #135030, el #135017 y el #135033 se fusionaron en main el 8 de octubre y no están en el tag v0.21.6 — el tag se cortó antes que ellos. Una instalación por git que siga main tiene el nuevo resolutor hoy; cualquier cosa que ejecute el tag (imágenes Docker incluidas) todavía no. La comprobación es el comando de antes: si hermes plugins enable touchdesigner pasa, tienes el código nuevo; si dice No plugin named, tienes el código antiguo.
Para terminar
- Los síntomas eran «se instala bien, se niega a dejarse gestionar» y «la mitad de escritorio nunca se enciende»; la causa eran cinco resolutores con reglas distintas.
- Ahora hay un solo resolutor,
_find_plugin_entry, con el orden key/nombre de manifest → leaf único → nombre de catálogo del registro de instalación, y falla ante la ambigüedad en lugar de adivinar. - Los nombres de catálogo se resuelven a través de el propio registro del instalador, así que un repo ajeno no puede hacerse pasar por una entrada del catálogo.
- Un interruptor por paquete; apagar sigue siendo por mitad, porque la mitad de escritorio se comparte entre profiles.
- El desarrollo de plugins de escritorio con symlink ahora funciona, y
hermes plugins doctorte señala una copia obsoleta.
Si tus plugins se instalaron sin problemas y no estás construyendo plugins de escritorio, este cambio es «comportamiento más predecible», no una migración. Para profundizar, lee el aislamiento de plugins en un proceso host y la puerta de confianza MCP para paquetes portables — esos dos tratan sobre dónde se ejecuta el código y qué puede tocar; este iba de cómo se llama.