52d5632d89
Issues planificados: - 0036: Claude Code streaming de progreso en Matrix - 0037: Agente que crea otros agentes/bots via Matrix - 0038: Webapps y dashboards embebidos en Element via widgets - 0039: Recordatorios dinámicos y crons que invocan agentes - 0040: Soporte para mensajes de voz (audio → STT) - 0041: Videollamadas con agentes via LiveKit Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
297 lines
15 KiB
Markdown
297 lines
15 KiB
Markdown
# 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. |
|