chore: auto-commit (3 archivos)
- tools_nav.go - tools_read.go - captcha_sniff.go Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
+9
-5
@@ -54,7 +54,7 @@ func (d *deps) handleTabNavigate(_ context.Context, _ mcp.CallToolRequest, a tab
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultError(err.Error()), nil
|
return mcp.NewToolResultError(err.Error()), nil
|
||||||
}
|
}
|
||||||
return mcp.NewToolResultText("navigated to " + a.URL), nil
|
return mcp.NewToolResultText("navigated to " + a.URL + d.captchaMarker(portOr(a.Port))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- tab_list ----
|
// ---- tab_list ----
|
||||||
@@ -88,14 +88,18 @@ type tabNewArgs struct {
|
|||||||
|
|
||||||
func tabNewTool() mcp.Tool {
|
func tabNewTool() mcp.Tool {
|
||||||
return mcp.NewTool("tab_new",
|
return mcp.NewTool("tab_new",
|
||||||
mcp.WithDescription("Open a new tab via PUT /json/new. Returns the new tab's JSON."),
|
mcp.WithDescription("Open a new tab in the BACKGROUND via Target.createTarget (background:true). No roba el foco del WM: la ventana del navegador no se eleva, el usuario sigue escribiendo donde estaba. Returns the new tab's JSON."),
|
||||||
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
mcp.WithNumber("port", mcp.Description("CDP port. Default 9333 (Chrome isolated del MCP); usa 9222 explícito solo para adjuntarte al navegador diario.")),
|
||||||
mcp.WithString("url", mcp.Description("Optional start URL. Empty = about:blank.")),
|
mcp.WithString("url", mcp.Description("Optional start URL. Empty = about:blank.")),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deps) handleTabNew(_ context.Context, _ mcp.CallToolRequest, a tabNewArgs) (*mcp.CallToolResult, error) {
|
func (d *deps) handleTabNew(_ context.Context, _ mcp.CallToolRequest, a tabNewArgs) (*mcp.CallToolResult, error) {
|
||||||
tab, err := browser.CdpNewTab("localhost", portOr(a.Port), a.URL)
|
// CdpNewTabBackground usa Target.createTarget{background:true} en vez de
|
||||||
|
// PUT /json/new. El endpoint HTTP /json/new SIEMPRE trae la pestaña al
|
||||||
|
// frente y eleva la ventana del SO, robando el foco del usuario que está
|
||||||
|
// escribiendo en otra ventana. La variante background no eleva la ventana.
|
||||||
|
tab, err := browser.CdpNewTabBackground("localhost", portOr(a.Port), a.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultError(err.Error()), nil
|
return mcp.NewToolResultError(err.Error()), nil
|
||||||
}
|
}
|
||||||
@@ -247,7 +251,7 @@ func (d *deps) handlePageWaitLoad(_ context.Context, _ mcp.CallToolRequest, a pa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultError(err.Error()), nil
|
return mcp.NewToolResultError(err.Error()), nil
|
||||||
}
|
}
|
||||||
return mcp.NewToolResultText("page loaded"), nil
|
return mcp.NewToolResultText("page loaded" + d.captchaMarker(portOr(a.Port))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- page_wait_idle ----
|
// ---- page_wait_idle ----
|
||||||
@@ -279,5 +283,5 @@ func (d *deps) handlePageWaitIdle(_ context.Context, _ mcp.CallToolRequest, a pa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultError(err.Error()), nil
|
return mcp.NewToolResultError(err.Error()), nil
|
||||||
}
|
}
|
||||||
return mcp.NewToolResultText("network idle"), nil
|
return mcp.NewToolResultText("network idle" + d.captchaMarker(portOr(a.Port))), nil
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -173,7 +173,7 @@ func (d *deps) handlePagePerceive(_ context.Context, _ mcp.CallToolRequest, a pa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultError(err.Error()), nil
|
return mcp.NewToolResultError(err.Error()), nil
|
||||||
}
|
}
|
||||||
return mcp.NewToolResultText(outline), nil
|
return mcp.NewToolResultText(outline + d.captchaMarker(port)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// perceiveOutline genera el outline AX accionable de la página entera sobre la
|
// perceiveOutline genera el outline AX accionable de la página entera sobre la
|
||||||
|
|||||||
Reference in New Issue
Block a user