myagent-ai 1.47.18 → 1.47.19

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.
@@ -1865,25 +1865,53 @@ class StealthBrowser:
1865
1865
  """Firefox+VNC 模式下导航到指定 URL。
1866
1866
 
1867
1867
  Firefox 支持远程打开 URL:firefox <url> 会在已运行的实例中打开新标签页。
1868
+ [v1.47.19] 改用 Popen 非阻塞方式:firefox <url> 在已运行实例中打开新标签页
1869
+ 后会阻塞等待窗口关闭,subprocess.run + timeout 会导致超时。
1870
+ 改用 Popen 后不等待命令完成,只确认启动即可。
1868
1871
  """
1869
1872
  display = os.environ.get("DISPLAY", ":99")
1870
1873
  env = {**os.environ, "DISPLAY": display}
1871
1874
  if not env.get("G_SLICE"):
1872
1875
  env["G_SLICE"] = "always-malloc"
1876
+ if not env.get("GSETTINGS_BACKEND"):
1877
+ env["GSETTINGS_BACKEND"] = "memory"
1878
+ if os.environ.get("DBUS_SESSION_BUS_ADDRESS"):
1879
+ env["DBUS_SESSION_BUS_ADDRESS"] = os.environ["DBUS_SESSION_BUS_ADDRESS"]
1873
1880
 
1874
1881
  try:
1875
1882
  firefox_path = shutil.which("firefox") or shutil.which("myagent-browser")
1876
1883
  if not firefox_path:
1877
1884
  return SkillResult(success=False, error="Firefox 未找到")
1878
1885
 
