myagent-ai 1.47.10 → 1.47.12

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.
@@ -214,15 +214,21 @@ def _ensure_display() -> Optional[Dict[str, Any]]:
214
214
  """
215
215
  确保有可用的 X11 显示,供浏览器有头模式使用。
216
216
 
217
- 优先级:
218
- 1. 复用 VNC 远程桌面的 Xvfb 显示(用户可在 VNC 中看到浏览器操作)
219
- 2. 自动启动 VNC(含 Xvfb + x11vnc + websockify)
220
- 3. VNC 不可用时独立启动 Xvfb
217
+ Termux+Ubuntu 环境: 只支持 VNC,无其他 fallback
218
+ Termux 容器环境: VNC > 独立 Xvfb 降级
221
219
 
222
220
  Returns:
223
221
  {"display": ":99", "vnc": True/False, "xvfb_standalone": True/False}
224
222
  失败返回 None
225
223
  """
224
+ # 检测是否为 Termux 环境
225
+ _is_termux = False
226
+ try:
227
+ from core.env_detect import is_termux
228
+ _is_termux = is_termux()
229
+ except ImportError:
230
+ pass
231
+
226
232
  # 1. 尝试复用 VNC 远程桌面
227
233
  try:
228
234
  from core.vnc_manager import get_vnc_manager
@@ -240,8 +246,6 @@ def _ensure_display() -> Optional[Dict[str, Any]]:
240
246
  import asyncio
241
247
  loop = asyncio.get_event_loop()
242
248
  if loop.is_running():
243
- # 在已有事件循环中,用 run_until_complete 不行,
244
- # 创建 task 等待完成
245
249
  import concurrent.futures
246
250
  with concurrent.futures.ThreadPoolExecutor() as pool:
247
251
  result = pool.submit(asyncio.run, vnc.start()).result(timeout=30)
@@ -263,9 +267,15 @@ def _ensure_display() -> Optional[Dict[str, Any]]:
263
267
  except Exception as e:
264
268
  logger.warning(f"VNC 检测异常: {e}")
265
269
 
266
- # 2. VNC 不可用 独立启动 Xvfb
267
- # [v1.47.10] 用 display :100 避免和 VNC 的 :99 冲突
268
- # 如果 VNC 刚被杀但锁文件还在,用 :99 会 returncode=1
270
+ # ── Termux+Ubuntu: VNC 是唯一方式,不允许其他 fallback ──
271
+ if _is_termux:
272
+ logger.error(
273
+ "Termux+Ubuntu 环境仅支持通过 VNC 启动浏览器,"
274
+ "VNC 启动失败。请检查 VNC 配置或手动启动 VNC 远程桌面。"
275
+ )
276
+ return None
277
+
278
+ # 2. 非 Termux 容器环境 → 独立启动 Xvfb 作为 fallback
269
279
  xvfb_display = _start_xvfb(display_num=100)
270
280
  if xvfb_display:
271
281
  return {"display": xvfb_display, "vnc": False, "xvfb_standalone": True}
@@ -561,17 +571,24 @@ class StealthBrowser:
561
571
  logger.info("桌面环境,使用系统 Chrome 原生参数")
562
572
 
563
573
  # ── 无显示环境处理 ──
564
- # 使用 is_desktop() 判断是否为桌面环境(Windows/macOS/Linux有真实显示器)
565
- # 桌面环境直接使用系统 Chrome,不需要 VNC/Xvfb
566
- # 仅在非桌面的 Linux(容器/Termux)环境中才尝试 VNC/Xvfb
574
+ # 桌面环境 (Windows/macOS/Linux有真实显示器): 直接用系统 Chrome
575
+ # Termux+Ubuntu: 仅支持 VNC,不降级到 headless
576
+ # Termux 容器: VNC > Xvfb > headless 降级
577
+ _is_termux_env = False
578
+ try:
579
+ from core.env_detect import is_termux
580
+ _is_termux_env = is_termux()
581
+ except ImportError:
582
+ pass
583
+
567
584
  if not self._headless:
568
585
  try:
569
586
  from core.env_detect import is_desktop
570
587
  if is_desktop():
571
- # 桌面环境(Windows/macOS/Linux有真实显示器): 直接用系统 Chrome
588
+ # 桌面环境: 直接用系统 Chrome
572
589
  logger.info("桌面环境,直接使用系统浏览器,跳过 VNC/Xvfb")
573
590
  else:
574
- # 非桌面 Linux(容器/Termux): 尝试 VNC > Xvfb > headless 降级
591
+ # 非桌面环境 (容器/Termux): 通过 _ensure_display() 获取显示
575
592
  display = _ensure_display()
576
593
  if display:
577
594
  self._vnc_used = display.get("vnc", False)
@@ -579,6 +596,17 @@ class StealthBrowser:
579
596
  if self._vnc_used:
580
597
  logger.info(f"复用 VNC 远程桌面显示 ({display['display']}),可在 VNC 中查看浏览器操作")
581
598
  else:
599
+ # ── Termux+Ubuntu: VNC 失败 → 直接报错,不降级 headless ──
600
+ if _is_termux_env:
601
+ return SkillResult(
602
+ success=False,
603
+ error=(
604
+ "Termux+Ubuntu 环境仅支持通过 VNC 启动浏览器,"
605
+ "VNC 启动失败。请先启动 VNC 远程桌面,"
606
+ "或通过 Web 管理面板打开 VNC 后再使用浏览器功能。"
607
+ ),
608
+ )
609
+ # ── 非 Termux 容器: 降级到 headless ──
582
610
  self._headless = True
583
611
  logger.warning(
584
612
  "无显示环境且 VNC/Xvfb 均不可用,自动降级为 headless 模式"
@@ -591,10 +619,39 @@ class StealthBrowser:
591
619
  self._vnc_used = display.get("vnc", False)
592
620
  self._xvfb_started_by_us = display.get("xvfb_standalone", False)
593
621
  else:
622
+ if _is_termux_env:
623
+ return SkillResult(
624
+ success=False,
625
+ error=(
626
+ "Termux+Ubuntu 环境仅支持通过 VNC 启动浏览器,"
627
+ "VNC 启动失败。请先启动 VNC 远程桌面后再使用浏览器功能。"
628
+ ),
629
+ )
594
630
  self._headless = True
595
631
  logger.warning(
596
632
  "无 DISPLAY 环境且 VNC/Xvfb 均不可用,自动降级为 headless 模式"
597
633
  )
634
+ else:
635
+ # headless=True 被显式请求,但 Termux 环境下仍强制使用 VNC
636
+ # 因为 headless Chromium 在 Termux 下容易被 OOM Kill
637
+ if _is_termux_env:
638
+ logger.info("Termux+Ubuntu 环境: 忽略 headless 请求,强制使用 VNC 模式")
639
+ self._headless = False
640
+ display = _ensure_display()
641
+ if display:
642
+ self._vnc_used = display.get("vnc", False)
643
+ self._xvfb_started_by_us = display.get("xvfb_standalone", False)
644
+ if self._vnc_used:
645
+ logger.info(f"Termux+Ubuntu: 已通过 VNC 获取显示 ({display['display']})")
646
+ else:
647
+ return SkillResult(
648
+ success=False,
649
+ error=(
650
+ "Termux+Ubuntu 环境仅支持通过 VNC 启动浏览器,"
651
+ "VNC 启动失败。headless 模式在此环境下不可用(容易被 OOM Kill)。"
652
+ "请先启动 VNC 远程桌面后再使用浏览器功能。"
653
+ ),
654
+ )
598
655
 
599
656
  # 无头模式(co.headless() 内部设置 --headless=new)
600
657
  if self._headless:
package/main.py CHANGED
@@ -83,11 +83,23 @@ def _open_browser_kiosk(url: str):
83
83
  """打开浏览器窗口,使用系统已安装的 Chrome 或默认浏览器。
84
84
 
85
85
  [v1.34.0] 优先使用系统已安装的 Chrome/Edge,而非 DrissionPage 控制的浏览器。
86
+ [v1.47.12] Termux 环境: 优先使用 termux-open-url(调用 Android 系统浏览器)。
86
87
  """
87
88
  import shutil
88
89
  import subprocess
89
90
  import platform
90
91
 
92
+ # [v1.47.12] Termux 环境: 优先使用 termux-open-url 打开 Android 系统浏览器
93
+ # 在 Termux proot 环境下,xdg-open 无法工作(Permission denied),
94
+ # 但 termux-open-url 可以跨 proot 边界调用 Android 系统浏览器
95
+ termux_open = shutil.which("termux-open-url")
96
+ if termux_open:
97
+ try:
98
+ subprocess.Popen([termux_open, url], start_new_session=True)
99
+ return
100
+ except Exception:
101
+ pass
102
+
91
103
  chrome_path = None
92
104
  system = platform.system()
93
105
 
@@ -1056,6 +1068,7 @@ def create_tray_icon(app: MyAgentApp, web_port: int = 8767):
1056
1068
  app.logger.warning(f"无法打开不存在的路径: {path}")
1057
1069
  return
1058
1070
  import subprocess
1071
+ import shutil
1059
1072
  import platform as pf
1060
1073
  system = pf.system()
1061
1074
  try:
@@ -1064,7 +1077,12 @@ def create_tray_icon(app: MyAgentApp, web_port: int = 8767):
1064
1077
  elif system == "Darwin":
1065
1078
  subprocess.Popen(["open", path])
1066
1079
  else:
1067
- subprocess.Popen(["xdg-open", path])
1080
+ # [v1.47.12] Termux 环境: 优先使用 termux-open-url
1081
+ termux_open = shutil.which("termux-open-url")
1082
+ if termux_open:
1083
+ subprocess.Popen([termux_open, path], start_new_session=True)
1084
+ else:
1085
+ subprocess.Popen(["xdg-open", path])
1068
1086
  except Exception as e:
1069
1087
  app.logger.warning(f"无法打开路径 {path}: {e}")
1070
1088
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.47.10",
3
+ "version": "1.47.12",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/start.js CHANGED
@@ -754,7 +754,11 @@ function cmdRun(pkgDir, userArgs) {
754
754
  try {
755
755
  if (IS_WIN) execSync(`start "" "${url}"`);
756
756
  else if (process.platform === "darwin") execSync(`open "${url}"`);
757
- else execSync(`xdg-open "${url}"`);
757
+ else {
758
+ // [v1.47.12] Termux 环境: 优先使用 termux-open-url(Android 系统浏览器)
759
+ try { execSync(`termux-open-url "${url}"`); }
760
+ catch (_) { execSync(`xdg-open "${url}"`); }
761
+ }
758
762
  } catch (_) {}
759
763
  }
760
764
  });
