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 条用例、扇出单元用例、本地工具契约用例全绿。
自测产生的任务/草稿已全部清理(未碰用户既有数据)。
This commit is contained in:
2026-09-14 08:14:11 +08:00
parent 46e6ea1f37
commit ed9e8bacb1
18 changed files with 384 additions and 67 deletions
+33 -12
View File
@@ -2,13 +2,13 @@
> 状态:**P0 已实现**(2026-09-13)——§1~§9 是设计稿(与实现基本一致),
> **§10 是实现记录**:差异、落地细节、怎么自测,以 §10 为准。
> 现状请读:[AI_CONSOLE.md](AI_CONSOLE.md)(AI 控制台已实现的能力)、[TASK_DEV.md](TASK_DEV.md)(任务与步骤)、[MCP.md](MCP.md)(已实现的 19 个工具)。
> 现状请读:[AI_CONSOLE.md](AI_CONSOLE.md)(AI 控制台已实现的能力)、[TASK_DEV.md](TASK_DEV.md)(任务与步骤)、[MCP.md](MCP.md)(已实现的 20 个工具)。
> 关联代码:`web/agent_api.py`、`mcp_server/`、`mcp_agent/`、`tasks/generic/`、`static/admin/editor.js`。最后核对:2026-09-10。
## 1. 背景与目标
平台已有两套能力,但互不相通:
- **AI 控制台**:一句话 + 选设备 → 多模态 Agent(DeepSeek)通过 19 个 `de_*` 工具在手机上「边看边做」(截图看屏、`de_ui_tree` 拿元素树、`de_tap_element/de_tap_text` 语义点按),流式回放步骤。
- **AI 控制台**:一句话 + 选设备 → 多模态 Agent(DeepSeek)通过 20 个 `de_*` 工具在手机上「边看边做」(截图看屏、`de_ui_tree` 拿元素树、`de_tap_element/de_tap_text` 语义点按),流式回放步骤。
- **任务系统 + 步骤编辑器**:`generic_steps` 任务 = 一棵可嵌套步骤树(open_app/click/swipe/loop/group/if_el…18 种节点),在编辑器里拖拽编排、单步试跑、定时调度。
目标:让**非工程用户用一句自然语言需求**(例:「创建一个每日养号刷视频的任务,每天 8:00-9:00 在 100.100.10.13 跑」)得到**一条可直接调度、可继续在现有步骤编辑器里手改的任务**。AI 先自己在设备上打开 App、看 UI 树、确认可点元素,再直接撰写编辑器的步骤 JSON。
@@ -36,7 +36,7 @@
## 3. 现状与可复用点(实现依据)
- 多模态 Agent 链路:`web/agent_api.py`(`POST /api/agent/run` 起后台线程、SSE `delta/step/done/error`、`/stop`、刷新恢复 `GET /api/agent/run`)→ `mcp_agent/agent.py`(OpenAI 兼容流式,工具经 fastmcp Client 拉 8033 的 19 个 `de_*`)→ `mcp_server/mcp_server.py`。
- 多模态 Agent 链路:`web/agent_api.py`(`POST /api/agent/run` 起后台线程、SSE `delta/step/done/error`、`/stop`、刷新恢复 `GET /api/agent/run`)→ `mcp_agent/agent.py`(OpenAI 兼容流式,工具经 fastmcp Client 拉 8033 的 20 个 `de_*`)→ `mcp_server/mcp_server.py`。
- 前端 AI 控制台骨架:`templates/admin/monitor.html` `#tab-agent` + `static/admin/agent.js`(设备选择、`sendAgentMsg`、`listenStream` 渲染 `agent-toolcard`、实时画面跟随)。
- 步骤权威 schema(前后端同构):`tasks/generic/task.py` `STEP_TYPES`(L44-83)/ `DEFAULT_PARAMS`(L86-103) 与 `static/admin/editor.js` `STEP_LIB`(L3-23)。
- 元素抓取 → XPath:`web/tasks_api.py /api/uiauto/elements`(L224)→ `core/uiauto_helper.get_elements`(`suggested.value` 即编辑器可用的 XPath)。
@@ -155,10 +155,10 @@
## 9. 需要转成 MCP / 平台工具的能力(分层,2026-09-09 与用户确认)
> 背景问答结论:目前 MCP 只有**设备层 19 个 `de_*`**(控制 + 只读 `de_list_tasks`),平台 CRUD(任务增改/启停/立即运行、分组、设备池、自定义动作、APK、备份、用户)**都还没 MCP 化**。方向认同「先把工具链补完善」,但不做"把所有平台 CRUD 一次性搬成 MCP"的大而全——**按消费方(AI 建任务 / 外部自动化)分层、按需补**。新增 MCP 工具一律:进 `doc/MCP.md` 手册 + `doc/MCP_DESIGN.md` 规格 + 与 web 同源的权限/busy/审计 + 校验逻辑下沉到 `core/` 共用(防双份漂移)。
> 背景问答结论:目前 MCP 只有**设备层 20 个 `de_*`**(控制 + 只读 `de_list_tasks`),平台 CRUD(任务增改/启停/立即运行、分组、设备池、自定义动作、APK、备份、用户)**都还没 MCP 化**。方向认同「先把工具链补完善」,但不做"把所有平台 CRUD 一次性搬成 MCP"的大而全——**按消费方(AI 建任务 / 外部自动化)分层、按需补**。新增 MCP 工具一律:进 `doc/MCP.md` 手册 + `doc/MCP_DESIGN.md` 规格 + 与 web 同源的权限/busy/审计 + 校验逻辑下沉到 `core/` 共用(防双份漂移)。
### 9.1 现状盘点
- MCP(`mcp_server/mcp_server.py`,19 个 `de_*`)= 设备控制 + 设备只读 + `de_list_tasks`(平台任务只读)。
- MCP(`mcp_server/mcp_server.py`,20 个 `de_*`)= 设备控制 + 设备只读 + `de_list_tasks`(平台任务只读)。
- 任务创建/修改/删除、toggle、立即运行、分组、设备池管理、自定义动作、APK、系统备份等 **REST 路由只给前端用,未暴露 MCP**。
### 9.2 P0 —— AI 建任务链路真正需要的「平台工具」(最小集)
@@ -215,8 +215,18 @@
`openTaskModal(null, prefill)` 预填(用唯一 draftKey,避开 localStorage 旧草稿覆盖)。
- **两个页面共用一个运行槽**:`GET /api/agent/run` 回 `mode`,两个前端各按 mode 决定
是否订阅 SSE(一个 run 只有一个事件队列,两个 EventSource 同时消费会互相瓜分事件)。
- **落库出口**:页面上的「✓ 直接创建任务」(`POST /api/agent/task_draft/create`)与
「在步骤编辑器中核对」(预填编辑器 → 用户点保存)——两条路最终都汇到 `add_job`。
### 10.3 附带修掉的一个协议 bug
### 10.3 附带修掉的两个 bug
**(1) 事件被另一个页面抢走**(用户报的"探索完没法创建任务"的真因):一轮的事件原先只有一个
`queue.Queue`,聊天页与建任务页同时开着时,两个 EventSource **瓜分**同一条队列——建任务页的回放
会卡在中间,`done` 事件被聊天页取走 → 永远等不到草稿 → 页面上没有可点的"创建"。
现改为**扇出**(`web/agent_api.py` 的 `_Fanout`):每个订阅者一个专属队列,多开页面各看各的;
前端两侧也按 `mode` 门控,不互相订阅。
**(2) 工具消息配对**
Chat 模式同样受益:一轮里若模型同时调了 `de_screenshot` 与别的工具,旧实现会把截图图像
作为一条 `user` 消息**插在两条 tool 消息之间**,模型侧判定"工具回应不足"直接 400
@@ -224,14 +234,25 @@ Chat 模式同样受益:一轮里若模型同时调了 `de_screenshot` 与别
现改为:本轮 tool 消息发完后再附一条 user 图像消息;`_repair_tool_messages` 也改成
只数**连续**的 tool 消息。
### 10.4 还没做(P1 / 后续)
### 10.4 后续批次(2026-09-14 已完成的三项)
- 「探索完直接创建」接口(服务端复验后 `add_job`)——目前一律走人工确认。
- draft → `agent_experience` / `agent_action` 沉淀(模板重用)。
- MCP 侧补 `de_snapshot`(截图+元素树一次取齐,见 `doc/research/U2_ELEMENT_SELECTORS.md`)。
- 多设备并行探索、成本控制。
- ✅ **「直接创建」**:`POST /api/agent/task_draft/create` + 页面上的按钮与「探索完直接创建任务」
勾选框。两条设计约束:草稿体只存在于服务端(客户端只能传 overrides,少一个可篡改入口);
创建前**再校验一次**,通过后走与「新建任务」完全相同的 `context.mgr.add_job`,
创建成功后清草稿(同一份草稿不会被重复建成多个任务)。
- ✅ **草稿沉淀经验/动作**:designer 轮次也走 `_distill_experience` / `_distill_actions`
沉淀,但**只在草稿通过校验时**(说明这轮探索确实走通了一条路;没草稿的试错不入库)。
- ✅ **MCP `de_snapshot`**:截图+元素树一次取齐(20 个 `de_*` 工具了),并把
`screen_state` 一并返回;designer/聊天两套提示词都改为**优先用 de_snapshot**。
见 [MCP.md](MCP.md) §4.2。
### 10.5 自测怎么跑
### 10.5 仍未做
- 多设备并行探索、探索成本/token 控制(一轮真机探索 3~30 万 token)。
- 把**动作库**当 generic_steps 的预制件复用(设计稿 §9.2 的只读清单类 MCP 工具)。
- 平台的 `/api/uiauto/snapshot` 目前只被 MCP 与抓取弹窗用,还可在 designer 里做"同屏缓存"。
### 10.6 自测怎么跑
1. 纯逻辑:`core/task_draft` 的正反例(未知 type / 空 selector / `click_xy` / cron 少段 /
深度 6 / probability 越界 …),断言 `errors` 文案模型能读懂。