业务页面旁边有了助手,用户很快会问:“这一行为什么这么高?”人看着屏幕知道是哪一行,助手只收到一句话,未必知道。让用户重述图名、时间和数值,实际是在重新输入已经存在的信息。
我希望页面把准确选区交给助手,助手也能把解释定位回页面。但选区、会话和执行是不同状态,消息必须能对应到正确的页面实例。
上下文包含引用与观察时间
资源引用用于重新查询,必要快照保存用户当时看到的数值、单位和条件。两者各有用途:实时值变化以后,新查询不能冒充当时画面的值,快照也不能替代当前访问授权。
本轮采用以下载荷声明,字段与接收器对应:
type PageContext = {
version: 1;
pageInstanceId: string;
conversationId: string;
selectionRevision: number;
selection: {
pageId: string;
componentId: string;
resourceRef: string;
observedAt: string;
filters: Record<string, unknown>;
snapshot?: Record<string, unknown>;
};
};内容按任务和权限选取,不把整页数据无条件上传。用户选择一行,也不自动等于同意把全部内容发给模型。可以提供“问 AI”动作,或者在发送区让用户核对上下文。
消息来源不只是一段 origin 字符串
跨窗口消息要检查 origin、event.source、消息结构、协议版本、页面实例和会话。发送方使用明确 targetOrigin,不对敏感数据采用通配目标。
function receiveContext(event, expected) {
if (event.origin !== expected.origin) return;
if (event.source !== expected.frame.contentWindow) return;
const value = event.data;
if (!value || value.version !== 1) return;
if (value.pageInstanceId !== expected.pageInstanceId) return;
if (value.conversationId !== expected.conversationId) return;
if (!Number.isSafeInteger(value.selectionRevision)) return;
if (value.selectionRevision <= expected.lastRevision) return;
// validatePayload 校验类型、长度和允许的字段,成功后再更新状态。
const context = expected.validatePayload(value);
expected.commit(context);
}validatePayload 还应限制字段、类型和长度,提交后更新最后接受的选区版本。相同或过期版本不重新覆盖。来源可信只证明来自预期页面,服务端仍要核对资源授权,页面文案也不能变成系统指令。
沙箱中的不透明来源不能用把字符串 null 加白名单的方式处理。需要与目标实例绑定的受限握手或消息通道,并继续保留业务授权。
高亮命令也有有效版本
助手返回定位、高亮或刷新的受限命令,包含页面实例、组件与依据的 selectionRevision。用户已经换页或重开窗口,旧命令返回 stale-context;对象存在并完成处理,才返回 applied。
组件名称相同不代表同一对象。接收端不运行模型给出的任意脚本,每种命令有固定参数与结果。修改业务数据则走服务端动作、确认与权限,不把 UI 消息变成另一个写入口。
回执说明完成了哪一层
消息收到、UI 完成和业务动作生效各自记录。高亮由页面确认,后台写入由实际接口结果确认。每条消息标识关联自己的结果,超时记录未确认,不补一个成功状态。
任务已经开始后,用户改选不会改变它的目标。新选区可以用于下一次提问,旧解释保留所依据的对象与时间。快照和当前数据不同,就明确说明差异。
验收使用快速切行、关闭重开、迟到消息、同源其他 frame 和资源撤权等情形。发送内容与接收对象保持对应,用户才能少描述一些,系统却仍然知道他说的是哪处内容。