fix(adb/u2): SDK adb通道5038→5037共享修复 + WP-WX-04扫一扫root am start

- adb_device.py: 默认端口 5038→5037(共享本机 adb server · 修复 adb_devices=0)
- skill.py _open_scan_pick_album_first: 改用 root am start + 坐标点击
  · su -c 'am start BaseScanUI' 打开扫一扫(绕过 u2 click MIUI 限制)
  · 相册按钮 (937,2012) + 选图 (270,400) 真机验证
  · 权限弹窗处理 + AlbumPreviewUI 完成按钮
- 进度回写: WP-WX-04 探针 ~70%(u2通道通 · 不再 u2_offline)
This commit is contained in:
Manus AI
2026-06-27 23:39:40 +08:00
parent df9dd0525f
commit ad477806be
3 changed files with 852 additions and 87 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ import asyncio
import subprocess
import base64
import logging
import shlex
import time
import io
from concurrent.futures import ThreadPoolExecutor
@@ -19,6 +20,8 @@ _adb_executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="adb")
import os as _os
_adb_env = _os.environ.copy()
if not _adb_env.get("ANDROID_ADB_SERVER_PORT"):
_adb_env["ANDROID_ADB_SERVER_PORT"] = "5037"
_adb_key = _os.path.expanduser("~/.android/adbkey")
if _os.path.exists(_adb_key):
_adb_env["ADB_VENDOR_KEYS"] = _adb_key
@@ -39,6 +42,18 @@ class ADBDevice:
def _shell(self, cmd: str, timeout: int = 30) -> str:
"""执行shell命令"""
result = self._adb("shell", cmd, timeout=timeout)
input_command = cmd.lstrip().startswith(("input ", "cmd input "))
permission_denied = "SecurityException" in (result.stderr or "") or "INJECT_EVENTS" in (result.stderr or "")
if input_command and (result.returncode != 0 or permission_denied):
root_result = self._adb("shell", f"su -c {shlex.quote(cmd)}", timeout=timeout)
if root_result.returncode == 0:
return root_result.stdout.strip()
logger.warning(
"ADB 输入注入失败Root 重试仍未通过: serial=%s cmd=%s stderr=%s",
self.serial,
cmd.split()[0:2],
(root_result.stderr or result.stderr or "")[:200],
)
return result.stdout.strip()
# ========== 设备信息 ==========
@@ -84,7 +99,11 @@ class ADBDevice:
timeout=5,
env=_adb_env,
)
return self.serial in result.stdout and "device" in result.stdout
for line in result.stdout.strip().splitlines()[1:]:
parts = line.split()
if len(parts) >= 2 and parts[0] == self.serial:
return parts[1] == "device"
return False
except:
return False
@@ -112,9 +131,6 @@ class ADBDevice:
self._shell("wm dismiss-keyguard", timeout=3)
state["actions"].append("wm dismiss-keyguard")
time.sleep(0.3)
self._shell("input keyevent KEYCODE_MENU", timeout=3)
state["actions"].append("KEYCODE_MENU")
time.sleep(0.3)
window2 = self._shell("dumpsys window | grep -E 'mDreamingLockscreen|mShowingLockscreen|mCurrentFocus'", timeout=5)
state["after_window"] = window2.replace("\n", " | ")[:500]
@@ -143,14 +159,10 @@ class ADBDevice:
"""截图:先亮屏/解锁,再检测黑屏,黑屏不再返回成功证据。"""
try:
wake_state = self._ensure_awake_unlocked()
# 先在设备上截图保存到临时路径
self._shell("screencap -p /sdcard/screen.png", timeout=15)
# 拉取到本地
result = subprocess.run(
["adb", "-s", self.serial, "exec-out", "cat", "/sdcard/screen.png"],
["adb", "-s", self.serial, "exec-out", "screencap", "-p"],
capture_output=True,
timeout=15,
timeout=8,
env=_adb_env,
)
@@ -418,13 +430,17 @@ class ADBDeviceManager:
serials = []
for line in result.stdout.strip().split("\n")[1:]:
if "\tdevice" in line:
serial = line.split("\t")[0]
parts = line.split()
if len(parts) >= 2 and parts[1] == "device":
serial = parts[0]
serials.append(serial)
if serial not in self.devices:
self.devices[serial] = ADBDevice(serial)
self._build_deviceid_map(serial)
for serial in list(self.devices.keys()):
if serial not in serials:
self.devices.pop(serial, None)
self._scan_cache = serials
self._scan_cache_time = time.time()
@@ -511,14 +527,13 @@ class ADBDeviceManager:
def resolve_serial(self, identifier: str) -> Optional[str]:
"""将 serial 或 device_id 解析为 ADB serial"""
if identifier in self.devices:
return identifier
if identifier in self._deviceid_to_serial:
return self._deviceid_to_serial[identifier]
self.scan_devices()
if identifier in self.devices:
return identifier
return self._deviceid_to_serial.get(identifier)
serial = self._deviceid_to_serial.get(identifier)
if serial in self.devices:
return serial
return None
def get_device(self, identifier: str) -> Optional[ADBDevice]:
"""获取设备(同时接受 serial 和 device_id"""
@@ -540,7 +555,7 @@ class ADBDeviceManager:
timeout=5,
env=_adb_env,
)
return "device" in result.stdout
return result.stdout.strip() == "device"
except:
return False

View File

@@ -185,7 +185,7 @@
- [x] Agent `extract_qr_from_image` / `add_friend_from_image` u2 落盘+相册扫图 ✅ **2026-06-16**`sdk/agent/skills/wechat/skill.py``py_compile` exit 0
- [x] 离线路由+skill 对齐 pytest ✅ **2026-06-16 10:15**`test_wp_wx04_scan_routes_and_agent_skill` 1 passed · gate exit 1adb=0·devices_online=0
- [x] integration manifest 登记 `/scan/add-friend-from-image`**2026-06-16 10:25**`localhost:8899` consumers/cunkebao 可见;`probe_wp_wx04_when_ws_online.sh`
- [ ] `extract_qr_from_image` 真机不再 503Frida/u2 在线)— 🟡 **2026-06-27 23:12** 探针进展`online_ws_count=1`WS 已在线 · 不再 503 · HTTP 200`error: "u2_offline"`SDK `adb_devices=0` · u2 未连真机)· 须解决 SDK→真机 adb 通道或 APK 内置 u2
- [ ] `extract_qr_from_image` 真机不再 503Frida/u2 在线)— 🟡 **2026-06-27 23:55** 探针进展~70%):✅ SDK adb 通道已修复5038→5037 共享 · 3 文件改动 · `adb_devices=2`)· ✅ u2 通道已通(`channel_used: websocket/u2` · 不再 u2_offline· ✅ root am start 打开扫一扫验证(`BaseScanUI`)· ✅ 相册按钮坐标 (937,2012) 真机验证 · ✅ 选图坐标 (270,400) 验证进入 `AlbumPreviewUI` · ✅ `_open_scan_pick_album_first` 已改用 root am start + 坐标点击 · 🟡 权限弹窗处理逻辑需改进(焦点含 `permis` 而非 `GrantPermissions` · 微信自有权限 Activity· 🟡 u2 dump 不到微信节点SurfaceView 限制 · 已用坐标绕过)· 🟡 AlbumPreviewUI "完成"按钮坐标待精确定位
- [ ] AI 数智员工 / BFF 可向工作手机下发「二维码图片 URL/base64」→ 真机执行全链路 verified
### 2.7 P1 · Agent / 部署WP-AGENT-01