From 91973ed6f9a7f3275e02b6981a6aaceff14c7e27 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Tue, 16 Jun 2026 20:05:51 +0200 Subject: [PATCH] =?UTF-8?q?feat(browser=5Flist):=20a=C3=B1ade=20campo=20he?= =?UTF-8?q?adless=20por=20master?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit browser_list ahora reporta si cada Chromium master se lanzo en modo headless, detectado por el flag de arranque (--headless / --headless=new / --headless=old) leido del cmdline. Una sola llamada devuelve navegadores activos + CDP + headless, sin tener que conectar a cada pagina para fingerprintear. Co-Authored-By: Claude Opus 4.8 (1M context) --- tools_lifecycle.go | 17 ++++++++++++++++- tools_lifecycle_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/tools_lifecycle.go b/tools_lifecycle.go index 526900b..7d4860b 100644 --- a/tools_lifecycle.go +++ b/tools_lifecycle.go @@ -55,6 +55,7 @@ type chromiumMaster struct { UserDataDir string `json:"user_data_dir"` // value of --user-data-dir CDPPort string `json:"cdp_port"` // value of --remote-debugging-port ("" if none) HasCDP bool `json:"has_cdp"` + Headless bool `json:"headless"` // true if launched with --headless / --headless=new / --headless=old } // parseCmdline turns the raw bytes of /proc//cmdline into argv. @@ -154,9 +155,23 @@ func parseChromiumMaster(pid int, args []string) (chromiumMaster, bool) { UserDataDir: udd, CDPPort: port, HasCDP: hasCDP, + Headless: isHeadless(args), }, true } +// isHeadless reports whether the process was launched in headless mode. Chromium +// spells it "--headless", "--headless=new" or "--headless=old"; matching the +// "--headless" prefix covers all three. There is no current Chromium flag that +// starts with "--headless" but means something else, so the prefix is safe. +func isHeadless(args []string) bool { + for _, a := range args { + if a == "--headless" || strings.HasPrefix(a, "--headless=") { + return true + } + } + return false +} + // firstNonEmpty returns the flag value or "" if absent. func firstNonEmpty(args []string, name string) string { v, _ := flagValue(args, name) @@ -256,7 +271,7 @@ type browserListArgs struct{} func browserListTool() mcp.Tool { return mcp.NewTool("browser_list", - mcp.WithDescription("List the running Chromium MASTER processes (one per user-data-dir master, NOT zygote/gpu/renderer children). For each: pid, profile (--profile-directory value), user_data_dir, cdp_port (--remote-debugging-port value, empty if none), has_cdp. Returns a JSON array. Read-only."), + mcp.WithDescription("List the running Chromium MASTER processes (one per user-data-dir master, NOT zygote/gpu/renderer children). For each: pid, profile (--profile-directory value), user_data_dir, cdp_port (--remote-debugging-port value, empty if none), has_cdp, headless (true if launched with --headless). Returns a JSON array. Read-only."), ) } diff --git a/tools_lifecycle_test.go b/tools_lifecycle_test.go index 21f4e78..5229580 100644 --- a/tools_lifecycle_test.go +++ b/tools_lifecycle_test.go @@ -195,3 +195,31 @@ func TestParseCmdline(t *testing.T) { t.Errorf("cmdline vacio debe devolver nil") } } + +// TestIsHeadless valida la deteccion de modo headless por el flag de lanzamiento: +// --headless, --headless=new y --headless=old cuentan; su ausencia es headed. +func TestIsHeadless(t *testing.T) { + cases := []struct { + name string + args []string + want bool + }{ + {"sin flag (headed)", []string{"/usr/lib/chromium/chromium", "--user-data-dir=/tmp/x"}, false}, + {"--headless legacy", []string{"/usr/lib/chromium/chromium", "--headless", "--user-data-dir=/tmp/x"}, true}, + {"--headless=new", []string{"/usr/lib/chromium/chromium", "--headless=new"}, true}, + {"--headless=old", []string{"/usr/lib/chromium/chromium", "--headless=old"}, true}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := isHeadless(c.args); got != c.want { + t.Errorf("isHeadless(%v) = %v, want %v", c.args, got, c.want) + } + }) + } + + // El master headed real (cmdline colapsado por espacios) debe reportar headless=false. + headed := parseCmdline([]byte("/usr/lib/chromium/chromium --remote-debugging-port=9333 --user-data-dir=/tmp/browser_mcp_userdata")) + if m, ok := parseChromiumMaster(1, headed); !ok || m.Headless { + t.Errorf("master headed: ok=%v headless=%v, want ok=true headless=false", ok, m.Headless) + } +}