8733b7d175
- 0038: lanzar Chrome/Edge/Brave externo con --remote-debugging-port + --user-data-dir por profile, control via CDP desde cdp-cli Go. Decision Go vs C++ in-process documentada; deja la puerta abierta a un cliente C++ minimo solo para streaming en el futuro. Supersedes 0032. - 0039: gestor de cookies/sesiones por profile via CDP — list, export EditThisCookie, import, clear selectivo, health checks con selectores, locks cuando un enricher esta usando el profile. - 0040: profiles como concepto de primera clase — metadata (color, icon, browser_preference, UA, project, template), templates anon/auth/work/ investigation, ProfilePicker reusable, project default, tag en executions.metrics. Actualiza 0038 para apuntar a 0040 como duenio del UX de profiles.
218 lines
11 KiB
Markdown
218 lines
11 KiB
Markdown
---
|
|
id: 0040
|
|
title: Gestion de multiples profiles de browser como concepto de primera clase
|
|
status: pending
|
|
priority: high
|
|
created: 2026-05-04
|
|
depends_on: [0038]
|
|
related: [0039]
|
|
---
|
|
|
|
## Contexto y objetivo
|
|
|
|
`0038` ya soporta tecnicamente N profiles en paralelo (cada uno con
|
|
`--user-data-dir` propio y puerto CDP propio). `0039` gestiona las
|
|
cookies dentro de cada profile. Falta la **capa de gestion humana**:
|
|
que cada profile sea un "usuario" identificable con metadata, que el
|
|
agente y el humano puedan elegir cual usar en cada momento, y que
|
|
existan templates para los casos tipicos del flujo OSINT.
|
|
|
|
Caso real: el usuario tiene en paralelo
|
|
- `personal` — su cuenta real de LinkedIn / Gmail / X.
|
|
- `osint_anon` — sin login, user-agent neutro, sin extensiones, para
|
|
scraping no atribuido.
|
|
- `corp_aurgi` — cuenta corporativa con SSO.
|
|
- `linkedin_alt` — cuenta secundaria para busquedas masivas.
|
|
- `investigation_<caso>` — profile temporal por investigacion concreta.
|
|
|
|
Hoy no hay forma de distinguir uno de otro mas alla del nombre de
|
|
carpeta. Este issue convierte el "profile" en una entidad propia con
|
|
metadata, ciclo de vida y selector unificado.
|
|
|
|
## Modelo de profile
|
|
|
|
Cada profile es una fila en `browser_profiles` (ya creada en `0039`)
|
|
ampliada con metadata operativa:
|
|
|
|
```sql
|
|
CREATE TABLE browser_profiles (
|
|
name TEXT PRIMARY KEY, -- 'personal', 'osint_anon', ...
|
|
display_name TEXT, -- 'Personal — Lucas'
|
|
color TEXT, -- '#7B61FF' para badge en UI
|
|
icon TEXT, -- nombre Tabler: 'user', 'mask', 'building'
|
|
browser_preference TEXT, -- 'chrome' | 'edge' | 'brave' | 'auto'
|
|
default_user_agent TEXT, -- override; null = el del browser
|
|
default_headless INTEGER DEFAULT 0, -- bool
|
|
proxy TEXT, -- 'http://user:pass@host:port' o null
|
|
project_id TEXT, -- FK opcional a projects/*/project.md
|
|
template TEXT, -- 'anon' | 'authenticated' | 'work' | null
|
|
notes TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
last_used_at TIMESTAMP
|
|
);
|
|
```
|
|
|
|
`name` sigue siendo el nombre de carpeta (`local_files/browser_profiles/<name>/`).
|
|
El resto es metadata pura — el directorio fisico no cambia.
|
|
|
|
## UX: panel **Profiles**
|
|
|
|
Pestaña dedicada (separada del panel **Browsers** del `0038`, que sigue
|
|
mostrando solo runtime de instancias vivas):
|
|
|
|
```
|
|
┌─ Profiles ────────────────────────────────────────────────────────────┐
|
|
│ [+ New] [Clone] [Import .json] [Filter: all|active|by-project ▼] │
|
|
│ │
|
|
│ ● personal 👤 Chrome Personal — cuenta real [edit] │
|
|
│ ● osint_anon 🎭 Chrome Anon scraping (UA neutro) [edit] │
|
|
│ ○ corp_aurgi 🏢 Edge SSO Aurgi [edit] │
|
|
│ ○ linkedin_alt 👥 Chrome Cuenta secundaria LI [edit] │
|
|
│ ○ inv_caso_2026_03 🔍 Chrome Investigacion <proyecto X> [edit] │
|
|
│ │
|
|
│ Selected: personal │
|
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Browser: Chrome 130 Display name: Personal — Lucas │ │
|
|
│ │ UA: (default) Proxy: - │ │
|
|
│ │ Project: (global) Template: authenticated │ │
|
|
│ │ Folder: local_files/browser_profiles/personal/ (124 MB) │ │
|
|
│ │ Last used: 2 hours ago Cookies: 312 in 47 domains │ │
|
|
│ │ │ │
|
|
│ │ [Launch] [Launch headless] [Sessions] [Cookies] [Delete] │ │
|
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
- Color + icon dan distincion visual rapida en cualquier lugar del app
|
|
donde aparezca el profile (badge en Toolbar, tag en executions, ...).
|
|
- ● vivo / ○ parado (estado runtime, leido del panel `Browsers`).
|
|
- "Sessions" abre el panel del `0039` filtrado por este profile.
|
|
|
|
## Templates
|
|
|
|
Botones de **[+ New]** ofrecen 4 templates con defaults distintos:
|
|
|
|
| Template | UA default | Headless | Notas tipicas |
|
|
|---|---|---|---|
|
|
| `anon` | `Mozilla/5.0 ... Chrome/130` (neutro, sin client hints) | true | scraping sin atribucion, sin login |
|
|
| `authenticated` | UA real del browser | false | tu cuenta real, requiere login interactivo |
|
|
| `work` | UA real | false | cuenta corporativa con SSO |
|
|
| `investigation` | UA neutro | false | profile efimero por caso, ligado a un project |
|
|
|
|
El template solo prerellena los campos al crear — despues se editan.
|
|
|
|
## Seleccion de profile al lanzar enricher
|
|
|
|
Cuando un enricher CDP se invoca (desde Echo, jobs queue o boton manual)
|
|
necesita decidir el profile. Reglas en orden:
|
|
|
|
1. **Param explicito** `browser_profile=<name>` → usa ese.
|
|
2. **Project default** — si la entidad/operacion pertenece a un project
|
|
y existe un profile con `project_id=<proj>` marcado como default →
|
|
usa ese.
|
|
3. **Template hint** — si el enricher tiene `prefers_template:
|
|
authenticated` (ej. `fetch_webpage_browser` para LinkedIn) → propon
|
|
los profiles con ese template ordenados por `last_used_at`.
|
|
4. **Fallback** → `default` (siempre existe, template `anon`).
|
|
|
|
Si la regla 1 no se cumple y hay ambiguedad, Echo pregunta:
|
|
> "Para enriquecer este perfil de LinkedIn necesito una sesion. Tengo
|
|
> `personal` (auth, last used 2h ago), `linkedin_alt` (auth, 3 dias),
|
|
> `osint_anon` (sin auth). ¿Cual uso?"
|
|
|
|
En la UI, cualquier dialogo que dispare un enricher CDP muestra un
|
|
**dropdown de profile** con el sugerido pre-seleccionado.
|
|
|
|
## Operaciones por profile
|
|
|
|
| Accion | Implementacion |
|
|
|---|---|
|
|
| **Create** | Crea fila + carpeta vacia. No lanza browser. |
|
|
| **Clone** | `cp -r` carpeta + nueva fila. Util para "voy a probar algo, hago clone de mi profile real". |
|
|
| **Rename** | Update fila + `mv` carpeta + actualizar `browser_locks` y referencias en jobs queue. |
|
|
| **Delete** | Confirm modal. Borra fila + carpeta. Falla si profile esta vivo. |
|
|
| **Export full** | Tar de la carpeta + manifest. Util para llevar profile a otro PC (con warning de secrets). |
|
|
| **Import full** | Untar + registrar fila. |
|
|
| **Set as project default** | Marca el profile como default para enrichers que operen sobre entidades del proyecto. |
|
|
|
|
Las operaciones de cookies/sesiones (`0039`) siguen siendo otro panel
|
|
y otro nivel.
|
|
|
|
## Profile como tag en `executions`
|
|
|
|
Cada `execution` de un enricher CDP guarda el `browser_profile` usado
|
|
en sus `metrics`. Permite consultas tipo:
|
|
|
|
```sql
|
|
-- Cuantas paginas LinkedIn enriquecio cada profile?
|
|
SELECT json_extract(metrics, '$.browser_profile') as profile, COUNT(*)
|
|
FROM executions
|
|
WHERE function_id LIKE 'fetch_webpage_browser%'
|
|
AND json_extract(metrics, '$.url') LIKE '%linkedin.com%'
|
|
GROUP BY profile;
|
|
|
|
-- Profiles que han fallado mas en la ultima semana
|
|
SELECT profile, SUM(status='failure') as failures
|
|
FROM executions
|
|
WHERE created_at > date('now', '-7 days')
|
|
GROUP BY profile
|
|
ORDER BY failures DESC;
|
|
```
|
|
|
|
## Plan de implementacion
|
|
|
|
| Fase | Entregable |
|
|
|---|---|
|
|
| 0040a | Schema de `browser_profiles` ampliado + migracion de profiles preexistentes (los del `0038` quedan como filas con metadata vacia). |
|
|
| 0040b | Panel **Profiles** read-only: lista + detalle. Lee carpetas existentes, muestra tamaño en disco, last_used_at. |
|
|
| 0040c | CRUD basico: New (con templates), Edit, Delete, Rename, Clone. |
|
|
| 0040d | Selector de profile en cualquier dialogo de enricher CDP — componente reusable `ProfilePicker` que aplica las reglas de seleccion de §"Seleccion de profile al lanzar enricher". |
|
|
| 0040e | Project default — UI para marcar profile X como default del project Y. Rule engine en `cdp-cli` lee este default. |
|
|
| 0040f | Tag `browser_profile` en `executions.metrics` automatico desde `cdp-cli`. |
|
|
| 0040g | Export/import full de profile (tar + manifest). Al importar, dialog para mapear si ya existe el nombre. |
|
|
| 0040h | Echo gana herramienta MCP `browser_profile_pick` para preguntar al usuario cuando hay ambiguedad. |
|
|
|
|
## Riesgos y mitigaciones
|
|
|
|
| Riesgo | Mitigacion |
|
|
|---|---|
|
|
| Borrar profile destruye 1 GB de cache | Confirm modal con tamaño. Opcion "delete cookies only, keep cache" para casos intermedios. |
|
|
| Confundir `personal` con `linkedin_alt` y postear con la cuenta equivocada | Color + icon prominentes en cualquier UI. Echo SIEMPRE confirma profile antes de acciones de escritura (post, like, message — fuera de v1 igualmente). |
|
|
| Profile `default` se borra por accidente | Bloqueado: no se puede eliminar el profile literal `default`, solo renombrar (y se autocrea uno vacio). |
|
|
| Race condition al renombrar mientras enricher corre | El rename adquiere el `browser_lock` (de `0039`) primero — falla si esta tomado. |
|
|
| Schema drift entre profiles creados a mano y registrados | Boton **Scan folders** en panel: detecta carpetas en `browser_profiles/` sin fila en BD y propone registrarlas. |
|
|
|
|
## Definicion de hecho
|
|
|
|
- Tengo 5 profiles visibles en el panel **Profiles** con icon/color
|
|
distintos, cada uno con su browser_preference y notas.
|
|
- Lanzo un enricher `fetch_webpage_browser` desde el menu contextual de
|
|
un nodo `Url`, aparece un dropdown de profile con `default` pre-seleccionado
|
|
y puedo cambiar a `personal` antes de ejecutar.
|
|
- Marco `corp_aurgi` como default del project `<X>` → al enriquecer una
|
|
entidad de ese project, el dropdown viene pre-seleccionado en
|
|
`corp_aurgi`.
|
|
- Clono `personal` a `personal_test`, modifico cookies en el clon, y
|
|
`personal` original sigue intacto.
|
|
- Borro un profile vivo → falla con mensaje claro "profile in use,
|
|
stop the browser instance first".
|
|
- Echo me pregunta "uso `personal` o `linkedin_alt`?" cuando lanza un
|
|
scraping LinkedIn y no hay default de project.
|
|
- Una query SQL sobre `executions.metrics->browser_profile` agrupa
|
|
correctamente todos los fetches por profile usado.
|
|
|
|
## Fuera de alcance
|
|
|
|
- **Sync de profiles entre PCs** — los profiles tienen secrets (cookies
|
|
de auth). El export/import manual es la via. Sync automatico fuera
|
|
de v1.
|
|
- **Compartir profile entre apps del registry** — hoy `local_files/`
|
|
es por-app. Si en el futuro otra app necesita los mismos profiles,
|
|
se decide entonces si centralizar en `~/.fn/browser_profiles/` o
|
|
symlinks.
|
|
- **Browser non-chromium (Firefox)** — un profile Firefox tiene formato
|
|
distinto. Misma decision que `0038`: solo chromium-based.
|
|
- **Profile encryption at rest** — Chrome ya cifra secrets con DPAPI
|
|
(Win) / Keychain (Mac). Linux usa cifrado debil pero esta fuera de
|
|
nuestro control.
|