myagent-ai 1.47.5 → 1.47.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/aiskills/browser_stealth.py +102 -27
- package/package.json +1 -1
|
@@ -100,6 +100,9 @@ def _start_xvfb(display_num: int = 99) -> Optional[str]:
|
|
|
100
100
|
"""
|
|
101
101
|
启动 Xvfb 虚拟显示服务器。
|
|
102
102
|
|
|
103
|
+
[v1.47.10] 增强:如果指定 display 号被占用,自动尝试其他 display 号(:100-:109)。
|
|
104
|
+
僵尸 Xvfb 进程(OOM Kill 后残留锁文件)是 returncode=1 的常见原因。
|
|
105
|
+
|
|
103
106
|
Returns:
|
|
104
107
|
DISPLAY 环境变量值(如 :99),失败返回 None
|
|
105
108
|
"""
|
|
@@ -127,36 +130,65 @@ def _start_xvfb(display_num: int = 99) -> Optional[str]:
|
|
|
127
130
|
logger.warning("Xvfb 未安装,无法启动虚拟显示")
|
|
128
131
|
return None
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
# [v1.47.10] 尝试多个 display 号,避免锁文件冲突
|
|
134
|
+
# 优先用 :99(和 VNC 一致),失败则尝试 :100-:109
|
|
135
|
+
display_candidates = [display_num] + list(range(100, 110))
|
|
136
|
+
|
|
137
|
+
for dn in display_candidates:
|
|
138
|
+
display_str = f":{dn}"
|
|
139
|
+
try:
|
|
140
|
+
# 先清理可能残留的 X11 锁文件
|
|
141
|
+
for lock_file in (f"/tmp/.X{dn}-lock", f"/tmp/.X11-unix/X{dn}"):
|
|
142
|
+
if os.path.exists(lock_file):
|
|
143
|
+
try:
|
|
144
|
+
os.unlink(lock_file)
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
# 检查是否有 Xvfb 进程已经在用这个 display
|
|
149
|
+
try:
|
|
150
|
+
result = subprocess.run(
|
|
151
|
+
["pgrep", "-f", f"Xvfb :{dn}"],
|
|
152
|
+
capture_output=True, text=True, timeout=3,
|
|
153
|
+
)
|
|
154
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
155
|
+
# 已有进程在用这个 display,复用它
|
|
156
|
+
_xvfb_display = display_str
|
|
157
|
+
os.environ["DISPLAY"] = display_str
|
|
158
|
+
logger.info(f"检测到已有 Xvfb 进程使用 display :{dn},复用")
|
|
159
|
+
return display_str
|
|
160
|
+
except Exception:
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
_xvfb_process = subprocess.Popen(
|
|
164
|
+
[xvfb_path, display_str, "-screen", "0", "1920x1080x24", "-ac", "-nolisten", "tcp"],
|
|
165
|
+
stdout=subprocess.DEVNULL,
|
|
166
|
+
stderr=subprocess.PIPE,
|
|
167
|
+
)
|
|
168
|
+
# 等待 Xvfb 启动
|
|
169
|
+
time.sleep(0.8)
|
|
170
|
+
if _xvfb_process.poll() is not None:
|
|
171
|
+
stderr = ""
|
|
135
172
|
try:
|
|
136
|
-
|
|
173
|
+
stderr = _xvfb_process.stderr.read().decode("utf-8", errors="replace")[:300]
|
|
137
174
|
except Exception:
|
|
138
175
|
pass
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
176
|
+
logger.debug(f"Xvfb :{dn} 启动失败 (rc={_xvfb_process.returncode}): {stderr}")
|
|
177
|
+
_xvfb_process = None
|
|
178
|
+
# 继续尝试下一个 display 号
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
_xvfb_display = display_str
|
|
182
|
+
os.environ["DISPLAY"] = display_str
|
|
183
|
+
logger.info(f"Xvfb 虚拟显示已启动: {display_str}")
|
|
184
|
+
return display_str
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.debug(f"启动 Xvfb :{dn} 异常: {e}")
|
|
149
187
|
_xvfb_process = None
|
|
150
|
-
|
|
188
|
+
continue
|
|
151
189
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
logger.info(f"Xvfb 虚拟显示已启动: {display_str}")
|
|
155
|
-
return display_str
|
|
156
|
-
except Exception as e:
|
|
157
|
-
logger.error(f"启动 Xvfb 失败: {e}")
|
|
158
|
-
_xvfb_process = None
|
|
159
|
-
return None
|
|
190
|
+
logger.error("所有 display 号均无法启动 Xvfb")
|
|
191
|
+
return None
|
|
160
192
|
|
|
161
193
|
|
|
162
194
|
def _stop_xvfb() -> None:
|
|
@@ -232,7 +264,9 @@ def _ensure_display() -> Optional[Dict[str, Any]]:
|
|
|
232
264
|
logger.warning(f"VNC 检测异常: {e}")
|
|
233
265
|
|
|
234
266
|
# 2. VNC 不可用 → 独立启动 Xvfb
|
|
235
|
-
|
|
267
|
+
# [v1.47.10] 用 display :100 避免和 VNC 的 :99 冲突
|
|
268
|
+
# 如果 VNC 刚被杀但锁文件还在,用 :99 会 returncode=1
|
|
269
|
+
xvfb_display = _start_xvfb(display_num=100)
|
|
236
270
|
if xvfb_display:
|
|
237
271
|
return {"display": xvfb_display, "vnc": False, "xvfb_standalone": True}
|
|
238
272
|
|
|
@@ -1664,9 +1698,42 @@ class StealthBrowser:
|
|
|
1664
1698
|
except Exception as e:
|
|
1665
1699
|
logger.debug(f"清理残留 Chrome 进程时出错: {e}")
|
|
1666
1700
|
|
|
1701
|
+
@staticmethod
|
|
1702
|
+
def _is_snap_wrapper(path: str) -> bool:
|
|
1703
|
+
"""[v1.47.10] 检测浏览器路径是否是 snap 包装器。
|
|
1704
|
+
|
|
1705
|
+
Ubuntu 22.04+ 的 chromium-browser 和 firefox 可能是 snap 包装脚本,
|
|
1706
|
+
在 proot 环境下不可用(snapd 需要 systemd)。
|
|
1707
|
+
"""
|
|
1708
|
+
try:
|
|
1709
|
+
if not os.path.isfile(path):
|
|
1710
|
+
return False
|
|
1711
|
+
# snap 包装器通常是 shell 脚本,包含 "snap" 关键字
|
|
1712
|
+
with open(path, "r", errors="replace") as f:
|
|
1713
|
+
content = f.read(2048)
|
|
1714
|
+
# 常见 snap 包装脚本特征
|
|
1715
|
+
snap_signatures = [
|
|
1716
|
+
"snap", # snap run chromium / snap run firefox
|
|
1717
|
+
"/snap/", # snap 路径
|
|
1718
|
+
"SNAP_NAME", # snap 环境变量
|
|
1719
|
+
"snapd", # snap daemon
|
|
1720
|
+
]
|
|
1721
|
+
# 只有文本文件(脚本)才检查,二进制不需要
|
|
1722
|
+
if content.startswith("#!") or content.startswith("/*"):
|
|
1723
|
+
for sig in snap_signatures:
|
|
1724
|
+
if sig in content:
|
|
1725
|
+
return True
|
|
1726
|
+
except Exception:
|
|
1727
|
+
pass
|
|
1728
|
+
return False
|
|
1729
|
+
|
|
1667
1730
|
@staticmethod
|
|
1668
1731
|
def _detect_browser() -> Optional[str]:
|
|
1669
|
-
"""自动检测 Chromium/Chrome 浏览器路径
|
|
1732
|
+
"""自动检测 Chromium/Chrome 浏览器路径
|
|
1733
|
+
|
|
1734
|
+
[v1.47.10] 增加 snap 包装器检测:proot 下 snap 不可用,
|
|
1735
|
+
跳过 snap 包装脚本,避免浏览器启动后无法连接。
|
|
1736
|
+
"""
|
|
1670
1737
|
# 1. 环境变量
|
|
1671
1738
|
for key in ("CHROME_PATH", "BROWSER_PATH", "CHROMIUM_PATH"):
|
|
1672
1739
|
val = os.environ.get(key, "").strip()
|
|
@@ -1680,6 +1747,10 @@ class StealthBrowser:
|
|
|
1680
1747
|
):
|
|
1681
1748
|
found = shutil.which(cmd)
|
|
1682
1749
|
if found:
|
|
1750
|
+
# [v1.47.10] 跳过 snap 包装器
|
|
1751
|
+
if StealthBrowser._is_snap_wrapper(found):
|
|
1752
|
+
logger.info(f"跳过 {cmd} ({found}) — snap 包装器,proot 下不可用")
|
|
1753
|
+
continue
|
|
1683
1754
|
return found
|
|
1684
1755
|
|
|
1685
1756
|
# 3. 操作系统特定路径
|
|
@@ -1718,6 +1789,10 @@ class StealthBrowser:
|
|
|
1718
1789
|
"/usr/bin/microsoft-edge",
|
|
1719
1790
|
):
|
|
1720
1791
|
if os.path.isfile(p) and os.access(p, os.X_OK):
|
|
1792
|
+
# [v1.47.10] 跳过 snap 包装器
|
|
1793
|
+
if StealthBrowser._is_snap_wrapper(p):
|
|
1794
|
+
logger.info(f"跳过 {p} — snap 包装器,proot 下不可用")
|
|
1795
|
+
continue
|
|
1721
1796
|
return p
|
|
1722
1797
|
|
|
1723
1798
|
# 4. Puppeteer / Playwright 缓存
|