ego-browser
Il runtime di automazione del browser che gli agenti AI usano per controllare la sessione Chromium reale di ego (lite).
ego-browser è il runtime di automazione del browser che ego (lite) fornisce agli agenti AI. Comunica tramite Chrome DevTools Protocol con la sessione reale di Chromium dentro ego (lite) e accetta uno script heredoc di Node.js come punto di ingresso: l'agente scrive l'intero flusso JS in un unico invio su stdin, tutti gli helper sono già inseriti nell'ambito dello script e lo stato del browser rimane attivo dentro un Space.
ego-browser non è pensato per consentire agli esseri umani di guidare manualmente un browser e non sostituisce Playwright o Puppeteer. Il lettore previsto è un agente LLM.
Per chi è
- agenti di codifica AI che devono gestire un browser: Claude Code, Codex, Cursor, agenti SDK personalizzati.
- Team che creano agenti verticali: automatizzando Lark, Google Docs, Salesforce e back office simili.
- Ripetendo flussi web fissi: accedi, compila, esporta, cerca, leggi tabelle.
- Chiunque abbia provato a inserire un DOM completo o una pagina HTML in un LLM e si sia scontrato con il token wall.
Installare
Viene fornito con ego (lite): vedi Avvio rapido. Dopo l'installazione, esegui ego-browser da qualsiasi directory.
Puoi anche installare la skill in modalità autonoma:
npx skills add github:CitroLabs/ego-lite/skills/ego-browser
Anello centrale
Il ritmo tipico degli agenti che guidano una pagina: tutto in un unico heredoc:
ego-browser nodejs <<'EOF'
const task = await useOrCreateTaskSpace('search github issues')
await openOrReuseTab('https://github.com/issues', { wait: true, timeout: 20 })
cliLog(await snapshotText())
EOF
- Riutilizza o crea un Task Space (dichiaralo in ogni heredoc — vedi Space).
- Apri la pagina di destinazione.
- Leggi l'istantanea (
snapshotText()) per ottenere un albero semantico con[ref=N, loc=..., url=...]. - Agisci sulla pagina tramite
@Nref o selettore CSS. - Stampa il risultato finale con
cliLog(...).
All'interno del documento sei in un processo Node.js; all'interno di
js(...)sei nel contesto della pagina. Non mescolarli.
Riferimento dell'aiutante
Ogni helper è disponibile nell'ambito dello script con il nome camelCase. Nessun import richiesto.
Task Space
await listTaskSpaces()
const task = await useOrCreateTaskSpace('describe task') // reuse or create
await completeTaskSpace(task.name) // done, keep the tab
await closeTaskSpace(task.name) // shut the space down
name dovrebbe essere una descrizione dell'attività in linguaggio naturale composta da 3 a 6 parole. Non utilizzare segnaposto.
Navigazione e stato
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() // a fresh task space may have no tab yet
Osservazione
await snapshotText() // full-page semantic snapshot (default)
await snapshotText({ scope: 'only_within_viewport' })
await captureScreenshot('result.png')
await drainEvents() // consume the nav / network event queue
Mouse e scorrimento
click, doubleClick, hover e dragMouse accettano lo stesso formato di destinazione (pixel CSS):
'string': selettore CSS o@ref. Fa clic sul centro dell'elemento.[x, y]o{x, y}: coordinate del viewport.{selector, x, y}: offset relativo dall'angolo in alto a sinistra dell'elemento.options.label: descrizione da 3 a 6 parole. Passalo e l'azione innesca un highlight visivo.
await click('@21', { label: 'check the login state' })
await click('button.primary', { label: 'click the submit button' })
await click([420, 260])
await hover('@5', { label: 'hover to reveal the menu' })
await dragMouse([from, to], { label: 'drag the card' })
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 },
)
Tastiera e input
await typeText('hello world')
await fillInput('@2', 'user@test.com')
await pressKey('Enter')
await dispatchKey({ ... })
File e rete
await uploadFile('input[type="file"]', '/absolute/path/to/file.pdf')
await httpGet('https://api.example.com/data') // GET issued in the page's context
In attesa
await wait(1) // seconds
await waitForLoad()
await waitForElement('@1')
await waitForNetworkIdle()
wait()etimeoutsono in secondi. Solo i parametri che terminano conMssono millisecondi.
Esecuzione del browser
js(source) è Runtime.evaluate sotto il cofano e prende una stringa. Non passargli una funzione e argomenti come fa Puppeteer: ciò produce un avviso, viene racchiuso in .toString() e le variabili di chiusura e il canale degli argomenti svaniscono entrambi.
Per la logica a più passaggi, avvolgilo in un IIFE che restituisce una volta:
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' })
Output e scoperta di sé
cliLog(value) // the only output channel in a heredoc
cliLog(help('click')) // look up a helper's usage
Flusso di lavoro consigliato
Inizia con snapshotText più ref / loc: mantiene intatta la semantica ed evita la fragilità delle coordinate:
- Riutilizza o crea il Task Space.
- Apri o passa alla pagina (
openOrReuseTab/gotoAndWait). snapshotText()per ottenere l'albero[ref=N, loc=..., url=...]. I riferimenti vengono registrati automaticamente nella refMap.- Agisci su
@Nconclick/fillInput/elementEvalo esegui un'estrazione DOM one-shot all'interno dijs(...). cliLog(...)il risultato finale.
Altri percorsi utili da abbinare:
captureScreenshot+click([x, y]): layout visivi, interfacce utente basate su canvas, elenchi virtuali, pagine con accessibilità incompleta.js/elementEval/cdp: estrae direttamente il DOM, controlla lo stato del browser o qualsiasi cosa che non si adatti perfettamente a un helper standard.
Mantieni la navigazione, l'osservazione, lo scorrimento, l'estrazione, il filtraggio, l'aggregazione e l'output all'interno di un singolo
ego-browser nodejsheredoc. Non reindirizzare i dati attraverso un secondo scriptnodelocale.
Ambito di riferimento
@N è valido solo rispetto alla refMap del snapshotText più recente. Ogni snapshotText() ricostruisce la refMap. I numeri di riferimento provengono dal CDP backendNodeId dell'elemento, quindi lo stesso elemento solitamente porta lo stesso numero tra gli snapshot, ma affinché @N sia utilizzabile, N deve apparire nell'output dello snapshot più recente.
Cause comuni di Unknown ref:
- L'elemento è uscito dal viewport.
- Il DOM è stato ri-renderizzato.
- Un round precedente utilizzava
scope: 'only_within_viewport'e il round successivo non copriva l'elemento.
Quando hai bisogno di un riferimento stabile allo stesso elemento in più round, utilizza il selettore loc=... dello Snapshot o scrivi direttamente un selettore CSS. Questa è anche la base per l'esperienza accumulata: vedi Skill.
Workspace delle skill
ego-browser non offre da solo un'esperienza modificabile dell'agente. Per impostazione predefinita, carica le estensioni dei helper e l'esperienza appresa sui siti dal pacchetto di skill del repository:
../../skills/ego-browser
Sostituisci tramite env var:
EGO_BROWSER_AGENT_WORKSPACE=/path/to/ego-browser ego-browser nodejs <<'EOF'
cliLog(await siteSkills())
EOF
L'esperienza dei siti in learnings/ è sempre attiva; ogni chiamata a un helper la legge. Il modello di scrittura e recupero dell'esperienza è descritto in Skill.
Convalidare l'esperienza appresa:
npm run validate:learnings
Disposizione delle directory
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
Note
snapshotText()per impostazione predefinita èscope: 'full_page'. Passa'only_within_viewport'solo quando hai veramente bisogno solo dell'area visibile.js()restituisce direttamente il risultato della valutazione. NonJSON.parse(...)di nuovo.- Quando scrivi un'espressione regolare all'interno di una stringa modello
js(), raddoppia le barre rovesciate (\\d,\\s) o passa aString.raw. - Un
returndi livello superiore viene inserito automaticamente in un IIFE. Anche unreturnall'interno di una richiamata nidificata può attivare questo, quindi scrivi espressioni complesse come(() => { ... })()in anticipo. - Quando l'utente ha richiesto esplicitamente ego-browser, il runtime è pronto. Non effettuare il preflight con
which ego-browser/node -v/ un dump della guida: fallo solo se la prima esecuzione effettivamente presenta errori.