Commit Graph
54 Commits
Author SHA1 Message Date
butubb ed9e8bacb1 feat(AI 建任务): 直接创建 + 草稿沉淀 + MCP de_snapshot;修「建任务页收不到 done」
用户报的"探索完无法点击创建任务"真因:一个 run 的事件原先只有**一条** queue.Queue,
聊天页与建任务页同时开着时两个 EventSource 会**瓜分**它——建任务页的回放卡在中间、
`done` 被聊天页取走 → 永远等不到草稿,页面上自然没有可点的"创建"。

一、修(根因 + 表现)
- `web/agent_api.py` 新增 `_Fanout`:**每个订阅者一个专属队列**,多开页面各看各的,
  还带单轮事件缓冲(晚订阅/刷新重连也能补齐回放,终止事件一定送达)。
  实测两路订阅者收到完全一致的 1039 条事件(含 done)。
- `static/admin/agent.js`:断线重连的兜底订阅也按 `mode` 让开(此前漏了这一处)。

二、补齐上一批的三项
- **「直接创建」**:`POST /api/agent/task_draft/create`(草稿体只在服务端、创建前再校验一次、
  成功后清草稿避免重复建)+ 草稿预览里的「✓ 直接创建任务」按钮 + 「探索完直接创建任务」勾选框。
- **草稿沉淀经验/动作**:designer 轮次也走 `_distill_experience/_distill_actions`,
  但**只在草稿通过校验时**(没走通的试错不入库,免得把误点当经验)。
- **MCP `de_snapshot`**(第 20 个工具):截图+元素树一次取齐(省一次来回、不会因界面在动而错位),
  附带 `screen_state`/`unstable`;两套提示词都改为优先用它。
  平台侧 `/api/uiauto/snapshot` 随之多返回 `screen_state`。

三、文档
- AI_TASK_GEN §10:§10.3 记两个 bug 的真因与修法、§10.4 三项标完成、§10.5 剩余项。
- AI_CONSOLE(扇出语义、多页面同时看一轮)、API(task_draft/create、snapshot 字段)、
  MCP/MCP_DESIGN/staffdeck/README/ARCHITECTURE:工具数 19→20 + de_snapshot 条目。
- backlog:记一条新发现的缺陷——`mcp_server/platform_client._login()` 会把"登录页 200"
  当成登录成功(现场进程缺 `MCP_PLATFORM_PASS` 时表现为含糊的 platform_unavailable)。

自测:真机浏览器端到端(勾上"探索完直接创建")→ 探索 12 步 → 草稿 → 自动建任务成功;
`de_snapshot` 直连真机校验;校验器 21 条用例、扇出单元用例、本地工具契约用例全绿。
自测产生的任务/草稿已全部清理(未碰用户既有数据)。
2026-09-14 08:14:11 +08:00
butubb 46e6ea1f37 feat(AI 建任务): AI 自己在真机探索 → 写出可调度任务 → 人工确认入库
AI 控制台下新增子分栏「🧭 AI 建任务」:描述要做什么(例:建一个跑 2 小时的任务、自动刷
某 App、随机点赞),AI 用 de_* 工具自己在设备上探索(看屏/读元素树/点按验证),把走通的
路径写成一条任务草稿,经服务端校验后交人在步骤编辑器里核对/手改/试跑,保存才入库。

链路:POST /api/agent/run{mode:"designer", settings}
  → Agent 自探 → 本地工具 submit_task(draft)
  → core/task_draft 校验(失败把 errors 回灌模型让它改)
  → 只暂存(运行态 + app_meta.agent_task_draft,**不落库**)
  → SSE done{mode,draft,warnings} → 页面草稿预览 → openTaskModal(null, prefill) 预填

关键实现
- core/task_draft.py(新):把执行器的"静默跳过点"(未知 type/空 selector/空 children/
  嵌套>5/节点>60/cron 非法/必填缺失)前移成显式 error——POST /api/jobs 对 params 是盲存的,
  执行器又静默跳过错误步骤,没有这道闸门就是"任务建好了、跑起来什么都没做"。
  归一化兜底任务名/target/schedule/retry/时长;页面填的设置以 overrides 优先于模型。
  故意**不比执行器更严**:loop_mode 近义值归一(count→rounds)、缺 max_iterations 补默认 10
  (执行器本来就默认)——实测卡太死会把一轮探索耗在改字段上。
  有副作用的步骤(评论/发送/购买/删除…)只警告并把触发概率压到 30%(编辑器可改回)。
- mcp_agent/agent.py:双系统提示词(CHAT/DESIGNER)+ 平台级本地工具
  (LOCAL_TOOL_SPECS,不进 MCP)+ 每工具调用上限 40 + designer 输出上限 8192 +
  **json.loads 容错**(草稿被截断时给模型可读错误,而不是整轮失败)。
- web/agent_api.py:mode/settings 透传、submit_task 处理器(app_context 内校验+暂存)、
  done 带 draft、GET /api/agent/task_draft{,+POST,/clear}(草稿走 app_meta,不新建表)。
- 前端:static/admin/taskgen.js + #agent-sub-taskgen 子面板(showSubTab 机制);
  tasks.js 的 openTaskModal(jobId, prefill) + 信封归一化 + 唯一 draftKey;
  agent.js 按 mode 门控(一个 run 只有一个事件队列,两个 EventSource 会互相瓜分事件)。

顺带修掉一个 chat 也踩的协议 bug:一轮里同时调 de_screenshot 与别的工具时,截图图像会被
插在两条 tool 消息之间 → 模型侧判"工具回应不足"直接 400。改为本轮 tool 消息发完再附图像,
_repair_tool_messages 也改成只数**连续**的 tool 消息。

真机实测(Redmi 22120RN86C,设置页):8 步探索(含 tap_text 验证)→ submit_task 一次通过 →
草稿 8 个顶层步骤(screen_on/open_app/wait_el/click/wait/key_event…)、max_duration 1800、
无 click_xy、3 条 evidence;页面恢复草稿 + 预填编辑器 + 提示块渲染均正常,无 JS 报错。
自测数据已清理(草稿已丢弃、未创建任何任务)。

文档:AI_TASK_GEN.md 状态改「P0 已实现」+ §10 实现记录(差异/护栏/未做项)、AI_CONSOLE.md
(子分栏、designer 分支、SSE done 负载、app_meta 键)、API.md、DATA_MODEL.md、ARCHITECTURE.md、
DEVELOPMENT.md(自测入口)、README.md 索引、backlog 勾掉 P0。
2026-09-13 23:08:59 +08:00
butubb 37a2b1c59b feat(抓取): 元素检查器改成本地版"大图 + 一次取齐"——复刻云检查器的体验
用户要的是 uiauto.dev 那个检查器的体验(左边大图点元素、右边层级树、选完回填),
并希望本地复刻(云页面跨域,拿不到它的选中结果,没法自动回传)。

- core/uiauto_helper.py: 新增 `snapshot(serial)`
  * **一个 u2 连接背靠背** dump_hierarchy() + screenshot():截图与元素树同源同刻,
    不再像原来那样分两个接口取(中间隔着 dump 本身的 1.3~1.8 秒)
  * `_xml_to_node()`:把 u2 的 XML 节点转成 uiautodev 那套结构,直接复用既有的
    选择器建议逻辑(不写第二遍)
  * **双截图校验**:dump 前后各截一张,差异明显就标 `unstable`,让前端明确提示
    "界面在变化中,请停在静止界面再抓",而不是悄悄给一个可能错位的框
- web/tasks_api.py: 新增 `GET /api/uiauto/snapshot`(登录 + 设备权限)
- static/admin/editor.js + templates/admin/monitor.html:
  * 抓取弹窗从 900px 加宽到 1280px,预览列从"固定 320px"改为铺满左侧 →
    **图片按原始分辨率 1:1 显示**(实测 720x1650 缩放 1.000),元素框严丝合缝;
    原来缩到 294px 时框全挤在一起,看着就像错位
  * 工具栏显示 `720x1650 · 247 个元素 · 2370ms`;界面在变时顶部弹黄色提示条
  * 鼠标在图上移动时高亮"最深命中"的元素框,便于确认真要点哪个

实测:抓取弹窗 1:1 显示、222 个框逐一贴合元素、无 JS 报错;
`unstable` 在静止界面为 false。
2026-09-13 21:17:12 +08:00
butubb 3422b4f265 feat: 监控页按名称排序 + 按钮改「看屏 / 打开设备端 Agent」;设备名匹配补 USB 设备
用户反馈的三件事:

1) **监控页设备排序**改成按**名称**(A01/A02/…),没名字的排最后按地址。
   之前是 adb 返回的顺序,看着像按 IP 排,同型号多台时对不上号。
   排序放在 `/api/status`(后端),大屏页一并受益。

2) **操作按钮改名 + 换实现**:
   - 「截图」→「**看屏**」(行为不变,就是查看设备实时画面)
   - 「定位」→「**打开设备端 Agent**」:改成让设备上的 Agent 弹身份大字页
     (名称/IP/平台地址),替代原来"推 HTML 让设备浏览器打开定位页"那套
     (那套要设备装浏览器、页面还依赖设备能访问平台,backlog A1/A2 一直没修)
     * 新接口 `POST /api/agent/show-info {serial, close?}`;没装 Agent 时明确报出来
     * **先点亮屏幕 + 解除锁屏再拉起** —— 设备息屏时 am start 会把大字页开在锁屏后面,
       人看到的还是一片黑(实测踩过)
     * 平台地址取操作者当前访问地址;若是 localhost 则换成本机局域网地址
       (设备上显示 127.0.0.1 毫无意义)

3) **安装应用的选择设备界面仍显示裸序列号**:USB 设备在 adb 里的 serial 就是
   `ro.serialno`,等于池里那条网络记录的**指纹** —— 之前只按 serial 匹配,插着 USB
   时会冒出一个没名字的 `s8o7nrt8pbzhfadq`。现在按 serial 或指纹任一命中都取名称,
   三处(剪贴板注入 / 安装弹窗 / 安装进度表)统一。

顺带修掉几个自己踩出来的坑:
- `device_pool.list_configured()` 返回的是 **serial 字符串列表**,我当成字典用了
  (`.get("serial")` 抛异常被 except 吞掉 → 名字全空)→ 改用 `list_devices()`
- `web/device_agent_api.py` 用了 `PERM_DEVICES` 但没导入 → 服务直接起不来(502)
- Agent 自检里的版本号是写死的常量(升到 1.4 还显示 1.0)→ 改成读包信息
- `doc/API.md` 章节编号(插了一节没把后面整体下移,出现两个 §16)

验证:安装弹窗里 USB 那台现在显示「A07」(指纹匹配);监控页按钮为
「看屏 / 打开设备端 Agent」;对装/未装 Agent 的设备分别得到大字页 / 明确报错;
大字页实测显示 A07 + 192.168.20.250:18050 + Agent v1.4。
2026-09-13 17:18:30 +08:00
butubb 3f041668ec feat: 设备配置二维码——设备池里出码,Agent 扫一下配好
配合设备端 Agent 新增的「扫码配置」:人在机架前、设备还没接进平台时,
不用插线也不用在手机上打字输地址/令牌(还容易漏指纹)。

- `GET /api/devices/qrcode?serial=`:生成配置二维码(PNG data-url),内容为
  {"v":1,"server":…,"token":…,"fingerprint":…,"name":…}
  * 前置:设备端商店已启用(令牌从那儿来),否则 400 并说明去哪儿开
  * `warn` 非空必须显示:①用 localhost 打开平台时,二维码里的地址手机会连不上
    (提示改用局域网地址,_lan_ip() 优先挑 192.168/10.x,**不用** gethostbyname ——
     那会返回 Tailscale 的 100.x 或 docker 的 172.17,手机根本连不上)
    ②设备还没采到指纹 → 扫完平台认不出是哪台
- 设备池每行加「二维码」按钮 → 弹窗显示二维码 + 设备名/地址 + 可展开看原始内容
- requirements 加 qrcode(纯 Python,配合已有的 Pillow 出 PNG,不引前端二维码库)
- 文档:API.md §6.2.2 新增、§6.2.1 名称呈现表补齐(剪贴板/安装弹窗/版本管理/
  安装进度四处);DEVICE_AGENT.md §5.3 新增二维码内容格式(设备端契约)

验证:二维码用 cv2.QRCodeDetector 解回来与 payload 完全一致;手机上实测——
扫 A07 的码 → 相机启动 → 解码 → 保存 → 立刻拉清单,平台认出「A07」。
2026-09-13 16:42:53 +08:00
butubb 25e4fbb053 feat: 应用列表按包名合并 + 剪贴板/安装/版本管理显示设备名
两件事都是"列表看不清楚"的问题:

1) **同一个应用传多个版本会并排显示多行**(v1.2 / v1.3 各占一行),看着像重复。
   现在按 **package_name 合并成一个应用一行**,显示最新版:
   - `apk_manager.list_latest()`:版本按 version_code 排序取最新,其余版本放进
     `versions` 字段;解析不出包名的 APK 各自独立成行(不硬合并)
   - 管理页版本列显示 `1.3(另有 1 个旧版本)`,多一个「清理旧版本」按钮
   - 「删除」语义改为**删掉这个应用的所有版本**(行=应用),另给
     `?scope=older` 只清理旧版本;老的单条删除行为保留(不带 scope)
   - **设备端商店也走同一个列表** —— 手机上不会再看到同一个 App 的 v1.2/v1.3 两行

2) **设备只显示地址**(`192.168.20.100:5555`),同型号多台时分不清是哪一台。
   三处补齐设备名(平台给设备起的名字,如 A08),统一「名称 · 地址」:
   - 剪贴板注入的设备勾选列表(`/api/adb/devices` 经 `_merged_device_list` 带 name)
   - 应用管理的安装弹窗(`/api/apks/install/devices` 带 name)
   - 应用版本管理表格(`/api/tools/appver` 结果里带 name)
   - 顺带:安装进度表里的「设备」列原来显示**型号**(而且是 STF 时代残留的空 dict),
     改为显示设备名
   没名字的设备(不在池里)自动回退只显示地址,不显示空占位

验证:上传同包名 v1.1/v1.2/v1.3 三个版本 → 列表只出现一行(v1.3,标注共 4 个版本)、
设备端 bootstrap 也只剩 2 个应用;`scope=older` 只留最新、`scope=package` 整包删除;
三个面板在浏览器里渲染无 JS 报错,devText 有名字/没名字两种形态都正确。
2026-09-13 16:29:38 +08:00
butubb fa07ed0804 feat(platform): 设备端商店支持"请平台静默装"—— 设备请求、平台走 adb 落地
背景:设备端自己调系统安装器在 MIUI 上要过「继续 → 勾选未经安全检测 → 继续更新」
甚至 ICP 备案检查好几步;而平台用 adb(推送 + pm install)装是**静默**的,实测
升级 2.7s、全新安装 7.9s、336MB 大包也没弹过一次框。

所以给设备端商店补一个"请求平台装"的出口:用户还是在手机上选应用(应用商店体验),
真正干活的是平台,几秒钟装完、零弹窗。

- web/device_agent_api.py 新增 `POST /api/device/agent/install`:
  设备带令牌调它 → 平台复用应用管理的安装器(`apk_mgr.install`,本身已是
  推送 + pm install、并发 5 台)→ 返回是否受理;失败时设备端自行退回本机安装
- 鉴权与设备识别沿用同一套(令牌 + 指纹),未登记设备拒绝并说明原因

注:设备端 APK 那边的对应按钮改动已暂停(用户决定先不动 Agent),
这个接口属于平台侧能力,先落在本次分支里,等 Agent 恢复时直接可用。
2026-09-13 15:38:14 +08:00
butubb dad1af8b0b feat: 设备端应用商店(平台侧)—— Agent 直连拉清单/下载/安装,绕开 MIUI 的 USB 安装拦截
背景:`adb install` 在 MIUI 上被「USB 安装」拦下(USER_RESTRICTED),必须人工点确认;
而**由设备上的 App 自己调系统安装器**走的是普通应用安装流程,不触发那道拦截。

平台侧实现(设备端 Agent APK 在另一个仓库,契约见 doc/DEVICE_AGENT.md):

- web/device_agent_api.py(新):
  * 设备侧 `/api/device/agent/{bootstrap,apk/<id>,report}` —— 无登录会话,靠
    `X-Device-Token`(常量时间比对)鉴权;`X-Device-Fingerprint` 让平台认出是哪台设备
    (换 IP 也认得出,并把平台分配的名称回给它);**默认关闭,关闭时统一 404**,
    不暴露接口存在性;只读清单与文件,不返回任何配置/密钥
  * 管理侧 `/api/agent-store/{config,logs}` —— 登录 + 应用管理权限;启用时自动生成令牌,
    可一键重置(旧令牌立即失效)
- core/models.py:新增 `device_install_log`(下载/安装记录,自动裁剪保留 500 条)
- 应用管理页新增「📱 设备端应用商店」面板:开关 / 接入地址 / 令牌(复制·重置)/ 安装记录;
  面板里写明它与「平台批量推送」的分工
- doc/DEVICE_AGENT.md(新):**两个仓库共享的接口契约** —— 接入流程、三个设备接口的
  完整规格与错误码、adb 指令协议(剪贴板/身份显示/配置下发)、版本兼容约定、安全须知;
  API.md、DATA_MODEL.md、doc/README.md 索引同步

验证:
- 后端 9 组用例:默认关闭 404、无/错令牌 401、指纹认出设备名、清单、下载、上报与失败
  原因落库、关闭后重新 404、重置令牌后旧令牌立即失效、未登录管理端被拒 —— 全通过
- 真实浏览器:面板渲染、点开关 → 自动生成令牌 → 设备端带令牌拉到清单且 device.name=A08、
  无 JS 报错;测试后已把开关还原为关闭
- 全量回归 105 项通过(42 GET + 62 写探测 + 7 关键业务)
2026-09-13 14:40:38 +08:00
butubb 4899e66c68 refactor: 备份/恢复改为「SQLite 归档 + 单事务整库替换」——不再换文件,也不再依赖 SQLite 运行时
SQLite 从"运行时数据库"降级为"备份交换格式":导出把当前库(MySQL 或 SQLite)
整库写成一份 SQLite 归档,恢复则是启动时在单个事务里 DELETE + INSERT。
前端三个接口的契约一行未改。

- core/system_backup.py 重写:
  * dump_to_sqlite()(旧名 snapshot_db 保留为别名)取代在线备份 API:MySQL 下把
    这条连接提到 REPEATABLE READ,保证 12 张表读的是同一时刻
  * zip 结构不变(users.db + manifest.json + apks/),既有老备份继续可导入;不依赖
    任何外部二进制(sqlite3 是标准库,python:slim 容器里现成)
  * manifest 增加 db_backend / deployment_env / source_db_id,用于识别备份来源环境
  * apply_restore:安全网从裸 pre_restore_*.db 升级为 pre_restore_*.zip(可直接再导入
    回滚);跨环境导入默认硬停(需 force_env_mismatch=true)
  * consume_pending_restore:启动时单事务整库替换,任何一步失败 rollback,当前数据
    完好(比换文件更安全);坏归档挪 restore_failed_* 且不阻塞启动
  * 归档库收尾时 checkpoint 回单文件模式并清掉 -wal/-shm(zip 只打包主文件)
