需求:任务编辑器里能单独配"这个任务每隔 N 秒检查一次"——熄屏就点亮、某个元素
出现就通知、掉出 App 就停本设备;通知标题正文要能自己写。
配置与执行分离(这是本次的关键设计):
- **配置是任务级的**(`params.watchers`),在任务编辑器单独一块,不进步骤画布;
- **执行是穿插的**:worker 每执行完一步、以及长等待的每个分片,看一眼哪个巡检
到点了。不起线程 → 不需要并发模型,也不会和主流程抢屏幕(两边同时点屏幕会
互相打断)。代价是精度受步长影响(某步卡 30s,巡检最多晚 30s),已在文档写明。
- core/patrol.py(新):检查项/动作注册表(CHECKS/ACTIONS)+ evaluate/act。
检查:屏幕熄灭/亮着、元素存在/不存在、前台是/不是某 App;
动作:只通知、点亮、息屏、停止本设备。屏幕走 `dumpsys power`(0.3s,
不用 d.info——那玩意在部分设备要 14s),前台走 d.app_current()(0.7s)。
- tasks/generic/task.py:`_maybe_patrol` / `_run_patrol`(冷却、命中记一条
步骤明细、发通知);**文案在动作之前渲染**——点亮后 {screen} 就成了"亮屏",
用户要看的是"发现熄屏,已点亮"。
- 任务编辑器新增「公共巡检」块(static/admin/tasks.js)+ 样式;保存进 params.watchers。
- 通知:新增事件 `task.patrol.hit`(巡检命中)与 `task.notify.custom`(步骤发通知);
给了 title 就用它当标题(不再拼前缀),level 字段可点名级别。
顺带(巡检需要的原语,也可单独用):
- if_el 条件判断支持 `selector_type=screen`(亮/熄)与 `foreground`(前台包名);
非元素条件不参与「选择器健康」统计(否则会攒出假的"选择器失效"告警)。
- 新增两个步骤:`notify`(发自定义通知)、`stop_self`(停本设备,记"被停止"
而不是失败,不触发重试)。步骤类型 18 → 20,相关文档计数一并更新。
修 bug:`wait` 步骤在巡检耗时超过剩余时间后 `sleep(负数)` 抛
"sleep length must be non-negative"(真机联调抓到,已 clamp 到 0)。
自测:假设备单测 12 组(命中/冷却/间隔/元素/前台/停止/静默/异常不炸);
真机联调:熄屏→点亮(False→True)+ 通知文案正确、掉出抖音按间隔命中 5 次、
长等待里穿插生效且步骤回到 ok、清理后用户通知配置原样恢复。
文档:TASK_DEV §4.5(含两个可抄的例子)与步骤表/条件类型、NOTIFY §3、README。
268 lines
22 KiB
Markdown
268 lines
22 KiB
Markdown
# 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)(已实现的 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…20 种节点),在编辑器里拖拽编排、单步试跑、定时调度。
|
||
|
||
目标:让**非工程用户用一句自然语言需求**(例:「创建一个每日养号刷视频的任务,每天 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 的 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)。
|
||
- 单步试跑:`/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 只有**设备层 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_*`:
|
||
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;服务端用 `_Fanout` 给**每个订阅者一条自己的队列**(谁都不丢事件)。
|
||
- **切 Tab 不丢回放**:`initTaskGen()` 每次进入子页都会 `tgRestore()`——已经在盯同一轮就
|
||
**不重复订阅**(否则服务端会把这一轮从头补发一遍,卡片成两份);换了一轮/刚刷新过页面才清空
|
||
回放区重新订阅(`_Fanout` 的历史缓冲会把已发生的事件补回来)。跟随画面的 MJPEG 长连接
|
||
在切回来时重新拉一次(`tgRearmLive`),避免定格。
|
||
- **落库出口**:页面上的「✓ 直接创建任务」(`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](MCP.md) §4.2。
|
||
|
||
### 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` 文案模型能读懂。
|
||
2. 真机:AI 控制台 →「AI 建任务」→ 选空闲设备 → 描述需求 → 看回放 → 核对草稿 →
|
||
「在步骤编辑器中打开」→ 单步试跑 → 保存。
|
||
3. 反例:需求里出现"自动评论并发送" → 草稿的这类步骤应是"只核对存在性"(`notes` 里提示
|
||
人工复核),**探索期审计日志里不应有对发送键的 `de_tap_*`**。
|
||
4. 自测产生的东西(草稿/自建任务)用后即删,别碰用户真实数据。
|