diff --git a/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/commands.rs index 425a44d40..920d93f7e 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/commands.rs @@ -96,7 +96,7 @@ pub(crate) async fn run_xhs_minitool_export_build( let result = run_build(&project_path).await; match &result { Ok(run) => { - clear_failure("build"); + clear_failure("build", &project_path); app_log!( "export.build.ok project={project_path} outputChars={} omittedCharacters={}", run.output_tail.chars().count(), @@ -172,14 +172,17 @@ async fn run_build( build::run(&root).await } -/// 上一次记过的失败(按命令名)。面板每 2 秒重读,同一个失败会反复落到同一条命令上。 -static LAST_FAILURE: LazyLock>> = +/// 上一次记过的失败(按命令名 + 项目路径)。面板每 2 秒重读,同一个失败会反复落到同一条命令上。 +/// +/// 去重键必须带上项目:`registryMalformed` 之类的现场文案不含文件路径,两个项目可能给出同一句 +/// `Debug`,只按命令名去重会把项目 B 的失败当成项目 A 的重复现场静默吞掉。 +static LAST_FAILURE: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); /// 命令边界的统一收口:成功清掉去重记录,失败记一条。 fn report(command: &'static str, project_path: &str, result: &Result) { match result { - Ok(_) => clear_failure(command), + Ok(_) => clear_failure(command, project_path), Err(error) => log_failure(command, project_path, error), } } @@ -193,11 +196,12 @@ const MAX_LOGGED_FAILURE_CHARS: usize = 512; fn log_failure(command: &'static str, project_path: &str, error: &impl std::fmt::Debug) { let message = bounded_for_log(&format!("{error:?}"), MAX_LOGGED_FAILURE_CHARS); + let key = (command, project_path.trim().to_string()); if let Ok(mut last) = LAST_FAILURE.lock() { - if last.get(command).is_some_and(|seen| *seen == message) { + if last.get(&key).is_some_and(|seen| *seen == message) { return; } - last.insert(command, message.clone()); + last.insert(key, message.clone()); } app_log!("export.{command}.failed project={project_path}: {message}"); } @@ -211,9 +215,9 @@ fn bounded_for_log(message: &str, max_chars: usize) -> String { format!("{head}…") } -fn clear_failure(command: &'static str) { +fn clear_failure(command: &'static str, project_path: &str) { if let Ok(mut last) = LAST_FAILURE.lock() { - last.remove(command); + last.remove(&(command, project_path.trim().to_string())); } }