v0.21.5

Hermes Agent v0.21.5


Resumen

v0.21.5 — otro tag que existe para los consumidores posteriores. Publicada el 24 de septiembre de 2026. Desde la v0.21.4: 1.610 commits sin merge · 4.828 archivos modificados (+164.132 / −149.440) · 460 PR fusionados · 475 incidencias cerradas, medido en el commit f97608f178d1ffeca59860195ab7da295f7c8e5f.

El tag está ahí por el mismo motivo que la v0.21.4: las imágenes Docker, Hermes Cloud y los despliegues alojados se construyen desde el tag de release más reciente, así que las correcciones de esta ventana tienen que aterrizar en una versión estable antes de que los usuarios de la nube puedan recibirlas. El cuerpo del release lo dice sin rodeos: el texto completo y curado se aplaza a la v0.22.0, que documentará todo desde la v0.21.0 en adelante: destacados, áreas funcionales y créditos completos de contribuidores. Nada de esta ventana se queda fuera.

Hasta entonces, esta página deja constancia de para quién es el tag y de los cambios de la ventana que ya están corriendo en tu máquina.


Destacados

1. El escritorio recibe catálogos completos en francés, alemán y español, además de un ajuste de dirección del texto

El escritorio ahora incluye catálogos de interfaz completos en francés, alemán y español, listados en Ajustes → Apariencia → Idioma. Los tres catálogos traducen todas las claves de en.ts, incluidos todos los rótulos y descripciones de ajustes de defineFieldCopy y la rotación de introducción que solo se muestra en pantalla (intro-{fr,de,es}.ts). Se registran como superposiciones de defineLocale() igual que ja, ar y ru, así que una clave que se añada al inglés más tarde cae al inglés en lugar de romper el typecheck de todos los contribuidores. El backend ya incluye locales/{fr,de,es}.yaml; display.language: fr|de|es, junto con alias regionales como fr-CA, de-AT y es-419, ahora resuelven al catálogo correspondiente en lugar de caer al inglés. El francés usa vous, el alemán usa Sie y el español usa el imperativo neutro tú. Los nombres propios y los identificadores técnicos (Hermes, MCP, API, OAuth, IDs de modelo, variables de entorno, nombres de archivo) se quedan sin traducir. PRs #120829.

