myagent-ai 1.33.0 → 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.
@@ -33,6 +33,7 @@ import asyncio
33
33
  import json
34
34
  import os
35
35
  import shutil
36
+ import subprocess
36
37
  import threading
37
38
  import time
38
39
  from pathlib import Path
@@ -44,6 +45,200 @@ from aiskills.base import Skill, SkillResult, SkillParameter
44
45
  logger = get_logger("myagent.skills.browser_stealth")
45
46
 
46
47
 
48
+ # ── Xvfb 虚拟显示管理 ──────────────────────────────────────
49
+
50
+ _xvfb_process: Optional[subprocess.Popen] = None
51
+ _xvfb_display: Optional[str] = None
52
+ _xvfb_lock = threading.Lock()
53
+
54
+
55
+ def _has_display() -> bool:
56
+ """检查当前环境是否有可用的 X11 显示"""
57
+ display = os.environ.get("DISPLAY", "").strip()
58
+ if not display:
59
+ return False
60
+ # DISPLAY 已设置,尝试检测是否真的可用
61
+ # 方法1: xdpyinfo
62
+ try:
63
+ result = subprocess.run(
64
+ ["xdpyinfo"],
65
+ capture_output=True, timeout=3,
66
+ env={**os.environ},
67
+ )
68
+ if result.returncode == 0:
69
+ return True
70
+ except Exception:
71
+ pass
72
+ # 方法2: xdpyinfo 不可用时,尝试用 Xvfb 的 -displayfd 验证
73
+ # 如果 DISPLAY 变量指向一个活跃的 X server,即使 xdpyinfo 不可用,
74
+ # 我们也可以认为有可用显示(例如 Xvfb 已经启动过了)
75
+ try:
76
+ # 尝试连接 X server(使用 xdotool 或 xprop 等轻量工具)
77
+ for cmd in (["xprop", "-root", "_NET_SUPPORTING_WM_CHECK"],
78
+ ["xdotool", "getdisplaywidth"]):
79
+ result = subprocess.run(
80
+ cmd, capture_output=True, timeout=3,
81
+ env={**os.environ},
82
+ )
83
+ if result.returncode == 0:
84
+ return True
85
+ except Exception:
86
+ pass
87
+ # 方法3: 如果 DISPLAY 格式正确且 /tmp/.X11-unix/ 下有对应 socket,认为可用
88
+ try:
89
+ if display.startswith(":"):
90
+ num = display[1:].split(".")[0]
91
+ socket_path = f"/tmp/.X11-unix/X{num}"
92
+ if os.path.exists(socket_path):
93
+ return True
94
+ except Exception:
95
+ pass
96
+ return False
97
+
98
+
99
+ def _start_xvfb(display_num: int = 99) -> Optional[str]:
100
+ """
101
+ 启动 Xvfb 虚拟显示服务器。
102
+
103
+ Returns:
104
+ DISPLAY 环境变量值(如 :99),失败返回 None
105
+ """
106
+ global _xvfb_process, _xvfb_display
107
+
108
+ with _xvfb_lock:
109
+ # 如果已经启动,直接返回
110
+ if _xvfb_process and _xvfb_process.poll() is None:
111
+ return _xvfb_display
112
+
113
+ # 清理旧的
114
+ if _xvfb_process:
115
+ try:
116
+ _xvfb_process.terminate()
117
+ _xvfb_process.wait(timeout=3)
118
+ except Exception:
119
+ try:
120
+ _xvfb_process.kill()
121
+ except Exception:
122
+ pass
123
+ _xvfb_process = None
124
+
125
+ xvfb_path = shutil.which("Xvfb")
126
+ if not xvfb_path:
127
+ logger.warning("Xvfb 未安装,无法启动虚拟显示")
128
+ return None
129
+
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):
135
+ try:
136
+ os.unlink(lock_file)
137
+ except Exception:
138
+ 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})")
149
+ _xvfb_process = None
150
+ return None
151
+
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
160
+
161
+
162
+ def _stop_xvfb() -> None:
163
+ """停止 Xvfb 虚拟显示服务器"""
164
+ global _xvfb_process, _xvfb_display
165
+
166
+ with _xvfb_lock:
167
+ if _xvfb_process:
168
+ try:
169
+ _xvfb_process.terminate()
170
+ _xvfb_process.wait(timeout=5)
171
+ except Exception:
172
+ try:
173
+ _xvfb_process.kill()
174
+ except Exception:
175
+ pass
176
+ _xvfb_process = None
177
+ _xvfb_display = None
178
+ logger.info("Xvfb 虚拟显示已停止")
179
+
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
+
47
242
  # ── 反检测浏览器管理器 ──────────────────────────────────────
