用户报的"探索完无法点击创建任务"真因:一个 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 条用例、扇出单元用例、本地工具契约用例全绿。 自测产生的任务/草稿已全部清理(未碰用户既有数据)。
22 KiB
AI 建任务(AI Task Generator)设计文档
状态:P0 已实现(2026-09-13)——§1~§9 是设计稿(与实现基本一致), §10 是实现记录:差异、落地细节、怎么自测,以 §10 为准。 现状请读:AI_CONSOLE.md(AI 控制台已实现的能力)、TASK_DEV.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)通过 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。
产品体验(一页)
- 用户进入「AI 控制台 → 模式=AI 建任务」,选一台空闲设备,填需求 +(可选)任务名/调度/目标。
- AI 自探并流式回放(工具卡 + 截图,与现在一致):开 App → dump UI 树 → 确认要点的元素可命中 → 写步骤。
- 完成后平台返回任务草稿
draft,服务端 schema 校验后,**前端直接打开现有「新建任务 → 步骤编辑器」**预填。 - 用户核对/手改/单步试跑 → 保存 → 进任务列表,走原有调度器执行。
核心信任原则:AI 只“提案”,不直接建库、不直接执行最终任务;最终落库/执行都在用户确认后由现有机制完成。
2. 范围(P0 定稿,2026-09-09 与用户确认)
- 入口:AI 控制台加「聊天 / AI 建任务」模式开关,复用会话/SSE/设备选择/停止/刷新恢复骨架。不新建顶级 Tab。
- 产出方式:AI 直接撰写编辑器的步骤 JSON(非操作轨迹翻译)。自探信息只作上下文与审计;探索中的误点/多余截图不会混入任务。
- 任务类型:只产
generic_steps(通用步骤)——平台当前唯一的任务类型 (原douyin_nurture预设参数生成已随该类型删除,不再做)。 - 定位原则:全部基于 UI 树:
- 模型定位/点击一律走
de_ui_tree拿到的元素(text/id/description/text_contains),多实例用 index,或后端uiauto_helper生成的//*[@resource-id=..][k]XPath; de_tap_element确认能命中后才写进click步骤;- 默认不产
click_xy坐标点击;UI 树给不了的元素记入 notes,交给人工/OCR 兜底; - 滑动用方向语义
swipe{direction},不用像素坐标。
- 模型定位/点击一律走
- 产出完整任务信封:
{name, task_type:"generic_steps", target, schedule, retry, enabled, params:{max_duration, steps}}。
3. 现状与可复用点(实现依据)
- 多模态 Agent 链路:
web/agent_api.py(POST /api/agent/run起后台线程、SSEdelta/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.pySTEP_TYPES(L44-83)/DEFAULT_PARAMS(L86-103) 与static/admin/editor.jsSTEP_LIB(L3-23)。 - 元素抓取 → XPath:
web/tasks_api.py /api/uiauto/elements(L224)→core/uiauto_helper.get_elements(suggested.value即编辑器可用的 XPath)。 - 单步试跑:
/api/steps/test(tasks_api.py L249)+editor.js _testStep;tasks/generic/task.py test_step(L633)。 - 任务/调度/目标:
core/task_manager.pyTaskJob(L121-199);POST /api/jobs(tasks_api.py L68)。 - 设备占用语义:MCP 写工具
_ensure_device_free(mcp_server.py L60-78,running/connecting →device_busy);agent_apirun 入口同样对 worker running/connecting 拒绝(409)。 - 经验库自进化:
agent_experience+ bigram 检索注入 + 每日巡检(web/agent_api.py)——P1 沉淀模板的现成载体。
4. 架构与数据流
需求+设备(空闲) ──▶ Designer Agent(自探, 模式=designer)
│ de_open_app / de_ui_tree / de_screenshot / de_tap_element…
▼
平台工具 submit_task(draft) ← 结束性调用
│ 服务端 draft schema 校验(白名单+必填+深度≤5)
▼
前端: 打开 openTaskModal 步骤编辑器, 预填 draft.task
│ 用户核对/手改/单步试跑(/api/steps/test)
▼
POST /api/jobs → 任务列表(原调度器执行)
(P1) draft+需求 沉淀 agent_experience, 相似需求注入参考
4.1 Designer Agent(新增模式,复用现有 Agent)
mcp_agent增加 designer 系统提示词:角色=自动化任务设计师;行为约束见 §5.1。- 增加平台级工具(不属设备
de_*):submit_task(draft):结束性工具,模型完成自探后提交草稿即停止,服务端立即校验。
- 会话内同时记录结构化 trace(每轮
on_tool的{tool, args 精简, 屏号/证据}),用于:校验证据(每个 selector 来自哪次树)、审计、P2 回放。 POST /api/agent/run增加mode:"designer";done事件负载携带draft(校验通过)或draft_error(校验失败+原因,让模型补一轮)。
4.2 draft 契约
{
"summary": "每日8-9点刷抖音养号:开抖音→循环(看5~35s+上滑)+随机间隔",
"task": {
"name": "抖音每日养号",
"task_type": "generic_steps",
"target": {"mode":"serial","serial":"100.100.10.13:5555"}
| {"mode":"group","group_name":"测试"} | {"mode":"all"},
"schedule": {"mode":"once"} | {"mode":"cron","cron":"0 8 * * *"}
| {"mode":"cron_stop","cron":"0 8 * * *","stop_cron":"0 9 * * *"},
"retry": {"max_attempts":5,"delay":30},
"enabled": true,
"params": {
"max_duration": 0,
"steps": [
{"type":"open_app","label":"打开抖音","params":{"package":"com.ss.android.ugc.aweme","wait_home":true}},
{"type":"loop","params":{"loop_mode":"rounds","max_iterations":30,"children":[
{"type":"wait","params":{"min":5,"max":35}},
{"type":"swipe","params":{"direction":"up","duration_min":0.25,"duration_max":0.5}}
]}}
]
}
},
"notes": ["评论按钮需先进入视频页才可见"],
"evidence": [{"screen":"抖音首页","element":{"text":"关注","id":"..."},"xpath":"//*[@resource-id=\".../gvo\"]"}]
}
- 步骤节点结构:
{id?, type, label?, params};id执行端忽略(编辑器重新生成),params必填。 - 必填字段语义(空值会被执行端静默跳过,校验器必须拦):
click / long_click / swipe_until / wait_el / if_el→params.selector_valueopen_app / stop_app→params.package- 容器:
loop / group→params.children(非空);if_el→params.then(else可选) swipe_until→ direction + max_swipes;click_xyP0 不产(如允许则 x/y 0-100)- 节点公共可选
params.probability(0-100,缺省 100)
selector_type允许值:xpath / description / text / resourceId / descriptionContains / className(if_el可ocr)。
4.3 服务端新增
core/task_draft.py:STEP_TYPES白名单 + 每类必填/深度校验validate_steps(steps, depth)(嵌套≤5);validate_draft(draft):任务信封(name 非空、task_type==generic_steps、target mode ∈ {all,group,serial}(group 名存在)、schedule cron 合法、steps 校验);- 归一化:把
schedule里「每天 8-9 点」这类由前端/向导填的值转成 cron/cron_stop。
web/taskgen_api.py(或并入agent_api,推荐并入以最大化复用):- 入口检查:serial 必须、设备在池/在线、worker 非 running/connecting(409,与现有语义一致);
- 起 designer 后台线程;SSE 事件在现有
delta/step/done/error基础上,done可带draft。
- 注意:现有
POST /api/jobs对 params 盲存(只校验 name+task_type)。AI 通道在保存前必须过validate_steps,避免「任务 done 但什么都没做」(执行器对未知 type/空 selector 静默跳过)。
4.4 前端
monitor.htmlAI 控制台加模式切换;建任务模式下输入栏旁有折叠「任务设置」(名称/调度时间/目标 serial·分组·全部/备注)。agent.js:done 携带 draft 后:- generic_steps → 调
openTaskModal()(tasks.js)并把draft.task灌入步骤编辑器(step 卡片可视化、可拖改、单步试跑、保存); - 弹窗内对
notes(含“需人工复核/OCR 兜底”项)给出醒目提示。
- generic_steps → 调
- 过程回放沿用现有
agent-toolcard渲染;可标记当前为 designer 轮以便后续区分。
5. 约束与安全(红线)
5.1 Designer 自探规则(写入提示词)
- 先
de_open_app(package),再de_ui_tree+de_screenshot看每屏;点到关键状态后再 dump 下一屏。 - 每个将写入步骤的目标元素,先用
de_tap_element(by=text/id/desc…)+ 截图确认可命中,并记下证据。 - 不做破坏性动作:需“评论/发送”时只确认输入框/发送键存在,不真发;产物里这类步骤
probability调低并在 notes 标注“请人工复核”。 - 探索步数上限(P0 建议 30 步),可被
/stop打断;结束后尽力还原前台 App。 - 未命中的元素一律不进任务;拿不准的进 notes 而非硬编。
5.2 平台级约束
- 自探/试跑只在用户选的空闲设备(busy → 409),杜绝与运行中任务在设备上物理打架。
- AI 不直接建库;生成任务仍需用户点保存(POST /api/jobs 现有权限)。
- 尽量不写死坐标;P0 默认禁
click_xy,产物以树元素定位为主。
6. 里程碑
- P0(本设计主体):designer 模式 → 自探(UI 树定位)→ 直接撰写 generic_steps draft → 服务端 schema 校验 → 前端步骤编辑器预填确认保存。验收:一句话在真实设备上生成一条可调度的 generic_steps,步骤全部来自 UI 树且编辑器可打开。(平台只剩 generic_steps 一种任务类型,与原规划一致)
- P1:模板沉淀:把 draft+需求写入
agent_experience(新列存结构化 steps 或 JSON),相似需求注入参考;整链「演示试跑」(把 steps 在设备上以受控方式跑一遍并截图回报,需新增端点,复刻device_busy拒绝语义)。- 进展(2026-09-10):动作级沉淀已落地——
agent_action表 + 从成功步骤蒸馏"命名动作"(steps 用编辑器 schema、带元素定位、禁坐标)+ 执行前按名/别名召回注入(web/agent_api.py)。AI 建任务可直接把这些动作当作 generic_steps 的预制件复用。
- 进展(2026-09-10):动作级沉淀已落地——
- P2:自定义动作支持(内联展开成 group,或新增
action_ref节点 + 执行器/编辑器同步);多设备并行;成本与 token 控制。
7. 实现时需新增/改动文件(规划)
- 改:
mcp_agent/(designer 提示词与submit_task工具、结构化 trace)、web/agent_api.py(mode=designer、done 带 draft)、static/admin/agent.js+templates/admin/monitor.html(模式切换/任务设置/draft 预填)、doc/(本文档关联)。 - 新:
core/task_draft.py(schema+校验+归一化)、(可选)web/taskgen_api.py。 - 不动:任务执行器、调度器、
POST /api/jobs主体(保持现有盲存,只在 AI 通道校验)。
8. 验收(P0 实现后自测)
- 目标设备空闲时:需求「每日 8-9 点刷抖音养号」→ 生成 generic_steps 任务,步骤为
open_app → loop(wait+swipe)结构,调度 cron_stop 8-9 点。 - 打开编辑器中该任务:步骤卡片完整、可拖改、单步试跑命中;保存后任务列表出现且下次运行时间正确。
- 反例:模型产出含
click_xy或未知 type / 空 selector → 服务端校验拦截并让模型补正;busy 设备入口 409。 - 探索全程可在前端回放(工具卡+截图),未发送真实评论/未污染设备状态。
9. 需要转成 MCP / 平台工具的能力(分层,2026-09-09 与用户确认)
背景问答结论:目前 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,20 个de_*)= 设备控制 + 设备只读 +de_list_tasks(平台任务只读)。 - 任务创建/修改/删除、toggle、立即运行、分组、设备池管理、自定义动作、APK、系统备份等 REST 路由只给前端用,未暴露 MCP。
9.2 P0 —— AI 建任务链路真正需要的「平台工具」(最小集)
只补两小类,其余设备操作全部复用现有 de_*:
- 只读清单(供自探确定 target/能不能做):
list_task_types/list_groups(target 选 group 用)/list_pool(可调度设备,含 busy 状态)- (可与现有
de_list_devices合并语义,避免重复)
- 校验 + 提交(结束性):
submit_task(draft)→ 服务端用 共用core/task_draft.validate_steps()/validate_draft()校验,不直接落库,返回 draft 供前端打开步骤编辑器预填、人工确认后走POST /api/jobs。
关键:P0 的 Agent 不暴露任务 CRUD 写权限(create/update/toggle/run),否则模型可绕过"AI 提案 → 人工确认"直接入库,破坏信任模型。
9.3 P1 —— 外部自动化 / 后续 Agent 的「写 MCP」(受权限约束,逐块加)
若目标延伸为"外部程序能像调 REST 一样操控平台",则按此清单逐个补(每加一个都做权限+busy+审计+校验下沉):
- 任务:
create_job/update_job/delete_job/toggle_job/run_job_now/query_jobs - 分组:
list_groups/create_group/update_group/delete_group - 设备池:
pool_list/pool_add/pool_remove/pool_toggle - 自定义动作 / APK 清单 等视使用再加
- 只读清单优先搬;写类确认有真实消费方再做,避免空转。
9.4 分层与登记(红线)
- 每新增/修改/删除一个 MCP 工具或平台配置:同步更新
doc/MCP.md(全清单)、doc/MCP_DESIGN.md(规格/层级),并在提交里体现——见全局「doc 同步红线」。 core/task_draft.py是 web 校验与 MCP 校验的唯一来源,杜绝两套规则漂移。
10. 实现记录(2026-09-13)
10.1 与设计稿的差异(以本节为准)
| 设计稿 | 实际实现 | 为什么 |
|---|---|---|
| 聊天页加「模式」开关 | AI 控制台下的独立子分栏「🧭 AI 建任务」 | 用户要求独立页面;探索回放 + 草稿预览需要自己的版面 |
submit_task 作为平台工具(§9.2,未指定放哪层) |
Agent 本地工具(mcp_agent/agent.py 的 LOCAL_TOOL_SPECS),不进 MCP Server |
放进 MCP 等于给外部客户端开"写任务"的口子,还要连带改 MCP.md/MCP_DESIGN.md 与审计;收益为零 |
| draft 经前端灌进步骤编辑器 | 同上,但草稿先落 app_meta(agent_task_draft,只留最近一份) |
刷新/重进页面能拿回来;不新建表,绕开备份覆盖红线(SUMMARY_TABLES / DEPLOY §3.5) |
| (未提) | designer 跑不绑会话、不吃聊天历史,且不沉淀经验/动作 | 探索轨迹里有试错与误点,沉淀会污染记忆库;"draft→经验"归到 P1 |
| —— | 校验失败把 errors 原样回灌模型,最多重提 3 次(提示词约束) |
这是"AI 写坏任务"的唯一闸门:POST /api/jobs 对 params 盲存、执行器又静默跳过错误步骤 |
10.2 关键实现点
- 校验器:
core/task_draft.py。validate_draft()逐条复刻执行器的"静默跳过点" (未知 type / 空 selector / 空 children / 嵌套 >5 / 深度节点数 >60 / cron 非法 / 必填 params 缺失 /click_xy直接拒)→ 返回可被模型读懂的errors+ 不拦的warnings。normalize_draft负责兜底(任务名、target、schedule、retry、时长);页面上的任务设置 以overrides形式优先于模型给的。 - 提示词:
mcp_agent/agent.py的DESIGNER_SYSTEM_PROMPT(16 条规则:先看再动、 定位优先级、禁坐标、禁序号型 XPath、副作用动作只核对不真点、时长语义映射、 随机化三件套、规模上限、收尾方式)。 - 护栏:单工具调用上限 40 次(
Agent.tool_call_limit;总步数由max_steps兜底, 两个值都要大于正常重试次数,否则会把一轮探索截断在"改字段"上——实测踩过);输出上限 8192(designer);json.loads容错(草稿被截断时返回可读错误而不是整轮崩)。 - 前端:
static/admin/taskgen.js+#agent-sub-taskgen(子 Tab 机制见static/admin/base.js的showSubTab)。草稿预览能直接点「在步骤编辑器中打开」→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
(1) 事件被另一个页面抢走(用户报的"探索完没法创建任务"的真因):一轮的事件原先只有一个
queue.Queue,聊天页与建任务页同时开着时,两个 EventSource 瓜分同一条队列——建任务页的回放
会卡在中间,done 事件被聊天页取走 → 永远等不到草稿 → 页面上没有可点的"创建"。
现改为扇出(web/agent_api.py 的 _Fanout):每个订阅者一个专属队列,多开页面各看各的;
前端两侧也按 mode 门控,不互相订阅。
(2) 工具消息配对
Chat 模式同样受益:一轮里若模型同时调了 de_screenshot 与别的工具,旧实现会把截图图像
作为一条 user 消息插在两条 tool 消息之间,模型侧判定"工具回应不足"直接 400
(An assistant message with 'tool_calls' must be followed by tool messages…)。
现改为:本轮 tool 消息发完后再附一条 user 图像消息;_repair_tool_messages 也改成
只数连续的 tool 消息。
10.4 后续批次(2026-09-14 已完成的三项)
- ✅ 「直接创建」:
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 §4.2。
10.5 仍未做
- 多设备并行探索、探索成本/token 控制(一轮真机探索 3~30 万 token)。
- 把动作库当 generic_steps 的预制件复用(设计稿 §9.2 的只读清单类 MCP 工具)。
- 平台的
/api/uiauto/snapshot目前只被 MCP 与抓取弹窗用,还可在 designer 里做"同屏缓存"。
10.6 自测怎么跑
- 纯逻辑:
core/task_draft的正反例(未知 type / 空 selector /click_xy/ cron 少段 / 深度 6 / probability 越界 …),断言errors文案模型能读懂。 - 真机:AI 控制台 →「AI 建任务」→ 选空闲设备 → 描述需求 → 看回放 → 核对草稿 → 「在步骤编辑器中打开」→ 单步试跑 → 保存。
- 反例:需求里出现"自动评论并发送" → 草稿的这类步骤应是"只核对存在性"(
notes里提示 人工复核),探索期审计日志里不应有对发送键的de_tap_*。 - 自测产生的东西(草稿/自建任务)用后即删,别碰用户真实数据。