Files
auto_control/doc/AI_TASK_GEN.md
T
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

243 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI 建任务(AI Task Generator)设计文档
> 状态:**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 个工具)。
> 关联代码:`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` 语义点按),流式回放步骤。
- **任务系统 + 步骤编辑器**:`generic_steps` 任务 = 一棵可嵌套步骤树(open_app/click/swipe/loop/group/if_el…18 种节点),在编辑器里拖拽编排、单步试跑、定时调度。
目标:让**非工程用户用一句自然语言需求**(例:「创建一个每日养号刷视频的任务,每天 8:00-9:00 在 100.100.10.13 跑」)得到**一条可直接调度、可继续在现有步骤编辑器里手改的任务**。AI 先自己在设备上打开 App、看 UI 树、确认可点元素,再直接撰写编辑器的步骤 JSON。
### 产品体验(一页)
1. 用户进入「AI 控制台 → 模式=AI 建任务」,选一台**空闲**设备,填需求 +(可选)任务名/调度/目标。
2. AI 自探并**流式回放**(工具卡 + 截图,与现在一致):开 App → dump UI 树 → 确认要点的元素可命中 → 写步骤。
3. 完成后平台返回任务草稿 `draft`,服务端 schema 校验后,**前端直接打开现有「新建任务 → 步骤编辑器」**预填。
4. 用户核对/手改/单步试跑 → 保存 → 进任务列表,走原有调度器执行。
**核心信任原则: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` 起后台线程、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`。
- 前端 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)。
- 单步试跑:`/api/steps/test`(tasks_api.py L249)+ `editor.js _testStep`;`tasks/generic/task.py test_step`(L633)。
- 任务/调度/目标:`core/task_manager.py` `TaskJob`(L121-199);`POST /api/jobs`(tasks_api.py L68)。
- 设备占用语义:MCP 写工具 `_ensure_device_free`(mcp_server.py L60-78,running/connecting → `device_busy`);`agent_api` run 入口同样对 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 契约
```json
{
"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_value`
- `open_app / stop_app` → `params.package`
- 容器:`loop / group` → `params.children`(非空);`if_el` → `params.then`(`else` 可选)
- `swipe_until` → direction + max_swipes;`click_xy` P0 不产(如允许则 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.html` AI 控制台加模式切换;建任务模式下输入栏旁有折叠「任务设置」(名称/调度时间/目标 serial·分组·全部/备注)。
- `agent.js`:done 携带 draft 后:
- generic_steps → 调 `openTaskModal()`(tasks.js)并把 `draft.task` 灌入步骤编辑器(step 卡片可视化、可拖改、单步试跑、保存);
- 弹窗内对 `notes`(含“需人工复核/OCR 兜底”项)给出醒目提示。
- 过程回放沿用现有 `agent-toolcard` 渲染;可标记当前为 designer 轮以便后续区分。
## 5. 约束与安全(红线)
### 5.1 Designer 自探规则(写入提示词)
1. 先 `de_open_app(package)`,再 `de_ui_tree` + `de_screenshot` 看每屏;点到关键状态后再 dump 下一屏。
2. 每个将写入步骤的目标元素,先用 `de_tap_element`(by=text/id/desc…)+ 截图**确认可命中**,并记下证据。
3. **不做破坏性动作**:需“评论/发送”时只确认输入框/发送键存在,不真发;产物里这类步骤 `probability` 调低并在 notes 标注“请人工复核”。
4. 探索步数上限(P0 建议 30 步),可被 `/stop` 打断;结束后尽力还原前台 App。
5. 未命中的元素一律不进任务;拿不准的进 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 的**预制件**复用。
- **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 实现后自测)
1. 目标设备空闲时:需求「每日 8-9 点刷抖音养号」→ 生成 generic_steps 任务,步骤为 `open_app → loop(wait+swipe)` 结构,调度 cron_stop 8-9 点。
2. 打开编辑器中该任务:步骤卡片完整、可拖改、单步试跑命中;保存后任务列表出现且下次运行时间正确。
3. 反例:模型产出含 `click_xy` 或未知 type / 空 selector → 服务端校验拦截并让模型补正;busy 设备入口 409。
4. 探索全程可在前端回放(工具卡+截图),未发送真实评论/未污染设备状态。
## 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/` 共用(防双份漂移)。
### 9.1 现状盘点
- MCP(`mcp_server/mcp_server.py`,19 个 `de_*`)= 设备控制 + 设备只读 + `de_list_tasks`(平台任务只读)。
- 任务创建/修改/删除、toggle、立即运行、分组、设备池管理、自定义动作、APK、系统备份等 **REST 路由只给前端用,未暴露 MCP**。
### 9.2 P0 —— AI 建任务链路真正需要的「平台工具」(最小集)
只补两小类,其余设备操作全部复用现有 `de_*`:
1. **只读清单**(供自探确定 target/能不能做):
- `list_task_types` / `list_groups`(target 选 group 用)/ `list_pool`(可调度设备,含 busy 状态)
- (可与现有 `de_list_devices` 合并语义,避免重复)
2. **校验 + 提交(结束性)**:
- `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 同时消费会互相瓜分事件)。
### 10.3 附带修掉的一个协议 bug
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 还没做(P1 / 后续)
- 「探索完直接创建」接口(服务端复验后 `add_job`)——目前一律走人工确认。
- draft → `agent_experience` / `agent_action` 沉淀(模板重用)。
- MCP 侧补 `de_snapshot`(截图+元素树一次取齐,见 `doc/research/U2_ELEMENT_SELECTORS.md`)。
- 多设备并行探索、成本控制。
### 10.5 自测怎么跑
1. 纯逻辑:`core/task_draft` 的正反例(未知 type / 空 selector / `click_xy` / cron 少段 /
深度 6 / probability 越界 …),断言 `errors` 文案模型能读懂。
2. 真机:AI 控制台 →「AI 建任务」→ 选空闲设备 → 描述需求 → 看回放 → 核对草稿 →
「在步骤编辑器中打开」→ 单步试跑 → 保存。
3. 反例:需求里出现"自动评论并发送" → 草稿的这类步骤应是"只核对存在性"(`notes` 里提示
人工复核),**探索期审计日志里不应有对发送键的 `de_tap_*`**。
4. 自测产生的东西(草稿/自建任务)用后即删,别碰用户真实数据。