- web_server.py:consume 从 init_db 之前移到之后(现在是事务替换,需要表与 app context)
- config.py:BACKUP_DIR / RESTORE_STAGING_DIR / RESTORE_PENDING_DIR 支持环境变量覆盖
  —— 自动化测试必须把恢复目录指到临时位置,否则测试造的"待生效恢复任务"会在服务
  下次重启时被当成用户的操作消费掉
- core/db_config.py:SQLite 回退模式也写库环境标签(备份要能标出来源环境)
- web/system_api.py + static/admin/system.js + templates/admin/monitor.html:
  预览显示来源/当前环境,跨环境时出现「允许跨环境导入」勾选项;文案同步

验证(隔离副本,绝不碰真实库):导出→zip 结构与 manifest 正确;预览带来源环境与
告警;应用→生成 pre_restore zip + 归档就位;跨环境 apply 被拒;模拟重启→数据回到
导出时刻、经验库/动作库完好;塞入坏归档→挪 restore_failed_*、服务照常启动、数据未变。
2026-09-13 10:34:58 +08:00
butubb c834349fac refactor: schema 收敛到 ORM metadata——5 张裸表升模型,方言无关的建表/补列/唯一索引
原来建表有三套来源(ORM create_all + 自建 SCHEMA_MIGRATIONS + agent_api 里的裸
CREATE TABLE)。这在 SQLite 下能活是因为 SQLite 的 DDL 极宽容;换 MySQL 后
agent_* 四张表的 `INTEGER PRIMARY KEY AUTOINCREMENT`、`TEXT DEFAULT ''`、
`TEXT PRIMARY KEY` 会全部建不出来,而失败被 `except: pass` 吞掉 —— 表现为
「经验库/动作库/会话功能静默失灵、日志里什么都看不到」。本期把它收敛成一套。

- core/models.py:
  * 新增 5 个 ORM 模型:AppMeta / AgentExperience / ExperienceAudit /
    AgentAction / AgentConversation(列名沿用历史,`app_meta.key` 在 MySQL 里是
    保留字,ORM 属性名用 k,读写统一走 db_config.meta_get/meta_set)
  * 新增 _long_text()(MySQL 用 MEDIUMTEXT,裸 TEXT 只有 64KB)与 _DOUBLE()
    (评分别用单精度 FLOAT);_migrate_schema 不再执行 DDL,只维护版本账本,
    SCHEMA_MIGRATIONS 的历史建表/加列条目 SQL 置 None
  * 新增 _sync_columns():用 sqlalchemy.inspect 比对模型与实表补缺列,取代原来
    靠 "duplicate column name" 报错文本判断的写法(换方言就失效)
  * _ensure_unique_indexes() 按方言分叉:MySQL 5.7 没有过滤索引,改用
    「虚拟生成列 + 唯一索引」复刻"空值不参与唯一约束"的语义
- web/agent_api.py: 删 4 段裸 CREATE TABLE;_ensure_* 收敛为 _ensure_tables()
  (db.create_all 薄封装,失败必记日志);app_meta 读写与 INSERT OR IGNORE
  改为方言中立
- core/device_discovery.py: app_meta 读写改用同一助手
- core/system_backup.py: SUMMARY_TABLES 由 db.metadata 派生 —— 备份覆盖红线
  从"靠人记"变成结构上不可能漏;CURRENT_SCHEMA_VERSION 改从 models 导入

验证(均在 users.db 的一致快照副本上,不碰真实库):
  空库 → 自建 12 张表 + 默认管理员 + 唯一索引 + schema_version=6;
  老库 → 逐表行数与真实库完全一致,app_meta 键未变;
  接口 → health/devices/jobs/groups/agent(经验库/动作库/会话) 全 200,
        会话增删与备份导出(12 张表、无覆盖缺失)正常。
2026-09-13 10:30:15 +08:00
butubb 79cdf61ed2 feat: 自动认领开关 + 全站按设备名称显示
一、指纹匹配自动认领(可选,默认关)
- 发现设置新增「指纹匹配自动认领」勾选(app_meta: discovery_auto_claim,默认 0)
- 打开后:扫描发现某设备指纹与池中已有记录一致(同一台换了 IP)→ 自动迁移记录到新地址
  并同步分组/任务引用,零点击;关闭时维持"识别自动 + 人工点一次确认"
- 默认关的原因:认领会改写分组/任务引用(数据结构变动),交人工确认更稳妥
- 扫描结果与状态行会显示本轮自动认领了几台

二、设备名称在界面上呈现(凡选择/展示设备处都显示名称)
- 新增前端 helper `devText(name, serial)`(base.js):有名称→「名称 · serial」
- 监控页设备表:名称加粗为主、地址作副行(未命名显示橙色提醒);任务概况的覆盖设备
  chip 也优先显示名称(tooltip 保留完整地址)
- AI 控制台:目标设备下拉、实时画面设备下拉、目标/运行中提示都带名称(serial→name 映射)
- 任务编辑器「指定设备」下拉、分组编辑的设备勾选列表:带名称
- 后端 `/api/devices` 新增 `items`([{serial,name,model}],`devices` 保持兼容);
  `/api/agent/devices` 增加 `name` 字段
- MCP `de_list_devices` 返回 `name`,并在工具说明与 Agent 系统提示里要求"汇报用名称、
  调工具用 serial"

文档:API.md(items/name/auto_claim + §6.2.1 名称呈现表)、MCP.md(工具返回)

自测(全通过):自动认领端到端(开开关→扫描→自动迁址 + 名称保留 + 分组/任务引用同步 +
待连接池清理 + 开关默认关且可持久化);名称显示浏览器验证(监控页/覆盖设备 chip/AI 目标与
观看下拉/任务编辑器/分组弹窗/发现设置开关);设备指纹与人工认领回归
2026-09-11 11:16:24 +08:00
butubb 0a1b4d6122 feat: 设备身份改为「名称 + 指纹」——更换 IP 自动认领,分组/任务引用自动同步
背景:设备池原先拿 serial(IP)当身份。设备一换 IP(DHCP 重新分配)旧记录就成了连不上的
僵尸条目(表现为"断联·自动重连中"但设备并没关机),分组与 serial 模式的任务还吊着死地址。
2026-09-11 实际发生:.70 变成 .71、.72 消失,平台两个条目永远连不上。

实现
- **设备名称必填且唯一**:加入设备必须填名称;库层面用部分唯一索引兜底
  (ux_device_name / ux_device_fingerprint,WHERE 非空 → 兼容历史空值),管理页可改名
- **设备指纹**(ro.serialno):网络设备添加/确认/扫描/采集型号时自动读取;
  身份三层拆分——名称(人可读,稳定)、指纹(机器识别,稳定)、serial(当前地址,可变)
- **自动认领**:添加或确认设备时指纹命中池中已有记录 → 迁移原记录到新地址
  (名称/型号/备注/启用状态/添加时间全保留),不新增条目
- **人工认领** `POST /api/devices/pool/relocate`:旧地址已断联、指纹没采过时的兜底——
  人工指认"这条就是那台,现在在 X",迁移并同步引用
- **引用同步**:认领/迁址时把 device_group.serials 与 task_job.target.serial 的旧地址
  换成新地址。⚠️ 必须同时改**内存**:分组/任务在 TaskManager 里另有内存副本且调度用内存对象,
  只改库不重启不生效 → device_pool 迁址后回调 TaskManager.sync_device_serial
  (装配层用 set_move_hook 注册;device_pool 不能反向 import task_manager,会循环依赖)
- **前端**:待连接池新增「识别」列(指纹命中时提示"≈ 名称(原 IP)",按钮变「认领为 X」);
  设备池新增「名称/指纹」列与「改名/换地址」操作;断联设备表也加「换地址」(用户看到断联就在这里)
- 添加设备接口改用 adb_connect_light(单次短超时),避免不可达 IP 让请求卡 30s+;重名校验提前到 adb 之前

文档:API.md §6 重写(设备身份/认领/新接口)、DATA_MODEL.md(新列 + v5/v6 迁移 + 唯一索引)、
ARCHITECTURE.md §4.0(身份三层与引用同步的内存坑)、DEPLOY.md 排查表加"断联但没关机"条目

自测(全通过):名称必填/唯一/改名/重名拒绝(含库层面约束);指纹采集(真实读到 .71 的
gy7lskwkkvj7c6b6);自动认领(指纹命中→迁址+保留名称+带指纹+未命中不误判);人工认领
(迁址+名称保留+分组与任务引用同步);浏览器验证设备池/断联表/待连接池三个界面
2026-09-11 11:03:38 +08:00
butubb 98cdc39224 chore: 删除抖音养号任务类型,平台只保留 generic_steps
tasks/douyin/(task_type=douyin_nurture)整体删除,唯一任务类型是 generic_steps。
所有默认值/文档/接口示例同步改成 generic_steps:

- tasks/:删 douyin 包;__init__ 只注册 generic;base.py/generic 注释改为照 generic 抄
- 默认值:core/models.py(列默认 + 旧 JSON 迁移默认)、core/task_manager.py TaskJob、
  web/tasks_api.py 建任务默认、static/admin/tasks.js 新建任务默认
- core/task_manager.py:去掉 douyin 专属的"清理废弃 comment 参数"迁移块,改为**启动时告警**
  仍残留已删类型的任务(只告警不改数据);run_job_now 对已删类型直接返回明确错误,
  不再"报已触发、线程里静默失败"
