myagent-ai 1.47.29 → 1.47.31

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.
@@ -55,7 +55,9 @@ logger = get_logger("myagent.vnc")
55
55
  # ── 默认配置 ──────────────────────────────────────────────
56
56
 
57
57
  DEFAULT_DISPLAY = ":99"
58
- DEFAULT_XVFB_RESOLUTION = "1080x1920x24" # [v1.47.3] 默认竖屏(手机),旋转切换到横屏
58
+ DEFAULT_XVFB_RESOLUTION = "1080x1920x24" # [v1.47.3] 默认竖屏(手机),连接后由客户端视口自适应
59
+
60
+
59
61
  DEFAULT_X11VNC_PORT = 5900
60
62
  DEFAULT_NOVNC_PORT = 6080 # WebSocket 代理端口(供 noVNC 客户端连接)
61
63
 
@@ -75,11 +77,24 @@ class VNCManager:
75
77
  def __init__(
76
78
  self,
77
79
  display: str = DEFAULT_DISPLAY,
78
- resolution: str = DEFAULT_XVFB_RESOLUTION,
80
+ resolution: str = "",
79
81
  x11vnc_port: int = DEFAULT_X11VNC_PORT,
80
82
  novnc_port: int = DEFAULT_NOVNC_PORT,
81
83
  ):
82
84
  self.display = display
85
+ # [v1.47.30] 初始分辨率 — 连接后由客户端视口自适应调整
86
+ # 也可通过 MYAGENT_SCREEN 环境变量手动指定初始分辨率(格式 "1097x550")
87
+ if not resolution:
88
+ env_screen = os.environ.get("MYAGENT_SCREEN", "").strip()
89
+ if env_screen and "x" in env_screen:
90
+ try:
91
+ parts = env_screen.lower().split("x")
92
+ resolution = f"{int(parts[0])}x{int(parts[1])}x24"
93
+ logger.info(f"VNC 初始分辨率来自环境变量: {resolution}")
94
+ except (ValueError, IndexError):
95
+ resolution = DEFAULT_XVFB_RESOLUTION
96
+ else:
97
+ resolution = DEFAULT_XVFB_RESOLUTION
83
98
  self.resolution = resolution
84
99
  self.x11vnc_port = x11vnc_port
85
100
  self.novnc_port = novnc_port
@@ -639,6 +654,71 @@ class VNCManager:
639
654
  except Exception:
640
655
  pass
641
656
 
657
+ def resize_display(self, width: int, height: int) -> Dict[str, Any]:
658
+ """[v1.47.30] 自适应调整 VNC 分辨率 — 匹配客户端视口大小。
659
+
660
+ 当 noVNC 客户端视口尺寸变化时(手机旋转、窗口大小改变),
661
+ 通过 xrandr 动态调整 Xvfb 帧缓冲区大小,实现像素级清晰显示。
662
+
663
+ Args:
664
+ width: 目标宽度(像素)
665
+ height: 目标高度(像素)
666
+
667
+ Returns:
668
+ {"success": bool, "resolution": str, "error": str}
669
+ """
670
+ if not self.is_running:
671
+ return {"success": False, "error": "VNC未运行"}
672
+
673
+ # 参数校验:合理范围
674
+ if width < 320 or height < 240 or width > 3840 or height > 3840:
675
+ return {"success": False, "error": f"分辨率超出合理范围: {width}x{height}"}
676
+
677
+ # 解析当前分辨率
678
+ try:
679
+ parts = self.resolution.split("x")
680
+ cur_w, cur_h = int(parts[0]), int(parts[1])
681
+ depth = parts[2] if len(parts) > 2 else "24"
682
+ except (ValueError, IndexError):
683
+ return {"success": False, "error": f"无法解析当前分辨率: {self.resolution}"}
684
+
685
+ # 如果分辨率没变,无需操作
686
+ if cur_w == width and cur_h == height:
687
+ return {"success": True, "resolution": self.resolution}
688
+
689
+ new_resolution = f"{width}x{height}x{depth}"
690
+ env = {**os.environ, "DISPLAY": self.display}
691
+
692
+ # 尝试 xrandr --fb 在线调整
693
+ xrandr_ok = False
694
+ if shutil.which("xrandr"):
695
+ try:
696
+ result = subprocess.run(
697
+ ["xrandr", "--fb", f"{width}x{height}"],
698
+ capture_output=True, text=True, timeout=5,
699
+ env=env, start_new_session=True,
700
+ )
701
+ if result.returncode == 0:
702
+ xrandr_ok = True
703
+ logger.info(f"VNC 自适应分辨率: {cur_w}x{cur_h} → {width}x{height}")
704
+ else:
705
+ logger.debug(f"xrandr --fb 失败: {(result.stderr or '')[:100]}")
706
+ except Exception as e:
707
+ logger.debug(f"xrandr --fb 异常: {e}")
708
+
709
+ if xrandr_ok:
710
+ self.resolution = new_resolution
711
+ # 更新旋转状态
712
+ if width > height:
713
+ self._current_rotation = "landscape"
714
+ else:
715
+ self._current_rotation = "portrait"
716
+ # 通知窗口管理器
717
+ self._notify_wm_resize(width, height, env)
718
+ return {"success": True, "resolution": new_resolution}
719
+
720
+ return {"success": False, "error": "xrandr 不支持,无法动态调整分辨率"}
721
+
642
722
  def ensure_dependencies(self) -> tuple:
