初始化 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,62 @@
# 设计整合说明与优先级
**版本:** 2026-08-07
**范围:**`lineup-app/设计/` 与原 `agent_ops/设计/`
## 1. 当前采用的整合规则
本目录下的“当前有效设计”统一采用以下优先级:
1. `01.当前有效设计/APP架构设计.md`
2. `01.当前有效设计/02.正式方案/*.md`
3. `90.历史设计归档/02.正式方案快照/*.md` 中与当前实现不冲突的补充内容
4. `90.历史设计归档/01.前期分析与设计/*``00.阶段记录/*` 的历史背景
一句话概括就是:
**如有冲突,在结合项目当前实际实现的前提下,优先采用已经迁入 `01.当前有效设计/` 的原 `lineup-app/设计/` 文档。**
## 2. 为什么这样定
对比两边设计目录后,可以看到:
-`lineup-app/设计/` 已经形成“权威入口 + 正式方案”的稳定结构;
- `APP架构设计.md` 明确声明自己是当前权威设计基线,并声明替代关系;
- 文档内容与当前 `lineup-app` 代码、当前 Host 基线、当前 Runtime 方向更一致;
-`agent_ops/设计/` 中的大量文档属于更早阶段的探索、协议草案或设计快照。
因此,本轮不再让两处旧目录平行竞争“谁代表当前设计”,而是让已迁入 `01.当前有效设计/` 的文档负责当前设计,让 `90.历史设计归档/` 负责背景、来源和历史演进。
## 3. 当前设计资料的角色划分
### 3.1 当前有效设计
主要回答“现在应该按什么实现”的问题:
- App、Runtime、Host 三者边界;
- MiniApp、Tool、Surface、Capability 的正式约束;
- 当前客户端基线;
- 当前 Tool 和运行时语义。
### 3.2 历史设计来源
主要回答“之前为什么那样想、后来怎么演进到现在”的问题:
- 直连 WebSocket 方案;
- 早期工具协议三分法;
- 早期移动端 / Web 方向;
- 阶段记录和早期架构草图。
## 4. 本轮之后应如何使用两套来源
### 4.1 看当前实现时
优先阅读本目录下的新整合文档,再回溯 `lineup-app/设计/` 的细节来源。
### 4.2 查历史背景时
回看 `90.历史设计归档/` 中的前期分析与阶段记录,但默认它们不再自动对当前实现生效。
## 5. 后续清理方向
本轮已经完成物理迁移;后续不再恢复旧的两处分散设计目录。
@@ -0,0 +1,130 @@
# 设计冲突与演进顺序
**版本:** 2026-08-07
**用途:** 说明两处设计文档的先后关系、冲突点和整合后的取舍依据
## 1. 设计演进的三个阶段
当前已迁入 `02.架构设计/` 的材料,大致对应三个阶段。
### 第一阶段:早期探索阶段
主要来源:
- `90.历史设计归档/00.阶段记录/`
- `90.历史设计归档/01.前期分析与设计/`
这一阶段的特征是:
- 仍以局域网直连 WebSocket、未来移动端等思路为主;
- 工具协议与消息协议处于早期草案阶段;
- 主要目标是确认产品定位和最小可行方向。
这一阶段保留了很多重要背景,但不再直接作为当前实现依据。
### 第二阶段:正式方案快照阶段
主要来源:
- `90.历史设计归档/02.正式方案快照/`
这一阶段已经开始形成 Runtime、App、SDK、Surface、Capability 的正式方案,但它仍然是一次较早的设计快照,并不完全等于今天的代码基线。
### 第三阶段:当前有效设计阶段
主要来源:
- `01.当前有效设计/README.md`
- `01.当前有效设计/APP架构设计.md`
- `01.当前有效设计/02.正式方案/`
这一阶段与当前 `lineup-app` 实现最贴近,也明确声明了权威入口和替代关系,因此作为今天的主设计基线。
## 2. 主要冲突点与取舍
### 2.1 连接主线
早期设计强调:
- 局域网 App 直连 Agent 插件 WebSocket
当前有效设计强调:
- `Tauri Desktop Host + Web Reference Host`
- `AppServer / Runtime / Adapter` 的运行时模型
整合结论:
- 保留早期直连方案作为历史探索背景;
- 当前实现和后续设计都以 Runtime 中心化的 Host / AppServer / Adapter 主线为准。
### 2.2 客户端形态
早期设计强调:
- Web / 未来移动端
当前有效设计强调:
- Tauri Desktop Host 与 Web Reference Host 的统一代码基线;
- 不再以独立 Android/Kotlin 客户端作为当前主线。
整合结论:
- 早期移动端方向作为历史背景保留;
- 当前产品和实现基线以 `lineup-app` 当前 Host 体系为准。
### 2.3 工具模型
早期设计强调:
- `app / toolset / action` 三分法;
- 工具协议作为较独立的一层来描述。
当前有效设计强调:
- Agent Tool、Inventory、Tool Call、进度、结果、activation、operation
- Tool 是 Runtime 模型的一部分,而不是脱离 Runtime 的独立漂浮协议。
整合结论:
- 早期三分法保留为历史理解工具;
- 当前实现与后续文档统一采用 Runtime 中心化 Tool 模型。
### 2.4 设计重心
早期设计更关注:
- 能否连通;
- 消息长什么样;
- 工具如何被粗粒度定义。
当前有效设计更关注:
- Runtime 的唯一所有权;
- MiniApp、Surface、Capability 的边界;
- 生命周期、恢复、子会话、工作区和 Agent 调用闭环。
整合结论:
- 现在的设计讨论必须站在第三阶段的视角进行;
- 第一、二阶段只用于解释“为什么演进到这里”。
## 3. 当前采用的统一排序
当同一主题在多份文档中出现冲突时,当前采用的优先顺序是:
1. `01.当前有效设计/APP架构设计.md`
2. `01.当前有效设计/02.正式方案/*.md`
3. `02.整合结论/` 中已经写明的整合判断
4. `90.历史设计归档/02.正式方案快照/`
5. `90.历史设计归档/01.前期分析与设计/`
6. `90.历史设计归档/00.阶段记录/`
## 4. 这份文档的作用
这份文档不是简单说明“哪个文件更新”,而是明确:
- 哪些冲突是设计演进带来的;
- 现在为什么优先采用当前有效设计;
- 早期文档应该怎样被使用,而不是继续和当前设计平级竞争。
@@ -0,0 +1,878 @@
# LineUp App 最终设计方案
**版本:** 1.0(当前开发基线)
**状态:** 当前 App 设计的唯一汇总入口
**日期:** 2026-08-06
**来源:** [App 层架构方案](lineup-app-layer-architecture.md)、[Runtime 与 App SDK 架构方案](lineup-runtime-sdk-architecture.md)、[运行时与智能体工具](运行时与智能体工具.md)、[UI Surface 与 App Capability 协议](lineup-ui-surface-protocol.md)
**专题约束:** 远端 Agent 的角色、Agent Tool 与本地 UI Action / Host 生命周期意图的入口边界,以 [运行时与智能体工具](运行时与智能体工具.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 在 SDK 源码中声明方法;构建生成 Manifest 的不可执行 Tool 描述,Runtime 汇总为动态 InventoryAgent 只能调用当前 Inventory 中的方法。 |
| Runtime SDK | SDK 提供状态订阅、Agent 消息订阅、Action、Tool、应用导航、Artifact、当前 App 子会话的通用业务数据、Capability 和生命周期接口。业务数据由 Runtime 持久化和隔离,App 不直接拥有存储。 |
| 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、启动状态与按 App 子会话隔离的通用业务数据。
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
Runtime 对跨边界、需要传输或审计的消息使用统一路由 Envelope;所有这类消息都必须带作用域。Envelope 统一的是消息承载、作用域与校验字段,不能抹平来源边界:远端 Agent Tool、本地 UI Action 与 Host 生命周期意图是三条独立入口,必须保留不同的 sender、来源校验与审计类型。用户 UI 不能因为复用了同一内部业务动作而被视为或包装成 Agent Tool:
```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 sessionData: MiniAppSessionDataAPI;
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 MiniApp session data、Artifact 与 Capability
Runtime 为当前 MiniApp App 子会话提供一份受限的通用 JSON 数据字典。MiniApp 定义其中的业务语义、嵌套
结构、`current` / `history`、是否保存历史以及业务上是否允许修改;Runtime 不需要、也不得根据每个 App 的
字段定义专属接口或业务 schema。Runtime 是隔离、并发裁决、持久化、恢复和技术性清理的唯一所有者。它不是
供 App 跨 scope 查询或读写的数据库,也不等同于 Conversation Store、Tool/operation Store、outbox 或 Artifact Store。
```ts
interface MiniAppSessionDataAPI<Data extends JsonObject = JsonObject> {
get(): Promise<{ data: Data | null; revision: number }>;
replace(request: { data: Data; expected_revision: number }): Promise<{ revision: number }>;
subscribe(listener: (snapshot: { data: Data | null; revision: number }) => void): Unsubscribe;
}
```
`sessionData` 的同步语义是通用 SDK 契约:空数据固定为 `{ data: null, revision: 0 }`;订阅在 Runtime 的当前
`app_scope + app_session_id` 串行顺序中注册,首次回调一定是注册时的完整快照,随后回调的 revision 严格递增。因而
`get()``subscribe()` 之间的成功写入不会丢失:它要么已成为订阅首帧,要么作为紧随其后的更新到达。重复 / 旧
revision 由 SDK 忽略;revision 跳跃或 Bridge 重连时,SDK 重新 `get()` 并建立新订阅。`expected_revision` 冲突只
拒绝旧写入,不自动合并 MiniApp 业务数据;多入口协作写入是后续专项 Runtime 设计,不能由本通用 JSON 接口猜测。
每次调用的存储边界由不可伪造的 SDK context 推导:
```text
app_scope + app_session_id
```
App 不得指定、读取或写入其他 App、其他 App 子会话、Conversation Store 或 Runtime 内部记录。Runtime 只校验
JSON 合法性、通用大小 / 解析深度配额与 `expected_revision`;旧 Surface、重复点击或并发写入不能覆盖较新的数据。
刷新、Surface 重载和 Runtime 重启后,Runtime 向同一个 App 子会话恢复最后一个有效数据版本。App 禁用或移除时,
Runtime 按用户确认和全局保留策略清理其数据;业务数据是否“历史只读”由 MiniApp 自己定义,不作为 Runtime 的
统一业务语义。
MiniApp session data 不替代 Runtime operation。凡是会影响 Agent Tool 终态、deadline、outbox、焦点、权限或
实例生命周期的动作,仍必须通过 Runtime Command / operation 原子裁决。例如 Pomodoro 可以将显示状态保存在
自己的 session data,但 `ends_at` 到期、用户中断与唯一 Tool result 必须由 Runtime 的 deadline operation 负责,
不能由 MiniApp 自行完成或直接发送结果。完成后写入 IM 的记录是独立、不可变的静态副本,不会随 MiniApp 后续
修改 session data 而改变。
```text
Artifact
Runtime 管理元数据、受限读取、创建、会话引用、下载和用户保存。
Session Data
Runtime 为每个 app_scope + app_session_id 提供隔离的通用 JSON 数据;管理 revision、配额、恢复与
生命周期清理,不理解 MiniApp 的业务 schema。UI 动画、滚动位置等短暂渲染状态不必持久化。
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
```
由 SDK Tool 声明生成的 Manifest Tool 描述至少包括:标题、面向 Agent 的说明、输入/输出 JSON Schema、调用模型、超时、幂等性、前台要求、风险等级、App 启动策略和可能使用的 Capability;它是安装和运行时使用的产物,不是开发者再维护的一份 Tool 源文件。
调用模型固定为:
| 模型 | 用途 | 示例 |
|---|---|---|
| `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 承接请求
→ Runtime / App SDK 发送 protocol receipt、progress / result / error
→ Runtime 校验输出、持久化、更新焦点并回传 Agent
```
长期 Tool 的协议状态与业务结果必须分开:`accepted` 表示 Runtime 已接收请求,`progress` 表示业务正在推进或刚刚
真正开始;它们是按 `call_id` 持久化、可重放的协议事实,不受 Tool output schema 约束。只有最终 `result`(或
受控 `error`)才必须通过 Tool 的 output schema,且同一 call_id 只能有一个最终业务结果。对于 Pomodoro,只有
Host 已确认前台且 Runtime 创建 operation 后的 `started` progress,才允许 Agent 向用户说“已经开始计时”。
MiniApp 启动也使用同一条 protocol receipt / progress 通道,而不让 MiniApp 直接联系 AgentRuntime 持久化
`starting → ready | failed | cancelled` 后,向触发启动的 call_id 发送 `accepted / starting``activation_ready`
`activation_failed``activation_cancelled`。Agent 收到 ready 后再发送依赖调用是推荐方式,但 Runtime 必须持久化并
等待提前到达的合法 `app_ready` / `foreground_required` 调用,ready 后按到达顺序执行;失败 / 取消时受控拒绝等待调用。
`activation_not_required` 调用不等待,Pomodoro 的 `interrupt` 因此可以取消启动中的专注。Pomodoro 的 `started` 已同时
表示 activation ready、已在前台且计时真实开始,不额外发送重复的 `activation_ready`
至少支持以下确定错误码:
```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
```
Agent Tool 是 App 在 SDK 源码中声明、构建生成 Manifest Tool 描述后由 Runtime 发布给远端 Agent 的业务调用契约;它不是用户 UI 的公共操作入口。Capability 是 Runtime / Host 的系统能力。Agent Tool 可在 Runtime 裁决后请求 Capability,但不会因声明 Tool 自动获得麦克风、文件或剪贴板权限;本地 UI Action 也必须走自己的 SDK / Bridge 校验路径。
## 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 调用、通过 Bridge 使用 Runtime 提供的当前 App 子会话 session data 和请求受控 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 最小契约
Manifest 的 tools 区块由 MiniApp SDK 的 `defineAgentTools(...)` 在构建时生成。开发者在代码中声明 Tool、schema、
activation requirement 和本地 handler,不维护第二份手写描述;生成的 Manifest 只保存不可执行的公开契约,Bundle
内部 SDK 自己保存 `method → handler` 映射。
```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",
"app.session-data.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",
"handling": "direct",
"delivery": "miniapp_sdk",
"activation_requirement": "app_ready",
"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、instance state snapshot、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 的通用可靠投递机制。
- 实现 `app.session-data.v1`:按 `app_scope + app_session_id` 隔离的通用 JSON 数据、revision 并发控制、
通用配额、重启恢复与 lifecycle 清理;Runtime 不校验 MiniApp 业务 schema,且它不得替代 Tool/operation/outbox
的终态裁决。
### 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 校验、持久化和审计。
[ ] MiniApp 只能读取和替换自身 App 子会话的合法 JSON session data;旧 revision、跨 App / 子会话、超配额
和失效 SDK context 均被 Runtime 拒绝,刷新/重启后仅恢复最后一个有效版本。
[ ] MiniApp session data 不能直接完成 Tool、写 outbox、改变焦点或绕过实例生命周期。
[ ] 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 │
│ instance state · scope 路由 · Tool · Capability │
└─────────────────────┬──────────────────────────────────┘
│ Runtime SDK
┌────────────────────────────────────────────────────────┐
│ Core / Installed App │
│ chat · app-registry · voice · whiteboard │
└────────────────────────────────────────────────────────┘
```
| 层 | 必须负责 | 不得负责 |
|---|---|---|
| `LineUpRuntime` | 处理 AppServer/Agent 通信、会话、原始协议兼容、scope 筛选、Store、sync、outbox、App Inbox、Tool 路由、App 实例和前台焦点、权限、按 App 子会话隔离的 MiniApp 通用 session data 与恢复。 | 具体业务页面 DOM 或任意 MiniApp 的业务语义。 |
| Interaction Core App | 用户实际使用的主交互界面:当前是 Chat/IM,未来包含 Audio/Video 模式、标准交互原语和扩展结果投影。 | 直连 AppServer、维护 cursor、解析 Agent 原始包、调度其他 App 或直接写 Runtime Store。 |
| Runtime SDK | App 与 Runtime 之间唯一的受限接口:提供 snapshot、订阅、Agent 消息、Inbox ACK、Runtime Action、App 导航、生命周期请求及当前 App 子会话的通用、revision 控制 session data。 | 把 Transport、token、Host 特权 API、Conversation Store 或其他 App/子会话数据暴露给 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 源码导航](../../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、instance state snapshot
├── 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,785 @@
# LineUp Runtime 与 App SDK 架构方案
**版本:** 0.1(架构基线)
**状态:** 已确认的 Runtime / SDK 方向;尚未完全落地到当前 Tauri 实现
**日期:** 2026-08-06
**相关专题:** [运行时与智能体工具](运行时与智能体工具.md)
**关联方案:** [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 内部函数。 |
| **本地 UI Action** | 用户在 MiniApp 界面触发、经 SDK / Bridge 发给 Runtime 的本地请求;可以复用内部业务动作,但不进入 Agent Inventory,也不是 Agent Tool。 |
| **Host 生命周期意图** | Host、工作区或系统发出的返回、切换、关闭、恢复等本地事件;Runtime 按生命周期和业务规则裁决,不能伪造成 Agent Tool。 |
| **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 能依据持久化状态恢复到可解释的协作状态。
9. **MiniApp 业务数据由 Runtime 通用托管。** App 只能通过 SDK 保存当前 App 子会话的 JSON 数据字典;Runtime
不理解其业务 schema,但保证隔离、revision、持久化与恢复。App 不能直接访问浏览器/Host 存储、Conversation
Store、Tool/operation Store 或其他 App 数据。
## 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
本章中的 Tool 专指远端 Agent Tool:它是 Runtime 对 Agent 发布的调用说明书,不是用户 UI 的公共操作入口。Agent Tool、本地 UI Action 与 Host 生命周期意图的完整入口边界,以 [运行时与智能体工具](运行时与智能体工具.md) 为准。
### 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 承接调用
→ Runtime / App 经 SDK 返回 protocol receipt、progress / result / error
→ Runtime 验证输出 schema、持久化、更新焦点并回传 Agent
```
若调用触发 App activationRuntime 在写入 `starting → ready | failed | cancelled` 后,通过同一 call_id 的受控
protocol receipt / progress 通知 Agent。`activation_ready` 表示 App 已能接收后续 `app_ready` ToolAgent 可据此顺序
编排调用,但 Runtime 仍须持久化并等待提前到达的合法调用,不能把时序正确性外包给 Agent。MiniApp、Surface 和 Host
不直接向 Agent 发送 ready`activation_not_required` Tool 不等待 activation。具体公开字段、等待队列和 Pomodoro
`started` 覆盖 `activation_ready` 的规则,以 [运行时与智能体工具](运行时与智能体工具.md) 为准。
标准终态错误码至少包括:
```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
Runtime 对跨边界、需要传输或审计的消息使用统一基础 Envelope。该 Envelope 统一的是消息承载、作用域与校验字段,不把来源不同的入口合并成同一种调用:远端 Agent Tool、本地 UI Action 和 Host 生命周期意图必须保留不同的 sender、来源校验和审计类型。UI 点击不能仅因复用相同内部业务服务就被包装成 Agent Tool Call
```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 sessionData: MiniAppSessionDataAPI;
readonly capabilities: CapabilityRequestAPI;
readonly diagnostics: DiagnosticsAPI;
}
```
App 得到的是按 App Scope、conversation 和实例作用域裁剪后的接口和 View Model,而不是全量 Runtime State 或原始 Transport。
本地 UI Action 只经 SDK / Bridge 进入 Runtime;它不调用 Agent Tool、不会进入 Agent Inventory,也不带 Agent 身份。若业务需要,Runtime 可以在完成本地校验后让 UI Action 与 Agent Tool 调用同一个内部业务服务。
### 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 MiniApp session data 与 Capability
每个 MiniApp App 子会话使用 Runtime 管理的、通用的业务数据字典:
```text
app_scope + app_session_id
→ 一份 MiniApp 自己定义的 JSON 数据字典
→ revisioned replace / subscribe
→ 重启后向同一 App 子会话恢复
```
这不是通用文件系统、键值数据库或跨子会话查询接口。MiniApp 负责定义数据的业务含义、嵌套结构、`current` /
`history`、是否追加历史以及业务上是否可修改;Runtime 不声明或校验 MiniApp 业务 schema。Runtime 只负责从不可
伪造的 SDK context 推导当前 `app_scope + app_session_id`,以及 JSON 合法性、通用配额、安全解析深度、revision
比较、原子写入与重启恢复。关闭、禁用或移除对记录的技术性清理由 Runtime 的全局保留策略负责,但 Runtime 不把
“关闭即业务数据只读”强加给每一个 MiniApp。
```ts
interface MiniAppSessionDataAPI<Data extends JsonObject = JsonObject> {
get(): Promise<{ data: Data | null; revision: number }>;
replace(request: { data: Data; expected_revision: number }): Promise<{ revision: number }>;
subscribe(listener: (snapshot: { data: Data | null; revision: number }) => void): Unsubscribe;
}
```
每个 `app_scope + app_session_id` 的 subscribe / replace 都由 Runtime 在同一串行顺序处理。没有已保存数据时,
`get()` 和订阅首帧均为 `{ data: null, revision: 0 }`;订阅注册时 Runtime 捕获并先投递完整当前快照,之后才投递
revision 严格递增的完整变化。于是读与订阅之间发生的 replace 不会漏掉:它要么在首帧中,要么紧随首帧出现。SDK
忽略重复或旧 revision;发现跳跃或 Bridge 重连时取消旧订阅、重新 `get()` 并建立新订阅。revision CAS 只拒绝陈旧
覆盖,不承担业务合并;多入口协作写入由后续独立的 mutation queue 方案解决。
`expected_revision` 不匹配时 Runtime 返回 `session_data_revision_conflict`;数据不是 JSON、SDK context 已失效或
超出通用配额时分别返回 `session_data_invalid``session_data_unavailable``session_data_quota_exceeded`。App
不得用 session data 直接完成 Agent Tool、写 outbox、改变焦点或执行 Capability。deadline、Tool 终态、outbox、
焦点或生命周期仍走 Runtime 的 Command / operation 路径;Runtime 可以在同一原子事务中更新 operation 与
session data,但二者保持不同的事实来源。
MiniApp 的短暂渲染状态(动画、滚动位置、临时 loading)不要求保存;Conversation / App 子会话保存人与
Agent 可见的静态交互和结果,也不是 MiniApp 业务数据的事实来源。IM 中一旦写入的完成记录是已发生结果的
不可变静态副本,不会随 MiniApp 之后修改 session data 而变化。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、事件、当前 App 子会话的 session data、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 中固化供应商身份或全局命名空间:
Manifest 的 Tool 描述由 MiniApp SDK 的 `defineAgentTools(...)` 在构建期从源码生成。开发者只维护 SDK 中的 Tool
声明和本地 handler;构建产物把不含函数、URL、回调或私有函数名的 schema / policy 描述写入 Manifest,供 Runtime
安装、Registry 与 Agent Inventory 使用。Runtime 向 ready App session 的统一 SDK Tool 接收入口投递 method +
paramsSDK 再在 Bundle 内部按 method 分发 handler。
```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", "app.session-data.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",
"handling": "direct",
"delivery": "miniapp_sdk",
"activation_requirement": "app_ready",
"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
app_state 按 app_scope + app_session_id 隔离的 MiniApp session data、revision 与 activation
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、instance state snapshot、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,恢复后可幂等补读。
- 实现 `app.session-data.v1`:按 `app_scope + app_session_id` 隔离的通用 JSON 数据、revisioned replace /
subscribe、通用配额、刷新/重启恢复与关闭/禁用/移除清理;Runtime 不校验 MiniApp 业务 schema,且 Tool/
operation/outbox 不得由该 API 直接改写。
### 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 审计和测试。
8. MiniApp 只能读写自身当前 App 子会话的 session data;非 JSON、旧 revision、跨 App / 子会话、超配额或
失效 SDK context 均被稳定拒绝,重启只恢复最后一个有效数据版本。
```
## 14. 与已有正式方案的关系
[运行时与智能体工具](运行时与智能体工具.md) 进一步冻结了 Agent 的角色、Agent Tool 的发布与调用模型,以及它和本地 UI Action、Host 生命周期意图的三条入口边界;本文的 Tool 与 Inventory 术语均按该专题理解。
- 本文将 [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 客户端。
@@ -0,0 +1,310 @@
# 运行时与智能体工具
**版本:** 0.1(当前 Runtime 契约)
**状态:** 已确认
**日期:** 2026-08-06
**关联方案:** [LineUp App 最终设计方案](app_final_design.md)、[LineUp Runtime 与 App SDK 架构方案](lineup-runtime-sdk-architecture.md)、[第 04 次迭代:Runtime 工作区与 Pomodoro MiniApp](../../迭代/04.runtime_workspace/04.runtime_workspace.md)
---
## 0. 目的与权威范围
本文定义 Runtime 面向远端 Agent 的工具模型,以及它与本地 MiniApp 界面操作、Host 生命周期事件之间的边界。它是全局 Runtime 设计,Pomodoro 只是当前的具体例子,并不限制本文的适用范围。
本文确认的关键结论是:
> **Agent Tool 是 Runtime 发布给远端 Agent 的功能调用说明书。它不是用户点击界面的公共入口,也不是 MiniApp 内部函数的别名。**
用户通常通过 IM 表达自然语言意图,未来也可以通过 Voice 表达;远端 Agent 负责理解意图、选择可见 Tool 并向 Runtime 发起调用。Runtime 负责校验、路由、执行、持久化和回传结果。用户未来可以在 MiniApp 页面点击按钮,但那属于另一条本地 UI Action 入口,不能伪装成 Agent Tool 调用。
本文细化 [LineUp App 最终设计方案](app_final_design.md) 中 Runtime、Tool、状态和边界的结论。后续若修改本文已确认的语义,必须先更新正式方案,再调整迭代定义和技术规范。
## 1. 术语与对象
| 术语 | 定义 | 不是 |
|---|---|---|
| **远端 Agent** | 运行在 LineUp 设备外、通过 AppServer / IM Transport 与 Runtime 通信的智能体。它理解用户自然语言,并在可见能力范围内选择 Tool。 | 不是 Runtime 内的计时器、存储或 UI 控制器。 |
| **Agent Tool** | MiniApp 在 Manifest 声明、Runtime 校验并发布给远端 Agent 的远程调用契约。它包含名称、说明、输入输出 schema、调用模型、可见性和策略。 | 不是用户按钮,也不是 App 任意 JavaScript 函数。 |
| **Agent Inventory** | Runtime 根据 App、Host、权限、策略、会话和 Agent 范围计算出的 Agent Tool 清单及其 revision。 | 不是安装包内全部函数的列表。 |
| **Tool Call** | Agent 对 Inventory 内某个 Agent Tool 发起的一次远端调用,带有 call_id、参数和 inventory revision。它可先收到协议回执和进度,最后才收到业务结果。 | 不等于业务 operation,也不等于 App instance。 |
| **Tool 协议回执 / 进度** | Runtime 对一条已接受调用的可重放协议事实,例如 `accepted``starting``activation_ready``started`;用于告诉 Agent 调用是否已接收、MiniApp 是否已就绪、业务是否真正开始。 | 不是 Tool 的业务 result,不受业务 output schema 约束,也不等于一次完成记录。 |
| **Tool 业务结果** | Tool 的最终业务结论;必须符合该 Tool Manifest 的 output schema,并且对同一 call_id 只持久化、回传一次。 | 不是“请求已收到”或“正在启动”的临时状态。 |
| **Runtime 内部业务动作** | Runtime 为实现业务请求执行的受控命令,例如创建 deadline operation、改变焦点、写 outbox、关闭实例。多个入口可以复用同一个动作。 | 不是自动公开给 Agent 的 Tool。 |
| **UI Action** | 用户在本地 MiniApp 页面点击、输入或拖动后,经 SDK / Bridge 发送给 Runtime 的本地请求。 | 不发布给 Agent,也不带 Agent 身份。 |
| **Host 生命周期意图** | Host 或工作区产生的前后台切换、返回、关闭、恢复、锁屏等本地事实或请求。 | 不是 Agent Tool,也不应伪造成用户的 IM 指令。 |
| **MiniApp instance** | 一个 MiniApp 的运行实例;Runtime 管理其生命周期、作用域和启动状态。 | 不必然对应一次业务 operation。 |
| **App 子会话** | Interact 中围绕一个 MiniApp instance 保存的、面向人与 Agent 的交互记录。 | 不等于一次专注、计时或其他业务 operation。 |
| **MiniApp activation** | Runtime 管理的一次 MiniApp 启动过程:`starting → ready | failed | cancelled`。每个 Agent Tool 声明业务执行前要求的就绪条件。 | 不是 MiniApp 的业务数据,也不是 operation。 |
| **MiniApp session data** | Runtime 为当前 `app_scope + app_session_id` 持久化的通用 JSON 数据字典;内容和可变性由 MiniApp 自己定义。 | 不是 Runtime 可理解的 App 业务 schema,也不是 IM 记录或 operation Store。 |
| **operation** | Runtime 管理的实际业务执行单元,例如一次有 deadline 的专注。它有独立的状态机和终态。 | 不等于 Tool Call、App 子会话或界面实例。 |
## 2. Agent 的角色、能力范围与边界
### 2.1 Agent 的职责
远端 Agent 是意图理解者和工具调用者,不是本地 Runtime 的替身。它的职责是:
1. 从用户 IM、Voice 或其他交互内容理解意图;
2. 根据 Runtime 已发布的 Agent Inventory 选择合适的 Tool
3. 按 Tool schema 构造参数,并携带当前 inventory revision 发起调用;
4. 接收 Runtime 回传的受控结果,在用户会话中解释、追问或发起下一步调用。
当用户说“现在开始 20 分钟的冥想,帮我计时”时,正确链路如下:
~~~text
用户的 IM 消息
→ 远端 Agent 理解为启动专注
→ Agent 调用 pomodoro.start
→ Runtime 请求 Pomodoro 进入前台
→ Host 确认前台激活后,Runtime 才创建并管理专注 operation
→ Pomodoro MiniApp 用自身 session data 显示界面
~~~
Agent 不需要、也不能自己一秒一秒倒数。时间事实由 Runtime 持久化的 deadline 和本地时间裁决;MiniApp 仅根据 Runtime 投影显示剩余时间。
### 2.2 Agent 可做的事
- 调用当前 Agent Inventory 中、对当前 Agent 和 conversation 可见的 Agent Tool
- 接收 Runtime 回传的 Tool 结果、进度和受控交互结果;
- 在会话范围内发送内容、交互或后续 Tool 请求;
- 在 Runtime 允许的范围内请求启动 Tool 所需的 MiniApp instance。
### 2.3 Agent 明确不能做的事
- 不能调用未在当前 Inventory 中声明的 Tool,或绕过 inventory revision、参数 schema、会话和策略校验;
- 不能直接读写 Runtime Store、MiniApp instance state、Conversation Store、operation Store、outbox 或审计记录;
- 不能直接调用 Tauri、文件、通知、麦克风、窗口或其他 Host 特权能力;
- 不能直接执行 MiniApp 的任意函数、修改其 DOM 或访问 Surface Bridge
- 不能指定、伪造或跨会话操控任意 instance_id、operation_id、app_scope
- 不能通过 Tool Call 绕过 App 安装、启用、权限确认、风险策略或 Host 能力限制。
Runtime 对 Agent 输入实行不信任原则:即使调用来自已认证 Agent,也必须重新验证可见性、作用域、幂等、生命周期和输出。
## 3. 三条独立入口
Runtime 可以接收不同来源的请求,但来源决定身份、校验、审计和可用能力。三者不是同一入口。
| 入口 | 发起者 | Runtime 接收的内容 | 是否进入 Agent Inventory | 主要校验 | 典型示例 |
|---|---|---|---|---|---|
| **Agent Tool** | 远端 Agent | 有 Agent 身份、call_id 和 inventory revision 的远端 Tool 调用 | 是 | Agent、Inventory、Tool schema、conversation、策略、幂等 | Agent 调用 pomodoro.start。 |
| **MiniApp UI Action** | 用户在本地 MiniApp 页面操作 | SDK / Bridge 发送的本地 Action | 否 | 当前 instance、App 权限、UI schema、业务前置状态、用户确认 | 用户未来点击开始计时。 |
| **Host 生命周期意图** | Runtime Shell、Host 或系统 | 前后台、返回、关闭、恢复、系统状态等本地事件 | 否 | instance 生命周期、焦点栈、系统策略、operation 收口规则 | 用户离开 Pomodoro 工作区。 |
三条入口的流程如下:
~~~text
一、远端 Agent Tool
用户 IM / Voice
→ 远端 Agent 理解意图
→ Agent Tool Call
→ Runtime 校验、路由和持久化
→ Runtime 内部业务动作
→ MiniApp 状态投影 / Agent 结果 outbox
二、本地 UI Action(未来可选)
用户点击 MiniApp 页面
→ SDK / Surface Bridge 的本地 Action
→ Runtime 校验和持久化
→ Runtime 内部业务动作
→ MiniApp 状态投影;必要时由 Runtime 产生 Agent 可见结果
三、Host 生命周期意图
返回 / 切换工作区 / 关闭 / 恢复 / 系统事件
→ Host Lifecycle Intent
→ Runtime 生命周期与焦点裁决
→ Runtime 内部业务动作 / operation 收口
→ MiniApp 状态投影 / 必要的 Agent 结果 outbox
~~~
后两条入口可以在 Runtime 内部复用与 Agent Tool 相同的业务服务。例如未来 UI 的开始按钮与 Agent Tool 的开始调用都可以请求同一个“创建专注 operation”动作。但复用内部动作不改变入口身份:UI Action 不会因此成为 Agent Tool,Host 生命周期也不会因此伪造成 Tool Call。
## 4. Agent Tool 的发布与调用
### 4.1 MiniApp 声明,Runtime 发布,Agent 调用
Agent Tool 的权威链条必须保持为:
~~~text
MiniApp 源码中的 SDK Tool 声明
→ 构建生成 Manifest 的不可执行 Tool 描述
→ Runtime 验证 Manifest、App 与 Host 条件
→ Runtime 计算并发布动态 Agent Inventory
→ 远端 Agent 看到 Inventory 后选择 Tool
→ Agent 发起 Tool Call
→ Runtime 验证、路由、执行并回传
~~~
MiniApp 只声明自己支持的候选能力,不能自行向 Agent 宣传、发送或执行 Tool Call。Runtime 是唯一的 Inventory 发布者、远端调用接收者和结果回传者。
Tool 声明的开发者入口是 MiniApp SDK,而不是第二份手写 ManifestMiniApp 用 `defineAgentTools(...)` 同时声明
公开 method、schema、activation requirement 和 `miniapp_sdk` Tool 的本地 handler。SDK 源码声明是唯一真相;构建阶段
自动生成随 Bundle 安装的 Manifest Tool 描述。该描述供 Runtime 验证、Registry 和 Inventory 使用,但不包含 handler、
私有函数名或其他可执行内容。Bundle 内 SDK 保留 `method → handler` 映射,收到 Runtime 的统一 Tool 调用后在 MiniApp
内部分发。安装包仍可有 App 身份、版本、签名与 Host 能力等基础元数据,但开发者不再维护一份独立的 Tool 清单来重复
描述源码已经定义的能力。`runtime` Tool 只能引用 Runtime 内置的受控执行类型。这样既有类似 MCP Tool 描述文件的稳定
能力契约,也不让 Runtime 知道 MiniApp 的内部实现。
一个 Agent Tool Descriptor 至少应定义:
- 稳定的 app_scope、method、contract_version
- 面向 Agent 的业务说明、输入 schema 和输出 schema
- 调用模型,例如 query、command、interactive 或 operation
- 幂等键、超时或终态规则;
- 启动就绪条件:`app_ready`(目标 MiniApp session 已 active、可接收方法调用)、`foreground_required`
(在 app_ready 的基础上还必须已在前台),或 `activation_not_required`(只操作已有 Runtime 事实,不能因此
启动或唤醒 MiniApp);
- 所需权限、风险级别和对前台 / Host 的要求。
Runtime 实际发布给特定 Agent 的清单是动态交集:
~~~text
已验证的 Manifest Tool
∩ App 已安装并启用
∩ Host 当前支持
∩ 用户 / 组织策略允许
∩ 当前 Agent 与 conversation 可见
∩ 所需前置权限满足
~~~
### 4.2 Runtime 收到 Tool Call 后的职责
Runtime 收到远端 Agent Tool Call 后,按以下顺序处理:
1. 验证远端 Envelope、Agent 身份、conversation 关联和 Inventory revision
2. 验证目标 Agent Tool 当前可见,且参数符合已发布 schema;
3. 用 call_id 执行幂等去重,持久化调用记录和审计;
4. 解析 Tool 所允许的作用域,不接受 Agent 任意指定跨会话目标;
5. 创建或复用 MiniApp activation,并在 Tool 所需的 `app_ready``foreground_required` 条件满足后,选择内部
执行路径:直接处理、将 method 与已验证参数投递 App SDK、创建 operation 或拒绝;Runtime 不解释参数的
MiniApp 业务含义,也不直接写 MiniApp 的 session data
6. 原子持久化业务状态、operation 终态与需要回传的 outbox
7. 仅向相关 MiniApp 投递经过裁剪的状态或调用投影;
8. 将协议回执 / 进度与最终业务结果分别持久化并经 outbox 回传:前者不使用业务 output schema;后者必须通过该 schema 校验。
Tool Call 的成功接收不等于业务已经完成。对于长期 operation,Runtime 可以先确认接受调用,业务真正开始时再报告
进度,之后只在进入唯一终态时回传最终结果。相同 `call_id` 重放时,Runtime 返回已持久化的**最新协议回执或最终
业务结果**,不重新执行业务,也不追加新的 outbox 消息。
### 4.3 activation 就绪通知与提前到达的后续调用
当 Agent 的一个 Tool 调用使 MiniApp 进入 `starting`Runtime 必须把 activation 的受控状态作为与该 `call_id`
关联的协议进度通知给发起 Agent:
```text
accepted / starting
= Runtime 已接受调用,MiniApp 正在准备
activation_ready
= MiniApp 已 active / ready;后续要求 app_ready 的 Tool 现在可以接收
activation_failed / activation_cancelled
= 本次启动不能继续;依赖它的等待中调用将得到受控失败结果
```
这些通知是 Runtime 持久化 activation 后,经 Runtime outbox 发出的事实;MiniApp、Surface 和 Host 只能向 Runtime
报告自己的就绪或失败,不能直接向 Agent 发送通知。公开通知只携带 `call_id``app_scope`、状态和受控 reason,不能
`instance_id``app_session_id` 变成 Agent 可指定或跨会话操控的目标。
Agent 收到 `activation_ready` 后再发送有依赖关系的后续 Tool,是推荐的编排方式;但它不是 Runtime 接收调用的硬前提。
若一条合法、同 conversation / app scope 的 `app_ready``foreground_required` Tool 在目标 activation 仍为
`starting` 时提前到达,Runtime 必须持久化该调用并把它挂到同一 activation 的等待队列,在 ready 后按已持久化的到达
顺序投递。activation 失败或取消时,Runtime 不执行这些 handler,而为各调用回传稳定的 `app_activation_failed`
`app_activation_cancelled``activation_not_required` Tool 不进入等待队列,仍立即处理;因此 Pomodoro 在启动中收到
`pomodoro.interrupt` 时可以立即取消启动。
某些 Tool 的业务进度比通用 activation 状态更强时,不应重复发两条没有新增信息的通知。第 04 次的
`pomodoro.start` 在 Host 确认前台的同一提交中同时得到 `activation = ready`、创建 operation 并开始计时,因此只向
Agent 发 `started`;它语义上已包含“Pomodoro ready”,且额外保证“前台已确认、计时已真实开始”。
## 5. 状态、身份与生命周期不得混同
本节是 Runtime 状态模型的一部分。下列对象可以互相关联,却不能共享语义或被一个 ID 替代。
~~~text
Conversation(人与主 Agent 的协作范围)
├── App 子会话(MiniApp 启动后可在 Interact 中保存的交互记录)
├── MiniApp instance(实际运行的界面 / SDK 上下文)
│ ├── activationstarting / ready / failed / cancelled
│ └── session data(该 App 子会话的通用、MiniApp 自解释 JSON 数据)
├── Agent Tool Call(远端调用与其幂等、审计和回执)
└── operation(实际业务执行;可无,也可独立于界面按规则收口)
~~~
| 对象 | 最小职责 | 应有的状态 / 身份 | 不能承担的事实 |
|---|---|---|---|
| Conversation | 用户与主 Agent 的协作边界 | conversation_id | 不能表示某次业务执行。 |
| App 子会话 | 保存围绕 MiniApp 的可见交互与历史 | app_session_id 或等价记录 | 不能替代 MiniApp instance 或 operation。 |
| MiniApp instance | 管理运行、焦点、挂起、关闭和 SDK scope | instance_id、生命周期状态 | 不自动代表一次业务 operation。 |
| MiniApp activation | 将 App 准备为 active / ready 的过程;某些 Tool 还要求成为前台 | instance_id、app_session_id、starting / ready / failed / cancelled | 不是业务 operation,不能自行产生 deadline 或终态。 |
| MiniApp session data | MiniApp 自己的业务 / 展示数据 | app_scope 加 app_session_id 加 revision | Runtime 不理解内容;不能裁决 deadline、Tool 终态或 outbox。 |
| Agent Tool Call | 一次远端请求、幂等与审计 | call_id、Tool key、inventory revision | 不能替代长期 operation。 |
| operation | 实际业务过程、deadline、进度和唯一终态 | operation_id、业务状态机 | 不等于一次 MiniApp 启动或 App 子会话。 |
因此,一个 MiniApp 会话或 instance 启动时未必启动业务 operation;一个 operation 也不应靠 UI 是否存在来判断是否完成。Tool 可以声明其业务开始是否需要前台:例如 `pomodoro.start` 只有在用户确实进入前台专注界面后才创建 operation;未来 `todo.add` 则可以在后台数据上下文就绪后完成。具体 Tool 的就绪条件不能上升为 Runtime 的统一前台要求。
Runtime 可以在一个原子事务中同时更新 operation、activation、session data 和 outbox,但它们仍保留各自的事实来源和恢复规则。
## 6. 各层能力边界
| 主体 | 负责什么 | 可以请求什么 | 明确禁止 |
|---|---|---|---|
| **用户** | 通过 IM / Voice 表达意图;未来可直接操作 MiniApp UI。 | Agent 对话;本地 UI Action。 | 不直接调用 Agent Tool 协议或 Runtime 内部 Store。 |
| **远端 Agent** | 意图理解、Tool 选择、调用参数和会话回复。 | 当前 Inventory 中的 Agent Tool。 | 直接访问 Host、Store、MiniApp DOM 或任意 App 函数。 |
| **Runtime** | Agent 通信、Tool Registry、校验、路由、状态、operation、生命周期、持久化、outbox、审计。 | 通过 Host Provider 使用受控系统能力;向 App 投递受限 SDK 投影。 | 将 Transport、密钥或跨 App 数据直接暴露给 Agent / MiniApp。 |
| **MiniApp** | 自己的界面、已声明业务处理和当前 App 子会话的 session data。 | SDK 提供的投影、声明性 Action、Capability request。 | 自行连接 Agent / AppServer、直接写 Runtime Store、直接使用 Tauri / Host。 |
| **Surface / UI** | 渲染和收集本地用户事件。 | 受限 Surface Bridge。 | 假冒 Agent、构造 Agent Tool Call、直接操作 Host DOM 或特权 API。 |
| **Host** | 提供窗口、通知、文件、音频等运行环境和系统事实。 | 向 Runtime 报告生命周期意图。 | 直接改变 MiniApp 业务状态、直接向 Agent 发布工具或交付特权能力。 |
特别地,Host 只报告本地事实或意图,最终如何改变业务状态由 Runtime 的业务规则裁决。例如“用户离开专注工作区”可触发 Runtime 中断当前专注,但 Host 不是在调用 pomodoro.interrupt,也不能假装成 Agent。
## 7. Pomodoro:第 04 次迭代的具体应用
第 04 次迭代用 Pomodoro 验证的是第一条入口,即 Agent Tool 到 Runtime 再到 MiniApp 投影。其冻结模型为:
~~~text
用户在 IM 中说:现在开始 20 分钟的冥想,帮我计时
→ 远端 Agent 调用 pomodoro.start
→ Runtime 创建 Pomodoro instance、App 子会话和 activation(starting)
→ Host 确认 Pomodoro 已成为当前前台 App
→ Runtime 创建 deadline operationPomodoro 进入 focusing 并被动显示
用户在 IM 中说:停止或结束这次专注
→ 远端 Agent 调用 pomodoro.interrupt
→ 若仍在 startingRuntime 取消启动;若已 focusingRuntime 原子收口当前 operation
→ Pomodoro 只接收通用 lifecycle 关闭通知
~~~
本迭代的 pomodoro.start 把“Pomodoro 已成为用户当前前台界面”作为开始专注的前提:Runtime 接受调用并创建
`starting` activation 后,先向 Agent 回传 `accepted / starting` 协议回执;这只能表示“正在进入专注模式”,不能说
“已经开始计时”。Host 前台确认与 operation 创建成功后,Runtime 再回传 `started` 进度,此时 Agent 才能说“已开始
为你计时”。确认之前不存在 operation、倒计时或专注 IM 记录;最终无法前台激活时,Runtime 回传符合 start output
schema 的 `not_started` 业务结果并恢复 Interact。pomodoro.interrupt 由 Agent 调用,而不是 Runtime 私下调用 Pomodoro
它可取消当前启动尝试,或收口当前 focusing operation。Runtime 根据调用所在 conversation 定位目标,不接受 Agent 指定
或伪造 instance_id、operation_id 等目标。
当前 Pomodoro Surface 不实现开始、暂停、继续、停止等业务按钮。未来如果引入“用户先让 Agent 设定时长、随后自己点击开始”之类的 UI,按钮的 onClick 应产生独立的本地 UI ActionRuntime 可以让它复用创建 operation 的内部动作,但不得改变当前 Agent Tool 的含义,也不要求为第 04 次迭代提前加入两阶段 Tool 模型。
用户切回 Interact、切至其他 MiniApp 或关闭 Pomodoro 属于 Host 生命周期意图。Runtime 按 Pomodoro 的业务规则将其收口为 interrupted,但不伪造一条 Agent Tool Call;自动暗屏、锁屏和 Surface 重载则不等于离开专注。
## 8. 对协议与实现的约束
1. Agent Inventory 只能包含 Agent Tool Descriptor;不得包含 UI Action、Host 生命周期事件或 MiniApp 任意内部方法。
2. Runtime 必须分别记录 Agent Tool Call、UI Action 和 Host 生命周期意图的来源、身份、关联 ID 与审计类型;不得用一种来源伪造另一种。
3. UI Action 的本地 Bridge 协议可在后续版本独立设计,但其入口、权限和审计不得依赖远端 Agent 身份或 inventory revision。
4. Runtime 内部业务服务可由多个入口复用,但每个入口都必须先完成自己的授权、作用域和幂等校验。
5. MiniApp session data 只能保存当前 App 子会话的 MiniApp 自定义 JSON 数据;不能借此完成 Tool、operation、outbox、焦点或 Host Capability 的特权动作。
6. Agent Tool 的调用结果必须由 Runtime 校验 schema、持久化并经 outbox 回传;MiniApp 不得直接向 Agent 或 Transport 发送结果。
7. 在没有明确产品决议前,不得为了未来 UI 的可能性改变已发布 Agent Tool 的语义,或向当前 Inventory 额外加入预设、打开、准备等 Tool。
8. 对 operation、activation、session data、Tool receipt、outbox 和工作区目标的同一次业务变更,Runtime 必须先原子持久化事实,
再让 Lifecycle、Host 和 Surface 对账;持久化失败不得改变任何层,提交后的 UI / Host 失败不得回滚已提交事实。
## 9. 非目标
本文不定义:
- Voice 的具体协议或语音识别实现;它未来只需复用“用户表达意图,再由 Agent 调用 Tool”的语义;
- UI Action 的具体事件名称、Bridge 线协议或按钮设计;
- 允许用户绕过 Agent 调用远端 Agent Tool
- 允许 MiniApp 直接与 Agent、AppServer、Tauri 或任意系统能力通信;
- 将所有 MiniApp 都强制设计为有 operation 的应用;
- 因未来 UI 交互扩展而修改第 04 次 Pomodoro 已冻结的直接开始模型。
@@ -0,0 +1,593 @@
# LineUp App 架构设计
**状态:** 当前权威设计基线
**更新日期:** 2026-08-04
**适用实现:** Tauri 2 Desktop Host 与同代码 Web Reference Host
**本文取代:** `DESIGN.md``M4_COMPATIBILITY_AND_RELEASE.md``TECHNOLOGY_SELECTION.md`
## 1. 设计结论
**LineUp 是用户安装和使用的主应用;LineUp Runtime 是 LineUp App 内部提供给小程序的核心
运行环境。** Runtime 不是与小程序平级的产品 App,也不是一个独立客户端。
```text
LineUp App
├── App Shell / Host
│ ├── 当前前台小程序的挂载与切换
│ ├── 连接状态、通知和安全恢复入口
│ └── Tauri / Web Host Provider
├── LineUp Runtime
│ ├── AppServer / Remote Agent 通信
│ ├── Conversation、Store、sync loop、outbox
│ ├── MiniApp Registry、Instance、Focus、Lifecycle
│ ├── Tool Router、Inbox、Inventory
│ ├── Surface、Capability、Artifact、Audit
│ └── LineUp MiniApp SDK
└── MiniApps
├── Interact(系统内建:IM / Audio / Video
├── Task Dashboard(参考 / 已安装小程序)
├── Whiteboard(参考 / 已安装小程序)
├── Draw-and-Guess(参考 / 已安装小程序)
└── 未来第三方小程序
```
任何 MiniApp 都必须通过 Runtime SDK 使用通信、Tool、生命周期、Surface 和系统能力;不得
建立独立 Agent 通信链路,或直接访问 Host 特权、Store、认证信息及其他 MiniApp 数据。
## 2. 产品边界与术语
| 术语 | 含义 | 不能做什么 |
|---|---|---|
| **LineUp App** | 用户实际使用的主应用、产品容器和交付单位。 | 不能把业务规则重新堆进 Shell。 |
| **App Shell / Host** | Tauri 或 Web 中的挂载、切换、通知、恢复和受限 Host Provider。 | 不解释 Agent 协议,不决定 Tool/App 路由。 |
| **LineUp Runtime** | App 内部的长期协调环境;唯一拥有通信、可靠性、MiniApp 调度与安全决策。 | 不负责任意 MiniApp 的具体 DOM 或业务 UX。 |
| **MiniApp** | 运行在 Runtime 上的功能单元。 | 不直连 Agent/AppServer,不直写 Runtime 状态。 |
| **System MiniApp** | 随 LineUp 发布、代码可信的内建 MiniApp,如 Interact。 | 仍不能绕过 Runtime 调度或 Capability Policy。 |
| **Installed MiniApp** | 经验证后在受限 Surface 中运行的小程序。 | 不访问 Host DOM、Tauri invoke、token、任意网络或其他 MiniApp。 |
| **MiniApp SDK** | Runtime 向 MiniApp 开放的受控能力集合。 | 不是通用 Web、Tauri 或 Agent SDK。 |
| **Tool** | Agent 通过 Runtime 调用的、Manifest 已声明的 MiniApp 功能。 | 不是 App 内部任意函数。 |
| **Capability** | Runtime/Host 保管的录音、文件、通知等系统能力。 | 不是 MiniApp 自动拥有的权限。 |
| **Surface** | 复杂交互使用的受限 UI 容器。 | 不承载未验证远端脚本或 Host 特权。 |
### 2.1 Interact MiniApp
`Interact` 是第一个系统级 MiniApp,也是用户与 Agent 的默认交互入口:
```text
Interact MiniApp
├── IM Mode:文字、图片、短消息、任务和结果投影
├── Audio Mode:实时语音(后续)
├── Video Mode:实时视频(后续)
└── IM 标准交互的默认 Renderer Providernotice / choice / confirm / input
```
当前实现仍保留兼容名称:
```text
产品概念:Interact MiniApp
当前 app_scopechat
当前目录:tauri/src/core-apps/chat/
```
目录和作用域改名属于独立迁移任务,不能和 Runtime/MiniApp SDK 重构混在一起。
Interact 是可信内建代码,因此可以使用 LineUp 的可信 DOM 组件;但它和所有 MiniApp 一样,
必须经 SDK 请求 Runtime 行为,不能直接访问 Transport、Conversation Store、焦点栈、App
Registry、原始 Agent Envelope 或系统能力。
Interact 的 SDK 采用“通用 Runtime 操作 + 可信 UI 投影”的适配方式:Runtime 操作负责消息发送、
标准交互提交/取消/过期、草稿和任务操作;UI 投影只负责 IM 视图、Agent 消息订阅和子会话展示。
UI 投影不是另一套通信或存储协议,Interact 的可信 DOM 也不因此取得 Transport、Store、原始
Agent Envelope、Host DOM 根节点或系统能力。当前 `chat` 兼容接口保留扁平方法,但新代码应按
`sdk.runtime.*``sdk.ui.*` 使用。
## 3. 当前技术与 Host 基线
唯一客户端实现与验收基线是:
```text
Tauri 2 Desktop Host + Web Reference Host
```
它们运行同一份 TypeScript Runtime 与 MiniApp 代码,并不是两个客户端:
- **Web Reference Host**:浏览器开发、Tailscale 联调和自动化验证入口;只能提供可安全降级
的浏览器能力。
- **Tauri Desktop Host**:正式桌面交付;只在 Runtime 授权后提供文件、通知、媒体等 Host
Provider 能力。
不规划独立 Android/Kotlin 客户端,也不规划 Wails Host。已退役的 Android/Wails Spike 只保留
在 Git 历史中,不能作为架构、协议、目录或验收依据。
开发命令:
```bash
cd lineup-app/tauri
npm run web:dev # Web / Tailscale Reference Host
npm run desktop:dev # Tauri Desktop Host
npm test -- --run # Runtime、MiniApp 与 Host 回归
npm run build # TypeScript 检查与 Web production build
```
Web Host 与 AppServer 的地址必须按访问位置匹配:同机浏览器使用 `127.0.0.1`Tailscale 浏览器
使用受信任的 Tailscale 地址。`0.0.0.0` 仅是服务器监听地址,不能作为浏览器 API 目标。
## 4. Runtime 的唯一所有权
Runtime 是以下对象的唯一所有者:
```text
Transport
Conversation Store
sync loop
outbox
协议兼容与原始 Envelope 校验
作用域筛选与 message_id 去重
MiniApp Inbox 与 ACK
MiniApp Registry、Instance、Focus、Lifecycle
Tool Router、Inventory、Capability、Surface、审计与恢复
```
当前消息路径必须保持单向:
```text
AppServer / Remote Agent
→ LineUp Runtime
→ 协议、scope、权限、去重和持久化
→ MiniApp SDK Inbox / Tool Call
→ MiniApp UX
→ SDK Result / Progress / Lifecycle Request
→ Runtime 校验、审计、outbox
→ AppServer / Remote Agent
```
禁止项:
```text
MiniApp 直接 fetch AppServer 或 Agent
MiniApp 直接读写 Conversation Store、sync cursor 或 outbox
MiniApp 构造或发送原始 Agent Envelope
MiniApp 直接切换其他 MiniApp、改写 focus stack 或 Registry
MiniApp 直接调用 Tauri invoke、Host DOM 根节点或系统能力
Surface 从消息 payload 直接加载 URL、HTML、CSS、JS、Bundle 或 source
```
## 5. MiniApp 信任模型与 SDK 权限视图
所有 MiniApp 共享 Runtime 的协议语义,但按来源获得不同的 SDK 视图。
| 能力 | Interact / System MiniApp | Installed MiniApp |
|---|---:|---:|
| 接收自身 Inbox、ACK | 可以 | 可以 |
| 接收 Runtime Tool Call | 可以 | 可以 |
| 上报 progress / result / error | 可以 | 可以 |
| 请求前台、后台、关闭 | 可以,由 Runtime 决定 | 可以,由 Runtime 决定 |
| 请求 Surface | 可以,经 Runtime Policy | 可以,经 Runtime Policy |
| 请求 Capability | 可以,经 Runtime Policy | 可以,经 Runtime Policy |
| 可信内建 DOM 组件 | 可以 | 不可以 |
| 隔离 iframe / Surface Bridge | 可选 | 必须 |
| 直连 Transport、Store、Agent | 不可以 | 不可以 |
| 直接 Host / Tauri / OS 调用 | 不可以 | 不可以 |
| 读取其他 MiniApp 数据 | 不可以 | 不可以 |
系统内建不等于绕过 Runtime:可信来源只决定代码的发布与 UI 执行方式,不改变 Runtime 的
通信、焦点、Tool、权限和审计边界。
## 6. MiniApp 实例、焦点与恢复
MiniApp Registry 管理“有哪些小程序”,Instance Manager 管理“哪些实例正在运行”。二者不能
混为一个状态。
```ts
type MiniAppInstanceRecord = {
instance_id: string;
app_scope: string;
conversation_id?: string;
state:
| "starting"
| "foreground"
| "background"
| "suspended"
| "stopping"
| "stopped"
| "failed";
parent_instance_id?: string;
started_at: string;
stopped_at?: string;
error?: string;
};
```
Runtime 保存焦点栈,不以单个 MiniApp 自己的布尔状态作为事实来源:
```text
focus stack:
chat:audio-001
draw-and-guess:game-001
foreground:
draw-and-guess:game-001
background:
chat:audio-001
```
当扩展 MiniApp 关闭或失败时,Runtime 必须恢复焦点栈中的前一有效实例。重启后,Runtime
恢复有界的实例与焦点状态;`starting``stopping` 中断的实例必须安全降级为 `suspended`
不得伪装为已完成的 Host 操作。
## 7. MiniApp SDK v1
下一阶段的核心交付是 **LineUp MiniApp SDK v1**。SDK 不是万能 API,而是 Runtime 根据
Manifest、实例状态、权限和 Policy 开放的最小请求集合。
```ts
interface LineUpMiniAppSDK {
readonly context: MiniAppContext;
inbox: MiniAppInboxAPI;
tools: MiniAppToolAPI;
lifecycle: MiniAppLifecycleAPI;
surfaces: MiniAppSurfaceAPI;
capabilities: MiniAppCapabilityRequestAPI;
}
type MiniAppContext = {
app_scope: string;
instance_id: string;
conversation_id: string;
app_version: string;
state: "starting" | "foreground" | "background" | "suspended";
};
```
### 7.1 Inbox
```ts
interface MiniAppInboxAPI {
list(after_message_id?: string): readonly MiniAppMessage[];
subscribe(listener: (message: MiniAppMessage) => void): Unsubscribe;
acknowledge(message_id: string): boolean;
}
```
Runtime 在投递前校验 Envelope、scope、目标 MiniApp、实例状态和去重;消息先持久化,再由
MiniApp ACK。后台化、刷新、Surface 重载或 Runtime 重启不能丢失未处理消息。
### 7.2 Tool
```ts
interface MiniAppToolAPI {
subscribe(listener: (call: MiniAppToolCall) => void): Unsubscribe;
get(call_id: string): MiniAppToolCall | undefined;
reportProgress(call_id: string, progress: MiniAppToolProgress): Promise<CommandReceipt>;
complete(call_id: string, result: JsonObject): Promise<CommandReceipt>;
fail(call_id: string, failure: MiniAppToolFailure): Promise<CommandReceipt>;
cancel(call_id: string, reason?: string): Promise<CommandReceipt>;
}
```
`complete``fail``cancel` 都是对 Runtime 的请求,不是直接网络发送。Runtime 必须验证
`call_id` 的实例归属、合法状态迁移、输入/输出 schema、幂等性和权限,然后持久化、审计并
写入 outbox。
一旦 Runtime 接受某个 App instance 的关闭,关闭表示释放该 instance,不是把它留在后台:Runtime 停止
向它投递新普通 Tool,将仍为 `received / routing / waiting_for_app / running` 的绑定 Tool 原子收敛为
`cancelled(app_closed)`,每条 Tool 只写一条 cancelled outbox;已 `submitted` 的结果不可改写,继续可靠发出。
然后才关闭 Surface、停止 instance、结束 App 子会话并恢复焦点。普通 Tool 的完成或失败本身不关闭 App。
标准交互是例外:App 关闭只通知 Agent,不自动取消仍 pending 的人与 Agent 交互。
### 7.3 人与 Agent 标准交互、Lifecycle、Surface 与 Capability
`notice``choice``confirm``input` 是 Interact 的人与 Agent 交互组件,与 IM 消息同属于
当前会话;它们不是 MiniApp SDK,也不用于 MiniApp 自己的业务表单、确认或输入。Agent 通过
标准 Tool 交互请求发起它们,Runtime 校验、持久化、恢复、审计并可靠回传结果,Interact 负责呈现。
这四种是普通的人与 Agent 会话交互,不在 SDK v1 中统一定义为密码或秘密输入。已提交内容遵循普通 IM
的会话历史、保留和日志基线;尤其不能因为它们是普通输入,就突破全局“日志不记录会话正文、Tool 参数、
token 等内容”的约束。未提交 `input` 草稿仅保留在当前运行期,重启清空。密码、卡密、私钥、临时 token
等真正秘密输入留待未来作为独立的 `password-input` 原语设计;届时 Runtime 必须按类型强制其展示、记录、
传递和清理规则,Agent 不能用普通 `input` 绕过这些保护。
每条人与 Agent 的标准交互都必须在 Interact / IM 中留下相应的卡片、气泡或结果记录。`notice` 是只读
IM 卡片;当前界面可同时短暂 Toast 提醒,但 Toast 只是同一条记录的辅助呈现,不能作为唯一消息或用户
已阅读的证明。Runtime 成功持久化 notice 卡片并交给 Interact 呈现后,即可向 Agent 返回 `accepted`
MVP 中,一个 LineUp Runtime 只连接一个 Agent,当前登录会话的 `agent_uid` 作为每条交互和
App 子会话的 `agent_id`。此处保存 Agent 身份是为了让答案按创建时的上下文回到正确对象;本阶段
不实现多 Agent 连接、切换、会话列表、outbox 或跨 Agent 路由。
Agent 发 interactive Tool 时,未带经过 Runtime 验证的 `app_session_context.app_session_id`,一律归入主 IM
Runtime 绝不能按当前前台 App 猜测归属。若带有该上下文,Runtime 只接受同一 Agent、同一父会话的真实
App 子会话;已经关闭的子会话可保留为历史上下文,但不复活旧 App。Runtime 创建子会话时会可靠告知 Agent
其稳定 ID,非法或跨会话的引用直接拒绝且不创建交互。
当 Interact 位于前台时,标准交互显示在 IM 时间线/卡片中;当 bundled MiniApp 位于前台时,
Interact / Shell 可以在当前 App 之上显示同一会话的交互层。视觉位置不改变归属:请求和结果始终
属于当前 conversation、Agent call 与 Interact instance,结果经 Runtime 回传 Agent,而不交给
前台 MiniApp。
用户作答时,Interact 只把“交互 ID + 用户动作/答案”交给 Runtime,不直接把答案发送给 Agent。Runtime
从创建时保存的记录取得 Agent、主会话、App 子会话和 Tool 的归属,并核对当前 LineUp / Interact 会话、
有效期、答案格式和是否已结束。只有第一次有效回答可以写入会话历史和可靠 outbox;后续重复或重放提交
不得改变结果,也不得再次通知 Agent。展示位置不是交互的 owner 或提交权限:App 前后台切换、关闭或从
覆盖层改在 IM 显示,都不改变交互归属。客户端不能提交或改写 Agent、会话、Tool 或 App 子会话的归属字段。
用户回答、Agent remote dismiss、到期和 Runtime 失败都只能竞争同一条交互的唯一终态。Runtime 以第一个
成功完成的原子状态写入为准,并同时持久化唯一 Tool 结果/outbox;后来到达的动作不得覆盖结果或再次通知
Agent。MVP 中 interactive Tool 的等待期限与交互的 `expires_at` 相同,Agent 主动停止等待必须走 Runtime
私有的 `interaction.dismiss(control_id, call_id)`Runtime 从受认证 Envelope 推导 Agent 和会话,以 `call_id`
定位交互。重放或目标已终态只返回稳定幂等回执,绝不再写 outbox;该控制契约不属于 MiniApp SDK。
启动一个 App instance 会在主 IM 会话中创建或恢复与之关联的 App 子会话,并可靠向当前 Agent 发送
`app_session.opened`,提供 `agent_id``conversation_id``app_session_id``app_scope``instance_id`
主 IM 将其显示为可展开的
折叠组,记录 Agent 的提问、用户回答和 Agent 的简洁结果;它不记录 App 内部业务操作,例如用户在
Task Dashboard 点击按钮或填写 App 自己的表单、在 Whiteboard 绘制和编辑内容。App 进入后台时,
子会话仍继续;Agent Tool 完成也不自动结束子会话。只有 Runtime 的 `AppLifecycleManager.close` 使
instance 进入 stopped 或 failed、并从 focus stack 移除时,子会话才结束但保留为主 IM 的历史。新的
App instance 必须创建新的子会话,不接续旧实例记录。
已结束子会话可在主 IM 中只读展开,但不会重新启动 App、重新执行操作或复活未回答的问题。用户选择
“继续处理”旧工作时,Runtime 启动新的 App instance 和新的子会话;新子会话可以引用旧会话、artifact
或已保存的 App 状态作为上下文,但不能向旧子会话追加记录。
若 App 子会话关联等待回答的 Agent 问题,App 位于前台时 Interact 可在其上方显示提问层;用户切换到
其他 App 或普通 IM 时,该层收起但问题继续等待,主 IM 的对应折叠组标记“等待你的回答”。用户既可
展开该组直接回答,也可回到原 App 后回答。Shell 可避免用户看到重复的视觉卡片,但展示位置不改变交互
归属或提交资格;Runtime 通过首次有效回答和终态检查防止重复结果。真正关闭 App 会按普通 Tool 的
`app_closed` 规则先收敛未终态 Tool,随后移除该 App 上方的展示层并结束 App 子会话。Runtime 将关闭事件
(含仍 pending 的 interaction call_id)可靠通知 Agent;问题本身仍属于 Interact / 主 IM,直到 Agent 远程
dismiss、用户回答、到期或 Runtime 失败才结束。
因此 bundled MiniApp 不能发起、读取、提交或取消这类 Agent 交互,也不因其显示在自身上方而获得
Host DOM 或会话数据。复杂、高频或私有业务 UI(例如 Task Dashboard 的“新建任务”表单、Whiteboard
画布文字编辑、画笔、颜色选择、拖放和工具栏)必须留在 MiniApp 自己的 Surface 内。系统权限确认
(文件、麦克风、通知等)则属于 Runtime/Host Capability Gateway,而不是普通 `confirm`
```ts
interface MiniAppLifecycleAPI {
requestForeground(): Promise<CommandReceipt>;
requestBackground(): Promise<CommandReceipt>;
requestClose(reason?: string): Promise<CommandReceipt>;
}
```
MiniApp 只能请求生命周期变化;Runtime 决定是否允许。若接受关闭,则按上面的 `app_closed` 收敛普通 Tool、
处理 Surface 并恢复焦点;若用户只想暂时离开,应请求后台化。
Surface 与 Capability 也只能请求:
```text
MiniApp requestOpenSurface / requestCapability
→ Runtime 校验 Manifest、Policy、实例状态和用户确认要求
→ Host Provider 执行受限操作
→ Runtime 记录审计并返回受控结果
```
不应在 SDK v1 提供 `fetch`、任意网络、任意文件系统、剪贴板、麦克风、摄像头或 Tauri
直接调用。高风险能力以后通过 Capability Request 按需开放。
## 8. Tool Contract 与 Inventory
MiniApp 只能在 Manifest 中声明 ToolAgent 只能调用当前 Runtime 已公布 Inventory 中的 Tool。
```ts
type ToolDescriptorBase = {
id: string; // 例如 task-dashboard.open
version: 1;
input_schema: JsonSchema;
output_schema: JsonSchema;
permissions?: readonly string[];
};
type ToolDescriptor = ToolDescriptorBase & (
| { handling: "interactive"; target?: never }
| {
handling: "direct" | "launch" | "foreground" | "operation";
target: {
app_scope: string;
requires_foreground: boolean;
restore_previous_focus: boolean;
};
timeout_ms?: number;
}
);
```
统一调度路径:
```text
Agent Tool Invoke
→ Runtime 校验 Envelope、Inventory revision、Tool Descriptor、参数、scope、App 状态和权限
→ 创建可持久化 Tool Call / 审计记录
→ Tool Router 判定 direct / interactive / launch / foreground / operation
├── interactive:不使用 target、不创建业务 instance,只由 Runtime 建立 Interact 会话交互
└── 其他 handling:投递到目标 MiniApp instance
→ MiniApp SDK 报告 progress / result / error
→ Runtime 校验输出 schema、持久化、更新焦点、写 outbox
→ Agent 收到可靠结果
```
`notice``choice``confirm``input` 是 Agent 调起的 Runtime 统一 `interactive` Tool/交互记录
能力。Interact 提供会话语义和呈现,Runtime 持有记录与可靠结果路径;其他 MiniApp 只能作为当前
前台界面被 Interact 交互层覆盖,不能调用、实现或取得该交互的内容与结果。
可回答交互的请求以相对 `expires_in_ms` 指定期限;Runtime 计算和持久化 `expires_at`,缺省 15 分钟,
仅接受 1 分钟至 24 小时。notice 不等待回答,也不接受期限。
建议稳定拒绝码至少包括:
```text
inventory_revision_mismatch
tool_not_advertised
tool_input_invalid
tool_output_invalid
app_disabled
app_instance_not_found
app_scope_mismatch
lifecycle_denied
capability_denied
call_already_final
```
## 9. Surface、Bundle、Capability 与发布安全
复杂 UI 只能运行在受限 Surface 中。Installed MiniApp 的生产 Bundle 必须满足:
```text
签名 Manifest
→ 精确 byte size 校验
→ SHA-256 校验
→ 原子写入已验证 cache
→ app_id + exact version 解析
→ sandbox="allow-scripts" 的隔离 Surface
```
### 9.1 Host 兼容矩阵
| 情况 | Host 必须行为 | 禁止行为 | 观测信号 |
|---|---|---|---|
| 旧 Client 收到未知 `lineup.v1.*` | 安全 fallback,不执行 | 将 payload 当 HTML/能力执行 | `unsupported_type` / `unsupported_version` |
| 新 Client 收到旧 plain text | 仅作为 Markdown 显示 | 赋予交互或能力 | legacy markdown renderer |
| 不支持目标 Surface version | 不挂载,显示安全提示 | 自动前移到相邻版本 | `surface_unavailable` |
| 收到生产 Surface | 仅在签名、长度、SHA-256 成功后挂载已验证 bytes | 接收 URL、HTML、JS、Bundle/source | install disposition |
| 候选 Bundle 验证失败 | 保留当前 active 已验证版本 | 覆盖 active bytes 或执行候选 | `signature_invalid` 等 |
| active Bundle 被撤销 | 原子回滚到已验证 predecessor;没有则不可用 | 回退到未验证缓存/远端 URL | `rolled_back` / `unavailable` |
| Bridge 收到非当前 iframe 消息 | 丢弃 | 仅相信 origin 字符串或调用 Host API | bridge rejection counter |
| Capability Catalog 变化 | 发布最小新 Inventory | 用过期 Inventory 自动执行 | catalog revision |
### 9.2 生产发布序列
1. 发布方创建不可变 artifact 与生产 Manifest`app_id`、精确版本、artifact ID、长度、
SHA-256、最小 Host 版本、权限、可信 `key_id`、Ed25519 签名。
2. Manifest 与 Bundle 部署在 Host 配置的 HTTPS artifact 服务;Manifest 不携带 URL、HTML、
CSS、JS 或 source。
3. Host 在临时内存中验证签名、精确长度与 SHA-256;失败不写 cache。
4. 验证成功后原子安装,并只解析精确 `app_id + version`;不得自动前移版本。
5. 仅向满足最小 Host 版本且已启用目标版本的 Client 灰度发布。
6. 监控签名、摘要、下载、Bridge 与 Capability 状态;安全事件发生时只回滚到已验证的
predecessor。
7. 回滚后保留审计元数据,但不记录 artifact 内容、URL、token、文件路径或 Capability 参数;
新 artifact 必须使用新版本号。
正式 HTTPS Host 才可被标记为 production-capable。HTTP LAN 开发 Host 在浏览器 WebCrypto
受限时必须安全拒绝验签,不能为了联调放宽该规则。
## 10. 当前实现状态
### 10.1 已完成:历史安全基础
历史 M0~M4 已完成并沉淀在当前代码中;它们不是新的平行排期:
| 历史阶段 | 保留能力 |
|---|---|
| M0 | HTTP Transport、Conversation Store、协议解码、Markdown、安全 fallback、基础回归 |
| M1 | Tool Call、Task、可靠交互结果、执行摘要、可信 choice/confirm/input |
| M2 | Surface Registry、实例生命周期、隔离 iframe 与恢复 |
| M3 | Capability Registry、用户确认、审计与受限 Host 执行 |
| M4 | 签名 Manifest、Bundle 校验/缓存/回滚、golden fixture |
### 10.2 已完成:第二次升级
```text
00.base
→ Runtime 托管 Interact IM(当前兼容名 chat)的稳定基线
01.kernel
→ MiniApp Instance / Focus / Lifecycle、Tool Router、App Orchestrator、
Extension SDK 雏形、Workspace 持久化和恢复
```
当前实现已验证登录、同步、本地回显、Markdown、安全渲染、Tool Call、Task、Surface、
Capability、App Inbox、outbox、实例焦点恢复和生产构建。自动化回归当前为 23 个测试文件、
99 个测试;`npm run build` 通过。
Runtime 的关键结构位于:
```text
tauri/src/runtime/app-management/
tauri/src/runtime/coordination/
tauri/src/runtime/persistence/
tauri/src/runtime/protocol/
tauri/src/runtime/surfaces/
tauri/src/runtime/capabilities/
tauri/src/core-apps/chat/
```
### 10.3 下一阶段:03.sdk_and_coreapp
下一阶段不是应用市场,也不是立即建设服务端下载目录。目标见
[03.sdk_and_coreapp.md](../迭代/03.sdk_and_coreapp/03.sdk_and_coreapp.md):定义并验证 MiniApp SDK v1
```text
冻结 AppManifest、ToolDescriptor、Tool Call、App Context、Result/Progress/Error 和错误码
→ Runtime 实现通用 SDK 请求路由、schema 校验、审计、恢复和 revisioned Inventory
→ Interact 适配为第一个 System MiniApp SDK 样本
→ Task Dashboard 作为第一个参考 MiniApp 验证 Tool、progress、result、focus 与恢复
→ Whiteboard 验证受限 Surface、状态 patch、Artifact 与 Capability 请求
```
参考 MiniApp 在此阶段使用 `kind = bundled`:随开发 Host 内置、有 Manifest 和 Runtime 注册记录,
但不依赖服务端 Catalog、远程下载或第三方发布。`bundled` 不是系统级信任;参考只描述这些
MiniApp 在当前阶段用于验证 SDK 的工作目的,不是 Manifest 类型名称。
### 10.4 后续阶段
MiniApp SDK 与参考实现稳定后,再按顺序推进:
```text
04.app-delivery-registry
→ Catalog Entry、Manifest/Bundle 下载、验签、安装、启用、禁用、更新、回滚、移除
05.catalog-and-market(按产品需要)
→ 搜索、分类、发布者、安装 UX、组织分发与可能的商业能力
```
`03.sdk_and_coreapp` 已经包含 Task Dashboard 和 Whiteboard 的端到端参考实现;不再另设
`03.reference-miniapps`,以免把同一批工作拆成两个编号。
“市场”属于最后的产品分发层,不能反向决定 Runtime、SDK、Tool 或安全模型。
## 11. 验收与发布门槛
每次 Runtime、MiniApp SDK 或 Host 改动至少满足:
```text
npm test -- --run
npm run build
```
对于 SDK/参考 MiniApp,还必须有以下证据:
1. 合法与非法 Manifest、Inventory revision、Tool 输入/输出 schema 的自动化测试;
2. Tool 启动 MiniApp、前后台切换、结果回传、失败回焦和重启恢复的场景测试;接受关闭后,未终态普通 Tool
仅产生一次 `app_closed` 取消、已 submitted 结果继续 outbox,随后才关闭 Surface / instance 并恢复焦点;
3. Interact 的 `notice / choice / confirm / input` 会话呈现不退化,覆盖 IM 内联与前台 bundled
MiniApp 上交互层的显示、conversation/call 绑定、结果可靠回传 Agent、默认/越界交互期限、非法
`app_session_context` 拒绝、`interaction.dismiss` 幂等重放,以及 bundled MiniApp 无法访问或提交交互的测试;
4. MVP 的单 Agent 身份随交互和 App 子会话持久化;不新增多 Agent 连接、切换、会话列表或路由;
5. App instance 的子会话创建、后台/暂停保留、`AppLifecycleManager.close` 后结束折叠、重启恢复和新 instance 隔离可验证;创建时会可靠向 Agent
提供 `app_session_id`,关闭时会发出包含仍 pending interaction call_id 的可靠 App 已关闭事件,但不会自动
取消 Interact 交互;子会话只包含人与 Agent 的交互,不包含 MiniApp 的内部业务操作;
6. 已结束子会话可只读查看;“继续处理”旧工作会创建新 instance / 新子会话,可引用旧上下文但不复活旧问题;
7. App 切换时未回答问题从前台 App 上方收起并在对应子会话标记;用户可在 IM 或回到原 App 后回答,且同一问题不可重复提交;
8. Installed MiniApp 无法取得 Host DOM、Tauri invoke、token、任意网络或其他 MiniApp 数据;
9. 有头浏览器验证登录、同步、本地回显和代表性 MiniApp 路径;
10. 对生产 Bundle,验证签名 `open → patch → close → rollback`、隔离 Bridge 和 Capability 拒绝路径;
11. `git diff --check` 无格式错误,日志不得包含身份、会话、消息正文、Tool 参数、token 或
artifact 内容。
## 12. 相关文档与源码导航
- [迭代/00.base/00.base.md](../迭代/00.base/00.base.md)Runtime 托管 Interact IM 的稳定基线。
- [迭代/01.kernel/01.kernel.md](../迭代/01.kernel/01.kernel.md)Runtime Kernel 与 MiniApp 编排目标和验收。
- [tauri/src/README.md](../tauri/src/README.md):当前源码目录和依赖方向。
- [tauri/README.md](../tauri/README.md)Tauri/Web Host 的运行、回归与历史实施细节。
- [程序文件清单与功能说明.md](../程序文件清单与功能说明.md):实现文件和功能说明。
- [LineUp App 最终设计方案](02.正式方案/app_final_design.md):跨文档详细契约来源;
若与本文的产品术语或当前迭代顺序冲突,以本文为准并同步更新上游方案。
@@ -0,0 +1,21 @@
# LineUp App 当前设计文档
这里集中放置 LineUp App 当前阶段仍在使用的设计文档。LineUp App 是用户使用的主应用,Runtime 是 App 内部提供给 MiniApp 的核心运行环境;当前设计以这组文档为准。
## 当前权威入口
- [APP架构设计.md](APP架构设计.md):当前产品边界、Runtime、MiniApp、SDK、Interact 以及发布安全约束的总入口。
- [LineUp App 最终设计方案](02.正式方案/app_final_design.md):当前架构、契约和实施顺序的详细汇总。
## 详细方案
- [LineUp App 层架构与迁移记录](02.正式方案/lineup-app-layer-architecture.md):M0~M4 的实施边界、迁移记录和分层规则。
- [LineUp Runtime 与 App SDK 架构方案](02.正式方案/lineup-runtime-sdk-architecture.md)Runtime、App Manager、Tool Registry、SDK 和消息路由的详细方案。
- [运行时与智能体工具](02.正式方案/运行时与智能体工具.md):远端 Agent 的角色与能力边界,以及 Agent Tool、本地 UI Action、Host 生命周期意图三条入口的正式约束。
- [LineUp UI Surface 与 App Capability 协议](02.正式方案/lineup-ui-surface-protocol.md)Surface、Capability、权限确认和 Bundle 安全规则。
## 迭代和评审
迭代定义以及每轮设计评审、验收记录保留在 [迭代/](../迭代/README.md)。设计文档描述长期架构和当前阶段的约束,迭代文档描述具体要交付的工作。
根目录下的 `设计/00.records``设计/01.前期分析与设计` 属于历史资料,不作为当前 LineUp App 设计的主入口。
@@ -0,0 +1,153 @@
# 当前权威架构基线
**版本:** 2026-08-07 整合版
**主来源:** `01.当前有效设计/APP架构设计.md``01.当前有效设计/02.正式方案/*.md`
**补充来源:** `90.历史设计归档/` 中与当前实现不冲突的背景材料
## 1. 一句话结论
当前 LineUp 的权威架构基线应理解为:
**LineUp 是用户设备上的主应用;Runtime 是 LineUp App 内部的核心协调环境;MiniApp、Tool、Surface 和 Capability 都必须通过 Runtime 被管理,而不是各自直连服务端或 Agent。**
这一定义以当前有效设计区为准,比历史归档中的直连插件、早期 WebSocket 草案更接近今天的实际实现。
## 2. 当前产品结构
当前主线结构可以收敛为:
```text
LineUp App
├── App Shell / Host
│ ├── Tauri Desktop Host
│ └── Web Reference Host
├── LineUp Runtime
│ ├── 通信、会话、同步和 outbox
│ ├── App Registry、Instance、Focus、Lifecycle
│ ├── Tool Router、Inventory、Inbox
│ ├── Surface、Capability、Artifact、Audit
│ └── MiniApp SDK
└── MiniApps
├── Interact
├── Task Dashboard
├── Whiteboard
├── Pomodoro
└── 后续其他 MiniApp
```
## 3. 当前最重要的架构边界
### 3.1 App、Runtime、Host 三者分离
这是当前设计最关键的共识:
- `LineUp App` 是用户安装和使用的产品容器;
- `Runtime` 是 App 内部唯一拥有通信、可靠性、调度和安全决策的协调中心;
- `Host` 负责提供桌面或浏览器环境,但不能绕过 Runtime 直接把系统能力交给 App 或 Agent。
### 3.2 MiniApp 不能绕过 Runtime
所有 MiniApp 都必须通过 Runtime SDK 获得:
- 消息与 Inbox
- Tool 调用;
- 生命周期;
- Surface
- Capability
- 受控状态投影。
它们不能:
- 直接连接 AppServer 或 Agent
- 直接读写 Runtime Store、cursor、outbox
- 直接调用 Host 特权或 Tauri 能力;
- 直接操控其他 MiniApp 或焦点栈。
### 3.3 Agent 只与 Runtime 通信
当前主线下,Agent 不应直接把某个 MiniApp 当作远端可执行对象,而是只能通过 Runtime 发布出来的受控 Tool / Inventory 与系统交互。
## 4. 当前 Host 基线
本轮整合后,当前唯一有效的客户端实现/验收基线是:
```text
Tauri 2 Desktop Host + Web Reference Host
```
这意味着:
- Web Host 是开发、联调和验证入口;
- Tauri Host 是正式桌面交付入口;
- 二者共享同一份 TypeScript Runtime 与 MiniApp 代码;
- 它们不是两套客户端。
与此冲突的早期判断,例如“当前以纯 Web 或未来移动端为主线”“局域网 App 直连 Agent 插件”,本轮都不再作为当前架构主结论保留。
## 5. 当前应用模型
### 5.1 Interact 是默认系统级入口
当前设计已经把“聊天页面”提升并重述为更高层的 `Interact`
- 它是系统级 MiniApp
- 当前默认模式是 IM
- 后续可扩展 Voice / Video
- 当前代码与作用域仍保留 `chat` 兼容命名,但概念上应理解为 `Interact`
### 5.2 Runtime 管理实例、焦点和恢复
当前基线不再把 App 看成静态页面集合,而是 Runtime 管理的一组实例:
- 哪些 App 已注册;
- 哪些实例正在运行;
- 谁在前台;
- 谁被挂起;
- 如何恢复;
- 关闭或失败后如何回退到前一有效实例。
这也是后续工作区、Pomodoro、子会话、恢复与验收闭环成立的基础。
## 6. 当前 Tool 与 Inventory 模型
早期文档把工具分成 `app / toolset / action` 三类,这是有历史价值的草案;但当前主线已经收敛为:
- MiniApp 在受控声明中暴露可发布能力;
- Runtime 进行校验、裁剪和动态汇总;
- Agent 只能调用当前可见 Inventory 中的方法;
- Tool 的生命周期、可见性、风险和结果由 Runtime 裁决。
因此,今天更适合使用“Agent Tool / Inventory / Tool Call / 业务结果 / 协议回执”这套术语,而不是继续把早期三分法当作当前实现的主模型。
## 7. 当前 Surface 与 Capability 约束
这一层是从当前有效设计区的 `02.正式方案/` 中明显成熟起来的:
- 复杂交互不再靠不受控页面,而是放入受限 Surface;
- 系统能力不属于 MiniApp 自动拥有的权限,而是 Runtime/Host 保管的 Capability
- Agent、MiniApp、Surface 都不能绕过 Runtime 直接获得系统动作执行权。
这比 `agent_ops/设计/` 早期协议文档中的能力模型更接近当前安全边界。
## 8. 本轮对旧设计的取舍
### 8.1 仍保留的背景价值
- 产品不是任务管理系统;
- 需要结构化消息与工具调用;
- Agent 与 UI 渲染要解耦;
- 工具、传输、连接需要分层思考。
### 8.2 不再作为当前权威基线的旧结论
- 局域网 WebSocket 直连是当前主线;
- 当前客户端基线是“Web / 未来移动端”;
- 工具协议可以脱离 Runtime 架构单独成立;
- 早期 `app / toolset / action` 三分法就是当前 Tool 模型本身。
## 9. 当前建议阅读顺序
1. 先看本文,建立当前架构全景;
2. 再看 [Agent工具与运行时边界](./03.Agent工具与运行时边界.md)
3. 如需原始来源,再回看 `01.当前有效设计/APP架构设计.md``01.当前有效设计/02.正式方案/` 各细化文档;
4. `90.历史设计归档/` 仅用于追溯历史背景,不再作为当前设计入口。
@@ -0,0 +1,116 @@
# Agent 工具与运行时边界
**版本:** 2026-08-07 整合版
**主来源:** `01.当前有效设计/02.正式方案/运行时与智能体工具.md`
**辅助来源:** `01.当前有效设计/APP架构设计.md``90.历史设计归档/01.前期分析与设计/tool-action-design.md`
## 1. 当前权威结论
当前最重要的结论是:
**Agent Tool 是 Runtime 发布给远端 Agent 的受控调用契约,不是用户按钮,也不是 MiniApp 内部函数的别名。**
这是当前设计相对于早期草案最重要的收敛之一。
## 2. Agent 当前扮演什么角色
远端 Agent 当前应该被理解为:
- 用户意图的理解者;
- Runtime 已发布能力的调用者;
- 结果解释与后续编排者。
它不应该被理解为:
- 本地 Runtime 的替身;
- MiniApp 内部状态的直接操作者;
- Host 权限的直接调用者;
- 任意前端函数的远程执行入口。
## 3. 三条入口必须分开
当前有效设计明确把三条入口区分开了:
### 3.1 Agent Tool
由远端 Agent 发起,经过 Runtime 校验、持久化、路由和回传。
### 3.2 本地 UI Action
由用户在 MiniApp 页面中的点击、输入、拖动等触发,经 SDK / Surface Bridge 进入 Runtime。
### 3.3 Host 生命周期意图
由前后台切换、返回、关闭、恢复、锁屏等本地环境事实触发,经 Runtime 的生命周期裁决进入状态机。
这三条入口可以复用内部业务动作,但不能混同身份。
## 4. 为什么早期工具三分法不能直接作为当前主模型
`agent_ops/设计/01.前期分析与设计/tool-action-design.md` 中的 `app / toolset / action` 三分法,在早期用于说明不同工具形态,很有价值;但它已经不能完整表达当前 Runtime 模型,原因是:
- 当前 Runtime 要管理 Inventory 可见性;
- 要管理 Tool Call 的幂等、状态、回执和终态;
- 要管理激活过程、App 就绪条件、前台要求和 operation;
- 要把 Tool、MiniApp instance、子会话、业务 operation 区分开。
因此,当前主模型应优先使用:
- Agent Tool
- Agent Inventory
- Tool Call
- 协议回执 / 进度;
- 业务结果;
- MiniApp activation
- operation。
## 5. 当前 Runtime 对 Tool 的唯一所有权
Tool 相关的这些职责都必须由 Runtime 统一承担:
- 发布 Inventory
- 校验调用;
- 做幂等去重;
- 决定是否可见;
- 决定是否允许启动或复用 MiniApp;
- 持久化业务状态与 outbox
- 回传协议回执与最终结果。
这意味着:
- MiniApp 不能自己向 Agent 宣传 Tool
- Surface 不能直接把前端动作伪造成 Tool Call;
- Agent 不能绕过 Inventory 直接指定任意 instance 或内部函数;
- Host 事件也不能伪造成 Tool 调用。
## 6. 当前与项目实际情况一致的几个重点
结合当前 `lineup-app` 的实现方向,以下几个判断具有最高优先级:
- Tool 是 Runtime 语义的一部分,不是单独漂浮在外的协议层;
- Interact / MiniApp / Workspace / Focus / Lifecycle 与 Tool 调用之间已经形成一套统一模型;
- 长时业务行为需要区分“调用被接受”“激活已就绪”“业务已开始”“业务已完成”;
- 用户本地操作、Agent 远程调用和 Host 生命周期变化必须各自可审计、可恢复。
## 7. 对旧设计中仍可保留的部分
早期协议文档中这些认识仍然有价值:
- 结构化调用比自然语言裸发更稳定;
- 工具定义需要 schema 化;
- 消息需要统一信封与版本化;
- 调用与返回需要成对、可追踪。
但这些内容如今应作为当前 Runtime Tool 模型的背景,而不是替代它。
## 8. 本轮整合后的使用建议
如果后续在设计、实现或迭代文档里出现以下歧义,优先按本文判断:
- 某个按钮是不是 Agent Tool
- 某个本地事件能不能当成 Tool 调用;
- 某个 MiniApp 能不能直接发消息给 Agent;
- 某个 operation 是否等同于一次 Tool Call
- 某个实例 ID 是否能由 Agent 任意指定。
这些问题的默认答案,都应该先经过 Runtime 边界来裁决,而不是回退到早期直连协议思路。
@@ -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 客户端。
+37
View File
@@ -0,0 +1,37 @@
# 架构设计主目录
`02.架构设计/` 现在是 LineUp 项目的统一设计主目录。
截至 **2026-08-07**
-`lineup-app/设计/` 中的当前有效设计文档,已经整体迁入本目录;
-`agent_ops/设计/` 中的前期分析、阶段记录和正式方案快照,已经迁入本目录的历史归档区;
- 后续关于架构、协议、Runtime、MiniApp、Surface、Capability 和 Agent Tool 的讨论,都应以这里为主入口。
## 当前结构
```text
02.架构设计/
├── README.md
├── 00.整合说明/
│ ├── 01.设计整合说明与优先级.md
│ └── 02.设计冲突与演进顺序.md
├── 01.当前有效设计/
│ ├── README.md
│ ├── APP架构设计.md
│ └── 02.正式方案/
├── 02.整合结论/
│ ├── 01.当前权威架构基线.md
│ └── 02.Agent工具与运行时边界.md
└── 90.历史设计归档/
├── 00.阶段记录/
├── 01.前期分析与设计/
└── 02.正式方案快照/
```
## 使用方式
- 想看当前设计正文,进入 `01.当前有效设计/`
- 想看整合后的主结论,进入 `02.整合结论/`
- 想看冲突判断和演进顺序,进入 `00.整合说明/`
- 想追溯早期方案,进入 `90.历史设计归档/`