页面能力模型
接入 Pling 的页面不会把 DOM 树或后端 API 直接交给 Agent,而是提供一个更小的业务模型:页面能执行哪些操作,这些操作可以作用于哪些对象,哪些区域赋予这些对象业务含义,以及哪些状态用于解释当前情况。
这个模型决定 Agent 能理解和调用什么。执行生命周期、页面事实如何投影、以及外部客户端如何承载这个模型,属于另外的问题。本章只定义模型本身。
四类信息
页面能力模型由四个部分组成。它们从不同角度描述同一个页面,而不是为应用保存四份副本。
| 部分 | 描述什么 | 能否改变业务状态 |
|---|---|---|
| 动作 | 页面已经准备好执行的一项业务操作 | 能,通过页面原有的执行路径完成 |
| 记录 | 动作可以作用、Agent 可以读取的业务对象 | 不能 |
| 区域 | 页面上有业务含义、用于组织动作和记录的范围 | 不能 |
| 状态 | 解释当前任务所需的只读信息 | 不能 |
只有动作能够引起业务变化。记录指出可以作用于什么,区域说明它属于哪里,状态提供当前上下文。把这三者与动作分开,Agent 才不会把“看得见的数据”误当成“可以执行的操作”。
动作
动作回答一个问题:这个页面能做什么?它的身份来自业务意图,而不是碰巧触发它的控件。“新增一条待办”“把待办标记为完成”“删除待办”是动作;“点击第三个按钮”不是。
一个动作需要向 Agent 提供足够的信息,使它能够选择并正确调用:
- 一个稳定的 id,不依赖行号、界面文案或 DOM 节点;
- 对业务意图和后果的说明;
- 调用方可以提供的输入,以及这些输入的类型和边界;
- 如果作用于业务对象,说明目标对象的类型;
- 当前是否可用;不可用时说明原因;
- 与操作风险相称的审批策略。
动作可以由按钮、输入框、菜单项实现,也可以对应一个没有专属 DOM 元素的页面函数。实现方式可以不同,业务含义保持同一份。
同一类行内操作可以收敛成一个动作族。例如,一百条待办不需要一百个“切换完成状态”动作,只需要一种操作形状和一个记录参数;一百个稳定的记录 id 负责指出可能的目标。这样,能力清单的数量随业务操作种类增长,而不是随渲染行数增长。
可用不等于有权。一个动作可以被声明、出现在页面模型里,却因为缺少前置条件而暂时不可用;也可以在当前页面上可用,但当前会话没有调用权限。前者告诉 Agent 下一步能补什么,后者仍然由授权机制决定。
记录
记录是业务对象,不是表格行。订单在排序、筛选或重新渲染后仍然是同一张订单;待办在列表中换到另一个位置后仍然是同一条待办。记录 id 承载这份身份,模型因此可以引用对象,而不依赖它在屏幕上的位置。
一条记录可以提供:
- 稳定的业务 id;
- 用于人和模型辨认的简短标签;
- 与当前决策相关、经过裁剪的字段;
- 所属的区域。
记录是只读的。看见一条记录,不会自动产生修改它的操作。如果页面没有声明写动作,Agent 可以读取这个对象,但不能改变它。
字段视图刻意小于底层对象。页面可以只暴露订单的状态、金额和到期日,而不暴露数据库行、审计历史或凭证。当任务只需要比较而不是披露时,字段还可以被掩码或替换为引用。
记录身份与记录详情有不同的数量边界。长列表可以为整个活跃集合保留 id,却只返回其中一部分字段。Agent 因此可以引用视口外的对象,而不必把每一行的每个字段都装入模型上下文。执行前,运行时会重新解析这个 id。
区域
区域为一组动作和记录划出业务边界。工具栏、列表、表单、详情面板和弹窗回答不同的用户问题,因此属于不同区域。区域不是 CSS 盒子、坐标范围或视觉样式。
每个区域可以提供 id、面向人的名称、用途说明、结构角色和父区域。这些信息组成一张页面地图。两个控件使用相同文案时,这张地图尤其重要:支付弹窗里的“确认”和地址表单里的“确认”可以是同一句话,但带有不同的区域路径。
区域也界定可见范围。它可以报告当前有多少动作可用、有多少内容可见、是否还有更多内容可以被展开。折叠的列表或尚未打开的弹窗因此能告诉 Agent“这里还有内容”,而不是让缺失的内容看起来不存在。
区域归属不授予权限。它影响页面如何被描述、哪些内容属于当前观察范围,但不替代权限检查。
状态
状态是解释页面所需的只读视图。典型内容包括当前筛选、选中的记录、当前页码、匹配记录总数,以及编辑表单是否打开。
状态不是应用 store。把整个 store 暴露出去,会把内部键、临时值和无关数据带进模型上下文。页面应当暴露有业务名称和说明的视图:filter: "open" 有用;一个包含所有响应式字段的原始对象没有用。
状态与记录字段采用同一套边界和敏感值处理。长字符串、深层对象和大数组会被截断或概括;敏感值可以掩码或替换为引用。当前任务不需要的状态视图可以完全不暴露。
状态不能被直接写入。筛选、选中项或表单值发生变化,是因为 Agent 调用了执行对应业务操作的动作。这样,校验、确认和审计仍然走与人工操作相同的路径。
四部分如何组合
下面这张页面地图描述一个待办页面:
页面
├─ 工具栏区域
│ └─ 动作:add_todo(text)
├─ 列表区域
│ ├─ 记录:t1、t2、t3
│ └─ 动作:toggle(record)、remove(record)
└─ 状态:filter = open,shown = 3,total = 12四者的关系有明确方向:
- 动作可以通过 id 作用于记录。“把牛奶标记为完成”选择
t1,用这个记录调用toggle。 - 记录不隐含动作。页面可以只开放记录供读取,而不提供修改它们的操作。
- 区域组织属于同一业务范围的动作和记录,也可以限定当前哪些内容处于活跃状态。
- 状态描述整个页面或当前任务,不替代记录字段,也不指向某一个对象。
假设用户说“删除第二条待办”。Agent 读取当前可见记录,识别出第二个对象是 t2,再用 record: "t2" 调用删除动作。它发送的不是“第二行”,也不是选择器。即使执行前列表重新排序,运行时仍然会把 t2 解析为同一条待办。
假设用户说“只看未完成的任务”。Agent 调用筛选动作;下一轮观察时,筛选值出现在状态中。Agent 不会直接给筛选变量赋值。
这些区分也解释了为什么行数很多的页面仍然可以操作:记录提供选择对象所需的数据,一个动作族接收选中的记录,区域说明还有多少行没有展示,状态说明当前列表处于什么条件。
不变量
只有页面遵守下面几条规则,能力模型才能保持可靠。
- 身份是业务身份。 记录 id 和动作 id 来自稳定的业务概念,不来自 DOM id、行号或随机渲染键。
- 读写分离。 记录、区域和状态只负责描述;只有动作改变状态。
- 声明不是权限。 暴露只说明页面愿意展示或调用什么,不说明谁有权调用。
- 不可用不等于不存在。 存在但此刻不能执行的操作应当带有原因;从未暴露的操作才属于模型之外。
- 所有视图都有边界。 动作、记录、区域和状态只暴露当前任务所需的内容。会影响下一步决策的省略必须明确说明。
- 没有未声明的兜底。 如果页面没有为某个任务声明动作,框架不会把记录、区域或状态临时变成写操作。
小结
页面能力模型把四件事分开:动作说明能做什么,记录指出业务对象,区域提供业务位置,状态解释当前情况。只有动作能够改变状态。这四类视图都有边界,都使用稳定的身份;除动作之外都只读。这样的分离让 Agent 依据已声明的业务操作作决定,而不是依据 DOM 细节或应用内部结构。

