Files
graph_explorer/issues/0040-multi-profile-management.md
egutierrez 8733b7d175 docs(issues): browser externo + CDP + multi-profile (0038, 0039, 0040)
- 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.
2026-05-04 22:16:42 +02:00

11 KiB

id, title, status, priority, created, depends_on, related
id title status priority created depends_on related
0040 Gestion de multiples profiles de browser como concepto de primera clase pending high 2026-05-04
0038
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:

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. Fallbackdefault (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:

-- 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.