CommunityProgramación y desarrollogithub.com

bitekas/servidor-jerarquia-web

Convierte toda la jerarquía de delegación de una sesión de agente en una página web EN VIVO y autoactualizable para un humano que no puede ver la terminal.

¿Qué es servidor-jerarquia-web?

Esta habilidad toma los registros de transcripción de sesiones de agentes (como Claude Code) y los convierte en una página web interactiva y en vivo. Un servidor Node.js (serve.mjs) lee las transcripciones generadas por el agente, construye un gráfico de árbol de procesos que muestra la sesión raíz y todos los subagentes (incluyendo tipo, descripción de la tarea, modelo, tiempo de inicio/fin, estado vivo o detenido, y la última línea de registro), y sirve una única página HTML autónoma en GET /. Además, ofrece un endpoint JSON GET /state que la página consulta cada 10 segundos para actualizarse sin recargar. La página incluye un interruptor para mostrar todos los agentes o solo los activos. El árbol de agentes se sirve completo, mientras que los registros de cada agente se obtienen bajo demanda mediante GET /agent/<id> cuando el usuario selecciona un agente. Esto evita que la página sea excesivamente pesada, ya que incluir todos los registros de todos los agentes en cada actualización podría generar documentos de cientos de megabytes. La habilidad está diseñada para ser instalada como un servicio persistente (systemd en Linux o LaunchAgent en macOS) o ejecutada como un proceso independiente. También incluye un modo de generación estática (build.mjs) para crear una instantánea congelada de todos los registros en un solo archivo, útil como registro histórico. La página muestra un gráfico de árbol de procesos, nombres y avatares de agentes, descripciones de tareas, modelos y la última línea de registro en tooltips. La configuración se realiza mediante un archivo config.json que define el puerto, directorio de transcripciones y otras opciones.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/bitekas/agent-fleet/tree/HEAD/.claude/skills/web-hierarchy-server

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Servidor de jerarquía web

El humano para el que trabaja un agente a menudo está en otro dispositivo y no puede ver la terminal de los subagentes. Esto convierte todo el árbol de procesos en una página en vivo que abren una vez y observan: un gráfico clicable en la parte superior, el registro completo del agente seleccionado abajo.

El contexto de instalación de esta habilidad - el directorio fleet where web-hierarchy-server imprime - contiene los dos archivos de esta instalación: CONTEXT.md nombra al agente, su puerto y su URL, y config.json contiene todos los valores que los scripts leen. config.example.json junto a este archivo documenta cada clave. Los scripts encuentran config.json preguntando a ese mismo comando, o leen el archivo que WEB_HIERARCHY_CONFIG nombra cuando está configurado; una ruta relativa dentro de él se resuelve contra su propio directorio.

Ejecute las comprobaciones y scripts por ruta:

"${CLAUDE_SKILL_DIR}/installation/check"     # ¿hay un servidor levantado para este directorio de configuración? ¿está actualizado?
node "${CLAUDE_SKILL_DIR}/serve.mjs" --help  # la configuración, la raíz de transcripción y el puerto que usaría

La única regla fija: UN servidor por directorio de configuración

Un directorio de configuración (CLAUDE_CONFIG_DIR) es la raíz de transcripción de un agente, y exactamente un servidor lo sirve. Un segundo servidor en el mismo puerto es el fallo que esta regla existe para prevenir: cualquier proceso que gane el enlace sirve la página, el otro muere con EADDRINUSE, y desde el lado del visor la página se ve bien de cualquier manera. Antes de iniciar uno, establezca que ningún servidor ya está activo para ese directorio de configuración (installation/check lo responde y nunca inicia nada).

La forma duradera es una unidad de servicio (installation/web-hierarchy-server.service.example): inicio con la máquina, Restart=always, activo desde el arranque antes de que se delegue cualquier trabajo, y que sobrevive a reinicios. Instalarlo requiere privilegios de root, que es un paso del humano. En macOS es un LaunchAgent en el dominio gui del usuario (installation/web-hierarchy-server.plist.example), instalado sin sudo. Donde nadie lo ha ejecutado, el servidor se ejecuta como un proceso separado iniciado una vez (setsid nohup ... & en Linux, nohup ... & en macOS, que no tiene setsid, por lo que sobrevive a la sesión que lo lanzó) - esa forma funciona y no sobrevive a un reinicio, que es exactamente lo que installation/check dice en palabras en lugar de reportar un verde.

