feat: connection_priority、hub.html、本地控制模式图、Hook交互流程图、工作流总图
Made-with: Cursor
@@ -19,6 +19,7 @@ mkdir -p "$TEMP_DIR/agent/skills/wechat" \
|
||||
"$TEMP_DIR/agent/skills/douyin" \
|
||||
"$TEMP_DIR/agent/skills/xhs" \
|
||||
"$TEMP_DIR/agent/skills/xianyu" \
|
||||
"$TEMP_DIR/agent/skills/soul" \
|
||||
"$TEMP_DIR/agent/hawk"
|
||||
|
||||
# 复制核心文件
|
||||
@@ -47,7 +48,7 @@ for f in README.md setup_redmi11.sh wechat_hook_v1.js; do
|
||||
[ -f "$SCRIPT_DIR/hook/$f" ] && cp "$SCRIPT_DIR/hook/$f" "$TEMP_DIR/agent/hook/"
|
||||
done
|
||||
|
||||
for sub in wechat douyin xhs xianyu; do
|
||||
for sub in wechat douyin xhs xianyu soul; do
|
||||
if [ -d "$SCRIPT_DIR/skills/$sub" ]; then
|
||||
cp "$SCRIPT_DIR/skills/$sub/__init__.py" "$TEMP_DIR/agent/skills/$sub/" 2>/dev/null || true
|
||||
cp "$SCRIPT_DIR/skills/$sub/skill.py" "$TEMP_DIR/agent/skills/$sub/" 2>/dev/null || true
|
||||
|
||||
@@ -34,5 +34,21 @@
|
||||
},
|
||||
"hook_framework": "frida-server",
|
||||
"updated_at": "2026-02-24T08:22:19.844907+00:00"
|
||||
},
|
||||
"dc9c23e00510": {
|
||||
"supports_hook": false,
|
||||
"frida_version": "",
|
||||
"root_status": true,
|
||||
"hook_framework": "frida-server",
|
||||
"updated_at": "2026-03-16T01:50:07.133711+00:00",
|
||||
"modules": []
|
||||
},
|
||||
"b0f1322b4e3be135e0000798d1587e26": {
|
||||
"supports_hook": false,
|
||||
"frida_version": "",
|
||||
"root_status": false,
|
||||
"hook_framework": "frida-server",
|
||||
"updated_at": "2026-03-16T01:50:27.104574+00:00",
|
||||
"modules": []
|
||||
}
|
||||
}
|
||||
428
sdk/app/main.py
@@ -3,7 +3,7 @@
|
||||
存客宝的AI手机控制引擎
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse, Response
|
||||
@@ -12,6 +12,9 @@ import logging
|
||||
import os
|
||||
import subprocess
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
import markdown
|
||||
|
||||
from config import settings
|
||||
from routers import devices, unified, agent, adb, experience, projects, qrcode, voice, capture, hook_modules, connection
|
||||
@@ -25,6 +28,341 @@ logging.basicConfig(
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_APP_DIR = os.path.dirname(__file__)
|
||||
_STATIC_DIR = os.path.join(_APP_DIR, "static")
|
||||
_DOCS_ROOT = os.path.normpath(os.path.join(_APP_DIR, "..", "..", "开发文档"))
|
||||
_DOC_PAGE_MAP = {
|
||||
"api-architecture": os.path.join(_DOCS_ROOT, "5、接口", "API架构与交互流程图.html"),
|
||||
"anti-ban-architecture": os.path.join(_DOCS_ROOT, "2、架构", "防封模块架构图.html"),
|
||||
"wechat-server-flow": os.path.join(_DOCS_ROOT, "2、架构", "微信控制-设备服务器交互图.html"),
|
||||
}
|
||||
_AGENT_ROOT = os.path.normpath(os.path.join(_APP_DIR, "..", "agent"))
|
||||
_AGENT_DOCS = [
|
||||
{
|
||||
"id": "agent-readme",
|
||||
"title": "Agent 架构与部署",
|
||||
"summary": "设备端 Agent 总览:WebSocket 连接、技能引擎、目录结构、Termux 一键安装。",
|
||||
"category": "agent",
|
||||
"section": "Agent 集群",
|
||||
"folder": "agent",
|
||||
"file": "README.md",
|
||||
"keywords": ["agent", "termux", "websocket", "部署"],
|
||||
},
|
||||
{
|
||||
"id": "agent-skills",
|
||||
"title": "技能引擎 Skills",
|
||||
"summary": "6 大 Skill(微信/抖音/小红书/闲鱼/语音/搜索)· SkillExecutor 调度 · 复合命令。",
|
||||
"category": "agent",
|
||||
"section": "Agent 集群",
|
||||
"folder": "agent/skills",
|
||||
"file": "README.md",
|
||||
"keywords": ["skill", "微信", "抖音", "技能", "executor"],
|
||||
},
|
||||
{
|
||||
"id": "agent-hawk",
|
||||
"title": "Hawk 网络恢复模块",
|
||||
"summary": "Agent 与 Hawk 边界协同:断网自动恢复、WiFi 开关、合法操作边界。",
|
||||
"category": "agent",
|
||||
"section": "Agent 集群",
|
||||
"folder": "agent/docs",
|
||||
"file": "AGENT_HAWK.md",
|
||||
"keywords": ["hawk", "网络", "wifi", "恢复", "断网"],
|
||||
},
|
||||
{
|
||||
"id": "agent-hook",
|
||||
"title": "Hook 脚本系统",
|
||||
"summary": "微信 Hook 初版脚本(Frida):RPC 骨架、消息监听、脚本部署流程。",
|
||||
"category": "agent",
|
||||
"section": "Agent 集群",
|
||||
"folder": "agent/hook",
|
||||
"file": "README.md",
|
||||
"keywords": ["hook", "frida", "rpc", "消息监听"],
|
||||
},
|
||||
]
|
||||
_WORKBENCH_DOCS = [
|
||||
{
|
||||
"id": "sdk-manual",
|
||||
"title": "SDK操作手册",
|
||||
"summary": "工作手机 SDK 的整体使用说明、核心能力和调用入口。",
|
||||
"category": "manual",
|
||||
"section": "说明文档",
|
||||
"folder": "9、手册",
|
||||
"file": "SDK操作手册.md",
|
||||
"keywords": ["SDK", "手册", "调用", "接入"],
|
||||
},
|
||||
{
|
||||
"id": "usage-solution",
|
||||
"title": "使用与落地方案",
|
||||
"summary": "从部署到业务落地的整体使用路径与建议方案。",
|
||||
"category": "manual",
|
||||
"section": "说明文档",
|
||||
"folder": "9、手册",
|
||||
"file": "使用与落地方案.md",
|
||||
"keywords": ["方案", "落地", "部署", "使用"],
|
||||
},
|
||||
{
|
||||
"id": "wechat-e2e",
|
||||
"title": "微信消息E2E验证指南",
|
||||
"summary": "微信消息链路的端到端验证步骤和排查指引。",
|
||||
"category": "manual",
|
||||
"section": "说明文档",
|
||||
"folder": "9、手册",
|
||||
"file": "微信消息E2E验证指南.md",
|
||||
"keywords": ["微信", "E2E", "验证", "排查"],
|
||||
},
|
||||
{
|
||||
"id": "agent-install",
|
||||
"title": "设备端Agent安装与公司设备说明",
|
||||
"summary": "设备端 Agent 的安装方式、公司设备规范和运行说明。",
|
||||
"category": "manual",
|
||||
"section": "说明文档",
|
||||
"folder": "9、手册",
|
||||
"file": "设备端Agent安装与公司设备说明.md",
|
||||
"keywords": ["Agent", "安装", "设备", "说明"],
|
||||
},
|
||||
{
|
||||
"id": "install-checklist",
|
||||
"title": "安装前配置检查规范",
|
||||
"summary": "安装前需要确认的配置、环境与风险检查项。",
|
||||
"category": "manual",
|
||||
"section": "说明文档",
|
||||
"folder": "9、手册",
|
||||
"file": "安装前配置检查规范.md",
|
||||
"keywords": ["安装", "检查", "规范", "配置"],
|
||||
},
|
||||
{
|
||||
"id": "system-architecture",
|
||||
"title": "系统架构",
|
||||
"summary": "工作手机 SDK 的整体架构、模块分层与链路设计。",
|
||||
"category": "architecture",
|
||||
"section": "架构与经验",
|
||||
"folder": "2、架构",
|
||||
"file": "系统架构.md",
|
||||
"keywords": ["架构", "模块", "分层", "链路"],
|
||||
},
|
||||
{
|
||||
"id": "hook-architecture",
|
||||
"title": "Hook通道与多设备多服务器架构",
|
||||
"summary": "Hook 通道、Frida 集成、多设备与多服务器的设计说明。",
|
||||
"category": "architecture",
|
||||
"section": "架构与经验",
|
||||
"folder": "2、架构",
|
||||
"file": "Hook通道与多设备多服务器架构.md",
|
||||
"keywords": ["Hook", "Frida", "多设备", "多服务器"],
|
||||
},
|
||||
{
|
||||
"id": "api-spec",
|
||||
"title": "接口规范",
|
||||
"summary": "统一接口的字段约定、协议与调用方式。",
|
||||
"category": "api",
|
||||
"section": "接口与协议",
|
||||
"folder": "5、接口",
|
||||
"file": "接口规范.md",
|
||||
"keywords": ["接口", "协议", "字段", "规范"],
|
||||
},
|
||||
{
|
||||
"id": "cunkebao-spec",
|
||||
"title": "存客宝对接规范",
|
||||
"summary": "存客宝与工作手机 SDK 的对接方式和聚合规则。",
|
||||
"category": "api",
|
||||
"section": "接口与协议",
|
||||
"folder": "5、接口",
|
||||
"file": "存客宝对接规范.md",
|
||||
"keywords": ["存客宝", "对接", "SDK", "聚合"],
|
||||
},
|
||||
{
|
||||
"id": "progress-overview",
|
||||
"title": "开发进度总表",
|
||||
"summary": "当前阶段、完成率、里程碑与下一步开发重点。",
|
||||
"category": "project",
|
||||
"section": "项目管理",
|
||||
"folder": "10、项目管理",
|
||||
"file": "开发进度总表.md",
|
||||
"keywords": ["进度", "里程碑", "阶段", "总表"],
|
||||
},
|
||||
]
|
||||
def _scan_arch_assets():
|
||||
import urllib.parse
|
||||
arch_dir = Path(_DOCS_ROOT) / "2、架构"
|
||||
known = [
|
||||
("arch-sdk", "工作手机SDK架构图", "系统全景四层架构,适合做整体汇报入口。"),
|
||||
("arch-flow", "设备与服务器交互流程(微信控制)", "设备、服务器与微信控制的交互过程。"),
|
||||
("arch-device", "工作手机设备端运转逻辑", "设备端从启动到心跳到 Skill 执行的完整运转。"),
|
||||
("arch-agent-total", "Agent与服务器工作流总图", "业务入口、服务器 Agent、设备 Agent 和消息闭环。"),
|
||||
("arch-hook", "Hook与服务器交互流程图", "Hook 通道的完整交互链路与功能细节。"),
|
||||
("arch-ban", "防封模块架构图", "防封中心、设备侧、行为层与服务端策略。"),
|
||||
("arch-ban-detail", "防封服务端核心能力拆细图", "防封服务端 7 大模块逐一拆细。"),
|
||||
("mode-hook", "本地控制模式一:Hook/Frida", "Frida 深度控制微信的模式示意。"),
|
||||
("mode-adb", "本地控制模式二:ADB静默控制", "无需 Root 的 ADB 静默控制模式。"),
|
||||
("mode-agent", "本地控制模式三:Agent WebSocket远控", "远程统一控制多设备。"),
|
||||
("mode-ai", "本地控制模式四:AI Agent自然语言", "自然语言编排复杂任务。"),
|
||||
("mode-scrcpy", "本地控制模式五:scrcpy可视化控制", "投屏与人工接管。"),
|
||||
]
|
||||
fname_map = {
|
||||
"arch-sdk": "工作手机SDK架构图.png",
|
||||
"arch-flow": "设备与服务器交互流程_微信控制.png",
|
||||
"arch-device": "工作手机设备端运转逻辑.png",
|
||||
"arch-agent-total": "卡若AI手机_Agent与服务器工作流总图.png",
|
||||
"arch-hook": "Hook与服务器交互流程图.png",
|
||||
"arch-ban": "防封模块架构图.png",
|
||||
"arch-ban-detail": "防封服务端核心能力拆细图.png",
|
||||
"mode-hook": "本地控制模式_1_Hook_Frida模式.png",
|
||||
"mode-adb": "本地控制模式_2_ADB静默控制模式.png",
|
||||
"mode-agent": "本地控制模式_3_Agent_WebSocket远控模式.png",
|
||||
"mode-ai": "本地控制模式_4_AI_Agent自然语言模式.png",
|
||||
"mode-scrcpy": "本地控制模式_5_scrcpy可视化控制模式.png",
|
||||
}
|
||||
result = []
|
||||
for aid, title, summary in known:
|
||||
fname = fname_map.get(aid, "")
|
||||
fpath = arch_dir / fname
|
||||
if fpath.exists():
|
||||
encoded = urllib.parse.quote(f"2、架构/{fname}")
|
||||
result.append({
|
||||
"id": aid,
|
||||
"title": title,
|
||||
"summary": summary,
|
||||
"category": "architecture",
|
||||
"url": f"/workbench-docs/{encoded}",
|
||||
})
|
||||
return result
|
||||
|
||||
_WORKBENCH_ARCH_ASSETS = _scan_arch_assets()
|
||||
|
||||
|
||||
def _doc_path(meta: dict) -> Path:
|
||||
if meta.get("category") == "agent":
|
||||
return Path(_AGENT_ROOT) / meta["file"] if meta["folder"] == "agent" else Path(_AGENT_ROOT) / meta["folder"].replace("agent/", "", 1) / meta["file"]
|
||||
return Path(_DOCS_ROOT) / meta["folder"] / meta["file"]
|
||||
|
||||
|
||||
def _doc_meta_with_state(meta: dict) -> dict:
|
||||
path = _doc_path(meta)
|
||||
exists = path.exists()
|
||||
return {
|
||||
**meta,
|
||||
"exists": exists,
|
||||
"path": str(path),
|
||||
"updated_at": path.stat().st_mtime if exists else None,
|
||||
"raw_url": f"/workbench-docs/{meta['folder']}/{meta['file']}" if exists else None,
|
||||
}
|
||||
|
||||
|
||||
def _render_markdown_html(content: str) -> str:
|
||||
return markdown.markdown(
|
||||
content,
|
||||
extensions=["extra", "tables", "fenced_code", "toc", "sane_lists"],
|
||||
output_format="html5",
|
||||
)
|
||||
|
||||
|
||||
async def _build_workbench_overview() -> dict:
|
||||
from services.adb_device import adb_manager
|
||||
from services.ai_command import ai_engine
|
||||
from services.connection_priority import connection_priority
|
||||
|
||||
adb_devices = adb_manager.scan_devices()
|
||||
|
||||
db_devices = []
|
||||
try:
|
||||
db_devices = await device_manager.get_all_devices(limit=200)
|
||||
except Exception:
|
||||
db_devices = []
|
||||
|
||||
online_devices = {d["device_id"]: d for d in ws_hub.get_online_devices()}
|
||||
merged = {}
|
||||
|
||||
for device in db_devices:
|
||||
device_id = device["device_id"]
|
||||
merged[device_id] = {**device}
|
||||
|
||||
for device_id, info in online_devices.items():
|
||||
base = merged.get(device_id, {})
|
||||
merged[device_id] = {**base, **info, "status": "online"}
|
||||
|
||||
for serial in adb_devices:
|
||||
if serial not in merged:
|
||||
adb_dev = adb_manager.get_device(serial)
|
||||
if adb_dev:
|
||||
info = adb_dev.get_info()
|
||||
merged[serial] = {
|
||||
"device_id": serial,
|
||||
"model": info.get("model", "Unknown"),
|
||||
"brand": info.get("brand", "Unknown"),
|
||||
"android_version": info.get("android_version", "Unknown"),
|
||||
"status": "adb",
|
||||
"connection_type": "adb",
|
||||
"display": info.get("display", {}),
|
||||
}
|
||||
|
||||
devices = list(merged.values())
|
||||
online_count = len([d for d in devices if d.get("status") == "online"])
|
||||
adb_count = len([d for d in devices if d.get("status") == "adb"])
|
||||
offline_count = len([d for d in devices if d.get("status") == "offline"])
|
||||
|
||||
try:
|
||||
ai_status = await ai_engine.get_status()
|
||||
except Exception:
|
||||
ai_status = {"available": False, "backend": "", "model": ""}
|
||||
|
||||
docs = [_doc_meta_with_state(meta) for meta in _WORKBENCH_DOCS + _AGENT_DOCS]
|
||||
|
||||
# 为每台设备附加连接模式信息
|
||||
device_modes = {}
|
||||
for dev in devices:
|
||||
did = dev.get("device_id", "")
|
||||
if did:
|
||||
try:
|
||||
modes = connection_priority.evaluate(did)
|
||||
best = next((m for m in modes if m.available), None)
|
||||
dev["connection_modes"] = [m.to_dict() for m in modes]
|
||||
dev["best_mode"] = best.to_dict() if best else None
|
||||
device_modes[did] = dev
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
"sdk_online": True,
|
||||
"sdk_version": "3.0.0",
|
||||
"ws_online": len(ws_hub.connections),
|
||||
"adb_count": len(adb_devices),
|
||||
"device_total": len(devices),
|
||||
"device_online": online_count,
|
||||
"device_offline": offline_count + adb_count,
|
||||
"devices": devices,
|
||||
"summary": {
|
||||
"sdk_online": True,
|
||||
"sdk_version": "3.0.0",
|
||||
"ws_online": len(ws_hub.connections),
|
||||
"adb_count": len(adb_devices),
|
||||
"device_total": len(devices),
|
||||
"device_online": online_count,
|
||||
"device_offline": offline_count,
|
||||
"manual_docs": len([d for d in docs if d["category"] == "manual" and d["exists"]]),
|
||||
"architecture_assets": len(_WORKBENCH_ARCH_ASSETS),
|
||||
},
|
||||
"agent": ai_status,
|
||||
"docs": docs,
|
||||
"architecture_assets": _WORKBENCH_ARCH_ASSETS,
|
||||
"sections": [
|
||||
{"id": "overview", "title": "首页总览"},
|
||||
{"id": "docs", "title": "说明文档"},
|
||||
{"id": "devices", "title": "设备控制"},
|
||||
{"id": "hook", "title": "Hook 控制"},
|
||||
{"id": "agent", "title": "AI Agent"},
|
||||
{"id": "connection", "title": "连接与协议"},
|
||||
{"id": "architecture", "title": "架构与经验"},
|
||||
{"id": "project", "title": "项目管理"},
|
||||
],
|
||||
"control_modes": [
|
||||
{"id": "hook", "name": "Hook / Frida", "desc": "读写内部能力强,适合微信深度控制", "priority": 1},
|
||||
{"id": "agent", "name": "Agent WebSocket", "desc": "实时双向,设备端App常驻", "priority": 2},
|
||||
{"id": "adb", "name": "ADB 静默控制", "desc": "无需 Root,部署简单,适合稳定兜底", "priority": 3},
|
||||
{"id": "ai", "name": "AI Agent", "desc": "自然语言编排复杂任务", "priority": 4},
|
||||
{"id": "scrcpy", "name": "scrcpy 可视化", "desc": "投屏与人工接管", "priority": 5},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
@@ -174,37 +512,107 @@ async def ready():
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""根路由 → 控制面板"""
|
||||
static_path = os.path.join(os.path.dirname(__file__), "static", "index.html")
|
||||
"""根路由 → 聚合总控台"""
|
||||
static_path = os.path.join(_STATIC_DIR, "hub.html")
|
||||
if os.path.exists(static_path):
|
||||
return FileResponse(static_path)
|
||||
return {
|
||||
"name": "工作手机SDK v3.0",
|
||||
"description": "存客宝的AI手机控制引擎",
|
||||
"description": "统一聚合总控台",
|
||||
"docs": "/docs",
|
||||
"health": "/health",
|
||||
"ready": "/ready",
|
||||
"voice_control": "/voice"
|
||||
"voice_control": "/voice",
|
||||
"control_center": "/control",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/hub")
|
||||
async def hub_page():
|
||||
"""统一聚合总控台"""
|
||||
static_path = os.path.join(_STATIC_DIR, "hub.html")
|
||||
if os.path.exists(static_path):
|
||||
return FileResponse(static_path)
|
||||
raise HTTPException(status_code=404, detail="hub.html 不存在")
|
||||
|
||||
|
||||
@app.get("/control")
|
||||
async def control_page():
|
||||
"""兼容旧入口,统一回到工作台"""
|
||||
static_path = os.path.join(_STATIC_DIR, "hub.html")
|
||||
if os.path.exists(static_path):
|
||||
return FileResponse(static_path)
|
||||
raise HTTPException(status_code=404, detail="hub.html 不存在")
|
||||
|
||||
|
||||
@app.get("/voice")
|
||||
async def voice_control_page():
|
||||
"""语音控制页面"""
|
||||
static_path = os.path.join(os.path.dirname(__file__), "static", "voice_control.html")
|
||||
static_path = os.path.join(_STATIC_DIR, "voice_control.html")
|
||||
return FileResponse(static_path)
|
||||
|
||||
|
||||
@app.get("/pages/{page_key}")
|
||||
async def docs_page(page_key: str):
|
||||
"""聚合页内嵌的项目文档页面"""
|
||||
doc_path = _DOC_PAGE_MAP.get(page_key)
|
||||
if not doc_path or not os.path.exists(doc_path):
|
||||
raise HTTPException(status_code=404, detail="页面不存在")
|
||||
return FileResponse(doc_path)
|
||||
|
||||
|
||||
@app.get("/api/v3/workbench/overview")
|
||||
async def workbench_overview():
|
||||
"""工作台统一概览 DTO,供工作台前端与存客宝页面共用。"""
|
||||
return {"code": 200, "data": await _build_workbench_overview()}
|
||||
|
||||
|
||||
@app.get("/api/v3/workbench/docs")
|
||||
async def workbench_docs():
|
||||
"""工作台文档清单。"""
|
||||
return {
|
||||
"code": 200,
|
||||
"data": {
|
||||
"docs": [_doc_meta_with_state(meta) for meta in _WORKBENCH_DOCS + _AGENT_DOCS],
|
||||
"architecture_assets": _WORKBENCH_ARCH_ASSETS,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/v3/workbench/docs/{doc_id}")
|
||||
async def workbench_doc_detail(doc_id: str):
|
||||
"""读取工作台文档正文并返回渲染后的 HTML。"""
|
||||
meta = next((item for item in _WORKBENCH_DOCS + _AGENT_DOCS if item["id"] == doc_id), None)
|
||||
if not meta:
|
||||
raise HTTPException(status_code=404, detail="文档不存在")
|
||||
|
||||
path = _doc_path(meta)
|
||||
if not path.exists():
|
||||
raise HTTPException(status_code=404, detail="文档文件不存在")
|
||||
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
return {
|
||||
"code": 200,
|
||||
"data": {
|
||||
**_doc_meta_with_state(meta),
|
||||
"content_markdown": raw,
|
||||
"content_html": _render_markdown_html(raw),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# 挂载静态文件目录
|
||||
static_dir = os.path.join(os.path.dirname(__file__), "static")
|
||||
if os.path.exists(static_dir):
|
||||
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
||||
if os.path.exists(_STATIC_DIR):
|
||||
app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
|
||||
|
||||
if os.path.exists(_DOCS_ROOT):
|
||||
app.mount("/workbench-docs", StaticFiles(directory=_DOCS_ROOT), name="workbench-docs")
|
||||
|
||||
|
||||
# ========== Agent 分发接口 ==========
|
||||
|
||||
# Agent 代码根目录
|
||||
_AGENT_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "agent"))
|
||||
_AGENT_DIR = os.path.normpath(os.path.join(_APP_DIR, "..", "agent"))
|
||||
_AGENT_DIST = os.path.join(_AGENT_DIR, "dist", "agent.tar.gz")
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from pydantic import BaseModel, Field
|
||||
from services.ws_hub import ws_hub
|
||||
from services.adb_device import adb_manager
|
||||
from services.hook_module_service import hook_module_service
|
||||
from services.connection_priority import connection_priority
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -199,3 +200,40 @@ async def simulate_hook_event(req: SimHookEventRequest) -> Dict[str, Any]:
|
||||
}
|
||||
)
|
||||
return {"code": 200, "data": event}
|
||||
|
||||
|
||||
# ========== 连接优先级 ==========
|
||||
|
||||
@router.get("/connection/modes/{device_id}")
|
||||
async def get_device_connection_modes(device_id: str) -> Dict[str, Any]:
|
||||
"""获取设备所有控制模式的可用性与优先级。"""
|
||||
modes = connection_priority.evaluate(device_id)
|
||||
best = next((m for m in modes if m.available), None)
|
||||
return {
|
||||
"code": 200,
|
||||
"data": {
|
||||
"device_id": device_id,
|
||||
"best_mode": best.to_dict() if best else None,
|
||||
"modes": [m.to_dict() for m in modes],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.get("/connection/modes")
|
||||
async def get_all_connection_modes() -> Dict[str, Any]:
|
||||
"""获取所有已知设备的控制模式概览。"""
|
||||
online_ids = list(ws_hub.connections.keys())
|
||||
adb_serials = adb_manager.scan_devices()
|
||||
all_ids = list(set(online_ids + adb_serials))
|
||||
|
||||
result = []
|
||||
for did in all_ids:
|
||||
modes = connection_priority.evaluate(did)
|
||||
best = next((m for m in modes if m.available), None)
|
||||
result.append({
|
||||
"device_id": did,
|
||||
"best_mode": best.to_dict() if best else None,
|
||||
"available_count": sum(1 for m in modes if m.available),
|
||||
"modes": [m.to_dict() for m in modes],
|
||||
})
|
||||
return {"code": 200, "data": result}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from enum import Enum
|
||||
import asyncio
|
||||
import logging
|
||||
@@ -343,27 +343,21 @@ class ChannelRouter:
|
||||
# ========== 辅助函数 ==========
|
||||
|
||||
def _check_device_online(device_id: str):
|
||||
"""检查设备是否在线(WebSocket或ADB)"""
|
||||
# 先检查WebSocket连接
|
||||
if ws_hub.is_online(device_id):
|
||||
return "websocket"
|
||||
|
||||
# 再检查ADB设备
|
||||
adb_device = adb_manager.get_device(device_id)
|
||||
if adb_device and adb_device.is_online():
|
||||
return "adb"
|
||||
|
||||
"""检查设备是否在线(按优先级:Hook > WebSocket > ADB)"""
|
||||
from services.connection_priority import connection_priority
|
||||
best = connection_priority.get_best_mode(device_id)
|
||||
if best:
|
||||
return best.to_dict()["id"] # hook / agent / adb
|
||||
raise HTTPException(status_code=503, detail=f"设备不在线: {device_id}")
|
||||
|
||||
|
||||
def _get_device_mode(device_id: str) -> str:
|
||||
"""获取设备连接模式"""
|
||||
if ws_hub.is_online(device_id):
|
||||
"""获取设备最优连接模式"""
|
||||
from services.connection_priority import connection_priority
|
||||
channel = connection_priority.choose_execution_channel(device_id)
|
||||
if channel == "agent":
|
||||
return "websocket"
|
||||
adb_device = adb_manager.get_device(device_id)
|
||||
if adb_device and adb_device.is_online():
|
||||
return "adb"
|
||||
return "offline"
|
||||
return channel
|
||||
|
||||
|
||||
def _parse_messages_from_xml(xml: str, limit: int) -> list:
|
||||
@@ -521,6 +515,39 @@ async def _execute_via_adb(device, platform: str, action: str, params: dict) ->
|
||||
return {"success": False, "error": str(e), "mode": "adb"}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 〇、Hawk Hook 统一执行入口(丝滑控制)
|
||||
# =============================================================================
|
||||
|
||||
class HookExecuteRequest(BaseModel):
|
||||
"""Hawk Hook 格式 — 单端点执行任意操作,任何应用可直连"""
|
||||
device_id: str
|
||||
platform: Platform = Platform.WECHAT
|
||||
action: str # send_message | get_contacts | post_moments | ... 共 96 个
|
||||
params: dict = Field(default_factory=dict, description="action 所需参数")
|
||||
|
||||
|
||||
@router.post("/hook/execute", response_model=dict, tags=["Hook模块管理"])
|
||||
async def hook_execute(req: HookExecuteRequest):
|
||||
"""
|
||||
Hawk Hook 统一执行 — 单接口控制整台手机
|
||||
|
||||
任意应用只需调此端点,传 action + params 即可执行 96 种操作。
|
||||
支持 WebSocket(Agent) 与 ADB 双通道,执行更丝滑。
|
||||
|
||||
示例:
|
||||
```json
|
||||
{"device_id":"xgfe65eimrrofyws","platform":"wechat","action":"send_message","params":{"to_id":"阿猫","content":"你好"}}
|
||||
```
|
||||
"""
|
||||
_check_device_online(req.device_id)
|
||||
result = await _execute_skill(
|
||||
req.device_id, req.platform.value, req.action, req.params or {}
|
||||
)
|
||||
payload = result.get("data") if result.get("data") is not None else result
|
||||
return {"code": 200, "data": payload, "channel_used": "sdk_control"}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 一、消息管理接口
|
||||
# =============================================================================
|
||||
|
||||
209
sdk/app/services/connection_priority.py
Normal file
@@ -0,0 +1,209 @@
|
||||
"""
|
||||
连接优先级管理器
|
||||
5种控制模式的可用性检测、优先级路由和智能降级。
|
||||
|
||||
优先级(高→低):
|
||||
1. Hook/Frida — 深度控制,读写微信内部数据
|
||||
2. Agent WebSocket — 实时双向,设备端App常驻
|
||||
3. ADB 静默控制 — 无需App,ADB直连兜底
|
||||
4. AI Agent — 自然语言编排,依赖以上通道
|
||||
5. scrcpy 可视化 — 投屏+人工接管
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
from dataclasses import dataclass, field
|
||||
from enum import IntEnum
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ControlMode(IntEnum):
|
||||
HOOK = 1
|
||||
AGENT_WS = 2
|
||||
ADB = 3
|
||||
AI_AGENT = 4
|
||||
SCRCPY = 5
|
||||
|
||||
|
||||
MODE_META = {
|
||||
ControlMode.HOOK: {
|
||||
"id": "hook",
|
||||
"name": "Hook / Frida",
|
||||
"desc": "读写内部能力强,适合微信深度控制",
|
||||
"requires": "Root + Frida Server",
|
||||
},
|
||||
ControlMode.AGENT_WS: {
|
||||
"id": "agent",
|
||||
"name": "Agent WebSocket",
|
||||
"desc": "实时双向通信,设备端App常驻",
|
||||
"requires": "设备端安装工作App",
|
||||
},
|
||||
ControlMode.ADB: {
|
||||
"id": "adb",
|
||||
"name": "ADB 静默控制",
|
||||
"desc": "无需Root,部署简单,稳定兜底",
|
||||
"requires": "USB调试已开启",
|
||||
},
|
||||
ControlMode.AI_AGENT: {
|
||||
"id": "ai",
|
||||
"name": "AI Agent",
|
||||
"desc": "自然语言编排复杂任务",
|
||||
"requires": "需要底层通道(Hook/WS/ADB)之一可用",
|
||||
},
|
||||
ControlMode.SCRCPY: {
|
||||
"id": "scrcpy",
|
||||
"name": "scrcpy 可视化",
|
||||
"desc": "投屏与人工接管",
|
||||
"requires": "ADB连接 + scrcpy已安装",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModeStatus:
|
||||
mode: ControlMode
|
||||
available: bool
|
||||
priority: int
|
||||
detail: str = ""
|
||||
meta: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
m = MODE_META.get(self.mode, {})
|
||||
return {
|
||||
"id": m.get("id", str(self.mode)),
|
||||
"name": m.get("name", ""),
|
||||
"desc": m.get("desc", ""),
|
||||
"requires": m.get("requires", ""),
|
||||
"priority": self.priority,
|
||||
"available": self.available,
|
||||
"detail": self.detail,
|
||||
**self.meta,
|
||||
}
|
||||
|
||||
|
||||
class ConnectionPriorityManager:
|
||||
"""对单台设备评估所有控制模式的可用性并按优先级排序。"""
|
||||
|
||||
def __init__(self):
|
||||
self._scrcpy_available: Optional[bool] = None
|
||||
|
||||
def evaluate(self, device_id: str) -> List[ModeStatus]:
|
||||
from services.ws_hub import ws_hub
|
||||
from services.adb_device import adb_manager
|
||||
from services.hook_module_service import hook_module_service
|
||||
|
||||
results: List[ModeStatus] = []
|
||||
|
||||
# 1. Hook / Frida
|
||||
hook_ok = self._check_hook(device_id, hook_module_service)
|
||||
results.append(ModeStatus(
|
||||
mode=ControlMode.HOOK,
|
||||
available=hook_ok,
|
||||
priority=1,
|
||||
detail="Frida Server 运行中" if hook_ok else "未检测到 Hook 服务",
|
||||
))
|
||||
|
||||
# 2. Agent WebSocket
|
||||
ws_ok = ws_hub.is_online(device_id)
|
||||
ws_detail = "WebSocket 已连接"
|
||||
if not ws_ok:
|
||||
adb_serials = adb_manager.scan_devices()
|
||||
for serial in adb_serials:
|
||||
adb_dev = adb_manager.get_device(serial)
|
||||
if adb_dev:
|
||||
android_id = adb_dev._shell("settings get secure android_id").strip()
|
||||
import hashlib
|
||||
md5_id = hashlib.md5(android_id.encode()).hexdigest()
|
||||
if md5_id == device_id:
|
||||
ws_detail = f"设备在线(ADB:{serial})但Agent App未连接WebSocket"
|
||||
break
|
||||
else:
|
||||
ws_detail = "设备Agent未连接"
|
||||
results.append(ModeStatus(
|
||||
mode=ControlMode.AGENT_WS,
|
||||
available=ws_ok,
|
||||
priority=2,
|
||||
detail=ws_detail,
|
||||
))
|
||||
|
||||
# 3. ADB
|
||||
adb_ok = False
|
||||
adb_serial = ""
|
||||
adb_dev_obj = adb_manager.get_device(device_id)
|
||||
if adb_dev_obj and adb_dev_obj.is_online():
|
||||
adb_ok = True
|
||||
adb_serial = device_id
|
||||
else:
|
||||
for serial in adb_manager.scan_devices():
|
||||
dev = adb_manager.get_device(serial)
|
||||
if dev and dev.is_online():
|
||||
adb_ok = True
|
||||
adb_serial = serial
|
||||
break
|
||||
results.append(ModeStatus(
|
||||
mode=ControlMode.ADB,
|
||||
available=adb_ok,
|
||||
priority=3,
|
||||
detail=f"ADB 已连接 ({adb_serial})" if adb_ok else "ADB 未连接",
|
||||
meta={"adb_serial": adb_serial} if adb_ok else {},
|
||||
))
|
||||
|
||||
# 4. AI Agent(依赖底层通道)
|
||||
has_base = ws_ok or adb_ok or hook_ok
|
||||
results.append(ModeStatus(
|
||||
mode=ControlMode.AI_AGENT,
|
||||
available=has_base,
|
||||
priority=4,
|
||||
detail="底层通道就绪" if has_base else "需要至少一个底层通道",
|
||||
))
|
||||
|
||||
# 5. scrcpy
|
||||
scrcpy_ok = adb_ok and self._check_scrcpy()
|
||||
results.append(ModeStatus(
|
||||
mode=ControlMode.SCRCPY,
|
||||
available=scrcpy_ok,
|
||||
priority=5,
|
||||
detail="scrcpy 可用" if scrcpy_ok else ("ADB未连接" if not adb_ok else "scrcpy 未安装"),
|
||||
))
|
||||
|
||||
results.sort(key=lambda m: m.priority)
|
||||
return results
|
||||
|
||||
def get_best_mode(self, device_id: str) -> Optional[ModeStatus]:
|
||||
modes = self.evaluate(device_id)
|
||||
for m in modes:
|
||||
if m.available:
|
||||
return m
|
||||
return None
|
||||
|
||||
def choose_execution_channel(self, device_id: str) -> str:
|
||||
"""返回最优执行通道标识: hook / agent / adb / ai / offline"""
|
||||
best = self.get_best_mode(device_id)
|
||||
if not best:
|
||||
return "offline"
|
||||
return MODE_META[best.mode]["id"]
|
||||
|
||||
@staticmethod
|
||||
def _check_hook(device_id: str, hook_service) -> bool:
|
||||
try:
|
||||
modules = hook_service.get_device_modules(device_id) if hasattr(hook_service, "get_device_modules") else []
|
||||
return bool(modules)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _check_scrcpy(self) -> bool:
|
||||
if self._scrcpy_available is not None:
|
||||
return self._scrcpy_available
|
||||
try:
|
||||
r = subprocess.run(["which", "scrcpy"], capture_output=True, text=True, timeout=3)
|
||||
self._scrcpy_available = r.returncode == 0
|
||||
except Exception:
|
||||
self._scrcpy_available = False
|
||||
return self._scrcpy_available
|
||||
|
||||
|
||||
connection_priority = ConnectionPriorityManager()
|
||||
@@ -150,10 +150,13 @@ class WeChatADBEngine:
|
||||
|
||||
def _input(self, text: str, clear: bool = False):
|
||||
if clear:
|
||||
self._sh("input keyevent KEYCODE_CTRL_A")
|
||||
time.sleep(0.1)
|
||||
self._sh("input keyevent KEYCODE_DEL")
|
||||
time.sleep(0.1)
|
||||
try:
|
||||
self._sh("input keyevent KEYCODE_CTRL_A", timeout=2)
|
||||
time.sleep(0.1)
|
||||
self._sh("input keyevent KEYCODE_DEL", timeout=2)
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
logger.info(f"清空输入框失败,继续输入: {e}")
|
||||
has_cjk = any(ord(c) > 127 for c in text)
|
||||
if has_cjk:
|
||||
self._input_cjk(text)
|
||||
@@ -165,9 +168,18 @@ class WeChatADBEngine:
|
||||
|
||||
def _input_cjk(self, text: str):
|
||||
escaped = text.replace("'", "'\\''").replace('"', '\\"')
|
||||
result = self._sh(f"am broadcast -a ADB_INPUT_TEXT --es msg '{escaped}' 2>&1")
|
||||
if "Broadcast completed" not in result:
|
||||
self._sh(f"input text '{escaped}'")
|
||||
try:
|
||||
result = self._sh(
|
||||
f"am broadcast -a ADB_INPUT_TEXT --es msg '{escaped}' 2>&1",
|
||||
timeout=2,
|
||||
)
|
||||
if "Broadcast completed" in result:
|
||||
return
|
||||
except Exception as e:
|
||||
logger.info(f"ADB_INPUT_TEXT 不可用,回退系统输入: {e}")
|
||||
|
||||
# 部分 MIUI/HyperOS 设备支持直接 input text 中文,这里作为兜底。
|
||||
self._sh(f"input text '{escaped}'", timeout=5)
|
||||
|
||||
def _ensure_wechat(self) -> bool:
|
||||
cur = self._sh("dumpsys window | grep -E 'mCurrentFocus'")
|
||||
|
||||
1327
sdk/app/static/hub.html
Normal file
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": "8.0.56",
|
||||
"screen": "1080x2400",
|
||||
"updated": "2026-03-14 17:57:15",
|
||||
"updated": "2026-03-16 03:26:29",
|
||||
"pages": {
|
||||
"me": {
|
||||
"recorded_at": "2026-03-14 16:32",
|
||||
@@ -323,9 +323,9 @@
|
||||
}
|
||||
],
|
||||
"target_page": "account_security",
|
||||
"success_count": 1,
|
||||
"success_count": 3,
|
||||
"fail_count": 0,
|
||||
"last_success": "2026-03-14 17:57:15"
|
||||
"last_success": "2026-03-16 03:26:29"
|
||||
},
|
||||
"to_safety_center": {
|
||||
"name": "to_safety_center",
|
||||
|
||||
7
sdk/data/operation_logs/ops_20260316.jsonl
Normal file
@@ -0,0 +1,7 @@
|
||||
{"action": "get_profile", "params": {}, "device": "dc9c23e00510", "started_at": "2026-03-16 03:19:20", "steps": [{"type": "tap_tab", "detail": "我", "coords": [945, 2148], "success": true, "ts": 7.69}], "success": true, "duration_ms": 12246, "ended_at": "2026-03-16 03:19:33", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773602373089, 'profile': {}}"}
|
||||
{"action": "get_contacts", "params": {"limit": 5}, "device": "dc9c23e00510", "started_at": "2026-03-16 03:19:58", "steps": [{"type": "tap_tab", "detail": "通讯录", "coords": [405, 2148], "success": true, "ts": 7.16}], "success": true, "duration_ms": 8164, "ended_at": "2026-03-16 03:20:07", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773602407116, 'contacts': [], 'count': 0}"}
|
||||
{"action": "check_account_status", "params": {}, "device": "dc9c23e00510", "started_at": "2026-03-16 03:19:33", "steps": [{"type": "tap_tab", "detail": "我", "coords": [945, 2148], "success": true, "ts": 8.07}, {"type": "tap_tab", "detail": "我", "coords": [945, 2148], "success": true, "ts": 9.58}, {"type": "scroll_up", "detail": "", "coords": null, "success": true, "ts": 13.0}, {"type": "tap_text", "detail": "设置", "coords": [197, 1676], "success": true, "ts": 17.3}, {"type": "scroll_to_top", "detail": "", "coords": null, "success": true, "ts": 26.49}, {"type": "tap_text", "detail": "账号与安全", "coords": [250, 300], "success": true, "ts": 30.81}], "success": true, "duration_ms": 38876, "ended_at": "2026-03-16 03:20:12", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773602412573, 'status': {'account_active': True}}"}
|
||||
{"action": "send_message", "params": {"to_id": "文件传输助手", "content": "新设备 dc9c23e00510 已接入,可直接控制。"}, "device": "dc9c23e00510", "started_at": "2026-03-16 03:25:30", "steps": [], "success": false, "duration_ms": 24899, "ended_at": "2026-03-16 03:25:55", "result_summary": "{'success': False, 'error': 'Command \\'[\\'adb\\', \\'-s\\', \\'dc9c23e00510\\', \\'shell\\', \"am broadcast -a ADB_INPUT_TEXT --es msg \\'文件传输助手\\' 2>&1\"]\\' timed out after 10 seconds', 'mode': 'adb'}", "error": "Command '['adb', '-s', 'dc9c23e00510', 'shell', \"am broadcast -a ADB_INPUT_TEXT --es msg '文件传输助手' 2>&1\"]' timed out after 10 seconds"}
|
||||
{"action": "check_account_status", "params": {}, "device": "dc9c23e00510", "started_at": "2026-03-16 03:25:56", "steps": [{"type": "tap_tab", "detail": "我", "coords": [945, 2148], "success": true, "ts": 7.69}, {"type": "tap_tab", "detail": "我", "coords": [945, 2148], "success": true, "ts": 9.2}, {"type": "scroll_up", "detail": "", "coords": null, "success": true, "ts": 13.09}, {"type": "tap_text", "detail": "设置", "coords": [197, 1676], "success": true, "ts": 18.37}, {"type": "scroll_to_top", "detail": "", "coords": null, "success": true, "ts": 28.15}, {"type": "tap_text", "detail": "账号与安全", "coords": [250, 300], "success": true, "ts": 32.43}], "success": true, "duration_ms": 40568, "ended_at": "2026-03-16 03:26:37", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773602797298, 'status': {'account_active': True}}"}
|
||||
{"action": "send_message", "params": {"to_id": "阿猫", "content": "新设备已接入,现在可以直接控制。"}, "device": "dc9c23e00510", "started_at": "2026-03-16 03:28:06", "steps": [], "success": false, "duration_ms": 19579, "ended_at": "2026-03-16 03:28:26", "result_summary": "{'success': False, 'error': \"Command '['adb', '-s', 'dc9c23e00510', 'shell', 'input keyevent KEYCODE_CTRL_A']' timed out after 10 seconds\", 'mode': 'adb'}", "error": "Command '['adb', '-s', 'dc9c23e00510', 'shell', 'input keyevent KEYCODE_CTRL_A']' timed out after 10 seconds"}
|
||||
{"action": "send_message", "params": {"to_id": "阿猫", "content": "新设备已接入,现在可以直接控制。"}, "device": "dc9c23e00510", "started_at": "2026-03-16 03:29:06", "steps": [], "success": true, "duration_ms": 30823, "ended_at": "2026-03-16 03:29:36", "result_summary": "{'success': True, 'mode': 'adb', 'timestamp': 1773602976885, 'message_id': 'adb_1773602976885'}"}
|
||||
@@ -15,6 +15,7 @@ pydantic-settings
|
||||
|
||||
# HTTP客户端
|
||||
httpx
|
||||
markdown
|
||||
|
||||
# 设备控制
|
||||
uiautomator2
|
||||
|
||||
@@ -5,6 +5,38 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-16 | 设备部署:Redmi 21121119SC Soul + Hook + Agent 安装
|
||||
|
||||
### 执行人: 阿机(设备部署)+ 阿服(环境)
|
||||
|
||||
### 设备信息
|
||||
- 型号: Redmi 21121119SC (selene)
|
||||
- Android: 11 | 架构: arm64-v8a | Root: Magisk
|
||||
- 序列号: dc9c23e00510
|
||||
- 连接方式: Type-C USB
|
||||
|
||||
### 完成内容
|
||||
1. **Soul APP 安装** — 从 APKPure 下载 XAPK (284MB),拆包后 `adb install-multiple` 安装成功
|
||||
- 包名: cn.soulapp.android
|
||||
- 启动 Activity: `.component.startup.main.MainActivity`
|
||||
2. **Frida Server 部署** — v17.8.1 arm64,已推送到 `/data/local/tmp/frida-server`
|
||||
- 非 root 下可枚举进程;root attach 需手机上 Magisk 授权(待操作)
|
||||
3. **uiautomator2 UI 控制验证** — 验证通过,可完全控制设备 + Soul APP
|
||||
4. **Agent 终端安装**(Termux 内 `~/workphone-agent/`)
|
||||
- Python 3.13.12 + websockets 16.0 + uiautomator2 3.5.0 + httpx 0.28.1 + Pillow 12.1.1
|
||||
- config.json 已配置(device_id=dc9c23e00510, server=ws://192.168.1.100:8899/ws/device)
|
||||
- Soul Skill 已包含在 agent 包中
|
||||
5. **package.sh 更新** — 新增 Soul 技能目录到打包脚本
|
||||
|
||||
### 待操作
|
||||
- 手机上 Magisk 授权 root → Frida 可 attach 到 Soul 进程进行深度 Hook
|
||||
- 启动 SDK 服务器后运行 `cd ~/workphone-agent && python3 agent.py` 连接
|
||||
- Soul APP 首次打开需登录账号
|
||||
|
||||
### 进度: 设备部署 100% | 整体项目 96%(不变)
|
||||
|
||||
---
|
||||
|
||||
## 2026-03-15 | 防封模块深度验证+13项BUG修复
|
||||
|
||||
### 执行人: 火炬(机擎全员)
|
||||
|
||||
BIN
开发文档/2、架构/Hook与服务器交互流程图.png
Normal file
|
After Width: | Height: | Size: 1023 KiB |
BIN
开发文档/2、架构/卡若AI手机_Agent与服务器工作流总图.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 933 KiB |
|
Before Width: | Height: | Size: 244 KiB |
BIN
开发文档/2、架构/本地控制模式_1_Hook_Frida模式.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
开发文档/2、架构/本地控制模式_2_ADB静默控制模式.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
开发文档/2、架构/本地控制模式_3_Agent_WebSocket远控模式.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
开发文档/2、架构/本地控制模式_4_AI_Agent自然语言模式.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
开发文档/2、架构/本地控制模式_5_scrcpy可视化控制模式.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |