myagent-ai 1.33.1 → 1.33.2
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 +86 -11
- package/aiskills/chromedev_mcp.py +65 -7
- package/package.json +1 -1
|
@@ -178,6 +178,67 @@ def _stop_xvfb() -> None:
|
|
|
178
178
|
logger.info("Xvfb 虚拟显示已停止")
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
def _ensure_display() -> Optional[Dict[str, Any]]:
|
|
182
|
+
"""
|
|
183
|
+
确保有可用的 X11 显示,供浏览器有头模式使用。
|
|
184
|
+
|
|
185
|
+
优先级:
|
|
186
|
+
1. 复用 VNC 远程桌面的 Xvfb 显示(用户可在 VNC 中看到浏览器操作)
|
|
187
|
+
2. 自动启动 VNC(含 Xvfb + x11vnc + websockify)
|
|
188
|
+
3. VNC 不可用时独立启动 Xvfb
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
{"display": ":99", "vnc": True/False, "xvfb_standalone": True/False}
|
|
192
|
+
失败返回 None
|
|
193
|
+
"""
|
|
194
|
+
# 1. 尝试复用 VNC 远程桌面
|
|
195
|
+
try:
|
|
196
|
+
from core.vnc_manager import get_vnc_manager
|
|
197
|
+
vnc = get_vnc_manager()
|
|
198
|
+
|
|
199
|
+
# VNC 已在运行 → 直接复用其 DISPLAY
|
|
200
|
+
if vnc.is_running:
|
|
201
|
+
display = vnc.display # 默认 :99
|
|
202
|
+
os.environ["DISPLAY"] = display
|
|
203
|
+
logger.info(f"VNC 远程桌面已在运行,复用显示: {display}")
|
|
204
|
+
return {"display": display, "vnc": True, "xvfb_standalone": False}
|
|
205
|
+
|
|
206
|
+
# VNC 未运行 → 尝试自动启动
|
|
207
|
+
try:
|
|
208
|
+
import asyncio
|
|
209
|
+
loop = asyncio.get_event_loop()
|
|
210
|
+
if loop.is_running():
|
|
211
|
+
# 在已有事件循环中,用 run_until_complete 不行,
|
|
212
|
+
# 创建 task 等待完成
|
|
213
|
+
import concurrent.futures
|
|
214
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
215
|
+
result = pool.submit(asyncio.run, vnc.start()).result(timeout=30)
|
|
216
|
+
else:
|
|
217
|
+
result = asyncio.run(vnc.start())
|
|
218
|
+
|
|
219
|
+
if result.get("success"):
|
|
220
|
+
display = vnc.display
|
|
221
|
+
os.environ["DISPLAY"] = display
|
|
222
|
+
logger.info(f"VNC 远程桌面已自动启动,显示: {display},可在 VNC 中查看浏览器操作")
|
|
223
|
+
return {"display": display, "vnc": True, "xvfb_standalone": False}
|
|
224
|
+
else:
|
|
225
|
+
logger.warning(f"VNC 自动启动失败: {result.get('message', '')}")
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.warning(f"VNC 自动启动异常: {e}")
|
|
228
|
+
|
|
229
|
+
except ImportError:
|
|
230
|
+
logger.debug("VNC 管理器不可用,跳过 VNC 集成")
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.warning(f"VNC 检测异常: {e}")
|
|
233
|
+
|
|
234
|
+
# 2. VNC 不可用 → 独立启动 Xvfb
|
|
235
|
+
xvfb_display = _start_xvfb()
|
|
236
|
+
if xvfb_display:
|
|
237
|
+
return {"display": xvfb_display, "vnc": False, "xvfb_standalone": True}
|
|
238
|
+
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
|
|
181
242
|
# ── 反检测浏览器管理器 ──────────────────────────────────────
|
|
182
243
|
|
|
183
244
|
|
|
@@ -214,7 +275,8 @@ class StealthBrowser:
|
|
|
214
275
|
self._browser = None # DrissionPage Chromium 实例
|
|
215
276
|
self._started = False
|
|
216
277
|
self._user_data_dir = ""
|
|
217
|
-
self._xvfb_started_by_us = False #
|
|
278
|
+
self._xvfb_started_by_us = False # 是否由本实例独立启动的 Xvfb
|
|
279
|
+
self._vnc_used = False # 是否使用了 VNC 远程桌面的显示
|
|
218
280
|
|
|
219
281
|
def _get_user_data_dir(self) -> str:
|
|
220
282
|
"""获取用户数据目录路径"""
|
|
@@ -266,17 +328,23 @@ class StealthBrowser:
|
|
|
266
328
|
co.set_argument("--disable-gpu")
|
|
267
329
|
|
|
268
330
|
# ── 无 DISPLAY 环境处理 ──
|
|
269
|
-
#
|
|
270
|
-
#
|
|
331
|
+
# 优先级: VNC远程桌面 > 自启动Xvfb > 降级headless
|
|
332
|
+
# 当 headless=False 但没有可用显示时:
|
|
333
|
+
# 1. 优先复用 VNC 远程桌面的 Xvfb 显示(用户可在 VNC 中看到浏览器操作)
|
|
334
|
+
# 2. VNC 未运行时自动启动 VNC(含 Xvfb + x11vnc + websockify)
|
|
335
|
+
# 3. VNC 启动失败则尝试独立启动 Xvfb
|
|
336
|
+
# 4. 都不可用则降级为 headless 模式
|
|
271
337
|
if not self._headless and not _has_display():
|
|
272
|
-
|
|
273
|
-
if
|
|
274
|
-
self.
|
|
275
|
-
|
|
338
|
+
display = _ensure_display()
|
|
339
|
+
if display:
|
|
340
|
+
self._vnc_used = display.get("vnc", False)
|
|
341
|
+
self._xvfb_started_by_us = display.get("xvfb_standalone", False)
|
|
342
|
+
if self._vnc_used:
|
|
343
|
+
logger.info(f"复用 VNC 远程桌面显示 ({display['display']}),可在 VNC 中查看浏览器操作")
|
|
276
344
|
else:
|
|
277
345
|
self._headless = True
|
|
278
346
|
logger.warning(
|
|
279
|
-
"无 DISPLAY 环境且 Xvfb
|
|
347
|
+
"无 DISPLAY 环境且 VNC/Xvfb 均不可用,自动降级为 headless 模式"
|
|
280
348
|
)
|
|
281
349
|
|
|
282
350
|
# 无头模式(co.headless() 内部设置 --headless=new)
|
|
@@ -369,7 +437,8 @@ class StealthBrowser:
|
|
|
369
437
|
self._browser = None
|
|
370
438
|
self._page = None
|
|
371
439
|
|
|
372
|
-
#
|
|
440
|
+
# 如果由本实例独立启动了 Xvfb(非 VNC),关闭浏览器后停止虚拟显示
|
|
441
|
+
# 注意:VNC 管理自己的 Xvfb 生命周期,不在此停止
|
|
373
442
|
if self._xvfb_started_by_us:
|
|
374
443
|
self._xvfb_started_by_us = False
|
|
375
444
|
# 检查是否还有其他浏览器实例在使用 Xvfb
|
|
@@ -381,6 +450,9 @@ class StealthBrowser:
|
|
|
381
450
|
if not other_active:
|
|
382
451
|
_stop_xvfb()
|
|
383
452
|
|
|
453
|
+
# VNC 显示不由浏览器管理,无需停止
|
|
454
|
+
self._vnc_used = False
|
|
455
|
+
|
|
384
456
|
return SkillResult(success=True, message="浏览器已关闭")
|
|
385
457
|
|
|
386
458
|
async def navigate(self, url: str, wait: float = 2.0) -> SkillResult:
|
|
@@ -902,8 +974,11 @@ def close_stealth_browser(profile_name: str = "") -> None:
|
|
|
902
974
|
pass
|
|
903
975
|
_browsers.clear()
|
|
904
976
|
|
|
905
|
-
#
|
|
906
|
-
|
|
977
|
+
# 所有浏览器关闭后,仅在独立 Xvfb(非 VNC)时才停止
|
|
978
|
+
# VNC 管理自己的 Xvfb 生命周期
|
|
979
|
+
any_vnc = any(b._vnc_used for b in _browsers.values()) if _browsers else False
|
|
980
|
+
if not any_vnc:
|
|
981
|
+
_stop_xvfb()
|
|
907
982
|
|
|
908
983
|
|
|
909
984
|
async def close_stealth_browser_async(profile_name: str = "") -> None:
|
|
@@ -165,6 +165,63 @@ def _stop_xvfb() -> None:
|
|
|
165
165
|
logger.info("Xvfb 虚拟显示已停止 (chromedev_mcp)")
|
|
166
166
|
|
|
167
167
|
|
|
168
|
+
def _ensure_display() -> Optional[str]:
|
|
169
|
+
"""
|
|
170
|
+
确保有可用的 X11 显示,供浏览器有头模式使用。
|
|
171
|
+
|
|
172
|
+
优先级:
|
|
173
|
+
1. 复用 VNC 远程桌面的 Xvfb 显示(用户可在 VNC 中看到浏览器操作)
|
|
174
|
+
2. 自动启动 VNC(含 Xvfb + x11vnc + websockify)
|
|
175
|
+
3. VNC 不可用时独立启动 Xvfb
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
DISPLAY 值(如 ":99"),失败返回 None
|
|
179
|
+
"""
|
|
180
|
+
# 1. 尝试复用 VNC 远程桌面
|
|
181
|
+
try:
|
|
182
|
+
from core.vnc_manager import get_vnc_manager
|
|
183
|
+
vnc = get_vnc_manager()
|
|
184
|
+
|
|
185
|
+
# VNC 已在运行 → 直接复用其 DISPLAY
|
|
186
|
+
if vnc.is_running:
|
|
187
|
+
display = vnc.display # 默认 :99
|
|
188
|
+
os.environ["DISPLAY"] = display
|
|
189
|
+
logger.info(f"VNC 远程桌面已在运行,复用显示: {display}")
|
|
190
|
+
return display
|
|
191
|
+
|
|
192
|
+
# VNC 未运行 → 尝试自动启动
|
|
193
|
+
try:
|
|
194
|
+
import asyncio
|
|
195
|
+
try:
|
|
196
|
+
loop = asyncio.get_event_loop()
|
|
197
|
+
if loop.is_running():
|
|
198
|
+
import concurrent.futures
|
|
199
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
200
|
+
result = pool.submit(asyncio.run, vnc.start()).result(timeout=30)
|
|
201
|
+
else:
|
|
202
|
+
result = asyncio.run(vnc.start())
|
|
203
|
+
except RuntimeError:
|
|
204
|
+
result = asyncio.run(vnc.start())
|
|
205
|
+
|
|
206
|
+
if result.get("success"):
|
|
207
|
+
display = vnc.display
|
|
208
|
+
os.environ["DISPLAY"] = display
|
|
209
|
+
logger.info(f"VNC 远程桌面已自动启动,显示: {display}")
|
|
210
|
+
return display
|
|
211
|
+
else:
|
|
212
|
+
logger.warning(f"VNC 自动启动失败: {result.get('message', '')}")
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.warning(f"VNC 自动启动异常: {e}")
|
|
215
|
+
|
|
216
|
+
except ImportError:
|
|
217
|
+
logger.debug("VNC 管理器不可用,跳过 VNC 集成")
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.warning(f"VNC 检测异常: {e}")
|
|
220
|
+
|
|
221
|
+
# 2. VNC 不可用 → 独立启动 Xvfb
|
|
222
|
+
return _start_xvfb()
|
|
223
|
+
|
|
224
|
+
|
|
168
225
|
# ── MCP 通信常量 ──────────────────────────────────────────────
|
|
169
226
|
|
|
170
227
|
# chrome-devtools-mcp 支持的完整工具列表 (v0.21.0)
|
|
@@ -280,22 +337,23 @@ class MCPClient:
|
|
|
280
337
|
args.extend(["--chromeArg", "--no-sandbox", "--chromeArg", "--disable-setuid-sandbox"])
|
|
281
338
|
|
|
282
339
|
# [v1.17.0] 有头模式: 设置 DISPLAY 环境变量
|
|
340
|
+
# 优先级: 用户指定 DISPLAY > VNC 远程桌面 > 独立 Xvfb > 降级 headless
|
|
283
341
|
if not self._headless:
|
|
284
342
|
# 优先使用用户指定的 DISPLAY
|
|
285
343
|
if self._display_override:
|
|
286
344
|
env["DISPLAY"] = self._display_override
|
|
287
345
|
logger.info(f"有头模式: DISPLAY={self._display_override}")
|
|
288
|
-
#
|
|
346
|
+
# 无可用显示时,优先尝试 VNC 远程桌面,再尝试独立 Xvfb
|
|
289
347
|
elif not _has_display():
|
|
290
|
-
|
|
291
|
-
if
|
|
292
|
-
env["DISPLAY"] =
|
|
293
|
-
logger.info(f"
|
|
348
|
+
display = _ensure_display()
|
|
349
|
+
if display:
|
|
350
|
+
env["DISPLAY"] = display
|
|
351
|
+
logger.info(f"有头模式: DISPLAY={display}(通过 VNC/Xvfb 提供)")
|
|
294
352
|
else:
|
|
295
|
-
#
|
|
353
|
+
# 都不可用,降级为 headless 模式
|
|
296
354
|
self._headless = True
|
|
297
355
|
args.append("--headless")
|
|
298
|
-
logger.warning("无 DISPLAY 环境且 Xvfb
|
|
356
|
+
logger.warning("无 DISPLAY 环境且 VNC/Xvfb 均不可用,自动降级为 headless 模式")
|
|
299
357
|
|
|
300
358
|
logger.info(f"启动 chrome-devtools-mcp: {' '.join(args)}")
|
|
301
359
|
|