770 lines
34 KiB
Markdown
770 lines
34 KiB
Markdown
# LineUp Runtime 与 App SDK 架构方案
|
||
|
||
**版本:** 0.1(架构基线)
|
||
**状态:** 已确认的 Runtime / SDK 方向;尚未完全落地到当前 Tauri 实现
|
||
**日期:** 2026-08-06
|
||
**关联方案:** [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 能依据持久化状态恢复到可解释的协作状态。
|
||
9. **MiniApp 业务状态由 Runtime 托管。** App 只能通过 SDK 保存其当前 instance 的、Manifest schema
|
||
声明的状态快照;不能直接访问浏览器/Host 存储、Conversation Store、Tool/operation Store 或其他 App 数据。
|
||
|
||
## 3. 运行时分层
|
||
|
||
```text
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ Runtime Shell │
|
||
│ 应用切换 · 默认入口 · 连接状态 · 通知 · 安全恢复 │
|
||
├──────────────────────────────────────────────────────────┤
|
||
│ Core App / Installed App │
|
||
│ Interaction App(IM / 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/Capability:Inventory、方法、参数、策略、前台条件
|
||
→ Scope:conversation、instance、operation 所属关系
|
||
→ Subscription:App Manifest 声明与 SDK 订阅交集
|
||
→ Persist:Runtime Event / App Inbox / cursor
|
||
→ Deliver:投递给目标 App 的 SDK
|
||
```
|
||
|
||
Runtime 不对所有 App 广播原始消息。一个白板 App 即使与 Chat 位于同一 conversation,也只能收到其 Manifest 声明且 Runtime 授权的实例 patch、Tool 调用或事件。
|
||
|
||
## 8. Runtime SDK v1
|
||
|
||
### 8.1 双通道模型
|
||
|
||
```text
|
||
Inbound:Runtime → App
|
||
- 已验证 Agent 消息
|
||
- App 专属状态投影
|
||
- Tool 调用
|
||
- Runtime / App 生命周期
|
||
|
||
Outbound:App → 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 instanceState: MiniAppInstanceStateAPI;
|
||
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 MiniApp instance 状态与 Capability
|
||
|
||
每个 MiniApp instance 使用 Runtime 管理的、按实例隔离的业务状态快照:
|
||
|
||
```text
|
||
app_scope + instance_id
|
||
→ 一份 schema 校验的 JSON state snapshot
|
||
→ revisioned replace
|
||
→ 恢复同一个未结束 instance 时投影给该 App
|
||
```
|
||
|
||
这不是通用文件系统、键值数据库或跨 instance 查询接口。App 负责定义状态的业务含义;Runtime 负责
|
||
根据 Manifest 校验 schema、schema version、大小配额和保留策略,并保证调用方只能操作当前 SDK context
|
||
中的 instance。Runtime 也负责 revision 比较、原子写入、重启恢复以及禁用、移除和关闭时的冻结/保留/清理。
|
||
第一版 `retention` 仅支持 `delete_on_close`(默认)与 `retain_readonly`;后者只保留历史或向新 instance
|
||
提供受控上下文,不能让已关闭 instance 再次写入。App 禁用或移除后的最终清理由 Runtime 按用户确认和全局
|
||
保留策略执行;状态 schema 升级只能经新 App 版本的受控读取/替换完成,Runtime 不执行 App 提供的迁移脚本。
|
||
|
||
```ts
|
||
interface MiniAppInstanceStateAPI<State extends JsonObject = JsonObject> {
|
||
get(): Promise<{
|
||
state: State | null;
|
||
revision: number;
|
||
}>;
|
||
|
||
replace(request: {
|
||
state: State;
|
||
expected_revision: number;
|
||
}): Promise<{
|
||
revision: number;
|
||
}>;
|
||
}
|
||
```
|
||
|
||
`expected_revision` 不匹配时 Runtime 返回稳定的 `state_revision_conflict`;非法 schema、越过实例边界或
|
||
超出配额时分别返回 `state_invalid`、`state_scope_mismatch`、`state_quota_exceeded`。App 不得用状态
|
||
快照直接完成 Agent Tool、写 outbox、改变焦点或执行 Capability。涉及 deadline、Tool 终态、outbox、焦点
|
||
或生命周期的业务命令仍要走 Runtime 的 Command / operation 路径;Runtime 可在同一原子事务中更新
|
||
operation 与 instance state,但两者保持不同的事实来源。
|
||
|
||
MiniApp 的短暂渲染状态(动画、滚动位置、临时 loading)不要求保存;Conversation / App 子会话保存人与
|
||
Agent 可见的静态交互和结果,也不是 MiniApp 业务状态的事实来源。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、事件、当前 instance 的 Runtime 状态快照、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", "app.instance-state.v1"],
|
||
"optional_features": ["artifact.create.v1"]
|
||
},
|
||
"entrypoints": [
|
||
{"id": "canvas", "kind": "contextual", "default_eligible": false}
|
||
],
|
||
"instance_state": {
|
||
"state_schema_version": 1,
|
||
"state_schema": {"type": "object", "additionalProperties": false},
|
||
"max_bytes": 65536,
|
||
"retention": "delete_on_close"
|
||
},
|
||
"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
|
||
app_state 按 app_scope + instance_id 隔离的业务状态快照与 revision
|
||
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 回归行为。
|
||
|
||
### R2:App Registry Core App
|
||
|
||
- 将现有开发期 `SurfaceRegistry` 升级为 Runtime App Registry;
|
||
- 实现 Core App 记录、Installed App 记录、enable/disable/remove 和默认 App 选择;
|
||
- 将 Registry UI 实现为 `app-registry`,仅调用 `RuntimeAppManager`;
|
||
- App 状态变化后正确更新 Runtime Inventory。
|
||
|
||
### R3:Tool Registry 与 App SDK Inbox
|
||
|
||
- 实现 Manifest Tool / Subscription 解析与可见性筛选;
|
||
- 将动态 Inventory 同步给 Agent;
|
||
- 实现 Agent `tool.invoke` → Runtime → App SDK → `tool.result` 的可靠闭环;
|
||
- App 未运行时持久化 Inbox,恢复后可幂等补读。
|
||
- 实现 `app.instance-state.v1`:Manifest schema / schema version / quota / retention 校验、instance 隔离、
|
||
revisioned replace、刷新/重启恢复与关闭/禁用/移除清理;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 只能读写自身当前 instance 的状态快照;非法 schema、旧 revision、跨 instance、超配额以及
|
||
关闭后的写入均被稳定拒绝,重启只恢复最后一个有效快照。
|
||
```
|
||
|
||
## 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` 作为本地路由字段;供应商身份和全局命名空间将在后续生态阶段通过独立、版本化的兼容设计重新引入。
|
||
- 当前 M0~M4 实现可作为 Runtime 的迁移基础,但其现有文件边界不是最终 Runtime/App/SDK 边界。
|