Files
fn_registry/dev/issues/0098-navegator-extractions-enhancement.md
T
egutierrez 212875ed0d chore: auto-commit (286 archivos)
- .claude/agents/fn-orquestador/SKILL.md
- .claude/commands/fn_claude.md
- .claude/rules/INDEX.md
- .claude/rules/cpp_apps.md
- .claude/rules/ids_naming.md
- CHANGELOG.md
- apps/dag_engine/README.md
- apps/dag_engine/api.go
- apps/dag_engine/dags_migrated/example.yaml
- apps/dag_engine/dags_migrated/example_lineage_tracking.yaml
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 16:33:22 +02:00

9.3 KiB

0098 — Navegator extractions enhancement (Pick + Network rows + AutoExtract + data_factory bridge)

Status: pendiente Created: 2026-05-16 Type: feature Priority: alta Depends: 0097 (data_factory v1 — DONE)

Problema

navegator_dashboard ya tiene paneles Browsers/Tabs/Tab Detail/Network/Agent. Pero extraccion de datos sigue siendo manual: abre DevTools, copia selectores, escribe JS, parsea respuestas. No hay flujo "URL -> esquema -> recipe -> run".

data_factory (issue 0097) tiene nodos vacios. Necesita una via para crearlos rapido desde una pagina web.

Objetivo

Anadir 4 features convergentes:

  1. Element picker (panel Pick / Tab Detail): click sobre elementos -> selector CSS robusto.
  2. Network -> rows: parse JSON responses XHR/fetch -> tabla -> CSV/Save recipe.
  3. AutoExtract IA: URL -> abrir tab -> capturar accessibility tree (no HTML completo) -> claude -p propone schema + selectors -> preview -> save recipe.
  4. data_factory bridge: cada recipe ejecutada -> registra run en data_factory.runs + crea node con kind=extractor.

Decisiones tecnicas

Decision Eleccion
LLM API claude -p "<prompt>" CLI subprocess (NO API key)
Modelo Sonnet (default de claude -p)
Page representation para LLM CDP Accessibility tree (Accessibility.getFullAXTree) + paginacion. NO HTML completo
Recipe format YAML en ~/.dagu/dags/recipes/<slug>.yaml o projects/navegator/profiles/<profile>/recipes/<slug>.yaml
Persistencia filesystem YAML + entry en data_factory.nodes cuando se guarda
Pagination accessibility tree truncado por chunks (~25KB chars) con nextPageToken simulado

Por que accessibility tree

  • Semantico: roles, labels, valores. Sin estilo, sin scripts, sin SVG.
  • Tipico 5-20x mas pequeño que HTML para misma info.
  • Chrome CDP: Accessibility.getFullAXTree { depth: -1 } devuelve array AXNode. Cada nodo: role, name, value, role.value + childIds.
  • Permite identificar campos via role (button/textbox/link/heading/table/cell) + name.
  • LLM razona mejor sobre AX tree porque encaja con su entrenamiento (apps accessibility).

Componentes

Fase A — Element picker

Panel "Pick" boton dentro Tab Detail.

  1. Inyecta JS via Runtime.evaluate que:
    • Hover -> highlight outline rojo via overlay.
    • Click -> captura: CSS selector (algoritmo nth-of-type ascendente truncado), XPath, textContent, tagName, attributes.
    • console.log({ picked: {...} }).
  2. C++ escucha Runtime.consoleAPICalled via WS, parsea payload.
  3. Render card en panel con los datos. Boton "Copy selector", "Save as data_factory node".

Funciones nuevas:

  • cdp_pick_element_js_browser (string JS snippet, registrada como funcion del registry para reutilizar).

Fase B — Network -> rows

En panel Network ya existente:

  • Filtra responses con content_type: application/json.
  • Click "Parse" en una row -> intenta JSON.parse -> si es array -> renderiza tabla.
  • Si es objeto con array dentro -> autodetect path (busca primer array > 0 elementos).
  • Boton "Save as recipe": genera YAML con url_pattern, intercept_response, schema inferido.

Funciones nuevas:

  • infer_json_rows_schema_py_core (puro, py) — recibe JSON, devuelve {root_path, fields: [{name, type, sample}]}.

Fase C — Recipe YAML + runner

Recipe format:

name: bbva_balance
description: Saldo cuenta principal
url_pattern: "bbva.es/.*/movimientos"
trigger: manual
schedule: ""              # opcional cron
steps:
  - wait_selector: "table.movimientos tbody tr"
  - js: |
      return [...document.querySelectorAll('table.movimientos tbody tr')].map(r => ({
        date: r.cells[0].innerText,
        concept: r.cells[1].innerText,
        amount: parseFloat(r.cells[2].innerText.replace(',', '.')),
      }));
output:
  schema:
    - {field: date, type: string}
    - {field: concept, type: string}
    - {field: amount, type: float}
  format: json
  sink: data_factory.runs

Funciones nuevas:

  • cdp_extract_recipe_py_pipelines (impuro pipeline). Args: recipe_path, [tab_id]. Output: dict rows + status.
    • Si no hay tab abierto con url_pattern -> error claro "no tab matching, open URL manually".
    • Ejecuta cada step. wait_selector -> CDP Runtime.evaluate polling. js -> Runtime.evaluate retorna value.
    • Si output.sink=data_factory.runs -> llama data_factory_record_run_py_pipelines.

Fase D — LLM proposer (claude -p)

