执行生命周期
页面能力模型定义页面能做什么。执行生命周期从一个 Agent 选中动作开始,到运行时返回一份回执结束。
这个过程不只是调用一个点击处理函数。运行时需要解析动作、解码参数、拒绝非法输入、应用确认策略、让目标变得可用、通过页面原有路径执行、观察结果,并说明发生了什么。回执随后进入下一轮模型请求。
本章跟随一个动作走完整条路径。权限策略、人工接力、敏感值和更外层的 Agent 循环各有自己的问题;它们只在这里改变执行路径时出现。
一份回执要回答什么
浏览器事件可以执行完,却不说明业务操作是否发生。页面处理函数可能在发出请求后立即返回 void,一次点击可能被应用忽略,界面也可能先更新、随后被服务端拒绝。
因此,执行生命周期产生一份结构化回执,回答四个问题:
- 调用是否到达了一个已声明的动作? 动作 id 必须存在于当前页面模型中。
- 动作是否被允许执行? 参数、权限和可用性都在执行前检查。
- 页面是否到达了预期状态? 可选的期望判据和执行前后的快照提供证据。
- 下一步应当做什么? 错误发生在哪一段,以及回执中的字段,决定 Agent 是检查页面、修正输入、询问用户、续跑流程还是停止。
成功回执不证明用户的业务目标已经完成。它记录页面运行时能够验证的内容。ok: true 表示执行路径完成且没有返回错误;expect 和 changed 才是页面确实发生了可观察变化的证据。
执行流水线
一次调用按顺序经过以下阶段:
动作 id + 参数
│
▼
1. 解析已声明的动作
2. 解码参数并还原引用值
3. 校验并规整参数
4. 应用确认策略
5. 让目标可见,并确认它可用
6. 执行:元素动作、函数动作、复合流程或人工接力
7. 观察页面变化,检查期望,并等待页面落定
8. 比较快照,返回有边界的回执第 1 至第 5 阶段发生在任何业务副作用之前。第 6 阶段是页面自己的写入路径第一次可能运行的位置。第 7 至第 8 阶段不改变业务结果,只决定运行时能够报告哪些证据。
参数校验有意放在确认之前。缺少必填字段或值不合法的调用没有必要打扰用户批准。参数有效之后,确认界面看到的才是实际会执行的操作。
副作用之前
解析动作
执行器在当前页面注册表中查找动作 id。找不到时返回 ACTION_NOT_FOUND。在错误交给模型之前,Agent 运行时会补上当前可用的动作,模型因此可以修正选择,而不是继续猜。
动作 id 是元素级或函数级的身份,不是选择器。对于折叠后的行内操作,Agent 运行时已经在此之前把模型的 record 参数还原成原始动作 id。
解码与校验参数
参数是模型生成的 JSON 文本,可能格式错误。运行时先解析 JSON;不合法时返回 INVALID_ARGS。随后还原参数中引用的敏感值,使校验看到真实值,而不是占位代号。
接下来检查参数声明。缺少必填字段、类型错误或值不在允许范围内,都会返回 INVALID_PARAMS。合法值会在执行前规整成统一形状,页面处理函数收到的参数与模型看到的声明一致。
这些检查不会改变页面状态。在这里失败,说明操作尚未开始。
应用确认策略
动作的风险等级和声明决定运行时是否必须请求确认。用户拒绝时返回 PERMISSION_DENIED;没有配置确认通道时,默认策略是拒绝,而不是静默执行。
复合动作按它此刻能够解析出的最高风险进行评估。后面的某一步可能比外层批准的风险更高;执行到那一步时会再次询问。人工接力不同:这一步由用户亲自完成,因此不会再要求用户批准自己的操作。
确认是执行前的闸门,不是身份认证,也不能替代后端授权。页面动作仍然通过应用原有的写入路径和服务端校验。
让目标可用
动作必须在当前页面中可达且可用。目标在可见区域之外并声明了显示处理函数时,运行时会把它带入视口,等待页面落定,再检查一次可见性。第二次检查不可省略:滚动可能触发懒加载,也可能替换原来的目标行。
如果仍然无法让目标可见,返回 ACTION_UNAVAILABLE。动作存在但业务前置条件不满足时也使用同一个错误码。回执在能取得原因时带上原因:按钮被禁用、没有选中记录、弹窗未打开或筛选条件不对。
显示目标可能让页面滚动,但不会改变业务数据。它发生在确认之后,因为用户批准的是业务操作,不是到达目标所需的滚动。
执行方式
所有执行方式都经过同一组前置检查,差别在第 6 阶段。
| 方式 | 运行什么 | 模型收到什么 |
|---|---|---|
| 元素动作 | 页面为按钮、输入框、菜单项等控件声明的处理函数 | 动作回执;有证据时附带证据 |
| 函数动作 | 没有专属 DOM 元素的页面函数 | 同样的回执,可附带函数返回的有界 data |
| 复合动作 | 一组按顺序执行的已声明动作 | 一份回执,包含步骤账本和整次操作的前后差异 |
| 人工接力 | 请求用户亲自完成一步 | 用户完成或取消后的回执;不会尝试模拟点击 |
函数结果与页面状态一样有边界。动作可以返回一个小的业务结果,例如数量或导出引用。大数组、深层对象和长字符串会在进入模型上下文之前被概括或截断。
元素动作在注册时会得到一个保真度值。回执只在它不是 high 时报告该值。high 表示开发者提供了执行路径、指定了明确目标,或者声明的目标本身就是运行时可以直接操作的原生控件。auto 表示运行时找到了一个嵌套的原生目标,但开发者没有确认过它。low 表示结果来自 DOM 模拟,框架没有取得应用效果确实发生的保证。低保真度回执会带警告,不会把模拟结果当成权威事实。
执行之后:期望与证据
页面处理函数返回,并不等于业务操作完成。提交处理函数可能发出请求后立即返回;删除动画可能仍在运行;服务端也可能在界面第一次渲染之后拒绝写入。
期望
动作可以声明一个期望:只有某个条件成立,这一步才算已验证。运行时会反复检查该条件,直到成立或超时。默认超时是两秒。
条件成立时,回执带有 expect 说明。这是开发者给出的成功判据,运行时已经检查过。超时则返回 EXPECTATION_FAILED。这个错误码表示动作已经发出,但没有观察到预期状态;它不证明动作没有效果,页面可能仍在处理,或者效果没有出现在快照中。
期望比快照差异更强。差异说明页面模型中哪些部分发生了变化;期望说明一个已声明的业务条件已经达到。
快照比较
开启证据时,运行时会先通过所有前置检查,并在执行前立即取一份快照。动作结束、期望检查和页面落定之后,再取一份快照,比较模型能够看到的部分:
- 页面身份;
- 记录及其字段;
- 可用动作;
- 状态视图;
- 页面反馈。
生成的 changed 对象会报告新增、删除、重排和更新的记录,动作的增减,状态键的变化,以及反馈的变化。记录、动作、状态和反馈列表都有上限。被省略的条目会带数量说明,模型不会把部分差异当成完整差异。
写操作完成但没有任何可观察变化时,回执会带一条警告。警告不会把动作改成失败;它说明页面模型没有变化,业务变化可能对当前层不可见。读操作不会收到这条警告,因为读取、滚动或导航本来就可能不改变页面模型。
因此,回执把几种证据分开:
| 字段 | 证明什么 |
|---|---|
ok | 执行路径完成,没有返回错误 |
expect | 声明的成功条件已经观察到 |
changed | 页面模型中的这些可见部分发生了变化 |
fidelity | 框架对执行路径有多大把握 |
warning | 调用成功,但仍需要解释 |
data | 函数动作返回的有界业务结果 |
失败位置与重试安全
错误最重要的信息之一是它发生在哪里。重试一个尚未开始的调用,与重复一个可能已经落地的写入,是两件不同的事。
| 失败位置 | 典型错误码 | 已知事实 |
|---|---|---|
| 查找动作或解码参数 | ACTION_NOT_FOUND、INVALID_ARGS、INVALID_PARAMS | 操作没有开始 |
| 权限决策 | PERMISSION_DENIED | 操作没有开始 |
| 可见性或业务可用性 | ACTION_UNAVAILABLE | 操作没有开始;页面可能需要补前置条件 |
| 页面处理函数或函数动作抛错 | EXECUTION_FAILED | 执行已经开始;效果可能只完成一部分,也可能状态未知 |
| 预期状态没有达到 | EXPECTATION_FAILED | 动作已经发出;没有观察到目标结果 |
| 复合动作中的某一步失败 | STEP_FAILED | 前面的步骤可能已经生效;剩余步骤是已知的 |
| 人工接力取消、超时或未验证 | HANDOFF_CANCELLED、HANDOFF_TIMEOUT、HANDOFF_FAILED | 人工步骤没有成功完成 |
运行时把预期内的失败作为结构化结果返回,而不是把它抛给 Agent 循环。实现抛出意外异常时,循环会捕获并转换成 EXECUTION_FAILED;模型仍然能拿到一份可推理的结果。
这些分类不保证重试一定安全。业务操作仍然必须幂等,或者由调用方使用稳定的操作 id、服务端幂等键,或者先读后写。框架报告失败位置,无法推断业务自己的重试语义。
复合动作
复合动作对模型是一个工具,对用户是一次业务操作。它的步骤依次针对每一步执行时的页面运行。搜索框、搜索后才出现的行内动作、弹窗打开后才出现的按钮以及保存动作,可以因此组合起来,而模型不需要知道中间界面的布局。
外层调用按能够解析出的最高风险请求确认。内部步骤仍然经过同一套解析、参数、可见性和可用性检查,不会绕开页面的动作声明。某一步如果比外层批准的风险更高,会在执行到它时再次询问。
整条流程只产生一份前后差异。对询问“这次业务操作改了什么”的用户来说,这是正确的单位;内部每一步各取一份差异,既会成倍增加快照工作,也会掩盖整体结果。回执还包含步骤账本,记录每一步的 id、结果,以及相关错误。
某一步失败时,结果是 STEP_FAILED,而不是笼统的失败。步骤账本说明哪些步骤已经执行;可续跑的失败会附带一个句柄,从失败的那一步继续。前面的步骤不会重放。流程已经创建或修改过记录时,这一点尤其重要:从第一步重来会重复副作用。
流程不是数据库事务。后面的步骤失败时,前面的步骤可能仍然生效。续跑句柄减少重复副作用,但不会回滚已经发生的变化。
生命周期不保证什么
生命周期提供顺序、闸门、观察和结构化结果。它不提供:
- 自动回滚。 某一步失败或等待超时之前,写入可能已经发生。
- 自动幂等。 重复同一个动作可能创建第二条记录或再次提交。
- 自动重试。 重试策略属于调用方,必须考虑失败位置。
- 后端真相。 快照描述页面暴露的内容。界面变化后,服务端仍可能拒绝写入;服务端接受的变化也可能没有显示在页面上。
- 通用 DOM 兜底。 动作没有声明时,执行器不会去 DOM 里寻找一个看起来相似的按钮。
- 身份认证或授权。 确认不是身份核验,页面能力也不能替代后端策略。
取消也遵循同一条边界。Agent 可以在下一个动作前停下,也可以中止模型请求,但已经进入页面处理函数的动作仍可能完成。取消不是回滚。等待期望超时同样不能证明操作没有发生。
一次完整执行
假设一个待办页面有十二条未完成任务。用户说:“删除周报。”Agent 已经把目标解析为 t9,并用空参数调用已声明的动作 remove@t9。
- 解析。 当前快照包含
remove@t9;执行器找到了它的绑定。 - 校验。 对于没有额外参数的动作,
{}合法。 - 确认。 删除属于危险操作。策略请求确认,用户批准。
- 让目标可用。
t9在视口之外。显示处理函数把它滚入视口,运行时等待页面落定,第二次可见性检查通过。 - 执行。 页面原有的删除处理函数移除这条待办。
- 检查期望。 开发者声明该行应当消失。条件在超时前成立。
- 观察。 运行时等待页面落定,然后取执行后的快照。
- 报告。 记录
t9消失,它的行内动作也消失,总数从十二变成十一。
回执可以概括为:
{
"result": {
"ok": true,
"action": {
"id": "remove@t9",
"description": "Delete the task"
},
"expect": "The task is no longer in the list",
"changed": {
"observed": true,
"records": {
"added": [],
"removed": ["t9"],
"updated": [],
"reordered": false,
"omitted": { "added": 0, "removed": 0, "updated": 0 },
"total": { "from": 12, "to": 11 }
},
"actions": {
"added": [],
"removed": ["remove@t9"],
"omitted": { "added": 0, "removed": 0 }
}
}
}
}用户拒绝确认时,结果是 PERMISSION_DENIED,页面没有被改动。动作已经发出,但期望的业务后置条件始终没有成立时,结果是 EXPECTATION_FAILED;Agent 必须检查页面,而不是再次发送同一个删除。删除处理函数在发出请求后抛错时,结果是 EXECUTION_FAILED;这一层无法判断服务端是否已经接受请求。
小结
执行生命周期把一个被选中的能力变成证据。它解析并校验调用,在任何业务副作用之前应用权限和可用性闸门,执行页面声明的操作,等待可选的期望,比较执行前后的可见页面,并返回一份有边界的回执。
回执不是“整个业务目标已经完成”的声明。它说明运行了什么、验证了什么、改变了什么,以及失败发生在哪一段。这个区别让下一轮模型能够判断应当停止、修正参数、检查页面、续跑流程,还是询问用户。

