# API 接口文档 `platform-tools` Web 后台提供 JSON API,绝大多数接口需登录后访问(Flask-Login session 认证);免登录例外见下方权限模型。 **Base URL**:`http://localhost:18050` **认证方式**:Cookie Session(先 POST `/login` 获取 session cookie,后续请求带上) **通用响应格式**: ```json {"ok": true, "data": "..."} {"ok": false, "error": "错误信息"} ``` --- **权限模型**(v2 起): - 所有接口需登录(Flask-Login session);**免登录例外**:`GET /api/health`(探活)、`GET /locate`(设备端定位页,只显示 serial 文本)、`/login` 与静态资源 - **多数查看类 GET**(状态/任务/分组/自定义动作/APK 列表等)仅需登录即可用;设备维护/看屏/元素抓取类 GET 需对应 `devices` 权限 - **写操作按权限位授权**(管理员拥有全部权限): | 权限位 | 中文 | 覆盖接口 | |--------|------|---------| | `tasks` | 任务管理 | 任务/自定义动作/分组的增删改、启停、立即执行 | | `devices` | 设备控制 | 停止设备、清除异常、定位、前台扫描、远程看屏/触控、元素抓取、设备池管理、自动发现 | | `apks` | 应用管理 | APK 上传、安装、删除 | | `logs` | 日志查看 | `GET /api/logs` | - **仅管理员可用**(普通用户即使被授予业务权限也无法访问):AI 控制台 `/api/agent/*`、系统备份 `/api/system/backup/*`、Tailscale `/api/tailscale/*`、用户管理 `/api/users`、adb 终端 `/api/adb/*`、工具 `/api/tools/*` - 403 文案两种:业务权限缺失 → `{"ok": false, "error": "无权限执行此操作(需要权限: X)"}`;仅管理员接口被非管理员访问 → `{"ok": false, "error": "仅管理员可执行此操作"}` - 当前用户权限查询:`GET /api/me` --- ## 1. 认证 ### POST /login 用户登录。 **请求**(form-data): | 参数 | 类型 | 说明 | |------|------|------| | username | string | 用户名 | | password | string | 密码 | **响应**:成功重定向到 `/`,失败返回登录页(含 error 信息)。 ### GET /logout 登出,重定向到登录页。 ### GET /api/me 当前登录用户信息(含权限位),前端据此隐藏无权限的功能入口。需登录。 **响应**: ```json {"ok": true, "user": { "id": 1, "username": "admin", "is_admin": true, "perms": ["tasks", "devices", "apks", "logs"] }} ``` 管理员返回全部权限位;普通用户返回其被授予的业务权限数组。 ### GET /api/csrf 获取当前会话的 CSRF token(登录后先获取一次;变更类请求需在 `X-CSRF-Token` 请求头携带)。 **响应**: ```json {"ok": true, "token": "…"} ``` --- ## 2. 页面路由 ### GET / 单页应用首页(需登录)。响应头设置 `Cache-Control: no-store, no-cache, must-revalidate, max-age=0`(并带 `Pragma: no-cache`)防止缓存。 ### GET /login 登录页面(GET)。 ### GET /wall 监控大屏页面(需登录,全屏深色控制室风格,供挂墙/电视展示):设备卡片网格 (缩略图/型号/状态/当前动作/进度)、顶部统计与时钟;状态每 5s 刷新、缩略图每 2.5s 轮询。 20 台设备整体开销约 0.2 核 CPU + 100KB/s 带宽,普通电脑无压力。 --- ## 3. 设备状态 ### GET /api/status 获取设备池 + Worker 综合状态(带 5 秒缓存;worker 状态实时读内存)。需登录。 字段说明:`server_time` 服务端时间戳;`fg_scanning`/`fg_last_scan` 前台 App 扫描状态;`devices` 设备数组。 **响应**: ```json { "ok": true, "server_time": 1700000000.0, "fg_scanning": false, "fg_last_scan": 1700000000.0, "devices": [ { "serial": "192.168.1.100:5555", "model": "Pixel 6", "device_name": "测试机1", "present": true, "ready": true, "owner": "", "worker_status": "running", "foreground_app": "抖音", "progress": {"done": 5, "total": 80, "unit": "视频", "action_counts": {"like": 3}}, "current_action": "观看视频 6", "last_error": "", "last_warning": "", "running_job": "", "task_job": "", "attempt": 0, "end_time": 0 } ] } ``` `worker_status` 取值:`idle` / `connecting` / `running` / `done` / `error` / `failed` ### GET /api/summary 失败/异常任务汇总(供监控页"异常汇总"面板),观察设备长期健康度。需登录。 **响应**: ```json { "ok": true, "counts": {"total": 8, "running": 1, "done": 5, "error": 1, "failed": 1, "idle": 0}, "errors": [ {"serial": "192.168.1.100:5555", "model": "Pixel 6", "status": "failed", "last_error": "重试3次失败", "task": "抖音养号", "attempt": 3, "updated": 1700000000.0} ] } ``` `counts` 各状态计数;`errors` 为 `error`/`failed` 且有 `last_error` 的异常设备 (按最近心跳倒序,最多 50 条;已不在设备池的陈旧失败记录不展示)。 ### GET /api/health 轻量健康检查(免登录,供运维探活):进程存活 + 设备/任务摘要,不暴露敏感信息。 **响应**: ```json {"ok": true, "status": "up", "time": 1700000000.0, "device_total": 8, "device_online": 6, "device_running": 2, "device_error": 1, "jobs": 3} ``` ### POST /api/scan_foreground 手动触发前台 App 扫描(后台异步执行,不打扰设备)。 (权限:设备控制) **响应**: ```json {"ok": true, "msg": "扫描已启动"} {"ok": false, "error": "已有扫描在进行中"} ``` ### GET /api/devices 返回所有在线设备 serial 列表(供分组表单勾选用)。 **响应**: ```json {"ok": true, "devices": ["192.168.1.100:5555", "192.168.1.101:5555"]} ``` ### GET /api/devices//apps 获取指定设备上已安装的应用列表(包名 + versionCode/versionName + APK 路径 + 应用名)。需登录。 **响应**: ```json {"ok": true, "apps": [ {"package": "com.ss.android.ugc.aweme", "path": "/data/app/.../base.apk", "version_code": 2500, "version_name": "25.0.0", "label": "抖音"} ]} ``` ### GET /api/device/screenshot 获取设备当前画面截图(PNG)。 **参数**: | 参数 | 类型 | 说明 | |------|------|------| | serial | string | 设备 serial | | t | int | 时间戳(避免缓存,前端自动加) | **响应**:成功返回 `image/png`,失败返回 JSON 错误。 > 用 `adb exec-out screencap -p`,只读操作,任务运行中调用安全。 --- ## 4. 任务类型 ### GET /api/task_types 返回所有已注册任务类型。 **响应**: ```json { "ok": true, "task_types": [ { "task_type": "douyin_nurture", "name": "抖音养号", "description": "自动观看抖音视频...", "default_params": {"watch_count": 80, "...": "..."} }, { "task_type": "generic_steps", "name": "通用步骤", "description": "通过步骤编辑器编排...", "default_params": {"steps": [...]} } ] } ``` ### GET /api/actions 返回指定任务类型支持的专属操作。 **参数**: | 参数 | 类型 | 说明 | |------|------|------| | task_type | string | 任务类型 | **响应**: ```json { "ok": true, "actions": [ {"action_type": "like", "name": "点赞", "description": "...", "default_params": {"rate": 0.3}} ] } ``` --- ## 5. 任务计划 CRUD ### GET /api/jobs 列出所有任务计划。 **响应**: ```json { "ok": true, "jobs": [{"id": "abc123", "name": "抖音养号", "task_type": "douyin_nurture", "next_run": "2026-08-12 09:00", "...": "..."}], "task_types": [...] } ``` `next_run`:下次真正执行时间(已按运行窗口跳过窗口外触发点,格式 `YYYY-MM-DD HH:MM`);手动任务/已停用为 `null`。 ### POST /api/jobs 创建任务计划。 (权限:任务管理) **请求**(JSON): ```json { "name": "抖音养号", "task_type": "douyin_nurture", "target": {"mode": "all"}, "params": {"watch_count": 50, "actions": {"like": {"params": {"rate": 0.5}}}}, "schedule": {"mode": "cron", "cron": "0 */2 * * *"}, "retry": {"max_attempts": 3, "delay": 60}, "enabled": true } ``` **响应**: ```json {"ok": true, "msg": "任务已创建", "job": {"id": "abc123", "...": "..."}} ``` `schedule` 字段格式: | 字段 | 说明 | |------|------| | `mode` | `once` 手动 / `cron` 定时启动 / `cron_stop` 定时启动+停止 | | `cron` | 标准 5 段 cron:`分 时 日 月 周`(周 `0`/`7`=周日);如 `0 */2 * * *` 每 2 小时整点 | | `stop_cron` | (cron_stop 必填)到点停止本任务 worker | | `window` | 可选,运行窗口 `{"start": "21:00", "end": "09:00"}`(每天重复,支持跨午夜)。窗口外定时触发和手动执行(`POST /api/jobs/:id/run`)均不启动,手动执行返回错误提示 | ### PUT /api/jobs/ 更新任务计划。只需传要更新的字段。 (权限:任务管理) **请求**(JSON): ```json {"params": {"watch_count": 100}} ``` **响应**: ```json {"ok": true, "msg": "任务已更新", "job": {"...": "..."}} ``` ### DELETE /api/jobs/ 删除任务计划。不存在返回 404。 (权限:任务管理) **响应**: ```json {"ok": true, "msg": "任务已删除"} ``` ### POST /api/jobs//run 立即执行任务(异步,不阻塞)。 (权限:任务管理) **响应**: ```json {"ok": true, "msg": "任务 抖音养号 已触发"} ``` ### POST /api/jobs//toggle 启用/停用任务。 **请求**(JSON): ```json {"enabled": false} ``` **响应**: ```json {"ok": true, "msg": "任务已停用"} ``` --- ## 6. 设备分组 ### GET /api/groups 列出所有分组。 **响应**: ```json { "ok": true, "groups": [ {"name": "A组", "serials": ["192.168.1.100:5555"], "description": "测试组"} ] } ``` ### POST /api/groups 创建分组。重名返回 400。 (权限:任务管理) **请求**(JSON): ```json {"name": "A组", "serials": ["192.168.1.100:5555"], "description": "测试组"} ``` **响应**:`{"ok": true, "msg": "分组已创建"}` ### PUT /api/groups/ 更新分组(serials/description 按需传字段)。`` 不存在返回 404。 (权限:任务管理) **请求**(JSON): ```json {"serials": ["192.168.1.100:5555", "192.168.1.101:5555"], "description": "更新描述"} ``` **响应**:`{"ok": true, "msg": "分组已更新"}` ### DELETE /api/groups/ 删除分组。不存在返回 404。 (权限:任务管理) **响应**:`{"ok": true, "msg": "分组已删除"}` --- ## 7. 运行控制 ### POST /api/stop_device 停止单台设备的 worker(并阻止后续重试)。 (权限:设备控制) **请求**(JSON): ```json {"serial": "192.168.1.100:5555"} ``` **响应**: ```json {"ok": true, "msg": "已发送停止信号给 192.168.1.100:5555"} ``` ### POST /api/stop_all 停止所有运行中的 worker。 (权限:设备控制) **响应**: ```json {"ok": true, "stopped": ["192.168.1.100:5555", "192.168.1.101:5555"]} ``` > 旧「释放设备占用」端点已随 STF 摘除移除,此接口不再存在;设备互斥由调度器内存锁保证。 ### POST /api/device/clear_error 清除单台设备的异常状态(`error`/`failed` → `idle`),供设备列表"清除异常"按钮使用。 设备正在运行或等待重试时返回 400。 (权限:设备控制) **请求**(JSON): ```json {"serial": "192.168.1.100:5555"} ``` **响应**: ```json {"ok": true, "msg": "已清除 192.168.1.100:5555 的异常状态"} ``` ### POST /api/device/clear_all_errors 一键清除所有异常/失败设备(自动跳过正在运行/等待重试的)。 (权限:设备控制) **响应**: ```json {"ok": true, "cleared": 2, "msg": "已清除 2 台设备的异常状态"} ``` --- ## 8. 用户管理 用户管理接口**仅管理员可用**(非管理员返回 403 `仅管理员可执行此操作`)。`uid` 为用户 id(整数)。 ### GET /api/users 列出所有用户。 **响应**: ```json {"ok": true, "users": [ {"id": 1, "username": "admin", "is_admin": true, "perms": []}, {"id": 2, "username": "user1", "is_admin": false, "perms": ["tasks", "devices"]} ]} ``` `perms`:用户被授予的业务权限位数组(存储值;管理员以 `is_admin` 为准,perms 照常保存,取消管理员后按 perms 生效)。 ### POST /api/users 创建用户。 **请求**(JSON): ```json {"username": "user1", "password": "pass123", "is_admin": false, "perms": ["tasks"]} ``` `perms` 可选,默认无业务权限;`is_admin` 默认 false。 **响应**:`{"ok": true, "msg": "用户已创建"}` ### PUT /api/users/ 更新用户(改密码 / 管理员权限 / 权限位),只需传要改的字段。`uid` 不存在返回 404。 **请求**(JSON): ```json {"password": "newpass", "is_admin": true} ``` **响应**:`{"ok": true, "msg": "用户已更新"}` > 不能取消最后一个管理员(返回 400)。 ### DELETE /api/users/ 删除用户。`uid` 不存在返回 404。 **响应**:`{"ok": true, "msg": "用户已删除"}` > 不能删除默认管理员 `admin`、不能删除当前登录用户、也不能删除最后一个管理员(均返回 400)。 --- ## 9. 日志 ### GET /api/logs 查看日志文件内容。 (权限:日志查看) **参数**: | 参数 | 类型 | 默认 | 说明 | |------|------|------|------| | file | string | `core.log` | 日志文件名 | | lines | int | 300 | 返回最后 N 行 | **可选文件**:`core.log` / `task.log` / `web.log` / `action.log` **响应**: ```json { "ok": true, "content": "2026-08-08 10:00:00 [INFO] [core.worker] ...", "file": "core.log", "files": ["core.log", "task.log", "web.log", "action.log"] } ``` `files`:可选日志文件名数组。 --- ## 10. 自定义动作 ### GET /api/custom_actions 列出所有自定义动作(步骤打包)。需登录。 **响应**: ```json {"ok": true, "actions": [ {"id": "a1b2c3d4", "name": "登录流程", "icon": "📦", "steps": [{"type": "click", "...": "..."}], "created_at": "2026-08-08 10:00:00"} ]} ``` ### POST /api/custom_actions 创建自定义动作。 (权限:任务管理) **请求**(JSON): ```json {"name": "登录流程", "icon": "📦", "steps": [{"type": "click", "...": "..."}]} ``` **响应**:`{"ok": true, "msg": "动作已保存", "action": {...}}` ### PUT /api/custom_actions/ 更新自定义动作(name/icon/steps,按需传字段)。不存在返回 404。 (权限:任务管理) **请求**(JSON): ```json {"name": "登录流程 v2", "steps": [{"type": "click", "...": "..."}]} ``` **响应**:`{"ok": true, "msg": "已更新", "action": {...}}` ### DELETE /api/custom_actions/ 删除自定义动作。不存在返回 404。 (权限:任务管理) **响应**:`{"ok": true, "msg": "已删除"}` --- ## 11. 元素抓取(uiauto2) ### GET /api/uiauto/status 探测 uiauto2 本地服务是否运行。 **响应**: ```json {"ok": true, "running": true} ``` ### GET /api/uiauto/devices 获取 uiauto2 已连接的设备列表。uiautodev 本地服务未运行(或列表获取失败)返回 503。 (权限:设备控制) **响应**: ```json {"ok": true, "devices": [{"serial": "192.168.1.100:5555", "model": "Pixel 6"}]} ``` ### GET /api/uiauto/screenshot 通过 uiauto2 获取设备截图(JPEG)。uiautodev 本地服务未运行返回 503。 (权限:设备控制) **参数**: | 参数 | 类型 | 说明 | |------|------|------| | serial | string | 设备 serial | **响应**:成功返回 `image/jpeg`,失败返回 JSON 错误。 ### GET /api/uiauto/elements 获取设备 UI 元素树。uiautodev 本地服务未运行返回 503。 (权限:设备控制) **参数**: | 参数 | 类型 | 说明 | |------|------|------| | serial | string | 设备 serial | **响应**: ```json { "ok": true, "elements": [ { "depth": 0, "name": "android.widget.FrameLayout", "resource_id": "", "text": "", "description": "", "class": "android.widget.FrameLayout", "bounds": "[0,0][1080,2400]", "suggested": {"type": "xpath", "value": "//*"} } ] } ``` --- ## 12. 应用管理(APK) ### GET /api/apks 列出所有已上传的 APK。 **响应**: ```json { "ok": true, "apks": [ { "id": "abc123", "display_name": "抖音", "package_name": "com.ss.android.ugc.aweme", "version_name": "25.0.0", "version_code": 2500, "size": 104857600, "upload_time": "2026-08-08 10:00:00" } ] } ``` ### POST /api/apks/upload 上传 APK 文件(自动解析包名/版本/应用名)。 (权限:应用管理) **请求**(multipart/form-data): | 字段 | 类型 | 说明 | |------|------|------| | file | file | APK 文件 | **响应**: ```json {"ok": true, "apk": {"...": "..."}, "msg": "上传成功: 抖音"} ``` ### DELETE /api/apks/ 删除 APK 文件和记录。 (权限:应用管理) **响应**:`{"ok": true, "msg": "..."}`;删除失败返回 400。 ### POST /api/apks/install 批量安装 APK 到指定设备。 (权限:应用管理) **请求**(JSON): ```json {"apk_id": "abc123", "serials": ["192.168.1.100:5555", "192.168.1.101:5555"]} ``` **响应**: ```json {"ok": true, "msg": "开始安装 抖音 到 2 台设备"} ``` ### GET /api/apks/install/devices 可安装设备列表:设备池在线设备 + 本机 adb 设备(含 USB 有线连接)。 安装弹窗用此列表,USB 设备安装时跳过 adb connect 直接安装。 `source` 取值:`pool`=设备池在线;`usb`=本机 USB 有线(serial 无冒号);`adb`=本机网络 adb(serial 含冒号)。 (权限:应用管理) **响应**: ```json {"ok": true, "devices": [ {"serial": "100.100.10.11:5555", "model": "22120RN86C", "source": "pool"}, {"serial": "192.168.1.5:5555", "model": "", "source": "adb"}, {"serial": "ZY322ABCDEF", "model": "", "source": "usb"} ]} ``` ### GET /api/apks/install/status 获取安装任务实时状态。 **响应**: ```json { "ok": true, "status": { "apk_name": "抖音", "finished": false, "total": 2, "success": 1, "failed": 0, "skipped": 0, "installing": 1, "pending": 0, "items": { "192.168.1.100:5555": {"name": "Pixel 6", "status": "success", "msg": "安装成功(已验证)"}, "192.168.1.101:5555": {"name": "Pixel 7", "status": "installing", "msg": "正在安装..."} } } } ``` --- ## 13. 维护 / 工具 设备池管理、自动发现、远程看屏/触控、定位、维护终端等集中在"工具"页(页内子分栏)。 **权限说明**:设备池管理、自动发现、远程看屏/触控、定位等接口需 `devices` 权限; **adb 终端仅管理员可用**(见各节标注)。 ### GET /api/devices/pool 设备池清单(SQLite devices 表),含实时在线状态。**权限**:`devices`。 **响应**: ```json {"ok": true, "devices": [ {"serial": "100.100.10.20:5555", "name": "", "model": "22120RN86C", "enabled": true, "online": true, "note": "", "created_at": "2026-08-17 11:10"} ]} ``` ### POST /api/devices/pool/add 添加/更新设备(upsert)。**权限**:`devices`。 **请求**(JSON):`{"serial": "100.100.10.20:5555", "name": "备注", "note": ""}` IP:5555 设备添加后立即尝试 adb connect,并后台采集型号(getprop ro.product.model)。 ### POST /api/devices/pool/toggle 启用/停用设备(停用后不参与任务调度)。**权限**:`devices`。 **请求**(JSON):`{"serial": "...", "enabled": false}` ### POST /api/devices/pool/remove 从设备池删除(不再参与调度,不影响设备本身)。**权限**:`devices`。 **请求**(JSON):`{"serial": "..."}` ### POST /api/devices/pool/reconnect 一键重连:并发 adb connect 池内全部 IP:5555 设备(后台执行),完成后自动批量刷新型号。**权限**:`devices`。 ### POST /api/devices/pool/refresh_models 批量采集池内在线设备的型号(后台执行)。**权限**:`devices`。 ### GET /api/devices/discovery 自动发现状态 + 待连接列表(pending)+ 正式池断联设备。前端约 10s 轮询一次。**权限**:`devices`。 **响应**: ```json { "ok": true, "enabled": true, "subnets": ["192.168.1.0/24"], "interval": 60, "port": 5555, "scanning": false, "last_scan": "2026-08-08 10:00", "last_result": {"found": 12, "verified": 3, "new": 1}, "last_error": "", "pending_count": 2, "pending": [{"serial": "192.168.20.5:5555", "source": "lan", "first_seen": "2026-08-08 10:00", "last_seen": "2026-08-08 10:05", "online": true}], "pool_offline": [{"serial": "192.168.1.100:5555", "model": "Pixel 6", "name": "测试机1"}] } ``` `pending` 只列当前在线设备(离线候选不可确认,下轮扫描自动更新);`pool_offline` 为正式池中已断联设备(发现线程每轮自动重连,也可手动触发重连)。 ### POST /api/devices/discovery/scan 手动触发一轮扫描(后台执行,约 5-30 秒)。**权限**:`devices`。 扫描只验证并把新设备放进待连接池(pending),**确认后才入正式设备池**。 **响应**: ```json {"ok": true, "msg": "扫描已启动(后台执行,约 5-30 秒)", "result": {"found": 12, "verified": 3, "new": 1}} ``` 扫描进行中返回 409;未配置网段返回 400 并附原因。 ### POST /api/devices/discovery/confirm 确认连接:把待连接设备加入正式设备池并后台 adb connect/采型号。**权限**:`devices`。 **请求**(JSON):`{"serial": "192.168.20.5:5555", "name": "客厅机"}` **响应**:`{"ok": true, "msg": "已加入设备池", "is_new": true}` ### POST /api/devices/discovery/ignore 忽略:从待连接列表删除(下轮扫描可能再次发现)。**权限**:`devices`。 **请求**(JSON):`{"serial": "..."}` **响应**:`{"ok": true, "msg": "已忽略"}` ### POST /api/devices/discovery/reconnect 手动立即重连正式池中的断联设备(后台 adb connect + 采型号)。**权限**:`devices`。 日常无需手动——发现线程每轮(默认 60s)自动重连断联设备。 **请求**(JSON):`{"serial": "..."}` **响应**:`{"ok": true, "msg": "重连已启动(约 5-15 秒生效)"}` ### POST /api/devices/discovery/settings 保存自动发现配置(部分字段更新)。**权限**:`devices`。 **请求**(JSON):`{"enabled": true, "subnets": ["192.168.1.0/24"], "interval": 60, "port": 5555}` `interval` 需在 10-3600 秒之间;`subnets` 逐项校验 CIDR,非法返回 400。 **响应**:`{"ok": true, "msg": "已保存"}` ### GET /api/screen/stream 远程看屏:MJPEG 实时画面流(`multipart/x-mixed-replace`)。**权限**:`devices`。 `?serial=xxx` 指定设备;浏览器 `` 直接渲染,客户端断开自动停止。数据源 u2(atx-agent minicap,约 3-6 帧/秒)。 ### GET /api/screen/thumb 大屏缩略图:单张 JPEG(360px 宽,质量 55)。**权限**:`devices`。 `?serial=xxx` 指定设备。监控大屏按设备每 ~2.5s 轮询一帧(页面不可见时暂停); 一次性请求(非流),与任务并发安全(与任务截图同走 u2 minicap)。 ### POST /api/screen/tap 点击设备屏幕。**权限**:`devices`。 **请求**(JSON):`{"serial": "...", "x": 360, "y": 800}`(设备原生分辨率坐标) ### POST /api/screen/swipe 滑动。**请求**(JSON):`{"serial": "...", "x1": 360, "y1": 1200, "x2": 360, "y2": 600, "duration": 0.2}` ### POST /api/screen/key 按键。**请求**(JSON):`{"serial": "...", "key": "back"}`。 支持:back/home/recent/menu/power/volume_up/volume_down/enter/delete/search/camera ### POST /api/screen/text 输入文字(需焦点在输入框)。**请求**(JSON):`{"serial": "...", "text": "你好"}` ### POST /api/screen/tap_text 按屏幕文字点击:先在 UI 树里做 text/description 子串匹配点元素中心(原生控件); 未命中则截图 OCR 找文字中心(WebView/图片/画布渲染文字)。**权限**:`devices`。 **请求**(JSON):`{"serial": "...", "text": "立即下载"}` **响应**: ```json {"ok": true, "found": true, "method": "ui", "matched": "立即下载", "x": 540, "y": 1200} ``` `method`:`ui` 或 `ocr`;`found=false` 表示屏幕确实没有该文字(业务结果,非设备错误)。 ### GET /api/screen/size 获取设备屏幕原生分辨率(只读,供坐标换算——截图常是缩放图、操作需原生坐标)。**权限**:`devices`。 离线/不可达返回 503。 `?serial=xxx` **响应**:`{"ok": true, "width": 1080, "height": 2400}` ### POST /api/device/screen_all 批量亮屏/息屏(并发)。**权限**:`devices`。 **请求**(JSON): ```json {"mode": "on", "serials": ["100.100.10.11:5555"]} ``` `serials` 可选:指定设备(离线自动过滤);不带则作用于全部在线设备。息屏会中断运行中的任务,前端有确认提示。 ### POST /api/device/locate 定位设备:点亮屏幕并解锁(WAKEUP → dismiss-keyguard → MENU 兜底)。 `show=true` 时额外用设备浏览器打开平台 `/locate` 大字定位页(更醒目,但会切换前台,任务运行中慎用)。**权限**:`devices`。 **请求**(JSON):`{"serial": "192.168.1.100:5555", "show": true}` **响应**:`{"ok": true, "msg": "192.168.1.100:5555 屏幕已点亮;已打开大字定位页(按返回键退出)"}` ### POST /api/device/locate/stop 结束定位:优先 force-stop 定位时启动的浏览器(无论前后台都能关掉);无记录时前台是 浏览器则 force-stop,否则按返回键轻量退出(不误杀任务应用)。**权限**:`devices`。 **请求**(JSON):`{"serial": "..."}` **响应**:`{"ok": true, "msg": "已关闭浏览器 com.android.chrome"}` ### GET /api/adb/devices 维护终端设备列表(**仅管理员**):本地 adb 已连接(含 offline)+ 设备池已配置设备(标记 `pool`)。 供终端设备选择器使用——选中后前端自动附加 `-s `。 **响应**: ```json {"ok": true, "devices": [ {"serial": "100.100.10.11:5555", "state": "device"}, {"serial": "100.100.10.13:5555", "state": "offline"}, {"serial": "100.100.10.12:5555", "state": "pool"} ]} ``` ### POST /api/adb/cmd adb 远程终端(**仅管理员**):用平台 adb 二进制执行任意 adb 命令(20s 超时)。 **请求**(JSON): ```json {"cmd": "adb -s 100.100.10.11:5555 shell ls /sdcard"} ``` (开头的 `adb` 前缀可省略) **安全红线**:包含 `kill-server` / `disconnect` 的命令直接拒绝(400)——会断开共享的 adb transport,导致全部设备连接重建。 **响应**: ```json {"ok": true, "stdout": "...", "stderr": "", "code": 0} ``` --- ## 14. 步骤测试(需"设备控制"权限) ### POST /api/steps/test 在指定设备上单步试执行(步骤编辑器"测试此步骤"按钮),验证选择器是否命中。 只读连接(adb connect + u2),与运行中任务互不干扰。 **请求**(JSON): ```json { "serial": "100.100.10.11:5555", "step": {"type": "click", "label": "测试", "params": {"selector_type": "xpath", "selector_value": "//*[@resource-id=\"x\"]"}} } ``` **响应**: ```json {"ok": true, "msg": "步骤已执行(测试)", "result": "命中"} ``` `result`:`命中` / `未找到` / `已执行`(无选择器命中语义的步骤)。 无"设备控制"权限返回 403。 --- ## 15. Tailscale 管理(仅管理员) 工具页"Tailscale 管理"子分栏,调用 Tailscale 官方 API v2。所有接口仅管理员可用。 前置:`.env` 配置 `TAILSCALE_API_KEY`(Settings → API Access Tokens)与 `TAILSCALE_TAILNET`(tailnet 名,个人账号一般为邮箱前缀)。 `GET /api/tailscale/status` 未配置时返回 `200 {"ok": true, "configured": false}` 并附 `hint` 提示; 其余接口在未配置/调用失败时返回 `502` 并附错误信息。 设备 IP 由 tailnet 分配,API 不可修改,列表只读展示。 ### GET /api/tailscale/status 配置状态检查。 **响应**: ```json {"ok": true, "configured": false, "hint": "TAILSCALE_API_KEY(...);TAILSCALE_TAILNET(...)"} ``` ### GET /api/tailscale/devices 列出 tailnet 全部设备。 **响应**: ```json {"ok": true, "devices": [ {"id": "d1", "name": "dev-a", "hostname": "dev-a", "os": "linux", "addresses": ["100.100.10.11"], "authorized": true, "key_expiry_disabled": false, "online": true, "last_seen": "...", "tags": []} ]} ``` ### POST /api/tailscale/devices/:device_id 更新设备(按传入字段分发到 Tailscale 专属端点,`POST /device/{id}` 本身是 405): `name` → `/name` 显示名;`authorized` → `/authorized` 授权开关; `key_expiry_disabled` → `/key`(true=密钥永不过期,即关闭设备密钥验证,恢复后按原定过期时间执行)。 **请求**(JSON): ```json {"key_expiry_disabled": true} ``` ### POST /api/tailscale/devices/:device_id/ip 设置设备的 Tailscale IPv4 地址(未公开端点,实测可用)。 **请求**(JSON): ```json {"ipv4": "100.100.10.16"} ``` ⚠ 改 IP 会断开设备当前 tailscale 会话;平台设备池 serial 随之变化,需同步更新设备池/分组/任务目标。 ### DELETE /api/tailscale/devices/:device_id 从 tailnet 移除设备(下次上线需重新授权)。 ### POST /api/tailscale/authkey 生成设备接入 auth key(key 只返回一次)。 **请求**(JSON): ```json {"description": "新设备接入", "reusable": false, "ephemeral": false, "preauthorized": true, "expiry_seconds": 3600} ``` **响应**: ```json {"ok": true, "key": "tskey-auth-...", "id": "k1", "expires": "2026-08-11T01:00:00Z"} ``` --- ## 16. 工具(仅管理员) 工具页(剪贴板注入 / 应用版本管理)接口,均仅管理员可用。 ### POST /api/tools/clipboard/set 剪贴板注入:把指定文字写入一台或多台设备的剪贴板。 **请求**(JSON): ```json {"serials": ["100.100.10.11:5555", "0123456789ABCDEF"], "text": "要注入的文字"} ``` 设备来源与维护终端一致(本地 adb 含 USB + 设备池)。 实现:通过 ClipInject(`com.example.clipinject`)透明 Activity 前台聚焦后写入剪贴板 (shell 启动前台 Activity 不受后台启动限制),再用 u2 读回比对校验,支持中文/引号/换行。 不再使用 u2 setClipboard / 自动推送 atx-agent(Android 10+ 禁止后台写剪贴板,旧 u2 调用"成功"但内容被系统静默丢弃)。 IP 设备先 adb connect(已连接跳过,绝不 disconnect);**设备未安装 ClipInject 时** am start 返回 unable to resolve Intent,接口明确报错提示先安装。 **响应**: ```json {"ok": true, "results": {"100.100.10.11:5555": {"ok": true, "msg": "已注入"}}, "ok_count": 1, "fail_count": 0, "error": null} ``` ### POST /api/tools/appver 应用版本管理:查询所有设备上指定包名的安装情况与版本号(并发 10 台)。 **请求**(JSON): ```json {"pkg": "com.ss.android.ugc.aweme"} ``` **响应**: ```json {"ok": true, "total": 8, "fail": 0, "results": {"100.100.10.11:5555": {"installed": true, "version_name": "28.5.0", "version_code": "280500"}}} ``` 未安装返回 `installed: false`;查询失败的设备带 `error` 字段。 --- ## 17. AI 控制台(仅管理员) 浏览器内 AI 助手(DeepSeek 式多会话):用 MCP 工具操作指定设备、多轮上下文延续、 成功后自动提炼经验记忆。**单实例:同时只允许一个 Agent 运行。** 以下接口均仅管理员可用。 ### GET /api/agent/config 读 Agent 配置。`api_key` 打码回显在 `api_key_masked` 字段。 **响应**: ```json {"ok": true, "api_base": "https://api.deepseek.com", "model": "deepseek-v4-flash-vision-exp", "api_key_masked": "sk-***abcd", "default_serial": "", "max_steps": "40"} ``` ### POST /api/agent/config 保存 Agent 配置(部分更新)。**请求**(JSON): `{"api_base": "...", "model": "...", "api_key": "...", "default_serial": "...", "max_steps": 40}` (`max_steps` 1-200,默认 40) **响应**:`{"ok": true, "msg": "已保存"}` ### GET /api/agent/devices AI 可用设备列表(在线状态 + 是否有任务运行,前端据此把 busy 设备禁选)。 **响应**: ```json {"ok": true, "devices": [ {"serial": "192.168.1.100:5555", "model": "Pixel 6", "online": true, "busy": false, "worker_status": "idle", "task_job": ""} ]} ``` ### POST /api/agent/run 启动 Agent(后台线程执行,立即返回 `run_id`)。**请求**(JSON): `{"prompt": "打开抖音并点赞前 3 条视频", "serial": "192.168.1.100:5555", "conversation_id": "abc..."}` `serial` 也可省略、用配置的 `default_serial`;`conversation_id` 绑定会话(历史从会话加载)。 **校验**:未配 API Key/模型名 → 400;设备不在池/离线 → 400;设备正有任务运行 → 409; 已有 Agent 运行中 → 409。 **响应**:`{"ok": true, "run_id": "8f3a2c9d"}` ### GET /api/agent/run 当前 Agent 运行状态(多窗口/页面刷新恢复用)。 **响应**: ```json {"ok": true, "state": "running"|"idle"|"done", "run_id": "...", "prompt": "...", "serial": "...", "started": "10:00:01", "answer": "...", "error": "", "history": [{"role": "user", "content": "..."}]} ``` ### GET /api/agent/stream?run_id= 订阅事件流(SSE,EventSource)。事件: - `event: delta` `{text, kind: content|reasoning}` — 流式文本增量 - `event: step` `{tool, args, image?}` — 工具调用完成(MCP 步骤,image 为缩略截图)。另有三类伪卡片:`tool="🧠 经验记忆"` 表示命中任务级经验(args 形如「命中 N 条同类历史经验,已注入参考:<配方摘要>」)或本轮已写入经验库;`tool="🧠 动作经验"` 表示命中**可复用动作**(「命中 N 个可复用动作,已注入参考:<动作名>」,执行前注入)或本轮已沉淀动作(「已沉淀 N 个可复用动作」,含元素定位、禁坐标) - `event: done` `{answer}` — 完成 - `event: error` `{message}` — 失败(若因 MCP Server 未启动/不可达,message 为明确文案「MCP server(8033) 不可达 …」,不再是 SDK 原始的 `Server returned an error response`) - 空闲时每 15s 发一行 `: keepalive` 注释防超时;`done`/`error` 后关流 ### POST /api/agent/stop 中断当前运行的 Agent(下一个检查点生效,数秒内)。无运行中任务返回 400。 **响应**:`{"ok": true, "msg": "已请求停止"}` ### POST /api/agent/clear 清空当前对话历史。 **响应**:`{"ok": true, "msg": "已清空"}` ### GET /api/agent/conversations 会话列表(按最近更新倒序)。 **响应**: ```json {"ok": true, "conversations": [ {"id": "abc...", "title": "打开抖音点赞", "updated_at": "2026-08-08 10:00", "count": 3} ]} ``` `count` 为轮数(用户+助手消息对数)。 ### POST /api/agent/conversations 新建会话(空消息)。 **响应**:`{"ok": true, "id": "新会话id", "title": "新会话"}` ### GET /api/agent/conversations/ 会话详情(全部消息文本)。不存在返回 404。 **响应**: ```json {"ok": true, "id": "...", "title": "...", "messages": [{"role": "user", "content": "..."}], "created_at": "...", "updated_at": "..."} ``` ### DELETE /api/agent/conversations/ 删除会话(消息一并删除,不可恢复)。 **响应**:`{"ok": true, "msg": "会话已删除"}` ### POST /api/agent/conversations//rename 重命名会话。**请求**(JSON):`{"title": "新标题"}` **响应**:`{"ok": true, "msg": "已重命名"}` ### GET /api/agent/experience 经验记忆库列表(自进化,含最近一次巡检结论)。另有一张**动作经验库**表 `agent_action`(命名动作 + 编辑器 schema 步骤 + 元素定位、禁坐标):任务成功后自动从**成功步骤**蒸馏沉淀,执行前按动作名/别名召回并注入;当前无独立查询接口(命中/沉淀在 AI 控制台的 🧠 卡片可见)。 **响应**: ```json {"ok": true, "running": false, "last": "2026-08-08 03:47", "last_summary": "评审 5 条,建议删除 1 条(待人工确认)", "experiences": [{"id": 1, "task_prompt": "打开抖音并点赞", "recipe": "...", "tool_seq": "...", "hits": 3, "created_at": "...", "audit": {"verdict": "delete", "score": 3, "reason": "...", "action": "pending", "at": "..."}}]} ``` `audit` 为最近一次 AI 巡检结论(无则 null)。 ### POST /api/agent/experience/delete 人工确认删除经验(真删,巡检绝不自动删)。**请求**(JSON):`{"id": 1}` **响应**:`{"ok": true, "msg": "经验 #1 已删除"}` ### POST /api/agent/experience/audit 手动触发一轮经验巡检(后台线程,AI 评审只建议不删)。巡检进行中返回 409。 **响应**:`{"ok": true, "msg": "巡检已启动,完成后刷新列表查看建议"}` ### GET /api/agent/actions 动作经验库列表(命名动作 + 编辑器 schema 步骤 + 元素定位、禁坐标)。仅管理员。 响应:`{"ok":true,"actions":[{id,name,app,aliases,params,steps,preconditions,hits,updated_at}]}`。 由任务成功后的**成功步骤**自动蒸馏沉淀;执行前按动作名/别名召回并注入 system prompt。 ### POST /api/agent/actions/delete 删除动作:`{id}`。仅管理员。 ### POST /api/agent/actions/save 新增/编辑动作:`{id?, name, app?, aliases?, params?, steps, preconditions?}`。仅管理员。 `steps` 可为数组或 JSON 字符串,经服务端校验(白名单 type + 必填;**拒绝坐标 click_xy**)。 校验失败返回 400 并附原因。 ### POST /api/agent/experience/keep 人工保留经验(撤销"建议删除",后续巡检不再重复建议)。**请求**(JSON):`{"id": 1}` **响应**:`{"ok": true, "msg": "经验 #1 已保留"}` --- ## 18. 系统备份(仅管理员) 整库备份导出/导入(users.db + 可选 APK 文件)。导入涉及整库替换,仅管理员可用。 ### POST /api/system/backup/export 生成导出 zip 并作为附件返回(含 users.db 一致快照 + manifest.json + 可选 `apks/`)。 **请求**(JSON):`{"include_apk": true}` **响应**:`application/zip` 附件下载(`download_name` 形如 `export_20260808_101000.zip`)。 ### POST /api/system/backup/preview 上传备份文件(multipart 字段 `file`,支持 .zip 或 .db)→ 暂存并校验 → 返回预览。 校验失败返回 400。 **响应**: ```json {"ok": true, "token": "12位hex", "preview": { "file_name": "export_xxx.zip", "file_size": 123456, "integrity": "ok", "schema_version": 4, "current_schema_version": 4, "tables": [{"table": "user", "label": "用户", "rows": 3}], "missing_optional": [], "warnings": ["备份为全量快照:含敏感信息,请妥善保管"] }} ``` ### POST /api/system/backup/apply 确认应用导入:自动备份当前库到 `BACKUP_DIR/pre_restore_*.db`(安全网),再把暂存库 落为「待生效恢复任务」。**重启 web_server 后生效**。token 无效/过期返回 400。 **请求**(JSON):`{"token": "12位hex"}` **响应**: ```json {"ok": true, "backup_name": "pre_restore_20260808_101000.db", "message": "恢复任务已生成:当前库已自动备份,重启 web_server 后即应用导入的数据"} ```