diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs index 93c0bc144..cfcd95c74 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs @@ -1883,10 +1883,24 @@ mod goal_contract_steer_transition_tests { .collect::>(); assert_eq!(results.iter().filter(|result| result.is_ok()).count(), 1); assert_eq!(results.iter().filter(|result| result.is_err()).count(), 1); - assert!(results + // 两个并发追加指令里必然有一个是 loser,但 loser 落在哪个错误分支取决于它抢到 + // transition 锁时 winner 推进到了哪一步: + // - winner 的替代 Run 已经写回 session 状态:`已被其他追加指令替换`; + // - winner 只终止了旧根 Run、替代 Run 尚未写回:`当前 Agent run 状态不接受追加指令`; + // - 读到的状态正好处于切换中途:`追加指令与当前 Agent 的 session/run 身份不匹配`。 + // 三者都表示这条根 Run 已被另一条追加指令接管,因此这里只钉死“败者被拒绝”这一语义, + // 真正的结构不变量(只产生一个替代 Run)由下面的任务日志断言保证。 + let loser_error = results .iter() .filter_map(|result| result.as_ref().err()) - .any(|error| error.contains("已被其他追加指令替换"))); + .next() + .expect("concurrent steer loser error"); + assert!( + loser_error.contains("已被其他追加指令替换") + || loser_error.contains("当前 Agent run 状态不接受追加指令") + || loser_error.contains("追加指令与当前 Agent 的 session/run 身份不匹配"), + "并发的追加指令 loser 必须以“根 Run 已被接管”类错误收口,实际错误:{loser_error}" + ); let replacement_run_ids = read_all_game_creator_agent_runtime_tasks( &game_creator_agent_runtime_task_path(&root, GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID),