package main import ( "fmt" "strings" "fn-registry/functions/browser" ) // captchaMarker corre la detección de captcha/challenge anti-bot sobre el tab // del puerto dado (vía browser.DetectCaptcha) y, si detecta un widget conocido // (reCAPTCHA, hCaptcha, Cloudflare Turnstile o un JS-challenge de Cloudflare), // devuelve un marcador de texto para appendear al output de la tool que la // invoca. Si no hay captcha devuelve "". // // Es best-effort por diseño: cualquier error de conexión o de evaluación se // ignora y devuelve "" — la detección NUNCA debe romper ni bloquear la tool de // navegación/percepción de la que cuelga. Garantiza que el captcha se detecta // SIEMPRE en los puntos de carga (navegar, esperar load/idle, percibir) sin que // el agente tenga que acordarse de comprobarlo. // // El MCP solo detecta y marca. La reacción (avisar al humano por PushNotification // al móvil, traer la ventana del Chrome al frente y PARAR la automatización) la // dispara Claude al leer el marcador. El binario no resuelve el captcha ni // notifica por sí mismo: el captcha lo resuelve el humano a mano en este mismo // navegador, porque el token va atado a la sesión, cookies y fingerprint de ESTE // Chrome y no es transferible a otra ventana. func (d *deps) captchaMarker(port int) string { var marker string _ = d.withConn(port, func(c *browser.CDPConn) error { detected, types, url, err := browser.DetectCaptcha(c) if err != nil || !detected { return nil } marker = fmt.Sprintf( "\n\n⚠️ CAPTCHA-DETECTED type=%s url=%s\n"+ "HANDOFF HUMANO: PARA aquí. Avisa al humano (PushNotification al móvil) y trae "+ "la ventana del Chrome al frente. NO intentes resolver el captcha ni sigas "+ "clicando — el humano lo resuelve a mano en este mismo navegador y luego dice "+ "\"sigue\".", strings.Join(types, ","), url) return nil }) return marker }