move runtime_workspace and agent_tool_route stages to completed

This commit is contained in:
2026-08-07 17:10:01 +08:00
parent 10840909ab
commit e94fbc4fb4
20 changed files with 0 additions and 0 deletions
@@ -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 直接启动的单次专注 operationRuntime 托管 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 / WhiteboardPomodoro 在第 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 schemaMiniApp 通过一个最小、受 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-06SDK 是否迁入 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 都迁入 Rustbundled 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-01D04-05 已形成决议并回填到 [04.runtime_workspace.md](04.runtime_workspace.md) 与
[plan.md](../plan.md)。所有 P0~P3 设计问题已清零;D04-06 作为 P5 carryover 保留,不阻塞本迭代。
@@ -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-01D04-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-C1instance 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、改变
焦点或绕过 lifecyclePomodoro 的到期/中断和唯一 Tool result 必须由 Runtime deadline operation 裁决。
这也没有把静态 IM/App 子会话误当作 MiniApp 业务存储:第 04 次主定义仍只让子会话记录人与 Agent 的交互和
结果,符合第 03 次“App 子会话不是 App 操作日志”的规则。
### D04-C2Agent 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-C4App 子会话和 pending Interact 已一致
主定义限制“仅恢复同一个未结束 instance 时复用同一个子会话”;结束后子会话只读;关闭时仍 pending 的
Interact 标准交互留在主 IM/Interact 中,回答不得追加到旧子会话。该规则与第 03 次的 instance 生命周期、
`pending_interaction_call_ids` 和“关闭不自动取消 Interact 交互”规则一致。前台 Pomodoro 上的 Interact
覆盖层也没有改变交互 owner。
### D04-C5Whiteboard 范围已消除冲突
`04.runtime_workspace.md` 第 5 节与 `plan.md` 第 3 节均明确:Whiteboard 只保持第 03 次已完成的 SDK/隔离
回归,不接入第 04 次工作区切换或完整用户流程;Pomodoro 才是本轮唯一新增的工作区闭环验证应用。
### D04-C6Rust 为正确的 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 原子完成后立即按一般收口关闭 instanceSurface 会先被
卸载,Pomodoro 无法兑现“App 内完成提示”;若 UI 本地自行显示后再关闭,则可能在重启、旧 Surface 或重复
回调下与已提交 outbox 脱节。第 03 次的既有规则还区分“Tool 完成不自动关闭 App/子会话”和
`AppLifecycleManager.close` 才结束子会话;第 04 次为 Pomodoro 采用不同的自动关闭策略是允许的,但必须
明确这是 Pomodoro 的特例及其顺序。
建议二选一并写入工作内容、恢复规则和验收:
1. **保留完成展示窗口。** deadline operation 原子写 `completed` 和唯一 result/outboxRuntime 将终态投影给
PomodoroSurface 在一个明确的受控窗口内显示完成提示;窗口结束、用户确认或用户离开后由 Runtime 发起
`AppLifecycleManager.close`,再结束子会话并恢复 Interact。重启落在窗口内时须能按持久化终态恢复到同一
收口路径,而不得再写结果。
2. **立即收口。** Runtime 完成后立刻关闭 App、结束子会话、恢复 Interact;删除“Pomodoro 显示自己的完成
提示”,或将完成提示明确改为 Interact 的可信呈现而非已关闭 MiniApp 的 UI。
无论选择哪种,验收都应覆盖 `completed` 与 close 并发、已提交 outbox 的重试、终态 UI 不产生第二次 Tool
完成,以及完成前/后 pending Interact 的归属。
## D04-09Pomodoro 的 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-07D04-09 已回填本轮实施权威 [04.runtime_workspace.md](04.runtime_workspace.md) 和
[plan.md](../plan.md);实施时须依照已冻结的 Tool、完成收口和 Manifest 契约提供 fixture 与自动化验收;
- D04-C6 继续作为 P5 carryover 留在本记录与第一轮记录中;没有 profiling 证据前,不把 Rust 迁移纳入当前
实现范围;
- 本评审只记录发现和建议,未修改第 04 次主定义、正式方案、计划或实现。
@@ -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 commandAgent 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 声明的 ToolPomodoro 本身只显示和接收 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` 的唯一结果 / outboxPomodoro 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
或 outboxAgent 必须先调用 `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-10D04-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. 本评审只记录发现,未修改主定义、计划、正式方案或实现。
@@ -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 ToolRuntime 只依据已验证的 `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({})` 是短 ToolTool 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 SchemaRuntime 在将 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 dataRuntime 保证 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 和生命周期稳定后,再单独定义任务管理业务模型。
@@ -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 ToolPomodoro 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
分发 handlerManifest 不传递 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.startAfterForegroundActivationinterrupt 调用
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(...)` 本地映射分发
handlerMiniApp 自己理解业务、写 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_executionpomodoro.start 固定使用
pomodoro.start_deadline.v1pomodoro.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 和 outboxHost 本身不能直接写这些记录。
## 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/outboxAgent Tool 返回对应的稳定
receiptlifecycle 继续执行安全 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 的 readyRuntime 取得其当前 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
→ ToolRouterEnvelope / 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<Data extends JsonObject = JsonObject> {
get(): Promise<{ data: Data | null; revision: number }>;
replace(request: { data: Data; expected_revision: number }): Promise<{ revision: number }>;
subscribe(listener: (snapshot: { data: Data | null; revision: number }) => void): Unsubscribe;
}
```
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 4Surface 随后
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 或 interruptHost 不能自行改持久化状态;
- 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)`,恢复 InteractAgent 得到 `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 均进入可见 InventoryInventory 不含内部 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 / readyRuntime 就能将已验证的 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 迁移或应用市场功能替代本规范中的任一
验收场景。
@@ -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_operationRuntimeOperationCommitCoordinator 先原子持久化业务事实,再驱动 Lifecycle、Host 和 Surface 对账;Runtime 不向 Pomodoro 或其他 MiniApp 暴露专属 operation / instance-state API,而是向当前 MiniApp 会话提供通用、隔离、版本化的业务数据字典;所有 MiniApp 都经历统一的 `starting → ready | failed | cancelled` 启动过程,而每个 Tool 决定自己只需 App ready、还要求前台,或根本不需要 activation。Runtime 只路由已验证的 App 方法和参数,不理解其业务含义或直接写 MiniApp 数据。**本轮 TIS-01TIS-05 已全部解决。** 后续实现必须按已定契约补齐 SDK / Bridge、Host 激活确认和验收 fixture,不得把实施细节重新解释为产品分歧。
## 问题清单(Outline
状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施规范;✅ 已确认通过;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但须记录原因与重新评估条件。
| 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 |
|---|---:|---|---|---|
| ✅ | P1 | TIS-01 | 已启用 MiniApp 如何将其 Agent Tool 声明注册给 Runtime,并由 Runtime 路由。 | 已确认并回填:安装时保存已验证 Manifest 的 Registry 投影;Runtime 启动和状态变化时主动重建 Tool Registry / Agent InventoryRouter 按受控 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` activationTool 声明 `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 交给
RuntimePomodoro 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-C3Runtime 事实、MiniApp 业务数据与 IM 静态记录各自归位
Runtime 内部的 operation 保存 deadline、终态竞争、Tool receipt、outbox 和恢复所需的可靠性事实;它不是
Pomodoro 需要读取或修改的专属数据模型。Runtime 同时为当前 MiniApp 会话保存一份通用 JSON 数据字典,但不认识
其中的 `current``history` 或其他业务字段。MiniApp 自己决定这些字段的组织、是否追加历史以及业务上是否允许
修改;Runtime 只负责作用域隔离、revision、持久化、恢复和技术安全限制。一次专注完成后写进 Interact / IM 的
记录是已发生事实的静态副本,不会随 MiniApp 后续更新自身业务数据而改变。
## TIS-01Manifest 信息在 Registry / Router 边界丢失
**已解决(2026-08-06,收敛决议)。** 实施规范第 3.1.1 节已经冻结:每个已安装 MiniApp 在安装时将已验证
Manifest 投影保存进 RegistryRuntime 启动以及 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.v1interrupt 固定路由到
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-03MiniApp 如何经 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 不查询或自动绑定最近的终态 operationoperation_already_final 只适用于
已经绑定到同一 operation 的迟到或重放调用。
实施规范和主定义均要求 `pomodoro.interrupt({})` 不接受 `operation_id`,并列出
`interrupted | no_active_focusing_operation | operation_already_final`。但一个不同的新 interrupt call 到达时,如果
当前 conversation 已不存在 focusing operationRuntime 没有携带目标 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-05MiniApp 启动就绪与前台激活
**已解决(2026-08-06,收敛决议)。**
此前实施规范把“Host 挂载失败”错误地只看成 Pomodoro operation 创建后的 UI 副作用,因此同时保留了“回滚”与
“interrupted 收口”两种互斥行为。用户已经确认:是否进入前台并非所有 MiniApp Tool 的统一前提;它是具体 Tool
的业务语义。Runtime 因此必须先管理通用 activation,而不是先假设业务 operation 已开始。
```text
所有 MiniAppstarting → ready | failed | cancelled
foreground_requiredpomodoro.start
= Host 确认 Surface 已成为用户当前前台 App,才 ready
= ready 后才创建 Pomodoro focusing operation 和 deadline
app_ready(未来 todo.add
= To-do 已 active / readyRuntime 可以取得当前 App session 并路由 method + 参数
= 不要求创建或显示 Surface;To-do 自己执行业务并写 session data
activation_not_requiredpomodoro.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 迁移。
@@ -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-01TIS2-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-C1Pomodoro 只有进入前台才真正开始
`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 或 historyRuntime 不识别这些字段,也不直接向字典写 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-01Runtime 如何调用未前台显示的 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 / readyRuntime 可路由方法调用;不要求前台
= 未来 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-02SDK 声明 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 私有函数 targetManifest 不包含函数、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 / startingRuntime 已收到请求,正在把 Pomodoro 打开到前台
= startedHost 已确认前台,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-05activation 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 CASCAS 只防止旧版本覆盖,不承诺
自动合并业务冲突。**重新评估触发条件:** 在开始任何一个“Agent Tool 与 UI Action 都能修改同一 App 子会话”的
功能之前,例如手动编辑 To-do 或 Whiteboard 的首个交互式绘制流程,必须先将本项提升为该迭代的 P1 并完成设计。
## TIS2-04session 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-01TIS2-05 已回填主迭代定义、实施规范与正式方案;TIS2-06 是已记录的 P4 延续项,在其重新评估触发条件出现前不阻塞第 04 次验收。
2. 第 04 次进入实现后,必须以本文件和实施规范的 Web / Tauri fixture、单元测试、构建与代表性 Host 流程验证这些决议;实现完成后进行下一轮独立验收评审。
@@ -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-A11Tauri 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 作为最终验收口径,则必须先更新主定义与评审基线,再重新做独立复核。
@@ -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 验收均已满足主定义与技术实施规范,当前可以宣布本轮验收通过。