ADR 0027:助手宿主内置工具——情景生成/修改

状态:已接受 日期:2026-08-31 关联:ADR 0022(功能定位与分级确认);ADR 0023(MCP 拓扑,本篇扩展其工具来源);#429(情景 v1 契约);#444

背景

情景 v1(#429,tod-scenario JSON:固定层记录集+参考历元+播放配置)已落地,经工具栏对话框手动保存/打开。#444 要求助手按工具卡片生成/修改情景。ADR 0023 决策 2 规定助手工具调用走标准 MCP(e2m2e mcp-serve);但 tod-scenario 是本仓前端契约(前端 scenario 模块为权威定义),e2m2e 不认识它,且「应用」语义(重建固定层+校准时间轴)是前端 App 状态操作,上游给不了——工具住哪成为第一个分叉(2026-08-31 grilling 定稿)。

决策

  1. 工具来源扩展:助手工具清单从「仅 MCP」扩为「MCP+宿主内置」。Rust 宿主在工具清单组装处注入内置工具、在执行分发处按保留前缀 scenario_ 拦截,宿主工具不经 MCP。

  2. 两个工具:

    • scenario_write(走确认链):结构化参数——文件名+records/referenceEpoch/playback 三块,宿主按 v1 规则序列化(缺省字段补默认值)写入固定目录;同名直接覆盖(确认卡已展示完整参数);写前不做 record_id 存在性预校验,打开情景的软失败(缺失跳过并列出)兜底。

    • scenario_list(进只读白名单,免确认直接执行):列出固定目录下的情景文件并返回解析后完整内容——情景文件是 KB 级整块 JSON,全文直接进上下文。

  3. 固定落盘目录:%APPDATA%/transfer-orbit-design/scenarios/(与助手会话 sessions/ 同级,沿 user_config_dir() 先例)。agent 无对话框,确定落点是「修改」可寻址的前提;手动保存对话框保留(用户自选路径),手动打开对话框默认定位固定目录。

  4. 修改语义:读全文(scenario_list)→ LLM 组新全文 → 整体覆盖(scenario_write);不做增量 patch 工具。

  5. 应用:done 卡片「应用情景」按钮点击触发(与「查看产物入树」同语义),prop 回调进 App,前端读文件后复用手动打开的同一条解析/软失败路径(含缺失提示、截断提示、时间轴校准、应用播放配置;结果层不动)。

  6. 范围外:不扩助手态势层上下文(固定层现状/当前时刻不进上下文——按意图组合 catalog 记录已覆盖主线,捕获当前态用户手动一键即可);无 record 的临时产物进情景明确不做(v1 契约仅收 catalog record_id,临时产物 record 化是独立议题)。

考虑过的选项

  • 上游 e2m2e MCP 工具(否决):契约所有权在本仓前端,上游得复刻一份解析校验、两边漂移;「应用」桥反正在前端;跨仓 PR+最低版本依赖让闭环分居两仓。

  • LLM 回复内吐文件内容+前端保存按钮(否决):绕开工具卡片确认/改参链,与「按工具卡片生成/修改」的产品语义相悖。

  • 增量 patch 工具(否决):情景文件是整块小 JSON,读—改—写覆盖更简单,patch 是为不存在的问题加抽象。

  • 写前经 catalog 校验 record_id(否决):宿主工具耦合 MCP 徒增复杂度;打开端软失败已有先例兜底。

后果

  • 工具清单组装与执行分发各多一个宿主分支;对 LLM 而言宿主工具与 MCP 工具无差别(同为 function)。

  • 只读白名单增 scenario_list,fail-closed 测试同步。

  • tod-scenario 序列化规则在 Rust 侧多一份实现(前端仍为契约权威);v1 格式「只增不改」的版本承诺限制了两份实现的漂移面,规则变更需两侧同步。

  • 前端 done 卡片新增「应用情景」动作与双语文案;App 手动打开逻辑抽公共函数供手动/卡片两条入口复用。