643
723
  """检测并安装 VNC 所需依赖。
644
724
 
@@ -5015,9 +5095,19 @@ exec "$BROWSER" "$@"
5015
5095
  "-nowcr", # 禁用 cursor shape updates
5016
5096
  "-nocursorshape",
5017
5097
  "-deferupdate", "5", # 延迟更新(降低带宽)
5018
- "-scale", "2/3", # 缩小 2/3(降低带宽)
5019
5098
  ]
5020
5099
 
5100
+ # [v1.47.29] VNC 缩放策略:分辨率匹配屏幕时不缩放,大分辨率缩小 2/3
5101
+ try:
5102
+ res_parts = self.resolution.split("x")
5103
+ res_w, res_h = int(res_parts[0]), int(res_parts[1])
5104
+ if res_w * res_h > 1600 * 900:
5105
+ # 大分辨率(如 1080x1920)→ 缩小 2/3 降低带宽
5106
+ cmd.extend(["-scale", "2/3"])
5107
+ # else: 小/匹配分辨率 → 不缩放,保持清晰
5108
+ except (ValueError, IndexError):
5109
+ cmd.extend(["-scale", "2/3"])
5110
+
5021
5111
  # [v1.18.5] 不使用 -threads 模式:与 -scale 组合在 0.9.17 中
5022
5112
  # 会导致父进程 fork 后立即退出,端口延迟监听
5023
5113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.47.29",
3
+ "version": "1.47.31",
4
4
  "description": "\u672c\u5730\u684c\u9762\u7aef\u6267\u884c\u578bAI\u52a9\u624b - Open Interpreter \u98ce\u683c | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/api_server.py CHANGED
@@ -628,6 +628,7 @@ class ApiServer:
628
628
  r.add_post("/api/vnc/restart", self.handle_vnc_restart)
629
629
  r.add_get("/api/vnc/screenshot", self.handle_vnc_screenshot)
630
630
  r.add_post("/api/vnc/rotate", self.handle_vnc_rotate) # [v1.47.1] 屏幕旋转
631
+ r.add_post("/api/vnc/resize", self.handle_vnc_resize) # [v1.47.30] 自适应分辨率
631
632
  r.add_get("/vnc/{path:.*}", self.handle_novnc_proxy) # noVNC 客户端页面代理
632
633
  # [v1.20.8] VNC WebSocket 代理 — 通过主服务器转发 VNC WebSocket,解决 HTTPS/WSS 问题
633
634
  r.add_get("/api/vnc/ws", self.handle_vnc_websocket)
@@ -958,6 +959,20 @@ class ApiServer:
958
959
  result = mgr.rotate_display(direction)
959
960
  return web.json_response(result)
960
961
 
962
+ async def handle_vnc_resize(self, request):
963
+ """POST /api/vnc/resize - [v1.47.30] 自适应调整 VNC 分辨率匹配客户端视口"""
964
+ mgr = self._get_vnc_manager()
965
+ try:
966
+ data = await request.json()
967
+ except Exception:
968
+ data = {}
969
+ width = data.get("width", 0)
970
+ height = data.get("height", 0)
971
+ if not width or not height:
972
+ return web.json_response({"success": False, "error": "需要 width 和 height 参数"})
973
+ result = mgr.resize_display(int(width), int(height))
974
+ return web.json_response(result)
975
+
961
976
  async def handle_novnc_proxy(self, request):
962
977
  """GET /vnc/{path} - 代理 noVNC 客户端页面