Las ediciones a serve.mjs o format.mjs son LATENTES por diseño. node las carga una vez al iniciar el proceso, por lo que un cambio aplicado llega a una página en ejecución solo en el siguiente reinicio. Eso es esperado; no es una razón para reiniciar un servidor que un visor está viendo, y ES la razón por la que un informe de "mi arreglo no funcionó" suele deberse a un proceso en ejecución que mantiene el código antiguo.

El árbol se SIRVE; los registros se OBTIENEN

Esta es la forma de todo el sistema, y es una regla de tamaño, no de estilo.

El ÁRBOL de una sesión cuesta aproximadamente un kilobyte por agente, independientemente de lo que ese agente haya hecho. Sus REGISTROS no tienen límite, y renderizar uno arrastra sus imágenes consigo. Poner el registro de cada agente en cada instantánea hace que GET / para una sesión de 228 agentes sea 358 MB, 110 MB de los cuales son datos de imagen en base64, reenviados en cada sondeo de 10 s. Nada está roto, y la página no se puede abrir.

Por lo tanto, la carga útil se divide, y cada ruta lleva un tipo de cosa:

RutaLlevaCostes
GET /todo el árbol, el registro de la sesión RAÍZ y las imágenes de ese contenido en línea como URI de datosun documento, completo sin un servidor
GET /statetodo el árbol y SIN registros (?logs=<id>,<id> nombra excepciones); ?view=active reduce el GRÁFICO a la vista activalo que paga el sondeo de 10 s, cada tick, para siempre
GET /agent/<id>exactamente el registro renderizado de ese agenteuna obtención, cuando el visor selecciona un bloque
GET /agent/<id>?since=<n>solo eventos desde el índice nun array vacío para un agente silencioso
GET /img/<hash>una imagen por hash de contenido, immutableuna vez por imagen por visor, luego en caché

Dos consecuencias que vale la pena conocer antes de cambiar algo aquí:

  • since indexa el propio count del árbol. Una fila del árbol lleva el recuento real de eventos del agente, incluso si su registro no se renderizó, y ese número es lo que un cliente devuelve como since. Ambos salen del mismo análisis de transcripción, y deben hacerlo: derivar uno de ellos en otro lugar y un cliente silenciosamente salta o repite eventos, sin nada que ver excepto un registro sutilmente incorrecto.
  • GET /img/<hash> está direccionado por contenido, por lo que una respuesta se almacena en caché para siempre. Por eso un hash desconocido es un 404 y nunca un 200 vacío - un 200 vacío es una imagen permanentemente rota para ese visor, y ninguna recarga lo arregla.

Nada se vuelve rojo si se pierde esta división. Ponga todos los registros en GET /state, o imágenes en línea en cada instantánea, y el árbol es correcto, las etiquetas son correctas, la página se renderiza, y tiene cientos de megabytes. El TAMAÑO es la propiedad protegida; las pruebas de tamaño en serve.test.mjs (6-9) son lo único que puede ver su regresión, y cada una es un PAR - ausente aquí, presente allí - porque "X está ausente" es verdadero por todas las razones equivocadas.

Lo que una página GUARDADA tiene, y lo que no

GET / es un documento HTML autónomo: CSS y JS en línea, sin activos externos, y una instantánea incorporada que se renderiza sin ningún servidor. Lo que está horneado es todo el árbol y el registro de la sesión raíz, con las imágenes de ese contenido como URI de datos.

No horneado: el registro de todos los demás agentes. Esos no están en el documento; llegan desde /agent/<id> al seleccionar. Por lo tanto, una página guardada en disco, o una cuyo servidor ha muerto, muestra el gráfico completo y el registro de la sesión raíz, y no muestra nada cuando se hace clic en un bloque de subagente. Las imágenes dentro de esos registros siguen la misma historia: se renderizan desde /img/<hash> y no hay un servidor detrás de un archivo guardado.

Para un registro congelado de TODOS los registros en un solo archivo, use build.mjs - la horneada estática incorpora en línea el registro de cada agente y cada imagen, porque un recuerdo no tiene nada que obtener. Ese es el único llamante que pide a snapshot() todos los registros (logsFor: null).

Cómo sigue las sesiones

