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.
@@ -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
- display_str = f":{display_num}"
131
- try:
132
- # 先清理可能残留的 X11 锁文件
133
- for lock_file in (f"/tmp/.X{display_num}-lock", f"/tmp/.X11-unix/X{display_num}"):
134
- if os.path.exists(lock_file):
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
- os.unlink(lock_file)
173
+ stderr = _xvfb_process.stderr.read().decode("utf-8", errors="replace")[:300]
137
174
  except Exception:
138
175
  pass
139
-
140
- _xvfb_process = subprocess.Popen(
141
- [xvfb_path, display_str, "-screen", "0", "1920x1080x24", "-ac", "-nolisten", "tcp"],
142
- stdout=subprocess.DEVNULL,
143
- stderr=subprocess.DEVNULL,
144
- )
145
- # 等待 Xvfb 启动
146
- time.sleep(0.5)
147
- if _xvfb_process.poll() is not None:
148
- logger.error(f"Xvfb 启动后立即退出 (returncode={_xvfb_process.returncode})")
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
- return None
188
+ continue
151
189
 
152
- _xvfb_display = display_str
153
- os.environ["DISPLAY"] = display_str
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
- xvfb_display = _start_xvfb()
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 缓存
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.47.5",
3
+ "version": "1.47.10",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {