Control UI (navegador)
El Control UI es una pequeña app de página única con Vite + Lit servida por el Gateway:
- por defecto:
http://<host>:18789/ - prefijo opcional: configura
gateway.controlUi.basePath(p. ej./openclaw)
Se comunica directamente con el WebSocket del Gateway en el mismo puerto.
Acceso rápido (local)
Si el Gateway se ejecuta en el mismo equipo, abre:
Si la página no carga, inicia el Gateway primero: openclaw gateway.
La autenticación se proporciona durante el handshake WebSocket mediante:
connect.params.auth.tokenconnect.params.auth.passwordEl panel de ajustes del dashboard mantiene un token para la sesión actual de la pestaña del navegador y la URL del gateway seleccionada; las contraseñas no se persisten. El asistente de onboarding genera un token de gateway por defecto, así que pégalo aquí en la primera conexión.
Vinculación de dispositivo (primera conexión)
Cuando te conectas al Control UI desde un nuevo navegador o dispositivo, el Gateway
requiere una aprobación de vinculación única — incluso si estás en la misma Tailnet
con gateway.auth.allowTailscale: true. Esta es una medida de seguridad para prevenir
el acceso no autorizado.
Lo que verás: “disconnected (1008): pairing required”
Para aprobar el dispositivo:
# Listar solicitudes pendientes
openclaw devices list
# Aprobar por ID de solicitud
openclaw devices approve <requestId>
Una vez aprobado, el dispositivo se recuerda y no requerirá re-aprobación a menos que
lo revoques con openclaw devices revoke --device <id> --role <role>. Consulta
CLI de dispositivos para rotación y revocación de tokens.
Notas:
- Las conexiones locales (
127.0.0.1) se aprueban automáticamente. - Las conexiones remotas (LAN, Tailnet, etc.) requieren aprobación explícita.
- Cada perfil de navegador genera un ID de dispositivo único, así que cambiar de navegador o borrar los datos del navegador requerirá re-vinculación.
Soporte de idiomas
El Control UI puede localizarse automáticamente en la primera carga según el locale de tu navegador, y puedes cambiarlo después desde el selector de idioma en la tarjeta de Acceso.
- Locales soportados:
en,zh-CN,zh-TW,pt-BR,de,es - Las traducciones no inglesas se cargan de forma lazy en el navegador.
- El locale seleccionado se guarda en el almacenamiento del navegador y se reutiliza en futuras visitas.
- Las claves de traducción faltantes recurren al inglés.
Qué puede hacer (actualmente)
- Chatear con el modelo vía WebSocket del Gateway (
chat.history,chat.send,chat.abort,chat.inject) - Transmitir llamadas de herramientas + tarjetas de salida de herramientas en vivo en el Chat (eventos del agente)
- Canales: WhatsApp/Telegram/Discord/Slack + canales de plugin (Mattermost, etc.) estado + login QR + configuración por canal (
channels.status,web.login.*,config.patch) - Instancias: lista de presencia + actualización (
system-presence) - Sesiones: lista + sobreescrituras de thinking/fast/verbose/reasoning por sesión (
sessions.list,sessions.patch) - Trabajos cron: listar/agregar/editar/ejecutar/habilitar/deshabilitar + historial de ejecución (
cron.*) - Skills: estado, habilitar/deshabilitar, instalar, actualización de claves API (
skills.*) - Nodos: lista + capacidades (
node.list) - Aprobaciones exec: editar allowlists del gateway o nodo + política ask para
exec host=gateway/node(exec.approvals.*) - Configuración: ver/editar
~/.openclaw/openclaw.json(config.get,config.set) - Configuración: aplicar + reiniciar con validación (
config.apply) y despertar la última sesión activa - Las escrituras de configuración incluyen un guard de hash base para prevenir sobreescrituras de ediciones concurrentes
- Esquema de configuración + renderizado de formulario (
config.schema, incluyendo esquemas de plugin + canal); el editor JSON raw sigue disponible - Debug: snapshots de estado/salud/modelos + log de eventos + llamadas RPC manuales (
status,health,models.list) - Logs: tail en vivo de logs de archivo del gateway con filtro/exportación (
logs.tail) - Actualización: ejecutar actualización de paquete/git + reinicio (
update.run) con reporte de reinicio
Notas del panel de trabajos cron:
- Para trabajos aislados, la entrega por defecto es resumen de anuncio. Puedes cambiar a ninguno si quieres ejecuciones solo internas.
- Los campos de canal/destino aparecen cuando se selecciona anuncio.
- El modo webhook usa
delivery.mode = "webhook"condelivery.toconfigurado a una URL HTTP(S) de webhook válida. - Para trabajos de sesión principal, los modos de entrega webhook y ninguno están disponibles.
- Los controles de edición avanzada incluyen eliminar después de ejecutar, limpiar sobreescritura de agente, opciones exact/stagger de cron, sobreescrituras de modelo/thinking del agente, y toggles de entrega best-effort.
- La validación del formulario es inline con errores a nivel de campo; los valores inválidos deshabilitan el botón de guardar hasta que se corrijan.
- Configura
cron.webhookTokenpara enviar un token bearer dedicado; si se omite, el webhook se envía sin header de auth. - Respaldo deprecado: los trabajos legacy almacenados con
notify: truepueden seguir usandocron.webhookhasta que se migren.
Comportamiento del chat
chat.sendes no bloqueante: confirma inmediatamente con{ runId, status: "started" }y la respuesta se transmite vía eventoschat.- Re-enviar con la misma
idempotencyKeydevuelve{ status: "in_flight" }mientras se ejecuta, y{ status: "ok" }tras completarse. - Las respuestas de
chat.historytienen límite de tamaño para seguridad de la UI. Cuando las entradas de transcripción son demasiado grandes, el Gateway puede truncar campos de texto largos, omitir bloques de metadata pesados, y reemplazar mensajes sobredimensionados con un placeholder ([chat.history omitted: message too large]). chat.injectagrega una nota del asistente a la transcripción de la sesión y transmite un eventochatpara actualizaciones solo de UI (sin ejecución del agente, sin entrega al canal).- Detener:
- Clic en Detener (llama a
chat.abort) - Escribe
/stop(o frases de aborto independientes comostop,stop action,stop run,stop openclaw,please stop) para abortar fuera de banda chat.abortsoporta{ sessionKey }(sinrunId) para abortar todas las ejecuciones activas de esa sesión
- Clic en Detener (llama a
- Retención parcial al abortar:
- Cuando se aborta una ejecución, el texto parcial del asistente puede seguir mostrándose en la UI
- El Gateway persiste el texto parcial del asistente abortado en el historial de transcripción cuando existe salida en buffer
- Las entradas persistidas incluyen metadata de aborto para que los consumidores de transcripción puedan distinguir parciales abortados de salida de completado normal
Acceso por Tailnet (recomendado)
Tailscale Serve integrado (preferido)
Mantén el Gateway en loopback y deja que Tailscale Serve lo proxee con HTTPS:
openclaw gateway --tailscale serve
Abre:
https://<magicdns>/(o tugateway.controlUi.basePathconfigurado)
Por defecto, las solicitudes Serve del Control UI/WebSocket pueden autenticarse vía headers de identidad de Tailscale
(tailscale-user-login) cuando gateway.auth.allowTailscale es true. OpenClaw
verifica la identidad resolviendo la dirección x-forwarded-for con
tailscale whois y comparándola con el header, y solo acepta esto cuando la
solicitud llega a loopback con los headers x-forwarded-* de Tailscale. Configura
gateway.auth.allowTailscale: false (o fuerza gateway.auth.mode: "password")
si quieres requerir token/contraseña incluso para tráfico Serve.
La auth Serve sin token asume que el host del gateway es de confianza. Si hay código local
no confiable que pueda ejecutarse en ese host, requiere auth por token/contraseña.
Bind a tailnet + token
openclaw gateway --bind tailnet --token "$(openssl rand -hex 32)"
Luego abre:
http://<tailscale-ip>:18789/(o tugateway.controlUi.basePathconfigurado)
Pega el token en los ajustes de la UI (se envía como connect.params.auth.token).
HTTP inseguro
Si abres el dashboard sobre HTTP plano (http://<lan-ip> o http://<tailscale-ip>),
el navegador se ejecuta en un contexto no seguro y bloquea WebCrypto. Por defecto,
OpenClaw bloquea las conexiones del Control UI sin identidad de dispositivo.
Solución recomendada: usa HTTPS (Tailscale Serve) o abre la UI localmente:
https://<magicdns>/(Serve)http://127.0.0.1:18789/(en el host del gateway)
Comportamiento del toggle de auth insegura:
{
gateway: {
controlUi: { allowInsecureAuth: true },
bind: "tailnet",
auth: { mode: "token", token: "replace-me" },
},
}
allowInsecureAuth es un toggle de compatibilidad solo local:
- Permite que las sesiones del Control UI en localhost procedan sin identidad de dispositivo en contextos HTTP no seguros.
- No omite las verificaciones de vinculación.
- No relaja los requisitos de identidad de dispositivo remoto (no-localhost).
Solo para emergencias:
{
gateway: {
controlUi: { dangerouslyDisableDeviceAuth: true },
bind: "tailnet",
auth: { mode: "token", token: "replace-me" },
},
}
dangerouslyDisableDeviceAuth deshabilita las verificaciones de identidad de dispositivo del Control UI y es una
degradación de seguridad severa. Revierte rápidamente después del uso de emergencia.
Consulta Tailscale para guía de configuración HTTPS.
Compilar la UI
El Gateway sirve archivos estáticos desde dist/control-ui. Compílalos con:
pnpm ui:build # auto-instala dependencias de UI en la primera ejecución
Base absoluta opcional (cuando quieres URLs de assets fijas):
OPENCLAW_CONTROL_UI_BASE_PATH=/openclaw/ pnpm ui:build
Para desarrollo local (servidor dev separado):
pnpm ui:dev # auto-instala dependencias de UI en la primera ejecución
Luego apunta la UI a tu URL del WebSocket del Gateway (p. ej. ws://127.0.0.1:18789).
Debugging/testing: servidor dev + Gateway remoto
El Control UI son archivos estáticos; el destino WebSocket es configurable y puede ser diferente del origen HTTP. Esto es útil cuando quieres el servidor dev de Vite localmente pero el Gateway se ejecuta en otro lugar.
- Inicia el servidor dev de la UI:
pnpm ui:dev - Abre una URL como:
http://localhost:5173/?gatewayUrl=ws://<gateway-host>:18789
Auth única opcional (si es necesario):
http://localhost:5173/?gatewayUrl=wss://<gateway-host>:18789#token=<gateway-token>
Notas:
gatewayUrlse almacena en localStorage tras la carga y se elimina de la URL.tokense importa del fragmento de URL, se almacena en sessionStorage para la sesión actual de la pestaña del navegador y la URL del gateway seleccionada, y se elimina de la URL; no se almacena en localStorage.passwordse mantiene solo en memoria.- Cuando
gatewayUrlestá configurado, la UI no recurre a credenciales de configuración o entorno. Proporcionatoken(opassword) explícitamente. Las credenciales explícitas faltantes son un error. - Usa
wss://cuando el Gateway está detrás de TLS (Tailscale Serve, proxy HTTPS, etc.). gatewayUrlsolo se acepta en una ventana de nivel superior (no embebida) para prevenir clickjacking.- Los despliegues del Control UI no-loopback deben configurar
gateway.controlUi.allowedOriginsexplícitamente (orígenes completos). Esto incluye configuraciones de desarrollo remotas. gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=truehabilita el modo de fallback de origen por header Host, pero es un modo de seguridad peligroso.
Ejemplo:
{
gateway: {
controlUi: {
allowedOrigins: ["http://localhost:5173"],
},
},
}
Detalles de configuración de acceso remoto: Acceso remoto.