Files
graph_explorer/issues/0032-browser-session-enrichers.md
egutierrez 35ace544d9 docs(issues): roadmap fase 2 navegador + ports Go + runtime embebido
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>
2026-05-02 16:10:35 +02:00

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
0028
0029

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 requests no 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") -> Page
  • html() -> str
  • screenshot(path, full_page=True)
  • cookies() -> list[dict] / set_cookies(list)
  • evaluate(js) -> any
  • close() — 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_login con profile=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_browser con un profile autenticado. Si no existe profile, propon browser_login antes.

Plan de implementacion (fases)

  1. 0032apython/functions/browser/browser_session.py con Playwright. Test que abre about:blank, persiste storage_state, recarga y verifica cookies.
  2. 0032bfetch_webpage_browser enricher. Test contra httpbin.org/cookies/set para verificar persistencia.
  3. 0032cfetch_screenshot (la mas simple, valida la pipeline visual end-to-end).
  4. 0032dweb_search_browser con google + ddg + fallback. Tests con paginas guardadas en tests/fixtures/.
  5. 0032ebrowser_login con UI no-headless. Test manual.
  6. 0032fextract_dom_data + extension del schema de Types con selectors. Test con HTML local complejo.
  7. 0032g — Tools MCP en gx-cli y 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_browser engine=google funciona masivamente (50+ busquedas seguidas) sin captcha.
  • Un Webpage enriquecido con extract_dom_data usando un Type con selectors queda con todos los campos en metadata.
  • 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.