Junto a esto llega Apariencia → Visualización del chat → Dirección del texto, con tres opciones: Automática / De derecha a izquierda / De izquierda a derecha. Hasta ahora, cada bloque de prosa del chat y cada línea del compositor elegían su dirección base a partir de su primer carácter fuerte (UAX#9 plaintext), y eso no distingue un párrafo en árabe que abre con un nombre en latín de un párrafo en inglés que cita árabe. Automática es exactamente el comportamiento anterior: ningún atributo nuevo, ninguna clave de almacenamiento; elegir RTL o LTR aplica un único dir a la prosa del chat y a los dos compositores, incluida cada línea de un borrador de varias líneas. El código se queda en LTR (los bloques de código, el código inline y KaTeX siguen aislados) y el cromo de la aplicación (barra lateral, barra de herramientas, botones del compositor) no se voltea. Es solo presentación: no entran caracteres de control bidi en los borradores, en los mensajes enviados ni en el texto copiado. La preferencia la gestiona el renderer, no es una clave de config.yaml. PRs #120822.

2. Las entregas de webhook pueden espejarse en la session del chat de destino

Por defecto, una entrega es fire-and-forget: cada evento corre en su propia session, así que responder «¿o sea que ya salió?» en ese chat te devuelve un «¿a quién te refieres?». Ahora una ruta puede activarlo con mirror_to_session: true (o hermes webhook subscribe … --mirror-to-session), y tras un envío correcto el texto entregado se añade a la session del chat de destino como [Webhook delivery: <route>] más el mensaje, en forma de turno de usuario etiquetado y con autoridad de usuario: la misma ruta y la misma convención de rol que los briefs de cron continuables (gateway.mirror.mirror_to_session). Desactivado por defecto; solo un booleano true real lo activa. El espejo corre dentro de _profile_scope(profile), así que una ruta /p/<profile>/ escribe en el state.db de ese profile y nunca en el de otro. Es best-effort: nunca hace fallar la entrega, y se omite cuando el chat aún no tiene session de gateway. En una ruta deliver_only el texto espejado es el payload renderizado en bruto, así que actívalo solo para fuentes que te parezca bien tener en tu conversación. PRs #120825.

3. Bot Screen llega a las imágenes alojadas mediante los tags -desktop

Los despliegues alojados e inmutables ahora obtienen Bot Screen descargando un tag de imagen -desktop. La imagen publicada sale sin Xvnc/Xfce y no puede instalarlos en tiempo de ejecución (hermes sin privilegios, sin sudo, /opt/hermes sellado), así que la capa de la imagen es la única vía de entrega. :latest / :main / :v* son la imagen que ya conoces; :latest-desktop / :main-desktop / :v*-desktop añaden los paquetes y el Chromium con interfaz de Playwright. Un manifiesto por variante, de modo que un fallo de publicación del escritorio no puede saltarse el :latest de la imagen slim. Los contenedores no tienen logind, así que XDG_RUNTIME_DIR=/tmp/hermes-runtime se crea en el arranque con permisos 0700 y propietario hermes; el antiguo fallback $HOME/.cache acababa en el volumen compartido /opt/data, donde dos instancias se peleaban por un mismo bloqueo de display, y un directorio con otro propietario o que sea un symlink se rechaza en lugar de aplicarle un chown. PRs #120914; la documentación de la función es website/docs/user-guide/features/bot-screen.md.

4. Añade un modelo por slug desde cualquier selector de modelos

El menú de modelos del compositor, el diálogo de cambio de modelo, Ajustes → Modelo y la lista de edición de modelos ya pueden añadir un modelo por slug: una nueva fila al pie, Añadir modelo personalizado…, convierte el cuadro de búsqueda en un campo de slug, ofrece una fila por cada provider configurado mientras escribes, y el slug recordado se queda en la lista como una fila normal con su × para quitarlo. PRs #120693.

5. Las voces locales dejan de recargarse en cada activación

tts.keep_warm_seconds (por defecto 60) es nuevo: después de liberar el último lease, la descarga espera ese tiempo, y cualquier interruptor que vuelva a encender la voz dentro de la ventana lo cancela y reutiliza el modelo ya cargado. 0 descarga de inmediato. Esto importa sobre todo en las conversaciones de voz que solo usan la palabra de activación, que terminan justo después del calentamiento de ~2 s de Piper: la voz se desalojaba milisegundos después de cargarse, así que cada activación pagaba la recarga completa. PRs #121080.

6. El bucle de voz del escritorio empieza a leer tu configuración

Leer en voz alta ya no pronuncia marcadores en inglés como «code block omitted» o «link», y ya no se salta las tablas Markdown: los bloques de código y las URL se quedan en silencio, los tokens de enlace a archivos MEDIA: nunca llegan a la voz (la puntuación que sigue justo a un token inline sí se conserva), un dos puntos al final de línea que quedaba colgando porque se eliminó el bloque siguiente pasa a ser un punto, y las tablas leen su fila de cabecera («Item, Value, Notes.») saltándose las filas del cuerpo. PRs #120752. La parada hablada ahora lee voice.stop_phrases (por defecto ["stop"]): antes comparaba contra una lista fija en inglés, así que un usuario ruso que decía «отбой» como indicaba el aviso lo enviaba al agent como prompt; además, la locución completa tiene que ser igual a una frase, y [] desactiva la parada hablada. La reproducción con barge-in ya no usa un disparador fijo de 0.14, que quedaba por encima del nivel de voz de unos auriculares Bluetooth HFP mientras se reproduce una respuesta, y por eso hablar encima de la respuesta nunca la interrumpía; ahora usa el valor existente voice.barge_in_threshold_multiplier (por defecto 3.0). PRs #120751.

7. Los atajos de voz con teclas de función se pueden reasignar

El dictado puntual del compositor es ahora una acción de teclado composer.dictate sin asignar, separada de la conversación de voz, y reutiliza la ruta de captura canónica del escritorio para teclas F, Bloq Mayús, Suprimir, Espacio y combinaciones con modificadores. El campo obsoleto voice.record_key del escritorio se elimina, con la nota de que esa configuración sigue siendo solo para CLI/TUI. El arbitraje de Electron hace que una F12 asignada llegue al renderer mientras que una F12 sin asignar conserva el comportamiento de DevTools. PRs #120928. En la misma ventana se arreglaron los flujos con teclas de función en macOS: el parser de la TUI mantiene intactas las secuencias de terminal F1–F12 (incluidas las rutas del protocolo de teclas modificadas), y se añadió Ctrl+R como alternativa portátil a F7 para el dock de trabajo en vivo, tanto en la TUI como en la CLI clásica. PRs #120666.

8. Una tanda de fallos que solo aparecen en Windows localizado

Cuatro problemas independientes, todos solo de Windows:

  • El crash del nombre de zona horaria. Windows devuelve los nombres de zona en la página de códigos ANSI (Paris, Madrid (heure d'été) como bytes cp1252). Cuando LC_CTYPE es UTF-8, esos bytes se decodifican con surrogateescape, de modo que tzname() acaba conteniendo surrogates sueltos; datetime.strftime inserta tzname() en el formato como UTF-8, así que %Z lanza UnicodeEncodeError dentro de strftime, antes de que exista ninguna cadena que la pasada _sanitize_surrogates ya existente pueda limpiar. El system prompt se reconstruye en cada inicio de conversación y en cada compactación, así que el fallo volvía una y otra vez. El nuevo hermes_time.safe_strftime() es idéntico byte a byte a strftime en locales sanos (se mantiene el invariante de la caché de prefijo) y, en caso contrario, decodifica los bytes con escape de surrogate a través de la página de códigos ANSI. PRs #120748.
  • Decodificación de procesos hijo en Windows chino. En Windows, _readerthread de subprocess.communicate() decodifica la salida del proceso hijo con subprocess._text_encoding() — locale.getencoding(), cp936/GBK en Windows en chino — siempre que una captura pase text=True sin encoding=. Los procesos hijos en cuestión escriben UTF-8, así que cualquier byte ilegal en GBK lanza UnicodeDecodeError en ese hilo, y el threading.excepthook del proceso serve lo imprime como una línea [gateway-crash]. hermes_cli/web_routers/files.py::_fs_git_branch era uno de ellos (sus kwargs están en un literal de diccionario, así que la regla por líneas de text=True nunca los encontraba) y el escritorio lo consulta continuamente. PRs #120749.
  • Rutas absolutas de Windows. C:\...\report.html y las rutas UNC \\server\share\... no se reconocían como rutas (así que no aparecía ninguna etiqueta de artefacto) ni las dejaba tranquilas el fallback del renderer, que trataba como relativo todo lo que no empezara por / y lo unía al cwd, produciendo rutas como /repo/C:\...; después, el rótulo de vista previa interpretaba C: como esquema de URL. El nuevo isWindowsAbsolutePath acepta una letra de unidad con cualquiera de las dos barras y un prefijo UNC. PRs #121074.
  • La sonda de arranque en frío. En un arranque en frío de Windows, el backend local responde por HTTP y luego su bucle de eventos se queda bloqueado entre 12 y 28 s mientras el GIL está retenido importando los módulos de plataforma del gateway. La sonda WebSocket del arranque se rendía tras un presupuesto fijo de 10 s, marcaba un fallo de arranque, derribaba el proceso hijo y acababa en la cascada «Ignoring stale Hermes backend exit (1)». Un backend lanzado por esta aplicación ahora sigue esperando más allá de los 10 s base mientras el proceso hijo siga vivo, hasta un tope de 90 s: el mismo presupuesto que el paso de anuncio de puerto ya concede a un arranque en frío. La señal de progreso es la vitalidad del proceso, no la frescura de su salida, porque el bloqueo es silencioso. Los gateways remotos, el botón «Test remote» y el enganche al backend del host mantienen el presupuesto fijo de 10 s. PRs #120750.

Mejoras

  • Toda la oleada del SDK de plugins del escritorio (API de borradores del compositor, slots de lista de sessions y de decoración de filas, preferencias de navegación de la barra lateral, etiquetas de las píldoras de modelo, puentes tipados de ajustes/skills/toolsets/profiles, una primitiva de embed en sandbox, un slot de ajustes de apariencia y un puente público de eventos para backends de plugins) aterrizó en esta ventana; su texto previsto también se aplaza.
  • La página de Conectores sustituyó a la pestaña MCP, con «Conectar ahora» para los servidores MCP de un plugin recién instalado, y las herramientas y skills de los plugins instalados pasan a estar activas en todos los chats abiertos; el onboarding ofrece plugins del catálogo junto a los conectores.
  • Kanban recibió una pasada de diseño: un modal de ticket a dos columnas y texto de tareas en markdown.
  • La ventana también trae una larga serie de trabajo de rendimiento en rutas calientes: carga de configuración, registro de herramientas, manejo de mensajes del gateway y selector de modelos.

Actualización

hermes update                 # instalaciones por git
# o vuelve a ejecutar el instalador de una línea

Docker / Hermes Cloud: las imágenes se construyen desde este tag, nousresearch/hermes-agent:v2026.9.24.

Changelog completo: v2026.9.21…v2026.9.24

Correspondencia de versiones: el tag se basa en la fecha (v2026.9.24) y la versión semántica es v0.21.5 — el mismo release.

← Hermes Agent Changelog