docs/plan/2026-09-08-embed-page-agent-technical-design.md
Rama main · réplica de lectura del repositorio original.
# Web Embed SDK 页面操作 Agent 技术方案
> 状态:方案已确认,待实现
> 范围:`sdk/embed`、Embed WebSocket 链路、后端 Agent 页面工具、浏览器端页面执行器
> 接入目标:业务项目只增加 SDK 脚本和 `MaoChat.init()`,不修改业务按钮、表单、路由或业务回调。
## 1. 需求背景
当前 Web Embed SDK 已支持将 Mao Agent 嵌入业务页面,并提供页面 URL、标题、宿主 `context()` 和用户选中文本作为对话上下文。实现位于 `sdk/embed/src/`,核心入口为 `sdk/embed/src/index.ts`,上下文采集位于 `sdk/embed/src/context/`,通信采用 Embed WebSocket。
当前 SDK 能够让 Agent“了解用户提供的页面信息”,但不能主动读取页面 DOM、获取页面截图,也不能修改页面内容或触发页面交互。CLOUD 会话中的工具由服务端执行,服务端无法直接访问用户浏览器中的业务页面。
本需求将 SDK 从“嵌入式对话窗口”扩展为“浏览器页面操作执行器”:Agent 负责理解页面、选择观察方式、规划动作;SDK 负责在浏览器本地读取页面、截图、滚动、点击、填写和验证结果。
业务方不需要为按钮和表单增加专用 `data-*` 标记,也不需要实现 selector 映射或页面操作回调。
## 2. 产品目标
用户可以在嵌入式 Mao 对话中要求 Agent:
- 查看当前页面有哪些可操作元素;
- 获取当前可视区域截图,辅助理解视觉布局;
- 滚动页面并重新观察;
- 点击普通按钮、链接、标签和展开控件;
- 填写文本、数字、日期、文本域和可编辑内容;
- 选择下拉、单选和多选项;
- 使用键盘输入和按键;
- 观察动作后的页面变化;
- 在用户授权范围内完成连续或批量页面操作。
执行过程必须可解释、可中止、可验证。Agent 不直接获得 CSS selector 或任意 JavaScript 执行能力,只能使用 SDK 为当前页面快照生成的元素引用。
## 3. 明确范围
### 3.1 本期必须实现
1. 页面交互元素结构化快照。
2. 当前可视区域 DOM 截图。
3. SDK 排除自身 UI 的截图和页面扫描。
4. 页面操作双向 WebSocket 协议。
5. `inspect`、`screenshot`、`scroll`、`focus`、`fill`、`select`、`check`、`uncheck`、`click`、`keyboard`、`wait`、`observe` 动作。
6. 单动作和有序批量动作。
7. `snapshotId + elementId` 作用域内的 opaque 元素引用。
8. 动作前元素有效性检查和动作后结果验证。
9. 用户授权级别:每次确认、本次任务授权、完全授权。
10. 授权跨刷新持久化,按用户、站点 Origin、Agent、操作范围和授权版本隔离。
11. SDK 浮窗中的动作日志、目标元素高亮、执行状态、错误详情和停止任务入口。
12. Agent 主动请求页面快照、截图和页面变化信息;SDK 不主动将 DOM 变化推送给 Agent。
13. 当前标签页 SPA 路由和整页导航感知;导航后旧快照失效。
14. 主文档、同源 iframe、开放 Shadow DOM 的元素发现和操作。
15. 与 React/Vue 受控表单兼容的值写入和事件触发机制。
### 3.2 本期明确不做
1. 任意 JavaScript 执行。
2. 通过屏幕坐标进行点击或拖拽。
3. 整页拼接截图;截图只覆盖当前可视区域。
4. 自动操作新标签页、其他标签页或浏览器地址栏。
5. 跨域 iframe 内的 DOM 读取和操作。
6. 闭合 Shadow DOM 内的 DOM 读取和操作。
7. 文件选择器、文件上传、下载确认和系统级弹窗。
8. 验证码、人机验证和浏览器权限弹窗。
9. 绕过浏览器安全策略、CSP、同源策略或真实用户手势限制。
10. SDK 自身 Shadow DOM 和 UI 的读取、截图和操作。
11. 让业务方提供页面 selector、字段映射或操作回调作为接入前提。
12. SDK 将页面变化持续、自动推送给 Agent。
13. 将页面操作结果伪装成普通用户消息发送。
14. 通过授权绕过浏览器安全边界。
### 3.3 高风险操作边界
完全授权允许所有 SDK 可访问的 DOM 页面操作,包括提交类按钮,但不能突破本方案列出的浏览器和系统边界。
授权级别如下:
| 级别 | 行为 |
|---|---|
| `per_action` | 每个页面动作执行前要求用户确认。 |
| `task` | 用户授权后,本次 Agent 任务内按授权范围执行;任务结束、刷新、切换会话或撤销后失效。 |
| `full` | 用户明确授权后,当前会话及持久化授权范围内允许所有 SDK 可访问的 DOM 页面操作。 |
授权要求:
- 默认级别为 `per_action`;
- 宿主 `init` 可以指定初始级别或收紧动作范围,但不能静默替用户授予高权限;
- 用户可以在 SDK 浮窗中升级、降级和撤销授权;
- 授权策略写入浏览器存储时不保存 Token;
- 授权按 `serverUrl`、用户身份、Origin、`agentId`、操作范围和授权版本隔离;
- 高风险动作在 `per_action` 下必须逐次确认;
- `task` 和 `full` 下仍执行动作前的元素有效性检查、动作后验证和硬性禁止项拦截。
## 4. 用户交互流程
### 4.1 页面观察
```text
用户提出页面任务
→ Agent 请求 page_inspect
→ SDK 返回当前可见交互元素快照
→ Agent 判断信息是否足够
→ 必要时请求 page_screenshot
→ 必要时请求 page_scroll 后再次 inspect/screenshot
```
SDK 默认只采集当前可见区域的交互元素。Agent 需要更多内容时主动请求滚动、局部详情或截图。SDK 不在空闲状态持续上传 DOM。
### 4.2 页面操作
```text
Agent 生成单动作或有序动作批次
→ SDK 校验 snapshotId、elementId、元素状态和授权
→ SDK 展示动作反馈/请求用户确认
→ SDK 执行浏览器动作
→ SDK 等待页面同步和异步变化
→ SDK 验证结果
→ SDK 返回 page_action_result
→ Agent 决定下一步
```
批量动作不是无条件连续执行:每步都必须重新检查引用和权限;页面结构变化、动作失败、导航发生或进入需要确认的动作时,SDK 立即停止剩余动作并返回中间结果。
### 4.3 任务生命周期
- 关闭浮窗不停止当前页面任务;
- 用户点击停止、SDK `destroy()`、页面刷新或会话断开时停止任务;
- 不跨标签页恢复任务;
- 当前页面导航后旧快照全部失效,Agent 必须重新 `inspect`;
- SDK 通过动作结果返回页面版本变化,不主动向 Agent 推送变化事件。
## 5. 技术架构
```text
┌───────────────┐ Embed WebSocket ┌────────────────┐
│ 后端 AgentLoop │ ◄──────────────────────────► │ sdk/embed │
│ page_* 工具 │ page_tool_request/result │ 页面执行器 │
└───────────────┘ └───────┬────────┘
│
DOM / iframe / Shadow DOM
│
┌───────▼────────┐
│ 业务页面 │
└────────────────┘
```
### 5.1 后端职责
- 注册 Embed 页面工具;
- 向 Agent 暴露工具 schema 和工具权限;
- 发送页面工具请求并等待 SDK 结果;
- 管理工具调用 ID、超时、取消和失败;
- 维护工具执行状态和审计信息;
- 不直接访问浏览器 DOM;
- 不把页面操作结果改写成普通用户消息。
### 5.2 SDK 职责
- 建立页面快照和 opaque 元素引用;
- 生成当前可视区域截图并排除 SDK UI;
- 执行页面动作;
- 校验授权、快照和元素生命周期;
- 触发兼容框架的 DOM 事件;
- 观察动作后的页面状态;
- 向用户显示动作和确认;
- 返回结构化成功、失败和变化结果。
## 6. 技术选型
### 6.1 页面快照
使用浏览器 DOM API 和可访问性语义构建结构化快照:
- 原生元素:`button`、`a`、`input`、`textarea`、`select`;
- 可编辑内容:`contenteditable`;
- ARIA 控件:`role=button/link/textbox/combobox/checkbox/radio/option/menuitem`;
- 标签来源:`label`、`aria-label`、`aria-labelledby`、`title`、`placeholder`、邻近文本;
- 状态:`visible`、`inViewport`、`disabled`、`readonly`、`required`、当前值和选项;
- 几何信息:当前视口中的坐标和尺寸;
- 表单归属:`formId`、`formName`、`action`、`method`。
默认不返回不可见元素、SDK 自身元素或不允许采集的字段。元素快照不向 Agent 暴露 CSS selector、XPath 或完整 `outerHTML`。
### 6.2 元素引用
每次快照生成:
```ts
interface PageSnapshot {
snapshotId: string
pageVersion: string
url: string
title: string
viewport: ViewportInfo
elements: PageElement[]
}
interface PageElement {
elementId: string
kind: 'button' | 'form-control' | 'link' | 'other'
role: string
type: string
label: string
text: string
value: unknown
options: PageOption[]
visible: boolean
inViewport: boolean
disabled: boolean
readonly: boolean
required: boolean
rect: Rect
}
```
SDK 内部保存 `snapshotId → elementId → Element` 映射。`elementId` 只在对应快照有效期内使用。页面路由、DOM 重建、关键属性变化或快照过期后,旧引用必须拒绝执行。
### 6.3 截图
首版使用浏览器端 DOM 截图方案生成当前可视区域截图。截图必须:
- 截取当前视口,不生成整页拼接图;
- 使用固定输出尺寸、格式和大小上限;
- 排除 SDK host 及 Shadow DOM;
- 支持截图前按授权级别进行敏感区域遮罩;
- 完全授权允许在授权范围内发送未遮罩截图;
- `per_action` 下发送原图必须显示确认;
- 不将图片写入 `localStorage` 或 BroadcastChannel;
- 失败时返回明确的截图错误,不阻断普通 DOM 观察。
### 6.4 页面通信协议
在现有 Embed WebSocket 上增加页面工具双向协议。工具请求必须包含调用 ID和页面会话标识:
```ts
interface PageToolRequest {
type: 'page_tool_request'
requestId: string
sessionId: number
tool: PageToolName
snapshotId?: string
actions?: PageAction[]
requireConfirmation?: boolean
}
interface PageToolResult {
type: 'page_tool_result'
requestId: string
success: boolean
pageVersion: string
snapshotId?: string
result?: unknown
stoppedAt?: number
error?: {
code: string
message: string
elementId?: string
}
}
```
工具集合:
```text
page_inspect
page_screenshot
page_scroll
page_focus
page_fill
page_select
page_check
page_uncheck
page_click
page_keyboard
page_wait
page_observe
```
动作可单独传递,也可通过有序 `actions` 批量传递。每个需要元素的动作都绑定 `snapshotId` 和 `elementId`。
### 6.5 页面动作执行
- `fill` 使用目标元素原生 value setter,并触发 `input`、`change`、`blur`;
- 对 React/Vue 受控组件验证 DOM 值与页面状态是否同步;
- `select` 优先操作原生 `select`,对可识别 ARIA 组件执行语义动作;
- `check/uncheck` 验证 `checked` 或对应 ARIA 状态;
- `click` 只对快照中可见且可操作的元素执行,不使用坐标点击;
- `keyboard` 支持文本输入、普通按键和组合键,不执行任意脚本;
- `scroll` 只操作当前页面或可识别的同源可滚动容器;
- `wait` 有最大等待时限;
- `observe` 返回页面版本、URL、标题和结构化变化摘要。
### 6.6 页面变化
SDK 不主动将变化推送给 Agent。页面执行器可以使用本地 `MutationObserver`、URL 监听和短时等待来确定动作结果,但只在收到 Agent 的 `page_observe` 或下一次页面工具请求时返回结构化信息。
变化结果至少包含:
- URL、标题是否变化;
- 页面版本是否变化;
- 新增/移除/更新的交互元素数量;
- 目标元素值或状态是否变化;
- 是否出现可识别错误/成功提示;
- 是否发生导航或弹窗状态变化。
## 7. 授权与安全
### 7.1 页面数据
页面快照默认仅发送当前可见交互元素。密码、验证码、银行卡、支付信息及其他疑似敏感字段按授权规则处理:
- `per_action`:读取或写入前逐次确认;
- `task`:在本次任务授权范围内允许;
- `full`:在持久化授权范围内允许;
- 浏览器不可访问或系统级控件不因授权而可访问。
截图遵循相同授权模型。SDK UI 永远不进入截图和页面快照。
### 7.2 操作安全
- 不接受 Agent 提供的任意 selector、XPath 或脚本;
- 不允许操作快照之外的元素;
- 操作前验证引用、可见性、可操作性和页面版本;
- 操作后验证目标状态和页面变化;
- 工具请求具备超时、取消和 requestId 幂等处理;
- 页面导航后取消旧页面动作;
- 用户可随时停止当前任务;
- 授权策略变更、撤销和关键动作写入 SDK 事件与后端审计日志;
- Token 不进入页面快照、截图、授权存储、URL 或 BroadcastChannel。
## 8. 实现步骤
1. **梳理并固定现有 Embed 协议契约**:读取 `sdk/embed/src/core/ws-client.ts`、`controller.ts`、`core/store.ts` 与后端 `streaming-ws-handler.ts`,定义页面工具请求、结果、取消、错误和超时语义。
2. **新增共享协议类型**:扩展现有共享协议类型,加入页面工具请求、结果、快照、元素、动作和授权类型。
3. **实现页面扫描器**:新增页面交互元素采集模块,覆盖主文档、同源 iframe 和开放 Shadow DOM;排除 SDK host、不可见元素和禁采集字段。
4. **实现快照生命周期**:生成 `snapshotId/pageVersion/elementId`,保存运行时引用,处理导航、DOM 重建和快照失效。
5. **实现截图模块**:生成当前可视区域截图,排除 SDK UI,处理敏感区域遮罩、尺寸上限、跨域图片失败和结果编码。
6. **实现页面执行器**:实现滚动、聚焦、填写、选择、勾选、点击、键盘输入、等待和观察,并补齐 React/Vue 受控控件事件触发与校验。
7. **实现授权中心**:实现 `per_action/task/full`,持久化授权按 Origin、用户、Agent、范围和版本隔离,提供升级、降级、撤销和失效处理。
8. **实现 Embed WS 页面工具通道**:接收后端请求、执行单动作/批量动作、支持确认中断、超时、取消和结构化结果回传。
9. **实现后端 `page_*` 工具**:注册工具和 schema,接入 Agent 工具权限、工具调用状态、结果等待、取消、超时和审计。
10. **实现操作反馈 UI**:新增动作状态、目标元素高亮、确认卡片、执行日志、失败详情和停止入口;确保页面扫描和截图排除 SDK UI。
11. **接入导航和任务生命周期**:监听 URL/title/SPA 路由变化,失效旧快照;在刷新、销毁、断开和用户停止时取消页面任务。
12. **补充测试**:覆盖协议状态机、元素扫描、快照失效、截图排除、授权持久化、单步/批量执行、事件触发、导航取消和安全边界。
13. **更新文档和构建链路**:同步 Embed SDK 接入文档、后端工具说明、CSP/图片上传要求、授权说明和明确不支持清单;按仓库规则更新根 `CHANGELOG.md`,并同步 `skills/mao-cli/`。
## 9. 落地清单
### 9.1 SDK 新增模块
```text
sdk/embed/src/page/
├── page-scanner.ts # 主文档/同源 iframe/开放 Shadow DOM 扫描
├── snapshot-manager.ts # snapshotId、pageVersion、elementId 生命周期
├── page-executor.ts # 页面动作执行和结果校验
├── visibility.ts # 可见性、遮挡、可编辑状态判断
├── framework-events.ts # React/Vue/原生控件事件触发
├── screenshot.ts # 当前视口截图和 SDK UI 排除
├── authorization.ts # 授权级别、持久化、撤销和失效
├── page-tool-client.ts # WS 页面工具请求/结果/取消
└── page-types.ts # SDK 页面能力类型
```
### 9.2 SDK UI 修改
```text
sdk/embed/src/ui/
├── PageActionCard.vue # 页面动作状态和执行日志
├── PageActionConfirm.vue # 授权/高风险动作确认
├── PageElementHighlight.vue # 页面目标元素高亮
└── ChatPanel.vue/RootApp.vue # 页面任务状态、停止入口和授权入口
```
### 9.3 后端修改
```text
backend-ts/src/harness/
├── tool/impl/page-*.ts # page_* 内置工具实现
├── tool/ # 工具注册和 schema
├── ... # AgentLoop/ToolDispatcher 页面工具委托
backend-ts/src/session/ws/
├── streaming-ws-handler.ts # Embed 页面工具请求和结果帧
├── streaming-ws-registry.ts # 页面工具请求路由和连接生命周期
└── ... # 协议类型、超时和取消处理
```
后端页面工具必须只能在 `client=embed` 的页面会话中使用;桌面 LOCAL 工具链不复用页面 DOM 执行器。
### 9.4 测试清单
- 页面快照只返回可见交互元素;
- SDK host 不出现在快照和截图;
- 密码/验证码/敏感字段按授权级别处理;
- 元素引用在页面重渲染和导航后失效;
- 主文档、同源 iframe、开放 Shadow DOM 扫描;
- 跨域 iframe、闭合 Shadow DOM 被明确拒绝;
- 原生和 React/Vue 受控输入值正确更新;
- 下拉、单选、多选、checkbox 状态正确回写;
- 单动作和批量动作在中间失败时停止剩余动作;
- 高风险操作遵循三档授权;
- 授权跨刷新恢复且按用户/Origin/Agent 隔离;
- 用户停止、断开、刷新和导航能取消任务;
- 页面变化由 Agent 主动 `inspect/observe` 获取,不发生 SDK 主动推送;
- 页面截图只生成当前视口且不包含 SDK;
- WebSocket requestId 幂等、超时、取消和重连行为正确;
- Agent 只可使用 opaque elementId,不可注入 selector 或任意脚本。
## 10. 验收标准
1. 业务页面只引入 SDK 脚本并调用 `MaoChat.init()`,无需修改业务按钮、表单、路由或回调。
2. Agent 能主动获取当前可见交互元素快照,并获得稳定的当前快照元素引用。
3. Agent 能按需获取不包含 SDK UI 的当前可视区域截图。
4. Agent 能通过单动作或批量动作完成滚动、聚焦、填写、选择、勾选、键盘输入和点击。
5. 每个页面动作执行前均校验授权和元素引用,执行后均返回结果验证。
6. 页面结构变化或导航后,旧元素引用不能继续执行,Agent 能重新获取快照。
7. 页面操作期间用户能看到目标元素、动作日志、执行状态并能停止任务。
8. 默认每次确认;任务授权和完全授权可由用户明确授予,完全授权可跨刷新持久化。
9. SDK 不主动向 Agent 推送页面变化,Agent 可通过工具请求获取最新状态。
10. 同源 iframe 和开放 Shadow DOM 可操作;跨域 iframe、闭合 Shadow DOM、系统弹窗、文件选择器、验证码和其他标签页明确失败并返回原因。
11. 任何页面操作都不能通过 Agent 参数执行任意 JavaScript、任意 selector 或坐标点击。
12. 后端 Agent、Embed SDK 和协议测试全部通过;SDK 构建产物和接入文档同步更新。
## 11. 主要风险与应对
| 风险 | 应对 |
|---|---|
| 页面自定义组件缺少语义 | 优先使用 DOM/ARIA;无法可靠识别时返回不支持,不猜测操作。 |
| React/Vue 受控状态不同步 | 使用原生 setter、标准事件和结果回读;失败时停止并报告。 |
| 页面重渲染导致 elementId 失效 | 所有引用绑定 snapshotId/pageVersion,失效后强制重新观察。 |
| 截图体积和性能不可控 | 只截当前视口,限制尺寸/格式/大小,Agent 按需请求。 |
| 页面数据泄露 | 默认可见交互元素、授权控制、敏感遮罩、排除 SDK UI、不落盘截图。 |
| Agent 批量误操作 | 每步校验、变化即停、高风险动作确认、支持用户停止。 |
| 跨域和系统控件不可控 | 明确拒绝并返回可解释错误,不尝试绕过浏览器安全机制。 |
| 后端和浏览器状态不一致 | requestId、超时、取消、页面版本和结果验证共同对账。 |
## 12. 结论
该能力可以在当前 Embed SDK 基础上实现,但本质上不是增加一个表单 API,而是新增一条“后端 Agent 页面工具 → 浏览器 SDK 执行器 → 页面工具结果”的双向执行链路。
最核心的产品承诺是:
> 业务项目只需完成 SDK 初始化,Mao Agent 即可在授权范围内观察和操作普通可访问页面;页面交互由 SDK 在浏览器本地执行,Agent 不直接接触 DOM,也不能绕过浏览器安全边界。
方案以 Agent 主动观察、opaque 快照引用、单步/批量执行、动作后验证和可持久化授权为核心,既覆盖常见页面操作,也明确排除了普通 Web SDK 无法安全或可靠实现的浏览器边界。