963
978
 
@@ -1671,6 +1686,51 @@ function initVNC(RFBClass) {{
1671
1686
  window.rfb = rfb;
1672
1687
  window._vncConnect = connect;
1673
1688
  window._vncUseProxy = useProxy;
1689
+
1690
+ // [v1.47.30] 自适应分辨率 — 检测视口大小,通知后端调整 Xvfb 分辨率
1691
+ // 实现:客户端知道自己的视口尺寸,通过 /api/vnc/resize 告诉后端
1692
+ // 后端用 xrandr --fb 动态调整 Xvfb 帧缓冲区,实现像素级清晰
1693
+ var _lastResizeW = 0, _lastResizeH = 0, _resizeDebounce = null;
1694
+
1695
+ function reportViewportSize() {{
1696
+ // 计算实际可用区域(去掉工具栏和虚拟按键栏的高度)
1697
+ var screenDiv = document.getElementById('screen');
1698
+ if (!screenDiv) return;
1699
+ var w = screenDiv.clientWidth;
1700
+ var h = screenDiv.clientHeight;
1701
+ if (w < 320 || h < 240) return;
1702
+ // 避免微小变化频繁触发(差值 < 10px 忽略)
1703
+ if (_lastResizeW > 0 && Math.abs(w - _lastResizeW) < 10 && Math.abs(h - _lastResizeH) < 10) return;
1704
+ _lastResizeW = w;
1705
+ _lastResizeH = h;
1706
+ fetch('/api/vnc/resize', {{
1707
+ method: 'POST',
1708
+ headers: {{ 'Content-Type': 'application/json' }},
1709
+ body: JSON.stringify({{ width: w, height: h }})
1710
+ }}).then(function(r) {{ return r.json(); }})
1711
+ .then(function(data) {{
1712
+ if (data.success) {{
1713
+ var statusEl = document.getElementById('statusText');
1714
+ if (statusEl) statusEl.textContent = data.resolution || '';
1715
+ }}
1716
+ }}).catch(function() {{}});
1717
+ }}
1718
+
1719
+ // 监听窗口大小变化(手机旋转 = resize 事件)
1720
+ window.addEventListener('resize', function() {{
1721
+ if (_resizeDebounce) clearTimeout(_resizeDebounce);
1722
+ _resizeDebounce = setTimeout(reportViewportSize, 500);
1723
+ }});
1724
+
1725
+ // VNC 连接成功后立即上报一次视口大小
1726
+ // 监听 connect 事件(通过轮询 isConnected)
1727
+ var _resizeCheckInterval = setInterval(function() {{
1728
+ if (isConnected) {{
1729
+ reportViewportSize();
1730
+ clearInterval(_resizeCheckInterval);
1731
+ }}
1732
+ }}, 1000);
1733
+
1674
1734
  window.sendCtrlAltDel = function() {{
1675
1735
  if (rfb) rfb.sendCtrlAltDel();
1676
1736
  }};