1879
- result = subprocess.run(
1886
+ # [v1.47.19] 使用 Popen 非阻塞方式启动
1887
+ # firefox <url> 在已有实例运行时会将 URL 发送给已有实例,
1888
+ # 然后阻塞等待(因为 Firefox 的 -no-remote 行为)。
1889
+ # 用 Popen 启动后不等待完成,只等待进程启动成功。
1890
+ process = subprocess.Popen(
1880
1891
  [firefox_path, "--profile", self._firefox_profile_dir, url],
1881
- capture_output=True, text=True, timeout=10,
1882
- env=env, start_new_session=True,
1892
+ stdout=subprocess.DEVNULL,
1893
+ stderr=subprocess.DEVNULL,
1894
+ env=env,
1895
+ start_new_session=True,
1883
1896
  )
1884
- logger.info(f"Firefox 打开 URL: {url}")
1897
+ # 等待短时间确认进程没有立即崩溃
1898
+ time.sleep(0.5)
1899
+ if process.poll() is not None:
1900
+ # 进程已退出,可能是错误
1901
+ exit_code = process.returncode
1902
+ # Firefox 在已有实例时,转发URL后以 exit code 0 退出(正常行为)
1903
+ if exit_code == 0:
1904
+ logger.info(f"Firefox 已转发 URL 到已有实例: {url}")
1905
+ else:
1906
+ return SkillResult(
1907
+ success=False,
1908
+ error=f"Firefox 打开 URL 失败 (exit code: {exit_code}): {url}",
1909
+ )
1910
+
1885
1911
  if wait > 0:
1886
1912
  time.sleep(wait)
1913
+
1914
+ logger.info(f"Firefox 已打开 URL: {url}")
1887
1915
  return SkillResult(
1888
1916
  success=True,
1889
1917
  message=f"Firefox 已打开: {url}",
@@ -1398,10 +1398,32 @@ class VNCManager:
1398
1398
  # --session: 会话总线
1399
1399
  # --address: 指定 unix socket
1400
1400
  env = {**os.environ, "DISPLAY": os.environ.get("DISPLAY", self.display)}
1401
+
1402
+ # [v1.47.19] 创建自定义 dbus 会话配置,禁用 launch-helper
1403
+ # 避免 "error: expected absolute path: --shm-helper" 错误
1404
+ # dbus-daemon 的默认 session.conf 包含 <servicedir> 和
1405
+ # <servicehelper> 指令,后者会调用 dbus-daemon-launch-helper --shm-helper
1406
+ # 在 proot 下路径翻译破坏了参数传递。改为创建不含 servicehelper 的配置。
1407
+ custom_config_path = f"{dbus_socket_dir}.conf"
1408
+ try:
1409
+ custom_config = self._create_dbus_session_config()
1410
+ with open(custom_config_path, "w") as f:
1411
+ f.write(custom_config)
1412
+ logger.debug(f"已创建自定义 D-Bus 会话配置: {custom_config_path}")
1413
+ except Exception as conf_err:
1414
+ logger.debug(f"创建自定义 D-Bus 配置失败,使用默认配置: {conf_err}")
1415
+ custom_config_path = None
1416
+
1417
+ dbus_cmd = [dbus_daemon, "--nofork",
1418
+ f"--address={dbus_socket}",
1419
+ f"--pidfile={dbus_pid_file}"]
1420
+ if custom_config_path:
1421
+ dbus_cmd.extend(["--config-file", custom_config_path])
1422
+ else:
1423
+ dbus_cmd.append("--session")
1424
+
1401
1425
  proc = subprocess.Popen(
1402
- [dbus_daemon, "--session", "--nofork",
1403
- f"--address={dbus_socket}",
1404
- f"--pidfile={dbus_pid_file}"],
1426
+ dbus_cmd,
1405
1427
  stdin=subprocess.DEVNULL,
1406
1428
  stdout=subprocess.DEVNULL,
1407
1429
  stderr=subprocess.DEVNULL,
@@ -1438,6 +1460,64 @@ class VNCManager:
1438
1460
  except Exception as e:
1439
1461
  logger.warning(f"dbus-daemon 直接启动异常: {e}")
1440
1462
 
1463
+ def _create_dbus_session_config(self) -> str:
1464
+ """[v1.47.19] 创建不含 servicehelper 的 D-Bus 会话配置。
1465
+
1466
+ 标准 session.conf 包含 <servicehelper> 指向 dbus-daemon-launch-helper,
1467
+ 后者会被 dbus-daemon 用 --shm-helper 参数调用。
1468
+ 在 proot 下路径翻译破坏了这个调用,导致反复出现:
1469
+ error: expected absolute path: "--shm-helper"
1470
+
1471
+ 解决方法: 读取系统默认 session.conf,移除 <servicehelper> 行,
1472
+ 保留其他配置(<servicedir>、<policy> 等)。
1473
+ 这样 D-Bus 服务仍可被发现,但不会被 launch-helper 激活
1474
+ (需要程序自己启动对应的 D-Bus 服务)。
1475
+ """
1476
+ import re as _re
1477
+
1478
+ # 查找系统默认 session.conf
1479
+ conf_paths = [
1480
+ "/usr/share/dbus-1/session.conf",
1481
+ "/etc/dbus-1/session.conf",
1482
+ ]
1483
+ default_conf = ""
1484
+ for cp in conf_paths:
1485
+ if os.path.isfile(cp):
1486
+ try:
1487
+ with open(cp, "r") as f:
1488
+ default_conf = f.read()
1489
+ break
1490
+ except Exception:
1491
+ pass
1492
+
1493
+ if not default_conf:
1494
+ # 无法读取默认配置,创建最小配置
1495
+ return """<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
1496
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
1497
+ <busconfig>
1498
+ <type>session</type>
1499
+ <listen>unix:tmpdir=/tmp</listen>
1500
+ <servicedir>/usr/share/dbus-1/services</servicedir>
1501
+ <policy context="default">
1502
+ <allow send_destination="*" eavesdrop="true"/>
1503
+ <allow eavesdrop="true"/>
1504
+ <allow own="*"/>
1505
+ </policy>
1506
+ </busconfig>"""
1507
+
1508
+ # 从默认配置中移除 <servicehelper> 行
1509
+ # 匹配 <servicehelper>...</servicehelper> 或 <servicehelper .../>
1510
+ lines = default_conf.split("\n")
1511
+ filtered = []
1512
+ for line in lines:
1513
+ stripped = line.strip()
1514
+ # 跳过 servicehelper 行(这是导致 --shm-helper 错误的根源)
1515
+ if stripped.startswith("<servicehelper"):
1516
+ continue
1517
+ filtered.append(line)
1518
+
1519
+ return "\n".join(filtered)
1520
+
1441
1521
  def _start_dbus_launch_standard(self) -> None:
1442
1522
  """[v1.43.3] 非 proot 环境下用 dbus-launch 标准方式启动 D-Bus。"""
1443
1523
  dbus_launch = shutil.which("dbus-launch")
@@ -3316,7 +3396,18 @@ exec {backup_path} "${{args[@]}}"
3316
3396
  ' DBUS_SOCKET_DIR="/tmp/dbus-myagent-$(id -u)"',
3317
3397
  ' mkdir -p "$DBUS_SOCKET_DIR" 2>/dev/null',
3318
3398
  ' chmod 700 "$DBUS_SOCKET_DIR" 2>/dev/null',
3319
- ' dbus-daemon --session --nofork --address="unix:path=$DBUS_SOCKET_DIR" &',
3399
+ # [v1.47.19] 使用自定义配置(移除 servicehelper,避免 --shm-helper 错误)
3400
+ ' DBUS_CONF="$DBUS_SOCKET_DIR.conf"',
3401
+ ' if [ ! -f "$DBUS_CONF" ]; then',
3402
+ ' if [ -f /usr/share/dbus-1/session.conf ]; then',
3403
+ ' grep -v "<servicehelper" /usr/share/dbus-1/session.conf > "$DBUS_CONF" 2>/dev/null',
3404
+ ' fi',
3405
+ ' fi',
3406
+ ' if [ -f "$DBUS_CONF" ]; then',
3407
+ ' dbus-daemon --nofork --config-file="$DBUS_CONF" --address="unix:path=$DBUS_SOCKET_DIR" &',
3408
+ ' else',
3409
+ ' dbus-daemon --session --nofork --address="unix:path=$DBUS_SOCKET_DIR" &',
3410
+ ' fi',
3320
3411
  ' export DBUS_SESSION_BUS_ADDRESS="unix:path=$DBUS_SOCKET_DIR"',
3321
3412
  ' elif [ -x /usr/bin/dbus-launch ]; then',
3322
3413
  ' # 非 proot: 标准方式',
@@ -4836,7 +4927,18 @@ if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
4836
4927
  if [ "$IS_PROOT" = "1" ] && [ -x /usr/bin/dbus-daemon ]; then
4837
4928
  DBUS_SOCK="/tmp/dbus-myagent-$(id -u)"
4838
4929
  mkdir -p "$DBUS_SOCK" 2>/dev/null && chmod 700 "$DBUS_SOCK" 2>/dev/null
4839
- dbus-daemon --session --nofork --address="unix:path=$DBUS_SOCK" &
4930
+ # [v1.47.19] 使用自定义配置(移除 servicehelper,避免 --shm-helper 错误)
4931
+ DBUS_CONF="$DBUS_SOCK.conf"
4932
+ if [ ! -f "$DBUS_CONF" ]; then
4933
+ if [ -f /usr/share/dbus-1/session.conf ]; then
4934
+ grep -v "<servicehelper" /usr/share/dbus-1/session.conf > "$DBUS_CONF" 2>/dev/null
4935
+ fi
4936
+ fi
4937
+ if [ -f "$DBUS_CONF" ]; then
4938
+ dbus-daemon --nofork --config-file="$DBUS_CONF" --address="unix:path=$DBUS_SOCK" &
4939
+ else
4940
+ dbus-daemon --session --nofork --address="unix:path=$DBUS_SOCK" &
4941
+ fi
4840
4942
  export DBUS_SESSION_BUS_ADDRESS="unix:path=$DBUS_SOCK"
4841
4943
  elif [ -x /usr/bin/dbus-launch ]; then
4842
4944
  eval $(dbus-launch --sh-syntax 2>/dev/null) || true
@@ -4884,7 +4986,18 @@ if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
4884
4986
  if [ "$IS_PROOT" = "1" ] && [ -x /usr/bin/dbus-daemon ]; then
4885
4987
  DBUS_SOCK="/tmp/dbus-myagent-$(id -u)"
4886
4988
  mkdir -p "$DBUS_SOCK" 2>/dev/null && chmod 700 "$DBUS_SOCK" 2>/dev/null
4887
- dbus-daemon --session --nofork --address="unix:path=$DBUS_SOCK" &
4989
+ # [v1.47.19] 使用自定义配置(移除 servicehelper,避免 --shm-helper 错误)
4990
+ DBUS_CONF="$DBUS_SOCK.conf"
4991
+ if [ ! -f "$DBUS_CONF" ]; then
4992
+ if [ -f /usr/share/dbus-1/session.conf ]; then
4993
+ grep -v "<servicehelper" /usr/share/dbus-1/session.conf > "$DBUS_CONF" 2>/dev/null
4994
+ fi
4995
+ fi
4996
+ if [ -f "$DBUS_CONF" ]; then
4997
+ dbus-daemon --nofork --config-file="$DBUS_CONF" --address="unix:path=$DBUS_SOCK" &
4998
+ else
4999
+ dbus-daemon --session --nofork --address="unix:path=$DBUS_SOCK" &
5000
+ fi
4888
5001
  export DBUS_SESSION_BUS_ADDRESS="unix:path=$DBUS_SOCK"
4889
5002
  elif [ -x /usr/bin/dbus-launch ]; then
4890
5003
  eval $(dbus-launch --sh-syntax 2>/dev/null) || true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.47.18",
3
+ "version": "1.47.19",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/worklog.md CHANGED
@@ -69,3 +69,51 @@ Stage Summary:
69
69
  - Async non-blocking: waiting for lock does NOT block the event loop (other coroutines run normally)
70
70
  - New `browser_wait_status` tool: agents can check lock status and optionally wait for browser to become free
71
71
  - All 19 tests passed (7 import/signature + 12 async contention)
72
+
73
+ ---
74
+ Task ID: 3
75
+ Agent: Main
76
+ Task: Fix model outputting `<output>` XML tags visible to users + VNC mode Firefox fix
77
+
78
+ Work Log:
79
+ - Analyzed the full streaming pipeline: LLM → _call_llm_stream → _emit_text_delta → _text_delta_callback → SSE → Frontend
80
+ - Found root cause: v1.38 switched to native tool_calling, but some models still output old `<output>` XML format
81
+ - When models output `<output>` XML without native tool_calls, the XML was treated as plain text and shown to users
82
+ - The frontend `_stripXmlTags` only ran on keyless assistant messages, not key="reply" messages
83
+
84
+ Changes made:
85
+ 1. **agents/main_agent.py** (_process_v2_inner):
86
+ - Added `<output>` XML fallback: when response.tool_calls is empty but content contains `<output>` XML, use output_parser to parse
87
+ - Handles mainsubject, remember, task_plan, and tools_to_call from parsed XML
88
+ - Executes tools from `<toolstocal>` and continues the LLM loop
89
+ - Extracts `<reply>` content for user display, strips all XML tags as fallback
90
+ - Saves clean reply as key="reply" and raw XML as key="llm_output"
91
+
92
+ 2. **web/api_server.py** (_text_delta_callback):
93
+ - Added "output_xml" mode to streaming filter
94
+ - Detects `<output>` tag and enters output_xml mode, suppressing all non-reply content
95
+ - Extracts and streams `<reply>` content in real-time
96
+ - Handles both closed `<reply>...</reply>` and unclosed `<reply>` during streaming
97
+ - Exits output_xml mode on `</output>` close tag
98
+ - Updated _flush_remaining_text to handle output_xml mode
99
+
100
+ 3. **web/ui/chat/flow_engine.js** (pollChatHistory, forceRefreshHistory):
101
+ - Extended XML stripping to also handle key="reply" messages (not just keyless)
102
+ - Condition: `(!mkey || mkey === 'reply')` for assistant messages starting with `<`
103
+
104
+ 4. **web/ui/chat/chat_main.js** (two locations):
105
+ - Same fix: extended XML stripping to handle key="reply" messages
106
+
107
+ 5. **aiskills/browser_stealth.py** (VNC+Firefox):
108
+ - Already implemented in previous session: VNC mode directly uses Firefox, skipping Chrome/DrissionPage
109
+ - `_start_firefox_in_vnc()` method handles Firefox launch with proper env vars for proot ARM64
110
+ - `_detect_browser(skip_puppeteer=True)` in VNC mode skips Puppeteer Chrome detection
111
+
112
+ 6. **package.json**: Bumped version to 1.47.18, published to npm
113
+
114
+ Stage Summary:
115
+ - `<output>` XML tags no longer visible to users in any path (streaming, polling, history)
116
+ - Models that don't support native tool_calling now have their XML output properly parsed and executed
117
+ - Streaming filter extracts only `<reply>` content for real-time display
118
+ - Frontend strips XML from both keyless and key="reply" assistant messages
119
+ - VNC mode Firefox support fully functional