feat(browser): auto-commit con 44 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 12:49:54 +02:00
parent e2c073b8b7
commit 5b10b419a2
44 changed files with 2543 additions and 28 deletions
@@ -0,0 +1,77 @@
---
name: cdp_perceive_outline
kind: pipeline
lang: py
domain: pipelines
version: "1.0.0"
purity: impure
signature: "def cdp_perceive_outline(debug_port: int, tab_id: str, max_chars: int = 20000) -> str"
description: "Pipeline de percepción: conecta a Chrome via CDP, obtiene el AX tree completo, lo poda y lo convierte en un outline indentado legible para LLMs. Cada nodo accionable lleva #ref=nodeId. Reemplaza enviar 1k-50k nodos JSON crudos al modelo."
tags: [browser, cdp, ax-tree, perception, navegator, llm]
uses_functions: [cdp_get_ax_tree_py_pipelines, trim_ax_tree_py_core, render_ax_outline_py_core]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [argparse, sys, os]
params:
- name: debug_port
desc: "Puerto de debug remoto de Chrome (ej. 9333). Chrome debe estar corriendo con --remote-debugging-port=PORT."
- name: tab_id
desc: "ID del tab CDP, campo 'id' de GET http://127.0.0.1:{port}/json/list. Usar cdp_list_tabs_go_browser para listarlo."
- name: max_chars
desc: "Límite de caracteres del outline resultante. Default 20000 (~5k tokens). 0 = sin límite. Si la página es muy densa, reducir a 10000 para no saturar el context window."
output: "String multi-línea con el outline indentado de la página. Nodos accionables tienen ' #ref=nodeId' alineado. El LLM puede responder 'haz clic en #ref=44' para operar la página."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/pipelines/cdp_perceive_outline.py"
---
## Ejemplo
```bash
# Via fn run (patrón canónico para agentes)
./fn run cdp_perceive_outline --debug-port 9333 --tab-id <id>
# Obtener tab_id primero:
curl -s http://127.0.0.1:9333/json/list | python3 -m json.tool | grep '"id"'
./fn run cdp_perceive_outline --debug-port 9333 --tab-id "A1B2C3D4..." --max-chars 15000
```
```python
# Uso desde Python (heredoc o pipeline propio)
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from pipelines.cdp_perceive_outline import cdp_perceive_outline
outline = cdp_perceive_outline(debug_port=9333, tab_id="A1B2C3D4...")
print(outline)
# RootWebArea "GitHub"
# navigation "Site navigation"
# link "Homepage" #ref=12
# button "Search" #ref=18
# main
# heading "Repositories"
# link "fn_registry" #ref=44
# textbox "Filter repositories" #ref=51
```
## Cuando usarla
Cuando un agente LLM necesita "ver" una página Chrome ya abierta para decidir qué elemento accionar a continuación. Sustituye enviar el AX tree crudo (1k-50k nodos JSON) al modelo por un outline compacto de ~200-500 líneas. El `#ref=nodeId` hace que el LLM pueda responder con una referencia exacta sin ambigüedad.
Flujo típico de un agente browser:
1. `cdp_list_tabs` → obtener `tab_id`
2. `cdp_perceive_outline` → outline compacto de la página
3. LLM decide acción (clic en #ref=44, texto en #ref=51, etc.)
4. `cdp_click_node` / `cdp_type_text` con el nodeId extraído del #ref
## Gotchas
- Chrome debe estar corriendo con `--remote-debugging-port=<port>`. En Linux nativo: `chromium --remote-debugging-port=9333 &`. Con CDP global activado en `/etc/chromium.d/cdp`, el puerto 9222 siempre está disponible.
- El tab no puede tener DevTools abierto (toma el debugger exclusivo). Cerrar DevTools antes de llamar.
- `Accessibility.getFullAXTree` puede tardar 2-10s en páginas muy pesadas (SPAs tipo Gmail con miles de nodos). El timeout total es 15s.
- El outline resultante puede superar `max_chars` en ~100 chars si el último nodo visible es muy largo. Usar margen holgado (ej. 18000 en vez de 20000 si el context window es ajustado).
- Si la página no tiene contenido accesible (ej. canvas puro, PDF embebido), el outline estará vacío o solo tendrá el RootWebArea. En ese caso usar CDP JS evaluation directamente.
- `tab_id` es el campo `"id"` del JSON de `/json/list`, no `"targetId"`. Son diferentes.
@@ -0,0 +1,79 @@
"""Pipeline: obtiene el AX tree de un tab Chrome y lo convierte en outline legible."""
import argparse
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from pipelines.cdp_get_ax_tree import cdp_get_ax_tree
from core.trim_ax_tree import trim_ax_tree
from core.render_ax_outline import render_ax_outline
def cdp_perceive_outline(
debug_port: int,
tab_id: str,
max_chars: int = 20000,
) -> str:
"""Obtiene el AX tree de un tab Chrome y devuelve un outline indentado legible.
Compone tres pasos:
1. cdp_get_ax_tree — obtiene nodos crudos via CDP WebSocket.
2. trim_ax_tree — poda nodos irrelevantes (ignored, generic sin hijos, etc.).
3. render_ax_outline — convierte en outline indentado con #ref para accionables.
Args:
debug_port: Puerto de debug remoto de Chrome (ej. 9333).
Chrome debe estar corriendo con --remote-debugging-port=PORT.
tab_id: ID del tab CDP. Obtenerlo via GET http://127.0.0.1:{port}/json/list
o con cdp_list_tabs_go_browser.
max_chars: Límite de caracteres del outline resultante. 0 = sin límite.
Default 20000 (~5k tokens), apropiado para context window de Claude.
Returns:
String con el outline indentado. Cada nodo accionable tiene #ref=nodeId
para que el LLM pueda referenciarlo en acciones posteriores.
Raises:
RuntimeError: Si Chrome no responde, el tab no existe, o falla la conexión WS.
TimeoutError: Si Accessibility.getFullAXTree no responde en 15s.
"""
nodes = cdp_get_ax_tree(debug_port=debug_port, tab_id=tab_id)
trimmed = trim_ax_tree(nodes)
return render_ax_outline(trimmed, max_chars=max_chars)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Obtiene el outline del AX tree de un tab Chrome via CDP."
)
parser.add_argument(
"--debug-port",
type=int,
default=9222,
help="Puerto de debug remoto de Chrome (default: 9222).",
)
parser.add_argument(
"--tab-id",
required=True,
help="ID del tab CDP (campo 'id' de GET /json/list).",
)
parser.add_argument(
"--max-chars",
type=int,
default=20000,
help="Límite de caracteres del outline. 0 = sin límite (default: 20000).",
)
args = parser.parse_args()
try:
outline = cdp_perceive_outline(
debug_port=args.debug_port,
tab_id=args.tab_id,
max_chars=args.max_chars,
)
print(outline)
except (RuntimeError, TimeoutError) as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)