myagent-ai 1.23.45 → 1.23.47
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/core/vnc_manager.py +153 -0
- package/package.json +1 -1
- package/scripts/cli.py +74 -35
package/core/vnc_manager.py
CHANGED
|
@@ -76,11 +76,13 @@ class VNCManager:
|
|
|
76
76
|
self._xvfb_process: Optional[subprocess.Popen] = None
|
|
77
77
|
self._x11vnc_process: Optional[subprocess.Popen] = None
|
|
78
78
|
self._websockify_process: Optional[subprocess.Popen] = None
|
|
79
|
+
self._wm_process: Optional[subprocess.Popen] = None # [v1.23.45] 窗口管理器进程
|
|
79
80
|
|
|
80
81
|
# 状态标记
|
|
81
82
|
self._running = False
|
|
82
83
|
self._xvfb_attached = False # 是否附加到已有的 Xvfb 实例
|
|
83
84
|
self._xvfb_attached_pid: Optional[int] = None # 附加的 Xvfb PID
|
|
85
|
+
self._wm_name: Optional[str] = None # [v1.23.45] 当前窗口管理器名称
|
|
84
86
|
self._lock = asyncio.Lock()
|
|
85
87
|
|
|
86
88
|
# noVNC web 文件路径(用于 aiohttp 静态文件服务)
|
|
@@ -477,6 +479,9 @@ class VNCManager:
|
|
|
477
479
|
# 等待 Xvfb 就绪
|
|
478
480
|
await asyncio.sleep(1)
|
|
479
481
|
|
|
482
|
+
# [v1.23.45] Step 2.5: 自动启动窗口管理器(解决黑屏问题)
|
|
483
|
+
await self._start_window_manager()
|
|
484
|
+
|
|
480
485
|
# Step 3: 启动 x11vnc
|
|
481
486
|
vnc_ok = await self._start_x11vnc()
|
|
482
487
|
if not vnc_ok:
|
|
@@ -527,6 +532,12 @@ class VNCManager:
|
|
|
527
532
|
errors.extend(await self._kill_process(self._x11vnc_process, "x11vnc"))
|
|
528
533
|
self._x11vnc_process = None
|
|
529
534
|
|
|
535
|
+
# [v1.23.45] 停止窗口管理器
|
|
536
|
+
if self._wm_process:
|
|
537
|
+
errors.extend(await self._kill_process(self._wm_process, f"wm-{self._wm_name}"))
|
|
538
|
+
self._wm_process = None
|
|
539
|
+
self._wm_name = None
|
|
540
|
+
|
|
530
541
|
# Xvfb: 只有自己启动的才杀,附加的不杀
|
|
531
542
|
if self._xvfb_process:
|
|
532
543
|
errors.extend(await self._kill_process(self._xvfb_process, "Xvfb"))
|
|
@@ -729,6 +740,148 @@ class VNCManager:
|
|
|
729
740
|
logger.error(f"Xvfb 启动异常: {e}")
|
|
730
741
|
return False
|
|
731
742
|
|
|
743
|
+
async def _start_window_manager(self) -> None:
|
|
744
|
+
"""[v1.23.45] 自动检测并启动窗口管理器,解决 VNC 黑屏问题。
|
|
745
|
+
|
|
746
|
+
优先级: 已运行的 WM > fluxbox > openbox > xfce4-session > icewm > jwm > 自动安装 fluxbox
|
|
747
|
+
"""
|
|
748
|
+
import shutil
|
|
749
|
+
|
|
750
|
+
# 1. 检查是否已有窗口管理器在目标 DISPLAY 上运行
|
|
751
|
+
env = {**os.environ, "DISPLAY": self.display}
|
|
752
|
+
try:
|
|
753
|
+
result = subprocess.run(
|
|
754
|
+
["pgrep", "-f", f"(fluxbox|openbox|xfce4-session|icewm|jwm|lxsession|gnome-session|startkde|mutter|kwin)"],
|
|
755
|
+
capture_output=True, text=True, timeout=5,
|
|
756
|
+
)
|
|
757
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
758
|
+
pids = result.stdout.strip().split()
|
|
759
|
+
logger.info(f"检测到已有窗口管理器运行 (PIDs={pids[:3]}),跳过自动启动")
|
|
760
|
+
self._wm_name = "existing"
|
|
761
|
+
return
|
|
762
|
+
except Exception:
|
|
763
|
+
pass
|
|
764
|
+
|
|
765
|
+
# 2. 按优先级尝试启动
|
|
766
|
+
# 优先级: fluxbox > openbox > xfce4-session > icewm > jwm > twm
|
|
767
|
+
_wm_commands = [
|
|
768
|
+
("fluxbox", ["fluxbox"]),
|
|
769
|
+
("openbox", ["openbox", "--sm-disable"]),
|
|
770
|
+
("xfce4-session", ["startxfce4"]),
|
|
771
|
+
("icewm", ["icewm-session"]),
|
|
772
|
+
("jwm", ["jwm"]),
|
|
773
|
+
("twm", ["twm"]), # twm 是 X11 自带的最小 WM
|
|
774
|
+
]
|
|
775
|
+
|
|
776
|
+
wm_name = None
|
|
777
|
+
wm_cmd = None
|
|
778
|
+
for name, cmd in _wm_commands:
|
|
779
|
+
if shutil.which(cmd[0]):
|
|
780
|
+
wm_name = name
|
|
781
|
+
wm_cmd = cmd
|
|
782
|
+
break
|
|
783
|
+
|
|
784
|
+
if wm_name and wm_cmd:
|
|
785
|
+
try:
|
|
786
|
+
logger.info(f"启动窗口管理器: {wm_name} ({' '.join(wm_cmd)})")
|
|
787
|
+
self._wm_process = subprocess.Popen(
|
|
788
|
+
wm_cmd,
|
|
789
|
+
stdin=subprocess.DEVNULL,
|
|
790
|
+
stdout=subprocess.DEVNULL,
|
|
791
|
+
stderr=subprocess.DEVNULL,
|
|
792
|
+
env=env,
|
|
793
|
+
preexec_fn=os.setpgrp,
|
|
794
|
+
)
|
|
795
|
+
self._wm_name = wm_name
|
|
796
|
+
await asyncio.sleep(0.5)
|
|
797
|
+
|
|
798
|
+
if self._wm_process.poll() is None:
|
|
799
|
+
logger.info(f"窗口管理器 {wm_name} 已启动 (PID={self._wm_process.pid})")
|
|
800
|
+
# 设置一个简单的深灰色背景(避免纯黑)
|
|
801
|
+
try:
|
|
802
|
+
subprocess.run(
|
|
803
|
+
["xsetroot", "-solid", "#2d2d2d"],
|
|
804
|
+
capture_output=True, timeout=5, env=env,
|
|
805
|
+
)
|
|
806
|
+
except Exception:
|
|
807
|
+
pass
|
|
808
|
+
return
|
|
809
|
+
else:
|
|
810
|
+
logger.warning(f"窗口管理器 {wm_name} 启动后立即退出")
|
|
811
|
+
except Exception as e:
|
|
812
|
+
logger.warning(f"启动窗口管理器 {wm_name} 失败: {e}")
|
|
813
|
+
|
|
814
|
+
# 3. 没有可用的 WM → 尝试自动安装 fluxbox
|
|
815
|
+
logger.info("未找到已安装的窗口管理器,尝试自动安装 fluxbox...")
|
|
816
|
+
install_ok = await self._auto_install_wm()
|
|
817
|
+
if install_ok:
|
|
818
|
+
# 安装成功,重新尝试启动
|
|
819
|
+
for name, cmd in _wm_commands[:1]: # 只尝试 fluxbox
|
|
820
|
+
if shutil.which(cmd[0]):
|
|
821
|
+
try:
|
|
822
|
+
logger.info(f"重新启动窗口管理器: {name}")
|
|
823
|
+
self._wm_process = subprocess.Popen(
|
|
824
|
+
cmd,
|
|
825
|
+
stdin=subprocess.DEVNULL,
|
|
826
|
+
stdout=subprocess.DEVNULL,
|
|
827
|
+
stderr=subprocess.DEVNULL,
|
|
828
|
+
env=env,
|
|
829
|
+
preexec_fn=os.setpgrp,
|
|
830
|
+
)
|
|
831
|
+
self._wm_name = name
|
|
832
|
+
await asyncio.sleep(0.5)
|
|
833
|
+
if self._wm_process.poll() is None:
|
|
834
|
+
logger.info(f"窗口管理器 {name} 已启动 (PID={self._wm_process.pid})")
|
|
835
|
+
try:
|
|
836
|
+
subprocess.run(
|
|
837
|
+
["xsetroot", "-solid", "#2d2d2d"],
|
|
838
|
+
capture_output=True, timeout=5, env=env,
|
|
839
|
+
)
|
|
840
|
+
except Exception:
|
|
841
|
+
pass
|
|
842
|
+
return
|
|
843
|
+
except Exception as e:
|
|
844
|
+
logger.warning(f"重新启动 {name} 失败: {e}")
|
|
845
|
+
break
|
|
846
|
+
|
|
847
|
+
logger.warning(
|
|
848
|
+
"未能启动任何窗口管理器,VNC 将显示黑屏。"
|
|
849
|
+
"请手动安装: sudo apt install -y fluxbox xterm"
|
|
850
|
+
)
|
|
851
|
+
|
|
852
|
+
async def _auto_install_wm(self) -> bool:
|
|
853
|
+
"""[v1.23.45] 自动安装 fluxbox 窗口管理器。"""
|
|
854
|
+
_apt_cmd = ["apt-get", "install", "-y", "fluxbox", "xterm"]
|
|
855
|
+
try:
|
|
856
|
+
# 尝试直接执行(root 环境)
|
|
857
|
+
result = subprocess.run(
|
|
858
|
+
_apt_cmd,
|
|
859
|
+
capture_output=True, text=True, timeout=120,
|
|
860
|
+
)
|
|
861
|
+
if result.returncode == 0:
|
|
862
|
+
logger.info("fluxbox 安装成功")
|
|
863
|
+
return True
|
|
864
|
+
# 尝试 sudo
|
|
865
|
+
result = subprocess.run(
|
|
866
|
+
["sudo"] + _apt_cmd,
|
|
867
|
+
capture_output=True, text=True, timeout=120,
|
|
868
|
+
)
|
|
869
|
+
if result.returncode == 0:
|
|
870
|
+
logger.info("fluxbox 安装成功 (sudo)")
|
|
871
|
+
return True
|
|
872
|
+
stderr = result.stderr[:300] if result.stderr else ""
|
|
873
|
+
logger.warning(f"fluxbox 自动安装失败: {stderr}")
|
|
874
|
+
return False
|
|
875
|
+
except FileNotFoundError:
|
|
876
|
+
logger.warning("未找到 apt-get,无法自动安装窗口管理器")
|
|
877
|
+
return False
|
|
878
|
+
except subprocess.TimeoutExpired:
|
|
879
|
+
logger.warning("fluxbox 安装超时 (120s)")
|
|
880
|
+
return False
|
|
881
|
+
except Exception as e:
|
|
882
|
+
logger.warning(f"fluxbox 安装异常: {e}")
|
|
883
|
+
return False
|
|
884
|
+
|
|
732
885
|
async def _start_x11vnc(self) -> bool:
|
|
733
886
|
"""启动 x11vnc VNC 服务器"""
|
|
734
887
|
try:
|
package/package.json
CHANGED
package/scripts/cli.py
CHANGED
|
@@ -153,6 +153,75 @@ async def _run():
|
|
|
153
153
|
sys.exit(1)
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
def _filter_unknown_args(args, allowed_prefixes=None):
|
|
157
|
+
"""[v1.23.46] 通用参数过滤器 — 去掉不认识的系统命令参数。
|
|
158
|
+
|
|
159
|
+
LLM 经常把系统命令的参数(如 ls -la, grep -n)传给 myagent-ai 子命令。
|
|
160
|
+
此函数只保留: 位置参数(不以 - 开头) + allowed_prefixes 匹配的选项。
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
args: 原始参数列表
|
|
164
|
+
allowed_prefixes: 允许的选项前缀集合(如 {"-p", "--pattern", "-r", "--max"})
|
|
165
|
+
Returns:
|
|
166
|
+
过滤后的参数列表
|
|
167
|
+
"""
|
|
168
|
+
if allowed_prefixes is None:
|
|
169
|
+
allowed_prefixes = set()
|
|
170
|
+
|
|
171
|
+
# 常见系统命令参数(标志,不带值)
|
|
172
|
+
_sys_flags = {
|
|
173
|
+
"-l", "-a", "-h", "-L", "-d", "-F", "-f", "-i", "-s", "-R",
|
|
174
|
+
"-S", "-t", "-r", "-u", "-c", "-1", "-m", "-x", "-o", "-g",
|
|
175
|
+
"-G", "-A", "-n", "-w", "-p", "-q", "-v", "-e", "-E", "-z",
|
|
176
|
+
"-b", "-B", "-T", "-H", "-I", "-k", "-N", "-W",
|
|
177
|
+
# GNU long options
|
|
178
|
+
"--color", "--group-directories-first", "--hide", "--indicator-style",
|
|
179
|
+
"--quoting-style", "--show-control-chars", "--sort", "--time",
|
|
180
|
+
"--time-style", "--format", "--human-readable", "-lh", "-la", "-al",
|
|
181
|
+
"-lr", "-lt", "-ls", "-ld", "-li", "-shal", "-lah",
|
|
182
|
+
}
|
|
183
|
+
# 带值的系统参数
|
|
184
|
+
_sys_opts = {
|
|
185
|
+
"--sort", "--time", "--time-style", "--format", "--hide",
|
|
186
|
+
"--indicator-style", "--quoting-style", "--context", "--width",
|
|
187
|
+
"--tabs", "--block-size",
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
filtered = []
|
|
191
|
+
skip_next = False
|
|
192
|
+
for arg in args:
|
|
193
|
+
if skip_next:
|
|
194
|
+
skip_next = False
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
# 以 - 开头的是选项参数
|
|
198
|
+
if arg.startswith("-"):
|
|
199
|
+
# 检查是否在允许列表中
|
|
200
|
+
matched = any(arg.startswith(prefix) for prefix in allowed_prefixes)
|
|
201
|
+
if matched:
|
|
202
|
+
filtered.append(arg)
|
|
203
|
+
# 如果参数不含 =,且不是标志模式,可能需要跳过下一个
|
|
204
|
+
if "=" not in arg and arg not in allowed_prefixes:
|
|
205
|
+
# 检查是否有对应的 long option 带 =
|
|
206
|
+
pass
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
# 不在允许列表中,检查是否是系统参数
|
|
210
|
+
stripped = arg.lstrip("-").rstrip("=")
|
|
211
|
+
if arg in _sys_flags or stripped in _sys_flags:
|
|
212
|
+
continue # 标志,跳过
|
|
213
|
+
if arg in _sys_opts or stripped in _sys_opts:
|
|
214
|
+
skip_next = True # 带值选项,跳过下一个
|
|
215
|
+
continue
|
|
216
|
+
# 未知选项,跳过
|
|
217
|
+
continue
|
|
218
|
+
else:
|
|
219
|
+
# 位置参数,保留
|
|
220
|
+
filtered.append(arg)
|
|
221
|
+
|
|
222
|
+
return filtered if filtered else args
|
|
223
|
+
|
|
224
|
+
|
|
156
225
|
# =============================================================================
|
|
157
226
|
# 感知命令
|
|
158
227
|
# =============================================================================
|
|
@@ -308,6 +377,8 @@ async def cmd_write(args):
|
|
|
308
377
|
async def cmd_ls(args):
|
|
309
378
|
"""列出目录内容"""
|
|
310
379
|
import argparse
|
|
380
|
+
# [v1.23.46] 过滤系统 ls 参数(-l, -a, -la 等)
|
|
381
|
+
args = _filter_unknown_args(args, allowed_prefixes={"-p", "--pattern", "-r", "--recursive", "--max"})
|
|
311
382
|
p = argparse.ArgumentParser(prog="myagent-ai ls", description="列出目录内容")
|
|
312
383
|
p.add_argument("path", help="目录路径")
|
|
313
384
|
p.add_argument("-p", "--pattern", default="*", help="文件匹配模式 (如 *.py)")
|
|
@@ -340,47 +411,15 @@ async def cmd_rm(args):
|
|
|
340
411
|
async def cmd_grep(args):
|
|
341
412
|
"""搜索文件内容"""
|
|
342
413
|
import argparse
|
|
343
|
-
# [v1.23.
|
|
344
|
-
|
|
345
|
-
_unsupported_flags = {"-n", "--line-number", "-l", "--files-with-matches",
|
|
346
|
-
"-r", "-R", "--recursive", "-i", "--ignore-case",
|
|
347
|
-
"-c", "--count", "-w", "--word-regexp", "-v",
|
|
348
|
-
"--invert-match", "-E", "-F", "-P", "-m"}
|
|
349
|
-
_unsupported_opts = {"-e", "--regexp", "--include", "--exclude",
|
|
350
|
-
"--after-context", "--before-context", "--context",
|
|
351
|
-
"-A", "-B", "-C", "--max-count", "--color"}
|
|
352
|
-
_filtered = []
|
|
353
|
-
_skip_next = False
|
|
354
|
-
for i, a in enumerate(args):
|
|
355
|
-
if _skip_next:
|
|
356
|
-
_skip_next = False
|
|
357
|
-
continue
|
|
358
|
-
if a in _unsupported_flags:
|
|
359
|
-
# 标志参数,不跳过下一个
|
|
360
|
-
continue
|
|
361
|
-
if a in _unsupported_opts:
|
|
362
|
-
# 带参数选项,跳过下一个
|
|
363
|
-
_skip_next = True
|
|
364
|
-
continue
|
|
365
|
-
# 处理组合标志如 -rn、长选项 --name=Value
|
|
366
|
-
if any(a.startswith(k) for k in _unsupported_flags):
|
|
367
|
-
# -rn 或 -ni 等组合标志,不跳过下一个
|
|
368
|
-
continue
|
|
369
|
-
if any(a.startswith(k) for k in _unsupported_opts):
|
|
370
|
-
if "=" not in a:
|
|
371
|
-
_skip_next = True
|
|
372
|
-
continue
|
|
373
|
-
_filtered.append(a)
|
|
374
|
-
if not _filtered:
|
|
375
|
-
_filtered = args # fallback
|
|
376
|
-
|
|
414
|
+
# [v1.23.46] 使用通用过滤器,过滤系统 grep 参数(-n, -i, -r 等)
|
|
415
|
+
args = _filter_unknown_args(args, allowed_prefixes={"-p", "--pattern", "--max", "--depth"})
|
|
377
416
|
p = argparse.ArgumentParser(prog="myagent-ai grep", description="在目录中搜索文件内容")
|
|
378
417
|
p.add_argument("query", help="搜索关键词")
|
|
379
418
|
p.add_argument("path", help="搜索目录路径")
|
|
380
419
|
p.add_argument("-p", "--pattern", default="*", help="文件匹配模式 (如 *.py)")
|
|
381
420
|
p.add_argument("--max", type=int, default=50, help="最大结果数 (默认50)")
|
|
382
421
|
p.add_argument("--depth", type=int, default=10, help="最大递归深度 (默认10)")
|
|
383
|
-
a = p.parse_args(
|
|
422
|
+
a = p.parse_args(args)
|
|
384
423
|
|
|
385
424
|
from skills.file_skill import FileSearchSkill
|
|
386
425
|
skill = FileSearchSkill()
|