初始化 agent_ops 文档治理体系

This commit is contained in:
2026-08-07 16:56:51 +08:00
commit 10840909ab
75 changed files with 15750 additions and 0 deletions
@@ -0,0 +1,60 @@
# Stage 1:需求分析与架构设计
**状态:** ✅ 已完成
**时间:** 2026-07-24 → 2026-07-27
---
## 目标
完成 LineUp Agents 的产品定位、架构设计、协议定义,输出第一版设计方案。
---
## 完成内容
### 产品定位
- 定义为"远程 Agent 操作交互端",不是任务管理系统
- MVP 范围:局域网直连,不做中转,不做 MCP 封装,不做通知推送
### 架构设计
- 三层协议:通讯协议层、消息协议层、工具协议层
- 局域网直连方案,Agent 插件内开 WebSocket 端口 9527
- 预共享 Token 鉴权
- 手动输入 IP:端口发现方式
### 消息协议
- 统一信封格式:`{ v, id, type, payload }`
- 7 种消息类型:system.hello、system.ping/pong、text、image、tool.list、tool.call、tool.result
### 工具协议
- 三类工具:app(有状态应用)、toolset(无状态工具集)、action(单指令工具)
- app 类型通过 instance_id 管理生命周期,open → use → close
- tool.call / tool.result 标准化格式
### 技术选型
- App 端:Vite + React + Tailwind + shadcn/ui
- 通信:裸 WebSocket,不引入 Socket.io
- Agent 端:Hermes 平台插件优先
### 文档产出
| 文档 | 位置 |
|------|------|
| v1 设计方案 | LineUpAgents/设计/design-v1.md |
| 早期分析与设计 | LineUpAgents/设计/早期分析与设计/ |
| README 项目入口 | LineUpAgents/README.md |
---
## 关键决策
- 不做独立 Channel 进程,插件嵌入 Agent
- 不做 MCP 封装,工具路由用字典映射
- 先纯 Web,后续再考虑 Tauri 壳子
- 结构化消息返回,不是自然语言裸发
@@ -0,0 +1,44 @@
# Stage 2App 端交互原型
**状态:** 🟡 进行中
**时间:** 2026-07-27 →
---
## 目标
搭建 LineUp App 端的 Vite + React 项目框架,完成核心页面的交互原型。不写业务逻辑(WebSocket 通信、工具调用),只搭 UI 和页面流。
---
## 要做的事
### 1. 搭建项目骨架
- 用 Vite + React + TypeScript 初始化项目
- 安装 Tailwind CSS + shadcn/ui
- 配置基础目录结构(pages / components / hooks / types
### 2. 确定核心页面与交互流程
需要跟用户确认的界面:
- 首页(连接 Agent)—— 输入 IP:端口 + Token,点击连接
- 连接状态指示 —— 已连接 / 连接中 / 断开
- 会话主界面 —— 消息流展示(text、image、tool 卡片)
- 消息输入区 —— 输入框 + 发送按钮
- 工具渲染 —— choice、confirm、input 三种工具的 UI 原型
### 3. 输出静态原型
- 每个页面一个独立组件,不带状态管理
- 用 mock 数据展示页面效果
- 串联成可点击的页面流
---
## 待讨论
- 主界面布局:左侧会话列表 + 右侧对话区,还是单栏对话?
- 工具卡片样式:choice 选择按钮的排列方式、confirm 确认/取消的视觉样式
- Dark/Light 主题偏好
@@ -0,0 +1,66 @@
# Stage 2:中转服务器方案设计
**状态:** 🟡 进行中
**最后更新:** 2026-07-30
---
## 目标
建立一套可在本机通过 Docker Compose 启动的 IM 服务环境,供唐僧叨叨客户端完成基础聊天验证,并作为 LineUp 远程 Agent 交互的后续通讯基础。
该环境是一套协作的服务集合,而非两个互相替代的 IM:WuKongIM 是通讯层,唐僧叨叨是业务层。
## 决策记录
| 决策 | 结论 |
|------|------|
| 通讯层 | **WuKongIM v2** |
| 业务层 | **唐僧叨叨服务端 v1.5** |
| 部署方式 | Docker Compose,本地单节点 |
| 业务依赖 | MySQL 8、Redis 7、MinIO |
| 前端入口 | 唐僧叨叨 Web 与 Manager,均仅绑定本机端口 |
| 消息客户端 | 唐僧叨叨客户端或 WuKongIM 官方 SDK |
| Agent 接入 | 后续基于 WuKongIM 官方 SDK 或已验证协议实现;不依赖其他平台的专用机器人网关 |
| 数据策略 | Docker 命名卷持久化;本地环境禁止将密钥写入仓库 |
## 系统架构
```text
唐僧叨叨 Web / 移动客户端
├─ HTTP 业务请求 ───────────► 唐僧叨叨服务端 :8090
└─ TCP / WebSocket 消息 ────► WuKongIM :5100 / :5200
HTTP API ◄────────────┤
└─ Webhook gRPC ─► 唐僧叨叨服务端 :6979
唐僧叨叨服务端 ──► MySQL / Redis / MinIO
```
## 本地端口边界
| 服务 | 容器端口 | 本机端口 | 作用 |
|------|----------|----------|------|
| WuKongIM HTTP API | 5001 | 15001 | 健康检查和本地调试 |
| WuKongIM TCP | 5100 | 15100 | 官方 SDK 的 TCP 长连接 |
| WuKongIM WebSocket | 5200 | 15200 | Web / WebSocket 客户端长连接 |
| WuKongIM 监控 | 5300 | 15300 | 本地监控 |
| 唐僧叨叨 API | 8090 | 18090 | 业务 API |
| 唐僧叨叨 Web | 80 | 18082 | 用户聊天界面 |
| 唐僧叨叨 Manager | 80 | 18083 | 后台管理 |
| MinIO | 9000 / 9001 | 19000 / 19001 | 文件服务及其控制台 |
所有映射通过 `HOST_BIND_IP` 精确绑定到指定宿主机网卡。当前部署目标为 Tailscale 地址 `100.121.118.116`,同时 `EXTERNAL_IP` 设为该地址,以供客户端连接。数据库、Redis 和容器间 gRPC 不发布到宿主机;如需进一步收紧公开面,可取消 Manager、监控和 MinIO 的端口映射。
## LineUp 集成边界
基础 IM 环境与 LineUp Agent 集成分两个阶段验收:
1. 先验证唐僧叨叨用户注册、登录、单聊/群聊、文件上传和服务重启后的数据持久化。
2. 再设计 LineUp 中转适配器。适配器需要处理 WuKongIM 的认证、频道/会话、收发消息及自定义消息载荷;其实现应基于官方 SDK 或严格按已验证协议开发。
`tool.call``tool.result` 仍由 LineUp 定义,但需要先确定与唐僧叨叨/WuKongIM 消息扩展机制的精确映射,不能把尚未验证的 JSON WebSocket 假设写入生产实现。
## 部署与验收
> 该阶段的唐僧叨叨 v1.5 + WuKongIM v2 Compose 套件已于 2026-08-03 退役并从仓库移除。本记录仅保留当时的方案历史;当前本地 IM 环境见 [`infra/wukongim-v3/`](../../infra/wukongim-v3/)。
@@ -0,0 +1,169 @@
# LineUp Agents — 架构设计文档
**状态:** 讨论稿,持续更新
**最后更新:** 2026-07-25
---
## 一、产品定位
LineUp Agents 是一个**远程 Agent 操作交互端**。用户通过手机或 Web App,与运行在本地或服务器上的 AI Agent 进行顺畅的交互。
不是任务管理系统。Agent 的任务拆解、执行、管理,属于 Agent 自己的工作范畴,不属于 LineUp 的职责。
---
## 二、架构总览
```text
┌─────────────────┐ ┌─────────────────────────┐
│ 用户 App 端 │ │ Agent 机器 │
│ (Web / 未来移动端)│ │ │
│ │ │ ┌─────────────────────┐ │
│ ┌─────────────┐ │ │ │ LineUp 插件 │ │
│ │ 工具渲染层 │ │ WebSocket │ │(嵌入 Agent 进程内) │ │
│ │ Confirm │ │◄────────►│ │ │ │
│ │ Input │ │ │ │ WebSocket 端口 │ │
│ │ SVG Renderer │ │ │ └─────────┬───────────┘ │
│ │ ... │ │ │ │ │
│ └─────────────┘ │ │ 进程内通信 │
│ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────▼───────────┐ │
│ │ 消息协议层 │ │ │ │ Agent 核心 │ │
│ │ WebSocket │ │ │ │ (Hermes / │ │
│ │ 连接管理 │ │ │ │ OpenClaw / 其他) │ │
│ └─────────────┘ │ │ └─────────────────────┘ │
└─────────────────┘ └─────────────────────────┘
```
---
## 三、已敲定的设计决策
### 3.1 总体架构
| 决策 | 当期结论 | 后续扩展思路 |
|------|---------|-------------|
| 网络模式 | 局域网直连,App 直接连 Agent 插件暴露的 WebSocket 端口 | — |
| 中转服务 | 不做中转 | 需要远程连接时引入中转服务 |
| Agent 接入方式 | 每个 Agent 框架写一个插件,插件内开 WebSocket 端口 + 进程内通信 | — |
| 端口配置 | 固定端口(默认 9527),支持环境变量覆盖 | — |
| 发现方式 | 手动输入 IP:端口 | mDNS 广播自动发现 |
| App 端技术方向 | 响应式 Web | 原生移动端 App |
| 连接认证 | 预共享 Token,用户在插件配置中设定,App 连接时提供 | 端到端加密,密钥交换 |
| 工具路由方式 | 字典路由,按工具名映射到对应的处理函数 | 可引入框架级路由 |
### 3.2 协议分层
三层结构,层间独立不耦合:
```
第一层:通讯协议层
解决的问题:管道怎么通
包含:WebSocket 连接建立、身份标识交换、心跳保活、断线重连
第二层:消息协议层
解决的问题:消息长什么样子
包含:统一信封格式、消息路由、版本号、消息去重
第三层:工具协议层
解决的问题:App 能干什么
包含:工具发现(查询可用工具)、工具调用、返回结果
注:工具定义格式复用 MCP 的 JSON Schema 方式
```
### 3.3 核心数据对象
| 对象 | 含义 | 关键字段 |
|------|------|---------|
| Agent | 一个可连接的 Agent 实例 | id、name、runtime、status |
| Session | 一条对话上下文 | id、agentId、title |
| Message | 用户或 Agent 产生的内容 | id、sessionId、role、type、payload、timestamp |
| Tool | App 端可被 Agent 调用的能力 | name、description、inputSchema、outputSchema |
### 3.4 与 MCP 的关系
- 工具协议层复用 MCP 的 `tools/list``tools/call` 定义方式
- 工具 Schema 格式遵循 MCP 的 JSON Schema 标准
- 通讯协议层和消息协议层是 MCP 未覆盖的部分——补充了远程安全双向传输的能力
### 3.5 工具分类
详见独立文档 [tool-action-design.md](tool-action-design.md)——第一节"工具协议定义"。
| 决策 | 当期结论 | 后续扩展思路 |
|------|---------|-------------|
| 工具结构 | `{ name, type, description, actions[] }`app/toolset)或 `{ name, type, description, inputSchema }`action | — |
| app 生命周期 | open → use → close,通过 instance_id 维护 | — |
| toolset 调用 | 每次独立,无状态 | — |
| action 调用 | 只有一个 action,最简 | — |
| 状态跟踪 | 依赖 Agent 自身对话上下文 | — |
| 与 MCP 对应关系 | Tool = MCP ServerAction = MCP Tool | — |
---
## 四、第一版技术栈
| 端 | 选型 |
|----|------|
| Agent 插件 | 按 Agent 框架选择语言(Hermes 用 PythonOpenClaw 待定) |
| App 端(Web | 待定(Next.js / Vite + React |
| 通信方式 | WebSocket(裸 ws,不引入 Socket.io |
| 消息格式 | JSON |
---
## 五、MVP 边界
| 做 | 不做 |
|----|------|
| 局域网内 App 直连 Agent | 远程广域网连接 |
| App 收发消息、查看 Agent 状态 | 任务生命周期管理 |
| Agent 调 App 的工具(confirm/input 等) | 审批卡片系统 |
| 简单的工具发现协议 | 多 Agent 编排、调度 |
| 文本 + 富内容消息展示 | 通知推送 |
| | 中转服务 |
| | MCP 封装(第一期不做) |
---
## 六、一次完整的交互流程(以 choice 工具为例)
```
Agent 需要用户做选择
├── Agent 发起 { type: "tool.call", payload: { name: "choice", call_id: "c1", arguments: { question: "部署到哪个环境?", options: ["测试", "预发布", "生产"] } } }
├── Agent 插件收到 → 通过 WebSocket 推给 App
├── App 渲染选择界面,三个选项展示给用户
├── 用户点击"预发布"
├── App 通过 WebSocket 发回 { type: "tool.result", payload: { name: "choice", call_id: "c1", result: { message: "预发布" } } }
├── Agent 插件收到 → 交回给 Agent 核心
└── Agent 拿到结果,继续执行
```
关键原则:
- App 端不关心 Agent 为什么问这个问题,只负责渲染和返回用户操作结果
- Agent 端不关心 App 用什么 UI 渲染的,只负责发起工具调用和处理返回结果
- 调用(Agent → App)和返回(App → Agent)都使用结构化消息,不是自然语言文本
- 整个交互走一条 WebSocket 连接,双向实时,消息协议统一
### 6.1 消息方向与格式
所有消息使用统一信封:`{ v, id, type, payload }`。完整格式定义见 [tool-action-design.md](tool-action-design.md)——第二节"消息协议定义"。
---
## 七、后续可能的扩展
- 加入中转服务,支持广域网远程连接
- 加入端到端加密,中转不可读消息内容
- 加入 mDNS 局域网自动发现(类似 Bonjour)
- 移动端原生 App
- 支持流式数据传输(如实时 SVG 渲染)
@@ -0,0 +1,577 @@
# LineUp IM Agent 交互平台:历史方案与实施计划(已归档)
**版本:** 0.2(提案)
**状态:** 已归档;不作为当前技术路线
**日期:** 2026-07-30
**原决策范围:** LineUp 的 IM 基础、人与 Agent 的交互协议、首个 Hermes 接入,以及唐僧叨叨 / WuKongIM 在体系中的职责。
> 归档说明(2026-08-03):本方案依赖唐僧叨叨业务服务与 WuKongIM v2 运行栈;该路线已经退役。当前项目使用 WuKongIM 3.0 官方源码 + LineUp AppServer,客户端与 Mini Runtime 路线见 `lineup-app/` 和 `设计/02.正式方案/`。本文仅保留作为历史决策与兼容性分析记录。
---
## 1. 决策摘要
LineUp 的定位是一个连接**人、人与人协作、人与智能体**的交互平台。它以即时通讯的可靠连接和消息能力为基础,但不把产品限制为传统的聊天窗口。
LineUp 的核心能力是:用户与 Agent 可以在同一个会话中交换文本、媒体、状态、产物和结构化交互;Agent 可在用户授权边界内调用用户端工具,用户端将结构化结果可靠返回给 Agent。
因此,本方案作出以下正式决策:
1. **LineUp 以唐僧叨叨作为成熟 IM 业务层基础,以 WuKongIM 作为通讯内核。** 用户、联系人、群组、文件、工作台与现有多端客户端能力优先复用;仅在 LineUp 的产品需要超出现有能力时再扩展或替换。
2. **LineUp 的正式 Agent 交互通道采用 `增强后的 LineUp Client + WuKongIM 官方 SDK + LineUp 自定义消息协议`。** 该协议在唐僧叨叨的用户与业务体系内运行,不把 LineUp 限制为机器人文本对话。
3. **LineUp Agent Adapter / Gateway 作为外部常驻服务运行,负责连接 Agent Runtime 与 LineUp 协议。** Hermes 是第一个适配目标,后续可接入其他 Agent Runtime。
4. **唐僧叨叨 Robot Events API 是可复用的机器人接入能力。** 它适合快速提供文本型 Hermes 入口、通知和兼容降级;结构化工具交互的正式主载体仍是 LineUp 自定义消息协议。
5. **WuKongIM AI Plugin 不作为第一阶段核心依赖。** 它保留为后续的服务端路由、低延迟流式输出和自动化处理能力;待确认当前部署版本的插件机制与唐僧叨叨配套版本兼容后再评估。
6. **LineUp Client 是对唐僧叨叨客户端能力的面向 Agent 扩展,而不是孤立重造一个聊天客户端。** choice、confirm、canvas、artifact 等由 LineUp 模块实现,同时保留已有 IM 业务体验。
---
## 2. 产品目标与边界
### 2.1 产品目标
LineUp 让人和 Agent 在远程、跨设备、可恢复的 IM 会话中协作。交互不局限于文本问答,而是包含:
- 人与人:文本、图片、语音、文件与群组协作;
- 人与 Agent:自然语言、任务进度、状态、图像、文件和流式内容;
- Agent 与用户端工具:选择、确认、表单、画板、文件预览、设备能力等;
- Agent 与 Agent:在受控会话或频道中交换结构化协作消息;
- 用户对 Agent 的控制:继续、取消、修改指令、批准、拒绝和恢复。
### 2.2 关键体验目标
用户面对的不是一个只会输出文本的机器人,而是一个可呈现和操作工作内容的 Agent 会话。例如:
```text
Agent:需要确认部署目标
└─ LineUp Client 渲染为 choice 卡片
用户:选择“预发布”
└─ Client 返回结构化 tool.result
Agent:开始执行,持续更新进度
└─ Client 渲染进度与状态,不强迫用户阅读多段文本
Agent:生成架构草图
└─ Client 打开画板,持续接收 canvas.patch
用户:点击“暂停”,修改颜色后继续
└─ Client 发送控制事件或 tool.result
```
### 2.3 明确不承担的职责
- LineUp 不取代 Agent Runtime 的任务规划、工具调用引擎或记忆系统;
- LineUp 不把 WuKongIM 替换为自研消息服务器;
- LineUp 不在第一阶段实现多 Agent 编排平台;
- LineUp 不默认授予 Agent 对用户设备、文件或系统操作的无限权限;
- LineUp 不把所有消息都交给服务端 AI 插件处理。
---
## 3. 总体架构
```text
┌─────────────────────────────────────────────────────────────┐
│ LineUp Client(唐僧客户端能力 + 扩展) │
│ │
│ 会话 / 聊天 / 媒体 / 文件 │
│ Agent 状态 / 进度 / 产物 │
│ Tool Registry / Choice / Confirm / Form / Canvas │
│ LineUp 自定义消息渲染与本地授权 │
└───────────────────────┬─────────────────────────────────────┘
│ WuKongIM SDK 长连接
│ 原生消息 + LineUp 自定义 Payload
┌─────────────────────────────────────────────────────────────┐
│ WuKongIM │
│ │
│ 认证后连接、频道、可靠投递、离线同步、重连、顺序、流消息 │
└───────────────────────┬─────────────────────────────────────┘
│ 指定 Agent 频道 / 绑定路由
┌─────────────────────────────────────────────────────────────┐
│ LineUp Agent Adapter / Gateway │
│ │
│ Actor / Device / Conversation 映射 │
│ LineUp 信封编解码、去重、持久 inbox/outbox │
│ call_id 生命周期、超时、取消、恢复、权限校验 │
│ Hermes Adapter、未来其他 Runtime Adapter │
└───────────────────────┬─────────────────────────────────────┘
Hermes / 其他 Agent Runtime 与其本地工具
┌─────────────────────────────────────────────────────────────┐
│ 唐僧叨叨业务层(复用与扩展) │
│ │
│ 用户与认证 / 联系人与群组 / 文件与对象存储 / 工作台 / 管理端 │
│ Android、Web、PC 基础 IM 能力 / Robot Events(可选接入) │
└─────────────────────────────────────────────────────────────┘
```
### 3.1 组件职责
| 组件 | 负责 | 不负责 |
|---|---|---|
| WuKongIM | 长连接、消息投递、离线同步、频道与消息流 | LineUp 工具语义、设备授权、Agent 会话管理 |
| 唐僧叨叨 | LineUp 的 IM 业务基础:用户、认证、好友、群组、文件、工作台、管理端与既有多端客户端 | 替代 LineUp 的 Agent 交互协议与工具语义 |
| LineUp Client | 在唐僧叨叨客户端基础能力上增加交互 UI、工具注册、本地授权、协议显示与回传 | Agent 任务规划与执行 |
| LineUp Adapter / Gateway | 协议路由、可靠性、会话与调用映射、Agent 适配 | 直接替代 IM 服务 |
| Hermes | 理解用户意图、执行任务、调用其工具、请求用户交互 | 负责移动端 UI 渲染 |
| WuKongIM AI Plugin(后续可选) | 服务端高性能路由、流式和自动化 Hook | 定义 LineUp 协议或取代唐僧叨叨业务层与 Adapter |
---
## 4. 协议分层与消息模型
### 4.1 三层分工
```text
LineUp 交互协议层
└─ 会话语义、Actor、设备能力、工具、状态、产物、取消与确认
WuKongIM 消息传输层
└─ 文本、媒体、文件、自定义 Payload 的可靠投递与同步
WuKongIM 连接层
└─ 认证、长连接、心跳、重连、离线恢复
```
LineUp 不重新实现下层 IM 可靠传输;WuKongIM 也不解释 LineUp 的工具和 UI 语义。
### 4.2 统一 LineUp 信封
所有自定义交互消息使用一个可演进的信封。基础媒体仍优先使用 IM 的原生媒体能力;信封保存其交互语义、引用关系或媒体元数据。
```json
{
"v": 1,
"id": "msg_01J...",
"type": "lineup.v1.tool.call",
"conversation_id": "conv_01J...",
"sender": {
"kind": "agent",
"id": "agent_hermes_main"
},
"target": {
"kind": "device",
"id": "device_phone_01"
},
"timestamp": "2026-07-30T14:00:00+08:00",
"payload": {}
}
```
字段规则:
- `v`:协议主版本。未知主版本必须拒绝执行,只可按安全降级显示;
- `id`:全局唯一的应用级消息 ID,用于幂等去重;
- `type`:以 `lineup.v1.` 为命名空间,禁止与基础 IM 类型混淆;
- `conversation_id`:LineUp 会话 ID,不以某一个 IM message ID 代替;
- `sender` / `target`:显式描述人、Agent、设备或群组,而不只依赖底层频道;
- `payload`:仅由对应 `type` 的 schema 定义。
### 4.3 第一批正式消息类型
| 类型 | 方向 | 目的 |
|---|---|---|
| `lineup.v1.text` | 双向 | 与 Agent 相关的结构化文本元数据;普通 IM 文本仍可原生发送 |
| `lineup.v1.device.hello` | Client → Gateway | 设备注册、客户端版本、协议版本、能力摘要 |
| `lineup.v1.tool.list` | 双向 | 声明或查询可用工具与 schema |
| `lineup.v1.tool.call` | Agent → Client | 请求调用用户端工具 |
| `lineup.v1.tool.result` | Client → Agent | 返回工具结果、拒绝、取消或错误 |
| `lineup.v1.tool.cancel` | 双向 | 取消尚未完成的调用 |
| `lineup.v1.agent.status` | Agent → Client | idle / thinking / waiting_input / running / interrupted / failed |
| `lineup.v1.agent.progress` | Agent → Client | 可合并、可覆盖的进度状态 |
| `lineup.v1.artifact.offer` | Agent → Client | 声明图片、文件、网页、报告、画板等可展示产物 |
| `lineup.v1.canvas.open` | Agent → Client | 创建一个受控画板实例 |
| `lineup.v1.canvas.patch` | Agent → Client | 增量更新画板内容 |
| `lineup.v1.canvas.close` | Agent → Client | 关闭画板实例 |
| `lineup.v1.stream.start/delta/end` | Agent → Client | 流式文本、代码、图形或状态输出 |
| `lineup.v1.ui.open/patch/close/event` | 双向 | 受控 HTML/CSS/JS UI Surface 的生命周期与用户事件 |
| `lineup.v1.app.list/call/result` | 双向 | Client 上层应用能力的声明、授权调用与结构化结果 |
不在第一阶段实现的消息类型可以预留命名空间,但不得先发布无 schema 的行为。
UI Surface 与 App Capability 的完整隔离、生命周期和权限约束见[《LineUp UI Surface 与 App Capability 协议》](lineup-ui-surface-protocol.md)。Surface 不是 Agent 在 Client 主进程执行任意代码的通道;它只能运行在受限容器中,并通过预定义 bridge 回传用户事件。任何上层 App / 原生能力必须经 `app.call`、用户确认和 capability registry 执行。
---
## 5. 用户端工具调用规范
### 5.1 工具注册
Client 在设备上线或能力变化后发布 `tool.list`。每个工具至少声明:
```json
{
"name": "choice",
"version": "1.0",
"type": "action",
"description": "展示选项并返回用户选择",
"inputSchema": {},
"outputSchema": {},
"risk": "user_confirmation"
}
```
工具分为三类:
- `action`:一次性操作,如 `choice``confirm``input`
- `toolset`:无状态工具集合,如计算器、文件选择器;
- `app`:有生命周期的交互应用,如 `canvas`,通过 `instance_id` 管理 `open → use → close`
### 5.2 调用与返回
```json
{
"v": 1,
"id": "msg_01J_call",
"type": "lineup.v1.tool.call",
"conversation_id": "conv_01J",
"sender": { "kind": "agent", "id": "agent_hermes_main" },
"target": { "kind": "device", "id": "device_phone_01" },
"payload": {
"call_id": "call_01J",
"name": "choice",
"action": "select",
"expires_at": "2026-07-30T14:10:00+08:00",
"arguments": {
"question": "部署到哪个环境?",
"options": ["测试", "预发布", "生产"]
}
}
}
```
返回必须携带相同 `call_id`,并且结果为可扩展对象,不长期限制为纯字符串:
```json
{
"v": 1,
"id": "msg_01J_result",
"type": "lineup.v1.tool.result",
"conversation_id": "conv_01J",
"sender": { "kind": "human", "id": "user_01" },
"target": { "kind": "agent", "id": "agent_hermes_main" },
"payload": {
"call_id": "call_01J",
"status": "completed",
"result": {
"selected": "预发布"
}
}
}
```
`status` 的第一版枚举为:
```text
completed | rejected | cancelled | expired | unsupported | failed
```
### 5.3 调用状态机
```text
created
→ delivered
→ acknowledged_by_client
→ waiting_user
→ completed | rejected | cancelled | expired | failed
```
规则:
1. `call_id``conversation_id` 范围内唯一;
2. 同一个 `call_id` 的重复 `tool.call` 必须幂等显示,不能重复执行本地副作用;
3. `tool.result` 必须幂等转交给 Agent,Gateway 需持久记录最终状态;
4. Client 离线时调用可等待到 `expires_at`,不得无限期阻塞 Agent
5. 用户、Agent 或系统取消后,旧调用及旧流输出不可覆盖新一代会话状态;
6. 高风险工具没有显式用户授权时,Client 必须返回 `rejected`,而不是静默执行。
---
## 6. 身份、会话、设备与权限
### 6.1 四种核心对象
| 对象 | 说明 | 示例 |
|---|---|---|
| Human | 一个经过 IM 认证的人类用户 | `user_01` |
| Device | 某个具体 Client 实例 | `device_phone_01` |
| Agent | 一个可接收和处理 LineUp 消息的 Agent 实例 | `agent_hermes_main` |
| Conversation | 人、人群或 Agent 间的一段稳定上下文 | `conv_01J` |
底层 WuKongIM 频道负责投递,LineUp `conversation_id` 负责产品语义。一个 Conversation 可映射到单聊、群聊、主题或多个设备,但映射关系必须由 Gateway 显式维护。
### 6.2 权限原则
1. 默认最小权限:Agent 只看得到被明确路由给它的会话;
2. 设备能力显式声明:没有在 `tool.list` 声明的工具不可调用;
3. 风险分级:`display_only``user_confirmation``system_permission``restricted`
4. 人类可取消:用户在任一 Agent 会话中必须可发出取消/中断;
5. 群聊默认只响应明确提及或授权的 Agent;
6. Gateway 记录调用、确认、结果和取消的审计事件;
7. Agent Runtime 的本地终端、文件和浏览器权限不因 IM 通道自动扩大。
---
## 7. 可靠性、重连与用户插入指令
### 7.1 双层可靠性
```text
WuKongIM
└─ 连接、顺序、离线消息、重连与传输级可靠性
LineUp Gateway
└─ 应用级 idempotency、call_id 状态、inbox/outbox、Agent 执行恢复
```
Gateway 维护持久 inbox/outbox。任何消息只有在成功落入 inbox 后才视为被应用层接收;任何对 Agent 或 Client 的关键发送都要记录投递状态和应用级消息 ID。
### 7.2 用户中断
用户的新指令不能因为旧 Agent 任务运行很久而被阻塞:
```text
Client 新消息
→ WuKongIM 实时送达 Gateway
→ Gateway 按 conversation_id 分发
→ Hermes Adapter 调用 Hermes 的会话中断/追加机制
→ 旧 run 标记为过期 generation
→ 旧 run 的迟到输出禁止回写 Client
→ 新指令立即开始或进入指定队列
```
第一版约定:
- 普通新文本:默认 `interrupt` 当前同会话 Agent run
- `/stop` 或显式“停止”:取消当前 run 并通知用户;
- “完成后再……”:由 Client 或 Adapter 标记为 queued follow-up
- 不同 Conversation:相互独立,可并发处理。
---
## 8. 技术选型与当前环境定位
### 8.1 主通道选择
| 方案 | 结论 | 原因 |
|---|---|---|
| 唐僧叨叨业务层 | 正式业务基础 | 已提供用户、认证、联系人、群组、文件、工作台、管理端及多端客户端基础;LineUp 优先在其上生长,而非在第一阶段重新建设这些通用 IM 业务能力 |
| 唐僧叨叨 Robot Events API | 机器人接入与兼容通道 | 具备事件队列、ACK、机器人身份,适合文本 Hermes 入口、通知和降级交互;当前发送能力不适合作为完整结构化工具协议的唯一主载体 |
| WuKongIM AI Plugin | 后续可选 | 适合低延迟和服务端 Hook;当前部署为唐僧叨叨配套 WuKongIM v2,插件协议和管理能力需单独验证,且不替代 Client/Gateway 的产品语义 |
| WuKongIM SDK + 外部 LineUp Gateway | 正式 Agent 交互通道 | 在唐僧叨叨业务体系内保留自定义消息、双向实时、客户端工具、多个 Agent Runtime 与独立演进能力 |
### 8.2 当前部署的使用方式
当前环境维持:
```text
唐僧叨叨服务端 v1.5 + WuKongIM v2 + MySQL + Redis + MinIO
监听:100.121.118.116Tailscale
```
该环境在本方案中的职责:
- 以唐僧叨叨作为 LineUp 的既有业务基础,继续提供用户、认证、联系人、群组、文件、工作台和管理能力;
- 以 WuKongIM 承担基础 IM 服务、长连接、离线同步和自定义消息传输;
- 以现有唐僧 Android / Web 作为可复用的客户端基础,并逐步加入 LineUp 交互渲染与工具模块;
- 用于验证 LineUp Client 的 SDK 连接、消息、离线和媒体行为;
- 为 LineUp Gateway 提供受控网络内的消息服务;
- 可选提供唐僧 Robot Events 文本机器人、通知和降级入口。
不得为了启用 AI Plugin 而直接将该稳定配套环境替换为 WuKongIM 主线版本;插件实验必须使用独立测试实例。
---
## 9. 分阶段实施计划
### Phase 0:协议与可行性基线
**目标:** 在不改变现有生产性 IM 数据的前提下,确认 WuKongIM v2 可以承载 LineUp 自定义 Payload。
**工作项:**
1. 定义 `lineup.v1` 自定义 Payload 的编码、消息类型编号和最大体积;
2. 使用两个测试账户,通过官方 SDK 收发并解码一条 LineUp 信封;
3. 验证离线消息、多设备同步、顺序、重复投递与撤回等边界;
4. 验证图片、文件与自定义交互消息的引用关系;
5. 定义测试频道/Agent 身份命名规范;
6. 记录 WuKongIM v2 的精确 SDK 与协议限制。
**验收标准:**
- 任一端离线重连后,`lineup.v1.tool.call` 不丢失且不重复执行;
- 自定义消息可以被测试 Client 正确识别;
- 不改变现有唐僧叨叨用户、消息和 Docker 命名卷。
### Phase 1LineUp 协议核心与最小 Gateway
**目标:** 打通 Client、Gateway 与一个模拟 Agent 的双向结构化交互。
**工作项:**
1. 实现 LineUp 信封编解码库与 JSON Schema
2. 实现 Gateway 的 Actor、Device、Conversation 映射;
3. 实现 SQLite 持久 inbox/outbox 与消息去重;
4. 实现 `device.hello``tool.list``tool.call``tool.result``tool.cancel`
5. 实现 `call_id` 状态机、超时和审计;
6. 使用模拟 Agent 发起 `choice``confirm`
**验收标准:**
- Client 断线、Gateway 重启后,待处理工具调用仍可恢复;
- 重复投递不导致工具执行两次;
- 不受支持工具返回 `unsupported`
- 用户拒绝、取消、超时能精确回到对应 Agent 调用。
### Phase 2LineUp Client 最小交互面
**目标:** 从“聊天 UI”进入“Agent 交互 UI”。
**工作项:**
1. 基于现有唐僧叨叨客户端能力建立 LineUp Client 扩展层或受控分叉,保留登录、联系人、聊天、媒体与文件基础;
2. 接入并验证 WuKongIM SDK 的 LineUp 自定义 Payload
3. 实现文本、Agent 状态、进度、choice、confirm、input
4. 实现工具注册、风险提示、用户授权与结果回传;
5. 实现会话列表中的 Agent 标识、运行状态与取消入口;
6. 实现最小 Artifact 展示:图片与文件;
7. 记录客户端能力及版本兼容策略。
**验收标准:**
- 用户可在手机上完成 Agent 发起的选择和确认;
- Agent 能得到结构化而非文本猜测式的结果;
- 用户可在 Agent 工作期间中断并发送替代指令;
- 图片、文件与进度不会破坏普通人与人聊天。
### Phase 3Hermes 首个正式 Adapter
**目标:** Hermes 成为第一个完整支持 LineUp 协议的 Agent Runtime。
**工作项:**
1. 实现 Hermes ↔ Gateway Adapter
2. 将 LineUp Conversation 映射到 Hermes session
3. 将用户新消息、取消和排队指令映射到 Hermes 原生会话控制;
4. 将 Hermes 的工具等待映射为 `tool.call` / `tool.result`
5. 将 Hermes 进度、状态和产物映射为 LineUp 消息;
6. 实现按用户/群组/Agent 的访问控制与审计。
**验收标准:**
- Hermes 可以发起 `choice` / `confirm` 并等待手机端结果后继续;
- 任务中途的新指令能取消旧 run,旧输出不会污染新会话;
- Gateway 或 Hermes 重启时不重复执行不可逆工具调用;
- 白名单外用户不能访问 Hermes 的本地执行能力。
### Phase 4:富交互与机器人接入
**目标:** 扩展产品表达能力,同时保持现有唐僧客户端可用。
**工作项:**
1. 实现 `canvas.open/patch/close` 与基础画板;
2. 实现 Artifact 卡片、图片预览、文件产物和流式状态;
3. 建立 `lineup_hermes` 唐僧机器人,提供文本入口、通知和不支持富交互时的安全降级;
4. Robot Events Adapter 将文本对话接入 Hermes
5. 对尚未安装 LineUp 扩展能力的客户端,发送安全的降级文本与 LineUp Client 跳转提示;
6. 建立跨客户端能力协商策略。
**验收标准:**
- LineUp Client 可以呈现至少一种非文本 Agent 交互(画板或结构化表单);
- 唐僧客户端仍可完成文本对话和基础确认降级;
- 两种入口不会对同一会话产生重复 Agent 回复。
### Phase 5WuKongIM AI Plugin 评估与增强
**前置条件:** 仅在独立测试实例确认当前 WuKongIM 版本、插件协议、插件管理与唐僧叨叨兼容边界后启动。
**目标:** 评估服务端 Plugin 是否为 LineUp 带来明确收益。
**评估项:**
- 流式延迟与 Gateway 外部 SDK 通道的对比;
- 插件绑定是否能精确限定 Agent 频道;
- 插件崩溃、升级、滚动重启的影响;
- Go Plugin 与 Hermes/Python 外部 Runtime 的超时、取消和恢复;
- 与唐僧叨叨 webhook 的消息所有权和重复处理风险;
- 安全审计、配置密钥和多租户隔离。
只有存在可量化收益时,才将 Plugin 用作 Gateway 的优化实现或服务端路由层。
---
## 10. 近期执行顺序
本方案确认后,近期工作的严格顺序如下:
```text
1. Phase 0:验证 WuKongIM v2 自定义 Payload 与 SDK 行为
2. 固化 LineUp v1 消息 schema 与 call 状态机
3. 建立最小外部 LineUp Gateway(模拟 Agent
4. 在唐僧叨叨客户端基础上实现 LineUp 最小交互:choice / confirm / input
5. 实现 Hermes 正式 Adapter
6. 补唐僧机器人作为文本、通知和降级入口
7. 最后才评估 WuKongIM AI Plugin
```
### 10.1 各阶段目标概览
| 阶段 | 阶段目标 | 完成后得到什么 |
|---|---|---|
| 1. 验证 WuKongIM 自定义消息能力 | 证明当前唐僧叨叨配套的 WuKongIM v2 能可靠传输 LineUp 自定义 Payload | 确认 IM 基础能够承载 `tool.call``tool.result` 等协议,而不是只能传普通文本 |
| 2. 固化 LineUp 协议 | 明确消息格式、类型、版本、会话、调用 ID、取消和错误语义 | Client、Gateway、Hermes 可遵循同一份可测试、可版本化的协议契约 |
| 3. 建立最小 Gateway | 建立 IM 和 Agent 之间的常驻中枢,负责收发、去重、会话映射与持久化 | 得到可靠的 LineUp 交换站,Client 或 Agent 重启时关键交互仍可恢复 |
| 4. 实现最小 LineUp Client | 在唐僧叨叨客户端基础上,让客户端从文本聊天界面进入可操作的 Agent 交互界面 | 用户可执行 choice、confirm、input 等结构化交互,而非回复编号或自然语言猜测 |
| 5. 接入 Hermes | 将真实 Hermes 会话映射为 LineUp 会话和工具调用 | 用户可远程操控 Hermes,Hermes 可请求确认、获得结果、展示状态并被中断 |
| 6. 提供唐僧叨叨机器人入口 | 在既有唐僧叨叨业务与客户端体系内提供 Hermes 文本、通知和降级交互 | 为尚未具备 LineUp 富交互能力的客户端提供可用入口;复杂操作安全降级为文本 |
| 7. 评估 WuKongIM AI Plugin | 判断服务端插件是否能在当前兼容边界内带来可量化收益 | 在确认版本、稳定性和收益后,决定是否加入低延迟流式与服务端路由增强层 |
依赖关系如下:
```text
先证明 IM 能传 LineUp 消息
再规定 LineUp 消息、会话和工具调用的统一语义
建立可靠的 Gateway
让 Client 将结构化消息渲染为可操作 UI
接入 Hermes,形成真实的人—Agent 协作闭环
补充唐僧叨叨机器人文本、通知与降级入口
最后按实际收益评估 WuKongIM AI Plugin
```
这保证产品核心先围绕“人和 Agent 的结构化协作”落地,而不是被某个现有 IM 客户端的文本机器人能力限制。
---
## 11. 与既有文档的关系
本方案继承早期文档中以下原则:
- 三层分离:连接层、消息传输层、工具/交互层;
- LineUp 协议只定义上层交互,不重复实现 IM 基础能力;
- `tool.call` / `tool.result` 使用结构化数据与 `call_id` 配对;
- Agent Runtime 插件化接入,避免将所有 Agent 逻辑写入客户端。
- 唐僧叨叨作为成熟 IM 业务层和客户端基础,优先复用并按 LineUp 实际需求扩展。
本方案替换或细化以下早期假设:
- 不再将“官方唐僧叨叨客户端上的文本机器人”视为 LineUp 的唯一体验;机器人是完整业务体系中的一种 Agent 接入形式;
- 不再将“基于某一个 Agent 的 WebSocket 插件”视为广域网交互的唯一架构;
- 正式引入 Client、Device、Agent、Conversation 与持久化 Gateway 的边界;
- 将自定义 IM Payload、客户端工具注册、调用状态机、取消与权限模型列为 MVP 基础,而不是后续附加能力;
- LineUp 的新增能力以唐僧叨叨已有用户、群组、文件、工作台和多端客户端能力为基础生长,不预设重造整套 IM 业务系统。
@@ -0,0 +1,441 @@
# LineUp Agents — MVP 产品与技术规格
**状态:** v0.1 / 讨论稿
**日期:** 2026-07-24
**目标:** 用最小可行的服务端、Web 客户端和 Agent Adapter,验证“统一的人机协作交互层”是否成立。
---
## 1. 一句话定义
**LineUp Agents 是一个面向个人与小团队的 Agent 协作入口:人通过统一的对话、任务、状态与确认组件,连接并管理自己已经拥有的 AI Agent。**
它类似于一个为 Agent 设计的 Channel:不是替换 OpenClaw、Codex、Hermes 或自定义运行时,而是把它们接入一个更适合持续协作的交互层。
## 2. 讨论结论与产品判断
| 已达成的判断 | 对首版的含义 |
|---|---|
| 先做好交互,不急着做万能 Agent 平台 | 任务、状态、确认和展示是核心,不把能力重心放在模型或编排 |
| Agent 的长期运行需要服务端 | 服务端持久化事件、会话和任务;客户端可随时离开和回来 |
| 主流 Agent 应能接入 | 定义轻量、事件驱动的 Adapter 协议,而非绑定单一框架 |
| 配置必须简单通用 | 用户只需绑定 Agent、选择会话、开始协作;复杂部署隐藏在 Adapter 后 |
| 自动拆分/分配可探索,但自动可靠完成仍很难 | v0.1 允许 Agent 报告子任务;不做自动抢占、调度或自治执行承诺 |
## 3. 问题与目标用户
### 3.1 要解决的问题
今天的人机协作通常被困在终端、网页聊天页或某个框架专属界面中。用户很难跨端了解:
- 我的 Agent 现在是否正在执行、卡在哪里、是否失联?
- 它何时需要我确认、补充信息或授权?
- 多个持续任务之间,哪个最重要、哪个已经完成?
- 换一个 Agent 框架后,为什么交互与历史记录都断裂?
LineUp 的工作不是替 Agent 思考,而是把这些协作状态变得可见、可控、连续。
### 3.2 首批用户
1. **重度个人 Agent 用户**:在本机或 VPS 上长时间运行一个或多个 Agent,希望在手机和 Web 上随时查看和介入。
2. **小型 AI 原生团队**:成员共享少量部署好的 Agent,需要看清谁在处理什么、何时要人类决策。
3. **Agent/Runtimes 开发者**:希望用少量接入工作获得成熟交互界面,而不是重造消息、任务与通知系统。
### 3.3 北极星任务
> 我在外出时收到一条通知:我的代码 Agent 需要决定是否执行数据库迁移。我打开 LineUp,看见完整任务上下文、影响说明和两个清晰选项;批准后 Agent 继续执行,我随后能看到结果与相关产物。
## 4. 产品原则
1. **状态先于文本。** 聊天记录很重要,但“正在运行”“等待我”“已失败”必须一眼可见。
2. **人保留关键控制权。** 有风险、费用、外部副作用的行动必须能由 Agent 发起清晰的确认请求。
3. **Agent 无关,但不抹平差异。** 统一最小能力;框架特有能力可通过扩展卡片展示。
4. **渐进披露。** 默认只展示当前决策所需的信息;详细日志、原始事件和调试字段按需展开。
5. **每一件事都可追溯。** 任务、消息、审批和状态转换写入不可变事件流,便于同步与审计。
6. **先轻后重。** 先交付一个连接可靠、体验细腻的 Channel,再逐步增加任务协作能力。
## 5. MVP 范围
### 5.1 必须交付
| 能力 | 用户价值 | v0.1 交付 |
|---|---|---|
| Agent 绑定 | 连接用户已有 Agent | 配对码/Token 绑定、Agent 在线状态、撤销绑定 |
| 会话 | 保持对话连续 | 会话列表、消息流、流式文本、附件链接 |
| 任务 | 看清长期工作 | 创建任务、状态、进度、父子任务展示、取消请求 |
| 人类介入 | 不让 Agent 默默卡住 | 单选、多选、文本补充、批准/拒绝四类交互卡片 |
| 实时同步 | 跨端了解当前状态 | WebSocket、断线重连、顺序事件、离线补偿 |
| 通知 | 用户离开时仍可介入 | 浏览器通知/邮件二选一,优先通知“等待用户”和“失败” |
| Adapter SDK | 接入至少一个实际 Agent | TypeScript SDK + 示例 Adapter + 事件协议文档 |
### 5.2 明确不做
- 自动从自然语言可靠拆解、排程并完成任意复杂任务。
- Agent 之间的自动抢单、竞价、自治协调与长期记忆治理。
- 模型 API 代理、模型计费、Sandbox 执行器或完整远程桌面。
- 企业 SSO、复杂 RBAC、多租户计费与组织级合规功能。
- 原生 iOS/Android App;首版先用响应式 Web 验证交互模型。
## 6. 信息架构
```text
工作区
├── 收件箱
│ ├── 等待我处理
│ ├── 失败 / 需要关注
│ └── 最近完成
├── 会话
│ └── 一个会话对应人与一个或多个 Agent 的协作上下文
├── 任务
│ ├── 全部
│ ├── 进行中
│ ├── 等待我
│ └── 已完成 / 已取消
├── Agent
│ ├── 在线状态与能力
│ ├── 绑定 / 配置
│ └── Adapter 诊断日志
└── 设置
└── 用户、通知、开发者 Token
```
### 6.1 核心对象
| 对象 | 含义 | 关键字段 |
|---|---|---|
| Workspace | 数据与访问边界 | `id`, `name` |
| User | 使用者 | `id`, `displayName` |
| Agent | 一个可连接的 Agent 实例 | `id`, `name`, `runtime`, `status`, `capabilities` |
| Conversation | 一个协作上下文 | `id`, `workspaceId`, `title`, `agentIds` |
| Message | 用户、Agent 或系统产生的内容 | `id`, `conversationId`, `author`, `content`, `createdAt` |
| Task | 可追踪的工作单元 | `id`, `conversationId`, `parentTaskId`, `title`, `status`, `progress` |
| Interaction | 必须由用户处理的结构化请求 | `id`, `taskId`, `type`, `status`, `payload`, `expiresAt` |
| Event | 所有状态变化的事实记录 | `id`, `sequence`, `type`, `subject`, `payload`, `occurredAt` |
### 6.2 任务状态机
```mermaid
stateDiagram-v2
[*] --> queued
queued --> running: Agent 接受 / 用户开始
running --> waiting_for_human: 发起 Interaction
waiting_for_human --> running: 用户响应
running --> succeeded: 完成
running --> failed: 失败
queued --> cancelled: 取消
running --> cancelled: 取消确认 / Agent 停止
waiting_for_human --> cancelled: 用户取消
succeeded --> [*]
failed --> [*]
cancelled --> [*]
```
状态定义:
- `queued`:任务已创建、尚未由 Agent 接受。
- `running`:Agent 正在执行,允许不断上报进度与日志摘要。
- `waiting_for_human`:工作被一个未处理的 Interaction 阻塞,应进入收件箱并通知用户。
- `succeeded` / `failed` / `cancelled`:终态;任务和完整事件历史可查。
## 7. 关键交互
### 7.1 首次连接 Agent
1. 用户在「Agent」页点击“连接我的 Agent”。
2. 选择 Adapter 类型(首版:Generic SDK;后续可有 OpenClaw 预设)。
3. 服务端生成一次性配对码和安装命令;配对码有效期 10 分钟。
4. 用户在 Agent 所在机器运行 AdapterAdapter 用配对码建立出站 WebSocket。
5. 服务端显示 Agent 名称、版本、可用能力与在线状态;用户确认命名后完成绑定。
**体验要求:** 不要求用户开放入站端口;Agent 主动连接中转服务。连接失败时必须显示可复制的诊断码及下一步建议。
### 7.2 发起与跟进一个任务
1. 用户在会话底部输入自然语言需求,可勾选“作为任务跟踪”。
2. 系统先创建 `task.created`,再把文本作为 `message.created` 发送给目标 Agent。
3. Agent 接受后上报 `task.status_changed: running`,可持续上报 0–100 的进度和一句简短状态。
4. 用户可在会话内看简要进度,也可打开任务详情查看子任务、产物和事件时间线。
5. Agent 完成、失败或需要人类处理时,状态即时同步到任务列表和收件箱。
### 7.3 Agent 请求人类决策
交互卡片的结构为:**为什么需要你 → 影响是什么 → 你能做什么 → 详细信息(折叠)**。
| 卡片类型 | 示例 | 必填交互 |
|---|---|---|
| `approval` | “将执行生产数据库迁移” | 批准 / 拒绝;可选备注 |
| `choice` | “部署区域选择” | 2–5 个选项;可标注推荐项 |
| `input` | “请提供测试环境 URL” | 文本输入、格式校验 |
| `review` | “请检查生成的变更计划” | 展示内容或附件;批准 / 要求修改 |
响应后,卡片变为只读并保留“谁在何时作出什么决定”;对应事件发送回 Agent,任务恢复 `running`
### 7.4 Agent 失联与恢复
- WebSocket 心跳间隔 25 秒;连续 2 次未收到心跳标记为 `offline`
- 运行中任务不立刻标为失败,而显示“Agent 连接中断”,并保留最后心跳时间。
- Adapter 重连后携带最后确认的 `sequence`;服务端补发缺失下行事件。
- 若 Agent 在配置的宽限期(默认 15 分钟)后仍未恢复,任务改为 `failed`,原因是 `agent_unreachable`
## 8. 页面与组件规格
### 8.1 桌面端主界面
```text
┌──────────────┬───────────────────────────────────────┬──────────────────────┐
│ 工作区 │ 会话:网站重构 │ 任务详情 │
│ │ │ │
│ 收件箱 (2) │ [用户] 分析现有页面并提交改进建议 │ ● 正在执行 60% │
│ 会话 │ │ “整理组件依赖” │
│ 任务 │ [Agent] 已建立任务计划,正在扫描仓库… │ │
│ Agent │ │ 子任务 │
│ │ ┌───────────────────────────────────┐ │ ✓ 扫描仓库 │
│ │ │ 需要你的确认 │ │ ● 整理组件依赖 │
│ │ │ 允许安装 3 个开发依赖? │ │ ○ 输出迁移方案 │
│ │ │ [查看变更] [拒绝] [允许] │ │ │
│ │ └───────────────────────────────────┘ │ 事件时间线 │
│ │ │ │
│ │ 输入消息… [发送] │ │
└──────────────┴───────────────────────────────────────┴──────────────────────┘
```
**响应式规则:** 宽度小于 900px 时隐藏右侧详情为抽屉;小于 640px 时侧栏收起,顶部保留收件箱未读数和 Agent 在线指示。
### 8.2 首版设计令牌
```css
:root {
--surface-canvas: #f7f8fa;
--surface-default: #ffffff;
--surface-subtle: #f1f3f5;
--text-primary: #1b1f24;
--text-secondary: #59636e;
--border-subtle: #d8dee4;
--brand: #356ae6;
--status-running: #356ae6;
--status-waiting: #b7791f;
--status-success: #168a5b;
--status-danger: #c93c37;
--radius-card: 12px;
--radius-control: 8px;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
}
```
可访问性底线:正文与背景对比度至少 4.5:1;不只依靠颜色表达状态;所有卡片操作支持键盘焦点、Enter/Space 触发及明确的加载/提交结果。
## 9. 系统架构
```mermaid
flowchart LR
subgraph Clients[用户客户端]
WEB[响应式 Web]
MOBILE[未来移动端]
end
subgraph Cloud[LineUp 中转服务]
API[HTTPS API]
RT[Realtime Gateway\nWebSocket]
AUTH[身份与设备绑定]
APP[协作应用服务]
DB[(PostgreSQL)]
BUS[(Event / Job Queue)]
PUSH[通知服务]
end
subgraph UserEnv[用户的本机或服务器]
ADAPTER[LineUp Adapter]
RUNTIME[OpenClaw / Codex /\nHermes / 自定义 Agent]
end
WEB <-- HTTPS + WS --> API
WEB <-- HTTPS + WS --> RT
MOBILE <-- HTTPS + WS --> API
MOBILE <-- HTTPS + WS --> RT
API --> AUTH
API --> APP
RT --> APP
APP --> DB
APP --> BUS
BUS --> PUSH
ADAPTER <-- 出站 WSS --> RT
ADAPTER <--> RUNTIME
```
### 9.1 推荐实现取舍
| 层 | 首版选择 | 原因 |
|---|---|---|
| Web | Next.js + TypeScript | 同时覆盖 Web、响应式移动体验与后续 BFF 需求 |
| 服务端 | TypeScriptFastify/NestJS 任选其一) | 与 SDK 共享类型和校验器,迭代快 |
| 实时连接 | 标准 WebSocket | 对 Agent 和客户端都简单;协议可公开 |
| 数据 | PostgreSQL | 可靠存储、查询任务,JSONB 容纳扩展事件 |
| 临时事件/队列 | Redis Streams 或 BullMQ | 处理通知、投递重试和长连接广播 |
| Adapter SDK | TypeScript 首发 | 覆盖主要 Node Agent;协议保持语言无关 |
| 鉴权 | 短期 Access Token + Agent 配对 Token | 安全模型足够简单,后续可演进 OAuth/设备授权 |
**关键边界:** 业务服务不执行 Agent 的工具调用,也不存放模型密钥。它仅路由已认证事件、持久化协作状态,并向用户端可靠同步。
## 10. 最小事件协议(LAP/0.1
所有 WebSocket 帧使用统一 envelope。`payload``type` 做 schema 校验;未知字段容忍但记录,未知事件类型忽略并保留兼容性告警。
```ts
type Envelope<T = unknown> = {
id: string; // UUID,幂等键
type: string; // 如 task.status_changed
version: '0.1';
occurredAt: string; // ISO 8601 UTC
workspaceId: string;
conversationId?: string;
taskId?: string;
sequence?: number; // 服务端下行事件全局递增序号
correlationId?: string; // 串联一次用户请求和后续事件
payload: T;
};
```
### 10.1 用户端 → 服务端
| 事件 | 说明 | 关键 payload |
|---|---|---|
| `message.create` | 发消息 | `content`, `targetAgentId` |
| `task.create` | 创建受跟踪任务 | `title`, `description`, `targetAgentId` |
| `interaction.respond` | 回应卡片 | `interactionId`, `response` |
| `task.cancel_requested` | 请求取消 | `reason?` |
### 10.2 Agent → 服务端
| 事件 | 说明 | 关键 payload |
|---|---|---|
| `agent.hello` | Adapter 握手与能力声明 | `agentVersion`, `runtime`, `capabilities`, `lastSequence?` |
| `message.create` | Agent 文本或富内容 | `content`, `blocks?` |
| `task.accepted` | 接受任务 | `taskId` |
| `task.status_changed` | 上报状态或进度 | `status`, `progress?`, `summary?`, `error?` |
| `task.child_created` | 仅用于展示的子任务 | `parentTaskId`, `title`, `status` |
| `interaction.requested` | 请求用户输入或确认 | `interaction` |
| `artifact.created` | 上报链接/文件/结果 | `name`, `url`, `mimeType?`, `summary?` |
### 10.3 服务端 → Agent
| 事件 | 说明 |
|---|---|
| `task.assigned` | 新任务及所属会话上下文 |
| `message.delivered` | 用户追加消息 |
| `interaction.resolved` | 用户的结构化回答 |
| `task.cancel_requested` | 用户请求停止 |
| `system.resync_required` | Adapter 无法从断点恢复,需拉取或重建状态 |
### 10.4 Interaction 示例
```json
{
"type": "interaction.requested",
"version": "0.1",
"id": "evt_01J...",
"occurredAt": "2026-07-24T12:00:00Z",
"workspaceId": "ws_01J...",
"conversationId": "conv_01J...",
"taskId": "task_01J...",
"payload": {
"interaction": {
"id": "int_01J...",
"type": "approval",
"title": "允许执行数据库迁移吗?",
"description": "此操作会在 production 执行 3 个已审阅的迁移文件。",
"risk": "high",
"options": [
{ "id": "approve", "label": "允许执行", "style": "primary" },
{ "id": "reject", "label": "拒绝", "style": "danger" }
],
"details": { "migrationCount": 3, "environment": "production" },
"expiresAt": "2026-07-25T12:00:00Z"
}
}
}
```
## 11. 安全与可靠性基线
- Agent 仅建立**出站** TLS WebSocket;用户无需暴露本机端口。
- 配对码为一次性、短有效期且仅用于兑换 Agent 凭据;凭据可单独撤销。
- 每一个 Agent 凭据限定到单 Workspace 和 Agent 身份,禁止跨工作区投递。
- 写入事件必须使用 envelope `id` 去重;客户端与 Adapter 应能安全重发。
- 明确区分“取消请求”与“已取消”:只有 Agent 确认停止或超时策略生效后任务才进入终态。
- 记录审批摘要、响应人、时间和关联任务;不要默认把原始敏感输入发到通知正文。
- 默认保留最小化运行日志;敏感字段可在 Adapter 侧脱敏后再上报。
## 12. 验证指标与首轮测试
### 12.1 产品假设
如果用户能在离开 Agent 所在机器后,仍清楚掌握任务状态并在 30 秒内完成关键介入,那么他们会愿意把更多长期任务交给已连接的 Agent。
### 12.2 成功指标
| 指标 | 首轮目标 |
|---|---|
| 成功绑定一个 Agent 的比例 | ≥ 80% |
| 从收到“等待用户”通知到完成处理的中位时间 | < 60 秒 |
| 用户判断任务当前状态的正确率 | ≥ 90% |
| 消息/状态在在线情况下端到端可见延迟 P95 | < 1 秒 |
| Agent 重连后未丢失的待处理事件比例 | 100% |
| 试用用户愿意在下一周继续连接 Agent 的比例 | ≥ 60% |
### 12.3 5 人可用性测试
找 3 位已有 Agent 使用经验的用户与 2 位刚接触长任务 Agent 的用户;每人完成以下任务:
1. 连接一个模拟 Agent,说明它是否在线。
2. 创建“整理项目依赖并产出计划”的任务,定位它的当前进度。
3. 在不打开日志的前提下处理一个高风险批准请求。
4. 让 Agent 断线并恢复,判断任务是否需要重新开始。
记录完成率、完成时间、误操作、口头疑问和 SUS 问卷。任何导致用户错误批准高风险行为的问题均视为 P0,必须在公开测试前解决。
## 13. 实施顺序与分工建议
### 13.1 三个迭代
| 迭代 | 目标 | 可演示结果 |
|---|---|---|
| I1:连通性 | 账户/工作区、配对、WebSocket、事件存储 | 一个模拟 Agent 在线,能发收消息 |
| I2:协作闭环 | 会话、任务状态、Interaction 卡片、收件箱 | 用户能创建任务、远程批准、看到 Agent 继续与完成 |
| I3:可靠性 | 重连补偿、通知、审计、示例 Adapter | Agent 断线恢复后协作连续,能供首批用户试用 |
### 13.2 初始分工(两人)
| 负责人 | 主责 | I1I3 输出 |
|---|---|---|
| Stephen | 中转服务与协议 | 数据模型、事件路由、WebSocket、鉴权、Adapter SDK、模拟 Agent |
| 爱德姆 | 产品交互与客户端 | 信息架构、Web 交互、任务/确认卡片、连接引导、可用性测试与反馈整理 |
共同决策:协议版本、风险确认策略、首个真实 Agent Adapter 的接入优先级。每周用一段真实的 Agent 长任务进行端到端验收,而不是只测 API。
## 14. 待决策清单
这些决策会改变实现范围,应在开始 I1 前由两位创始人确认:
1. **首个真实接入对象:** OpenClaw 优先,还是先做 Generic SDK + 模拟 Agent?建议先后者,避免因特定框架阻塞核心验证。
2. **首批部署模式:** 托管中转服务,还是可自托管?建议先托管开发环境,协议与部署保持可自托管。
3. **账号范围:** 仅个人 Workspace,还是首版就允许邀请第二位成员?建议数据模型支持成员,界面先围绕个人。
4. **通知通道:** 浏览器推送、邮件、Telegram/飞书任选一条。建议浏览器通知优先,集成型 Channel 放在 I3 后评估。
5. **产品命名:** “LineUp Agents”暂作工作名;上线前需检查域名、商标及中文名称可用性。
## 15. Definition of DoneMVP 演示
一次合格的演示应从零完成以下闭环:
1. 用户注册并创建一个 Workspace。
2. 用户通过一次性配对码连接运行在另一台机器上的模拟或真实 Agent。
3. 用户创建一个任务,关闭浏览器后再打开,任务与会话仍完整存在。
4. Agent 上报至少两个进度变化,并创建一个子任务。
5. Agent 发起一个 `approval` 请求,用户在收件箱处理后,Agent 收到响应并继续执行。
6. 断开 Adapter,界面在合理时间内显示失联;重连后不重复执行或丢失审批响应。
7. Agent 产出一条结果消息和一个 Artifact,任务成为 `succeeded`,全部过程在事件时间线中可追溯。
完成这套闭环后,再根据试用反馈决定是优先扩充移动端、增加框架 Adapter,还是深化任务协作能力。
@@ -0,0 +1,398 @@
# LineUp Agents v1 — 设计方案
**版本:** v1.1(第二版)
**最后更新:** 2026-07-27
---
## 一、产品定位
LineUp Agents 是一个**远程 Agent 操作交互端**。用户通过手机或 Web App,与运行在本地或服务器上的 AI Agent 进行顺畅的交互。不是任务管理系统,Agent 的任务拆解、执行、管理属于 Agent 自己的工作范畴。
### MVP 范围
| 做 | 不做 |
|----|------|
| App 直连 Agent(局域网 WebSocket| 任务生命周期管理 |
| 通过 IM 中转连接 Agent(远程)| 自动化编排、调度 |
| App 收发消息、查看 Agent 状态 | 通知推送 |
| Agent 调 App 的工具(choice / 画板 / 计算器 等)| MCP 封装 |
| 工具发现与调用协议 | 多 Agent 工作流编排 |
| 用户 ↔ 用户基础通信(由 IM 平台提供)| 端到端加密(后续加) |
---
## 二、核心设计原则
**LineUp 协议只定义工具交互,不定义基础消息。**
这条原则是整个设计的关键。工具相关的内容(`tool.call``tool.result``tool.list`)由 LineUp 协议定义。文本、图片、语音、文件等基础消息,由下层传输平台(IM 服务或 WebSocket)原生处理,LineUp 不重复定义。
这让协议非常薄,换传输平台时工具协议不受影响。
---
## 三、架构总览
### 3.1 两种网络模式
LineUp 支持两种连接模式,插件根据配置自动切换。
**直连模式(局域网)**
```
Agent 插件 ── WebSocket 端口 :9527 ──→ App 端(Web
```
Agent 插件在本地开 WebSocket ServerApp 端手动输入 IP:端口连接。适合局域网内无公网 IP 的场景。
**中转模式(广域网)**
```
Agent 适配器 ──→ WuKongIM ←── 唐僧叨叨客户端 / LineUp App
│ Webhook / HTTP API
唐僧叨叨业务服务
```
WuKongIM 负责客户端长连接、消息投递和消息存储;唐僧叨叨业务服务负责用户、好友、群组、文件等 IM 业务能力,并通过 Webhook 与 WuKongIM 协作。客户端以 WuKongIM 官方 SDK 建立长连接,以唐僧叨叨 API 处理业务操作。LineUp 的中转适配器需使用 WuKongIM 官方 SDK 或经验证的协议实现,不能复用其他 IM 平台的网关协议。
### 3.2 分层架构
```
┌──────────────────────────────────────────────┐
│ 工具协议层(LineUp 定义) │
│ 内容:工具定义、tool.call / tool.result │
│ App 渲染 choice / canvas / calculator 等工具 │
├──────────────────────────────────────────────┤
│ 消息传输层(IM 平台或直连 WebSocket
│ 内容:文本 / 图片 / 文件 / 语音的收发 │
│ 直连模式:WebSocket 原生传输 │
│ 中转模式:WuKongIM 消息通道传输 │
├──────────────────────────────────────────────┤
│ 连接层(IM 平台或 WebSocket Server
│ 内容:连接建立、心跳保活、断线重连 │
│ 直连模式:Agent 插件 WS Server │
│ 中转模式:WuKongIM 连接管理 │
└──────────────────────────────────────────────┘
```
LineUp 协议只关心最上面一层。下面两层由传输平台处理。
### 3.3 设计决策
| 决策 | 当期结论 | 后续扩展思路 |
|------|---------|-------------|
| 网络模式 | 局域网直连 + WuKongIM 中转并存 | — |
| 中转服务 | 唐僧叨叨业务层 + WuKongIM 通讯层 | — |
| Agent 接入方式 | 直连模式用 LineUp 适配器;中转模式待基于 WuKongIM SDK 实现 | — |
| IM 平台选型 | WuKongIM;唐僧叨叨提供配套业务层和客户端 | — |
| App 端技术方向 | Vite + React(直连模式用 WebSocket;中转模式使用 WuKongIM JS SDK / 唐僧叨叨 API | 原生移动端 App |
| 连接认证 | 直连:预共享 Token;中转:唐僧叨叨用户系统与 WuKongIM Token | 端到端加密 |
| 工具路由方式 | 字典路由 | 可引入框架级路由 |
---
## 四、工具协议定义(LineUp 协议的全部内容)
LineUp 协议只定义工具相关的三个消息类型。
### 4.1 工具定义结构
所有工具使用统一的结构模板,通过 `type` 区分行为差异。
| type | 含义 | 定义结构 |
|------|------|---------|
| `app` | 有状态应用,有生命周期 | `{ name, type, description, actions[] }` |
| `toolset` | 无状态工具集,多个动作 | `{ name, type, description, actions[] }` |
| `action` | 单指令工具,一个动作 | `{ name, type, description, inputSchema }` |
### 4.2 三类工具的完整定义
#### type: app — 有状态应用
```json
{
"name": "canvas",
"type": "app",
"description": "画板应用,支持绘制、撤销、清空等操作",
"actions": [
{
"name": "open",
"description": "创建画板实例,返回 instance_id",
"inputSchema": {
"type": "object",
"properties": {
"width": { "type": "integer", "description": "画板宽度" },
"height": { "type": "integer", "description": "画板高度" }
},
"required": ["width", "height"]
}
},
{
"name": "draw",
"description": "在画板上绘制路径",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string", "description": "画板实例 ID" },
"path": { "type": "string", "description": "SVG 路径数据" },
"color": { "type": "string", "description": "线条颜色" },
"width": { "type": "integer", "description": "线条粗细" }
},
"required": ["instance_id", "path"]
}
},
{
"name": "undo",
"description": "撤销上一步操作",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string", "description": "画板实例 ID" }
},
"required": ["instance_id"]
}
},
{
"name": "clear",
"description": "清空画板内容",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string", "description": "画板实例 ID" }
},
"required": ["instance_id"]
}
},
{
"name": "close",
"description": "关闭画板释放资源",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string", "description": "画板实例 ID" }
},
"required": ["instance_id"]
}
}
]
}
```
#### type: toolset — 无状态工具集
```json
{
"name": "calculator",
"type": "toolset",
"description": "基础计算器,无状态,每次调用独立",
"actions": [
{
"name": "add",
"description": "两个数相加",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
},
{
"name": "subtract",
"description": "两个数相减",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
},
{
"name": "multiply",
"description": "两个数相乘",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
},
{
"name": "divide",
"description": "两个数相除",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
}
]
}
```
#### type: action — 单指令工具
```json
{
"name": "choice",
"type": "action",
"description": "向用户提供一组选项让其选择",
"inputSchema": {
"type": "object",
"properties": {
"question": { "type": "string", "description": "问题描述" },
"options": { "type": "array", "items": { "type": "string" }, "description": "可选列表" }
},
"required": ["question", "options"]
}
}
```
### 4.3 三类工具对比
| | app | toolset | action |
|--|-----|---------|--------|
| 定义结构 | name + type + description + actions[] | name + type + description + actions[] | name + type + description + inputSchema |
| 适用场景 | 需要维护内部状态的应用 | 多个独立功能可归类 | 单个独立功能 |
| 生命周期 | open → use → close | 无 | 无 |
| instance_id | 需要 | 不需要 | 不需要 |
| 每次调用需 action | 是 | 是 | 仍需 action 字段 |
| 示例 | canvas(画板) | calculator(计算器) | choice / confirm / input |
### 4.4 tool.call 消息格式
```json
{
"call_id": "c1",
"name": "canvas",
"action": "open",
"arguments": { "width": 800, "height": 600 }
}
```
字段说明:
- `call_id` — 调用方生成,唯一标识一次调用,用于 tool.result 配对
- `name` — 工具名
- `action` — 动作名(app 和 toolset 类型必填,action 类型可省略)
- `arguments` — 动作的参数,按 inputSchema 结构传入
### 4.5 tool.result 消息格式
```json
{
"call_id": "c1",
"name": "canvas",
"action": "open",
"result": {
"message": "instance_id: inst_001"
}
}
```
字段说明:
- `call_id` — 与 tool.call 中的 call_id 一致,用于配对
- `name` — 工具名
- `action` — 动作名
- `result` — 返回数据,约定统一放在 result.message,后续按工具定义扩展
### 4.6 tool.list 消息
```json
{
"tools": [
{ "name": "choice", "type": "action", "description": "...", "inputSchema": {} },
{ "name": "calculator", "type": "toolset", "description": "...", "actions": [] },
{ "name": "canvas", "type": "app", "description": "...", "actions": [] }
]
}
```
设备注册和查询工具列表用统一的数据结构。直连模式下双向可查,中转模式下 App 端通过自定义消息类型注册工具。
---
## 五、完整交互流程
### 5.1 choice 工具调用
```
Agent 需要用户做选择
├── Agent 发 tool.call
│ { call_id: "c1", name: "choice", action: "select",
│ arguments: { question: "部署到哪个环境?", options: ["测试", "预发布", "生产"] } }
├── App 渲染选择界面,用户点击"预发布"
├── App 返回 tool.result
│ { call_id: "c1", name: "choice", action: "select",
│ result: { message: "预发布" } }
└── Agent 拿到结果,继续执行
```
### 5.2 canvas 完整生命周期
Agent 调 `canvas``open` 动作:
```json
{ "call_id": "c1", "name": "canvas", "action": "open", "arguments": { "width": 800, "height": 600 } }
```
App 返回 instance_id
```json
{ "call_id": "c1", "name": "canvas", "action": "open", "result": { "message": "instance_id: inst_001" } }
```
Agent 绘制路径:
```json
{ "call_id": "c2", "name": "canvas", "action": "draw", "arguments": { "instance_id": "inst_001", "path": "M10 10 L50 50", "color": "red" } }
```
Agent 关闭画板:
```json
{ "call_id": "c3", "name": "canvas", "action": "close", "arguments": { "instance_id": "inst_001" } }
```
---
## 六、与 MCP 的关系
```
MCP LineUp
───────────────────────────────────────
MCP Server ──────────────── Tool(画板 / 计算器 / 选择框)
│ │
├─ MCP Tool A ├─ Actionopen / draw / add / select
├─ MCP Tool B ├─ Action
└─ MCP Tool C └─ Action
```
Tool = MCP Server 层,Action = MCP Tool 层。Action 才是实际可调用的最小能力单元。
工具定义格式复用 MCP 的 JSON Schema 标准。
---
## 七、后续可能的扩展
- 端到端加密,中转不可读消息内容
- 移动端原生 App(目前用响应式 Web)
- 直连模式切换到中转模式时的无缝过渡
- 更多 Agent 框架的插件支持(目前以 Hermes 为第一期)
- 框架级工具路由替代字典路由
@@ -0,0 +1,388 @@
# LineUp — 协议定义
## 一、工具协议定义
工具协议定义 App 端可以向 Agent 暴露哪些能力,以及每个能力长什么样。
### 1.1 工具定义结构
所有工具使用统一的结构模板,通过 `type` 区分结构差异。
| type | 含义 | 定义结构 |
|------|------|---------|
| `app` | 有状态应用,有生命周期 | `{ name, type, description, actions[] }` |
| `toolset` | 无状态工具集,多个动作 | `{ name, type, description, actions[] }` |
| `action` | 单指令工具,一个动作 | `{ name, type, description, inputSchema }` |
app 和 toolset 包含多个动作,用 `actions` 数组描述。action 只有一个操作,直接用 `inputSchema`,不需要数组包一层。
### 1.2 三类工具的完整定义
#### type: app — 有状态应用
需要生命周期管理,先 open 创建实例,执行动作,最后 close 释放。
```json
{
"name": "canvas",
"type": "app",
"description": "画板应用,支持绘制、撤销、清空等操作",
"actions": [
{
"name": "open",
"description": "创建画板实例,返回 instance_id",
"inputSchema": {
"type": "object",
"properties": {
"width": { "type": "integer", "description": "画板宽度" },
"height": { "type": "integer", "description": "画板高度" }
},
"required": ["width", "height"]
}
},
{
"name": "draw",
"description": "在画板上绘制路径",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string", "description": "画板实例 ID" },
"path": { "type": "string", "description": "SVG 路径数据" },
"color": { "type": "string", "description": "线条颜色" },
"width": { "type": "integer", "description": "线条粗细" }
},
"required": ["instance_id", "path"]
}
},
{
"name": "undo",
"description": "撤销上一步操作",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string", "description": "画板实例 ID" }
},
"required": ["instance_id"]
}
},
{
"name": "clear",
"description": "清空画板内容",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string", "description": "画板实例 ID" }
},
"required": ["instance_id"]
}
},
{
"name": "close",
"description": "关闭画板释放资源",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string", "description": "画板实例 ID" }
},
"required": ["instance_id"]
}
}
]
}
```
#### type: toolset — 无状态工具集
多个动作,无生命周期。每次调用独立,动作间不共享状态。
```json
{
"name": "calculator",
"type": "toolset",
"description": "基础计算器,无状态,每次调用独立",
"actions": [
{
"name": "add",
"description": "两个数相加",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
},
{
"name": "subtract",
"description": "两个数相减",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
},
{
"name": "multiply",
"description": "两个数相乘",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
},
{
"name": "divide",
"description": "两个数相除",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
}
]
}
```
#### type: action — 单指令工具
只有一个动作,最简结构。
```json
{
"name": "choice",
"type": "action",
"description": "向用户提供一组选项让其选择",
"inputSchema": {
"type": "object",
"properties": {
"question": { "type": "string", "description": "问题描述" },
"options": { "type": "array", "items": { "type": "string" }, "description": "可选列表" }
},
"required": ["question", "options"]
}
}
```
### 1.3 三类工具对比
| | app | toolset | action |
|--|-----|---------|--------|
| 定义字段 | name + type + description + actions[] | name + type + description + actions[] | name + type + description + inputSchema |
| 适用场景 | 需要维护内部状态的应用 | 多个独立功能可归类 | 单个独立功能 |
| 生命周期 | open → use → close | 无 | 无 |
| instance_id | 需要 | 不需要 | 不需要 |
| 调用指定动作 | 每次调用需 action | 每次调用需 action | action 字段仍需传 |
| 示例 | canvas | calculator | choice / confirm / input |
---
## 二、消息协议定义
消息协议定义 Agent 和 App 之间交换的所有消息格式。
### 2.1 统一信封
所有消息使用同一个信封格式。
```json
{
"v": 1,
"id": "消息唯一 ID,用于去重和配对",
"type": "消息类型",
"payload": { }
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| v | integer | 协议版本号 |
| id | string | 消息唯一 ID,全局唯一 |
| type | string | 消息类型 |
| payload | object | 消息内容,按 type 决定结构 |
### 2.2 消息类型
| type | 方向 | 说明 |
|------|------|------|
| `system.hello` | 双向 | 连接握手,交换身份 |
| `system.ping` / `system.pong` | 双向 | 心跳保活 |
| `text` | 双向 | 普通文本消息 |
| `image` | Agent → App | 展示图片 |
| `tool.list` | 双向 | 查询或返回工具列表 |
| `tool.call` | Agent → App | 调用 App 端工具 / 动作 |
| `tool.result` | App → Agent | 返回工具执行结果 |
### 2.3 各类型消息格式
#### system.hello
连接建立后,双方通过 hello 交换身份。
Agent → App
```json
{
"v": 1,
"id": "msg_hello_agent",
"type": "system.hello",
"payload": {
"name": "我的 AI 助手",
"token": "预设的配对 Token",
"agent": "Hermes",
"version": "0.19.0"
}
}
```
App → Agent
```json
{
"v": 1,
"id": "msg_hello_app",
"type": "system.hello",
"payload": {
"name": "我的手机",
"platform": "web",
"version": "1.0.0"
}
}
```
#### text
两边都可以发送。
```json
{
"v": 1,
"id": "msg_001",
"type": "text",
"payload": {
"content": "帮我查一下下周的天气"
}
}
```
#### image
Agent 展示图片给用户。
```json
{
"v": 1,
"id": "msg_002",
"type": "image",
"payload": {
"url": "http://192.168.1.100:9527/files/screenshot.png",
"alt": "系统架构图",
"caption": "这是当前项目的架构图"
}
}
```
#### tool.list
查询 App 端支持哪些工具。
```json
{
"v": 1,
"id": "msg_003",
"type": "tool.list",
"payload": {}
}
```
App 返回含工具列表。
```json
{
"v": 1,
"id": "msg_004",
"type": "tool.list",
"payload": {
"tools": [ ]
}
}
```
#### tool.call
Agent 触发 App 端的一个工具动作。
```json
{
"v": 1,
"id": "msg_005",
"type": "tool.call",
"payload": {
"name": "工具名",
"action": "动作名",
"call_id": "本次调用的唯一 ID",
"arguments": { }
}
}
```
#### tool.result
App 返回工具执行结果给 Agent。
```json
{
"v": 1,
"id": "msg_006",
"type": "tool.result",
"payload": {
"name": "工具名",
"action": "动作名",
"call_id": "对应的调用 ID",
"result": {
"message": "用户操作结果"
}
}
}
```
V1 简化约定:App 返回的用户操作结果统一放在 `result.message` 字段。后续工具需要更丰富的返回格式时(如文件路径、选择详情),再按工具定义扩展 `result` 结构。
---
## 三、协议分层关系
```
通讯协议层(WebSocket 连接、心跳、重连、鉴权)
消息协议层(信封 + 消息类型)
工具协议层(工具定义 + 工具调用流程)
```
三层独立不耦合。通讯层换了,上面两层不用改。消息层升版本号,工具层不受影响。工具层加新工具,上面两层不需要动。
---
## 四、与 MCP 的对应关系
```
MCP LineUp
──────────────────────────────────────
MCP Server ────────────── Tool(画板 / 计算器 / 选择框)
│ │
├─ MCP Tool A ├─ Actionopen / draw / add / select
├─ MCP Tool B ├─ Action
└─ MCP Tool C └─ Action
```
MCP 里每一个 Tool 是无状态的一次性调用单元。LineUp 里 Tool 多了一层容器层。app 类型让有状态工具在逻辑上是一个整体,Agent 看到 canvas 就知道这是一个画板应用,再看 actions 就知道具体可以做什么。
@@ -0,0 +1,813 @@
# LineUp App 最终设计方案
**版本:** 1.0(当前开发基线)
**状态:** 当前 App 设计的唯一汇总入口
**日期:** 2026-08-04
**来源:** [App 层架构方案](lineup-app-layer-architecture.md)、[Runtime 与 App SDK 架构方案](lineup-runtime-sdk-architecture.md)、[UI Surface 与 App Capability 协议](lineup-ui-surface-protocol.md)
---
## 0. 一句话定义
**LineUp 是运行在用户设备上的协作 Runtime,也就是整个产品的本地协调中心。**
Runtime 负责连接 AppServer 和 Remote Agent,保存会话与消息,管理应用、工具、权限和
本地恢复。图文聊天、应用注册表、语音对话、画板和游戏则是在这个底座上运行的 App。
用户使用 App 完成操作,Agent 发来消息或请求;两边都先交给 Runtime 处理,因此任何 App
都不需要、也不能自己处理连接、登录态或系统权限。
```text
Remote Agent
│ 标准 LineUp 消息
AppServer / IM
┌─────────────────────────────────────────────────────────┐
│ LineUp Runtime │
│ │
│ 连接 · 会话 · 消息筛选 · App 管理 · Tool 管理 │
│ 权限 · Artifact · 存储 · Outbox · 恢复 · 审计 │
└─────────────┬───────────────────────────┬───────────────┘
│ Runtime SDK │ Host Provider
▼ ▼
Core / Installed App Tauri / OS
chat · app-registry 文件 · 麦克风
voice · whiteboard 通知 · 窗口
game · … 安全存储
```
## 1. 本方案的结论与优先级
本文件收敛正式方案目录下的三份已有文档,并在冲突处做出当前开发阶段的明确选择。
| 主题 | 最终决定 |
|---|---|
| App 的本体 | 整个产品是 `LineUpRuntime`;聊天不是 Runtime 本身。默认的 `Interaction App` 负责组织人与 Agent 的主交互,当前实现形态是 `chat`/图文 IM。 |
| 默认入口 | `Interaction App` 是当前默认和 Recovery App;当前默认模式是 `im`(代码兼容作用域仍为 `chat`),以后可切换到实时音频或视频模式。 |
| App 之间的关系 | App 只调用 Runtime SDK,不直接访问 AppServer、Agent、另一个 App 或 Tauri 特权 API。 |
| Agent 交互 | Agent 只与 Runtime 通信;Runtime 按会话和应用作用域筛选并投递给 App。 |
| 路由最小键 | 所有可路由消息必须有 `app_scope``conversation_id`;可选 `instance_id``operation_id``call_id`。 |
| App 生态阶段 | 当前只使用本地短作用域,如 `chat``whiteboard``draw-and-guess`;暂不引入供应商身份、反向域名 App ID 或全局命名空间。 |
| 丰富交互 | 标准内容先用可信内建组件;复杂互动用受限 Surface;设备/系统动作只能经 Capability Gateway。 |
| Agent 工具 | App 在 Manifest 声明方法,Runtime 汇总为动态 InventoryAgent 只能调用当前 Inventory 中的方法。 |
| Runtime SDK | SDK 提供状态订阅、Agent 消息订阅、Action、Tool、应用导航、Artifact、存储、Capability 和生命周期接口。 |
| Host | Tauri Desktop Host 与同代码的 Web Reference Host 是当前唯一实现/验收基线:前者是正式桌面外壳,后者是浏览器开发和验证入口;它们不是两个客户端。不规划独立 Android/Kotlin 客户端或 Wails Host。 |
本文件优先于来源文档中关于**后续 Runtime 重构**的结构描述。来源文档中的 M0~M4 完成记录、当前已实现字段和历史验收事实仍然有效;它们不自动成为新 Runtime 的长期模块边界。
## 2. 系统边界
### 2.1 Runtime、App 与 Host:先分清三者
这三个词经常同时出现,但职责不同:Runtime 是后台协调者,App 是用户看到的功能,Host
是让 Runtime 运行在桌面窗口或浏览器中的外壳。Host 不能绕过 Runtime 直接把系统能力
交给 App 或 Agent。
```text
Runtime
管理连接、状态、应用、工具、权限和副作用。
App
面向用户或 Agent 的功能单元;只能通过 SDK 与 Runtime 协作。
Host
提供操作系统或 WebView 能力;由 Runtime 使用,不直接暴露给 App/Agent。
```
| 主体 | 必须负责 | 明确不负责 |
|---|---|---|
| Runtime | 连接 AppServer/Agent、管理会话与路由、安排 App 生命周期、Tool、Capability、持久化和审计 | 具体页面 DOM、任意 App 的业务 UI。 |
| Chat App | 图文会话、时间线、输入、任务/交互卡片和 Artifact 基础展示 | `fetch` AppServer、同步 cursor、Agent 原始包解析、文件系统。 |
| App Registry App | 让用户查看已安装 App,并请求安装/启用/禁用/移除或选择默认入口 | 直接写 Registry、删除 Bundle、绕过验签。 |
| Installed App | 自己的交互体验、已声明 Tool、已声明事件和私有状态 | Tauri `invoke`、Host DOM、token、任意网络和其他 App 数据。 |
| Tauri Host Provider | 在 Runtime 授权后实现文件、音频、通知、窗口和安全存储等系统操作 | 协议解释、应用策略和 Agent 路由。 |
### 2.2 关键禁止项
```text
App → AppServer / Agent 直接网络连接 禁止
App → 直接构造并发送未校验 Agent Envelope 禁止
Surface → Tauri invoke / 父页面 DOM / 登录态 禁止
Agent → 任意 App JavaScript 函数或 Host 特权 API 禁止
Agent → 静默安装、启用、禁用或移除 App 禁止
Surface Event → 直接执行系统能力 禁止
```
## 3. Runtime 的内部模型
### 3.1 三类输入输出
Runtime 采用 Event / Command / Effect 分层。
```text
Event:已经发生的事实
Agent 文本、任务进度、用户点击、画板变更、录音完成、连接断开。
Command:希望 Runtime 处理的动作
发送消息、调用 Tool、启动 App、启用 App、请求保存文件。
EffectRuntime 决定执行的副作用
网络发送、文件选择、录音、通知、创建 Surface、写存储。
```
处理顺序固定为:
```text
接收 Event / Command
→ 验证与授权
→ 更新 Runtime State
→ 持久化事实 / Outbox
→ 产生 Effect
→ Effect 结果重新进入 Runtime,成为 Event
→ 生成面向 App 的状态投影或消息投递
```
第一版采用“事件驱动状态机 + 必要快照”,不实施完整 Event Sourcing。关键入站事件、用户决定、Tool 终态、outbox 和审计需持久化;纯渲染细节、焦点和滚动位置不必写入 Runtime 事实日志。
### 3.2 顶层状态
```text
identity
用户、设备、认证会话。
connection
AppServer 连接、sync cursor、重试、实时状态、outbox。
conversations
conversation、关联 Agent、消息引用、任务、交互、Artifact 元数据。
apps
App Registry、默认 App、运行实例、每个 App 的可靠 Inbox。
tools
Tool 声明、Inventory revision、调用记录、operation 与进度。
capabilities
权限请求、用户决定、系统权限、执行状态与最小审计。
```
### 3.3 Runtime 生命周期
```text
created
→ restoring
→ authenticating
→ synchronizing
→ online
↘ reconnecting / offline_degraded
→ stopping
→ stopped
```
启动时 Runtime 必须恢复 App Registry、默认入口、cursor、outbox、App Inbox、未完成 Tool 和可恢复实例。追平服务器历史后才开始正常实时投递;离线时可接受的用户/App Action 进入 outbox,重连后以幂等键和顺序发送。
## 4. 应用模型
### 4.1 本地应用作用域
当前阶段使用 Runtime 本地注册的 `app_scope`,而不是全球 App 身份:
```text
chat
app-registry
settings
voice
whiteboard
draw-and-guess
runtime
```
`app_scope` 的职责仅是本机启动、消息路由、Tool 路由、存储隔离和实例归属。它不是供应商身份,不要求跨市场唯一,也不承担开放生态的所有权模型。
未来如需市场、多供应商和跨设备分发,可以在 Manifest 中增加稳定身份映射;但不能改变本方案中的 `app_scope + conversation_id` 路由与隔离语义。
### 4.2 应用类型
| 类型 | 典型项 | 来源与执行方式 | 管理规则 |
|---|---|---|---|
| Core App | `chat``app-registry``settings` | 跟随 Runtime 发行的可信代码 | 不可由市场 Bundle 覆盖;`chat` 不可移除。 |
| Installed App | `voice``whiteboard``draw-and-guess` | 经 Runtime 验证的 Bundle,在受限 Surface 中运行 | 可安装、启用、禁用、更新、回滚、移除。 |
| Capability | 录音、选文件、保存、通知 | Tauri/OS 经 Runtime Host Provider 提供 | 不是 App,也不是自动授予的权限。 |
### 4.3 App Registry 与默认入口
Runtime App Manager 是安装和状态的唯一事实源:
```ts
type AppRecord = {
app_scope: AppScope;
kind: "core" | "installed";
installed_version: string;
previous_version?: string;
enabled: boolean;
install_state: "installed" | "updating" | "failed";
active_instance_count: number;
installed_at: string;
enabled_at?: string;
};
interface RuntimeAppManager {
listApps(filter?: AppListFilter): readonly AppRecord[];
getApp(appScope: AppScope): AppRecord | undefined;
install(request: InstallAppRequest): Promise<AppOperation>;
enable(appScope: AppScope): Promise<AppOperation>;
disable(appScope: AppScope, options?: DisableAppOptions): Promise<AppOperation>;
update(appScope: AppScope, targetVersion?: string): Promise<AppOperation>;
rollback(appScope: AppScope): Promise<AppOperation>;
remove(appScope: AppScope, options?: RemoveAppOptions): Promise<AppOperation>;
launch(request: LaunchAppRequest): Promise<AppInstanceHandle>;
closeInstance(instanceID: AppInstanceID): Promise<void>;
}
```
App Registry App 只调用上述接口,不直接管理 Bundle 文件。安装、验证、启用、升级和移除是异步操作,必须有可观察的 `AppOperation`
Runtime Shell 始终独立于上层 App,负责应用切换、全局连接状态、通知和安全恢复。其启动目标优先级:
```text
显式目标(深链接 / 通知)
→ 可安全恢复的前台实例
→ 用户设定的 Default App
→ chat Recovery App
```
`chat` 是当前图文 IM 主入口,也是不可移除的 Recovery App。未来用户可将 `voice` 设为默认入口;这只改变主要交互方式,不改变会话、Agent、outbox 或 Artifact 的归属。
### 4.4 App 实例生命周期
```text
not_running → launching → active → backgrounded → suspended
↘ restoring → active
active / suspended → closing → closed
↘ failed
```
- Chat 与 App Registry 默认是单一全局实例;
- 工具型 App 默认按 `conversation_id` 创建实例,可用 `instance_id` 区分同会话的多个实例;
- 禁用阻止新调用并收口现有实例;移除在实例关闭且用户确认后清理 Bundle/私有数据;
- Surface 被回收时 App 通过 Runtime snapshot 恢复,不能重复发送先前的用户事件。
### 4.5 Runtime 编排、Interact 与扩展 App
`Interaction App` 是默认的人与 Agent 交互应用,但它不是整个 App 生态的调度器。应用
启动、Tool 路由、前后台焦点、实例生命周期和恢复均由 Runtime 管理;Interact 只负责
当前主会话的交互体验,以及把扩展 App 的结果投影回会话。
```text
LineUp Runtime
├── App Registry / App Instance Manager
├── Tool Router / App Router
├── Focus Manager / Lifecycle Manager
└── Conversation / Store / Outbox
├── Interaction AppCore App
│ ├── IM mode:文字、图片、短消息
│ ├── Audio mode:实时语音
│ ├── Video mode:实时视频
│ └── 标准交互原语:choice / confirm / input
└── Installed App
├── whiteboard
├── draw-and-guess
└── task-dashboard
```
Runtime 的职责是判断一个 Agent 请求应由哪个应用实例、交互模式或标准组件承接;Interact
不直接执行任意 Tool,也不负责切换其他 App 的前台状态。它通过 SDK 接收 Runtime 已经
校验过的交互事件,并提交声明性用户 Action。
应用焦点是 Runtime 状态的一部分:
```text
interaction:audio-001 running / foreground
↓ Agent 请求启动 draw-and-guess
interaction:audio-001 background 或 suspended
draw-and-guess:game-001 starting → running / foreground
```
原来的实例不会因为切换前台就被删除。Runtime 保存焦点栈和实例状态,以便游戏结束、用户
退出或新应用失败时恢复原来的 Interaction App 和会话模式。
一次“启动你画我猜”的完整流程是:
```text
Agent launch(draw-and-guess)
→ Runtime 校验 Envelope、Inventory、Manifest、参数和 App 状态
→ 创建 draw-and-guess App Instance
→ 保存当前 Interaction App/Audio Instance 的焦点位置
→ 将旧实例切换为 background / suspended
→ 将新实例切换为 foreground
→ App 通过 SDK 创建自己的 Surface 并接收用户操作
→ 结果经 Runtime 持久化、审计并回传 Agent
→ 游戏结束或关闭后恢复焦点栈中的 Interaction App
```
标准选择框、确认框和输入框属于 Interaction App 的内建交互原语,不需要作为独立安装包;
画板、游戏和复杂任务面板属于可安装扩展 App,与 Interaction App 是 Runtime 上的平级应用。
因此,Runtime 负责“能否调用、调用到哪里、哪个实例在前台以及如何恢复”;Interact 负责
“如何在主交互体验中呈现和协调结果”;扩展 App 负责“具体功能和自己的交互界面”。
## 5. 消息、筛选与实时订阅
### 5.1 统一路由 Envelope
所有 Agent、Runtime、App、User 与 Host 之间的可路由消息都必须带作用域:
```ts
type RuntimeEnvelope = {
v: 1;
id: string;
type: string;
timestamp: string;
sender: {
kind: "agent" | "runtime" | "app" | "user" | "host";
id: string;
};
target: {
kind: "runtime" | "app";
app_scope: AppScope;
instance_id?: AppInstanceID;
};
scope: {
app_scope: AppScope;
conversation_id: ConversationID;
instance_id?: AppInstanceID;
operation_id?: OperationID;
};
correlation?: {
reply_to?: string;
call_id?: ToolCallID;
inventory_revision?: string;
sequence?: number;
};
payload: JsonValue;
};
```
全局 Runtime 事件同样保留作用域:
```text
target.app_scope = runtime
scope.app_scope = runtime
conversation_id = runtime:global
```
### 5.2 现有协议兼容
当前 Tauri 实现与 M0M4 golden fixture 使用 `lineup.v1.*` 类型,以及 Surface payload 内的旧 `app.id` 字段。这些是**当前实现兼容事实**,不能在没有版本迁移和 golden fixture 的情况下直接删除。
Runtime 重构的规则是:
```text
旧 LineUp v1 Envelope
→ Runtime Compatibility Adapter
→ 补齐/映射 app_scope、conversation_id、instance_id
→ RuntimeEnvelope
→ App SDK
```
新的 Runtime/App SDK 边界不得再依赖旧 `app.id` 的供应商命名语义。R0 负责冻结新旧字段的映射表、拒绝规则和双向 fixture;在映射完成前,旧协议继续只由 Runtime 适配层处理,App 永远不直接读取它。
### 5.3 Runtime 筛选链
```text
Transport 原始输入
→ 版本、type、app_scope/conversation_id、大小、schema 校验
→ Agent 身份与会话关联校验
→ id / sequence / cursor 去重与顺序处理
→ App 安装、启用、版本、Host 兼容性校验
→ Tool / Capability、Inventory、参数、策略、App 启动和前台条件校验
→ conversation / instance / operation 所属关系校验
→ Manifest 订阅声明与 SDK 订阅条件求交
→ Runtime Event / App Inbox / cursor 持久化
→ 向目标 App SDK 投递标准化消息
```
Runtime 不广播原始 Agent 消息。相同 conversation 下,Chat 和 Voice 可以收到经 Runtime 投影的 Agent status;白板只收到本实例声明的 patch、Tool 调用和 lifecycle 事件,除非其 Manifest 明确申请并获准其他订阅。
### 5.4 App SDK 的实时 Agent 消息能力
SDK 同时提供实时订阅和可靠 Inbox 补读:
```ts
interface AgentMessageAPI {
list(request?: {
conversation_id?: ConversationID;
after?: AgentMessageCursor;
limit?: number;
}): Promise<AgentMessagePage>;
subscribe(
options: {
conversation_id?: ConversationID;
types?: readonly AgentMessageType[];
include_pending?: boolean;
},
handler: (message: AgentAppMessage) => Promise<void> | void,
): Unsubscribe;
acknowledge(message_id: string): Promise<void>;
}
```
投递语义:
1. Runtime 先校验、标准化和持久化,后调用订阅者;
2. 每条消息有唯一 `message_id`App 必须幂等处理;
3. 展示类消息可自动 ACK;Tool 调用、状态 patch 和需业务处理的消息要求 App 显式 ACK;
4. App 未运行、暂停或崩溃时,消息进入该 App 的 Inbox;恢复后 `list()``subscribe()` 补齐;
5. App 禁用、移除或不兼容时,Runtime 对关键 Agent 调用返回确定拒绝,不能静默丢弃。
实际投递集是:
```text
Manifest agent_subscriptions
∩ SDK subscribe filter
∩ App 当前权限
∩ conversation / instance scope
∩ Agent target app_scope
∩ Runtime Policy
```
## 6. Runtime SDK v1
SDK 是上层 App 使用 Runtime 的唯一标准入口。Core App 拿到完整 App SDKInstalled App 只得到同一语义的受限 Surface Bridge SDK。
```ts
interface LineUpAppRuntimeSDK {
readonly app: AppContext;
readonly lifecycle: AppLifecycleAPI;
readonly state: AppStateAPI;
readonly agentMessages: AgentMessageAPI;
readonly actions: AppActionAPI;
readonly tools: AppToolAPI;
readonly apps: AppNavigationAPI;
readonly artifacts: ArtifactAPI;
readonly storage: ScopedStorageAPI;
readonly capabilities: CapabilityRequestAPI;
readonly diagnostics: DiagnosticsAPI;
}
```
### 6.1 Context、状态和生命周期
```ts
interface AppContext {
app_scope: AppScope;
app_version: string;
instance_id: AppInstanceID;
kind: "core" | "installed";
entrypoint: string;
scope: {
conversation_id?: ConversationID;
operation_id?: OperationID;
parent_instance_id?: AppInstanceID;
};
granted_permissions: readonly AppPermission[];
}
interface AppStateAPI<ViewModel = unknown> {
snapshot(): ViewModel;
subscribe(listener: (view: ViewModel, change: AppStateChange) => void): Unsubscribe;
}
```
App 只读取按其 Scope 裁剪的 View Model,不读取全量 Runtime State。生命周期包含 `start``foreground``background``suspend``restore``closing`;Runtime 保留禁用、移除和强制收口的最终权力。
### 6.2 Action、Tool 与跨 App 导航
```ts
interface AppActionAPI {
dispatch(action: AppAction): Promise<CommandReceipt>;
}
interface AppToolAPI {
onInvoke(listener: (call: AppToolInvocation) => Promise<AppToolOutcome>): Unsubscribe;
progress(request: { call_id: ToolCallID; progress: ToolProgress }): Promise<void>;
complete(request: { call_id: ToolCallID; result: JsonValue }): Promise<void>;
fail(request: { call_id: ToolCallID; code: AppToolErrorCode; message?: string }): Promise<void>;
}
interface AppNavigationAPI {
listAvailable(): readonly AppSummary[];
launch(request: LaunchAppRequest): Promise<AppInstanceHandle>;
focus(instanceID: AppInstanceID): Promise<void>;
close(instanceID: AppInstanceID): Promise<void>;
openDefaultApp(): Promise<void>;
}
```
App 只能提交声明性 Action,不能构造原始 Agent Envelope。跨 App 打开、聚焦和关闭也必须经 Runtime 校验目标 App 的启用状态、入口、展示模式和会话作用域。
### 6.3 Artifact、存储与 Capability
```text
Artifact
Runtime 管理元数据、受限读取、创建、会话引用、下载和用户保存。
Storage
Runtime 为每个 app_scope 提供隔离 JSON 数据与 snapshot;管理配额、迁移和清理。
Capability
App 以 purpose + input 请求;Runtime 决定是否确认、调用 Host、审计并返回受控结果。
```
Capability SDK 形状:
```ts
interface CapabilityRequestAPI {
request<T extends JsonValue>(request: {
capability: CapabilityName;
purpose: string;
input: JsonValue;
scope?: AppScope;
}): Promise<CapabilityResult<T>>;
}
```
Runtime 必须检查:Manifest 声明、App 启用状态、Host 支持、前台要求、用户确认、系统权限、策略、速率限制和审计。结果只能是 `completed``cancelled_by_user``permission_denied``unsupported_on_host``policy_denied``expired` 或受控 `failed`,不暴露路径、token 或底层原生异常。
## 7. Tool、Inventory 与 Agent 调用
### 7.1 App Tool 声明
每个 App 可以声明 Agent 可调用 Tool。当前的完整定位是:
```text
app_scope / method / contract_version
whiteboard / board.create / 1
voice / voice.request_recording / 1
draw-and-guess / game.start_round / 1
```
Tool Manifest 至少包括:标题、面向 Agent 的说明、输入/输出 JSON Schema、调用模型、超时、幂等性、前台要求、风险等级、App 启动策略和可能使用的 Capability。
调用模型固定为:
| 模型 | 用途 | 示例 |
|---|---|---|
| `query` | 只读、快速 | 查询画板摘要。 |
| `command` | 确定性状态改变 | 创建白板、开始一局游戏。 |
| `interactive` | 必须等待用户参与 | 录音、填写复杂表单。 |
| `operation` | 长时间执行并有进度 | 导出画板、处理大文件。 |
### 7.2 动态 Runtime Inventory
Runtime 向每个活跃 Agent 会话发布 revisioned Inventory。它包含当前可用的标准组件、已启用 App、Surface、Tool 和 Capability;它不是安装命令,也不携带 Bundle 源码、token、文件路径或用户私有内容。
```text
Agent 可见 Tool
= 已验证 Manifest Tool
∩ App enabled
∩ Host supported
∩ 用户 / 组织策略允许
∩ Agent + conversation scope 允许
∩ 所需前置条件满足
```
App 启用、禁用、更新、撤销、Host 能力变化或策略收紧后,Runtime 增加 revision 并重新发布。Agent 的 Tool 调用必须携带 `inventory_revision`;旧 revision 的调用必须安全拒绝并提示 Agent 刷新清单。
### 7.3 Tool 调用闭环
```text
Agent tool.invoke
→ Runtime 校验 Envelope / Inventory / Tool / 参数 / Scope
→ Tool Router 判断直接执行、启动 App、切换前台或等待用户交互
→ App Instance / Focus Manager 创建或切换实例
→ Interaction App、交互模式或扩展 App 承接请求
→ App SDK 发送 progress / result / error
→ Runtime 校验输出、持久化、更新焦点并回传 Agent
```
至少支持以下确定错误码:
```text
cancelled_by_user permission_denied app_disabled
app_not_installed app_version_mismatch tool_not_visible
invalid_arguments foreground_required operation_expired
handler_failed
```
Tool 是 App 的业务方法;Capability 是 Runtime/Host 的系统能力。Tool 可请求 Capability,但不会因声明 Tool 自动获得麦克风、文件或剪贴板权限。
## 8. Surface 与安全边界
### 8.1 标准组件优先
文字、Markdown、图片、链接、状态、任务进度、choice、confirm、input、Artifact 基础预览优先采用 Runtime / Core App 的可信内建渲染器。它们必须可访问、可测试、可离线恢复,并保持 Markdown sanitizer、外链保护和严格 schema。
Surface 仅用于画板、地图、图表、复杂表单、游戏、专业编辑器等内建组件不足以表达的复杂交互。
### 8.2 Installed App Surface
下载型 App 运行在隔离容器中:
```text
iframe sandbox="allow-scripts"
- 不带 allow-same-origin
- 不带 allow-popups / allow-top-navigation / allow-forms
- 默认 CSP 禁止网络和外部脚本
- 只经严格 postMessage / Surface Bridge SDK 通信
```
Runtime/Host 必须验证 `event.source`、当前 `instance_id`、消息类型、事件名、JSON schema、消息大小和声明的投递策略。Surface 只能接收 state patch、触发已声明事件、处理已声明 Tool 调用、使用隔离存储和请求受控 Capability。
Surface 不能:
```text
读取父页面 DOM、localStorage 或 token
调用 Tauri invoke
访问其他 App 的数据和实例
直接访问 Agent / AppServer
任意联网、导航、弹窗或加载远端脚本
直接执行 app.call / 系统能力
```
用户在 Surface 中的操作只是 App Event;需要系统副作用时,Runtime 仍按 Capability 规则确认、执行和审计。
### 8.3 Bundle 与发布
当前已验证的生产安全原则保持不变:Bundle 必须是不可变 artifact,执行前检查来源、大小、SHA-256、签名、Host 兼容性和回滚条件;验证失败不得进入 active cache。开发期 inline bundle 只能是受限开发 fixture,不是长期市场供应方式。
本阶段仅定义本地 `app_scope`,不定义开放市场发布者身份。Bundle 信任、App 管理和 Tool 可见性仍必须由 Runtime 控制;将来引入供应商身份时须以新 Manifest 版本扩展,不能放宽本节隔离规则。
## 9. App Manifest 最小契约
```json
{
"format": "lineup.app.v1",
"app_scope": "whiteboard",
"version": "1.0.0",
"runtime_sdk": {
"api_version": "1",
"required_features": [
"app.lifecycle.v1",
"app.tools.v1",
"surface.state.v1"
],
"optional_features": ["artifact.create.v1"]
},
"entrypoints": [
{"id": "canvas", "kind": "contextual", "default_eligible": false}
],
"agent_subscriptions": [
{"type": "lineup.app.state.patch", "scope": "instance"},
{"type": "lineup.tool.invoke", "scope": "conversation"}
],
"tools": [
{
"method": "board.create",
"contract_version": "1",
"invocation": {"kind": "command", "activation": "launch_if_needed"},
"input_schema": {"type": "object", "additionalProperties": false},
"output_schema": {"type": "object", "additionalProperties": false}
}
]
}
```
Runtime 在安装、启用和启动时验证 SDK API 版本与 feature 集;App 不兼容、未启用或当前 Host 不支持时,不得进入 Inventory、创建实例或接收 Tool 调用。
## 10. Tauri 参考实现的最终模块边界
```text
lineup-app/
├── runtime-core/
│ ├── communication/ AppServer transport、sync、outbox
│ ├── coordination/ event、state、router、policy、audit
│ ├── apps/ app registry、instance manager、default app
│ ├── tools/ tool registry、inventory、call lifecycle
│ ├── capabilities/ capability gateway
│ └── persistence/ runtime store、app inbox、artifact metadata
├── runtime-sdk/
│ ├── app.ts Core App SDK
│ ├── manifest.ts App/Tool/Subscription 类型
│ ├── protocol.ts RuntimeEnvelope / schema
│ ├── errors.ts 稳定错误码
│ └── testkit/
├── surface-sdk/
│ ├── bridge.ts sandbox message bridge
│ └── surface.ts Installed App 最小 SDK
└── tauri/
├── src/
│ ├── bootstrap/ 应用启动装配层
│ ├── runtime-host/ Web/Tauri Host Provider adapter
│ ├── shell/ switcher、default、recovery
│ └── core-apps/ chat、app-registry、settings
└── src-tauri/ Rust 文件、音频、通知、窗口 Provider
```
`lineup-app/tauri/src/main.ts` 是当前 Tauri/Web Host 的启动装配入口:它创建 Host 侧适配、
`LineUpRuntime` 与默认 Runtime App Host,并把它们接起来。它不创建或持有 Transport、
Store、sync loop、outbox,也不解析原始 Agent payload。现阶段仍有可信 Renderer、Surface
与 Capability 的 Host 集成代码;后续可以继续迁移这些 UI 适配,但不得把 Runtime 所有权
重新放回 `main.ts`
可直接迁移的现有基础:
| 当前模块 | 最终归属 |
|---|---|
| `transport-adapter.ts` | `runtime-core/communication/` |
| `conversation-store.ts` | `runtime-core/persistence/` |
| `interaction-kernel.ts` | `runtime-core/coordination/` 的协议入口/兼容适配基础 |
| Tool/Task/Execution 状态机 | Chat App 的投影 + Runtime call state |
| `surface-registry.ts` | `runtime-core/apps/` 的 App Registry 基础 |
| `surface-instance-manager.ts` | `runtime-core/apps/` 的 Instance Manager 基础 |
| Bundle manifest/cache/policy | `runtime-core/apps/` 与 Execution Plane |
| `client-inventory.ts` | `runtime-core/tools/` 的动态 Inventory Publisher |
| `capability-*` | `runtime-core/capabilities/` |
| `trusted-dom-renderers.ts` | `tauri/src/core-apps/chat/` |
## 11. 实施顺序
### F0:冻结契约与兼容映射
- 定义 `RuntimeEnvelope``app_scope``conversation_id``instance_id``operation_id``call_id` 的类型和校验;
- 定义旧 `lineup.v1.*` 到 RuntimeEnvelope 的兼容映射;
- 定义 Runtime SDK v1、Surface Bridge SDK v1、App Manifest、Tool Descriptor 和错误码;
- 为全部契约建立 golden fixture;不改变当前可验证的 M0~M4 行为。
### F1Runtime Core 与 Interaction Core App(当前 `chat` 实现)
-`main.ts` 提取单一 `LineUpRuntime`
- Runtime 独占 Transport、Store、outbox、原始消息校验和筛选;
- 将当前图文 IM 迁为 Interaction Core App 的 `chat` 实现;
- 当前 `chat``state.subscribe()``agentMessages.subscribe()` 获取已验证投影,不再依赖 Transport;
- 保持登录、同步、本地回显、Markdown、Tool Call、Task、Artifact 的回归行为。
#### MVP-R1 实施状态(2026-08-04
F0 与 F1 中支撑图文聊天迁移的最小闭环已落地并经自动化回归验证。此处的“完成”仅指下列 MVP 边界;Manifest、Installed App、完整 Tool Inventory 和应用市场仍属于后续阶段。
| MVP 项 | 当前实现 | 验证位置 |
|---|---|---|
| Runtime 单一所有权 | `LineUpRuntime` 唯一创建并持有 `TransportAdapter``ConversationStore``InteractionKernel`、同步循环与 outbox。 | `tauri/src/runtime/coordination/lineup-runtime.ts``lineup-runtime.test.ts` |
| Core App 入口 | `CoreAppRegistry` 记录当前可作为默认入口的 Core App;`CoreAppHostRegistry` 再根据 `app_scope` 找到对应的可信装配器。当前默认实现仍是 `chat`,但 `main.ts` 不再直接依赖 Chat 内部 Renderer 和 Shell。 | `runtime/app-management/app-registry.ts``runtime-app-host.ts``core-apps/chat/chat-app-host.ts``runtime-app-host.test.ts` |
| Chat SDK 边界 | Chat 仅以 `ChatRuntimeSDK` 订阅状态/Agent 消息、读取 Inbox、ACK,并以 Runtime Action 发送文本或交互动作。 | `runtime/app-management/app-sdk.ts``main.ts` |
| 作用域与旧协议兼容 | 入站消息先经 `resolveIncomingScope`;旧 `lineup.v1` 自动映射为当前会话的 `chat` 作用域,显式非法或不匹配的 scope 在投递前拒绝。 | `runtime/coordination/runtime-envelope.ts``lineup-runtime.test.ts` |
| 恢复与幂等 | `ConversationStore` 持久化 App Inbox;未 ACK 消息在 Runtime 重建后恢复,同一 `message_id` 不重复投递。 | `runtime/persistence/conversation-store.ts``conversation-store.test.ts``lineup-runtime.test.ts` |
本阶段不修改 AppServer 或 Hermes 的既有 `lineup.v1` 协议。兼容层只存在于 Runtime 内部,因此上层 Chat App 不读取旧协议字段,也不需要同步升级远端。
### F2App Registry 与默认 App
- 将开发期 Surface Registry 迁为 Runtime App Registry
- 实现 App Instance Manager、App Focus Manager 和生命周期状态(foreground/background/suspended);
- 实现 Runtime Tool Router / App Orchestrator:校验 Tool 后决定直接执行、启动 App、切换前台或等待用户交互;
- 实现 App 启用、禁用、移除、版本记录、实例收口和默认入口选择;
- 实现 `app-registry` Core App
- 将当前 `chat` 作为 Interaction App 的 IM 实现,为后续 Audio/Video 模式预留 entrypoint 和恢复策略。
### F3Tool Registry、Inventory 与 SDK Inbox
- 解析 Manifest Tool/Subscription 并计算可见性;
- 在 Runtime 连接与状态变化时同步 revisioned Inventory 给 AgentTool 可声明是否需要启动 App、是否要求前台以及结束后是否恢复原焦点;
- 实现 Agent Tool → Runtime → App SDK → result/progress 的可靠闭环;
- 将 MVP-R1 已实现的 Chat App Inbox、ACK、断线/崩溃恢复和拒绝码推广为所有受管理 App 的通用可靠投递机制。
### F4:第一个 Installed App
- 选择画板或任务面板作为第一个完整 Installed App
- 验收 install → enable → Inventory → Tool 调用 → App 启动/前台切换 → Surface 事件 → Agent result → 原前台恢复 → disable/remove
- 语音/视频属于 Interaction App 的模式或受 Runtime 管理的扩展 App,必须复用 Runtime SDK、Tool、Artifact 和 Capability 通道,不建立独立 Agent 通信链路。
## 12. 完成准入条件
```text
消息与隔离
[ ] 缺少/非法 app_scope 或 conversation_id 的可路由消息被拒绝。
[ ] App 不能收到其他 App 或其他 conversation 的 Agent 原始消息。
[ ] App 未运行时关键消息可从 Inbox 有界恢复,重复投递不重复执行。
应用管理
[ ] 启用/禁用/移除改变 Tool 可见性,并更新 Agent Inventory revision。
[ ] 默认 App 失效时必定回退至 Interaction App 的 IM/`chat` Recovery 入口。
[ ] 禁用/移除能收口活动实例、焦点栈、调用和私有数据清理流程。
[ ] Agent 启动扩展 App 时,Runtime 能将当前前台实例切换到后台,并在扩展结束后恢复原焦点。
工具与能力
[ ] Agent 只能调用当前 Inventory 中、参数 schema 合法的 Tool。
[ ] Tool 的 result/progress/error 经 Runtime 校验、持久化和审计。
[ ] App / Surface 无法绕过 Capability Gateway 获得系统权限。
安全
[ ] Installed App 无法访问 Tauri invoke、Host DOM、token、任意网络或其他 App 数据。
[ ] Bundle 只有在完整性和签名验证后才可运行;失败可回滚且不污染 active cache。
```
## 13. 旧文档的后续定位
| 文档 | 保留价值 | 在本方案后的定位 |
|---|---|---|
| [App 层架构方案](lineup-app-layer-architecture.md) | M0~M4 已实现边界、测试和验收事实;Markdown、Surface、Capability 原则 | 迁移基础与历史实施记录。 |
| [Runtime 与 App SDK 架构方案](lineup-runtime-sdk-architecture.md) | Runtime、App Manager、Tool Registry、SDK、消息筛选的完整初稿 | 被本文件收敛后的详细来源;以本文件的 `app_scope` 和实施顺序为准。 |
| [UI Surface 与 App Capability 协议](lineup-ui-surface-protocol.md) | Surface sandbox、bridge、Capability 风险分级、inventory 和 Bundle 安全规则 | 协议细节来源;R0 需完成其旧字段到 RuntimeEnvelope 的版本化映射。 |
以后新增 App 设计、SDK 方法、Agent Tool、Surface 或 Capability 时,先修改本文件的边界/契约,再实施代码和细节协议,避免重新把功能堆回聊天页面或创建绕开 Runtime 的平行通道。
@@ -0,0 +1,132 @@
# LineUp App 层架构与迁移记录
**版本:** 2.0Runtime 基线)
**状态:** 当前实现边界与历史迁移记录
**日期:** 2026-08-04
**最终决策入口:** [LineUp App 最终设计方案](app_final_design.md)
## 1. 当前结论
LineUp 是运行在用户设备上的协作 Runtime,不是某个平台上的独立聊天客户端。可以把
Runtime 理解为本地协调中心:它负责连接、消息、存储和恢复;`chat` 是它首先加载的可信
内置功能。未来的语音、画板、游戏和应用中心也必须使用同一套 Runtime / SDK 边界,不能
各自再建立一条到 Agent 的通信链路。
当前唯一实现与验收基线:
```text
Tauri 2 Desktop Host + Web Reference Host
```
这两个 Host 是同一套 TypeScript Runtime 的两种外壳:Tauri Desktop Host 用于正式桌面
交付及受控系统能力;Web Reference Host 用于浏览器开发、Tailscale 联调和自动化测试。
它们不是两套产品,Web 版也不能替代桌面版的系统能力。独立 Android/Kotlin 客户端与
Wails Host 均不在当前或后续规划范围内;早期实验细节仅保留在 Git 历史,不构成架构或
验收依据。
## 2. App 层边界
```text
Remote Agent / AppServer
│ lineup.v1 wire protocol
┌────────────────────────────────────────────────────────┐
│ LineUpRuntime │
│ Transport · sync loop · Store · Outbox · App Inbox │
│ scope 路由 · Compatibility Adapter · Tool · Capability │
└─────────────────────┬──────────────────────────────────┘
│ Runtime SDK
┌────────────────────────────────────────────────────────┐
│ Core / Installed App │
│ chat · app-registry · voice · whiteboard │
└────────────────────────────────────────────────────────┘
```
| 层 | 必须负责 | 不得负责 |
|---|---|---|
| `LineUpRuntime` | 处理 AppServer/Agent 通信、会话、原始协议兼容、scope 筛选、Store、sync、outbox、App Inbox、Tool 路由、App 实例和前台焦点、权限与恢复。 | 具体业务页面 DOM。 |
| Interaction Core App | 用户实际使用的主交互界面:当前是 Chat/IM,未来包含 Audio/Video 模式、标准交互原语和扩展结果投影。 | 直连 AppServer、维护 cursor、解析 Agent 原始包、调度其他 App 或直接写 Runtime Store。 |
| Runtime SDK | App 与 Runtime 之间唯一的受限接口:提供 snapshot、订阅、Agent 消息、Inbox ACK、Runtime Action、App 导航和生命周期请求。 | 把 Transport、token、Host 特权 API 暴露给 App。 |
| Tauri/Web Host | 把 Runtime 放进桌面窗口或浏览器,并提供对应的 DOM/系统能力。 | 解释协议、决定 App 调度、绕过 Runtime 路由和策略。 |
| Surface | 在隔离执行域呈现已验证 Bundle,并经受限 bridge 上报事件。 | 访问 Host DOM、登录态、Tauri API、任意网络。 |
## 3. MVP-R1Runtime 托管 Chat
当前 MVP 已完成并以 Tauri/Web 自动化回归验证以下八项条件:
| 条件 | 当前事实 |
|---|---|
| MVP-01 | `LineUpRuntime` 唯一创建并持有 Transport、`ConversationStore`、sync loop 与 outbox。 |
| MVP-02 | Runtime 从本地 `CoreAppRegistry` 解析默认 `chat``RuntimeAppHost` 负责挂载其 Shell`main.ts` 不创建聊天页面。 |
| MVP-03 | Chat 只经 `ChatRuntimeSDK` 订阅 Agent 消息、状态和 Chat-safe Runtime 事件,并以 Action 发送文本、交互、结果与取消。 |
| MVP-04 | Runtime 向 Chat 投递前验证 `app_scope = chat` 与当前 `conversation_id`;非法/不匹配消息被拒绝。 |
| MVP-05 | Compatibility Adapter 在 Runtime 内将旧 `lineup.v1` 映射为内部 `RuntimeEnvelope`;远端无须同步重写。 |
| MVP-06 | 登录、同步、发送、本地回显、Markdown、Agent 状态、Tool/Task 与刷新恢复保持回归。 |
| MVP-07 | Store 持久化每个 App 的 Inbox;运行时重建后未 ACK 消息可恢复,`message_id` 去重。 |
| MVP-08 | `npm test -- --run` 通过 21 个文件 / 94 个测试;`npm run build` 通过。 |
源代码目录和依赖方向见 [Tauri/Web 源码导航](../../lineup-app/tauri/src/README.md)。
## 4. 当前源码归属
```text
lineup-app/tauri/src/
├── main.ts # 启动装配入口:连接 Tauri/Web Host、Runtime 与默认 App
├── core-apps/chat/ # 当前 Interaction App 的 IM 实现、Renderer、样式
└── runtime/
├── app-management/ # Registry、App Instance、焦点、生命周期、Host
├── communication/ # Transport Adapter
├── coordination/ # Runtime、Kernel、Envelope、Tool/Task/交互编排
├── persistence/ # Conversation Store、Outbox、App Inbox
├── protocol/ # lineup.v1 解码与 golden fixture
├── surfaces/ # Surface 生命周期、Bundle、隔离 Host
├── capabilities/ # Registry、执行、审计
├── artifacts/ # Artifact 元数据与缓存
└── inventory/ # Agent 可见 Inventory
```
依赖必须保持单向:
```text
core-apps/interaction(当前 chat → runtime/app-management SDK → runtime/coordination
└→ communication / persistence / protocol
Host adapters → runtime/coordination
Surface → restricted bridge → Runtime Action / Capability Gateway
```
## 5. 历史迁移的保留价值
早期 M0M4 工作建立了当前 Runtime 的可复用安全基础。它们是迁移记录,不是新的功能
排期或平行客户端路线:
| 历史阶段 | 保留成果 | 当前归属 |
|---|---|---|
| M0 | HTTP Transport、Conversation Store、协议解码、Markdown、基础回归 | `communication``persistence``protocol`、Chat Renderer |
| M1 | Tool Call、Task、可靠交互结果、受限执行摘要 | `coordination`、Chat SDK 投影 |
| M2 | 本地 Surface Registry、实例生命周期、隔离 iframe 与恢复 | `surfaces` |
| M3 | Capability Registry、用户确认、审计与受限 Host 执行 | `capabilities` |
| M4 | 签名 Manifest、Bundle 校验/缓存/回滚、golden fixture | `surfaces``protocol/golden` |
早期文档中的阶段性“尚未实现”或旧目录路径不得用来判断当前能力;需要审计细节时通过
Git 历史检索。
## 6. 后续阶段
```text
F2 持久化 App Registry、App Instance/Focus Manager、默认 App 切换
F3 Runtime Tool Router、动态 Inventory、可靠 App SDK Tool 闭环
F4 Interaction App 的 IM/Audio/Video 模式边界与第一个 Installed App
```
未来 App 必须复用 `app_scope + conversation_id`、Runtime SDK、Tool/Capability、Artifact、
Store、outbox 与恢复机制;禁止新建独立 Agent 通信通道。
## 7. 文档关系
| 文档 | 用途 |
|---|---|
| [app_final_design.md](app_final_design.md) | 当前产品、Runtime、App、SDK 与实施优先级的唯一汇总入口。 |
| [lineup-runtime-sdk-architecture.md](lineup-runtime-sdk-architecture.md) | Runtime / SDK 的详细接口与未来 App Manager 契约。 |
| [lineup-ui-surface-protocol.md](lineup-ui-surface-protocol.md) | Surface sandbox、bridge、Capability 和 Bundle 安全协议。 |
| 本文件 | 当前实现边界、源码归属与 M0~M4 的迁移价值。 |
@@ -0,0 +1,726 @@
# LineUp Runtime 与 App SDK 架构方案
**版本:** 0.1(架构基线)
**状态:** 已确认的 Runtime / SDK 方向;尚未完全落地到当前 Tauri 实现
**日期:** 2026-08-04
**关联方案:** [LineUp App 层架构设计方案](lineup-app-layer-architecture.md)、[LineUp UI Surface 与 App Capability 协议](lineup-ui-surface-protocol.md)
> **汇总入口:** 本文是 Runtime / SDK 初稿的详细来源。后续设计以 [LineUp App 最终设计方案](app_final_design.md) 为准;后者收敛了本地 `app_scope`、兼容迁移和实施顺序。
---
## 0. 结论与范围
LineUp 不是“聊天应用里附带一个交互 Runtime”。**LineUp App 本身就是运行在用户设备上的
协作 Runtime**,可以把它理解为本地协调中心:它独占与 AppServer / Remote Agent 的连接,
维护会话和本地应用生态,并为上层应用提供统一的消息、工具、存储、权限和生命周期接口。
聊天只是第一个使用这些接口的内置 App。
```text
Remote Agent
│ LineUp Runtime Envelope
AppServer / IM Transport
┌───────────────────────────────────────────────────────────────┐
│ LineUp Runtime │
│ │
│ Connection · Session · Event Routing · App Manager │
│ Tool Registry · Capability Gateway · Storage · Audit │
└───────────────┬──────────────────────────────┬────────────────┘
│ │
▼ ▼
Core App Installed App
chat whiteboard
app-registry voice
settings draw-and-guess
```
本方案定义:
- Runtime 与 Remote Agent、AppServer、上层 App、Tauri Host 的边界;
- 动态应用列表、应用身份、应用安装/启用/禁用/移除与实例生命周期;
- 应用向 Agent 公布工具方法的标准模型;
- Runtime 对消息的统一 Envelope、筛选、可靠投递与 App SDK 订阅接口;
- Core App、Installed App 和 Tauri Host 的信任分层;
- SDK v1 的最小接口和工程模块边界。
本方案不在本次冻结:市场目录的商业模型、支付、公开开发者身份认证细节、多人/多 Agent 共享工作区、完整事件溯源和后台持续执行策略。它们必须建立在本方案的身份、路由和权限边界上。
## 1. 基本术语
| 术语 | 含义 |
|---|---|
| **Runtime** | 用户设备上的长期协作运行底座;唯一负责 AppServer/Agent 通信、应用管理和 Host 能力调度。 |
| **App** | 用户实际使用的功能单元。Chat、应用注册表、语音、白板和游戏都是 App。 |
| **Core App** | 跟随 LineUp Runtime 一起发布、默认受信任的内置 App,例如 `chat`。 |
| **Installed App** | 从受信来源安装、验证后在受限执行环境中运行的 App。 |
| **App Registry** | Runtime 在本机保存的应用清单,记录安装包、版本、启用状态、实例和回滚信息;它是这些状态的唯一依据。 |
| **Tool** | App 先在 Manifest 中说明、Runtime 再公布给 Agent 的可调用方法。Agent 不能任意调用 App 内部函数。 |
| **Capability** | Runtime / Host 保管的系统能力,例如录音、选择文件、保存 Artifact;App 必须请求,不能自动获得。 |
| **Surface** | 一个受限的交互小界面,例如一块画板、一个语音会话面板或一局游戏。 |
| **Conversation** | 用户与一个主 Agent 协作的一段会话范围;App 实例、工具调用和 Artifact 默认都归属这段会话。 |
## 2. 不可变架构原则
1. **Runtime 是唯一 Agent 通信入口。** App 不直接访问 AppServer、IM SDK、WebSocket、HTTP cursor 或 Agent endpoint。
2. **App 只与 Runtime 通信。** Chat、Voice、Market、白板均通过 SDK 读状态、订阅消息、提交动作和请求能力。
3. **每一条可路由 Runtime 消息必须具有 `app_scope` 与 `conversation_id`。** Runtime 以它们作为本地路由、授权、筛选、持久化和投递的首要键。
4. **原始 Agent payload 不交给 App。** Runtime 完成版本、身份、schema、大小、去重和作用域校验后,才产生可订阅的标准化消息。
5. **应用可调用方法必须先声明、后公布、再调用。** Agent 不执行 App 内任意函数,只能调用当前 Inventory 中精确声明的方法。
6. **系统能力由 Runtime 独占。** Agent 和 App 只能请求 Capability;文件、麦克风、通知、剪贴板、窗口、密钥等均不得直接访问。
7. **应用状态、消息和副作用分离。** Event 是已发生事实,Command 是请求动作,Effect 是 Runtime 执行的受控副作用。
8. **UI 是 Runtime 状态的投影。** App 重启、断网或 Surface 回收后,Runtime 能依据持久化状态恢复到可解释的协作状态。
## 3. 运行时分层
```text
┌──────────────────────────────────────────────────────────┐
│ Runtime Shell │
│ 应用切换 · 默认入口 · 连接状态 · 通知 · 安全恢复 │
├──────────────────────────────────────────────────────────┤
│ Core App / Installed App │
│ Interaction AppIM / Audio / Video · App Registry │
│ Settings · Whiteboard · Game · …(Installed App
├──────────────────────────────────────────────────────────┤
│ LineUp App Runtime SDK / Surface Bridge SDK │
├──────────────────────────────────────────────────────────┤
│ LineUp Runtime Core │
│ Communication · Coordination · App Management · Tooling │
├──────────────────────────────────────────────────────────┤
│ Runtime Host Provider │
│ Tauri Desktop / Web Reference Host 的文件、通知、窗口等 │
└──────────────────────────────────────────────────────────┘
```
Runtime 内部有三个平面:
| 平面 | 职责 |
|---|---|
| Communication Plane | 登录、AppServer 连接、历史同步、实时入站、ACK、outbox、重连。 |
| Coordination Plane | Envelope 校验、事件路由、会话/Agent 状态、App 生命周期、Tool Registry、Inventory、策略与审计。 |
| Execution Plane | Tauri/Host Capability、Surface Sandbox、Bundle Cache、Artifact 内容和受控副作用。 |
## 4. 应用作用域
本阶段**不定义跨市场、跨供应商的 App 身份、反向域名命名空间或发布者归属模型**。Runtime 仅维护本机可用的应用作用域键(`app_scope`),用于启动、隔离存储、消息路由和 Tool 路由。
```text
chat
app-registry
settings
voice
whiteboard
draw-and-guess
```
`app_scope` 是 Runtime 本地注册表中的短名称,不承诺在未来开放市场中全局唯一。开放生态时再通过版本化 Manifest 引入稳定 App ID、供应商身份和命名空间映射;届时不得破坏本节定义的 scope 路由语义。
### 4.1 作用域键
| 键 | 用途 |
|---|---|
| `conversation_id` | 协作会话边界;所有可路由消息均必填。 |
| `app_scope` | 消息所属/目标 App 边界;所有可路由消息均必填。 |
| `instance_id` | 可选;精确标识某块白板、语音会话或游戏实例。 |
| `operation_id` | 可选;标识长任务、导出、执行进度或异步工具操作。 |
| `call_id` | 可选;标识 Agent 发起的一次 Tool 调用。 |
Runtime 的全局管理事件也不省略路由键,而使用保留作用域:
```text
app_scope: runtime 或具体 Core App
conversation_id: runtime:global
```
第三方 App 不得使用或伪造上述保留身份。
## 5. Runtime App Manager 与交互编排
### 5.1 应用目录与状态
Runtime 维护本地 `App Registry`,它是 App 安装、版本、信任和启用状态的唯一事实源;
`App Instance Manager` 单独维护运行实例、前后台焦点和生命周期。App Catalog/市场只提供
候选条目;市场 App 不直接写文件、删除 Bundle 或改写 Registry。
```text
Catalog Entry → Downloading → Verifying → Installed → Enabled
│ │
│ ├── Active / Background / Suspended instances
│ └── Disabled
└── Update / Rollback / Remove
```
建议的 App Record
```ts
type AppRecord = {
app_scope: AppScope;
kind: "core" | "installed";
installed_version: string;
previous_version?: string;
enabled: boolean;
install_state: "installed" | "updating" | "failed";
active_instance_count: number;
installed_at: string;
enabled_at?: string;
};
type AppInstanceRecord = {
instance_id: AppInstanceID;
app_scope: AppScope;
conversation_id?: ConversationID;
state: "starting" | "foreground" | "background" | "suspended" | "stopping" | "stopped" | "failed";
parent_instance_id?: AppInstanceID;
started_at: string;
stopped_at?: string;
error?: string;
};
```
### 5.2 Runtime 管理接口
```ts
interface RuntimeAppManager {
listApps(filter?: AppListFilter): readonly AppRecord[];
getApp(appScope: AppScope): AppRecord | undefined;
subscribe(listener: (change: AppRegistryChange) => void): Unsubscribe;
install(request: InstallAppRequest): Promise<AppOperation>;
enable(appScope: AppScope): Promise<AppOperation>;
disable(appScope: AppScope, options?: DisableAppOptions): Promise<AppOperation>;
update(appScope: AppScope, targetVersion?: string): Promise<AppOperation>;
rollback(appScope: AppScope): Promise<AppOperation>;
remove(appScope: AppScope, options?: RemoveAppOptions): Promise<AppOperation>;
launch(request: LaunchAppRequest): Promise<AppInstanceHandle>;
closeInstance(instanceID: AppInstanceID): Promise<void>;
}
interface RuntimeAppOrchestrator {
launch(request: LaunchAppRequest): Promise<AppInstanceHandle>;
foreground(instanceID: AppInstanceID): Promise<AppInstanceHandle>;
background(instanceID: AppInstanceID, reason?: string): Promise<AppInstanceHandle>;
suspend(instanceID: AppInstanceID, reason?: string): Promise<AppInstanceHandle>;
restore(instanceID: AppInstanceID): Promise<AppInstanceHandle>;
closeInstance(instanceID: AppInstanceID): Promise<void>;
getFocusStack(conversationID?: ConversationID): readonly AppInstanceID[];
}
```
安装、验签、升级、禁用和移除是异步操作,必须返回可观察的 `AppOperation`。禁用会阻止新实例和新 Tool 调用,并收口现有实例;移除会在实例关闭及用户确认后清理 Bundle 和 App 私有数据。
### 5.3 默认应用与安全回退
Runtime Shell 不属于任何 App。它负责应用切换、通知、连接状态和安全恢复。用户可选择一个具备 `default_eligible` entrypoint 的已启用 App 作为默认入口:
```text
当前默认:chat → 图文 IM 为主
未来默认:voice → 实时语音为主
```
`chat` 是内建、不可移除的 Recovery App:默认 App 不兼容、被禁用、损坏或启动失败时,Runtime 必须回退到它。
启动优先级:
```text
显式启动目标(深链接/通知)
→ 可安全恢复的前台工作
→ 用户 Default App
→ chat Recovery App
```
### 5.4 前台焦点与 App 间切换
`Interaction App` 是默认的人与 Agent 交互入口,但不是全局调度器。Runtime 的
`App Orchestrator``Tool Router``Focus Manager` 负责决定 Agent 请求由哪个 App、哪个
交互模式或哪个标准组件承接。Interact 只通过 SDK 呈现当前会话并提交用户 Action。
```text
Interaction App / Audio Mode foreground
→ Agent 请求 launch(draw-and-guess)
Runtime 校验 Tool、Manifest、Inventory、参数和前台条件
→ Audio Instance background / suspended
→ Draw-and-Guess Instance starting → foreground
→ 游戏结果经 Runtime 回传 Agent
→ 关闭或结束后按 focus stack 恢复 Interaction App
```
前后台切换必须保留原实例和 `conversation_id` 的关联,不能因为界面暂时不可见就删除
未完成 Tool Call、Surface 或 App Inbox。若目标 App 启动失败,Runtime 应恢复原前台实例并
向 Agent 返回受控的 `app_start_failed``handler_failed` 结果。
## 6. Runtime Tool Registry 与动态 Inventory
### 6.1 App Tool
App 可在 Manifest 中声明 Agent 可调用方法。当前 Tool 由 App Scope、局部方法和契约版本定位:
```text
whiteboard/board.create@1
whiteboard/board.apply_diagram@1
voice/voice.request_recording@1
```
Runtime 内部使用结构化键,而不依赖字符串拼接:
```ts
type ToolKey = {
app_scope: AppScope;
method: string;
contract_version: string;
};
```
每个 Tool 声明至少包含:
- 标题、面向 Agent 的说明、输入/输出 JSON Schema
- `query``command``interactive``operation` 四种调用模型之一;
- 前台要求、超时、幂等规则和风险等级;
- 是否需启动 App 实例、是否允许 Runtime 自动启动;
- 该方法可能请求的 Capability。
### 6.2 可见性与 Inventory
Runtime 向 Agent 公布的工具不是安装包的全部声明,而是动态交集:
```text
Agent 可见 Tool
= 已验证 Manifest Tool
∩ App 已启用
∩ 当前 Host 支持
∩ 用户/组织策略允许
∩ 当前 Agent 和 conversation scope 允许
∩ 所需前置权限满足
```
Inventory 包含 App、Tool、Surface 和 Runtime Capability,并具有 revision。Runtime 在连接建立、App 启用/禁用/升级/撤销、Host 能力变化或策略收紧后重新发布。Agent 调用必须携带其看到的 `inventory_revision`;过期清单的调用应被安全拒绝并提示刷新。
### 6.3 Tool 调用流程
```text
Agent lineup.tool.invoke
→ Runtime 验证 Envelope、Agent、inventory revision、App 状态和参数 schema
→ 创建 call record / 审计记录
→ Tool Router 判断直接执行、启动 App、切换前台、等待用户交互,或创建异步 operation
→ App Orchestrator / Interaction App / Installed App 承接调用
→ App 经 SDK 返回 progress / result / error
→ Runtime 验证输出 schema、持久化、更新焦点并回传 Agent
```
标准终态错误码至少包括:
```text
cancelled_by_user
permission_denied
app_disabled
app_not_installed
app_version_mismatch
tool_not_visible
invalid_arguments
foreground_required
operation_expired
handler_failed
```
## 7. 统一消息 Envelope 与路由
### 7.1 Runtime Envelope
所有 Agent、Runtime、App、User 和 Host 之间的可路由消息使用统一基础 Envelope:
```ts
type RuntimeEnvelope = {
v: 1;
id: string;
type: string;
timestamp: string;
sender: {
kind: "agent" | "runtime" | "app" | "user" | "host";
id: string;
};
target: {
kind: "runtime" | "app";
app_scope: AppScope;
instance_id?: AppInstanceID;
};
scope: {
app_scope: AppScope;
conversation_id: ConversationID;
instance_id?: AppInstanceID;
operation_id?: OperationID;
};
correlation?: {
reply_to?: string;
call_id?: string;
inventory_revision?: string;
sequence?: number;
};
payload: JsonValue;
};
```
`scope.app_scope` 是消息所属的本地应用作用域,`target.app_scope` 是指定的消费 App。通常二者相同;Runtime 协调型消息可使用 `runtime` 作为 target,再由 Runtime 生成安全的 App 投影并分发。
### 7.2 消息分类
第一版类型分组:
```text
lineup.content.* 文本、图片、音频、链接、Artifact 引用
lineup.agent.* presence、status、task、progress
lineup.interaction.* choice、confirm、input、form
lineup.tool.* invoke、accepted、progress、result、error、cancel
lineup.app.* state.patch、event、open、close、lifecycle
lineup.capability.* request、result
lineup.runtime.* inventory、connection、app registry、policy、error
```
具体 schema 将在协议文档版本化;SDK 不暴露未经校验的原始 JSON。
### 7.3 Runtime 筛选链
```text
Transport 收到原始消息
→ 协议:版本、必填 app_scope/conversation_id、type、大小、schema
→ 身份:Agent、用户、会话关联
→ 去重/顺序:id、sequence、cursor
→ App:安装、启用、版本、签名、Host 兼容性
→ Tool/CapabilityInventory、方法、参数、策略、前台条件
→ Scopeconversation、instance、operation 所属关系
→ SubscriptionApp Manifest 声明与 SDK 订阅交集
→ PersistRuntime Event / App Inbox / cursor
→ Deliver:投递给目标 App 的 SDK
```
Runtime 不对所有 App 广播原始消息。一个白板 App 即使与 Chat 位于同一 conversation,也只能收到其 Manifest 声明且 Runtime 授权的实例 patch、Tool 调用或事件。
## 8. Runtime SDK v1
### 8.1 双通道模型
```text
InboundRuntime → App
- 已验证 Agent 消息
- App 专属状态投影
- Tool 调用
- Runtime / App 生命周期
OutboundApp → Runtime
- 用户与 App 声明性 Action
- Tool result / progress / error
- Surface Event
- Capability request
- 可恢复 State Snapshot
```
顶层接口:
```ts
interface LineUpAppRuntimeSDK {
readonly app: AppContext;
readonly lifecycle: AppLifecycleAPI;
readonly state: AppStateAPI;
readonly agentMessages: AgentMessageAPI;
readonly actions: AppActionAPI;
readonly tools: AppToolAPI;
readonly apps: AppNavigationAPI;
readonly artifacts: ArtifactAPI;
readonly storage: ScopedStorageAPI;
readonly capabilities: CapabilityRequestAPI;
readonly diagnostics: DiagnosticsAPI;
}
```
App 得到的是按 App Scope、conversation 和实例作用域裁剪后的接口和 View Model,而不是全量 Runtime State 或原始 Transport。
### 8.2 App Context、状态与生命周期
```ts
interface AppContext {
app_scope: AppScope;
app_version: string;
instance_id: AppInstanceID;
kind: "core" | "installed";
entrypoint: string;
scope: {
conversation_id?: ConversationID;
operation_id?: OperationID;
parent_instance_id?: AppInstanceID;
};
granted_permissions: readonly AppPermission[];
}
interface AppStateAPI<ViewModel = unknown> {
snapshot(): ViewModel;
subscribe(listener: (view: ViewModel, change: AppStateChange) => void): Unsubscribe;
}
```
生命周期包含 `start``foreground``background``suspend``restore``closing`。App 可以保存快照并请求延迟关闭,但 Runtime 保留禁用、移除、内存回收和强制收口的最终权力。
### 8.3 Agent 消息订阅与可靠 Inbox
SDK 提供实时订阅,同时提供持久化 Inbox 补读:
```ts
interface AgentMessageAPI {
list(request?: {
conversation_id?: ConversationID;
after?: AgentMessageCursor;
limit?: number;
}): Promise<AgentMessagePage>;
subscribe(
options: {
conversation_id?: ConversationID;
types?: readonly AgentMessageType[];
include_pending?: boolean;
},
handler: (message: AgentAppMessage) => Promise<void> | void,
): Unsubscribe;
acknowledge(message_id: string): Promise<void>;
}
```
投递语义:
1. Runtime 先验证并持久化,再回调 App;
2. 每条消息有唯一 `message_id`App 必须幂等处理;
3. 展示类消息可自动确认;Tool 调用、状态 patch 等业务消息需 App 显式 ACK;
4. App 未运行、暂停或崩溃时,Runtime 写入该 App Inbox;恢复后通过 `list()``subscribe()` 补齐;
5. App 禁用、移除或不兼容时,Runtime 不静默投递或丢弃关键调用,而向 Agent 返回标准拒绝。
App 的订阅条件只是请求;实际投递集为:
```text
Manifest agent_subscriptions
∩ SDK subscribe filter
∩ App 权限
∩ conversation / instance scope
∩ Agent target app_scope
∩ Runtime Policy
```
### 8.4 Action、Tool、App 导航与 Artifact
App 只提交声明性 Action;不得构造原始 Agent Envelope 或直接访问 Transport
```ts
interface AppActionAPI {
dispatch(action: AppAction): Promise<CommandReceipt>;
}
interface AppToolAPI {
onInvoke(listener: (call: AppToolInvocation) => Promise<AppToolOutcome>): Unsubscribe;
progress(request: { call_id: ToolCallID; progress: ToolProgress }): Promise<void>;
complete(request: { call_id: ToolCallID; result: JsonValue }): Promise<void>;
fail(request: { call_id: ToolCallID; code: AppToolErrorCode; message?: string }): Promise<void>;
}
interface AppNavigationAPI {
listAvailable(): readonly AppSummary[];
launch(request: LaunchAppRequest): Promise<AppInstanceHandle>;
focus(instanceID: AppInstanceID): Promise<void>;
background(instanceID: AppInstanceID, reason?: string): Promise<void>;
suspend(instanceID: AppInstanceID, reason?: string): Promise<void>;
close(instanceID: AppInstanceID): Promise<void>;
openDefaultApp(): Promise<void>;
}
```
`AppNavigationAPI` 只是 App 发给 Runtime 的受限请求接口。真正的 Tool Router、App Instance
Manager 和 Focus Manager 位于 Runtime 内部:它们负责检查 App 是否已安装/启用、是否满足
前台条件、是否需要用户确认,以及切换失败时恢复原前台实例。Interact 可以请求启动扩展
App,但不能直接决定其他 App 的生命周期。
Artifact、媒体与文件通过 Runtime Artifact Store 暴露元数据、受限读取、创建、会话引用和用户保存;App 不取得任意路径或跨 App 文件访问权。
### 8.5 存储与 Capability
每个 App 使用 Runtime 管理的作用域隔离存储:
```text
app-data/chat/
app-data/app-registry/
app-data/voice/
app-data/whiteboard/
```
SDK 中的 `storage` 提供 JSON 数据和可恢复 snapshotRuntime 负责配额、升级迁移、禁用和移除时的清理。Capability 必须通过统一请求接口:
```ts
interface CapabilityRequestAPI {
request<T extends JsonValue>(request: {
capability: CapabilityName;
purpose: string;
input: JsonValue;
scope?: AppScope;
}): Promise<CapabilityResult<T>>;
}
```
Runtime 检查 Manifest 声明、App 启用状态、Host 支持、前台要求、用户确认、系统权限、策略、限流与审计;App 只能获得受控结果。
## 9. 信任分层与 Surface Bridge SDK
| 层级 | 例子 | 可用接口 | 明确禁止 |
|---|---|---|---|
| Core App | Chat、App Registry、Settings | 完整 App Runtime SDK(仍受 Scope 和 Capability 约束) | 直接绕过 Transport/Capability Gateway。 |
| Installed App | 白板、语音、游戏 | 受限 Surface Bridge SDK:状态、已声明 Tool、事件、隔离存储、Capability request | Tauri invoke、父 DOM、token、任意网络、其他 App 数据。 |
| Host Provider | Tauri Desktop / Web Reference Host 实现 | Runtime 内部 Host Provider API | 向 Runtime 提供系统能力;不直接暴露给 Agent/Surface。 |
下载型 App 使用 `iframe sandbox="allow-scripts"` 或等价隔离容器;不包含 `allow-same-origin`。它经严格 CSP、来源验证和消息 schema 校验的桥接访问 Surface SDK。第一版不支持市场下载包执行本地 Rust、Node、Shell 或任意浏览器特权代码。
## 10. App Manifest 的 Runtime/SDK 声明
每个 App Manifest 除 Bundle、签名和 Surface 外,还应声明 Runtime SDK 与消息/工具契约。当前阶段只使用本地 `app_scope`,不在 Manifest 中固化供应商身份或全局命名空间:
```json
{
"format": "lineup.app.v1",
"app_scope": "whiteboard",
"version": "1.2.0",
"runtime_sdk": {
"api_version": "1",
"required_features": ["app.lifecycle.v1", "app.tools.v1", "surface.state.v1"],
"optional_features": ["artifact.create.v1"]
},
"entrypoints": [
{"id": "canvas", "kind": "contextual", "default_eligible": false}
],
"agent_subscriptions": [
{"type": "lineup.app.state.patch", "scope": "instance"},
{"type": "lineup.tool.invoke", "scope": "conversation"}
],
"tools": [
{
"method": "board.create",
"contract_version": "1",
"invocation": {"kind": "command", "activation": "launch_if_needed"},
"input_schema": {"type": "object", "additionalProperties": false},
"output_schema": {"type": "object", "additionalProperties": false}
}
]
}
```
Runtime 在安装、启用和启动时进行 SDK 版本及 feature 协商;不兼容 App 不进入可用 Inventory,也不得启动。
## 11. Runtime 状态与恢复
顶层 Runtime State 至少包含:
```text
identity 用户、设备、认证会话
connection AppServer 状态、cursor、重试、outbox
conversations Agent 映射、消息引用、任务、交互、Artifact 元数据
apps App Registry、默认 App、运行实例、App Inbox
tools 已声明 Tool、可见性、调用与 operation
capabilities 授权、执行状态、最小审计
```
第一版采用“事件驱动状态机 + 必要快照”,而非完整 Event Sourcing:关键入站事件、用户决定、Tool 结果、outbox 和审计记录持久化;渲染细节和短暂 UI 状态不必全部写入事件日志。
Runtime 生命周期:
```text
created → restoring → authenticating → synchronizing → online
↘ reconnecting / offline_degraded
online / offline_degraded → stopping → stopped
```
启动时恢复 App Registry、默认入口、会话 cursor、outbox、App Inbox、未完成 Tool 和可恢复实例;追平历史后再开始实时投递。离线时用户/App Action 进入 outbox,重连后按幂等键和顺序处理。
## 12. 推荐工程边界
```text
lineup-app/
├── runtime-core/
│ ├── communication/ Transport、sync、outbox
│ ├── coordination/ Event、state、router、policy、focus、audit
│ ├── apps/ App Manager、Registry、Instance、Focus、Lifecycle Manager
│ ├── tools/ Tool Registry、Inventory、Router、call lifecycle
│ ├── capabilities/ Capability Gateway
│ └── persistence/ Runtime Store、App Inbox、Artifact metadata
├── runtime-sdk/
│ ├── app.ts Core App SDK 类型
│ ├── manifest.ts App/Tool/Subscription Manifest 类型
│ ├── protocol.ts Runtime Envelope 与 schema
│ ├── errors.ts 稳定错误码
│ └── testkit/
├── surface-sdk/
│ ├── bridge.ts sandbox postMessage bridge
│ └── surface.ts Installed App 最小接口
└── tauri/
├── src/
│ ├── bootstrap/ Tauri/Web 的应用启动装配层
│ ├── runtime-host/ Runtime Host Provider 适配
│ ├── core-apps/ chat、app-registry、settings
│ └── shell/ app switcher、default app、recovery
└── src-tauri/ 文件、音频、通知、窗口等 Rust Provider
```
当前 `lineup-app/tauri/src/main.ts` 同时承担 Runtime、Chat、Host Adapter 和开发 Fixture 的职责,是拆分的主要对象。现有 `InteractionKernel`、Transport、Conversation Store、Surface Registry、Instance Manager、Bundle Cache、Capability Registry 与 Inventory Publisher 可作为上述 Runtime Core 的迁移基础。
## 13. 第一阶段落地顺序与验收
### R0:冻结 Runtime/SDK 合约
- 固化 `RuntimeEnvelope`、app scope / conversation ID / instance ID 规则;
- 固化 Event / Command / Effect 边界;
- 定义 `LineUpAppRuntimeSDK``Surface Bridge SDK` 与 Manifest TypeScript 类型;
- 为 Envelope、App Manifest、Tool Descriptor、SDK 错误码准备 golden fixtures。
### R1:建立 Runtime Core 和 Interaction Core App(当前 `chat` 实现)
- 将 Runtime 的创建与装配集中在单一启动入口,不让 `main.ts` 变成通信和业务逻辑的堆放处;
- Runtime 独占 Transport、Store、outbox 和入站筛选;
- 将当前图文 IM 重构为 Interaction Core App 的 `chat` 实现;
- 当前 `chat` 通过 `agentMessages.subscribe()` 获取已验证的 Agent 消息,不再直接依赖 Transport;
- 保持当前登录、同步、本地回显、Markdown、Tool Call、Task 与 Artifact 回归行为。
### R2App Registry Core App
- 将现有开发期 `SurfaceRegistry` 升级为 Runtime App Registry
- 实现 Core App 记录、Installed App 记录、enable/disable/remove 和默认 App 选择;
- 将 Registry UI 实现为 `app-registry`,仅调用 `RuntimeAppManager`
- App 状态变化后正确更新 Runtime Inventory。
### R3Tool Registry 与 App SDK Inbox
- 实现 Manifest Tool / Subscription 解析与可见性筛选;
- 将动态 Inventory 同步给 Agent
- 实现 Agent `tool.invoke` → Runtime → App SDK → `tool.result` 的可靠闭环;
- App 未运行时持久化 Inbox,恢复后可幂等补读。
### R4:第一个 Installed App 闭环
- 接入一个已签名的画板或任务面板 App;
- 验收 install → enable → inventory 更新 → Agent 调用 Tool → App 启动 → Surface 事件 → Agent result → disable/remove
- 后续语音 App 复用同一 SDK、Tool、Capability 与 Artifact 通道,而不是再实现独立通信链路。
硬性验收:
```text
1. 缺少或非法 app_scope / conversation_id 的可路由消息被 Runtime 拒绝;
2. App 不会收到其他 App 或其他 conversation 的 Agent 原始消息;
3. App 被禁用后,其 Tool 从 Inventory 移除且新调用被拒绝;
4. 断线/重启后 App Inbox、outbox、Tool 调用和实例状态能有界恢复;
5. 下载型 App 无法访问 Tauri invoke、Host DOM、token 或未授予 Capability
6. 默认 App 不可用时 Runtime 自动进入 chat Recovery App
7. 所有 Tool 调用、权限拒绝与终态结果均可按 app_scope、conversation_id、call_id 审计和测试。
```
## 14. 与已有正式方案的关系
- 本文将 [App 层架构方案](lineup-app-layer-architecture.md) 中的 Interaction Runtime 从“聊天 Host 内部模块”收敛为整个 App 的 Runtime,并补充了 App Manager、App Tool Registry、SDK 和消息路由模型。
- 本文不废弃 [UI Surface 与 App Capability 协议](lineup-ui-surface-protocol.md) 中已完成的 Surface 隔离、Capability 确认、生产 Bundle 验签和动态 inventory 原则;后续应将其协议字段迁移/扩展为本文定义的 Runtime Envelope、App Manifest 和 Tool Registry,而不能引入绕开 Runtime 的平行通道。
- 现有 M2/M4 文档和实现使用的 `app id` 是当前 Surface 协议的实现字段。本方案在 Runtime 重构阶段以 `app_scope` 作为本地路由字段;供应商身份和全局命名空间将在后续生态阶段通过独立、版本化的兼容设计重新引入。
- 当前 M0M4 实现可作为 Runtime 的迁移基础,但其现有文件边界不是最终 Runtime/App/SDK 边界。
@@ -0,0 +1,226 @@
# 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 使用解析器后仍须 sanitizerHTML Surface 只能放进 sandbox `srcdoc`
7. 生产环境应只接受已签名或在 Agent allowlist 中的 bundle hashReference 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": "<main><button id='refresh'>刷新</button></main>",
"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-03Tauri Reference Host 已完成 M0 和 M1-01M1-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 段落是后续 M2M4 的正式契约,不是当前 Web Chat 已开放的功能。M2 建立应用中心本地启用注册表与隔离 Surface;M3 才生成/更新并通知 `client.inventory`,再实施 Capability policy;M4 负责下载、完整性校验、缓存、回滚与 Host 一致性。当前只以 Tauri Desktop Host 与同代码 Web Reference Host 实现这些边界;不规划独立 Android/iOS/Wails 客户端。