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>
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user