Skip to content

从单站点桥到开放协议 ​

一个页面只服务一个页面内助手时,私有桥通常已经足够。页面把动作登记给助手,助手按约定调用,团队可以快速迭代,不必先定义一套公共标准。

当另一个客户端也要操作同一页面时,问题开始变化。新的客户端需要重新理解页面动作,新的页面又需要重新适配已有客户端。此时真正缺少的不是又一层 RPC,而是双方对“能力是什么、能看到什么、谁可以调用、结果意味着什么”的共同语义。

协议的价值就在这里。它不因为系统分布式而存在,而因为独立实现必须对同一组事实达成一致。

私有桥在哪里失效 ​

假设一个订单页面已经接入了页面内助手。助手通过私有对象调用 approve_order,并读取组件内部状态。这个实现可以很好地完成一个产品内的任务,但它把四件事绑定在一起:

  • 页面如何表达动作;
  • 助手如何发现动作;
  • 双方如何表示会话与权限;
  • 执行结果如何回到助手。

第二个客户端出现后,这些约定要么被复制,要么被另一套近似约定替代。复制之后,页面需要维护两份语义;替代之后,同一动作在不同客户端里可能意味着不同的目标、确认条件或错误处理。

浏览器自动化解决的是另一个问题。CDP、Playwright 和视觉 Agent 可以在没有页面契约的情况下操作界面,适合页面不能或不愿声明能力的场景。但它们不能稳定回答:这个按钮是读取还是不可逆写入,是否需要第二位审批人,Agent 可以引用哪些记录,失败后业务状态是否已经改变。

当这些答案必须在多个实现之间保持一致时,就需要协议。

协议需要固定什么 ​

pling.surface/0.1 把问题压缩为四类对象:

  1. 能力。 页面愿意开放的业务操作,以及它的输入、影响类型和确认要求。
  2. 上下文。 调用方做当前决策所需的有界只读视图。
  3. 会话。 Agent 代表哪个用户、在哪个应用范围内、到什么时候为止可以使用哪些能力。
  4. 结果。 调用是否被理解、是否执行、业务是否改变,以及下一步能观察到什么。

传输、认证、业务授权、存储和模型执行不属于这套协议。协议可以承载在 postMessage、MCP、HTTP 或其他传输上,但传输不会改变能力的业务含义。

设计目标 ​

协议被刻意保持得很小。下列目标分别约束数据模型和实现行为。

目标要求
不依赖 DOM 发现调用方通过结构化响应获得页面身份和可用能力
业务语义动作按业务意图命名,不按选择器或视觉位置命名
有界观察上下文有选择、有边界,不返回整棵 DOM 或整个应用 store
显式控制会话范围、有效期、撤销、确认和后端授权被分别表示
可行动的结果响应区分协议错误、执行错误和业务效果
传输无关同一契约可运行于多种传输
失败关闭未知版本、未知方法、越界能力或不可用的保护适配器一律拒绝

协议不提供通用浏览器 API。它没有 click(selector)、eval(script) 或等价逃生口。它不执行认证,不替业务后端作授权决定,不存储凭证,也不取代 MCP。这些排除项属于设计边界,不是暂时遗漏。

基本模型 ​

设 C_page 为页面声明的能力集合,C_session 为经过验证的会话允许的能力集合。有效能力集合为:

text
C_eff = C_page ∩ C_session

describe 和 invoke 都作用于 C_eff。不在这个集合中的能力,不能被该会话发现或调用,即使页面在全局层面声明了它。

三个判断必须保持分离:

判断回答的问题责任方
声明页面是否实现了这个能力页面应用
暴露这个能力是否在本次会话的作用域内宿主与会话验证
授权当前用户是否有权对这个资源执行这个操作业务后端

前两个判断描述软件边界;第三个是业务决定,可能依赖角色、归属、金额、时间和审批状态。协议可以执行第二个判断,并为第三个判断携带证据;它不能替代第三个判断。

能力声明 ​

能力不是按钮,也不是任意函数。它是一项有稳定 id、业务说明和执行语义的操作。实现需要区分以下核心信息:

字段含义约束
id稳定的能力标识折叠到记录的动作不得包含行内小名
description面向人的业务说明由页面提供,不来自用户生成内容
inputSchema参数的 JSON Schema只描述动作参数,不暴露选择器或凭证
effectread、write 或 destructive必须声明
approvalnone、ask 或 always省略时按应用策略解析
fidelityhigh、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 绑定到用户和作用域:

text
session = (
  agentId,
  sessionId,
  userId,
  expiresAt,
  scope,
  delegationId?,
  revokedAt?
)

协议接收的是已经验证的会话。令牌解析、登录和身份提供方属于传输层或身份服务;协议对象中不得出现 Cookie、JWT 或原始委托 token。

会话 scope 是能力上限,不是业务授权。后端可以拒绝一个位于 C_eff 内的调用,也可以要求页面自身无法制造的审批。

某些高风险操作需要把审批绑定到更具体的事实:用户、Agent、会话、能力、记录、规范化参数 hash、请求 id 和失效时间。审批 token 或签名材料只应存在于可信页面流程与审批后端之间。协议可以把公开的 approvalId 写入审计,但不能把它返回给 Agent。

线协议 ​

每个请求携带协议版本、请求 id 和方法。describe 请求可以选择记录窗口或能力增量;invoke 请求携带能力 id 和输入对象。

json
{
  "protocol": "pling.surface/0.1",
  "id": "req_01",
  "method": "describe",
  "input": { "limit": 8 }
}
json
{
  "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。顺序本身属于安全模型,因为它决定报告哪种失败,以及是否可能产生副作用。

  1. 校验包络、协议版本、请求 id 和方法。
  2. 解析经过验证的会话,拒绝缺失、过期或已撤销的会话。
  3. 计算 C_eff = C_page ∩ C_session,拒绝越界能力。
  4. 应用会话限流;限流器不可用时失败关闭。
  5. 在防重放守卫中原子占用请求 id;重复请求拒绝,守卫不可用时失败关闭。
  6. 解析能力并校验输入。
  7. 通过页面执行器执行,保留与人工界面相同的校验、确认和业务路径。
  8. 从 Agent 可见结果中移除审批材料,写入审计事件。
  9. 返回执行结果和有界的 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 快速上手。