# 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. |