Un servidor tiene una larga vida; una sesión de Claude no: finalizar una sesión, una actualización y un reinicio de la unidad crean un nuevo id de sesión. Por lo tanto, el servidor vuelve a elegir la sesión más recientemente activa en cada instantánea en lugar de resolver una al inicio, y sirve una página de "esperando una sesión..." mientras no exista ninguna - el estado normal al arrancar. "Más recientemente activa" incluye la transcripción RAÍZ, no solo los archivos de subagente: una sesión recién iniciada que aún no ha delegado se sigue inmediatamente y se renderiza como un árbol solo raíz, y el árbol de procesos crece desde su primera delegación. No es necesario reiniciar nada cuando comienza una nueva sesión; la página detecta el cambio de id y reconstruye sus registros.

--session=<id> fija una sesión en su lugar (una vista congelada para depuración).

Se escanea cada slug de proyecto bajo el directorio de configuración, no solo uno. La transcripción de una sesión se ubica bajo el slug de proyecto de su CWD, por lo que una sesión que trabaja en un worktree de git escribe bajo el slug del WORKTREE; fijar un slug hace que cada sesión de worktree sea invisible mientras la página aún se renderiza perfectamente.

Qué agente dice estar mostrando una página

El bloque raíz está etiquetado con el miembro del fleet al que pertenece el directorio de configuración, resuelto a través de config.json's fleet.by_config_dir. La clave se deriva, no es el nombre base sin procesar, y la derivación es la parte que vale la pena leer:

fleetKey() de format.mjs toma el nombre base del directorio de configuración, EXCEPTO cuando ese nombre base es el literal .claude, donde toma el nombre base del directorio PADRE en su lugar. Un fleet que da a cada agente un directorio de inicio coloca cada directorio de configuración en <home>/<agent>/.claude, por lo que si se usara el nombre base, todos ellos chocarían en una sola entrada y la página de cada agente se etiquetaría con el agente que nombra esa entrada.

Ese fallo es silencioso: la página se renderiza completamente y dice el nombre equivocado, y un visor con dos páginas abiertas no puede distinguirlas y no sabe que no puede. Una entrada literal .claude se honra como respaldo, por lo que una instalación de un solo agente con clave .claude mantiene su etiqueta; no puede distinguir dos agentes y no debe usarse donde exista más de uno. La misma clave derivada elige el puerto predeterminado de config.json's servers, por la misma razón: una coincidencia de nombre base daría a cada agente el puerto de la primera entrada.

Archivos

  • serve.mjs - el servidor en vivo: GET /, GET /state, GET /agent/<id> y GET /img/<hash> (vea la tabla de rutas arriba). La herramienta principal. --port=N, --host=<addr>, --cap=KB, --session=<id>, cada uno con valor predeterminado de config.json; --help imprime el uso, la ruta de configuración resuelta, la raíz de transcripción y el puerto predeterminado, y no enlaza nada. --port=0 toma un puerto efímero y el banner imprime el que realmente obtuvo.
  • format.mjs - el formateador compartido: análisis de transcripción, diseño del árbol de procesos, HTML por evento, el CSS del tema, y el único lugar donde se leen valores locales (cfg() sobre el config.json que fleet where nombra, o sobre WEB_HIERARCHY_CONFIG cuando está configurado).
  • build.mjs - instantánea ESTÁTICA opcional (node build.mjs <out.html>): hornea el estado actual en un archivo autónomo para un registro congelado y compartible. Úselo para un recuerdo, no para la vista en vivo.
  • identities.json + avatars/ - el registro de nombres para mostrar, usado a menos que identities.file nombre otro.
  • serve.test.mjs + fixtures/ - la prueba: inicia un servidor real en un puerto efímero contra un árbol de transcripción sintético, afirma el árbol, conduce cada ruta (árbol sin registros, el registro de un agente y de nadie más, ?since enviando solo lo que es nuevo, una imagen servida byte por byte por hash), y luego ELIMINA la transcripción de un subagente y requiere que el árbol Y la ruta por agente lo pierdan. Su encabezado establece por qué cada prueba está allí, de qué par es mitad cada prueba de tamaño, y por qué el control negativo es la parte que soporta la carga. La prueba 11 requiere que GET /state?view=active elimine el bloque detenido que la vista predeterminada dibuja.
  • view.test.mjs - la regla "solo activos" por sí sola (filterView() sobre un árbol sintético): bloques vivos, los bloques encima de ellos y la raíz, con los hermanos e hijos detenidos junto a ellos eliminados, y "todos" devolviendo cada fila sin cambios.
  • format.test.mjs - cómo se encuentra y lee la configuración: a través de fleet where cuando WEB_HIERARCHY_CONFIG no está configurado (un fleet falso seleccionado por FLEET_BIN), la variable ganando, un fleet fallido retransmitido en lugar de adivinado, rutas relativas, el registro predeterminado y packages.dir_pattern.
  • installation/ - la plantilla de unidad, el procedimiento de instalación y check.