48
243
 
49
244
 
@@ -80,6 +275,8 @@ class StealthBrowser:
80
275
  self._browser = None # DrissionPage Chromium 实例
81
276
  self._started = False
82
277
  self._user_data_dir = ""
278
+ self._xvfb_started_by_us = False # 是否由本实例独立启动的 Xvfb
279
+ self._vnc_used = False # 是否使用了 VNC 远程桌面的显示
83
280
 
84
281
  def _get_user_data_dir(self) -> str:
85
282
  """获取用户数据目录路径"""
@@ -130,7 +327,27 @@ class StealthBrowser:
130
327
  co.set_argument("--disable-dev-shm-usage")
131
328
  co.set_argument("--disable-gpu")
132
329
 
133
- # 无头模式
330
+ # ── 无 DISPLAY 环境处理 ──
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 模式
337
+ if not self._headless and not _has_display():
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 中查看浏览器操作")
344
+ else:
345
+ self._headless = True
346
+ logger.warning(
347
+ "无 DISPLAY 环境且 VNC/Xvfb 均不可用,自动降级为 headless 模式"
348
+ )
349
+
350
+ # 无头模式(co.headless() 内部设置 --headless=new)
134
351
  if self._headless:
135
352
  co.headless()
136
353
 
@@ -138,13 +355,33 @@ class StealthBrowser:
138
355
  browser_path = self._detect_browser()
139
356
  if browser_path:
140
357
  co.set_browser_path(browser_path)
358
+ else:
359
+ return SkillResult(
360
+ success=False,
361
+ error=(
362
+ "未找到 Chrome/Chromium 浏览器。"
363
+ "请安装 Chrome 或设置 CHROME_PATH 环境变量。"
364
+ ),
365
+ )
141
366
 
142
367
  # 设置窗口大小
143
368
  co.set_argument("--window-size=1920,1080")
144
369
 
145
- # 创建浏览器实例
146
- self._browser = Chromium(co)
147
- self._page = self._browser.latest_tab
370
+ # 创建浏览器实例(增加重试逻辑)
371
+ last_error = None
372
+ for attempt in range(3):
373
+ try:
374
+ self._browser = Chromium(co)
375
+ self._page = self._browser.latest_tab
376
+ break
377
+ except Exception as e:
378
+ last_error = e
379
+ logger.warning(f"浏览器启动第 {attempt + 1}/3 次尝试失败: {e}")
380
+ # 清理可能残留的 Chrome 进程
381
+ self._kill_stale_chrome(user_data)
382
+ time.sleep(2 * (attempt + 1))
383
+ else:
384
+ raise last_error
148
385
 
149
386
  # 如果浏览器已崩溃或无法获取 tab
150
387
  if not self._page:
@@ -195,11 +432,27 @@ class StealthBrowser:
195
432
  self._browser = None
196
433
  self._page = None
197
434
  logger.info("反检测浏览器已关闭")
198
- return SkillResult(success=True, message="浏览器已关闭")
199
435
  except Exception as e:
200
436
  logger.error(f"关闭浏览器异常: {e}")
201
437
  self._browser = None
202
438
  self._page = None
439
+
440
+ # 如果由本实例独立启动了 Xvfb(非 VNC),关闭浏览器后停止虚拟显示
441
+ # 注意:VNC 管理自己的 Xvfb 生命周期,不在此停止
442
+ if self._xvfb_started_by_us:
443
+ self._xvfb_started_by_us = False
444
+ # 检查是否还有其他浏览器实例在使用 Xvfb
445
+ with _browser_lock:
446
+ other_active = any(
447
+ b._started and b is not self
448
+ for b in _browsers.values()
449
+ )
450
+ if not other_active:
451
+ _stop_xvfb()
452
+
453
+ # VNC 显示不由浏览器管理,无需停止
454
+ self._vnc_used = False
455
+
203
456
  return SkillResult(success=True, message="浏览器已关闭")
204
457
 
205
458
  async def navigate(self, url: str, wait: float = 2.0) -> SkillResult:
@@ -559,6 +812,34 @@ class StealthBrowser:
559
812
  logger.debug(f"页面检查失败 (profile={self.profile_name}),浏览器可能已关闭")
560
813
  return False
561
814
 
