# LineUp UI Surface 与 App Capability 协议 **版本:** 1.0(提案) **状态:** 已有 Web Chat Reference Host **日期:** 2026-08-02 > **汇总入口:** 本文保留 Surface 与 Capability 的协议和安全细节。后续 Runtime、SDK、App 管理及旧 `app.id` 字段的兼容迁移以 [LineUp App 最终设计方案](app_final_design.md) 为准。 ## 1. 目标 LineUp 的 Agent 不应只能回一段文本,也不应获得在宿主 App 任意执行代码的权限。本协议定义一个类似“小程序表现层”的受控扩展模型: ```text Agent / Adapter ├─ lineup.v1.ui.open / patch / close ─────→ UI Surface Host │ └─ sandbox HTML + CSS + JS ├─ lineup.v1.app.call ─────→ App Capability Registry │ └─ 用户确认 / 系统权限 / 本机执行 ←─ lineup.v1.ui.event / app.result ────── 用户动作或受控调用结果 ``` 它有两层,不可混用: | 层 | 作用 | 能做什么 | 不能做什么 | |---|---|---|---| | `UI Surface` | 呈现交互界面 | 显示 HTML/CSS/JS、收集用户事件、接收状态更新 | 读取宿主登录态、直接访问设备能力、直接调用 IM / 网络 | | `App Capability` | 调用宿主上层应用能力 | 在能力注册、权限和用户确认后打开链接、写剪贴板、选文件、调用原生模块等 | 由 Surface 脚本绕过权限直接调用 | 标准 Markdown、文本、状态、进度、choice、confirm、input 等仍应优先由宿主内置渲染器实现。Surface 适用于仪表盘、地图、复杂表单、图表、可视化编辑器等无法由内置组件良好表达的界面。 ## 2. 安全模型(不可省略) 1. Agent 的 UI bundle 被视为**不可信内容**,不是 App 代码的一部分。 2. Web Host 必须使用独立 origin 的 `iframe sandbox="allow-scripts"`。禁止 `allow-same-origin`、`allow-top-navigation`、`allow-popups`、`allow-forms`。 3. Surface 的 CSP 至少为 `default-src 'none'; connect-src 'none'; img-src data: blob:; style-src 'unsafe-inline'; script-src 'unsafe-inline'`。默认不得联网、加载远程脚本、访问摄像头或地理位置。 4. 宿主与 Surface 仅用 `postMessage` 通信;宿主必须同时验证 `event.source`、消息命名空间、`instance_id`、事件名、JSON 类型与大小。 5. Surface 事件只是“用户意图”回传。任何上层 App / 原生能力都必须由 Agent 另行发送 `app.call`,再由宿主依据注册表、风险等级和用户授权执行。 6. Content 不得写入宿主 DOM。Markdown 使用解析器后仍须 sanitizer;HTML Surface 只能放进 sandbox `srcdoc`。 7. 生产环境应只接受已签名或在 Agent allowlist 中的 bundle hash;Reference Host 先以严格 sandbox 保障隔离,并保留 `app.integrity` 字段用于升级。 ## 3. 消息类型 所有消息均使用既有 `lineup.v1` 信封。`conversation_id`、`id`、`sender`、`target` 的规则不变。 | Type | 方向 | 含义 | |---|---|---| | `lineup.v1.ui.open` | Agent → Client | 创建或替换一个 Surface 实例 | | `lineup.v1.ui.patch` | Agent → Client | 向已打开实例推送新的状态;不可注入或替换代码 | | `lineup.v1.ui.close` | Agent → Client | 关闭实例 | | `lineup.v1.ui.event` | Client → Agent | Surface 中的用户事件或用户关闭事件 | | `lineup.v1.client.inventory` | Client → Agent | 当前 Host 的 revisioned 最小接口清单:标准组件、应用中心已启用 app Surface 与 policy 筛选后的 capability | | `lineup.v1.app.list` | Client → Agent | 可选的兼容 capability-only 投影;不得替代完整 inventory 或宣称安装/授权 | | `lineup.v1.app.call` | Agent → Client | 请求调用一个声明过的 capability | | `lineup.v1.app.result` | Client → Agent | 调用完成、拒绝、失败或不支持的结果 | 未知的 `ui.*` 或 `app.*` type 只能安全显示或回复 `unsupported`,不得执行。 ## 4. UI Surface 合约 ### 4.1 打开 ```json { "v": 1, "id": "msg_ui_01", "type": "lineup.v1.ui.open", "conversation_id": "conv_01", "sender": {"kind": "agent", "id": "agent_hermes_main"}, "payload": { "instance_id": "weather.dashboard.01", "app": { "id": "com.lineup.weather.dashboard", "name": "天气面板", "version": "1.0.0", "integrity": "sha256-BASE64_DIGEST", "html": "
", "css": "main { padding: 16px }", "js": "document.querySelector('#refresh').onclick=()=>LineUpSurface.event('refresh',{unit:'c'})" }, "state": {"city": "北京", "temperature": 22} } } ``` 约束: - `instance_id` 在一个会话内唯一,格式为 `[A-Za-z0-9._:-]{1,128}`;相同 ID 的 `ui.open` 表示替换旧实例; - `app.id` 为反向域名风格的稳定应用 ID,`version` 为 SemVer; - `html`、`css`、`js` 是 Surface bundle;单个 bundle 的生产上限建议为 160 KiB,资源应使用经过 Artifact 管理和签名的本地引用,禁止任意公网 URL; - `state` 必须是 JSON object。UI bundle 将在 `ready` 后和每次 `ui.patch` 收到它; - **`ui.patch` 只能更新 `state`,绝不能更新 HTML/CSS/JS。** 若要升级 bundle,关闭旧实例并以新 `version` 打开新实例。 ### 4.2 Surface bridge Host 注入唯一的全局对象: ```js LineUpSurface.event('refresh', { unit: 'c' }) LineUpSurface.onState((state) => render(state)) LineUpSurface.resize(document.documentElement.scrollHeight) ``` 它只允许以下上行消息: ```json { "namespace": "lineup.surface.v1", "type": "event", "event": "refresh", "data": {"unit": "c"} } ``` Host 将其转换为: ```json { "type": "lineup.v1.ui.event", "payload": { "instance_id": "weather.dashboard.01", "event": "refresh", "data": {"unit": "c"} } } ``` `event` 采用 `[A-Za-z][A-Za-z0-9._:-]{0,63}`,`data` 必须为小于 16 KiB 的 JSON object。Surface 不拥有 `app.call` bridge。 ### 4.3 状态更新与关闭 ```json {"type":"lineup.v1.ui.patch","payload":{"instance_id":"weather.dashboard.01","state":{"city":"上海","temperature":28}}} ``` ```json {"type":"lineup.v1.ui.close","payload":{"instance_id":"weather.dashboard.01","reason":"completed"}} ``` 用户主动关闭时 Client 应回送 `ui.event`,其中 `event` 为 `close`。 ## 5. App Capability 合约 ### 5.1 动态 Client Inventory 客户端在会话建立完成后,以及应用中心的 app 启用/停用/升级、Host policy 或 capability 可见性变化时,向**对应 Agent 会话**发送 `client.inventory`。它有单调递增或不可复用的 `revision`;Agent 必须用最新 revision 决定可请求的组件、Surface 和能力。收到撤销后的旧 app id、Surface 或 capability 引用时,Client 只返回确定的 `unsupported / revoked` 结果,绝不回退到执行或加载远端 bundle。 ```json { "type": "lineup.v1.client.inventory", "payload": { "revision": "catalog-42", "standard_components": [ {"id":"choice","version":"1"}, {"id":"confirm","version":"1"}, {"id":"input","version":"1"} ], "applications": [ { "id":"com.lineup.svg-canvas", "version":"1.0.0", "surfaces":[{"id":"canvas","events":["draw","resize","snapshot"]}] } ], "capabilities": [ {"name":"artifact.save","version":"1.0","risk":"user_confirmation"} ] } } ``` `applications` 只能列出应用中心中**已启用且已在本地完成相应阶段验证**的应用,不包含 bundle 源码、安装来源、登录态、文件路径或用户内容。SVG 画板这样的 app 只允许 Agent 以 app id 创建受限 Surface,并通过用户触发的 `ui.event(draw / resize / snapshot)` 获取声明性画布事件;inventory 不授予远程脚本注入、读取宿主 DOM 或本机能力权限。 ### 5.2 Capability 声明(`app.list` 兼容投影) 客户端可在 inventory 之后或 capability 变化时发送 `app.list` 作为仅包含 capability 的兼容投影。只有 inventory / app.list 中声明且仍未被撤销的 `capability` 才能被请求;声明从不等同于授权。 ```json { "type": "lineup.v1.app.list", "payload": { "capabilities": [ {"name":"app.open_url","version":"1.0","risk":"user_confirmation","input_schema":{"type":"object","required":["url"]}}, {"name":"clipboard.write","version":"1.0","risk":"user_confirmation","input_schema":{"type":"object","required":["text"]}}, {"name":"device.pick_file","version":"1.0","risk":"system_permission","input_schema":{"type":"object"}} ] } } ``` 风险必须是下列之一:`display_only`、`user_confirmation`、`system_permission`、`restricted`。后两者不能被记住为永久授权,且必须经过原生系统权限或额外身份校验。 ### 5.3 调用与结果 ```json { "type": "lineup.v1.app.call", "payload": { "call_id": "call_open_docs_01", "capability": "app.open_url", "reason": "打开部署文档供你核对", "expires_at": "2026-08-02T12:10:00Z", "arguments": {"url": "https://example.com/docs"} } } ``` Client 必须向用户说明 capability 和 `reason`,收到确认后再执行,随后使用相同 `call_id` 回传: ```json { "type": "lineup.v1.app.result", "payload": { "call_id": "call_open_docs_01", "status": "completed", "result": {"opened": true} } } ``` `status`:`completed | rejected | cancelled | expired | unsupported | failed`。同一 `call_id` 必须幂等;调用记录与授权决定应由 Gateway 审计。 ## 6. Reference Host 当前实现与阶段边界 截至 2026-08-03,Tauri Reference Host 已完成 M0 和 M1-01~M1-03:严格 Envelope / Kernel / Store / Renderer 边界,Markdown、status/progress/error、choice、confirm、input 的可信原生渲染与本地状态恢复。它**尚未**实现 `client.inventory` 发送、应用中心、`ui.open / patch / close` 的 Surface Host、远端或本地 app bundle 加载、`app.call` 执行、Capability Registry 或 `app.result` 网络回传。 因此,这份协议中的 Surface、inventory 与 Capability 段落是后续 M2~M4 的正式契约,不是当前 Web Chat 已开放的功能。M2 建立应用中心本地启用注册表与隔离 Surface;M3 才生成/更新并通知 `client.inventory`,再实施 Capability policy;M4 负责下载、完整性校验、缓存、回滚与 Host 一致性。当前只以 Tauri Desktop Host 与同代码 Web Reference Host 实现这些边界;不规划独立 Android/iOS/Wails 客户端。