package/tray_manager.py CHANGED
@@ -347,19 +347,37 @@ def restart_server() -> bool:
347
347
 
348
348
  def open_dashboard():
349
349
  """Open chat dashboard in default browser"""
350
- import webbrowser
350
+ import shutil, subprocess
351
351
  port = _read_port_from_env()
352
352
  url = f"http://localhost:{port}"
353
353
  log(f"Opening dashboard: {url}")
354
+ # [v1.47.12] Termux 环境: 优先使用 termux-open-url
355
+ termux_open = shutil.which("termux-open-url")
356
+ if termux_open:
357
+ try:
358
+ subprocess.Popen([termux_open, url], start_new_session=True)
359
+ return
360
+ except Exception:
361
+ pass
362
+ import webbrowser
354
363
  webbrowser.open(url)
355
364
 
356
365
 
357
366
  def open_admin():
358
367
  """Open admin dashboard in default browser"""
359
- import webbrowser
368
+ import shutil, subprocess
360
369
  port = _read_port_from_env()
361
370
  url = f"http://localhost:{port}/admin"
362
371
  log(f"Opening admin panel: {url}")
372
+ # [v1.47.12] Termux 环境: 优先使用 termux-open-url
373
+ termux_open = shutil.which("termux-open-url")
374
+ if termux_open:
375
+ try:
376
+ subprocess.Popen([termux_open, url], start_new_session=True)
377
+ return
378
+ except Exception:
379
+ pass
380
+ import webbrowser
363
381
  webbrowser.open(url)
364
382
 
365
383
 
package/web/api_server.py CHANGED
@@ -6424,10 +6424,21 @@ window.addEventListener('beforeunload', function() {{
6424
6424
  "message": message,
6425
6425
  })
6426
6426
  else:
6427
- # 找不到 Chrome,降级使用 webbrowser.open
6428
- import webbrowser
6429
- if login_url:
6430
- webbrowser.open(login_url)
6427
+ # 找不到 Chrome,降级使用系统浏览器
6428
+ import shutil, subprocess as _sp
6429
+ _opened = False
6430
+ # [v1.47.12] Termux 环境: 优先使用 termux-open-url
6431
+ termux_open = shutil.which("termux-open-url")
6432
+ if termux_open and login_url:
6433
+ try:
6434
+ _sp.Popen([termux_open, login_url], start_new_session=True)
6435
+ _opened = True
6436
+ except Exception:
6437
+ pass
6438
+ if not _opened:
6439
+ import webbrowser
6440
+ if login_url:
6441
+ webbrowser.open(login_url)
6431
6442
  logger.info(f"桌面环境: 未找到 Chrome,使用系统默认浏览器打开 {login_url}")
6432
6443
 
6433
6444
  display_name = site.get('display_name', name)