815
+ @staticmethod
816
+ def _kill_stale_chrome(user_data_dir: str = "") -> None:
817
+ """清理可能残留的 Chrome/Chromium 进程"""
818
+ try:
819
+ # 查找使用相同 user-data-dir 的残留 Chrome 进程
820
+ patterns = [
821
+ "chromium-browser.*--remote-debugging-port",
822
+ "chromium.*--remote-debugging-port",
823
+ "chrome.*--remote-debugging-port",
824
+ "google-chrome.*--remote-debugging-port",
825
+ "headless_shell.*--remote-debugging-port",
826
+ ]
827
+ for pattern in patterns:
828
+ result = subprocess.run(
829
+ ["pgrep", "-f", pattern],
830
+ capture_output=True, text=True, timeout=5,
831
+ )
832
+ if result.returncode == 0 and result.stdout.strip():
833
+ for pid_str in result.stdout.strip().split("\n"):
834
+ try:
835
+ pid = int(pid_str.strip())
836
+ os.kill(pid, 9) # SIGKILL
837
+ logger.info(f"已清理残留 Chrome 进程: PID {pid}")
838
+ except (ValueError, ProcessLookupError, PermissionError):
839
+ pass
840
+ except Exception as e:
841
+ logger.debug(f"清理残留 Chrome 进程时出错: {e}")
842
+
562
843
  @staticmethod
563
844
  def _detect_browser() -> Optional[str]:
564
845
  """自动检测 Chromium/Chrome 浏览器路径"""
@@ -693,6 +974,12 @@ def close_stealth_browser(profile_name: str = "") -> None:
693
974
  pass
694
975
  _browsers.clear()
695
976
 
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()
982
+
696
983
 
697
984
  async def close_stealth_browser_async(profile_name: str = "") -> None:
698
985
  """关闭浏览器实例(异步版本,供 async 上下文调用)"""
@@ -39,6 +39,189 @@ from aiskills.base import Skill, SkillResult, SkillParameter
39
39
 
40
40
  logger = get_logger("myagent.skills.chromedev_mcp")
41
41
 
42
+
43
+ # ── Xvfb 虚拟显示管理 ──────────────────────────────────────
44
+
45
+ _xvfb_process: Optional[subprocess.Popen] = None
46
+ _xvfb_display: Optional[str] = None
47
+
48
+
49
+ def _has_display() -> bool:
50
+ """检查当前环境是否有可用的 X11 显示"""
51
+ display = os.environ.get("DISPLAY", "").strip()
52
+ if not display:
53
+ return False
54
+ # DISPLAY 已设置,尝试检测是否真的可用
55
+ # 方法1: xdpyinfo
56
+ try:
57
+ result = subprocess.run(
58
+ ["xdpyinfo"], capture_output=True, timeout=3,
59
+ env={**os.environ},
60
+ )
61
+ if result.returncode == 0:
62
+ return True
63
+ except Exception:
64
+ pass
65
+ # 方法2: 用 xprop/xdotool 等轻量工具验证
66
+ try:
67
+ for cmd in (["xprop", "-root", "_NET_SUPPORTING_WM_CHECK"],
68
+ ["xdotool", "getdisplaywidth"]):
69
+ result = subprocess.run(
70
+ cmd, capture_output=True, timeout=3,
71
+ env={**os.environ},
72
+ )
73
+ if result.returncode == 0:
74
+ return True
75
+ except Exception:
76
+ pass
77
+ # 方法3: 检查 /tmp/.X11-unix/ 下的 socket 文件
78
+ try:
79
+ if display.startswith(":"):
80
+ num = display[1:].split(".")[0]
81
+ socket_path = f"/tmp/.X11-unix/X{num}"
82
+ if os.path.exists(socket_path):
83
+ return True
84
+ except Exception:
85
+ pass
86
+ return False
87
+
88
+
89
+ def _start_xvfb(display_num: int = 98) -> Optional[str]:
90
+ """
91
+ 启动 Xvfb 虚拟显示服务器。
92
+
93
+ Returns:
94
+ DISPLAY 环境变量值(如 :98),失败返回 None
95
+ """
96
+ global _xvfb_process, _xvfb_display
97
+
98
+ # 如果已经启动,直接返回
99
+ if _xvfb_process and _xvfb_process.poll() is None:
100
+ return _xvfb_display
101
+
102
+ # 清理旧的
103
+ if _xvfb_process:
104
+ try:
105
+ _xvfb_process.terminate()
106
+ _xvfb_process.wait(timeout=3)
107
+ except Exception:
108
+ try:
109
+ _xvfb_process.kill()
110
+ except Exception:
111
+ pass
112
+ _xvfb_process = None
113
+
114
+ xvfb_path = shutil.which("Xvfb")
115
+ if not xvfb_path:
116
+ logger.warning("Xvfb 未安装,无法启动虚拟显示")
117
+ return None
118
+
119
+ display_str = f":{display_num}"
120
+ try:
121
+ # 清理可能残留的 X11 锁文件
122
+ for lock_file in (f"/tmp/.X{display_num}-lock", f"/tmp/.X11-unix/X{display_num}"):
123
+ if os.path.exists(lock_file):
124
+ try:
125
+ os.unlink(lock_file)
126
+ except Exception:
127
+ pass
128
+
129
+ _xvfb_process = subprocess.Popen(
130
+ [xvfb_path, display_str, "-screen", "0", "1920x1080x24", "-ac", "-nolisten", "tcp"],
131
+ stdout=subprocess.DEVNULL,
132
+ stderr=subprocess.DEVNULL,
133
+ )
134
+ time.sleep(0.5)
135
+ if _xvfb_process.poll() is not None:
136
+ logger.error(f"Xvfb 启动后立即退出 (returncode={_xvfb_process.returncode})")
137
+ _xvfb_process = None
138
+ return None
139
+
140
+ _xvfb_display = display_str
141
+ os.environ["DISPLAY"] = display_str
142
+ logger.info(f"Xvfb 虚拟显示已启动 (chromedev_mcp): {display_str}")
143
+ return display_str
144
+ except Exception as e:
145
+ logger.error(f"启动 Xvfb 失败: {e}")
146
+ _xvfb_process = None
147
+ return None
148
+
149
+
150
+ def _stop_xvfb() -> None:
151
+ """停止 Xvfb 虚拟显示服务器"""
152
+ global _xvfb_process, _xvfb_display
153
+
154
+ if _xvfb_process:
155
+ try:
156
+ _xvfb_process.terminate()
157
+ _xvfb_process.wait(timeout=5)
158
+ except Exception:
159
+ try:
160
+ _xvfb_process.kill()
161
+ except Exception:
162
+ pass
163
+ _xvfb_process = None
164
+ _xvfb_display = None
165
+ logger.info("Xvfb 虚拟显示已停止 (chromedev_mcp)")
166
+
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
+
42
225
  # ── MCP 通信常量 ──────────────────────────────────────────────
43
226
 
44
227
  # chrome-devtools-mcp 支持的完整工具列表 (v0.21.0)
@@ -154,9 +337,23 @@ class MCPClient:
154
337
  args.extend(["--chromeArg", "--no-sandbox", "--chromeArg", "--disable-setuid-sandbox"])
155
338
 
156
339
  # [v1.17.0] 有头模式: 设置 DISPLAY 环境变量
157
- if not self._headless and self._display_override:
158
- env["DISPLAY"] = self._display_override
159
- logger.info(f"有头模式: DISPLAY={self._display_override}")
340
+ # 优先级: 用户指定 DISPLAY > VNC 远程桌面 > 独立 Xvfb > 降级 headless
341
+ if not self._headless:
342
+ # 优先使用用户指定的 DISPLAY
343
+ if self._display_override:
344
+ env["DISPLAY"] = self._display_override
345
+ logger.info(f"有头模式: DISPLAY={self._display_override}")
346
+ # 无可用显示时,优先尝试 VNC 远程桌面,再尝试独立 Xvfb
347
+ elif not _has_display():
348
+ display = _ensure_display()
349
+ if display:
350
+ env["DISPLAY"] = display
351
+ logger.info(f"有头模式: DISPLAY={display}(通过 VNC/Xvfb 提供)")
352
+ else:
353
+ # 都不可用,降级为 headless 模式
354
+ self._headless = True
355
+ args.append("--headless")
356
+ logger.warning("无 DISPLAY 环境且 VNC/Xvfb 均不可用,自动降级为 headless 模式")
160
357
 
161
358
  logger.info(f"启动 chrome-devtools-mcp: {' '.join(args)}")
162
359
 
@@ -629,6 +826,9 @@ class MCPClient:
629
826
  # chrome-devtools-mcp 启动的 Chrome 可能不会随 MCP 进程一起退出
630
827
  self._kill_stale_chrome()
631
828
 
829
+ # 清理 Xvfb 虚拟显示
830
+ _stop_xvfb()
831
+
632
832
  def _kill_stale_chrome(self):
633
833
  """清理残留的 Chrome/Chromium 进程
634
834
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.33.0",
3
+ "version": "1.33.2",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {