ego-browser
El entorno de ejecución de automatización del navegador que los agentes de IA usan para controlar la sesión real de Chromium de ego (lite).
ego-browser es el entorno de ejecución de automatización del navegador que ego (lite) ofrece a los agentes de IA. Se comunica mediante Chrome DevTools Protocol con la sesión real de Chromium dentro de ego (lite) y recibe un script heredoc de Node.js como punto de entrada: el agente escribe todo el flujo JS en un único envío por stdin, todos los helpers ya están inyectados en el ámbito del script y el estado del navegador se mantiene dentro de un Space.
ego-browser no está diseñado para que los humanos manejen un navegador manualmente y no sustituye a Playwright ni a Puppeteer. Está dirigido a agentes de IA basados en LLM.
Para quién es
- Agentes de IA para programación que necesitan controlar un navegador: Claude Code, Codex, Cursor y agentes SDK personalizados.
- Equipos que crean agentes verticales: automatizan Lark, Google Docs, Salesforce y back office similares.
- Repetir flujos web fijos: iniciar sesión, completar, exportar, buscar, leer tablas.
- Cualquiera que haya intentado introducir un DOM completo o una página HTML en un LLM y se haya topado con el límite de tokens.
Instalar
Se incluye con ego (lite); consulta Inicio rápido. Después de instalarlo, ejecuta ego-browser desde cualquier directorio.
También puedes instalar la Skill por separado:
npx skills add github:CitroLabs/ego-lite/skills/ego-browser
Bucle central
El ritmo típico de los agentes que dirigen una página: todo en un solo documento heredoc:
ego-browser nodejs <<'EOF'
const task = await useOrCreateTaskSpace('buscar incidencias en GitHub')
await openOrReuseTab('https://github.com/issues', { wait: true, timeout: 20 })
cliLog(await snapshotText())
EOF
- Reutiliza o crea un Task Space (decláralo en cada heredoc; consulta Space).
- Abre la página de destino.
- Lee el Snapshot (
snapshotText()) para obtener un árbol semántico con[ref=N, loc=..., url=...]. - Actúa en la página mediante una ref
@No un selector CSS. - Imprime el resultado final con
cliLog(...).
Dentro del heredoc estás en un proceso Node.js; dentro de
js(...)estás en el contexto de la página. No los mezcles.
Referencia de ayuda
Cada helper está disponible en el ámbito del script por su nombre camelCase. No se necesita ningún import.
Task Space
await listTaskSpaces()
const task = await useOrCreateTaskSpace('describir la tarea') // reutilizar o crear
await completeTaskSpace(task.name) // tarea terminada; mantener la pestaña
await closeTaskSpace(task.name) // cerrar el Space
name debe ser una descripción de la tarea en lenguaje natural de 3 a 6 palabras. No uses marcadores de posición.
Navegación y estado
await listTabs()
await openOrReuseTab(url, { wait: true, timeout: 20 })
await gotoAndWait(url, { timeout: 20, settle: 1 })
await newTab(url)
await switchTab(tabId)
await currentTab()
await pageInfo()
await ensureRealTab() // un Task Space nuevo puede no tener ninguna pestaña
Observación
await snapshotText() // Snapshot semántico de la página completa (predeterminado)
await snapshotText({ scope: 'only_within_viewport' })
await captureScreenshot('result.png')
await drainEvents() // consumir la cola de eventos de navegación y red
Ratón y desplazamiento
click, doubleClick, hover y dragMouse aceptan el mismo formato de destino (píxeles CSS):
'string': selector CSS o@ref. Hace clic en el centro del elemento.[x, y]o{x, y}: coordenadas de la ventana gráfica.{selector, x, y}: desplazamiento relativo desde la parte superior izquierda del elemento.options.label: descripción de 3 a 6 palabras. Pásalo y la acción desencadenará un resalte visual.
await click('@21', { label: 'comprobar el estado de inicio de sesión' })
await click('button.primary', { label: 'hacer clic en Enviar' })
await click([420, 260])
await hover('@5', { label: 'mostrar el menú al pasar el puntero' })
await dragMouse([from, to], { label: 'arrastrar la tarjeta' })
await scrollBy(900)
await scroll({ dy: 900 })
await scrollToBottomUntil(
async () => await js(String.raw`document.querySelectorAll('article').length`) >= 20,
{ step: 900, wait: 1, maxSteps: 20 },
)
Teclado y entrada
await typeText('hello world')
await fillInput('@2', 'user@test.com')
await pressKey('Enter')
await dispatchKey({ ... })
Archivos y red
await uploadFile('input[type="file"]', '/absolute/path/to/file.pdf')
await httpGet('https://api.example.com/data') // solicitud GET emitida en el contexto de la página
Espera
await wait(1) // segundos
await waitForLoad()
await waitForElement('@1')
await waitForNetworkIdle()
wait()ytimeoutestán en segundos. Sólo los parámetros que terminan enMsson milisegundos.
Ejecución del navegador
js(source) usa Runtime.evaluate internamente y recibe una cadena. No le pases una función y argumentos como harías con Puppeteer: la función se convierte mediante .toString(), se pierden las variables del cierre léxico y desaparece el canal de argumentos.
Para una lógica de varios pasos, envuélvala en un IIFE que devuelva una vez:
const data = await js(String.raw`(() => {
const items = [...document.querySelectorAll('article')]
return items.map(el => ({
text: el.innerText,
links: [...el.querySelectorAll('a')].map(a => a.href),
}))
})()`)
await elementEval('@1', el => el.getBoundingClientRect())
await cdp('Page.captureScreenshot', { format: 'png' })
Producción y autodescubrimiento
cliLog(value) // único canal de salida de un heredoc
cliLog(help('click')) // consultar cómo se usa una función auxiliar
Flujo de trabajo recomendado
Empieza con snapshotText y ref/loc: mantiene intacta la semántica y evita la fragilidad de las coordenadas:
- Reutiliza o crea el Task Space.
- Abre la página o cambia a ella (
openOrReuseTab/gotoAndWait). snapshotText()para obtener el árbol[ref=N, loc=..., url=...]. Las referencias se registran en refMap automáticamente.- Actúa sobre
@Nconclick/fillInput/elementEval, o realiza una extracción del DOM de una sola vez dentro dejs(...). cliLog(...)el resultado final.
Otros caminos útiles para combinar:
captureScreenshot+click([x, y]): diseños visuales, UI basadas en lienzo, listas virtuales, páginas con accesibilidad incompleta.js/elementEval/cdp: extrae DOM directamente, inspecciona el estado del navegador o cualquier cosa que no se ajuste limpiamente a un asistente estándar.
Mantén la navegación, la observación, el desplazamiento, la extracción, el filtrado, la agregación y la salida dentro de un único heredoc
ego-browser nodejs. No canalices los datos a través de un segundo scriptnodelocal.
Alcance de la referencia
@N solo es válido contra el refMap del snapshotText más reciente. Cada snapshotText() reconstruye el refMap. Los números de referencia provienen del CDP backendNodeId del elemento, por lo que el mismo elemento generalmente lleva el mismo número en todas las instantáneas, pero para que @N sea operable, N debe aparecer en la salida de la instantánea más reciente.
Causas comunes de Unknown ref:
- El elemento salió de la ventana gráfica.
- El DOM volvió a renderizarse.
- Una ronda anterior usó
scope: 'only_within_viewport'y la siguiente ronda no cubrió el elemento.
Cuando necesites una referencia estable al mismo elemento durante varias rondas, usa el selector loc=... del Snapshot o escribe directamente un selector CSS. Esta es también la base de la experiencia acumulada; consulta Skills.
Área de trabajo de la Skill
ego-browser no gestiona por sí solo la Experience modificable del agente de IA. De forma predeterminada, carga las extensiones de las funciones auxiliares y la Experience adquirida en cada sitio desde el paquete de la Skill del repositorio:
../../skills/ego-browser
Anular mediante env var:
EGO_BROWSER_AGENT_WORKSPACE=/path/to/ego-browser ego-browser nodejs <<'EOF'
cliLog(await siteSkills())
EOF
La Experience del sitio en learnings/ siempre está activa; cada llamada a una función auxiliar la lee. El modelo de escritura y descubrimiento de Experience se describe en Skills.
Validar la experiencia aprendida:
npm run validate:learnings
Diseño del directorio
package/ego-browser/
├── src/ # browser-runtime / helpers / run.js
│ ├── browser-runtime.js # ego runtime bridge on the browser side
│ ├── helpers.js # helpers exposed to the agent script
│ ├── run.js # CLI entry point (executes stdin)
│ └── learning/ # experience index, domain check, format check
├── artifacts/ego-browser/ # build output; npm bin points here
└── test/ # unit tests
skills/ego-browser/
├── SKILL.md / SKILL.zh.md # entry point for the agent
└── learnings/ # site experience directory
Notas
snapshotText()usascope: 'full_page'de forma predeterminada. Pasa'only_within_viewport'solo cuando necesites limitarlo al área visible.js()devuelve el resultado de la evaluación directamente. No lo hagasJSON.parse(...)otra vez.- Al escribir expresiones regulares dentro de una cadena de plantilla
js(), duplique las barras invertidas (\\d,\\s) o cambie aString.raw. - Un
returnde nivel superior se incluye automáticamente en un IIFE. Unreturndentro de una devolución de llamada anidada también puede desencadenar eso, así que escribe expresiones complejas como(() => { ... })()por adelantado. - Cuando el usuario haya solicitado explícitamente ego-browser, el entorno de ejecución estará listo. No hagas una comprobación previa con
which ego-browser,node -vo un volcado de ayuda; hazla solo si la primera ejecución genera un error.