commit 10840909abb1014e9e2733a48eac3d9879afba01 Author: kyugao Date: Fri Aug 7 16:56:51 2026 +0800 初始化 agent_ops 文档治理体系 diff --git a/00.目录治理/01.文档现状分析与迁移规划.md b/00.目录治理/01.文档现状分析与迁移规划.md new file mode 100644 index 0000000..4797b63 --- /dev/null +++ b/00.目录治理/01.文档现状分析与迁移规划.md @@ -0,0 +1,106 @@ +# 文档现状分析与迁移规划 + +**日期:** 2026-08-07 +**范围:** 工作区根目录、`lineup-app/`、现有 `agent_ops/设计/` +**目标:** 为 `agent_ops/` 建立由 agent 持续维护的文档目录结构和迁移顺序 + +## 1. 现状结论 + +当前文档资产主要来自三个来源: + +1. 工作区根目录的项目级文档; +2. `lineup-app/` 的客户端文档体系; +3. `agent_ops/设计/` 中已有的设计历史材料。 + +它们的内容本身已经开始形成边界,但入口分散、时效性标注不统一、历史与当前有效资料仍有混放。 + +## 2. 当前文档的四种角色 + +### 2.1 权威入口 + +负责快速建立共识与导航,例如: + +- 根 `README.md` +- `lineup-app/README.md` +- `lineup-app/设计/README.md` +- `lineup-app/迭代/README.md` + +### 2.2 持续事实 + +反映当前已实现、已验证、已知限制与当前阶段判断,例如: + +- `项目状态记录.md` +- 根 `程序文件清单与功能说明.md` +- `lineup-app/程序文件清单与功能说明.md` +- `lineup-app/迭代/plan.md` + +### 2.3 当前有效设计 + +仍然对实现有约束力,例如: + +- `lineup-app/设计/APP架构设计.md` +- `lineup-app/设计/02.正式方案/*.md` +- `lineup-app-server/DESIGN.md` + +### 2.4 历史归档 + +保留背景和演进上下文,但不再直接指导当前实现,例如: + +- `agent_ops/设计/00.records/*` +- `agent_ops/设计/01.前期分析与设计/*` +- 已完成阶段的旧评审记录 + +## 3. 为什么不能直接搬运 + +如果只是把原文档换个目录继续堆放,会保留以下问题: + +- 多份内容重复但没有权威版本; +- 文档中的“最后核验时间”仍然散落,读者不容易判断哪些结论仍可信; +- 项目总览、运行状态、程序清单、设计和迭代会继续混放; +- 早期设计和当前生效设计无法快速区分。 + +因此迁移必须以“整合、改写、重定入口”为主,而不是简单复制。 + +## 4. 目标目录结构 + +```text +agent_ops/ +├── 00.目录治理/ +├── 01.项目总览/ +├── 02.架构设计/ +├── 03.迭代规划/ +├── 04.程序清单/ +├── 05.运行运维/ +└── 90.历史归档/ +``` + +## 5. 迁移顺序 + +### 第一阶段 + +先整合工作区根目录文档: + +- 项目总览; +- 当前状态; +- 仓库职责边界; +- 跨仓程序清单。 + +### 第二阶段 + +整合 `lineup-app/设计/`: + +- 提炼当前有效设计; +- 把历史背景和正式方案分开; +- 建立面向实现的设计入口。 + +### 第三阶段 + +整合 `lineup-app/迭代/`: + +- 分离总体计划与具体迭代; +- 保留设计评审与验收评审链路; +- 建立可持续追踪的阶段索引。 + +## 6. 本轮输出 + +本轮已进入第一阶段,并已在新目录中落位第一批整合文档。 diff --git a/00.目录治理/02.根目录文档迁移记录.md b/00.目录治理/02.根目录文档迁移记录.md new file mode 100644 index 0000000..7c0ec32 --- /dev/null +++ b/00.目录治理/02.根目录文档迁移记录.md @@ -0,0 +1,68 @@ +# 根目录文档迁移记录 + +**迁移批次:** 第 1 批 +**日期:** 2026-08-07 +**范围:** 工作区根目录文档 + +## 1. 本批处理原则 + +- 不直接复制旧文档; +- 先提炼当前仍有效的结论; +- 明确文档中的时间边界; +- 把运行事实和长期结构分开; +- 对时效性较强的内容标记“仍有效 / 需复核 / 历史背景”。 + +## 2. 已分析的源文档 + +### 2.1 作为主来源整合 + +- `/README.md` +- `/项目状态记录.md` +- `/仓库职责说明.md` +- `/程序文件清单与功能说明.md` + +### 2.2 作为辅助判断 + +- `/demo.md` + +处理结论: + +- `demo.md` 是演示 Markdown,不纳入项目主文档迁移; +- 若后续需要保留,可作为格式样例归档到 `90.历史归档/` 或单独样例目录。 + +## 3. 新落位文档 + +- `01.项目总览/01.项目总览.md` +- `01.项目总览/02.当前状态与阶段判断.md` +- `01.项目总览/03.仓库职责与协作边界.md` +- `04.程序清单/01.跨仓程序清单与功能说明.md` + +## 4. 时效性判断摘要 + +### 4.1 仍适合作为当前主结论的内容 + +- 项目定位为“远程 Agent 操作交互端”; +- 当前主线为 `lineup-app`、`lineup-adapter/hermes`、`lineup-app-server` 三个核心仓边界; +- 客户端基线为 Tauri Desktop Host + Web Reference Host; +- 程序清单已将客户端与跨仓服务端职责分离。 + +### 4.2 需要保留日期边界的内容 + +- “当前在线”“端口监听”“已运行中”等运行状态; +- 具体工具链版本、提交号、APK 体积; +- 某次联调或验收已通过的结论。 + +这些内容并非无效,但必须注明它们的观察日期,不能被误读为 2026-08-07 当天仍自动成立。 + +### 4.3 不作为本轮主入口整合的内容 + +- 演示文件; +- 二进制包与图片; +- 需要进入后续设计/迭代阶段再整合的文档链接明细。 + +## 5. 后续动作 + +下一步按用户要求继续整合: + +1. `lineup-app/设计/` +2. `lineup-app/迭代/` diff --git a/00.目录治理/03.设计文档整合记录.md b/00.目录治理/03.设计文档整合记录.md new file mode 100644 index 0000000..35fa6bb --- /dev/null +++ b/00.目录治理/03.设计文档整合记录.md @@ -0,0 +1,85 @@ +# 设计文档整合记录 + +**迁移批次:** 第 2 批 +**日期:** 2026-08-07 +**范围:** `agent_ops/设计/` 与 `lineup-app/设计/` + +## 1. 本批整合原则 + +本批不做“两个目录简单合并”,而是按以下优先级整合: + +1. 如设计结论与当前实现存在冲突,优先采用 `lineup-app/设计/`; +2. `lineup-app/设计/` 中由 `APP架构设计.md` 明确声明为当前权威入口的结论,作为主基线; +3. `lineup-app/设计/02.正式方案/` 中的文档作为主基线的细化来源; +4. `agent_ops/设计/02.正式方案/` 主要视为历史同步快照,只有在与当前实现不冲突、且能补充背景时才吸收; +5. `agent_ops/设计/01.前期分析与设计/` 与 `00.records/` 主要作为历史来源,不再直接作为当前实现依据。 + +## 2. 为什么以 `lineup-app/设计/` 为主 + +经过本轮对比,`lineup-app/设计/` 具备更高确定性,原因包括: + +- 更新时间更晚; +- 明确声明“当前权威设计基线”; +- 与 `lineup-app` 当前实际代码结构更一致; +- 已经主动声明取代若干旧设计文档; +- 在 Runtime、MiniApp、Host、Tool、Surface、Capability 等边界上更收敛。 + +相比之下,`agent_ops/设计/` 中较早的文档仍保留了: + +- 局域网直连 WebSocket 的早期主线; +- 未来移动端 / 纯 Web 设想; +- 工具分类与协议草案的早期形态; +- 尚未收敛到当前 `lineup-app` Runtime 架构前的阶段性判断。 + +这些内容仍有背景价值,但不能继续和当前有效设计平级。 + +## 3. 本轮主要源文档 + +### 3.1 当前主来源 + +- `lineup-app/设计/README.md` +- `lineup-app/设计/APP架构设计.md` +- `lineup-app/设计/02.正式方案/app_final_design.md` +- `lineup-app/设计/02.正式方案/lineup-runtime-sdk-architecture.md` +- `lineup-app/设计/02.正式方案/lineup-ui-surface-protocol.md` +- `lineup-app/设计/02.正式方案/运行时与智能体工具.md` + +### 3.2 历史补充来源 + +- `agent_ops/设计/01.前期分析与设计/architecture-design.md` +- `agent_ops/设计/01.前期分析与设计/tool-action-design.md` +- `agent_ops/设计/02.正式方案/*.md` + +## 4. 本轮输出 + +本轮在 `02.架构设计/` 下新增: + +- `01.设计整合说明与优先级.md` +- `02.当前权威架构基线.md` +- `03.Agent工具与运行时边界.md` + +后续迁移已进一步完成: + +- 原 `lineup-app/设计/` 已整体迁入 `agent_ops/02.架构设计/01.当前有效设计/` +- 原 `agent_ops/设计/` 已整体迁入 `agent_ops/02.架构设计/90.历史设计归档/` +- 两处旧设计目录已不再保留为有效入口 + +## 5. 冲突判断摘要 + +### 5.1 已明确放弃作为当前主线的旧结论 + +- “局域网 App 直连 Agent 插件 WebSocket 端口”作为当前主连接主线; +- “未来移动端 App”作为当前客户端基线; +- 将工具协议理解为独立于 Runtime / MiniApp 的外层薄协议设计; +- 用早期 `app / toolset / action` 三分法直接代表当前 Runtime Tool 模型。 + +### 5.2 仍保留背景价值的旧结论 + +- 产品不是任务管理系统; +- Agent 与 App 之间需要结构化工具调用,而不是自然语言裸发; +- 工具调用、消息传输、连接管理应分层; +- 设计应保持 Agent 与 UI 渲染解耦。 + +## 6. 后续处理建议 + +迁移完成后,所有设计过程、整合结论、当前有效方案和历史来源,统一在 `agent_ops/02.架构设计/` 下维护。 diff --git a/01.项目总览/01.项目总览.md b/01.项目总览/01.项目总览.md new file mode 100644 index 0000000..7996d27 --- /dev/null +++ b/01.项目总览/01.项目总览.md @@ -0,0 +1,69 @@ +# 项目总览 + +**版本:** 2026-08-07 整合版 +**来源整合:** 根 `README.md`、`仓库职责说明.md`、`程序文件清单与功能说明.md` + +## 1. 项目一句话定义 + +LineUp 是一个连接用户设备、消息服务与远程 Agent 的协作运行体系。用户通过客户端与 Agent 协作,Agent 的执行、工具调用和交互请求通过统一协议与运行时能力落到用户侧。 + +当前更准确的理解方式是: + +```text +用户侧 Host / Runtime + ←→ AppServer / Message Transport + ←→ Hermes Adapter / Agent Platform +``` + +它不是单纯的聊天客户端,也不是任务管理系统。项目长期核心资产是: + +- LineUp 协议; +- Runtime 与 Host 边界; +- Agent 适配层; +- 交互与工具的标准化运行方式。 + +## 2. 当前主线定位 + +从现有根文档整合后,可以把项目主线收敛为以下结构: + +```text +lineup-app/ 客户端 Runtime、Host、Interact 与 MiniApp +lineup-adapter/ Agent 平台适配层,当前主线为 Hermes +lineup-app-server/ 登录、同步、消息收发和 Web Reference Host 服务端边界 +infra/wukongim-v3/ 本地运行配置 +wukongim/ 上游 IM 基线源码 +agent_ops/ 文档治理、迁移、设计与阶段资料整理目录 +``` + +## 3. 当前产品边界 + +根文档里最稳定、且跨多份文档一致的结论包括: + +- 产品定位是“远程 Agent 操作交互端”; +- 当前主交互仍围绕 IM 展开,但目标不止于聊天; +- Agent 任务拆解和执行属于 Agent 自己,不由 LineUp 变成任务管理系统; +- 客户端长期核心是 Runtime,而不是某一个具体聊天页面; +- AppServer 负责传输与服务边界,不负责高层交互语义; +- Adapter 负责平台私有协议到 LineUp 标准协议的桥接,不把平台特判下沉进 Runtime。 + +## 4. 当前实现基线 + +截至本轮整合,可以认为以下内容仍然是当前主线基线: + +- 客户端主仓是 `lineup-app/`; +- 主要 Agent 适配路径是 `lineup-adapter/hermes/`; +- 服务端主仓是 `lineup-app-server/`; +- 通讯基础设施主线围绕 WuKongIM 3.0; +- 当前客户端唯一有效实现/验收基线是 Tauri Desktop Host + Web Reference Host; +- 不再规划独立 Android/Kotlin 客户端作为当前产品主线; +- 历史唐僧叨叨相关材料仅保留背景参考,不再作为当前运行主线。 + +## 5. 阅读顺序建议 + +如果第一次进入项目,推荐从这里继续看: + +1. [当前状态与阶段判断](./02.当前状态与阶段判断.md) +2. [仓库职责与协作边界](./03.仓库职责与协作边界.md) +3. [跨仓程序清单与功能说明](../04.程序清单/01.跨仓程序清单与功能说明.md) + +设计与迭代文档现已迁入 `agent_ops/02.架构设计/` 与 `agent_ops/03.迭代规划/`,后续可直接从这两个目录继续深入。 diff --git a/01.项目总览/02.当前状态与阶段判断.md b/01.项目总览/02.当前状态与阶段判断.md new file mode 100644 index 0000000..a4bbcb3 --- /dev/null +++ b/01.项目总览/02.当前状态与阶段判断.md @@ -0,0 +1,88 @@ +# 当前状态与阶段判断 + +**版本:** 2026-08-07 整合版 +**主要来源:** 根 `项目状态记录.md` +**说明:** 本文不原样复制运行细节,而是提炼当前阶段结论,并明确哪些判断带有时间边界。 + +## 1. 当前阶段结论 + +根据 `项目状态记录.md` 中截至 2026-08-03 的核验内容,以及根目录其他文档的交叉表述,当前更稳妥的阶段判断是: + +**LineUp 已经完成了以 `lineup-app`、`lineup-app-server`、`lineup-adapter/hermes` 为核心的一条主线闭环,重点已经从“是否能跑通”转向“如何收敛 Runtime、MiniApp、工作区、设计和文档体系”。** + +这比“某个服务此刻是否在线”更适合作为当前文档主结论。 + +## 2. 当前仍有效的高层事实 + +以下结论在多份文档中相互支持,仍适合作为当前状态摘要: + +- 项目主线已从早期探索阶段进入结构收敛阶段; +- `lineup-app` 已经成为客户端主仓与当前唯一有效客户端基线; +- `Hermes Adapter` 是当前正式保留的 Agent 适配主线; +- `lineup-app-server` 承担登录、消息收发、同步与 Web Reference Host 服务边界; +- 文档、设计、程序清单和迭代资料已经足够多,继续分散维护的成本开始高于整合成本; +- 下一阶段重点不只是继续写功能,也包括整理信息架构与文档治理。 + +## 3. 需要保留日期边界的事实 + +原 `项目状态记录.md` 中有很多高价值事实,但它们都是“截至某次核验”的事实,不能直接提升为无日期的长期结论。例如: + +- 某个端口正在监听; +- 某个服务“运行中”; +- 某个提交号是当前基线; +- 某个验证在某天通过; +- 某个 APK 体积是多少; +- 某个密钥尚未配置。 + +这些内容建议后续拆到 `05.运行运维/`,并保留: + +- 核验日期; +- 核验方式; +- 适用范围; +- 是否仍需复核。 + +其中,2026-08-03 的首轮运行基线和验证事实,现已迁入: + +- [2026-08-03运行基线与验证记录](../05.运行运维/01.2026-08-03运行基线与验证记录.md) + +## 4. 以今天视角看,哪些内容已经偏“运行记录” + +以下内容不适合继续放在项目总览层,而更适合作为运行/联调记录保存: + +- 在线架构图中的实时地址和端口; +- “当前在线”组件状态表; +- 某次端到端验证通过时间点; +- 某次已知问题与部署待办。 + +它们不是不重要,而是时效性太强。把这些内容长期放在总览文档中,会导致读者混淆“结构性结论”和“当日运行事实”。 + +## 5. 当前阶段的文档判断 + +从文档治理角度看,当前项目已经到了必须做三件事的阶段: + +1. 把项目总览、状态、职责和程序清单从根目录散点文档收拢成统一入口; +2. 维护已经迁入 `agent_ops/02.架构设计/` 的当前有效设计文档,作为新的架构主入口; +3. 把 `lineup-app/迭代/` 中的计划、设计评审和验收评审建立清晰索引。 + +## 6. 对原文档时效性的判断 + +### 6.1 仍可直接沿用的部分 + +- 总体阶段判断; +- 项目组件分层认识; +- 运行主线已经跑通这一结论; +- 已知限制需要继续收敛的方向。 + +### 6.2 需要带日期沿用的部分 + +- 服务在线状态; +- 具体端口、地址、版本、提交号; +- 某次真机、浏览器、Adapter 验收记录。 + +### 6.3 本轮不直接沿用为主入口的部分 + +- 细粒度部署说明; +- 历史兼容性论证全文; +- 逐文件组件明细。 + +这些内容后续分别下沉到 `05.运行运维/`、`02.架构设计/`、`04.程序清单/` 更合适。 diff --git a/01.项目总览/03.仓库职责与协作边界.md b/01.项目总览/03.仓库职责与协作边界.md new file mode 100644 index 0000000..548a2fa --- /dev/null +++ b/01.项目总览/03.仓库职责与协作边界.md @@ -0,0 +1,99 @@ +# 仓库职责与协作边界 + +**版本:** 2026-08-07 整合版 +**主要来源:** 根 `仓库职责说明.md`,辅以根 `README.md` + +## 1. 核心结论 + +当前 LineUp 主线的长期稳定分层,不是按“前后端”粗分,而是按能力边界分为三层: + +```text +lineup-app/ LineUp Runtime / Host / Interact / MiniApp +lineup-adapter/hermes/ Agent 平台适配层 +lineup-app-server/ 登录、同步、消息收发和服务接入层 +``` + +其中最应避免的事情是:为了图快,把某个层的问题一路下沉到不该负责的仓库。 + +## 2. `lineup-app/` 的职责 + +`lineup-app/` 是客户端主仓,负责用户真正使用到的交互运行时。 + +它长期应该承接: + +- Runtime; +- Interact 与主交互模式; +- MiniApp SDK; +- Tool Router; +- App Workspace; +- Surface / Capability 承载边界; +- Tauri / Web Host 装配。 + +它不应该承接: + +- Hermes 或其他 Agent 平台的私有 prompt / approval 语义; +- 登录、同步、消息中转服务逻辑; +- 平台专属网关状态机。 + +## 3. `lineup-adapter/hermes/` 的职责 + +`lineup-adapter/hermes/` 负责把具体 Agent 平台接到 LineUp 标准协议上。 + +它长期应该承接: + +- 平台私有协议与 LineUp 协议之间的桥接; +- approval / clarify / confirm 等平台私有交互的映射; +- `call_id`、选项 id、内部 request id 的解析与账本; +- 白名单化、安全化的 Agent 输出规范化。 + +它不应该承接: + +- Runtime 的长期业务状态机; +- MiniApp 工作区快照; +- AppServer 的登录、同步和服务职责; +- 直接修改客户端展示层语义。 + +## 4. `lineup-app-server/` 的职责 + +`lineup-app-server/` 是传输与服务边界层。 + +它长期应该承接: + +- 登录与鉴权; +- 消息发送与同步; +- Webhook 和基础服务端 API; +- 与 WuKongIM 等基础设施对接; +- Web Reference Host 的服务端部分。 + +它不应该承接: + +- 高层 `lineup.v1` 交互语义解析; +- Runtime Tool 生命周期; +- Agent 平台私有协议兼容逻辑; +- 前端具体展示决策。 + +## 5. 改动落点判断顺序 + +整合根文档后,可以把跨仓改动判断顺序压缩为三步: + +1. 如果是 LineUp 自己的标准能力、运行时能力、交互协议或工作区语义,优先落在 `lineup-app/`。 +2. 如果是某个 Agent 平台的私有兼容问题,优先落在 `lineup-adapter/hermes/`。 +3. 如果是登录、同步、消息收发或服务接入问题,优先落在 `lineup-app-server/`。 + +## 6. 为什么这份文档仍具有时效性 + +与“某个服务今天是否在线”不同,仓库职责文档主要表达的是边界约束。只要主线架构未重组,这类判断就比运行状态更稳定。 + +因此,本轮整合后认为以下内容仍然具有较强时效性: + +- 三仓主边界划分; +- Runtime / Adapter / AppServer 的分层原则; +- 跨仓改动的优先判断顺序。 + +## 7. 后续如何使用本文件 + +后续在维护 `agent_ops/02.架构设计/` 和 `agent_ops/03.迭代规划/` 时,这份文档可以作为边界校验基线: + +- 设计方案是否把平台私有逻辑错误下沉到了 Runtime; +- 迭代目标是否混淆了客户端、适配层和服务端职责; +- 文档命名和归档是否按这条边界来组织。 diff --git a/02.架构设计/00.整合说明/01.设计整合说明与优先级.md b/02.架构设计/00.整合说明/01.设计整合说明与优先级.md new file mode 100644 index 0000000..46196a2 --- /dev/null +++ b/02.架构设计/00.整合说明/01.设计整合说明与优先级.md @@ -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. 后续清理方向 + +本轮已经完成物理迁移;后续不再恢复旧的两处分散设计目录。 diff --git a/02.架构设计/00.整合说明/02.设计冲突与演进顺序.md b/02.架构设计/00.整合说明/02.设计冲突与演进顺序.md new file mode 100644 index 0000000..ed51bb0 --- /dev/null +++ b/02.架构设计/00.整合说明/02.设计冲突与演进顺序.md @@ -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. 这份文档的作用 + +这份文档不是简单说明“哪个文件更新”,而是明确: + +- 哪些冲突是设计演进带来的; +- 现在为什么优先采用当前有效设计; +- 早期文档应该怎样被使用,而不是继续和当前设计平级竞争。 diff --git a/02.架构设计/01.当前有效设计/02.正式方案/app_final_design.md b/02.架构设计/01.当前有效设计/02.正式方案/app_final_design.md new file mode 100644 index 0000000..f8d4180 --- /dev/null +++ b/02.架构设计/01.当前有效设计/02.正式方案/app_final_design.md @@ -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 汇总为动态 Inventory;Agent 只能调用当前 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、请求保存文件。 + +Effect:Runtime 决定执行的副作用 + 网络发送、文件选择、录音、通知、创建 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; + enable(appScope: AppScope): Promise; + disable(appScope: AppScope, options?: DisableAppOptions): Promise; + update(appScope: AppScope, targetVersion?: string): Promise; + rollback(appScope: AppScope): Promise; + remove(appScope: AppScope, options?: RemoveAppOptions): Promise; + launch(request: LaunchAppRequest): Promise; + closeInstance(instanceID: AppInstanceID): Promise; +} +``` + +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 App(Core 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 实现与 M0~M4 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; + + subscribe( + options: { + conversation_id?: ConversationID; + types?: readonly AgentMessageType[]; + include_pending?: boolean; + }, + handler: (message: AgentAppMessage) => Promise | void, + ): Unsubscribe; + + acknowledge(message_id: string): Promise; +} +``` + +投递语义: + +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 SDK;Installed 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 { + 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; +} + +interface AppToolAPI { + onInvoke(listener: (call: AppToolInvocation) => Promise): Unsubscribe; + progress(request: { call_id: ToolCallID; progress: ToolProgress }): Promise; + complete(request: { call_id: ToolCallID; result: JsonValue }): Promise; + fail(request: { call_id: ToolCallID; code: AppToolErrorCode; message?: string }): Promise; +} + +interface AppNavigationAPI { + listAvailable(): readonly AppSummary[]; + launch(request: LaunchAppRequest): Promise; + focus(instanceID: AppInstanceID): Promise; + close(instanceID: AppInstanceID): Promise; + openDefaultApp(): Promise; +} +``` + +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 { + 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(request: { + capability: CapabilityName; + purpose: string; + input: JsonValue; + scope?: AppScope; + }): Promise>; +} +``` + +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 直接联系 Agent:Runtime 持久化 +`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 行为。 + +### F1:Runtime 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 不读取旧协议字段,也不需要同步升级远端。 + +### F2:App 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 和恢复策略。 + +### F3:Tool Registry、Inventory 与 SDK Inbox + +- 解析 Manifest Tool/Subscription 并计算可见性; +- 在 Runtime 连接与状态变化时同步 revisioned Inventory 给 Agent;Tool 可声明是否需要启动 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 的平行通道。 diff --git a/02.架构设计/01.当前有效设计/02.正式方案/lineup-app-layer-architecture.md b/02.架构设计/01.当前有效设计/02.正式方案/lineup-app-layer-architecture.md new file mode 100644 index 0000000..514bb39 --- /dev/null +++ b/02.架构设计/01.当前有效设计/02.正式方案/lineup-app-layer-architecture.md @@ -0,0 +1,132 @@ +# LineUp App 层架构与迁移记录 + +**版本:** 2.0(Runtime 基线) +**状态:** 当前实现边界与历史迁移记录 +**日期:** 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-R1:Runtime 托管 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. 历史迁移的保留价值 + +早期 M0~M4 工作建立了当前 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 的迁移价值。 | diff --git a/02.架构设计/01.当前有效设计/02.正式方案/lineup-runtime-sdk-architecture.md b/02.架构设计/01.当前有效设计/02.正式方案/lineup-runtime-sdk-architecture.md new file mode 100644 index 0000000..aaf9acf --- /dev/null +++ b/02.架构设计/01.当前有效设计/02.正式方案/lineup-runtime-sdk-architecture.md @@ -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 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; + enable(appScope: AppScope): Promise; + disable(appScope: AppScope, options?: DisableAppOptions): Promise; + update(appScope: AppScope, targetVersion?: string): Promise; + rollback(appScope: AppScope): Promise; + remove(appScope: AppScope, options?: RemoveAppOptions): Promise; + + launch(request: LaunchAppRequest): Promise; + closeInstance(instanceID: AppInstanceID): Promise; +} + +interface RuntimeAppOrchestrator { + launch(request: LaunchAppRequest): Promise; + foreground(instanceID: AppInstanceID): Promise; + background(instanceID: AppInstanceID, reason?: string): Promise; + suspend(instanceID: AppInstanceID, reason?: string): Promise; + restore(instanceID: AppInstanceID): Promise; + closeInstance(instanceID: AppInstanceID): Promise; + 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 activation,Runtime 在写入 `starting → ready | failed | cancelled` 后,通过同一 call_id 的受控 +protocol receipt / progress 通知 Agent。`activation_ready` 表示 App 已能接收后续 `app_ready` Tool;Agent 可据此顺序 +编排调用,但 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/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 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 { + 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; + + subscribe( + options: { + conversation_id?: ConversationID; + types?: readonly AgentMessageType[]; + include_pending?: boolean; + }, + handler: (message: AgentAppMessage) => Promise | void, + ): Unsubscribe; + + acknowledge(message_id: string): Promise; +} +``` + +投递语义: + +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; +} + +interface AppToolAPI { + onInvoke(listener: (call: AppToolInvocation) => Promise): Unsubscribe; + progress(request: { call_id: ToolCallID; progress: ToolProgress }): Promise; + complete(request: { call_id: ToolCallID; result: JsonValue }): Promise; + fail(request: { call_id: ToolCallID; code: AppToolErrorCode; message?: string }): Promise; +} + +interface AppNavigationAPI { + listAvailable(): readonly AppSummary[]; + launch(request: LaunchAppRequest): Promise; + focus(instanceID: AppInstanceID): Promise; + background(instanceID: AppInstanceID, reason?: string): Promise; + suspend(instanceID: AppInstanceID, reason?: string): Promise; + close(instanceID: AppInstanceID): Promise; + openDefaultApp(): Promise; +} +``` + +`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 { + 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(request: { + capability: CapabilityName; + purpose: string; + input: JsonValue; + scope?: AppScope; + }): Promise>; +} +``` + +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 + +params,SDK 再在 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 回归行为。 + +### 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.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` 作为本地路由字段;供应商身份和全局命名空间将在后续生态阶段通过独立、版本化的兼容设计重新引入。 +- 当前 M0~M4 实现可作为 Runtime 的迁移基础,但其现有文件边界不是最终 Runtime/App/SDK 边界。 diff --git a/02.架构设计/01.当前有效设计/02.正式方案/lineup-ui-surface-protocol.md b/02.架构设计/01.当前有效设计/02.正式方案/lineup-ui-surface-protocol.md new file mode 100644 index 0000000..604b734 --- /dev/null +++ b/02.架构设计/01.当前有效设计/02.正式方案/lineup-ui-surface-protocol.md @@ -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 使用解析器后仍须 sanitizer;HTML Surface 只能放进 sandbox `srcdoc`。 +7. 生产环境应只接受已签名或在 Agent allowlist 中的 bundle hash;Reference Host 先以严格 sandbox 保障隔离,并保留 `app.integrity` 字段用于升级。 + +## 3. 消息类型 + +所有消息均使用既有 `lineup.v1` 信封。`conversation_id`、`id`、`sender`、`target` 的规则不变。 + +| Type | 方向 | 含义 | +|---|---|---| +| `lineup.v1.ui.open` | Agent → Client | 创建或替换一个 Surface 实例 | +| `lineup.v1.ui.patch` | Agent → Client | 向已打开实例推送新的状态;不可注入或替换代码 | +| `lineup.v1.ui.close` | Agent → Client | 关闭实例 | +| `lineup.v1.ui.event` | Client → Agent | Surface 中的用户事件或用户关闭事件 | +| `lineup.v1.client.inventory` | Client → Agent | 当前 Host 的 revisioned 最小接口清单:标准组件、应用中心已启用 app Surface 与 policy 筛选后的 capability | +| `lineup.v1.app.list` | Client → Agent | 可选的兼容 capability-only 投影;不得替代完整 inventory 或宣称安装/授权 | +| `lineup.v1.app.call` | Agent → Client | 请求调用一个声明过的 capability | +| `lineup.v1.app.result` | Client → Agent | 调用完成、拒绝、失败或不支持的结果 | + +未知的 `ui.*` 或 `app.*` type 只能安全显示或回复 `unsupported`,不得执行。 + +## 4. UI Surface 合约 + +### 4.1 打开 + +```json +{ + "v": 1, + "id": "msg_ui_01", + "type": "lineup.v1.ui.open", + "conversation_id": "conv_01", + "sender": {"kind": "agent", "id": "agent_hermes_main"}, + "payload": { + "instance_id": "weather.dashboard.01", + "app": { + "id": "com.lineup.weather.dashboard", + "name": "天气面板", + "version": "1.0.0", + "integrity": "sha256-BASE64_DIGEST", + "html": "
", + "css": "main { padding: 16px }", + "js": "document.querySelector('#refresh').onclick=()=>LineUpSurface.event('refresh',{unit:'c'})" + }, + "state": {"city": "北京", "temperature": 22} + } +} +``` + +约束: + +- `instance_id` 在一个会话内唯一,格式为 `[A-Za-z0-9._:-]{1,128}`;相同 ID 的 `ui.open` 表示替换旧实例; +- `app.id` 为反向域名风格的稳定应用 ID,`version` 为 SemVer; +- `html`、`css`、`js` 是 Surface bundle;单个 bundle 的生产上限建议为 160 KiB,资源应使用经过 Artifact 管理和签名的本地引用,禁止任意公网 URL; +- `state` 必须是 JSON object。UI bundle 将在 `ready` 后和每次 `ui.patch` 收到它; +- **`ui.patch` 只能更新 `state`,绝不能更新 HTML/CSS/JS。** 若要升级 bundle,关闭旧实例并以新 `version` 打开新实例。 + +### 4.2 Surface bridge + +Host 注入唯一的全局对象: + +```js +LineUpSurface.event('refresh', { unit: 'c' }) +LineUpSurface.onState((state) => render(state)) +LineUpSurface.resize(document.documentElement.scrollHeight) +``` + +它只允许以下上行消息: + +```json +{ + "namespace": "lineup.surface.v1", + "type": "event", + "event": "refresh", + "data": {"unit": "c"} +} +``` + +Host 将其转换为: + +```json +{ + "type": "lineup.v1.ui.event", + "payload": { + "instance_id": "weather.dashboard.01", + "event": "refresh", + "data": {"unit": "c"} + } +} +``` + +`event` 采用 `[A-Za-z][A-Za-z0-9._:-]{0,63}`,`data` 必须为小于 16 KiB 的 JSON object。Surface 不拥有 `app.call` bridge。 + +### 4.3 状态更新与关闭 + +```json +{"type":"lineup.v1.ui.patch","payload":{"instance_id":"weather.dashboard.01","state":{"city":"上海","temperature":28}}} +``` + +```json +{"type":"lineup.v1.ui.close","payload":{"instance_id":"weather.dashboard.01","reason":"completed"}} +``` + +用户主动关闭时 Client 应回送 `ui.event`,其中 `event` 为 `close`。 + +## 5. App Capability 合约 + +### 5.1 动态 Client Inventory + +客户端在会话建立完成后,以及应用中心的 app 启用/停用/升级、Host policy 或 capability 可见性变化时,向**对应 Agent 会话**发送 `client.inventory`。它有单调递增或不可复用的 `revision`;Agent 必须用最新 revision 决定可请求的组件、Surface 和能力。收到撤销后的旧 app id、Surface 或 capability 引用时,Client 只返回确定的 `unsupported / revoked` 结果,绝不回退到执行或加载远端 bundle。 + +```json +{ + "type": "lineup.v1.client.inventory", + "payload": { + "revision": "catalog-42", + "standard_components": [ + {"id":"choice","version":"1"}, + {"id":"confirm","version":"1"}, + {"id":"input","version":"1"} + ], + "applications": [ + { + "id":"com.lineup.svg-canvas", + "version":"1.0.0", + "surfaces":[{"id":"canvas","events":["draw","resize","snapshot"]}] + } + ], + "capabilities": [ + {"name":"artifact.save","version":"1.0","risk":"user_confirmation"} + ] + } +} +``` + +`applications` 只能列出应用中心中**已启用且已在本地完成相应阶段验证**的应用,不包含 bundle 源码、安装来源、登录态、文件路径或用户内容。SVG 画板这样的 app 只允许 Agent 以 app id 创建受限 Surface,并通过用户触发的 `ui.event(draw / resize / snapshot)` 获取声明性画布事件;inventory 不授予远程脚本注入、读取宿主 DOM 或本机能力权限。 + +### 5.2 Capability 声明(`app.list` 兼容投影) + +客户端可在 inventory 之后或 capability 变化时发送 `app.list` 作为仅包含 capability 的兼容投影。只有 inventory / app.list 中声明且仍未被撤销的 `capability` 才能被请求;声明从不等同于授权。 + +```json +{ + "type": "lineup.v1.app.list", + "payload": { + "capabilities": [ + {"name":"app.open_url","version":"1.0","risk":"user_confirmation","input_schema":{"type":"object","required":["url"]}}, + {"name":"clipboard.write","version":"1.0","risk":"user_confirmation","input_schema":{"type":"object","required":["text"]}}, + {"name":"device.pick_file","version":"1.0","risk":"system_permission","input_schema":{"type":"object"}} + ] + } +} +``` + +风险必须是下列之一:`display_only`、`user_confirmation`、`system_permission`、`restricted`。后两者不能被记住为永久授权,且必须经过原生系统权限或额外身份校验。 + +### 5.3 调用与结果 + +```json +{ + "type": "lineup.v1.app.call", + "payload": { + "call_id": "call_open_docs_01", + "capability": "app.open_url", + "reason": "打开部署文档供你核对", + "expires_at": "2026-08-02T12:10:00Z", + "arguments": {"url": "https://example.com/docs"} + } +} +``` + +Client 必须向用户说明 capability 和 `reason`,收到确认后再执行,随后使用相同 `call_id` 回传: + +```json +{ + "type": "lineup.v1.app.result", + "payload": { + "call_id": "call_open_docs_01", + "status": "completed", + "result": {"opened": true} + } +} +``` + +`status`:`completed | rejected | cancelled | expired | unsupported | failed`。同一 `call_id` 必须幂等;调用记录与授权决定应由 Gateway 审计。 + +## 6. Reference Host 当前实现与阶段边界 + +截至 2026-08-03,Tauri Reference Host 已完成 M0 和 M1-01~M1-03:严格 Envelope / Kernel / Store / Renderer 边界,Markdown、status/progress/error、choice、confirm、input 的可信原生渲染与本地状态恢复。它**尚未**实现 `client.inventory` 发送、应用中心、`ui.open / patch / close` 的 Surface Host、远端或本地 app bundle 加载、`app.call` 执行、Capability Registry 或 `app.result` 网络回传。 + +因此,这份协议中的 Surface、inventory 与 Capability 段落是后续 M2~M4 的正式契约,不是当前 Web Chat 已开放的功能。M2 建立应用中心本地启用注册表与隔离 Surface;M3 才生成/更新并通知 `client.inventory`,再实施 Capability policy;M4 负责下载、完整性校验、缓存、回滚与 Host 一致性。当前只以 Tauri Desktop Host 与同代码 Web Reference Host 实现这些边界;不规划独立 Android/iOS/Wails 客户端。 diff --git a/02.架构设计/01.当前有效设计/02.正式方案/运行时与智能体工具.md b/02.架构设计/01.当前有效设计/02.正式方案/运行时与智能体工具.md new file mode 100644 index 0000000..6f2fbe3 --- /dev/null +++ b/02.架构设计/01.当前有效设计/02.正式方案/运行时与智能体工具.md @@ -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,而不是第二份手写 Manifest:MiniApp 用 `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 上下文) + │ ├── activation(starting / 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 operation,Pomodoro 进入 focusing 并被动显示 + +用户在 IM 中说:停止或结束这次专注 + → 远端 Agent 调用 pomodoro.interrupt + → 若仍在 starting,Runtime 取消启动;若已 focusing,Runtime 原子收口当前 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 Action;Runtime 可以让它复用创建 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 已冻结的直接开始模型。 diff --git a/02.架构设计/01.当前有效设计/APP架构设计.md b/02.架构设计/01.当前有效设计/APP架构设计.md new file mode 100644 index 0000000..0fcdaa4 --- /dev/null +++ b/02.架构设计/01.当前有效设计/APP架构设计.md @@ -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 Provider:notice / choice / confirm / input +``` + +当前实现仍保留兼容名称: + +```text +产品概念:Interact MiniApp +当前 app_scope:chat +当前目录: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; + complete(call_id: string, result: JsonObject): Promise; + fail(call_id: string, failure: MiniAppToolFailure): Promise; + cancel(call_id: string, reason?: string): Promise; +} +``` + +`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; + requestBackground(): Promise; + requestClose(reason?: string): Promise; +} +``` + +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 中声明 Tool;Agent 只能调用当前 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):跨文档详细契约来源; + 若与本文的产品术语或当前迭代顺序冲突,以本文为准并同步更新上游方案。 diff --git a/02.架构设计/01.当前有效设计/README.md b/02.架构设计/01.当前有效设计/README.md new file mode 100644 index 0000000..f1acdae --- /dev/null +++ b/02.架构设计/01.当前有效设计/README.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 设计的主入口。 diff --git a/02.架构设计/02.整合结论/01.当前权威架构基线.md b/02.架构设计/02.整合结论/01.当前权威架构基线.md new file mode 100644 index 0000000..253f586 --- /dev/null +++ b/02.架构设计/02.整合结论/01.当前权威架构基线.md @@ -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.历史设计归档/` 仅用于追溯历史背景,不再作为当前设计入口。 diff --git a/02.架构设计/02.整合结论/02.Agent工具与运行时边界.md b/02.架构设计/02.整合结论/02.Agent工具与运行时边界.md new file mode 100644 index 0000000..2943de9 --- /dev/null +++ b/02.架构设计/02.整合结论/02.Agent工具与运行时边界.md @@ -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 边界来裁决,而不是回退到早期直连协议思路。 diff --git a/02.架构设计/90.历史设计归档/00.阶段记录/stage-01-需求分析与架构设计.md b/02.架构设计/90.历史设计归档/00.阶段记录/stage-01-需求分析与架构设计.md new file mode 100644 index 0000000..4728974 --- /dev/null +++ b/02.架构设计/90.历史设计归档/00.阶段记录/stage-01-需求分析与架构设计.md @@ -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 壳子 +- 结构化消息返回,不是自然语言裸发 diff --git a/02.架构设计/90.历史设计归档/00.阶段记录/stage-02-App端交互原型.md.delayed b/02.架构设计/90.历史设计归档/00.阶段记录/stage-02-App端交互原型.md.delayed new file mode 100644 index 0000000..5f82a94 --- /dev/null +++ b/02.架构设计/90.历史设计归档/00.阶段记录/stage-02-App端交互原型.md.delayed @@ -0,0 +1,44 @@ +# Stage 2:App 端交互原型 + +**状态:** 🟡 进行中 +**时间:** 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 主题偏好 diff --git a/02.架构设计/90.历史设计归档/00.阶段记录/stage-02-中转服务器方案设计.md b/02.架构设计/90.历史设计归档/00.阶段记录/stage-02-中转服务器方案设计.md new file mode 100644 index 0000000..7da24ac --- /dev/null +++ b/02.架构设计/90.历史设计归档/00.阶段记录/stage-02-中转服务器方案设计.md @@ -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/)。 diff --git a/02.架构设计/90.历史设计归档/01.前期分析与设计/architecture-design.md b/02.架构设计/90.历史设计归档/01.前期分析与设计/architecture-design.md new file mode 100644 index 0000000..b688ba3 --- /dev/null +++ b/02.架构设计/90.历史设计归档/01.前期分析与设计/architecture-design.md @@ -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 Server,Action = MCP Tool | — | + +--- + +## 四、第一版技术栈 + +| 端 | 选型 | +|----|------| +| Agent 插件 | 按 Agent 框架选择语言(Hermes 用 Python,OpenClaw 待定) | +| 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 渲染) diff --git a/02.架构设计/90.历史设计归档/01.前期分析与设计/archived/lineup-im-agent-interaction-platform-plan-legacy.md b/02.架构设计/90.历史设计归档/01.前期分析与设计/archived/lineup-im-agent-interaction-platform-plan-legacy.md new file mode 100644 index 0000000..3ea5a67 --- /dev/null +++ b/02.架构设计/90.历史设计归档/01.前期分析与设计/archived/lineup-im-agent-interaction-platform-plan-legacy.md @@ -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.116(Tailscale) +``` + +该环境在本方案中的职责: + +- 以唐僧叨叨作为 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 1:LineUp 协议核心与最小 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 2:LineUp 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 3:Hermes 首个正式 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 5:WuKongIM 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 业务系统。 diff --git a/02.架构设计/90.历史设计归档/01.前期分析与设计/archived/mvp-spec.md b/02.架构设计/90.历史设计归档/01.前期分析与设计/archived/mvp-spec.md new file mode 100644 index 0000000..00fef13 --- /dev/null +++ b/02.架构设计/90.历史设计归档/01.前期分析与设计/archived/mvp-spec.md @@ -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 所在机器运行 Adapter,Adapter 用配对码建立出站 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 需求 | +| 服务端 | TypeScript(Fastify/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 = { + 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 初始分工(两人) + +| 负责人 | 主责 | I1–I3 输出 | +|---|---|---| +| 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 Done:MVP 演示 + +一次合格的演示应从零完成以下闭环: + +1. 用户注册并创建一个 Workspace。 +2. 用户通过一次性配对码连接运行在另一台机器上的模拟或真实 Agent。 +3. 用户创建一个任务,关闭浏览器后再打开,任务与会话仍完整存在。 +4. Agent 上报至少两个进度变化,并创建一个子任务。 +5. Agent 发起一个 `approval` 请求,用户在收件箱处理后,Agent 收到响应并继续执行。 +6. 断开 Adapter,界面在合理时间内显示失联;重连后不重复执行或丢失审批响应。 +7. Agent 产出一条结果消息和一个 Artifact,任务成为 `succeeded`,全部过程在事件时间线中可追溯。 + +完成这套闭环后,再根据试用反馈决定是优先扩充移动端、增加框架 Adapter,还是深化任务协作能力。 diff --git a/02.架构设计/90.历史设计归档/01.前期分析与设计/design-v1.md b/02.架构设计/90.历史设计归档/01.前期分析与设计/design-v1.md new file mode 100644 index 0000000..604636d --- /dev/null +++ b/02.架构设计/90.历史设计归档/01.前期分析与设计/design-v1.md @@ -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 Server,App 端手动输入 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 ├─ Action(open / 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 为第一期) +- 框架级工具路由替代字典路由 diff --git a/02.架构设计/90.历史设计归档/01.前期分析与设计/tool-action-design.md b/02.架构设计/90.历史设计归档/01.前期分析与设计/tool-action-design.md new file mode 100644 index 0000000..cc13afe --- /dev/null +++ b/02.架构设计/90.历史设计归档/01.前期分析与设计/tool-action-design.md @@ -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 ├─ Action(open / draw / add / select) + ├─ MCP Tool B ├─ Action + └─ MCP Tool C └─ Action +``` + +MCP 里每一个 Tool 是无状态的一次性调用单元。LineUp 里 Tool 多了一层容器层。app 类型让有状态工具在逻辑上是一个整体,Agent 看到 canvas 就知道这是一个画板应用,再看 actions 就知道具体可以做什么。 diff --git a/02.架构设计/90.历史设计归档/02.正式方案快照/app_final_design.md b/02.架构设计/90.历史设计归档/02.正式方案快照/app_final_design.md new file mode 100644 index 0000000..c981e50 --- /dev/null +++ b/02.架构设计/90.历史设计归档/02.正式方案快照/app_final_design.md @@ -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 汇总为动态 Inventory;Agent 只能调用当前 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、请求保存文件。 + +Effect:Runtime 决定执行的副作用 + 网络发送、文件选择、录音、通知、创建 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; + enable(appScope: AppScope): Promise; + disable(appScope: AppScope, options?: DisableAppOptions): Promise; + update(appScope: AppScope, targetVersion?: string): Promise; + rollback(appScope: AppScope): Promise; + remove(appScope: AppScope, options?: RemoveAppOptions): Promise; + launch(request: LaunchAppRequest): Promise; + closeInstance(instanceID: AppInstanceID): Promise; +} +``` + +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 App(Core 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 实现与 M0~M4 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; + + subscribe( + options: { + conversation_id?: ConversationID; + types?: readonly AgentMessageType[]; + include_pending?: boolean; + }, + handler: (message: AgentAppMessage) => Promise | void, + ): Unsubscribe; + + acknowledge(message_id: string): Promise; +} +``` + +投递语义: + +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 SDK;Installed 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 { + 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; +} + +interface AppToolAPI { + onInvoke(listener: (call: AppToolInvocation) => Promise): Unsubscribe; + progress(request: { call_id: ToolCallID; progress: ToolProgress }): Promise; + complete(request: { call_id: ToolCallID; result: JsonValue }): Promise; + fail(request: { call_id: ToolCallID; code: AppToolErrorCode; message?: string }): Promise; +} + +interface AppNavigationAPI { + listAvailable(): readonly AppSummary[]; + launch(request: LaunchAppRequest): Promise; + focus(instanceID: AppInstanceID): Promise; + close(instanceID: AppInstanceID): Promise; + openDefaultApp(): Promise; +} +``` + +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(request: { + capability: CapabilityName; + purpose: string; + input: JsonValue; + scope?: AppScope; + }): Promise>; +} +``` + +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 行为。 + +### F1:Runtime 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 不读取旧协议字段,也不需要同步升级远端。 + +### F2:App 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 和恢复策略。 + +### F3:Tool Registry、Inventory 与 SDK Inbox + +- 解析 Manifest Tool/Subscription 并计算可见性; +- 在 Runtime 连接与状态变化时同步 revisioned Inventory 给 Agent;Tool 可声明是否需要启动 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 的平行通道。 diff --git a/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-app-layer-architecture.md b/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-app-layer-architecture.md new file mode 100644 index 0000000..d0f2b55 --- /dev/null +++ b/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-app-layer-architecture.md @@ -0,0 +1,132 @@ +# LineUp App 层架构与迁移记录 + +**版本:** 2.0(Runtime 基线) +**状态:** 当前实现边界与历史迁移记录 +**日期:** 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-R1:Runtime 托管 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. 历史迁移的保留价值 + +早期 M0~M4 工作建立了当前 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 的迁移价值。 | diff --git a/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-runtime-sdk-architecture.md b/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-runtime-sdk-architecture.md new file mode 100644 index 0000000..047bc9f --- /dev/null +++ b/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-runtime-sdk-architecture.md @@ -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 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; + enable(appScope: AppScope): Promise; + disable(appScope: AppScope, options?: DisableAppOptions): Promise; + update(appScope: AppScope, targetVersion?: string): Promise; + rollback(appScope: AppScope): Promise; + remove(appScope: AppScope, options?: RemoveAppOptions): Promise; + + launch(request: LaunchAppRequest): Promise; + closeInstance(instanceID: AppInstanceID): Promise; +} + +interface RuntimeAppOrchestrator { + launch(request: LaunchAppRequest): Promise; + foreground(instanceID: AppInstanceID): Promise; + background(instanceID: AppInstanceID, reason?: string): Promise; + suspend(instanceID: AppInstanceID, reason?: string): Promise; + restore(instanceID: AppInstanceID): Promise; + closeInstance(instanceID: AppInstanceID): Promise; + 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 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 { + 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; + + subscribe( + options: { + conversation_id?: ConversationID; + types?: readonly AgentMessageType[]; + include_pending?: boolean; + }, + handler: (message: AgentAppMessage) => Promise | void, + ): Unsubscribe; + + acknowledge(message_id: string): Promise; +} +``` + +投递语义: + +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; +} + +interface AppToolAPI { + onInvoke(listener: (call: AppToolInvocation) => Promise): Unsubscribe; + progress(request: { call_id: ToolCallID; progress: ToolProgress }): Promise; + complete(request: { call_id: ToolCallID; result: JsonValue }): Promise; + fail(request: { call_id: ToolCallID; code: AppToolErrorCode; message?: string }): Promise; +} + +interface AppNavigationAPI { + listAvailable(): readonly AppSummary[]; + launch(request: LaunchAppRequest): Promise; + focus(instanceID: AppInstanceID): Promise; + background(instanceID: AppInstanceID, reason?: string): Promise; + suspend(instanceID: AppInstanceID, reason?: string): Promise; + close(instanceID: AppInstanceID): Promise; + openDefaultApp(): Promise; +} +``` + +`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 数据和可恢复 snapshot,Runtime 负责配额、升级迁移、禁用和移除时的清理。Capability 必须通过统一请求接口: + +```ts +interface CapabilityRequestAPI { + request(request: { + capability: CapabilityName; + purpose: string; + input: JsonValue; + scope?: AppScope; + }): Promise>; +} +``` + +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 回归行为。 + +### 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,恢复后可幂等补读。 + +### 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` 作为本地路由字段;供应商身份和全局命名空间将在后续生态阶段通过独立、版本化的兼容设计重新引入。 +- 当前 M0~M4 实现可作为 Runtime 的迁移基础,但其现有文件边界不是最终 Runtime/App/SDK 边界。 diff --git a/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-ui-surface-protocol.md b/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-ui-surface-protocol.md new file mode 100644 index 0000000..36bd0f5 --- /dev/null +++ b/02.架构设计/90.历史设计归档/02.正式方案快照/lineup-ui-surface-protocol.md @@ -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 使用解析器后仍须 sanitizer;HTML Surface 只能放进 sandbox `srcdoc`。 +7. 生产环境应只接受已签名或在 Agent allowlist 中的 bundle hash;Reference Host 先以严格 sandbox 保障隔离,并保留 `app.integrity` 字段用于升级。 + +## 3. 消息类型 + +所有消息均使用既有 `lineup.v1` 信封。`conversation_id`、`id`、`sender`、`target` 的规则不变。 + +| Type | 方向 | 含义 | +|---|---|---| +| `lineup.v1.ui.open` | Agent → Client | 创建或替换一个 Surface 实例 | +| `lineup.v1.ui.patch` | Agent → Client | 向已打开实例推送新的状态;不可注入或替换代码 | +| `lineup.v1.ui.close` | Agent → Client | 关闭实例 | +| `lineup.v1.ui.event` | Client → Agent | Surface 中的用户事件或用户关闭事件 | +| `lineup.v1.client.inventory` | Client → Agent | 当前 Host 的 revisioned 最小接口清单:标准组件、应用中心已启用 app Surface 与 policy 筛选后的 capability | +| `lineup.v1.app.list` | Client → Agent | 可选的兼容 capability-only 投影;不得替代完整 inventory 或宣称安装/授权 | +| `lineup.v1.app.call` | Agent → Client | 请求调用一个声明过的 capability | +| `lineup.v1.app.result` | Client → Agent | 调用完成、拒绝、失败或不支持的结果 | + +未知的 `ui.*` 或 `app.*` type 只能安全显示或回复 `unsupported`,不得执行。 + +## 4. UI Surface 合约 + +### 4.1 打开 + +```json +{ + "v": 1, + "id": "msg_ui_01", + "type": "lineup.v1.ui.open", + "conversation_id": "conv_01", + "sender": {"kind": "agent", "id": "agent_hermes_main"}, + "payload": { + "instance_id": "weather.dashboard.01", + "app": { + "id": "com.lineup.weather.dashboard", + "name": "天气面板", + "version": "1.0.0", + "integrity": "sha256-BASE64_DIGEST", + "html": "
", + "css": "main { padding: 16px }", + "js": "document.querySelector('#refresh').onclick=()=>LineUpSurface.event('refresh',{unit:'c'})" + }, + "state": {"city": "北京", "temperature": 22} + } +} +``` + +约束: + +- `instance_id` 在一个会话内唯一,格式为 `[A-Za-z0-9._:-]{1,128}`;相同 ID 的 `ui.open` 表示替换旧实例; +- `app.id` 为反向域名风格的稳定应用 ID,`version` 为 SemVer; +- `html`、`css`、`js` 是 Surface bundle;单个 bundle 的生产上限建议为 160 KiB,资源应使用经过 Artifact 管理和签名的本地引用,禁止任意公网 URL; +- `state` 必须是 JSON object。UI bundle 将在 `ready` 后和每次 `ui.patch` 收到它; +- **`ui.patch` 只能更新 `state`,绝不能更新 HTML/CSS/JS。** 若要升级 bundle,关闭旧实例并以新 `version` 打开新实例。 + +### 4.2 Surface bridge + +Host 注入唯一的全局对象: + +```js +LineUpSurface.event('refresh', { unit: 'c' }) +LineUpSurface.onState((state) => render(state)) +LineUpSurface.resize(document.documentElement.scrollHeight) +``` + +它只允许以下上行消息: + +```json +{ + "namespace": "lineup.surface.v1", + "type": "event", + "event": "refresh", + "data": {"unit": "c"} +} +``` + +Host 将其转换为: + +```json +{ + "type": "lineup.v1.ui.event", + "payload": { + "instance_id": "weather.dashboard.01", + "event": "refresh", + "data": {"unit": "c"} + } +} +``` + +`event` 采用 `[A-Za-z][A-Za-z0-9._:-]{0,63}`,`data` 必须为小于 16 KiB 的 JSON object。Surface 不拥有 `app.call` bridge。 + +### 4.3 状态更新与关闭 + +```json +{"type":"lineup.v1.ui.patch","payload":{"instance_id":"weather.dashboard.01","state":{"city":"上海","temperature":28}}} +``` + +```json +{"type":"lineup.v1.ui.close","payload":{"instance_id":"weather.dashboard.01","reason":"completed"}} +``` + +用户主动关闭时 Client 应回送 `ui.event`,其中 `event` 为 `close`。 + +## 5. App Capability 合约 + +### 5.1 动态 Client Inventory + +客户端在会话建立完成后,以及应用中心的 app 启用/停用/升级、Host policy 或 capability 可见性变化时,向**对应 Agent 会话**发送 `client.inventory`。它有单调递增或不可复用的 `revision`;Agent 必须用最新 revision 决定可请求的组件、Surface 和能力。收到撤销后的旧 app id、Surface 或 capability 引用时,Client 只返回确定的 `unsupported / revoked` 结果,绝不回退到执行或加载远端 bundle。 + +```json +{ + "type": "lineup.v1.client.inventory", + "payload": { + "revision": "catalog-42", + "standard_components": [ + {"id":"choice","version":"1"}, + {"id":"confirm","version":"1"}, + {"id":"input","version":"1"} + ], + "applications": [ + { + "id":"com.lineup.svg-canvas", + "version":"1.0.0", + "surfaces":[{"id":"canvas","events":["draw","resize","snapshot"]}] + } + ], + "capabilities": [ + {"name":"artifact.save","version":"1.0","risk":"user_confirmation"} + ] + } +} +``` + +`applications` 只能列出应用中心中**已启用且已在本地完成相应阶段验证**的应用,不包含 bundle 源码、安装来源、登录态、文件路径或用户内容。SVG 画板这样的 app 只允许 Agent 以 app id 创建受限 Surface,并通过用户触发的 `ui.event(draw / resize / snapshot)` 获取声明性画布事件;inventory 不授予远程脚本注入、读取宿主 DOM 或本机能力权限。 + +### 5.2 Capability 声明(`app.list` 兼容投影) + +客户端可在 inventory 之后或 capability 变化时发送 `app.list` 作为仅包含 capability 的兼容投影。只有 inventory / app.list 中声明且仍未被撤销的 `capability` 才能被请求;声明从不等同于授权。 + +```json +{ + "type": "lineup.v1.app.list", + "payload": { + "capabilities": [ + {"name":"app.open_url","version":"1.0","risk":"user_confirmation","input_schema":{"type":"object","required":["url"]}}, + {"name":"clipboard.write","version":"1.0","risk":"user_confirmation","input_schema":{"type":"object","required":["text"]}}, + {"name":"device.pick_file","version":"1.0","risk":"system_permission","input_schema":{"type":"object"}} + ] + } +} +``` + +风险必须是下列之一:`display_only`、`user_confirmation`、`system_permission`、`restricted`。后两者不能被记住为永久授权,且必须经过原生系统权限或额外身份校验。 + +### 5.3 调用与结果 + +```json +{ + "type": "lineup.v1.app.call", + "payload": { + "call_id": "call_open_docs_01", + "capability": "app.open_url", + "reason": "打开部署文档供你核对", + "expires_at": "2026-08-02T12:10:00Z", + "arguments": {"url": "https://example.com/docs"} + } +} +``` + +Client 必须向用户说明 capability 和 `reason`,收到确认后再执行,随后使用相同 `call_id` 回传: + +```json +{ + "type": "lineup.v1.app.result", + "payload": { + "call_id": "call_open_docs_01", + "status": "completed", + "result": {"opened": true} + } +} +``` + +`status`:`completed | rejected | cancelled | expired | unsupported | failed`。同一 `call_id` 必须幂等;调用记录与授权决定应由 Gateway 审计。 + +## 6. Reference Host 当前实现与阶段边界 + +截至 2026-08-03,Tauri Reference Host 已完成 M0 和 M1-01~M1-03:严格 Envelope / Kernel / Store / Renderer 边界,Markdown、status/progress/error、choice、confirm、input 的可信原生渲染与本地状态恢复。它**尚未**实现 `client.inventory` 发送、应用中心、`ui.open / patch / close` 的 Surface Host、远端或本地 app bundle 加载、`app.call` 执行、Capability Registry 或 `app.result` 网络回传。 + +因此,这份协议中的 Surface、inventory 与 Capability 段落是后续 M2~M4 的正式契约,不是当前 Web Chat 已开放的功能。M2 建立应用中心本地启用注册表与隔离 Surface;M3 才生成/更新并通知 `client.inventory`,再实施 Capability policy;M4 负责下载、完整性校验、缓存、回滚与 Host 一致性。当前只以 Tauri Desktop Host 与同代码 Web Reference Host 实现这些边界;不规划独立 Android/iOS/Wails 客户端。 diff --git a/02.架构设计/README.md b/02.架构设计/README.md new file mode 100644 index 0000000..bcfbe6b --- /dev/null +++ b/02.架构设计/README.md @@ -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.历史设计归档/` diff --git a/03.迭代规划/00.迭代方法/01.迭代工作流与四步法.md b/03.迭代规划/00.迭代方法/01.迭代工作流与四步法.md new file mode 100644 index 0000000..a5d49c9 --- /dev/null +++ b/03.迭代规划/00.迭代方法/01.迭代工作流与四步法.md @@ -0,0 +1,120 @@ +# 迭代工作流与四步法 + +**版本:** 2026-08-07 +**适用范围:** 所有后续 LineUp 迭代 + +## 1. 为什么要统一四步法 + +从 `04A.agent_tool_route` 开始,一次迭代不再只是某个前端仓的局部改动,而可能同时联动: + +- Runtime +- Adapter +- AppServer +- 协议文档 +- Host 验收 +- 联调与证据 + +如果没有统一方法,很容易出现: + +- 规划写在一个地方; +- 评审写在另一个地方; +- 技术规范散落在实现仓; +- 跨项目边界没有统一判断依据。 + +因此,从现在开始,每次迭代都至少经过四个固定步骤。 + +## 2. 固定四步 + +### 第一步:规划 + +目标: + +- 说明为什么做这个迭代; +- 冻结范围边界; +- 说明涉及哪些项目和端; +- 给出完成标准和不在范围内的内容。 + +标准产物: + +- `01.迭代规划.md` + +### 第二步:设计评审 + +目标: + +- 用独立视角检查规划中的产品边界、架构边界、状态机和风险; +- 清理 P0~P3 问题; +- 把必须回填的结论反映回主规划文档。 + +标准产物: + +- `02.设计评审.md` +- 如有多轮,可继续编号:`03.设计评审(二).md`、`04.设计评审(三).md` + +### 第三步:技术实施规范 + +目标: + +- 把已经冻结的产品/架构结论映射到具体模块、协议、状态、存储、测试和验收实现; +- 明确每个项目各自承担什么修改; +- 说明跨仓联调和验证方式。 + +标准产物: + +- `05.技术实施规范.md` + +### 第四步:技术实施规范评审 + +目标: + +- 检查技术规范是否真实覆盖冻结边界; +- 识别实现层遗漏、跨仓接口风险、测试缺口和不可实施部分; +- 清理 P0~P3 技术问题。 + +标准产物: + +- `06.技术实施规范评审.md` +- 如有第二轮,可继续编号 + +## 3. 实施后的补充步骤 + +虽然用户当前强调的是四步法,但一个完整迭代通常还需要: + +- 实施 +- 验收评审 +- 证据归档 + +因此建议默认继续补齐: + +- `08.验收评审.md` +- `09.验收评审(二).md` +- `evidence/` + +## 4. 进入实现的门槛 + +每个迭代在开始大规模实现前,至少应满足: + +1. 主规划文档已写明范围与完成标准; +2. 设计评审中的 P0~P3 已清零; +3. 技术实施规范已把跨项目责任拆开; +4. 技术实施规范评审中的 P0~P3 已清零。 + +## 5. 状态定义 + +建议统一使用以下阶段状态: + +- `规划中` +- `设计评审中` +- `技术规范中` +- `技术评审中` +- `开发实施中` +- `待验收` +- `已完成` +- `已归档` + +## 6. 这套方法的核心收益 + +- 让每次迭代都有统一入口; +- 让跨项目修改不再只挂在某一个代码仓名下; +- 让设计结论、技术规范和验收标准之间形成可追溯链路; +- 让后续 agent 可以稳定接手迭代文档工作。 diff --git a/03.迭代规划/00.迭代方法/02.迭代目录与命名规范.md b/03.迭代规划/00.迭代方法/02.迭代目录与命名规范.md new file mode 100644 index 0000000..85b9b5a --- /dev/null +++ b/03.迭代规划/00.迭代方法/02.迭代目录与命名规范.md @@ -0,0 +1,62 @@ +# 迭代目录与命名规范 + +**版本:** 2026-08-07 +**适用范围:** `agent_ops/03.迭代规划/` 下的新迭代目录 + +## 1. 迭代目录命名 + +迭代目录统一使用: + +```text +<迭代编号>.<简短名称>/ +``` + +例如: + +- `04.runtime_workspace/` +- `04A.agent_tool_route/` +- `05.first_isolated_miniapp/` + +## 2. 每个迭代目录的推荐结构 + +```text +<迭代目录>/ +├── 01.迭代规划.md +├── 02.设计评审.md +├── 03.设计评审(二).md # 按需要增加 +├── 05.技术实施规范.md +├── 06.技术实施规范评审.md +├── 07.技术实施规范评审(二).md # 按需要增加 +├── 08.验收评审.md +├── 09.验收评审(二).md # 按需要增加 +└── evidence/ +``` + +## 3. 命名原则 + +- 主规划始终用 `01.迭代规划.md` +- 第一轮设计评审固定用 `02.设计评审.md` +- 技术实施规范固定用 `05.技术实施规范.md` +- 技术实施规范评审固定用 `06.技术实施规范评审.md` +- 验收评审从 `08` 开始,避免和前四步混淆 + +## 4. 为什么保留编号空位 + +保留 `03 / 04 / 07 / 09` 等位置,是为了支持: + +- 多轮设计评审; +- 多轮技术规范评审; +- 多轮验收评审; +- 不打乱主文件的稳定编号。 + +## 5. 跨项目迭代的写法 + +从 `04A` 开始,主规划和技术规范都必须明确写出涉及项目,例如: + +- `lineup-app` +- `lineup-adapter/hermes` +- `lineup-app-server` +- `infra/wukongim-v3` +- 文档目录本身 + +不能再把跨项目迭代只写成某一个仓的局部事项。 diff --git a/03.迭代规划/00.迭代方法/03.跨项目迭代边界与协作方式.md b/03.迭代规划/00.迭代方法/03.跨项目迭代边界与协作方式.md new file mode 100644 index 0000000..a44ab4a --- /dev/null +++ b/03.迭代规划/00.迭代方法/03.跨项目迭代边界与协作方式.md @@ -0,0 +1,65 @@ +# 跨项目迭代边界与协作方式 + +**版本:** 2026-08-07 +**适用范围:** 从 `04A.agent_tool_route` 开始的跨项目迭代 + +## 1. 当前现实 + +从 `04A` 开始,一次迭代已经不再只作用于 `lineup-app/`。 + +一个完整闭环可能同时涉及: + +- `lineup-app/`:Runtime、Host、UI、SDK +- `lineup-adapter/hermes/`:协议转换、Agent 兼容层 +- `lineup-app-server/`:必要时的传输边界 +- `agent_ops/`:规划、设计、协议与验收文档 + +因此,迭代必须以“能力闭环”为单位,而不是以“某个仓库”命名。 + +## 2. 规划时必须写清的三件事 + +### 2.1 涉及哪些项目 + +每份 `01.迭代规划.md` 都必须列清: + +- 主要改动项目; +- 次要联动项目; +- 只需验证、不需修改的项目。 + +### 2.2 哪个项目负责什么 + +规划和技术规范都要写清: + +- Runtime 责任; +- Adapter 责任; +- 服务端责任; +- Host / 验收责任; +- 文档与协议责任。 + +### 2.3 哪些层不应被修改 + +跨项目迭代最常见的问题,不是“漏改一个文件”,而是“把不该动的边界动了”。 + +所以规划里必须明确: + +- 哪一层是执行权威; +- 哪一层只做兼容; +- 哪一层视为透明传输; +- 哪些旧语义在本轮保持不变。 + +## 3. 评审时的检查顺序 + +跨项目迭代建议按以下顺序评审: + +1. 先看产品与架构边界是否清楚; +2. 再看跨仓责任是否分清; +3. 再看协议与状态机是否闭环; +4. 最后看实施和验收能否真正跨端跑通。 + +## 4. 当前统一入口 + +从现在开始: + +- 迭代规划、设计评审、技术规范、技术评审都在 `agent_ops/03.迭代规划/` 下组织; +- 具体实现仍分布在各代码仓; +- 但“本次迭代到底在做什么”只在这里定义和冻结。 diff --git a/03.迭代规划/00.迭代方法/原始来源/lineup-app迭代说明.md b/03.迭代规划/00.迭代方法/原始来源/lineup-app迭代说明.md new file mode 100644 index 0000000..5f25e77 --- /dev/null +++ b/03.迭代规划/00.迭代方法/原始来源/lineup-app迭代说明.md @@ -0,0 +1,54 @@ +# LineUp App 迭代文档 + +每个迭代使用一个独立目录,目录名固定为“迭代编号.简短名称”。目录内至少保留该迭代的主定义文档; +每进行一次设计、架构或实现评审,都必须新增一份独立评审记录,不覆盖之前的记录。 + +```text +迭代/ +├── plan.md # 当前阶段之后的总体路线和排期原则 +├── 00.base/ +│ └── 00.base.md +├── 01.kernel/ +│ └── 01.kernel.md +└── 03.sdk_and_coreapp/ + ├── 03.sdk_and_coreapp.md # 本迭代的权威目标、范围、契约、步骤和验收 + ├── 01.design_review.md # 第 1 次评审记录 + ├── 02.design_review.md # 第 2 次评审记录 + └── 03.design_review.md # 第 3 次评审记录 +``` + +约定如下: + +1. 主定义文档命名为 `<迭代目录名>.md`,是当前实施依据; +2. 评审记录按发生顺序编号,命名为 `<两位序号>.design_review.md`; +3. 每份评审记录必须在元信息之后、评审正文之前维护“问题清单(Outline)”,格式参考 + [`01.design_review.md`](03.sdk_and_coreapp/01.design_review.md):列出状态、编号、问题和当前结论/下一步; +4. 评审记录必须写明评审日期、编号、对象、方式和总体结论。新问题先在 Outline 中登记;问题确认后先 + 更新 Outline 状态,再回填主定义文档、验收项和评审正文; +5. 已确认的结论需要回填主定义文档和验收项;评审记录保留原始意见,用于追溯,不能替代主定义文档; +6. 迭代完成后的实现验收、发布复盘等文档也保留在对应目录内,并使用清晰的递增编号。 + +## 后续总计划 + +当前阶段之后的路线、迭代拆分、范围边界和完成标准见 [plan.md](plan.md)。该文件记录已确认的整体方向; +每个具体迭代开始后,仍需在自己的目录中创建主定义文档和独立评审记录。 + +## 评审优先级与遗留问题 + +评审问题使用 `P0`~`P5` 标示处理优先级。优先级表达的是“最晚何时必须解决”,而不是问题描述的 +修辞强弱。 + +| 级别 | 含义 | 当前迭代的处理规则 | +|---|---|---| +| P0 | 根本性阻塞:安全边界、数据正确性或总体架构不能成立。 | 立即处理;在解决前不得进入相关实现。 | +| P1 | 关键规则未定:不解决会让不同实现互相冲突或明显返工。 | 在开始相关模块实现前处理。 | +| P2 | 交付或验收缺口:核心方向正确,但缺失会使恢复、安全验证或验收不完整。 | 必须在本迭代验收前处理。 | +| P3 | 局部行为、一致性或质量问题:存在安全默认行为,但相关模块完成前仍需收敛。 | 必须在本迭代结束前处理。 | +| P4 | 优化、可读性或维护性建议。 | 可以登记为遗留问题,不阻塞当前迭代。 | +| P5 | 观察记录或未来机会,当前没有足够的产品需求或证据进入排期。 | 可以登记为遗留问题,不纳入当前迭代。 | + +因此,**P0~P3 必须在当前迭代处理完毕,P4~P5 可以作为遗留问题延期处理。** + +延期不是删除:评审中出现 P4/P5 时,必须在该评审文件的 Outline 或“遗留问题”段落中保留编号、问题、 +延期原因和建议重新评估的阶段/条件。只有在对应迭代主定义文档或新的评审记录中明确重新纳入后,才将 +其提升为当前迭代的 P0~P3 问题。 diff --git a/03.迭代规划/01.总览与路线/01.迭代总览与阶段结论.md b/03.迭代规划/01.总览与路线/01.迭代总览与阶段结论.md new file mode 100644 index 0000000..ed4e91e --- /dev/null +++ b/03.迭代规划/01.总览与路线/01.迭代总览与阶段结论.md @@ -0,0 +1,32 @@ +# 迭代总览与阶段结论 + +**版本:** 2026-08-07 整合版 +**范围:** `lineup-app/迭代/` 的主定义、计划、设计评审与验收评审 + +## 1. 总体判断 + +当前迭代体系已经从“客户端内部阶段记录”演进成“跨项目能力闭环推进”。 + +最重要的阶段事实是: + +- `00.base` 已完成并验证最小客户端闭环; +- `01.kernel` 明确了应用工作区与运行时编排方向; +- `03.sdk_and_coreapp` 已完成并冻结 MiniApp SDK v1 的关键边界; +- `04.runtime_workspace` 已于 2026-08-06 完成最终验收; +- `04A.agent_tool_route` 已于 2026-08-07 完成当前定义下的联合验收。 + +## 2. 当前阶段地图 + +| 阶段 | 状态 | 作用 | +|---|---|---| +| `00.base` | 已完成 | 最小 Runtime 闭环基线 | +| `01.kernel` | 目标设计 | 应用工作区与编排骨架 | +| `03.sdk_and_coreapp` | 已完成 | SDK v1 与内置参考 MiniApp | +| `04.runtime_workspace` | 已完成 | 工作区与 Pomodoro 主闭环 | +| `04A.agent_tool_route` | 已完成 | Agent Tool 通路与 Hermes 兼容层 | + +## 3. 当前主优先级 + +1. 以 `04.runtime_workspace` 与 `04A.agent_tool_route` 为已完成样板,启动下一轮跨项目迭代。 +2. 继续按统一四步法推进后续阶段。 +3. 逐步清理旧来源目录和历史入口。 diff --git a/03.迭代规划/01.总览与路线/02.后续路线与阶段优先级.md b/03.迭代规划/01.总览与路线/02.后续路线与阶段优先级.md new file mode 100644 index 0000000..d05b567 --- /dev/null +++ b/03.迭代规划/01.总览与路线/02.后续路线与阶段优先级.md @@ -0,0 +1,28 @@ +# 后续路线与阶段优先级 + +**版本:** 2026-08-07 整合版 +**依据:** `lineup-app/迭代/plan.md` + +## 1. 当前北极星 + +LineUp 的目标是成为面向 Agent 的协议化交互运行时,而不是某个固定 channel 的 UI 外壳。 + +## 2. 当前推荐路线 + +```text +已完成 Runtime 工作区闭环 + → 已补齐 Agent Tool 通路 + → 下一步推进下一类真正可用的隔离 MiniApp +``` + +## 3. 当前不宜提前拉入主线的事项 + +- 应用市场 +- 第三方远程下载与发布 +- 多 Agent 连接与切换 +- 真实音视频能力 +- 复杂任务管理膨胀 + +## 4. 与新迭代主目录的关系 + +今后这份路线文档在 `agent_ops/03.迭代规划/` 维护,作为所有跨项目迭代的总路线入口,而不再只放在 `lineup-app/迭代/plan.md`。 diff --git a/03.迭代规划/01.总览与路线/原始来源/lineup-app后续迭代计划.md b/03.迭代规划/01.总览与路线/原始来源/lineup-app后续迭代计划.md new file mode 100644 index 0000000..5485f62 --- /dev/null +++ b/03.迭代规划/01.总览与路线/原始来源/lineup-app后续迭代计划.md @@ -0,0 +1,422 @@ +# LineUp App 后续迭代计划 + +**更新时间:** 2026-08-07 +**计划状态:** 已确认,作为后续迭代拆分和排期依据 + +## 1. 产品北极星 + +LineUp 的最终目标,不是做一个“把 Agent 接到某个聊天 channel 上”的客户端,也不是做一个只能展示固定卡片格式的 +Agent IM 外壳;它要成为一个 **面向 Agent 的协议化交互运行时**。 + +这意味着: + +- Agent 不只输出文本,还能基于标准协议选择更合适的交互形态; +- 用户与 Agent 的核心交互方式仍然是 `IM`、`voice`、`video conference` 等主交互模式; +- Runtime、SDK、Tool、MiniApp、Surface、Capability 和标准交互原语,是这些主交互模式在任务过程中可调用、可组合、可恢复的工具与组件; +- LineUp 的核心资产,不是某一个具体小程序,而是“Agent 如何知道该用什么形式表达任务,以及宿主如何稳定执行这种表达”的标准协议与运行时。 + +可以将目标结构收敛为: + +```text +主交互模式 + = IM / Voice / Video Conference / 未来其他模式 + +任务过程中的可用工具 + = 标准交互原语 + Runtime Tool + MiniApp Workspace + Host Capability + +LineUp 的核心职责 + = 让 Agent 基于统一协议,在这些交互模式中选择、调用、编排合适的工具和组件 +``` + +因此,LineUp 与当前常见 Agent channel 方案的本质区别在于: + +- 普通 channel 方案主要解决“Agent 内容如何适配某个平台已有的固定展示格式”; +- LineUp 要解决的是“Agent 如何在一个统一运行时里,按协议动态组织文本、卡片、工具调用和微型工作区,与用户完成真实任务协作”。 + +这里的 MiniApp 往往不需要很大。像 Pomodoro 这样的能力,本质上不是一个独立产品线,而是 Agent 在任务过程中临时拉起的、 +最适合当前目标的一个受控交互载体。未来这类载体可以是番茄钟、白板、选择器、安排器、表单、确认器或其他小型工作区。 + +本计划后续所有迭代,都应围绕这条北极星判断优先级: + +- 是否在强化协议和运行时,而不是回退成某个 channel 的格式适配; +- 是否在增强 Agent 对“该用什么交互形态”的可选择能力; +- 是否在让 IM / Voice / Video 等主交互模式能够共享同一套 Runtime、SDK 和 Tool 语义; +- 是否在让工具与组件成为交互过程中的标准能力,而不是散落的页面特判。 + +## 2. 计划结论 + +前三个阶段已经建立了 LineUp App 的基础:Runtime 托管 IM、Runtime 具备应用编排能力、MiniApp SDK v1 +和两个内置 MiniApp 参考实现已经完成验证。 + +下一步不建设应用市场,也不马上扩展多 Agent、语音或视频。下一阶段先把已有的 Runtime、SDK 和 MiniApp +契约做成用户真正可以使用的“应用工作区”。 + +当前推荐路线: + +```text +Runtime 工作区与应用闭环 + → 第一个真正可用的 MiniApp(番茄时钟) + → 本地 App 管理与签名 Bundle + → 再评估应用分发和市场 +``` + +## 3. 已完成的基础 + +### 3.1 Runtime 托管 IM + +`00.base` 已建立客户端基础闭环: + +- Runtime 独占网络连接、同步循环、消息存储、outbox 和 App Inbox; +- 登录、同步、本地回显、Markdown 和 Agent 状态保持稳定; +- 入站消息经过作用域校验、去重和恢复; +- Chat 只能通过 SDK 和 Runtime Action 工作。 + +### 3.2 Runtime 应用编排基础 + +Runtime 已具备应用实例、焦点和生命周期的核心模型: + +- App Registry; +- App Instance Manager; +- App Focus Manager; +- App Lifecycle Manager; +- Tool Router 和 App Orchestrator; +- App 子会话、前后台切换、关闭和恢复的契约。 + +### 3.3 MiniApp SDK v1 与参考实现 + +`03.sdk_and_coreapp` 已完成并通过自动化测试和浏览器验收: + +- Interact 是唯一的 `system` MiniApp; +- Task Dashboard 和 Whiteboard 都是普通 `bundled` MiniApp 的参考实现; +- MiniApp 通过 SDK 接收 Inbox、Tool、进度、结果、生命周期、Surface 和 Capability 请求; +- 标准 `notice / choice / confirm / input` 属于 Interact 的人与 Agent 交互,不是 MiniApp 内部表单; +- App 子会话、交互归属、关闭收口、继续处理和重启恢复已有明确规则; +- 当前单 Agent MVP 边界保持不变。 + +需要注意:当前完成主要证明了 Runtime 契约和参考实现正确,用户可见的完整应用工作区仍需下一阶段收敛。 + +## 4. 第四次迭代:Runtime 工作区与应用闭环 + +**设计状态:** 已冻结,待实施;冻结基线为 +[04.runtime_workspace.md](04.runtime_workspace/04.runtime_workspace.md)、 +[05.technical_implementation_spec.md](04.runtime_workspace/05.technical_implementation_spec.md) 及其 +[03.design_review.md](04.runtime_workspace/03.design_review.md)、[06.technical_implementation_spec_review.md](04.runtime_workspace/06.technical_implementation_spec_review.md)、 +[07.技术实施规范评审(二).md](04.runtime_workspace/07.技术实施规范评审(二).md)。当前没有未解决的 P0~P3; +TIS2-06 是未来多入口 session data 协作写入的 P4 延续项,不阻塞第 04 次实施。 + +建议迭代目录: + +```text +迭代/04.runtime_workspace/ +``` + +### 4.1 目标 + +把 Runtime 的生命周期和会话模型接到真实 Web/Tauri 界面,跑通一条完整的用户流程: + +```text +进入 Interact IM + → Agent 请求启动 Pomodoro 番茄时钟 + → Runtime 创建 App instance、App 子会话和 deadline operation + → 番茄时钟进入前台,Interact 进入后台 + → 用户进行写作业、看书或冥想等固定时长专注;自动暗屏/锁屏不终止本轮 + → 到时间完成,或用户主动离开 Pomodoro 工作区而中断 + → Runtime 回传唯一结果并关闭 App + → 子会话结束并在 IM 中折叠保存 + → Interact 恢复 + → 用户查看历史或继续开始下一轮 +``` + +### 4.2 主要工作 + +1. **接通 App 工作区界面** + - 显示当前前台 App; + - 支持由 `pomodoro.start` 从 Interact 进入 Pomodoro,以及用户明确返回 Interact; + 返回/切换会中断当前专注,不把 Pomodoro 作为通用后台计时器继续运行; + - 显示后台、挂起、关闭和恢复状态; + - App 启动失败时恢复 Interact; + - 刷新或重启后恢复工作区。 + + Whiteboard 在第 04 次只保持第 03 次已有的 SDK/隔离回归,不接入本轮工作区切换或新的完整流程。 + +2. **接通 App 生命周期** + - 将 `AppLifecycleManager`、`AppFocusManager` 和 `AppOrchestrator` 接入真实 Host; + - 区分自动暗屏/锁屏、Surface 重载、工作区切换和真正关闭;前两者不终止专注,后两者中断专注; + - Runtime 使用持久化 `ends_at` 的 deadline operation,而不是 MiniApp UI 定时器; + - 关闭后停止新 Tool 投递; + - 用户退出专注时先将 `pomodoro.start` 终结为 `interrupted`;其他未终态普通 Tool 才收敛为 + `cancelled(app_closed)`; + - 已提交结果继续通过 outbox 发送; + - 关闭后恢复前一个有效前台 App。 + +3. **完成 App 子会话展示** + - 主 IM 显示 App 子会话折叠卡片; + - 支持展开已结束子会话; + - 已结束子会话只读; + - “继续处理”创建新 instance 和新子会话; + - 不把 App 内部按钮、表单和编辑动作写入 IM。 + +4. **完成 Interact 交互覆盖层** + - Interact 前台时,在 IM 中以内联卡片显示标准交互; + - bundled MiniApp 前台时,可以在其上方显示 Interact 的交互层; + - 展示位置变化不改变交互归属; + - App 关闭不自动取消未回答的标准交互; + - Agent 可以通过 `interaction.dismiss` 远程取消交互。 + +5. **让 Pomodoro 成为第一条完整用户流程** + - 用户在 IM 中以自然语言要求 Agent 开始或结束写作业、看书或冥想等专注;MiniApp 是 Agent 的业务工具, + 而不是由用户自行操作业务状态的独立应用。未来 Voice 复用相同意图和 Tool 语义,但不属于本轮实现或验收; + - Agent 以 `pomodoro.start({ duration_seconds, activity? })` 创建长期 Tool operation,并以 + `pomodoro.interrupt({})` 请求结束当前专注; + - 同一会话已有 `focusing` 专注时,不同调用的第二次 `pomodoro.start` 稳定拒绝为 + `active_focusing_operation`,不创建新的 App、子会话或 deadline operation; + - Runtime 创建 Pomodoro instance、App 子会话和 deadline operation,Pomodoro 直接进入专注界面; + - 到时间得到 `completed`;Agent 调用 `pomodoro.interrupt`,或用户切回 Interact、切到其他 MiniApp、关闭 + Pomodoro 时得到 `interrupted`;后者由 Runtime 的生命周期处理,不伪造 Agent Tool;不支持暂停、继续或恢复; + - 自动暗屏、锁屏与 Surface 重载不终止专注;刷新或重启后根据 `ends_at` 恢复或结算一次; + - 到时间后 Runtime 回传一次结果并立即关闭 Pomodoro,完成结果由 Interact / Agent 呈现; + - 完成记录和子会话可以在 Interact 中查看。 + + 第一版只保留这些状态和数据: + + ```text + state = focusing | completed | interrupted + operation_id + source_tool_call_id + activity? + duration_seconds + started_at + ends_at + ended_at? + interruption_reason? + ``` + + 最小 Agent Tool: + + ```text + pomodoro.start({ duration_seconds, activity? }) + pomodoro.interrupt({}) + ``` + + Pomodoro 使用 `app.instance-state.v1` 保存严格 schema v1、最大 4 KiB 的展示快照;关闭后采用 + `retain_readonly`,旧快照只用于历史展示,不能再次写入或恢复为运行中的专注。 + + 番茄钟到点后不保留 App 内完成提醒:Runtime 关闭 Pomodoro 并恢复 Interact,由 Interact / Agent 呈现 + 完成结果。如果以后需要系统通知,再通过 Runtime 的 Capability 请求,不能让 MiniApp 直接调用 Tauri + 或操作系统接口。Agent 询问用户是否开始下一轮时,仍然必须使用 Interact 的标准交互。 + +6. **补充端到端验收** + - Web Reference Host 完整验收; + - Tauri Desktop Host 至少完成一条代表性流程; + - 验证刷新、断线、重启、关闭、恢复和重复提交; + - 检查 Runtime 生命周期、交互、Tool 和恢复日志。 + +### 4.3 不在本迭代范围 + +- 应用市场和服务端 App Catalog; +- 远程下载、第三方发布和在线更新; +- 多 Agent 连接、切换和多 Agent 会话列表; +- Audio Mode 的真实媒体能力; +- Video Mode 的真实媒体能力; +- Whiteboard 的工作区接入、切换、多人协作和复杂绘图能力;第 04 次只保持其已有回归;通用 mutation queue 与 + Agent Tool、UI Action 共同修改同一 App 子会话数据的协作协议同样延期,首次实现此类双入口写入功能前必须专项设计; +- Task Dashboard 的完整项目管理功能; +- Pomodoro 的统计报表、多个计时器、日历和复杂提醒计划。 + +### 4.4 完成标准 + +```text +用户可以在 IM 中请求 Agent 立即开始或停止 Pomodoro 专注;未来 Voice 只复用相同 Tool 语义; +Runtime 可以正确创建、运行、中断、关闭和恢复 Pomodoro App; +App 子会话能在 IM 中折叠、展开和继续处理; +标准交互在不同前台 App 下仍归 Interact; +到点/中断竞争唯一终态,Tool 和 outbox 收口正确; +到点后立即关闭 Pomodoro 并由 Interact / Agent 呈现完成结果; +Pomodoro 关闭后的业务快照保留为只读历史,不能复活旧 instance; +刷新/重启后 App、焦点、子会话、`ends_at` 专注状态和待处理交互按规则恢复; +Web/Tauri 代表性流程通过端到端验收; +Whiteboard 保持第 03 次回归,但不属于第 04 次工作区闭环或完成范围; +所有 P0~P3 评审问题清零。 +``` + +## 5. 第五次迭代:第二个真正可用的隔离 MiniApp + +建议迭代目录: + +```text +迭代/05.first_isolated_miniapp/ +``` + +第四次迭代完成工作区和 Pomodoro 后,再把另一个 MiniApp 从“参考实现”推进为“真实隔离运行的应用”。 + +### 5.1 推荐顺序 + +优先选择 Whiteboard 作为第二条安全和 Surface 验证流程;Task Dashboard 留到后续,避免任务管理业务影响 Runtime 核心设计。 + +### 5.2 Whiteboard 方向 + +- 受限 Surface 中的画布状态; +- 状态 patch; +- Artifact 导出; +- Artifact 元数据校验; +- 重启恢复; +- 验证不能访问 Host DOM、Tauri、认证状态、任意网络和其他 MiniApp 数据。 + +暂时不做多人协作、云端同步和复杂绘图工具。 + +### 5.3 Task Dashboard 的后续定位 + +Task Dashboard 仍然保留为后续普通 `bundled` MiniApp,但不作为 Runtime 的第一条验证流程。 +等工作区、SDK、生命周期和 Surface 边界稳定后,再单独定义任务、项目、状态和历史等业务模型。 + +## 6. 第六次迭代:Agent 平台适配与原生命令兼容层 + +建议迭代目录: + +```text +迭代/06.agent_platform_adapter/ +``` + +`04A.agent_tool_route` 已经证明:Hermes 这类 Agent 平台与 LineUp 之间,真正需要稳定下来的不是某个平台的单次补丁,而是一个可扩展的“平台私有命令 / 交互 -> LineUp 标准能力”兼容机制。 + +这一轮的目标不是把 Runtime 变成各家 Agent 的命令执行器,而是冻结三层分工,并为 Hermes、未来 Open Claw 等平台提供统一承接面: + +```text +Agent Native Command / Prompt + -> Agent Adapter Compatibility Layer + -> LineUp Standard Action / Interaction + -> Runtime / App / Host Capability +``` + +### 6.1 分层原则 + +1. **LineUp Standard Ability** + - 由 LineUp 自己定义并长期稳定维护; + - 只包含 Runtime Tool、标准交互与 Host capability 等通用协议: + - `lineup.v1.tool.invoke` + - `lineup.v1.tool.call` + - `lineup.v1.tool.result` + - `lineup.v1.app.call` + - `lineup.v1.app.result` + - 不直接暴露 Hermes、Open Claw 等平台私有 slash / prompt 语义。 + +2. **Agent Adapter Compatibility Layer** + - 位于各平台自己的 adapter / plugin; + - 负责把平台私有 command、approval、clarify、confirm、update prompt 等交互,映射到 LineUp 标准协议; + - 维护平台内部 request / callback token 与 LineUp `call_id` / option id 的受控映射; + - 未来 Hermes、Open Claw 等都复用同一套分层原则,而不是继续把兼容逻辑下沉到 Runtime。 + +3. **Runtime / App Implementation Layer** + - 只实现 LineUp 自己的业务能力和受控宿主能力; + - 例如 `pomodoro.start`、`pomodoro.interrupt`、打开 surface、文件选择、剪贴板、artifact 保存等; + - 不直接理解 `/model`、`/reload-mcp`、`/reset`、`/approve` 之类 Agent-native command。 + +### 6.2 本迭代拟解决的问题 + +- 冻结“Agent Native Command 不等于 LineUp Runtime Ability”的架构边界; +- 抽象统一的 `command-to-action` 与 `prompt-to-interaction` 转换模型; +- 为多个 Agent 平台定义一致的 adapter 承接面,而不是每个平台都重新发明一套私有兼容逻辑; +- 明确哪些交互由 adapter 收口,哪些能力才允许进入 Runtime / Host; +- 为 `slash_confirm`、`update_prompt` 这类当前在 ACP 路径下缺少稳定触发源的 contract,定义后续 bridge / trigger 承接方案。 + +### 6.3 不在本迭代范围 + +- 把各家 Agent 平台的原生命令直接下沉到 Runtime; +- 让 `lineup-app-server` 理解平台私有 command / prompt 语义; +- 一次性实现所有第三方 Agent 平台; +- 扩展多 Agent 会话编排或平台市场能力。 + +### 6.4 完成标准 + +```text +形成一份权威分层设计:Agent Native Command、Adapter Compatibility Contract、LineUp Standard Action 三层边界清晰; +Hermes 适配结论可被抽象为平台无关的兼容模型,而不是 Hermes 特判; +未来 Open Claw 等新平台能够按同一 adapter contract 接入,不要求先改 Runtime; +明确 slash / confirm / approval / clarify / update prompt 等 contract 的归属与映射原则; +为需要 bridge / trigger 的平台原生交互,给出单独的小迭代承接口径。 +``` + +## 7. 第七次迭代:本地 App 管理和签名 Bundle + +建议迭代目录: + +```text +迭代/07.local_app_management/ +``` + +这一阶段仍然不建设在线应用市场,只解决本机的应用管理和安全运行边界: + +- App Registry 管理界面; +- 本地安装记录; +- App enable / disable / remove; +- 版本记录和回滚; +- 本地签名 Bundle; +- Bundle 完整性和签名校验; +- 验证失败不污染当前可运行缓存; +- Inventory 更新和 Agent 可见性变化; +- App 删除时实例、焦点、Inbox、Surface 和私有数据的收口。 + +## 8. 第八次迭代之后:再评估应用分发和市场 + +只有在 Runtime 工作区、本地 App 管理、SDK、Surface 和签名 Bundle 都稳定之后,才评估: + +- 服务端 App Catalog; +- 应用搜索和详情; +- 下载和更新; +- 发布者身份; +- 第三方 MiniApp; +- 审核、撤回和安全策略。 + +应用市场不是当前阶段的基础设施,而是建立在前面几层都稳定之后的分发能力。 + +## 7. 更后面的方向 + +### Runtime Core 的 Rust 演进 + +在 Runtime 工作区已跑通并积累 Web / Tauri 两个 Host 的性能数据后,单独评估是否将部分**无 UI 的 Runtime +Core** 下沉到 Rust,以改善状态处理和原子收口的效率与一致性。这不是第 04 次迭代的工作,也不等于把整个 +MiniApp SDK 改写成 Rust:Web Reference Host、sandbox iframe、postMessage Bridge、DOM 和 UI 仍须保持在 +TypeScript / 浏览器侧。 + +潜在的 Rust 候选范围: + +- instance state 的持久化、schema 校验和 revision 比较; +- deadline operation 调度; +- Tool / outbox 的原子终态裁决; +- lifecycle 收口和审计。 + +只有在 profiling 显示上述路径存在明确热点时,才建立独立设计与迁移迭代。评估必须同时测量状态快照读写、 +deadline / Tool 吞吐、主线程阻塞、Tauri IPC 往返、JSON 序列化成本,以及 Web / Tauri Host 的实际差异; +并证明收益覆盖跨语言实现、测试、调试和错误边界所增加的复杂度。 + +### 多 Agent + +在单 Agent 工作区稳定后再设计: + +- Agent 列表和切换; +- 多 Agent 会话; +- 多 Agent outbox; +- App 子会话归属; +- Inventory 和权限隔离。 + +### Audio Mode 与 Video Mode + +语音和视频应复用当前 Runtime SDK、Tool、Capability、Artifact 和会话模型,不建立独立的 Agent +通信链路。它们需要单独处理媒体权限、设备选择、实时连接、中断和恢复,因此不进入当前两次迭代。 + +## 8. 当前排期原则 + +```text +先完成“真实可用的应用工作区” +再完成“一个真正隔离的 MiniApp” +再完成“本地安装和签名管理” +最后才评估“远程分发和应用市场” +``` + +任何新增功能都应先回答三个问题: + +1. 它是否依赖 Runtime 已经稳定的生命周期和会话模型? +2. 它是否能通过现有 MiniApp SDK、Surface、Capability 和 Artifact 边界实现? +3. 它是否会把应用业务逻辑、网络通信或系统权限重新塞回 Interact 或 `main.ts`? + +如果前两个问题没有准备好,或者第三个问题答案为“会”,就不应提前进入应用市场或新的 App 类型建设。 diff --git a/03.迭代规划/02.已完成阶段/00.base/01.阶段摘要.md b/03.迭代规划/02.已完成阶段/00.base/01.阶段摘要.md new file mode 100644 index 0000000..f2476fa --- /dev/null +++ b/03.迭代规划/02.已完成阶段/00.base/01.阶段摘要.md @@ -0,0 +1,16 @@ +# 00.base 阶段摘要 + +**状态:** 已完成 +**来源:** `lineup-app/迭代/00.base/00.base.md` + +## 1. 结论 + +`00.base` 建立了后续所有迭代不可倒退的基线: + +- Runtime 独占 Transport、Store、sync loop、outbox 和 App Inbox; +- Chat 只能通过 SDK 与 Runtime Action 工作; +- Tauri Desktop Host + Web Reference Host 是统一客户端基线。 + +## 2. 现在如何看待这个阶段 + +它现在不是未来新迭代的讨论现场,而是所有后续阶段的底层约束来源。 diff --git a/03.迭代规划/02.已完成阶段/00.base/原始文档/00.base.md b/03.迭代规划/02.已完成阶段/00.base/原始文档/00.base.md new file mode 100644 index 0000000..4bd99cc --- /dev/null +++ b/03.迭代规划/02.已完成阶段/00.base/原始文档/00.base.md @@ -0,0 +1,124 @@ +# LineUp App 迭代基线:Runtime 托管 Chat + +**迭代编号:** 00.base +**状态:** 已实现、已验证 +**日期:** 2026-08-04 +**定位:** 后续迭代必须保持的客户端基础能力 + +## 1. 基线说明 + +本迭代建立了 LineUp Runtime 承载第一个 Core App 的最小闭环。当前实现中的 `chat` 是 +图文 IM 形态的应用;在后续设计中,它将演进为 `Interaction App` 的 IM 模式,但现阶段 +仍保留 `app_scope = "chat"` 作为代码和协议兼容名称。 + +当前客户端基线是: + +```text +Tauri 2 Desktop Host + Web Reference Host +``` + +两种 Host 运行同一份 TypeScript Runtime 和 Core App:Tauri 用于正式桌面交付,Web 用于 +浏览器开发、Tailscale 联调和自动化验证。它们不是两套客户端,也不各自实现通信、同步或 +消息存储。 + +## 2. 已实现的 Runtime 边界 + +`LineUpRuntime` 是以下对象的唯一所有者: + +```text +Transport +Conversation Store +sync loop +outbox +App Inbox +作用域筛选 +旧 lineup.v1 兼容 +去重与恢复 +``` + +Chat 不能直接访问 AppServer、Transport、Store、登录态或 Host 特权能力,只能使用 +`ChatRuntimeSDK`: + +```text +Runtime + → 校验、持久化和筛选 Agent 消息 + → 投递 app_scope + conversation_id 匹配的消息 + → 通过 SDK 提供订阅、Inbox、ACK 和 Runtime Action + → 接收 Chat 的用户动作并进入 outbox +``` + +## 3. 已实现的应用加载关系 + +当前已经将“默认应用选择”和“应用具体挂载”分开: + +```text +CoreAppRegistry + → 选择默认 app_scope = chat + +CoreAppHostRegistry + → 找到 chat 对应的 Host 装配器 + +chat-app-host.ts + → 加载 Chat Shell、样式、Renderer、DOM Context 和 SDK +``` + +`main.ts` 现在只负责启动 Runtime、创建 Host 绑定和请求挂载默认 App,不再直接导入 +Chat 的样式、Renderer 或 Shell。 + +相关实现: + +- `tauri/src/runtime/coordination/lineup-runtime.ts` +- `tauri/src/runtime/app-management/app-registry.ts` +- `tauri/src/runtime/app-management/runtime-app-host.ts` +- `tauri/src/core-apps/chat/chat-app-host.ts` +- `tauri/src/main.ts` + +## 4. 已实现的消息和交互能力 + +当前基线已经覆盖: + +- 登录、退出和会话恢复; +- HTTP 增量同步与包含式 cursor; +- 用户消息本地回显、发送状态和 outbox; +- Agent Markdown 消息和安全 DOM 渲染; +- Agent status、progress、error 和执行摘要; +- `choice`、`confirm`、`input` Tool Call 的状态机和可信交互卡片; +- Tool Result / Tool Cancel 回声; +- Surface 生命周期的基础管理; +- Capability Registry、确认、受限 Host 执行和审计; +- Artifact 元数据校验与 Host 临时内容缓存; +- App Inbox 的持久化、恢复、ACK 和 `message_id` 去重。 + +所有入站内容必须先经过 Runtime 协议和作用域校验。非法或不匹配的消息不得进入 Chat, +未知内容只能安全降级,不能执行远端脚本或系统能力。 + +## 5. 基线验证 + +```text +npm test -- --run +21 个测试文件通过 +94 个测试通过 + +npm run build +TypeScript 检查通过 +Vite production build 通过 +``` + +## 6. 本基线的边界 + +以下能力尚未作为本迭代的完整实现: + +```text +动态 App Registry +App 安装、更新、回滚和移除 +App Instance Manager +App Focus Manager +Runtime Tool Router +Interaction App 的 IM / Audio / Video 模式统一运行时 +扩展 App 的前后台切换和恢复 +完整应用市场 +``` + +这些内容属于 [01.kernel.md](../01.kernel/01.kernel.md) 定义的下一阶段目标。`00.base` 的意义是保护 +已经完成的通信、可靠投递、作用域和安全边界,后续重构不得让这些能力回到 App 或 +`main.ts` 中。 diff --git a/03.迭代规划/02.已完成阶段/01.kernel/01.阶段摘要.md b/03.迭代规划/02.已完成阶段/01.kernel/01.阶段摘要.md new file mode 100644 index 0000000..4c9093e --- /dev/null +++ b/03.迭代规划/02.已完成阶段/01.kernel/01.阶段摘要.md @@ -0,0 +1,19 @@ +# 01.kernel 阶段摘要 + +**状态:** 目标设计 +**来源:** `lineup-app/迭代/01.kernel/01.kernel.md` + +## 1. 结论 + +`01.kernel` 把 LineUp 从“Runtime 加载一个页面”推进为“Runtime 管理应用工作区”的设计骨架。 + +它明确了: + +- App Registry +- Instance Manager +- Focus Manager +- Lifecycle Manager +- Tool Router +- App Orchestrator + +这些能力是后续 `03`、`04`、`04A` 的共用骨架。 diff --git a/03.迭代规划/02.已完成阶段/01.kernel/原始文档/01.kernel.md b/03.迭代规划/02.已完成阶段/01.kernel/原始文档/01.kernel.md new file mode 100644 index 0000000..8c7be91 --- /dev/null +++ b/03.迭代规划/02.已完成阶段/01.kernel/原始文档/01.kernel.md @@ -0,0 +1,293 @@ +# LineUp App 迭代目标:Runtime Kernel 与应用编排 + +**迭代编号:** 01.kernel +**状态:** 目标设计,待实现 +**日期:** 2026-08-04 +**前置基线:** [00.base.md](../00.base/00.base.md) + +## 1. 迭代目标 + +本迭代要把 LineUp 从“Runtime 加载一个 Chat 页面”推进为“Runtime 管理一个应用工作区”。 +Runtime 不仅负责网络、消息和存储,还要负责应用实例、工具路由、前后台焦点和恢复; +`Interaction App` 则负责用户与 Agent 的核心交互体验。 + +目标结构: + +```text +LineUp Runtime +│ +├── Runtime Shell / Desktop +│ ├── 应用启动 +│ ├── 应用切换 +│ ├── 前后台与焦点 +│ ├── 通知和安全恢复 +│ └── Host 挂载协调 +│ +├── Interaction App(Core App) +│ ├── IM Mode:文字、图片、短消息 +│ ├── Audio Mode:实时语音 +│ ├── Video Mode:实时视频 +│ └── 标准交互原语:choice / confirm / input +│ +└── Installed / Extension Apps + ├── Whiteboard + ├── Draw-and-Guess + ├── Task Dashboard + └── 其他可安装应用 +``` + +当前代码中的 `chat` 继续作为兼容实现名称,产品概念上将其定位为: + +```text +Interaction App 的 IM 实现 +``` + +后续是否将目录和作用域从 `chat` 正式迁移到 `interaction`,另行作为命名迁移任务处理, +不与本迭代的运行时编排重构混在一起。 + +## 2. 核心职责分工 + +### 2.1 LineUp Runtime + +Runtime 是整个应用工作区的底层协调中心,拥有最终调度和安全决策权: + +- 管理 App Registry、App Instance 和焦点栈; +- 校验 Agent Tool Call、Manifest、Inventory、参数和作用域; +- 决定调用应直接执行、启动 App、切换前台、等待用户交互还是创建异步任务; +- 维护 App 的启动、运行、后台、挂起、恢复、关闭和失败状态; +- 管理 Conversation、Store、App Inbox、outbox、权限和审计; +- 在扩展 App 结束或启动失败时恢复原来的前台实例。 + +### 2.2 Runtime Shell / Desktop + +Runtime Shell 是一个拥有特殊权限的内置工作区。它在产品体验上类似桌面或 Launcher, +但最终的生命周期和安全决策仍由 Runtime Core 管理。 + +它负责: + +- 把 App 实例挂载到 Tauri 或 Web Host; +- 显示当前前台 App; +- 管理应用切换、恢复和关闭; +- 提供全局连接状态、通知和安全恢复入口; +- 向 App 传递受限的 Host 能力。 + +普通 App 不能伪造 Runtime Shell,也不能直接修改焦点栈。 + +### 2.3 Interaction App + +Interaction App 是默认的人与 Agent 交互应用,但不是整个应用生态的调度器。 + +它负责: + +- 当前 Conversation 的主要交互体验; +- IM、Audio、Video 等交互模式; +- 选择框、确认框、输入框和进度卡片等标准交互原语; +- 显示普通 Agent 消息、任务、Tool 结果和扩展 App 的结果; +- 通过 SDK 提交用户动作。 + +它不负责: + +- 直接连接 AppServer 或 Agent; +- 决定其他 App 是否启动; +- 管理其他 App 的前后台状态; +- 直接执行未经 Runtime 授权的 Tool 或 Capability。 + +### 2.4 Extension App + +扩展 App 与 Interaction App 是 Runtime 上的平级应用。画板、你画我猜和任务面板拥有 +自己的页面、状态、Tool、Surface 和实例生命周期,但必须使用 Runtime SDK。 + +扩展 App 可以请求: + +```text +启动自己 +创建 Surface +进入前台 +进入后台 +提交结果 +请求关闭 +``` + +最终是否允许、如何持久化、是否需要用户确认,由 Runtime 决定。 + +## 3. 应用实例与焦点模型 + +App Registry 管理“有哪些应用”,App Instance Manager 管理“哪些应用正在运行”。两者 +不能混为一个状态。 + +```ts +type AppInstanceRecord = { + 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 需要保存当前会话的焦点栈: + +```text +focus_stack: + interaction:audio-001 + draw-and-guess:game-001 + +foreground: + draw-and-guess:game-001 + +background: + interaction:audio-001 +``` + +应用切换不会自动删除原实例。原实例可能进入 `background` 或 `suspended`,在新应用关闭、 +失败或用户返回时恢复。 + +## 4. Tool 调度模型 + +Tool 调度权在 Runtime,不在 Interaction App。 + +```text +Agent Tool Call + → Runtime 校验 Envelope / Inventory / Manifest / 参数 / Scope + → Tool Router 判断处理方式 + → App Orchestrator 创建或切换 App Instance + → Focus Manager 调整前后台 + → Interaction App / Mode / Extension App 承接 + → App 通过 SDK 返回 progress / result / error + → Runtime 校验、持久化、审计并回传 Agent +``` + +Tool 的处理方式至少包括: + +```text +direct 直接由 Runtime 或受信 App 处理 +interactive 交给 Interaction App 等待用户选择/确认/输入 +launch 启动或唤醒一个扩展 App +foreground 要求目标 App 进入前台 +operation 创建长时间运行的异步任务 +``` + +Interact 可以请求 Runtime 启动扩展 App,但不能直接加载 Bundle、切换其他 App 或执行 +系统能力。 + +## 5. 典型场景:语音切换到你画我猜 + +开始时: + +```text +Runtime Shell + └── Interaction App + └── Audio Mode(foreground) +``` + +用户说:“我们来玩一局你画我猜吧。” Agent 发来启动请求后: + +```text +Agent launch(draw-and-guess) + → Runtime 检查 App 是否已安装、启用和兼容 + → 校验 Tool、Manifest、Inventory、参数和权限 + → 创建 draw-and-guess:game-001 + → 保存 interaction:audio-001 的焦点位置 + → Audio Mode 进入 background / suspended + → Draw-and-Guess 进入 starting → foreground +``` + +游戏完成后: + +```text +Draw-and-Guess App + → 通过 SDK 提交 game.result + → Runtime 持久化并回传 Agent + → 游戏实例关闭或挂起 + → Runtime 恢复焦点栈中的 Audio Mode + → Interaction App 显示游戏结果 +``` + +游戏和原来的语音交互使用同一个 `conversation_id`,但拥有不同的 `instance_id`。这样既 +能把结果归还给同一个 Agent 会话,也能独立管理每个应用实例。 + +## 6. 标准交互原语与扩展 App 的边界 + +以下内容属于 Interaction App 的内建能力,不需要安装独立 App: + +```text +choice +confirm +input +progress +error +``` + +例如 Agent 请求选择框: + +```text +Agent tool.call(choice) + → Runtime 校验并持久化 pending Tool Call + → Interaction App 在 IM 中显示 Choice Card + → 用户选择 + → Runtime 校验 call_id 并写入 outbox + → Choice Card 变为 submitted / completed,不再可操作 + → IM 时间线显示“你选择了……” +``` + +选择框可以从界面上退出可操作状态,但交互记录不能从 Store 中删除。这样才能支持刷新 +恢复、Agent 回声、幂等去重和审计。 + +画板、游戏和复杂任务面板则属于可安装扩展 App,拥有自己的 Tool 和 Surface。 + +## 7. 目标模块 + +```text +runtime/app-management/ +├── app-registry.ts +├── app-instance-manager.ts +├── app-focus-manager.ts +├── app-lifecycle-manager.ts +└── runtime-app-host.ts + +runtime/coordination/ +├── tool-router.ts +├── app-orchestrator.ts +└── interaction-orchestrator.ts + +core-apps/interaction/(当前由 core-apps/chat 兼容实现) +├── interaction-runtime.ts +├── interaction-shell.ts +├── interaction-mode-registry.ts +├── modes/im/ +├── modes/audio/ +├── modes/video/ +└── primitives/ + +installed-apps/ +├── whiteboard/ +├── draw-and-guess/ +└── task-dashboard/ +``` + +`RuntimeAppHost` 负责实际挂载和卸载;App Instance、Focus、Lifecycle 和 Tool Router 负责 +状态和决策。这样不会把所有业务规则重新堆回 `main.ts`。 + +## 8. 本迭代验收目标 + +```text +1. Runtime 能从 App Registry 选择默认 Interaction App。 +2. Runtime 能创建、前台化、后台化、挂起、恢复和关闭 App Instance。 +3. Agent 启动扩展 App 时,当前前台 App 能安全进入后台。 +4. 扩展 App 结束或失败后,Runtime 能恢复原来的焦点和交互模式。 +5. Tool 调用必须经过 Runtime 的 Inventory、Manifest、参数、作用域和权限校验。 +6. Interaction App 能处理 choice / confirm / input 等标准交互原语。 +7. 扩展 App 只能通过 SDK 返回结果,不能直接访问 Agent、Host DOM 或系统特权。 +8. 断线或重启后,App Instance、Tool Call、App Inbox、outbox 和焦点栈可以有界恢复。 +9. 00.base 中已经通过的 21 个测试文件、94 个测试和生产构建不能退化。 +``` diff --git a/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/01.阶段摘要.md b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/01.阶段摘要.md new file mode 100644 index 0000000..3da1df8 --- /dev/null +++ b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/01.阶段摘要.md @@ -0,0 +1,18 @@ +# 03.sdk_and_coreapp 阶段摘要 + +**状态:** 已完成 +**来源:** `lineup-app/迭代/03.sdk_and_coreapp/03.sdk_and_coreapp.md` + +## 1. 结论 + +这一阶段已经确认: + +- `Interact` 是唯一 `system` MiniApp; +- `Task Dashboard` 与 `Whiteboard` 是 `bundled` MiniApp; +- 标准交互属于 Interact; +- App 子会话、关闭收口、只读历史和继续处理规则已固定; +- MiniApp SDK v1 已成为当前主契约。 + +## 2. 当前角色 + +这是后续所有工作区与 Tool 迭代的契约支柱,不再只是客户端内部参考阶段。 diff --git a/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/01.design_review.md b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/01.design_review.md new file mode 100644 index 0000000..800a915 --- /dev/null +++ b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/01.design_review.md @@ -0,0 +1,353 @@ +# `03.sdk_and_coreapp` 第 1 次设计评审记录 + +> 评审日期:2026-08-05 +> 评审编号:01 +> 评审基线:[APP架构设计.md](../../设计/APP架构设计.md) +> 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) +> 评审方式:独立子 agent 只读评审;本文件记录评审意见,不代表已采纳或已实现。 + +> **后续决议(2026-08-05):** `MiniAppManifest.kind` 收敛为 `system | bundled`。Task Dashboard +> 与 Whiteboard 均为非系统级 `bundled` MiniApp,必须采用与一般 MiniApp 相同的受限 Surface / +> Bridge 模型;“参考”仅描述它们在本迭代验证 SDK 的目的。下文的 `bundled-reference` 为评审时的 +> 历史术语,已由此决议取代。 + +> **后续决议(2026-08-05):** `notice / choice / confirm / input` 始终是 Interact 的人与 Agent +> 会话交互,不属于 MiniApp SDK,也不用于 MiniApp 内部业务逻辑。当前 bundled MiniApp 位于前台时, +> Interact / Shell 可在其上方显示同一会话的交互层;请求与答案仍只属于 Interact、当前会话和 Agent。 + +> **后续决议(2026-08-05):** 每个启动的 App instance 在主 IM 中创建或恢复一个 App 子会话;其中 +> 只保留人与 Agent 围绕该 App 的提问、回答和简洁结果。App 的内部按钮、表单、画布编辑等业务操作 +> 不进入子会话。后台化不结束子会话;真正结束时保留折叠历史;新的 instance 创建新的子会话。 + +> **后续决议(2026-08-05):** 已结束的 App 子会话可以在主 IM 中只读展开。用户选择“继续处理”时, +> Runtime 创建新的 App instance 和新的子会话;新会话可引用旧会话或 App 数据,但不能续写旧记录或 +> 复活旧问题。 + +> **后续决议(2026-08-05,已更新):** App 子会话只在 `AppLifecycleManager.close` 使对应 instance +> 停止或失败时结束;前后台切换、暂停和 Agent Tool 完成都不结束子会话。关闭时,Runtime 向 Agent +> 可靠发送 App 已关闭事件;尚未回答的 Interact 交互不自动取消,Agent 可 remote dismiss,或让其继续 +> 在主 IM 中等待用户回答/超时。子会话保留为主 IM 历史。 + +> **后续决议(2026-08-05):** MVP 中一个 LineUp Runtime 只连接一个 Agent。每条标准交互和 +> App 子会话仍保存当前 `agent_uid` 作为 `agent_id`,但本迭代不实现多个 Agent 的连接、切换、 +> 会话列表、outbox 或路由。 + +## 问题清单(Outline) + +> **状态标记:** ✅ 已解决并回填设计; 🟡 待产品/架构决策; 🔵 待在契约中细化; ⚪ 待实施编排或文档整理; — 不再适用。 +> +> 本清单是当前有效视图;后文保留评审时的原始问题、分析和建议作为依据。每次确认一个决策或完成 +> 回填时,应先更新此处状态,再更新对应正文和验收项。 + +| 状态 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---|---|---| +| ✅ | B1 | Task Dashboard 的信任级别与 UI 容器 | 已确认:Task Dashboard、Whiteboard 均为非系统级 `kind = bundled` MiniApp,必须运行于受限 Surface / Bridge,不能使用可信 Host DOM。 | +| ✅ | I1 | `bundled-reference` / `bundled-development` 命名歧义 | 已确认:Manifest 枚举为 `system \| bundled`;“参考”仅描述当前迭代的工作目的。 | +| ✅ | I2 | 标准交互由谁显示、答案如何回传 | 已确认:Interact / Shell 显示人与 Agent 的交互;bundled MiniApp 前台时可被该交互层覆盖。结果由 Runtime 可靠回传 Agent,不交给 MiniApp。 | +| ✅ | I3 | 标准交互与 Agent Tool Call 的关系 | 已确认:标准交互仅由 Agent Tool 调起并创建交互记录;MiniApp 不存在 `sdk.ui.request`,不会自行创建 Agent Tool Call。 | +| ✅ | I4 | 标准交互结果的返回通道 | 已确认:用户答案先回 Runtime,再可靠回传 Agent;不经 MiniApp Inbox、Tool 订阅或 MiniApp SDK 返回。 | +| ✅ | I5 | Request/Result schema、状态机、幂等与错误码 | 已确认第一版:`notice` 非阻塞;`confirm` 二选一且默认取消;`choice` 只支持 2~6 项单选;`input` 只支持一个文本输入;首次有效回答终结交互;默认 15 分钟超时,可在 1 分钟~24 小时内调整。 | +| ✅ | I6 | 敏感输入的持久化、恢复、审计和日志边界 | 已确认:未提交草稿仅存在当前运行期间,重启即清空;已提交的人与 Agent 答案保留在所属主 IM 或 App 子会话中,随父 IM 会话处理;二者均不写日志、Telemetry 或普通审计明文。 | +| ✅ | I7 | App 子会话与主 IM 的关系 | 已确认:每个 App instance 创建/恢复一个子会话;后台保留,真正结束后在主 IM 中折叠存档,新 instance 独立。仅记录人与 Agent 围绕 App 的交互,不记录 App 内部业务操作。 | +| ✅ | I8 | 已结束 App 子会话的查看与继续处理 | 已确认:旧子会话可只读展开;继续旧工作创建新 instance / 新子会话,可引用旧上下文,但不追加旧记录或复活旧问题。 | +| ✅ | I9 | App 子会话何时结束 | 已确认:仅 `AppLifecycleManager.close` 导致 instance stopped/failed 时结束;前后台切换、暂停、Agent Tool 完成不结束。关闭时 Runtime 向 Agent 发送 App 已关闭事件,但不自动取消未回答的 Interact 交互。 | +| ✅ | I10 | MVP 的 Agent 身份范围 | 已确认:Runtime 仅连接一个 Agent;交互和 App 子会话保存该 `agent_id`,但不实现多 Agent 连接、切换或路由。 | +| — | S1 | `parent_call_id` 的关联校验与父 Tool 终止后的处置 | 不再适用:MiniApp 不再发起标准交互;标准交互直接绑定 Agent call 与 conversation。 | +| ✅ | S2 | 交互层显示与恢复规则 | 已确认:原 App 前台时显示交互层;切换到其他 App/IM 时收起并在子会话标记“等待你的回答”;用户可在 IM 或回到原 App 后回答;关闭 App 只移除覆盖层并通知 Agent,不自动取消交互。 | +| ✅ | S3 | Interact 呈现与 Agent Tool 回传的分层 | 已确认:Interact / Shell 只显示并把用户动作/答案交给 Runtime;Runtime 根据已保存的交互上下文校验、持久化和可靠回传 Agent;不经过 MiniApp。 | +| ✅ | S4 | 阶段编号重复 | 已确认:Task Dashboard / Whiteboard 的端到端参考实现属于 `03.sdk_and_coreapp`;移除重复的 `03.reference-miniapps`,后续应用分发阶段保持为 `04.app-delivery-registry`。 | +| ✅ | S5 | 实施步骤顺序 | 已确认:先完成通用 Tool 闭环,再完成 Runtime 独占的 Agent interaction service 和 Interact 回传契约,之后才适配 Interact、实现两个 bundled 参考 MiniApp,最后端到端验收。 | + +## 1. 总体结论 + +这份文件保留了首次评审时提出的问题和建议,方便追溯讨论过程;其中涉及 `sdk.ui`、MiniApp 发起 +标准交互、owner 注入等早期设想,均已被本文件顶部的“后续决议”和问题清单中的最终结论取代,不能作为 +实施依据。 + +当前已经收敛的核心结论是: + +- 产品层级是 **LineUp App → LineUp Runtime → MiniApps**,Runtime 不是与 MiniApp 平级的产品 App。 +- `notice / choice / confirm / input` 是 Interact 中人与 Agent 对话的一部分,不是 MiniApp SDK,也不能由 + bundled MiniApp 发起、读取、提交或取消。 +- Interact / Shell 只呈现和收集用户答案;Runtime 保存交互归属、校验首次有效回答、写入可靠 outbox,并 + 向当前唯一 Agent 回传结果。 +- Task Dashboard 与 Whiteboard 都是 `kind = bundled`;它们使用一般 MiniApp 的受限 Surface、Bridge、 + 生命周期和 SDK,只保留自身的业务 UI。 +- 保留 `lineup.v1.tool.call(choice | confirm | input)` 的 Agent 交互兼容入口;不在本迭代迁移 + `chat → interact` 命名。 +- 本迭代不建设 AppServer Catalog、下载、安装、更新、市场、远程 Bundle、真实媒体/文件能力或多 Agent + Runtime 连接。 + +本轮评审问题均已得到设计结论,后续进入实现时应以 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) +和 [APP架构设计.md](../../设计/APP架构设计.md) 为准。 + +## 2. 阻塞问题 + +### B1. Task Dashboard 的可信 DOM / Host adapter 权限突破了当前信任模型 + +**架构基线**在 [APP架构设计.md](../../设计/APP架构设计.md) 的 MiniApp 信任模型中明确: + +- `Interact / System MiniApp` 可使用可信内建 DOM 组件; +- Installed MiniApp 必须经隔离 iframe / Surface Bridge 运行。 + +而 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 同时写了: + +```text +Task Dashboard 可由受信 Host adapter 挂载;Whiteboard 必须走受限 Surface +``` + +并将 Task Dashboard 定义为: + +```text +kind = bundled-reference +``` + +且说明它可使用“随 LineUp 发布的受信 Host adapter”。 + +**问题:** 当前权威架构没有明确授权 `bundled-reference` 获得与 System MiniApp 相同的可信 DOM 容器。若 Host adapter 可执行或挂载 Task Dashboard 的 UI,而未经过受限 Surface,隔离边界、SDK 受限视图和“禁止访问 Host DOM”就无法以同一安全模型证明。 + +**需要冻结的决策(二选一):** + +1. **保守方案,且最符合当前权威基线:** Task Dashboard 和 Whiteboard 都是非 System MiniApp,必须经隔离 Surface / Bridge 运行。Host adapter 仅作为 Runtime/Shell 的不可见装配层,不向 MiniApp 提供可直接操作的可信 Host DOM。 +2. **若产品确实需要 Task Dashboard 使用可信原生 DOM:** 将其升级为 `kind = system`,并在架构主文档新增或明确“bundled trusted system MiniApp”信任层、发布来源、可用 DOM 权限和审计边界;不能继续称其为普通 `bundled-reference`。 + +**建议验收:** + +- 若维持 `bundled-reference`,Task Dashboard 无法获得 Host 根 DOM、Tauri invoke 或未授权 Host adapter。 +- 若改为 `system`,仍要验证其特权只限 Runtime SDK,不能取得 Transport、Store 或 Agent 访问权。 + +## 3. 重要问题 + +### I1. `bundled-development` 与 `bundled-reference` 的术语、Manifest 枚举未建立映射 + +迭代文档把“内置”解释为 `bundled-development`,但冻结的 `MiniAppManifest.kind` 只有: + +```ts +kind: "system" | "bundled-reference"; +``` + +架构主文档也使用了“参考 MiniApp 在此阶段可以是 `bundled-development`”的表述。 + +这两个词可以共存,但必须明确它们不是互相竞争的 `kind` 枚举值。 + +**建议补充最小映射表:** + +| 概念 | 建议语义 | +|---|---| +| `bundled-development` | 本迭代的交付/加载方式:随开发 Host 预置,不下载、不安装、不经服务端 Catalog。 | +| `kind = bundled-reference` | Manifest 身份:参考 MiniApp,决定 SDK / Surface 信任策略。 | +| `kind = system` | 随产品发布、代码可信的系统 MiniApp,例如 Interact。 | + +并明确:`bundled-development` **不是** `MiniAppManifest.kind` 的第三个取值。 + +### I2. `sdk.ui` 缺少 Renderer 如何安全提交用户结果给 Runtime 的闭环契约 + +当前公开 API 只有: + +```ts +request(...) +get(...) +subscribe(...) +cancel(...) +``` + +但未定义: + +- Runtime 如何把被 Policy 选中、可呈现的 interaction record 交给 renderer; +- renderer 如何提交 `submitted`、`result` 或 `cancelled`; +- Runtime 如何校验 `interaction_id`、owner、renderer binding、状态迁移、一次性提交和结果 schema; +- 在 opaque-origin iframe / Surface Bridge 内,呈现和回传采用何种受限消息协议; +- renderer 与 owner 是同一 MiniApp 时,是否通过 `sdk.ui` 提交,还是只能通过 Runtime/Shell 私有 Renderer API 提交。 + +**建议冻结 Runtime 私有 Renderer Contract / 最小 Bridge Contract:** + +```text +Runtime 分配 interaction_id + renderer_binding + presentation container/surface +→ renderer 仅接收 presentation-safe request projection +→ renderer 向 Runtime-owned endpoint 提交 action/result +→ Runtime 校验: + - 被分配的 renderer / container + - interaction_id + - owner binding + - 当前状态与一次性提交 + - result schema + - expiry +→ Runtime 持久化状态迁移,并向 owner-scoped ui 订阅发出更新 +``` + +隔离 Surface 至少还要限定消息白名单、`surface_id` / `instance_id` 绑定和 nonce 或等效关联方式;不得只凭 origin 信任。 + +### I3. 标准交互与 `RuntimeToolCallRecord` 的关系不清晰 + +当前文档同时规定: + +- MiniApp 可调用 `sdk.ui.request(...)`; +- owner 的 source 可为 `agent_tool | miniapp_tool | runtime_policy`; +- 所有标准交互“必须映射为 Runtime 统一的 `interactive` Tool Call / `StandardInteractionRecord`”。 + +因此会产生问题:Task Dashboard 在没有 Agent Tool Call、例如用户点击“关闭未完成任务”时调用 `sdk.ui.request(confirm)`,Runtime 是否必须凭空创建 Agent-facing `RuntimeToolCallRecord`? + +**建议明确拆成三条路径:** + +| 来源 | Runtime 记录 | 与 Agent Tool 的关系 | +|---|---|---| +| Agent / legacy `lineup.v1.tool.call(choice/confirm/input)` | `RuntimeToolCallRecord` + `StandardInteractionRecord` | 交互终态受控驱动该 Tool 的后续处理。 | +| MiniApp `sdk.ui.request(...)` | owner-bound `StandardInteractionRecord` | 不自动创建新的 Agent Tool;若提供合法 `parent_call_id`,只建立关联。父 Tool 的完成由 owner 经 `sdk.tools.*` 决定。 | +| Runtime Policy 自发提示 | `runtime_policy` owner 的 interaction record | 需明确其是否绑定某一 Capability / lifecycle request。 | + +没有 parent Tool 的交互只改变 MiniApp 本地工作流,不应制造 Inventory、Agent 回传或 outbox 语义。 + +### I4. 结果投递位置与公开 API 不一致 + +流程写“结果仅投递回 owner MiniApp 的 Tool/Inbox”,但 SDK 已公开 `sdk.ui.get/subscribe`。 + +否则实现可能分叉成: + +1. 结果通过 `sdk.ui.subscribe` 的 record update 返回; +2. 结果作为 Inbox 消息; +3. 结果通过 parent Tool 的 SDK Tool event 返回。 + +**建议冻结为:** + +```text +sdk.ui.get / sdk.ui.subscribe += owner 获取标准交互状态与结果的唯一 UI-domain 通道 + +sdk.inbox += Agent / Runtime 入站消息;不复用为 interaction result transport + +sdk.tools.complete / fail / cancel += owner 根据 UI result 自行决定是否推进关联 parent Tool +``` + +还需定义:订阅是否立即回放 owner 当前 pending records、终态保留/清理策略、断线重连后的重放及去重语义。 + +### I5. Schema、状态机、幂等规则不足以支持安全验收 + +已有四类 request 及基础状态,但还需要明确: + +- `StandardInteractionResult` 与 `InteractionReceipt` 的类型; +- `choice.actions[].id` 的唯一性、最大数量、空数组是否允许,以及 single/multi/button 的选择基数; +- `input.fields[].id` 的唯一性、字段数上限、必填、数值解析、空字符串、长度和范围的适用规则; +- `notice` 的“只读”与 `acknowledged` 的精确定义:是否需要用户确认、是否自动完成、是否可超时; +- `request`、`submit`、`cancel`、超时、恢复之间的合法状态迁移; +- 并发提交、重放、重复取消、跨 Surface 重放的确定性返回; +- 交互专用稳定拒绝码。 + +建议至少增加: + +```text +interaction_not_found +interaction_owner_mismatch +interaction_state_invalid +interaction_expired +interaction_result_invalid +interaction_renderer_mismatch +parent_call_invalid +``` + +### I6. 持久化、审计与敏感输入的日志最小化约束尚未衔接 + +`input` 可以含用户自由文本;标准交互要求持久化、恢复和审计,但架构文档又要求日志不得包含身份、会话、消息正文、Tool 参数、token 或 artifact 内容。 + +**建议明确:** + +- 恢复所需的受保护 operational state、审计最小元数据、日志/Telemetry 三者的分层; +- `input` 草稿和结果的保留期、终态清理策略,以及是否加密; +- 审计仅记录 interaction ID、kind、受保护 owner 标识、状态迁移和错误码; +- title、prompt、字段值和 action label 不进入日志、指标、错误详情或普通审计明文; +- 验收增加日志负向断言。 + +## 4. 建议完善项 + +### S1. 扩大 `parent_call_id` 校验条件 + +除“属于当前实例且状态允许关联”外,Runtime 至少应校验: + +```text +同 app_scope +同 instance_id +同 conversation_id +父 Tool 非终态 +不存在跨 owner 关联 +``` + +还要定义父 Tool 被取消或超时时关联 interaction 的处理:自动取消、保留为独立操作,或交由 Policy 决定。否则恢复后容易遗留孤儿卡片。 + +### S2. 将 presentation policy 写成可判定矩阵 + +至少应覆盖: + +| owner 状态 / 容器 | 建议行为 | +|---|---| +| Agent IM / Interact 有效 | Interact IM timeline/card。 | +| owner 前台且存在合法 renderer | owner 容器或已绑定 Surface 的标准 modal/sheet/card。 | +| owner 后台或 suspended | 保持 pending;通知/唤醒仅经 Policy,不得抢占焦点。 | +| Surface 已关闭或失效 | 不向旧 Surface 投递;恢复、降级或安全失败。 | +| renderer 不可用 | 保持 pending 或返回稳定拒绝码;不能把 payload 注入 Host DOM。 | +| 系统能力确认 | 走 Capability Gateway,禁止降级为普通 `confirm`。 | + +### S3. Interact 呈现与 Agent Tool 回传的分层 + +**已确认(2026-08-05):** 用户在 Interact 中回答时,Interact 只负责显示问题、收集用户动作并把 +答案交给 Runtime。它不是 Agent 的消息发送端,也不负责判断答案属于哪个 Agent、哪个会话或哪个 App +子会话。 + +```text +Interact / Shell 显示问题 +→ 用户作答 +→ Interact 向 Runtime 提交「interaction_id + 用户动作/答案」 +→ Runtime 从已保存的交互记录取得 agent_id、conversation、App 子会话和 Agent Tool 归属 +→ Runtime 核对 Interact 实例、展示位置、状态、期限、答案格式和首次提交 +→ Runtime 持久化会话记录与终态,并写入可靠 outbox +→ Runtime 向当前唯一 Agent 回传一次结果 +``` + +提交端不能传递或覆盖 `agent_id`、`conversation_id`、`call_id`、`app_session_id` 等归属信息;这些 +只能由 Runtime 创建交互时保存。重复点击、刷新后的旧页面、无效展示位置、过期或已结束问题的提交都 +必须被拒绝,且不能改变已经保存的结果或再次通知 Agent。Task Dashboard、Whiteboard 等 bundled +MiniApp 始终不参与这条链路,也读不到问题或答案。 + +### S4. 统一阶段编号 + +**已确认(2026-08-05):** Task Dashboard 与 Whiteboard 是 `03.sdk_and_coreapp` 中用来验证 SDK 的 +bundled 参考 MiniApp,本阶段就完成其端到端路径。因此删除重复的 `03.reference-miniapps`;后续阶段 +保持为 `04.app-delivery-registry`,不需要整体重编号。 + +### S5. 在实施步骤中拆出标准交互服务和 renderer/Bridge 契约 + +**已确认(2026-08-05):** 实施顺序已调整为: + +1. 冻结契约与测试样本; +2. 实现 Runtime 通用 Tool 闭环; +3. 实现 Runtime 独占的 Agent interaction service,以及 Runtime ↔ Interact 的最小呈现/提交契约; +4. 再适配 Interact IM 和 App 子会话; +5. 最后通过 Task Dashboard、Whiteboard 验证一般 bundled MiniApp 路径,并进行端到端验收。 + +这样标准交互不会先被做成 Chat 专用或 MiniApp SDK 能力;Interact 只是第一个使用 Runtime 私有交互 +契约的系统级呈现者,两个 bundled MiniApp 则只验证各自的 Tool、Surface 和业务 UI 边界。 + +## 5. 建议新增的最小验收场景 + +1. A 实例提交属于 B 实例、不同 conversation 或已终态 Tool 的 `parent_call_id` 时,Runtime 拒绝且不创建 record。 +2. A 创建 interaction 后,B 无法 `get`、订阅、取消、提交,或通过伪造 `interaction_id` 获得任何 payload/result。 +3. 同一 interaction 的两次 renderer submit 只有一次成功;第二次得到稳定终态错误且不改变已持久化结果。 +4. 旧 `lineup.v1.tool.call(choice|confirm|input)` 创建关联 Tool Call 和 owner 为 Interact IM context 的 interaction;用户结果先由 Runtime 落库,再由 Interact 以 owner 身份完成 Tool。 +5. Task Dashboard 无 parent Tool 的 `sdk.ui.request(confirm)` 不创建 Agent-facing Tool Call、不写 Agent outbox;带合法 parent Tool 时仅关联,不自动完成该 Tool。 +6. Task Dashboard 前台时,标准 modal/sheet/card 在其合法容器显示,焦点栈与 Interact background 状态不被改写。 +7. Whiteboard 在 Surface reload、关闭、旧 iframe 重放提交时,`surface_id` 与 renderer binding 都被验证,旧 iframe 消息被拒绝。 +8. 非法 input、重复/未知 choice action、过期 interaction、跨 owner cancel 都被拒绝且不写错误结果。 +9. 标准交互输入值、提示正文、选择标签不出现在日志、Telemetry 或 Audit 明文中;重启恢复仅保留设计允许的最小数据。 +10. 若维持 `bundled-reference`,Task Dashboard 不能直接挂载可信 Host DOM;若改为 `system`,必须有对应的 manifest/source-trust 回归用例。 + +## 6. 建议的阅读和决策顺序 + +明天继续时,建议按以下顺序决策和回填: + +1. 先决定 **B1:Task Dashboard 的信任级别与 UI 容器**; +2. 冻结 **I2:Renderer → Runtime 的提交/Bridge 契约**; +3. 冻结 **I3/I4:StandardInteractionRecord 与 Tool Call 的关系,以及 UI result 的唯一返回通道**; +4. 细化 **I5/I6:schema、状态机、幂等、错误码、持久化与日志最小化**; +5. 把 S1~S5 与第 5 节的验收场景回填入 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 和必要的 [APP架构设计.md](../../设计/APP架构设计.md)。 + +在上述收敛前,不建议开始 `standard-interaction-service` 或参考 MiniApp 的实现,以免重新形成 Chat / Interact 专用旁路。 diff --git a/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/02.design_review.md b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/02.design_review.md new file mode 100644 index 0000000..c107d10 --- /dev/null +++ b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/02.design_review.md @@ -0,0 +1,164 @@ +# `03.sdk_and_coreapp` 第 2 次设计评审记录 + +> 评审编号:02 +> 评审日期:2026-08-05 +> 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md)、[01.design_review.md](01.design_review.md) +> 评审方式:独立子 agent 只读复核 +> 结论:核心方向已收敛;没有 P0 阻塞问题。以下 P1 项需在开始编码前逐项确认并回填主定义文档。 + +## 问题清单(Outline) + +> **状态标记:** ✅ 已确认并回填设计; 🟡 待产品/架构决策; 🔵 待在契约中细化; ⚪ 文档或实施编排建议; — 不适用或无此问题。 +> +> 本清单是本次评审的当前有效视图。后文保留每个问题的背景和建议;在问题被确认前,建议不是实施依据。 +> 确认后应先更新本清单,再回填 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 的契约、实施步骤和验收项。 + +| 状态 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---|---|---| +| — | P0 | 阻塞级架构冲突 | 本次未发现需要推倒既有模型的 P0 问题。 | +| ✅ | P1-1 | Task Dashboard 示例中 Tool 完成后关闭 App | 已回填:Tool 完成只回传 Agent;App 保留、后台或关闭由用户动作、`requestClose` 或 Lifecycle Policy 决定,只有真正 `AppLifecycleManager.close` 才结束 App 与子会话。 | +| ✅ | P1-2 | `interactive` Tool 的专用路由 | 已回填:`interactive` 是 Runtime 自己处理的 Agent 交互分支,不创建/复用业务 MiniApp instance,不调整业务 App 焦点,也不进入任何 MiniApp SDK Tool Inbox;Runtime 私有契约交给 Interact / Shell 呈现。 | +| — | P1-3 | 展示位置切换后的提交资格 | 不再适用:展示位置不是交互 owner 或提交权限。切换 App 前后台、关闭 App 或改在 IM 显示不改变同一 `interaction_id` 的归属;Runtime 只接受首次有效回答,之后才拒绝重复/重放。 | +| ✅ | P1-4 | Tool 与交互的取消、超时和并发提交 | 已回填:用户回答、Agent dismiss、到期和 Runtime 失败竞争同一唯一终态;Runtime 第一个原子状态写入获胜,Tool 跟随同一结果,且只写一条 outbox。interactive Tool 仅使用交互 `expires_at`,不维护独立超时。 | +| ✅ | P1-5 | `notice` 的 Tool 完成时点与结果 | 已回填:notice 必须保留为 Interact / IM 的只读卡片,可选同时 Toast 数秒;Runtime 持久化卡片并安排呈现后,立即以 `{ outcome: "accepted" }` 完成并回传,不表示用户已阅读。 | +| — | P2-1 | 将普通标准交互统一当作敏感秘密输入 | 不再适用:`notice / choice / confirm / input` 按普通 IM 会话内容与日志基线处理;本迭代不为它们另设秘密输入契约或专门负向扫描。未提交草稿仍只在运行期存在,重启清空。 | +| ⚪ | P4-1 | 首次评审中的历史提案可读性 | **遗留问题:** 不阻塞本迭代;建议在下一次文档整理或新评审时,进一步突出其中 `sdk.ui`、owner、`parent_call_id` 等旧提案仅供追溯、不可实施。 | +| ⚪ | P4-2 | `password-input` 秘密输入原语 | **遗留问题:** 当前不阻塞 `03`;真正出现密码、卡密、私钥或临时 token 的 Agent 交互需求时,单独定义该原语及其不显示明文、不进入普通 IM 正文/日志/Telemetry/审计、可靠传递与清理等安全契约。 | + +## 通过项 + +- 标准交互已经清楚限定为 Interact 中的人与 Agent 会话交互,而非 MiniApp SDK;公开 SDK 不包含 `sdk.ui`。 +- Task Dashboard、Whiteboard 已收敛为 `kind = bundled`,必须走受限 Surface / Bridge,不能取得可信 Host DOM、Tauri、Transport、Store、Agent 或其他 MiniApp 数据。 +- 用户答案经 Interact 交给 Runtime;Runtime 保存交互归属、校验、持久化并可靠回传当前唯一 Agent,bundled MiniApp 不参与。 +- App 子会话正确区分了人与 Agent 的记录和 MiniApp 内部业务操作;前后台、关闭、历史只读和“继续处理”的总体规则一致。 +- MVP 单 Agent 边界明确,没有提前引入多 Agent 连接、切换或路由。 + +## P0:阻塞问题 + +无。 + +## P1:开始编码前需要确认的事项 + +### P1-1:Tool 完成不应自动关闭 Task Dashboard + +**已解决(2026-08-05):** 主定义文档已经确认“Agent Tool 完成不自动关闭 App,也不结束 App +子会话”。Task Dashboard 示例与验收现已统一为该规则。 + +当前规则: + +```text +Tool 完成 +→ Runtime 持久化并回传 Agent +→ App 是否保持前台、进入后台或关闭,取决于用户动作、MiniApp requestClose 或 Lifecycle Policy +→ 只有真正执行 AppLifecycleManager.close,才结束 App 与 App 子会话 +``` + +验收已要求:Tool 成功回传后 Dashboard 可继续使用;只有真正关闭才结束子会话。 + +### P1-2:interactive Tool 必须不进入 MiniApp SDK Tool Inbox + +**已解决(2026-08-05):** 通用 Tool 路由、实施步骤和验收已明确拆开 `interactive` 与一般 Tool。 +`notice / choice / confirm / input` 不会投递到 App Orchestrator、业务 MiniApp instance、SDK Tool Inbox +或 `sdk.tools.subscribe`。 + +当前规则: + +```text +handling = interactive +→ 不投递任何 MiniApp SDK Tool Inbox +→ Runtime Agent interaction service 创建 StandardInteractionRecord +→ Runtime 私有契约交给 Interact / Shell 呈现 +→ Interact 提交 interaction_id + 用户动作/答案 +→ Runtime 校验、持久化、outbox 回传 Agent + +handling = direct / launch / foreground / operation +→ 才进入 App Orchestrator、SDK Tool Inbox、MiniApp tools.* 路径 +``` + +验收已要求:Agent 发起的四类标准交互不得出现在任何 MiniApp Inbox 或 `sdk.tools.subscribe`。 + +### P1-3:切换展示位置后,旧页面必须失去提交资格 + +**不再适用(2026-08-05):** 该问题错误地把 App 上方覆盖层或 IM 卡片当成了交互的 owner。标准交互 +始终属于 Interact / 主 IM;`app_session_id` 只保留与哪段 App 工作相关的上下文,不形成 UI 父子关系。 + +切换 App 前后台、关闭 App 或改变展示位置不改变同一个 `interaction_id` 的归属,也不需要用 +`presentation_id` / `presentation_revision` 废止旧页面的回答资格。只要用户仍处于有效 LineUp / +Interact 会话且交互尚未终态,Runtime 可接受该交互的首次有效回答;以后到达的重复、重放或迟到提交 +因交互已经终态而被拒绝。Shell 可以避免用户看到重复视觉卡片,但这是体验策略,不是结果正确性的依据。 + +验收应覆盖:交互从 Whiteboard 覆盖层改在 IM 中显示、Whiteboard 关闭后交互仍 pending 时,任一有效 +Interact 页面提交的第一份答案只产生一次持久化结果和一次 Agent 回传;后续提交不改变结果。 + +### P1-4:Tool 与交互的取消、超时和并发提交需要收敛规则 + +**已解决(2026-08-05):** Runtime 是标准交互的唯一终态裁决者。用户回答、Agent dismiss、到期和 +Runtime 失败都竞争同一条交互的唯一结果。 + +当前规则: + +```text +Runtime 对仍为 pending / presented 的 interaction 执行原子状态写入 +→ 第一个成功写入的动作获胜 +→ 用户回答:持久化答案,Tool 得到 answered +→ Agent dismiss:交互与 Tool 得到 cancelled +→ expires_at 到期:交互与 Tool 得到 expired +→ Runtime 失败:交互与 Tool 得到 failed +→ 同一事务或等价原子动作中只创建一条 Tool 结果 outbox + +任何后到的回答、dismiss、取消或超时处理 +→ interaction_already_final +→ 不覆盖结果,不创建第二条 outbox +``` + +MVP 中 interactive Tool 没有另一套独立 timeout;其期限就是交互 `expires_at`。Agent 主动停止等待必须走 +`dismiss`,不走独立的通用 Tool 取消路径。 + +### P1-5:`notice` 的完成时点和 Tool 结果需要固定 + +**已解决(2026-08-05):** `notice` 是 Interact / IM 中持久保留的只读卡片;Toast 只是同一条 notice +记录的可选短暂提醒,不是没有历史的独立消息。 + +```text +Runtime 校验并持久化 notice 卡片 +→ 交给 Interact 呈现 +→ 当前前台界面可同时 Toast 数秒 +→ Runtime 立即以 { outcome: "accepted" } 完成并回传一次 Tool 结果 +``` + +`accepted` 只表示 Runtime 已接受、保存并安排呈现,不表示用户已看见或阅读。Toast 自动消失、用户忽略 +Toast 或收起 notice 卡片,均不产生新的 Agent 结果;持久化或安排呈现前 Runtime 失败时,结果为 `failed`。 + +## P2:本迭代验收前必须处理(本次无未决项) + +### P2-1:将普通标准交互统一当作敏感秘密输入 + +**不再适用(2026-08-05):** 此问题把所有标准交互一概当成秘密输入,边界过重且不符合产品语义。 +`notice`、`choice`、`confirm` 与普通 `input` 是人与 Agent 的正常 IM 会话内容:已提交的内容按普通 IM +会话历史、保留与日志基线处理,不在 SDK v1 中额外定义为密码或秘密。全局日志基线仍然有效,不能因此 +随意打印会话正文、Tool 参数或 token。 + +未提交的 `input` 草稿仍只能存在于当前运行期;重启后清空,不自动提交、发送或进入 outbox。这是恢复 +和正确性要求,不代表普通 `input` 已升级为秘密输入能力。 + +## P4:遗留问题(不阻塞当前迭代) + +### P4-1:首次评审中的历史提案可读性 + +**延期原因:** 顶部 Outline 和总体结论已经说明早期 `sdk.ui`、owner、`parent_call_id` 等提案不再是 +实施依据;这不影响当前 SDK、Runtime 或验收实现。 + +建议在下一次文档整理或新的设计评审时,将原始评审正文加上“仅供追溯,禁止作为实现、测试或验收依据” +的更醒目标识,或移至附录。当前有效结论始终以主定义文档和评审记录顶部的 Outline 为准。 + +### P4-2:`password-input` 秘密输入原语 + +**延期原因:** 当前 `03` 要完成的是 `notice / choice / confirm / input` 的 SDK v1 与会话交互闭环; +目前没有真实的密码、卡密、私钥或临时 token 输入场景。把秘密输入仅做成普通 `input` 的掩码样式,不能 +解决内容怎样保留、传递、重试和清理的问题,因此不在本迭代仓促实现。 + +**重新评估条件:** Agent 确实需要向用户收集密码、卡密、私钥、临时 token 或同类秘密时,启动单独设计。 +届时 `password-input` 应作为与 `input` 并列的 Agent 交互原语,由 Runtime 按 `kind` 强制执行安全契约, +而不是允许 Agent 用普通 `input` 自行约定保护方式。至少要明确:界面不显示明文;主 IM 只保留“已提交 +敏感信息”等替代记录;内容不进入普通日志、Telemetry 或审计;可靠 outbox 的暂存、加密(如需要)、 +Agent 接收后的清理、重启、失败、重试与一次性传递规则。 diff --git a/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/03.design_review.md b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/03.design_review.md new file mode 100644 index 0000000..75bff2b --- /dev/null +++ b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/03.design_review.md @@ -0,0 +1,174 @@ +# `03.sdk_and_coreapp` 第 3 次设计评审记录 + +> 评审编号:03 +> 评审日期:2026-08-05 +> 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) +> 参考资料:[APP架构设计.md](../../设计/APP架构设计.md)、[01.design_review.md](01.design_review.md)、[02.design_review.md](02.design_review.md) +> 评审方式:基于当前主定义的独立只读复核;重点检查已确认的边界在契约、实施步骤与验收目标之间是否能够由同一套实现兑现。 +> 结论:产品层级、标准交互归属和受限 MiniApp 信任模型已稳定;未发现 P0 架构冲突。经本轮统一收敛,3 项 P1 与 2 项 P3 均已确认并回填主定义文档;后续不再进行纯设计评审,直接进入实现,并在实现完成后做一次关闭式复核。 + +## 问题清单(Outline) + +> **状态标记:** ✅ 已确认并回填设计; 🟡 待产品/架构决策; 🔵 待在契约中细化; ⚪ 文档或实施编排建议; — 不适用或无此问题。 +> +> 本清单是本次评审的当前有效视图。后文说明问题为何会造成实现分歧,并给出需要冻结的最小决策;在问题确认前,建议不是实施依据。确认后应先更新本清单,再回填 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 的契约、实施步骤和验收项。 + +| 状态 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---|---|---| +| — | P0 | 阻塞级架构冲突 | 未发现。Runtime 为唯一通信与裁决方、标准交互属于 Interact、bundled MiniApp 受限运行的主线一致。 | +| ✅ | P1-1 | 用户关闭 App 时,尚未结束的普通 MiniApp Tool 怎样收敛 | 已确认并回填:一旦 Runtime 接受关闭,未终态的普通 Tool 原子转为 `cancelled(app_closed)`;已提交结果只继续 outbox;随后关闭 Surface、停止 instance、结束子会话并恢复焦点。标准交互不自动取消。 | +| ✅ | P1-2 | Agent `dismiss` 等待中的标准交互,缺少可执行的控制契约 | 已确认并回填:Runtime 私有 `interaction.dismiss` 以 `control_id + call_id` 请求;Runtime 从受认证 Envelope 推导 Agent/会话,幂等处理并只写唯一 cancelled outbox。App 已关闭事件带出仍 pending 的交互 call_id。 | +| ✅ | P1-3 | `interactive` Tool 如何确定 App 子会话关联,和通用 `ToolDescriptor.target` 怎样一致 | 已确认并回填:无 `app_session_context` 一律归主 IM;有上下文时 Runtime 验证同 Agent、同父会话的 App 子会话,已关闭会话可仅作历史关联。interactive 不使用普通 Tool target。 | +| ✅ | P3-1 | 标准交互请求类型不能直接作为 fixture 或代码依据 | 已确认并回填:去除重复字段,定义请求联合类型;可回答交互使用 Runtime 计算的 `expires_in_ms`,默认 15 分钟、范围 1 分钟至 24 小时;notice 不接受期限。 | +| ✅ | P3-2 | Whiteboard 的完成后关闭表述与统一生命周期规则冲突 | 已确认并回填:所有 bundled App 的普通 Tool 完成只结束 Tool;关闭 Surface/instance/子会话只能由显式 Lifecycle 关闭处理。重复目录条目已删除。 | + +## 通过项 + +- `Interact` 是唯一 `kind = system` MiniApp;Task Dashboard 与 Whiteboard 均是 `kind = bundled`,没有可信 Host DOM、Tauri、Transport、Store、Agent 或任意网络特权。 +- `notice / choice / confirm / input` 始终是人与 Agent 的会话交互,不进入任何 MiniApp SDK Inbox 或 `sdk.tools.subscribe`;前台 bundled App 上的视觉覆盖不成为 owner 或提交权限。 +- Runtime 保存交互归属并以原子状态写入决定唯一结果;用户首次有效回答、Agent dismiss、到期和 Runtime 失败不会产生多条 Agent outbox。 +- App 子会话只保存人与 Agent 围绕 App 的交互,不记录 Dashboard 表单、画板编辑等 App 内部业务动作;关闭 App 不自动取消仍 pending 的 Interact 交互。 +- 普通 `input` 按现有 IM 内容与日志基线处理;秘密输入已正确作为未来独立的 `password-input` P4 遗留事项保留在 [02.design_review.md](02.design_review.md)。 + +## P0:阻塞问题 + +无。 + +## P1:开始相关编码前必须确认(均已解决) + +### P1-1:用户关闭 App 时,尚未结束的普通 MiniApp Tool 怎样收敛 + +**用人话说:** 用户把 Task Dashboard 或 Whiteboard 关掉时,Runtime 不能让刚才交给那个 App 的工作 +悬在半空。Agent 要么收到“这项工作已取消/失败”的唯一结果,要么 Runtime 明确保留一个可恢复、仍有执行者 +的工作;不能只关闭画面而不说明 Tool 的命运。 + +当前文档同时出现了三种没有被统一的说法: + +```text +Task Dashboard Tool 完成 +→ 不自动关闭 Dashboard 或 App 子会话 + +Whiteboard App 完成或失败 +→ Runtime 关闭 Surface、恢复 Interact + +MiniApp requestClose() +→ Runtime 检查 pending Tool、Surface、operation 和 Policy +→ 允许关闭或返回拒绝码 +``` + +最后一条没有说明“检查之后”的规则。若用户关闭正在执行 Tool 的 App,Tool 是被拒绝关闭、由 Runtime 先取消 +Tool、由 App 收到取消后再关闭,还是允许 Surface 消失但实例在后台恢复执行?这些选择对 outbox、恢复和用户 +看到的状态都不同。Whiteboard 的“完成后关闭”也与 Task Dashboard 的“完成不关闭”相互冲突。 + +**已确认(2026-08-05):** 一旦 Runtime 接受用户、MiniApp 或 Policy 的关闭请求,关闭的意思就是释放该 +App instance,不是把它偷偷留在后台。Runtime 不再向该 instance 投递新 Tool;对绑定该 instance 且仍为 +`received / routing / waiting_for_app / running` 的普通 Tool,以 `app_closed` 原因原子转为 `cancelled`,并且 +每条 Tool 只写一条 cancelled outbox。已处于 `submitted` 的 Tool 结果不可改写,Runtime 继续将已固定的结果 +可靠发出。之后 Runtime 关闭 Surface、停止 instance、结束 App 子会话并恢复前一有效前台 App。 + +同一时刻 Tool 完成与关闭竞争时,第一个原子终态写入获胜;重复关闭、重启恢复和迟到上报不得产生第二条 +outbox。用户若只想暂时离开 App,应进入后台而不是关闭。此规则只处理普通 MiniApp Tool;仍 pending 的 +人与 Agent 标准交互不自动取消,Runtime 仅通知 Agent,由 Agent 选择是否 dismiss。 + +以下原“需要冻结”的项目均由上述决议覆盖: + +1. 对每种 Tool 状态(`waiting_for_app`、`running`、`submitted` 等),规定用户/Policy 请求关闭时的行为; +2. 规定普通 Tool 的最终结果由谁写入、是否必须先让 Tool 进入 `completed / failed / cancelled / expired` 才能完成关闭; +3. 明确“关闭 App instance”“关闭该 App 的 Surface”“Tool 成功/失败”三者不是同一个事件,并定义允许的先后顺序; +4. 将 Task Dashboard 与 Whiteboard 的完成、关闭和焦点恢复规则统一到同一条生命周期原则; +5. 增加关闭进行中 Tool、重启恢复和重复关闭只产生一个最终 Tool outbox 的验收 fixture。 + +### P1-2:Agent `dismiss` 等待中的标准交互,缺少可执行的控制契约 + +**用人话说:** 文档已经允许 Agent 看到“画板已关闭”后,决定把之前的问题收起来。这很好;但还没有写清 +Agent 发来的“收起这题”消息长什么样,Runtime 怎么确认它收的是正确那一道题,以及网络重发时如何不重复 +通知 Agent。实现者因此可能各自发明一个临时控制消息。 + +当前仅有行为描述: + +```text +Agent remote dismiss +→ Runtime 将 pending / presented interaction 终结为 cancelled +→ 写入唯一 cancelled outbox +``` + +但缺少下面的契约: + +- Agent 发起 `dismiss` 使用 `interaction_id`、原始 `call_id`,还是二者都使用; +- Runtime 如何从已保存记录校验 `agent_id`、`conversation_id` 与当前状态,而不是信任客户端字段; +- 目标已回答、已到期或同一条 dismiss 重放时,应得到何种稳定回执,是否绝不新增 outbox; +- Runtime 发出的 App 已关闭事件如何关联原 Tool/interaction,使 Agent 能准确选择要 dismiss 的问题; +- dismiss 控制请求本身如何去重、审计并在 Runtime 重启后恢复处理。 + +**已确认(2026-08-05):** 定义 Runtime 私有的 Agent → Runtime `interaction.dismiss` 控制契约。Agent 使用 +自己发起原 interactive Tool 时持有的 `call_id` 定位目标,并为每次控制请求提供唯一 `control_id`。Runtime 从 +受认证 Envelope 取得 `agent_id` 与 `conversation_id`,不信任请求额外携带的归属字段;它以这两个值和 +`call_id` 查找交互,原子地将仍 pending / presented 的记录转为 cancelled 并写入唯一 outbox。已终态或重放 +请求只返回稳定幂等回执,不覆盖结果、不再写 outbox。 + +App 已关闭事件会携带仍 pending 的 `pending_interaction_call_ids`,让 Agent 能按业务需要精确 dismiss;该控制 +契约不属于 MiniApp SDK,也不能投递给 Interact 或 bundled MiniApp。 + +### P1-3:`interactive` Tool 如何确定 App 子会话关联,和通用 `ToolDescriptor.target` 怎样一致 + +**用人话说:** 视觉上哪个 App 在前台,不等于 Agent 的问题就属于哪个 App。例如用户正看 Whiteboard, +Agent 仍可能问一条普通 IM 问题;反过来,画板已经关了,Agent 仍可能针对刚才的画板提问。因此 Runtime +不能用“当前前台 App”猜测问题应该放进哪个子会话。 + +当前记录有可选 `app_session_id`,且要求与 App 有关的问题写进子会话;但没有说明 Agent Tool 请求如何 +表达这层上下文、Runtime 如何验证它。与此同时,所有 `ToolDescriptor` 都要求 `target.app_scope`,而 +`interactive` 明确不创建/复用任何业务 MiniApp,也不投递给 Interact 的 SDK Tool Inbox。这会造成至少两种 +不兼容实现:有人将交互强行 target 到 `chat`,有人直接跳过 `target`,也有人按当前前台 App 自动归档。 + +**已确认(2026-08-05):** 不带 `app_session_context` 的 interactive Tool 一律记录在主 IM,不创建或猜测 +App 子会话。Agent 若要关联某段 App 工作,可在 interactive Tool 的运行时上下文中提供 +`app_session_context.app_session_id`;Runtime 必须验证其属于同一 `agent_id`、父 conversation 和真实 App +子会话。已经关闭的子会话仍可作为历史上下文关联,但不会复活 instance 或旧问题。验证失败则拒绝该 Tool, +不创建交互。 + +`handling = interactive` 不使用普通 MiniApp Tool 的 `target`,也不因此变为 Interact MiniApp Tool。Runtime +在创建 App 子会话时向当前 Agent 可靠发送其稳定 ID,供后续 Agent 交互引用。 + +以下原“需要冻结”的项目均由上述决议覆盖: + +1. 不带经验证 App 上下文的 `interactive` Tool 一律写入主 IM,不创建 App 子会话; +2. 若 Agent 需要关联某段 App 工作,定义它可引用的稳定 App 子会话标识,以及 Runtime 必须验证的 + `agent_id`、父 conversation、App session 状态和权限边界;已关闭 App 关联的问题是否仍允许保留,需要明确; +3. 为 `handling = interactive` 定义与普通 `ToolDescriptor.target` 不同的明确规则:例如 target 对它不适用, + 或只允许一个受限的“会话上下文”字段;不能让它暗中变成 Interact MiniApp Tool; +4. 在 fixture 中覆盖“Whiteboard 前台但问题属于主 IM”“App 已关闭但问题仍关联旧子会话”“非法/跨会话 + `app_session_id` 被拒绝并不产生交互”三种情况。 + +## P2:本迭代验收前必须处理(本次无未决项) + +本次未发现独立 P2 问题。P1 项处理后,应把其中的关闭、dismiss、上下文绑定 fixture 纳入本迭代验收。 + +## P3:本迭代结束前必须处理(均已解决) + +### P3-1:标准交互请求类型不能直接作为 fixture 或代码依据 + +`ChoiceRequest` 代码块中 `prompt` 出现两次。它不是不同字段,直接照抄会造成 TypeScript 重复属性定义。 +此外,记录使用了尚未声明的 `StandardInteractionRequest`,文字规定 Agent 可给出 1 分钟至 24 小时的期限, +但四种最低请求类型都没有统一的期限字段。 + +**已确认(2026-08-05):** 删除重复 `prompt`;`StandardInteractionRequest` 由四种请求组成。可回答交互 +在请求中使用 `expires_in_ms`,Runtime 根据自身当前时间计算并持久化 `expires_at`;未提供时默认 15 分钟, +仅接受 1 分钟至 24 小时。notice 不等待回答,也不接受期限。fixture 覆盖默认、越界和 notice 错带期限。 + +### P3-2:Whiteboard 的完成后关闭表述与统一生命周期规则冲突 + +Task Dashboard 已清楚规定 Tool 完成不会关闭 App;Whiteboard 的最小体验却写成“App 完成或失败,Runtime +关闭 Surface、恢复 Interact”。这会让两个同为 `kind = bundled` 的参考 App 获得不同且没有声明依据的 +生命周期语义,也和“只有 `AppLifecycleManager.close(instance_id)` 才停止实例/结束子会话”不一致。 + +**已确认(2026-08-05):** Whiteboard 与 Task Dashboard 使用相同规则:Tool `complete / fail` 只结束 Tool; +Surface、instance、焦点和 App 子会话仅由前后台或显式 `AppLifecycleManager.close` 处理。SDK v1 不为 +Whiteboard 预设“提交后自动关闭”的例外;将来若需要,必须作为显式 Lifecycle Policy 并遵循 P1-1 的关闭收敛。 + +实现模块目录树中的 `conversation-store.ts` 也重复出现一次,应一并删除重复行,避免错误引导目录改动。 + +## P4~P5:遗留问题 + +本次没有新增 P4/P5。第 2 次评审已登记的 `P4-1`(历史提案可读性)和 `P4-2`(未来 `password-input` +秘密输入原语)继续作为不阻塞当前迭代的遗留问题,其延期原因与重新评估条件以 +[02.design_review.md](02.design_review.md) 为准。 diff --git a/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/03.sdk_and_coreapp.md b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/03.sdk_and_coreapp.md new file mode 100644 index 0000000..7096eba --- /dev/null +++ b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/03.sdk_and_coreapp.md @@ -0,0 +1,1118 @@ +# LineUp App 迭代定义:MiniApp SDK v1 与内置参考 MiniApp + +**迭代编号:** 03.sdk_and_coreapp +**状态:** 已完成(P0~P3 问题已清空,代码、自动化测试和真实浏览器验收通过) +**日期:** 2026-08-05 +**前置基线:** [00.base.md](../00.base/00.base.md)、[01.kernel.md](../01.kernel/01.kernel.md) +**权威架构:** [APP架构设计.md](../../设计/APP架构设计.md) + +## 1. 迭代目标 + +本迭代不建设应用市场、服务端 Catalog、远程下载或第三方发布能力。它的目标是定义并实现 +**LineUp MiniApp SDK v1**,再用多个随 LineUp 开发版本内置的参考 MiniApp 验证这份 SDK。 + +```text +LineUp App + → LineUp Runtime + → LineUp MiniApp SDK v1 + → Interact MiniApp + → Task Dashboard MiniApp + → Whiteboard MiniApp +``` + +本迭代完成后,应能证明: + +```text +Runtime 能以同一套 SDK 语义承载系统级 MiniApp 与受限参考 MiniApp; +MiniApp 只能通过 SDK 收消息、接 Tool、报告进度/结果/错误、请求生命周期与 Surface; +Runtime 始终拥有通信、Tool、焦点、存储、权限和审计的最终决定权。 +``` + +这里的“内置参考 MiniApp”指 Task Dashboard 与 Whiteboard 的 Manifest、代码和测试 fixture 随当前 +LineUp 开发 Host 提供。它们是 `kind = bundled`,不是远程下载的 Bundle,也不等于已经开放的应用市场。 +Interact 同样随产品发布,但它是唯一的 `kind = system` MiniApp。 + +`kind = bundled` 描述非系统级 MiniApp 的当前交付来源与受限运行策略;`kind = system` 只用于随 +LineUp 可信代码发布的系统级 MiniApp。本迭代中只有 Interact 是 System MiniApp;Task Dashboard +与 Whiteboard 都是 `kind = bundled` MiniApp。“参考”只描述它们在本迭代用于验证 SDK 的工作目的, +不是 Manifest 类型名称,也不增加 Host 信任或特权。 + +### 1.1 已确认的实施决议 + +本节是本迭代的实施基线。后文对每项决议给出更完整的契约、状态和验收要求;评审记录中的早期方案 +或未确认建议不得覆盖本节。 + +| 主题 | 已确认的结论 | +|---|---| +| 产品层级 | 产品是 **LineUp App → LineUp Runtime → MiniApps**。Runtime 是 App 内的核心运行环境,不是与 MiniApp 平级的产品。 | +| MiniApp 类型 | `MiniAppManifest.kind` 只有 `system \| bundled`。Interact 是唯一 `system`;Task Dashboard、Whiteboard 都是 `bundled`,必须使用一般 MiniApp 相同的 SDK、权限、生命周期和受限 Surface / Bridge。 | +| MVP Agent 范围 | 一个 LineUp Runtime 只连接一个 Agent。交互和 App 子会话保存当前 `agent_uid` 作为 `agent_id`,但本迭代不做多 Agent 连接、切换、会话列表、outbox 或路由。 | +| 人与 Agent 交互 | `notice / choice / confirm / input` 是 Interact 中人与 Agent 的会话交互,与 IM 消息同层;它们不是 MiniApp SDK,也不用于 App 内业务 UI。只有 Agent Tool 能发起。 | +| 回传责任 | Interact / Shell 只展示问题并收集用户动作/答案。Runtime 保存交互归属,校验提交、持久化、去重、恢复并可靠回传 Agent。bundled MiniApp 不可发起、读取、提交、取消或伪造这类交互。 | +| App 前台时的交互 | bundled App 在前台时,Interact / Shell 可以把同一会话的交互层显示在其上方;视觉覆盖不改变交互归属,App 也不会取得问题或答案。 | +| App 子会话 | 每个启动的 App instance 创建或恢复一个与主 IM 关联的子会话。它只记录人与 Agent 围绕该 App 的说明、提问、回答和简洁结果;App 内按钮、表单、画板编辑等业务操作不记录。 | +| 子会话生命周期 | 前后台切换、暂停和 Agent Tool 完成都不结束子会话。只有 `AppLifecycleManager.close(instance_id)` 使 instance 停止或失败时,才结束子会话并在主 IM 保留折叠历史;未回答的 Interact 交互不随 App 自动取消,Runtime 会将关闭事件通知 Agent。 | +| App 关闭与普通 Tool | Runtime 一旦接受关闭请求,即停止向该 instance 投递新 Tool,并将其未终态普通 Tool 原子收敛为 `cancelled(app_closed)`;已提交结果只继续可靠发送。随后关闭 Surface、停止 instance、结束子会话并恢复焦点。用户若仅暂时离开 App,应进入后台而不是关闭。 | +| App 子会话关联 | Agent 的 interactive Tool 未带 `app_session_context` 时归主 IM;只有 Runtime 验证通过的 `app_session_id` 才能关联子会话,绝不从当前前台 App 猜测。已关闭子会话可保留为历史上下文,不会复活旧 instance。 | +| 历史与继续处理 | 已结束子会话只能只读查看。用户选择“继续处理”时,Runtime 创建新的 instance 和新的子会话;可引用旧 artifact 或已保存状态,但不续写旧记录,也不复活旧问题。 | +| 草稿与已提交答案 | 未提交 `input` 草稿仅存在当前运行期间,重启即清空,不自动提交或发送;已提交答案保存在所属会话历史中,二者均遵循普通 IM 的内容保留与日志基线。SDK v1 不把普通 `input` 统一视为秘密输入;`password-input` 不在本迭代范围。 | + +## 2. 本迭代的产品与信任模型 + +### 2.1 正确层级 + +```text +LineUp App 用户使用的主应用 +├── App Shell / Host 挂载、切换、通知、恢复、Host Provider +├── LineUp Runtime 通信、可靠性、MiniApp 调度和安全决策 +└── MiniApps 运行在 Runtime 上的功能单元 + ├── System MiniApp: Interact + ├── Bundled MiniApp: Task Dashboard(SDK 参考实现) + └── Bundled MiniApp: Whiteboard(SDK 参考实现) +``` + +Runtime 不是一个与 MiniApp 平级的产品 App;它是 LineUp App 内部提供给 MiniApp 的运行环境。 + +### 2.2 Interact 的定位 + +`Interact` 是第一个系统级 MiniApp,是用户与 Agent 的默认入口: + +```text +Interact +├── IM Mode:本迭代必须实现并适配 SDK +├── Audio Mode:只定义 SDK / 生命周期扩展位,不实现真实媒体能力 +├── Video Mode:只定义 SDK / 生命周期扩展位,不实现真实媒体能力 +└── IM 标准交互的默认 Renderer Provider:notice / choice / confirm / input +``` + +当前实现仍保持兼容名称: + +```text +产品概念:Interact MiniApp +当前 app_scope:chat +当前目录:tauri/src/core-apps/chat/ +``` + +本迭代不得把 `chat` 重命名为 `interact`,不得迁移目录或旧 `lineup.v1` scope;命名迁移另行 +定义,避免破坏已验证的通信与恢复行为。 + +### 2.3 相同语义,不同权限视图 + +所有 MiniApp 都遵守 MiniApp SDK v1 的同一语义;系统级与参考/已安装 MiniApp 的区别是来源和 +UI 容器,而不是是否可以绕过 Runtime。 + +| 能力 | Interact System MiniApp | Task Dashboard / Whiteboard | +|---|---:|---:| +| 读取自身 Inbox、ACK | 可以 | 可以 | +| 接收 Runtime Tool Call | 可以 | 可以 | +| 上报 progress / result / error | 可以 | 可以 | +| 请求前台、后台、关闭 | 可以,Runtime 决定 | 可以,Runtime 决定 | +| 请求 Surface / Capability | 可以,经 Policy | 可以,经 Policy | +| 使用可信内建 DOM | 可以 | 不可以;两者都必须在受限 Surface / Bridge 中运行 | +| 直接访问 Transport、Store、Agent | 不可以 | 不可以 | +| 直接访问 Tauri、Host DOM 根节点、任意网络 | 不可以 | 不可以 | +| 修改 Focus、Registry、其他 MiniApp 数据 | 不可以 | 不可以 | + +Task Dashboard 和 Whiteboard 的安全与运行时模型必须与未来的一般非系统级 MiniApp 相同。它们的 +区别仅是本迭代随开发 Host 预置、并分别覆盖轻量 Tool/lifecycle 和复杂 Surface/Artifact 能力面, +不是拥有额外 Host 信任或特权。Host adapter 如存在,只能是 Runtime/Shell 内部创建、销毁、恢复和 +绑定受限 Surface 的装配代码;它不得把 MiniApp 业务 UI 挂到可信 Host DOM,也不得提供 Tauri、 +Transport、Store、Agent、任意网络或其他 MiniApp 数据访问。 + +#### 2.4 Interact 的 SDK 适配边界 + +Interact 使用“通用 Runtime 操作 + 可信 UI 投影”的适配方式: + +```text +Interact SDK +├── runtime:消息发送、标准交互提交/取消/过期、草稿和任务操作 +└── ui:IM 视图、Agent 消息订阅、消息确认和子会话展示 +``` + +`runtime` 是行为的统一入口,所有提交、取消、发送和生命周期动作仍由 Runtime 校验、持久化和回传。 +`ui` 只是给可信 DOM Renderer 使用的 IM 投影,不是第二套 Transport、Store、Tool 或 Capability +协议。Interact 可以继续使用可信 DOM,但不能因为可信就直接访问 Transport、ConversationStore、原始 +Agent Envelope、Host DOM 根节点或系统能力。 + +当前代码中的扁平方法(例如 `sdk.dispatch()`、`sdk.interactions.submit()`)只是兼容别名;新代码应使用 +`sdk.runtime.*` 和 `sdk.ui.*`,以明确区分 Runtime 行为与 UI 展示。普通 bundled MiniApp 不获得这组 +IM UI 投影,只使用通用 MiniApp SDK 和自己的受限 Surface。 + +## 3. MiniApp SDK v1 契约 + +SDK 是 Runtime 根据 Manifest、实例状态、scope 和 Policy 注入的受限对象。SDK 中的所有写操作都 +是 **request**,不是对 Host、Store、Transport 或其他 MiniApp 的直接命令。 + +```ts +interface LineUpMiniAppSDK { + readonly context: MiniAppContext; + + readonly inbox: MiniAppInboxAPI; + readonly tools: MiniAppToolAPI; + readonly lifecycle: MiniAppLifecycleAPI; + readonly surfaces: MiniAppSurfaceAPI; + readonly capabilities: MiniAppCapabilityRequestAPI; +} + +type MiniAppContext = { + app_scope: string; + instance_id: string; + conversation_id: string; + app_version: string; + state: "starting" | "foreground" | "background" | "suspended"; +}; +``` + +`context` 由 Runtime 在创建实例时注入。MiniApp 不可传入、伪造或修改 `app_scope`、 +`instance_id`、`conversation_id`,也不可获得用户身份、Agent 身份、登录 token、AppServer +地址、Transport endpoint 或其他实例上下文。 + +### 3.1 Inbox API + +```ts +interface MiniAppInboxAPI { + list(after_message_id?: string): readonly MiniAppMessage[]; + subscribe(listener: (message: MiniAppMessage) => void): Unsubscribe; + acknowledge(message_id: string): boolean; +} +``` + +行为约束: + +```text +Agent / Runtime 消息 +→ Runtime 校验 Envelope、scope、目标 app_scope、instance 和去重 +→ 先持久化到该 MiniApp Inbox +→ SDK 投递已筛选消息 +→ MiniApp 成功接管后 ACK +→ Runtime 移除该可投递记录 +``` + +后台化、刷新、Surface 重载、断线和 Runtime 重启不得丢失未 ACK 的 Inbox 消息。MiniApp 只能 +列出和 ACK 自己 `app_scope + conversation_id + instance_id` 范围内的投递。 + +### 3.2 Tool API + +```ts +interface MiniAppToolAPI { + subscribe(listener: (call: MiniAppToolCall) => void): Unsubscribe; + get(call_id: string): MiniAppToolCall | undefined; + reportProgress(call_id: string, progress: MiniAppToolProgress): Promise; + complete(call_id: string, result: JsonObject): Promise; + fail(call_id: string, failure: MiniAppToolFailure): Promise; + cancel(call_id: string, reason?: string): Promise; +} +``` + +每一个调用都必须有 Runtime 持久化的通用记录: + +```ts +type RuntimeToolCallRecord = { + call_id: string; + tool_id: string; + inventory_revision: string; + app_scope: string; + instance_id?: string; + conversation_id: string; + status: + | "received" + | "routing" + | "waiting_for_app" + | "running" + | "submitted" + | "completed" + | "failed" + | "cancelled" + | "expired" + | "rejected"; + input: JsonObject; + result?: JsonObject; + error_code?: string; + cancel_reason?: "app_closed" | "agent_cancelled" | "runtime_cancelled"; + created_at: string; + updated_at: string; +}; +``` + +`complete`、`fail`、`cancel` 绝不能直接发送网络请求。Runtime 必须验证: + +```text +call_id 属于当前 MiniApp instance +调用状态允许此迁移 +结果符合 ToolDescriptor.output_schema +调用不是重复终态 +调用仍属于当前 conversation / scope +MiniApp 和 Tool 仍处于启用状态 +``` + +验证成功后,Runtime 持久化记录和审计,再写入可靠 outbox 并回传 Agent。 + +### 3.3 Lifecycle API + +```ts +interface MiniAppLifecycleAPI { + requestForeground(): Promise; + requestBackground(): Promise; + requestClose(reason?: string): Promise; +} +``` + +MiniApp 只能请求,不能直接改变焦点或挂载其他 MiniApp: + +```text +MiniApp requestClose() +→ Runtime 检查 pending Tool、Surface、operation 和 Policy +→ 允许关闭或返回受控拒绝码 +→ 一旦允许,instance 进入 closing,Runtime 不再投递新 Tool +→ 仍为 received / routing / waiting_for_app / running 的、绑定该 instance 的普通 Tool + 原子进入 cancelled(app_closed),每条只写一条 cancelled outbox +→ 已 submitted 的 Tool 结果不可改写,Runtime 继续可靠发送既有结果 +→ 关闭该 instance 的 Surface,停止 instance,结束 App 子会话 +→ Runtime 调整 focus stack,并恢复前一有效 foreground instance +``` + +“关闭 App instance”“关闭 Surface”“普通 Tool 完成”是三个不同事件。普通 Tool 的 `complete / fail` 不会 +自动关闭 App、Surface 或子会话;只有 Runtime 接受的显式 Lifecycle 关闭才执行上面的释放流程。Tool 完成 +与关闭请求并发时,以 Runtime 的第一个原子终态写入为准;重试、重启恢复和迟到上报均不得再写第二条 outbox。 +用户若只想离开当前页面,应请求后台化,而不是关闭。 + +### 3.4 人与 Agent 的标准交互(不属于 MiniApp SDK) + +`notice`、`choice`、`confirm`、`input` 是 **Interact 的人与 Agent 交互组件**,与 IM 消息同属 +用户和 Agent 的对话过程。它们不是 MiniApp SDK,也不能被 Task Dashboard、Whiteboard 或未来的 +普通 MiniApp 用作自身业务表单、确认框或输入框。 + +MVP 阶段,单个 LineUp Runtime 只连接一个 Agent:当前登录会话中的 `agent_uid` 就是所有交互的 +`agent_id`。本迭代不实现多 Agent 连接、Agent 切换、多个 Agent 的主 IM 会话、多个 outbox 或 +跨 Agent 路由;但 Runtime 仍须在每个交互记录中保存 `agent_id`,不能只依赖当前前台页面猜测 +答案该交给谁。 + +Agent 需要说明、让用户选择、确认或输入信息时,通过已定义的 Agent Tool 交互请求(当前兼容 +`lineup.v1.tool.call(choice | confirm | input)`)发起。Runtime 负责校验、持久化、去重和可靠回传; +Interact 负责将其作为当前会话的一部分呈现给用户。 + +第一版固定支持以下四种原语: + +| 原语 | 用途 | 用户结果 | +|---|---|---| +| `notice` | 安全显示只读提示、状态或下一步说明。 | 不等待用户结果;Runtime 返回 `accepted`,不表示用户已阅读。 | +| `choice` | 在有限选项中选择一个。 | 一个 `action_id`。 | +| `confirm` | 明确同意/取消一个可解释操作。 | `approved: boolean`。 | +| `input` | 输入一段受长度限制的文本或多行文本。 | `text: string`。 | + +最低请求形态: + +```ts +type NoticeRequest = { + kind: "notice"; + title: string; + message: string; + dismiss_label?: string; +}; + +type AnswerableInteractionRequestBase = { + title: string; + prompt: string; + // Runtime 用自身当前时间计算 expires_at;缺省 15 分钟,范围 1 分钟至 24 小时。 + expires_in_ms?: number; +}; + +type ChoiceRequest = AnswerableInteractionRequestBase & { + kind: "choice"; + mode: "single-choice"; + actions: readonly { id: string; label: string; description?: string }[]; +}; + +type ConfirmRequest = AnswerableInteractionRequestBase & { + kind: "confirm"; + approve_label?: string; + cancel_label?: string; +}; + +type InputRequest = AnswerableInteractionRequestBase & { + kind: "input"; + field: { + id: string; + label: string; + type: "text" | "textarea"; + required?: boolean; + placeholder?: string; + max_length?: number; + }; + submit_label?: string; + cancel_label?: string; +}; + +type StandardInteractionRequest = + | NoticeRequest + | ChoiceRequest + | ConfirmRequest + | InputRequest; +``` + +第一版的问答范围必须保持小而清晰: + +| 原语 | 第一版规则 | +|---|---| +| `notice` | 只用于告知,不等待用户回答,也不阻塞 Agent。每条 notice 必须作为 Interact / IM 的只读卡片或气泡保留;当前界面可同时以 Toast 提醒数秒,但 Toast 不是唯一载体。需要用户决定时必须使用 `confirm` 或 `choice`。 | +| `confirm` | 只有确认和取消两个结果;默认安全结果是取消。涉及删除、覆盖、发送等后果时,确认按钮必须写出真实动作,不能只写“确定”。 | +| `choice` | 只支持单选;必须有 2~6 个 ID 唯一的选项,用户只能提交其中一个 ID。多选留待后续版本。 | +| `input` | 只支持一个文本或多行文本输入框;可要求非空,默认最多 1,000 字,Agent 只能请求更小的上限。标题、描述、日期等多字段业务表单属于 MiniApp 自己的 UI,不属于 Interact。 | + +这四种交互是普通会话交互,不在 SDK v1 中统一按密码或秘密输入对待。已提交的内容按普通 IM 的会话历史、 +保留和日志基线处理;未提交 `input` 草稿只在当前运行期存在。密码、卡密、私钥、临时 token 等真正的 +秘密输入将来使用独立的 `password-input` 原语,本迭代不实现。届时由 Runtime 按交互类型强制相应保护, +不能让 Agent 用普通 `input` 来绕过该保护。 + +每个需要用户回答的交互都有且只有一次有效回答。用户的第一次有效提交会结束该问题;重复点击、 +网络重试、刷新、旧页面重放或已经失效的交互都不能改变答案,也不能向 Agent 再发送一次结果。 +若 Agent 需要追问,必须创建新的交互,不能重新打开旧问题。 + +标准交互的流程如下: + +```text +Agent 在当前会话中请求 choice / confirm / input / notice +→ 可选携带 app_session_context.app_session_id,表示“这与哪段 App 工作有关” +→ Runtime 校验 call_id、conversation_id、请求 schema、状态、去重与 App 子会话上下文 + ├── 未携带 app_session_context:归入主 IM,不创建或猜测 App 子会话 + └── 携带时:只接受同一 agent_id、同一父会话且由 Runtime 创建的 app_session_id; + 已关闭 App 子会话可作为历史上下文关联,非法或跨会话引用则拒绝且不创建交互 +→ Runtime 创建持久化 StandardInteractionRecord +→ Interact 将它作为人与 Agent 的会话交互呈现 + ├── Interact 位于前台:显示在 IM 时间线 / 卡片内 + └── bundled MiniApp 位于前台:由 Interact / Shell 在当前 App 之上显示同一会话的交互层 +→ 用户选择、确认、输入、取消或超时 +→ Interact 仅把用户动作和答案交给 Runtime;不直接发送给 Agent +→ Runtime 核对交互 ID、当前会话、创建时保存的 agent_id、有效期、答案格式和是否已经结束 +→ Runtime 只接受第一次有效回答,持久化结果和状态变化,并写入可靠 outbox +→ Agent 收到对应的 Tool 结果或取消结果 +``` + +换句话说,Interact 是用户操作的入口,不是消息发送端。它提交的只是“这个问题的用户答案”;Runtime +根据自己保存的上下文判断该答案属于哪一个 Agent Tool 和哪一个主会话/App 子会话。这样即使页面刷新、 +用户重复点击或网络暂时失败,也不会把同一个答案交给 Agent 两次;网络重试由 Runtime 的 outbox 处理, +用户不必重新回答。 + +Interact 向 Runtime 提交回答时,Runtime 核对 `interaction_id`、当前 LineUp / Interact 会话、当前状态和 +有效期。客户端不传递、也不能改写 `agent_id`、`conversation_id`、`call_id` 或 `app_session_id`;这些都只从 +Runtime 创建交互时保存的记录中取得。App 前后台切换、App 关闭或交互从覆盖层改在 IM 卡片显示,都不改变 +这条交互的归属,也不废止此前有效 Interact 页面提交同一问题的资格;Runtime 只用“首次有效回答”决定结果, +之后的重复或重放提交才因交互已经终态而被拒绝。 + +当 bundled MiniApp 位于前台时,这个交互层在视觉上可以覆盖当前 App,但它仍属于 Interact 和当前 +会话:MiniApp 不读取请求内容、不接收用户答案、不决定提交或取消,也不因它获得 Host DOM 权限。 +用户回答后,Runtime 将结果交回 Agent;若 Agent 后续要创建或更新任务,再由 Agent 调用对应的 +MiniApp Tool。 + +这意味着标准交互不会成为 MiniApp 的业务 UI 或跨 App 操作旁路: + +```text +MiniApp 不能发起、读取、提交、取消或伪造人与 Agent 的标准交互 +MiniApp 不能在 Host DOM 注入任意弹窗、表单或远端 HTML +Interact 不能直接把结果发送给 Agent +交互结果必须先回到 Runtime,再由 Runtime 可靠回传 Agent +Interact 不能代替 Runtime 判断答案应发送给哪个 Agent、哪个会话或哪个 App 子会话 +``` + +所有人与 Agent 的交互都必须在 Interact / IM 中留下对应的卡片、气泡或结果记录;不能只靠一次性 +Toast 而不保留会话痕迹。`notice` 是只读记录:它在 IM 中保留一张 notice 卡片,当前前台界面可选同时 +显示数秒 Toast。Toast 与卡片是同一 `interaction_id` 的两种呈现,不是两条消息;Toast 自动消失或用户 +未注意到,不改变记录和 Agent 结果。 + +所有 Agent 标准交互共享一套 `StandardInteractionRecord`、状态机、持久化、过期、去重和可靠回传路径; +它们可以根据当前前台 App 选择 IM 内联或系统交互层的显示方式,但不改变其 Interact / 会话归属。 + +`StandardInteractionRecord` 至少包含以下 Runtime 内部字段: + +```ts +type StandardInteractionRecord = { + interaction_id: string; + call_id: string; + agent_id: string; + conversation_id: string; + interact_instance_id: string; + app_session_id?: string; + presentation_surface_id?: string; // 仅记录展示位置元数据,不是 owner 或提交权限 + kind: "notice" | "choice" | "confirm" | "input"; + request: StandardInteractionRequest; + status: "pending" | "presented" | "submitted" | "completed" | "cancelled" | "expired" | "failed"; + result?: StandardInteractionResult; + created_at: string; + updated_at: string; + expires_at?: string; +}; +``` + +状态必须有界: + +```text +notice:pending → presented → completed + +choice / confirm / input: +pending → presented → submitted → completed | cancelled | expired | failed +``` + +对 Agent 而言,标准结果只表达用户最后是否回答及答案,不携带界面的排版、点击次数或草稿变化: + +```ts +type StandardInteractionResult = + | { outcome: "accepted" } // notice 已持久化并交给 Interact 呈现,不代表用户阅读 + | { outcome: "answered"; answer: { action_id: string } } + | { outcome: "answered"; answer: { approved: boolean } } + | { outcome: "answered"; answer: { text: string } } + | { outcome: "cancelled" } + | { outcome: "expired" } + | { outcome: "failed"; error_code: string }; +``` + +`notice` 的 `completed` 表示 Runtime 已持久化 notice 卡片、安排 Interact 呈现并写入一次 `{ outcome: +"accepted" }` 的 Tool 结果/outbox;它不等待 Toast 的显示时长,也不等待用户阅读。若在持久化或安排 +呈现前 Runtime 失败,则 notice 的 Tool 结果为 `failed`。Toast 自动消失或用户手动收起 notice 卡片,都 +只是本地展示动作,不能产生新的 Agent 结果。 + +`choice`、`confirm`、`input` 的 `expires_in_ms` 未提供时默认等待 15 分钟;仅接受 1 分钟到 24 小时。 +Runtime 按自己的当前时间计算并保存 `expires_at`,不信任 Agent 的绝对时间。`notice` 不等待回答,也不得 +携带期限。用户点击取消、关闭交互层或明确放弃时,结果为 `cancelled`;到期未回答时为 `expired`;Runtime +自身无法继续时为 `failed`。系统不得猜测用户意图,也不得把取消或超时伪装成确认。 + +Agent 也可以向 Runtime 发送远程 `interaction.dismiss` 指令,要求收起仍为 `pending` / `presented` 的交互; +它是 Runtime 私有的 Agent 控制契约,不属于 MiniApp SDK: + +```ts +type InteractionDismissRequest = { + control_id: string; // 本次控制请求的唯一编号,用于 Agent 重试去重 + call_id: string; // 原 interactive Tool Call;MVP 中一条 Call 对应一条交互 + reason?: string; // 受控诊断原因,例如 app_closed +}; +``` + +Runtime 从受认证 Envelope 取得 `agent_id`、`conversation_id`,并以它们和 `call_id` 查找记录;客户端不能 +传入或改写归属。若交互仍为 `pending / presented`,Runtime 原子将其按 `cancelled` 终结、从 Interact 中移除 +并写入唯一 cancelled outbox。若目标已终态或同一 `control_id` 被重放,只返回稳定的幂等回执,不覆盖结果、 +不再写 outbox。典型场景是 Runtime 已通知 Agent“关联的 App 已关闭”,而 Agent 判断这个问题已不再有意义。 +App 本身不能发送该指令;关闭 App 也不会隐式产生该指令。 + +##### 最终结果只能产生一次 + +`choice`、`confirm`、`input` 的用户回答、Agent 的远程 `dismiss`、到期和 Runtime 失败,都在争夺同一条 +交互的唯一最终结果。Runtime 是唯一裁决者:它以同一个持久化事务或等价的原子比较/更新动作检查交互 +仍为 `pending` / `presented`,并由第一个成功写入的动作获胜。 + +```text +用户首次有效回答先成功落库 +→ Runtime 持久化答案,交互进入 submitted(该答案已不可被取消、超时或其他答案覆盖) +→ 写入唯一的 Tool 结果 outbox +→ 交互完成为 completed,Tool 得到 answered + +Agent dismiss 先成功落库 +→ 交互与 Tool 均为 cancelled +→ 写入唯一的 cancelled outbox + +Runtime 在 expires_at 后先成功处理到期 +→ 交互与 Tool 均为 expired +→ 写入唯一的 expired outbox + +Runtime 无法继续先成功记录失败 +→ 交互与 Tool 均为 failed +→ 写入唯一的 failed outbox +``` + +之后到达的回答、dismiss、取消或超时处理不得覆盖已有结果,也不得创建第二条 outbox,只返回稳定的 +`interaction_already_final` 或等价状态。这里“先”指 Runtime 成功完成原子状态写入的先后,而不是客户端 +点击时间或网络到达顺序。 + +MVP 中,`interactive` Tool 不维护另一套独立的等待期限:其唯一超时来源就是关联交互的 `expires_at`。 +Agent 若要主动停止等待,必须请求 `dismiss`,而不是走独立的通用 Tool 取消路径。这样 Tool 的终态始终 +跟随交互终态,且每条交互只产生一次 Agent 结果。 + +刷新或短暂断线后,可恢复尚未终态、且仍未到期的交互;LineUp 重启后也可恢复仍有效的未回答问题, +但 `input` 的未提交草稿必须直接清空,用户需要重新输入。草稿绝不能自动提交、自动发送给 Agent 或进入 +outbox;它的本地观测仍遵循普通 IM 的日志基线。终态交互不可再次操作。 + +建议稳定拒绝码至少包括: + +```text +interaction_not_found +interaction_state_invalid +interaction_expired +interaction_result_invalid +interaction_already_final +``` + +#### App 子会话与主 IM + +Interact 是所有人与 Agent 交互的系统级入口,但对话会随着用户当前处理的 App 形成清晰的上下文。 +当 Runtime 启动一个 MiniApp instance 时,必须为它创建或恢复一个与主 IM 会话关联的 **App 子会话**: + +```text +主 IM 会话 +├── 普通人与 Agent 对话 +├── Task Dashboard 子会话 +└── Whiteboard 子会话 +``` + +主 IM 中的 App 子会话以可展开的折叠组显示,例如“Whiteboard · 4 条交互”。展开后只显示用户和 +Agent 围绕该 App 的对话:Agent 的说明、提问、用户回答,以及 Agent 给出的简洁结果。标准交互在 +前台 bundled MiniApp 上方显示时,也必须写入该 App 子会话,而不是混入主 IM 的普通消息。 + +App 子会话**不是 App 操作日志**。用户在 Task Dashboard 点击“新建任务”、填写 Dashboard 自己的 +表单,或在 Whiteboard 画线、拖放、选择颜色、点击保存,均属于 App 内部业务操作,不自动写入 +主 IM 或 App 子会话。只有用户与 Agent 围绕该 App 的交互才会被记录。 + +```text +启动一个 App instance +→ 创建新的 App 子会话;若正在恢复同一个未结束 instance,则恢复原子会话 +→ Runtime 可靠向当前 Agent 发送 app_session.opened,至少关联 agent_id、conversation_id、app_session_id、 + app_scope 与 instance_id;Agent 后续可用 app_session_id 作为 interactive Tool 的可选上下文 + +App 进入 background / suspended +→ 子会话继续存在 + +用户明确关闭 App,或 App 失败并被 Runtime 关闭 +→ 子会话结束,在主 IM 中保留为可展开的历史记录 +→ Runtime 向当前 Agent 可靠发送“该 App instance / App 子会话已关闭”的事件 + (至少包含 agent_id、conversation_id、app_session_id、instance_id、关闭原因和仍 pending 的 pending_interaction_call_ids) +→ 尚未回答的 Interact 交互保持 pending;它仍属于主 IM / Interact,只保留与已关闭 App 子会话的上下文关联 + +以后启动一个新的 App instance +→ 创建新的子会话,不接续旧 instance 的记录 +``` + +每个 App 子会话至少绑定 `agent_id`、`parent_conversation_id`、`app_session_id`、`app_scope`、 +`instance_id`、创建/结束时间和状态。已提交的用户回答属于该子会话的会话历史,随其父 IM 会话的 +既有保留、删除和日志基线处理;SDK v1 不为普通 `input` 另设秘密输入规则。 + +App 子会话的生命周期必须接入 Runtime 的 `AppLifecycleManager`,而不是 `RuntimeAppHost`: + +```text +foreground / background / suspended +→ 只改变 App 是否在前台或是否暂时暂停 +→ 不结束 instance,也不结束 App 子会话 + +Agent Tool 完成 +→ 只表示 Agent 当前工作完成 +→ 不自动关闭 App,也不自动结束 App 子会话 + +AppLifecycleManager.close(instance_id) +→ Runtime 按普通 Tool 的 app_closed 收敛规则处理该 instance 的未终态 Tool;已 submitted 的结果继续可靠发送 +→ 关闭 Surface,App instance 进入 stopped 或 failed,并从 focus stack 移除 +→ 结束 App 子会话,保留其 IM 历史折叠组 +→ Runtime 向当前 Agent 可靠发送 App 已关闭事件,事件至少关联 conversation_id、app_session_id、instance_id、关闭原因、agent_id 和 pending_interaction_call_ids +→ 不自动取消、改写或重建关联的 Interact 交互;交互仍按自身的回答、取消、超时或失败规则收敛 +``` + +当某个 App 子会话有尚未回答的 Agent 问题时,显示规则如下: + +```text +该 App 位于前台 +→ Interact 的提问层可显示在该 App 上方 + +用户切换到另一个 App 或回到普通 IM +→ 原 App 上方的提问层收起 +→ 问题保持等待,不取消 +→ 主 IM 的对应 App 子会话折叠组显示“等待你的回答” + +用户展开该 App 子会话 +→ 可以直接在 Interact 中回答,不需要强制重新打开原 App + +用户回到原 App +→ 同一个尚未结束的问题可再次显示在该 App 上方 + +用户真正关闭原 App +→ App 上方的交互层消失,因为该 App 已不再存在 +→ 同一条 Interact 交互仍在主 IM 中等待;Runtime 将关闭事件发送给 Agent +→ Agent 可根据业务上下文发送远程 dismiss 指令,将交互按 cancelled 结束;也可保留问题,等待用户在 IM 中回答或自然超时 +``` + +Shell 可将同一个问题优先显示在前台 App 上方或主 IM 的关联位置,避免用户看见重复的视觉卡片;这只是 +展示策略,不是交互的 parent、owner 或提交权限。切换前后台、关闭 App 或切换展示位置不会改变问题所属 +的主 IM / Interact 交互记录和 `app_session_id` 关联。Runtime 依靠交互状态和“首次有效回答”防止重复 +结果,而不是依靠展示位置废止回答资格。 + +用户可以随时在主 IM 中展开已结束的 App 子会话,查看当时 Agent 的提问、用户回答和结果;这是 +**只读查看历史**,不会重新打开 App、重新执行操作或让旧问题再次可回答。 + +若用户选择“继续处理”某次旧工作,Runtime 必须启动一个新的 App instance 并创建新的 App 子会话。 +新的子会话可保存 `continued_from_app_session_id`,并在 App 支持时以旧 artifact 或已保存的 App 状态 +作为初始内容,但只能引用旧会话,不能向其中追加新的 **App 业务记录**。已终态交互永远不会复活;仍 +pending 的 Interact 交互也不属于新 App instance,Agent 如仍需要针对新工作提问,必须在新子会话中创建 +新的交互。 + +### 3.5 Surface 与 Capability API + +SDK v1 定义请求语义,不直接授予 Host 权限: + +```ts +interface MiniAppSurfaceAPI { + requestOpen(request: MiniAppSurfaceRequest): Promise; + update(surface_id: string, patch: JsonObject): Promise; + requestClose(surface_id: string): Promise; +} + +interface MiniAppCapabilityRequestAPI { + request(request: { + capability: string; + reason: string; + arguments: JsonObject; + }): Promise; +} +``` + +Runtime 必须根据 Manifest、实例状态、Capability Policy、用户确认和 Host 支持情况决定是否 +执行。SDK v1 不开放: + +```text +fetch / 任意网络 +任意文件系统 +Tauri invoke +直接剪贴板、麦克风、摄像头 +Host DOM 根节点 +原始 AppServer / Agent 协议 +``` + +本迭代可定义 Capability Request 的类型、拒绝码、审计和测试,但不必为参考 MiniApp 开放真实 +麦克风、摄像头或文件选择权限。 + +## 4. Manifest、Tool Contract 与 Inventory + +### 4.1 MiniApp Manifest v1 + +本迭代冻结最小 Manifest 契约: + +```ts +type MiniAppManifest = { + app_scope: string; + version: string; + kind: "system" | "bundled"; + tools: readonly ToolDescriptor[]; + subscriptions: readonly MiniAppSubscription[]; + requested_capabilities: readonly string[]; + host: { + min_version: string; + surface_required: boolean; + }; +}; +``` + +Manifest 是 Runtime 的输入,不是 MiniApp 可写状态;MiniApp 不可在运行时扩展 Tool、订阅或 +权限。动态安装、下载、更新、回滚和移除不属于本迭代。 + +`kind = bundled` 的 Manifest 必须声明 `host.surface_required = true`,并只在 Runtime +创建和绑定的受限 Surface / Bridge 内运行;不得因其随 Host 预置而退化为可信 Host DOM。只有 +`kind = system` 的 Interact 可使用受信内建 UI 容器,且同样不能绕过 Runtime 的 SDK、Tool、 +Lifecycle、Capability 或审计边界。 + +### 4.2 Tool Descriptor v1 + +```ts +type ToolDescriptorBase = { + id: string; // 例如 task-dashboard.open + version: 1; + input_schema: JsonSchema; + output_schema: JsonSchema; + permissions?: readonly string[]; +}; + +type ToolDescriptor = ToolDescriptorBase & ( + | { + // Agent → Runtime → Interact 会话交互;不是目标 MiniApp 的 Tool。 + handling: "interactive"; + target?: never; + } + | { + handling: "direct" | "launch" | "foreground" | "operation"; + target: { + app_scope: string; + requires_foreground: boolean; + restore_previous_focus: boolean; + }; + timeout_ms?: number; + } +); + +type InteractiveToolInvokeContext = { + // 只表示与哪段 App 工作有关;不是展示 owner,也不改变提交权限。 + app_session_context?: { + app_session_id: string; + }; +}; +``` + +`handling = interactive` 不使用 `target.app_scope`,也不能把 `chat` 伪装为其 target。Agent 如需将提问关联 +到某段 App 工作,只能在该次 Tool Invoke 的 `app_session_context` 中提供上面的 `app_session_id`;未提供时 +Runtime 一律归入主 IM。Runtime 验证失败必须以 `app_session_context_invalid` 拒绝请求,不创建交互或 outbox。 + +第一版 JSON Schema 只支持可明确实现和测试的子集: + +```text +object / string / number / boolean / array +required +properties +additionalProperties = false +enum +minLength / maxLength +minimum / maximum +maxItems +``` + +禁止接受远端 schema 中的递归引用、正则执行、脚本、URL 加载、函数名或任意扩展关键字。 + +### 4.3 Revisioned Inventory + +Runtime 在 MiniApp 的启用状态、Manifest Tool 或可见 Capability 发生变化时,生成最小化且带 +revision 的 Inventory。Agent Tool Invoke 必须携带该 revision: + +```text +Agent Tool Invoke +→ Runtime 发现 inventory_revision 不匹配 +→ 拒绝,不投递给 MiniApp,不执行任何 Host 行为 +→ 以受控状态提示 Agent 使用最新 Inventory +``` + +本迭代可复用现有 `lineup.v1.client.inventory` 发送路径;AppServer 只需要像普通会话消息一样 +透明转发,不需要实现应用目录或下载 API。 + +### 4.4 通用 Tool 路由 + +```text +Agent Tool Invoke +→ Runtime 校验 Envelope、Inventory revision、ToolDescriptor、输入 schema、scope、MiniApp 状态和权限 +→ 创建 RuntimeToolCallRecord 与审计记录 +→ Tool Router 判定 direct / interactive / launch / foreground / operation + ├── handling = interactive + │ → 不创建或复用业务 MiniApp instance,不调整业务 App 焦点,不投递 SDK Tool Inbox + │ → 可选 app_session_context 仅用于关联已验证的 App 子会话;缺省一律归主 IM,不从前台 App 猜测 + │ → Runtime 的 Agent interaction service 创建 StandardInteractionRecord + │ → Runtime 私有呈现契约交给 Interact / Shell + │ → Interact 只提交 interaction_id + 用户动作/答案 + │ → Runtime 校验、持久化并写 outbox,向 Agent 回传唯一结果 + └── handling = direct / launch / foreground / operation + → 按 ToolDescriptor 的目标创建或复用所需 MiniApp instance,并调整焦点 + → 向目标 MiniApp 的 SDK Tool Inbox 投递 + → MiniApp 经 SDK 报告 progress / result / error + → Runtime 校验 output schema,持久化,写 outbox,回传 Agent +``` + +`notice`、`choice`、`confirm`、`input` 是 Agent 调起的 Runtime 统一 `interactive` Tool Call / +`StandardInteractionRecord`。Interact 负责呈现和会话语义;当其他 MiniApp 在前台时,Interact / +Shell 可把交互层显示在其上方,但这些 MiniApp 不能调用、实现或取得该交互的内容与结果。 + +因此,`interactive` 不是“投递给 Interact 的公开 MiniApp Tool”。它是 Runtime 自己处理的 Agent +交互分支:Interact 只通过 Runtime 私有的呈现/提交契约参与,Task Dashboard、Whiteboard 及其他 +bundled MiniApp 的 `sdk.tools.subscribe` 和 Inbox 中均不得出现这四类交互。 + +建议稳定拒绝码: + +```text +inventory_revision_mismatch +tool_not_advertised +tool_input_invalid +tool_output_invalid +app_disabled +app_instance_not_found +app_scope_mismatch +app_session_context_invalid +lifecycle_denied +capability_denied +call_already_final +``` + +## 5. 内置参考 MiniApp 工作定义 + +本迭代通过三个内置 MiniApp 覆盖 SDK v1 的不同能力面。它们不是应用市场候选,也不要求完整 +业务功能;每个 MiniApp 只实现足以验证 SDK 契约的最小真实闭环。 + +### 5.1 Interact:系统级 MiniApp 样本 + +**身份:** `kind = system`;当前兼容 `app_scope = chat`。 +**本迭代交付:** IM Mode 适配到 MiniApp SDK v1。 + +| 范围 | 工作定义 | +|---|---| +| Inbox | 使用通用 `sdk.inbox` 订阅、恢复和 ACK;不再依赖 Chat 特有投递语义。 | +| 用户消息 | 保留 `conversation.sendText` 这一系统级扩展,但它必须进入 Runtime Action / outbox。 | +| 标准原语 | 作为 Agent/IM 请求的默认可信 Renderer Provider,在时间线/卡片内呈现 Runtime 投递的标准交互记录。 | +| 交互边界 | 负责所有人与 Agent 标准交互的会话语义与呈现;交互记录和 Agent 回传仍由 Runtime 持有。前台 bundled MiniApp 只能被覆盖,不读取或处理交互内容。 | +| Tool 结果 | 用户完成、取消或超时后由 Runtime 持久化并写 outbox,可靠回传给 Agent。 | +| Mode | 明确 IM 的 `mode = im` Context;仅定义 Audio/Video 入口和恢复语义,不启用真实媒体。 | +| 焦点 | 被参考 MiniApp 覆盖时接受 Runtime 的 background/suspended;不能自行切换回前台。 | + +**不在范围:** 语音录制、视频通话、摄像头、独立 Audio/Video MiniApp、改名 `chat → interact`。 + +### 5.2 Task Dashboard:Tool 与生命周期样本 + +**身份:** `kind = bundled`,`app_scope = task-dashboard`;本迭代作为 SDK 参考实现。 +**目标:** 验证 MiniApp Tool、progress/result/error、instance、focus 和恢复的最小闭环。 + +最小 Tool: + +```text +task-dashboard.open + handling = launch + 输入:task_id、title、可选初始状态 + 输出:status = completed | cancelled | failed + +task-dashboard.update + handling = operation + 输入:task_id、进度或状态 + 输出:当前任务摘要 +``` + +最小用户体验: + +```text +Agent 调用 task-dashboard.open +→ Runtime 创建 task-dashboard instance +→ Interact 进入 background +→ Runtime 请求并绑定 task-dashboard 的受限 Surface +→ Host 挂载 opaque-origin iframe / Surface Bridge +→ MiniApp 展示任务标题、进度、当前状态和关闭动作 +→ MiniApp 仅通过 Bridge SDK 使用 sdk.tools.reportProgress / complete / fail 上报 +→ Runtime 持久化并回传 Agent +→ Tool 的完成不关闭 Task Dashboard,也不结束其 App 子会话 +→ Dashboard 是否继续留在前台、进入后台或关闭,取决于用户动作、MiniApp requestClose 或 Lifecycle Policy +→ 只有真正执行 AppLifecycleManager.close(instance_id) 后,才停止实例、结束子会话并恢复前一有效 foreground App +``` + +Task Dashboard 的业务状态必须通过 SDK Tool / Inbox 获得;不得自行访问 AppServer、Store 或 +Agent。它必须与一般非系统级 MiniApp 一样运行在受限 Surface / Bridge 中,不能访问可信 Host DOM、 +Tauri、认证状态或其他 MiniApp 数据。Host adapter 如存在,只能做 Runtime/Shell 的 Surface 装配, +不可成为 MiniApp 的 UI 容器、Transport 或 Tool Router 的第二实现。 + +Task Dashboard 必须验证两种不同交互:第一,Agent 在任务流程中请求用户是否创建任务或输入任务 +描述时,Interact 的会话交互层可显示在当前 Task Dashboard 之上,用户回答由 Runtime 回传 Agent; +第二,用户自己点击“新建任务”时,Task Dashboard 使用其 Surface 内的私有业务表单。两者不得混用: +Task Dashboard 不能调用或处理 Agent 标准交互,也不能将自己的业务表单伪装为 Agent 提问。 + +Runtime 必须将第一类交互记录在该 Task Dashboard instance 的 App 子会话中,并在主 IM 中作为可展开 +的折叠组显示;第二类 App 内部操作不进入子会话记录。 + +### 5.3 Whiteboard:隔离 Surface 样本 + +**身份:** `kind = bundled`,`app_scope = whiteboard`;本迭代作为 SDK 参考实现。 +**目标:** 验证 SDK Surface Bridge、隔离执行、状态 patch、Artifact 元数据与恢复。 + +最小 Tool: + +```text +whiteboard.open + handling = launch + 输入:board_id、title、可选初始画布状态 + 输出:status、artifact_id(可选) + +whiteboard.submit + handling = operation + 输入:board_id、提交请求 + 输出:artifact_id 或结构化画布摘要 +``` + +最小用户体验: + +```text +Agent 调用 whiteboard.open +→ Runtime 校验已注册 bundled Manifest +→ Runtime 创建 whiteboard instance 并请求受限 Surface +→ Host 挂载 opaque-origin iframe / Surface Bridge +→ Whiteboard 仅通过 Bridge SDK 请求状态更新和提交结果 +→ Runtime 校验 patch / Artifact 元数据并持久化 +→ Tool 完成或失败只结束该 Tool;Whiteboard、Surface 与 App 子会话保持,直到用户、MiniApp 或 Policy 显式请求关闭 +``` + +Whiteboard 不需要在本迭代实现多人协作、任意网络同步或完整绘图工具;重点是证明隔离 Surface +不能读取 Host DOM、Tauri、认证状态或其他 MiniApp 数据。 + +即使 Whiteboard 处于前台,Agent 对用户的提问仍由 Interact 的会话交互层负责;Whiteboard 不调用 +也不处理这些交互。画板内的文字编辑、画笔与颜色选择、拖放、工具栏、上下文菜单、确认提交等 +业务 UI 必须保留在 Whiteboard 自己的 Surface 中,不能被错误抽象为人与 Agent 的标准交互。 + +Whiteboard instance 启动时创建自己的 App 子会话;Agent 与用户围绕该画板的提问、回答和简洁结果 +记录在其中,而画板的内部编辑操作不写入该子会话。 + +## 6. Runtime 与 Host 的实现模块 + +当前实现已落地以下边界:标准交互记录已接入 ConversationStore(当前版本 14),Runtime 登录时恢复并清理未提交 input 草稿;`interaction.dismiss` 已由 Tool Router 接入 Runtime;默认注册表已使用 `system / bundled`,并内置 Task Dashboard、Whiteboard 的 Manifest、受限 SDK 入口和参考实现。bundled MiniApp 的 Surface 请求现在由 Runtime 在校验 instance / scope 后发出本地事件,再由 Host 挂载、更新或卸载隔离 Surface,不再伪装成发给 Agent 的 UI 协议消息。Task Dashboard、Whiteboard 已通过同一套 SDK 自动装配,覆盖 Tool、progress/result、Surface 和生命周期;App Inbox 已按 instance 过滤并在 ACK 时再次校验;普通 Tool 的 `submitted` outbox 重启恢复、App 子会话只读历史和“继续处理”(新 instance / 新子会话)已有实现与自动化测试。 + +验收记录:`npm run build`、`npm test -- --run`(30 个测试文件、137 个测试)和 `git diff --check` 全部通过;已使用全新 `agent-browser` 会话登录本地测试账号,确认页面进入、同步状态、主 IM 消息、应用子会话区域和“启用任务面板”入口均正常;发送“第三次迭代验收测试”后页面显示“已发送”。AppServer 频道同步会返回其他用户的消息,Runtime 已按发送者和 Agent 目标用户过滤;同时兼容 AppServer 使用的 `lineup:::` 会话键,并将出站协议统一改为该格式。最终浏览器控制台未再出现 `scope_mismatch`,只保留跨用户消息的安全忽略日志。后续独立验收复核保持全绿,并确认 A1~A11 的代码约束没有回退;本轮新增 Registry kind、Tool 多权限回归后达到 30/137。 + +本迭代预计在现有目录中演进,不重建并行 Runtime: + +```text +tauri/src/runtime/ +├── app-management/ +│ ├── miniapp-manifest.ts # Manifest v1、bundled MiniApp 注册 +│ ├── miniapp-sdk.ts # SDK v1 公共类型与受限视图 +│ ├── miniapp-tool-call-store.ts # 通用 Tool Call 持久化/恢复 +│ ├── app-session-store.ts # 接入 Lifecycle 的子会话创建、结束、恢复与主 IM 分组 +│ └── runtime-app-host.ts # 按 foreground instance 挂载/卸载 Host +│ +├── coordination/ +│ ├── tool-router.ts # revision、schema、路由决策 +│ ├── miniapp-tool-orchestrator.ts # Tool → instance → SDK Inbox +│ └── agent-interaction-service.ts # Agent 交互记录、回传、恢复与显示协调 +│ +├── inventory/ +│ └── client-inventory.ts # revisioned Tool Inventory +│ +└── persistence/ + └── conversation-store.ts # 主 IM、App 子会话、Tool Call、Inbox、workspace 有界恢复 + +tauri/src/ +├── core-apps/chat/ # Interact IM 的兼容实现 +│ └── standard-interaction-im-renderer.ts # Agent/IM 的默认标准交互 Renderer +├── core-apps/task-dashboard/ # bundled Tool/lifecycle 参考实现 +└── core-apps/whiteboard/ # bundled Surface/Bridge 参考实现 +``` + +若目录名称最终改为 `miniapps/`,应单独进行机械迁移;本迭代优先保证 Runtime 边界和 SDK +兼容,不能让命名迁移扩大风险。 + +## 7. 不在本迭代范围 + +以下内容必须明确排除: + +```text +服务端 App Catalog 或应用清单 API +远程 Manifest / Bundle 下载 +安装、更新、回滚、卸载 UI +第三方开发者发布、账号、审核、评分、支付或搜索 +任意网络 API +真实麦克风、摄像头、文件选择或系统通知授权 +多人白板、实时游戏、完整语音/视频通话 +`password-input` 及密码、卡密、私钥、临时 token 等秘密输入的安全交互原语 +chat / interaction 命名和协议 scope 迁移 +多 Agent Runtime 连接、Agent 切换、跨 Agent 会话或结果路由 +``` + +这些能力将在 MiniApp SDK 与参考 MiniApp 完成验证后,作为独立的应用分发和市场阶段推进。 + +## 8. 实施步骤 + +1. **冻结契约与 golden fixtures** + - 定义 `MiniAppManifest v1`、`ToolDescriptor v1`、`MiniAppContext`、通用 Tool Call、 + Result/Progress/Error、Standard Interaction、SDK 稳定错误码; + - 为合法、重复、过期 revision、非法 schema、scope 不匹配和终态重复建立 fixture; + - 明确旧 `lineup.v1.tool.call` 的 `choice/confirm/input` 到 `interactive` Tool 的兼容映射;冻结 + `StandardInteractionRequest`、`expires_in_ms`、`interaction.dismiss` 和 `app_session_context`; + - 为 `notice`、期限缺省/越界、会话/App 子会话绑定、非法上下文、前台 bundled App 上的 Interact 交互层、 + dismiss、取消、超时和重启恢复建立 fixture。 + +冻结后的 v1 基线保存在 +[`miniapp-sdk-v1.json`](../../tauri/src/runtime/app-management/golden/miniapp-sdk-v1.json),并由 +[`miniapp-sdk-golden-contract.test.ts`](../../tauri/src/runtime/app-management/miniapp-sdk-golden-contract.test.ts) +校验 fixture 版本和用例顺序。后续实现必须逐项满足这份 fixture;若确需修改,必须先更新本迭代设计决议与 +fixture,再修改实现,不能在业务代码里悄悄改变契约。 + +2. **实现 Runtime 通用 Tool 闭环** + - 将 Inventory revision、输入/输出 schema、Tool Call Record、审计和 outbox 接入 Runtime; + - 让 `ToolRouter` 先区分 `interactive` 与一般 Tool:只有 `direct / launch / foreground / operation` + 进入 `AppOrchestrator`、创建/复用实例并投递 SDK Tool Call;`interactive` 不得进入任何 MiniApp + SDK Tool Inbox,也不使用普通 `target`; + - 实现 App 关闭的普通 Tool 收敛:接受关闭后停止新投递,将未终态 Tool 原子转为 `cancelled(app_closed)`, + 已 submitted 结果继续 outbox,随后关闭 Surface / instance / 子会话并恢复焦点; + - 完成 Tool、Inbox、workspace 的断线与重启恢复。 + +3. **实现 Agent 交互服务与 Runtime → Interact 回传契约** + - 实现 Runtime 独占的 Agent interaction service:创建和保存交互记录、绑定唯一 `agent_id`、会话、 + Agent Tool、Interact instance 与可选 App 子会话,处理状态机、首次有效回答、取消、超时和重启恢复; + - 定义 Runtime 到 Interact 的最小呈现数据,以及 Interact 到 Runtime 的最小回答提交:提交端只可提交 + `interaction_id` 和用户动作/答案,不能指定或改写 Agent、会话、Tool、App 子会话等归属; + - 由 Runtime 校验当前 LineUp / Interact 会话、有效期、答案 schema 和一次性提交,持久化终态后通过 + outbox 向当前唯一 Agent 回传;展示位置不是 owner 或提交权限,重试只由 Runtime 发起,Interact 不直接发送 Agent 消息; + - 实现私有 `interaction.dismiss(control_id, call_id)`,从受认证 Agent / 会话上下文查找交互并幂等终结; + 在 `app_session.opened` / `app.closed` 事件中可靠提供 App 子会话上下文及仍 pending 的交互 call_id; + - 为重复点击、刷新后的旧页面、前后台/关闭 App 后仍 pending 的交互、用户回答与 dismiss/超时/失败并发、 + 过期问题、断线和 outbox 重试建立自动化 fixture;每个并发场景必须只产生一个终态和一条 outbox。 + +4. **适配 Interact IM** + - 以 `sdk.inbox` 和 `sdk.tools` 替换 Chat SDK 中的专用交互旁路; + - 实现 Agent/IM 标准交互层:所有交互在 IM 中有对应卡片/气泡;notice 为持久只读卡片并可在当前界面 + 同时 Toast 数秒,choice / confirm / input 为可操作卡片;既支持 IM 时间线/卡片,也支持覆盖在前台 + bundled App 之上;保持 Markdown、消息、Task 和现有回归行为; + - 实现主 IM 的 App 子会话折叠组,以及 instance 启动、后台、结束和恢复时的子会话生命周期; + - 实现前后台切换时 Agent 提问层的收起、子会话“等待回答”标记、IM 内直接回答及回到原 App 后的再次显示; + - 将子会话结束、普通 Tool 的 app_closed 收敛、App 已关闭事件和折叠历史保留接入 + `AppLifecycleManager.close`,不得依赖 `RuntimeAppHost` 或 Tool 完成事件;关闭不自动取消未回答交互, + Agent 可用 call_id 远程 dismiss; + - 实现已结束子会话的只读展开,以及“继续处理”创建新 instance / 新子会话并引用旧上下文的路径; + - 固化 IM Mode Context 与被覆盖/恢复的生命周期语义。 + +5. **实现 Task Dashboard 参考 MiniApp** + - 注册 bundled Manifest 和两个最小 Tool; + - 实现受限 Surface / Bridge 中的启动、进度、结果、错误、关闭、回焦和重启恢复; + - 验证 Agent 交互层覆盖时的会话回答回传,以及用户主动创建任务时的私有业务表单; + - 使用真实 SDK,不测试用 Runtime 内部对象直连。 + +6. **实现 Whiteboard 参考 MiniApp** + - 注册 bundled Manifest、最小 Tool 和受限 Surface; + - 验证 Bridge、patch、Artifact 元数据、关闭、失败和恢复; + - 验证隔离拒绝路径与 Capability Request 拒绝路径。 + +7. **端到端验收与文档回填** + - 运行完整单元测试和生产构建; + - 通过 Tauri/Web Reference Host 完成代表性浏览器验收; + - 将最终 SDK 形态与参考 MiniApp 结果回填 [APP架构设计.md](../../设计/APP架构设计.md)。 + +## 9. 验收目标 + +```text +1. Runtime 向每个 MiniApp instance 注入不可伪造、范围受限的 MiniAppContext。 +2. Interact、Task Dashboard、Whiteboard 均通过 SDK 获取 Inbox、Tool 和生命周期能力; + 不直接访问 Transport、Store、Agent 或 Host 特权。 +3. Manifest Tool 具有稳定 ID、输入/输出 schema、handling 和版本;普通 MiniApp Tool 必须有目标 scope, + `interactive` 明确不使用普通 target。 +4. Runtime 只接受当前 Inventory revision 中、输入 schema 合法的 Tool Invoke。 +5. Runtime 拒绝未知 Tool、过期 revision、非法输入/输出、scope 不匹配、禁用 MiniApp、 + 错误 instance 和重复终态;拒绝请求不得到达 MiniApp 或 Host。 +6. Interact 的 notice / choice / confirm / input 始终属于人与 Agent 的会话交互,并在 IM 中保留对应卡片/ + 气泡;notice 可额外 Toast 数秒但不以 Toast 作为唯一记录,也不等待用户阅读。IM 前台时以内联卡片呈现, + bundled MiniApp 前台时可显示为 Interact 管理的交互层,完成、恢复和回声均不退化。 +7. 每个标准交互必须绑定当前 conversation、Agent call 和 Interact instance;未提供经验证的 + `app_session_context` 时归主 IM,提供时只可关联同 Agent、同父会话的真实 App 子会话,非法引用必须拒绝且 + 不创建交互。`interactive` 不创建或复用 + 业务 MiniApp instance,也不得投递到任何 MiniApp 的 SDK Tool Inbox 或 `sdk.tools.subscribe`。只有 Runtime + 可持久化结果并可靠回传 Agent,bundled MiniApp 不能调用、读取、提交、取消或伪造此类交互。用户回答、 + Agent dismiss、到期和 Runtime 失败竞争时,Runtime 以第一个原子终态写入为准,且每条交互至多产生一个 + Tool 结果和一条 outbox。`interaction.dismiss(control_id, call_id)` 只能由当前 Agent 经 Runtime 私有契约调用; + 重放或已终态交互只能得到稳定幂等回执,不能新增 outbox。 +8. MVP 中 Runtime 只连接当前登录会话的一个 Agent;每条标准交互和 App 子会话均记录该 `agent_id`, + 不新增多个 Agent 的连接、切换、会话列表、outbox 或路由能力。 +9. 每个启动的 App instance 创建或恢复一个 App 子会话;Runtime 向当前 Agent 可靠发送包含稳定 + `app_session_id` 的 opened 事件。前后台切换、暂停和 Agent Tool 完成均不结束子会话;仅在 + `AppLifecycleManager.close` 使 instance stopped/failed 后结束并在主 IM 保留折叠历史。Runtime 一旦接受关闭, + 不再向该 instance 投递新普通 Tool,并将其未终态 Tool 原子收敛为 `cancelled(app_closed)`;已 submitted + 的结果继续可靠发出,随后关闭 Surface、停止 instance、结束子会话并恢复焦点。关闭事件包含仍 pending 的 + interaction call_id;关闭不自动取消关联的 Interact 交互,Agent 可 remote dismiss,或保留其在主 IM 中等待 + 用户回答/超时。新 instance 不接续旧子会话;已结束子会话可只读展开;“继续处理”创建新 instance / 新子会话 + 并可引用旧上下文,但不复活已终态问题。子会话只记录人与 Agent 围绕 App 的交互,不记录 App 内部操作。 +10. App 子会话关联的未回答 Agent 问题在原 App 前台时可显示为 Interact 提问层;切换到其他 App/IM 时 + 收起并在子会话标记“等待你的回答”,用户可在 IM 内直接回答或回到原 App 后回答。展示位置不改变交互 + 归属或提交资格;Runtime 只持久化和回传首次有效回答,后续重复提交不得改变结果。 +11. Task Dashboard 可验证 launch → foreground → progress → 标准交互 → result/error → Agent 回传,且 Tool + 完成后 Dashboard 仍可留在前台或后台、子会话仍可继续;用户真正关闭时,未终态普通 Tool 收敛为一次 + `app_closed` 取消、已提交结果继续 outbox,随后才 close → 恢复 Interact。Agent 交互层覆盖时不触发焦点 + 切换,交互记录仅在带有已验证上下文时写入 Task Dashboard 子会话;Task Dashboard 也不能访问可信 Host + DOM/Tauri/token。 +12. Whiteboard 可验证隔离 Surface open → patch → submit → 保持 App 可继续编辑或由用户显式 close,且不能 + 访问 Host DOM/Tauri/token;提交 Tool 的成功或失败不自动关闭 Whiteboard。画板内的文字编辑等私有高频 UI + 不通过 Agent 标准交互路由。 +13. MiniApp 的 progress/result/error 经 Runtime schema 校验、持久化、审计和 outbox 后才回传 Agent。 +14. 断线或重启后,pending Tool、Standard Interaction、App 子会话、MiniApp Inbox、实例、焦点和 Surface 以有界方式恢复; + `app_closed` 取消、submitted 结果继续投递、`interaction.dismiss` 重放、默认/越界 `expires_in_ms` 和非法 + `app_session_context` 均可验证,且中断操作不得伪装为完成。 +15. 不新增 AppServer Catalog、下载或市场接口;现有服务端只透明转发会话/Inventory 消息。 +16. 00.base 和 01.kernel 中的登录、同步、本地回显、Markdown、安全 fallback、Tool Call、 + Task、Surface、Capability、App Inbox、outbox 和焦点恢复测试不退化。 +17. `npm test -- --run`、`npm run build`、`git diff --check` 通过;浏览器验收无未处理错误。 +``` + +## 10. 完成定义 + +本迭代完成不是“已经有应用市场”,也不是“完成全部 MiniApp 业务功能”。完成的判断是: + +```text +LineUp Runtime 已提供经过类型、schema、scope、Inventory revision、生命周期、权限、持久化和 +审计约束的 MiniApp SDK v1; + +Interact、Task Dashboard、Whiteboard 已以不同信任和 UI 形式使用同一套 SDK 语义,证明 +LineUp 可在不增加旁路通信或 Host 特权泄漏的前提下承载系统级与受限 MiniApp。 +``` diff --git a/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/04.acceptance_review.md b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/04.acceptance_review.md new file mode 100644 index 0000000..49dc831 --- /dev/null +++ b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/04.acceptance_review.md @@ -0,0 +1,105 @@ +# `03.sdk_and_coreapp` 第 4 次验收评审记录 + +> 评审编号:04 +> 评审日期:2026-08-05 +> 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) +> 架构基线:[APP架构设计.md](../../设计/APP架构设计.md) +> 参考评审:[01.design_review.md](01.design_review.md)、[02.design_review.md](02.design_review.md)、[03.design_review.md](03.design_review.md) +> 评审方式:主 agent 交叉检查 + 独立子 agent 只读验收;检查构建、自动化测试、Runtime/MiniApp/Host 代码、SDK 契约、Manifest 和真实浏览器路径。 +> 总体结论:本轮 P0~P3 问题已完成修复,并通过代码、自动化测试和真实浏览器闭环检查;独立验收 agent 已确认本迭代可以标记为“已完成”。 + +## 问题清单(Outline) + +> **状态标记:** 🔴 未解决,必须处理;🟡 已提出修复方向,尚未实现;✅ 已解决;⚪ 可延期但必须保留记录。 +> +> P0~P3 必须在当前迭代处理;P4~P5 可以延期,但要写清延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 证据 | 当前结论 / 下一步 | +|---|---:|---|---|---|---| +| ✅ | P1 | A1 | MiniApp 的 Capability 请求绕过 Runtime 的 Manifest、Policy 和审计边界 | `tauri/src/runtime/coordination/lineup-runtime.ts`、`capability-audit.ts` | 已统一经过 Manifest 声明、Capability Registry/Policy、输入 schema、前台要求和审计记录,再进入普通 Agent 确认请求;拒绝原因也由 Runtime 返回。 | +| ✅ | P1 | A2 | `bundled` MiniApp 的业务逻辑仍在可信 Host JS 中运行,没有真正的受限执行边界 | `tauri/src/main.ts`、`isolated-surface-host.ts`、agent-browser | 已移除 Host 直接实例化;Task Dashboard/Whiteboard 业务逻辑运行在 opaque `sandbox="allow-scripts"` iframe 内,只能通过 Runtime Bridge 请求能力;真实浏览器已复验。 | +| ✅ | P1 | A3 | SDK 暴露完整 workspace,MiniApp 可看到其他 App 实例和全局焦点栈 | `lineup-runtime.ts`、`miniapp-sdk.ts` | 已从 `LineUpMiniAppSDK` 删除 `workspace()`,不再把全局实例和焦点栈交给 bundled MiniApp。 | +| ✅ | P1 | A4 | 同一个 App 的多个 instance 之间 Inbox 订阅没有隔离 | `lineup-runtime.ts` | 已按 `app_scope + conversation_id + instance_id` 过滤实时订阅,并补充双实例回归测试。 | +| ✅ | P1 | A5 | Interact 使用专用 `ChatRuntimeSDK` 旁路,没有和其他 MiniApp 共用 SDK v1 语义 | `tauri/src/runtime/app-management/app-sdk.ts`、`lineup-runtime.ts`、`main.ts` | 已明确为“通用 Runtime 操作 + 可信 UI 投影”:Interact 保留可信 DOM 和 IM 展示,但行为统一走 `sdk.runtime.*`,展示走 `sdk.ui.*`;旧扁平方法仅作兼容别名。 | +| ✅ | P1 | A6 | 标准交互的超时、dismiss、提交和答案 schema 没有由 Runtime 统一裁决 | `lineup-runtime.ts`、`agent-interaction-service.ts`、`standard-interaction-contract.ts` | 已让本地提交、取消、过期和 Agent dismiss 同步推进 Interaction 与 Kernel;notice 也会完成 Kernel;四种答案在 Runtime 按固定 schema 校验,默认有效期和重启过期均只写一条 outbox。 | +| ✅ | P2 | A7 | 迭代文档中的 Tool 名称和代码 Manifest 不一致 | 主文档 §5.2/§5.3;`reference-miniapps.ts` | 已统一为 `task-dashboard.open`、`task-dashboard.update`、`whiteboard.open`、`whiteboard.submit`;Manifest schema、MiniApp 实现、sandbox iframe、fixture 和测试均已同步。 | +| ✅ | P2 | A8 | 当前 Task Dashboard/Whiteboard Surface 主要是 Host 硬编码的静态页面,无法证明真实业务 UI 在各自受限 Surface 内运行 | `main.ts`、`isolated-surface-host.ts`、agent-browser Console/IM 结果 | 已修复 Bridge 方法校验不接受 SDK 的 camelCase `tools.reportProgress` 的问题。真实浏览器中分别注入并完成 `task-dashboard.open` 与 `whiteboard.open`:两者均经过 `tools.list → tools.reportProgress → surface.patch(如适用)→ tools.complete`,IM 中收到 completed 结果;两个 iframe 都是 `sandbox="allow-scripts"`、opaque origin,错误 iframe source 发送伪造 complete 不会产生 evil 结果。 | +| ✅ | P2 | A9 | MiniApp progress 没有进入可恢复的 Tool 状态记录 | `miniapp-tool-state.ts`、`lineup-runtime.test.ts` | 已在 Tool 记录保存最后一次 `percent/status/detail`,ConversationStore 随记录持久化;Runtime 重启后可恢复,且补充状态机和重启回归测试。 | +| ✅ | P3 | A10 | App session 记录和 opened/closed 事件没有显式保存 `agent_id` / `conversation_id` 字段 | `lineup-runtime.ts`、`lineup-runtime.test.ts` | opened/closed 事件现在都带 `agent_id`、`conversation_id`、`app_session_id`、`instance_id`、`app_scope`;并补充生命周期回归测试。 | +| ✅ | P3 | A11 | 旧的 `openExtensionApp` 公开入口仍提供较宽的旁路能力 | `lineup-runtime.ts`、`app-sdk.ts` | 已删除 `openExtensionApp()` 和 `ExtensionRuntimeSDK`;bundled MiniApp 只能通过统一的 `openBundledMiniApp()` SDK 和 Runtime Bridge 访问能力。旧测试已改为验证统一生命周期路径。 | + +## 1. 已验证通过的部分 + +- `npm run build` 通过。 +- `npm test -- --run` 通过:29 个测试文件、135 个测试(含 Bridge camelCase 方法、Tool 契约、默认有效期、重启过期和答案 schema 回归)。 +- `git diff --check` 通过。 +- MiniApp progress 回归通过:状态机保存最新进度,Runtime 重启后恢复 `percent/status/detail`。 +- 使用独立 `agent-browser` 会话登录本地测试账号成功;AppServer `/login`、`/messages/send`、`/messages/sync` 均可用。 +- 主 IM 页面、同步状态、应用子会话区域和“启用任务面板”入口可见。 +- 用户消息可以发送并显示“已发送”。 +- AppServer 频道中的其他用户消息已能被 Runtime 安全忽略;真实页面不再出现此前大量的 `scope_mismatch`。 + +这些证据与后续真实 Tool 闭环日志共同证明 MiniApp 隔离、统一 SDK 和状态机契约满足本轮架构要求。 + +## 2. P1 问题说明 + +### A1:Capability 请求没有经过 Runtime 的完整裁决(已修复) + +现在 `sdk.capabilities.request(name, reason, input)` 只会进入 Runtime 的统一入口。Runtime 依次检查当前实例 +对应的 bundled Manifest 是否声明该能力、Capability Registry/Policy 是否可用、输入是否符合 schema,以及 +能力要求的前台状态;拒绝会返回明确原因,并写入不含敏感参数的审计记录。 + +通过检查后,Runtime 生成唯一 `call_id`,记录 pending 审计项,再进入普通 `lineup.v1.app.call` Agent 确认流程。 +MiniApp 不会拿到 Host handler、Transport 或权限对象。 + +### A2:bundled MiniApp 实际仍是可信代码(已修复) + +已删除 `main.ts` 中对 `TaskDashboardMiniApp` 和 `WhiteboardMiniApp` 的直接实例化。开发版 Surface 现在把 +Task Dashboard/Whiteboard 的业务脚本放进 opaque sandbox iframe,iframe 只能发送经过校验的 +`lineup.miniapp.v1.request`,由 Runtime 执行 Inbox、Tool、生命周期和 Surface 操作;Host 不再把 Runtime 对象、 +Store 或 Transport 注入 MiniApp。 + +这与架构文档要求一致:`bundled` MiniApp 也必须使用受限 Surface/Bridge,不能因为随 Host 内置就取得 +System MiniApp 的可信 DOM 或其他特权。Task Dashboard 和 Whiteboard 已按这一边界运行,真实浏览器验收也确认 +它们的 Bridge 请求会经过 Runtime 的 instance/source 校验。 + +### A3/A4:SDK 数据边界没有做到 instance 级别(已修复) + +`LineUpMiniAppSDK` 已不再提供 `workspace()`,因此 MiniApp 不能查看其他 instance 或 Runtime 焦点栈。 + +`inbox.list()`、ACK 和 `inbox.subscribe()` 现在都按 `app_scope + conversation_id + instance_id` 过滤;新增回归 +测试验证同一 App 的两个 instance 不会互收消息。 + +### A5:Interact 的 SDK 语义没有统一(已修复) + +Interact 仍然可以使用可信 DOM,但它的 SDK 已明确拆成两层:`sdk.runtime.*` 提供 Runtime 行为入口, +`sdk.ui.*` 提供 IM 和子会话的可信展示投影。标准交互、消息发送、草稿、任务操作仍由 Runtime 完成;UI +投影没有 Transport、Store、Agent 原始 Envelope 或 Host 特权。旧的扁平 `ChatRuntimeSDK` 方法只保留为 +兼容别名,避免把 Interact 的 UI 适配误解成另一套协议。 + +### A6:标准交互有两套状态,可能互相打架(已修复) + +已统一处理以下路径: + +- 本地提交、取消、过期和 Agent dismiss 都同时更新 Interaction 与 Kernel Tool;任一侧不能迁移时会恢复另一侧的旧快照。 +- notice 在呈现完成时也会同步完成 Kernel Tool,避免重启后出现 Interaction 已完成而 Tool 仍 pending。 +- `choice` 只接受 `{ action_id }`,且 action 必须来自请求;`confirm` 只接受 `{ approved: boolean }`;`input` 只接受 `{ text: string }`,并校验必填和长度。 +- 终态只创建一次对应的 Tool result/cancel outbox;重复提交、重复 dismiss 和已过期提交不会再次写出站消息。 + +现在 UI、持久化记录和 Agent 看到的最终结果由 Runtime 统一推进,重复终态不会再次写出站消息。 + +## 3. 验收结论 + +当前结论为(完成本轮 P1-1~P1-4,并收敛 A7 后): + +```text +功能回归:通过 +基础构建与自动化测试:通过 +真实登录和消息发送:通过 +App 架构边界:通过(A1/A2/A5/A6 已实现,A2 已完成真实 iframe 复验) +MiniApp 隔离与 SDK 规范:A1~A11 已通过。 +标准交互终态契约:通过 +迭代整体验收:通过代码、自动化测试、真实浏览器闭环和独立验收;主迭代文档已标记为“已完成”。 +``` + +本轮独立验收由子 agent `acceptance_round_08` 完成:29 个测试文件、135 个测试通过,构建和 `git diff --check` 通过;A8 的共享 agent-browser 日志确认 Task Dashboard 与 Whiteboard 均完成 `tools.list → tools.reportProgress → surface.patch(适用时)→ tools.complete`,错误 source/instance 请求未产生伪造结果。A1~A11 全部关闭,没有 P0~P3 遗留问题。 diff --git a/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/05.acceptance_review.md b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/05.acceptance_review.md new file mode 100644 index 0000000..ddaacec --- /dev/null +++ b/03.迭代规划/02.已完成阶段/03.sdk_and_coreapp/原始文档/05.acceptance_review.md @@ -0,0 +1,94 @@ +# `03.sdk_and_coreapp` 第 5 次验收评审记录 + +> 评审编号:05 +> 评审日期:2026-08-06 +> 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) +> 架构基线:[APP架构设计.md](../../设计/APP架构设计.md) +> 参考评审:[04.acceptance_review.md](04.acceptance_review.md) +> 评审方式:独立子 agent 只读验收 + 主 agent 逐项复核;检查当前代码、Manifest、SDK 契约、自动化测试、构建和文档证据。 +> 总体结论:本轮发现的 P3 契约/证据问题均已逐项修复并分别提交;当前没有遗留 P0~P3 问题。 + +## 问题清单(Outline) + +> **状态标记:** 🔴 未解决,必须处理;🟡 已提出修复方向,尚未实现;✅ 已解决;⚪ 可延期但必须保留记录。 +> P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须保留问题、延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 证据 | +|---|---:|---|---|---| +| ✅ | P3 | A12 | Registry 安装输入仍接受旧的 `core` / `extension` kind 别名,和冻结的 `system \| bundled` 契约不一致 | `CoreAppRecordInput` 已收紧为 `system \| bundled`;旧测试 fixture 已迁移;提交 `44b6a83`。 | +| ✅ | P3 | A13 | 主迭代文档中的测试数量过期 | 主文档已更新为当前 `30 个测试文件、137 个测试`;提交 `94817f7` 后随新增回归测试再次更新,提交 `ca9e8b9`。 | +| ✅ | P3 | A14 | Manifest Tool 的多项权限原先只保留第一项,可能造成后续权限漏检 | `requires_permissions[]` 已完整保存并逐项校验;Manifest/Registry 也拒绝未声明或重复权限;新增回归测试;提交 `e91d07b`。 | +| ✅ | P3 | A15 | 当前冻结约束未硬性保证只有 Interact 能使用 `kind = system` | Registry 现在拒绝除 `chat`(Interact 兼容 scope)之外的 system MiniApp,并有回归测试;提交 `fec04a9`。 | + +## 1. 本轮基础门禁 + +当前执行结果: + +```text +npm test -- --run 30 个测试文件、137 个测试通过 +npm run build 通过 +git diff --check 通过 +git status 工作树干净 +``` + +本轮独立子 agent 的只读审计确认: + +- Interact 仍使用 `sdk.runtime.*` 与 `sdk.ui.*` 两层适配,没有第二套 Transport、Store、Tool 或 Capability 协议; +- Task Dashboard 和 Whiteboard 仍是 `kind = bundled`,运行于 `sandbox="allow-scripts"` 的 opaque iframe; +- MiniApp 只能通过 Runtime Bridge 请求 Inbox、Tool、Lifecycle、Surface 和 Capability; +- `workspace()`、`openExtensionApp()`、`ExtensionRuntimeSDK` 等旧旁路没有恢复; +- A1~A11 的已修复约束没有发现回退。 + +## 2. P3 修复说明 + +### A12:冻结 kind 词汇 + +主文档规定 `MiniAppManifest.kind` 只有 `system | bundled`。本轮发现 Runtime 的注册输入仍允许旧的 +`core | extension`,虽然最终会转换,仍会让调用方继续依赖已废弃词汇。 + +现在 `CoreAppRecordInput` 只接受 `system | bundled`,Registry 不再负责旧名称转换;受影响的测试 fixture +已全部迁移为 `bundled`。这样 Manifest、Registry、Inventory 和 Tool Router 使用同一套名称。 + +### A13:更新验收证据 + +新增回归测试后,测试总数已经变化。本轮把主迭代文档中的旧统计更新为当前实际值 `30/137`,避免完成定义引用过期数字。 + +### A14:完整检查 Tool 权限 + +一个 Tool 可能声明多个权限。Runtime 现在保留完整的 `requires_permissions[]`,注册时要求这些权限都在 Manifest +的 `permissions` 中且没有重复,路由时逐项确认 App 当前确实拥有每一项;缺任何一项都不会把调用交给 MiniApp。 + +### A15:限制 system MiniApp 范围 + +本迭代只有 Interact 是系统级 MiniApp,当前兼容 scope 是 `chat`。Registry 在安装边界拒绝其他 scope 的 +`kind = system`,并通过回归测试固定这一不变量。Task Dashboard 和 Whiteboard 仍只能作为受限 `bundled` MiniApp。 + +## 3. 浏览器证据说明 + +本轮独立 agent 检查时,当前环境没有正在监听的 Web Host/AppServer 进程,因此没有把本轮称为“重新触发的真实 +浏览器闭环”。Task Dashboard / Whiteboard 的真实 Bridge 闭环证据沿用上一轮共享会话记录: + +```text +tools.list +→ tools.reportProgress +→ surface.patch(Task Dashboard 适用) +→ tools.complete +→ IM 收到 completed 结果 +``` + +上一轮还验证了 iframe 的 `sandbox="allow-scripts"`、opaque origin、CSP、`event.source + instance_id` 校验, +以及错误 source/instance 伪造消息不会改变 Runtime 状态。本轮新增修改没有触及这条浏览器路径;代码门禁和回归 +测试均通过。若下一轮需要重新取得独立浏览器证据,应先启动 Web Host 和 AppServer,再按相同路径复验。 + +## 4. 结论 + +```text +P0:无 +P1:无 +P2:无 +P3:A12、A13、A14、A15 均已解决 +P4~P5:无新增遗留 +``` + +本轮没有需要留到下一迭代的 P0~P3 问题。主迭代文档仍可保持“已完成”状态;后续若增加新的 system MiniApp, +必须先重新评审并修改本迭代冻结的唯一 system 约束,而不能绕过 Registry 安装边界。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/01.阶段摘要.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/01.阶段摘要.md new file mode 100644 index 0000000..82490cb --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/01.阶段摘要.md @@ -0,0 +1,20 @@ +# 04.runtime_workspace 阶段摘要 + +**状态:** 已完成 +**来源:** `lineup-app/迭代/04.runtime_workspace/04.runtime_workspace.md` + +## 1. 结论 + +本阶段目标是把 Runtime 生命周期和会话模型真实接到 Web/Tauri Host,跑通 Pomodoro 工作区闭环。 + +最重要的冻结结论包括: + +- Pomodoro 是 `bundled` MiniApp; +- `ends_at` 是唯一时间事实; +- Runtime 是唯一终态裁决者; +- 子会话关闭后只读; +- 到点、关闭、中断竞争同一唯一终态。 + +## 2. 当前角色 + +该阶段已于 2026-08-06 完成最终验收,现作为后续工作区类迭代的已完成样板保留。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/01.design_review.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/01.design_review.md new file mode 100644 index 0000000..7ed7d8d --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/01.design_review.md @@ -0,0 +1,120 @@ +# 04.runtime_workspace 设计评审(一) + +**评审编号:** 01 +**日期:** 2026-08-06 +**评审对象:** [04.runtime_workspace.md](04.runtime_workspace.md)、[03.sdk_and_coreapp.md](../03.sdk_and_coreapp/03.sdk_and_coreapp.md)、[01.kernel.md](../01.kernel/01.kernel.md)、[00.base.md](../00.base/00.base.md)、[plan.md](../plan.md)、[APP架构设计.md](../../设计/APP架构设计.md) +**评审方法:** 对照既有 Runtime 所有权、SDK v1、Tool、生命周期、App 子会话和 Web/Tauri Host 契约,检查第 04 次迭代是否引入相互矛盾或无法按既有边界实现的设定。 +**总体结论:** 第 04 次迭代对 MiniApp 隔离、Interact 标准交互、关闭收口和 Host 基线的方向与第 03 次迭代及权威架构一致。Pomodoro 已收敛为 Agent 直接启动的单次专注 operation:Runtime 托管 instance state、deadline 和唯一结果;Pomodoro 只渲染与请求退出。D04-01~D04-05 已回填主定义或总体计划,所有 P0~P3 设计问题已清零;D04-06 是有明确触发条件的 P5 遗留项,不阻塞实施。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或落入主定义;✅ 已解决;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须记录延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P1 | D04-01 | Pomodoro 状态由 Runtime 保存,但 Runtime 又“不解释番茄钟业务”;SDK v1 没有对应的私有状态持久化/恢复契约 | Runtime 已在正式方案中定义按 `app_scope + instance_id` 隔离、Manifest schema 校验、revision 控制的业务状态快照;04 已明确 operation 与 instance state 的不同事实来源和最小数据。 | +| ✅ | P1 | D04-02 | 用户暂停/继续/取消与 Agent Tool 的关系未定义;现有 `sdk.tools.*` 只能终结 Runtime 已投递的 Agent Tool | 已收敛为 Agent Tool `pomodoro.start`(长操作)和 `pomodoro.interrupt`(短操作);不支持暂停/继续。用户以语言请求停止时由 Agent 调用 interrupt;切换/关闭由 Runtime 生命周期以相同规则中断,均不伪造 Agent Tool。 | +| ✅ | P1 | D04-03 | “后台继续计时、到点回传、重启按 `ends_at` 恢复”没有 Runtime deadline 裁决和 Tool 终态规则 | 已确定 Runtime deadline operation 是唯一裁决者:持久化 `ends_at`,到点/中断竞争一个原子终态,唯一 result/outbox;重启已到点结算一次,进程未运行时不承诺即时提醒。 | +| ✅ | P2 | D04-04 | “创建或恢复 App 子会话”与“新 instance 必须新子会话”、以及关闭后 pending Interact 问题仍可回答的既有规则未完整写出 | 已明确仅恢复同一个未结束 instance 才复用子会话;关闭后子会话只读,pending Interact 问题留在主 IM 等待且不得追加到旧子会话;验收已加入该场景。 | +| ✅ | P3 | D04-05 | 总体计划把 Pomodoro 写成第 03 次迭代已完成的参考 MiniApp,并要求第 04 次工作区可切换 Whiteboard;第 04 次定义却将 Pomodoro 作为新验证应用且只承诺 Interact/Pomodoro 切换 | 已更新 `plan.md`:第 03 次参考实现只含 Task Dashboard / Whiteboard;Pomodoro 在第 04 次引入。Whiteboard 本轮仅保持第 03 次回归,不接入工作区切换或完成范围。 | +| ⚪ | P5 | D04-06 | 未来是否将 SDK 的实现迁入 Rust 层以提升运行效率 | **延期讨论:** 当前没有性能瓶颈证据,且 Web Reference Host / sandbox iframe 中的 SDK 与 UI Bridge 必须保留 TypeScript/浏览器侧实现。未来可评估把状态存储、schema 校验、deadline 调度、Tool/outbox 原子收口等无 UI Runtime Core 下沉到 Rust。重新评估触发:profiling 证明这些路径是热点,或 Rust Runtime Core 成为跨 Host 的正式实现边界。 | + +## 通过项 + +- **信任与隔离没有倒退。** 第 04 次迭代将 Pomodoro 定为 `bundled`,要求受限 Surface/SDK,禁止访问 Transport、Store、Agent、Host DOM、Tauri 和系统能力。这与权威架构对 bundled MiniApp 的限制一致,也避免了第 03 次验收已修复的“业务逻辑回到可信 Host JS”问题。 +- **Interact 的归属正确。** `notice / choice / confirm / input` 仍由 Interact 呈现;Pomodoro 前台时仅允许 Interact 覆盖层,视觉位置不改变 owner。这符合既有“标准交互不属于 MiniApp SDK”的规则。 +- **关闭收口正确。** 文档保留“停止新普通 Tool 投递 → 未终态 Tool 收敛为 `cancelled(app_closed)` → 已 submitted 结果继续 outbox → 恢复有效前台 App”的顺序;也没有把 Tool 完成错误地等同于关闭 App。 +- **Host 范围正确。** Web Reference Host 与 Tauri Desktop Host 作为同一 Runtime/MiniApp 代码的两种 Host 验收,与权威架构一致。 + +## D04-01:状态所有权与持久化边界未定义 + +**已解决(2026-08-06)。** 正式方案已增加 `app.instance-state.v1`:Runtime 为当前 +`app_scope + instance_id` 托管 Manifest schema 校验、revision 控制和 lifecycle 清理的业务状态快照。 +本迭代已将 Pomodoro 的 instance snapshot 与 deadline operation 分开:前者服务 UI 恢复,后者是 +`ends_at`、Tool 终态和 outbox 的唯一事实来源。以下为发现时的风险分析。 + +第 04 次定义要求 Runtime“保存计时状态和工作区快照”,并要求 Pomodoro 根据 Runtime 状态刷新;同时又规定 Runtime 不解释番茄钟业务,Pomodoro 负责暂停、继续和取消。它给出的 `timer_id`、`duration_seconds`、`started_at`、`ends_at`、`remaining_seconds`、`state` 正是需要跨刷新和重启持久化的业务状态。 + +但既有 SDK v1 只开放 Inbox、Agent Tool 的 `progress / complete / fail / cancel`、生命周期、Surface 与 Capability。它不提供 MiniApp 私有状态的读取、schema 校验写入、版本迁移或恢复快照 API;并且 Runtime 是 Conversation Store 的唯一所有者,bundled iframe 不能直接写它。若不补契约,实现只能在两条均违反边界的路径中选择:由 Runtime 写死 Pomodoro 的字段/状态机,或由 MiniApp 自己绕过 SDK 访问持久化。 + +建议把 Runtime 的职责限定为通用的、按 instance 隔离的持久化容器和 deadline-operation 调度,而非理解“番茄钟”。Pomodoro Manifest 应声明其状态 schema 和可恢复 operation schema;MiniApp 通过一个最小、受 Runtime 校验的 SDK 请求提交状态变更;Runtime 负责原子持久化、恢复投影和审计元数据。主定义还应说明:`remaining_seconds` 是派生展示值,还是暂停时的持久化事实值,避免与 `ends_at` 双事实源冲突。 + +## D04-02:用户控制动作没有合法的 SDK 入口 + +**已解决(2026-08-06,后续澄清)。** Pomodoro 接受 Agent 的长期 Tool `pomodoro.start` 和短 Tool +`pomodoro.interrupt`;用户不再有暂停或继续入口。用户以 IM / Voice 表达“停止/结束这次专注”时,由 Agent +调用 `pomodoro.interrupt({})`;Runtime 只从该调用所在会话绑定当前 `focusing` instance,负责路由与唯一终态 +裁决。切换工作区和关闭则由 Runtime 生命周期以同一中断规则处理,但不伪造 Agent Tool。Runtime 只接受第一 +次有效中断;重复/迟到调用只返回稳定回执,不能改写终态或直接写第二条 outbox。以下为发现时的备选分析。 + +第 04 次迭代要求用户在 Pomodoro 内暂停、继续、取消;又列出 `pomodoro.start / pause / resume / cancel / status`。前置契约中,`sdk.tools.*` 表示 MiniApp 对 Runtime 已投递的 Agent Tool Call 报告进度或唯一终态,不能被用于任意用户动作,更不能由 bundled MiniApp 自行构造 Agent call 或写 outbox。 + +因此必须先选择并写清一个模型: + +1. `pomodoro.pause / resume / cancel / status` 都是 Agent 可调用 Tool,用户点击只请求 Runtime 执行相同的已声明 MiniApp command;或 +2. 只有 `pomodoro.start` 是长操作 Tool,用户动作是独立的实例内 command,不会伪造额外 Agent Tool,Runtime 可按产品协议选择是否产生状态事件;或 +3. 另一种明确的、同样受 Manifest、schema、instance 和幂等校验约束的模型。 + +无论选择哪种,需给出 command ID、输入/输出 schema、目标 `instance_id`、重复点击行为、状态非法时的稳定拒绝码,以及关闭竞态下命令被拒绝还是已持久化。否则 Web/Tauri 两个 Host 会分别把按钮实现为局部状态或直接 Tool 操作,无法证明 Runtime 的唯一所有权。 + +## D04-03:后台/重启到点完成缺少唯一裁决者 + +**已解决(2026-08-06)。** Runtime 的通用 deadline operation 持久化绝对 `ends_at`;到点、用户中断和 +关闭竞争同一原子终态,先成功者产生唯一 Tool result/outbox。自动暗屏、锁屏和 Surface 重载不终止专注; +重启发现已到点时只结算一次。Runtime / Host 完全未运行时不承诺即时提醒。以下为发现时的风险分析。 + +“后台继续运行”在受限 iframe 不等于存在可靠的定时器:Surface 可能卸载、浏览器页面可能被节流,进程也可能在 deadline 前后重启。仅在 UI 中 `setTimeout` 会导致漏完成、重复完成或把全时长重新开始。第 04 次虽然正确要求重启按 `ends_at` 计算剩余时间,却没有定义 Runtime 在何时把 operation 原子转为 `completed`、谁完成关联 Tool、以及同时发生暂停、取消、App close、恢复和 deadline 时谁胜出。 + +建议新增一个通用 Runtime deadline-operation 状态机。运行中的 operation 持久化绝对 `ends_at`;恢复时 Runtime 用当前时间重新计算,若已到期则以一次原子转换完成并写唯一 result/outbox,若未到期则投影更新。暂停把剩余时长固化并清除/失效 deadline;恢复从新的 deadline 开始。接受 `AppLifecycleManager.close` 时,先让关闭取得与 operation 完成相同的终态竞争权:先完成者生效,另一个仅收到稳定幂等回执。这样 Runtime 仍不需要知道“番茄钟”,只实现可复用的 deadline 可靠性。 + +还须明确第一版“App 内完成提示”的含义:它应由 MiniApp 在收到 Runtime 已完成状态时显示;系统通知仍不在范围内。若运行进程完全不存在,到点时不承诺即时可见提示,但下次 Runtime/Host 可运行时必须按已到期状态恢复,不能重置或重复回传。 + +## D04-04:子会话复用、只读与关闭后问题的措辞不完整 + +**已解决(2026-08-06)。** 主定义已限定:只有恢复同一个未结束 instance 才能复用子会话;关闭后子会话 +只读。仍 pending 的 Interact 标准交互保留在主 IM 中等待回答、dismiss、超时或 Runtime 失败,且不得向 +已结束子会话追加记录。以下为发现时的风险分析。 + +前置契约只允许在“恢复同一个未结束 instance”时复用同一个 App 子会话;以后启动的每个新 instance 都必须新建子会话。第 04 次的“创建或恢复 App 子会话”没有限定这一前提,容易被实现为按 `app_scope` 或旧子会话复用,和“继续处理新建 instance / 新子会话”冲突。 + +另外,既有规则规定 App 真正关闭后:子会话结束并成为只读历史,仍 pending 的标准交互不被取消,留在 Interact/主 IM 中等待回答、dismiss、超时或 Runtime 失败。第 04 次仅说“已结束子会话只读”,没有写出这个例外;若把回答追加进已关闭子会话,就破坏只读,若关闭时取消问题,又违反 Interact 归属规则。 + +建议在第 04 次的子会话章节和验收中逐字继承这一关闭后路径,并加一条自动化场景:Pomodoro 关闭时存在带 `app_session_context` 的 pending `choice`,App 覆盖层消失、子会话只读、用户仍可在主 IM 回答,且产生一次结果/outbox。 + +## D04-05:计划与第 04 次最小范围相互矛盾 + +**已解决(2026-08-06)。** 总体计划已将 Pomodoro 从第 03 次已完成参考实现中移除,明确它在第 04 次 +作为 Agent 直接启动的专注验证应用引入;Whiteboard 在本轮仅保持第 03 次已有回归,不参与工作区切换、 +完整闭环或完成验收。以下为发现时的风险分析。 + +`plan.md` 说第 03 次已完成的参考 MiniApp 包含 Pomodoro,但第 03 次主定义及其验收对象是 Interact、Task Dashboard 和 Whiteboard;第 04 次又明确把 Pomodoro 选为本轮验证应用。与此同时,计划要求第 04 次工作区支持 Interact、Pomodoro、Whiteboard 切换,而第 04 次主定义只承诺 Interact/Pomodoro。 + +这不会改变底层架构,却会直接导致实现范围、浏览器验收用例和“第一条完整用户流程”的判断不一致。建议以第 04 次主定义选择的“单一 Pomodoro 闭环”为基准:将计划中的“Pomodoro 已完成参考实现”改成“Pomodoro 在第 04 次引入”;对白板明确标注为本轮仅保持既有回归,或若确实要求可切换则把它加入第 04 次工作内容、验收和时间预算。 + +## D04-06:SDK 是否迁入 Rust 层(P5,后续讨论) + +这是值得保留的性能和实现演进方向,但当前没有证据表明它阻塞第 04 次迭代。需要先分清 SDK 的 +协议/能力语义和 SDK 的运行位置:协议必须跨 Host 保持一致,具体实现不必全部使用同一种语言。 + +```text +必须留在 TypeScript / 浏览器侧的部分 +→ Web Reference Host、bundled MiniApp 的 sandbox iframe、postMessage Surface Bridge、DOM 与 UI 渲染。 + +适合未来评估迁入 Rust 的 Runtime Core 部分 +→ 状态快照持久化、schema 校验、revision 比较、deadline operation 调度、Tool/outbox 原子终态、 + 生命周期收口和审计。 +``` + +如果把全部 SDK 都迁入 Rust,bundled MiniApp 仍必须经浏览器 Bridge 调用 Runtime,反而可能增加 +Tauri IPC、序列化和跨语言错误处理开销;它不能替代 Web Reference Host 中必须运行的 TypeScript Bridge。 +因此候选方向是“保持版本化 SDK 契约与 TypeScript Surface API,按需把 Runtime 的无 UI 核心实现下沉到 Rust”, +而不是把 MiniApp SDK 整体替换为 Rust。 + +重新评估前必须先有可重复的 profiling 证据,包括:状态快照读写频率与耗时、deadline/Tool 路由吞吐、主线程 +阻塞、Tauri IPC 往返、JSON 序列化成本以及 Web/Tauri 两个 Host 的差异。只有收益大于跨 Host 实现、测试、 +调试和错误边界的复杂度时,才为此创建独立设计和迁移迭代。 + +## 设计就绪条件 + +D04-01~D04-05 已形成决议并回填到 [04.runtime_workspace.md](04.runtime_workspace.md) 与 +[plan.md](../plan.md)。所有 P0~P3 设计问题已清零;D04-06 作为 P5 carryover 保留,不阻塞本迭代。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/02.design_review.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/02.design_review.md new file mode 100644 index 0000000..f08dad0 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/02.design_review.md @@ -0,0 +1,178 @@ +# 04.runtime_workspace 设计评审(二) + +**评审编号:** 02 +**日期:** 2026-08-06 +**评审对象:** [04.runtime_workspace.md](04.runtime_workspace.md)(实施权威)、[01.design_review.md](01.design_review.md)、[00.base.md](../00.base/00.base.md)、[01.kernel.md](../01.kernel/01.kernel.md)、[03.sdk_and_coreapp.md](../03.sdk_and_coreapp/03.sdk_and_coreapp.md)、[plan.md](../plan.md)、[app_final_design.md](../../设计/02.正式方案/app_final_design.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md)、[lineup-app-layer-architecture.md](../../设计/02.正式方案/lineup-app-layer-architecture.md)。 +**评审方法:** 独立地以第 04 次迭代定义为实现依据,逐项回溯前三次迭代和正式 Runtime 契约;重点检查 instance state / deadline operation 的所有权、`pomodoro.start` 与用户中断的终态竞争、离开/暗屏/重启、App 子会话与 pending Interact、Whiteboard 范围和 Rust P5 延期。 +**总体结论:** D04-01~D04-09 已确认的方向均已被第 04 次主定义或计划正确吸收:业务快照与 deadline operation 分离、Agent Tool `pomodoro.start` / `pomodoro.interrupt`、到点/中断原子终态、暗屏/锁屏不等于离开、子会话只读与 pending Interact 留在主 IM、Whiteboard 不进入本轮闭环、Rust 仅为 P5 后续评估。D04-07 已澄清为 Agent Tool 路由契约;D04-08 已选择“立即收口、由 Interact 呈现完成结果”;D04-09 已选择 `retain_readonly` 快照保留策略。所有 P0~P3 设计问题已清零,本迭代设计已就绪,待实施。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施权威;✅ 已解决;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须记录延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P1 | D04-07 | `pomodoro.interrupt` 的调用方向与目标绑定未明确。 | 已确定为 Pomodoro 在 Manifest 中声明、由 Agent 调用的短 Tool;Runtime 从调用会话绑定当前 focusing operation,负责路由、原子裁决和稳定回执。 | +| ✅ | P1 | D04-08 | `completed` 后既要求 Pomodoro 显示自身完成提示,又在主流程中要求 Runtime 收口关闭 App、恢复 Interact;没有定义两者的顺序和触发者。 | 已选择立即收口:Runtime 原子完成并投递唯一结果/outbox 后立即关闭 Pomodoro、结束子会话、恢复 Interact;完成提示由 Interact / Agent 呈现。 | +| ✅ | P2 | D04-09 | Pomodoro 要保存 instance snapshot,但没有声明其 `app.instance-state.v1` Manifest 配置、schema 版本/字段、配额和 `retention` 选择。 | 已确定 `app.instance-state.v1` 最小 Manifest:严格 schema v1、4 KiB 配额、`retain_readonly`。关闭后旧快照只作历史展示,不可写入、不可复活为 focusing。 | +| ✅ | — | D04-C1 | instance state 与 deadline operation 的事实来源和持久化边界 | 已正确同步;不构成新问题。 | +| ✅ | — | D04-C2 | `pomodoro.start`、到点/中断竞态和 outbox 唯一性 | 已正确同步;D04-07 补足 Agent Tool 的路由与目标绑定契约。 | +| ✅ | — | D04-C3 | 用户离开、自动暗屏/锁屏、Surface 重载和重启 | 已正确同步;不构成新问题。 | +| ✅ | — | D04-C4 | App 子会话、只读历史和 pending Interact | 已正确同步;不构成新问题。 | +| ✅ | — | D04-C5 | Whiteboard 第 04 次范围 | 已正确同步到主定义和 `plan.md`;不构成新问题。 | +| ⚪ | P5 | D04-C6 | Runtime Core / SDK 是否迁入 Rust | 已保留为延期项;当前不应扩大到本轮实现。重新评估条件仍为 profiling 证明 Runtime Core 是性能热点。 | + +## 通过项与一致性证据 + +### D04-C1:instance state 与 deadline operation 的边界已一致 + +主定义第 2.2、2.3 和第 3 节将 Runtime 定为 schema/revision 受控的 instance snapshot 保存者,同时将 +`ends_at`、Tool 终态和 outbox 交给 deadline operation;`remaining_seconds` 仅由 UI 按绝对截止时间派生。 +这与正式方案的明确边界一致:`app_scope + instance_id` 隔离状态快照,快照不能完成 Tool、写 outbox、改变 +焦点或绕过 lifecycle;Pomodoro 的到期/中断和唯一 Tool result 必须由 Runtime deadline operation 裁决。 + +这也没有把静态 IM/App 子会话误当作 MiniApp 业务存储:第 04 次主定义仍只让子会话记录人与 Agent 的交互和 +结果,符合第 03 次“App 子会话不是 App 操作日志”的规则。 + +### D04-C2:Agent Tool 的终态竞争与 outbox 所有权已一致 + +第 04 次主定义将 `pomodoro.start({ duration_seconds, activity? })` 定义为长期 Agent operation,并由 Agent +调用短 Tool `pomodoro.interrupt({})` 请求停止当前专注;到点、Agent 中断和关闭竞争一个原子终态,第一成功者 +产生一次结果/outbox;重复或迟到的中断只获得稳定幂等回执。 +这符合第 03 次对 Tool/交互的“Runtime 原子终态 + 唯一 outbox”模式,也符合正式方案中 App 不能自行完成 +Tool 或写 outbox 的限制。D04-07 已补足:用户以语言提出停止意图后,Agent 调用 Pomodoro 声明的 Tool, +Runtime 而非 MiniApp 界面负责最终裁决。 + +### D04-C3:离开、自动暗屏、重启的产品语义已一致 + +主定义已清楚区分:用户切回 Interact、切到其他 MiniApp 或关闭 Pomodoro 会中断;自动暗屏、锁屏、Surface +重载不会中断;重启按持久化 `ends_at` 恢复或一次性结算;Runtime/Host 完全未运行时不承诺即时系统提醒。 +这不再依赖 iframe/UI 定时器,且没有把来电检测、系统免打扰、静音或系统通知偷偷纳入第 04 次范围。 + +### D04-C4:App 子会话和 pending Interact 已一致 + +主定义限制“仅恢复同一个未结束 instance 时复用同一个子会话”;结束后子会话只读;关闭时仍 pending 的 +Interact 标准交互留在主 IM/Interact 中,回答不得追加到旧子会话。该规则与第 03 次的 instance 生命周期、 +`pending_interaction_call_ids` 和“关闭不自动取消 Interact 交互”规则一致。前台 Pomodoro 上的 Interact +覆盖层也没有改变交互 owner。 + +### D04-C5:Whiteboard 范围已消除冲突 + +`04.runtime_workspace.md` 第 5 节与 `plan.md` 第 3 节均明确:Whiteboard 只保持第 03 次已完成的 SDK/隔离 +回归,不接入第 04 次工作区切换或完整用户流程;Pomodoro 才是本轮唯一新增的工作区闭环验证应用。 + +### D04-C6:Rust 为正确的 P5 carryover + +主定义明确本轮保持 TypeScript Runtime / Web Bridge 基线,且仅在 profiling 已证明 Runtime Core 是热点后另行 +评估。该范围不会破坏 Web Reference Host、sandbox iframe、postMessage Bridge 和 UI 必须保留在浏览器侧的 +既有边界。 + +## D04-07:用户中断的 Agent Tool 路由契约 + +**已解决(2026-08-06,用户决议)。** + +`pomodoro.interrupt` 不是 MiniApp 通过 SDK 主动发送的 Runtime command,而是 Pomodoro 在 Manifest 中声明、供 +Agent 调用的短 Tool。用户主要经 IM / Voice 向 Agent 表达业务意图;MiniApp 是 Agent 的业务工具,而不是 +用户直接操作业务状态的独立应用。Tool Router 从该 Tool 调用的 `conversation_id` 自动绑定当前唯一 +`focusing` Pomodoro operation,调用者不能提供 `instance_id`、`app_scope` 或 `operation_id`。Runtime 路由调用、 +在与 deadline/close 相同的原子裁决中将长 Tool `pomodoro.start` 收口为 `interrupted`,并给短 Tool 返回 +`interrupted`、`no_active_focusing_operation` 或 `operation_already_final` 的稳定回执。若 Surface 不在,Runtime +仍可完成裁决;若存在,只投影最终状态。用户直接离开工作区仍由 Runtime 生命周期中断处理,不伪造 Agent Tool。 + +以下为发现时、尚未澄清调用方向的风险分析。 + +第 04 次主定义第 3 节规定用户显式退出通过受限 SDK Runtime command +`pomodoro.interrupt(reason = "user_exit")` 请求;又规定 Runtime 仅接受当前 `focusing` instance 的第一次有效 +中断,迟到请求返回幂等回执。这个产品语义正确,但尚不足以决定 SDK/Bridge、Web Host 和 Tauri Host 应如何 +实现同一动作。 + +现有正式 SDK 的唯一通用出站入口是 `actions.dispatch(action: AppAction)`,但没有定义 `AppAction` 的 +schema、Command type、由 context 派生的 instance 绑定、receipt 格式或稳定拒绝码。第 03 次冻结的 Manifest +也只定义 Agent Tool(`direct / launch / foreground / operation`)和 `AppNavigationAPI.close`;它没有把 +业务 MiniApp 任意命令自动变成合法 Agent Tool。若不冻结该层,至少会出现两种相互冲突的实现:MiniApp 用 +`sdk.tools.complete/fail` 伪造 `pomodoro.start` 终态,或 Web/Tauri Host 在 UI 侧直接关闭实例;两者都绕开 +了 Runtime 的 deadline/outbox 原子裁决。 + +建议在主定义中明确一个最小、仅 Runtime 可执行的 command,例如概念上: + +```ts +type PomodoroInterruptCommand = { + type: "pomodoro.interrupt"; + reason: "user_exit"; + // instance_id、app_scope、conversation_id 从不可伪造 SDK context 推导,调用者不得传入。 + // Runtime 从当前 instance 关联的 operation / source_tool_call_id 查找目标。 +}; + +type PomodoroInterruptReceipt = + | { accepted: true; operation_id: string; state: "interrupted" } + | { accepted: false; code: "operation_already_final" | "operation_not_focusing" | "instance_closing" }; +``` + +实际字段命名可以不同,但必须同时冻结以下规则:调用者只能操作自己的当前 instance;谁提供或由 Runtime +生成幂等键;`focusing`、deadline、`closing` 与已终态各自的稳定回执;以及 command、deadline 与 +`AppLifecycleManager.close` 如何在同一持久化事务或等价 compare-and-set 中竞争唯一终态和唯一 outbox。 +Host 的“返回/切换/关闭”应调用同一个 Runtime 内部操作,而不是从 Surface 绕过该 Command。 + +## D04-08:完成提示与关闭的顺序未定义 + +**已解决(2026-08-06,用户决议:方案 B)。** Runtime 到达 `ends_at` 后,先原子写入 `completed`、唯一 +`pomodoro.start` 结果与 outbox;随后立即关闭 Pomodoro、结束 App 子会话并恢复 Interact。Pomodoro 不显示 +独立的完成提示;完成结果由 Interact / Agent 呈现。关闭后的 outbox 重试不依赖 Pomodoro Surface,且不得再次 +完成 Tool。完成与关闭并发时,已提交的 `completed` 终态不得被 `cancelled(app_closed)` 改写。 + +以下为决议前的风险分析。 + +主定义同时作出了两项要求:Pomodoro 到时间“显示自己的完成提醒”(第 2.3、3、4.4 节),迭代目标的主流程又 +写为“到时间显示完成提醒并回传结果 → Runtime 收口并关闭 App,恢复 Interact”。但它没有规定完成态是否先 +投影给仍存活的 Surface、提示展示多久或由谁确认、何时调用 `AppLifecycleManager.close`、以及 App 子会话在 +哪个时点结束。 + +这是可观测行为的分歧,不是纯 UI 细节。若 Runtime 原子完成后立即按一般收口关闭 instance,Surface 会先被 +卸载,Pomodoro 无法兑现“App 内完成提示”;若 UI 本地自行显示后再关闭,则可能在重启、旧 Surface 或重复 +回调下与已提交 outbox 脱节。第 03 次的既有规则还区分“Tool 完成不自动关闭 App/子会话”和 +`AppLifecycleManager.close` 才结束子会话;第 04 次为 Pomodoro 采用不同的自动关闭策略是允许的,但必须 +明确这是 Pomodoro 的特例及其顺序。 + +建议二选一并写入工作内容、恢复规则和验收: + +1. **保留完成展示窗口。** deadline operation 原子写 `completed` 和唯一 result/outbox,Runtime 将终态投影给 + Pomodoro;Surface 在一个明确的受控窗口内显示完成提示;窗口结束、用户确认或用户离开后由 Runtime 发起 + `AppLifecycleManager.close`,再结束子会话并恢复 Interact。重启落在窗口内时须能按持久化终态恢复到同一 + 收口路径,而不得再写结果。 +2. **立即收口。** Runtime 完成后立刻关闭 App、结束子会话、恢复 Interact;删除“Pomodoro 显示自己的完成 + 提示”,或将完成提示明确改为 Interact 的可信呈现而非已关闭 MiniApp 的 UI。 + +无论选择哪种,验收都应覆盖 `completed` 与 close 并发、已提交 outbox 的重试、终态 UI 不产生第二次 Tool +完成,以及完成前/后 pending Interact 的归属。 + +## D04-09:Pomodoro 的 instance-state Manifest 与关闭后保留策略未确定 + +**已解决(2026-08-06,用户决议:方案 B)。** Pomodoro 声明 `app.instance-state.v1`,使用严格的 schema +version 1、4 KiB 配额和 `retain_readonly`。快照只保存 UI/历史展示所需的活动、关联 operation、时间和展示 +状态;operation 仍是终态、deadline 与 outbox 的唯一事实来源。App 关闭后,旧快照仅供只读历史展示,不能 +再写入、不能恢复为 `focusing`,也不能作为新一轮专注的运行状态。 + +以下为决议前的风险分析。 + +主定义要求 Pomodoro 通过 SDK 保存展示所需的 instance snapshot,并列出可能字段;正式 Runtime 契约则规定 +只有声明 `app.instance-state.v1` 的 Manifest 才能使用 schema/revision 受控状态,并要求声明 +`state_schema_version`、`state_schema`、`max_bytes` 和 `retention`。第 04 次定义尚未选择 Pomodoro 的实际 +Manifest 配置,也没有说明关闭后应使用默认 `delete_on_close` 还是 `retain_readonly`。 + +这会直接改变重启/关闭后的可见行为和验收:`delete_on_close` 允许完成或中断时清理 UI snapshot,而历史由 +App 子会话和 Tool/operation 记录承载;`retain_readonly` 会保留一个不可写旧 snapshot,只能作为历史或新 +instance 的受控上下文。两者都可以符合产品目标,但实现、fixture 和隐私/清理预期不同;不能靠默认值隐式 +决定。 + +建议主定义补一份 Pomodoro 的最小 Manifest 片段或等价表格:required feature、严格 schema(至少允许的展示 +字段)、schema version、最大大小和明确 retention。还应说明完成/中断/关闭时 instance snapshot 与 deadline +operation、App 子会话、Tool/outbox 的独立清理顺序,确保“保留 UI 状态”不会被误解为旧 instance 可恢复为 +`focusing` 或可再次写入。 + +## 评审关闭条件 + +- D04-07~D04-09 已回填本轮实施权威 [04.runtime_workspace.md](04.runtime_workspace.md) 和 + [plan.md](../plan.md);实施时须依照已冻结的 Tool、完成收口和 Manifest 契约提供 fixture 与自动化验收; +- D04-C6 继续作为 P5 carryover 留在本记录与第一轮记录中;没有 profiling 证据前,不把 Rust 迁移纳入当前 + 实现范围; +- 本评审只记录发现和建议,未修改第 04 次主定义、正式方案、计划或实现。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/03.design_review.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/03.design_review.md new file mode 100644 index 0000000..524ae8b --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/03.design_review.md @@ -0,0 +1,153 @@ +# 04.runtime_workspace 设计评审(三) + +**评审编号:** 03 +**日期:** 2026-08-06 +**评审对象:** [04.runtime_workspace.md](04.runtime_workspace.md)(实施权威)、[01.design_review.md](01.design_review.md)、[02.design_review.md](02.design_review.md)、[00.base.md](../00.base/00.base.md)、[01.kernel.md](../01.kernel/01.kernel.md)、[03.sdk_and_coreapp.md](../03.sdk_and_coreapp/03.sdk_and_coreapp.md)、[plan.md](../plan.md)、[app_final_design.md](../../设计/02.正式方案/app_final_design.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md)、[lineup-app-layer-architecture.md](../../设计/02.正式方案/lineup-app-layer-architecture.md)。 +**评审方法:** 独立以第 04 次主定义作为唯一实施依据,对照前置迭代冻结的 Manifest / Tool Descriptor / 生命周期契约和正式 Runtime 方案。只记录会使本轮实现或验收无法得到唯一结论的问题;不把 Rust 演进、系统通知、真实语音、Whiteboard 工作区或其他范围外设想重新升级为当前问题。 +**总体结论:** 已确认的核心方向保持一致:MiniApp 是 Agent 的业务工具;`pomodoro.start` / `pomodoro.interrupt` 由 Agent 调用;deadline、Agent 中断和生命周期中断由 Runtime 原子裁决;完成后立即关闭并由 Interact 呈现;快照采用 `retain_readonly`;子会话和 pending Interact 的归属规则正确;Whiteboard 和 Rust Core 均未误入本轮范围。D04-10~D04-12 已按收敛决议回填:两个 Tool 已有最小可发布 Descriptor,同一会话只允许一轮 `focusing` 专注,当前验收入口限定为 IM、未来 Voice 只复用语义。所有 P0~P3 设计问题已清零,本迭代设计就绪,待实施。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施权威;✅ 已解决;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须记录延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P1 | D04-10 | 已命名 `pomodoro.start` / `pomodoro.interrupt`,但尚未给出能进入 Agent Inventory 的完整 Tool Descriptor,也未明确短 Tool 的 Runtime 与 Pomodoro 各自承担的处理步骤。 | 已冻结两个 Tool 的 v1 Descriptor:输入 / 输出 schema、调用模型、启动/前台策略、超时/幂等和 Runtime 优先的原子裁决顺序。 | +| ✅ | P1 | D04-11 | `interrupt` 假定每个会话只有一个 `focusing` operation,但再次收到同一会话的 `pomodoro.start` 时的规则没有定义。 | 已确定同一 `conversation_id` 只允许一轮 `focusing` operation;不同 call 的第二次 start 稳定拒绝为 `active_focusing_operation`,不创建任何新资源。 | +| ✅ | P2 | D04-12 | 主定义和验收把“IM 或 Voice”写为当前可验收入口,但本迭代明确不实现真实 Audio Mode。 | 已限定第 04 次仅以 IM 验收完整闭环;未来 Voice 复用同一意图和 Tool 语义,不要求本轮实现或验收。 | +| ✅ | — | D04-C7 | Agent 驱动、唯一终态与 outbox | `pomodoro.interrupt` 已不再被误作用户直接 SDK command;Agent Tool、deadline 和 lifecycle 共同进入 Runtime 的原子终态裁决,第一结果唯一。D04-10 仅补 Tool Descriptor 层,未推翻此决议。 | +| ✅ | — | D04-C8 | 完成后的工作区和会话收口 | 已确定 `completed` 后 Runtime 写唯一结果 / outbox,立即关闭 Pomodoro、结束子会话、恢复 Interact;不保留 Pomodoro 完成页。 | +| ✅ | — | D04-C9 | 业务快照、子会话与 Interact | snapshot 只作 UI / 历史展示,`retain_readonly` 旧实例不可写、不可复活;已结束子会话只读,pending 标准交互留在主 IM。 | +| ✅ | — | D04-C10 | 当前范围 | Whiteboard 仅保持第 03 次 SDK / 隔离回归;系统通知、来电检测、免打扰和真实 Audio Mode 均不在第 04 次范围。 | +| ⚪ | P5 | D04-C11 | Runtime Core 是否下沉 Rust | 已在 `plan.md` 留作后续方向;没有 profiling 证明状态、deadline、Tool/outbox 或 lifecycle 是热点之前,不进入第 04 次实施。重新评估触发条件不变。 | + +## 已确认的一致性 + +### Agent 是业务入口,Pomodoro 不是用户直接操作的独立 App + +主定义第 2、3 节已清楚表达:用户用当前 IM(未来可用 Voice)向 Agent 说明开始或停止的意图;Agent 调用 +Pomodoro Manifest 声明的 Tool;Pomodoro 本身只显示和接收 Runtime 投影,不设置暂停、继续或结束专注的业务按钮。 +这与前置迭代“Agent Tool 经 Runtime Tool Router 和动态 Inventory 调用、bundled App 不直连 Agent”的边界一致。 + +### 三种结束来源已有共同的可靠收口点 + +到达 `ends_at`、Agent 调用 `pomodoro.interrupt({})`、用户返回 Interact / 切换 App / 关闭工作区,已明确竞争同一 +Runtime 原子终态。先成功者把长期 `pomodoro.start` 收口为 `completed` 或 `interrupted`,并只产生一次结果和 +outbox;迟到事件获得稳定回执。自动暗屏、锁屏与 Surface 重载不是退出,刷新或重启按绝对 `ends_at` 恢复或结算。 +这符合 Runtime 对 operation、outbox、焦点与生命周期拥有最终权力的正式架构。 + +### 完成、历史和 Interact 归属已经无冲突 + +完成路径已选择“立即收口”:Runtime 原子完成后立即关闭 Pomodoro、结束子会话并恢复 Interact,由 Interact / +Agent 呈现结果。`app.instance-state.v1` 的 snapshot 与 deadline operation 分属不同事实来源;Pomodoro 使用严格 +schema v1、4 KiB、`retain_readonly`,关闭后只能作为只读历史。已结束子会话同样只读;仍 pending 的标准交互 +由 Interact / 主 IM 继续承接,不能追加到旧子会话。 + +## D04-10:两个 Agent Tool 缺少可执行的 Manifest / 路由契约 + +**已解决(2026-08-06,收敛决议)。** 主定义已冻结 `pomodoro.start` / `pomodoro.interrupt` 的 v1 Descriptor: +Runtime 先校验、定位和原子裁决;Pomodoro Surface 只接收已裁决投影,既不决定终态也不影响短 Tool 的回执。 +以下为决议前的风险分析。 + +### 为什么这是 P1 + +第 04 次主定义已经确定两个名称和高层语义: + +```text +pomodoro.start({ duration_seconds, activity? }) 长期 operation +pomodoro.interrupt({}) 短 Tool +``` + +但第 03 次迭代冻结的 Tool Descriptor 要求每项 Agent Tool 至少具备稳定 ID、版本、输入 / 输出 JSON Schema、 +`handling`、目标 App、前台要求和超时等信息;正式 Runtime 方案还要求声明面向 Agent 的说明、幂等规则、风险和 +启动策略。这些字段决定 Runtime 是否创建 instance、何时前台化、如何验证输入、以及何时可向 Agent 发送结果。 +当前 Pomodoro Manifest 片段只声明了 `app.instance-state.v1`,未给出这两项 Tool 的等价契约。 + +这在 `pomodoro.interrupt` 上尤其不能由实现自行猜测:当前主定义同时说“Runtime 将调用路由给该 Pomodoro +instance”与“Runtime 在同一原子裁决中将 `pomodoro.start` 收口”。若没有清晰的 Descriptor 和处理顺序,Web / +Tauri 实现可能分别选择“Surface 收到短 Tool 后自己 complete”或“Runtime 直接给 Agent 回执”。前一种会使 +Surface 的存活状况影响中断可靠性,违反已确认的终态所有权;后一种是合理选择,但必须作为契约写明。 + +### 最小收敛内容 + +不需要新增用户能力或扩大 SDK。建议在实施权威中为两个 Tool 加一个最小 Manifest 表 / JSON 片段,至少固定: + +1. `pomodoro.start` 的 `operation` 调用模型、输入 schema(`duration_seconds` 为正整数,`activity` 为可选受限字符串)、最终输出 schema(`completed` / `interrupted` 及约定的结果字段)、启动 / 前台策略、超时或由 `ends_at` 约束的规则、幂等键与重复调用结果; +2. `pomodoro.interrupt` 的短调用模型、空对象输入 schema、三种既定稳定回执的输出 schema,以及它不接受目标 ID 的规则; +3. Runtime 在验证、按 `conversation_id` 定位并原子收口后写入短 Tool 自身回执和长期 `start` 的唯一结果 / outbox;Pomodoro Surface 只接收已裁决状态投影,不能以 `sdk.tools.complete` 决定或补写任何终态; +4. 两项 Tool 在当前 app 未运行、Surface 已卸载、instance 正在 closing、Inventory revision 过期和 schema 非法时的受控拒绝 / 恢复路径。 + +字段名称不必照搬本记录;关键是由 Runtime 发布到 Agent 的契约能让 Web 与 Tauri 得到同一行为。完成回填后,本项可关闭。 + +## D04-11:同一会话的第二次 `pomodoro.start` 没有唯一规则 + +**已解决(2026-08-06,收敛决议)。** 同一 `conversation_id` 只允许一个 `focusing` Pomodoro operation。 +不同 `source_tool_call_id` 的第二次 `pomodoro.start` 稳定返回 `active_focusing_operation`,不创建 instance、 +子会话、deadline operation、焦点变更或 outbox;Agent 必须先停止旧轮,或等待其已经终态。 +以下为决议前的风险分析。 + +### 为什么这是 P1 + +主定义让 `pomodoro.interrupt({})` 从调用的 `conversation_id` 绑定“当前唯一的 `focusing` Pomodoro operation”。 +但 `pomodoro.start` 在已有 `focusing` operation 时是否可以再创建一个 instance / 子会话 / deadline operation 尚未 +定义。若两个 Host 自行选择不同处理,至少会产生以下不兼容情况: + +```text +实现 A:允许第二个 start + → 同一 conversation 同时存在两个 focusing operation + → interrupt({}) 无法再唯一定位目标。 + +实现 B:静默覆盖第一个 start + → 第一个长期 Tool 没有可靠的 interrupted 结果 / outbox。 + +实现 C:拒绝第二个 start + → 需要向 Agent 返回什么稳定结果尚未定义。 +``` + +这不是未来“多个计时器”功能的讨论,而是当前单次专注约束必须明确拒绝或替换的边界。否则无法完成 +`pomodoro.interrupt` 的唯一目标验收,也无法实现本轮的唯一 outbox 要求。 + +### 最小收敛内容 + +推荐第一版采用最保守规则:同一 `conversation_id` 已有 `focusing` Pomodoro operation 时,新的 +`pomodoro.start` 被 Runtime 稳定拒绝(例如 `active_focusing_operation`),不创建任何 instance、子会话、deadline +或 outbox;Agent 必须先调用 `pomodoro.interrupt({})`,或在旧 operation 已 `completed` / `interrupted` 后再开始。 +若产品希望“新的开始替换旧的开始”,也可采用先原子中断旧 operation、再创建新 operation 的规则,但必须定义两个 +Tool 结果 / outbox 的先后和任一事务失败时的恢复,复杂度更高。 + +无论选择哪种,都应在验收加入:重复 start、并发 start、start 与 interrupt 并发、start 与 deadline 并发,且确认 +每个 operation 只有一次终态和一次长期 Tool outbox。 + +## D04-12:当前 IM 验收与未来 Voice 语义混在一起 + +**已解决(2026-08-06,收敛决议)。** 第 04 次仅通过 IM 验证完整闭环;未来 Voice 只能复用相同的自然语言 +意图、Agent Tool 和 Runtime 语义,本轮不实现或验收 Audio Mode、语音采集、识别或媒体能力。 +以下为决议前的风险分析。 + +### 为什么这是 P2 + +用户已经确认:当前用户主要以 IM 消息与 Agent 交互,未来 Voice 必须复用同一“自然语言意图 → Agent Tool → +Runtime”模型。这个语义是正确的。可是主定义的目标和验收第 1、4 条目前写成“IM 或 Voice”,同时本迭代范围又 +明确排除 Audio Mode 的真实媒体能力。按字面,验收人员无法判断是否必须交付语音采集、识别、语音对话入口或 +Voice 端到端测试。 + +这不要求本轮实现 Voice,也不要求改变 Agent Tool。需要的只是把时间边界写清:第 04 次实际验证当前 IM;未来 +Voice 接入后必须把识别出的同类意图映射到相同的两个 Tool,并遵守相同的会话绑定、终态和 outbox 语义。 + +### 最小收敛内容 + +将主定义、总体计划和验收中的“IM 或 Voice”改为类似表述: + +```text +第 04 次通过 IM 验证用户向 Agent 表达开始 / 停止意图的完整闭环。 +未来 Voice 复用相同的 Agent Tool 和 Runtime 语义;本轮不实现或验收真实 Audio Mode、语音采集、识别或媒体能力。 +``` + +然后保留 Web Reference Host 和 Tauri Desktop Host 的 IM 代表性流程验收。回填后,本项可关闭。 + +## 评审关闭条件 + +1. D04-10~D04-12 已回填 [04.runtime_workspace.md](04.runtime_workspace.md)、[plan.md](../plan.md) 和验收条目; +2. 实施时依据冻结后的 Tool Descriptor 增加合法、非法输入、Inventory 过期、重复 / 并发和 Surface 缺席的 fixture 与自动化验收; +3. D04-C11 继续作为 P5 carryover 留在计划中;没有 profiling 证据前,不启动 Rust 迁移; +4. 本评审只记录发现,未修改主定义、计划、正式方案或实现。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/04.runtime_workspace.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/04.runtime_workspace.md new file mode 100644 index 0000000..2a920f8 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/04.runtime_workspace.md @@ -0,0 +1,382 @@ +# LineUp App 迭代定义:Runtime 工作区与 Pomodoro MiniApp + +**迭代编号:** 04.runtime_workspace +**状态:** 设计已冻结,待实施 +**日期:** 2026-08-06 +**前置基线:** [03.sdk_and_coreapp.md](../03.sdk_and_coreapp/03.sdk_and_coreapp.md) +**总体计划:** [plan.md](../plan.md) +**权威架构:** [APP架构设计.md](../../设计/APP架构设计.md) +**实施规范:** [05.technical_implementation_spec.md](05.technical_implementation_spec.md) + +**冻结说明:** 本定义以 [03.design_review.md](03.design_review.md)、[06.technical_implementation_spec_review.md](06.technical_implementation_spec_review.md) +和 [07.技术实施规范评审(二).md](07.技术实施规范评审(二).md) 已关闭的 P0~P3 决议为实施基线;后续若调整 +Pomodoro Tool、Runtime 终态、状态快照、session data 同步语义或本迭代范围,须通过新的设计评审记录确认。 + +本文定义产品与验收边界;[05.technical_implementation_spec.md](05.technical_implementation_spec.md) 只将这些 +已冻结规则映射到模块、存储、协议和测试,不改变本迭代的产品决议。当前没有未解决的 P0~P3; +TIS2-06(多入口 session data mutation queue)是具有明确触发条件的 P4 延续项,不属于本轮实现。 + +## 1. 迭代目标 + +前三次迭代已经完成 Runtime、Interact、MiniApp SDK v1 和参考 MiniApp 的基础契约。本迭代不再扩展 +复杂的业务应用,而是把这些基础接到真实的 Web/Tauri 工作区界面,证明用户可以启动、运行、关闭、恢复 +一个 MiniApp,并在 Interact 中查看对应的子会话。 + +本迭代选择一个极简的 **Pomodoro 番茄时钟 MiniApp** 作为 Runtime 验证应用。它服务于写作业、看书、 +冥想等单次专注活动:第 04 次中用户通过 **IM** 请求 Agent 立即开始或停止一段固定时长的专注;未来 Voice +复用相同意图与 Tool 语义,但不属于本轮实现或验收。Pomodoro 只负责低干扰显示,不负责任务、项目、统计或 +通用烹饪/家务倒计时。 + +```text +Interact IM + → Agent 请求启动 Pomodoro + → Runtime 创建 Pomodoro App 子会话并进入启动中 + → Host 确认 Pomodoro 已进入前台,Interact 才进入后台 + → Runtime 创建 deadline operation,专注正式开始 + → 用户专注;自动暗屏或锁屏不改变本轮计时 + → 到时间完成,或用户主动离开专注工作区而中断 + → Runtime 回传结果并立即收口关闭 App,恢复 Interact + → Interact / Agent 呈现本次专注完成结果 + → 子会话在 IM 中折叠保存 + → 重启后按规则恢复或继续处理 +``` + +## 2. 产品和架构边界 + +### 2.1 Interact + +Interact 仍然是系统级 `system` MiniApp,负责人与 Agent 的交互: + +- Agent 通过 `pomodoro.start` 请求立即开始一段专注,也可以通过 `pomodoro.interrupt` 结束当前专注; +- Agent 向用户询问是否开始下一轮; +- Agent 需要用户确认时使用 `notice / choice / confirm / input`; +- 交互记录属于 Interact 和对应会话,不属于 Pomodoro 的内部 UI。 + +### 2.2 Runtime + +Runtime 负责最终裁决和可靠性: + +- 创建和管理 Pomodoro App instance; +- 管理前台、后台、挂起、关闭和恢复; +- 校验 Tool、作用域、实例和会话上下文; +- 为当前 MiniApp App 子会话保存通用、revision 控制的 JSON 业务数据;Runtime 不理解其中的业务字段; +- 管理所有 MiniApp 统一的启动状态:`starting → ready | failed | cancelled`,并按每个 Agent Tool 声明的就绪条件 + 判断是否可执行;将该状态作为与触发调用的 `call_id` 关联的受控协议进度通知 Agent; +- 管理通用 deadline operation:持久化 `ends_at`、裁决完成/中断唯一终态并写唯一 outbox; +- Runtime 的 operation 提交协调器先一次持久化 operation、MiniApp session data、子会话 / workspace 目标、Tool receipt 和 outbox; + 只有提交成功后才驱动 Lifecycle、Host 与 Surface 对账。提交失败不改变任何层;提交后的 UI / Host 失败不回滚 + 已提交业务事实,而按已保存状态恢复、重试或收口; +- 处理刷新、断线和重启恢复; +- 关闭 App 时停止新 Tool 投递,并收口未完成操作; +- 通过 outbox 可靠回传结果; +- 需要系统通知时通过 Capability Gateway 处理。 + +对需要由 MiniApp 处理的 Agent Tool,Runtime 只依据已验证的 `app_scope`、method、schema、当前会话和幂等键 +进行路由:它知道“调用哪一个 App 的哪一个方法、参数是否合格”,但不理解参数值的业务含义,也不直接修改 +MiniApp 的业务数据。Runtime 先解析或建立该 App 当前可用的 App 子会话,等待其 activation 成为 `ready`,再把调用 +投递给 MiniApp;MiniApp 自己执行业务方法、写自己的 session data,并把受控结果交回 Runtime。 + +`activation_ready` 是“这个 App 已经能接收后续 `app_ready` Tool”的 Runtime 事实,Agent 收到它后再连续编排相关 +指令是推荐方式,但不是 Runtime 接收合法后续指令的前置条件。若后续 `app_ready` / `foreground_required` 调用在同一 +App 的 activation 仍为 `starting` 时先到,Runtime 持久化并等待该 activation,ready 后按到达顺序投递;失败或取消则 +回传 `app_activation_failed` / `app_activation_cancelled`,不会执行 MiniApp handler。`activation_not_required` 调用不等 +ready,例如启动中的 `pomodoro.interrupt` 必须立即取消启动。所有 ready / failed / cancelled 通知由 Runtime 发给 Agent, +MiniApp、Surface 和 Host 都不能直接与 Agent 通信。 + +### 2.3 Pomodoro MiniApp + +Pomodoro 是普通 `bundled` MiniApp,必须使用与其他非系统级 MiniApp 相同的 SDK、受限 Surface 和 +Capability 规则。 + +它只负责: + +- 显示活动名称、倒计时和低干扰专注界面; +- 通过自身的 session data 显示活动名称、倒计时和展示状态; +- 接收 Runtime 路由的受限 Tool / 生命周期事件;不以界面按钮直接改变专注业务状态; +- 通过 SDK 读取和保存自己定义的通用 session data; + +它不能: + +- 直接访问 AppServer、Transport、Conversation Store 或 Agent; +- 直接调用 Tauri、系统通知或其他 Host 能力; +- 发起 Interact 标准交互; +- 修改 Runtime 的焦点、Registry 或其他 App 数据。 + +## 3. Pomodoro 最小模型 + +Pomodoro App instance 和一次专注 operation 不是同一个对象:`app_session_id` 表示 Interact 中围绕该 +MiniApp 的子会话,`instance_id` 表示 MiniApp 实例,`operation_id` 才表示一次实际专注。`pomodoro.start` +会先建立一个处于 `starting` 的 App 子会话 / instance;只有 Host 确认它已经成为当前前台界面后,才创建 +`focusing` operation。因此它们不在数据模型上互为同义词。 + +一次专注 operation 只保留以下状态: + +```text +focusing 正在专注 +completed 到达约定时长 +interrupted 用户明确离开、切换工作区或关闭 App 后中断 +``` + +最小 operation 数据: + +```text +operation_id +source_tool_call_id +activity? +duration_seconds +started_at +ends_at +state +ended_at? +interruption_reason? +``` + +运行中只以 `ends_at` 作为时间事实;`remaining_seconds` 是 Pomodoro UI 从 `ends_at - now` 派生的显示值, +不单独持久化。Pomodoro 可以在自己的通用 session data 中保存活动名称、`operation_id`、`started_at`、 +`ends_at` 与展示状态;这些字段的结构和可变性由 Pomodoro 自己决定,Runtime 不解释它们,也不以其替代 +Runtime operation 的 Tool 终态和 outbox。 + +Pomodoro 在 MiniApp SDK 源码中声明以下两个由 Agent 调用的 Tool;构建时自动生成 Manifest 中对应的不可执行 +Tool 描述,开发者不再手写第二份 Tool 清单。MiniApp 是 Agent 的业务工具:用户在 IM 中以自然语言表达开始、停止等 +意图(未来同样适用于 Voice),由 Agent 决定并调用 Tool;Pomodoro 的界面不提供暂停、继续或“结束专注”的业务按钮。 + +```text +pomodoro.start({ duration_seconds, activity? }) +pomodoro.interrupt({}) +``` + +`pomodoro.start` 是要求前台就绪的长期 Tool operation:它进入 `starting` 后,只有 Host 确认 Pomodoro 已经 +成为当前前台界面时才进入 `focusing` 并开始计算 `ends_at`;最终只在 `completed` 或 `interrupted` 时回传一次 +业务结果。若前台启动最终失败或被取消,则返回“本次计时未开始”的启动失败结果,而不创建 operation 或专注 +中断记录。`pomodoro.interrupt({})` 是短 Tool:Tool Router 只从该 Agent 调用所在的 +`conversation_id` 中绑定当前唯一的 `starting` 或 `focusing` Pomodoro;调用者不能传入或伪造 `instance_id`、 +`app_scope`、`operation_id` 或其他会话目标。Runtime 将调用路由给该 Pomodoro instance,并在同一原子裁决中 +在 `starting` 时取消启动、在 `focusing` 时将 `pomodoro.start` 收口为 `interrupted`。若 Surface 已卸载,Runtime +仍必须完成已经开始的专注裁决;Surface 只接收通用 lifecycle 关闭通知。`pomodoro.interrupt` 的稳定回执为 +`focus_start_cancelled`、`interrupted`、`no_active_focusing_operation` 或 `operation_already_final`;重复或迟到调用 +不得重写终态或新增 outbox。用户不需要在 App 内再次点击“开始”,也没有暂停、继续或恢复入口。 + +长期 `pomodoro.start` 的“过程消息”和“业务结果”必须分开。Runtime 在接受调用并提交 `starting` activation 后,持久化并 +回传一次协议回执 `accepted / starting`;它只表示“正在打开 Pomodoro”,Agent 不得据此宣称已经开始计时。Host 确认 +前台、Runtime 创建 `focusing` operation 与 `ends_at` 后,再持久化并回传一次 `started` 进度;只有此时 Agent 才能 +对用户说“已开始计时”。随后 `completed`、`interrupted` 或 `not_started` 才是符合 `pomodoro.start` result schema 的 +唯一最终业务结果。相同 `source_tool_call_id` 的重放只返回已持久化的最新回执、进度或最终结果,不创建新 operation, +也不重复追加 outbox。 + +对于 Pomodoro,`started` 已经包含 `activation_ready`:它不仅表示 MiniApp 可以接收调用,还表示 Host 已确认前台、 +Runtime 已创建 operation,倒计时已真实开始。因此第 04 次不额外发送一条独立的 `activation_ready`,避免 Agent 收到 +两个含义重叠的“已经好了”消息;启动失败或取消则用最终 `not_started` 结果收口。 + +启动中尚未前台激活时,用户 / Agent 要求停止、用户离开该启动中的工作区或 Host 最终报告无法激活,都只取消 +启动尝试;它们不产生 `interrupted` operation。已经进入 `focusing` 后,用户切回 Interact、切到其他 MiniApp +或关闭 Pomodoro 才属于 Runtime 生命周期事件:Runtime 以同一原子中断规则收口,但不伪造 Agent Tool。自动暗屏、 +锁屏和 Surface 重载不等于退出。到点、Agent 调用 +`pomodoro.interrupt` 与生命周期关闭竞争同一个终态,先成功者生效;后续调用只得到上述稳定回执。到点取得 +`completed` 后,Runtime 先原子写入唯一结果/outbox,再立即关闭 Pomodoro、结束子会话并恢复 Interact; +Pomodoro 不显示独立完成提示,完成结果由 Interact / Agent 呈现。如果 Agent 要询问用户是否开始下一轮, +必须回到 Interact 的标准交互。 + +两个 Tool 的 v1 Descriptor 如下。Runtime 只发布通过由 SDK 声明生成的 Manifest、Inventory revision、会话作用域和 +输入 schema 校验的 Tool;校验失败、过期 Inventory 或不存在的目标均稳定拒绝,且不启动或唤醒 Pomodoro。 + +| Tool | 调用模型与启动策略 | 输入 | 输出 / 稳定拒绝 | 幂等、超时与路由顺序 | +|---|---|---|---|---| +| `pomodoro.start` v1 | 长期 `operation`;`foreground_required`。Runtime 先创建处于 `starting` 的 instance 与 App 子会话,持久化 `accepted / starting` 回执;Host 确认前台激活后才创建 deadline operation,并持久化 `started` 进度。 | `{ duration_seconds: 正整数, activity?: 字符串 }`;拒绝额外字段。 | 唯一最终业务结果:`{ state: "completed" \| "interrupted", operation_id, started_at, ends_at, ended_at, interruption_reason? }`;启动最终失败或取消时返回 `not_started`(原因是 `focus_start_failed` / `focus_start_cancelled`),且没有 `operation_id`;若同一会话已有 `focusing` 或 `starting` 专注,稳定拒绝 `active_focusing_operation` / `focus_start_in_progress`。 | `source_tool_call_id` 是幂等键:同一调用重试返回既有的最新回执、进度或最终结果;不同调用由同一个 conversation 的活动 / 启动槽位作原子 compare-and-set。开始倒计时的时刻只能是前台激活确认时刻,运行期限才以持久化 `ends_at` 为准。 | +| `pomodoro.interrupt` v1 | 短 Tool;`activation_not_required`,不为此调用启动、唤醒或等待 Pomodoro Surface。 | 空对象 `{}`;不接受 `instance_id`、`operation_id`、`app_scope` 或其他目标字段。 | `{ status: "focus_start_cancelled" \| "interrupted" \| "no_active_focusing_operation" \| "operation_already_final", operation_id? }`。 | `source_tool_call_id` 是幂等键。Runtime 从调用的 `conversation_id` 绑定当前唯一 `starting` 或 `focusing` 对象;前者只取消启动,后者原子写 `interrupted` 终态与长期 start 的唯一结果/outbox。Surface 不决定或补写终态。 | + +上表中的输入/输出 schema 冻结为以下 JSON Schema;Runtime 在将 Descriptor 发布到 Agent Inventory 前验证它们。 + +```json +{ + "pomodoro.start": { + "input_schema": { + "type": "object", + "additionalProperties": false, + "required": ["duration_seconds"], + "properties": { + "duration_seconds": {"type": "integer", "minimum": 1}, + "activity": {"type": "string", "maxLength": 256} + } + }, + "result_schema": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["state", "operation_id", "started_at", "ends_at", "ended_at"], + "properties": { + "state": {"enum": ["completed", "interrupted"]}, + "operation_id": {"type": "string"}, + "started_at": {"type": "string"}, + "ends_at": {"type": "string"}, + "ended_at": {"type": "string"}, + "interruption_reason": {"type": "string"} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["status", "reason"], + "properties": { + "status": {"enum": ["not_started"]}, + "reason": {"enum": ["focus_start_failed", "focus_start_cancelled"]} + } + } + ] + } + }, + "pomodoro.interrupt": { + "input_schema": { + "type": "object", + "additionalProperties": false + }, + "result_schema": { + "type": "object", + "additionalProperties": false, + "required": ["status"], + "properties": { + "status": { + "enum": ["focus_start_cancelled", "interrupted", "no_active_focusing_operation", "operation_already_final"] + }, + "operation_id": {"type": "string"} + } + } + } +} +``` + +同一 `conversation_id` 第一版只允许一个 `focusing` 或 `starting` Pomodoro。不同 `source_tool_call_id` 的第二次 +`pomodoro.start`:若已有 `focusing`,稳定拒绝为 `active_focusing_operation`;若已有 `starting`,稳定拒绝为 +`focus_start_in_progress`。两种拒绝都不得创建第二个 instance、子会话、deadline operation、焦点变更或 outbox。 +若用户要修改尚未开始的时长,Agent 必须先调用 `pomodoro.interrupt({})` 取消原启动尝试,再发起新的 start。 + +所有 MiniApp 都通过 SDK 获得按 `app_scope + app_session_id` 隔离的通用 session data。其内容是 MiniApp 自己 +定义的任意嵌套 JSON 字典:Pomodoro 可以把当前展示信息或历史写在其中;未来 To-do 可以把待办业务数据写在 +其中。Runtime 只保证隔离、revision、持久化、恢复、原子提交和通用安全配额;它不声明、校验或冻结业务 schema, +也不把关闭后的业务数据统一变成只读。已发生的专注结果写入 Interact / IM 时,才由 Runtime 生成独立的、不可 +篡改的静态记录。 + +第 04 次的 session data 同步规则冻结为:不存在数据时 `get()` 返回 `{ data: null, revision: 0 }`;每次 +`subscribe()` 成功注册后,Runtime 必须先投递当前完整快照,再按严格递增的 revision 投递后续完整快照。订阅注册 +与 `replace()` 在同一 App 子会话的串行顺序中处理,因此 `get()` 与 `subscribe()` 之间发生的更新不会丢失。SDK 忽略 +重复或更旧 revision;发现 revision 跳跃、或 Web / Tauri Bridge 重连时,重新 `get()` 并建立新的订阅,以 Runtime +当前快照为准。`expected_revision` 冲突仍只返回 `session_data_revision_conflict`,本轮不自动合并业务数据;多入口 +协作写入见已延期的 mutation queue 专项设计。 + +## 4. 工作内容 + +### 4.1 工作区界面 + +- 显示当前前台 App; +- 支持由 `pomodoro.start` 从 Interact 切入 Pomodoro,以及用户显式返回 Interact;返回/切换会中断当前 + 专注 operation,而不是把它作为通用后台计时器继续运行; +- 显示 App 前台、后台、挂起、关闭和恢复状态; +- App 启动失败时恢复 Interact;对于需要前台就绪的 Tool,失败表示业务尚未开始; +- 刷新或重启后恢复工作区快照。 + +### 4.2 生命周期接入 + +- 将 `AppLifecycleManager`、`AppFocusManager` 和 `AppOrchestrator` 接入真实 Host; +- 区分自动暗屏/锁屏、Surface 重载、工作区切换和真正关闭:前两者不终止专注,后两者中断专注; +- Runtime 用持久化 `ends_at` 而非 MiniApp UI 定时器管理 deadline;到点、用户中断和关闭竞争同一唯一终态; +- deadline 取得 `completed` 后,Runtime 依次提交唯一结果/outbox、立即关闭 Pomodoro、结束子会话并恢复 + Interact;完成提示由 Interact / Agent 呈现,不保留 Pomodoro 完成展示窗口; +- 关闭后停止向该 instance 投递新 Tool; +- 用户退出专注时先将 `pomodoro.start` 终结为 `interrupted` 并提交结果;随后关闭不会将该已终态 Tool 改写为 + `cancelled(app_closed)`。其他未终态普通 Tool 仍按 `cancelled(app_closed)` 收口; +- 已提交结果继续通过 outbox 发送; +- 关闭后恢复前一个有效前台 App。 + +### 4.3 子会话和 Interact 交互 + +- 接受 `pomodoro.start` 时创建 `starting` Pomodoro instance 和 App 子会话;只有 Host 确认前台激活后才创建 + focusing operation。只有恢复同一个未结束 instance 时才恢复同一个子会话; +- 在主 IM 中显示可折叠的 Pomodoro 子会话; +- 子会话只记录人与 Agent 围绕该番茄钟的交互和结果; +- 倒计时和退出按钮等 App 内部操作不写入 IM; +- App 前台时,Interact 的标准交互可以显示在其上方; +- 展示位置变化不改变交互归属; +- 已结束子会话只读;“继续处理”创建新的 instance 和新的子会话;关闭时仍 pending 的 Interact 标准交互 + 不自动取消,保留在主 IM / Interact 中等待回答、dismiss、超时或 Runtime 失败,且不能再追加到已结束子会话。 + +### 4.4 恢复和提醒 + +- 重启后根据 `ends_at` 重新计算剩余时间;若已到点,Runtime 原子转为 `completed` 并只写一次结果/outbox; +- 不允许重启后无条件重新开始完整时长; +- 不承诺 Runtime / Host 完全未运行时的即时系统提醒;下次恢复时必须按已到期状态结算,不能重置或重复回传; +- 来电等外部打断预留 `host_interruption` 语义,但第 04 次不实现电话检测、免打扰或静音能力; +- 明确自动暗屏/锁屏、工作区离开、关闭、完成和中断的区别; +- 第一版不实现 Pomodoro App 内完成提示;完成结果由 Interact / Agent 呈现; +- 系统通知作为后续 Capability 验证,不允许 MiniApp 直接调用系统 API。 + +### 4.5 自动化和真实验收 + +- 覆盖 `focusing → completed / interrupted` 状态机、`pomodoro.start` 长 Tool、Agent 调用的 + `pomodoro.interrupt` 与 Runtime 生命周期中断; +- 覆盖 Tool Descriptor 的合法/非法输入、Inventory 过期、同 call 重试、同会话重复/并发 start、启动中取消、 + 前台激活失败、start 与 interrupt/deadline 并发;只有已经 focusing 的专注才可由 Surface 缺席时 Runtime 收口; +- 覆盖自动暗屏/锁屏不终止、工作区切换/关闭中断、重复/迟到退出和到点与中断并发时只产生一个终态; +- 覆盖重启前未到点恢复、重启后已到点结算和一次 outbox; +- 覆盖 `completed` 后立即关闭 Pomodoro、子会话只读、Interact 呈现完成结果,以及完成与关闭并发时终态不可改写; +- 覆盖通用 session data 的 App / 子会话隔离、JSON / 配额限制、revision CAS、原子写入与重启恢复;验证 Runtime + 不认识 Pomodoro 的 `current` / `history` 等业务字段; +- 覆盖 App 子会话创建、折叠、只读和继续处理; +- 覆盖 Interact 标准交互在 Pomodoro 前台时仍归 Interact,以及 Pomodoro 关闭后 pending 交互仍可在主 IM + 回答但不改写只读子会话; +- 通过 Web Reference Host 完成完整浏览器验收; +- 在 Tauri Desktop Host 完成至少一条代表性流程; +- 检查生命周期、Tool、交互、恢复和拒绝路径日志。 + +## 5. 不在本迭代范围 + +- 应用市场、服务端 App Catalog 和远程下载; +- 第三方 MiniApp 发布和在线更新; +- 多 Agent 连接和 Agent 切换; +- Audio Mode 和 Video Mode 的真实媒体能力; +- Pomodoro 统计报表、多个计时器、日历和复杂提醒计划; +- 通用倒计时(如烹饪、停车、家务)以及用户预设后手动开始的 timer 模式; +- 电话检测、系统专注模式、免打扰、静音和系统通知; +- Task Dashboard 的任务、项目、优先级、标签和历史管理; +- Whiteboard 的工作区接入、切换、完整用户流程、多人协作、云端同步和复杂绘图工具;第 04 次只保持第 + 03 次已有的 SDK / 隔离回归。 +- 通用 mutation queue,以及 Agent Tool、UI Action 与 Runtime 动作共同修改同一 App 子会话数据时的有序协作协议; + 当前 Pomodoro 没有修改型 UI Action,继续使用通用 session data 的 revision CAS。首次实现手动编辑 To-do 或 + Whiteboard 的交互式绘制流程前,必须先完成该通用机制的专项设计。 +- 将 MiniApp SDK 或 Runtime SDK 的实现迁入 Rust;本轮保持现有 TypeScript Runtime / Web Bridge 基线, + 未来仅在 profiling 证明 Runtime Core 存在性能热点后另行评估。 + +## 6. 验收目标 + +```text +1. 用户可以在 **IM** 中请求 Agent 立即开始或停止一段固定时长的写作业、看书或冥想专注;未来 Voice 必须 + 复用同一意图和 Tool 语义,但不属于本轮实现或验收。 +2. `pomodoro.start` 先创建真实 App instance 和 App 子会话并进入 `starting`;仅在 Host 确认 Pomodoro 已成为当前 + 前台后,才创建 deadline operation 并进入 `focusing`。前台激活最终失败或被取消时,本次计时未开始。 +3. Pomodoro 进入前台并开始后,Interact 可以退到后台;自动暗屏、锁屏和 Surface 重载不终止专注。 +4. 用户以 IM 要求 Agent 停止时,Agent 调用 `pomodoro.interrupt({})`;用户返回 Interact、切到其他 MiniApp + 或关闭 Pomodoro 时,Runtime 直接处理生命周期中断。两类路径的本轮唯一终态均为 `interrupted`,随后 + Interact 恢复前台;Pomodoro 不作为通用后台计时器继续运行。 +5. 到 `ends_at` 时,本轮唯一终态为 `completed`;Runtime 提交唯一结果/outbox 后立即关闭 Pomodoro、结束子会话 + 并恢复 Interact,由 Interact / Agent 呈现完成结果;到点和中断并发时只接受第一个原子终态。 +6. Runtime 对已经开始的 `pomodoro.start` 只回传一次 `completed` 或 `interrupted` 结果;若尚未前台激活即失败或 + 取消,则回传一次 `not_started` 结果。`pomodoro.interrupt` 不可伪造目标、可绑定当前会话的 `starting` 或 + `focusing` Pomodoro,并对重复/迟到调用给出稳定回执;关闭流程不得将已提交终态改写。 +7. 同一 `conversation_id` 不会同时存在两轮 `focusing` 或 `starting` Pomodoro;不同调用的第二次 `pomodoro.start` + 稳定拒绝为 `active_focusing_operation` 或 `focus_start_in_progress`,不创建任何新的 App 或 operation 资源。 +8. 刷新或重启后,未到点的本轮按 `ends_at` 恢复;已到点的本轮结算一次而不重置完整时长或重复回传。 +9. 子会话在主 IM 中折叠保存,关闭后只读;新的“继续处理”创建新的 instance / 子会话,pending Interact + 交互仍可在主 IM 回答但不能向已关闭子会话追加记录。 +10. Agent 的标准交互始终由 Interact 负责,Pomodoro 不伪造交互组件。 +11. 任意 MiniApp 都只能经 SDK 操作当前 `app_scope + app_session_id` 的通用 session data;Runtime 保证 JSON、 + 配额、revision、原子持久化和恢复,但不理解或限制其 `current` / `history` 等业务字段。不存在数据时为 revision 0; + 每个订阅首帧都是当前完整快照,之后 revision 严格递增,断线、跳跃时 SDK 重拉并重订阅。IM 完成记录是独立的 + 不可变静态快照。 +12. MiniApp 无法访问 Transport、Store、Agent、Host DOM、Tauri 或任意系统 API。 +13. `npm test -- --run`、`npm run build` 和 Web/Tauri 代表性验收全部通过。 +14. P0~P3 设计和验收问题清零。 +``` + +## 7. 后续定位 + +本迭代完成后,Whiteboard 可以作为下一条 Surface、Artifact 和隔离边界验证应用;Task Dashboard 保留 +为后续普通 `bundled` MiniApp,等 Runtime 工作区、SDK 和生命周期稳定后,再单独定义任务管理业务模型。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/05.technical_implementation_spec.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/05.technical_implementation_spec.md new file mode 100644 index 0000000..b17ff6c --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/05.technical_implementation_spec.md @@ -0,0 +1,682 @@ +# 04.runtime_workspace 技术实施规范 + +**状态:** 开发实施基线 +**日期:** 2026-08-06 +**实施权威:** [04.runtime_workspace.md](04.runtime_workspace.md) +**评审基线:** [03.design_review.md](03.design_review.md) +**前置实现:** [03.sdk_and_coreapp.md](../03.sdk_and_coreapp/03.sdk_and_coreapp.md) + +## 1. 目的、边界与强制约束 + +本规范将第 04 次迭代已冻结的设计映射为开发任务。实现必须以本文件和主迭代定义共同为准;若两者出现 +冲突,以主迭代定义为准,并先补充设计评审,不能在代码中自行选择另一种产品语义。 + +本轮交付的是通过 **IM** 由 Agent 启动、停止和观察的单次专注 Pomodoro。Voice 未来复用相同 Tool 语义, +但本轮不实现音频采集、识别或语音端到端路径。 + +以下约束不可违反: + +1. `pomodoro.start` 与 `pomodoro.interrupt` 都是 Agent Tool;Pomodoro Surface 不提供开始、暂停、继续或 + 结束专注的业务按钮。 +2. Runtime 是 `ends_at`、operation 终态、Tool result、outbox、焦点和生命周期的唯一裁决者。Surface 不能 + `complete`、`fail`、`cancel` 这两个 Tool,也不能发送 Agent 协议消息。 +3. 每个 `conversation_id` 同时至多存在一个 `focusing` 的 Pomodoro operation。 +4. deadline、Agent interrupt、返回 Interact、切换其他 MiniApp 和关闭 App 竞争同一个原子终态;第一个成功者 + 生效,所有后到者不得改写终态或新增长期 Tool outbox。 +5. `remaining_seconds` 只由 `ends_at - now` 派生,绝不持久化为第二个时间事实。 +6. completed 后立即关闭 Pomodoro、结束 App 子会话、恢复 Interact;完成结果由 Interact / Agent 呈现。 +7. 所有 MiniApp 都有 Runtime 管理的启动状态 `starting → ready | failed | cancelled`。启动状态不是 MiniApp + 的业务数据,也不是 Pomodoro operation;每个 Agent Tool 声明其成功执行前要求的就绪条件。 +8. MiniApp 只能通过 SDK 操作当前 `app_scope + app_session_id` 的通用 session data。Runtime 只管理隔离、JSON + 安全限制、revision、原子持久化和恢复,不理解或校验 `current`、`history` 等 MiniApp 业务字段。 + +## 2. 现有基线与改动边界 + +下表列出当前仓库已存在的模块与第 04 次必须补齐的责任。新增业务逻辑不得堆入 `src/main.ts`;该入口只能 +装配 Runtime、Host 和可信 Surface。 + +| 现有模块 | 当前责任 | 第 04 次改动 | +|---|---|---| +| `src/runtime/app-management/miniapp-manifest.ts` | Manifest、Tool Descriptor 的静态校验 | 增加 Tool 的通用启动就绪条件和 Runtime 托管 deadline operation 的声明类型及校验。 | +| `src/runtime/app-management/reference-miniapps.ts` | 内置 bundled App 的 Manifest | 新增 `POMODORO_MANIFEST`,并纳入默认 bundled manifests。 | +| `src/runtime/coordination/tool-router.ts` | 校验 Agent Tool、Inventory revision、schema、scope | 保持首层校验;为通过校验的 Pomodoro Tool 返回 Runtime-managed operation 路由,不能直接投递给 Surface。 | +| `src/runtime/coordination/lineup-runtime.ts` | Runtime 装配、持久化、outbox、事件 | 接入 Pomodoro operation 服务、deadline 调度、恢复、关闭编排和 Host 投影。 | +| `src/runtime/coordination/miniapp-tool-state.ts` | 普通 MiniApp Tool 的 Surface 回执状态机 | 保持普通 Tool 语义;Pomodoro 的 Runtime-managed Tool 不得走 Surface `sdk.tools.complete` 路径。 | +| `src/runtime/persistence/conversation-store.ts` | 会话、本地 outbox、Tool、App workspace 持久化 | 升级版本并持久化 operation、App 启动状态、通用 session data、Tool 去重及 IM 静态结果。 | +| `src/runtime/app-management/app-lifecycle-manager.ts`、`app-orchestrator.ts` | instance、焦点、关闭和恢复 | 提供由 Pomodoro operation 服务调用的受控 close,避免普通 `app_closed` 取消覆盖已提交的终态。 | +| `src/core-apps/pomodoro/`(新增) | 不存在 | 实现受限 Pomodoro SDK Surface;从通用 Tool / lifecycle 事件和自身 session data 渲染,不读取 Runtime operation。 | +| `src/main.ts` | Web / Tauri Host 装配 | 注册 Pomodoro 的本地 Surface 与工作区投影;不放入 Tool、计时、状态机或存储逻辑。 | + +## 3. Manifest、Tool 与 Runtime operation 契约 + +### 3.1 Manifest 扩展 + +在 `miniapp-manifest.ts` 增加以下结构;名称可保持一致,字段语义不得改变。`runtime_operation` 是通用的 +Runtime deadline operation 声明,不允许 MiniApp 注入代码或回调。`activation_requirement` 说明一个 Agent Tool +在业务处理或创建 Runtime operation 前,需要 Runtime 将目标 MiniApp 准备到什么程度;这不是 UI Action。 + +```ts +type ToolActivationRequirement = "activation_not_required" | "foreground_required" | "app_ready"; + +type RuntimeDeadlineOperationDeclaration = Readonly<{ + kind: "deadline"; + duration_input: "duration_seconds"; + concurrency_scope: "conversation_and_app"; + execution_owner: "runtime"; +}>; + +type MiniAppToolDescriptor = Readonly<{ + // 由 MiniApp SDK 的 defineAgentTools(...) 声明,并在构建时生成到 Manifest;不是手写的第二份文件。 + id: string; + version: 1; + handling: "direct" | "interactive" | "launch" | "foreground" | "operation"; + delivery: "miniapp_sdk" | "runtime"; + input_schema: JsonObject; + output_schema: JsonObject; + timeout_ms?: number; + activation_requirement: ToolActivationRequirement; + runtime_operation?: RuntimeDeadlineOperationDeclaration; + runtime_execution?: RuntimeToolExecutionRoute; +}>; + +type MiniAppManifestV1 = Readonly<{ + // 保留既有字段;不声明 MiniApp 的业务数据 schema。 + // tools 为 SDK 构建期生成的不可执行 Tool 契约。 +}>; +``` + +开发者只在 MiniApp 代码中调用 SDK 的 `defineAgentTools(...)`:每一项同时声明公开 method、说明、输入 / 输出 schema、 +activation requirement 和本地 handler。构建工具必须从这些 SDK 声明生成 Manifest 的 tools 投影;开发者不维护第二份 +手写 Manifest。生成物只包含描述数据,绝不包含 handler 函数、私有函数名、URL 或回调;`delivery = miniapp_sdk` +时 Bundle 内的 SDK 保留 `method → handler` 的本地映射。构建必须拒绝重复 method、schema 不合法、miniapp_sdk Tool +缺 handler 或 handler 类型与 schema 不一致;`delivery = runtime` 则必须匹配 Runtime 内置、受控注册的 execution。 + +下面是开发者实际维护的唯一一处 Tool 声明的形态(字段名称可以按 SDK 最终 API 微调,语义不得变化): + +```ts +export const agentTools = defineAgentTools({ + "todo.add": { + description: "向当前待办列表添加一项", + activation_requirement: "app_ready", + delivery: "miniapp_sdk", + input_schema: todoAddInputSchema, + output_schema: todoAddOutputSchema, + handler: async (input, context) => { + // 只有 MiniApp 理解 input 的业务含义,并使用 context.sessionData 更新自己的数据。 + return addTodoToSessionData(input, context.sessionData); + }, + }, +}); +``` + +构建从这一个声明同时得到两件不同的东西:Bundle 内 SDK 的 `method → handler` 映射,以及安装包 Manifest 的 Tool +描述。后者是 Runtime 的安装、校验与发布输入,而不是要求开发者另外同步维护的图纸;App 身份、版本、签名等安装包 +基础元数据仍按打包配置提供,但不重复定义 Tool。 + +静态校验规则: + +- 每一项 Agent Tool 都必须声明 `activation_requirement`;仅接受 `activation_not_required`、 + `foreground_required` 或 `app_ready`。 +- `activation_not_required` 表示 Tool 只操作已有 Runtime 事实,不能为此调用创建或唤醒 MiniApp; +- `app_ready` 表示 Runtime 已解析或建立目标 App 子会话,该 MiniApp 已 active / ready、可接收受限 Tool 调用; + 它不要求创建或显示 Surface。Runtime 不理解或处理此调用参数的业务含义,而是将方法与已验证参数投递给 MiniApp。 +- `foreground_required` 表示在满足 `app_ready` 的基础上,Host 还必须确认该 MiniApp 已成为当前前台 App 后,Tool + 才可进入业务执行;前台是该 Tool 的业务前提,而不是通用路由要求。 +- `delivery = "miniapp_sdk"` 的普通 Tool 一律投递到当前 ready App session 的统一 SDK Tool 接收入口;Runtime + 不读取或保存其私有函数 target。 +- `runtime_operation` 仅允许 `handling = "operation"`、`delivery = "runtime"`,且 `execution_owner` 必须为 + `runtime`。 +- Manifest 是声明,不授予 Transport、Tauri、系统通知或 Agent 权限。 + +### 3.1.1 Agent Tool 的 Registry 投影与内部执行路由 + +本规范中的 MiniApp Tool 都是向远端 Agent 开放的 Agent Tool 声明;它们不是用户 UI 的公共操作入口。每个 +已安装 MiniApp 必须在安装时由 Runtime 验证 Manifest,并把可路由的 Manifest 投影写入 App Registry。Runtime +不能依赖 MiniApp Surface 已启动、临时读取 Bundle 源码,或在收到调用后按 Tool 名字散落地猜测其行为。 + +现有 Manifest 的 tools 数组仍可沿用原字段名,但其中的每一项在本规范中都按 Agent Tool 处理;UI Action 与 +Host 生命周期意图不写入该数组,也不进入 Agent Inventory。 + +Registry 中每个已验证 MiniApp 至少保存以下事实: + +~~~ts +type RegisteredMiniAppRecord = Readonly<{ + app_scope: string; + installed_version: string; + enabled: boolean; + verified_manifest_digest: string; + agent_tools: readonly RegisteredAgentTool[]; +}>; + +type RuntimeToolExecutionRoute = Readonly<{ + owner: "runtime"; + handler_key: + | "pomodoro.start_deadline.v1" + | "pomodoro.interrupt_current.v1"; +}>; + +type RegisteredAgentTool = Readonly<{ + tool_key: { + app_scope: string; + method: string; + contract_version: string; + }; + handling: "direct" | "interactive" | "launch" | "foreground" | "operation"; + delivery: "miniapp_sdk" | "runtime"; + activation_requirement: ToolActivationRequirement; + input_schema: JsonObject; + output_schema: JsonObject; + visible_to_agent: boolean; + runtime_execution?: RuntimeToolExecutionRoute; +}>; +~~~ + +这里的 handler_key 只是 `delivery = "runtime"` 时 Runtime 内部的受控执行路由,不是发布给 Agent 的第二个 +Runtime Tool。它不得出现在 Agent Inventory、Agent Tool 参数或 MiniApp SDK context 中。对于 +`delivery = "miniapp_sdk"`,Runtime 一律投递到 SDK 固定的 Tool 接收入口,由 MiniApp Bundle 内部按 method +分发 handler;Manifest 不传递 JavaScript、URL、回调、私有函数名或其他可执行内容。 + +Runtime 只接受自己在 RuntimeOperationHandlerRegistry 中预注册、且与 app_scope、method、版本精确匹配的 +handler_key。未知 key、与 Tool 不匹配的 key,或未由当前安装版本验证过的声明,均以 manifest_denied 拒绝。 +第一版至少预注册两个映射: + +~~~text +pomodoro.start v1 + → pomodoro.start_deadline.v1 + +pomodoro.interrupt v1 + → pomodoro.interrupt_current.v1 +~~~ + +Runtime 启动、安装、启用、禁用、升级、卸载、Host 能力变化或策略变化时,必须按以下方式更新动态 Inventory: + +~~~text +读取 App Registry + → 只选择已安装、已启用、Manifest digest 与版本匹配的 MiniApp + → 读取每个 Record 的 agent_tools + → 与 Host、权限、策略、当前 Agent 和 conversation scope 求交 + → 重建 Runtime Tool Registry + → 生成新的 Inventory revision + → 在连接可用时同步给远端 Agent +~~~ + +因此,Pomodoro 尚未启动、没有前台 Surface 或 Surface 暂时卸载时,只要 App 仍已启用,Agent 仍可看到并调用 +pomodoro.start 和 pomodoro.interrupt。禁用或卸载流程必须先从 Tool Registry 和 Agent Inventory 移除新调用入口, +再按生命周期规则收口现有 instance 和 operation。 + +ToolRouter 只能读取 RegisteredAgentTool,而不能读取原始 Manifest 后自行推断。它必须返回显式 Route: + +~~~ts +type ToolRoute = + | { kind: "runtime"; tool: RegisteredAgentTool; execution: RuntimeToolExecutionRoute } + | { kind: "miniapp_sdk"; tool: RegisteredAgentTool }; +~~~ + +当 ToolRoute 为 runtime 时,LineUpRuntime 从 RuntimeOperationHandlerRegistry 取得已注册服务后执行。Pomodoro +的 start 调用 PomodoroOperationService.startAfterForegroundActivation;interrupt 调用 +PomodoroOperationService.interruptCurrent。两者都不得以 tool_id 的字符串特判散落在 Runtime 的多个分支中; +其中 start 必须等待 Host 的前台激活确认,interrupt 则不要求 Surface 存在。 + +当 ToolRoute 为 miniapp_sdk 时,Runtime 解析或建立已 ready 的 App session,向该 session 的统一 SDK Tool +入口投递 `{ call_id, method, params }` 及受限 context。SDK 根据构建时的 `defineAgentTools(...)` 本地映射分发 +handler;MiniApp 自己理解业务、写 session data,并通过 SDK 返回 receipt、progress、result 或 error。Runtime +只验证、持久化和回传这些受控数据,不调用 MiniApp 私有函数。 + +### 3.2 Pomodoro Manifest + +新增 `POMODORO_MANIFEST`,固定 `app_scope = "pomodoro"`、`kind = "bundled"`、`version = "1.0.0"`, +`host.surface_required = true`、`requested_capabilities = []`。Manifest 不声明 Pomodoro 的业务数据 schema; +它与其他 MiniApp 一样,只使用当前 App 子会话的通用 session data。 + +两项 Tool 的实现 Descriptor: + +| Tool | `handling` / delivery | Runtime operation | Host 行为 | +|---|---|---|---| +| `pomodoro.start` | `operation`;`delivery = runtime`;`activation_requirement = foreground_required`,`restore_previous_focus = true` | `{ kind: "deadline", duration_input: "duration_seconds", concurrency_scope: "conversation_and_app", execution_owner: "runtime" }` | 先进入 `starting` 并请求前台化;只有 Host 确认已成为当前前台后,才创建 deadline operation。 | +| `pomodoro.interrupt` | `direct`;`delivery = runtime`;`activation_requirement = activation_not_required`,`restore_previous_focus = false` | 不声明 deadline;由 `PomodoroOperationService.interruptCurrent` 处理。 | 不挂载、不唤醒、不等待 Surface;已有 starting 时取消启动,已有 focusing 时中断 operation。 | + +两个 Descriptor 都必须写入 3.1.1 定义的 runtime_execution:pomodoro.start 固定使用 +pomodoro.start_deadline.v1,pomodoro.interrupt 固定使用 pomodoro.interrupt_current.v1。该投影只保存于 +Registry 和 Runtime Tool Registry;面向 Agent 发布的 Inventory 保留 Tool 名称、业务说明、schema、调用模型、 +可见性和版本,不暴露内部 handler_key。 + +输入和最终业务结果 schema 直接使用主定义第 3 节的 JSON Schema。协议回执 / 进度不是业务 result,不能拿 +activation 或 operation 对象去冒充 `pomodoro.start` 的 result。路由层拒绝使用现有通用错误码: +`invalid_request`、`inventory_revision_mismatch`、`scope_mismatch`、`not_installed`、`disabled`、 +`manifest_denied`、`permission_denied`。通过路由后,Pomodoro 业务稳定回执为: + +```text +pomodoro.start + active_focusing_operation + focus_start_in_progress + focus_start_failed + focus_start_cancelled + +pomodoro.interrupt + interrupted + focus_start_cancelled + no_active_focusing_operation + operation_already_final +``` + +`source_tool_call_id` 是每个 Tool 的幂等键。相同 `call_id` 的重放必须返回先前持久化的**最新协议回执、进度或最终 +业务结果**,不得再次创建 instance、子会话、deadline、焦点记录、Tool result 或 outbox。 + +## 4. 持久化模型与原子提交 + +### 4.1 新增记录 + +在 `src/runtime/coordination/pomodoro-operation-state.ts` 定义纯状态机和数据类型;在 +`src/runtime/coordination/pomodoro-operation-service.ts` 定义编排服务。服务不得依赖 DOM、`window` 或 Tauri。 + +```ts +type PomodoroOperationState = "focusing" | "completed" | "interrupted"; + +type PomodoroOperationRecord = Readonly<{ + operation_id: string; + source_tool_call_id: string; + conversation_id: string; + app_scope: "pomodoro"; + instance_id: string; + app_session_id: string; + activity?: string; + duration_seconds: number; + started_at: string; + ends_at: string; + state: PomodoroOperationState; + ended_at?: string; + interruption_reason?: "agent_interrupt" | "user_exit" | "workspace_switch" | "app_closed"; + result_outbox_id?: string; + close_committed_at?: string; +}>; + +type MiniAppActivationState = "starting" | "ready" | "failed" | "cancelled"; + +type MiniAppActivationRecord = Readonly<{ + app_scope: string; + instance_id: string; + app_session_id: string; + conversation_id: string; + source_tool_call_id: string; + activation_requirement: ToolActivationRequirement; + state: MiniAppActivationState; + created_at: string; + ready_at?: string; + terminal_reason?: "surface_activation_failed" | "cancelled_before_ready"; + attempt_count: number; + // 通过 Router 校验、但必须等待本 activation ready 后才能投递的 call_id,按持久化到达顺序保存。 + pending_tool_call_ids: readonly string[]; +}>; + +type MiniAppSessionDataRecord = Readonly<{ + app_scope: string; + app_session_id: string; + revision: number; + data: JsonObject | null; + updated_at: string; +}>; + +type ToolProtocolReceipt = Readonly<{ + call_id: string; + // activation_* 是通用 App 启动进度;started 是像 Pomodoro 一样更强的业务开始进度。 + phase: + | "accepted" + | "activation_ready" + | "activation_failed" + | "activation_cancelled" + | "started"; + activation: Readonly<{ + app_scope: string; + state: MiniAppActivationState; + }>; + reason?: "surface_activation_failed" | "cancelled_before_ready"; + operation_id?: string; + started_at?: string; + ends_at?: string; +}>; + +type PersistedAgentToolCall = Readonly<{ + call_id: string; + tool_key: { app_scope: string; method: string; contract_version: string }; + // 最后一个可重放的过程事实;终态存在时 final_result 优先于它返回。 + latest_receipt?: ToolProtocolReceipt; + final_result?: JsonObject; +}>; +``` + +`ConversationStore` 从当前版本 14 升级到 **15**。新增 `pomodoro_operations`、`miniapp_activations` 与 +`miniapp_session_data`,并实现以下最小 API: + +```ts +replacePomodoroOperations(records: readonly PomodoroOperationRecord[]): void; +replaceMiniAppActivations(records: readonly MiniAppActivationRecord[]): void; +replaceMiniAppSessionData(records: readonly MiniAppSessionDataRecord[]): void; +runtimeWorkspaceSnapshot(): { + operations: readonly PomodoroOperationRecord[]; + activations: readonly MiniAppActivationRecord[]; + sessionData: readonly MiniAppSessionDataRecord[]; +}; +``` + +迁移要求:v1~v14 的既有数据保持现有兼容路径;缺失的新数组按空数组恢复。不得因旧记录无法拥有 Pomodoro +字段而丢弃既有 IM、outbox、Tool、workspace 或 App 子会话数据。 + +### 4.2 必须保持的唯一性 + +所有比较均在同一 `conversation_id` 内进行: + +| 约束 | 行为 | +|---|---| +| `source_tool_call_id` 唯一 | 相同调用重放先返回已持久化的最终业务结果;尚未终态时返回最后一个协议回执 / 进度。 | +| `(conversation_id, app_scope = pomodoro, state = starting \| ready)` 启动槽位唯一 | 第二个不同 start 返回 `focus_start_in_progress`;不会创建第二个启动中的 instance。 | +| `(conversation_id, app_scope = pomodoro, state = focusing)` operation 唯一 | 第二个不同 start 返回 `active_focusing_operation`,不产生任何资源。 | +| `(app_scope, app_session_id)` session data 唯一 | 只能以正确 `expected_revision` 整体替换;revision 每次成功写入加一。 | +| `result_outbox_id` 唯一 | 终态首次成功时创建;后续 deadline / interrupt / close 不能追加第二条长期 Tool result。 | + +### 4.3 原子提交接口 + +由 LineUpRuntime 拥有唯一的 RuntimeOperationCommitCoordinator。PomodoroOperationService、deadline scheduler、 +Tool Router 和 Lifecycle 事件只能向该协调器提交业务变更请求;它们不能各自直接写 ConversationStore、改变 +AppLifecycleManager、发送 outbox 或通知 Surface。 + +协调器在内存副本中完成校验、幂等和 compare-and-set,并以一次 ConversationStore.persist 写入完整的下一份 +Runtime 事实。一次提交至少同时包含 operation、activation、MiniApp session data、App 子会话 / workspace 的目标状态、Tool +调用记录或 receipt,以及需要回传的 outbox。若任一校验或持久化失败,持久化事实、Lifecycle 内存状态、Host 和 +Surface 均不得改变。 + +提交成功后,协调器按固定顺序执行派生动作: + +~~~text +1. ConversationStore.persist(next_runtime_snapshot) 成功 +2. AppLifecycleManager 以已保存 workspace 对账;它只更新内存投影,不再次持久化业务事实 +3. Runtime 发出 lifecycle / state / workspace 投影 +4. Host 挂载、卸载、前后台切换等副作用执行 +5. outbox 网络发送异步重试;网络成功与否不改变已提交 operation +~~~ + +AppLifecycleManager 不再拥有与 Store 平行的业务事实。它必须支持从已提交 workspace 重建或对账自己的 instance / +focus 内存投影。若提交成功后对账失败,Runtime 不得回滚 operation、session data、Tool receipt 或 outbox;它必须停止 +向 Surface 发新投影,标记需要重新对账,并立即或在下次恢复时从已保存 workspace 重建 Lifecycle。Host / Surface +副作用失败同样不得回滚已提交 operation,后续按第 5.2 节的挂载重试和最终失败收口规则处理。 + +以下操作必须各自在一次事务内完成: + +1. foreground-required start 的第一阶段:检查幂等与启动 / active slot → 创建 instance / 子会话 / `starting` + activation / workspace → 写 `accepted` protocol receipt 及其唯一 outbox → 请求 Host 前台化;此阶段不得创建 + Pomodoro operation 或倒计时。 +2. Host 确认前台激活:以同一事务将 activation 置为 `ready`,创建 operation / `focusing`、写 workspace、更新为 + `started` protocol receipt 及其唯一 progress outbox,并投递通用 lifecycle 事件;`started_at` 与 `ends_at` 以该 + 确认时刻计算。 +3. interrupt:若存在 starting activation,写 `cancelled`、短 Tool receipt 和启动取消结果,关闭子会话;若存在 + focusing operation,写 `interrupted`、长期 Tool result、短 Tool receipt、outbox → 标记待关闭。 +4. deadline:定位 operation → 写 `completed`、长期 Tool result、outbox → 标记待关闭。 +5. lifecycle close:若 activation 仍 `starting`,取消启动;若 operation 尚在 `focusing`,写 `interrupted` 和唯一 + result;若已终态,只执行尚未完成的 close。 + +当 interrupt 找到 focusing operation 时,短 Tool receipt、长期 start 的终态 result、outbox 与 +workspace 的待关闭目标必须属于同一次提交,并引用同一 operation。没有 focusing operation 的短 Tool receipt +不改变业务 operation,但仍必须按 call_id 持久化以支持幂等重放。Host 的前台激活确认只能报告前台事实;由 +Runtime 根据该事实在同一提交中写 operation、`started` protocol receipt 和 outbox,Host 本身不能直接写这些记录。 + +## 5. 状态机、路由和收口顺序 + +### 5.1 Runtime deadline operation 状态机 + +```text +start accepted + → focusing + ├── now >= ends_at → completed + ├── Agent pomodoro.interrupt → interrupted(agent_interrupt) + ├── 返回 Interact → interrupted(user_exit) + ├── 切换其他 MiniApp → interrupted(workspace_switch) + └── AppLifecycle close → interrupted(app_closed) + +completed / interrupted + → 仅允许幂等读取、outbox 重试和一次 close 收口 + → 不可返回 focusing +``` + +在同一事务内,deadline、Agent interrupt、lifecycle close 使用 `operation_id + state = focusing` 的 +compare-and-set。CAS 失败时读取既有终态:不改写 record、不新增长期 result/outbox;Agent Tool 返回对应的稳定 +receipt,lifecycle 继续执行安全 close。 + +### 5.2 通用 MiniApp 启动与 `pomodoro.start` 顺序 + +每个 MiniApp instance / App 子会话的启动都进入 Runtime 管理的 activation 状态;一个 Tool 是否需要新建或 +等待 activation,则由 Descriptor 的 `activation_requirement` 决定,而不是由某个 MiniApp 的业务名称决定。 +Runtime 在这一层只做“找到 App、方法和已验证参数,并确认目标 App session 已 ready”的路由工作;它不解释参数 +字段的业务含义,也不直接修改 MiniApp session data: + +```text +Tool 调用已通过 Router 校验 + ├── activation_not_required + │ → 不创建或唤醒 MiniApp,直接在已有 Runtime 事实范围内执行 + ├── foreground_required + │ → 创建 app_session / instance / activation(starting) + │ → Host 挂载并确认该 instance 已成为当前前台 + │ → activation(ready) → 执行 Tool 的业务效果 + └── app_ready + → 创建 app_session / instance / activation(starting) + → MiniApp 报告自身可接收 Tool 的 ready,Runtime 取得其当前 app_session + → Runtime 将 method + 已验证参数投递给 MiniApp + → MiniApp 自己执行业务效果、写 session data 并返回结果;无需显示 Surface +``` + +#### 5.2.1 activation 进度通知与等待队列 + +一次 activation 的启动、就绪、失败和取消都必须由 Runtime 在持久化后,以与原始 `call_id` 关联的 Tool protocol +receipt / progress 通知该 Agent:`accepted` 表示 `starting` 已提交,`activation_ready` 表示目标 App session 已可接收 +`app_ready` Tool,`activation_failed` / `activation_cancelled` 表示启动已经不能继续。公开 payload 只能包含 call_id、 +app_scope、状态与受控 reason;`instance_id` 与 `app_session_id` 仅保留在 Runtime 内部记录,不得作为 Agent 可指定的 +路由参数或公开的 activation 控制柄。 + +收到合法 Tool 后,Router 不要求 Agent 先收到 `activation_ready` 才能提交下一条调用。若目标 conversation / app scope +存在 `starting` activation,且调用要求 `app_ready` 或 `foreground_required`,Runtime 必须在同一提交中持久化 Tool Call +record,将它的 call_id 追加到该 activation 的 `pending_tool_call_ids`,并回传 accepted receipt。activation 进入 ready 后, +Runtime 按数组中的持久化顺序向统一 SDK 接收入口投递这些调用;不得因重新挂载、恢复或 outbox 重试改变顺序或重复投递。 +若 activation 失败或取消,Runtime 为每个尚未投递的调用持久化且回传 `app_activation_failed` / `app_activation_cancelled`, +不调用 handler。`activation_not_required` 不得进入该队列,仍立即由 Runtime 在已有事实范围内处理。 + +对 `pomodoro.start`,前台确认的同一提交既使 activation ready,又创建 deadline operation;因此只发送 `started` +progress,不额外发送语义重复的 `activation_ready`。若启动失败或取消,`pomodoro.start` 发送其唯一的 +`not_started(focus_start_failed | focus_start_cancelled)` business result;不再附加重复的 activation 终态通知。 + +这使未来的 `todo.add` 可以在 To-do 已 active / ready、但页面未前台显示时处理自己的 session data 并回报结果;只有像 +`pomodoro.start` 一样把“用户已进入前台专注模式”作为业务前提的 Tool,才使用 `foreground_required`; +`pomodoro.interrupt` 则为 `activation_not_required`,以保证它永不因自身调用而唤醒 Pomodoro。 +启动状态属于 Runtime workspace / lifecycle 事实,不写入 MiniApp 的业务 JSON 数据,也不等于 Pomodoro operation。 + +Pomodoro 的具体顺序如下: + +```text +Agent envelope + → ToolRouter:Envelope / conversation / Inventory / Manifest / input schema 校验 + → Runtime:按 call_id 查重 + → Runtime:检查同会话 focusing / starting slot + ├─ 已有 focusing:持久化稳定拒绝 active_focusing_operation,结束 + ├─ 已有 starting:持久化稳定拒绝 focus_start_in_progress,结束 + └─ 无:一次事务创建 instance、app_session、activation(starting)、workspace、accepted receipt/outbox + → AppOrchestrator:请求前台化 Pomodoro + → Host:确认 Surface 已挂载且该 instance 已成为当前前台 + → Runtime:一次事务 activation(ready) + 创建 deadline operation/focusing + started receipt/progress outbox + → Runtime:投递通用 app_session.ready lifecycle 事件;Pomodoro 自行把需要的展示数据写入 session data +``` + +Host 的确认不能只是 iframe / 页面对象已创建,而必须表示该 instance 已是用户当前看到的前台 App。在此确认前, +Pomodoro 没有 `operation_id`、没有 `focusing`、没有 `ends_at`,也不产生专注的 IM 静态记录。 + +Host 可以按 Runtime 配置进行有限重试;每次尝试和最终失败都只改变 activation 事实。重启或 Host 重连发现 +`starting` activation 时,继续同一 `instance_id` 的激活尝试,不能新建第二个专注。当 Host 最终报告 +`surface_activation_failed`,Runtime 原子写 `failed`、持久化 start 的唯一 `not_started(focus_start_failed)` business +result / Agent result outbox、关闭 App 子会话并恢复 Interact;不创建 `interrupted` operation 或专注结果记录。 + +### 5.3 `pomodoro.interrupt` 顺序 + +```text +Agent envelope + → ToolRouter 完成通用校验 + → Runtime 仅用 envelope.conversation_id 查找当前 starting / focusing Pomodoro + → 有 starting:原子写 cancelled(cancelled_before_ready) + 长期 start 的 not_started result/outbox + interrupt 自己的短 result,关闭子会话,返回 focus_start_cancelled + → 无 starting 且无 focusing:返回 no_active_focusing_operation + → 有 focusing:原子写 interrupted(agent_interrupt) + 长 Tool result/outbox + 短 Tool receipt + → Runtime 关闭实例、结束子会话、恢复 Interact + → 若 Surface 仍存在,只投影终态后卸载;不等待其回执 +``` + +Agent 不可提供 instance、operation、app scope 或其他目标 ID。`operation_already_final` 只用于已经绑定到同一 +operation 的迟到/重放调用;找不到当前 `starting` 或 `focusing` 对象时返回 `no_active_focusing_operation`。 + +### 5.4 deadline、恢复和关闭 + +新增 `DeadlineOperationScheduler`,由 `LineUpRuntime` 创建并只接收注入的 `now()` 与调度器适配器,便于测试。 + +- Runtime 打开会话、恢复 workspace、Host 重新可用和每次 scheduler tick 时,扫描当前会话所有 `focusing` + operation;`now >= ends_at` 则调用同一终态 CAS。 +- UI 的 `setInterval` 只用于倒计时重绘;不得触发 completion。 +- 自动暗屏、锁屏、iframe Surface 重载只重挂载或重绘,不调用 interrupt。 +- 返回 Interact、切换其他 App、显式 close:若 activation 仍 `starting`,调用 + `PomodoroOperationService.cancelStartForLifecycle(reason)`;若已 `focusing`,调用 + `PomodoroOperationService.interruptForLifecycle(reason)`;不得从 Host 直接删除 instance。 +- completed 的收口顺序固定为:终态与长期 Tool result/outbox → close instance → 结束子会话 → 恢复 Interact + → Interact 显示由 Runtime 投影的完成结果。outbox 的网络投递可以稍后重试,不阻塞 close。 +- 进程完全未运行期间不承诺提醒;下一次 Runtime 恢复只按 `ends_at` 结算一次。 + +## 6. MiniApp SDK、Surface 与工作区 + +### 6.1 通用 MiniApp session data API + +实现 `sdk.sessionData`。Runtime 必须从不可伪造的 SDK context 推导 `app_scope + app_session_id`;MiniApp 不传入 +目标 scope / session,也不能读取其他 App、其他会话、Runtime operation、outbox 或 Conversation Store。 + +```ts +interface MiniAppSessionDataAPI { + 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; +} +``` + +Bridge 使用同名的稳定方法 `session_data.get`、`session_data.replace`,以及 Runtime → Surface 事件 +`session_data.changed`。`get` 无参数,返回 `{ data, revision }`;`replace` 仅接受 `{ data, expected_revision }`, +成功返回 `{ revision }`;变化事件携带新的 `{ data, revision }`。Web 与 Tauri 必须复用完全相同的请求、响应与事件 +golden fixture。 + +同步语义按每个 `app_scope + app_session_id` 串行冻结,不能由 Web / Tauri 各自决定: + +1. 从未保存过数据时,`get()` 与订阅首帧均返回 `{ data: null, revision: 0 }`。 +2. `subscribe(listener)` 在该子会话的串行执行器中先注册 listener、捕获当前完整 snapshot,并将该 snapshot 作为 + 本订阅的首次 `session_data.changed` 投递;注册成功前不得让订阅者错过已提交的 replace。 +3. 每次成功 `replace` 先原子持久化 revision 加一后的完整 data,再向当时已注册的订阅者投递该完整 snapshot。对 + 同一个订阅,首次 snapshot 与后续事件必须按 revision 严格递增:竞争中的 replace 要么成为首次 snapshot 的版本, + 要么成为紧随其后的 change,不能消失或倒序。 +4. SDK 收到重复或更旧 revision 时忽略;收到比本地最后 revision 大于 1 的 snapshot,或检测到 Bridge 重连时,必须 + 取消旧订阅,重新 `get()`,再建立新订阅。重拉后的完整 snapshot 是新的基线。 +5. `expected_revision` 不匹配只返回 `session_data_revision_conflict`;第 04 次不对 JSON 做重试、合并或协作写入 + 队列。本项由 TIS2-06 作为 P4 延续项处理。 + +因此下面这个竞态必须得到确定结果:Surface 已经得到 revision 3,另一条合法写入提交 revision 4,Surface 随后 +subscribe 时,其首帧必须为 revision 4;反过来若订阅先注册,则它必须先收到 revision 3、随后收到 revision 4。 + +`replace` 的固定验证顺序:SDK context 有效且 App 子会话仍可用 → data 是 JSON object → UTF-8 字节长度和安全 +解析深度未超过 Runtime 通用配额 → expected revision 相等。稳定错误码为: + +```text +session_data_unavailable +session_data_invalid +session_data_quota_exceeded +session_data_revision_conflict +``` + +Runtime 不校验 MiniApp 的业务 JSON Schema,不识别 `current`、`history` 或嵌套结构,也不直接写 Pomodoro 的 +私有数据字段。Pomodoro 接收已验证的 Tool invocation 输入和通用 `app_session.ready` lifecycle 事件后,自行写入 +展示所需数据;`ready_at` 是 Runtime 的激活确认时间,Pomodoro 可据此从 `duration_seconds` 派生界面倒计时。 +Runtime 自己保存的 `ends_at` 和终态仍只由 Runtime operation 管理。 + +### 6.2 Pomodoro Surface + +新增 `src/core-apps/pomodoro/pomodoro-miniapp.ts` 及对应测试。Surface 输入只应来自 SDK lifecycle、已验证的 +Tool invocation 输入与自身 session data;它不读取 Runtime operation 投影。它显示:活动名称(可缺省)、 +由 `ready_at + duration_seconds - now` 派生的剩余时间和自身展示状态;没有暂停、 +继续、开始、停止、系统通知或直接 Agent 通信入口。 + +当收到 lifecycle `closing` 时,Surface 只停止渲染计时并等待 Runtime 卸载。完成提示属于 Interact,不在 +Pomodoro 内显示。Surface 被重载时从 SDK 重新读取 session data;不得从本地 `setTimeout` 推断 operation 终态。 + +### 6.3 Host 工作区 + +`main.ts` 的 bundled App 对账逻辑加入 `pomodoro`,但应尽量抽出 Host adapter / workspace renderer,避免继续扩大 +入口文件。Host 只根据 Runtime `app-lifecycle` / workspace 事件挂载、更新和卸载隔离 Surface: + +- 收到 `foreground_required` activation 后,挂载 Pomodoro;仅在实际成为当前前台 App 时,向 Runtime 发出 + `foreground_activated(instance_id)` 确认。iframe 创建、资源加载或后台预载都不能替代此确认; +- Runtime 进入 `focusing` 或投影 lifecycle `closing` 后:显示或卸载 Pomodoro,并恢复 Interact; +- 点击/键盘返回 Interact、选择其他 App、关闭当前 App:向 Runtime 发出 host lifecycle intent,由 Runtime 决定 + cancel-start 或 interrupt;Host 不能自行改持久化状态; +- Web Reference Host 与 Tauri Desktop Host 使用同一 Runtime、同一 Manifest、同一 Surface Bridge,不增加 + Tauri 专用业务旁路。 + +## 7. 日志、错误与可观测性 + +每个状态变化写结构化 Runtime 日志。日志可记录 `conversation_id`、`operation_id`、`instance_id`、`call_id`、 +旧/新 state、原因、是否首次 CAS 成功、outbox local id;不得记录完整 IM 内容、活动文本或 Agent 推理。 + +必须可从日志区分:`miniapp_activation_started`、`miniapp_activation_ready`、`miniapp_activation_failed`、 +`miniapp_activation_cancelled`、`pomodoro_started`、`pomodoro_start_rejected`、`pomodoro_deadline_won`、 +`pomodoro_interrupt_won`、`pomodoro_lifecycle_won`、`pomodoro_terminal_lost_race`、`pomodoro_close_committed`、 +`pomodoro_recovered_due`、`miniapp_session_data_rejected`。 + +## 8. 测试与验收矩阵 + +新增或扩展测试必须以 fake clock、内存 Storage、可控 outbox / scheduler / Surface adapter 执行;禁止依赖真实 +等待 20 分钟。建议新增: + +```text +src/runtime/coordination/pomodoro-operation-state.test.ts +src/runtime/coordination/pomodoro-operation-service.test.ts +src/runtime/coordination/deadline-operation-scheduler.test.ts +src/runtime/persistence/conversation-store.pomodoro.test.ts +src/core-apps/pomodoro/pomodoro-miniapp.test.ts +``` + +| 场景 | 必须断言 | +|---|---| +| 合法 foreground-required start | 先创建一个 `starting` instance / app_session,前台激活确认后才创建 operation / focusing / ends_at;确认前没有 result outbox 或专注 IM 记录。 | +| 前台激活最终失败 | `failed(surface_activation_failed)`,恢复 Interact;Agent 得到 `focus_start_failed`;没有 operation、deadline、专注 outbox 或专注静态记录。 | +| 启动中 interrupt / 生命周期离开 | `cancelled_before_ready`,返回 `focus_start_cancelled`;没有 operation、deadline 或专注中断记录。 | +| 相同 start call 重放 | 返回原 activation / operation;没有第二个 instance、子会话、deadline 或 outbox。 | +| Runtime 启动后的 Tool 重建 | Pomodoro 未启动时,已启用 Registry Record 的两个 Agent Tool 均进入可见 Inventory;Inventory 不含内部 handler_key。 | +| 禁用、升级或卸载 Pomodoro | 先撤销旧 Inventory revision 中的两个 Tool;未知或旧版本的 route 被拒绝,不能由旧 Surface 继续承接新调用。 | +| Runtime-owned Tool 路由 | start 和 interrupt 都由 Registry 的显式 runtime_execution 路由到 Runtime 服务;interrupt 在 Surface 缺席时仍可完成,不存在按 Tool 字符串散落特判。 | +| operation 提交持久化失败 | operation、activation、session data、workspace、Tool record、outbox、Lifecycle 内存和 Surface 投影均保持提交前状态;不得发出成功或终态事件。 | +| operation 提交后 Lifecycle 对账失败 | 已提交 operation / receipt / outbox 不回滚、不重复;Runtime 停止新的 Surface 投影并从已保存 workspace 重新对账,Host 最终挂载结果按 TIS-05 处理。 | +| 不同 start 且已有 focusing / starting | 分别为 `active_focusing_operation` / `focus_start_in_progress`;所有持久化集合长度不变。 | +| 非法 schema、过期 Inventory、scope 错误 | 在 Router 拒绝;不创建 operation 或挂载 Surface。 | +| Agent interrupt | starting 时取消启动;focusing 时写长 Tool 一次 interrupted result、短 Tool 一次 receipt、一次 close;后者在 Surface 缺席时同样成立。 | +| deadline / interrupt / lifecycle 并发 | 恰好一个 terminal winner、一个长期 Tool result/outbox;其余为稳定回执。 | +| 自动暗屏、锁屏、Surface reload | operation 仍 focusing,`ends_at` 不变,无 outbox。 | +| 返回 Interact / 切换 App / close | 以相应 reason interrupted;恢复 Interact;不得产生 `cancelled(app_closed)` 覆盖。 | +| completed | 先提交 result/outbox,后 close / 子会话只读 / Interact 显示结果;Pomodoro 不显示完成页。 | +| 刷新或重启,未到点 | 以原 `ends_at` 恢复;剩余时间不重置。 | +| 刷新或重启,已到点 | 只结算一次 completed;重复恢复不新增 outbox。 | +| session data | 当前 App / 子会话隔离、JSON / 通用配额、revision CAS、持久化和恢复均被校验;不存在数据固定为 revision 0;每次订阅先收到当前完整快照,之后严格递增,重复 / 旧 revision 忽略,跳跃 / Bridge 重连重拉并重订阅。必须覆盖 `get(revision 3) → replace(revision 4) → subscribe(首帧 revision 4)` 与反向注册顺序。Runtime 不校验 `current` / `history` 等业务字段,其他 scope 不可读写。 | +| app-ready 通用路由回归 | 以测试 MiniApp 验证:页面不在前台时,只要目标 App session 已 active / ready,Runtime 就能将已验证的 method + 参数投递给它;MiniApp 自己写 session data 并回传结果。它证明未来 `todo.add` 不会被 Pomodoro 的前台规则限制。 | +| activation Agent 通知与提前调用 | 启动调用按 call_id 收到 `accepted` 与一次 `activation_ready`(失败 / 取消时收到对应受控终态);同 conversation / app scope 的 `app_ready` 调用若提前到达,先持久化排队,ready 后按到达顺序仅投递一次;失败 / 取消时不执行 handler 而各自回传稳定错误。Pomodoro 只收 `started`,不重复收 `activation_ready`;启动中 interrupt 立即处理。 | +| pending Interact | Pomodoro close 后子会话只读;问题仍在主 IM 可回答,回答不写旧子会话。 | +| Host 验收 | Web 完整 IM 流程;Tauri 至少跑 start → interrupt 或 start → deadline 的代表路径。 | + +所有现有 `npm test -- --run`、`npm run build`、前 03 次迭代的 Tool / Surface / App 子会话回归与 +`git diff --check` 必须通过。 + +## 9. 推荐实施顺序与完成门槛 + +1. 先扩展 Manifest、Tool Descriptor、golden fixture、Inventory 和 schema 测试;未通过前不写 UI。 +2. 实现 ConversationStore v15、activation repository、session data repository、Pomodoro operation 状态机和所有原子竞争单元测试。 +3. 实现通用 activation coordinator、`PomodoroOperationService` 与 `DeadlineOperationScheduler`,接入 ToolRouter / LineUpRuntime / outbox / + AppLifecycleManager;完成恢复与并发测试。 +4. 增加 Pomodoro Manifest、受限 Surface、SDK session data Bridge 与 Host 工作区对账及 foreground activation 确认;不在 `main.ts` 放业务逻辑。 +5. 接入 Interact 的完成结果投影、折叠子会话、pending interaction 回归。 +6. 完成 Web Reference Host 与 Tauri Desktop Host 验收,保存可复现的测试步骤和日志证据。 + +进入下一项前的门槛:前一项的新增测试、全部既有测试、构建和格式检查均通过。实施完成前,不得以 +真实 Audio Mode、系统通知、多个 Pomodoro、Whiteboard 工作区、Rust 迁移或应用市场功能替代本规范中的任一 +验收场景。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/06.technical_implementation_spec_review.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/06.technical_implementation_spec_review.md new file mode 100644 index 0000000..6ba460b --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/06.technical_implementation_spec_review.md @@ -0,0 +1,210 @@ +# 04.runtime_workspace 技术实施规范评审(一) + +**评审编号:** 01 +**日期:** 2026-08-06 +**评审对象:** [05.technical_implementation_spec.md](05.technical_implementation_spec.md)(以下简称“实施规范”)、[04.runtime_workspace.md](04.runtime_workspace.md)(产品/验收权威)、[03.design_review.md](03.design_review.md)、[03.sdk_and_coreapp.md](../03.sdk_and_coreapp/03.sdk_and_coreapp.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md),以及当前 `src/runtime/` 的 Manifest、Registry、Tool Router、Runtime、Store、SDK Bridge 与 Lifecycle 实现。 +**后续决议依据:** [运行时与智能体工具.md](../../设计/02.正式方案/运行时与智能体工具.md) +**评审方法:** 从程序员实际会触及的入口向内追踪:Manifest → Registry 投影 → Inventory / Tool Router → Runtime 持久化与终态 → SDK / Surface Bridge → Host 与恢复;只记录会让两个开发者产生不兼容实现、或无法证明本轮验收的缺口。 +**总体结论:** 实施规范正确继承了已冻结的产品边界:Agent 是业务入口,Runtime 是唯一终态裁决者,Surface 只是投影,完成后立即回到 Interact,完成记录是不可变的 IM 静态快照。随后已确认五项关键决议:已启用 MiniApp 的 Agent Tool 从已验证 Registry 投影主动重建为 Runtime Tool Registry / Agent Inventory;无目标 interrupt 的新调用在没有 focusing operation 时稳定返回 no_active_focusing_operation;RuntimeOperationCommitCoordinator 先原子持久化业务事实,再驱动 Lifecycle、Host 和 Surface 对账;Runtime 不向 Pomodoro 或其他 MiniApp 暴露专属 operation / instance-state API,而是向当前 MiniApp 会话提供通用、隔离、版本化的业务数据字典;所有 MiniApp 都经历统一的 `starting → ready | failed | cancelled` 启动过程,而每个 Tool 决定自己只需 App ready、还要求前台,或根本不需要 activation。Runtime 只路由已验证的 App 方法和参数,不理解其业务含义或直接写 MiniApp 数据。**本轮 TIS-01~TIS-05 已全部解决。** 后续实现必须按已定契约补齐 SDK / Bridge、Host 激活确认和验收 fixture,不得把实施细节重新解释为产品分歧。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施规范;✅ 已确认通过;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但须记录原因与重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P1 | TIS-01 | 已启用 MiniApp 如何将其 Agent Tool 声明注册给 Runtime,并由 Runtime 路由。 | 已确认并回填:安装时保存已验证 Manifest 的 Registry 投影;Runtime 启动和状态变化时主动重建 Tool Registry / Agent Inventory;Router 按受控 runtime_execution 路由,不以 Tool ID 散落特判。 | +| ✅ | P1 | TIS-02 | operation、snapshot、workspace、Tool receipt / outbox 与 Lifecycle 如何避免分裂。 | 已确认并回填:LineUpRuntime 拥有 RuntimeOperationCommitCoordinator;先一次持久化事实,再由 Lifecycle / Host / Surface 对账。持久化失败不改变任何层;提交后的对账或 Host 失败不回滚事实。 | +| ✅ | P1 | TIS-03 | MiniApp 如何经 SDK 读取和保存其业务数据。 | **已确认并回填评审结论:** Runtime 仅向当前 `app_scope + app_session_id` 提供通用、持久化、版本化的 JSON 数据字典;MiniApp 自行定义 `current`、`history` 等业务结构及其可变性。Runtime 不公开 Pomodoro operation 或专属 instance-state API。SDK / Bridge 的接口、错误码和 Web / Tauri fixture 必须按此已定方案写入实施规范。 | +| ✅ | P1 | TIS-04 | 无目标 interrupt 的新调用如何定位已结束 operation。 | 已确认并回填:同一 call_id 重放返回首次持久化 receipt;不同新调用在没有 focusing operation 时一律返回 no_active_focusing_operation,不自动绑定最近终态。 | +| ✅ | P2 | TIS-05 | 要求前台的 Tool 在 Surface 未成功显示时,是否已经开始业务 operation。 | **已确认并回填评审结论:** Runtime 为所有 MiniApp 管理 `starting → ready | failed | cancelled` activation;Tool 声明 `app_ready`、`foreground_required` 或 `activation_not_required`。Runtime 只把已验证的 method 与参数路由至 ready MiniApp,不理解其业务或直接写其数据。`pomodoro.start` 必须等 Host 确认已成为当前前台 App 才创建 focusing operation / deadline;最终失败或启动中取消均为“本次计时未开始”,不产生 interrupted operation 或专注 IM 静态记录。未来 `todo.add` 等 Tool 只需 To-do 已 active / ready、无需前台;`pomodoro.interrupt` 不会为自身调用启动或唤醒 App。 | +| ✅ | — | TIS-C1 | Agent、Runtime、Surface 的权限边界 | 规范禁止 Surface 完成 Pomodoro Tool 或直接写 Agent 协议,并保留 Runtime 唯一终态/Outbox 所有权;与正式 SDK 架构一致。 | +| ✅ | — | TIS-C2 | deadline、退出、完成与恢复的产品语义 | 规范正确使用 `ends_at`,区分暗屏/Surface reload 与离开工作区,并定义 completed 后立即 close → Interact。 | +| ✅ | — | TIS-C3 | 单会话专注、MiniApp 业务数据与 IM 静态记录的边界 | 规范正确要求一个 focusing operation、重复 start 稳定拒绝;Runtime 只管理通用数据容器的 revision、配额与隔离,MiniApp 自行决定业务数据结构与可变性;完成记录作为 IM 静态快照保留。 | +| ✅ | — | TIS-C4 | 范围与验证策略 | 当前以 IM 验收,Voice、系统通知、多个计时器、Whiteboard 工作区和 Rust 迁移未被重新纳入;fake clock 和 Web/Tauri 验收方向正确。 | + +## 通过项与一致性证据 + +### TIS-C1:权限边界没有倒退 + +实施规范第 1、3、5、6 节将 Agent Tool 的输入校验、deadline、operation 终态、outbox、焦点与 close 交给 +Runtime;Pomodoro Surface 只显示投影、保存展示快照,不能调用 `sdk.tools.complete/fail/cancel` 来完成 Pomodoro +Tool。这符合正式 SDK 架构中“instance snapshot 不能完成 Tool、写 outbox、改变焦点或绕过 lifecycle”的规则, +也避免把可信业务逻辑重新塞入 `main.ts` 或 iframe。 + +### TIS-C2:计时和完成收口与主定义一致 + +规范使用绝对 `ends_at`,并要求 Runtime 恢复时结算已到期 operation;UI 定时器只重绘。这与主定义的 +`remaining_seconds = ends_at - now` 一致。deadline、Agent interrupt 与 lifecycle close 竞争同一终态,completed +后依次提交结果/outbox、关闭 Pomodoro、结束子会话、恢复 Interact,亦没有重新引入 App 内完成页或系统通知。 + +### TIS-C3:Runtime 事实、MiniApp 业务数据与 IM 静态记录各自归位 + +Runtime 内部的 operation 保存 deadline、终态竞争、Tool receipt、outbox 和恢复所需的可靠性事实;它不是 +Pomodoro 需要读取或修改的专属数据模型。Runtime 同时为当前 MiniApp 会话保存一份通用 JSON 数据字典,但不认识 +其中的 `current`、`history` 或其他业务字段。MiniApp 自己决定这些字段的组织、是否追加历史以及业务上是否允许 +修改;Runtime 只负责作用域隔离、revision、持久化、恢复和技术安全限制。一次专注完成后写进 Interact / IM 的 +记录是已发生事实的静态副本,不会随 MiniApp 后续更新自身业务数据而改变。 + +## TIS-01:Manifest 信息在 Registry / Router 边界丢失 + +**已解决(2026-08-06,收敛决议)。** 实施规范第 3.1.1 节已经冻结:每个已安装 MiniApp 在安装时将已验证 +Manifest 投影保存进 Registry;Runtime 启动以及 App / Host / 策略状态变化时,从全部已启用 Record 主动重建 +Runtime Tool Registry 和 Agent Inventory。Router 只读取投影后的 RegisteredAgentTool,并根据受控 +runtime_execution 路由至 RuntimeOperationHandlerRegistry 或 MiniApp SDK;内部 handler_key 不会发布给 Agent, +也不允许 Manifest 携带可执行代码。 + +评审初稿曾要求在 `MiniAppManifestV1` 中加入 `instance_state`,并在第 3.2 将 `pomodoro.start` 直接等同于 +Runtime-owned deadline operation;这两个前提已被后续 TIS-03 / TIS-05 决议替换为通用 session data 和先 activation、 +后创建 operation 的模型。 + +但当前 `CoreAppRegistry.installMiniAppManifest()` 只把 `app_scope`、version、permissions 和 Tool 的 name、handling、 +input/output schema、target、permissions 投影到另一套 `AppManifest/AppToolDefinition`;新增的 feature、state 声明和 +`runtime_operation` 都会丢失。`ToolRouter` 只读取后者,并且其 `miniapp` 路由会被 `LineUpRuntime` 交给普通 +`MiniAppToolStateMachine`,等待目标 instance / Surface。按现有路径实现,`pomodoro.interrupt` 会退化为普通 direct +Tool,和“不唤醒、不等待 Surface、Runtime 自己收口”的已冻结语义冲突。 + +以下为原评审识别出的风险和收敛方向;现已采用其中“Registry 保留验证后的运行时元数据,并由显式 Route +分支交给 Runtime 内置服务”的方案: + +```text +MiniAppManifestV1 + → RegisteredMiniAppRecord 中保存已验证的 Agent Tool、activation requirement 与 runtime_execution 投影 + → Inventory 只发布 Agent 所需 Tool Descriptor,不泄露内部 execution 或 MiniApp 私有数据 + → ToolRouter 返回显式 runtime Route(包含已验证 descriptor 和内部 execution) + → LineUpRuntime 的 RuntimeOperationHandlerRegistry 按受控 handler_key 调用相应 Runtime 服务 +``` + +Pomodoro 的 start 固定路由到 pomodoro.start_deadline.v1,interrupt 固定路由到 +pomodoro.interrupt_current.v1。handler_key 只能由 Runtime 内置注册;MiniApp Manifest 不能传递函数、URL +或可执行代码,LineUpRuntime 也不能在多处分支以 tool_id 字符串猜测行为。 + +## TIS-02:原子提交的拥有者与失败处理未冻结 + +**已解决(2026-08-06,收敛决议)。** LineUpRuntime 拥有唯一的 RuntimeOperationCommitCoordinator。 +PomodoroOperationService、deadline scheduler、Tool Router 和 Lifecycle 事件只提交业务变更请求;协调器在内存副本 +校验后一次持久化 operation、snapshot、workspace、Tool receipt 和 outbox,再驱动 Lifecycle / Host / Surface 对账。 +持久化失败则所有层保持旧状态;提交后的对账或 Host 失败不回滚业务事实,而是按已保存 workspace 重新对账,并交给 +TIS-05 的挂载重试 / 最终失败规则收口。 + +实施规范第 4.3 要求在一次 `commitPomodoroMutation` 中更新 operation、snapshot、workspace、MiniApp Tool projection +和 outbox。然而当前实现中 `ConversationStore` 保存 `app_workspace` 和 outbox,`AppLifecycleManager` 在内存中 +保存 instance / focus;`LineUpRuntime` 目前的 `orchestrator.launch/close`、`persistWorkspace()`、 +`replaceMiniAppToolCalls()` 和 `queueProtocolAction()` 是多个独立 mutation / persist。若服务先创建 operation,再 +由 `AppOrchestrator` 前台化失败,或先 close Lifecycle、再写 outbox 失败,恢复时会得到彼此不一致的事实。 + +本轮已确定由 `LineUpRuntime` 内部的 `RuntimeOperationCommitCoordinator` 取得 Store 快照,并在提交成功后 +驱动 `AppLifecycleManager` 对账: + +```text +读取 Store snapshot +→ 在副本中校验 CAS 并计算 next operation / snapshot / workspace / receipt / outbox +→ 一次 Store.persist(next runtime snapshot) +→ 用已保存 workspace 对账 Lifecycle 内存投影 +→ 才 emit lifecycle / state / surface 事件,并执行 Host 副作用 +``` + +若 Store 持久化失败,Lifecycle 必须仍保持旧 snapshot,且不得 emit 或发送 outbox。提交成功后若 Lifecycle +对账或 Host 操作失败,Runtime 不得回滚已经提交的 operation;它停止新的 Surface 投影、从已保存 workspace +重新对账,并进入 TIS-05 定义的可恢复启动 / 最终失败流程。 + +## TIS-03:MiniApp 如何经 SDK 读取和保存自己的业务数据 + +**已解决(2026-08-06,收敛决议);实施契约待回填。** + +此前问题把 `instanceState` 和 `operations` 当作 SDK 的两个能力,其中 `operations` 还要求向 Pomodoro Surface +公开 Runtime 的 operation 投影。这个方向已否决:若 Runtime 为 Pomodoro 的 `current`、`history` 或 operation +提供专属 API,它就会开始理解并适配每一个 MiniApp 的业务模型,失去通用 Runtime 的边界。 + +本轮确认的分工如下: + +```text +Runtime 内部 operation + = deadline、终态竞争、Tool result / receipt、outbox、恢复等可靠性事实 + = 不作为 Pomodoro 专属 SDK 数据模型公开 + +MiniApp session data + = 当前 MiniApp 自己定义和解释的通用 JSON 数据字典 + = 可组织 current、history、统计、UI 所需状态等任意业务结构 + +IM / Interact record + = 已发生结果的静态、不可变副本 + = 不随着 MiniApp 后续修改自身业务数据而变化 +``` + +Runtime 应从不可伪造的 SDK context 取得 `app_scope + app_session_id`,并只向当前 MiniApp 暴露该作用域的一份 +通用、持久化、版本化 JSON 数据字典。MiniApp 不能传入或伪造 App / Session 标识,不能读取其他 MiniApp 或其他 +会话的数据,也不能读取 Runtime operation Store、outbox 或 Conversation Store。 + +MiniApp 自己决定数据结构、`current` / `history` 的含义、是否保存历史、是否提供历史给 Agent 分析,以及业务上 +哪些内容可变或只追加。Runtime 不校验这些业务语义,但仍应施加通用技术限制:只接受 JSON、数据与单次写入的 +大小配额、安全解析深度,以及基于 revision 的 CAS 以避免并发覆盖。 + +实施规范需要据此提供一个通用的当前会话数据接口;名称暂定可使用更清楚的 `sdk.sessionData`,而不是会与文件或 +Artifact 存储混淆的泛称 `sdk.storage`。其最小能力为读取、带 `expected_revision` 的整体替换,以及订阅当前数据 +版本的变化。接口和 Web / Tauri Bridge 还须写清方法名、请求响应 JSON、CAS 冲突错误码、初始化数据投影和共用 +golden fixture;这些是已确认方案的实施细节,不再是 Runtime 与 MiniApp 的边界选择。 + +## TIS-04:无目标 interrupt 的 `operation_already_final` 回执不可判定 + +**已解决(2026-08-06,收敛决议)。** 实施规范第 5.3 节已冻结:同一个 interrupt call_id 的重放返回首次 +持久化 receipt;不同的新 interrupt call 在当前 conversation 没有 focusing operation 时,一律返回 +no_active_focusing_operation。Runtime 不查询或自动绑定最近的终态 operation;operation_already_final 只适用于 +已经绑定到同一 operation 的迟到或重放调用。 + +实施规范和主定义均要求 `pomodoro.interrupt({})` 不接受 `operation_id`,并列出 +`interrupted | no_active_focusing_operation | operation_already_final`。但一个不同的新 interrupt call 到达时,如果 +当前 conversation 已不存在 focusing operation,Runtime 没有携带目标 ID 的输入,不能知道调用者要指的是最近 +completed/interrupted 的哪一个 operation。将任何“最近 operation”自动绑定会让旧自然语言消息误伤或得到不稳定 +回执;只按当前 active slot 查找则永远只能返回 `no_active_focusing_operation`。 + +原评审提出的二选一规则如下;现已采用第一项: + +1. **推荐:** 同一 interrupt `call_id` 的重放返回首次持久化 receipt;不同的新 call 在没有 focusing operation + 时一律返回 `no_active_focusing_operation`,删除或不再承诺新 call 的 `operation_already_final`;或 +2. 为 interrupt 增加 Runtime 生成、Agent 可从先前 start result 得到的受控 operation reference,并定义可接受 + 的历史状态和 scope 校验。这会改变当前空对象输入,需要新的产品/主定义决议。 + +在没有这项决议前,开发者会在“查询最近终态”和“只查询运行中 slot”之间自行选择,重放、恢复和自动化验收 +无法一致。 + +## TIS-05:MiniApp 启动就绪与前台激活 + +**已解决(2026-08-06,收敛决议)。** + +此前实施规范把“Host 挂载失败”错误地只看成 Pomodoro operation 创建后的 UI 副作用,因此同时保留了“回滚”与 +“interrupted 收口”两种互斥行为。用户已经确认:是否进入前台并非所有 MiniApp Tool 的统一前提;它是具体 Tool +的业务语义。Runtime 因此必须先管理通用 activation,而不是先假设业务 operation 已开始。 + +```text +所有 MiniApp:starting → ready | failed | cancelled + +foreground_required(pomodoro.start) + = Host 确认 Surface 已成为用户当前前台 App,才 ready + = ready 后才创建 Pomodoro focusing operation 和 deadline + +app_ready(未来 todo.add) + = To-do 已 active / ready,Runtime 可以取得当前 App session 并路由 method + 参数 + = 不要求创建或显示 Surface;To-do 自己执行业务并写 session data + +activation_not_required(pomodoro.interrupt) + = 不为该调用创建或唤醒 MiniApp,只操作已有 Runtime 事实 +``` + +`pomodoro.start` 在前台确认前只持久化 App 子会话、instance 与 `starting` activation;没有 operation_id、 +`focusing`、`ends_at`、专注 outbox 或 IM 静态记录。Host 的确认必须表示“已成为当前前台 App”,而不只是 iframe +或页面对象已创建。Runtime 可对同一 `instance_id` 有限重试并在重启 / Host 重连后继续;最终失败时写 +`failed(surface_activation_failed)`,回传 `focus_start_failed`,关闭子会话并恢复 Interact。用户 / Agent 在启动中 +说停止、或生命周期离开时写 `cancelled_before_ready`,回传 `focus_start_cancelled`。两者都表示“本次计时未开始”, +不产生 `interrupted` operation 或专注结果记录。 + +只有已经 `focusing` 的 Pomodoro 才会因返回 Interact、切换 App、关闭或 Agent interrupt 进入 +`interrupted`。这既保留了 Pomodoro 的“前台即专注模式”语义,也使未来 To-do 等后台业务工具不被错误地要求打开 UI。 + +## 评审关闭条件 + +1. 已定的通用 session data、activation / foreground confirmation 方案须在 [05.technical_implementation_spec.md](05.technical_implementation_spec.md) + 中以 SDK / Bridge 方法、请求响应 JSON、CAS 错误码、Host fixture 与 Web / Tauri 验收落实;并同步主设计中的旧 + `instanceState` / operation projection 表述。这是已决方案的实施工作,不再构成未解决评审项。 +2. 所有补充都必须保持 TIS-C1~TIS-C4 已通过边界;不得借机加入 Voice、系统通知、多计时器、Whiteboard 工作区 + 或 Rust 迁移。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/07.技术实施规范评审(二).md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/07.技术实施规范评审(二).md new file mode 100644 index 0000000..0fcf5bb --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/07.技术实施规范评审(二).md @@ -0,0 +1,202 @@ +# 04.runtime_workspace 技术实施规范评审(二) + +**评审编号:** 02 +**日期:** 2026-08-06 +**评审对象:** [04.runtime_workspace.md](04.runtime_workspace.md)(产品/验收权威)、[05.technical_implementation_spec.md](05.technical_implementation_spec.md)、[06.technical_implementation_spec_review.md](06.technical_implementation_spec_review.md),以及 [运行时与智能体工具.md](../../设计/02.正式方案/运行时与智能体工具.md)、[LineUp Runtime 与 App SDK 架构方案](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md)、[LineUp App 最终设计方案](../../设计/02.正式方案/app_final_design.md)。 +**评审方法:** 由独立子代理只读复核当前文件,不沿用上一轮“已解决”结论;重点检查统一 activation、Agent Tool 路由、SDK session data、Runtime 原子提交,以及 Web / Tauri 是否能按同一契约实现。 +**总体结论:** Pomodoro 前台语义、`activation_not_required`、通用 session data 和“先持久化、后对账”已经对齐。本轮进一步确认:Runtime 只按已验证的 App、方法、参数和当前 ready App session 做路由,不理解 MiniApp 的业务参数或直接写其数据;前台只是个别 Tool 的额外业务前提。MiniApp 通过 SDK 声明 Agent Tool 与 handler,构建时自动生成 Manifest 的不可执行 Tool 描述,开发者无需维护第二份手写 Manifest。长期 Tool 已分开协议回执 / 进度与最终业务结果;activation ready 也成为 Runtime 持久化、向 Agent 可见的进度事实,同时不把提前到达的合法调用丢给 Agent 处理。session data 已冻结 revision 0、订阅首帧、严格递增和重连对齐语义。因此 TIS2-01~TIS2-05 已解决;TIS2-06 是有明确触发条件的 P4 延续项。**本轮没有剩余 P0~P3,第 04 次技术规范可以进入实现阶段。** + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施规范;✅ 已确认通过;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但须记录原因与重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 证据与当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P1 | TIS2-01 | Runtime 如何在页面未前台显示时调用 MiniApp 的方法。 | **已确认并回填:** Runtime 只验证并路由 `app_scope + method + 参数`,解析或建立当前 App session 并等待 MiniApp active / ready;随后将调用投递给 MiniApp。MiniApp 自己理解方法和参数、修改 session data、返回结果。`app_ready` 不要求前台;`foreground_required` 在其上额外要求前台;`activation_not_required` 不启动 App。具体无界面执行容器是后续实现选择,不改变这条 Runtime / MiniApp 契约。 | +| ✅ | P1 | TIS2-02 | MiniApp 如何声明面向 Agent 的方法,并让 Runtime 路由到正确的处理逻辑。 | **已确认并回填:** MiniApp 在 SDK 中用 `defineAgentTools(...)` 声明公开 method、schema、activation requirement 和 handler;构建自动生成 Manifest 的不可执行 Tool 描述,开发者不写第二份 Manifest。Runtime 只按已验证 Tool 路由到 ready App session 的统一 SDK 接收入口;SDK 在 Bundle 内按 method 分发本地 handler。Registry 不保存私有函数 target。 | +| ✅ | P1 | TIS2-03 | 长期 `pomodoro.start` 在 `starting` / `focusing` 时的 Agent 回执与冻结 result schema 不一致。 | **已确认并回填:** `accepted / starting` 与 `started` 是持久化、可重放的协议回执 / 进度;只有 `completed`、`interrupted`、`not_started` 是符合 output schema 的唯一最终业务结果。Agent 只能在 `started` 后说“已开始计时”。同一 call_id 返回最新已持久化回执,终态后返回最终结果,不追加 outbox。 | +| ✅ | P1 | TIS2-05 | Agent 如何得知 MiniApp 已启动就绪,以及是否必须等待该通知后才能发送后续调用。 | **已确认并回填:** Runtime 将 activation 的 `starting / ready / failed / cancelled` 作为与触发 call_id 关联的受控协议进度通知 Agent;Agent 收到 `activation_ready` 后再编排依赖调用是推荐方式,但不是硬门槛。Runtime 持久化并排队提前到达的 `app_ready` / `foreground_required` 调用,ready 后按顺序投递;失败 / 取消则受控拒绝。`activation_not_required` 仍立即执行。Pomodoro 的 `started` 包含 ready,不重复发 `activation_ready`。 | +| ✅ | P2 | TIS2-04 | `sessionData.get()` 与 `subscribe()` 之间可能丢失一次更新,首帧与 revision 语义未冻结。 | **已确认并回填:** 空数据固定 revision 0;订阅在同一 App 子会话的串行顺序中注册并先收到当前完整快照,后续 revision 严格递增;因此读与订阅之间的 replace 要么成为首帧、要么紧随其后。重复 / 旧 revision 忽略,跳跃 / Bridge 重连重新 get 并订阅;Web / Tauri 共享 fixture。revision CAS 不做业务自动合并,TIS2-06 延续。 | +| ⚪ | P4 | TIS2-06 | 多入口同时修改 session data 时,是否建立 Runtime 通用 mutation queue。 | **延续项,不属于第 04 次实现:** 当前 Pomodoro 没有修改型 UI Action,保持 `get / replace / subscribe + revision CAS` 即可。首次实现“Agent Tool 与 UI Action 都可修改同一 App 子会话”的 MiniApp(例如可手动修改的 To-do 或 Whiteboard)前,必须先冻结统一 mutation queue、操作顺序、幂等恢复与业务冲突边界;离线多端协同 / CRDT 另行设计。 | +| ✅ | — | TIS2-C1 | Pomodoro 的前台即专注语义 | 前台确认后才创建 operation;仅挂载 / 预载不等于开始,失败 / 取消不产生 operation 或 IM 静态记录。 | +| ✅ | — | TIS2-C2 | `pomodoro.interrupt` 不唤醒 App | `activation_not_required` 可取消已有 starting 或中断已有 focusing,但不会因停止指令新开 App。 | +| ✅ | — | TIS2-C3 | session data、operation 与 IM 记录的职责边界 | session data 按 `app_scope + app_session_id` 隔离并由 MiniApp 自解释;operation / outbox 是 Runtime 事实;IM 是不可变静态结果。 | +| ✅ | — | TIS2-C4 | 原子提交和三条入口 | Runtime 先持久化 operation、activation、session data、workspace、receipt / outbox,再让 Lifecycle / Host / Surface 对账;Agent Tool、UI Action、Host 生命周期意图不混同。 | + +## 本轮已通过的边界 + +### TIS2-C1:Pomodoro 只有进入前台才真正开始 + +`pomodoro.start` 被接受后,Runtime 可以创建 instance、App 子会话和 `starting` activation;但 Host 必须明确确认该 instance 已成为用户当前看到的前台 App,Runtime 才创建 `focusing` operation 与 `ends_at`。iframe 创建、资源加载或后台预载都不是这个确认。最终激活失败、启动中取消或用户在启动中离开,都表示“本次计时未开始”,不产生 `operation_id`、deadline、专注中断记录或专注 IM 静态记录。 + +### TIS2-C2:停止指令不会反而打开 Pomodoro + +`pomodoro.interrupt` 只查询调用所在 conversation 已有的 `starting` 或 `focusing` Pomodoro:前者取消启动,后者中断 operation;两者都不会为这条停止指令创建 instance、Surface 或前台焦点。 + +### TIS2-C3:通用 session data 不泄露 Runtime 业务事实 + +SDK 从不可伪造的 context 推导 `app_scope + app_session_id`,MiniApp 只能读取和替换自己的 JSON 数据字典。Pomodoro 可自行组织活动、显示信息、current 或 history;Runtime 不识别这些字段,也不直接向字典写 Pomodoro 私有业务数据。deadline、终态竞争、Tool receipt、outbox 和恢复继续属于 Runtime operation;一次完成后写入 Interact / IM 的记录是独立的不可变静态副本。 + +### TIS2-C4:持久化仍先于 Host 副作用 + +`RuntimeOperationCommitCoordinator` 的职责没有被 activation 新模型削弱:持久化失败时 activation、operation、session data、workspace、receipt、outbox、Lifecycle 和 Surface 均保持旧状态;持久化成功后 Host / Surface 对账失败时,Runtime 从已保存事实恢复或重试,不能倒写或伪造终态。 + +## TIS2-01:Runtime 如何调用未前台显示的 MiniApp 方法 + +**已解决(2026-08-06,收敛决议)。** + +此前问题错误地把重点放在“后台执行容器究竟是 Worker、iframe 还是别的实现”。用户已明确 Runtime 的架构职责: +它接收 Agent Tool 后只根据 schema 验证调用结构,找到 `app_scope` 下的 method 与参数,确认目标 MiniApp 当前 +session 是否 active / ready,然后把调用路由给 MiniApp。Runtime 不理解参数字段和数值的业务意义,也不直接写 +MiniApp 的数据;MiniApp 才知道 `todo.add` 表示新增待办,并自行通过 session data 完成业务修改。 + +```text +Agent Tool Call + → Runtime:验证 Tool、schema、scope、幂等 + → Runtime:解析或建立目标 App session,等待 MiniApp ready + → Runtime:投递 method + 已验证参数 + → MiniApp:理解业务、修改自己的 session data、返回结果 + → Runtime:验证、持久化 receipt / result,并回传 Agent +``` + +三个 activation requirement 的意义随之明确: + +```text +app_ready + = MiniApp 已 active / ready,Runtime 可路由方法调用;不要求前台 + = 未来 todo.add + +foreground_required + = app_ready,且 Host 已确认 MiniApp 成为当前前台 + = pomodoro.start;前台是“开始专注”的业务前提 + +activation_not_required + = 不为这次调用启动或唤醒 MiniApp,只操作已有 Runtime 事实 + = pomodoro.interrupt +``` + +运行中但不在前台的 MiniApp 如何落到具体 Host 容器,是后续实现层的选择;它不得改变上述统一的 Runtime 路由、 +MiniApp 业务处理与 SDK 回传契约。本轮不再把“Runtime 是否直接写 To-do 数据”或“是否前台”误当作这个问题。 + +Runtime 同时会把 activation 的受控状态回传给触发启动的 Agent:`accepted / starting`、`activation_ready`,或 +`activation_failed / activation_cancelled`。这条通知帮助 Agent 在需要时顺序编排后续调用,但 Runtime 不把正确性 +交给 Agent:如果合法的 `app_ready` / `foreground_required` 调用在 ready 前已经到达,它会被持久化到同一 activation +等待队列,ready 后按顺序投递;`activation_not_required` 则保持立即执行。 + +## TIS2-02:SDK 声明 Agent Tool,构建自动生成 Manifest + +**已解决(2026-08-06,收敛决议)。** + +MiniApp Manifest 的 Tool 区块仍是 Runtime 安装、Registry、Inventory 和 Agent 调用所依赖的能力说明书;但它不应是 +开发者手写和维护的第二份图纸。每个 MiniApp 在自身代码中通过 SDK 的 `defineAgentTools(...)` 声明公开 Tool、 +输入 / 输出 schema、activation requirement 和 `miniapp_sdk` Tool 的本地 handler。构建阶段从该声明自动生成 +Manifest 的不可执行 Tool 投影,并与 Bundle 一同校验、签名和安装。 + +```text +MiniApp 源码中的 SDK Tool 声明 + ├── 供 TypeScript 校验 handler、输入和输出 + ├── 供 Bundle 内 SDK 建立 method → handler 本地映射 + └── 构建自动生成 Manifest tools 描述 + → Runtime 安装时验证并投影 Registry + → Runtime 发布 Agent Inventory +``` + +运行时对普通 MiniApp Tool 的路径固定为: + +```text +Agent 调用 app_scope + method + params + → Runtime 按生成 Manifest 校验、定位 ready App session + → Runtime 投递到该 session 的统一 SDK Tool 接收入口 + → SDK 按 method 调用 Bundle 内已声明 handler + → MiniApp 处理业务、写 session data、返回受控结果 +``` + +Runtime 不保存、更不调用 MiniApp 私有函数 target;Manifest 不包含函数、URL、回调或其他可执行内容。`delivery = +miniapp_sdk` 表示上述固定入口;`delivery = runtime` 才使用 Runtime 内部 handler key,例如第 04 次 Pomodoro +的 deadline / interrupt 可靠性操作。构建必须拒绝重复 method、非法 schema、miniapp_sdk Tool 缺少 handler 或 +handler 与 schema 不匹配;runtime Tool 则必须匹配 Runtime 内置的受控 execution。 + +## TIS2-03:长期 Tool 的协议 receipt 与业务 result 分层 + +**已解决(2026-08-06,收敛决议)。** + +`pomodoro.start` 的业务 result schema 只允许最终业务结果:已完成、已中断或未开始。同一个 call_id 在 `starting` 或 +`focusing` 时不能伪造终态,也不能把 activation / operation 对象塞进业务 result。因此这条调用固定分成两层: + +```text +Tool protocol receipt / progress + = accepted / starting:Runtime 已收到请求,正在把 Pomodoro 打开到前台 + = started:Host 已确认前台,Runtime 已创建 operation,计时真实开始 + = 对 Agent 只包含 call_id、app_scope、受控状态,以及 started 后必要的时间事实 + = 不属于 Tool 的业务 result_schema;每个阶段各自只写一次可重试 outbox + +Tool business result + = 已完成、已中断、未开始等业务终态 + = 必须符合 Manifest result_schema + = 每次 Tool Call 只持久化并回传一次 +``` + +首次 start 与开始成功的原子提交分别写 `accepted / starting` 和 `started` 协议消息。相同 call_id 在 `starting` / `focusing` +时返回已持久化的最新协议回执;仅在进入 completed、interrupted 或 not_started 后返回已持久化的业务 result。`accepted` +只表示 Runtime 接到请求,不代表 Agent 可以对用户说“已开始计时”;真正开始仍以 Host 前台确认和 operation 创建为准。 + +## TIS2-05:activation ready 如何通知 Agent、提前调用由谁负责 + +**已解决(2026-08-06,收敛决议)。** + +所有 MiniApp 都有 `starting → ready | failed | cancelled` activation。Runtime 在持久化状态变化后,通过与原启动 +call_id 关联的 protocol receipt / progress 向 Agent 发出 `accepted / starting`、`activation_ready` 或 +`activation_failed / activation_cancelled`;MiniApp、Surface 与 Host 都不得直接通知 Agent。通知只携带 call_id、 +app_scope、状态和受控失败原因,不能把 instance / App 子会话变成 Agent 可指定的控制目标。 + +Agent 在收到 `activation_ready` 后再发送依赖该 App 的后续指令,是推荐的编排方式,但不是请求能否接收的硬门槛。 +Runtime 自己持久化并排队在 ready 前到达的合法 `app_ready` / `foreground_required` Tool,ready 后按到达顺序投递; +若启动失败或取消,Runtime 不执行业务 handler,而为每个等待调用回传稳定错误。`activation_not_required` 保持立即处理, +因而启动中的 `pomodoro.interrupt` 可以直接取消当前启动。Pomodoro 的 `started` 同时表达 activation ready、前台确认与 +operation 已开始,故不额外向 Agent 发送重复的 `activation_ready`。 + +## TIS2-06:通用 mutation queue 与多入口协作写入 + +**优先级:P4(延续项,不在第 04 次迭代实现)。** + +To-do 若只展示数据、所有修改都经 Agent Tool 进入,实际上天然只有一个业务写入来源;画板若同时允许 Agent Tool +和用户 UI Action 修改同一份 session data,则需要 Runtime 为同一个 `app_scope + app_session_id` 建立统一、持久化、 +幂等的 mutation queue,避免两个来源各自基于旧 JSON 快照整体覆盖。该机制应让 Agent Tool、UI Action 与受控 +Runtime 动作都以“业务操作”而非完整 JSON 快照进入同一顺序,再由 MiniApp 在最新数据上实现自己的业务效果。 + +这个方向是通用 Runtime 能力,不应要求开发者预先选择“单点 / 协作”模式;实际有哪些写入入口,是 MiniApp 已经 +声明的 Tool / UI Action 自然决定的。但实现它会同时引入写入 handler 上下文、队列持久化与重启恢复、UI Action 写入 +协议、顺序 / 幂等规则,以及未来离线多人协作与 CRDT / OT 的清晰边界。第 04 次只验证没有修改型 UI Action 的 +Pomodoro,不能为此提前扩大范围。 + +本迭代继续使用通用 `sdk.sessionData.get / replace / subscribe` 与 revision CAS;CAS 只防止旧版本覆盖,不承诺 +自动合并业务冲突。**重新评估触发条件:** 在开始任何一个“Agent Tool 与 UI Action 都能修改同一 App 子会话”的 +功能之前,例如手动编辑 To-do 或 Whiteboard 的首个交互式绘制流程,必须先将本项提升为该迭代的 P1 并完成设计。 + +## TIS2-04:session data 首帧与 revision 同步窗口 + +**已解决(2026-08-06,收敛决议)。** + +本项只处理 Surface 读取自身 session data 时的版本同步,不处理 Agent 与 UI 同时修改数据的业务合并;后者已作为 +TIS2-06 的 P4 延续项。若 Surface 先 `get()`、再 `subscribe()`,两次调用之间发生的 replace 不能被遗漏。第 04 次 +冻结以下最小且跨 Host 一致的规则: + +```text +不存在 session data + → get() 返回 { data: null, revision: 0 } + +subscribe(listener) + → 在同一 app_scope + app_session_id 串行顺序中注册 + → 注册后先投递当前完整 { data, revision } + → 之后投递 revision 严格递增的 session_data.changed +``` + +因此,若 replace 在注册前提交,首帧就是新 revision;若注册先完成,则新 revision 作为后续事件抵达。SDK 收到重复或 +旧 revision 时忽略;若发现 revision 跳跃或 Bridge 重连,取消旧订阅、重新 `get()` 并建立新的订阅。Web / Tauri 共享 +golden fixture,且必须测试“get(revision 3) 与 subscribe 之间发生 replace(revision 4)”以及反向注册顺序的场景。 +`expected_revision` 冲突只返回稳定错误 `session_data_revision_conflict`,第 04 次不自动合并 JSON。 + +## 本轮关闭条件 + +1. TIS2-01~TIS2-05 已回填主迭代定义、实施规范与正式方案;TIS2-06 是已记录的 P4 延续项,在其重新评估触发条件出现前不阻塞第 04 次验收。 +2. 第 04 次进入实现后,必须以本文件和实施规范的 Web / Tauri fixture、单元测试、构建与代表性 Host 流程验证这些决议;实现完成后进行下一轮独立验收评审。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/08.acceptance_review.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/08.acceptance_review.md new file mode 100644 index 0000000..8fd04e2 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/08.acceptance_review.md @@ -0,0 +1,117 @@ +# 04.runtime_workspace 验收复核(第 08 轮) + +**评审轮次:** 08 +**日期:** 2026-08-06 +**主定义:** [04.runtime_workspace.md](04.runtime_workspace.md) +**实施规范:** [05.technical_implementation_spec.md](05.technical_implementation_spec.md) +**评审方式:** 当前工作树实现核对 + 全量 gate + 新鲜浏览器验收 + 独立只读复核 +**独立复核:** `Rawls`(fresh read-only acceptance audit) +**总体结论:** 当前实现与自动化回归已覆盖主实现路径,`RW-A10` 已关闭;仍有 `1` 个未关闭 `P2`,本轮不能宣布最终验收通过。 + +## 问题清单(Outline) + +状态图例:`🔴` 未解决|`🟡` 进行中 / 证据不足|`✅` 已关闭|`⚪` 延续观察 + +| 状态 | 优先级 | ID | 问题 | 当前结论 / 下一步 | +|---|---|---|---|---| +| ✅ | P2 | RW-A10 | “未到点重启恢复”缺少直接、场景级自动化证明。 | 已关闭。`runtime-workspace-host` 恢复顺序测试、Pomodoro remount 倒计时测试、Runtime overdue restart 测试和组合恢复测试已共同覆盖“未到点 / 已到点”恢复矩阵。 | +| 🔴 | P2 | RW-A11 | Tauri Desktop Host 尚缺一条可审计的代表性业务流验收证据。 | 仍未关闭。当前只有 fresh browser 代表路径证据,以及 prior-round / exploratory desktop window visible evidence;还缺少 Tauri Host 上 `start → interrupt` 或 `start → deadline` 的业务闭环证据。 | + +## Gate 结果 + +- `npm test -- --run`:通过,`35` 个文件 / `195` 个测试全部通过。 +- `npm run build`:通过。 +- `git diff --check`:通过。 +- 独立复核聚焦套件:`pomodoro-miniapp.test.ts`、`runtime-workspace-host.test.ts`、`lineup-runtime.test.ts` 通过;独立 reviewer 报告其本轮复核为 `3` 个文件 / `56` 个测试通过。 + +## 本轮确认 + +### RW-A10 已关闭 + +本轮重新核对后,`RW-A10` 不再成立。当前代码和测试已直接覆盖规范要求的“刷新或重启,未到点 / 已到点”恢复矩阵: + +- 恢复装配顺序已抽到 [tauri/src/runtime/coordination/runtime-workspace-host.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/runtime-workspace-host.ts:15),先 `applySnapshot`,再挂载已恢复 surface,再 `reconcileBundledMiniApps`,最后恢复执行摘要。 +- [tauri/src/runtime/coordination/runtime-workspace-host.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/runtime-workspace-host.test.ts:32) 固定恢复顺序。 +- [tauri/src/runtime/coordination/runtime-workspace-host.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/runtime-workspace-host.test.ts:55) 直接覆盖“登录恢复路径 -> Pomodoro remount -> 按持久化 `ends_at` 继续倒计时”。 +- [tauri/src/core-apps/pomodoro/pomodoro-miniapp.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/core-apps/pomodoro/pomodoro-miniapp.test.ts:135) 固定 MiniApp remount 后继续按原 `ends_at` 派生剩余时间。 +- [tauri/src/runtime/coordination/lineup-runtime.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/lineup-runtime.test.ts:733) 固定“已到点后重启只结算一次 completed,不重复追加 outbox”。 + +独立 reviewer `Rawls` 的本轮结论也明确将 `RW-A10` 关闭。 + +### RW-A11 仍未关闭 + +规范的验收矩阵仍要求: + +- [05.technical_implementation_spec.md](/home/gao/Development/lineup/lineup-app/迭代/04.runtime_workspace/05.technical_implementation_spec.md:679) 的 Host 验收项:`Web 完整 IM 流程;Tauri 至少跑 start → interrupt 或 start → deadline 的代表路径。` +- [04.runtime_workspace.md](/home/gao/Development/lineup/lineup-app/迭代/04.runtime_workspace/04.runtime_workspace.md:326) 将 Web Reference Host 与 Tauri Desktop Host 都写入本轮验收边界。 + +当前证据状态: + +- Web 代表路径:`有 fresh evidence`。 +- Tauri Desktop Host:`只有窗口可见 / 进程存活类证据,不足以证明业务代表路径`。 + +因此 `RW-A11` 不是实现缺陷,而是当前仍未满足文档要求的验收证据缺口。 + +## 新鲜浏览器验收证据 + +本轮重新获取了 fresh browser evidence: + +```text +/snap/bin/chromium --headless=new --disable-gpu --virtual-time-budget=7000 \ + --dump-dom 'http://127.0.0.1:1421/?runtime-workspace-e2e=1&runtime-workspace-sequence=start-interrupt' +``` + +结果要点: + +- `body[data-runtime-workspace-e2e="sequence_interrupted"]` +- 页面消息区出现: + - `正在打开 Pomodoro` + - `正在停止 Pomodoro` +- `#runtime-workspace-e2e-report` 中的 `surface_trace` 为: + - `miniapp.start` + - `surface-request open` + - `surface-open-result accepted` + - `surface-request patch` + - `surface-patch-result accepted` + - `surface-request close` + - `surface-close-result accepted` +- `app_sessions` 中: + - `chat` 为 `foreground` + - `pomodoro` 为 `stopped` + +这条证据证明当前 Web Host 的 `start → interrupt → close` 代表路径仍可复现,且 Runtime、Host 和本地 Pomodoro Surface 的受控对账链条是闭合的。 + +## 桌面证据现状 + +本轮未取得满足规范的 fresh Tauri 业务流证据。 + +已有可保留的辅助事实: + +- prior round 的真实桌面启动中,用户明确确认“看到了”窗口; +- 本轮探索中,Tauri Host 也能在 Xvfb 上渲染出 `LineUp` 窗口。 + +但这些都只能证明“桌面窗口出现”,不能替代规范要求的: + +```text +Tauri Desktop Host 至少一条 start → interrupt 或 start → deadline 代表路径验收 +``` + +因此本轮不能把 `RW-A11` 关闭。 + +## 本轮结论 + +```text +Round: 08 +Primary definition: 迭代/04.runtime_workspace/04.runtime_workspace.md +Independent reviewer: Rawls +P0: 0 P1: 0 P2: 1 P3: 0 +Resolved this round: RW-A10 +Carryover: RW-A11(Tauri Desktop Host 代表路径证据不足) +Gates: npm test -- --run ✅ | npm run build ✅ | git diff --check ✅ | browser host evidence ✅ | desktop representative flow ❌ +Current conclusion: continue loop +``` + +下一步只剩两种合规收口方式: + +1. 在不改验收口径的前提下,补一条 Tauri Desktop Host 的代表性业务流证据并关闭 `RW-A11`。 +2. 若产品 / 评审决定本轮只以 Web Host 作为最终验收口径,则必须先更新主定义与评审基线,再重新做独立复核。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/09.acceptance_review.md b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/09.acceptance_review.md new file mode 100644 index 0000000..4b42207 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/09.acceptance_review.md @@ -0,0 +1,129 @@ +# 04.runtime_workspace 验收复核(第 09 轮) + +**评审轮次:** 09 +**日期:** 2026-08-06 +**主定义:** [04.runtime_workspace.md](04.runtime_workspace.md) +**实施规范:** [05.technical_implementation_spec.md](05.technical_implementation_spec.md) +**上一轮记录:** [08.acceptance_review.md](08.acceptance_review.md) +**评审方式:** 当前工作树复核 + 既有全量 gate + fresh browser evidence + fresh Tauri Host screenshot evidence + fresh independent read-only review +**独立复核:** `Galileo`(fresh read-only acceptance audit) +**总体结论:** 当前实现、自动化覆盖与 Host 验收证据已经满足本轮主定义和技术实施规范,最新独立复核结论为 `P0:0 / P1:0 / P2:0 / P3:0`。本轮验收通过。 + +## 问题清单(Outline) + +状态图例:`🔴` 未解决|`🟡` 进行中 / 证据不足|`✅` 已关闭|`⚪` 延续观察 + +| 状态 | 优先级 | ID | 问题 | 当前结论 / 下一步 | +|---|---|---|---|---| +| ✅ | P2 | RW-A10 | “未到点重启恢复”缺少直接、场景级自动化证明。 | 已关闭。恢复顺序、登录恢复 remount、按原 `ends_at` 继续倒计时、已到点只结算一次 completed 均已被直接覆盖。 | +| ✅ | P2 | RW-A11 | Tauri Desktop Host 尚缺一条可审计的代表性业务流验收证据。 | 已关闭。fresh Tauri Host screenshot 已直接呈现 `start → interrupt` 代表路径与验收报告面板状态,不再只是“窗口可见”。 | + +## Gate 结果 + +- `npm test -- --run`:通过,`35` 个文件 / `195` 个测试全部通过。 +- `npm run build`:通过。 +- `git diff --check`:通过。 +- 工作树附加验收材料: + - [08.acceptance_review.md](08.acceptance_review.md) + - [09.acceptance_review.md](09.acceptance_review.md) + - [evidence/runtime-workspace-tauri-start-interrupt-20260806.png](evidence/runtime-workspace-tauri-start-interrupt-20260806.png) + +## 本轮关闭项 + +### RW-A10 已关闭 + +“未到点重启恢复”当前已有直接、场景级自动化证明,证据链如下: + +- [tauri/src/runtime/coordination/runtime-workspace-host.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/runtime-workspace-host.ts:15) 固定登录恢复顺序:`applySnapshot → mountRestoredSurfaces → reconcileBundledMiniApps → restoreExecutionSummaries`。 +- [tauri/src/runtime/coordination/runtime-workspace-host.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/runtime-workspace-host.test.ts:29) 固定恢复顺序。 +- [tauri/src/runtime/coordination/runtime-workspace-host.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/runtime-workspace-host.test.ts:55) 直接覆盖“登录恢复路径 -> Pomodoro remount -> 按持久化 `ends_at` 继续倒计时”。 +- [tauri/src/core-apps/pomodoro/pomodoro-miniapp.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/core-apps/pomodoro/pomodoro-miniapp.test.ts:135) 固定 remount 后按原 `ends_at` 派生剩余时间。 +- [tauri/src/runtime/coordination/lineup-runtime.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/lineup-runtime.test.ts:733) 固定“已到点后重启只结算一次 completed,不重复 outbox”。 + +这些覆盖已经与 [05.technical_implementation_spec.md](05.technical_implementation_spec.md) 第 8 节的“刷新或重启,未到点 / 已到点”验收矩阵对齐。 + +### RW-A11 已关闭 + +主定义和技术规范对 Host 验收的要求仍然是: + +- [04.runtime_workspace.md](/home/gao/Development/lineup/lineup-app/迭代/04.runtime_workspace/04.runtime_workspace.md:326) 要求通过 Web Reference Host 完成完整浏览器验收,并在 Tauri Desktop Host 完成至少一条代表性流程。 +- [05.technical_implementation_spec.md](/home/gao/Development/lineup/lineup-app/迭代/04.runtime_workspace/05.technical_implementation_spec.md:679) 的 Host 验收项要求:`Web 完整 IM 流程;Tauri 至少跑 start → interrupt 或 start → deadline 的代表路径。` + +本轮新增的 Tauri Host 证据已满足这条要求: + +- 证据文件: [evidence/runtime-workspace-tauri-start-interrupt-20260806.png](evidence/runtime-workspace-tauri-start-interrupt-20260806.png) +- 证据来源:真实 Tauri Host 在 Xvfb 桌面中运行并渲染后的屏幕截图。 +- 截图中可直接观察到: + - `正在打开 Pomodoro` + - `正在停止 Pomodoro` + - 右下验收报告面板中 `label: "sequence_interrupted"` + - `chat` 为前台会话 + - `pomodoro` 为 `stopped` + +这张图已经不是“桌面窗口出现”的弱证据,而是 Tauri Host 内部确实走完了一条 `start → interrupt` 代表路径的业务证据。 + +## Fresh Host 证据 + +### Browser Host + +本轮可复验的 browser evidence 仍为: + +```text +/snap/bin/chromium --headless=new --disable-gpu --virtual-time-budget=7000 \ + --dump-dom 'http://127.0.0.1:1421/?runtime-workspace-e2e=1&runtime-workspace-sequence=start-interrupt' +``` + +结果要点: + +- `body[data-runtime-workspace-e2e="sequence_interrupted"]` +- 消息区出现 `正在打开 Pomodoro`、`正在停止 Pomodoro` +- `surface_trace` 为 `open → patch → close`,且均为 `accepted` +- `chat` 前台、`pomodoro` 停止 + +### Tauri Desktop Host + +本轮新增证据: + +- [evidence/runtime-workspace-tauri-start-interrupt-20260806.png](evidence/runtime-workspace-tauri-start-interrupt-20260806.png) + +该截图直接表明 Tauri Host 的页面内容已经进入并完成 `start → interrupt` 代表路径: + +- 工作区消息区包含 `正在打开 Pomodoro` 与 `正在停止 Pomodoro` +- 验收面板包含 `sequence_interrupted` +- 会话摘要显示 `chat foreground`、`pomodoro stopped` + +这满足 “Tauri 至少跑 `start → interrupt` 或 `start → deadline` 代表路径” 的验收要求。 + +## 独立复核结论 + +fresh independent reviewer `Galileo` 的结论为: + +```text +P0: 0 +P1: 0 +P2: 0 +P3: 0 +P4: 0 +P5: 0 +``` + +其判断要点: + +- `RW-A10` 已被直接测试链覆盖,不再构成验收阻塞。 +- `RW-A11` 已被新的 Tauri Host screenshot 证据关闭,不再是证据缺口。 +- 当前需要做的是形成正式收口记录,而不是继续补代码。 + +## 最终结论 + +```text +Round: 09 +Primary definition: 迭代/04.runtime_workspace/04.runtime_workspace.md +Independent reviewer: Galileo +P0: 0 P1: 0 P2: 0 P3: 0 +Resolved this round: RW-A11 +Carryover P4/P5: 无 +Gates: npm test -- --run ✅ | npm run build ✅ | git diff --check ✅ | browser host evidence ✅ | Tauri host representative flow ✅ +Current conclusion: complete +``` + +截至 **2026 年 8 月 6 日**,第 04 次迭代 `runtime_workspace` 的实现、测试、浏览器验收和 Tauri Host 验收均已满足主定义与技术实施规范,当前可以宣布本轮验收通过。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/evidence/runtime-workspace-tauri-start-interrupt-20260806.png b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/evidence/runtime-workspace-tauri-start-interrupt-20260806.png new file mode 100644 index 0000000..6cd6200 Binary files /dev/null and b/03.迭代规划/03.进行中与待实施阶段/04.runtime_workspace/原始文档/evidence/runtime-workspace-tauri-start-interrupt-20260806.png differ diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/01.阶段摘要.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/01.阶段摘要.md new file mode 100644 index 0000000..51ba74b --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/01.阶段摘要.md @@ -0,0 +1,17 @@ +# 04A.agent_tool_route 阶段摘要 + +**状态:** 已完成 +**来源:** `lineup-app/迭代/04A.agent_tool_route/04A.agent_tool_route.md` + +## 1. 结论 + +本阶段不是重做 Runtime 工作区,而是补齐 Agent / Adapter 一半链路: + +- Runtime 发布 Tool inventory; +- Hermes Adapter 负责协议转换和平台兼容; +- 用户通过自然语言真正触发合法 Tool invoke; +- Hermes 的 approval / clarify / update prompt 收敛为 LineUp 标准协议。 + +## 2. 当前角色 + +该阶段已于 2026-08-07 完成当前定义下的联合验收,是第一个明确的跨项目迭代样板。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/01.design_review.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/01.design_review.md new file mode 100644 index 0000000..b0a5f12 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/01.design_review.md @@ -0,0 +1,111 @@ +# 04A.agent_tool_route 设计评审(一) + +**评审编号:** 01 +**日期:** 2026-08-07 +**评审对象:** [04A.agent_tool_route.md](04A.agent_tool_route.md)(实施权威)、[04.runtime_workspace.md](../04.runtime_workspace/04.runtime_workspace.md)、[05.technical_implementation_spec.md](../04.runtime_workspace/05.technical_implementation_spec.md)、[运行时与智能体工具.md](../../设计/02.正式方案/运行时与智能体工具.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md)。 +**评审方法:** 独立以 `04A.agent_tool_route.md` 作为唯一实施权威,核对它与 `04.runtime_workspace` 已冻结的 Tool 调用 / 回程语义是否一致,并检查 Hermes Adapter 新增边界是否足以让协议、实现与验收得到唯一结论。 +**总体结论:** `04A` 已成功收敛为 Adapter 层的两个核心目标:一是补齐 `applications[].tools` 到 `lineup.v1.miniapp.tool.call` 的 MiniApp Tool 通路,二是把 Hermes 内置审批 / 澄清 / 更新提示等交互收口到 LineUp 协议。评审中确认了两个必须冻结的边界:`miniapp.tool.call` 不新增第二套专用 result 协议,而是继续复用既有 Agent Tool 回程语义;Hermes `clarify` 在本轮仅兼容单选、`other -> input` 和纯文本,`multi_select` 稳定拒绝。上述决议已回填实施权威,当前无未解决的 P0~P3 问题,设计可进入技术实施规范阶段。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施权威;✅ 已解决;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须记录延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P1 | D04A-01 | `lineup.v1.miniapp.tool.call` 已新增请求信封,但主定义未明确它的协议回执、进度和最终业务结果是否复用既有 Agent Tool 回程语义。 | 已确认并回填:不新增 `lineup.v1.miniapp.tool.result`;`miniapp.tool.call` 的 receipts / progress / final result 全部继续复用 `04.runtime_workspace` 已冻结的 Agent Tool 回程语义,并始终以同一 `call_id` 关联。 | +| ✅ | P2 | D04A-02 | Hermes `clarify` 已纳入兼容范围,但未说明 `multi_select = true` 的处理规则,存在实现静默降级或行为分叉风险。 | 已确认并回填:`04A` 只兼容单选、`other -> input` 和纯文本澄清;`multi_select = true` 稳定拒绝,不静默降级成单选或自由文本。 | + +## 已确认的一致性 + +### `04A` 是 Adapter 补齐,不是重写 Runtime + +主定义已经把边界写清:`04.runtime_workspace` 继续是 Runtime、Lifecycle、Inventory revision、Tool schema 与 Pomodoro 裁决语义的权威;`04A` 只补齐 Agent / Adapter 如何消费这些事实、如何发起合法调用、以及如何把 Hermes 私有交互收口到 LineUp 协议。 +这意味着本轮不应在 Runtime 中新增 Hermes 特判,也不应为 `miniapp.tool.call` 重新发明一套业务状态机。 + +### `lineup-app-server` 继续是透明传输层 + +主定义将 `lineup-app-server` 明确排除在本轮核心改造范围之外,这与当前代码角色一致:服务端负责消息收发与传输,不承担 `lineup.v1` 业务语义解析。 +因此,本轮实现责任应集中在 Hermes Adapter、Prompt 约束、协议规范化与测试证据,不应让实施误入 Go 服务端语义改造。 + +### Hermes 的平台私有交互兼容责任位于 Adapter 层 + +主定义已经吸收了飞书模式的关键点:不是让模型生成平台私有协议,也不是让 Runtime 理解 Hermes 私有 prompt kind,而是由 Adapter 把 Hermes 内部审批 / 澄清 / 更新提示转换成 LineUp 标准交互,再把用户回传 resolve 回 Hermes 内部状态。 +这条边界对未来 Open Claw 等其他 Agent 平台同样成立,因此本次评审认为该分层方向正确,且应继续保持。 + +## D04A-01:`miniapp.tool.call` 缺少明确的回程协议归属 + +**已解决(2026-08-07,收敛决议)。** 主定义已回填:`lineup.v1.miniapp.tool.call` 只新增请求信封,不新增 `lineup.v1.miniapp.tool.result`。与之关联的协议回执、进度和最终业务结果,全部继续复用 `04.runtime_workspace` 已冻结的 Agent Tool 回程语义,并始终按同一 `call_id` 相关联。 + +### 为什么这是 P1 + +`04.runtime_workspace` 已经明确冻结了 Agent Tool 的回程模型: + +- 请求被接受后可先收到 `accepted / starting` 等协议回执; +- 激活完成后可收到 `started` 或其他受控进度; +- 最终只回传一次符合 Tool result schema 的业务结果; +- 同一 `call_id` 的重放返回已持久化的最新回执、进度或最终结果。 + +但 `04A` 在新增 `lineup.v1.miniapp.tool.call` 时,如果只定义请求信封而不定义回程归属,就会出现至少三种实现分叉: + +```text +实现 A:为 miniapp.tool.call 再发明一套 miniapp.tool.result + -> Hermes Adapter、Runtime、验收脚本都要多维护一套协议。 + +实现 B:沿用既有回程语义,但不写进主定义 + -> 各模块只能靠口头共识实现,验收口径不唯一。 + +实现 C:把 MiniApp Tool 的 started / progress 当成最终业务结果 + -> 直接破坏 04.runtime_workspace 已冻结的 Tool result 语义。 +``` + +这会影响 Hermes Adapter 的协议白名单、回程解析、幂等与验收证据,因此必须在实现前冻结。 + +### 收敛决议 + +本轮已确认: + +1. `lineup.v1.miniapp.tool.call` 只负责表达“Agent 要调用某个 MiniApp Tool”; +2. 不新增 `lineup.v1.miniapp.tool.result` 或其他第二套 MiniApp 专用回程协议; +3. 该调用的 receipts / progress / final result 全部继续复用 `04.runtime_workspace` 已冻结的 Agent Tool 回程语义; +4. Hermes Adapter 必须把这些回程都视作“同一条 Tool 调用”的不同阶段,而不是新的协议族。 + +该结论已回填 [04A.agent_tool_route.md](04A.agent_tool_route.md) 的“协议冻结”和“完成标准”。 + +## D04A-02:`clarify` 的 `multi_select` 变体没有唯一处理规则 + +**已解决(2026-08-07,收敛决议)。** 主定义已回填:本轮 `clarify` 只兼容单选、`other -> input` 和纯文本澄清;若 Hermes 发起 `multi_select = true`,Adapter 必须稳定拒绝,不得静默降级成单选或自由文本。 + +### 为什么这是 P2 + +Hermes 的 `clarify` 并不只有一种形态。当前 `04A` 已决定兼容 `clarify`,但如果不明确 `multi_select` 的处理方式,至少会出现以下风险: + +```text +实现 A:把 multi_select 静默改成单选 + -> 用户损失语义,Agent 得到错误决策结果。 + +实现 B:改成自由文本输入,让用户自己拼多个选项 + -> Host、Adapter 和 Hermes 对返回值结构无法形成唯一约定。 + +实现 C:某些平台支持,某些平台直接忽略 + -> 同一协议在不同 Adapter 上表现不一致,验收不可复现。 +``` + +这虽然不影响本轮 MiniApp Tool 通路的最小闭环,但会直接影响 Hermes 交互兼容层的实现一致性与验收,因此必须在迭代设计阶段给出唯一规则。 + +### 收敛决议 + +本轮已确认: + +1. `clarify` 的兼容范围冻结为单选 `choice`、`other -> input` 的二段式澄清,以及纯文本输入型澄清; +2. `multi_select = true` 不属于 `04A` 的兼容范围; +3. 若 Hermes 发起该变体,Adapter 必须返回受控 unsupported / not_supported 结果; +4. 不允许静默降级、隐式拆分成多轮单选,或把多选伪装成自由文本。 + +该结论已回填 [04A.agent_tool_route.md](04A.agent_tool_route.md) 的“兼容层上限”“冻结规则”“范围内”和“完成标准”。 + +## 评审关闭条件 + +1. D04A-01 与 D04A-02 已回填 [04A.agent_tool_route.md](04A.agent_tool_route.md); +2. 后续 `02.technical_implementation_spec.md` 必须把这两项设计决议继续落到协议解析、白名单、错误码、测试夹具与验收证据; +3. 本评审未发现需要延期保留的 P4 / P5 事项;后续若出现新的平台私有交互类型,应通过新的评审记录进入,而不是直接扩充 `04A` 主定义; +4. 本评审结论不替代主定义,实施仍以 [04A.agent_tool_route.md](04A.agent_tool_route.md) 为唯一权威。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/02.design_review.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/02.design_review.md new file mode 100644 index 0000000..daa9d87 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/02.design_review.md @@ -0,0 +1,116 @@ +# 04A.agent_tool_route 设计评审(二) + +**评审编号:** 02 +**日期:** 2026-08-07 +**评审对象:** [04A.agent_tool_route.md](04A.agent_tool_route.md)(实施权威)、[02.technical_implementation_spec.md](02.technical_implementation_spec.md)、[04.runtime_workspace.md](../04.runtime_workspace/04.runtime_workspace.md)、[05.technical_implementation_spec.md](../04.runtime_workspace/05.technical_implementation_spec.md)、[运行时与智能体工具.md](../../设计/02.正式方案/运行时与智能体工具.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md)、[app_final_design.md](../../设计/02.正式方案/app_final_design.md)、[lineup-ui-surface-protocol.md](../../设计/02.正式方案/lineup-ui-surface-protocol.md)。 +**评审方法:** 以 `04A` 主定义与实施规范为当前候选方案,联合对照 `04.runtime_workspace` 已冻结的实现边界,以及 `02.正式方案` 中 Runtime / Tool / Inventory / Surface 的正式契约,重点检查协议命名、Inventory 结构、Tool 回程语义与分层责任是否一致。 +**总体结论:** 联合评审确认 `04A` 的方向正确,仍然是 Adapter 层补齐而不是重写 Runtime;但原稿中有两处会和正式方案产生实现分叉的 P1 冲突:其一,把 Agent Tool 绑定到 `client.inventory.payload.applications[].tools`,与正式方案“Inventory 同时包含 App、Tool、Surface、Capability,且 Tool 是独立投影”的方向不一致;其二,新增 `lineup.v1.miniapp.tool.call`,会把正式方案中的统一 Agent Tool 模型拆成第二套 MiniApp 私有调用协议。两项问题均已在本次评审中回填:`04A` 现改为消费独立 Tool inventory 投影,并以 `lineup.v1.tool.invoke` 作为正式方案统一 Agent Tool invoke 的当前版本化落地;标准交互兼容入口 `lineup.v1.tool.call(choice | confirm | input)` 继续保留,但不再承担 Runtime Agent Tool invoke 语义。当前无未解决的 P0~P3 问题,`04A` 可以继续进入实现或验收准备。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施权威;✅ 已解决;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须记录延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P1 | D04A-03 | `04A` 原稿把 MiniApp Tool 绑定到 `client.inventory.payload.applications[].tools`,与正式方案中 Inventory 的 App / Tool / Surface / Capability 独立边界不一致。 | 已确认并回填:`applications` 继续只表达 App / Surface 可见性;Agent Tool 改为独立 Tool 投影进入 inventory,Hermes Adapter 只消费该最小 Tool 投影。 | +| ✅ | P1 | D04A-04 | `04A` 原稿新增 `lineup.v1.miniapp.tool.call`,会把正式方案中的统一 Agent Tool 模型拆成第二套 MiniApp 私有调用协议。 | 已确认并回填:不再引入 `miniapp.tool.call`;本轮以 `lineup.v1.tool.invoke` 作为正式方案统一 Agent Tool invoke 的版本化落地。`lineup.v1.tool.call` 继续仅作为 Interact 标准交互兼容入口。 | + +## 已确认的一致性 + +### `04A` 继续服从 `04.runtime_workspace` 已冻结的 Tool / Runtime 边界 + +`04.runtime_workspace` 和其技术实施规范已经冻结:Pomodoro 的 `pomodoro.start` / `pomodoro.interrupt` 都是 Runtime 发布给 Agent 的统一 Agent Tool;Runtime 负责 Tool Router、activation、foreground gating、operation 终态和 outbox;MiniApp 只声明 Tool、接收投影、写自己的 session data。 +本次联合评审确认,`04A` 不应重新发明第二套 MiniApp Tool 平面,而应只补 Hermes 如何理解和调用这套已存在的 Runtime Tool 模型。 + +### 标准交互与 Agent Tool 仍是两条不同的协议语义 + +`03.sdk_and_coreapp` 已经把 `lineup.v1.tool.call(choice | confirm | input)` 冻结为 Interact 的标准交互兼容入口。 +正式方案中的 Agent Tool invoke 则是 Runtime 面向 Agent 的统一工具调用模型。两者都可复用 `call_id`、receipt、result 等概念,但不能混成同一种“Tool Call”来让实现自行猜测。 + +### `lineup-app-server` 仍不需要进入本轮语义改造 + +本次联合评审没有发现必须让 `lineup-app-server` Go 服务理解 Hermes 私有协议、Inventory Tool 投影或 Tool invoke 语义的证据。 +因此,`04A` 仍然应把改动集中在 Hermes Adapter、Prompt 约束、协议校验与测试证据,不扩大到传输层。 + +## D04A-03:Inventory 把 Tool 塞进 `applications[].tools` 与正式方案冲突 + +**已解决(2026-08-07,收敛决议)。** `04A.agent_tool_route.md` 与 `02.technical_implementation_spec.md` 已回填:Hermes Adapter 不再把 Agent Tool 绑定到 `applications[].tools`;Inventory 中的 `applications` 继续只表达 App / Surface 可见性,Tool 改为独立顶层投影发布。 + +### 为什么这是 P1 + +正式方案已经给出清晰方向: + +- [lineup-ui-surface-protocol.md](../../设计/02.正式方案/lineup-ui-surface-protocol.md) 当前把 `applications` 定义为“已启用 app 的 surface 可见性”,不含 Tool; +- [lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md) 与 [app_final_design.md](../../设计/02.正式方案/app_final_design.md) 明确 Runtime Inventory 同时包含 App、Tool、Surface 与 Capability; +- [04.runtime_workspace/05.technical_implementation_spec.md](../04.runtime_workspace/05.technical_implementation_spec.md) 则已经把 Tool 作为从 Registry 独立投影到 Runtime Tool Registry / Agent Inventory 的对象。 + +如果 `04A` 继续把 Tool 内嵌进 `applications[].tools`,就会产生三种实现分叉: + +```text +实现 A:Hermes Adapter 按 applications[].tools 解析 + -> 与正式方案中的独立 Tool Inventory 不一致。 + +实现 B:Runtime 继续按独立 Tool Registry / Inventory 发布 + -> Hermes Adapter 看不到 Tool,退回 revision=none。 + +实现 C:为适配 Hermes 再改 Runtime Inventory shape + -> 会反向破坏 04.runtime_workspace 已冻结的 Registry / Tool Router 方向。 +``` + +这属于会直接导致模块间协议不兼容的 P1。 + +### 收敛决议 + +本轮已确认: + +1. `applications` 继续只承载 App / Surface 可见性; +2. Agent Tool 改为独立 Tool 投影进入 inventory; +3. Hermes Adapter 只保存规划与出站校验所需的最小 Tool 投影; +4. `04A` 不要求修改 `04.runtime_workspace` 已冻结的 Runtime Tool Registry / Agent Inventory 方向。 + +该结论已回填 [04A.agent_tool_route.md](04A.agent_tool_route.md) 与 [02.technical_implementation_spec.md](02.technical_implementation_spec.md)。 + +## D04A-04:`miniapp.tool.call` 会把统一 Agent Tool 平面拆成两套协议 + +**已解决(2026-08-07,收敛决议)。** `04A` 主定义与实施规范已回填:不再引入 `lineup.v1.miniapp.tool.call`,而是以 `lineup.v1.tool.invoke` 作为正式方案统一 Agent Tool invoke 的当前版本化落地;`lineup.v1.tool.call(choice | confirm | input)` 继续只承载标准交互兼容入口。 + +### 为什么这是 P1 + +正式方案和 `04.runtime_workspace` 一直在强调同一个事实: + +- Agent Tool 是 Runtime 面向远端 Agent 的统一调用模型; +- Tool invoke、receipt、progress、result、activation_ready 等都属于这条统一调用链; +- `choice / confirm / input` 是 Interact 标准交互原语,不等于 MiniApp 业务 Tool。 + +如果 `04A` 为 MiniApp Tool 新增 `lineup.v1.miniapp.tool.call`,就会把“统一 Agent Tool”拆成以下两套协议: + +```text +一套:标准交互兼容入口 lineup.v1.tool.call +一套:MiniApp 业务 Tool lineup.v1.miniapp.tool.call +``` + +这样会直接带来三类分叉风险: + +1. Hermes Adapter、Runtime、验收脚本要同时维护两套 Agent Tool 调用语义; +2. `04.runtime_workspace` 已冻结的 Tool receipt / progress / result 语义会被误解成“只对 miniapp.tool.call 生效”; +3. 后续非 MiniApp 但仍属于 Runtime Agent Tool 的能力,会不知道该走哪条调用协议。 + +这同样属于必须在实现前冻结的 P1。 + +### 收敛决议 + +本轮已确认: + +1. 不再新增 `lineup.v1.miniapp.tool.call`; +2. Runtime Agent Tool 统一走 `lineup.v1.tool.invoke`; +3. `lineup.v1.tool.call(choice | confirm | input)` 继续只作为 `03.sdk_and_coreapp` 遗留兼容入口; +4. `tool.invoke` 的 receipts / progress / final result 继续完全复用 `04.runtime_workspace` 已冻结的 Agent Tool 回程语义。 + +这等价于把正式方案中的统一 Agent Tool invoke 模型,在 Adapter 当前实现中落成一个版本化 envelope,而不是再分出一条 MiniApp 私有通路。 + +## 评审关闭条件 + +1. D04A-03 与 D04A-04 已回填 [04A.agent_tool_route.md](04A.agent_tool_route.md) 与 [02.technical_implementation_spec.md](02.technical_implementation_spec.md); +2. 后续实现必须同步更新 Hermes Adapter 的协议常量、Prompt 文案、inventory parser 与测试夹具,避免代码层仍遗留 `applications[].tools` 或 `miniapp.tool.call`; +3. 若后续需要修改正式方案中的 Inventory JSON shape 或 Agent Tool invoke 命名,应先回写 `02.正式方案`,而不是在迭代文档中继续引入第三套局部协议; +4. 本评审未发现需要延期保留的 P4 / P5 事项;当前无未解决 P0~P3。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/02.technical_implementation_spec.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/02.technical_implementation_spec.md new file mode 100644 index 0000000..02e4b10 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/02.technical_implementation_spec.md @@ -0,0 +1,344 @@ +# 04A.agent_tool_route 技术实施规范 + +**状态:** 开发实施基线 +**日期:** 2026-08-07 +**实施权威:** [04A.agent_tool_route.md](04A.agent_tool_route.md) +**评审基线:** [01.design_review.md](01.design_review.md) +**前置实现:** [04.runtime_workspace.md](../04.runtime_workspace/04.runtime_workspace.md)、[05.technical_implementation_spec.md](../04.runtime_workspace/05.technical_implementation_spec.md) + +## 1. 目的、边界与强制约束 + +本规范将 `04A.agent_tool_route` 已冻结的设计映射为“**Hermes Adapter 为主、Runtime 请求侧最小兼容补丁为辅**”的开发任务。实现必须以本文件和主迭代定义共同为准;若两者出现冲突,以主迭代定义为准,并先补充设计评审,不能在代码中自行发明新的协议分支。 + +`04A` 的目标不是重写 Runtime,而是补齐 Agent / Adapter 入口,使 `04.runtime_workspace` 已实现的 Runtime / Pomodoro / Host 能通过 Hermes 稳定使用。 + +以下约束不可违反: + +1. Runtime Agent Tool 必须继续使用统一的 invoke 通路,不再为 MiniApp Tool 另起第二套协议;本轮以 `lineup.v1.tool.invoke` 作为 Adapter 侧的版本化落地,它统一的是**请求入口**。回程继续复用 `04.runtime_workspace` 已冻结且当前 Runtime 仍在发送的 Agent Tool 回程事实,并始终以同一个 `call_id` 关联。 +2. Hermes Adapter 只能基于当前 conversation 最近一次有效 inventory 中声明的独立 Tool 投影发出 `tool.invoke`;不允许模型伪造 `instance_id`、`operation_id`、`app_session_id` 或其他 Runtime 内部目标字段。 +3. `app.call` 继续只表示 Host capability 请求;MiniApp Tool 不得复用 `app.call`,也不得通过 capability 语义绕过 Runtime 的 Tool schema 与 lifecycle 裁决。 +4. Hermes 私有 approval / clarify / update prompt 的兼容责任位于 Adapter 层;Runtime 和 `lineup-app-server` 不新增 Hermes 特判,但 Runtime 允许补最小的 `tool.invoke` 请求兼容,不视为 Hermes 特判。 +5. `clarify` 在本轮只兼容单选、`other -> input` 的二段式澄清和纯文本澄清;若 Hermes 发起 `multi_select = true`,Adapter 必须稳定拒绝,不得静默降级。 +6. 用户交互回传必须继续走现有 `tool.result` / `tool.cancel` / `app.result` 消息链路;不设计公网 webhook 或平台回调 URL。 +7. 现有 `lineup.v1.tool.call`、`lineup.v1.app.call`、`lineup.v1.ui.open / patch / close`、`lineup.v1.artifact.offer` 路径不得回归;其中 `lineup.v1.tool.call` 继续只承担 Interact 标准交互兼容入口,不与 Runtime Agent Tool invoke 混同。 +8. 正式方案中出现的 `tool.invoke` / `tool.result` 若未带 `lineup.v1.*` 前缀,应按“统一 Agent Tool 概念名”理解;本轮实际实现、fixture、prompt 示例和验收证据一律以 `lineup.v1.*` 当前兼容信封为准,不得再并行引入裸 `tool.invoke` 变体。 +9. `client.inventory` 中 Tool 独立投影的权威来源是 Runtime 正式方案与 `04.runtime_workspace` 已冻结实现;`lineup-ui-surface-protocol.md` 中较早的 inventory 示例只可视作 Surface / Capability 最小示意,不能据此否定 `tools[]` 顶层投影。 +10. 若某类 Hermes contract 在当前接入路径下缺少安全、稳定、可重复的真实触发源,则验收必须将其记录为“结构性证据限制”,而不是继续当作当前代码缺陷无限挂起;不得为补证据而把 Hermes 私有语义下沉到 Runtime 或 `lineup-app-server`。 + +## 2. 现有基线与改动边界 + +本轮实现主场在 `lineup-adapter/hermes/lineup/`,并允许对 `lineup-app/tauri/src/runtime/` 做最小请求兼容补丁。`lineup-app-server` Go 服务不承担本轮协议语义改造,除非后续验收证据证明传输层存在硬性阻塞。 + +| 模块 | 当前责任 | 04A 必须补齐 | +|---|---|---| +| `protocol.py` | LineUp envelope 常量、出站白名单、`client.inventory` / `tool.result` / `app.result` 等协议校验 | 扩展 inventory 解析以接受独立 Tool 投影;新增 `TOOL_INVOKE` 常量、解析与规范化;定义 Agent Tool 最小投影与严格字段校验。 | +| `core.py` | Prompt 组装、Agent 出站内容收敛、用户输入回注、ACP 会话驱动 | 将 Agent Tool 注入 prompt;允许并规范化 `tool.invoke` 出站;接收既有 Tool 回程作为同一 `call_id` 的 receipts / progress / final result;增加 Hermes 私有交互到 LineUp 标准交互的路由。 | +| `acp_client.py` | ACP stdio transport;当前对 `session/request_permission` 直接取消 | 将稳定的 Hermes 交互请求转成 Adapter 内部的受控 interactive request,而不是一律 `cancelled`。 | +| `state.py` | inbox / outbox、standard interaction ledger、capability ledger | 视实现需要增加一张 Hermes interactive request 账本或复用现有 Tool/App call ledger,保证 approval / clarify / confirm 是 one-shot、可过期、可拒绝、可恢复的。 | +| `tests/test_protocol.py` | 协议解析与规范化测试 | 覆盖 inventory tools 投影、`tool.invoke` 严格校验、非法字段拒绝、`clarify multi_select` 拒绝。 | +| `tests/test_core.py` | Adapter 运行时行为与隔离测试 | 覆盖 prompt 中 Agent Tool 注入、`tool.invoke` 出站收敛、Hermes 交互兼容闭环、过期 / 重复回传与恢复路径。 | +| `tests/test_acp_client.py` | ACP transport 与更新流测试 | 覆盖 `session/request_permission`、clarify / confirm / update prompt 的内部事件投影和受控拒绝。 | +| `tauri/src/runtime/inventory/client-inventory.ts`、`app-management/miniapp-sdk.ts`、`app-management/app-registry.ts` 及对应测试 | Runtime 把已启用且 Agent 可见的声明投影为 `client.inventory` | `applications[]` 只保留 App / Surface 可见性;从 SDK Declaration 经 Manifest / Registry 发布顶层 `tools[]`,其中必须包含 `app_scope`、`tool_id`、受控 description、`input_schema`、handling、delivery 与 activation requirement。 | +| `tauri/src/runtime/protocol/lineup-v1.ts`、`coordination/tool-router.ts` 及对应测试 | Runtime 当前协议解析与 Agent Tool 路由 | 最小化接受 `lineup.v1.tool.invoke` 请求入口;保持既有 `04.runtime_workspace` 执行、持久化与回程行为不变。 | + +## 3. 目标 A:MiniApp Tool 通路补齐 + +### 3.1 `client.inventory` 的 Adapter 投影 + +`protocol.py` 中的 `parse_client_inventory(...)` 必须从“只接受 App / Surface / Capability 的旧投影”扩展为接受与正式方案一致的**独立 Tool inventory 投影**。 +解析后仍然只保留供 Hermes 规划和出站校验使用的**最小投影**,不得把 Bundle、路径、handler、secret 或宿主实现细节放进 prompt 或持久上下文。 + +建议冻结的最小投影结构如下: + +```ts +type AdapterMiniAppToolProjection = { + app_scope: string; + tool_id: string; + description: string; + input_schema: JsonObject; + handling: "direct" | "interactive" | "launch" | "foreground" | "operation"; + delivery: "miniapp_sdk" | "runtime"; + activation_requirement: "activation_not_required" | "foreground_required" | "app_ready"; +}; +``` + +实现约束: + +1. `applications[]` 继续只保留 `id`、`version`、`surfaces` 等 App / Surface 可见性信息; +2. `tools[]` 作为独立顶层投影发布;每个 tool 只接受受控字段集合; +3. 任意未知字段、可执行内容、URL、bundle path、内部 handler key、输出 schema、secret 都必须拒绝; +4. 任意 inventory 解析失败时,整个 inventory 视为无效,Hermes 继续退回 `revision = none` 默认上下文; +5. 任意成功解析的 inventory 都必须带着其 `revision` 一起进入 conversation 级上下文缓存。 + +### 3.2 `lineup.v1.tool.invoke` 的协议规范化 + +`protocol.py` 新增: + +```python +TOOL_INVOKE = "lineup.v1.tool.invoke" +``` + +并将其纳入 Agent 允许输出的受控协议集合,但仅在通过严格校验后才能真正出站。 + +建议冻结的最小 payload: + +```json +{ + "v": 1, + "type": "lineup.v1.tool.invoke", + "payload": { + "call_id": "call_...", + "inventory_revision": "inv_...", + "app_scope": "pomodoro", + "tool_id": "pomodoro.start", + "input": { + "duration_seconds": 300 + } + } +} +``` + +Adapter 必须执行的首层校验: + +1. 顶层只允许 `call_id`、`inventory_revision`、`app_scope`、`tool_id`、`input`; +2. `call_id` 必须符合既有 identifier 规则; +3. `inventory_revision` 必须与当前 conversation 最近一次有效 inventory 一致; +4. `app_scope + tool_id` 必须存在于当前 inventory 投影中; +5. `input` 必须是 JSON object; +6. 顶层禁止 `sender`、`target`、`instance_id`、`operation_id`、`app_session_id`、`conversation_id`、`delivery` 覆盖等模型可伪造元数据; +7. Adapter 只做**顶层结构和 inventory 绑定校验**,不复制 Runtime 的最终 input schema、lifecycle 或业务状态机。 + +`core.py` 中对 Agent 输出的收敛逻辑必须新增一条: + +```text +raw model output + -> parse as tool.invoke + -> validate against current inventory projection + -> if valid: emit canonical lineup.v1.tool.invoke + -> if invalid: degrade to lineup.v1.text +``` + +### 3.3 Prompt 注入规则 + +`core.py` 组装 Hermes prompt 时,必须把当前 conversation 的 inventory 中可见的 Agent Tool 作为独立通路明确告知模型,至少覆盖以下规则: + +1. 只有 inventory 中声明的 MiniApp Tool 才可调用; +2. 必须使用最新 `inventory_revision`; +3. `app.call` 是 Host capability 请求,不是 MiniApp Tool 调用; +4. 当用户请求开始或停止专注时,应优先考虑 `pomodoro.start` / `pomodoro.interrupt`; +5. 不得猜测 Runtime 内部目标字段; +6. 不得把 approval / clarify 伪装成 `tool.invoke`。 + +建议在 prompt 中以“你可以返回的唯一结构化协议类型”方式列出三类请求: + +```text +lineup.v1.tool.call +lineup.v1.app.call +lineup.v1.tool.invoke +``` + +并对每种协议分别给出字段级示例,避免模型把三类 payload 混写。 + +### 3.4 回程协议归属 + +`04A` 不为 MiniApp Tool 再新增第三套结果协议。 +因此 `core.py` 与相关解析逻辑必须明确: + +1. `tool.invoke` 发出后,Runtime 返回的 `accepted / starting`、`started`、最终业务结果,继续按既有 Agent Tool 回程语义处理; +2. 这些回程全都以同一 `call_id` 关联; +3. Adapter 不得再发明新的本地回程 envelope 分支; +4. Runtime 当前已经存在的 MiniApp Tool 回程 envelope 继续按兼容事实处理,是否统一改名不属于本轮; +5. 验收证据中必须能证明:同一 `call_id` 在 start / interrupt 流程下可看到既有 receipts / progress / final result 行为。 + +### 3.5 Runtime 请求兼容补丁 + +`04.runtime_workspace` 已完成的 Runtime 代码与测试,当前主要按旧请求类型接收 MiniApp Tool 调用。 +为使 `04A` 的 `lineup.v1.tool.invoke` 真正端到端可用,本轮允许对 Runtime 增加一个窄兼容层,但必须满足以下约束: + +1. 只补 `lineup.v1.tool.invoke` 的 parser / router 接受能力; +2. 兼容层必须继续接受 `04.runtime_workspace` 既有请求类型,避免回归历史验收路径; +3. 不改写 Tool Router 的 schema / revision / lifecycle / persistence / outbox 语义; +4. 不借此把 Hermes 私有交互引入 Runtime; +5. 不在本轮把既有 MiniApp Tool 回程 envelope 大规模迁移到新名字。 + +## 4. 目标 B:Hermes 交互兼容层补齐 + +### 4.1 兼容层总原则 + +Hermes 私有交互在 Adapter 内部统一收口为两类: + +1. **标准交互**:发起 `lineup.v1.tool.call`,接收 `lineup.v1.tool.result` / `lineup.v1.tool.cancel` +2. **宿主能力授权**:发起 `lineup.v1.app.call`,接收 `lineup.v1.app.result` + +Runtime 不理解 Hermes 私有 `prompt kind`;Host 也不接触 Hermes 内部 callback token。 +Adapter 必须自己维护“LineUp 交互 call_id / option_id”到“Hermes 内部待决请求”的受控映射。 + +### 4.2 Hermes contract 到 LineUp 协议的冻结映射 + +本轮只实现下表中的四类 Hermes contract: + +| Hermes contract | LineUp 发起协议 | 用户可见交互 | 用户回传协议 | Adapter 内部 resolve | +|---|---|---|---|---| +| `exec_approval` | `lineup.v1.tool.call` | `choice` | `tool.result` / `tool.cancel` | `once / session / always / deny` | +| `slash_confirm` | `lineup.v1.tool.call` | `choice`,必要时退化 `confirm` | `tool.result` / `tool.cancel` | `once / always / cancel` | +| `clarify` | `lineup.v1.tool.call` | 单选 `choice`、`other -> input`、纯文本 `input` | `tool.result` / `tool.cancel` | choice text / free text | +| `update_prompt` | `lineup.v1.tool.call` | `confirm` | `tool.result` / `tool.cancel` | `y / n` | + +不在本轮范围内的 Hermes 变体: + +- `clarify multi_select = true` +- 任意平台特有按钮布局 / 卡片视觉细节 +- 任意无法映射到上述四类 contract 的 Hermes 私有 system message + +### 4.3 `acp_client.py` 的内部事件投影 + +当前 `acp_client.py` 对 `session/request_permission` 直接取消。 +04A 需要把稳定的 Hermes 交互请求改投影为 Adapter 内部事件,供 `core.py` 消费。 + +建议引入受控内部结构: + +```ts +type PendingHermesInteraction = + | { kind: "exec_approval"; request_id: string; session_id: string; title: string; prompt: string; options: [...] } + | { kind: "slash_confirm"; request_id: string; session_id: string; title: string; prompt: string; options: [...] } + | { kind: "clarify"; request_id: string; session_id: string; title: string; prompt: string; choices?: [...]; allow_other: bool; expects_text: bool; multi_select: bool } + | { kind: "update_prompt"; request_id: string; session_id: string; title: string; prompt: string }; +``` + +实现要求: + +1. `acp_client.py` 只做 transport 与 Hermes 请求的最小投影,不在该层拼装 LineUp 协议; +2. 任意不属于四类已冻结 contract 的请求,直接返回稳定拒绝; +3. 任意 `clarify` 若为 `multi_select = true`,直接返回稳定拒绝; +4. 日志中仍不得落 Hermes 原始敏感参数、命令、路径或自由文本; +5. 所有投影都必须带有一个 Adapter 可追踪的内部 request id。 + +### 4.4 `core.py` 的交互收口与 resolve + +`core.py` 负责把内部 `PendingHermesInteraction` 转成真正对外的 LineUp 消息: + +```text +Hermes internal interactive request + -> adapter builds controlled lineup.v1.tool.call / app.call + -> host renders it + -> user returns tool.result / tool.cancel / app.result + -> adapter validates response against its own ledger + -> adapter resolves the original Hermes request +``` + +实现细则: + +1. `choice` 的 option id 由 Adapter 生成,不能复用 Hermes 或平台原生 callback token; +2. `clarify` 若用户选择 `other`,Adapter 必须主动发起第二轮 `input`,而不是把“等待自由文本”状态泄露给 Runtime; +3. `tool.result` / `tool.cancel` 回来后,Adapter 只把最小结果注入 Hermes: + - approval / confirm 注入受控选项值 + - clarify 注入选中的 choice text 或用户输入文本 +4. 用户输入自由文本仍不得写入持久审计表;如需校验,只做内存内 schema / shape 校验; +5. 任意重复、过期或未知 `call_id` 的回传继续沿用现有 Tool/App ledger 的 one-shot 语义。 + +Host 对单选交互的标准结果可以使用 `result.action_id` 表示唯一选项;Adapter +必须在验证通过后将其规范化为内部 `action_ids: [action_id]` 形状,再执行既有 +映射和 Hermes resolve。对于多选、缺失选项、未知选项、重复选项或非字符串选项, +仍必须拒绝,不能因为兼容 Host 形状而放宽交互边界。 + +### 4.5 状态与持久化 + +`state.py` 现有 `tool_calls` / `app_calls` 账本已经覆盖 standard interaction 和 Host capability 的 one-shot 语义。 +本轮有两种可接受实现方式: + +1. **推荐**:兼容层发起的所有 Hermes 交互都继续复用现有 `tool_calls` / `app_calls` 账本,只新增一张轻量映射表保存 `call_id -> pending Hermes request metadata` +2. **备选**:新增独立 `hermes_interactions` 表,但仍复用现有 `tool_calls` / `app_calls` 处理用户回传的幂等、过期和审计 + +无论采用哪种方式,都必须满足: + +1. Adapter 重启后,不会把已发出的 approval / clarify 交互重复 resolve; +2. 未完成交互会按现有 `expires_at` 语义过期; +3. 审计表不保存用户自由文本、文件路径、URL、命令或 Hermes 原始私有 token; +4. 同一 `call_id` 至多 resolve 一次。 + +### 4.6 当前 ACP 接入路径的证据边界 + +本轮真实实现与联调基线是 `1420 页面 + hermes acp`。 +在这条路径下,Adapter 当前可以稳定接收到的 Hermes server request 已证明包括 `session/request_permission`,并可据此完成 `exec_approval` 的投影与闭环;同时,Adapter 主回复通路也已证明能够稳定发出 `choice / input`,从而覆盖 `clarify` 的单选、`other -> input` 与纯文本输入。 + +但截至 2026-08-07,`slash_confirm` 与 `update_prompt` 的原生触发源仍主要存在于 Hermes gateway / native platform adapter 路径: + +1. `slash_confirm` 由 gateway 的 `_request_slash_confirm(...)` 驱动,再调用平台 adapter 的 `send_slash_confirm(...)`; +2. `update_prompt` 由 gateway watcher 监听 `.update_prompt.json`,再调用平台 adapter 的 `send_update_prompt(...)` 或回退文本提示; +3. 当前 ACP client 没有与这两类 contract 对应的稳定 server request method 投影入口。 + +因此本规范要求: + +1. `slash_confirm` 与 `update_prompt` 继续保留在 Adapter 目标映射表中; +2. 但若当前 ACP 路径缺少稳定触发源,不得为了本轮验收临时向 Runtime 或 `lineup-app-server` 注入 Hermes 私有桥接; +3. 验收应输出结构性限制记录,并给出后续 bridge / trigger 承接建议; +4. 只有在新增可控触发源后,才重新要求这两项的 fresh runtime evidence。 + +## 5. 测试与验收实现要求 + +### 5.1 `protocol.py` 自动化测试 + +至少新增以下用例: + +1. `client.inventory` 接受包含独立 `tools[]` 投影的受控定义; +2. inventory 中含未知 tool 字段、handler、bundle/path/secret 时整体拒绝; +3. 合法 `tool.invoke` 能被规范化; +4. `tool.invoke` 缺少 revision、tool 不存在、顶层多字段、伪造内部 target 字段时拒绝; +5. `tool.invoke` 不会升级成 `app.call` 或其他 envelope; +6. `clarify multi_select = true` 被稳定拒绝。 + +### 5.2 `core.py` 自动化测试 + +至少新增以下用例: + +1. prompt 中包含当前 inventory 的 Agent Tool 投影; +2. 用户请求开始 / 停止专注时,模型合法输出可被收敛为 `lineup.v1.tool.invoke`; +3. 非法 `tool.invoke` 降级为 `text`; +4. approval / slash confirm / clarify / update prompt 能映射成标准 `tool.call`; +5. `clarify other -> input` 走二段式闭环; +6. `multi_select` 请求被稳定拒绝; +7. 既有 `tool.call`、`app.call`、`ui.*`、`artifact.offer` 路径不回归。 + +### 5.3 `acp_client.py` 自动化测试 + +至少新增以下用例: + +1. `session/request_permission` 不再一律直接取消,而是被投影为受控内部交互请求; +2. 不支持的 Hermes 请求会稳定拒绝; +3. ACP reader 线程不会把原始敏感参数写进日志; +4. adapter 关闭、超时、Hermes 退出时,pending request 能稳定收口。 + +### 5.4 Runtime 兼容测试 + +至少新增以下用例: + +1. Runtime 协议 parser 接受 `lineup.v1.tool.invoke`; +2. Tool Router 能把 `lineup.v1.tool.invoke` 路由到与旧请求类型相同的 MiniApp Tool 路径; +3. 至少一条普通 MiniApp Tool 路径和一条 Pomodoro 路径通过 `lineup.v1.tool.invoke` 跑通; +4. 旧请求类型的兼容测试不回归。 + +### 5.5 Fresh Evidence + +`03.acceptance_review.md` 必须至少记录以下 fresh evidence: + +1. 真实用户语言触发 `pomodoro.start`,Hermes 输出 `tool.invoke`,Runtime 成功执行; +2. 真实用户语言触发 `pomodoro.interrupt`,Hermes 输出 `tool.invoke`,Runtime 成功执行; +3. 至少一种 Hermes approval / confirm / clarify 交互通过 LineUp 消息链路完成闭环; +4. 至少一种不在本轮范围内的 Hermes 请求被稳定拒绝,且未形成隐式授权。 + +补充约束: + +- `slash_confirm` / `update_prompt` 只有在当前接入路径存在稳定触发源时,才要求 fresh runtime evidence; +- 若当前接入路径不存在稳定触发源,则必须在验收文档中记录源码对照、运行期现象与后续 bridge 建议,作为关闭本轮验收时的正式结论之一。 + +## 6. 实施顺序建议 + +建议按以下顺序推进,避免多模块同时漂移: + +1. 先改 `protocol.py`,冻结 inventory tools 投影和 `tool.invoke` 校验; +2. 再改 `core.py` prompt 与出站收敛,让 Pomodoro start / interrupt 通路能先跑通; +3. 再补 Runtime 对 `lineup.v1.tool.invoke` 的最小 parser / router 兼容与对应测试; +4. 再改 `acp_client.py` 和 `core.py` 的交互兼容层,完成 approval / clarify / confirm 闭环; +5. 最后补 `state.py`、自动化测试和 fresh evidence。 + +本阶段不要求改动 `lineup-app-server`。若联调时发现传输层对新 envelope 有硬阻塞,应先补设计评审,再决定是否将其升级为 `04A` 范围内改动。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/03.acceptance_review.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/03.acceptance_review.md new file mode 100644 index 0000000..397eb4e --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/03.acceptance_review.md @@ -0,0 +1,163 @@ +# 04A.agent_tool_route 验收评审(中间记录,已被后续评审收敛) + +**状态:** 中间记录;最终以 [05.acceptance_review.md](05.acceptance_review.md) 为准 +**日期:** 2026-08-07 +**评审对象:** `lineup-adapter/hermes/lineup/` 当前实现、[04A.agent_tool_route.md](04A.agent_tool_route.md)、[02.technical_implementation_spec.md](02.technical_implementation_spec.md) +**评审口径:** 以 `04A` 主定义和实施规范为准,先记录当时已经拿到的自动化证据与已闭环子能力,再明确尚未满足的最终验收项。本文不是最终完成声明;后续 fresh runtime evidence、结构性限制记录与最终关闭结论,均已收敛到 [05.acceptance_review.md](05.acceptance_review.md)。 + +## 1. 当前已验证的实现 + +### 1.1 Runtime Agent Tool invoke 的 Adapter 侧基线已通过自动化测试 + +已具备并已由自动化测试覆盖的能力: + +- `client.inventory` 接受独立 `tools[]` 顶层投影; +- `lineup.v1.tool.invoke` 已进入 Hermes Adapter 允许输出集合; +- `tool.invoke` 会绑定当前 conversation 的最新 `inventory_revision`; +- 非法 `tool.invoke` 会被拒绝或降级,不允许伪造 Runtime 内部目标字段。 + +对应测试命令: + +```bash +npm test -- \ + src/runtime/app-management/miniapp-sdk.test.ts \ + src/runtime/app-management/reference-miniapps.test.ts \ + src/runtime/app-management/app-registry.test.ts \ + src/runtime/inventory/client-inventory.test.ts \ + src/runtime/coordination/tool-router.test.ts \ + src/runtime/coordination/lineup-runtime.test.ts +``` + +2026-08-07 当前结果: + +```text +Test Files 6 passed (6) +Tests 71 passed (71) +``` + +当前已通过自动化测试验证的点: + +- Runtime `client.inventory` 以顶层 `tools[]` 发布 Agent Tool,不再把 Tool 塞进 `applications[].tools`; +- Tool projection 从 MiniApp SDK Declaration 经 Manifest / Registry 产生,保留 `app_scope`、`tool_id`、description、`input_schema`、handling、delivery 与 activation requirement; +- Pomodoro 的 `pomodoro.start` / `pomodoro.interrupt` 会进入顶层投影;禁用、升级、卸载会递增 revision 并撤销对应 Tool; +- inventory 不泄露 `output_schema`、permissions、handler、bundle 或本地实现信息。 + +### 1.2 Hermes Adapter 已通过自动化测试消费同一顶层 Tool projection + +对应测试命令: + +```bash +python3 -m unittest \ + lineup-adapter/hermes/lineup/tests/test_protocol.py \ + lineup-adapter/hermes/lineup/tests/test_core.py \ + lineup-adapter/hermes/lineup/tests/test_acp_client.py +``` + +2026-08-07 当前结果: + +```text +Ran 32 tests in 0.022s +OK +``` + +已具备并已由自动化测试覆盖的能力: + +- `client.inventory` 接受独立 `tools[]` 顶层投影; +- `lineup.v1.tool.invoke` 已进入 Hermes Adapter 允许输出集合; +- `tool.invoke` 会绑定当前 conversation 的最新 `inventory_revision`; +- 非法 `tool.invoke` 会被拒绝或降级,不允许伪造 Runtime 内部目标字段。 + +### 1.3 Runtime 已接受 `lineup.v1.tool.invoke` 作为最小请求兼容入口 + +当前已拿到的 Runtime 自动化证据表明,`04A` 不再只是 Adapter 内部自洽,而是已经把新的请求入口真正接到了 `04.runtime_workspace` 既有执行链路上。 + +当前已通过自动化测试验证的点: + +- Runtime parser 接受 `lineup.v1.tool.invoke`; +- Tool Router 会把 `lineup.v1.tool.invoke` 路由到与旧请求类型相同的 MiniApp Tool 路径; +- 至少一条普通 bundled MiniApp Tool 路径已经通过 `lineup.v1.tool.invoke` 跑通; +- `pomodoro.start` 已通过 `lineup.v1.tool.invoke` 跑通 activation、foreground 确认、deadline 结算与最终结果; +- `pomodoro.interrupt` 已通过 `lineup.v1.tool.invoke` 跑通短 Tool 自身结果,以及对长 `pomodoro.start` 的中断收口; +- Runtime 对旧请求类型的兼容测试没有因此回归。 + +### 1.4 Hermes 交互兼容层的统一状态机已落地 + +本轮已在 Adapter 内实现并通过自动化测试验证以下兼容闭环: + +```text +Hermes ACP session/request_permission + -> Adapter 投影为内部 permission request + -> Adapter 发出 lineup.v1.tool.call(choice) + -> 用户回传 lineup.v1.tool.result + -> Adapter resolve 回 ACP permission outcome + +Adapter 内部 interactive request + -> Adapter 发出 lineup.v1.tool.call(choice | confirm | input) + -> 用户回传 lineup.v1.tool.result / tool.cancel + -> Adapter resolve 回 Hermes 内部 interaction outcome +``` + +当前已通过自动化测试验证的点: + +- ACP permission request 会被解析为受控请求,而不是一律 `cancelled`; +- Adapter 会为该请求生成标准 `tool.call(choice)`; +- `tool.result` 中的受控选项会被映射回 ACP `optionId`; +- 该交互会复用现有 `tool_calls` one-shot ledger,并配套持久化 `hermes_interactions` 映射; +- Adapter 重启恢复时,未完成的 Hermes 交互会被过期收口,而不是在新进程里继续假定可 resolve。 +- `slash_confirm` 已能通过 `tool.call(choice)` 生成并回收 `once / always / cancel` 一类受控结果; +- `update_prompt` 已能通过 `tool.call(confirm)` 生成并回收 `y / n` 一类受控结果; +- `clarify` 已能覆盖单选 `choice`、纯文本 `input`、以及 `other -> input` 的二段式路径; +- `clarify multi_select = true` 当前会被 Adapter 稳定拒绝,不向 Host 发出不受支持的交互请求。 + +### 1.5 当前已新增的实现文件 + +- [acp_client.py](/home/gao/Development/lineup/lineup-adapter/hermes/lineup/acp_client.py) +- [core.py](/home/gao/Development/lineup/lineup-adapter/hermes/lineup/core.py) +- [state.py](/home/gao/Development/lineup/lineup-adapter/hermes/lineup/state.py) +- [test_acp_client.py](/home/gao/Development/lineup/lineup-adapter/hermes/lineup/tests/test_acp_client.py) +- [test_core.py](/home/gao/Development/lineup/lineup-adapter/hermes/lineup/tests/test_core.py) +- [client-inventory.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/inventory/client-inventory.ts) +- [miniapp-sdk.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/app-management/miniapp-sdk.ts) +- [app-registry.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/app-management/app-registry.ts) +- [tool-router.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/tool-router.ts) +- [lineup-v1.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/protocol/lineup-v1.ts) +- [tool-router.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/tool-router.test.ts) +- [lineup-runtime.test.ts](/home/gao/Development/lineup/lineup-app/tauri/src/runtime/coordination/lineup-runtime.test.ts) + +### 1.6 Fresh Runtime / Browser Evidence(2026-08-07) + +在重新安装当前工作树的 Hermes Adapter、重启 Adapter 并对 Host 执行强制刷新后,取得了新的真实运行期证据。该证据不是历史截图或旧消息: + +- Host 新发送的 `client.inventory` 消息序号为 `1201`,`revision = catalog-1`; +- 该 inventory 使用独立顶层 `tools[]` 投影,包含 `pomodoro.start`、`pomodoro.interrupt`、`task-dashboard.open/update` 和 `whiteboard.open/submit`; +- 用户消息序号 `1202` 为真实用户输入:`开始一个 5 分钟的专注`; +- 同一条启动调用 `call_id = call_5f9c2a7d1e` 收到 `accepted` 进度回执(消息序号 `1207`); +- 随后收到 `started` 进度回执(消息序号 `1209`),其中 `operation_id = pomodoro:operation_msiet305_zwa4ckb9f2`,并绑定到前台 Pomodoro instance; +- 用户提供的页面截图显示“专注工作区”“5分钟专注”,倒计时从 `05:00` 进入 `04:56`,证明前台工作区已真实打开并运行; +- 用户消息序号 `1219` 为真实用户输入:`停止当前专注`; +- 停止后收到两个 `lineup.v1.miniapp.tool.result` 终态: + - `call_id = call_7d3e9f2a1b`:Pomodoro interrupt 短工具结果为 `status = interrupted`; + - `call_id = call_5f9c2a7d1e`:原始 start 调用结果为 `state = interrupted`,同一个 `operation_id` 为 `pomodoro:operation_msiet305_zwa4ckb9f2`,`interruption_reason = agent_interrupt`; +- 两个终态均由 Runtime 发送并被 Adapter 收到,证明 start / interrupt 的请求、进度、终态和 operation 关联在真实消息链路中闭环。 + +## 2. 当前尚未证明完成的验收项 + +以下项目仍未被当前证据证明完成,因此 `04A` 不能视为已验收通过: + +- 虽然 `slash_confirm`、`clarify`、`update_prompt` 已具备 Adapter 侧自动化测试证据,但 Hermes ACP 当前是否会以可直接消费的真实请求形态发出这些 contract,尚未拿到运行期 fresh evidence; +- 至少一种“本轮范围外 Hermes 请求被稳定拒绝”的运行证据尚未补入。 + +## 3. 当前结论 + +`04A` 已从“仅完成协议设计和 invoke parser”推进到“Runtime 已发布 Adapter 可消费的顶层 Tool inventory,Adapter 侧 invoke 基线完成,Runtime 已接受 `lineup.v1.tool.invoke` 请求入口,Hermes 交互兼容层已有统一状态机并具备多类自动化证据”的阶段。 +但按主定义与实施规范的最终完成标准来看,本轮只能判定为: + +```text +Runtime Tool inventory:已从 SDK / Manifest / Registry 端到端发布顶层 tools[],并有 71 个 Runtime 定向测试证据 +MiniApp Tool invoke 基线:已具备 Adapter 侧实现与 32 个 Adapter 定向测试证据 +Runtime 请求兼容:已具备 lineup.v1.tool.invoke 的 parser / router / 高价值集成测试证据 +Hermes 交互兼容层:已完成 exec_approval、slash_confirm、clarify、update_prompt 的 Adapter 侧统一桥接与自动化覆盖 +Hermes ACP 真源验证:当前仍需补充除 request_permission / exec_approval 外的 contract,及一种范围外请求的真实拒绝证据 +真实 Pomodoro fresh evidence:已完成 start / interrupt;见 1.6 +整体 04A:Runtime inventory 与 Pomodoro start / interrupt 已完成真实 Host 验证;仍需补齐 Hermes 权限/交互真源和范围外请求拒绝证据,尚未完成最终验收 +``` diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/03.design_review.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/03.design_review.md new file mode 100644 index 0000000..469a6f5 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/03.design_review.md @@ -0,0 +1,87 @@ +# 04A.agent_tool_route 联合评审(三) + +**评审编号:** 03 +**日期:** 2026-08-07 +**评审对象:** [04A.agent_tool_route.md](04A.agent_tool_route.md)、[02.technical_implementation_spec.md](02.technical_implementation_spec.md)、[04.runtime_workspace.md](../04.runtime_workspace/04.runtime_workspace.md)、[05.technical_implementation_spec.md](../04.runtime_workspace/05.technical_implementation_spec.md)、[运行时与智能体工具.md](../../设计/02.正式方案/运行时与智能体工具.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md)、[app_final_design.md](../../设计/02.正式方案/app_final_design.md)、[lineup-ui-surface-protocol.md](../../设计/02.正式方案/lineup-ui-surface-protocol.md)。 +**评审方法:** 以 `04A` 主定义与技术实施规范为候选实现基线,联合对照 `04.runtime_workspace` 已冻结实现,以及 `02.正式方案` 中涉及 Runtime、Inventory、Tool invoke、Compatibility Adapter 的条目,重点检查是否仍存在命名分叉、Inventory shape 误读或责任边界回退。 +**总体结论:** 本轮联合评审未发现新的 Runtime / Adapter 架构冲突,`04A` 继续可以按 Adapter 侧补齐推进;但正式方案文档中还存在两处“历史表述与当前兼容实现并存”的误导点,需要在 `04A` 文档中明确消歧,否则实现者容易在协议名和 inventory shape 上走回旧分支。两项问题均已在本次评审中回填到 `04A` 主定义与技术实施规范:一是正式方案中的概念名 `tool.invoke` 需要在当前实现中统一落到 `lineup.v1.tool.invoke`;二是较早 `client.inventory` 示例未展示 `tools[]`,不能再被当作当前 Tool 投影的权威 shape。当前无未解决的 P0~P3 问题。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施权威;✅ 已解决;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须记录延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P2 | D04A-05 | 正式方案文档同时出现概念名 `tool.invoke` 与当前兼容信封 `lineup.v1.tool.invoke`,若 `04A` 不主动消歧,Adapter 实现、fixture 与验收可能各自使用不同名字。 | 已回填:`04A` 明确正式方案中的 `tool.invoke` 仅表示统一 Agent Tool 概念名;本轮实现、prompt、fixture、验收一律使用 `lineup.v1.tool.invoke`。 | +| ✅ | P2 | D04A-06 | `lineup-ui-surface-protocol.md` 的较早 `client.inventory` 示例未包含 `tools[]`,容易让实现者误以为 `04A` 的独立 Tool 投影 shape 与正式方案冲突。 | 已回填:`04A` 明确 Tool 独立投影的权威来源是 Runtime 正式方案和 `04.runtime_workspace`;较早 Surface 协议示例只作为 Surface / Capability 最小示意,不再作为 Tool shape 权威。 | + +## 已确认的一致性 + +### `04A` 与 `04.runtime_workspace` 的 Runtime 事实保持一致 + +`04.runtime_workspace` 已冻结:Runtime 是 Tool Router、activation、lifecycle、outbox、Tool receipt / progress / final result 的唯一裁决者;Adapter 只做 inventory 绑定、顶层结构校验和 Hermes 私有交互收口。 +本次复核没有发现 `04A` 重新把业务语义拉回 Adapter 或 `lineup-app-server` 的迹象。 + +### 正式方案允许当前实现继续使用 `lineup.v1.*` 兼容信封 + +[app_final_design.md](../../设计/02.正式方案/app_final_design.md) 已明确:当前 Tauri 实现与 golden fixture 仍使用 `lineup.v1.*` 类型,Runtime Compatibility Adapter 负责把旧实现映射到新 Runtime 边界。 +因此 `04A` 以 `lineup.v1.tool.invoke` 作为当前 Adapter 落地,与正式方案并不冲突;真正需要避免的是在当前实现里再并行制造“裸 `tool.invoke`”第二种 envelope。 + +### Tool 独立投影与较早 Surface inventory 示例不再矛盾 + +[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md) 和 [app_final_design.md](../../设计/02.正式方案/app_final_design.md) 都已把 Inventory 收敛为同时包含 App、Tool、Surface、Capability,且 Tool 调用必须携带 `inventory_revision`。 +[lineup-ui-surface-protocol.md](../../设计/02.正式方案/lineup-ui-surface-protocol.md) 中未展示 `tools[]` 的 `client.inventory` 示例,现应视作较早阶段围绕 Surface / Capability 的最小示意,而不是 04A 当前 Tool 投影的最终 JSON shape。 + +## D04A-05:`tool.invoke` 概念名与 `lineup.v1.tool.invoke` 当前信封并存,易造成实现分叉 + +**已解决(2026-08-07,收敛决议)。** `04A.agent_tool_route.md` 与 `02.technical_implementation_spec.md` 已补充说明:正式方案中的 `tool.invoke` / `tool.result` 若未带前缀,应按统一 Agent Tool 概念名理解;`04A` 代码、prompt、fixture、验收证据统一采用当前兼容信封 `lineup.v1.*`。 + +### 为什么这是 P2 + +如果不消歧,至少会出现以下风险: + +```text +实现 A:Adapter 代码使用 lineup.v1.tool.invoke +实现 B:测试夹具或 prompt 示例使用裸 tool.invoke +实现 C:验收人员按正式方案字面再补第三套命名解释 +``` + +这不会推翻 Runtime 架构,但会直接影响自动化测试、联调日志、验收口径和后续迁移路径,因此需要在当前迭代关闭。 + +### 收敛决议 + +1. 当前实现、prompt、fixture、验收证据统一使用 `lineup.v1.tool.invoke`; +2. 正式方案中的 `tool.invoke` 只作为“统一 Agent Tool invoke”概念名引用; +3. 若未来正式迁移到去前缀的新 Runtime Envelope,必须先回写正式方案和兼容迁移计划,不能在 `04A` 期间并行引入第二套可执行名字。 + +## D04A-06:较早 `client.inventory` 示例未展示 `tools[]`,容易误导 `04A` 的 Tool 投影判断 + +**已解决(2026-08-07,收敛决议)。** `04A.agent_tool_route.md` 与 `02.technical_implementation_spec.md` 已明确:`tools[]` 顶层投影的权威来源是 Runtime 正式方案与 `04.runtime_workspace` 已冻结实现;`lineup-ui-surface-protocol.md` 中较早示例不再作为 Tool shape 权威。 + +### 为什么这是 P2 + +当前正式方案文档内部其实存在时间差: + +- Runtime 架构和最终方案已经收敛到 “Inventory 同时包含 App、Tool、Surface、Capability”; +- 较早 Surface 协议示例仍只展示了 `standard_components + applications + capabilities`。 + +如果不明确权威顺序,后续实现者很容易错误得出以下结论: + +```text +04A 新增 tools[] = 偏离正式方案 +``` + +这会直接干扰 Adapter parser、测试夹具、评审判断和后续 Runtime 文档演进。 + +### 收敛决议 + +1. `04A` 当前以独立 `tools[]` 顶层投影为准; +2. `applications[]` 继续只表达 App / Surface 可见性; +3. 若后续要统一正式方案中的 inventory 示例,应在正式方案文档中统一回填,而不是让 `04A` 回退到 `applications[].tools` 或无 `tools[]` 的旧分支。 + +## 评审关闭条件 + +1. D04A-05 与 D04A-06 已回填 [04A.agent_tool_route.md](04A.agent_tool_route.md) 和 [02.technical_implementation_spec.md](02.technical_implementation_spec.md); +2. 后续代码实现、测试夹具和验收记录必须统一使用 `lineup.v1.*` 当前兼容信封; +3. 后续若补正式方案文档示例,应把 `client.inventory` 的 Tool 顶层投影同步回填到正式方案,而不是在 `04A` 再引入本地例外; +4. 本评审当前无未解决的 P0~P3。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/04.design_review.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/04.design_review.md new file mode 100644 index 0000000..a8beaf3 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/04.design_review.md @@ -0,0 +1,103 @@ +# 04A.agent_tool_route 联合评审(四) + +**评审编号:** 04 +**日期:** 2026-08-07 +**评审对象:** [04A.agent_tool_route.md](04A.agent_tool_route.md)、[02.technical_implementation_spec.md](02.technical_implementation_spec.md)、[04.runtime_workspace.md](../04.runtime_workspace/04.runtime_workspace.md)、[05.technical_implementation_spec.md](../04.runtime_workspace/05.technical_implementation_spec.md)、[运行时与智能体工具.md](../../设计/02.正式方案/运行时与智能体工具.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md)、[app_final_design.md](../../设计/02.正式方案/app_final_design.md),以及当前 `tauri/src/runtime/` 中与 Tool invoke / Tool Router / 回程 outbox 相关的实现。 +**评审方法:** 以 `04A` 主定义和实施规范为候选基线,联合对照 `04.runtime_workspace` 已冻结的 Runtime 行为、`02.正式方案` 中 Runtime Tool 模型的正式边界,以及当前 Tauri Runtime 的协议 parser / router / outbox 代码,重点检查“文档是否把 04A 的真实改动边界写实”以及“请求侧统一与回程侧兼容是否被错误混写”。 +**总体结论:** 本轮联合评审发现两处新的 P2 级口径冲突,都会直接影响实现范围和验收判断,但都不是架构方向错误:第一,`04A` 文档先前把工作描述成“仅 Adapter 补齐”,与当前 Runtime 仍主要按旧请求类型接收 MiniApp Tool 调用的实现事实不符;要让 `lineup.v1.tool.invoke` 真正打通,必须补一个最小 Runtime 请求兼容层。第二,`04A` 文档先前把“统一 Agent Tool invoke”表述得过宽,容易让实现者误以为本轮还要把 Runtime 既有 MiniApp Tool 回程 envelope 一并收敛。结合 `04.runtime_workspace` 已冻结实现与当前代码现状,更准确的结论应是:`04A` 统一的是**请求入口**,回程仍沿用 Runtime 已冻结的兼容事实,并由 Adapter 解释为同一条 `call_id` 的 receipts / progress / final result。两项问题均已在本次评审中回填到主定义与实施规范;当前无未解决的 P0~P3 问题。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施权威;✅ 已解决;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须记录延期原因和重新评估条件。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P2 | D04A-07 | `04A` 文档把实现口径写成“仅 Adapter 补齐”,但当前 Runtime 仍主要按旧请求类型接收 MiniApp Tool 调用;若不补一层 Runtime 请求兼容,`lineup.v1.tool.invoke` 无法真正端到端生效。 | 已回填:`04A` 改为“Adapter 为主、附带最小 Runtime 请求兼容补丁”;允许仅在 Runtime parser / router / 测试夹具上补 `lineup.v1.tool.invoke` 接受能力,不改写 `04.runtime_workspace` 已冻结的业务语义。 | +| ✅ | P2 | D04A-08 | `04A` 文档把“统一 Agent Tool invoke”描述得过宽,容易误导为本轮还要把 Runtime 既有 MiniApp Tool 回程 envelope 一并统一。 | 已回填:`04A` 当前统一的是请求入口;回程仍沿用 Runtime 已冻结且当前代码仍在发送的兼容事实,Adapter 只按同一 `call_id` 解释,不再新增第三套协议分支。 | + +## 已确认的一致性 + +### `app-server` 仍可保持透明传输层 + +本次复核没有发现 `lineup-app-server` 需要理解 `lineup.v1.tool.invoke` 业务语义的证据。当前阻塞并不在传输层,而在 Runtime 本地 parser / router 对新请求类型的接受能力。 +因此,用户此前关于“是否涉及 app-server”的判断仍成立:`04A` 不应扩大为 Go 服务端语义改造。 + +### `04A` 的 Runtime 改动属于兼容补丁,不是边界回退 + +`04.runtime_workspace` 已冻结的权威仍然是 Runtime 对 revision、schema、activation、foreground gating、operation、outbox 与恢复的唯一裁决。 +这次要求的 Runtime 变更,只是让它在**请求入口**上接受 `lineup.v1.tool.invoke`;不改变 Tool Router 的核心裁决,也不把 Hermes 私有 contract 拉进 Runtime。 + +### 正式方案与当前实现可以通过“概念层 / 兼容层”两级解释保持一致 + +`02.正式方案` 中的 `tool.invoke / tool.result / progress` 描述的是统一 Agent Tool 模型。当前 Tauri Runtime 则仍保留 `lineup.v1.*` 兼容信封和已验收的 MiniApp Tool 回程 envelope。 +因此,本轮正确做法不是宣称“现有代码已经和正式方案一字不差”,而是明确: + +1. `lineup.v1.tool.invoke` 是当前实现中的统一请求入口; +2. 当前回程仍是 `04.runtime_workspace` 已冻结的兼容事实; +3. 未来若做回程统一迁移,需要单独立项,不应在 `04A` 中顺手扩大。 + +## D04A-07:若不补 Runtime 请求兼容,`tool.invoke` 只会停留在 Adapter 内部 + +**已解决(2026-08-07,收敛决议)。** `04A.agent_tool_route.md` 与 `02.technical_implementation_spec.md` 已明确:本轮实现主场仍在 Hermes Adapter,但允许在 `lineup-app/tauri/src/runtime/` 中补最小请求兼容层,使 Runtime 接受 `lineup.v1.tool.invoke`。 + +### 为什么这是 P2 + +当前冲突并不只是“代码还没写”,而是文档口径会直接误导实施范围: + +```text +文档口径:04A 仅改 Adapter +实际代码:Runtime 主要仍按旧请求类型接收 MiniApp Tool +结果:Adapter 即使正确发出 lineup.v1.tool.invoke,也无法端到端命中既有 Tool Router +``` + +这会让实现者在联调时误判为: + +1. 需要回退到旧请求类型; +2. 需要让 Adapter 同时发两种请求; +3. 需要扩大到 app-server 语义改造。 + +这三种方向都偏离了本轮已经收敛的架构结论,因此必须在当前迭代文档里把 Runtime 的最小兼容补丁正名。 + +### 收敛决议 + +本轮已确认: + +1. `04A` 仍是 Adapter 主导迭代; +2. Runtime 允许补 parser / router / 测试夹具级别的最小请求兼容; +3. 该兼容层必须同时保留 `04.runtime_workspace` 既有请求路径,避免回归历史验收; +4. 该兼容层不改变 Runtime 的 schema / lifecycle / persistence / result 语义; +5. `lineup-app-server` 仍不进入本轮范围。 + +## D04A-08:请求统一不等于本轮已完成回程统一 + +**已解决(2026-08-07,收敛决议)。** `04A.agent_tool_route.md` 与 `02.technical_implementation_spec.md` 已把描述收窄为:`lineup.v1.tool.invoke` 统一的是请求入口;回程继续沿用 Runtime 已冻结且当前实现仍在发送的兼容事实,Adapter 以同一 `call_id` 解释,不再新增第三套协议分支。 + +### 为什么这是 P2 + +如果不收窄这层表述,会出现两个危险后果: + +```text +后果 A:实现者误以为 04A 必须顺手重构 Runtime 全部 miniapp.tool.progress/result/cancel + -> 范围扩张到超出本轮验收与回归预算。 + +后果 B:验收者误以为只要代码里仍出现 miniapp.tool.result,就说明 04A 与正式方案冲突 + -> 会把“兼容事实尚在”误判成“架构方向错误”。 +``` + +这不是语义吹毛求疵,而是直接关系到本轮是否还能保持“最小补丁闭环”的实现策略。 + +### 收敛决议 + +本轮已确认: + +1. `04A` 不新增新的本地回程协议族; +2. Adapter 需要能把现有回程解释为同一条 Agent Tool 调用的 receipts / progress / final result; +3. Runtime 既有回程 envelope 是否重命名,不在 `04A` 范围内; +4. 若未来推进回程统一,应以新的迭代和新的设计评审承接,而不是在 `04A` 中隐式扩大。 + +## 评审关闭条件 + +1. D04A-07 与 D04A-08 已回填 [04A.agent_tool_route.md](04A.agent_tool_route.md) 与 [02.technical_implementation_spec.md](02.technical_implementation_spec.md); +2. 后续实现与验收必须按“Adapter 主导 + Runtime 最小请求兼容 + app-server 不动”的边界推进; +3. 后续若要推动 Runtime 回程 envelope 统一,必须新增独立设计评审记录,不能在 `04A` 中以顺手修正方式扩容; +4. 本评审当前无未解决的 P0~P3。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/04A.agent_tool_route.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/04A.agent_tool_route.md new file mode 100644 index 0000000..73ec0f7 --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/04A.agent_tool_route.md @@ -0,0 +1,417 @@ +# LineUp App 迭代定义:04A Agent Tool 通路补齐 + +**迭代编号:** 04A.agent_tool_route +**状态:** 开发实施中,核心实现已完成,待最终验收 +**日期:** 2026-08-07 +**前置基线:** [04.runtime_workspace.md](../04.runtime_workspace/04.runtime_workspace.md)、[05.technical_implementation_spec.md](../04.runtime_workspace/05.technical_implementation_spec.md)、[09.acceptance_review.md](../04.runtime_workspace/09.acceptance_review.md) +**总体计划:** [plan.md](../plan.md) +**权威架构:** [运行时与智能体工具.md](../../设计/02.正式方案/运行时与智能体工具.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md) + +## 1. 迭代定位与目标总览 + +`04.runtime_workspace` 已经完成了 Runtime、Pomodoro MiniApp、Host 工作区、Lifecycle、Inventory 发布与端到端 Host 验收; +但它证明的是“Runtime 这一侧怎样工作”,并没有真正补齐 “Agent 如何拿到这些 Tool,并把它们变成稳定可执行调用”。 + +因此 `04A` 不是重做 Pomodoro,也不是继续扩展 Host 工作区,而是补齐 **Agent / Adapter 这一半链路**,让用户能在真实会话中: + +- 通过自然语言启动 / 中断 Pomodoro; +- 通过标准交互完成 Hermes 需要的确认、澄清与审批; +- 全程只暴露 LineUp 自己定义的协议,不把 Hermes 私有 system message 直接暴露到产品面。 + +本次会话已将 `04A` 的工作收敛为 **以 Adapter 为主、附带最小 Runtime 请求兼容补丁的两个核心目标**: + +1. **MiniApp Tool 通路补齐** + - 让 Hermes Adapter 正确消费 Runtime 发布的 Agent Tool inventory; + - 让 Agent 能生成合法的统一 Agent Tool invoke 请求; + - 让 Runtime 接受 `lineup.v1.tool.invoke` 这一新的请求入口,并继续基于 `inventory_revision`、schema、scope 与 lifecycle 执行 `pomodoro.start` / `pomodoro.interrupt`。 + +2. **Hermes 交互兼容层补齐** + - 让 Hermes 内置的 approval / clarify / update prompt 等交互意图,不再依赖 Hermes 私有 permission request; + - 让它们被 Adapter 收敛为 LineUp 已定义的 `tool.call` / `tool.result` / `app.call` / `app.result`; + - 让用户的选择通过 LineUp 自己的可靠消息链路回传给 Hermes Adapter,再 resolve 回 Hermes 内部状态。 + +为便于后续评审与实施,本文按以下层次展开: + +1. 先冻结 `04A` 的架构边界与分层原则; +2. 再分别定义目标 A 与目标 B 的当前缺口、协议结论与责任分界; +3. 最后给出范围、完成标准与交付物。 + +## 2. 架构边界与分层原则 + +本迭代后续的设计评审、技术规范与实现,都以以下分层原则为准。 + +### 2.1 Runtime 仍是执行权威 + +- `04.runtime_workspace` 已冻结的 `inventory revision`、`app_scope`、schema、lifecycle 与 Pomodoro 裁决语义继续作为权威; +- `04A` 不重新定义 `pomodoro.start` / `pomodoro.interrupt` 的运行规则; +- Adapter 不复制 Runtime 的业务状态机,只负责把 Agent 侧输入收敛到 Runtime 可接受的协议边界; +- 但为让 `lineup.v1.tool.invoke` 真正打通,本轮允许对 Runtime 做**最小请求兼容补丁**:只补 parser / router / 测试夹具对新请求信封的接受能力,不重写 `04.runtime_workspace` 已冻结的 Tool 执行、持久化或结果语义。 + +### 2.2 Agent 平台私有协议兼容责任位于 Adapter 层 + +- Hermes、未来的 Open Claw 或其他 Agent 平台,可能各自带有不同的 system message、approval 流程或工具调用约定; +- 这些差异属于“具体 Agent 平台如何适配 LineUp 协议”的问题,应在对应平台的插件 / Adapter 层完成收敛; +- Runtime 只接收已经进入 LineUp 协议边界的受控消息,不承担理解各家 Agent 私有协议的责任。 + +这一原则与 Hermes 既有飞书接入经验一致: + +- 飞书 approval 不是让模型自己产出飞书协议; +- 而是 Hermes gateway / Feishu adapter 把内部 approval 事件转换成飞书 interactive card; +- 再把按钮回调解析为 Hermes 内部 approval 结果。 + +`04A` 对飞书的借鉴,是 **adapter 负责转换与 resolve 的分层模式**,不是 webhook 或卡片 schema 本身。 + +### 2.3 LineUp 内部不依赖公网 webhook 回传 + +- LineUp 当前不是一个可被公网平台回调的 Bot 平台; +- 因此 04A 不设计 “平台回调 URL -> Adapter 收到 HTTP webhook” 这一链路; +- 用户选择应走现有 IM 长连接 / 消息同步链路回传。 + +对于 LineUp 内部交互,统一采用如下闭环: + +```text +Adapter 发起受控协议 + -> Host / Interact 渲染 + -> 用户选择 + -> Host 通过 tool.result / tool.cancel / app.result 回写 + -> Adapter 消费结果并 resolve 回 Hermes 内部状态 +``` + +### 2.4 `lineup-app-server` 在本轮默认视为透明传输层 + +- 当前 `lineup-app-server` Go 服务主要负责消息收发、base64 编解码与 WuKongIM API 对接; +- 它不解析 `lineup.v1` 的业务语义; +- 因此 04A 默认不要求改动 `lineup-app-server` 服务端来适配 Agent Tool invoke 或 Tool inventory 投影。 + +### 2.5 Runtime 仅补“请求侧兼容”,不在本轮改写回程协议 + +- `04.runtime_workspace` 已冻结并已验收的 Runtime 代码,当前仍保留一套面向 MiniApp Tool 的兼容回程 envelope; +- `04A` 当前要解决的是“Agent 如何以统一入口发起 Runtime Agent Tool 调用”,因此本轮只要求 Runtime 接受 `lineup.v1.tool.invoke` 请求; +- 对于回程,Adapter 必须继续兼容 Runtime 现有的 receipts / progress / final result 事实,并按同一 `call_id` 解释为同一条 Agent Tool 调用; +- 是否将现有回程 envelope 进一步统一为新的 `lineup.v1.tool.*` 族,不属于 `04A` 当前收敛范围,后续若要推进,必须单独立项并补设计评审。 + +### 2.6 正式方案中的“概念协议名”与当前 `lineup.v1.*` 信封兼容 + +- `02.正式方案` 中部分 Runtime 文档会用 `tool.invoke` / `tool.result` 这样的概念名描述统一 Agent Tool 模型; +- 但当前 Tauri / Runtime 兼容实现仍以 `lineup.v1.*` 作为真实线上信封,这是 [app_final_design.md](../../设计/02.正式方案/app_final_design.md) 已保留的兼容事实; +- 因此 `04A` 在 Adapter 侧的实际落地继续采用 `lineup.v1.tool.invoke`,其语义对齐正式方案中的统一 Agent Tool invoke,而不是新增第二套协议; +- 本轮不得在代码或验收中同时引入“裸 `tool.invoke`”与 `lineup.v1.tool.invoke` 两条并行通路。 + +## 3. 目标 A:MiniApp Tool 通路补齐 + +### 3.1 当前缺口 + +当前与目标 A 直接相关的缺口如下。 + +1. **Hermes inventory 解析模型落后于 Runtime** + - 正式方案要求 Runtime Inventory 同时包含 App、Tool、Surface 与 Capability; + - `04.runtime_workspace` 的具体实现也已明确 Tool 由 Registry 独立投影进入 Runtime Tool Registry / Agent Inventory; + - 但 Hermes Adapter 仍主要按旧的 App / Surface 结构理解 `client.inventory`,无法稳定消费 Runtime 发布的 Tool 投影。 + +2. **Hermes 协议白名单缺少统一 Agent Tool invoke** + - 当前 Hermes 允许的结构主要是: + - `lineup.v1.tool.call` + - `lineup.v1.app.call` + - `lineup.v1.ui.open / patch / close` + - `lineup.v1.artifact.offer` + - 还没有面向 Runtime Agent Tool 的统一 invoke 输出路径。 + +3. **Prompt 与测试尚未把 MiniApp Tool 变成可规划对象** + - 当前 prompt 只约束 capability request,不指导模型基于 Runtime 发布的 Tool Inventory 生成 + `pomodoro.start` / `pomodoro.interrupt`; + - Adapter 测试也没有覆盖 “inventory -> tool.invoke -> revision-bound invoke” 这条链路。 + +### 3.2 目标说明 + +让 Hermes Adapter 真正具备以下能力: + +- 读取由 Runtime 发布的最新 Tool Inventory; +- 识别哪些 MiniApp Tool 当前可调用; +- 在用户自然语言触发时生成合法的统一 Agent Tool invoke 请求; +- 携带正确的 `inventory_revision`、`app_scope`、`tool_id` 与受 schema 约束的 `input`; +- 由 Runtime 正确执行并回传进度 / 最终结果。 + +首个验收对象固定为 Pomodoro: + +```text +“开始一个 5 分钟的专注” + -> Hermes 生成 pomodoro.start + -> Runtime 创建并启动 Pomodoro + -> 用户看到前台专注工作区 + +“停止当前专注” + -> Hermes 生成 pomodoro.interrupt + -> Runtime 中断当前 Pomodoro + -> 用户回到 Interact 并收到稳定结果 +``` + +### 3.3 协议冻结 + +本轮不再为 MiniApp Tool 新增第二套独立协议,而是沿用正式方案的统一 Agent Tool 调用模型: + +- `app.call` 继续只表示 Host capability 请求; +- Runtime Agent Tool 统一走 `lineup.v1.tool.invoke`; +- 旧的 `lineup.v1.tool.call(choice | confirm | input)` 继续保留为 Interact 标准交互兼容入口,不承担 Runtime Agent Tool invoke 语义; +- `tool.invoke` 在本轮只统一**请求入口**;其回程继续复用 `04.runtime_workspace` 已冻结且当前 Runtime 仍在发送的 Agent Tool 回程事实,并始终以同一个 `call_id` 关联。 + +建议冻结的最小 payload 形态如下: + +```json +{ + "v": 1, + "type": "lineup.v1.tool.invoke", + "payload": { + "call_id": "call_...", + "inventory_revision": "inv_...", + "app_scope": "pomodoro", + "tool_id": "pomodoro.start", + "input": { + "duration_seconds": 300, + "activity": "冥想" + } + } +} +``` + +也就是说,对 `lineup.v1.tool.invoke`: + +- Runtime 仍可先回传 `accepted / starting` 一类协议回执; +- 在启动成功后继续回传 `started` 或其他已冻结的受控进度; +- 最终仍只回传一次符合该 Tool result schema 的业务结果; +- Hermes Adapter 需要把这些回程继续视作“同一条 Agent Tool 调用的 receipts / progress / final result”; +- 本轮不要求 Runtime 把既有 MiniApp Tool 回程 envelope 全量改名;Adapter 只是不再新增第三套协议分支。 + +### 3.4 Inventory 最小投影 + +Hermes 侧只保留用于规划与出站校验的最小 Tool 投影。 +该投影在 Inventory 中应独立于 `applications` 发布;`applications` 继续只表达 App / Surface 可见性。 +每个工具至少保留以下字段: + +- `app_scope` +- `tool_id` +- `description` +- `input_schema` +- `handling` +- `delivery` +- `activation_requirement` + +这里的权威来源依次是: + +- `02.正式方案` 中 Runtime / Tool / Inventory 的正式边界; +- `04.runtime_workspace` 已冻结的 Runtime Tool Registry / Agent Inventory 落地; +- `lineup-ui-surface-protocol.md` 中较早的 `client.inventory` 示例只能视为 Surface / Capability 最小示意,不再作为 Tool 投影 shape 的权威。 + +以下内容不进入 Hermes prompt 或 Adapter 持久上下文: + +- `output_schema` +- 内部 handler +- bundle / path / secret +- 宿主实现细节 + +### 3.5 Adapter 与 Runtime 的责任分界 + +Adapter 负责: + +- 仅接受当前 conversation 最近一次有效 inventory 中存在的 MiniApp Tool; +- 校验 `call_id / inventory_revision / app_scope / tool_id / input` 顶层结构; +- 拒绝模型伪造 `sender / target / instance_id / operation_id / app_session_id` 等字段; +- 把合法 `tool.invoke` 原样交给 Runtime。 + +Runtime 继续负责: + +- 最新 revision 对账; +- schema 校验; +- lifecycle / activation / foreground_required / activation_not_required 语义; +- 幂等、持久化、进度与最终结果。 + +本轮 Runtime 额外需要补上的,只是让上述执行链路接受 `lineup.v1.tool.invoke` 这一请求入口;它不是一项新的业务责任。 + +## 4. 目标 B:Hermes 交互兼容层补齐 + +### 4.1 当前缺口 + +当前与目标 B 直接相关的缺口如下。 + +1. **Hermes 内置 permission / system message 尚未收敛到 LineUp 协议** + - 当前 ACP 通道会向 Adapter 发出 `session/request_permission` 一类 Hermes 内置请求; + - 现有 Adapter 为避免越权,直接返回 `cancelled`; + - 用户无法稳定完成这类交互,也没有一个明确的产品化协议边界。 + +2. **Hermes 的通用 interactive contract 尚未在 LineUp 中落位** + - Hermes 现有平台适配已稳定使用若干通用交互语义: + - `exec_approval` + - `slash_confirm` + - `clarify` + - `update_prompt` + - 但 LineUp 侧还没有把它们统一映射到 `choice / confirm / input / app.call`。 + +### 4.2 目标说明 + +让 Hermes 内置的确认、澄清与审批意图不再依赖 ACP 私有 permission prompt,而是通过 LineUp 已定义协议完成: + +- 标准交互:`lineup.v1.tool.call` +- 标准交互结果:`lineup.v1.tool.result` / `lineup.v1.tool.cancel` +- 宿主能力调用:`lineup.v1.app.call` +- 宿主能力结果:`lineup.v1.app.result` + +### 4.3 本轮一次性实现的兼容层上限 + +参考 Hermes 现有 Feishu / Relay / Telegram / Slack / WhatsApp 适配实现,本轮一次性抽象的“通用交互语义层”限定为四类: + +1. `exec_approval` +2. `slash_confirm` +3. `clarify` +4. `update_prompt` + +其中 `clarify` 在 `04A` 的兼容范围进一步冻结为: + +- 单选 `choice` +- 单选后进入 `other -> input` 的二段式澄清 +- 纯文本输入型澄清 + +当前不进入本轮兼容范围的 `clarify` 变体: + +- `multi_select = true` +- 任意要求 Host 原生复选组件或一次回收多值的私有交互 + +不在本轮一起实现的内容: + +- 平台特有卡片样式、按钮布局、reaction ack、thread 元数据; +- 任意 Hermes 私有 system message 的泛化透传; +- Runtime 侧对 Hermes 私有 prompt kind 的直接理解。 + +### 4.4 Hermes -> LineUp 映射表 + +| Hermes contract | 典型语义 | LineUp 发起协议 | 用户可见交互 | 用户回传协议 | Adapter resolve 目标 | +|---|---|---|---|---|---| +| `exec_approval` | 危险操作审批;典型选项 `once / session / always / deny` | `lineup.v1.tool.call` | `choice` | `lineup.v1.tool.result` / `lineup.v1.tool.cancel` | Hermes 内部 approval 结果:`once` / `session` / `always` / `deny` | +| `slash_confirm` | slash command 或系统级动作确认;典型选项 `once / always / cancel` | `lineup.v1.tool.call` | `choice`,必要时可退化为 `confirm` | `lineup.v1.tool.result` / `lineup.v1.tool.cancel` | Hermes 内部 slash-confirm / confirm resolve | +| `clarify` | 多项澄清选择;可包含 `other` | `lineup.v1.tool.call` | 首轮 `choice`;若选 `other`,再发 `input` | `lineup.v1.tool.result` / `lineup.v1.tool.cancel` | Hermes 内部 clarify resolve;`other` 分支进入后续文本 / 输入回填 | +| `update_prompt` | 更新、重载、恢复等 `y / n` 询问 | `lineup.v1.tool.call` | `confirm` | `lineup.v1.tool.result` / `lineup.v1.tool.cancel` | Hermes 内部 update prompt resolve | +| Host capability approval | 打开链接、写剪贴板、选文件、保存 artifact 等宿主能力授权 | `lineup.v1.app.call` | Host 原生确认界面 / 受控授权组件 | `lineup.v1.app.result` | Hermes capability result / approval result | + +### 4.5 交互兼容层冻结规则 + +- `exec_approval`、`slash_confirm`、`clarify`、`update_prompt` 都属于 Adapter 层把 Hermes 交互语义收口到 LineUp 标准协议的范畴; +- 这四类 contract 在 Runtime 看来都只是 LineUp 的标准交互或 capability 调用,不暴露 Hermes 私有 prompt kind; +- `choice` 的 option id 必须由 Adapter 生成并受控映射,不能直接把 Hermes 或平台侧的任意 callback token 透传给 Host; +- `clarify` 的 `other` 路径由 Adapter 显式驱动第二次 `input`,而不是要求 Runtime 理解 Hermes 的“等待自由文本”内部状态; +- Hermes `clarify` 若携带 `multi_select = true`,本轮必须稳定拒绝并返回受控 unsupported / not_supported 结果,不能静默降级成单选或自由文本; +- Host 对单选卡片回传唯一的 `action_id` 时,Adapter 必须将其严格规范化为内部的单元素 `action_ids`,再映射回 Hermes;未知、缺失、重复或多于一个选项仍必须拒绝; +- 若某个 Hermes 私有请求不能落在上表任一项中,则本轮默认稳定拒绝,而不是新增第五种临时协议。 + +### 4.6 当前接入路径下的验证边界 + +截至 2026-08-07,本轮真实联调采用的是 LineUp 1420 页面 + `hermes acp` 的接入路径。 +在这条路径上,`exec_approval` 与 `clarify` 已有可重复的运行期事件源,因此完成了 fresh evidence;但 `slash_confirm` 与 `update_prompt` 的原生触发源仍主要位于 Hermes gateway / native platform adapter 路径,而不是当前 ACP server request 流。 + +因此本轮对这两类 contract 的结论冻结为: + +- **设计上保留映射目标**:它们继续属于 Adapter 兼容层应该承接的通用 contract; +- **实现上不放到 Runtime / app-server**:不能为了补证据把 Hermes 私有语义下沉到 Runtime 或 `lineup-app-server`; +- **验收上不继续做 1420 盲测**:若当前 ACP 路径没有稳定 trigger source,则记录为结构性证据限制,而不是把它当成 04A 尚未修复的代码缺陷; +- **后续若要补 fresh evidence**:需要单独增加 bridge / trigger path,或引入可控的 gateway-native 事件源再复验。 + +## 5. 本迭代范围 + +### 5.1 范围内 + +1. **扩展 Hermes inventory 解析** + - 接受独立的 Tool inventory 投影; + - 为每个工具保留最小声明信息; + - 收到包含 Tool 投影的有效 inventory 后,不再退回 `revision = none`。 + +2. **补齐统一 Agent Tool invoke** + - 为 Hermes Adapter 增加受控 Agent Tool 输出通路; + - 完成顶层结构校验与 inventory 绑定; + - 禁止伪造 Runtime 内部目标字段。 + +3. **补全 prompt 约束** + - inventory 中的 MiniApp Tool 才可调用; + - 必须使用最新 revision; + - capability 与 MiniApp Tool 是不同通路; + - `pomodoro.start` / `pomodoro.interrupt` 的输入形态与边界明确; + - 不得依赖 Hermes / ACP 私有 permission prompt 或 system message 完成用户授权。 + +4. **补全 Hermes 交互兼容层** + - 将 `exec_approval`、`slash_confirm`、`clarify`、`update_prompt` 映射到 LineUp 协议; + - 让用户选择通过既有消息流回传,再由 Adapter resolve 回 Hermes; + - 明确 `clarify` 当前只兼容单选 / `other -> input` / 纯文本,`multi_select` 稳定拒绝; + - 对当前协议无法安全表达的 Hermes 私有请求,稳定拒绝。 + +5. **补全联调测试与真实证据** + - inventory 解析测试; + - `tool.invoke` 规范化测试; + - prompt / adapter 输出约束测试; + - Runtime 对 `lineup.v1.tool.invoke` 的 parser / router 兼容测试; + - Hermes 内置 permission request 处置测试; + - approval / clarify / confirm 回传闭环测试; + - Pomodoro 开始 / 停止两条真实链路 fresh evidence。 + - 对 `slash_confirm` / `update_prompt`,若当前接入路径无稳定触发源,则必须输出结构性限制记录,而不是继续把它们列为“待盲测补证据”。 + +6. **补充文档与验收口径** + - 明确 `04A` 与 `04.runtime_workspace` 的关系; + - 明确 Adapter 校验边界,避免与 Runtime 的 schema / lifecycle 责任重复; + - 明确“Agent 平台私有协议的兼容责任位于各自 Adapter 层”的架构结论。 + +### 5.2 不在范围内 + +- 新的 MiniApp 业务功能; +- 应用市场、远程下载和签名安装链路; +- 多 Agent、语音或视频能力; +- Pomodoro 统计、历史分析、暂停 / 继续; +- 改写 `04.runtime_workspace` 的 Runtime 生命周期语义; +- 为适配 Agent Tool invoke 改造 `lineup-app-server` Go 服务端; +- 在本轮内把 Runtime 既有 MiniApp Tool 回程 envelope 全量重构为新的协议族; +- 为 Web Chat 参考前端增加新的协议可视化,除非后续验收证据明确要求; +- 在 Runtime 中增加面向 Hermes、Open Claw 等具体 Agent 平台的私有协议特判。 + +## 6. 完成标准 + +满足以下条件时,`04A` 可视为完成: + +```text +Hermes Adapter 能正确解析包含独立 Tool 投影的 Runtime inventory; +Hermes 不再把这类 inventory 退回 revision=none 的默认上下文; +模型在用户请求开始/停止专注时,可以合法产出统一 Agent Tool invoke 请求; +Adapter 能基于当前 conversation 的最新 inventory 校验并规范化 tool.invoke,再交给 Runtime; +Runtime 能基于最新 inventory revision 执行 pomodoro.start / pomodoro.interrupt; +Runtime 已接受 `lineup.v1.tool.invoke` 作为新的请求入口; +tool.invoke 的协议回执、进度与最终业务结果继续复用既有 Agent Tool 回程语义;本轮不再新增第三套协议分支,也不要求立即重构 Runtime 既有回程 envelope; +Hermes 不再依赖 ACP 私有 permission/system message 作为 LineUp 产品中的主授权路径; +当前可由 LineUp 协议表达的 Hermes 交互意图,已明确收敛到 tool.call / app.call / tool.invoke 之一; +Hermes clarify 的单选、`other -> input` 与纯文本路径可稳定闭环;`multi_select` 请求会被稳定拒绝,不发生静默降级; +当前不可安全表达的 ACP permission request,会被稳定拒绝且不会形成隐式授权; +若 `slash_confirm` / `update_prompt` 在当前 ACP 接入路径下缺少安全、稳定、可重复的触发源,则需有明确的结构性限制记录与后续 bridge 承接建议,不能再被当作未修复代码缺陷挂起; +真实用户语言 -> Agent tool invoke -> Runtime -> Pomodoro 的“开始专注”和“停止专注”两条链路都至少完成一条 fresh evidence; +现有 standard interaction、app.call、ui.* 和 artifact.offer 路径不回归; +所有新增 P0~P3 评审问题清零。 +``` + +## 7. 建议交付物 + +建议至少产生以下文档与实现产物: + +```text +迭代/04A.agent_tool_route/ + 04A.agent_tool_route.md + 01.design_review.md + 02.technical_implementation_spec.md + 03.acceptance_review.md +``` + +以及代码层面的对应修改: + +```text +lineup-adapter/hermes/lineup/protocol.py +lineup-adapter/hermes/lineup/core.py +lineup-adapter/hermes/lineup/acp_client.py +lineup-adapter/hermes/lineup/tests/ +lineup-app/tauri/(用于最小 Runtime 请求兼容补丁与对应测试) +``` + +## 8. 当前结论 + +`04.runtime_workspace` 没有失败,它已经完成了 Runtime / Host / Pomodoro 这一侧的设计与实现闭环。 +`04A.agent_tool_route` 的任务是把这一闭环真正接到 Agent / Adapter 入口上,并把 Hermes 的必要交互收敛到 +LineUp 自己定义的协议里,使用户能够通过自然语言稳定使用已实现的 Pomodoro Tool。 diff --git a/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/05.acceptance_review.md b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/05.acceptance_review.md new file mode 100644 index 0000000..214e00a --- /dev/null +++ b/03.迭代规划/03.进行中与待实施阶段/04A.agent_tool_route/原始文档/05.acceptance_review.md @@ -0,0 +1,498 @@ +# 04A.agent_tool_route 验收评审(五) + +**状态:** 已完成本轮联合验收,A04A-09 / A04A-10 / A04A-11 已通过 fresh runtime 验证;`slash_confirm` / `update_prompt` 已转为当前 ACP 接入路径下的结构性证据限制记录 +**日期:** 2026-08-07 +**评审对象:** `lineup-adapter/hermes/lineup/` 当前实现、[04A.agent_tool_route.md](04A.agent_tool_route.md)、[02.technical_implementation_spec.md](02.technical_implementation_spec.md)、实际 LineUp Host / Hermes Adapter 运行状态 +**评审方法:** 将自动化测试与真实消息链路证据交叉核对;本轮重点复核 Hermes `session/request_permission` 投影后的单选结果是否与 Host 实际发送的 `lineup.v1.tool.result` 形状一致。 +**总体结论:** 已发现并修复三个会破坏 04A 交互边界的 P2 缺陷。权限卡片闭环、`clarify other -> input`、纯文本 `clarify`、`multi_select` 稳定拒绝,以及至少一种范围外稳定拒绝的 fresh evidence 均已通过。结合 `02.正式方案`、`04.runtime_workspace` 与当前实际接入实现复核后,可以确认 `slash_confirm` 与 `update_prompt` 在本轮 1420 + `hermes acp` 接入路径下缺少安全、稳定、可重复的运行期触发源;它们当前不应继续按“未修复缺陷”处理,而应作为后续 bridge / trigger 能力的小迭代承接项记录。 + +## 问题清单(Outline) + +状态标记:🔴 未解决,必须处理;🟡 已修复但等待运行期确认;✅ 已解决;⚪ 可延期但必须保留记录。 + +| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | +|---|---:|---|---|---| +| ✅ | P2 | A04A-09 | Host 单选卡片实际回传 `result.action_id`,Adapter 只接受 `result.action_ids[]`,导致用户点击允许后结果被拒绝,Hermes 交互保持 pending 并最终超时。 | 已在 `protocol.py` 增加严格单选规范化;真实 Host 验证通过,交互 `call_b9c089f04ec44e19bbffca63ddc66c17` 已 `resolved`,Hermes 已收到 `allow_once`,目标文件已创建。 | +| ✅ | P2 | A04A-10 | `clarify other -> input` 的第二段输入卡片真实回传 `result.text`,Adapter 只接受 `result.values.{field_id}`,导致用户提交后卡片失效但 Hermes 未被解除阻塞。 | 已在 `protocol.py` 增加单字段输入 `text -> values.{field_id}` 严格规范化;新的 1420 页面 fresh 复验已通过,`call_input_task_01` 已 `completed`,Hermes 已确认收到 `验收 04A other-input`。 | +| ✅ | P2 | A04A-11 | 04A 要求 `clarify multi_select = true` 稳定拒绝,但 Hermes 主回复提示词和 `tool.call` 校验仍允许模型直接发出 `multi-choice` 卡片,导致多选请求被错误放行。 | 已移除 Adapter 出站层对 `multi-choice` 的接受并更新回归测试;新的 1420 页面 fresh 复验已确认“不再出现多选卡片”,Hermes 改为明确提示“LineUp 卡片只支持单选,不支持多选”。 | + +## 1. 真实失败证据 + +测试请求为: + +```text +请在 /tmp/lineup-permission-test.txt 写入一行内容:LineUp permission test +``` + +真实记录: + +- 用户请求消息序号:`1267`; +- Adapter 创建权限交互:`call_72f3571755a94aff909d40db4ef53936`; +- 交互类型:`exec_approval`; +- 选项映射:`action_01 -> allow_once`、`action_02 -> deny`; +- 用户点击后的消息序号:`1270`; +- Host 实际回传: + +```json +{ + "call_id": "call_72f3571755a94aff909d40db4ef53936", + "status": "completed", + "result": { + "action_id": "action_01" + } +} +``` + +修复前该消息被记录为 `invalid hermes interaction response`,`tool_audit` 为 +`result / rejected`,交互仍为 `pending`,目标文件不存在,原始 Hermes 请求最终为 +`Hermes ACP response timeout`。旧交互在 Adapter 重启恢复时已被正确标记为 +`aborted`,不能继续重复操作。 + +## 2. 修复与自动化证据 + +修复内容: + +- `validate_tool_response()` 在 `mode = single-choice` 且不存在 `action_ids` 时接受唯一字符串 `action_id`; +- 统一规范化为内部 `action_ids = [action_id]`; +- 保留 inventory/schema 边界,未知、缺失、非字符串、多选及重复选项继续拒绝; +- Hermes resolve 继续只消费规范化后的内部形状,因此不新增第二套 resolve 分支。 + +自动化证据: + +```bash +PYTHONPATH=lineup-adapter/hermes python3 -m unittest \ + lineup-adapter/hermes/lineup/tests/test_protocol.py \ + lineup-adapter/hermes/lineup/tests/test_core.py \ + lineup-adapter/hermes/lineup/tests/test_acp_client.py \ + lineup-adapter/hermes/lineup/tests/test_state.py +``` + +结果: + +```text +Ran 38 tests +OK +``` + +`git -C lineup-app diff --check` 已通过。修复后的插件已经安装到运行目录 +`/home/gao/.hermes/plugins/lineup/`,当前 Adapter PID 为 `3895319`。 + +## 3. 待完成的 fresh 验证 + +需要重新发起一次低风险 `/tmp` 写入请求,并确认: + +1. 新权限卡片可以交互; +2. 点击 `Allow edit` 后,`hermes_interactions.state` 变为 `resolved`; +3. ACP responder 收到 `allow_once`; +4. 原始 Hermes 请求最终完成,不再出现权限超时; +5. `/tmp/lineup-permission-test.txt` 被创建且内容正确; +6. 同一结果重复回传不会二次 resolve。 + +上述 fresh evidence 已取得,`A04A-09` 已关闭;04A 整体仍保持未完成,直到其他 +强制验收项补齐。 + +## 4. A04A-09 Fresh Runtime Evidence(2026-08-07) + +修复后的 Adapter 重启为 PID `3895319`。用户重新发起同一低风险写入请求并点击 +新权限卡片的 `Allow edit` 后,取得以下证据: + +- 新的 `exec_approval` call:`call_b9c089f04ec44e19bbffca63ddc66c17`; +- `hermes_interactions.state = resolved`; +- 对应 `tool_calls.state = completed`; +- `tool_audit` 的 `result` disposition 为 `accepted`; +- Hermes ACP 实际完成了 `write_file`,工具消息记录 `bytes_written = 22`; +- Hermes 随后返回完成文本; +- `/tmp/lineup-permission-test.txt` 已创建,文件大小 `22` 字节,内容为: + +```text +LineUp permission test +``` + +这证明真实 Host 的单选 `action_id` 回传已经被 Adapter 规范化并 resolve 回 +Hermes,且没有再次出现 `Hermes ACP response timeout`。 + +## 5. 当前验收结论 + +- `clarify` 单选、`other -> input`、纯文本输入、`multi_select` 稳定拒绝与至少一种范围外稳定拒绝均已具备 fresh evidence; +- 当前自动化门禁:Adapter `39 tests`、Runtime 定向测试 `71 tests`、Tauri `tsc --noEmit + Vite build`、`git -C lineup-app diff --check` 均通过; +- 本轮已发现的 P0-P3 缺陷均已关闭; +- `slash_confirm`、`update_prompt` 不再作为 04A 当前实现的阻塞项,原因见第 13 节“当前 ACP 接入路径下的结构性证据限制”。 + +## 6. Clarify Single-Choice Fresh Runtime Evidence(2026-08-07) + +Adapter 重启后,用户在 1420 页面实际发送: + +```text +请先让我从“专注工作”和“休息”两个选项中选择一个,再继续后续操作。 +``` + +Hermes 生成了真实标准交互: + +- `call_id = call_8f3a2c91d4e6b7a0`; +- `tool = choice`; +- `mode = single-choice`; +- `action_ids = ["focus", "rest"]`; +- Host 回传消息序号:`1287`; +- 实际回传 `result.action_id = "focus"`; +- Adapter `tool_audit`:`call / accepted`、`result / accepted`; +- 对应 `tool_calls.state = completed`; +- Hermes 最终回复已明确收到“专注工作”选择。 + +这证明真实 Hermes -> LineUp `tool.call`、用户选择 -> `tool.result`、 +Adapter ledger -> Hermes resolve 的单选路径已闭环,并再次验证 Host 的 +`action_id` 形状兼容。 + +同一次会话的 Hermes 回复指出当前 inventory 为 `revision:none`。这是因为 Adapter +重启后 Host 尚未重新下发新的 `client.inventory`,不属于 clarify 交互失败;但在 +继续验证 `pomodoro.start` 之前,必须先刷新 1420 Host 或让 Host 重新发布 inventory。 + +## 7. A04A-10 Real Runtime Failure And Fix Pending Fresh Recheck(2026-08-07) + +用户在 1420 页面实际发送: + +```text +请先让我在“专注工作”和“其他”里选一个;如果我选“其他”,再让我输入具体要做的事情。 +``` + +真实链路证据如下: + +- 用户原始请求消息序号:`1304`; +- 第一段 `choice` call:`call_6e2f81a4c9d3b7`; +- Host 单选回传消息序号:`1309`; +- 第一段实际回传: + +```json +{ + "call_id": "call_6e2f81a4c9d3b7", + "status": "completed", + "result": { + "action_id": "other" + } +} +``` + +- Adapter 接受该结果后,发出第二段 `input` call:`call_9c3d5e7f1a2b4c`; +- 用户提交输入后的消息序号:`1315`; +- 第二段实际回传: + +```json +{ + "call_id": "call_9c3d5e7f1a2b4c", + "status": "completed", + "result": { + "text": "验收 04A other-input" + } +} +``` + +失败时的 Adapter 账本状态: + +- `tool_audit(call_6e2f81a4c9d3b7)`:`call / accepted`、`result / accepted`; +- `tool_audit(call_9c3d5e7f1a2b4c)`:`call / accepted`、`result / rejected`; +- `tool_calls(call_9c3d5e7f1a2b4c).state = emitted`; +- 用户侧实际观察为第二张输入卡片提交后变为不可交互,但 Hermes 未完成 `clarify` resolve。 + +这证明真实 Host 对单字段 `input` 的结果回传 shape 为 `result.text`,而 Adapter 先前只接受 `result.values.{field_id}`,因此属于会阻断 `clarify other -> input` 闭环的 P2 兼容缺陷。 + +修复内容: + +- `validate_tool_response()` 在 `tool = input` 且 schema 只有一个字段时,接受唯一字符串 `result.text`; +- 规范化为内部 `values.{field_id}` 后再走原有 schema 校验; +- 保持严格边界:多字段表单仍必须使用 `values`,非字符串 `text` 继续拒绝。 + +自动化证据: + +```bash +PYTHONPATH=lineup-adapter/hermes python3 -m unittest \ + lineup-adapter/hermes/lineup/tests/test_protocol.py \ + lineup-adapter/hermes/lineup/tests/test_core.py \ + lineup-adapter/hermes/lineup/tests/test_acp_client.py \ + lineup-adapter/hermes/lineup/tests/test_state.py +``` + +结果: + +```text +Ran 39 tests +OK +``` + +修复后的插件已重新安装到 `/home/gao/.hermes/plugins/lineup/`,并重启为新的 +Adapter PID `3913413`。 + +## 8. A04A-10 Fresh Runtime Evidence(2026-08-07) + +修复后,用户在 1420 页面重新执行同一条两段式澄清请求: + +```text +请先让我在“专注工作”和“其他”里选一个;如果我选“其他”,再让我输入具体要做的事情。 +``` + +新的真实链路证据如下: + +- 用户原始请求消息序号:`1316`; +- 第一段 `choice` call:`call_choice_focus_other`; +- 第一段 Host 回传消息序号:`1321`; +- 第一段实际回传: + +```json +{ + "call_id": "call_choice_focus_other", + "status": "completed", + "result": { + "action_id": "other" + } +} +``` + +- 第二段 `input` call:`call_input_task_01`; +- 第二段 Host 回传消息序号:`1327`; +- 第二段实际回传: + +```json +{ + "call_id": "call_input_task_01", + "status": "completed", + "result": { + "text": "验收 04A other-input" + } +} +``` + +修复后的账本状态: + +- `tool_calls(call_choice_focus_other).state = completed`; +- `tool_calls(call_input_task_01).state = completed`; +- Hermes 最终回复消息已明确确认: + - 选择:`其他` + - 输入:`验收 04A other-input` + - 结论:两段式交互已跑通。 + +用户侧同步观察为:提交后输入卡片变为不可交互,且 Hermes 立即返回确认文本。 +这证明单字段 `input` 的 `result.text` 已被 Adapter 严格规范化并成功 resolve 回 Hermes,`A04A-10` 可以关闭。 + +## 9. Clarify Pure-Text Fresh Runtime Evidence(2026-08-07) + +本项先经过一次失败尝试,再取得 fresh 成功证据: + +第一次用户发送: + +```text +先不要给我选项,直接问我一句开放式问题,让我用文字回答今天要处理的事项。 +``` + +Hermes 仅返回普通文本追问: + +```text +今天有什么要处理的事项?直接打给我,我一件件帮你安排。 +``` + +该次未进入标准输入交互,不能记为 `clarify` 纯文本兼容成功。 + +随后用户改为发送更强约束提示: + +```text +在继续之前,请不要直接文本追问;请必须通过 LineUp 的标准输入交互发起一个 input 卡片,让我填写“今天要处理的事项”。 +``` + +新的真实链路证据如下: + +- 强约束提示消息序号:`1380`; +- Hermes 发出的标准 `input` call:`call_input_today_tasks`; +- Host 回传消息序号:`1385`; +- 实际回传: + +```json +{ + "call_id": "call_input_today_tasks", + "status": "completed", + "result": { + "text": "验收 04A pure-text clarify" + } +} +``` + +账本状态: + +- `tool_calls(call_input_today_tasks).state = completed`; +- `tool_audit(call_input_today_tasks)`:`call / accepted`、`result / accepted`; +- Hermes 最终确认文本明确回显: + - 交互方式:`input` 卡片(标准输入交互) + - 填写内容:`验收 04A pure-text clarify` + - 结论:卡片流程跑通。 + +这证明在明确要求下,Hermes 纯文本 `clarify` 已可通过 Adapter 投影为 +LineUp 标准 `input` 交互,并通过现有消息链路闭环 resolve。 + +## 10. A04A-11 Real Runtime Failure And Fix Pending Fresh Recheck(2026-08-07) + +`04A.agent_tool_route.md` 与 `02.technical_implementation_spec.md` 已明确规定: + +- Hermes `clarify multi_select = true` 本轮必须稳定拒绝; +- 不得静默降级成单选、普通文本,或直接放行为多选卡片。 + +但在真实运行中,用户发送: + +```text +请先让我同时从“专注工作”“休息”“学习”里多选两个,再继续后续操作。 +``` + +取得如下失败证据: + +- 用户请求消息序号:`1391`; +- Hermes 最终实际发出的标准交互: + +```json +{ + "v": 1, + "type": "lineup.v1.tool.call", + "payload": { + "call_id": "call_choice_multi_02", + "tool": "choice", + "title": "选择两项", + "prompt": "请从以下选项中选择两项", + "expires_at": "2026-08-07T05:15:00Z", + "data": { + "action_group": { + "mode": "multi-choice", + "actions": [ + {"id": "focus", "label": "专注工作"}, + {"id": "rest", "label": "休息"}, + {"id": "study", "label": "学习"} + ] + } + } + } +} +``` + +- Adapter 账本记录: + - `tool_calls(call_choice_multi_02).state = emitted` + - `response_schema.mode = multi-choice` +- 用户侧实际观察为:1420 页面出现了多选提示卡。 + +这证明问题并不在 ACP clarify 投影层,而在 Hermes 主回复通路本身: +Adapter 的主提示词仍显式允许 `multi-choice`,`validate_tool_call_payload()` 也仍接受 +`mode = multi-choice`,因此模型可以绕过“Hermes clarify 多选必须拒绝”的设计结论, +直接生成一张多选卡片。 + +修复内容: + +- 从 Adapter `tool.call` 出站校验中移除 `multi-choice` 允许集; +- 更新 Hermes 主提示词,明确 `multi-choice` 在当前 LineUp Hermes 通路中不受支持; +- 新增回归测试,确保模型直接输出 `multi-choice` 会被拒绝,不能再进入 Host 交互账本。 + +自动化证据: + +```bash +PYTHONPATH=lineup-adapter/hermes python3 -m unittest \ + lineup-adapter/hermes/lineup/tests/test_protocol.py \ + lineup-adapter/hermes/lineup/tests/test_core.py \ + lineup-adapter/hermes/lineup/tests/test_acp_client.py \ + lineup-adapter/hermes/lineup/tests/test_state.py +``` + +结果: + +```text +Ran 39 tests +OK +``` + +修复后的插件已重新安装到 `/home/gao/.hermes/plugins/lineup/`,并重启为新的 +Adapter PID `3918848`。 + +## 11. A04A-11 Fresh Runtime Evidence(2026-08-07) + +修复后,用户再次发送同一条多选请求: + +```text +请先让我同时从“专注工作”“休息”“学习”里多选两个,再继续后续操作。 +``` + +新的真实结果如下: + +- 新一轮用户请求消息在 Hermes 会话中对应到后续消息 `30727 -> 30728`; +- Hermes 未再发出新的 `lineup.v1.tool.call multi-choice`; +- 最新实际回复为纯文本明确拒绝: + +```text +LineUp 的卡片只支持单选,不支持多选(系统限制),所以没法直接弹出"选两个"的卡片。 +``` + +并继续给出安全替代路径: + +```text +麻烦你直接用文字告诉我选哪两个... +``` + +这证明修复后的 Adapter 已阻断 `multi-choice` 出站,不再把多选请求下发为 +Host 卡片,而是迫使 Hermes 返回受控的显式拒绝与文本回退说明。 + +注意:在修复前已发出的旧多选卡 `call_choice_multi_02` 曾继续收到一次用户回传: + +```json +{ + "call_id": "call_choice_multi_02", + "status": "completed", + "result": { + "action_id": "study" + } +} +``` + +这属于修复前遗留交互的尾部回传,不影响本轮新的 fresh 结论;新的 fresh 复验中, +Hermes 已不再发出新的多选卡片。因此 `A04A-11` 可以关闭。 + +## 12. Out-Of-Scope Request Fresh Runtime Evidence(2026-08-07) + +用户要求: + +```text +请给我一个可以同时勾选多个选项的交互卡片,并允许我自己定义每个选项后端要执行的 shell 命令。 +``` + +真实最终回复为明确拒绝,并同时指出两层边界: + +1. `LineUp` 卡片协议不支持多选; +2. 当前会话 inventory 为空,没有任何 shell 执行能力暴露给 Hermes。 + +Hermes 没有下发任何新的危险交互卡片,也没有伪造 shell / app capability / +runtime tool 权限,而是改为提供受控的文本替代方案。 +这可作为“范围外 Hermes 请求被稳定拒绝,且未形成隐式授权”的 fresh runtime 证据。 + +## 13. `slash_confirm` / `update_prompt`:当前 ACP 接入路径下的结构性证据限制(2026-08-07) + +本轮在真实 1420 页面上已多次尝试补充 `slash_confirm` / `update_prompt` 的 fresh runtime evidence,但结合源码与运行事实复核后,可以确认这两项当前缺的不是“Adapter 尚未修好”,而是**当前 ACP 接入路径本身没有稳定触发源投影**。 + +### 13.1 源码对照 + +- 当前 LineUp Hermes 插件通过 `hermes acp` 挂接,插件侧入口位于 `/home/gao/.hermes/plugins/lineup/acp_client.py`; +- 该 ACP client 当前唯一显式处理的 server request method 是 `session/request_permission`; +- 在同一文件中,未知 server request method 会直接返回 `Unsupported ACP client method`,没有看到与 `slash_confirm`、`update_prompt` 对应的 ACP request 分发入口; +- Hermes 原生 `slash_confirm` 触发源位于 gateway/native platform 路径: + - `/home/gao/.hermes/hermes-agent/gateway/run.py` 中的 `_request_slash_confirm(...)` + - `/home/gao/.hermes/hermes-agent/gateway/slash_commands.py` 中对 `_request_slash_confirm(...)` 的调用 + - 其交互依赖平台 adapter 的 `send_slash_confirm(...)`,失败时退回 gateway 文本确认; +- Hermes 原生 `update_prompt` 触发源同样位于 gateway watcher / native platform 路径: + - `/home/gao/.hermes/hermes-agent/gateway/run.py` 中对 `.update_prompt.json` 的轮询 + - 再调用平台 adapter 的 `send_update_prompt(...)`,或退回 gateway 文本提示。 + +这说明:当前 LineUp 1420 页面走的是 **ACP stdio 接入链路**,而不是 Hermes gateway 的原生平台 adapter 按钮链路。两者属于不同事件源。 + +### 13.2 运行期事实 + +- `/reload-mcp` 在当前会话里因 `~/.hermes/config.yaml` 中 `mcp_servers: {}` 被短路为普通文本说明,没有进入可复用的确认交互; +- `/model custom/deepseek-v4-pro` 在当前会话里也没有稳定进入 `slash_confirm`,而是走成了“编辑 `config.yaml`”的写入审批路径,用户看到的是 `edit config.yaml` 审批卡; +- 已通过的 fresh evidence 全部来自当前 ACP 路径可稳定发出的两类来源: + - `session/request_permission` -> `exec_approval` + - Adapter 主回复 / `tool.call` -> `clarify` / `input` + +因此,继续在 1420 页面上重复盲测,并不能合理提高 `slash_confirm` / `update_prompt` 的证据覆盖率。 + +### 13.3 评审结论 + +- `04A` 当前实现已经证明:Adapter 层可以把可达的 Hermes 内置交互收敛到 LineUp 协议,并通过现有消息链路完成用户选择回传; +- `slash_confirm` / `update_prompt` 仍应保留在 04A 的目标模型与映射表中,作为 Adapter 兼容层未来要承接的 contract; +- 但在当前 `1420 + hermes acp` 接入方式下,它们缺少安全、稳定、可重复的运行期触发源,不再作为本轮关闭验收的阻塞条件; +- 若后续需要 fresh runtime evidence,必须新增单独的 bridge / trigger 路径,或在后续小迭代中引入可控的 gateway-native event source,再重新立项验收。 diff --git a/03.迭代规划/90.迁移记录/01.迭代迁移记录.md b/03.迭代规划/90.迁移记录/01.迭代迁移记录.md new file mode 100644 index 0000000..4d49d01 --- /dev/null +++ b/03.迭代规划/90.迁移记录/01.迭代迁移记录.md @@ -0,0 +1,24 @@ +# 迭代迁移记录 + +**迁移批次:** 第 3 批 +**日期:** 2026-08-07 + +## 1. 本次迁移完成了什么 + +- 将 `lineup-app/迭代/` 的方法逻辑提升为 `agent_ops/03.迭代规划/` 的统一方法; +- 将“每个迭代至少四步”的工作流正式写入主目录; +- 将当前阶段资料按“总览 / 已完成 / 进行中与待实施”重组; +- 明确以后 `agent_ops/03.迭代规划/` 才是迭代讨论主入口。 +- 在确认 `04` 与 `04A` 已完成后,移除 `lineup-app/迭代/` 旧目录。 + +## 2. 旧目录的新角色 + +`lineup-app/迭代/` 现在主要保留: + +- 原始主定义长文; +- 原始设计评审正文; +- 原始技术实施规范; +- 原始验收评审; +- 证据与历史记录。 + +它不再承担未来跨项目迭代的唯一入口角色。 diff --git a/03.迭代规划/README.md b/03.迭代规划/README.md new file mode 100644 index 0000000..8df724d --- /dev/null +++ b/03.迭代规划/README.md @@ -0,0 +1,53 @@ +# 迭代规划主目录 + +`03.迭代规划/` 现在是 LineUp 项目的**统一迭代主目录**。从 `04A.agent_tool_route` 开始,一次迭代往往同时涉及: + +- `lineup-app/` +- `lineup-adapter/` +- `lineup-app-server/` +- 必要时还会联动运行配置、协议文档与验收记录 + +因此,迭代不再只属于 `lineup-app/`,而应在 `agent_ops/03.迭代规划/` 中统一规划、评审、拆解与归档。 + +截至 2026-08-07,`lineup-app/迭代/` 中的原始迭代文档已经物理迁移到本目录下对应阶段的 `原始文档/` 或 `原始来源/` 子目录中。 + +## 当前原则 + +- 以后每次迭代都在这里讨论和沉淀; +- `lineup-app/迭代/` 仅保留迁移说明,不再作为未来迭代的主入口; +- 每次迭代至少经过四个固定步骤: + 1. 规划 + 2. 设计评审 + 3. 技术实施规范 + 4. 技术实施规范评审 +- 若进入实施与验收,还应继续补充验收评审与证据记录。 + +## 目录结构 + +```text +03.迭代规划/ +├── README.md +├── 00.迭代方法/ +│ ├── 01.迭代工作流与四步法.md +│ ├── 02.迭代目录与命名规范.md +│ └── 03.跨项目迭代边界与协作方式.md +├── 01.总览与路线/ +│ ├── 01.迭代总览与阶段结论.md +│ └── 02.后续路线与阶段优先级.md +├── 02.已完成阶段/ +│ ├── 00.base/ +│ ├── 01.kernel/ +│ └── 03.sdk_and_coreapp/ +├── 03.进行中与待实施阶段/ +│ ├── 04.runtime_workspace/ +│ └── 04A.agent_tool_route/ +└── 90.迁移记录/ + └── 01.迭代迁移记录.md +``` + +## 当前使用方式 + +- 看方法和模板:先看 `00.迭代方法/` +- 看全局阶段判断:看 `01.总览与路线/` +- 看某一阶段:进入对应迭代目录 +- 查原始长文、评审全文和证据:进入对应阶段下的 `原始文档/` 或 `原始来源/` diff --git a/04.程序清单/01.跨仓程序清单与功能说明.md b/04.程序清单/01.跨仓程序清单与功能说明.md new file mode 100644 index 0000000..33e0bcf --- /dev/null +++ b/04.程序清单/01.跨仓程序清单与功能说明.md @@ -0,0 +1,131 @@ +# 跨仓程序清单与功能说明 + +**版本:** 2026-08-07 整合版 +**主要来源:** 根 `程序文件清单与功能说明.md` +**用途:** 只描述跨客户端的服务端、适配层、IM 基线与运行配置;客户端细节后续由 `lineup-app` 专项文档整合承接。 + +## 1. 这份清单保留什么,不保留什么 + +本清单保留跨仓主线里最稳定的“代码地图”信息: + +- 哪些目录属于当前主线; +- 每个目录的职责是什么; +- 哪些文件是主要维护入口; +- 哪些目录属于上游或工具链,不应混入业务提交。 + +本清单不再复写: + +- 客户端 Runtime / Host / MiniApp 的细粒度源码分层; +- 单次联调、运行状态和端口明细; +- 已经写在 `lineup-app` 客户端专属清单中的内容。 + +## 2. 当前跨仓结构 + +```text +lineup-app/ 客户端 Runtime、Host、Interact 与 MiniApp +lineup-app-server/ 登录、消息中转、同步与 Web Reference Host 服务端 +lineup-adapter/hermes/ Hermes Agent 协议桥与 ACP 客户端 +infra/wukongim-v3/ WuKongIM 本地运行配置 +wukongim/ WuKongIM 3.0 上游源码 +upstream/ 第三方上游源码与历史实验参考 +tools/ 本地工具链与构建依赖 +agent_ops/ 文档治理与整合目录 +``` + +## 3. 各目录职责摘要 + +### 3.1 `lineup-app-server/` + +当前主职责: + +- 登录; +- 消息发送; +- 消息同步; +- Agent 控制面; +- Webhook; +- Web Reference Host 的服务端部分。 + +主要入口文件: + +- `main.go` +- `modules/user.go` +- `modules/channel.go` +- `modules/message.go` +- `modules/agent.go` +- `modules/webhook.go` +- `internal/wkapi/wkapi.go` +- `configs/lineup.yaml` + +### 3.2 `lineup-adapter/hermes/lineup/` + +当前主职责: + +- 轮询 AppServer; +- 组织收发消息; +- 调用 Hermes ACP / CLI; +- 把平台私有事件收敛为 LineUp 协议消息; +- 维护最小状态与审计账本。 + +主要入口文件: + +- `core.py` +- `acp_client.py` +- `protocol.py` +- `state.py` +- `plugin.yaml` +- `run-hermes-adapter.sh` + +### 3.3 `infra/wukongim-v3/` + +当前主职责: + +- 保存本地运行脚本与配置; +- 作为当前 IM 基线的运行配置层; +- 与业务仓边界清晰分离。 + +主要入口文件: + +- `run-local.sh` +- `wukongim.host.toml` +- `wukongim.toml` +- `compose.yaml` + +### 3.4 `wukongim/` + +当前定位: + +- 上游源码基线; +- 用作 IM 能力来源与必要验证对象; +- 不应把 LineUp 业务改动随意混入这个目录。 + +## 4. 哪些结论仍然具有时效性 + +以下结论来自原程序清单,当前仍然稳定: + +- 跨仓主线由客户端、服务端、适配层和 IM 运行配置组成; +- 客户端清单与跨仓清单应该分开维护; +- `wukongim/` 是上游基线,不是 LineUp 业务主仓; +- `upstream/` 和 `tools/` 不应与当前业务主线混淆。 + +## 5. 哪些内容不再放在这份整合版主文档中 + +原文档中有不少价值很高的细节,但更适合落到其他层: + +- 客户端源码详细模块表; +- 运行端口与实时地址; +- 具体测试命令与某次验收结果; +- 某次版本和构建体积。 + +这些内容后续分别归入: + +- `03.迭代规划/` +- `05.运行运维/` +- `lineup-app` 专项整合文档 + +## 6. 当前阅读路径 + +如果想快速定位跨仓入口,建议顺序是: + +1. 先看 [项目总览](../01.项目总览/01.项目总览.md) +2. 再看 [仓库职责与协作边界](../01.项目总览/03.仓库职责与协作边界.md) +3. 最后用本清单找具体目录和主入口文件 diff --git a/05.运行运维/01.2026-08-03运行基线与验证记录.md b/05.运行运维/01.2026-08-03运行基线与验证记录.md new file mode 100644 index 0000000..88b7dbc --- /dev/null +++ b/05.运行运维/01.2026-08-03运行基线与验证记录.md @@ -0,0 +1,114 @@ +# 2026-08-03 运行基线与验证记录 + +**记录日期:** 2026-08-03 +**来源:** 根目录 `项目状态记录.md` +**用途:** 保存当时已经核验过的运行事实、技术基线和验证结论,作为历史运行基线,而不是当前实时状态页。 + +## 1. 这份记录为什么仍然有价值 + +`项目状态记录.md` 里最有价值的内容,不是“某个端口此刻在线”,而是以下几类长期信息: + +- 当时已经跑通的系统主线是什么; +- 哪条技术路线已经退役、哪些边界已经明确; +- 核心组件之间如何协作; +- 哪些问题是结构性的,后续仍需要记住; +- 哪些运行细节属于 2026-08-03 的历史快照,不能直接当作今天状态。 + +因此,本文件把 2026-08-03 的内容保留为**历史基线**。 + +## 2. 当时已经确认的主线架构 + +截至 **2026 年 8 月 3 日**,项目已经形成如下运行主线: + +```text +客户端 / Host + → AppServer + → WuKongIM 3.0 + → Hermes Adapter + → Hermes +``` + +当时已确认的长期有效结论: + +- 当前 IM 主线基于 **WuKongIM 3.0**,不是旧的 WuKongIM v2; +- LineUp 业务层采用 **AppServer + Hermes Adapter** 的组合; +- 客户端与 Agent 的连接不再以唐僧叨叨旧业务栈为主线; +- 历史唐僧叨叨本地环境已经退役,只保留背景参考价值。 + +## 3. 当时已经明确的技术基线 + +### 3.1 通讯与存储 + +- WuKongIM 3.0 官方 `main` 是当前通讯层基线; +- 单节点集群使用 PebbleDB 持久化; +- 当前核心启动不依赖 MySQL / Redis。 + +### 3.2 业务层 + +- `lineup-app-server/` 负责登录、路由、消息收发、消息同步与 Webhook; +- `lineup-adapter/hermes/lineup/` 负责 Hermes 对接、协议桥接与会话调用。 + +### 3.3 客户端形态 + +截至 2026-08-03,当时记录中仍包含 Android 实验链路与 Web Host 验证链路。 +这部分内容的历史价值在于说明项目曾经同时验证过多种前端承载方式,但它们不应自动等同于今天的唯一产品形态。 + +## 4. 当时已经验证通过的高价值事实 + +以下事实具有较强保留价值,因为它们说明“这条路线曾经真实跑通过”: + +- Hermes Adapter 与 AppServer 的端到端消息链路已验证; +- AppServer 的 Webhook 接入已验证; +- Adapter 对 HTTP 与 Hermes CLI 已设置超时; +- SQLite 已用于 inbox/outbox/cursor 等最小持久化; +- WuKongIM 3.0 单节点本地运行链路已验证可用; +- 旧唐僧叨叨 Compose 套件已从当前主线中移除。 + +## 5. 从今天看仍然值得保留的工程结论 + +### 5.1 已退役路线必须继续保持退役 + +这份记录明确说明: + +- 唐僧叨叨 v1.5 + WuKongIM v2 本地套件已退役; +- 后续不得再把旧兼容性假设混入当前主线结论。 + +这条边界今天仍然重要。 + +### 5.2 运行时细节中的两个关键坑 + +这份记录里有两条非常有价值的“踩坑结论”: + +- WuKongIM `/channel/messagesync` 的 `start_message_seq` 是**包含式**; +- Adapter / Agent 侧 HTTP 请求必须设置超时,避免挂死。 + +这两条都值得继续保留,因为它们是会反复影响实现正确性的工程事实。 + +### 5.3 密钥与部署边界必须明确 + +记录中明确提到: + +- `LINEUP_AGENT_SHARED_SECRET` 未进入私有 `.env` 时,部分在线状态判断不会成立; +- 这类问题不一定阻断消息通路,但会影响部署完整性与上线判断。 + +这类结论仍然值得保留为运维注意事项。 + +## 6. 应被视为历史快照,而不是当前状态的内容 + +以下内容应继续保留,但必须带日期阅读: + +- 当天在线的 IP、端口、URL; +- 当天使用的提交号、版本号和 APK 体积; +- 当天某台真机、某个 Web Host、某个 Manager 页面是否可访问; +- 当天的已知问题、待办项和临时限制。 + +这些内容对于追溯 2026-08-03 的系统状态有价值,但不应再作为“今天系统还处于该状态”的直接依据。 + +## 7. 对根目录 `项目状态记录.md` 的整合结论 + +从文档治理角度,这份源文档最有价值的部分已经分为两层: + +- 项目级阶段判断:应保留在 `agent_ops/01.项目总览/02.当前状态与阶段判断.md` +- 历史运行基线与验证事实:应保留在本文件 + +这能避免把“历史核验记录”和“当前项目总览”混在一份文档里继续膨胀。 diff --git a/05.运行运维/README.md b/05.运行运维/README.md new file mode 100644 index 0000000..6825877 --- /dev/null +++ b/05.运行运维/README.md @@ -0,0 +1,21 @@ +# 运行运维文档说明 + +本目录预留给后续沉淀以下高时效性资料: + +- 运行状态核验; +- 部署说明; +- 联调记录; +- 端到端验证记录; +- 端口、地址、版本、提交号等带日期边界的事实。 + +在本轮整合中,这类内容暂时不直接迁入主入口文档,而是在 `01.项目总览/02.当前状态与阶段判断.md` 中只保留高层结论与时效性判断。 + +当前已迁入: + +- [2026-08-03运行基线与验证记录](./01.2026-08-03运行基线与验证记录.md) + +后续如果需要继续消化根目录 `项目状态记录.md` 的其余内容,应优先拆成: + +- 历史运行基线; +- 部署/联调注意事项; +- 已失效的历史快照或实验记录。 diff --git a/90.历史归档/README.md b/90.历史归档/README.md new file mode 100644 index 0000000..8e42fed --- /dev/null +++ b/90.历史归档/README.md @@ -0,0 +1,11 @@ +# 历史归档说明 + +本目录用于承接以下材料: + +- 已退役设计; +- 早期探索方案; +- 阶段记录; +- 演示样例; +- 不再作为当前权威入口的旧文档。 + +迁入这里不代表文档没有价值,而是代表它主要用于追溯,而不是直接指导当前实现。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e1f0611 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# Agent Ops 文档仓 + +`agent_ops/` 是 LineUp 项目的文档整理与治理目录。它的目标不是简单收集 Markdown,而是把当前仍有效的项目文档、架构文档、迭代文档、运行记录和历史资料按统一规则沉淀下来,便于后续由 agent 持续维护。 + +## 当前整理原则 + +- 目录名和文件名统一使用中文; +- 目录按用途分层,并按阅读/维护顺序增加编号前缀; +- 迁移以“整合”为主,不做原样搬运; +- 每类内容只保留一个权威入口; +- 历史材料保留,但必须和当前有效文档分开。 + +## 当前目录结构 + +```text +agent_ops/ +├── README.md +├── 00.目录治理/ +├── 01.项目总览/ +├── 02.架构设计/ +├── 03.迭代规划/ +├── 04.程序清单/ +├── 05.运行运维/ +├── 90.历史归档/ +└── 设计/ # 迁移前的旧设计目录,后续分步收口 +``` + +## 各目录职责 + +### `00.目录治理/` + +放文档治理本身需要的资料: + +- 目录规划; +- 迁移计划; +- 迁移记录; +- 权威文档清单; +- 命名与维护规范。 + +### `01.项目总览/` + +放项目级入口文档,服务于第一次进入项目的人或 agent: + +- 项目总览; +- 当前状态; +- 仓库边界; +- 阶段判断。 + +### `02.架构设计/` + +放仍然有效的设计和架构文档;原 `lineup-app/设计/` 与原 `agent_ops/设计/` 已迁入这里。 + +### `03.迭代规划/` + +放计划、阶段目标、设计评审、技术实施规范和验收评审。后续从 `lineup-app/迭代/` 分步整合。 + +### `04.程序清单/` + +放跨仓程序清单、模块说明和目录职责文档,用作代码地图层。 + +### `05.运行运维/` + +放运行事实、部署说明、联调记录和验证记录。这里承接比“项目状态总览”更细的证据型材料。 + +### `90.历史归档/` + +放已退役、仅用于追溯、或不再作为当前实现依据的历史资料。 + +## 本轮进度 + +本轮已完成: + +- 建立 `agent_ops` 的中文编号目录规划; +- 开始整合迁移工作区根目录文档; +- 将“项目总览、状态结论、协作边界、跨仓程序清单”沉淀为新的目标文档。 + +后续两步将按用户要求继续推进: + +1. 继续维护 `02.架构设计/` +2. 继续维护 `03.迭代规划/` + +## Git 说明 + +用户计划将 `agent_ops/` 单独初始化为 Git 仓库,并推送到: + +`ssh://git@100.121.118.116:2222/lineup/agent_ops.git` + +截至 2026-08-07,本目录内尚未检测到独立 Git 仓库元数据,因此本轮先完成目录规划与本地文档整合;待仓库初始化后,后续修改应在 `agent_ops` 仓库内独立提交与推送。