# `03.sdk_and_coreapp` 第 3 次设计评审记录 > 评审编号:03 > 评审日期:2026-08-05 > 评审对象:[03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) > 参考资料:[APP架构设计.md](../../设计/APP架构设计.md)、[01.design_review.md](01.design_review.md)、[02.design_review.md](02.design_review.md) > 评审方式:基于当前主定义的独立只读复核;重点检查已确认的边界在契约、实施步骤与验收目标之间是否能够由同一套实现兑现。 > 结论:产品层级、标准交互归属和受限 MiniApp 信任模型已稳定;未发现 P0 架构冲突。经本轮统一收敛,3 项 P1 与 2 项 P3 均已确认并回填主定义文档;后续不再进行纯设计评审,直接进入实现,并在实现完成后做一次关闭式复核。 ## 问题清单(Outline) > **状态标记:** ✅ 已确认并回填设计; 🟡 待产品/架构决策; 🔵 待在契约中细化; ⚪ 文档或实施编排建议; — 不适用或无此问题。 > > 本清单是本次评审的当前有效视图。后文说明问题为何会造成实现分歧,并给出需要冻结的最小决策;在问题确认前,建议不是实施依据。确认后应先更新本清单,再回填 [03.sdk_and_coreapp.md](03.sdk_and_coreapp.md) 的契约、实施步骤和验收项。 | 状态 | 编号 | 问题 | 当前结论 / 下一步 | |---|---|---|---| | — | P0 | 阻塞级架构冲突 | 未发现。Runtime 为唯一通信与裁决方、标准交互属于 Interact、bundled MiniApp 受限运行的主线一致。 | | ✅ | P1-1 | 用户关闭 App 时,尚未结束的普通 MiniApp Tool 怎样收敛 | 已确认并回填:一旦 Runtime 接受关闭,未终态的普通 Tool 原子转为 `cancelled(app_closed)`;已提交结果只继续 outbox;随后关闭 Surface、停止 instance、结束子会话并恢复焦点。标准交互不自动取消。 | | ✅ | P1-2 | Agent `dismiss` 等待中的标准交互,缺少可执行的控制契约 | 已确认并回填:Runtime 私有 `interaction.dismiss` 以 `control_id + call_id` 请求;Runtime 从受认证 Envelope 推导 Agent/会话,幂等处理并只写唯一 cancelled outbox。App 已关闭事件带出仍 pending 的交互 call_id。 | | ✅ | P1-3 | `interactive` Tool 如何确定 App 子会话关联,和通用 `ToolDescriptor.target` 怎样一致 | 已确认并回填:无 `app_session_context` 一律归主 IM;有上下文时 Runtime 验证同 Agent、同父会话的 App 子会话,已关闭会话可仅作历史关联。interactive 不使用普通 Tool target。 | | ✅ | P3-1 | 标准交互请求类型不能直接作为 fixture 或代码依据 | 已确认并回填:去除重复字段,定义请求联合类型;可回答交互使用 Runtime 计算的 `expires_in_ms`,默认 15 分钟、范围 1 分钟至 24 小时;notice 不接受期限。 | | ✅ | P3-2 | Whiteboard 的完成后关闭表述与统一生命周期规则冲突 | 已确认并回填:所有 bundled App 的普通 Tool 完成只结束 Tool;关闭 Surface/instance/子会话只能由显式 Lifecycle 关闭处理。重复目录条目已删除。 | ## 通过项 - `Interact` 是唯一 `kind = system` MiniApp;Task Dashboard 与 Whiteboard 均是 `kind = bundled`,没有可信 Host DOM、Tauri、Transport、Store、Agent 或任意网络特权。 - `notice / choice / confirm / input` 始终是人与 Agent 的会话交互,不进入任何 MiniApp SDK Inbox 或 `sdk.tools.subscribe`;前台 bundled App 上的视觉覆盖不成为 owner 或提交权限。 - Runtime 保存交互归属并以原子状态写入决定唯一结果;用户首次有效回答、Agent dismiss、到期和 Runtime 失败不会产生多条 Agent outbox。 - App 子会话只保存人与 Agent 围绕 App 的交互,不记录 Dashboard 表单、画板编辑等 App 内部业务动作;关闭 App 不自动取消仍 pending 的 Interact 交互。 - 普通 `input` 按现有 IM 内容与日志基线处理;秘密输入已正确作为未来独立的 `password-input` P4 遗留事项保留在 [02.design_review.md](02.design_review.md)。 ## P0:阻塞问题 无。 ## P1:开始相关编码前必须确认(均已解决) ### P1-1:用户关闭 App 时,尚未结束的普通 MiniApp Tool 怎样收敛 **用人话说:** 用户把 Task Dashboard 或 Whiteboard 关掉时,Runtime 不能让刚才交给那个 App 的工作 悬在半空。Agent 要么收到“这项工作已取消/失败”的唯一结果,要么 Runtime 明确保留一个可恢复、仍有执行者 的工作;不能只关闭画面而不说明 Tool 的命运。 当前文档同时出现了三种没有被统一的说法: ```text Task Dashboard Tool 完成 → 不自动关闭 Dashboard 或 App 子会话 Whiteboard App 完成或失败 → Runtime 关闭 Surface、恢复 Interact MiniApp requestClose() → Runtime 检查 pending Tool、Surface、operation 和 Policy → 允许关闭或返回拒绝码 ``` 最后一条没有说明“检查之后”的规则。若用户关闭正在执行 Tool 的 App,Tool 是被拒绝关闭、由 Runtime 先取消 Tool、由 App 收到取消后再关闭,还是允许 Surface 消失但实例在后台恢复执行?这些选择对 outbox、恢复和用户 看到的状态都不同。Whiteboard 的“完成后关闭”也与 Task Dashboard 的“完成不关闭”相互冲突。 **已确认(2026-08-05):** 一旦 Runtime 接受用户、MiniApp 或 Policy 的关闭请求,关闭的意思就是释放该 App instance,不是把它偷偷留在后台。Runtime 不再向该 instance 投递新 Tool;对绑定该 instance 且仍为 `received / routing / waiting_for_app / running` 的普通 Tool,以 `app_closed` 原因原子转为 `cancelled`,并且 每条 Tool 只写一条 cancelled outbox。已处于 `submitted` 的 Tool 结果不可改写,Runtime 继续将已固定的结果 可靠发出。之后 Runtime 关闭 Surface、停止 instance、结束 App 子会话并恢复前一有效前台 App。 同一时刻 Tool 完成与关闭竞争时,第一个原子终态写入获胜;重复关闭、重启恢复和迟到上报不得产生第二条 outbox。用户若只想暂时离开 App,应进入后台而不是关闭。此规则只处理普通 MiniApp Tool;仍 pending 的 人与 Agent 标准交互不自动取消,Runtime 仅通知 Agent,由 Agent 选择是否 dismiss。 以下原“需要冻结”的项目均由上述决议覆盖: 1. 对每种 Tool 状态(`waiting_for_app`、`running`、`submitted` 等),规定用户/Policy 请求关闭时的行为; 2. 规定普通 Tool 的最终结果由谁写入、是否必须先让 Tool 进入 `completed / failed / cancelled / expired` 才能完成关闭; 3. 明确“关闭 App instance”“关闭该 App 的 Surface”“Tool 成功/失败”三者不是同一个事件,并定义允许的先后顺序; 4. 将 Task Dashboard 与 Whiteboard 的完成、关闭和焦点恢复规则统一到同一条生命周期原则; 5. 增加关闭进行中 Tool、重启恢复和重复关闭只产生一个最终 Tool outbox 的验收 fixture。 ### P1-2:Agent `dismiss` 等待中的标准交互,缺少可执行的控制契约 **用人话说:** 文档已经允许 Agent 看到“画板已关闭”后,决定把之前的问题收起来。这很好;但还没有写清 Agent 发来的“收起这题”消息长什么样,Runtime 怎么确认它收的是正确那一道题,以及网络重发时如何不重复 通知 Agent。实现者因此可能各自发明一个临时控制消息。 当前仅有行为描述: ```text Agent remote dismiss → Runtime 将 pending / presented interaction 终结为 cancelled → 写入唯一 cancelled outbox ``` 但缺少下面的契约: - Agent 发起 `dismiss` 使用 `interaction_id`、原始 `call_id`,还是二者都使用; - Runtime 如何从已保存记录校验 `agent_id`、`conversation_id` 与当前状态,而不是信任客户端字段; - 目标已回答、已到期或同一条 dismiss 重放时,应得到何种稳定回执,是否绝不新增 outbox; - Runtime 发出的 App 已关闭事件如何关联原 Tool/interaction,使 Agent 能准确选择要 dismiss 的问题; - dismiss 控制请求本身如何去重、审计并在 Runtime 重启后恢复处理。 **已确认(2026-08-05):** 定义 Runtime 私有的 Agent → Runtime `interaction.dismiss` 控制契约。Agent 使用 自己发起原 interactive Tool 时持有的 `call_id` 定位目标,并为每次控制请求提供唯一 `control_id`。Runtime 从 受认证 Envelope 取得 `agent_id` 与 `conversation_id`,不信任请求额外携带的归属字段;它以这两个值和 `call_id` 查找交互,原子地将仍 pending / presented 的记录转为 cancelled 并写入唯一 outbox。已终态或重放 请求只返回稳定幂等回执,不覆盖结果、不再写 outbox。 App 已关闭事件会携带仍 pending 的 `pending_interaction_call_ids`,让 Agent 能按业务需要精确 dismiss;该控制 契约不属于 MiniApp SDK,也不能投递给 Interact 或 bundled MiniApp。 ### P1-3:`interactive` Tool 如何确定 App 子会话关联,和通用 `ToolDescriptor.target` 怎样一致 **用人话说:** 视觉上哪个 App 在前台,不等于 Agent 的问题就属于哪个 App。例如用户正看 Whiteboard, Agent 仍可能问一条普通 IM 问题;反过来,画板已经关了,Agent 仍可能针对刚才的画板提问。因此 Runtime 不能用“当前前台 App”猜测问题应该放进哪个子会话。 当前记录有可选 `app_session_id`,且要求与 App 有关的问题写进子会话;但没有说明 Agent Tool 请求如何 表达这层上下文、Runtime 如何验证它。与此同时,所有 `ToolDescriptor` 都要求 `target.app_scope`,而 `interactive` 明确不创建/复用任何业务 MiniApp,也不投递给 Interact 的 SDK Tool Inbox。这会造成至少两种 不兼容实现:有人将交互强行 target 到 `chat`,有人直接跳过 `target`,也有人按当前前台 App 自动归档。 **已确认(2026-08-05):** 不带 `app_session_context` 的 interactive Tool 一律记录在主 IM,不创建或猜测 App 子会话。Agent 若要关联某段 App 工作,可在 interactive Tool 的运行时上下文中提供 `app_session_context.app_session_id`;Runtime 必须验证其属于同一 `agent_id`、父 conversation 和真实 App 子会话。已经关闭的子会话仍可作为历史上下文关联,但不会复活 instance 或旧问题。验证失败则拒绝该 Tool, 不创建交互。 `handling = interactive` 不使用普通 MiniApp Tool 的 `target`,也不因此变为 Interact MiniApp Tool。Runtime 在创建 App 子会话时向当前 Agent 可靠发送其稳定 ID,供后续 Agent 交互引用。 以下原“需要冻结”的项目均由上述决议覆盖: 1. 不带经验证 App 上下文的 `interactive` Tool 一律写入主 IM,不创建 App 子会话; 2. 若 Agent 需要关联某段 App 工作,定义它可引用的稳定 App 子会话标识,以及 Runtime 必须验证的 `agent_id`、父 conversation、App session 状态和权限边界;已关闭 App 关联的问题是否仍允许保留,需要明确; 3. 为 `handling = interactive` 定义与普通 `ToolDescriptor.target` 不同的明确规则:例如 target 对它不适用, 或只允许一个受限的“会话上下文”字段;不能让它暗中变成 Interact MiniApp Tool; 4. 在 fixture 中覆盖“Whiteboard 前台但问题属于主 IM”“App 已关闭但问题仍关联旧子会话”“非法/跨会话 `app_session_id` 被拒绝并不产生交互”三种情况。 ## P2:本迭代验收前必须处理(本次无未决项) 本次未发现独立 P2 问题。P1 项处理后,应把其中的关闭、dismiss、上下文绑定 fixture 纳入本迭代验收。 ## P3:本迭代结束前必须处理(均已解决) ### P3-1:标准交互请求类型不能直接作为 fixture 或代码依据 `ChoiceRequest` 代码块中 `prompt` 出现两次。它不是不同字段,直接照抄会造成 TypeScript 重复属性定义。 此外,记录使用了尚未声明的 `StandardInteractionRequest`,文字规定 Agent 可给出 1 分钟至 24 小时的期限, 但四种最低请求类型都没有统一的期限字段。 **已确认(2026-08-05):** 删除重复 `prompt`;`StandardInteractionRequest` 由四种请求组成。可回答交互 在请求中使用 `expires_in_ms`,Runtime 根据自身当前时间计算并持久化 `expires_at`;未提供时默认 15 分钟, 仅接受 1 分钟至 24 小时。notice 不等待回答,也不接受期限。fixture 覆盖默认、越界和 notice 错带期限。 ### P3-2:Whiteboard 的完成后关闭表述与统一生命周期规则冲突 Task Dashboard 已清楚规定 Tool 完成不会关闭 App;Whiteboard 的最小体验却写成“App 完成或失败,Runtime 关闭 Surface、恢复 Interact”。这会让两个同为 `kind = bundled` 的参考 App 获得不同且没有声明依据的 生命周期语义,也和“只有 `AppLifecycleManager.close(instance_id)` 才停止实例/结束子会话”不一致。 **已确认(2026-08-05):** Whiteboard 与 Task Dashboard 使用相同规则:Tool `complete / fail` 只结束 Tool; Surface、instance、焦点和 App 子会话仅由前后台或显式 `AppLifecycleManager.close` 处理。SDK v1 不为 Whiteboard 预设“提交后自动关闭”的例外;将来若需要,必须作为显式 Lifecycle Policy 并遵循 P1-1 的关闭收敛。 实现模块目录树中的 `conversation-store.ts` 也重复出现一次,应一并删除重复行,避免错误引导目录改动。 ## P4~P5:遗留问题 本次没有新增 P4/P5。第 2 次评审已登记的 `P4-1`(历史提案可读性)和 `P4-2`(未来 `password-input` 秘密输入原语)继续作为不阻塞当前迭代的遗留问题,其延期原因与重新评估条件以 [02.design_review.md](02.design_review.md) 为准。