Merge branch 'master' into feat/ui-editor-auto-seperation
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m48s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m21s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m16s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m33s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 3m55s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m52s
Project CI / Frontend tests (pull_request) Successful in 4m10s
Project CI / Repository checks (pull_request) Successful in 3m14s
Project CI / Native shell tests (pull_request) Successful in 6m35s
Project CI / Backend tests (pull_request) Successful in 7m32s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m58s

This commit is contained in:
2026-09-14 18:31:38 +08:00
9 changed files with 163 additions and 45 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@genarrative/ai-game-creator-shell",
"private": true,
"version": "0.1.27",
"version": "0.1.29",
"type": "module",
"scripts": {
"dev": "node scripts/start-tauri-dev.mjs",
+1 -1
View File
@@ -1725,7 +1725,7 @@ dependencies = [
[[package]]
name = "genarrative-ai-game-creator-shell"
version = "0.1.27"
version = "0.1.29"
dependencies = [
"agent-runtime-core",
"axum",
@@ -1,6 +1,6 @@
[package]
name = "genarrative-ai-game-creator-shell"
version = "0.1.27"
version = "0.1.29"
edition = "2021"
publish = false
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Genarrative AI Game Creator",
"version": "0.1.27",
"version": "0.1.29",
"identifier": "world.genarrative.ai-game-creator",
"build": {
"beforeDevCommand": "npm --prefix ../.. run agc:serve",
@@ -1211,8 +1211,8 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 现象:Windows 上执行 `npm run dev:api-server` 时,api-server 在 Cargo 启动阶段失败,日志出现 `error: multiple input filenames provided (first two filenames are ... rustc.exe and -)``/healthz` 无法访问。
- 原因:`server-rs/.cargo/config.toml` 默认配置 `rustc-wrapper = "sccache"`;本地 dev 脚本为了绕过损坏的 sccache 需要覆盖 wrapper。Windows 下如果把 `RUSTC_WRAPPER` 设置为 `rustc`Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,真实 rustc 把 wrapper 传入的 rustc 路径和 stdin `-` 都当输入文件。
- 处理:Windows 本地 dev 脚本应把 `RUSTC_WRAPPER` `CARGO_BUILD_RUSTC_WRAPPER` 显式设为空字符串,让 Cargo 覆盖项目配置并直连真实 rustcLinux 保持 `/usr/bin/env` 绕过 sccache。
- 验证:`npm run test -- scripts/dev.test.ts -t "Windows 下本地 dev Rust env 用空 wrapper 覆盖项目 sccache"`,并`npm run dev:api-server` 拉起后访问实际 api 端口的 `/healthz` 返回 200。
- 处理:Windows 本地 dev 脚本默认把两个 wrapper 设为空字符串;只有用户显式配置 `RUSTC_WRAPPER` `CARGO_BUILD_RUSTC_WRAPPER` 时才处理 sccache。sccache 配置会在当前 dev 编排进程内只执行一次限时真实 `sccache rustc -vV` 探测,并缓存成功或失败结果;失败、超时或两个变量冲突时回退到直接 rustc,避免每次 API / worker 重启重复同步阻塞Linux 保持 `/usr/bin/env` 绕过 sccache。
- 验证:`npm run test -- scripts/dev.test.ts -t "dev scheduler Rust build env"`POSIX 显式配置 sccache 时日志应明确说明绕过并使用直接 rustc;再`npm run dev:api-server` 拉起后访问实际 api 端口的 `/healthz` 返回 200。
- 关联:`scripts/dev.mjs``scripts/dev.test.ts``docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`
## Pingora 直连 80/443 不能只改 env
@@ -2692,7 +2692,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)``sccache: error: Timed out waiting for server startup`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。
- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时,Cargo 的 `sccache rustc -vV` 可能先超时。
- 处理:保留 `server-rs/.cargo/config.toml``rustc-wrapper = "sccache"`;本地 `npm run dev` / `npm run dev:spacetime` / `npm run dev:api-server` `scripts/dev.mjs` 给 Rust 子进程注入直通 wrapper自动绕过项目默认 sccache,避免损坏的 daemon 阻断 `spacetime publish``api-server` 启动;显式设置的非 sccache 自定义 wrapper 会被保留。Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro``sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`
- 处理:保留 `server-rs/.cargo/config.toml``rustc-wrapper = "sccache"`;本地 `npm run dev` / `npm run dev:spacetime` / `npm run dev:api-server` 在 Windows 下限时执行真实 wrapper 探测 `sccache rustc -vV`,成功才启用 sccache,缺少命令、daemon 启动超时或 wrapper 返回非零时立即给 Rust 子进程注入 wrapper回退到直接 rustc,避免损坏的 daemon 阻断启动;显式设置的非 sccache 自定义 wrapper 会被保留。Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro``sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`
- 验证:`rustc -Vv` 能输出版本;本地 `npm run dev` 能完成 `spacetime publish``api-server` `/healthz`、主站 Vite 和后台 Vite 启动;冷启动后原始 `cargo check -p api-server``cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明原始 Cargo/Jenkins 路径仍可使用 sccache/OSS 缓存;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。
- 关联:`scripts/dev.mjs``jenkins/Jenkinsfile.production-stdb-module-build``docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md``docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`
@@ -66,7 +66,7 @@ AI 游戏创作客户端使用 `npm run agc`。该入口由 `apps/ai-game-creato
Tauri `beforeDevCommand` 默认与客户端构建并行,不能把上述检查只放在 `beforeDevCommand` 内:选定地址上若已有旧 Vite,Tauri 可能先创建加载旧前端的窗口,随后配套后端才因代理不匹配退出。外层启动器会把 Tauri CLI 放入受控进程树;CLI 正常退出、启动失败或收到终止信号后,POSIX 先向保留的 PGID 发送 `SIGTERM`、有界等待后升级 `SIGKILL`Windows 使用 `taskkill /PID <pid> /T /F`。Windows 下每个长驻服务都经 `cmd.exe /d /s /c` 包装层启动,Ctrl+C 会先杀掉包装层(退出码 `0xC000013A`),因此清理不能只看直接子进程是否存活:`taskkill` 对已退出的 PID 只会失败,必须继续按记录下来的根 PID 遍历,并在退出时按本工作树 `api-server.exe` 绝对路径(以及本次自己拉起的 SpacetimeDB `--data-dir`)做一次身份兜底清扫;`scripts/dev-windows-process.mjs` 是这套判定的唯一实现。Linux 容器中的孤儿后代退出后可能暂时保留为 zombie,`kill(-PGID, 0)` 仍会返回成功;启动器必须结合 `/proc/<pid>/stat` 判断同组是否还存在非 zombie 成员,不能把等待 PID 1 回收误报为清理失败。配套后端和 Vite 仍由 `start-dev-stack.mjs` 各自持有,退出时同样有界收束,避免只剩客户端、Runner、Cargo 或旧订阅进程。排障时同时核对控制台输出的 AGC Vite 实际地址及其 marker、`.app/dev-stack.json` 的实际 API URL 和进程 cwd;不要把“终端已返回”当成客户端及其 Runner 已退出的证据。
Windows 本地 `npm run dev` / `npm run dev:api-server` / `npm run dev:bgfilter-worker` 会用空的 `RUSTC_WRAPPER` / `CARGO_BUILD_RUSTC_WRAPPER` 覆盖 `server-rs/.cargo/config.toml` 里的 `sccache`,从而直连真实 `rustc`。完整栈和 `dev:api-server` 把 API 与 BgFilter worker 作为一个 Rust 重启单元:源码变化时先停两个进程,再先启动并验活 worker、最后启动并验活 API,避免两个 `cargo run` 并发链接同一个 Windows 可执行文件。不要把 wrapper 绕过值写成 `rustc`Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,最终报 `multiple input filenames provided` 并导致 api-server 无法启动。排查本地启动失败时,先看 dev 日志是否出现该错误,再确认脚本注入的 wrapper 为空
Windows 本地 `npm run dev` / `npm run dev:api-server` / `npm run dev:bgfilter-worker` 默认不主动启用 sccache;只有用户通过 `RUSTC_WRAPPER` `CARGO_BUILD_RUSTC_WRAPPER` 显式配置 wrapper 时才进入处理流程。配置为 sccache 时会限时执行真实 wrapper 探测,成功才使用缓存;未安装、不可执行、超时或两个变量冲突时设置为空值,回退到真实 `rustc`,不阻断启动。完整栈和 `dev:api-server` 把 API 与 BgFilter worker 作为一个 Rust 重启单元:源码变化时先停两个进程,再先启动并验活 worker、最后启动并验活 API,避免两个 `cargo run` 并发链接同一个 Windows 可执行文件。不要把 wrapper 绕过值写成 `rustc`Cargo 会按 wrapper 协议调用 `rustc <真实rustc路径> - ...`,最终报 `multiple input filenames provided` 并导致 api-server 无法启动。排查本地启动失败时,先看 dev 日志中的 wrapper 启用、冲突或回退提示
### 本地 Rust 构建缓存与磁盘上限
+1 -1
View File
@@ -95,7 +95,7 @@
},
"apps/ai-game-creator-shell": {
"name": "@genarrative/ai-game-creator-shell",
"version": "0.1.27",
"version": "0.1.29",
"dependencies": {
"@cubone/react-file-manager": "^1.35.0",
"@genarrative/image-canvas-core": "0.1.0",
+60 -13
View File
@@ -146,9 +146,34 @@ function applyLocalFfmpegEnv(env, platform = process.platform) {
return env;
}
function resolveLocalDevRustcWrapperBypass() {
// Windows 下不能把 rustc 自身当成 Cargo wrapper;空值会覆盖仓库 .cargo/config.toml 中的 sccache。
return process.platform === 'win32' ? '' : '/usr/bin/env';
const localSccacheProbeCache = new Map();
function resolveLocalDevRustcWrapperBypass(configuredWrapper, options = {}) {
if (process.platform !== 'win32') {
return '/usr/bin/env';
}
const sccacheAvailable =
options.sccacheAvailable ??
(() => {
const cacheKey = `${process.platform}:${configuredWrapper}`;
const cached = localSccacheProbeCache.get(cacheKey);
if (cached !== undefined) {
return cached;
}
const result = spawnSync(configuredWrapper, ['rustc', '-vV'], {
cwd: repoRoot,
encoding: 'utf8',
shell: true,
windowsHide: true,
timeout: options.sccacheProbeTimeoutMs ?? 10_000,
});
const available = !result.error && result.status === 0;
localSccacheProbeCache.set(cacheKey, available);
return available;
})();
return sccacheAvailable ? configuredWrapper : '';
}
const SERVICE_NAMES = [
@@ -583,21 +608,43 @@ function buildLocalRustProcessEnv(env, options = {}) {
String(mergedEnv.RUSTC_WRAPPER ?? '').trim(),
String(mergedEnv.CARGO_BUILD_RUSTC_WRAPPER ?? '').trim(),
].filter(Boolean);
const customWrapper = wrappers.find(
(wrapper) => !isSccacheRustcWrapper(wrapper),
);
if (customWrapper) {
mergedEnv.RUSTC_WRAPPER = customWrapper;
mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = customWrapper;
const uniqueWrappers = [...new Set(wrappers)];
if (uniqueWrappers.length > 1) {
mergedEnv.RUSTC_WRAPPER = '';
mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = '';
if (options.log !== false) {
console.warn(
'[dev:rust] RUSTC_WRAPPER 与 CARGO_BUILD_RUSTC_WRAPPER 配置冲突,回退到直接 rustc。',
);
}
return mergedEnv;
}
const rustcWrapperBypass = resolveLocalDevRustcWrapperBypass();
mergedEnv.RUSTC_WRAPPER = rustcWrapperBypass;
mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = rustcWrapperBypass;
const configuredWrapper = uniqueWrappers[0] ?? '';
if (configuredWrapper && !isSccacheRustcWrapper(configuredWrapper)) {
mergedEnv.RUSTC_WRAPPER = configuredWrapper;
mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = configuredWrapper;
return mergedEnv;
}
const rustcWrapper = configuredWrapper
? resolveLocalDevRustcWrapperBypass(configuredWrapper, options)
: '';
mergedEnv.RUSTC_WRAPPER = rustcWrapper;
mergedEnv.CARGO_BUILD_RUSTC_WRAPPER = rustcWrapper;
if (options.log !== false) {
const isPosixSccacheBypass =
configuredWrapper &&
isSccacheRustcWrapper(configuredWrapper) &&
process.platform !== 'win32';
console.warn(
'[dev:rust] 本地 dev 构建绕过项目 sccache wrapper,避免缓存进程异常阻断启动。',
isPosixSccacheBypass
? '[dev:rust] POSIX 本地 dev 构建绕过 sccache,使用直接 rustc。'
: rustcWrapper
? '[dev:rust] 本地 dev 构建启用 sccache wrapper。'
: configuredWrapper
? '[dev:rust] sccache wrapper 探测失败,回退到直接 rustc。'
: '[dev:rust] 未显式配置 Rust wrapper,使用直接 rustc。',
);
}
return mergedEnv;
+94 -23
View File
@@ -657,34 +657,55 @@ describe('dev scheduler local worker cleanup', () => {
});
describe('dev scheduler Rust build env', () => {
test('local dev Rust env bypasses project sccache wrapper', () => {
const env = buildLocalRustProcessEnv(
{
RUSTC_WRAPPER: '/usr/bin/sccache',
CARGO_BUILD_RUSTC_WRAPPER: 'sccache',
},
{ log: false },
test('Windows 下未显式配置 wrapper 时默认关闭 sccache', () => {
const originalPlatform = Object.getOwnPropertyDescriptor(
process,
'platform',
);
expect(env.RUSTC_WRAPPER).not.toBe('/usr/bin/sccache');
expect(env.RUSTC_WRAPPER).not.toBe('sccache');
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe(env.RUSTC_WRAPPER);
Object.defineProperty(process, 'platform', {
configurable: true,
value: 'win32',
});
try {
const env = buildLocalRustProcessEnv(
{},
{ log: false, sccacheAvailable: true },
);
expect(env.RUSTC_WRAPPER).toBe('');
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe('');
} finally {
if (originalPlatform)
Object.defineProperty(process, 'platform', originalPlatform);
}
});
test('local dev Rust env keeps healthy custom wrapper untouched', () => {
const env = buildLocalRustProcessEnv(
{
RUSTC_WRAPPER: 'custom-wrapper',
CARGO_BUILD_RUSTC_WRAPPER: 'sccache',
},
{ log: false },
test('POSIX 显式配置 sccache 时日志说明实际绕过', () => {
const originalPlatform = Object.getOwnPropertyDescriptor(
process,
'platform',
);
expect(env.RUSTC_WRAPPER).toBe('custom-wrapper');
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe('custom-wrapper');
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
Object.defineProperty(process, 'platform', {
configurable: true,
value: 'linux',
});
try {
const env = buildLocalRustProcessEnv(
{ RUSTC_WRAPPER: 'sccache', CARGO_BUILD_RUSTC_WRAPPER: 'sccache' },
{ log: true },
);
expect(env.RUSTC_WRAPPER).toBe('/usr/bin/env');
expect(warn).toHaveBeenCalledWith(
'[dev:rust] POSIX 本地 dev 构建绕过 sccache,使用直接 rustc。',
);
} finally {
warn.mockRestore();
if (originalPlatform)
Object.defineProperty(process, 'platform', originalPlatform);
}
});
test('Windows 下本地 dev Rust env 用空 wrapper 覆盖项目 sccache', () => {
test('local dev Rust env enables available sccache wrapper', () => {
const originalPlatform = Object.getOwnPropertyDescriptor(
process,
'platform',
@@ -700,7 +721,48 @@ describe('dev scheduler Rust build env', () => {
RUSTC_WRAPPER: 'sccache',
CARGO_BUILD_RUSTC_WRAPPER: 'sccache',
},
{ log: false },
{ log: false, sccacheAvailable: true },
);
expect(env.RUSTC_WRAPPER).toBe('sccache');
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe(env.RUSTC_WRAPPER);
} finally {
if (originalPlatform) {
Object.defineProperty(process, 'platform', originalPlatform);
}
}
});
test('Rust wrapper 配置为同一个自定义 wrapper 时保持不变', () => {
const env = buildLocalRustProcessEnv(
{
RUSTC_WRAPPER: 'custom-wrapper',
CARGO_BUILD_RUSTC_WRAPPER: 'custom-wrapper',
},
{ log: false },
);
expect(env.RUSTC_WRAPPER).toBe('custom-wrapper');
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe('custom-wrapper');
});
test('Windows 下未安装 sccache 时回退到空 wrapper', () => {
const originalPlatform = Object.getOwnPropertyDescriptor(
process,
'platform',
);
Object.defineProperty(process, 'platform', {
configurable: true,
value: 'win32',
});
try {
const env = buildLocalRustProcessEnv(
{
RUSTC_WRAPPER: 'sccache',
CARGO_BUILD_RUSTC_WRAPPER: 'sccache',
},
{ log: false, sccacheAvailable: false },
);
expect(env.RUSTC_WRAPPER).toBe('');
@@ -711,6 +773,15 @@ describe('dev scheduler Rust build env', () => {
}
}
});
test('Rust wrapper 配置冲突时回退到空 wrapper', () => {
const env = buildLocalRustProcessEnv(
{ RUSTC_WRAPPER: 'sccache', CARGO_BUILD_RUSTC_WRAPPER: 'custom-wrapper' },
{ log: false },
);
expect(env.RUSTC_WRAPPER).toBe('');
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe('');
});
});
describe('dev scheduler stack state file', () => {