第 2 课
观察、操作、验证:按计算器、写编辑器、处理保存对话框
用 find 找到按钮引用,按出 6×7 并用 wait 等到 42;用 set-value 一次写入算式,弄清 delivered_verified 为什么也不等于程序已接受;往 Mousepad 写字后用窗口标题的星号做第二重核对并截图;关闭程序时遇到保存对话框,用 list-surfaces 和 snapshot --surface 找到 Don't Save 并核对程序已退出。
课程目录第 2 / 3 课
学习位置仅保存在当前浏览器,有效期 180 天。
图文教材
本课对照的官方页面:The observe, act, verify loop 、Quick start 、Commands 、Linux 。测试环境同课时 1(
agent-desktop1.2.0,Debian 13 无头桌面,GNOME 计算器 48、Mousepad 0.6.3)。下面所有「对照结果」都是这台机器的真实输出,快照 ID 每次都不一样,耗时类数字每次会有几十毫秒出入。
官方文档把 agent-desktop 的用法概括成一个循环:先观察(拿引用),再操作(对引用点击或填值),最后验证(重新读界面确认效果)。它反复强调一句话:ok: true 只说明命令发出去了,不说明程序真的照做了。这一课用计算器和文本编辑器把这个循环完整走三遍,每一步都用一次独立的读取来核对。
第 1 步:用 find 直接找到要点的按钮
上一课读了整个窗口的快照。如果已经知道控件的角色和名字,官方推荐更省事的 find,它只返回一个匹配项和它的引用:
agent-desktop-run find --app gnome-calculator --role button --name 7 --exact --first \
| jq -c '.data.match | {ref_id, role, name}'
对照结果:
{"ref_id":"@s9zm3qau3gsmf:e1","role":"button","name":"7"}
每次 find 都会存一份新快照,引用里的快照 ID 跟着变。所以别把引用抄下来复用,每次要用时现找。
第 2 步:按按钮算出 6×7,用 wait 核对结果
把「找引用」包成一个 shell 函数,依次按 6、×、7、=,再等显示屏的值变成 42。新建 calc_click.sh:
ref() {
agent-desktop-run find --app gnome-calculator "$@" --first | jq -r '.data.match.ref_id'
}
SCREEN=$(ref --role textfield)
echo "SCREEN=$SCREEN"
for key in 6 × 7 =; do
r=$(ref --role button --name "$key" --exact)
agent-desktop-run click "$r" | jq -c --arg k "$key" '{key: $k, d: .data.disposition}'
done
agent-desktop-run wait --element "$SCREEN" --predicate value --value 42 --timeout 5000 \
| jq -c '{ok, found: .data.found, elapsed_ms: .data.elapsed_ms}'
agent-desktop-run get "$SCREEN" --property value | jq -c '.data'
运行:
bash calc_click.sh
对照结果:
SCREEN=@s26fa2j7obzz7c:e1
{"key":"6","d":{"delivery":"delivered_unverified","retry":"unsafe"}}
{"key":"×","d":{"delivery":"delivered_unverified","retry":"unsafe"}}
{"key":"7","d":{"delivery":"delivered_unverified","retry":"unsafe"}}
{"key":"=","d":{"delivery":"delivered_unverified","retry":"unsafe"}}
{"ok":true,"found":true,"elapsed_ms":401}
{"property":"value","ref":"@s26fa2j7obzz7c:e1","value":"42"}
看懂两个字段就够用了。delivery 是 delivered_unverified:点击已经通过无障碍接口发出,但没人确认效果。retry 是 unsafe:重发可能多按一次,变成 77。所以官方的做法不是点完马上读一次,而是用 wait --predicate value 等显示屏变成预期值,程序异步刷新也不怕。这里等了约 400 毫秒就等到了 42,再用 get 独立读一次确认。
整个过程鼠标没有动,窗口焦点也没有被抢:默认模式下所有操作都走无障碍接口的语义调用。需要真实的鼠标和键盘输入时才加 --headed。
第 3 步:用 set-value 直接写入算式
显示屏的 available_actions 里有 SetValue,可以一次把整条算式写进去,不用一个个按键。官方建议优先用 set-value,而不是逐字输入的 type。新建 calc_setvalue.sh:
ref() {
agent-desktop-run find --app gnome-calculator "$@" --first | jq -r '.data.match.ref_id'
}
SCREEN=$(ref --role textfield)
agent-desktop-run set-value "$SCREEN" "128+256" | jq -c '{ok, d: .data.disposition}'
agent-desktop-run get "$SCREEN" --property value | jq -c '.data.value'
agent-desktop-run click "$(ref --role button --name = --exact)" | jq -c '.ok'
agent-desktop-run wait --element "$SCREEN" --predicate value --value 384 --timeout 5000 \
| jq -c '{ok, found: .data.found}'
运行:
bash calc_setvalue.sh
对照结果:
{"ok":true,"d":{"delivery":"delivered_verified","retry":"unsafe"}}
"128+256"
true
{"ok":true,"found":true}
这次 delivery 变成了 delivered_verified:agent-desktop 写完后回读了一次,值确实是 128+256。但官方特意说明,这只证明字段里的值对了,不证明程序已经接受了它。所以按下 = 之后,仍然用 wait 等到 384 才算数。
测试中遇到过一次反例:先按 C 清屏、紧接着 set-value,返回同样是 delivered_verified,可按 = 后显示屏是空的,因为清屏晚了一步才生效,把刚写进去的算式也清掉了。这正是「每一步都要独立核对」的原因。
第 4 步:往编辑器里写字,用窗口标题做第二重核对
计算器只有几十个控件。Mousepad 文本编辑器的整窗快照有 271 个引用,大部分是菜单项,这时 find 的优势更明显。新建 mousepad_demo.sh:
agent-desktop-run launch mousepad | jq -c '{ok, title: .data.window.title}'
EDITOR_REF=$(agent-desktop-run find --app mousepad --role textfield --first \
| jq -r '.data.match.ref_id')
echo "EDITOR_REF=$EDITOR_REF"
agent-desktop-run set-value "$EDITOR_REF" "Hello from agent-desktop" \
| jq -c '{ok, d: .data.disposition}'
agent-desktop-run get "$EDITOR_REF" --property text | jq -c '.data.value'
agent-desktop-run list-windows --app mousepad | jq -c '[.data[].title]'
agent-desktop-run screenshot --app mousepad mousepad.png \
| jq -c '{ok, w: .data.width, h: .data.height}'
运行:
bash mousepad_demo.sh
对照结果:
{"ok":true,"title":"Untitled 1 - Mousepad"}
EDITOR_REF=@s3w056yj4bn0nl:e1
{"ok":true,"d":{"delivery":"delivered_verified","retry":"unsafe"}}
"Hello from agent-desktop"
["*Untitled 1 - Mousepad"]
{"ok":true,"w":642,"h":507}
get --property text 读回了刚写的文字。窗口标题从 Untitled 1 变成 *Untitled 1,星号表示文档有未保存的修改,这是程序自己给出的证据,比字段回读更可靠,官方把这类检查叫「文档级效果」的独立读取。screenshot 把窗口存成了 mousepad.png,打开能看到编辑区里那行字。截图只是给人看的,智能体做判断仍然靠无障碍树。
第 5 步:关闭程序,处理弹出的保存对话框
文档有未保存的修改,直接关程序会怎样?新建 close_try.sh:
agent-desktop-run close-app mousepad \
| jq -c '{ok, code: .error.code, cause: .error.details.cause}'
agent-desktop-run list-surfaces --app mousepad \
| jq -c '[.data.surfaces[] | {type, title}]'
运行:
bash close_try.sh
对照结果:
{"ok":false,"code":"ACTION_FAILED","cause":"dialog_pending"}
[{"type":"window","title":"*Untitled 1 - Mousepad"},{"type":"window","title":"Question"},
{"type":"sheet","title":"Question"}]
close-app 没有硬关,而是报告有一个对话框在等回答(dialog_pending)。list-surfaces 列出了这个对话框,类型是 sheet。官方规则是:结果里出现菜单、sheet 或 alert 时,程序在等这个浮层,先用 snapshot --surface 观察它,再管后面的主窗口。用 find 在主窗口里找「Don't Save」是找不到的,测试时返回了 null。新建 dont_save.sh:
NO=$(agent-desktop-run snapshot --app mousepad --surface sheet -i --compact \
| jq -r '.. | objects | select(.name? == "Don'\''t Save") | .ref_id')
echo "NO=$NO"
agent-desktop-run click "$NO" | jq -c '{ok, d: .data.disposition}'
agent-desktop-run list-apps | jq -c '[.data.apps[].name]'
运行:
bash dont_save.sh
对照结果:
NO=@s3sx4mkop6qjoy:e2
{"ok":true,"d":{"delivery":"delivered_unverified","retry":"unsafe"}}
["gnome-calculator"]
对话框里只有三个按钮(Cancel、Don't Save、Save),点了 Don't Save 之后,list-apps 里已经没有 mousepad,说明程序确实退出了。真想强行结束进程可以用 close-app --force,但那会丢掉未保存的内容,交给智能体时要慎用。
常见坑
- 把 ok: true 当成功:它只说明命令发出去或者状态本来就满足。点击类操作要用
wait、get或窗口标题再核对一次。 - 同一个按钮连按两次只生效一次:官方的 Linux 排错表写明,GTK4 会把间隔不到约 250 毫秒的两次点击当成一次。连续按同一个键时,等前一次的效果出现再按。下一课会亲眼看到 11+22 被算成 13。
- 符号对不上:计算器的乘号是
×、减号是−、除号是÷。--name加--exact时差一个字符都找不到。 - 在主窗口里找对话框按钮:对话框是单独的浮层,用
snapshot --surface sheet(或 alert、menu)去读。 - 用 type 往已有内容的输入框里打字:测试中
type在光标处插入,回读的值和预期不符,返回ACTION_FAILED(post_action_verification)。要整体替换内容,用set-value。 - 用 sleep 代替 wait:程序刷新时间不固定,sleep 短了读到旧值,长了浪费时间。
wait一满足条件就返回。
小结
这一课把「观察、操作、验证」走了三遍:用 find 和 click 按出 6×7,用 wait 等到 42;用 set-value 一次写入算式,并看到 delivered_verified 也不等于程序已接受;往编辑器写字后用窗口标题做第二重核对,再处理关闭时弹出的保存对话框。下一课把这些规则写进一个 Python 脚本,让它像智能体一样自己找引用、自己核对、按错误码决定能不能重试,再接入会话和追踪,最后看看怎样把它交给 Claude Code、Codex 这类编程智能体。
