35ace544d9
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>
183 lines
7.5 KiB
Markdown
183 lines
7.5 KiB
Markdown
---
|
|
id: 0032
|
|
title: Fase 2 — Navegador controlable con sesion persistente (cookies, login, JS)
|
|
status: pending
|
|
priority: high
|
|
created: 2026-05-02
|
|
depends_on: [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.
|
|
|
|
```python
|
|
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:
|
|
```yaml
|
|
- { 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
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
# 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. **0032a** — `python/functions/browser/browser_session.py` con
|
|
Playwright. Test que abre about:blank, persiste storage_state,
|
|
recarga y verifica cookies.
|
|
2. **0032b** — `fetch_webpage_browser` enricher. Test contra
|
|
`httpbin.org/cookies/set` para verificar persistencia.
|
|
3. **0032c** — `fetch_screenshot` (la mas simple, valida la pipeline
|
|
visual end-to-end).
|
|
4. **0032d** — `web_search_browser` con google + ddg + fallback.
|
|
Tests con paginas guardadas en `tests/fixtures/`.
|
|
5. **0032e** — `browser_login` con UI no-headless. Test manual.
|
|
6. **0032f** — `extract_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.
|