# 04.runtime_workspace 设计评审(二) **评审编号:** 02 **日期:** 2026-08-06 **评审对象:** [04.runtime_workspace.md](04.runtime_workspace.md)(实施权威)、[01.design_review.md](01.design_review.md)、[00.base.md](../00.base/00.base.md)、[01.kernel.md](../01.kernel/01.kernel.md)、[03.sdk_and_coreapp.md](../03.sdk_and_coreapp/03.sdk_and_coreapp.md)、[plan.md](../plan.md)、[app_final_design.md](../../设计/02.正式方案/app_final_design.md)、[lineup-runtime-sdk-architecture.md](../../设计/02.正式方案/lineup-runtime-sdk-architecture.md)、[lineup-app-layer-architecture.md](../../设计/02.正式方案/lineup-app-layer-architecture.md)。 **评审方法:** 独立地以第 04 次迭代定义为实现依据,逐项回溯前三次迭代和正式 Runtime 契约;重点检查 instance state / deadline operation 的所有权、`pomodoro.start` 与用户中断的终态竞争、离开/暗屏/重启、App 子会话与 pending Interact、Whiteboard 范围和 Rust P5 延期。 **总体结论:** D04-01~D04-09 已确认的方向均已被第 04 次主定义或计划正确吸收:业务快照与 deadline operation 分离、Agent Tool `pomodoro.start` / `pomodoro.interrupt`、到点/中断原子终态、暗屏/锁屏不等于离开、子会话只读与 pending Interact 留在主 IM、Whiteboard 不进入本轮闭环、Rust 仅为 P5 后续评估。D04-07 已澄清为 Agent Tool 路由契约;D04-08 已选择“立即收口、由 Interact 呈现完成结果”;D04-09 已选择 `retain_readonly` 快照保留策略。所有 P0~P3 设计问题已清零,本迭代设计已就绪,待实施。 ## 问题清单(Outline) 状态标记:🔴 未解决,必须处理;🟡 已提出修复方向,尚未确认或未回填实施权威;✅ 已解决;⚪ 可延期但必须保留记录。P0~P3 必须在当前迭代处理;P4~P5 可以延期,但必须记录延期原因和重新评估条件。 | 状态 | 优先级 | 编号 | 问题 | 当前结论 / 下一步 | |---|---:|---|---|---| | ✅ | P1 | D04-07 | `pomodoro.interrupt` 的调用方向与目标绑定未明确。 | 已确定为 Pomodoro 在 Manifest 中声明、由 Agent 调用的短 Tool;Runtime 从调用会话绑定当前 focusing operation,负责路由、原子裁决和稳定回执。 | | ✅ | P1 | D04-08 | `completed` 后既要求 Pomodoro 显示自身完成提示,又在主流程中要求 Runtime 收口关闭 App、恢复 Interact;没有定义两者的顺序和触发者。 | 已选择立即收口:Runtime 原子完成并投递唯一结果/outbox 后立即关闭 Pomodoro、结束子会话、恢复 Interact;完成提示由 Interact / Agent 呈现。 | | ✅ | P2 | D04-09 | Pomodoro 要保存 instance snapshot,但没有声明其 `app.instance-state.v1` Manifest 配置、schema 版本/字段、配额和 `retention` 选择。 | 已确定 `app.instance-state.v1` 最小 Manifest:严格 schema v1、4 KiB 配额、`retain_readonly`。关闭后旧快照只作历史展示,不可写入、不可复活为 focusing。 | | ✅ | — | D04-C1 | instance state 与 deadline operation 的事实来源和持久化边界 | 已正确同步;不构成新问题。 | | ✅ | — | D04-C2 | `pomodoro.start`、到点/中断竞态和 outbox 唯一性 | 已正确同步;D04-07 补足 Agent Tool 的路由与目标绑定契约。 | | ✅ | — | D04-C3 | 用户离开、自动暗屏/锁屏、Surface 重载和重启 | 已正确同步;不构成新问题。 | | ✅ | — | D04-C4 | App 子会话、只读历史和 pending Interact | 已正确同步;不构成新问题。 | | ✅ | — | D04-C5 | Whiteboard 第 04 次范围 | 已正确同步到主定义和 `plan.md`;不构成新问题。 | | ⚪ | P5 | D04-C6 | Runtime Core / SDK 是否迁入 Rust | 已保留为延期项;当前不应扩大到本轮实现。重新评估条件仍为 profiling 证明 Runtime Core 是性能热点。 | ## 通过项与一致性证据 ### D04-C1:instance state 与 deadline operation 的边界已一致 主定义第 2.2、2.3 和第 3 节将 Runtime 定为 schema/revision 受控的 instance snapshot 保存者,同时将 `ends_at`、Tool 终态和 outbox 交给 deadline operation;`remaining_seconds` 仅由 UI 按绝对截止时间派生。 这与正式方案的明确边界一致:`app_scope + instance_id` 隔离状态快照,快照不能完成 Tool、写 outbox、改变 焦点或绕过 lifecycle;Pomodoro 的到期/中断和唯一 Tool result 必须由 Runtime deadline operation 裁决。 这也没有把静态 IM/App 子会话误当作 MiniApp 业务存储:第 04 次主定义仍只让子会话记录人与 Agent 的交互和 结果,符合第 03 次“App 子会话不是 App 操作日志”的规则。 ### D04-C2:Agent Tool 的终态竞争与 outbox 所有权已一致 第 04 次主定义将 `pomodoro.start({ duration_seconds, activity? })` 定义为长期 Agent operation,并由 Agent 调用短 Tool `pomodoro.interrupt({})` 请求停止当前专注;到点、Agent 中断和关闭竞争一个原子终态,第一成功者 产生一次结果/outbox;重复或迟到的中断只获得稳定幂等回执。 这符合第 03 次对 Tool/交互的“Runtime 原子终态 + 唯一 outbox”模式,也符合正式方案中 App 不能自行完成 Tool 或写 outbox 的限制。D04-07 已补足:用户以语言提出停止意图后,Agent 调用 Pomodoro 声明的 Tool, Runtime 而非 MiniApp 界面负责最终裁决。 ### D04-C3:离开、自动暗屏、重启的产品语义已一致 主定义已清楚区分:用户切回 Interact、切到其他 MiniApp 或关闭 Pomodoro 会中断;自动暗屏、锁屏、Surface 重载不会中断;重启按持久化 `ends_at` 恢复或一次性结算;Runtime/Host 完全未运行时不承诺即时系统提醒。 这不再依赖 iframe/UI 定时器,且没有把来电检测、系统免打扰、静音或系统通知偷偷纳入第 04 次范围。 ### D04-C4:App 子会话和 pending Interact 已一致 主定义限制“仅恢复同一个未结束 instance 时复用同一个子会话”;结束后子会话只读;关闭时仍 pending 的 Interact 标准交互留在主 IM/Interact 中,回答不得追加到旧子会话。该规则与第 03 次的 instance 生命周期、 `pending_interaction_call_ids` 和“关闭不自动取消 Interact 交互”规则一致。前台 Pomodoro 上的 Interact 覆盖层也没有改变交互 owner。 ### D04-C5:Whiteboard 范围已消除冲突 `04.runtime_workspace.md` 第 5 节与 `plan.md` 第 3 节均明确:Whiteboard 只保持第 03 次已完成的 SDK/隔离 回归,不接入第 04 次工作区切换或完整用户流程;Pomodoro 才是本轮唯一新增的工作区闭环验证应用。 ### D04-C6:Rust 为正确的 P5 carryover 主定义明确本轮保持 TypeScript Runtime / Web Bridge 基线,且仅在 profiling 已证明 Runtime Core 是热点后另行 评估。该范围不会破坏 Web Reference Host、sandbox iframe、postMessage Bridge 和 UI 必须保留在浏览器侧的 既有边界。 ## D04-07:用户中断的 Agent Tool 路由契约 **已解决(2026-08-06,用户决议)。** `pomodoro.interrupt` 不是 MiniApp 通过 SDK 主动发送的 Runtime command,而是 Pomodoro 在 Manifest 中声明、供 Agent 调用的短 Tool。用户主要经 IM / Voice 向 Agent 表达业务意图;MiniApp 是 Agent 的业务工具,而不是 用户直接操作业务状态的独立应用。Tool Router 从该 Tool 调用的 `conversation_id` 自动绑定当前唯一 `focusing` Pomodoro operation,调用者不能提供 `instance_id`、`app_scope` 或 `operation_id`。Runtime 路由调用、 在与 deadline/close 相同的原子裁决中将长 Tool `pomodoro.start` 收口为 `interrupted`,并给短 Tool 返回 `interrupted`、`no_active_focusing_operation` 或 `operation_already_final` 的稳定回执。若 Surface 不在,Runtime 仍可完成裁决;若存在,只投影最终状态。用户直接离开工作区仍由 Runtime 生命周期中断处理,不伪造 Agent Tool。 以下为发现时、尚未澄清调用方向的风险分析。 第 04 次主定义第 3 节规定用户显式退出通过受限 SDK Runtime command `pomodoro.interrupt(reason = "user_exit")` 请求;又规定 Runtime 仅接受当前 `focusing` instance 的第一次有效 中断,迟到请求返回幂等回执。这个产品语义正确,但尚不足以决定 SDK/Bridge、Web Host 和 Tauri Host 应如何 实现同一动作。 现有正式 SDK 的唯一通用出站入口是 `actions.dispatch(action: AppAction)`,但没有定义 `AppAction` 的 schema、Command type、由 context 派生的 instance 绑定、receipt 格式或稳定拒绝码。第 03 次冻结的 Manifest 也只定义 Agent Tool(`direct / launch / foreground / operation`)和 `AppNavigationAPI.close`;它没有把 业务 MiniApp 任意命令自动变成合法 Agent Tool。若不冻结该层,至少会出现两种相互冲突的实现:MiniApp 用 `sdk.tools.complete/fail` 伪造 `pomodoro.start` 终态,或 Web/Tauri Host 在 UI 侧直接关闭实例;两者都绕开 了 Runtime 的 deadline/outbox 原子裁决。 建议在主定义中明确一个最小、仅 Runtime 可执行的 command,例如概念上: ```ts type PomodoroInterruptCommand = { type: "pomodoro.interrupt"; reason: "user_exit"; // instance_id、app_scope、conversation_id 从不可伪造 SDK context 推导,调用者不得传入。 // Runtime 从当前 instance 关联的 operation / source_tool_call_id 查找目标。 }; type PomodoroInterruptReceipt = | { accepted: true; operation_id: string; state: "interrupted" } | { accepted: false; code: "operation_already_final" | "operation_not_focusing" | "instance_closing" }; ``` 实际字段命名可以不同,但必须同时冻结以下规则:调用者只能操作自己的当前 instance;谁提供或由 Runtime 生成幂等键;`focusing`、deadline、`closing` 与已终态各自的稳定回执;以及 command、deadline 与 `AppLifecycleManager.close` 如何在同一持久化事务或等价 compare-and-set 中竞争唯一终态和唯一 outbox。 Host 的“返回/切换/关闭”应调用同一个 Runtime 内部操作,而不是从 Surface 绕过该 Command。 ## D04-08:完成提示与关闭的顺序未定义 **已解决(2026-08-06,用户决议:方案 B)。** Runtime 到达 `ends_at` 后,先原子写入 `completed`、唯一 `pomodoro.start` 结果与 outbox;随后立即关闭 Pomodoro、结束 App 子会话并恢复 Interact。Pomodoro 不显示 独立的完成提示;完成结果由 Interact / Agent 呈现。关闭后的 outbox 重试不依赖 Pomodoro Surface,且不得再次 完成 Tool。完成与关闭并发时,已提交的 `completed` 终态不得被 `cancelled(app_closed)` 改写。 以下为决议前的风险分析。 主定义同时作出了两项要求:Pomodoro 到时间“显示自己的完成提醒”(第 2.3、3、4.4 节),迭代目标的主流程又 写为“到时间显示完成提醒并回传结果 → Runtime 收口并关闭 App,恢复 Interact”。但它没有规定完成态是否先 投影给仍存活的 Surface、提示展示多久或由谁确认、何时调用 `AppLifecycleManager.close`、以及 App 子会话在 哪个时点结束。 这是可观测行为的分歧,不是纯 UI 细节。若 Runtime 原子完成后立即按一般收口关闭 instance,Surface 会先被 卸载,Pomodoro 无法兑现“App 内完成提示”;若 UI 本地自行显示后再关闭,则可能在重启、旧 Surface 或重复 回调下与已提交 outbox 脱节。第 03 次的既有规则还区分“Tool 完成不自动关闭 App/子会话”和 `AppLifecycleManager.close` 才结束子会话;第 04 次为 Pomodoro 采用不同的自动关闭策略是允许的,但必须 明确这是 Pomodoro 的特例及其顺序。 建议二选一并写入工作内容、恢复规则和验收: 1. **保留完成展示窗口。** deadline operation 原子写 `completed` 和唯一 result/outbox,Runtime 将终态投影给 Pomodoro;Surface 在一个明确的受控窗口内显示完成提示;窗口结束、用户确认或用户离开后由 Runtime 发起 `AppLifecycleManager.close`,再结束子会话并恢复 Interact。重启落在窗口内时须能按持久化终态恢复到同一 收口路径,而不得再写结果。 2. **立即收口。** Runtime 完成后立刻关闭 App、结束子会话、恢复 Interact;删除“Pomodoro 显示自己的完成 提示”,或将完成提示明确改为 Interact 的可信呈现而非已关闭 MiniApp 的 UI。 无论选择哪种,验收都应覆盖 `completed` 与 close 并发、已提交 outbox 的重试、终态 UI 不产生第二次 Tool 完成,以及完成前/后 pending Interact 的归属。 ## D04-09:Pomodoro 的 instance-state Manifest 与关闭后保留策略未确定 **已解决(2026-08-06,用户决议:方案 B)。** Pomodoro 声明 `app.instance-state.v1`,使用严格的 schema version 1、4 KiB 配额和 `retain_readonly`。快照只保存 UI/历史展示所需的活动、关联 operation、时间和展示 状态;operation 仍是终态、deadline 与 outbox 的唯一事实来源。App 关闭后,旧快照仅供只读历史展示,不能 再写入、不能恢复为 `focusing`,也不能作为新一轮专注的运行状态。 以下为决议前的风险分析。 主定义要求 Pomodoro 通过 SDK 保存展示所需的 instance snapshot,并列出可能字段;正式 Runtime 契约则规定 只有声明 `app.instance-state.v1` 的 Manifest 才能使用 schema/revision 受控状态,并要求声明 `state_schema_version`、`state_schema`、`max_bytes` 和 `retention`。第 04 次定义尚未选择 Pomodoro 的实际 Manifest 配置,也没有说明关闭后应使用默认 `delete_on_close` 还是 `retain_readonly`。 这会直接改变重启/关闭后的可见行为和验收:`delete_on_close` 允许完成或中断时清理 UI snapshot,而历史由 App 子会话和 Tool/operation 记录承载;`retain_readonly` 会保留一个不可写旧 snapshot,只能作为历史或新 instance 的受控上下文。两者都可以符合产品目标,但实现、fixture 和隐私/清理预期不同;不能靠默认值隐式 决定。 建议主定义补一份 Pomodoro 的最小 Manifest 片段或等价表格:required feature、严格 schema(至少允许的展示 字段)、schema version、最大大小和明确 retention。还应说明完成/中断/关闭时 instance snapshot 与 deadline operation、App 子会话、Tool/outbox 的独立清理顺序,确保“保留 UI 状态”不会被误解为旧 instance 可恢复为 `focusing` 或可再次写入。 ## 评审关闭条件 - D04-07~D04-09 已回填本轮实施权威 [04.runtime_workspace.md](04.runtime_workspace.md) 和 [plan.md](../plan.md);实施时须依照已冻结的 Tool、完成收口和 Manifest 契约提供 fixture 与自动化验收; - D04-C6 继续作为 P5 carryover 留在本记录与第一轮记录中;没有 profiling 证据前,不把 Rust 迁移纳入当前 实现范围; - 本评审只记录发现和建议,未修改第 04 次主定义、正式方案、计划或实现。