Panel AutoExtract nuevo:

UI:

  • Input URL.
  • Boton "Open & Analyze" -> abre nueva tab Chrome (CDP Target.createTarget).
  • Wait Page.loadEventFired.
  • Captura accessibility tree via Accessibility.getFullAXTree.
  • Trim tree: descarta nodos role=generic sin name/children utiles.
  • Si tree > 25KB: pagina (split por subarboles top-level).
  • Llama claude -p con prompt + chunk[i].
  • Para cada chunk, recibe {fields: [...], notes}. Merge resultados.
  • Render schema propuesto en tabla editable (puedes editar field name, selector, type).
  • Boton "Test extraction" -> ejecuta JS construido a partir del schema + selectors -> preview filas.
  • Boton "Save as recipe" -> escribe YAML + crea node en data_factory.

Funciones nuevas:

  • claude_cli_prompt_py_infra (impura, py) — wrapper subprocess.run(["claude", "-p", prompt]). Captura stdout. Timeout configurable. Error si no encuentra claude en PATH.
  • cdp_get_ax_tree_py_pipelines (impura) — connect to chrome debugging, call Accessibility.getFullAXTree, return trimmed JSON.
  • trim_ax_tree_py_core (puro) — descarta nodos generic-sin-info, colapsa cadenas single-child, devuelve estructura compacta.
  • chunk_ax_tree_py_core (puro) — splittea tree en chunks de N chars max preservando contexto root.
  • llm_propose_scraping_schema_py_infra (impura) — orquesta: trim + chunk + N calls claude -p + merge. Output schema final.
  • cdp_open_url_and_wait_py_pipelines (impura) — abre URL via CDP, waits load event, devuelve tab_id.

Fase E — data_factory bridge

Cuando recipe corre OK:

  1. cdp_extract_recipe_py_pipelines se asegura que node existe en data_factory.nodes (upsert por name).
  2. Llama data_factory_record_run_py_pipelines(node_id, "cdp_extract_recipe_py_pipelines", args=[recipe_path], trigger="manual").
  3. UI navegator_dashboard muestra link "Open in data_factory" al lado del recipe (futuro tab nav cross-app).

Fase F — Recipes panel

Nuevo panel "Recipes":

  • Tabla: name | url_pattern | schedule | last_run_status | last_run_at | rows_last_run
  • Acciones por row: Run, Edit (abre YAML en selectable_text editable), Delete, Open in data_factory.
  • Filtro por tag/url_pattern.

Fase G — e2e_checks + deploy

app.md e2e_checks:

  • build_windows (ya existe).
  • exe_present (ya existe).
  • api_health (ya existe).
  • claude_cli_availablecommand -v claude exit 0.

redeploy_cpp_app_windows navegator_dashboard projects/navegator/apps/navegator_dashboard --build.

Riesgos

Riesgo Mitigacion
claude -p no instalado en PATH check al arrancar app + tooltip "install claude code CLI". Boton AutoExtract deshabilitado.
AX tree gigante (paginas tipo dashboards admin) trim + chunk + max 5 chunks por URL. Notas claras si truncamos.
LLM propone selectors fragiles user edita antes de save. Recipe versionada YAML.
Recipe corre contra tab equivocada url_pattern + match estricto. Confirma antes de run.
Cookies/sesion no persisten v1 asume user mantiene chrome con sesion abierta. v2: cookies/auth manager.
claude -p lento (5-15s) UI spinner + cancel button. No bloquea otros paneles.
Recipe YAML format inconsistente validator schema-check antes de save. Funcion validate_recipe_yaml_py_core.

No-objetivos v1

  • Cookies/auth manager (Fase F future).
  • Headless scraping (asume chrome visible).
  • Multi-page navigation dentro de recipe (1 url = 1 recipe).
  • Scheduled recipes via cron (se delega a dag_engine: DAG step function: cdp_extract_recipe_py_pipelines args: [recipe_path]).

Funciones nuevas (resumen, ~9)

ID Lang Purity
claude_cli_prompt_py_infra py impure
cdp_pick_element_js_browser js (str) n/a
cdp_get_ax_tree_py_pipelines py impure
trim_ax_tree_py_core py pure
chunk_ax_tree_py_core py pure
llm_propose_scraping_schema_py_infra py impure
cdp_open_url_and_wait_py_pipelines py impure
cdp_extract_recipe_py_pipelines py impure
infer_json_rows_schema_py_core py pure
validate_recipe_yaml_py_core py pure

Tag de capability group: navegator (nuevo, >=3 funciones encajan). Mother page en docs/capabilities/navegator.md.

Aceptacion

  • 4 paneles nuevos visibles: Pick (Tab Detail), AutoExtract, Recipes. Network panel extendido con boton "Parse JSON".
  • claude -p invocable desde la app si CLI disponible.
  • Test E2E: abro URL https://news.ycombinator.com -> autoextract -> obtengo schema {title, url, points, comments} -> save recipe -> run -> >=20 rows -> aparece run en data_factory.
  • redeploy_cpp_app_windows navegator_dashboard pass + exe corriendo.
  • fn doctor cpp-apps OK para navegator_dashboard.
  • 1 recipe canonica salvada en repo como ejemplo.

Telemetria objetivo

  • Capability group navegator con >=8 funciones.
  • data_factory.nodes con >=3 nodos kind=extractor creados via recipe.
  • data_factory.runs con runs reales.