Lo que muestra la página

Se lee como la consola de Claude Code en el tema oscuro para daltonismo (dark-daltonized) - la paleta tomada textualmente del paquete de Claude Code (fondo negro, texto monoespaciado blanco, viñetas de herramientas en azul éxito #3399ff, errores #ff6666, líneas de resultado atenuadas con expansores "... +N líneas", marcadores de pensamiento, mensajes de tarea y usuario enmarcados como la entrada de la consola).

  • Gráfico de árbol de procesos. La sesión raíz es el nodo superior; cada agente sin padre cuelga de él. Azul = vivo (con un arco giratorio junto al rango de tiempo; estático bajo prefers-reduced-motion), gris = detenido. Haga clic en un bloque para mostrar el registro completo de ese agente abajo.
  • Un interruptor "todos / solo activos" encima del gráfico. "todos" es el predeterminado y dibuja cada bloque. "solo activos" dibuja los bloques azules, cada bloque encima de uno de ellos, y la raíz: la bandera de vivo es la que colorea un bloque, por lo que el interruptor nunca discrepa de los colores. Los bloques encima permanecen porque un padre esperando a un hijo largo no escribe nada en su propia transcripción y se lee como detenido mientras el hijo se ejecuta; sin ellos el hijo colgaría de la nada. Con nada vivo, la vista es la raíz sola. El interruptor reduce solo el GRÁFICO: el sello aún cuenta toda la sesión (más "N mostrados"), y un enlace desde o #a-<id> a un agente oculto aún abre su registro. La elección se recuerda por visor en el localStorage del navegador; donde el almacenamiento está bloqueado, el interruptor aún funciona para esa visita. filterView() de format.mjs es la regla; view.test.mjs la fija.
  • Descripción de tarea por agente en cada bloque. Fuente de verdad: la description de la llamada Agent que lo creó, que el arnés copia en agent-<id>.meta.json. Respaldo cuando está ausente: la primera línea del prompt de tarea del agente. La descripción se ajusta y el bloque crece para adaptarse a ella.
  • La última línea de registro en cada bloque, como un tooltip al pasar el ratón. Con unos pocos cientos de agentes, es lo único que separa dos bloques con el mismo tipo y la misma descripción, y para un agente vivo es lo que está haciendo ahora mismo. Viaja en el ÁRBOL porque es la única pieza de contenido de registro lo suficientemente barata como para hacerlo: texto plano, limitado, nunca HTML - en el momento en que renderiza marcado arrastra imágenes y expansores a cada instantánea.
  • Modelo en cada bloque. parseTranscript lee el modelo de los turnos de asistente de la transcripción (el prefijo claude- eliminado); cuando el agente aún no ha respondido, el alias grueso en su .meta.json sirve como sustituto. Un nodo sin ninguno de los dos no renderiza ninguna línea en lugar de un marcador de posición.
  • Agentes nombrados con avatares. Cada bloque muestra un nombre, su avatar, el agentType, la descripción, el modelo y el rango de tiempo, por lo que cinco agentes concurrentes de un tipo son distinguibles de un vistazo.
    • El nombre es una etiqueta de visualización solamente: asignado desde el registro en identities.file como una función pura de (agentType, orden de spawn dentro de ese tipo), recalculado desde las transcripciones en cada actualización. Nada se almacena en ninguna parte, y los agentes no conocen sus propios nombres - el arnés posee las transcripciones y este formateador es un observador de solo lectura.
    • Orden de spawn, no un hash del id del agente: con N nombres y N agentes un hash choca por principio del palomar; el orden de spawn no puede.
    • El orden de names es crítico - solo añadir. Reordenar o insertar en medio de la lista renombra silenciosamente el gráfico de cada sesión pasada.
    • avatar = la silueta del rol; portraits = arte opcional a medida para un nombre. El avatar identifica

Skills relacionados