diff --git a/apps/ai-game-creator-shell/src-tauri/src/environment_check.rs b/apps/ai-game-creator-shell/src-tauri/src/environment_check.rs index facfc30f6..290594405 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/environment_check.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/environment_check.rs @@ -355,10 +355,25 @@ fn valid_version(value: &str) -> bool { } async fn probe_version(runtime: &NodeRuntime, npm: bool) -> Result { + // npm 初始化也会查询用户目录;显式隔离,避免依赖 Windows 后备查询。 + let home = tempfile::tempdir().map_err(|_| "runtime-probe-home-unavailable")?; + let config = home.path().join("user.npmrc"); + let global_config = home.path().join("global.npmrc"); + fs::write(&config, b"").map_err(|_| "runtime-probe-home-unavailable")?; + fs::write(&global_config, b"").map_err(|_| "runtime-probe-home-unavailable")?; let mut command = tokio::process::Command::new(&runtime.node); command .env_clear() .env("PATH", &runtime.safe_path) + .current_dir(home.path()) + .env("HOME", home.path()) + .env("USERPROFILE", home.path()) + .env("APPDATA", home.path()) + .env("LOCALAPPDATA", home.path()) + .env("npm_config_userconfig", &config) + .env("npm_config_globalconfig", &global_config) + .env("npm_config_cache", home.path().join("npm-cache")) + .env("npm_config_update_notifier", "false") .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::null()) @@ -651,5 +666,19 @@ mod tests { )); assert!(valid_version(&probe_version(&runtime, true).await.unwrap())); assert_eq!(fs::read_dir(root.path()).unwrap().count(), 0); + let fixture = root.path().join("probe.cjs"); + fs::write( + &fixture, + include_str!("../tests/fixtures/environment-probe.cjs"), + ) + .unwrap(); + let fixture_runtime = NodeRuntime { + npm_cli: fixture, + ..runtime + }; + assert_eq!( + probe_version(&fixture_runtime, true).await.unwrap(), + "1.0.0" + ); } } diff --git a/apps/ai-game-creator-shell/src-tauri/tests/fixtures/environment-probe.cjs b/apps/ai-game-creator-shell/src-tauri/tests/fixtures/environment-probe.cjs new file mode 100644 index 000000000..26ccd2acb --- /dev/null +++ b/apps/ai-game-creator-shell/src-tauri/tests/fixtures/environment-probe.cjs @@ -0,0 +1,20 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const home = process.env.HOME; +assert.ok(home && fs.statSync(home).isDirectory()); +for (const key of ['USERPROFILE', 'APPDATA', 'LOCALAPPDATA']) { + assert.equal(process.env[key], home); +} +assert.equal(fs.realpathSync(process.cwd()), fs.realpathSync(home)); +assert.equal( + fs.realpathSync(require('node:os').homedir()), + fs.realpathSync(home), +); +for (const key of ['npm_config_userconfig', 'npm_config_globalconfig']) { + assert.equal(path.dirname(process.env[key]), home); + assert.equal(fs.readFileSync(process.env[key], 'utf8'), ''); +} +assert.equal(process.env.npm_config_cache, path.join(home, 'npm-cache')); +process.stdout.write('1.0.0\n'); diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 167e07a21..74f4537b9 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -1,5 +1,9 @@ # 踩坑与排障记录 +## AGC 版本探测必须显式提供隔离用户目录 + +清空子进程环境后缺少 `USERPROFILE/HOME` 会让 npm 依赖 Windows 后备用户查询,部分机器报 `uv_os_homedir` / `ENOMEM`。正常机器探测成功不能证明环境完整,需同时验证子进程环境。版本探测使用临时用户目录和空 npm 配置,详见 [AGC 实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md) 的“Web 环境版本探测的用户目录隔离”。 + ## 美术包固定 PNG 路径与平台返回格式不一致 - 平台生成成功仍可能返回 JPEG/WebP;直接按 `assets/art-spec.png` 等固定路径保存会在扩展名校验时失败,图片尚未落盘但已付费结果账本仍存在,不能据 manifest 为空认定没有生成结果。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index de8f53ce6..8ee04d922 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -1,5 +1,11 @@ # AI 游戏创作智能体 App 实施计划 +## 2026-09-28 Web 环境版本探测的用户目录隔离 + +Node/npm 版本探测清空继承环境后,必须设置客户端创建的临时 `HOME`、`USERPROFILE`、`APPDATA` 和 `LOCALAPPDATA`,并指定空的用户及全局 npm 配置、临时缓存和临时工作目录。临时目录保留到探测子进程退出,不依赖 Windows 用户资料查询,也不读取用户或项目的 `.npmrc`。探测继续使用校验后的客户端运行时;初始化临时环境失败返回 `runtime-probe-home-unavailable`,不回退到系统 Node 或真实用户目录。 + +验证使用真实 Node/npm 的 `environment_check::tests::real_node_npm_environment_versions`,同时用 `src-tauri/tests/fixtures/environment-probe.cjs` 检查隔离目录及空配置,避免正常机器的 Windows 后备查询掩盖回归。测试通过 `include_str!` 引用独立夹具,不把 Node 环境断言内嵌到生产源码扫描范围。此测试需要本地 Node/npm,默认忽略,定向验收时显式运行;另运行 `npm run ai-game-creator-shell:typecheck` 覆盖配置静态检查。 + ## 2026-09-28 AGC 客户端现行分工:离线前端、事件驱动与维护态出口 本节是 AGC 客户端「React 只做展示与交互」的现行口径,取代更早文档里渲染层直连平台网络、按固定频率轮询、自行解析维护响应的做法。