diff --git a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md new file mode 100644 index 000000000..d3dfe8d0c --- /dev/null +++ b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md @@ -0,0 +1,319 @@ +# AI 游戏创作 Agent Runtime 交互边界重构实施计划 + +更新时间:`2026-08-12` +状态:评审中 + +## 0. 目标与范围 + +统一 Consumer(GUI / CLI / 自动化测试)与 Runtime 之间的公开交互边界,达到: + +- **Consumer 只做两件事**:`render(state)` 与 `dispatch(intent)`,中间不保留决策。 +- **交互 Loop 收归后端 Supervisor Shell**:Consumer 不再根据 Runtime 状态自行选择 start / steer / confirm / retry / resume。 +- **GUI、CLI、测试夹具是同一套协议的平等 Consumer**,GUI 没有任何特权通道。 +- **Runner 自驱**:工作发现、恢复、继续执行不依赖 Consumer 在线或主动触发。 + +本重构**不重新设计 Runtime 内部执行模型**(Part D 保持黑盒),只补充 Shell 需要的边界能力。 + +### 不在本轮 + +- Agent 执行状态机(main_loop / task_queue / recovery)内部重构。 +- Runner 进程生命周期策略(开机自启 / 无 GUI 常驻)——自驱只限于"Runner 存活期间",保留 GUI 启动 + GUI-owner watchdog。 +- LLM / Provider / 提示词体系改动。 + +--- + +## 1. 交互 Loop 协议(Interaction Contract) + +这是 Consumer 与 Supervisor Shell 之间唯一的公开协议。协议不绑定 transport(Tauri 命令 / Runner 协议 / 进程内函数均可用同一套 DTO)。 + +### 1.1 出向事件(Shell → Consumer) + +| 事件 | 含义 | 现状落点 | 是否新增 | +|---|---|---|---| +| `progress` | 运行推进 | `status/phase` + `recentEvents` + `waitingOn/nextStep`,经 `read_game_creator_agent_runtimes` 与 `game-creator-agent-progress` 事件 | 收敛 | +| `needs_input` | 等待澄清回答 | `userInputRequest` / phase `waiting-for-user-input`(`AgentRuntimeUserInputRequest`) | 收敛 | +| `approval_required` | 等待开发者批准工具动作 | `pendingToolAction` / phase `waiting-for-confirmation`(`AgentRuntimePendingToolActionSummary`) | 收敛 | +| `tool_request` | 工具已请求/执行中 | 现散在 `recentToolCalls` + phase `action` | **新增派生** | +| `artifact` | 产物 / manifest 变化 | `game-creator-manifest-invalidated` + finalization journal | 收敛 | +| `done` | 本轮终态 | `completed` + `AgentRuntimeFinalizationJournal` + responseStream `committed` | 收敛 | +| `error` | 失败 / 需人工核对 | `failed` / `needs-reconciliation` + `error` | 收敛 | + +协议 DTO(camelCase 序列化,与现有一致): + +```rust +enum AgentRuntimeOutboundEvent { + Progress(AgentRuntimeProgress), + NeedsInput { request: AgentRuntimeUserInputRequestView }, + ApprovalRequired { action: AgentRuntimePendingToolActionSummary }, + ToolRequest(AgentRuntimeToolRequest), // 新增:Shell 从 recentToolCalls+phase 投影 + Artifact(AgentRuntimeArtifactEvent), // 收敛 manifest-invalidated + finalization + Done(AgentRuntimeDoneEvent), + Error(AgentRuntimePublicError), +} + +struct AgentRuntimeProgress { + project_path: String, agent_id: String, run_id: String, + status: String, phase: String, + current_task: String, current_action: String, + plan_steps: Vec, active_plan_step_index: Option, + waiting_on: String, next_step: String, // Shell 负责填充,Consumer 不再映射 phase→文案 + updated_at: u64, +} +``` + +> 与现状的关键差异:`progress` 里的 `waiting_on`/`next_step` 由 **Shell 填充**。当前是前端在 `model.ts:612/644` 硬编码 phase→中文文案,收归 Shell 后前端删除该映射。 + +### 1.2 入向命令(Consumer → Shell) + +| 命令 | 吸收的旧命令 | 说明 | +|---|---|---| +| `submit_intent` | `start_*` / `steer_*` | 一条消息可能 steer 进现有 run,也可能 start 新 run,由 Shell 判定 | +| `answer` | `answer_game_creator_agent_runtime_user_input` | 回答澄清 | +| `approve` | `confirm_*` / `reject_*` | 批准或拒绝,含 policy 确认卡 | +| `cancel` | `cancel_game_creator_agent_runtime_task` | 取消 | +| `resume` | `resume_*` / `confirm_resume_*` / `retry_*` / `confirm_retry_*` / `schedule_game_creator_agent_ready_tasks` | 恢复/重试/调度统一入口 | + +命令签名: + +```rust +#[tauri::command] +async fn submit_game_creator_agent_intent( + project_path: String, session_id: String, intent: String, + run_profile: Option, source: Option, +) -> Result; + +#[tauri::command] +async fn answer_game_creator_agent_interaction( + project_path: String, run_id: String, action_id: String, + request_id: String, response_id: String, answers: BTreeMap, +) -> Result; + +#[tauri::command] +async fn approve_game_creator_agent_interaction( + project_path: String, run_id: String, action_id: String, + approved: bool, note: String, +) -> Result; + +#[tauri::command] +async fn cancel_game_creator_agent_run( + project_path: String, agent_id: String, run_id: String, +) -> Result; + +#[tauri::command] +async fn resume_game_creator_agent_project( + project_path: String, +) -> Result, String>; +``` + +协议外 API(不进交互 Loop,作为管理面保留在 Shell 上):goal CRUD、`compact`、会话管理、配置读写。 + +### 1.3 统一 InteractionRequired 语义 + +Shell 向 Consumer 暴露"必须等待外部回答"的单一概念,替代现在分散的 `pendingToolAction` / `userInputRequest` / policy 确认卡: + +```rust +enum AgentRuntimeInteractionRequired { + UserInput { request: AgentRuntimeUserInputRequestView }, + Approval { action: AgentRuntimePendingToolActionSummary }, + PolicyApproval { policy: String }, // 新增:吸收 confirm_resume/confirm_retry 的 agent.resume 确认卡 +} +``` + +> 现状的 `confirm_resume`(`commands.rs:1148`)与 `confirm_retry`(`commands.rs:1020`)是"要求用户确认策略"的产物,由 GUI 弹确认卡实现。收归 Shell 后,Shell 评估 `enforce_project_auto_permission_policy`(`verification.rs:813`),需要确认时返回 `InteractionRequired::PolicyApproval`,用户确认后经统一的 `approve` 命令继续。 + +--- + +## 2. 现状盘点与复用清单 + +### 2.1 可直接复用的资产(近 1 个月内形成,活跃演进期) + +| 资产 | 位置 | 复用方式 | +|---|---|---| +| CLI 交互决策状态机 | `swarm_cli/turn_dispatch.rs:245` `decide_interaction_action`、`:48` `handle_swarm_user_turn`(steer/start、Reply/Execute/Resume、goal 门禁) | **整体上提**到后端 Shell(纯 Rust、无 UI 纠缠) | +| Runner 自驱续跑定时器 | `runtime_driver/provider_recovery.rs` 的 `schedule_waiting_provider_retry_wake_after_lane_release` 等 | 已存在,P4 直接复用 | +| 确定性 e2e | `scripts/agent-runtime-deterministic-playable-e2e.mjs` + `deterministic-lane-defense-provider.mjs`(`expectedProviderStats`/`expectedChildReport` 断言) | P0 基线扩展(协议级 trace) | +| 版本协商 fallback | 前端 `model.ts:1186-1226` `isMissing*CommandError` | 迁移期新旧并存的标准模式 | +| 逐命令幂等 | `accepted_run_id`(`runtime_state.rs:1548`)、runner requestId 缓存(`runner/protocol.rs:353`)、goal CAS | P1 设计直接沿用 | +| 进程内集成测试 | `src-tauri/tests/`(`command_runtime.rs`、`runtime_actions/`、`collaboration/`、`goal.rs`) | P1-P6 每步回归的护栏 | + +### 2.2 需要收敛/改造的点 + +| 点 | 位置 | 动作 | +|---|---|---| +| 前端 phase→文案映射 | `model.ts:612/644/974/1498/1554` | Shell 填充 `waiting_on/next_step` 后删除 | +| 前端 steer/start 决策 | `model.ts:849` `submitProjectSupervisorRuntimeTask`、`App.tsx:5791` | 迁入 Shell(`submit_intent`) | +| 前端 confirm/retry/resume 门禁 | `model.ts:1131-1154`、`panels.tsx:381-497` | 由 `InteractionRequired` + `approve/resume` 取代 | +| 前端跨轮状态修补 | `model.ts:244` `normalizeAgentRuntimeState`、`:385` `mergeAgentRuntimeStateIntoMap` | 依赖 P2 Snapshot 稳定后删除 | +| CLI 独立状态机 | `swarm_cli/turn_dispatch.rs` | 上提 Shell,CLI 只留终端交互(stdin/stdout/observer) | +| 重复触发恢复 | `App.tsx:2799/10341`、`useDeveloperAgentPanel.ts:684`(启动时 resume)、`App.tsx:10550`(devMode schedule 按钮) | P4 后删除,由 Runner 自驱 | + +--- + +## 3. 分阶段实施计划 + +> 主线:**先建安全网 → 建统一入口 → 统一状态读取 → 收回决策 → Runner 自驱 → 迁移全部 Consumer → 删旧面**。 + +### P0 行为基线(安全网) + +**目标**:在改动前建立可判断"公开行为是否变化"的验证能力。 + +- 复用 `deterministic-lane-defense-provider.mjs`,在现有 `agent-runtime-deterministic-playable-e2e.mjs` 基础上**增加协议级事件 trace**: + - 录制一条确定性完整会话的**出向事件序列**(progress/needs_input/approval_required/done/error 的顺序与关键载荷),归一化 timestamp、`run_id`/`steer_id`/`request_id` 随机 ID、`accepted_run_id` 对账值。 + - 断言当前 master 的 trace 与预期一致(快照 diff)。 +- 建立统一的回归命令,P1-P6 每阶段结束必跑: + - 确定性 e2e(功能正确性) + - `src-tauri/tests/` 进程内集成测试(单元级护栏) + - 协议级 trace(迁移等价性) +- **不动 Runtime 代码**,只建安全网。 + +**验收**:上述三条命令在当前 master 全绿,trace 基线文件入库。 + +### P1+P2 统一入口 + 状态读取(adapter + Snapshot 投影) + +**目标**:Consumer 改走新协议调用旧实现;状态读取收敛为稳定的公开 Snapshot。**旧接口全保留**为迁移期兼容路径。 + +**P1 适配器(`submit_intent` / `approve` / `answer` / `cancel` / `resume` 新命令)**: + +> **P1 与 P3 的边界**:P1 只做"命令可用 + `submit_intent` 内部判定 steer/start"。`answer`/`approve`/`resume` 在 P1 **只是入口封装**(内部调旧函数),"收到交互该调哪个命令、能否重试"的判定**仍在前端**;到 P3 才把判定收走,前端只剩 `submit`/`respond` 两种动作。 + +- 新增 `src-tauri/src/agent/supervisor_shell/`,内含: + - `intent.rs`:`submit_intent` 内部路由——读当前 runtime → 判定 steer/start(逻辑取自 `swarm_cli/turn_dispatch.rs` 的 steer 判定与前端 `matchingAgentRuntimeForSteer`)→ 调现有 `steer_game_creator_agent_runtime_task_for_profile_at` 或 `start_game_creator_supervisor_background_task_for_session_at` → 返回 `{ mode, accepted_run_id, runtime }`。 + - `interaction.rs`:`approve/answer/cancel` 薄封装现有 `confirm/reject/answer/cancel` 内部函数(P1 只封装,判定仍在前端)。 + - `resume.rs`:`resume_game_creator_agent_project` 吸收 `resume/confirm_resume/retry/confirm_retry/schedule_ready`——Shell 判定是否需 policy 确认、是否需先 cancel 再重试(`needs-reconciliation` 分支,逻辑取自 `App.tsx:6205` `handleProjectSupervisorRetry`)。 +- 新命令与旧命令**同时注册**(`main.rs` invoke_handler)。 +- 前端新增"调新命令 → 后端报 unknown command → 回退旧命令"的版本协商(复用 `isMissing*CommandError` 模式),保证打包版本不一致时旧链路可用。 +- **fallback 边界**:仅"命令不存在(版本不兼容)"确定性回退;写操作的其他错误原样呈现,不做盲目重试(防重复入队)。 + +**P2 Snapshot 投影(状态读取收敛)**: + +> **投影 = 读模型**:把同一份 Runtime durable state(唯一事实来源)按一个稳定、精简、面向消费的 schema 重新导出,作为 Consumer 的权威视图。它**不是新的事实来源**,只是同一份事实的另一种呈现;Consumer 依赖投影,业务真相仍在 durable state。 + +- 新增 `supervisor_shell/snapshot.rs`:`AgentRuntimeSnapshot` 投影。 + - 输入:现有 `AgentRuntimeResult.state` + `recent_events`/`recent_tasks`/`response_stream`/`user_input_request`。 + - 输出:稳定的公开视图(agent/session/run 身份、status/phase、`InteractionRequired`、progress、终态、公开错误)。 + - **内部字段(recentToolCalls、observations、allowedTools、contextUsage 等)不进 Snapshot**。 +- 关键:**后端先补"稳定读取"**——现状前端 `normalizeAgentRuntimeState` 做跨轮 carry-forward,是因为后端 read 在恢复/竞态时字段不稳定。P2 后端投影保证同一 run 身份下字段自洽,前端才能删掉自己的修补。 +- 新增 `read_game_creator_agent_runtime_snapshot(s)` 命令(或改造现有 read 返回 Snapshot),旧 `read_game_creator_agent_runtime(s)` 保留。 +- 前端 `normalizeAgentRuntimeState` / `mergeAgentRuntimeStateIntoMap` / phase→文案映射**依赖 P2 稳定后删除**(本轮先做投影,下一阶段删前端逻辑)。 + +**验收**: +- 新命令与旧命令对同一场景返回的终态一致(用 P0 trace + 确定性 e2e 断言)。 +- 前端在"走新 Snapshot"下渲染与旧路径一致(组件回归)。 +- 现有 `command_runtime.rs` / `collaboration/` 测试全绿(旧逻辑未动)。 + +### P3 Loop 收归 Supervisor Shell + +**目标**:Consumer 只 dispatch 意图,不再持有生命周期判断。 + +- 完成 `submit_intent` / `approve` / `answer` / `cancel` / `resume` 对全部旧分叉的吸收(P1 已建,本轮做全): + - `approve` 吸收 confirm/reject,并按 interaction kind 分派(`UserInput`/`Approval`/`PolicyApproval`)。 + - `resume` 吸收 resume/confirm_resume/retry/confirm_retry/schedule_ready。 +- 建立 `AgentRuntimeInteractionRequired`(见 1.3),Shell 统一暴露"需等待外部回答"。 +- Shell 负责填充 `waiting_on`/`next_step`(从 phase 映射,逻辑上提自 `model.ts:612/644`)。 +- 新增派生事件 `tool_request`(Shell 从 `recentToolCalls` + phase 投影)。 +- **前端删除**: + - `submitProjectSupervisorRuntimeTask` 的 steer/start 决策(`model.ts:849`)。 + - `agentRuntimeCanCancel/CanRetry/CanConfirm` 门禁(`model.ts:1131-1154`)。 + - `App.tsx:5981/6127/6205` 的 confirm/retry/repair 路由逻辑。 +- CLI(`swarm_cli`)改为调用同一 Shell:决策逻辑上提后,CLI 只保留 stdin/stdout 终端交互与 observer 渲染。 + +**验收**: +- CLI 走新协议跑通完整 supervisor+子 Agent 流程(`agent-swarm-test-chat.mjs` / 确定性 e2e)。 +- GUI 提交、确认、重试、恢复均通过统一 `submit_intent/approve/resume`,无 `steer_*`/`confirm_*`/`retry_*` 直接调用。 +- P0 协议级 trace 在"新旧实现各放一遍"下事件序列一致。 + +### P4 Runner 自驱 + +**目标**:工作发现、恢复、继续执行不依赖 Consumer 触发。**这是重构主线的一部分,不是独立项目。** + +- 项目目录簿:`runner/state.rs` 的 `known_roots`(当前进程内)→ 持久化到 AppData(复用 runner 的 `--config-dir`),Runner 重启后仍知道持有过哪些项目。 +- 启动自恢复:`runner/server.rs` 启动完成后,对 known roots 调 `has_recoverable_game_creator_agent_background_tasks_at`(`recovery_scan.rs:425`),有可恢复工作则自动 `resume_game_creator_agent_background_tasks_at`。 +- 空闲自扫描:主 accept loop(`server.rs:275`,已有 25ms `EXTERNAL_AGENT_RUNNER_LOOP_INTERVAL`)内增加"是否有 pending 任务需 wake"的轻量检查,替代 Consumer 调 `schedule_game_creator_agent_ready_tasks` / `wake_pending`。 +- 吸收 `schedule_game_creator_agent_ready_tasks`:manifest ready 任务由 Runner 扫描发现并调度,删除前端 devMode 按钮(`App.tsx:10550`)。 +- **边界(不在本轮)**:Runner 仍由 GUI 启动(`main.rs:2124`),保留 GUI-owner watchdog(`server.rs:169`)与 `game_chat_release` 退出协议(`main.rs:2311`)。"自驱 = 存活期间自调度 + 启动自恢复",**不含**开机自启/无 GUI 常驻。 +- 对应删除前端恢复触发职责:`App.tsx:2799/10341`、`useDeveloperAgentPanel.ts:684` 的启动时 resume、resume 确认卡回调。 + +**验收**: +- Runner 进程内:确认一个 pending 任务后无需任何 Consumer 调用即自动执行;恢复 pending 动作后自动续跑。 +- 确定性 e2e 增加"Runner 独立进程跑完整流程"用例(复用 `agent-runtime-real-e2e/harness/process.mjs` 的二进制编译能力)。 +- `confirm_resume` 恢复确认卡流程改为 `InteractionRequired::PolicyApproval` → `approve`。 + +### P5 迁移 GUI / CLI / Tests + +**目标**:三类 Consumer 全部迁移到统一 Intent、Snapshot、Runtime Output。 + +- GUI: + - 状态渲染改读 `AgentRuntimeSnapshot`;删除 `normalizeAgentRuntimeState` / `mergeAgentRuntimeStateIntoMap` / phase 文案映射。 + - 交互全走 `submit_intent/approve/answer/cancel/resume`;删除 steer/confirm/retry/resume 直接调用与门禁。 + - 保留只读消费形态:response stream 展示、对话合并、画布资产编排(这些不进 Loop 协议)。 +- CLI:`cli.rs` 与 `swarm_cli` 改用 Shell 命令;删除各自状态机(决策已上提)。 +- Tests:`src-tauri/tests/` 迁移到新命令;`agent-runtime-real-e2e` 与确定性 e2e 走同一协议。 +- 迁移顺序:先 CLI(最薄)→ 再 Tests → 最后 GUI(唯一消费 response stream / conversation 合并 / goal CAS / 委派修复路由,工作量最大)。 + +**验收**:GUI、CLI、Tests 调用面收敛到同一组命令,代码差异只剩输入输出形式。 + +### P6 删除旧公开面 + +**目标**:Interaction Contract 成为唯一稳定公开边界。 + +- 删除旧 Tauri 命令:`start_game_creator_agent_runtime_task` / `start_game_creator_supervisor_runtime_task` / `steer_*` / `confirm_*` / `reject_*` / `retry_*` / `confirm_retry_*` / `resume_game_creator_agent_runtime_tasks` / `confirm_resume_*` / `schedule_game_creator_agent_ready_tasks` / `read_game_creator_agent_runtime(s)`(`main.rs:2188-2208`)。 +- 删除对应后端 wrapper 与前端 `app/types.ts` 旧 DTO、旧事件协议、迁移期兼容逻辑。 +- 现有 start / steer / resume / recovery 能力作为 Shell 内部实现保留(改名/内联)。 + +**验收**:`grep` 无旧命令名残留;全量回归(P0 基线 + 单元 + e2e)全绿。 + +--- + +## 4. 迁移策略(新旧并存 + 等价性) + +1. **接口即实现,不留空窗**:新命令从注册第一天起就是真实可用——内部套用现有旧函数(adapter 套旧实现是**常态、透明**,前端不知道也不关心)。不存在"接口先立、实现待填"的中间态;分阶段的不是"接口 vs 实现",而是"谁先切到新接口"。 +2. **新旧并存**:P1 起新命令与旧命令同时注册,Consumer 逐个切换,旧路径逐条下线(strangler fig)。 +3. **版本协商 fallback(仅用于迭代空窗期)**:前端调新命令,**仅当**后端报 unknown command(前端版本 ≠ 后端版本,打包错位)时回退旧命令;其他运行错误**原样呈现,不盲目重试**(防重复入队)。后端新版随应用覆盖到位后,fallback 即死代码,P6 删除。 +4. **等价性保障**: + - 确定性 provider + 协议级 trace(P0)作为新旧实现的对照基线。 + - 进程内集成测试每阶段全跑。 + - 关键迁移点(steer/start 判定、resume 路由、needs-reconciliation 重试)用"同一输入 → 新旧实现输出一致"的单测锁定。 + +--- + +## 5. 里程碑与验收门禁 + +| 里程碑 | 交付 | 门禁 | +|---|---|---| +| M0 | P0 基线 + trace 入库 | 回归三件套全绿 | +| M1 | P1 新命令 + adapter(新旧并存) | 新/旧命令终态一致;现有测试全绿 | +| M2 | P2 Snapshot 投影 + 前端读 Snapshot | 前端删 normalize 后渲染回归一致 | +| M3 | P3 Loop 收归(Intent + InteractionRequired) | CLI 走新协议跑通完整流程;前端无 steer/confirm/retry 直调 | +| M4 | P4 Runner 自驱 | Runner 独立进程自动跑完;resume 确认走统一 approve | +| M5 | P5 全部 Consumer 迁移 | GUI/CLI/Tests 调用面收敛到同一协议 | +| M6 | P6 删旧面 | 无旧命令残留;全量回归绿 | + +--- + +## 6. 风险与未决问题 + +| 风险/问题 | 影响 | 缓解 | +|---|---|---| +| P2 依赖"后端 read 先稳定",否则前端不敢删 normalize | 阶段顺序敏感 | P2 后端投影先行,前端删逻辑放同一阶段尾 | +| P4 自驱与 GUI-owner 安全模型冲突 | 若误解为"无 GUI 常驻"会引安全评审 | 计划内明确边界,实现不越界 | +| steer/start 判定含 UX 语义(mode/source/runProfile、steerId 生成、acceptedRunId 对账) | 收归 Shell 后前端展示可能退化 | Shell 返回 `{ mode, accepted_run_id, steer_decision }` 补足展示信息 | +| `tool_request` 无现成单一落点 | 需 Shell 派生投影 | 提前排进 P2/P3 投影工作量 | +| 协议级 trace 的随机 ID 归一化 | 基线易碎 | 复用确定性 provider,归一化规则集中一处 | + +## 7. 建议实施顺序(一句话) + +P0 建安全网 → P1+P2(adapter + Snapshot,纯增量、旧接口全留、fallback 兜底)→ P3 收 Loop → P5 迁移(先 CLI 后 GUI)→ P6 删旧面;P4(Runner 自驱)作为主线中心件贯穿 M4,不单独立项,但边界(存活期间自调度,不含无 GUI 常驻)在计划内写死。 + +--- + +## 8. 相对原方案的调整点 + +本计划在原方案基础上做了以下调整,均基于 master 现状与既有安全模型: + +1. **P0 基线**:原方案的 Golden Replay 改为"确定性 e2e + 协议级事件 trace"——在既有确定性 provider e2e(`deterministic-lane-defense-provider.mjs`)上扩展,录制协议级事件序列并归一化 timestamp 与随机 ID,不重建基线体系。 + +2. **P4 定位与边界**:原方案将"Supervisor Lifecycle Coordinator"列为独立阶段;现作为主线组成部分(里程碑 M4,不单独立项)。自驱限于"Runner 存活期间自调度 + 启动自恢复",排除"开机自启 / 无 GUI 常驻",与既有 GUI-owner 安全模型一致。 + +3. **术语收敛**:原方案"Interaction Contract / Intent / Snapshot"统一为本计划"交互 Loop 协议"(5 入向命令 + 7 出向事件)与"投影"(读模型),含义不变。 + +4. **迁移原则显式化**:接口即实现(adapter 套旧逻辑为常态);fallback 仅在版本空窗期、只认 unknown command。原方案未明确此点。