From a0d72bbef283c983049818f8b7ce5e7289f6f68a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 22 Sep 2026 19:18:30 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=94=AF=E6=8C=81=EF=BC=9A?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=A8=A1=E5=BC=8F=E4=B8=8B=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20Codex=20CLI=20=E6=89=A7=E8=A1=8C=E5=99=A8=E9=94=9A=E5=AE=9A?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E7=A1=AE=E4=BF=9D=E8=A3=B8=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E5=90=8D=E6=8C=89=20PATH=20=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E4=B8=BA=E7=BB=9D=E5=AF=B9=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src-tauri/src/agent/codex_cli.rs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_cli.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_cli.rs index e0ba84d09..707e5e29a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_cli.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_cli.rs @@ -203,12 +203,51 @@ pub(in crate::agent) fn game_creator_codex_cli_version_at( Ok(version.to_string()) } +/// 开发态允许从宿主 PATH 里找到 Codex,但宿主必须持有可锚定的绝对文件: +/// 裸命令名按 PATH 解析成真实路径,否则 `bind_codex_executor` 的 canonicalize 会按 CWD 解析并失败。 +/// 发行构建不走这段,候选顺序、校验与返回值都与原先一致(打包环境用内置侧车/npm 绝对路径)。 +#[cfg(debug_assertions)] +fn anchor_codex_cli_executable_candidate( + candidate: &Path, + path: Option<&std::ffi::OsStr>, +) -> Option { + let is_bare_command_name = candidate + .parent() + .is_some_and(|parent| parent.as_os_str().is_empty()); + if !is_bare_command_name { + return candidate.is_file().then(|| candidate.to_path_buf()); + } + let name = candidate.as_os_str(); + for directory in path.into_iter().flat_map(std::env::split_paths) { + if directory.as_os_str().is_empty() { + continue; + } + let target = directory.join(name); + if target.is_file() { + // 第一个实际命中的项就是 OS 会执行的项;锚定失败时不再从 PATH 里换另一个。 + return target.canonicalize().ok(); + } + } + None +} + pub(crate) fn game_creator_codex_cli_executable_path() -> Result { let mut last_error = None; let mut seen = std::collections::HashSet::new(); let bundled = game_creator_bundled_codex_cli_path(game_creator_bundled_resource_dir().as_deref()); for candidate in game_creator_codex_cli_executable_candidates() { + #[cfg(debug_assertions)] + let candidate = match anchor_codex_cli_executable_candidate( + &candidate, + std::env::var_os("PATH").as_deref(), + ) { + Some(candidate) => candidate, + None => { + last_error = Some("候选执行器不是可锚定的文件".to_string()); + continue; + } + }; let identity = candidate.to_string_lossy().to_ascii_lowercase(); if !seen.insert(identity) { continue;