- 清理残留:douyin_running 状态位(无任何读取方)、core/__init__、core/actions/*、
  core/logger.py 注释里的抖音示例
- 文档:README(特性/目录树/类型表/参数表/示例)、TASK_DEV(目录树/注册说明/模板引用)、
  ARCHITECTURE(注册示例/action 注册表示例)、API.md(task_types 与任务 JSON 示例)、
  AI_TASK_GEN(P1 去掉 douyin 预设)、DEVELOPMENT
- 注:示例里"抖音"作为**App 名**(MCP 列应用、AI 建任务的需求举例)保留,与任务类型无关

自测(全部通过):类型列表只剩 generic_steps;建任务不传类型默认 generic_steps;传
douyin_nurture 被 400 拒;库里塞残留旧类型任务 → 启动日志告警 + 执行返回明确错误 +
不自动删用户数据;前端新建任务下拉 1 项且默认选中、界面建任务成功;监控页卡片两个按钮 +
覆盖设备正常。临时任务/数据验完已清理,任务集合复原。
2026-09-10 21:43:08 +08:00
butubb 86c65236a2 fix: 监控页任务概况去掉「编辑/删除」,只留执行/停用 + 显示覆盖设备
监控页的任务卡片原来带「编辑」,但 openTaskModal 依赖 _taskTypes/_groupsList/
_devicesList,这三个列表只在任务 Tab 的 loadTasks() 里加载——从监控页直接点编辑,
下拉框是空的(这就是"编辑有问题")。按需求把编辑/删除从监控页移除(编辑统一去任务 Tab),
每张卡片只留:执行任务 + 停用任务/启用任务。

并给卡片加「覆盖设备」一行:
- 后端 /api/jobs(含 POST/PUT 返回的 job)新增 coverage = {mode, serials, total}:
  按 target 定义解析(all=设备池全量 / group=分组∩设备池 / serial=该设备),
  不做离线过滤、不写调度日志(该接口每 5s 轮询),与调度用的 resolve_serials 口径差异已写入文档
- 前端用已在手的 /api/status 标注在线状态,渲染成彩色 chip:
  在线=蓝、运行中=黄+⏳、离线或不在池=灰划线+✕,完整 serial/型号放 tooltip
- 文档:API.md §5 补 coverage 字段与三种模式口径、ARCHITECTURE §5.1 补卡片说明

自测:coverage 三种模式端到端(含分组过滤池外 serial)全通过;Edge headless 验卡片
(2 个按钮、无编辑/删除、chip 文案/样式/tooltip)全通过;离线分支与任务 Tab 编辑器回归通过
2026-09-10 21:15:23 +08:00
butubb 4b5b836d31 feat: AI 控制台回答支持 Markdown 渲染 + 推理链可折叠 + token 用量显示
- markdown.js(新增,无 CDN 依赖):轻量 Markdown 渲染(标题/列表含嵌套/表格/
  代码块/引用/链接…),先 esc() 转义再套标记,模型输出的 HTML 只当文本显示
- agent.js:回答改走 Markdown;推理链改为 <details> 可折叠(流式时展开、正文开始
  自动收起、手动点过后不再自动改);单条消息 token 脚注 + 顶栏「本会话累计」
- monitor.html:消息结构加 .reasoning/.agent-usage、顶栏 token 徽标、md 相关样式,
  引入 markdown.js(base.js 之后、agent.js 之前)
- mcp_agent/agent.py:请求带 stream_options.include_usage,按「每次模型调用」累计
  usage(末尾 chunk),on_usage 回调吐累计值;网关不认该参数(400/422/点名)时
  自动降级重试一次
- web/agent_api.py:SSE 新增 usage 事件、done 带 usage;推理链与用量随会话落库
  (_REASONING_KEEP=6000 截断),回灌模型时只取 role/content
- 文档:API.md(usage 事件/done/会话消息字段)、ARCHITECTURE §5.4.1、DEVELOPMENT
  前端 JS 清单

自测:假模型端点单测 3/3(正常/降级/多轮累加);Edge headless 全链路 27 项全通过
(真实 Flask+SSE+SQLite,含 XSS 转义、刷新后回看);Markdown 渲染器 18 用例全通过
2026-09-10 18:22:20 +08:00
butubb 972db13f81 fix: 经验/动作蒸馏不再静默丢弃(关推理 + 质量门槛 + 截断容忍)+ 前端会话显示 ID
问题:蒸馏模型把 token 预算烧在 reasoning 上 → content 为空/被截断(finish_reason=length),
代码只读 content → 经验与动作被静默丢弃("使用小红书找苏州饭店"跑完什么都没存,动作侧日志
'原始输出 0 字符')。

修复(web/agent_api.py):
- 蒸馏调用统一加 "thinking": {"type":"disabled"}(该代理支持;实测关掉后 reasoning=0、
  配方 3/3 合格)——关键修复
- 配方:纯文本问法 + _recipe_ok 质量门槛(过短/含省略号占位丢弃,避免把提示词示例当真配方;
  曾因提示词里写了占位示例,模型照抄成 "1. …\n2. …" 存进库)+ 空则重试一次;
  仅动作提炼允许回退 reasoning_content(配方不回退,防思考草稿污染)
- 动作:JSON 输出 + _loads_lenient 截断容忍(逐对象抢救)+ {action,params} 形状归一 +
  输入/产出限量(≤10 步、≤3 动作×4 步);失败日志带样本
- 前端 agent.js:会话列表显示会话 ID 前 8 位(等宽小字),点击复制完整 ID(便于引用 conv=<id>)
- doc/ARCHITECTURE.md:§3.8 序号语义 + §5.4 蒸馏健壮性与会话 ID 说明
实测:真机复跑同一句需求 → 经验已保存(配方 196 字符)+ 动作经验已保存 2 条
2026-09-10 15:31:39 +08:00
butubb 91b2785c6b feat: 动作库查看/管理(接口 + AI 控制台「🎬 动作库」面板)+ 未沉淀灰卡提示
- 后端:GET /api/agent/actions(列表)、POST /api/agent/actions/delete、
  POST /api/agent/actions/save(新增/编辑,steps 走 _sanitize_actions 校验:
  type 白名单 + 必填 + **拒绝坐标 click_xy**),均 @admin_required
- 前端:AI 控制台右上角新增「🎬 动作库」按钮与模态框——列出 名称/app/别名/
  步骤摘要(含元素定位)/命中次数/更新时间;支持编辑(表单 + steps JSON)、删除、
  手动新建;保存失败(如写坐标)即时 toast 原因
- 未沉淀提示:本轮无可沉淀动作时也推送一张 🧠 动作经验 灰卡
  「本轮未沉淀动作(步骤以坐标定位为主,缺少可复用的元素定位信息)」
- doc/API.md:登记三个新接口
实测:列表返回自动沉淀的动作;保存合法动作 200;提交 click_xy 步骤被 400 拒绝;
删除生效
2026-09-10 13:58:38 +08:00
butubb 104964aa53 feat: 动作经验库(agent_action)——成功步骤蒸馏命名动作(带元素定位/禁坐标)+ 执行前召回注入
- 新表 agent_action(name/app/aliases/params/steps/preconditions/hits/时间),
  独立于人工维护的 custom_action(2B 决策):AI 自学动作不污染手建动作
- 沉淀:任务成功后从**成功**工具轨迹(_ACTION_TOOLS: open_app/tap_text/tap_element/
  type_text/clipboard/swipe/press_key/wake/sleep)用模型蒸馏为命名动作;steps 用
  编辑器 schema,**必须元素定位**(xpath/text/resourceId/description…),
  **显式剔除 click_xy 等坐标类**;on_tool 记录带 result 的结构化轨迹以判成败
- 兼容模型形状漂移:顶层 {action,params} 自动归一为 {name,steps};宽容 JSON 解析
  (围栏/尾逗号/中文引号/坏对象逐条抢救),实测模型常返回带语法错误的 JSON
- 召回:执行前按动作名/别名命中(或相似度≥0.34)取 top3,注入 system prompt
  「可复用动作」段(含元素定位),模型可跳过重新探索;hits 回写
- 文档同步:ARCHITECTURE §3.6(agent_action 表)、API.md(🧠 动作经验 伪卡片 + 动作库
  说明)、AI_TASK_GEN P1(沉淀进展)
实测:跑「打开抖音,点搜索」→ 沉淀「打开抖音」;下一轮同指令命中并注入;日志
「命中可复用动作 1 个」「动作提炼: 轨迹 5 步, 成功可沉淀 1 步」「动作经验已保存 1 条」
2026-09-10 13:53:04 +08:00
butubb 4e71f79a10 fix: 经验库命中数算错(单条恒显示 0)+ 蒸馏前缀误杀 + 命中卡片带摘要
- _find_experiences 改为返回 (注入文本, 命中配方列表):此前卡片用
  exp_ctx.count('\n- ') 计数,单条恒为 0、两条为 1(永远少 1),用户看到
  「命中 0 条」误判为没参考;现用 len(列表) 得真实条数并附配方摘要
- 新增 _clean_recipe():清洗模型可能加的「操作配方:」前缀,替换旧判定
  "配方" not in recipe[:50]——该判定与蒸馏提示词(要求以「操作配方」作答)
  自相矛盾,模型照做即被整条丢弃(静默),是经验存不下来的主因
- doc/API.md:SSE step 说明补 🧠 伪卡片语义(N 为实际条数 + 摘要)
实测:同一指令由「命中 0 条」变为「命中 1 条同类历史经验,已注入参考:<摘要>」
2026-09-10 13:41:10 +08:00
butubb 357fe98e22 fix: AI 控制台 MCP 不可达给出明确文案——不再显示 SDK 含糊报错
MCP 客户端(streamable_http)在工具服务不可达/返回非 MCP 响应时只抛
'Server returned an error response',用户无法判断原因。改为:
- 后端 web/agent_api.py:_execute 包裹 _load_tools/run_stream,识别连接类错误
  (Server returned an error response/ConnectError/refused 等)后抛明确文案:
  'MCP server(8033) 不可达:无法加载设备工具(<url>)。请确认 MCP server 已启动…'
- 前端 static/admin/agent.js 加 _friendlyAgentError() 兜底映射并去掉
  'RuntimeError:' 之类前缀,三处错误展示统一使用
- 文档同步:MCP.md(依赖提示+本机启动命令)、API.md(SSE error 文案说明)、
  DEPLOY.md(故障排查新增一行)
实测:停掉 MCP 复现 → 前端显示明确文案;启动 MCP 后 AI 控制台正常完成任务
2026-09-10 10:28:57 +08:00
butubb 55b1b74944 feat: 系统数据备份导出/导入后端——sqlite 在线快照 + apk 打包导出;上传校验预览→应用(自动快照当前库)+ 重启生效
导出:sqlite3 在线备份 API 对 data/users.db 做一致快照 → zip(users.db +
manifest.json:schema_version/逐表行数/apk 清单)+ 可选 data/apks/*.apk。
导出文件读入内存(BytesIO)发送后即删磁盘副本,避免 Windows 流式句柄锁残留。
导入:上传 zip/db → 暂存校验(integrity + 必需表 app_meta/user/task_job/
device_group + schema 版本提示)→ 确认应用:先自动快照当前库到
data/backups/pre_restore_*.db,再把备份落到 data/restore_pending/,由
web_server.py 在 init_db 之前 consume 换库——TaskManager 启动时才读库入内存、
Windows 不能热替换正被持有的库文件,故导入必须重启生效。

新增 web/system_api.py(export/preview/apply,全 @admin_required)与目录常量
BACKUP_DIR/RESTORE_STAGING_DIR/RESTORE_PENDING_DIR;.gitignore 排除运行产物。
2026-09-09 14:49:40 +08:00
butubb caa646e89c fix: 会话落库/加载缺 app context——后台线程 db 操作必须包 _flask_app.app_context()(历史加载与完成写回两处),否则静默失败(消息不落库、标题不生成) 2026-09-06 15:14:30 +08:00
butubb a77635c925 feat: AI 控制台历史会话(DeepSeek 式)——①agent_conversation 表:会话持久化(单表 JSON 消息),一轮 run 完成后 user/assistant 自动落库、新会话以首条消息作标题;②会话 API:列表/新建/详情/删除/重命名;③run 绑定 conversation_id:history 从会话加载(多轮上下文延续)、完成写回;④前端三栏布局:左侧会话栏(新建/列表/高亮/悬停删除/轮数时间)+ 聊天 + 实时画面,切换会话即切换上下文,刷新后自动恢复当前会话与消息 2026-09-06 15:12:56 +08:00
butubb 9b8ecd63f7 fix: AI 控制台切页/刷新不丢——①EventSource onerror 不再误 endRun(断网/后台节流/瞬时抖动都会触发 onerror 而后端任务仍在跑;EventSource 自动重连 + 服务端队列保留积压事件,重连后补发,连接彻底关闭才提示可刷新恢复);②页面刷新恢复:GET /api/agent/run 增加 run_id/answer/error/history,前端 restoreAgentView 渲染历史轮次、running 时自动重订阅事件流、done/error 展示结果——切走再回来任务与对话都在 2026-09-06 09:41:02 +08:00
butubb 0f75db289d feat: AI 目标设备必选 + 设备任务占用锁——①前端新增「🎯 目标设备」选择器(在线设备含型号,任务运行中的设备禁选),发送必须选定设备,AI 只操作你指定的设备;②/api/agent/run 校验:serial 不在池/离线/任务 running-connecting → 拒绝(409 提示任务名);③MCP 11 个写工具加 busy 锁(_ensure_device_free,5s 缓存):任务运行中的设备 AI 一律拒绝,AI 不与任务抢设备(外部 MCP 客户端同样受保护);④运行状态端点 GET /api/agent/run + 前端 8s 轮询:多人/多窗口能看到运行中任务(发起时间/任务/设备)并可停止(stop 加确认);未选设备直接 400 引导请选择设备,去掉模型自己乱挑设备的行为 2026-09-06 09:32:11 +08:00
butubb 850d2e2e66 feat: 最大执行步骤可配置——AI 控制台 ⚙ 配置弹窗新增「最大执行步骤」输入(1-200,默认 40),存 app_meta(agent_max_steps),Agent 线程运行时覆盖 agent.s.max_steps;此前仅环境变量 AGENT_MAX_STEPS 可改 2026-09-06 09:26:53 +08:00
butubb f7aa71cca4 fix: AI 控制台实时画面跟随 AI 所选设备——后端 step 事件 args 原为 str() 的 Python repr(单引号),前端 JSON.parse 失败导致 followSerialFromArgs 从未生效;改为保留对象序列化,前端兼容对象/字符串并修 step 卡片 args 展示(避免 [object Object]) 2026-09-06 09:21:03 +08:00
butubb edfd529c59 feat: 正式池断联设备自动重连——①扫描线程每轮顺带对断联的网络设备 adb_connect_light(幂等,连上即恢复,无需人工;此前只有启动时预连接一次,断联后不会自己回来);②断联设备不删不进待连接池(pending 语义=未授权新设备),GET discovery 返回 pool_offline(serial/型号/备注);③POST /api/devices/discovery/reconnect 手动立即重连;④发现面板加「设备池断联设备」区块(自动重连中 + 立即重连按钮) 2026-09-05 09:58:42 +08:00
butubb fbd01a056c fix: 经验巡检评审失败重试——模型偶发输出解释文字不带 JSON(实测 4/11 条评审失败自动保留),失败后重试一次并强调只输出 JSON;仍失败才保守保留 2026-09-04 16:19:23 +08:00
butubb 5681989194 feat: 经验库每日 AI 巡检 + 管理面板——①experience_audit 表:逐条评审(具体可执行/无过时坐标/非对话续语/配方与序列一致 checklist),keep 直接归档、delete 标 pending 待人工,绝不自动删;人工 kept 后不再重复建议;②web_server 注册每日 03:47 巡检 job(Asia/Shanghai);③API:经验列表(含巡检建议)/确认删除/保留/手动触发巡检;④AI 控制台右上角「🧠 经验库」面板:经验卡片 + 建议徽章与理由 + 删除(confirm)/保留按钮 2026-09-04 15:53:08 +08:00
butubb 9c700292c3 fix: 经验入库质量门槛——①对话续语/质疑/纠错轮次不再存为经验(prompt 过短、继续/还有等续语开头、你确定/还没有/不是吧等质疑词、无任务动词的疑问短句→弃),被用户纠正的失败轮次入库会教坏后续任务;②配方含不存在的 de_* 工具弃存(蒸馏模型编造 de_input 之类);③检索命中 hits 回写 +1,高频有效经验浮前 2026-09-04 15:43:21 +08:00
butubb 729f40893e feat: 经验写入即时提示——提炼+保存挪到 done 事件之前(原在 done 后执行、SSE 已关流,用户看不到),成功后推 🧠 经验记忆 step 卡片「已写入记忆库,下次相似任务自动参考」;_save_experience 返回成功与否;蒸馏超时 60s→25s 防收尾拖沓 2026-09-04 15:12:55 +08:00
butubb 4fad0b1198 fix: tap_text UI 树命中路径——u2 3.x 的 el.bounds 是方法不是属性,取 bounds 抛 TypeError 被吞导致永远走 OCR 兜底;改兼容写法 2026-09-04 14:36:34 +08:00
butubb 44a9dafcd8 fix: AI 点击精度优化——①坐标吸附:/api/screen/tap 加 snap=1,MCP de_tap 固定开启(dump UI 树找包含点击点的最小可点击元素点中心,模型坐标偏 20-50px 也点得准,大屏触控不带 snap 行为不变);②新 de_tap_text 语义点击:按屏幕可见文字一次完成找+点(UI 树 textContains/descriptionContains → OCR 中心兜底,WebView/图片文字也能点);③de_tap_element 支持 text_contains/desc_contains 模糊匹配;④de_ui_tree 可点击元素优先 + limit 参数防 token 膨胀;⑤agent 提示词重写:文字语义点击优先、坐标仅纯图形兜底且自动吸附、点击后无变化禁止重复同坐标 2026-09-04 14:29:17 +08:00
butubb 1560a13d86 fix: tool_calls 流中断自愈——400 配对错误时移除不完整段重试一次;加消息结构诊断日志 2026-09-04 13:43:20 +08:00
butubb c0c1be05f8 fix: 经验保存缺 app context(后台线程 db 报错被吞)——register_blueprints 注入 app,线程内 db 操作包 app_context 2026-09-04 13:37:33 +08:00
butubb 1ada51a7f6 feat: 自进化经验记忆——任务成功后用模型把工具序列提炼成「操作配方」存入 agent_experience 表;下次相似任务(bigram 相似度检索 top2)自动注入 system prompt 参考,AI 越用越聪明(同类操作不再摸索) 2026-09-04 13:33:30 +08:00
butubb 5145f83f06 feat: AI 控制台增强——①de_read_clipboard 剪贴板回读工具;②任务可中断(stop_event 循环检查点 + API stop + 前端■停止按钮);③智能滚动(回看历史不被打断,贴底才跟随);④右侧实时画面(MJPEG 流自动跟随操作设备,可手动选择) 2026-09-04 13:32:35 +08:00
butubb 44cab88546 fix: Agent 步骤上限优化——max_steps 20→40(多步任务如打开抖音搜索),system prompt 加收敛规范(无进展 6 步主动停止总结),超限时无工具收尾请求让模型给有意义总结(不再机械提示);web 超时 600→900s 2026-09-04 13:23:04 +08:00
butubb ce4e5464b9 feat: AI 控制台会话化 + 视觉优化——①多轮对话连续性(后端 history 持久化 12 轮,Agent 注入历史上下文,DeepSeek 式会话:新建会话才清空);②聊天区改浅色(DeepSeek 风格,图片文字清晰);③截图点击放大查看 2026-09-04 13:18:36 +08:00
butubb e24458a085 feat: AI 控制台升级为顶级 Tab——DeepSeek 风格聊天界面(气泡+流式渲染+思考折叠),实时 MCP 步骤卡片(工具/参数/截图缩略),SSE 流式输出(delta/step/done/error 事件),配置模态(模型/Key/设备前端可配);工具页旧子栏移除 2026-09-04 13:09:34 +08:00
butubb 25ef7e7746 fix: agent_api 同步 dev(事务/_execute 遮蔽/600s 超时) 2026-09-04 13:04:44 +08:00
butubb fea5a57fc3 feat(M2): Web AI 控制台——工具页新增子分栏:模型/Key/设备前端可配置(app_meta 存储,key 打码回显),指令执行 + 步骤流轮询(含截图缩略)+ 回答展示;Agent 加 on_step 回调、后台线程单实例运行 2026-09-04 12:54:17 +08:00
butubb 084ed59ad5 feat: 平台新增只读端点 /api/screen/size——返回设备屏幕原生分辨率(u2 window_size),供 MCP 截图坐标换算(截图是缩放图,操作需原生坐标) 2026-09-04 12:47:09 +08:00
butubb 4efe17c1c9 feat: 设备自动发现——定时扫描局域网+Tailscale 网段开放 adb 5555 的设备进入待连接池,用户确认后才加入正式设备池(不自动连接);socket 并发探测+adb 短超时验证(state=device 过滤 unauthorized),配置存 app_meta 可前端调整;配套:adb_connect_light 验证短超时、DISABLE_SCHEDULER 回归开关 2026-08-30 10:57:07 +08:00
butubb 1e39f5490a fix: 大屏触控优化——①点击错位:ctrl-img object-fit:contain 时图片绘制区小于盒子(居中黑边),坐标换算改为按实际绘制区域,黑边点击忽略;②滑动卡顿:tap/swipe/key/text 每次 u2.connect 重建连接(1-3s 握手),加 60s 连接缓存,失败自动清缓存重建(实测点击 2.97s→1.13s) 2026-08-20 13:22:21 +08:00
butubb 90ca2c2100 fix: 任务动作编辑器剪贴板注入改用 ClipInject 通道——u2 set_clipboard 在 Android 10+ 静默失效(粘贴模式必抛 UiAutomationError);提取 core/clipboard_helper 公共模块供工具页与任务执行端共用,粘贴模式 atx-agent pasteClipboard + set_text 兜底 2026-08-20 10:51:54 +08:00
butubb 5d637bd4f5 fix: 剪贴板注入改用 ClipInject 通道——u2.set_clipboard 在 Android 10+ 被系统静默丢弃(atx-agent 后台身份写剪贴板受限),改为 am start 透明 Activity(shell 身份启动不受后台限制),注入后 u2 读回比对验证,未装 APK 明确报错 2026-08-20 10:34:53 +08:00
butubb e0c3373dd1 feat: 全量路由回归测试脚本 scripts/regression_test.py(GET 全量 + 写操作空 body 探测抓 import 遗漏 + 关键业务 POST,500=缺陷 502/503=业务提示)+ fix: clear_all_worker_errors 缺导入(回归脚本首跑即抓到) 2026-08-19 15:24:11 +08:00
butubb 2adc54b512 fix: adb 相关功能全挂——tools_api 缺 ADB_PATH/_ADB_BLOCKED_PATTERNS 导入(模块拆分遗漏),应用版本/adb终端/剪贴板恢复,全量 28 个 GET 路由回归通过 2026-08-19 15:12:42 +08:00