# 0038 — Webapps y dashboards embebidos en Element via widgets **Estado:** pendiente ## Objetivo Incorporar un servidor HTTP embebido en el launcher que sirva dashboards y mini-apps de los agentes, integrables en rooms de Element como Matrix widgets. Los usuarios podran ver estado en tiempo real, metricas e interfaces interactivas de sus agentes directamente desde sus rooms Matrix, sin salir del cliente. ## Contexto - El launcher ya arranca multiples agentes en paralelo (`cmd/launcher/main.go`) y tiene un logger centralizado con JSONL rotado por dia. - `shell/logger/query.go` ya expone `ReadLogs()` y `ReadDayLogs()` para consultar logs JSONL por agente y fecha — reutilizable para las API de metricas. - `internal/config/schema.go` define `AgentConfig` con todas las secciones; falta una seccion `WebCfg` para el servidor HTTP. - Matrix soporta widgets via state events `im.vector.modular.widgets` (Element Web y Desktop). El agente puede enviar estos state events usando mautrix-go. - Actualmente no existe `shell/web/` ni ningun endpoint HTTP en el proyecto. - El issue 0035 (audit trail + `!metrics`) agrega metricas del dia actual; este issue va mas alla con visualizacion web persistente y en tiempo real. ## Arquitectura ### Pure core / impure shell - **`pkg/`** — no se modifica. No hay logica pura nueva; la transformacion de datos de logs a metricas se puede hacer con funciones helper dentro de `shell/web/handlers.go` (son inherentemente I/O-bound: leen archivos). - **`shell/web/`** — NEW, 100% impuro: servidor HTTP, handlers API, SSE streaming, widget registration via Matrix. - **`internal/config/schema.go`** — MOD: agregar `WebCfg` al schema de configuracion. - **`cmd/launcher/main.go`** — MOD: arrancar servidor web junto con los agentes. ### Fase 1 — Servidor HTTP embebido ``` shell/web/ NEW — package del servidor web shell/web/server.go NEW — setup del servidor HTTP + routes shell/web/handlers.go NEW — handlers de los endpoints API shell/web/static/ NEW — archivos estaticos del dashboard (embed.FS) ``` Endpoints: - `GET /api/agents` — lista de agentes en ejecucion con estado (running/stopped/error) - `GET /api/agents/{id}` — detalle del agente (config filtrada, uptime, ultima actividad) - `GET /api/agents/{id}/metrics` — metricas agregadas del dia (reutiliza `shell/logger/query.go`) - `GET /api/agents/{id}/logs` — SSE stream de logs en tiempo real - `GET /dashboard` — SPA del dashboard (HTML/JS/CSS embebido via `embed.FS`) - `GET /dashboard/{id}` — vista filtrada por agente (util para widgets) ### Fase 2 — Integracion Matrix widget ``` shell/web/widget.go NEW — helper para registrar widgets en rooms Matrix ``` - Cuando un agente se une a un room, opcionalmente registra un widget via state event `im.vector.modular.widgets`. - Widget URL apunta al dashboard embebido filtrado para ese agente: `{base_url}/dashboard/{agent-id}?room={room_id}`. - Usa mautrix-go `client.SendStateEvent()` para enviar el state event. - Config: `matrix.widgets.enabled`, `matrix.widgets.base_url`, `matrix.widgets.auto_register`. ### Fase 3 — Dashboard UI ``` shell/web/static/index.html NEW — SPA entry point shell/web/static/app.js NEW — logica JS del dashboard shell/web/static/style.css NEW — estilos ``` - SPA con vanilla JS (o Preact si crece), embebido en el binario Go via `embed.FS`. - Vistas: - **Lista de agentes**: estado (running/stopped/error), tipo (agent/robot), uptime. - **Detalle de agente**: resumen de config, mensajes recientes, uso de tools. - **Log viewer en vivo**: via SSE (`EventSource` en JS), muestra logs en tiempo real. - **Graficas de metricas**: mensajes/hora, tool calls, errores, latencia LLM. ## Archivos afectados | Archivo | Cambio | Descripcion | |---------|--------|-------------| | `shell/web/` | NEW | Package completo del servidor web | | `shell/web/server.go` | NEW | Setup HTTP server, router, middleware | | `shell/web/handlers.go` | NEW | Handlers API: agents, metrics, logs SSE | | `shell/web/widget.go` | NEW | Helper para registrar widgets Matrix en rooms | | `shell/web/static/` | NEW | Dashboard SPA (HTML/JS/CSS embebido) | | `internal/config/schema.go` | MOD | Agregar `WebCfg` con Enabled, Port, Host, BasePath, Auth | | `cmd/launcher/main.go` | MOD | Arrancar servidor web junto con los agentes | | `shell/matrix/client.go` | MOD | Agregar metodo para enviar state events de widget (si no existe) | ## Tareas ### Fase 1 — Servidor HTTP embebido - [ ] **1.1** Agregar `WebCfg` a `internal/config/schema.go`: ```go type WebCfg struct { Enabled bool `yaml:"enabled"` // habilitar servidor web (default false) Host string `yaml:"host"` // bind address (default "127.0.0.1") Port int `yaml:"port"` // puerto HTTP (default 8080) BasePath string `yaml:"base_path"` // prefijo de rutas (default "/") Auth WebAuthCfg `yaml:"auth"` // autenticacion } type WebAuthCfg struct { Enabled bool `yaml:"enabled"` // requerir autenticacion TokenEnv string `yaml:"token_env"` // env var con el token de acceso } ``` Agregar campo `Web WebCfg yaml:"web"` a `AgentConfig` (o a un nuevo `LauncherConfig` si se decide no atar a cada agente). - [ ] **1.2** Crear `shell/web/server.go`: - Struct `Server` con `http.Server`, referencia a la lista de agentes en ejecucion, config, logger. - Constructor `New(cfg WebCfg, agents []AgentInfo, logDir string, logger *slog.Logger) *Server`. - Metodo `Start(ctx context.Context) error` — arranca el servidor HTTP en goroutine, se detiene con ctx. - Router usando `http.ServeMux` de la stdlib (Go 1.22+ soporta `{id}` patterns). - Middleware basico: logging, CORS (necesario para iframe de widgets), auth opcional. - [ ] **1.3** Crear `shell/web/handlers.go` — handler `GET /api/agents`: - Devuelve JSON array con: `id`, `name`, `type`, `status`, `uptime`, `description`. - La info de agentes se obtiene de un registry que el launcher puebla al arrancar. - [ ] **1.4** Handler `GET /api/agents/{id}`: - Config del agente (filtrada: sin tokens, passwords, API keys). - Uptime, ultima actividad, cantidad de mensajes procesados. - Error si el `{id}` no existe. - [ ] **1.5** Handler `GET /api/agents/{id}/metrics`: - Reutilizar `shell/logger/ReadDayLogs()` para obtener logs del dia actual. - Calcular: mensajes recibidos, comandos ejecutados, llamadas LLM (count + tokens + latencia media), tool calls (count + errores), errores totales. - Devuelve JSON con los agregados. - [ ] **1.6** Handler `GET /api/agents/{id}/logs` (SSE): - Server-Sent Events stream con los ultimos N logs y nuevos logs en tiempo real. - `Content-Type: text/event-stream`. - Tail del archivo JSONL actual con polling o fsnotify. - [ ] **1.7** Integrar arranque del servidor en `cmd/launcher/main.go`: - Leer config web (puede ser una seccion nueva en un `launcher.yaml` o reutilizar env vars). - Si `web.enabled`, crear `web.Server` y arrancarlo en el mismo `WaitGroup`. - Pasar la lista de agentes al servidor para que los pueda consultar. - [ ] **1.8** Tests: handlers con `httptest`: - Test de `/api/agents` con lista de agentes mock. - Test de `/api/agents/{id}` con agente existente y no existente. - Test de `/api/agents/{id}/metrics` con logs JSONL de ejemplo en tmpdir. - Test del middleware de auth (token valido, invalido, deshabilitado). ### Fase 2 — Integracion Matrix widget - [ ] **2.1** Investigar formato del state event `im.vector.modular.widgets`: - Campos requeridos: `type`, `url`, `name`, `id`, `creatorUserId`. - Verificar compatibilidad con Element Web 1.x actual. - [ ] **2.2** Crear `shell/web/widget.go`: - Funcion `RegisterWidget(ctx context.Context, client *mautrix.Client, roomID, widgetID, widgetName, baseURL, agentID string) error`. - Construye el state event content con la URL del dashboard filtrado. - Envia via `client.SendStateEvent(roomID, "im.vector.modular.widgets", widgetID, content)`. - Funcion `UnregisterWidget(...)` para limpiar al salir. - [ ] **2.3** Agregar seccion `matrix.widgets.*` al config: ```yaml matrix: widgets: enabled: false # habilitar registro automatico de widgets base_url: "" # URL publica del servidor web (requerido si enabled) auto_register: true # registrar widget al unirse a room widget_name: "Dashboard" # nombre visible del widget ``` - [ ] **2.4** Integrar auto-registro en el runtime: - En `devagents/runtime.go` o `devagents/handler.go`, despues de join a room, si `widgets.enabled` y `base_url` configurado, llamar a `RegisterWidget`. - Manejar error gracefully (log warning, no romper el agente). - [ ] **2.5** Tests: - Test del formato del state event generado (campos requeridos presentes). - Test de `RegisterWidget` con mock de mautrix client. - Test de la URL generada (incluye agent ID y room ID como query params). ### Fase 3 — Dashboard UI - [ ] **3.1** Crear `shell/web/static/index.html`: - HTML minimo con viewport meta, link a CSS, script tag. - Routing basico client-side (hash-based: `#/`, `#/agent/{id}`). - [ ] **3.2** Crear `shell/web/static/app.js`: - Fetch `/api/agents` y renderizar lista de agentes con indicadores de estado. - Colores por status: verde (running), rojo (error), gris (stopped). - Click en agente → navega a vista detalle. - [ ] **3.3** Vista detalle de agente: - Fetch `/api/agents/{id}` y `/api/agents/{id}/metrics`. - Mostrar: nombre, tipo, uptime, descripcion, metricas del dia en tabla. - Seccion de metricas con numeros grandes y colores. - [ ] **3.4** Log viewer en vivo: - Conectar a `/api/agents/{id}/logs` via `EventSource`. - Mostrar logs en panel scrollable con auto-scroll. - Colores por nivel: DEBUG (gris), INFO (blanco), WARN (amarillo), ERROR (rojo). - [ ] **3.5** Graficas de metricas (simple): - Canvas o SVG basico (sin librerias externas) para mensajes/hora y tool calls. - Alternativa: ASCII-art charts si se quiere mantener minimalismo extremo. - [ ] **3.6** Embed estaticos en Go: ```go //go:embed static/* var staticFS embed.FS ``` - Servir con `http.FileServer(http.FS(staticFS))` en el router. - Fallback a `index.html` para SPA routing. - [ ] **3.7** Tests del dashboard: - Test de que `embed.FS` contiene los archivos esperados. - Test de que `/dashboard` sirve HTML valido. - Test de que las rutas SPA redirigen a `index.html`. ### Fase 4 — Tests de integracion y cleanup - [ ] **4.1** Test de integracion end-to-end: arrancar servidor, verificar que todos los endpoints responden correctamente con agentes mock. - [ ] **4.2** Documentar configuracion en el config.yaml template de `agents/_template/`. - [ ] **4.3** Agregar seccion en `CLAUDE.md` sobre el servidor web y widgets. ## Ejemplo de uso ### Configuracion basica ```yaml # En la config del launcher o en un agent config web: enabled: true host: "0.0.0.0" port: 8080 auth: enabled: true token_env: "WEB_DASHBOARD_TOKEN" ``` ### Dashboard standalone 1. Habilitar en config: `web.enabled: true`, `web.port: 8080` 2. Arrancar launcher: `./dev-scripts/server/start.sh` 3. Navegar a `http://localhost:8080/dashboard` 4. Ver lista de agentes con estado, click en uno para ver metricas y logs en vivo ### Widget en Element 1. Configurar adicionalmente: ```yaml matrix: widgets: enabled: true base_url: "https://bots.example.com" auto_register: true ``` 2. Agente se une a un room → auto-registra widget 3. En Element Web aparece un panel con el dashboard filtrado para ese agente 4. El usuario ve metricas y logs sin salir del room ### Acceso directo a API ```bash # Lista de agentes curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/agents # Metricas de un agente curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/agents/asistente-2/metrics # Stream de logs en vivo curl -H "Authorization: Bearer $TOKEN" -N http://localhost:8080/api/agents/asistente-2/logs ``` ## Decisiones de diseno 1. **`net/http` sin frameworks**: consistente con el estilo del proyecto (stdlib, sin dependencias externas para HTTP). Go 1.22+ tiene routing con path params nativo en `http.ServeMux`. 2. **`embed.FS` para estaticos**: deployment de un solo binario. No se necesitan archivos externos ni pasos de build frontend separados. El dashboard es lo suficientemente simple para vanilla JS. 3. **SSE en vez de WebSocket para logs en vivo**: SSE es mas simple, funciona a traves de proxies HTTP, reconexion automatica en el browser, y es suficiente para un flujo unidireccional (servidor → cliente). WebSocket seria overkill para este caso. 4. **Config `WebCfg` a nivel launcher, no por agente**: el servidor web es uno solo para todos los agentes (lo sirve el launcher). Evita N puertos por N agentes. La info por agente se filtra en los endpoints. 5. **Widget registration opcional**: el dashboard funciona standalone sin Matrix widgets. Los widgets son un bonus para integracion en Element. Si el usuario no configura `widgets.base_url`, simplemente no se registran widgets. 6. **Auth por token simple**: para la primera iteracion, un bearer token en env var es suficiente. Integracion con Matrix OIDC o session cookies se puede agregar despues si es necesario. 7. **Filtrar secrets del API**: el endpoint `/api/agents/{id}` nunca expone tokens, API keys, passwords ni recovery keys. Se filtran los campos `*_env`, `access_token_env`, etc. antes de serializar. ## Prerequisitos - **Issue 0035 (audit trail + !metrics)**: no es bloqueante pero si esta implementado, el endpoint de metricas puede reutilizar la logica de agregacion. Sin el, se implementa directamente leyendo JSONL. - `shell/logger/query.go` — ya existe y funciona. - Go 1.22+ — necesario para `http.ServeMux` con path params (el proyecto usa Go 1.23.5, OK). ## Riesgos | Riesgo | Mitigacion | |--------|------------| | Element widget support varia entre clientes (Web vs mobile vs Desktop) | Testear con Element Web primero (el cliente principal del proyecto). Mobile puede no soportar widgets custom. Documentar limitaciones. | | CORS necesario para iframe de widgets | Agregar headers CORS configurables en el middleware del servidor. Restringir origenes al homeserver. | | HTTPS obligatorio para widgets en produccion | Element requiere HTTPS para widgets. Documentar que en produccion se necesita reverse proxy (nginx/caddy) con TLS. En desarrollo localhost funciona sin HTTPS. | | Dashboard crece en complejidad → SPA inmanejable con vanilla JS | Empezar simple. Si crece, migrar a Preact (~3KB) que se puede embeber sin build system. No usar React/Vue/frameworks pesados. | | Servidor web expuesto → superficie de ataque | Auth por defecto deshabilitada → solo escucha en 127.0.0.1. En produccion, auth habilitada + HTTPS + reverse proxy. Nunca exponer secretos en la API. | | SSE streaming consume memoria si hay muchos clientes | Limitar a N conexiones SSE simultaneas (configurable). Desconectar clientes idle. Buffer limitado de logs en memoria. | | `embed.FS` aumenta tamano del binario | Los archivos estaticos son HTML/JS/CSS minimo (estimado <100KB). Impacto negligible vs las dependencias Go existentes. |