snapshot 把一棵可访问性树读成 JSON,并给其中每个智能体能操作的元素分配一个 ref。
智能体之后所有的动作都指向这个 ref。
ref 的构成
@s8f3k2p9:e8
└─┬─────┘ └┬─┘
│ └ 元素在这次快照里的序号
└ snapshot_idid 两段缺一不可。只写 @e8 会被拒绝并返回 INVALID_ARGS——脱离快照的序号没有含义,
下一次快照里的 e8 很可能完全是另一个元素。
ref 指向什么
一个 ref 背后是这次快照里那个节点的读数:角色、名字、可用动作,以及它是从哪棵子树里来的。
{
"role": "treeitem",
"name": "Documents",
"ref_id": "@s8f3k2p9:e8",
"available_actions": ["Click"]
}available_actions 是选择动作的依据。对不在列表里的动作发起请求,会被平台拒绝。
ref 如何被重新识别
动作执行前,agent-desktop 会从存储的条目重新识别这个 ref。从 2.6 起,Windows、Linux, 以及 macOS 上无法取得元素地址的 ref,采用同一套证据规则:
- 快照必须完整读取过它的窗口,并证明捕获时恰好有一个元素匹配它的完整谓词: 角色加上记录的稳定字段(native id、稳定 name、稳定 description)。
- 解析时在当前窗口重新计数这个谓词,只接受恰好一个匹配。
- 骨架、深度截断和
--root下钻都只是同一次完整窗口观察的浅层呈现,它们的 ref 仍然按整个窗口计数。 - 值(value)和位置(bounds)永远不参与身份判断。
如果快照需要完整读取窗口才能证明 ref,但没能读完,data.identity_unproven 为 true。
这不影响 complete 对当前呈现树的描述,但这批 ref 都会以 identity_not_unique 失败;
用更大的 --timeout-ms 重拍。
macOS 上带元素地址的 ref 走另一条路:地址失效,或进程、角色、标识符不再匹配时,
直接返回 retained_object_stale,不执行搜索,避免落到相似元素上。
什么时候失效
界面是活的。产生 ref 的那次快照对应的界面状态一旦被替换,ref 就不再安全。
| 情形 | 结果 | 该怎么做 |
|---|---|---|
| 只写了序号,没带快照 id | INVALID_ARGS |
用完整的 @snapshot:序号 |
| 元素消失,或 macOS 的元素地址失效 | STALE_REF(details.kind 为 saved_ref_absent / retained_object_stale) |
重新快照,改用新快照里的 ref |
| 捕获之后出现了完全相同的元素 | AMBIGUOUS_TARGET(saved_ref_ambiguous) |
换一个在窗口内唯一的控件;重新快照会同时拒绝两个双胞胎 |
| 捕获时没有证明 ref 唯一,或窗口没能在超时内读完 | AMBIGUOUS_TARGET(identity_not_unique) |
换唯一控件;只是超时则用更大的 --timeout-ms 重拍 |
用 --app 选目标时应用有多个窗口 |
AMBIGUOUS_TARGET,error.details 列出候选窗口 id |
用 --window-id 指定其中一个 |
最后一行的多窗口歧义是目标选择层面的问题,和 ref 身份歧义不是一回事。遇到
AMBIGUOUS_TARGET 先读 details.kind,不要一律用 --window-id。
上述 ref 身份失败都带 details.retryable: false,并且是 not_delivered。
它们的 disposition.retry 仍然是 safe,因为什么都没发送;但重复同一个 ref 会得到同样的拒绝。
唯一的例外是 resolution_identity_unknown:实时字段读不到导致身份计数未完成,它会以
TIMEOUT 报告,可以重试。
正确的用法
一次动作之后必然跟一次读取。这不是保守,是因为动作成功只说明请求被接受了, 不说明界面按你的预期变了。
agent-desktop snapshot --app Finder -i # 读,拿到 snapshot_id
agent-desktop click @s8f3k2p9:e8 # 用这次读到的 ref 动作
agent-desktop snapshot --app Finder -i # 再读,核对结果需要「等到某个值变成什么」时,用 wait 而不是反复快照:
agent-desktop wait --element @s8f3k2p9:e6 --predicate value --value 7