从单站点桥到开放协议
一个页面只服务一个页面内助手时,私有桥通常已经足够。页面把动作登记给助手,助手按约定调用,团队可以快速迭代,不必先定义一套公共标准。
当另一个客户端也要操作同一页面时,问题开始变化。新的客户端需要重新理解页面动作,新的页面又需要重新适配已有客户端。此时真正缺少的不是又一层 RPC,而是双方对“能力是什么、能看到什么、谁可以调用、结果意味着什么”的共同语义。
协议的价值就在这里。它不因为系统分布式而存在,而因为独立实现必须对同一组事实达成一致。
私有桥在哪里失效
假设一个订单页面已经接入了页面内助手。助手通过私有对象调用 approve_order,并读取组件内部状态。这个实现可以很好地完成一个产品内的任务,但它把四件事绑定在一起:
- 页面如何表达动作;
- 助手如何发现动作;
- 双方如何表示会话与权限;
- 执行结果如何回到助手。
第二个客户端出现后,这些约定要么被复制,要么被另一套近似约定替代。复制之后,页面需要维护两份语义;替代之后,同一动作在不同客户端里可能意味着不同的目标、确认条件或错误处理。
浏览器自动化解决的是另一个问题。CDP、Playwright 和视觉 Agent 可以在没有页面契约的情况下操作界面,适合页面不能或不愿声明能力的场景。但它们不能稳定回答:这个按钮是读取还是不可逆写入,是否需要第二位审批人,Agent 可以引用哪些记录,失败后业务状态是否已经改变。
当这些答案必须在多个实现之间保持一致时,就需要协议。
协议需要固定什么
pling.surface/0.1 把问题压缩为四类对象:
- 能力。 页面愿意开放的业务操作,以及它的输入、影响类型和确认要求。
- 上下文。 调用方做当前决策所需的有界只读视图。
- 会话。 Agent 代表哪个用户、在哪个应用范围内、到什么时候为止可以使用哪些能力。
- 结果。 调用是否被理解、是否执行、业务是否改变,以及下一步能观察到什么。
传输、认证、业务授权、存储和模型执行不属于这套协议。协议可以承载在 postMessage、MCP、HTTP 或其他传输上,但传输不会改变能力的业务含义。
设计目标
协议被刻意保持得很小。下列目标分别约束数据模型和实现行为。
| 目标 | 要求 |
|---|---|
| 不依赖 DOM 发现 | 调用方通过结构化响应获得页面身份和可用能力 |
| 业务语义 | 动作按业务意图命名,不按选择器或视觉位置命名 |
| 有界观察 | 上下文有选择、有边界,不返回整棵 DOM 或整个应用 store |
| 显式控制 | 会话范围、有效期、撤销、确认和后端授权被分别表示 |
| 可行动的结果 | 响应区分协议错误、执行错误和业务效果 |
| 传输无关 | 同一契约可运行于多种传输 |
| 失败关闭 | 未知版本、未知方法、越界能力或不可用的保护适配器一律拒绝 |
协议不提供通用浏览器 API。它没有 click(selector)、eval(script) 或等价逃生口。它不执行认证,不替业务后端作授权决定,不存储凭证,也不取代 MCP。这些排除项属于设计边界,不是暂时遗漏。
基本模型
设 C_page 为页面声明的能力集合,C_session 为经过验证的会话允许的能力集合。有效能力集合为:
C_eff = C_page ∩ C_sessiondescribe 和 invoke 都作用于 C_eff。不在这个集合中的能力,不能被该会话发现或调用,即使页面在全局层面声明了它。
三个判断必须保持分离:
| 判断 | 回答的问题 | 责任方 |
|---|---|---|
| 声明 | 页面是否实现了这个能力 | 页面应用 |
| 暴露 | 这个能力是否在本次会话的作用域内 | 宿主与会话验证 |
| 授权 | 当前用户是否有权对这个资源执行这个操作 | 业务后端 |
前两个判断描述软件边界;第三个是业务决定,可能依赖角色、归属、金额、时间和审批状态。协议可以执行第二个判断,并为第三个判断携带证据;它不能替代第三个判断。
能力声明
能力不是按钮,也不是任意函数。它是一项有稳定 id、业务说明和执行语义的操作。实现需要区分以下核心信息:
| 字段 | 含义 | 约束 |
|---|---|---|
id | 稳定的能力标识 | 折叠到记录的动作不得包含行内小名 |
description | 面向人的业务说明 | 由页面提供,不来自用户生成内容 |
inputSchema | 参数的 JSON Schema | 只描述动作参数,不暴露选择器或凭证 |
effect | read、write 或 destructive | 必须声明 |
approval | none、ask 或 always | 省略时按应用策略解析 |
fidelity | high、auto 或 low | 表示执行目标由何种方式得到 |
record | 折叠行内动作的记录参数 | 用于把一项模板动作绑定到具体记录 |
实现还可以提供 scopePath、handoff、flowSteps、offscreen 等可选信息。它们帮助调用方理解区域、人工接力和复合动作,但不改变基本授权模型。
列表中的同类行内动作使用一个稳定模板 id,例如 approve@order,再由 record 指定目标。这样既避免为每一行返回一个能力,也保留记录级引用。执行前仍需重新解析目标、检查可用性,并走与人工界面相同的执行器。
fidelity 不是授权属性。它描述执行的可信来源:开发者声明的目标、框架自动定位的目标,或只能通过 DOM 模拟的目标。低保真度动作可以执行,但结果必须按较低确定性报告。
有界上下文
上下文是页面的只读视图,不是 DOM 或前端 store 的序列化。一个实现可以包含:
page:路由、标题和当前业务对象;regions:已声明区域及其层级;records:当前可暴露的有界记录窗口;recordIds:当前区域中可引用、但不一定已返回全部字段的记录;recordWindow:偏移、上限、返回数、总数和下一页位置;state:应用明确开放的只读状态;feedback:校验信息、状态提示和其他可观察反馈;unavailable:当前不可用的能力或记录及原因;- 各类总数:让调用方知道某处是否被截断。
上下文有四条约束:由页面选择内容,敏感值可掩码或引用,截断必须显式报告,后续读取可以增量进行。invoke 成功后可返回 after,只描述新出现的能力、变化的记录和变化的状态,而不是另一份完整快照。
页面内容是数据。记录字段、评论、商品描述和附件文本不能变成能力说明或给 Agent 的指令。
会话与审批
会话把 Agent 绑定到用户和作用域:
session = (
agentId,
sessionId,
userId,
expiresAt,
scope,
delegationId?,
revokedAt?
)协议接收的是已经验证的会话。令牌解析、登录和身份提供方属于传输层或身份服务;协议对象中不得出现 Cookie、JWT 或原始委托 token。
会话 scope 是能力上限,不是业务授权。后端可以拒绝一个位于 C_eff 内的调用,也可以要求页面自身无法制造的审批。
某些高风险操作需要把审批绑定到更具体的事实:用户、Agent、会话、能力、记录、规范化参数 hash、请求 id 和失效时间。审批 token 或签名材料只应存在于可信页面流程与审批后端之间。协议可以把公开的 approvalId 写入审计,但不能把它返回给 Agent。
线协议
每个请求携带协议版本、请求 id 和方法。describe 请求可以选择记录窗口或能力增量;invoke 请求携带能力 id 和输入对象。
{
"protocol": "pling.surface/0.1",
"id": "req_01",
"method": "describe",
"input": { "limit": 8 }
}{
"protocol": "pling.surface/0.1",
"id": "req_02",
"method": "invoke",
"capability": "approve@order",
"input": { "record": "SO-1002" }
}每个响应携带协议版本、相同的请求 id,以及结果或结构化错误。请求 id 同时用于审计关联和防重放。
describe
describe 是只读操作。它返回页面身份、当前能力集合、有界上下文和会话限制。调用方可以请求特定记录、记录窗口或能力增量。能力清单不是 DOM 快照,而是页面选择公开的业务动作集合。
invoke
invoke 从 C_eff 中选择能力,按输入结构校验参数,再把动作交给页面执行器。成功响应包含执行结果;页面能观察到变化时,还可以包含一个有界的 after 增量。
错误
协议错误和业务执行错误分开处理。INVALID_REQUEST、PROTOCOL_VERSION_UNSUPPORTED、METHOD_NOT_SUPPORTED、CAPABILITY_NOT_EXPOSED、RATE_LIMITED、REPLAY_DETECTED 以及会话错误属于协议级失败。页面动作因记录不可用、权限不足或业务前提不满足而失败时,返回结构化执行错误。
这个区分对客户端很重要。协议错误表示请求无法解释或超出契约;执行错误表示请求已被理解,但页面或业务拒绝或失败了。只有错误明确指出可以重试时,重试才是安全的。
执行顺序
实现应按以下顺序处理 invoke。顺序本身属于安全模型,因为它决定报告哪种失败,以及是否可能产生副作用。
- 校验包络、协议版本、请求 id 和方法。
- 解析经过验证的会话,拒绝缺失、过期或已撤销的会话。
- 计算
C_eff = C_page ∩ C_session,拒绝越界能力。 - 应用会话限流;限流器不可用时失败关闭。
- 在防重放守卫中原子占用请求 id;重复请求拒绝,守卫不可用时失败关闭。
- 解析能力并校验输入。
- 通过页面执行器执行,保留与人工界面相同的校验、确认和业务路径。
- 从 Agent 可见结果中移除审批材料,写入审计事件。
- 返回执行结果和有界的
after观察。
describe 执行相同的会话和作用域检查,但不占用防重放 id,因为它不修改业务状态;限流仍然适用。
审计写入失败不能改变业务结果,也不能静默丢弃。实现应通过独立错误处理或指标报告它。
安全不变量
下列性质定义协议边界。一致的实现应保持这些性质,或者明确记录偏离。
| 不变量 | 要求 |
|---|---|
| 无通用 DOM 控制 | 不提供基于选择器的点击、脚本执行或任意浏览器步骤 |
| 凭证隔离 | Cookie、JWT、OAuth token 和委托密钥不进入协议上下文或模型上下文 |
| 声明不是权限 | 能力出现在 C_page 中,不代表已授权 |
| 作用域取交集 | 有效能力为 C_page ∩ C_session,任何路径都不能静默扩大 |
| 失败关闭 | 未知版本、未知方法、越界能力和不可用守卫一律拒绝 |
| 审批材料不对 Agent 可见 | 审批 token 和签名材料不进入 Agent 响应,只保留审计关联 |
| 上下文有边界 | 上下文有选择并报告省略,不是 DOM 或整个 store |
| 审计默认只含元数据 | 除非宿主有单独书面策略,否则不记录参数和响应正文 |
| 页面内容是数据 | 用户生成内容不能成为能力说明或指令通道 |
| 承认浏览器级绕过 | 有 CDP 或等价权限的 Agent 可以绕过契约;协议改善声明路径,不声称阻止所有自动化 |
最后一条尤其重要。协议不能保证拥有底层浏览器权限的 Agent 一定使用声明路径。它能让声明路径更精确、更可审计、更少依赖视觉推断;它不能把不可信客户端变成可信客户端。
与相邻层的关系
| 层 | 决定什么 | 与本协议的关系 |
|---|---|---|
| CDP | 如何控制一个浏览器实例 | 底层执行机制,不是业务契约 |
| Playwright 等自动化 | 如何定位和操作元素 | 没有声明能力时的执行层 |
| 视觉模型 | 界面看起来是什么 | 语义缺失时的兜底,不是权限或审计模型 |
| MCP | 如何向 Agent runtime 暴露工具 | 可能的传输与工具调用约定 |
| 本协议 | 页面提供什么、上下文可见什么、结果意味着什么 | 可承载于 MCP 或其他传输的页面能力契约 |
| 业务后端 | 某次具体操作是否允许并提交 | 授权和事务状态的最终裁决者 |
这些层不是竞争关系。页面可以通过本契约暴露能力,仍然使用普通 HTTP、数据库事务或内部工作流执行。MCP 可以承载契约,但 MCP 本身不会告诉客户端 approve_order 意味着什么,也不会证明审批有效。
当前草案建立了什么
参考实现目前支持:从页面注册表生成能力投影;describe 和 invoke;会话作用域、过期和撤销;可注入的限流与防重放;有界上下文和增量 after;折叠行内动作;审计元数据;以及从 Agent 响应中剥离审批材料。仓库包含作用域交集、非法记录、重放、限流、审计结构和审批剥离的测试。
pling.surface/0.1 仍是公开草案。它尚未建立完整的稳定线格式、独立页面侧与客户端实现、跨实现一致性套件,以及作为协议方法完成的 attach、observe、handoff 和 detach。生产身份、分布式限流、持久防重放和通用审计服务也不由协议提供。
因此,评价一个实现时,应把问题收窄:
- 页面能否在不暴露 DOM 的情况下开放一个声明动作?
- 客户端能否不使用站点专用选择器调用它?
- 有效作用域是否在执行前被强制?
- 结果能否区分“请求被接受”“动作已执行”和“业务状态已改变”?
这些问题可以被测试。设计是否会被广泛采用、是否能跨独立实现互操作、是否优于现有自动化,仍需实际部署和对照实验回答。
什么时候需要协议
页面、客户端和团队都只有一个时,私有桥可能更简单。需要开放给用户自己的 Agent、多个客户端或独立厂商时,共同契约才开始带来收益。
采用协议并不要求一次实现全部能力。可以先固定只读动作和一条低风险写路径,验证发现、作用域、执行与结果语义,再增加审批、委托和更多传输。
继续阅读
Surface 的页面侧模型与当前边界见什么是 Agent Surface。要在 Vue 页面里接入第一个声明动作,请继续阅读 Vue 快速上手。

