Anade siete issues que definen el camino para hacer graph_explorer distribuible como binario Windows autocontenido (sin WSL): - 0032 — browser_session enrichers via Playwright (login interactivo, cookies persistentes, fetch_webpage_browser, web_search_browser). - 0033 — dispatcher multi-lenguaje (lang: go|python|bash en manifest) + runtime Python embebido en <app>/runtime/. 3 fases (A=dispatcher, B=runtime, C=UI badges). - 0033b — vendoring de funciones Python por enricher (_vendored/ + .vendor.lock) para que los enrichers no dependan de registry_root en runtime. - 0033c — fn check vendored: drift detection con --fix. - 0033d — fn index lee python_runtime / python_runtime_deps de app.md. - 0033e — /compile orquesta freeze + vendor + go builds. - 0034 — port de los 5 enrichers de sistema a Go. Reusa funciones Go del registry directamente (no copias). Tests pytest existentes pasan sin cambios. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.5 KiB
id, title, status, priority, created, depends_on
| id | title | status | priority | created | depends_on | ||
|---|---|---|---|---|---|---|---|
| 0032 | Fase 2 — Navegador controlable con sesion persistente (cookies, login, JS) | pending | high | 2026-05-02 |
|
Contexto y objetivo
La fase 1 (enrichers fetch_webpage, web_search, extract_links,
extract_text_entities, extract_domain) cubre el caso "sitio publico,
HTML estatico, sin JS". Limitaciones reales que estamos viendo:
- DDG HTML cambia el markup y hay que reparar el parser. Ademas en busquedas masivas devuelve captcha tras N requests.
- Sitios SPA (LinkedIn, X, Telegram) no se pueden enriquecer porque
requestsno ejecuta JS — el HTML viene vacio. - Para investigaciones serias hacen falta sesiones autenticadas (LinkedIn, foros, Telegram web). Sin cookies persistentes, cada enricher empieza de cero y se rompe el flujo.
Fase 2 introduce un navegador controlable que comparten todos los enrichers que necesiten JS, cookies o login.
Decision de stack
| Opcion | Pros | Contras | Veredicto |
|---|---|---|---|
| Playwright (Python) | Bateria incluida, captura cookies/storage, easy install via pip, multinavegador | 200 MB de Chromium descargado | Elegida — mejor DX para enrichers Python |
| pychrome + Chrome instalado | Mas ligero | API basica, reinventar capa de helpers | descartada |
| CDP en Go (issue 0029) | Reusa funciones del registry, binario unico | Mantener wrapper aparte por enricher | aplazado a v3 |
| Selenium | Estandar viejo | Mas lento que Playwright, peor API moderna | descartada |
Stack final: Playwright Python en python/.venv, persistencia de
estado con BrowserContext.storage_state (cookies + localStorage) en
<app_dir>/browser_profiles/<profile>.json.
Arquitectura
Componente compartido: browser_session_py_browser
Funcion del registry nueva en python/functions/browser/. NO es un
enricher — es la primitiva que usan los enrichers.
def open_session(profile: str, *, headless: bool = True,
user_agent: str | None = None) -> BrowserSession: ...
BrowserSession expone:
goto(url, wait="load") -> Pagehtml() -> strscreenshot(path, full_page=True)cookies() -> list[dict]/set_cookies(list)evaluate(js) -> anyclose()— persiste storage_state al disco antes de cerrar.
El profile vive en <app_dir>/browser_profiles/<name>.json. Si no
existe se crea vacio. Echo y los enrichers comparten profile via param
browser_profile (default "default").
Enrichers nuevos
| id | applies_to | reemplaza/complementa |
|---|---|---|
fetch_webpage_browser |
Url, Webpage | superset de fetch_webpage para JS/auth |
web_search_browser |
text, Concept | superset de web_search (Google/DDG con JS y sin captcha facil) |
fetch_screenshot |
Url, Webpage | nuevo — solo evidencia visual |
browser_login |
Account, Credential | nuevo — abre login interactivo, guarda cookies |
extract_dom_data |
Webpage | nuevo — extrae datos via selector CSS o XPath (JSON-LD, microdata, etc.) |
Todos comparten params:
- { name: browser_profile, type: string, default: "default" }
- { name: headless, type: bool, default: true }
- { name: timeout_s, type: int, default: 30 }
Login interactivo (browser_login)
Modo especial: lanza Chromium no-headless y deja que el humano haga el login a mano (CAPTCHA, 2FA, etc.). Cuando el usuario cierra la ventana, las cookies quedan guardadas en el profile. Los enrichers posteriores que usen ese profile heredan la sesion.
UX en Echo:
Usuario: "Echo, prepara una sesion para LinkedIn" Echo: ejecuta
browser_loginconprofile=linkedin, target=linkedin.com. Aparece la ventana del browser. Usuario hace login. Cierra ventana. Echo confirma: "sesion linkedin guardada, 15 cookies".
web_search_browser con multiples motores
params:
- { name: engine, type: string, default: "google" } # google|ddg|bing|brave
- { name: limit, type: int, default: 10 }
- { name: browser_profile, type: string, default: "default" }
Con browser real, Google deja de bloquear. Cada engine tiene su
selector CSS para resultados (mantenidos en engines.yaml dentro del
enricher). El enricher cae en orden engine → engine si uno falla:
google → ddg → bing.
Extraccion CSS/XPath (extract_dom_data)
Para nodos Webpage enriquecidos con un browser, permite definir
selectores en el Type del nodo:
# en types.yaml de un proyecto
- name: LinkedInProfile
selectors:
full_name: "h1.profile-name"
headline: "div.profile-headline"
company: "[data-section=experience] li:first-child .company"
extract_dom_data lee los selectores del Type, los aplica al DOM
post-JS y guarda los valores en metadata. Esto es lo que conecta
el grafo con scrapers tipados sin escribir un enricher por sitio.
Integracion con Echo
Echo gana 3 tools nuevas en el MCP server (gx-cli):
browser_login(profile, url)— pide al usuario hacer login.browser_session_status(profile)— lista profiles, valid/expired, cookie count, ultima url visitada.browser_close(profile)— cierra la sesion y persiste.
System prompt amplia el bloque WORKFLOW:
Cuando una URL devuelva HTML vacio o redirija a login, propon usar
fetch_webpage_browsercon un profile autenticado. Si no existe profile, proponbrowser_loginantes.
Plan de implementacion (fases)
- 0032a —
python/functions/browser/browser_session.pycon Playwright. Test que abre about:blank, persiste storage_state, recarga y verifica cookies. - 0032b —
fetch_webpage_browserenricher. Test contrahttpbin.org/cookies/setpara verificar persistencia. - 0032c —
fetch_screenshot(la mas simple, valida la pipeline visual end-to-end). - 0032d —
web_search_browsercon google + ddg + fallback. Tests con paginas guardadas entests/fixtures/. - 0032e —
browser_logincon UI no-headless. Test manual. - 0032f —
extract_dom_data+ extension del schema de Types conselectors. Test con HTML local complejo. - 0032g — Tools MCP en
gx-cliy prompt update en chat.cpp.
Riesgos y mitigaciones
| Riesgo | Mitigacion |
|---|---|
| Playwright pesa 200 MB | Lazy install: pip install playwright && playwright install chromium solo cuando se ejecuta el primer enricher browser. Documentar en app.md. |
| Profiles con secretos en disco | <app_dir>/browser_profiles/ en .gitignore. Documentar advertencia en browser_login. |
| Sites detectan headless | Default user-agent realista. Bloque opcional stealth: true en params (usa playwright-stealth). |
| Concurrencia: 2 jobs leyendo el mismo profile | Lock por profile en sqlite (browser_locks tabla en operations.db). Si esta tomado, esperar 30s antes de fallar. |
| Tests con red real | NO hay tests con red real. Fixtures HTML guardados o servidor mock con pytest-httpserver. |
Definicion de hecho
- Echo puede pedir al usuario "abre LinkedIn y haz login" y a partir de ahi enriquecer perfiles.
web_search_browserengine=google funciona masivamente (50+ busquedas seguidas) sin captcha.- Un Webpage enriquecido con
extract_dom_datausando un Type conselectorsqueda con todos los campos enmetadata. - Tests pasan en CI sin red — todos los enrichers browser tienen tests con fixtures locales.
Fuera de alcance (v3)
- Reescribir los enrichers a Go con CDP (issue 0029 sigue vivo como alternativa de bajo nivel si Playwright no escala).
- Captcha solving — manual via
browser_login, nunca automatico. - Anti-bot bypass agresivo (residential proxies, fingerprint randomizacion). Pendiente hasta que se demuestre necesidad real.