src/dashboard/sse.ts.
- const delay = 1000; + const delay = Math.min(30_000, 1_000 * 2 ** attempts); + scheduleHeartbeat(15_000);
[sse] 4 pass · 0 fail · 0 skip [runtime] ✓ reconnects with jittered backoff [runtime] ✓ heartbeat fires every 15s
Dashboard web con pestañas multi-proyecto, streaming en vivo vía SSE, notificaciones por Telegram y Web Push, túnel público opcional, y una PWA para seguir tus sesiones desde el celular. Funciona con OpenCode nativamente y con Codex CLI vía hook bridge.
OpenCode (y Codex) son agentes que viven en tu terminal. Funcionan perfecto mientras estés sentado frente a ellos — pero en cuanto te levantás, todo se frena. El agente pide permiso para ejecutar un comando, no hay nadie para aprobarlo. Querés seguir la ejecución desde el teléfono mientras hacés otra cosa, no podés. Abrís otro proyecto en otra terminal y perdés el hilo.
Herramientas peligrosas necesitan aprobación, pero no estás ahí.
Desde el móvil, otro laptop o cualquier dispositivo en tu red.
Cada uno con su tab. Cambiás de contexto sin perder sesiones.
Web Push o Telegram — en tiempo real, sin estar en el dashboard.
Pair programming asincrónico detrás de un túnel público.
OpenCode y Codex viven en pestañas distintas — acá conviven.
La capa integrations/ implementa un puerto único AgentIntegration. Cada CLI vive en su propia carpeta y la composición root la conecta con una línea. Sumar un nuevo agente (Cursor, Aider, etc.) es un folder + una línea.
Integración nativa vía hooks del SDK de OpenCode. El plugin se carga dentro del proceso TUI: ve eventos de sesión, permission.ask, tool.start/end, y emite cada uno al event bus interno.
integrations/opencode/
├── index.ts # opencodeIntegration
└── hooks/
├── event.ts
├── permission.ask.ts
└── tool.ts Codex no tiene un SDK de plugins — usa hooks por HTTP. El plugin expone POST /codex/hooks/:event y Codex postea ahí. Mismo bus de eventos, mismo dashboard, misma cola de permisos.
# ~/.codex/config.toml
[hooks]
endpoint = "http://127.0.0.1:4097/codex/hooks"
token = "${PILOT_HOOK_TOKEN}" | Method | Path | Descripción |
|---|---|---|
| POST | /codex/hooks/SessionStart | Codex abrió una sesión — emite pilot.session.started |
| POST | /codex/hooks/UserPromptSubmit | El usuario envió un prompt — emite pilot.prompt.received |
| POST | /codex/hooks/PreToolUse | Una herramienta va a correr — emite pilot.tool.started |
| POST | /codex/hooks/PostToolUse | Tool finalizada — emite pilot.tool.completed |
| POST | /codex/hooks/PermissionRequest | Bloqueante: espera tu approve/deny en el dashboard |
| POST | /codex/hooks/Stop | Sesión cerrada — emite pilot.session.stopped |
Un servidor HTTP+SSE corre dentro del proceso del plugin. El dashboard es una SPA de módulos ES nativos servida desde el mismo origen — sin React, sin build, sin latencia.
Pestañas para todos los proyectos que abriste con OpenCode o Codex. Cambiás de contexto sin perder sesiones. Cada tab recuerda sus propias sesiones, mensajes y estado.
Cada token aparece a medida que el agente lo genera. Sin recargar, sin polling, sin latencia. Efecto typewriter real.
Cuando el agente (OpenCode o Codex) pide permiso, aparece un banner en el dashboard — desde tu celular también. Aprobás o rechazás con un toque. Si hay cola, se muestra el contador 1/N.
Ctrl/Cmd + K abre la paleta. Cambiás de agente, modelo, proveedor, creás sesión nueva, cambiás de proyecto — todo con teclado.
Configurás puerto, host, túnel, Telegram, VAPID keys, hookToken, timeouts y más desde una modal. Se guarda atómicamente en ~/.opencode-pilot/config.json.
Explorás el árbol del proyecto, buscás por patrón glob con debounce, y abrís archivos para ver su contenido (opt-in).
Recibís notificaciones aunque el dashboard esté cerrado. Generás las claves VAPID desde Settings con un clic.
Configurás token + chat ID (con hints en el dashboard). Recibís alertas de permisos, errores y fin de sesión en Telegram.
Usa la Notifications API nativa cuando el permiso está concedido. Notificaciones inline sin perder la sesión.
Interfaz responsive. Instalás el dashboard como app desde el menú del navegador. Service worker con cache versionado y detección de cambios de red.
Un QR para LAN, otro para túnel público. Empareja en segundos desde el celular.
/remote levanta el dashboard y enfoca automáticamente la pestaña de la carpeta donde estás trabajando. Si no existe, la crea.
Envía prompts, aprueba permisos y cambia agentes como si estuvieras en el escritorio.
PILOT_TUNNEL=cloudflared (o ngrok) y el plugin levanta un túnel automático, detecta la URL, te la muestra en el dashboard y genera un QR.
POST /codex/hooks/:event recibe los 6 lifecycle hooks de Codex CLI. PermissionRequest es bloqueante hasta que apruebes desde el dashboard.
Si OpenCode reinició y tu token quedó stale, la pantalla de "token inválido" te deja pegar la URL fresca y extrae el nuevo token sola.
bunx @lesquel/opencode-pilot doctor hace 6 checks de salud en <2 segundos. uninstall revierte toda la instalación (opcional --keep-config).
Cada campo de Settings tiene un hint con dónde conseguir el valor. Al perder foco, valida formato básico sin bloquear el save.
Click en las sesiones para ver cómo cambia la transcripción. Todo corre desde el mismo origen que el proceso del CLI — cero latencia artificial.
src/dashboard/sse.ts.
- const delay = 1000; + const delay = Math.min(30_000, 1_000 * 2 ** attempts); + scheduleHeartbeat(15_000);
[sse] 4 pass · 0 fail · 0 skip [runtime] ✓ reconnects with jittered backoff [runtime] ✓ heartbeat fires every 15s
Desde v1.18.0, las carpetas anuncian su propósito, no su tecnología. La regla de dependencias es estricta: infra → core → (transport, integrations, notifications) → server. Sumar un nuevo CLI es una carpeta + una línea en el composition root.
┌─────────────────────┐ ┌─────────────────────────┐
│ OpenCode TUI │ │ Codex CLI (hooks) │
│ (SDK plugin) │ │ POST /codex/hooks/* │
└──────────┬──────────┘ └──────────┬──────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────┐
│ integrations/ ports.ts │
│ ├── opencode/ (native SDK) │
│ └── codex/ (HTTP bridge) │
└─────────────────┬────────────────────────┘
│
┌─────────────────▼────────────────────────┐
│ core/ │ ← domain
│ permissions · events · audit · settings │
└─────────────────┬────────────────────────┘
│
┌─────────────────▼────────────────────────┐
│ transport/http ←→ notifications/ │
│ Bun.serve + SSE Telegram + Push │
└─────────────────┬────────────────────────┘
│
┌─────────────────▼────────────────────────┐
│ dashboard/ (vanilla SPA, same origin) │
└──────────────────────────────────────────┘ Permisos, eventos, audit, settings, state, errores. Cero HTTP / Telegram / Codex.
Bun.serve + routes + handlers + middlewares. La forma en que el core se expone.
OpenCode (SDK hooks) y Codex (HTTP hooks). Puerto único: AgentIntegration.
Telegram y Web Push. Puerto único: NotificationChannel. Pipeline orquesta el fan-out.
Tunnel, QR, banner, logger, network, auth, paths, dotenv, circuit breaker.
SPA vanilla servida por transport. Sin React, sin build, módulos ES nativos.
Slash commands para OpenCode TUI + binario CLI (init, doctor, uninstall).
Façade: el único lugar que importa de todas las capas. Wires y devuelve el handle.
El instalador localiza tu config dir de OpenCode (XDG, APPDATA, etc.), registra el plugin en opencode.json y tui.json, y limpia wrappers viejos. Para Codex, agregás el endpoint en ~/.codex/config.toml.
OpenCode (npm i -g opencode) o Codex CLI instalado. Bun (recomendado) o Node moderno.
Una sola línea. Detecta XDG_CONFIG_HOME / APPDATA automáticamente.
Cerrá todas las sesiones TUI y reabrí. Verás un toast: "Remote control plugin loaded".
Tipeá /remote en el TUI. Se abre el navegador. El banner imprime URL, token y QR.
Tu agente está ejecutando un refactor largo. Te levantás a hacer café. En el celular, una notificación Web Push te avisa que pide permiso para ejecutar rm -rf node_modules. Aprobás con un toque.
Activás el túnel (PILOT_TUNNEL=cloudflared). El dashboard queda accesible con un link público. Un colega sigue la sesión en tiempo real para hacer pair programming asincrónico.
Tenés OpenCode trabajando en una refactor y Codex generando tests en otra terminal. Los dos eventos llegan al mismo dashboard, en pestañas separadas, con permisos coordinados.
Tres terminales, tres proyectos, un dashboard. Las pestañas guardan estado entre cambios. Cero context switch tax.
Necesitás ver qué hizo el agente hace 2 horas en el servidor de CI. Te conectás al túnel, abrís la sesión, leés el histórico completo, descargás el diff.
Bot de Telegram conectado a un canal del equipo. Cada vez que un agente termina una tarea larga, el canal recibe el resumen. Todos ven el progreso sin abrir el dashboard.
Misma WiFi con PILOT_HOST=0.0.0.0, o expón un túnel público con Cloudflare o ngrok. El dashboard es mobile-first: drawer sidebar, bottom-sheets, 44×44 tap targets.
PILOT_HOST=0.0.0.0 PILOT_TUNNEL=cloudflared 🔒 La URL de túnel contiene el token. Tratala como una contraseña — rotala con /pilot-token.
Para quienes prefieren teclear antes que hacer click. Pulsá ? dentro del dashboard para la paleta completa.
tui.json::plugin incluye el spec. OpenCode usa dos loaders — uno para el servidor, otro para la TUI — y necesitan registros separados. Desde v1.12 editás todo desde el ícono de engranaje. Los valores viven en ~/.opencode-pilot/config.json. Si preferís archivos, un .env también funciona — y las env vars del shell siempre ganan.
| Variable | Default | Descripción |
|---|---|---|
| PILOT_PORT | 4097 | Puerto del servidor HTTP |
| PILOT_HOST | 127.0.0.1 | Dirección de bind. 0.0.0.0 para LAN. |
| PILOT_TUNNEL | — | cloudflared o ngrok para acceso público |
| PILOT_PERMISSION_TIMEOUT | 300000 | Timeout de solicitud de permiso (ms) |
| PILOT_HOOK_TOKEN new | — | Token bearer dedicado para /codex/hooks/*. Se acepta junto con el token principal. |
| PILOT_CODEX_PERMISSION_TIMEOUT_MS new | 250000 | Timeout para PermissionRequest de Codex (max 250000 — cap de Bun.serve idleTimeout) |
| PILOT_PROJECT_STATE new | auto | auto | always | off. Controla escrituras de pilot-state por proyecto |
| PILOT_DEV new | false | Re-lee el HTML del dashboard en cada request (DX) |
| PILOT_TELEGRAM_TOKEN | — | Bot token de @BotFather para notificaciones Telegram |
| PILOT_TELEGRAM_CHAT_ID | — | Chat ID al que enviar las alertas |
| PILOT_VAPID_PUBLIC_KEY | — | Clave pública Web Push (genera con un click en Settings) |
| PILOT_VAPID_PRIVATE_KEY | — | Clave privada Web Push |
| PILOT_ENABLE_GLOB_OPENER | false | Habilita /fs/glob y /fs/read para búsqueda glob desde el dashboard |
| PILOT_FETCH_TIMEOUT_MS | 10000 | Timeout para llamadas HTTP salientes (Telegram, push) |
OpenCode Pilot empezó como un dashboard remoto. Hoy es el puente entre tu agente y cualquier dispositivo. El siguiente paso es claro: integrarlo donde más tiempo pasamos.
Dos integraciones reales en producción. 6 endpoints de hooks Codex, 9+ event types compartidos, mismo dashboard, misma cola de permisos.
Extensión nativa para VS Code. Webview embebido del dashboard, comandos en la palette, status bar con sesiones activas, notificaciones via VS Code Notifications API y handler de URI para abrir desde links externos. La meta: que abrir el dashboard sea un atajo en lugar de un /remote.
Servicio relay multi-tenant para que NAT y firewalls dejen de ser bloqueador (diseño en docs/CLOUD_RELAY_v2_DESIGN.md). Más adapters: Cursor, Aider, Continue.dev — el puerto AgentIntegration ya está listo, solo falta escribir cada bridge.
¿Querés contribuir o sugerir? Las issues abiertas en GitHub Issues son el lugar — especialmente la integración con VS Code.
El docs/ del repo cubre desde la arquitectura de loaders hasta el diseño futuro del relay.
Cada env var, con .env de ejemplo para escenarios comunes.
read →Por qué OpenCode necesita dos entradas de plugin y cómo debuggearlas.
read →Setup completo del bridge: config.toml, hookToken, payload examples.
read →Screaming Architecture, dependency rules y por qué no hay clases.
read →Guía end-to-end de verificación del túnel, con security checklist.
read →Arquitectura del servicio centralizado futuro. Diseño, no implementación.
read →