workflow-design · diff
git:20260821.c0c683c to git:20260829.9b78830
146 added, 388 removed. Audit C to C.
---
title: 工作流设计经验手册
id: workflow-design
type: skill
- tags: [workflow, 设计, 经验, 调试, 闭环]
+ tags: [workflow, 设计, 编排, schema, 调试, 闭环]
---
# 工作流设计经验手册
- > **阶段门禁、产出规范、步骤 schema、检查清单、验收标准均已内置在 WorkAgent L2 提示词中,本文档不复制。**
- > 本文档只提供 L2 没有的差量内容:实战经验 + 检索/提炼方法 + 陷阱详情 + 场景模式。
+ > 完整工作流编排能力:步骤 schema、变量语法、条件表达式、params 固化、设计模式、验证闭环、经验闭环。
+ > L2 提示词只给阶段门禁与交互纪律;本文档是可执行的方法论全集。
---
- ## 〇、闭环总览
-
- > 四阶段流程(查→探→固→验)和门禁条件见 L2,此处强调经验积累闭环:
+ ## 〇、编排闭环总览
```
接收任务
- │
- ├─→ [查] ui_maps_search 检索同类应用/操作经验
- │ 已有经验?→ 直接复用布局参数和操作链,跳过重复探索
- │
- ├─→ [探] 逐屏探索 → ui_maps_save_screen 固化布局
- │
- ├─→ [固] 生成 params.json + workflow.json + guide.md
- │
- ├─→ [验] dry_run 校验 → workflow_run 执行验证
- │ 跑通?→ 提炼经验 → ui_maps_save_experience → 闭环
- │ 失败?→ 分析原因 → 修正参数 → 重新执行
- │
- └─→ [馈] 异常回写 exceptions,新发现补充到经验
+ ├─ [查] ui_maps_search 检索同类经验 → 有 screen/experience 直接复用,跳过重复探索
+ ├─ [探] 逐屏探索 → ui_maps_save_screen 固化布局(每屏经用户确认)
+ ├─ [固] 生成 params.json + workflow.json + guide.md(参数即契约,全部有界面证据)
+ ├─ [验] workflow_validate 编译校验 → workflow_run 执行 → 连续 3 次一致 + 至少一个异常路径
+ └─ [馈] 跑通后 ui_maps_save_experience 提炼经验;新异常回写 params.json exceptions
```
- **核心原则**:每次设计都是对经验库的充实。不重复探索已被验证的路径。
-
---
- ## 一、阶段差量要点
-
- > 各阶段的目标/退出条件见 L2。此处只补充 L2 没写的实战细节。
-
- ### 阶段 0:复用检索
-
- 检索方法与决策见「二、经验检索指南」。骨架有 screen → 跳过阶段一布局解析;有 experience → 参考 tool_chain 调参复用;无结果 → 完整探索。
+ ## 一、步骤 Schema(V2,唯一真相源 `src/workflow/step_schema.json`)
- ### 阶段 1:探索跑通
+ ### 公共字段
- **探索前先对齐**(硬性门禁,逐项确认后才能进入探索):
+ | 字段 | 必填 | 说明 |
+ |------|------|------|
+ | `id` | ✅ | 步骤唯一标识(断点续连按 id 跳过已完成步骤) |
+ | `name` | ✅ | 人类可读名称 |
+ | `description` | 可选 | 详细说明 |
+ | `on_error` | 可选 | `"abort"`(默认) / `"skip"` / `{retry:{max,backoff_ms?,backoff_multiplier?}}` / `{allow_codes:{codes:[...]}}` |
+ | `capture` | 可选 | **字符串**:步骤输出存入变量名(无对象格式) |
+ | `timeout_secs` | 可选 | 步骤超时秒数 |
+ | `do` | ✅ | 动作定义,仅下列一种 key |
- ```
- 对齐清单:
- □ 目标应用/页面的启动/打开方式?(需用户手动打开还是工作流自动?)
- □ 目标界面的触发方式?(快捷键/点击/URL/API?)
- □ 目标界面的关闭/退出方式?(Esc/遮罩/X按钮/自动消失?)
- □ 操作流程中哪些步骤需要用户介入?(登录/验证码/确认对话框?)
- □ 是否有不可逆操作需要用户确认?(删除/发送/提交?)
- □ 是否有已知的异常状态或边界情况?(弹窗/加载延迟/权限不足?)
- ```
+ ### 动作类型(do 的 13 种形态)
- 确认后记录到 `params.json` 的 `exceptions` 或其他对应字段。禁止把功能约束误判为坐标/定位问题。
+ | kind | 写法 | 要点 |
+ |------|------|------|
+ | tool | `{"tool":"desktop_mouse","with":{...}}` | with 支持 `{{var}}` 模板 |
+ | seq | `{"seq":[...]}` | 顺序容器,可嵌套 |
+ | loop | `{"loop":{"for_each"\|"repeat"\|"until":..., "max":100, "do":[...]}}` | until=条件**满足即停**;max 防死循环 |
+ | if | `{"if":{"condition":{...},"then":[...],"else":[...]}}` | else 可省略 |
+ | call | `{"call":"wf-id","with":{"inputs":{...},"outputs":{...}}}` | inputs 传子变量;outputs 子变量名→父变量名回写 |
+ | wait | `{"wait":"提示语","auto":[...]}` | auto=等待期自动执行步骤(可选) |
+ | chat | `{"chat":"LLM任务描述","with":{...}}` | LLM 决策节点(原 chat_agent) |
+ | script | `{"script":{"runtime":"python"\|"node"\|"ahk"\|"pwsh","code":"...","cwd":"?"}}` | code 支持 `{{var}}` 替换 |
+ | assert | `{"assert":{"condition":{...},"message":"?"}}` | on_error=skip 时变非阻断校验 |
+ | mcp | `{"mcp":{"server":"key","tool":"name","with":{...}}}` | server 对应 servers.yaml |
+ | sleep | `{"sleep":N}` | 秒,0.1–3600 |
+ | break | `{"break":true}` | 跳出当前循环 |
+ | continue | `{"continue":true}` | 跳过当前迭代 |
- **浏览器页面(snapshot + screenshot 互补)**:
- 1. `browser_snapshot` 获取页面骨架和 @eN ref
- 2. snapshot 未返回目标元素 ref → 立即 `browser_screenshot` → `desktop_vision` 分析
- 3. 两个工具互相补盲,缺什么补什么。**不换 JS 策略重试。**
+ ### chat 步骤 with(LLM 决策配置)
- **反爬预检(浏览器第一屏必须执行)**:
- `browser_screenshot` → `desktop_vision` 检测页面是否正常渲染。
- 检测词:登录/验证码/Captcha/空空如也/请先登录。
- 命中 → 立即标记 exceptions,预设 `wait` 步骤让用户介入。不尝试绕过。
- 1688/淘宝/京东/拼多多等电商平台默认视为反爬平台。
+ `agent_id`(ChatAgentConfig ID)/ `screenshot`(执行前截图注入)/ `tools`(白名单)/ `knowledge`(知识库路径)/ `model` / `temperature` / `max_tokens` / `system_prompt` / `persona` / `goal` / `constraints` / `requirements` / `max_iterations`(ReAct 最大轮数,**与 max_steps 语义重叠,统一用 max_iterations**)
- ### 阶段 2:params.json 字段规范
+ ### 容器 on_error
- | 字段 | 内容 |
- |------|------|
- | `workflow_id` | 工作流唯一标识 |
- | `window` | 尺寸、URL、标题模式 |
- | `login_detection` | 登录态判定逻辑 |
- | `regions` | 区域定义 + 定位特征 |
- | `navigation_graph` | 屏间跳转关系 |
- | `exceptions` | 异常路径 + 降级策略 |
+ seq / loop / if 容器同样支持 on_error;子步骤失败且容器设置了 skip 时继续执行容器内后续步骤。
---
- ## 二、经验检索指南
-
- **两级搜索**:先骨架后详情,不会爆 output。
-
- **第一级 — 骨架**(发现有哪些可用资源):
- ```
- ui_maps_search(query="微信 im")
- → [{ app_name: "微信", app_category: "im",
- matched_screens: [{ screen_name: "chat-list", region_count: 3 }, ...],
- matched_experiences: [{ id: "send-msg", name: "发消息", keywords: [...] }, ...]
- }]
- ```
+ ## 二、变量与模板语法
- **第二级 — 详情**(精确获取完整数据):
- ```
- ui_maps_search(query="微信", screen_name="chat-list")
- → [{ app_name: "微信",
- matched_screens: [{ screen_name: "chat-list", regions: [{ name, rect, elements: [...], anchor: {...} }, ...] }],
- matched_experiences: [{ id, name, tool_chain, summary: "完整原文", ... }] // 仅 screen_ref=="chat-list" 的经验
- }]
- ```
+ ### 三套引用,边界必须分清
- ### 什么时候查
+ | 写法 | 语义 | 适用 |
+ |------|------|------|
+ | `{{var}}` | 模板替换。**整串**时保留原始类型(数字/布尔/对象不字符串化);**内嵌文本**(如 `"x={{var}}px"`)时字符串化 | with 参数值、script code |
+ | `{{var \| get "f"}}` / `{{var \| json "k"}}` / `{{var \| len}}` / `{{ENV:HOME}}` / `{{var \| default "v"}}` | 管道表达式 | 同上 |
+ | `{params.window.url}` | **单花括号**,仅整串引用 params.json 字段,返回原始类型;点号路径下钻 | 引用固化参数 |
+ | `{ "var": "name" }` | 对象形式变量引用(VarRef),支持点号路径 `{ "var": "coords.x" }` | 条件表达式、loop.for_each.items |
- | 时机 | 示例 |
- |------|------|
- | 开始新设计前 | `ui_maps_search(query="微信 im")` — 查 app 有没有已有 screen |
- | 遇到类似操作 | `ui_maps_search(query="发送消息 enter")` — 查操作经验 |
- | 定位困难 | `ui_maps_search(query="图标 tooltip hover")` — 查定位经验 |
- | 跨应用参考 | `ui_maps_search(app_category="im", query="发送")` — 限定类别 |
+ > 坑:坐标字段若内嵌 `{{x}}` 会字符串化,务必整串引用或用 `| get` 下钻保持数字类型。
- ### 怎么用查到的结果
+ ### 变量池来源
- 1. **有 matched_screens** → 直接用 regions 数据(name/rect/description/elements),跳过阶段一布局解析
- 2. **有 matched_experiences** → 参考 tool_chain 和 summary,调整参数复用
- 3. **都没匹配** → 进入完整探索,结束后 save_screen + save_experience 补充经验库
+ `workflow_run(inputs)` 注入 → params.json(`{params.x}` 兑现)→ 各步骤 `capture` 写入。子工作流有独立变量池,靠 `call.with.inputs/outputs` 跨池传递。
---
- ## 三、经验提炼指南
-
- ### 什么值得存为经验
-
- - ✅ 非显而易见的操作序列(如 hover→等2s→小范围OCR→点击)
- - ✅ 跨应用可复用的模式(如登录检测模式)
- - ✅ 曾被踩过的坑及解决方案
- - ✅ 特殊定位技巧
- - ❌ 常规操作(如 browser_navigate 打开网页)
- - ❌ 一次性的特定操作
- - ❌ 已经在提示词中充分覆盖的内容
-
- ### 经验摘要(summary)写法
-
- 好的 summary 遵循:**做什么 → 怎么做 → 为什么这样做 → 跨应用适用性**
-
- ```
- 微信桌面版发送文字消息:
-
- 搜索框 desktop_input 输入联系人名称 → enter 直接进入对话窗口
- (非传统思路:省略了 OCR 定位联系人列表 → click 选中 → click chat-area 获焦,省 2-3 步)
+ ## 三、条件表达式(Condition,12 种)
- chat-area 已自动获焦,直接 desktop_input 输入文字 → enter 发送。
+ 二元([VarRef, 值] 两元数组):`equals` / `not_equals` / `contains` / `starts_with` / `regex` / `gt` / `lt` / `gte` / `lte`
+ 一元:`not_empty` / `empty`
+ 恒真:`always`
- 关键判断:微信搜索框输入正确联系人后 enter,会直接跳转对话且 chat-area 自动获焦。
- 可跨应用:即时通讯桌面应用中,凡搜索框支持 enter 直达对话的,此模式适用。
+ ```json
+ { "if": { "condition": { "equals": [ { "var": "login_status" }, "LOGGED_IN" ] }, "then": [...] } }
```
- 差的 summary:`"微信发送消息:点击联系人,输入文字,回车发送。"`
-
---
- ## 四、产出规范
-
- ### 目录结构(严格约定)
-
- ```
- plugin/workflows/{workflow.id}/
- ├── workflow.json # 主工作流定义,id 字段 = 目录名
- ├── params.json # 固化参数(可选,UIMap 兼容格式)
- └── guide.md # 使用文档(可选)
- ```
-
- - 目录名 = `workflow.json` 中的 `id` 字段
- - `params.json` 若存在,需包含 `app_name`、`process_name`、`window.title_pattern` 等窗口定位字段(UIMap 兼容格式)
- - 不要依赖 `wf.name` 作为目录名(name 可能含空格/中文)
- - runtime 自动在根目录生成的 `{name}.json`/`{name}.md` 为内部缓存,不手动创建
-
- ## 五、常见陷阱
+ ## 四、params.json 固化规范
- > 以下 5 条的警示已内置 L2(关键警示 + 检查清单),此处不再展开:
- > 跳过布局解析直接找元素 / 窗口尺寸未固化 / 探索阶段写步骤 / 用 if contains 文案做登录检测 / 忘记 SPA 状态残留。
+ | 字段 | 内容 |
+ |------|------|
+ | `workflow_id` | 工作流唯一标识 |
+ | `window` | 尺寸 / URL / 标题模式(**窗口尺寸必须固化**,W2) |
+ | `login_detection` | 登录态判定特征 |
+ | `regions` | 区域定义 + 定位特征(每参数有界面证据) |
+ | `navigation_graph` | 屏间跳转关系 |
+ | `exceptions` | 异常路径 + 降级策略(探索中异常即时记录,W4) |
- ### 把 tooltip OCR 截图截到其他窗口
- **症状**:hover 图标后 OCR 识别出桌面其他窗口的文字。
- **正确做法**:先 `desktop_window_info` 获取客户区边界,截图范围约束在客户区内。
+ 模板骨架:
- ### 降级路径导致数据丢失
- **症状**:重试时重复提交表单。
- **正确做法**:重试前检测当前状态,已完成步骤跳过。
+ ```json
+ {
+ "workflow_id": "demo-flow",
+ "window": { "title_pattern": "App 标题", "width": 1280, "height": 800 },
+ "login_detection": { "indicator": ["登录", "Sign in"] },
+ "regions": [{ "name": "chat-list", "rect": { "x": 0, "y": 100, "w": 300, "h": 600 }, "anchor": { "type": "text", "value": "会话" } }],
+ "navigation_graph": { "chat-list": { "to": ["chat-window"], "trigger": "click-contact" } },
+ "exceptions": [{ "condition": "登录弹窗", "fallback": "wait 用户介入" }]
+ }
+ ```
---
- ## 六、复杂场景模式
+ ## 五、设计模式库
- ### 模式 A:登录态检测与自动登录
+ ### 模式 A:登录态检测守卫
```json
- { "id": "login_guard", "name": "登录保障", "do": { "seq": [
+ { "id": "guard", "name": "登录保障", "do": { "seq": [
{ "id": "check", "name": "判断登录态",
"do": { "chat": "根据 login_detection 特征判断是否已登录", "with": {
- "agent_id": "login-checker",
- "screenshot": true,
- "requirements": ["只输出 LOGGED_IN 或 LOGIN_REQUIRED"]
- } },
+ "agent_id": "login-checker", "screenshot": true,
+ "requirements": ["只输出 LOGGED_IN 或 LOGIN_REQUIRED"] } },
"capture": "login_status" },
{ "id": "branch", "name": "按登录态分支",
"do": { "if": {
"condition": { "equals": [ { "var": "login_status" }, "LOGIN_REQUIRED" ] },
"then": [
- { "id": "goto", "name": "打开登录页",
- "do": { "tool": "browser_navigate", "with": { "url": "{params.login.url}" } } },
- { "id": "manual", "name": "手动登录",
- "do": { "wait": "请完成登录后点击继续" } }
+ { "id": "manual", "name": "手动登录", "do": { "wait": "请完成登录后点击继续" } }
],
- "else": []
- } } }
+ "else": [] } } }
] } }
```
- 要点:语义判断(chat 步骤 + screenshot)而非文案匹配;登录动作优先 `wait` 让用户完成,不碰凭据。
- ### 模式 B:for_each 循环遍历
+ 要点:**语义判断**(chat+screenshot)而非文案匹配;登录动作交用户,不碰凭据。
+ ### 模式 B:for_each 遍历
+
```json
- {
- "id": "tour",
- "name": "面板巡览",
- "do": {
- "loop": {
- "for_each": { "items": { "var": "panels" }, "as": "p" },
- "max": 100,
- "do": [
- { "id": "click", "name": "点击",
- "do": { "tool": "desktop_mouse",
- "with": { "action": "click", "x": "{{p | get \"ix\"}}", "y": "{{p | get \"iy\"}}" } },
- "on_error": "abort" }
- ]
- }
- }
- }
+ { "id": "tour", "name": "遍历", "do": { "loop": {
+ "for_each": { "items": { "var": "panels" }, "as": "p" },
+ "max": 100,
+ "do": [ { "id": "hit", "name": "点击", "do": { "tool": "desktop_mouse",
+ "with": { "action": "click", "x": "{{p | get \"ix\"}}", "y": "{{p | get \"iy\"}}" } } } ]
+ } } }
```
- 要点:
- - `for_each.items` 引用变量(`{ "var": "panels" }`),`for_each.as` 指定当前迭代项变量名,子步骤中通过 `{{p | get "field"}}` 引用
- - `max` 可选,默认 100,防止死循环
- - `with` 字段支持 `| get` 管道;`code` 字段(脚本步骤)同样支持(v0.1 起)
-
### 模式 C:表单提交 + 结果验证
```
seq: 提交
- ├─ 填写 + 提交
+ ├─ 填写 + 提交(desktop_input 输入+发送一次调用,不拆分)
├─ browser_wait_for(结果页元素)
├─ assert: 成功标志存在
- └─ if: 失败 → screenshot → chat 步骤分析 → 决定重试或终止
+ └─ if: 失败 → screenshot → chat 分析 → 重试或终止
```
- ### 模式 D:桌面应用多窗口操作
+ ### 模式 D:多窗口操作
- ```
- seq: 跨窗口
- ├─ desktop_windows_list → 记录所有 hwnd
- ├─ 窗口 A 操作 → desktop_window_activate(hwnd_a) → ...
- ├─ 窗口 B 操作 → desktop_window_activate(hwnd_b) → ...
- ```
- hwnd 会变化,每次操作前重新 windows_list。
+ 每次操作前 `desktop_windows_list` 重取 hwnd(hwnd 会变)→ activate → 操作。
### 模式 E:大工作流拆分
- **何时拆**:单层步骤 > 15 个或嵌套深度 > 3 层时,拆分为子工作流。
+ 单层 >15 步或嵌套 >3 层 → 拆子工作流。主文件含 `call` 步骤,子工作流 JSON 同目录。用 `with.outputs` 回传结果。
- ```
- 主工作流: 子工作流 login-flow:
- seq seq
- ├─ call: login-flow ├─ browser_navigate(login)
- ├─ call: fetch-data ├─ browser_type(user)
- ├─ call: send-msg ├─ browser_type(pass)
- └─ call: logout └─ browser_click(submit)
- ```
+ ### 模式 F:SPA 搜索框提交(优先级从高到低)
- **产出方式**:先 Write 主 workflow.json(含 call 步骤),再逐个 Write 子工作流 JSON 到同目录。不要试图在一轮对话中输出所有 JSON。
+ 1. `browser_snapshot` 找 @eN ref → click
+ 2. 无 ref → `browser_screenshot` → OCR 找按钮坐标 → JS 模拟点击
+ 3. 都不行 → `browser_evaluate` 触发 form.submit()(最后手段)
- ### 模式 F:SPA 搜索框提交
+ ### 模式 G:验证码/滑块
- **优先级从高到低**(视觉定位优先,不依赖页面 JS 实现):
- 1. `browser_snapshot` 找到搜索按钮 `@eN` → `browser_click`
- 2. 无 ref → `browser_screenshot` → OCR 找按钮坐标 → JS 模拟点击坐标
- 3. 以上都不行 → `browser_evaluate` 触发 `form.submit()`(最后手段)
+ `desktop_mouse_drag` 起点→终点;无法自动处理 → `wait` 用户介入并标记 exceptions。
---
- ## 七、工具使用要点
-
- `desktop_input` 输入和发送是一次调用,不要拆成两步。普通文本直接用,不用剪切板。
- 剪切板只用于 >500 字的大段文本,用后清空。敏感内容禁用剪切板。
-
- ### 坐标体系
-
- | 工具 | 输入坐标系 | 输出坐标系 |
- |------|-----------|-----------|
- | `desktop_perceive` | 图片像素坐标(取决于截图来源) | 同左 |
- | `desktop_window_screenshot` | — | 客户区像素 + `screen_x`/`screen_y` 偏移 |
- | `desktop_screenshot(region)` | — | 区域像素 + `screen_x`/`screen_y` 偏移 |
- | `desktop_mouse` (无 hwnd) | **屏幕绝对坐标** | — |
- | `desktop_mouse` (有 hwnd) | 屏幕绝对坐标 | — |
-
- **规则:永远用屏幕坐标调用 `desktop_mouse`。**
- perceive 结果 = 客户区坐标时,手动加截图返回的 `screen_x`/`screen_y` 偏移。
-
- ### 工具选择速查
-
- | 场景 | 首选 | 备选 |
- |------|------|------|
- | 定位网页元素 | `browser_snapshot` → @eN ref | `browser_screenshot` + OCR |
- | 桌面布局解析 | Vision 全窗口语义分析 | `desktop_find_text`(需字库) |
- | 定位桌面文字坐标 | 按 Vision 划定的功能区精准定位 | `desktop_find_text`(需字库) |
- | 定位桌面图标 | hover + 小范围 Vision OCR tooltip | `request_user_input(icon_confirm)` |
- | 等待网页加载 | `browser_wait_for(selector)` | `system_sleep`(不得已) |
- | 验证页面状态 | `browser_snapshot` + `chat` 步骤 | `browser_extract` 文本匹配 |
- | 验证桌面状态 | `desktop_window_screenshot` + OCR | `desktop_find_image` 模板匹配 |
- | 查经验/布局 | `ui_maps_search(query="微信 im")` — 返回 screens + experiences | `Read` 直接读 ui-maps JSON |
- | 跨应用参考 | `ui_maps_search(app_category="im", query="发送")` | 不限类别模糊搜索 |
-
- ### 浏览器 snapshot + screenshot 配合流程
+ ## 六、验证闭环方法论
```
- browser_snapshot(骨架 + ref)
- ↓
- 目标元素有 ref?
- ├─ 有 → browser_click / browser_type
- └─ 无 → browser_screenshot → desktop_vision 分析
+ workflow_validate(编译校验:步骤合法性/工具名/必填/变量引用/call 环)
+ → 干净环境 workflow_run(第 1 次:探路,记录偏差)
+ → 分析异常 → 修正 params / workflow(设计缺陷才改文件;运行时阻塞就地解决)
+ → 重跑(同 id 断点续连,禁新建复制)→ 连续 3 次结果一致
+ → 至少触发一个异常路径验证降级
```
- **snapshot 拿不到 ref,立刻截图。不换 JS 策略重试。**
- ### DOM 数据提取规则
-
- - **卡片边界**:用语义 class 限定(如 `[class*="offer-item"]`),不用固定层级 `closest`
- - **图片提取**:按 `data-src` → `data-original` → `src` 顺序 fallback
- - **数据隔离**:先 `querySelector` 限定卡片范围再正则。禁止全卡片 `textContent` 跨产品污染
-
- ### 连续失败熔断
-
- 同类操作失败 ≥3 次 → 停止 → 判断类别:
-
- | 类别 | 动作 |
- |------|------|
- | 工具限制 | 换工具配合链 |
- | 定位失败 | `request_user_input(region)` |
- | 反爬拦截 | `request_user_input` 让用户处理 |
-
- ### browser_extract 返回空
+ 验收:跑通 ∧ 3 次一致 ∧ 异常路径生效 ∧ 降级不丢数据不重复提交 ∧ 无敏感数据残留。
- 不重复调用。改用:
- `browser_screenshot` → `desktop_vision(prompt:"提取页面所有可见文本")`
+ **运行时故障恢复**:失败 → 识别阻塞(验证码/弹窗/登录态/网络)→ 就地解决(browser_*/desktop_* 同会话,状态保留)→ 同 id 续跑。同一步骤同阻塞连续 3 次失败 → 停止,用 completed_steps 汇报。
---
- ## 八、Step JSON 速查附录
-
- ### loop(for_each)
-
- ```json
- { "id": "l1", "name": "遍历列表",
- "do": { "loop": { "for_each": { "items": { "var": "items" }, "as": "it" },
- "max": 100, "do": [/* 子步骤中用 {{it | get "field"}} 引用迭代项字段 */] } } }
- ```
-
- ### loop(until)
-
- ```json
- { "id": "l2", "name": "翻页循环",
- "do": { "loop": { "until": { "equals": [ { "var": "has_next" }, "false" ] },
- "max": 100, "do": [/* 子步骤,可用 break / continue */] } } }
- ```
-
- ### loop(repeat)
-
- ```json
- { "id": "l3", "name": "重试3次",
- "do": { "loop": { "repeat": 3, "do": [/* 子步骤 */] } } }
- ```
-
- ### if(条件分支)
-
- ```json
- { "id": "i1", "name": "需滚动?",
- "do": { "if": {
- "condition": { "equals": [ { "var": "need_scroll" }, "yes" ] },
- "then": [/* matched steps */],
- "else": [/* else steps,可省略 */]
- } } }
- ```
-
- 条件 `var` 支持点号路径下钻:`{ "var": "coords.need_scroll" }` → 取 `variables["coords"]["need_scroll"]`。
-
- 可用条件操作:`equals` / `not_equals` / `contains` / `starts_with` / `regex`(取 `VarRef[]`)、`not_empty` / `empty`(取 `VarRef`)、`gt` / `lt` / `gte` / `lte`(取 `VarRef[]`)、`always`(取 boolean)。
-
- ### script
-
- ```json
- { "id": "s1", "name": "算坐标",
- "do": { "script": { "runtime": "python",
- "code": "import json\nwinfo=json.loads('''{{winfo}}''')\nprint(json.dumps({'ix':winfo['wx']+587}))" } },
- "capture": "coords", "on_error": "abort" }
- ```
-
- `code` 字段支持 `{{var}}` 替换和 `| get` / `| default` 管道(v0.1 起)。
-
- ### tool
-
- ```json
- { "id": "t1", "name": "点击",
- "do": { "tool": "desktop_mouse", "with": { "action": "click", "x": "{{coords | get \"ix\"}}" } },
- "capture": "result", "on_error": "abort" }
- ```
-
- ### call(子工作流)
+ ## 七、经验检索 / 提炼
- ```json
- { "id": "c1", "name": "登录",
- "do": { "call": "login-flow", "with": { "inputs": { "user": "{{username}}" }, "outputs": { "token": "auth_token" } } } }
- ```
+ ### 两级检索
- ### wait(人工介入)
+ 1. 骨架:`ui_maps_search(query="微信 im")` → 有哪些 screen / experience
+ 2. 详情:`ui_maps_search(query="微信", screen_name="chat-list")` → 完整 regions + 关联经验
- ```json
- { "id": "w1", "name": "手动登录",
- "do": { "wait": "请在浏览器中完成登录后点击继续", "auto": [/* 可选:等待期间可执行的检查步骤 */] } }
- ```
+ ### 什么值得存(存「法」不存「案」)
- ### chat(LLM 决策)
+ - ✅ 非显而易见操作序列 / 跨应用可复用模式 / 踩过的坑 / 特殊定位技巧
+ - ❌ 常规操作(browser_navigate)/ 一次性案例 / 提示词已覆盖内容
- **ChatAgent 配置四段法**:
+ ### summary 写法
- ChatAgent 的 persona 必须按四层独立语义设计,每层职责不交叉:
+ `做什么 → 怎么做 → 为什么 → 跨应用适用性`(非传统思路要标注省了几步)。
- | 层次 | 字段 | 含义 | 允许内容 | 禁止内容 |
- |------|------|------|----------|----------|
- | 身份定义 | `persona` | 它是谁 | 角色、专业领域、工作方式 | 任务目标、操作步骤 |
- | 任务目标 | `goal` | 要达成什么 | 可衡量的目标描述 | 具体怎么做 |
- | 约束条件 | `constraints` | 不能做什么 | 边界、红线、禁止行为 | 正向操作指令 |
- | 操作规范 | `requirements` | 怎么做 | 工具使用规范、操作流程 | 任务细节、对话内容 |
+ ---
- **正确示例** — 终端操作员:
+ ## 八、工具使用要点
- ```json
- {
- "id": "terminal-operator",
- "name": "终端操作员",
- "persona": "你是终端操作员,通过桌面自动化工具与 CLI 程序交互。",
- "goal": null,
- "constraints": [
- "不要预设或硬编码对话内容",
- "不要跳过状态确认步骤"
- ],
- "requirements": [
- "发送前先截图确认终端状态",
- "使用 desktop_input mode=type,send=enter 发送"
- ]
- }
- ```
+ | 场景 | 首选 | 备选 |
+ |------|------|------|
+ | 定位网页元素 | `browser_snapshot` → @eN ref | screenshot + OCR |
+ | 桌面布局解析 | Vision 全窗口语义分析 | perceive 精确坐标 |
+ | 定位桌面文字 | Vision 划定功能区 | `desktop_find_text`(需字库) |
+ | 等待加载 | `browser_wait_for(selector)` | system_sleep(不得已) |
+ | 验证状态 | snapshot + chat 语义判断 | extract 文本匹配 |
+ | 查经验 | ui_maps_search 两级检索 | Read ui-maps JSON |
- **反模式** — 把任务目标塞进 requirements:
+ **坐标体系**:`desktop_mouse` 一律用**屏幕绝对坐标**;perceive 结果为客户区坐标时手动加 `screen_x/screen_y` 偏移。
- ```json
- // ❌ 错误:requirements 里混杂了任务约束("第2轮引用第1轮")
- {
- "persona": "你是终端操作员",
- "requirements": [
- "输入前激活窗口",
- "第2轮必须引用第1轮内容", // ← 这是任务目标,不是操作规范
- "第3轮引用前两轮"
- ]
- }
- ```
+ **输入**:`desktop_input` 输入+发送一次调用;普通文本直接输入,>500 字用 clipboard 并事后 clean;敏感内容禁用 clipboard。
- > `goal` 为 null 时,由 workflow step 的 `chat` 消息承载任务目标;有值时作为默认目标。
+ ---
- ```json
- { "id": "ca1", "name": "判断登录态",
- "do": { "chat": "根据截图判断是否已登录", "with": {
- "agent_id": "login-checker",
- "screenshot": true,
- "persona": "登录态判断专家",
- "goal": "判断当前页面是否已登录",
- "constraints": ["只输出 LOGGED_IN 或 LOGIN_REQUIRED,不输出其他内容"],
- "requirements": ["先截图观察页面状态再判断"]
- } },
- "capture": "login_status" }
- ```
+ ## 九、陷阱清单
- ### assert(断言验证)
+ | 陷阱 | 正确做法 |
+ |------|---------|
+ | 跳过布局解析直接找元素(W1) | 逐屏 vision+perceive 解析,保存 ui-maps |
+ | 窗口尺寸未固化(W2) | params.json window 字段固化 |
+ | 探索阶段写步骤(W3) | 核心路径手动跑通后才设计 |
+ | if contains 文案做登录检测 | chat 语义判断 + screenshot |
+ | 忘记 SPA 状态残留 | 新流程前重置(about:blank / resize 固化尺寸) |
+ | tooltip OCR 截到其他窗口 | 先 desktop_window_info 拿客户区边界,截图限域 |
+ | 重试重复提交表单 | 重试前检测状态,已完成步骤跳过 |
+ | 纯色/低纹理模板匹配误报 | find_image 模板需含纹理;用 region 限定加速 |
+ | 动态 UI 区匹配失败 | 识别为动态区域,改用文本/语义定位 |
- ```json
- { "id": "a1", "name": "验证成功",
- "do": { "assert": {
- "condition": { "equals": [ { "var": "status" }, "ok" ] },
- "message": "状态异常"
- } },
- "on_error": "abort" }
- ```