Merge origin/master (b1c89fbba) into chore/rust-compile-warnings:按「让路 master + 最小集恢复」收口跨文件契约冲突
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

解冲突口径(本次同步 master 16 个提交):

- `agent/generation/canvas_generation.rs`、`command_exec.rs` 两个冲突文件不再整文件取一边,改为按
  hunk 三分法:master 的功能 hunk 全量移植;我们的清理只在 master 调用方不依赖时才保留。
  未恢复整块模块 / 整文件,未新增任何 `#[allow]` / `#[expect]`。
- 成对回退我们的「摘掉 brief 传参空转」清理:master #628 等新代码仍按带 briefs 的契约调用生成链,
  故 cg 的 8 处签名与 6 个调用方(direct_runtime / direct_tool_bridge / commands / commands/desktop /
  asset_generation_tasks)回到 master 形状,并按最小集恢复 `main.rs` 的 `AgentRoleBrief` /
  `AgentGroupBrief`(未恢复其它已删模块)。
- master #628 已收敛切片用途判定:客户端不再暴露/发送 `sliceCount`,本次跟 master 走(
  `PlatformArtAssetGenerationOptions` 不再有该字段),只保留结果侧 256 上限门。
- command_exec 的 `project_command_actual_target` 采用 master 的 `Vec<OsString>` 字节精确契约,
  同时保留我们「调用点全在 Linux 编译面」的 `#[cfg(target_os = "linux")]` 收窄(与 master 调用方不冲突)。
- 脚本缺陷修复:`scripts/check-rust-warnings.mjs` 编译失败不再可能假通过(cargo 退出码非 0 一律 exit 1,
  并把 stderr 尾部提到前面打印;去掉 `warnings.length === 0` 条件)。
- 取 master 版本带来的回流告警 2 条(`main.rs` 两个 brief 结构体的字段未被读)已登记进
  `scripts/warning-baseline.json`(agc-windows 16 条 / agc-linux 71 条,server-rs 0 条);
  按既定口径「不修上游告警」,只做记账。
This commit is contained in:
2026-10-07 21:01:34 +08:00
42 changed files with 3032 additions and 883 deletions
@@ -2306,8 +2306,7 @@
"description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue",
"description": "可重放的生成输入。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据必须写入 imageSequenceFrames 和 imageSequenceDurationMs。服务端会移除 screenColorHex、mattingProvider、mattingModel 等内部处理审计字段。"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。本接口保存经清理的元数据,删除调用方 references 及顶层 screenColorHex、mattingProvider、mattingModel;不会执行生成配方。序列化后最多 64 KiB,禁止内联媒体 Data URL。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据使用 imageSequenceFrames 和 imageSequenceDurationMs。可通过素材库读取保存值。"
}
},
"additionalProperties": false
@@ -2404,8 +2403,7 @@
"description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue",
"description": "可重放的生成输入。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据必须写入 imageSequenceFrames 和 imageSequenceDurationMs。服务端会移除 screenColorHex、mattingProvider、mattingModel 等内部处理审计字段。"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。本接口保存经清理的元数据,删除调用方 references 及顶层 screenColorHex、mattingProvider、mattingModel;不会执行生成配方。序列化后最多 64 KiB,禁止内联媒体 Data URL。assetKind=character-animation 时不得包含 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段;正式媒体数据使用 imageSequenceFrames 和 imageSequenceDurationMs。可通过项目详情读取保存值。"
}
},
"additionalProperties": false
@@ -2797,7 +2795,7 @@
"description": "assetKind=character-animation 时完整图片序列播放一次的毫秒时长;与音频、视频生成参数 durationSeconds 无关。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "实际保存的生成上下文与来源元数据,接受任意 JSON 值,也可能为空;不是完整请求或自动执行指令。已按 owner 读取边界移除内联媒体与顶层内部审计字段;生成接口可能清理、补充或重建请求值,派生记录不保证继承原图自定义字段。fields/references 可供显示,识别的 V2 action/字段 ID 可供支持的面板恢复;参考规范图的“游戏类型”等已知字段也有后续消费者,不能视为全部无业务作用。任意 artSpec 等扩展字段不保证 UI 展示或自动复用。复用时由调用方将所需信息显式转换成新请求参数。通过项目详情的 project.resources 读取。"
},
"createdAt": {
"type": "string",
@@ -2948,7 +2946,7 @@
"description": "权威媒体类别。character-animation 渲染为序列帧,video 渲染为视频,audio/sound-effect/background-music 渲染为音频,其余渲染为图片。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "实际保存的生成上下文与来源元数据,接受任意 JSON 值,也可能为空;不是完整请求或自动执行指令。已按 owner 读取边界移除内联媒体与顶层内部审计字段;生成接口可能清理、补充或重建请求值,派生记录不保证继承原图自定义字段。fields/references 可供显示,识别的 V2 action/字段 ID 可供支持的面板恢复;参考规范图的“游戏类型”等已知字段也有后续消费者,不能视为全部无业务作用。任意 artSpec 等扩展字段不保证 UI 展示或自动复用。复用时由调用方将所需信息显式转换成新请求参数。通过素材库的 library.assets 读取。"
},
"createdAt": {
"type": "string",
@@ -3195,8 +3193,7 @@
"type": ["string", "null"]
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue",
"description": "场景配方由服务端重建;仅保留 source 精确等于 ai-game-creator-client 的客户端来源标记,用于选择 AGC 队列结果与幂等命名空间。调用方 fields、action 和引用 provenance 不会覆盖服务端配方。"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。本接口按 sceneContent、stylePreset、customStyle 和规范化尺寸 / 模型参数重建 V2 version/action/fields/references;仅额外保留 source 精确等于 ai-game-creator-client 的标记,用于 AGC 队列结果与幂等命名空间。调用方 fields、action、引用 provenance 和 artSpec 等其它扩展字段不会保留或覆盖服务端配方。"
},
"assetFolderId": {
"type": ["string", "null"]
@@ -3307,7 +3304,7 @@
"description": "生成产物分类。External v1 通用图片接口禁止使用 scene;结构化游戏场景必须使用主站场景专用契约。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。内容与构图要求写入 prompt,参考图写入 referenceImageSrcs,尺寸 / 风格使用正式参数。本接口清理内部审计字段并按真实且已鉴权的参考输入重建 references,其余自定义对象字段可保存到生成原图记录。角色透明化等派生记录可另建处理阶段和来源元数据;不保证最终派生图继承原图 artSpec。通过项目 / 素材库查询实际保存值。"
},
"assetFolderId": {
"type": ["string", "null"],
@@ -3405,7 +3402,7 @@
"description": "项目上下文。提供 targetLayerId 时必须同时提供非空 projectId,否则返回 400。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。编辑要求写入 prompt,原图使用 sourceReferenceId,辅助参考使用 referenceImageSrcs。本接口保留经清理的自定义字段,references 由已鉴权原图和实际辅助参考重建;不能通过本字段指定或伪造源图身份。通过项目 / 素材库查询实际保存值。"
},
"assetFolderId": {
"type": ["string", "null"]
@@ -3492,7 +3489,7 @@
"x-genarrative-media-family": "static-image"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。本字段不配置去背景算法或选取源图;来源由 sourceImageSrc/sourceResourceId 等正式参数确定。保留经清理的自定义字段,并从实际且已鉴权来源重建 references。服务端处理审计字段不向普通调用方返回。通过项目 / 素材库查询实际保存值。"
},
"assetFolderId": {
"type": ["string", "null"]
@@ -3701,7 +3698,7 @@
"type": ["string", "null"]
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。需生效的内容、构图和分隔要求写入 iconDescriptions,规范图使用 referenceId。本接口清理内部审计字段并重建 references,自定义字段可保存于生成原图;透明图集和切片另建处理阶段 / 来源元数据,不自动继承 artSpec。后续可按来源链查询原图记录。本接口会读取已保存参考规范图 fields 中标题为“游戏类型”的值来组装类型提示,但不会解析本次请求的任意元数据作为提示词。"
},
"assetFolderId": {
"type": ["string", "null"]
@@ -3764,7 +3761,7 @@
"type": ["string", "null"]
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。提取来源、标注与尺寸使用正式请求参数。本接口清理内部审计字段并按实际已鉴权参考重建 references,自定义字段可保存于生成原图;透明图集和切片另建处理阶段 / 来源元数据,不自动继承全部字段。需原始上下文时按来源链查询项目 / 素材库记录。"
},
"assetFolderId": {
"type": ["string", "null"]
@@ -4065,7 +4062,7 @@
"description": "带项目上下文生成时,服务端据此直接写入画布完成态并返回最新项目快照。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。动作、源角色和生成设置由本接口的正式参数决定。经清理的上下文可保存到最终动作序列记录;不得用 characterAnimation、frames、previewVideoPath、frameCount、fps 或 durationSeconds 等旧运行字段承载序列。正式输出帧与时长通过资源 / 素材的 imageSequenceFrames 和 imageSequenceDurationMs 读取。"
},
"sourceResourceId": {
"type": ["string", "null"]
@@ -4317,7 +4314,7 @@
"description": "带项目上下文生成时,服务端据此直接写入画布完成态并返回最新项目快照。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。视频内容与参考媒体使用 prompt 及本接口的正式参考参数。本接口保留经清理的上下文;对象元数据可根据规范化生成时长补充或更新 fields 中的时长展示项。通过项目 / 素材库查询实际保存值。"
},
"sourceResourceId": {
"type": ["string", "null"]
@@ -4493,7 +4490,7 @@
"description": "带项目上下文生成时,服务端据此直接写入画布完成态并返回最新项目快照。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。音效由 prompt、duration、loop、model 等正式参数决定。本接口根据实际生成重建 fields、空 references 和 soundEffect 元数据;只从调用方对象复制 source、conversationId、toolCallMessageId,artSpec 等其它字段不保留。不能通过本字段覆盖实际时长、Loop 或提示词。"
},
"assetFolderId": {
"type": ["string", "null"],
@@ -4533,7 +4530,7 @@
"description": "带项目上下文生成时,服务端据此直接写入画布完成态并返回最新项目快照。"
},
"generationInputs": {
"$ref": "#/components/schemas/JsonValue"
"description": "可选生成上下文与来源元数据,接受任意 JSON 值;有命名字段时使用对象。自定义字段(包括 artSpec)不会自动进入提示词或覆盖正式生成参数。保存、重建及已知字段消费规则依接口而定,不能视为完整 HTTP 重放载荷。音乐内容与是否纯音乐由 gptDescriptionPrompt、makeInstrumental 等正式参数决定。本接口保存经清理的上下文,不从自定义字段提取提示词或生成选项。通过项目 / 素材库查询实际保存值。"
},
"assetFolderId": {
"type": ["string", "null"],
@@ -9,13 +9,13 @@
## 目标
开发构建的 Linux 命令沙箱能原生解析并复用宿主 fnm / nvm 托管的 Node 安装:宿主发现与沙箱只读挂载使用同一套窄叶校验;`.nvmrc` / `.node-version` pin 权威、`engines.node` 仅为偏好;npm 以受信任 `node <npm-cli.js>` 形态启动且不丢失 `npm install` 的联网判定。不再要求用户改系统 Node、把宿主 shim 指向 `/usr/bin`,或把托管目录软链进系统路径。
开发构建的 Linux 命令沙箱能原生解析并复用宿主 fnm / nvm 托管的 Node 安装:宿主发现与沙箱只读挂载使用同一套窄叶校验;`.nvmrc` / `.node-version`(nvm / fnm 都读)命中已安装版本时优先、未安装时继续回退,`engines.node` 仅为偏好;npm 以受信任 `node <npm-cli.js>` 形态启动且不丢失 `npm install` 的联网判定。不再要求用户改系统 Node、把宿主 shim 指向 `/usr/bin`,或把托管目录软链进系统路径。
## 范围
- 移除把 `node` / `npm` / `npx` 指向 `/usr/bin/*` 的宿主 shim 依赖,开发构建默认解析托管安装。
- 枚举 fnm `node-versions/<version>/installation`(含 `aliases/default`)与 nvm `versions/node/<version>`,按窄叶规则校验完整安装前缀。
- 版本选择:`.nvmrc` / `.node-version` pin > PATH 可解析的可用 Node > 版本管理器回退链(`engines.node` 最高匹配 > 活动版本 > 默认别名 > 已安装最高版本)。
- 版本选择:`.nvmrc` / `.node-version` pin > PATH 可解析的可用 Node > 版本管理器回退链(`engines.node` 最高匹配 > 默认别名 > 已安装最高版本)。活动版本不单独查询:激活时它已在 `PATH` 里,回退链不再重复一份可能失效的副本(原 `FNM_MULTISHELL_PATH` / `NVM_BIN` 分支已删除)。
- Linux 命令沙箱把通过校验的完整安装前缀只读挂载,并以 `node <npm-cli.js>` 启动 npm;`npm install` 保持联网判定。
- 沙箱内联环境不继承 fnm multishell 等临时版本管理器变量。
@@ -35,7 +35,7 @@
## 验收标准
- [x] 开发构建在没有 `/usr/bin` shim 的情况下从 fnm / nvm 找到 Node,并在沙箱内运行出真实 `node --version` 与 `npm --version`。
- [x] `.nvmrc` / `.node-version` 指定的版本未安装时命令失败关闭,且不回退到其它已安装版本;`engines.node` 不匹配时不阻塞。
- [x] `.nvmrc` / `.node-version`(nvm / fnm 都读)指定的版本未安装时命令不阻塞,继续回退到 PATH / `engines` 偏好 / 默认别名 / 已安装最高版本;`engines.node` 不匹配时同样不阻塞。
- [x] 不支持的 pin 写法(如 `iojs`、`>20`、`<=20`)按未 pin 处理并回退,而不是误判为「已理解但未命中」。
- [x] 只有通过窄叶校验的完整安装前缀会被只读挂载;HOME、管理器根、`aliases`、宽泛目录、不完整前缀和逃逸 symlink 全部失败关闭。
- [x] npm 在 Linux 上以受信任 `node <npm-cli.js> ...` 启动,`npm install` 仍被判定为联网命令,普通 `npm run` 仍离线。
@@ -4,11 +4,11 @@
- 背景:开发构建里 AGC 让命令沙箱执行 `npm run build` / `npm install` 时,宿主 Node 由 fnm 托管,`node` / `npm` 实际是随 shell 会话变化的 fnm multishell 目录里的 shim;bwrap `--tmpfs /run` 会抹掉该路径,而只按单文件挂载 `<前缀>/bin/npm`(它软链到 `lib/node_modules/npm/bin/npm-cli.js`)会因 `Cannot find module '../lib/cli.js'` 失败。此前把宿主 `node` / `npm` / `npx` shim 指到 `/usr/bin/*` 是错误取舍:系统 Node 26 默认启用实验性 Web Storage,会顶掉 vitest 0.34 jsdom 的 localStorage,使 AGC 测试套件在 HEAD 即失败(见 `pitfalls.md` 2026-10-03 条)。
- 决策(宿主发现与沙箱挂载共用窄叶校验):新增 `validate_node_installation_prefix`,canonicalize 后拒绝 `/home`、`/root`、`/tmp`、`/var`、`/etc`、`/proc`、`/dev`、`/run`、`/sys`、`/boot`、`/srv` 根、HOME 及其祖先和相对路径,并要求前缀同时含 `bin/node` 与 `lib/node_modules/npm/bin/npm-cli.js`(bundle 形态为 `<前缀>/node` + `node_modules/npm/bin/npm-cli.js`)。宿主版本枚举与 Linux 沙箱只读挂载都调用它,避免两处信任口径漂移。
- 决策(版本选择):`.nvmrc` / `.node-version` 是权威 pin,能理解但未安装时返回 `node-version-pinned-not-installed` 失败关闭;`package.json` `engines.node` 只是偏好,永不阻塞;不支持的写法(`iojs`、`||`、部分 `>` / `<=`、hyphen range、prerelease)按未 pin 回退。整体顺序为 pin 命中 > PATH 可解析的可用 Node > 版本管理器回退链(`engines` 最高匹配 > 活动版本 > 默认别名 > 已安装最高版本),只实现文档化比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*`、`lts/*`);`v22.23.3` 这类带 `v` 的 `.nvmrc` 必须先剥前缀。nvm 的 `alias/default` 常只写主版本号(如 `22`),必须按同一 pin 子集在已安装版本里选最高匹配,不能当成完整三元组 `(22,0,0)`。
- 决策(沙箱内启动形态):Linux npm 改为 `node <npm-cli.js>`,因为单文件挂载 npm 软链必然丢 `../lib/cli.js`;只读挂载整棵已验证的安装前缀(不是整个 HOME、`FNM_DIR` 或 `NVM_DIR`)。`npm install` 的联网判定跟随真实启动形态(`node` + `npm-cli.js` + `install`),不因包装变化丢 `--share-net`。
- 决策(版本选择):`.nvmrc` / `.node-version`(nvm / fnm 都读)只改变搜索顺序、不是硬性要求:命中已安装版本就优先用它,能理解但未安装时按未 pin 继续回退,不再返回 `node-version-pinned-not-installed`;`package.json` `engines.node` 同样是偏好,永不阻塞;不支持的写法(`iojs`、`||`、部分 `>` / `<=`、hyphen range、prerelease)按未 pin 回退。整体顺序为 pin 命中 > PATH 可解析的可用 Node > 版本管理器回退链(`engines` 最高匹配 > 默认别名 > 已安装最高版本);活动版本不单独查询(`FNM_MULTISHELL_PATH` / `NVM_BIN`),因为激活时它已在 `PATH` 里,回退链无需重复一份可能失效的副本;只实现文档化比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*`;`lts/*` 与 `lts/<codename>` 都需要 LTS 行映射,当前按未 pin 回退而不猜);`v22.23.3` 这类带 `v` 的 `.nvmrc` 必须先剥前缀。nvm 的 `alias/default` 常只写主版本号(如 `22`),必须按同一 pin 子集在已安装版本里选最高匹配,不能当成完整三元组 `(22,0,0)`。
- 决策(沙箱内启动形态与联网放行):Linux npm 改为 `node <npm-cli.js>`,因为单文件挂载 npm 软链必然丢 `../lib/cli.js`;只读挂载整棵已验证的安装前缀(不是整个 HOME、`FNM_DIR` 或 `NVM_DIR`)。`npm install` 的联网放行只认解析层给出的可信 `(node, npm_cli)` 精确匹配(executable 是对应 node、首个参数是对应 `npm-cli.js`、子命令是 `install`),不再按 basename 判定:项目里同名的 `npm-cli.js` 可写,只加「路径必须绝对」也能绕过;拿不到可信对时一律不放网。
- 决策(范围与非目标):托管版本管理器发现只在 `debug_assertions` / development 生效,发布构建继续只认随包 bundle;不把 fnm / nvm CLI 做成沙箱内工具;Windows 不变;不新增 fnm / nvm 之外的版本管理器。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/environment_check.rs`(窄叶校验、托管安装枚举、pin / engines 解析、选择与回退)、`command_sandbox.rs`(Node 工具链挂载收集与合并、`command_sandbox_requests_npm_install`、`FNM_MULTISHELL_PATH` 清理)、`command_exec.rs`(Linux npm `node_launcher`、`project_command_actual_target`、非 Node 程序 PATH 前置工具链 bin)、`process_session_bridge.rs`(`ProcessSessionLaunchPlan::from_launch` 改用实际目标)。
- 验证方式:`environment_check` 24 passed、`command_sandbox` 14 passed、`command_exec` 19 passed、`process_session` 27 passed(均 `--test-threads=1`);`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 真机 bwrap 内 fnm v22.23.3 的 `node --version` 与 npm 10.9.9 通过;另装 nvm v0.40.8 + Node v22.23.3,`command_sandbox_real_linux_opt_in_runs_nvm_installation_prefix` 证明真实 nvm 前缀可在 bwrap 内跑 node / npm,`real_node_npm_environment_versions`(仅 `NVM_DIR` + 空 PATH + 临时 HOME)证明托管解析确实选中 nvm;`cargo fmt --check`、`npm run check:encoding`、`git diff --check` 通过。验证后 fnm 仍是宿主默认,nvm 未写入任何 shell profile。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/environment_check.rs`(窄叶校验、托管安装枚举、pin / engines 解析、选择与回退)、`command_sandbox.rs`(Node 工具链挂载收集与合并、`prepare_command_sandbox_launch` 透传可信 `(node, npm_cli)`、`LinuxSandboxPlan::npm_install_network`、`FNM_MULTISHELL_PATH` 清理)、`command_exec.rs`(Linux npm `node_launcher`、`project_command_actual_target`、`prepare_project_command_launch_spec` 把可信 launcher 交给沙箱、非 Node 程序 PATH 前置工具链 bin)、`process_session_bridge.rs`(`ProcessSessionLaunchPlan::from_launch` 改用实际目标)。
- 验证方式:`environment_check` 25 passed、`command_sandbox` 15 passed、`command_exec` 19 passed、`process_session` 27 passed(均 `--test-threads=1`);`npm_install_network_detection_requires_the_trusted_node_launcher` 覆盖「项目同名 `npm-cli.js` 不放网、无可信 launcher 不放网、executable 不匹配不放网」,`namespace_arguments_share_net_only_when_trusted` 覆盖 `--share-net` 开关,`npm_command_targets_node_plus_npm_cli_on_linux` 断言解析层可信 `(node, npm_cli)` 与实际目标逐字一致;`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 真机 bwrap 内 fnm v22.23.3 的 `node --version` 与 npm 10.9.9 通过;另装 nvm v0.40.8 + Node v22.23.3,`command_sandbox_real_linux_opt_in_runs_nvm_installation_prefix` 证明真实 nvm 前缀可在 bwrap 内跑 node / npm,`real_node_npm_environment_versions`(仅 `NVM_DIR` + 空 PATH + 临时 HOME)证明托管解析确实选中 nvm;`cargo fmt --check`、`npm run check:encoding`、`git diff --check` 通过。验证后 fnm 仍是宿主默认,nvm 未写入任何 shell profile。
- 边界:nvm 已在验证主机安装(v0.40.8 + Node v22.23.3)并跑通真实前缀与仅 nvm 解析;CI 仍由临时目录夹具覆盖 fnm / nvm 布局,真实安装路径测试保持 opt-in。真实验收前不宣称发布构建也支持 fnm / nvm。
## 2026-10-06 小红书导出 validate/pack:Chrome 61 能力按硬性 ERROR 拦下,pack 自带白名单不再共享
@@ -112,6 +112,8 @@ Gitea 基础镜像通过专用 `genarrative-ci-images` Buildx builder 持久复
Gitea Rust 缓存自动维护由宿主 `genarrative-ci-cache.timer` 收集同一 master push run 六个 Rust job 的原生 V4 缓存产物,不重复执行 Cargo 预热。只传本轮新 key,命中对象只传使用时间;宿主与真实来源镜像对象合并、去重、按新近使用时间裁剪到 4 GiB,从无对象缓存基础镜像重新组装。源 run 不要求全绿,但取消、缺组、旧 attempt、未完成上传或混用来源镜像不得采用。网关暂停新 FetchTask、在途领取结束、持久化任务账本清空且内层活动容器为空才切换,不打断运行中的 CI。首次接入/升级网关需空闲窗口;Token 只需普通仓库 `write:repository`,不查管理员 API。候选装载后清理已收集 artifact,遗留项保留 7 天;真实 master CI 验证后才清理旧镜像,保留当前、一个回滚版、基础镜像及容器引用。部署入口见 `deploy/container/README.md`,合并代码不等于服务启用。
领取网关只对已核对 Gitea 1.26.4 事务回滚的精确 `CreateTaskForRunner: update run <id>: run has changed` Connect 错误允许 runner 重试,避免普通并发更新冲突永久阻断 CI;完整响应格式约束见 `deploy/container/README.md`。其它未知领取结果继续持久化保护,不自动清除 `uncertain`;恢复既有锁前需核实任务与在途请求收敛。容器日志只记录固定原因分类,不打印请求、响应或认证信息。
修改 Gitea workflow 的 job 显示名称、ID 或缓存导出组时,必须同步维护器的 `JOBS` / `RUST_JOB_IDS`;`test_gitea_cache_maintenance.py` 直接对照实际 workflow 检查全集和导出映射,避免自动刷新或镜像验收因名单漂移长期等待。维护器 `Api.request` 的 `method` 是必填关键字参数,GET 也必须显式指定,不根据 body 推断请求方法。
Gitea 缓存部署必须区分网络:runner 的 RPC 走 `gitea-runner-fetch-gate:8080`;内层 job 的 checkout/上传走映射到 `172.30.0.3` 的 `http://genarrative-station/git`;宿主专用 clone 走 `http://127.0.0.1:3003`。不要把 runner 可达的 `gitea:3000` 配给 job。内层 Docker 使用 `10.240.0.0/16`、每 job `/24` 的默认地址池,避开外层 `172.30/172.31` 网段;恢复领取前必须在真实 job 网络里验证 checkout 与 Gitea API,不能只验证 FetchTask。具体配置与遗留空网络处理见 `deploy/container/README.md`。
+40 -7
View File
@@ -2,6 +2,14 @@
这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。
## 2026-10-05 generationInputs 的保存与复用不等于自动生效或完整透传
- **现象 / 根因**:调用方把构图等要求只写入 `generationInputs.artSpec`,但实际生图输入没有这些要求;把 `JsonValue` 和“可复用规范”误读为服务端会自动组装提示词、完整保留全部元数据或自动用于下一次生成。
- **现行边界**:整个 `generationInputs` 承载生成上下文、来源和应用元数据,保存规则取决于接口。场景与音效重建配方;图集原图可保存自定义字段,透明图集与切片另建处理阶段 / 来源元数据。已知字段仍有实际消费者,例如规范图的“游戏类型”、V2 面板恢复字段和客户端来源标记,不能把整个对象描述为无业务作用。
- **排查 / 使用**:同时核对正式请求字段、当前接口的重建 / 清理逻辑,以及最终资源 / 素材记录;重要要求必须进入 `prompt`、`iconDescriptions` 或场景结构化参数。项目与素材库读取可取得服务端实际保存的上下文,但它不等于完整 HTTP 重放载荷;重试仍保存原始请求和幂等键。
- **AGC 美术包**:核心图集新请求将原 brief 与共用的风格、内容和间距要求分别写入 `iconDescriptions`;普通图标入口仍原文单项透传。新增模板只影响新请求,旧请求继续按冻结正文和操作身份恢复,不改原 brief 的意图判据。真实 HTTP 载荷与旧请求恢复必须同时验证,不能只测试 `artSpec` 包含文案。
- **权威说明**:[External v1 OpenAPI](../../openapi/genarrative-external-v1.openapi.json) 与 [API 指南的 Generation Inputs Metadata](../../../.codex/skills/genarrative-external-editor-api/references/requests-and-outputs.md#generation-inputs-metadata)。本条记录现有行为,不引入 API、存储或 UI 行为变更。
## 2026-10-06 陶泥儿导出与发布媒体直传:幂等摘要、幂等账本、skill-pack 红与 zip 根入口
- **multipart 发布的幂等摘要必须纳入图片字节**:写路径改 `multipart/form-data` 后,`metadata` 文本 part 只描述槽位;若摘要只序列化元数据,同一 `Idempotency-Key` 换一张新图或换一个沿用 objectKey 会被判成同请求重放,服务端静默复用旧结果、新图丢失。现行口径:服务端 `publish_request_digest` = 规范化元数据 JSON + 每个 `cover`/`screenshot` part 的 SHA-256 + 槽位里的 objectKey;AGC 侧 `metadata_digest` 同样把图片原始字节喂进哈希。create / create version / update metadata 三条写路径共用;回归时「只换图不改文案」必须得到不同摘要。
@@ -17,8 +25,9 @@
- **根因 2(`--tmpfs /run` 抹掉活动版本)**:fnm 的活动 `PATH` 项是 `/run/user/<uid>/fnm_multishells/<pid>/bin`;sandbox 的 `--tmpfs /run` 会清空该目录,sandbox 内解析到的 `node` 随之失效或退回系统版本。不要依赖宿主 `PATH` 原样进入沙箱:canonicalize 路径,并把活动版本管理器变量(如 `FNM_MULTISHELL_PATH`)从 sandbox 环境里剔除。
- **根因 3(错误取舍会打穿测试)**:把宿主 `node` / `npm` / `npx` shim 指到 `/usr/bin/*` 能让沙箱借用系统 Node,但在本机系统 Node 26 上会默认启用实验性 Web Storage,顶掉 vitest 0.34 jsdom 的 localStorage,AGC 测试在 HEAD 即红(见下方 2026-10-03「AGC 测试不在任何 tsconfig 里」条的环境提示)。正确方向是原生支持托管安装,而不是改宿主 shim。
- **补充(nvm default 别名是主版本号)**:`nvm alias default 22` 写进 `$NVM_DIR/alias/default` 的内容是 `22`,不是完整三元组。若按精确 `(22,0,0)` 去匹配 `versions/node/v22.23.3` 会永远落空,默认别名形同不存在;必须用与 `.nvmrc` 相同的比较子集在已安装版本里选最高匹配。
- **现行口径**:宿主发现与沙箱挂载共用 `validate_node_installation_prefix`;`.nvmrc` / `.node-version` 权威、`engines.node` 偏好;Linux npm 以 `node <npm-cli.js>` 启动并保留 `npm install` 联网判定。契约见技术方案 V1.11.2。
- **验证**:`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 跑 `command_sandbox_real_linux_opt_in_runs_host_node_and_npm_cli`,在真实 fnm v22 前缀下 bwrap 内 `node --version` 与 `node <npm-cli.js> --version` 均通过;单元用例覆盖 pin / engines / 不支持写法 / 宽叶与逃逸前缀 / 联网判定。
- **根因 4(联网放行只看 basename 会被同名文件冒充)**:`npm install` 是唯一允许联网的入口,最初只按「首个参数 basename 是 `npm-cli.js` 且第二个参数是 `install`」放行。Linux 上 `command.exec` 不对 `node` 的脚本参数做路径校验,项目根又可写,于是在项目里放一个自写的 `npm-cli.js` 再 `node 项目/npm-cli.js install` 就能拿到 `--share-net`。修法是让联网放行与解析层给出的可信 `(node, npm_cli)` 精确比对,拿不到可信对时一律不放网;只加「路径必须绝对」不够,绝对路径的项目内文件照样绕过。
- **现行口径**:宿主发现与沙箱挂载共用 `validate_node_installation_prefix`;`.nvmrc` / `.node-version`(nvm / fnm 都读)命中已安装版本时优先、未安装时继续回退,`engines.node` 同为偏好;Linux npm 以 `node <npm-cli.js>` 启动,`npm install` 联网放行只认解析层给出的可信 `(node, npm_cli)` 精确匹配。契约见技术方案 V1.11.2。
- **验证**:`GENARRATIVE_COMMAND_SANDBOX_REAL_TEST=1` 跑 `command_sandbox_real_linux_opt_in_runs_host_node_and_npm_cli`,在真实 fnm v22 前缀下 bwrap 内 `node --version` 与 `node <npm-cli.js> --version` 均通过;单元用例覆盖 pin / engines / 不支持写法 / 宽叶与逃逸前缀 / 可信 launcher 联网放行。
## 2026-10-07 cargo 目标目录里被"刷新 mtime"的陈旧 shared-contracts 会让编译报源文件里明明存在的字段缺失
@@ -189,7 +198,6 @@
- **处理(现行口径)**:`.project-chat-composer.is-direct-codex` 是单列网格(`gap: 8px`);**控件全部集中在唯一一行工具条**——左组 `@` / `+`,右组 模型 / 语音 / **「AI 润色 / 恢复原文」(紧贴发送左侧)** / 发送,六颗 `flex-wrap: nowrap` 平铺(润色钮由 `ResourceReferenceInput` 的 `actionsPortalTarget` portal 进右组发送钮之前的挂点,不再自成一行;它与发送钮同为圆角矩形按钮,相邻成组,DOM / Tab 顺序都是 模型 → 麦克风 → 润色 → 发送);状态/提示层只在有内容时占行(空输入态第二行为 0 高、行间距为 0,状态自己带 6px 上间距)。窄宽度用 `min-width` 兜(控件排 252px / 外壳 278px)+ 横向溢出可见:**不换行、不堆叠、不重叠**,发送钮仍可点击(夹具 `elementFromPoint` 命中)。模型钮封顶 160px 且名字走省略号(收缩由它承担),左组是固定 28px 动作钮、`flex: 0 0 auto` 不参与收缩。**模型失败提示后来改成走浮层**(见本文件顶部 2026-10-04 / 2026-10-05 两条):控件排里既不留常驻红字、也不留常驻状态文字——「控件排内独占一行」那次快照已退役,行高回到由模型触发钮决定。
- **判据/取证**:`apps/ai-game-creator-shell/tests/chatDialogFrameLayout.test.ts` 用层叠求值钉住单行工具条(240 / 280 / 296 / 320 / 360 / 438 / 900 七个宽度下 `flex-wrap` 都是 `nowrap`、控件排 `min-width: 252px`、外壳 `min-width: 278px`)、输入盒最高高度算式(12 盒内上内边距 + 160 编辑器 + 0 输入区行距 + 0 状态行 + 0 输入区下内边距 + 8 输入盒与工具栏行距 + **30 工具栏行高(模型触发钮 30px 撑起来,发送/语音是 28px)** + 12 盒内下内边距 = **222**),以及组件源码里润色钮渲染在工具条挂点(`actionsPortalTarget={controlsActionsSlot}`、挂点在控件排之后);`tests/appSurface/project-development.suite.ts` 钉住 composer 的 `gap: 8px`。真实渲染对照见 #600 PR:同一夹具在 495/438/360/296/280px 面板 × 空 / 长多行 / 队列+提示 四态下,改前 2~4 处相交(含文字被提示盖住),改后 0 处相交、0 处溢出。
- **关联**:`apps/ai-game-creator-shell/src/styles.css`(文件末尾「输入区工具栏与状态提示」区块)、`docs/【功能说明】AGC聊天AI润色与发送前提醒-2026-09-10.md`。
## 2026-10-03 AGC 随包 plugins 的 feature 档位必须与消费方一致,且门禁会因 build.rs 未重跑而假通过
- **现象**:Windows 本机 `npm run check:generated-bindings`(`npm run lint` 链内,`scripts/check-repository-ci.sh` 的 Repository checks 也走它)在 `build.rs:167:29` panic:`插件随包资源校验失败:随包插件存在未声明文件:.../src-tauri/resources/plugins/agc-godot-editor/native/gdextension/bin/win-x64/agc_godot_editor.dll(目标 x86_64-pc-windows-msvc 与当前 feature 组合不允许;请先执行随包资源准备步骤)`;树上换成 `agc-unity-editor/dotnet/publish/win-x64/Agc.Unity.Attach.exe` 时报同一类错。反向还有更隐蔽的形态:门禁 2 秒就 exit 0 说「通过」,但 tree 上其实带着编辑器产物。
@@ -556,7 +564,7 @@ Copy Artifact 插件在**非 SYSTEM 认证**下按「认证用户」判权:只
## 同一条链路两处上限不一致:平台合法产出被客户端整条丢弃
- 现象:客户端报「生成素材失败:platform-generation-result-unknown: 异步生成完成结果无法绑定到 operationId:External Editor 旧同步结果的图集切片超过 64 个」,而平台侧这次生成**其实已经成功并切完图**(任务账本耗时正常、`assetId` 为空、没有任何素材落盘,付费产物被丢)。
- 成因:图集切片上限在链路里存在两份字面量——平台切分、Agent 工具 schema `sliceCount` 与持久化产物批次都是 256,客户端结果绑定门写着 64(`agent/generation/{canvas_generation.rs,external_generation_state.rs}`)。自动切分(`connected-components` + `sliceCount=null`)切出 65~256 片是合法产出,客户端比平台更严就会把结果整条判失败。
- 成因:图集切片上限在链路里存在两份字面量——平台切分与持久化产物批次都是 256,客户端结果绑定门写着 64(`agent/generation/{canvas_generation.rs,external_generation_state.rs}`)。自动切分(`connected-components` 且省略数量参数)切出 65~256 片是合法产出,客户端比平台更严就会把结果整条判失败。
- 处理:客户端门统一到 `PLATFORM_ART_SPRITESHEET_MAX_SLICES = 256`,判据与文案各只留一份(数字由常量插值),并在注释里点名三处同值权威(平台切分常量、工具 schema、公开契约)。
- 复用判据:凡是「平台产出 → 客户端校验后落盘」的链路,客户端门只能表达**安全 / 预算**约束,不得比平台的产品上限更严;两边上限要引同一个常量或同一份文档,改一边时必须同时改另一边,并补一条「上限之内必须能落盘」的回归用例。
@@ -5588,10 +5596,10 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 处理:显式重做使用 `mode=regenerate`,普通请求使用 `reuse-or-create`。模式由 Codex 根据当前用户请求通过审核工具显式选择;客户端不再使用 Unicode NFKC、关键词、否定词表或独立确认句式判断业务意图。旧文本授权规则已被 2026-09-03 MCP 决策替代,相关无调用实现于 2026-09-23 删除。工具桥绑定活动客户端回合与稳定 `clientTurnId`,冻结首次 `brief` 摘要;缺少活动回合或摘要冲突仍拒绝。项目权限、账号、计费、幂等、锁与未知结果恢复合同继续有效。
- 幂等与恢复:同一调用完成回包丢失只从 `completed` 持久结果等值重放,不能因重试再次扣费。App 必须在 Direct 调用前落盘原始 User 消息和回合 ID,Tauri 必须在成功返回前幂等落盘同 ID assistant 终态;同进程重复水合若命中“回合仍在运行”,只能显示瞬时占用提示,不得以稳定 assistant messageId 写成终态并抢占原执行的成功回复。恢复扫描与启动前置恢复必须发现 `resetting / compensating / anchored in-progress` 并在专用锁内恢复,重开项目只续跑真正未回答的原身份。整条付费链必须持有专用跨进程执行锁;换新回合时先持久化 `resetting` 再清理旧阶段账本,不得通过删除 workflow 留出无主窗口。崩溃补偿只恢复旧文件并清 replacement CAS 锚点,已 `prepared / accepted` 阶段账本、原 `Idempotency-Key / operationId` 必须保留,同冻结意图续跑复用旧请求;未知账本在文件 mutation 前失败关闭。只有没有任何阶段账本和替换锚点的孤立 workflow 空壳可原子接管;旧 schema 和其余冲突失败关闭。遇到 prompt 或当前 art-spec 身份不一致的未决账本必须保留原 operation 并返回对账错误。
- 执行边界(2026-09-24 校准):DirectProject 的 cwd 与 AGC 业务身份根是用户选择的 canonical 项目根;其原生 OS 路径字节与权威 manifest `projectId` 经域标签和独立长度前缀编码后绑定连接池和 thread 身份。旧的 `game/` 唯一可写根、禁止全部网络 / 命令 / MCP 的描述已失效;也不能把后来的“完整访问”描述理解为绕过当前宿主门禁。当前 thread 使用 `sandbox=read-only`、`approvalPolicy=untrusted`,turn 使用 `sandboxPolicy.type=readOnly`;原生命令按逐次审批与宿主执行许可处理,客户端 MCP 仍校验项目绑定、业务权限和副作用许可。具体边界以[主实施计划“宿主验收与执行许可合同”](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md#宿主验收与执行许可合同)及当前实现为准,Provider 凭据保持隔离。
- 资源投影:标准图集首次创建和重生成都要求四张透明、可见、像素及平台身份唯一的 canonical 切片;工具只回传通过私有回执、公开清单、源图和顶层登记交叉验证的 `slicePaths` 与安全 `resources`。部分/opaque/重复/缺回执切片必须告警,不能把公开清单或顶层自述身份当作 Canvas 权威。
- 资源投影:标准图集首次创建和重生成均按实际切片集合提交,保留透明、可见、像素及平台身份唯一的校验;有效总图零切片时仍返回成功与告警;工具只回传通过私有回执、公开清单、源图和顶层登记交叉验证的 `slicePaths` 与安全 `resources`。部分/opaque/重复/缺回执切片必须告警,不能把公开清单或顶层自述身份当作 Canvas 权威。
- 同进程恢复补充:命中“同一 stable turn 仍在运行”后除禁止写 assistant 终态外,还必须删除当前 App 实例的恢复 claim。这样原调用随后成功时显式刷新能读取其终态,随后失败时也能按相同 `clientTurnId` 再次续跑;不要靠重载 WebView 清理进程内 claim,也不要用无界定时轮询制造并发调用。
- 严格图集崩溃补充:规范图和背景图的两文件 rollback 不覆盖严格图集事务已经整体修改的 `.agent/manifest.json`、私有回执、公开清单、主图集、四切片和切片清单。必须在严格调用前持久化 pending 及九项旧合同身份;重启恢复先对账底层严格事务,完整新合同直接收口完成,完整旧合同才补偿前两阶段,混合或漂移状态失败关闭。不要在严格提交成功后局部恢复前两张图。
- 部分旧包补充:rollback 的规范图/背景图必须保存旧字节与旧 manifest entry,不能把这两项缺失隐式当成空内容;显式 `regenerate` 因此只在这两项可信可回滚时开放。历史主图集、私有回执、公开清单或 canonical 切片可以缺失,但八个严格路径与受管顶层 asset identity 必须逐项冻结其真实 `Present/Some` 或 `Missing/None` 状态,补偿也必须恢复相同存在性。不要因为旧美术包缺切片而阻断重生成,也不要把本轮新建的严格文件误记成旧文件。
- 严格图集崩溃补充:规范图和背景图的两文件 rollback 不覆盖严格图集事务已经整体修改的 `.agent/manifest.json`、私有回执、公开清单、主图集、实际切片和切片清单。必须在严格调用前持久化 pending 及完整旧合同身份;重启恢复先对账底层严格事务,完整新合同直接收口完成,完整旧合同才补偿前两阶段,混合或漂移状态失败关闭。不要在严格提交成功后局部恢复前两张图。
- 部分旧包补充:rollback 的规范图/背景图必须保存旧字节与旧 manifest entry,不能把这两项缺失隐式当成空内容;显式 `regenerate` 因此只在这两项可信可回滚时开放。历史主图集、私有回执、公开清单或 canonical 切片可以缺失,但固定合同文件、实际切片与受管顶层 asset identity 必须逐项冻结其真实 `Present/Some` 或 `Missing/None` 状态,补偿也必须恢复相同存在性。不要因为旧美术包缺切片而阻断重生成,也不要把本轮新建的严格文件误记成旧文件。
- 对话扫描与 claim 补充:历史中出现 `User A / User B / Assistant B` 时,B 已回答不代表 A 已回答,扫描必须继续寻找 A。成功 Direct 回复在 Rust 返回前已经落盘,前端冗余 append 失败不能据此重跑;普通错误回复的显式落盘失败时,恢复 claim 要保持到 React fallback writer 的同一 messageId append 明确收敛。writer 成功或明确失败后才释放;失败路径要停止该消息的自动迟到重试,再由显式重新加载对话复用原 stable turn。终态后及时删除 claim,避免 Set 无界增长。
## Native shell CI 不能在测试阶段重新解析 Cargo registry(2026-08-26)
@@ -6578,6 +6586,14 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **判据/取证**:`node --test scripts/check-nginx-spa-routes.test.mjs`(正/反用例,含「写回精确匹配即红」)、`npm run check:nginx-spa-routes`、`npm run check:pingora-route-parity`、`cargo test -p pingora-gateway -- pay_checkout_deep_link matches_nginx_route_parity_matrix`;线上复验 `curl -s -o /dev/null -w '%{http_code}' https://<平台域名>/pay/<checkoutToken>` → 200 且正文与 `/` 同一份 `index.html`。
- **关联**:`scripts/check-nginx-spa-routes.mjs`、`deploy/pingora/nginx-route-parity.matrix.json`、`server-rs/crates/pingora-gateway/src/main.rs`、`server-rs/crates/api-server/src/payment.rs`、`deploy/nginx/genarrative.conf`。
## AGC 图集数量与返回次序不表达用途(2026-10-05)
- 图集请求中的 sliceCount 不影响模型提示词,只会约束服务端后处理;客户端不再暴露或发送它,服务端 API 仍保留现状。四类内容需求不等于四个连通域,也不能按返回次序或历史 player 等文件名分配用途。
- 新切片按源资源摘要隔离、以中性序号保存;普通清单记录实际总图路径,美术包回执按实际集合验证。工具返回全部身份与路径标注预览,Agent 看图识别后使用或处理;零片仍交付有效总图与告警。
- 动态集合必须同时进入事务 journal、重生成快照、完整性检查和工具投影。旧数量请求需先定位原动作槽并使用原请求体、幂等键和 operation,候选不唯一则对账;不能删掉指纹字段后直接发起新付费请求。完成结果要保留 sliceMode/gridX/gridY,否则重放会丢失服务端回显。
- 美术包仅有规范图和背景图时,`reuse-or-create` 必须继续补齐图集,不能把两图包返回为 completed。补齐优先恢复现有阶段请求;无请求才只读查找并按需生成,查询失败、身份冲突或账本损坏不能当作资源不存在而重发。补齐失败保留有效基础图,三张主图齐全且可验证后才交付;零切片告警不等于缺失总图。
- 规范图的原始生成资源 ID 与当前账号重新登记后的参考 ID 可以不同。整包完成和中断恢复只按阶段完成结果核对图集资源、对象、任务身份,不能额外要求请求参考 ID 等于规范图原始 ID;这会在素材已经保存后误报身份未绑定。规范图来源关系沿用现有绑定校验。
## 2026-10-05 在线游玩「一直黑屏」:加载面缺失 + 发行网关不压不发 ETag
- **现象**:用户反馈「进入游玩…加载有点慢,一直黑屏体验不好」。真实栈(真实发行包 + Chromium + 4 Mbps/100 ms 模拟链路)实测:网页游玩页点击「开始游戏」后 99.3% 像素亮度 < 24 的近黑面板 + 一行 `游戏正在启动…`,游戏画面 **3298 ms** 才出现;后台审核页点「试玩当前待审版本」后 iframe 直接以 `opacity:1` 出现、区域**全白空白** 4587 ms,页面**全程没有任何加载文案**。
@@ -6596,6 +6612,23 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **验证**:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features=cocos-editor-execute,unity-editor-execute,godot-editor-execute --bin genarrative-ai-game-creator-shell claude_` 34 passed(新增 `claude_code_session_lookup_uses_the_project_directory_and_newest_trace`、`claude_direct_resume_falls_back_to_disk_and_prefers_the_process_map`)。真实环境用随包 `claude.exe`(2.1.285)复验:把项目隔离 home 的 `projects/` 拷进临时 `CLAUDE_CONFIG_DIR`、cwd 设为项目根,`--resume 00000000-…` 立刻回 `No conversation found with session ID`;`--resume f79dae28-…`(磁盘上最新那份)不报找不到,直接进入模型请求,落在本地抓包桩上的请求体里带着完整历史(`messages` 13 条,首条就是 14:23 的「做个废土风的扫雷…」原文)。
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs`。
## 2026-10-07 默认模型目录里没有 cc 模型,本地开发栈根本选不到 Claude Code CLI
- **现象**:本地 `/api/llm/models` 的 14 条模型全部是 `agentMode: "codex"`(`claude-opus-5-5` 也是 `protocol: openai_responses`),cc 模式在本地开发栈里没有可选项,只能靠后台手工加模型;而安装版走 dev 目录时 `claude-opus-5-5` 是 cc 模型。
- **根因**:默认目录由 `AgcModelCatalog::from_upstream_models` 按上游模型列表生成,所有条目一律落在 `AgcAgentMode::default()`(Codex),"上游模型名 → 执行器"没有任何默认绑定,所以任何全新环境都不会自带 cc 条目。
- **顺带查到的环境陷阱**:本地 8082 上的 `api-server.exe`(PID 15748)是从**另一个 worktree** 起的 —— `C:\Users\kdletters\.codex\worktrees\5cd1\Genarrative`(分支 `codex/agc-harness-cloud-config`,模型 DTO 多一个 `harnessProfileId`),不是 `F:\Projects\Genarrative` 的 master。所以"本地开发栈"看到的模型目录、字段和 master 不一致,用 master 的现象去对本地结果会得出错误结论;判断本地行为前先看 `Get-CimInstance Win32_Process` 的 `ExecutablePath`。
- **现行口径**:`from_upstream_models` 为上游的 `claude-opus-5-5` 额外生成一条 `id=claude-opus-5-5-cc`、`alias=claude-opus-5-5 (Claude Code)`、`agentMode=cc`、`protocol=anthropic` 的条目(原 Codex 条目与默认项保留;超过目录项数上限时不再追加)。后台手工保存过的目录不受影响:启动期只在目录缺失或校验不通过时重建,已有合法目录不会因为这行默认值被覆盖。
- **验证**:`cargo test -p module-runtime --lib agc_models::tests`(新增 `catalog_default_binding_adds_claude_code_cli_for_the_official_cc_model`,断言 cc 条目、`protocol=anthropic`、默认项仍是 Codex 条目,以及非该模型不额外生成)。
- **关联**:`server-rs/crates/module-runtime/src/agc_models.rs`、`server-rs/crates/api-server/src/agc_models.rs`。
## 2026-10-07 cc 模型说"我没有任何可调用的工具":先分清没连上还是模型说错
- **现象**:安装版 0.1.230,新建项目 `gameagent-63d3fda7` 的第一个回合(19:25),模型回答"在当前对话里,我**没有任何可调用的工具**…我能看到的上下文只有工作目录/平台/不是 git 仓库";同一版本 19:22 在 `gameagent-0514673b` 的回合里却真实调用了 `mcp__agc__client_session_info` 与 `mcp__agc__conversation_list` 并拿到回执。**同一个构建既能用工具、又会被模型说成没有工具**,所以"模型自述"不能当证据。
- **受控复现(本地 SDK + 桩 MCP + 抓包桩)**:把随包 sidecar 指向桩 MCP 与桩 Anthropic 端点,抓到的模型请求里 `tools=["mcp__probe__echo_probe"]`;把 MCP 握手延迟 12 秒、把 SSE `GET` 打成 405,请求里依然带着这个工具。也就是说 `mcpServers` / `allowedTools` / `Authorization` 这套接线本身是通的,工具缺失只可能来自真实链路里的连接失败——而宿主当时**没有任何记录**能证明 init 里工具到底有没有下发。
- **现行口径**:`ClaudeCodeStreamState.observe` 现在解析 SDK 的 `system/init`,并把工具数与我方 loopback MCP(`agc`)的状态写进应用日志 `agent.direct_codex.claude_init tools=<n> mcp=agc:<status>`。当 init 明确给出 `failed` / `error` / `unavailable` / `disconnected` / 服务器缺失 / `connected` 却没有工具,**且本轮一次工具都没请求过**时,这一轮不再把模型回复当成功交付,而是判成可重放的 `McpUnavailable`:先落一条可见过程行(`客户端 MCP 工具未就绪,正在自动重试(第 N/3 次)`),按既有上限自动重试,用满次数后回`客户端 MCP 工具未就绪(agc=…):本轮模型看不到项目工具`。`pending` / `connecting` / 看不到状态只留证据不判死(SDK 若改成非阻塞连接,不能把每一轮都判死);真调用过工具的一轮也不翻案。
- **验证**:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features=cocos-editor-execute,unity-editor-execute,godot-editor-execute --bin genarrative-ai-game-creator-shell claude_` 38 passed(新增 `claude_init_reports_whether_the_loopback_mcp_tools_are_actually_there`,覆盖 connected / failed / missing / connected-无工具 / pending / 无 mcp_servers 六种 init 事实)。
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/claude_code_cli.rs`。
## 2026-10-07 cc 回合超时后宿主不重试,只能用户手动点「重试」
- **现象**:用户截图 `等待模型回执超时(已尝试 1 次),本轮未完成;请稍后重试:Claude Agent SDK sidecar 回合超时:连续 180000 ms 没有任何事件(已收到 3 个事件)`——一轮卡满 3 分钟就判失败,用户只能自己点「重试」,而那次手动重试 58 秒就成功了;同一项目当天 15:07 又原样复现一次(15:10 失败)。
@@ -621,9 +621,9 @@ Runner-kill E2E 不再以 latest task 或单个 process record 推断整体恢
开发构建(`debug_assertions`)下,AGC 生成和验证 Web 项目所用的 Node 往往由 fnm / nvm 等版本管理器托管:安装前缀位于用户目录下,活动 `PATH` 指向随 shell 会话变化的临时目录(如 fnm 的 multishell)。V1.11.2 让开发构建的命令沙箱原生解析并只读复用这些托管安装,不再要求用户改系统 Node、把宿主 shim 指到 `/usr/bin`,或把托管目录软链进系统路径。发布构建继续只认随包 bundle,不新增托管版本管理器探测,也不改变 bundle 缺失时的失败口径;Windows 行为不变。
- 版本来源是机器上可枚举的托管安装:fnm 的 `node-versions/<version>/installation`(含 `aliases/default` 指向的默认别名)与 nvm 的 `versions/node/<version>`。宿主发现和沙箱只读挂载共用同一套窄叶校验,不允许两处信任口径漂移。
- 解析优先级为:`.nvmrc` / `.node-version` 的权威 pin 命中 > 宿主 `PATH` 能解析出的可用 Node > 版本管理器回退链(`package.json` `engines.node` 偏好中的最高匹配 > 当前活动版本 > 默认别名 > 已安装最高版本)。`.nvmrc` / `.node-version` 能理解但未安装时必须失败关闭(`node-version-pinned-not-installed`),不得静默回退;`engines.node` 只是偏好,任何情况下都不阻塞。只实现文档化的比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*` 通配、`lts/*`);不支持或无法解析的写法按「未 pin」处理并回退。
- 解析优先级为:`.nvmrc` / `.node-version`(nvm / fnm 都读)命中已安装版本 > 宿主 `PATH` 能解析出的可用 Node > 版本管理器回退链(`package.json` `engines.node` 偏好中的最高匹配 > 默认别名 > 已安装最高版本)。不单独查询活动版本(`FNM_MULTISHELL_PATH` / `NVM_BIN`):激活时它已经在 `PATH` 里,回退链重复一份可能失效的副本只会带来误导。`.nvmrc` / `.node-version` 只改变搜索顺序,不是硬性要求:能理解但未安装时按「未 pin」继续回退,不返回 `node-version-pinned-not-installed`、不阻塞命令;`engines.node` 同样只是偏好,任何情况下都不阻塞。只实现文档化的比较子集(精确三元组、major、`>=` / `>` / `<=` / `<`、`^`、`~`、`x` / `*` 通配);`lts/*` 与 `lts/<codename>` 都需要 LTS 行映射,当前与其它不支持写法一样按「未 pin」处理并回退,不声称匹配任意版本。
- 只读挂载只允许通过窄叶校验的完整安装前缀(同时含 `bin/node` 与 npm 的 `npm-cli.js`)。HOME、`FNM_DIR` / `NVM_DIR` 根、`aliases` 目录、宽泛用户目录、不完整前缀,以及 canonicalize 后逃逸出受控前缀的 symlink 全部拒绝并失败关闭;不得为了兼容而挂载整个用户 HOME 或版本管理器数据目录。
- Linux 上 npm 不再直接执行 npm shim,而是以受信任的 `node <npm-cli.js> ...` 启动;`npm install` 的联网判定必须跟随这条真实启动形态,不能因为包装方式变化而丢失联网或反向放开。
- Linux 上 npm 不再直接执行 npm shim,而是以受信任的 `node <npm-cli.js> ...` 启动;`npm install` 的联网放行必须与解析层给出的可信 `(node, npm_cli)` 精确一致(executable 是对应的 node、首个参数是对应的 `npm-cli.js`、子命令是 `install`),不能只匹配 basename——项目根可写,项目里同名的 `npm-cli.js` 会骗到 `--share-net`;拿不到可信对时一律不放网。
- 沙箱内联环境不继承宿主活动版本管理器的临时变量(如 fnm multishell 路径);版本管理器 CLI(`fnm` / `nvm`)本身不需要在沙箱内可用。
## V1.12 受控本地 Git 提交
@@ -544,7 +544,7 @@ App 界面测试中,关闭 Agent 弹窗后的迟到读取用例先等待「刷
已有持久生成账本的 Provider 待执行动作恢复时,若动作省略了旧视觉 Agent 自动补齐的参数,只在 Agent、动作身份、生成种类和冻结提示词均匹配旧合同后补齐缺省参数;显式参数不得被覆盖。新请求继续按当前自由图片合同执行,不能重新引入固定视觉产物门禁。恢复复用原 operation 与幂等账本,不因默认值变化重复提交已受理请求。
单 HTML 测试须在项目初始化前准备 HTML;npm 项目的预览与导出测试须准备构建目录。图片测试按现行数量和布局合同验证资源、透明度、引用和持久恢复,不继续要求固定四切片。
单 HTML 测试须在项目初始化前准备 HTML;npm 项目的预览与导出测试须准备构建目录。图片测试按现行数量和布局合同验证资源、透明度、引用和持久恢复,不要求固定切片数量。
本地 Provider smoke 在启动 Agent 前准备已有的 `game/index.html`,按 JSON Generator 的单 HTML 项目合同验证生成、资源引用与浏览器预览。Agent 子进程失败时,诊断必须包含退出码、终止信号及有长度上限的 stderr/stdout 尾部,避免编译 warning 淹没实际错误。定位此阶段失败时单独运行 `npm run ai-game-creator-shell:agent-run:smoke`。
@@ -867,7 +867,7 @@ Agent 可见的系统指令、工具与参数说明、恢复指引和上下文
2026-07-14 V1.11.1 最终真实验收:发布 AppData 的真实 `gpt-5.5` `process-session` 形成 41 条 task、75 条 event、63 条 Agent DB 和 8 条 receipt,唯一 start、3 poll、唯一 stdin / terminate、3 次 cursor 推进及唯一 terminal / completed / assistant全部通过;Runner kill套件形成 13 条 task、19 条 event、19 条 Agent DB,真实 SIGKILL后项目 cwd进程清零、新 boot保持同 run / session并只形成 1 条 reconciliation。两套的 reconnect、重放、重复 action / message / receipt、公共进程正文、密钥和诱饵泄漏均为 0,disposable项目均自动清理;V1.11.1 持久进程链路据此完成验收。
2026-10-07 V1.11.2 切片:Linux 命令沙箱原生支持 fnm / nvm 托管的 Node。开发构建(`debug_assertions`)先从 `.nvmrc` / `.node-version` 的权威 pin、再按 `package.json` `engines.node` 偏好、活动版本、版本管理器默认别名和已安装最高版本选择托管安装;`.nvmrc` / `.node-version` 能理解但未安装时失败关闭,`engines.node` 和无法解析的写法不阻塞。宿主发现与沙箱只读挂载共用同一窄叶校验,只挂载完整安装前缀(含 `bin/node` 与 npm 的 `npm-cli.js`),拒绝 HOME、管理器根、`aliases`、宽泛目录、不完整前缀和逃逸 symlink。Linux npm 以 `node <npm-cli.js>` 启动,`npm install` 的联网判定跟随该真实形态;sandbox 环境剔除 fnm multishell 变量。发布构建仍只认随包 bundle,Windows 不变。定向 Rust 测试与真实 bwrap 内 fnm v22 的 `node` / npm 运行已通过;真实 nvm 端到端待补。
2026-10-07 V1.11.2 切片:Linux 命令沙箱原生支持 fnm / nvm 托管的 Node。开发构建(`debug_assertions`)按 `.nvmrc` / `.node-version`(nvm / fnm 都读)命中、`package.json` `engines.node` 偏好、版本管理器默认别名和已安装最高版本的顺序选择托管安装;`.nvmrc` / `.node-version` 未安装时继续回退、不阻塞,`engines.node` 和无法解析的写法同样不阻塞。宿主发现与沙箱只读挂载共用同一窄叶校验,只挂载完整安装前缀(含 `bin/node` 与 npm 的 `npm-cli.js`),拒绝 HOME、管理器根、`aliases`、宽泛目录、不完整前缀和逃逸 symlink。Linux npm 以 `node <npm-cli.js>` 启动,`npm install` 的联网判定跟随该真实形态;sandbox 环境剔除 fnm multishell 变量。发布构建仍只认随包 bundle,Windows 不变。定向 Rust 测试与真实 bwrap 内 fnm v22 的 `node` / npm 运行已通过;真实 nvm 端到端待补。
2026-07-14 起,同一文档的“V1.12 受控本地 Git 提交”补齐单 Agent 的修改、验证、审阅、本地提交闭环。新增且只新增 `project.git_commit`,输入绑定 `message / paths / expectedHead / expectedSnapshotFingerprint`,最多提交 12 个显式安全路径;它是不可降为 `auto` 的强制确认工具,legacy 空策略也继续要求确认,项目策略仍可显式拒绝。动态隔离 child 无条件禁用该工具,最终提交由父 Agent 统一发起。当前 run 必须在当前非零 project revision 上已有 passed verification gate。`git.inspect` 签发的 `commitSnapshotFingerprint` 绑定 HEAD、附着分支、规范化安全状态和全部安全变更文件内容;`.agent` 等控制面正常落盘不制造跨动作漂移,安全源码、HEAD、分支、revision 或 gate 任一变化仍失败关闭。提交只支持标准仓库根和本地附着分支,要求真实 index 没有 staged 内容,并用临时 index、真实 `index.lock`、`commit-tree` 和带 expected old HEAD 的 `update-ref HEAD` 精确前移 ref,同步 HEAD / branch reflog后跨平台原子安装 index;未选改动保持未暂存。它不开放 remote、分支切换、merge / rebase、reset、stash、tag、submodule 或 worktree 写操作,也不能通过 `command.exec` 绕过 `.git` 只读沙箱。成功 observation、Agent DB 审计和 terminal receipt 只保留 parent / commit SHA、分支、安全路径、message SHA-256 和剩余变更计数;ref 前移后的不确定错误或审计失败进入 `needs-reconciliation`,已知 commit 的审计失败仍在 fallback receipt 保存 commit SHA,恢复不得重放提交。
@@ -1497,7 +1497,7 @@ game-project/
- 2026-07-27 补充 tool-plan 成功响应交接的内容边界:Provider 的自然语言计划叙述,以及结构化 arguments 中 `body / code / content / css / html / newText / oldText / patch / script / text` 等源码内容字段,只检查真实密钥 token 形状、凭据头标记和不安全控制字符;仅仅提及 `.env` 或 `game-creator.config` 不能阻断已经计费的安全响应。结构化输入中的敏感 JSON key、非内容字段中的配置痕迹或绝对路径、真实 token、容量、thinking、身份、顺序和账本完整性门禁仍失败关闭。成功 handoff 失败进入 reconciliation 时,Runtime 额外只持久化受控 `failureKind`、脱敏错误 SHA-256 和字符数,不保存 Provider 正文、function arguments、密钥或绝对路径。定向回归覆盖叙述/源码字段放行、`.env.local` 路径和真实 token 拒绝、全部 tool-plan handoff 回归及诊断零正文。
- 自然语言 interaction 的 `resume` 只代表“继续当前未完成 Runtime”。宿主在进入 interaction 前已确认当前 Session 没有可 steer、pending 或 running 的 Runtime 时,模型返回的自然语言 `resume` 必须规范化为 `execute`,基于会话历史新建 run;显式恢复入口(`--agent-resume`)仍只执行恢复扫描且无任务时不新建,避免“那就继续修复”被反复吞成空恢复。
- `prepared` 发布后,创建阶段锚定的事务目录句柄和 identity 必须由 live rollback 对象一直持有到 `committed` 清理或 rollback 结束,提交和回滚不得按 `PathBuf` 重新接受替换目录;live commit 在发布 `committed` 前后都必须验证 retained handle 仍对应权威 pathname,身份漂移不得降级为提交成功 warning。Unix 清理先把权威叶子通过 no-replace rename 原子隔离为固定 retired 目录,复核 retained inode 后再清空和删除,删除前还需再次复核;进程若在隔离后退出,下次持项目写锁恢复先幂等清理 retired 目录。恢复在首个 canonical 写入前一次性冻结九路径全部前态,晚序普通文件内容变化也必须触发 CAS 冲突并逆序回滚早序安装。历史 `.previous / .replacement` 若没有 durable journal,只允许通过锚定父目录识别后进入 reconciliation;不得凭 pathname 自动 hard-link、move、恢复 canonical 或删除残留。
- 图集本地提交以主图 staging 为线性化前置:任何新主图先写随机私有 staging 文件,替换时保留 previous,canonical 主图完整安装后才写四切片、公开清单、私有回执和项目资产登记。进程若在 backup/install 窗口退出,同一 accepted External generation 恢复先识别唯一同 suffix 的 previous/replacement 对并恢复旧主图,再按远端结果完成替换;若 canonical 已等于远端摘要,则不再要求替换授权,直接补齐其余合同。成功后清理主图、四切片、公开清单、私有回执和项目 manifest 的全部遗留 staging/backup。首次生成也禁止直接流式写 canonical 路径,避免部分 PNG 被误认为已安装结果。
- 图集本地提交以主图 staging 为线性化前置:任何新主图先写随机私有 staging 文件,替换时保留 previous,canonical 主图完整安装后才写实际切片、公开清单、私有回执和项目资产登记。进程若在 backup/install 窗口退出,同一 accepted External generation 恢复先识别唯一同 suffix 的 previous/replacement 对并恢复旧主图,再按远端结果完成替换;若 canonical 已等于远端摘要,则不再要求替换授权,直接补齐其余合同。成功后清理主图、实际切片、公开清单、私有回执和项目 manifest 的全部遗留 staging/backup。首次生成也禁止直接流式写 canonical 路径,避免部分 PNG 被误认为已安装结果。
- ESM 投影中的顶层 function/class/variable 声明必须直接使用 Oxc statement span 提取,不能用首个分号或换行截断箭头函数、多行 initializer 或多 declarator;对象、数组、默认值与 rest 解构声明必须递归收集全部 binding,并保证同一声明只投影一次。import symbol 与 importer 自有 root binding 分开保存,即使名称仅大小写不同也不得在组合前折叠;投影根分配 canonical 名时必须避让 importer 与 origin 的全部非 import semantic binding,不能被嵌套局部捕获。模块组合必须按依赖深度迭代到稳定闭包,把被导出函数继续依赖的 imported origin 带入最终 consumer 单元;循环 ESM 以原始声明 identity 去重回流,不能不断生成重命名副本,并以最终 span replacement 后的单 unit `2 MiB`、累计投影处理 `32 MiB` 为失败关闭上限。固定字符串 dynamic import 同样由下游实际使用的 export 反向驱动加载;未使用 export、未调用嵌套函数和恒假分支中的 dynamic source 不得进入模块单元,同名 dynamic export 不能反向选择未引用的本地声明。只有被选声明中的 `await import` 解构、namespace member 或 `.then(...)` 静态 binding 才能进入组合投影;callback 参数、解构 alias 和 namespace member 必须按 semantic symbol span 改接到投影根,dynamic object shorthand 改名时还必须显式保留原属性键,不能靠追加同名文本跨过局部遮蔽。span replacement 完成后还要对完整组合 unit 重跑 parser 与 semantic,启发式扫描只能在原始源码完成 AST 解析和掩码后再统一小写。匿名 default function / arrow 必须在原始 AST 中以 collision-safe synthetic binding 注册可外调 root span,使 wrapper 内 imported member 的传递依赖继续传播;synthetic binding 必须避开用户真实根名,改名只能更新 default target,不能污染同名命名导出。namespace 经过 renamed re-export 时,同时保留 importer 使用的 member 名和最终 origin export 名:前者定位 importer member span,后者选择 origin declaration,不能混用。named import、namespace import 和 namespace 解构 alias 的成员调用必须保留完整静态成员路径,并按调用 span 排除恒假分支后再把 demand 传播到上游;对象 / class 直接成员、对象解构 alias、实例 alias 与下游 wrapper 都使用同一条可达性链。对象 method shorthand、函数表达式值、箭头函数值以及 class function-valued field 必须按精确函数 span 注册成员根,只加载被实际调用成员中的 dynamic dependency,不能因声明写法遗漏,也不能把属性内未调用的嵌套函数升级为根。
- 被选 export root 的直接顶层 assignment 必须与声明一起进入 projection,并沿 RHS semantic dependency 继续闭包,覆盖声明后 live binding 初始化、导出对象成员安装与 class prototype 安装。assignment target 必须解析到相同 root symbol;函数体内、嵌套控制流或其它 root 的写入不随文本同名混入。importer 外部调用被选 export 时,assignment RHS 的 function / arrow、对象成员安装与 prototype 成员安装必须成为对应 root / member 的 projected reachability root,且 class static 与 instance 成员严格分离。全部依赖声明先完成投影,再按原始源码位置输出延后的初始化写入,避免 projection traversal 引入 TDZ;组合结果继续受 canonical 重命名、循环 identity 去重及既有体积门禁约束。
- 顶层 object / array destructuring assignment 写入 exported live binding 时,projection 必须按 assignment target 内实际解析到的 root symbol 收集整条写入,并从最终一次写入中精确选取对应对象属性或数组槽位的 function / arrow 作为外部 callable root;更早写入和其它解构槽位不得反向加载 dynamic dependency。
@@ -1630,20 +1630,36 @@ game-project/
## 2026-08-23 Direct Codex 美术包显式重生成与切片投影
- 客户端图集入口不暴露或发送 `sliceCount`;保留 `sliceMode` 与网格参数。服务端 API、OpenAPI 及服务端数量限制保持现状。四类内容只是生成需求,不能推导四个连通域、切片数量或返回次序对应的语义。
- 美术包和普通图集按实际返回的 0–256 张切片保存、登记与返回。有效透明总图没有独立切片时仍完成并返回切片告警;Agent 必须看图确认实体、状态、用途及缺失内容,再决定使用、进一步本地处理或补充生成。已有授权、付费幂等与资产登记规则继续适用,不能虚构切片、坐标或 Canvas 身份。
- 美术包 `reuse-or-create` 复用有效规范图和背景图,继续补齐缺失图集。补齐时若图集阶段有持久化请求,优先恢复原请求、原幂等键和操作身份,不以远端资源查找绕过账本;没有请求时才尝试只读恢复已有图集,确认没有可恢复产物后才发起图集生成。查询失败、账本损坏或身份冲突返回错误并保留现场,不把失败解释为不存在而新建请求,也不重生成已有效的前两张图。完整本地包仍按已有规则直接复用。
- 美术包成功结果统一要求规范图、背景图和总图通过已有文件、来源与登记校验,并返回这三张主图及实际可验证切片;缺失任一主图时返回工具错误,不返回 `completed`。有效总图没有独立切片仍为完成并附切片告警。补齐图集失败时保留已有规范图、背景图和可恢复请求,明确告知图集未完成及真实原因;游戏工程本身允许复用已有基础素材的验收规则不随之收紧。
- 整包重生成完成与中断恢复按阶段完成结果核对图集的资源、对象和任务身份,不再要求请求参考 ID 等于规范图最初的资源 ID;规范图在当前账号重新登记后可具有不同 ID。既有来源绑定、请求恢复和本轮替换记录校验继续适用,不增加新的参考图对账流程。
- 新切片使用源图集身份隔离的目录与中性序号,不再生成按 player 等用途命名的别名。普通图集的清单记录实际总图路径并保存在自己的目录;美术包公共清单与私有回执只证明来源、完整性和实际产物集合,不表达语义分类。历史四用途路径仅白名单兼容读取与恢复,文件名不能证明用途。
- 本地事务按固定合同文件与本次实际切片集合冻结快照、提交及恢复,保留摘要、真实 alpha、可见像素、唯一身份、项目归属、资源预算和 CAS 校验。重生成的旧快照包含实际旧切片;历史固定集合事务仍可恢复,不重写旧账本或请求体。普通调用升级前携带数量的活动请求必须在新 POST 前被定位并继续使用原身份;多个可能候选时失败关闭,不能任选或重复付费。
- 图集完成结果账本保留响应的 `sliceMode/gridX/gridY`,以支持同身份重放。历史账本已丢失切分声明且返回零切片时,声明无法用于证明切片方式;保留有效总图并明确告警,不因此重发生成。存在实际切片时仍要求切分声明一致。
- 两个工具都返回总图、完整切片路径、全部资源身份、告警以及有界预览。每张预览明确绑定路径,列出未内嵌预览的路径,Agent 使用现有图片查看能力继续检查。可验证的本地处理产物经现有导入工具登记;游戏语义绑定放在游戏工程中。
- 验收覆盖新 HTTP 请求完全省略数量字段、0/2/4/6 张切片、不同图集目录隔离、完整资源投影、旧账本零新增生成 POST、动态集合事务恢复与路径白名单。
- 美术包核心图集的新请求必须把 brief 原文与客户端的素材内容、风格和排布要求共同写入 `iconDescriptions`。内容覆盖当前玩法需要的玩家主体及状态、目标或收集物、障碍或场景元素、反馈特效;各素材独立排布并留出切分间距,沿用规范图的轮廓、材质、色板与光照。描述条数和内容类别不代表切片数量或返回顺序。规范图通过 `referenceId`、比例和尺寸通过正式参数传入;透明结果由服务端纯色底生成与抠图提供,不向模型追加直接生成透明背景的要求。`generationInputs.artSpec` 仅保留描述性上下文,不能作为要求已进入生成提示词的证据。
- Agent 在普通图集的 `prompt` 和美术包的 `brief` 中,需求明确时须列出各项素材的数量、状态及独立排布与留白要求;数量未确定时不编造。数量用于表达生成目标,不构成服务端切片数量保证,也不赋予返回顺序语义;生成后仍须看图确认实际切片内容与用途。工具参数说明与随包 skill 使用同一口径。
- 美术包 `brief` 的 Agent 工具说明简洁标注“不超过 200 字符”。本次仅补充说明,schema 的 4000 字符上限、请求组装、阶段校验和实际生成行为保持现状;删除旧 100 条描述输入方式并适当提高完整提示词字符上限的后续改造由 [issue #660](https://git.genarrative.world/git/GenarrativeAI/Genarrative/issues/660) 跟踪。
- 美术包工具继续只接收 `brief` 与 `mode`,Agent 在 brief 中提供具体主题、风格、实体和反馈需求;客户端补齐通用要求。普通画布及 `agc_generate_image` 图标入口仍将 trim 后原文作为唯一描述项。美术包新增要求使用独立描述项并校验当前 API 的每条 200 字符、合计 2000 字符 / 6144 UTF-8 字节及 100 条上限,不挤占或截断原文;本次不扩展已有 brief 长度合同。只在没有冻结请求的新提交路径组装要求,已有 `prepared / accepted / legacy-completed` 请求按原请求体、幂等键与操作身份恢复,原 brief 的意图比较不受新增模板影响。验证必须覆盖实际 HTTP 请求、普通入口原文、描述边界及旧请求恢复,不以元数据或提示词文本存在代替传输证据。
- 请求要求的自动化证据由 `art_package_spritesheet_posts_requirements_but_ordinary_icons_keep_original_text`、`art_package_spritesheet_recovers_old_requests_without_injecting_new_requirements` 与描述边界用例提供:生产生成入口经过本地 HTTP 夹具,核对真实 POST、原请求字节、幂等键及重复恢复零新增生成 POST。真实 Provider 的视觉效果不由请求夹具替代。
- 标准美术包在客户端将规范图、背景图和主图集统一保存为 PNG:下载仍校验来源、声明类型与文件签名,随后按真实内容接受 PNG/JPEG/WebP,在已有 20 MiB、4096 像素单边和 64 MiB 解码内存限制内完整解码。有效 PNG 原样保留,JPEG/WebP 编码成 PNG,最终内容也不得超过 20 MiB;本地媒体类型固定为 `image/png`。转码不补造透明度,主图集与独立切片继续执行真实 alpha、可见像素、尺寸和唯一性合同;平台独立切片仍须为 PNG。此行为仅属于美术包,普通图片工具的指定扩展名合同不变。
- 美术包的转码在项目提交锁和本地写入之前完成,文件摘要、已安装结果识别、替换恢复和 manifest 登记均使用最终 PNG。转码失败保留原生成账本与平台身份,同冻结意图重试重新读取已有结果,不提交新的付费生成;不改变请求快照、幂等身份、固定资源路径或旧 PNG 包的复用方式,无数据迁移。验收覆盖 JPEG/WebP 转码、PNG 字节不变、损坏/超限拒绝、真实透明度以及已有结果重复恢复零生成 POST;客户端真实 Provider 的整包验证单独记录。
- 转码自动化验收由 `canvas_generation_tests` 的格式/边界用例和 `retained_runtime_generation_retries_a_completed_stage_without_posting_again` 本地 HTTP 夹具覆盖:PNG/JPEG/WebP 均先下载损坏内容,再从同一已完成账本恢复两次,核对最终 PNG、稳定本地 asset ID、Canvas 来源身份、账本保留/清理及零生成 POST;替换与补偿继续由现有图集事务和 Direct 重生成用例覆盖。真实 Provider 的客户端整包效果不由这些夹具替代。
- `agc_tools.taonier_prepare_game_art` 的请求模式固定为 `reuse-or-create | regenerate`。缺省使用 `reuse-or-create`,完整且可信的本地包继续零付费复用;只有用户显式要求重做、替换或切换视觉风格时使用 `regenerate`,并绕过完整包短路,按规范图、背景图、透明图集顺序生成和原位替换。`regenerate` 的旧包前置门只要求规范图和背景图已经可下载、可解码、来源一致且存在可信 manifest 登记,使两项旧字节与登记可以完整 rollback;历史主图集、私有回执、公开清单或 canonical 切片可以缺失。客户端必须把八个严格路径的实际存在性和摘要,以及其中受管顶层 asset identity,逐项冻结为 `Present/Some` 或 `Missing/None`,不能把缺失状态伪造成空文件或虚假登记。规范图或背景图任一缺失或身份无效时才失败关闭并提示先用 `reuse-or-create` 修复基础素材。
- `agc_tools.taonier_prepare_game_art` 的请求模式固定为 `reuse-or-create | regenerate`。缺省使用 `reuse-or-create`,完整且可信的本地包继续零付费复用;只有用户显式要求重做、替换或切换视觉风格时使用 `regenerate`,并绕过完整包短路,按规范图、背景图、透明图集顺序生成和原位替换。`regenerate` 的旧包前置门只要求规范图和背景图已经可下载、可解码、来源一致且存在可信 manifest 登记,使两项旧字节与登记可以完整 rollback;历史主图集、私有回执、公开清单或 canonical 切片可以缺失。客户端必须把固定合同文件与实际旧切片的存在性和摘要,以及其中受管顶层 asset identity,逐项冻结为 `Present/Some` 或 `Missing/None`,不能把缺失状态伪造成空文件或虚假登记。规范图或背景图任一缺失或身份无效时才失败关闭并提示先用 `reuse-or-create` 修复基础素材。
- 显式重生成不放宽 External Editor 幂等与未知态边界。固定阶段已有 `prepared / accepted` 账本时,本次生成 prompt 必须与账本冻结 prompt 一致才可恢复;不一致返回 `platform-generation-result-unknown` 并保留原 `Idempotency-Key / operationId` 对账,禁止把旧结果解释为新意图,也禁止另起付费 POST。
- 整包重生成在首个付费阶段前建立客户端私有 v4 workflow,状态固定为 `resetting / in-progress / compensating / completed`,并同时绑定意图摘要和客户端稳定 `clientTurnId`。专用 `direct-codex-art` 跨进程执行锁覆盖整个付费重生成生命周期,但不持有通用项目写锁等待网络。规范图和背景图替换后立即持久化旧字节、旧 manifest entry 与本轮双 CAS 锚点;任一后续阶段失败时进入 `compensating`,可在进程重启后继续恢复旧文件及旧登记。已成功阶段的生成账本继续保留;`completed` 持久化经脱敏和数量 / 长度限制的完整工具结果,同一 `clientTurnId` 回包丢失时必须等值重放且零新 POST。新的显式用户回合先持久化目标回合所有的 `resetting` workflow,再清理上一轮三阶段账本并转回 `in-progress`,任一崩溃点都不得出现无 workflow 窗口。只有尚无任何阶段账本且无替换锚点的孤立 `in-progress` 空壳允许被新回合原子接管;其余身份冲突、未知版本以及缺少新恢复字段的旧 v2/v3 workflow 均失败关闭,不能用 serde 缺省值把旧状态升级成可执行状态。
- 恢复扫描必须把 `resetting`、`compensating` 和仍带替换锚点的 `in-progress` 识别为可恢复状态,并在 Direct app-server 启动前持有同一专用执行锁完成阶段清理、补偿和中性化。补偿只恢复旧文件并清除本地 replacement CAS 锚点;已 `prepared / accepted` 的阶段账本、原 `Idempotency-Key` 与 `operationId` 必须保留,同冻结意图续跑复用原请求身份,未知账本在文件 mutation 前失败关闭。冻结意图一致但进程 invocation 已变化时允许安全接管本轮;`completed` 则以外层原始 `clientTurnId` 为权威,忽略模型重采样 brief 并等值回放。客户端必须在启动 Direct Codex 前幂等落盘原始 User 消息与稳定回合 ID;最终 assistant 回复必须在 Tauri 成功返回和 `completed` 事件前,以同一稳定回合 ID 幂等写入项目主对话,重启后项目对话只续跑真正未回答的原始回合,不能生成新身份或重复应用已完成代码修改。
- workflow 在调用严格图集事务前必须先持久化 `strictSpritesheetPending`,并冻结严格事务覆盖的九项旧合同身份:`.agent/manifest.json` 中受管 asset identity、客户端私有回执、公开 `assets/manifest.art.json`、主图集、四张 canonical 切片和公开切片清单;旧路径允许按真实状态冻结为缺失。异步 Provider 返回终态后,客户端必须先把脱敏且可恢复的完成结果绑定到原 retained stage ledger,再允许本地严格事务提交。恢复在同一项目写锁内完成底层严格事务对账与 workflow CAS;若九项新合同与当前规范图身份完整一致、规范图/背景图替换锚点属于本轮,且私有回执的 resource/asset/task identity 与本轮 retained spritesheet 完成结果一致,才保留整组新结果并补写 `completed`。若九项仍逐项精确等于冻结的旧合同,严格合同判定、写入 `compensating`、恢复规范图/背景图与登记、回读验证和清除锚点必须全部位于同一项目锁内;`compensating` 重启也必须重新验证旧合同。任一文件存在性、摘要、顶层 asset identity、retained result 或 CAS 处于第三种状态时进入本地 reconciliation,保留 workflow、阶段账本和文件现场,禁止制造新旧混合包或重新付费。恢复若只能证明完整新合同而无法重建中断前尚未持久化的阶段告警,完成结果必须追加明确恢复告警,不能用空 warning 集合伪装为原阶段没有告警。
- 工具完成结果同时返回主包 `assetPaths`、实际成功持久化的 `slicePaths`、安全身份投影 `resources`,并把普通 `warnings` 与 `sliceWarnings` 分开。每张本地切片都以真实 Canvas `resourceId / assetObjectId / taskId` 和源图集 `sourceResourceId` 登记为顶层 manifest asset;同路径替换保留本地 asset ID。严格图集事务继续覆盖主图、四张 canonical 切片、公开切片清单、私有回执和 `.agent/manifest.json`,失败时整组恢复。旧项目缺顶层切片登记时只能由客户端私有回执授权补登记;可编辑的公开切片清单不能单独成为 `.agent` Canvas 身份来源。
- workflow 在调用严格图集事务前必须先持久化 `strictSpritesheetPending`,并冻结严格事务覆盖的旧合同身份:`.agent/manifest.json` 中受管 asset identity、客户端私有回执、公开 `assets/manifest.art.json`、主图集、实际切片和公开切片清单;旧路径允许按真实状态冻结为缺失。异步 Provider 返回终态后,客户端必须先把脱敏且可恢复的完成结果绑定到原 retained stage ledger,再允许本地严格事务提交。恢复在同一项目写锁内完成底层严格事务对账与 workflow CAS;若完整新合同与当前规范图身份完整一致、规范图/背景图替换锚点属于本轮,且私有回执的 resource/asset/task identity 与本轮 retained spritesheet 完成结果一致,才保留整组新结果并补写 `completed`。若旧合同仍逐项精确等于冻结的旧合同,严格合同判定、写入 `compensating`、恢复规范图/背景图与登记、回读验证和清除锚点必须全部位于同一项目锁内;`compensating` 重启也必须重新验证旧合同。任一文件存在性、摘要、顶层 asset identity、retained result 或 CAS 处于第三种状态时进入本地 reconciliation,保留 workflow、阶段账本和文件现场,禁止制造新旧混合包或重新付费。恢复若只能证明完整新合同而无法重建中断前尚未持久化的阶段告警,完成结果必须追加明确恢复告警,不能用空 warning 集合伪装为原阶段没有告警。
- 工具完成结果同时返回主包 `assetPaths`、实际成功持久化的 `slicePaths`、安全身份投影 `resources`,并把普通 `warnings` 与 `sliceWarnings` 分开。每张本地切片都以真实 Canvas `resourceId / assetObjectId / taskId` 和源图集 `sourceResourceId` 登记为顶层 manifest asset;同路径替换保留本地 asset ID。严格图集事务继续覆盖主图、实际切片、公开切片清单、私有回执和 `.agent/manifest.json`,失败时整组恢复。旧项目缺顶层切片登记时只能由客户端私有回执授权补登记;可编辑的公开切片清单不能单独成为 `.agent` Canvas 身份来源。
- `regenerate` 的模式选择遵循 2026-09-03 MCP 能力边界:Codex 根据当前用户请求,经审核后的工具显式选择 `mode=regenerate`;客户端不再通过自然语言关键词、Unicode 归一化、否定词表或独立确认句式判断高层业务意图。工具桥继续校验项目权限,将操作绑定活动客户端回合与稳定 `clientTurnId`、冻结首次 `brief` 摘要,串行处理同一重生成动作,并在同回合等值重试时返回已完成结果;缺少活动回合或摘要冲突仍拒绝。账号、计费、幂等账本、锁、付费结果未知与恢复合同继续有效。2026-09-23 已删除无调用的旧文本判断函数,不恢复该旧语义门禁。同一进程重复水合相同 `clientTurnId` 时,“回合仍在运行”只属于瞬时占用状态,前端不得以稳定 assistant messageId 将其写成终态;原执行的成功回复仍由 Tauri 在返回前持久化。DirectProject 的 cwd 和 AGC 项目身份根使用用户选择的 canonical 项目根;其原生 OS 路径字节与权威 manifest `projectId` 通过域标签和独立长度前缀编码后绑定连接池及 thread 身份,项目被替换时不能复用旧连接。进程 sandbox 与文件、命令、权限请求的批准规则按本文件后续“DirectProject Codex 完整访问覆盖”;客户端 MCP 仍保持项目绑定和业务权限校验。`resources` 只返回本地 asset/path/kind/media type、Canvas project/resource/asset/task ID 与 reference resource IDs,不返回 prompt、model、provider route、绝对路径、URL、Token、Cookie 或 API Key。客户端付费资源生成(图片、视频、角色动画、音效、背景音乐)统一调用站内 `/api/editor/...` 路由并复用平台登录态,不走 External v1;External v1 只保留给外部开发者模式和历史账本重放兼容。
- 成功响应中的 `warnings / sliceWarnings` 与错误响应采用同一脱敏边界:逐条移除宿主绝对路径、凭据与 URL,并设置固定长度上限;非阻断告警不成为绕开错误分支隐私保护的旁路。
- Direct 同进程重复水合若收到“同一 stable turn 仍在运行”,必须释放当前 App 实例的恢复 claim;该结果不落 assistant 终态,后续显式刷新对话可按原 `clientTurnId` 再次读取已落盘回复或续跑,不要求重载整个 WebView,也不启动无界自动轮询。
- 对话恢复从新到旧扫描全部合法 Direct User 回合;较新的 User 已有稳定 assistant 时必须继续寻找更早未回答回合,不能提前结束扫描。普通成功回复或普通错误回复若终态 assistant 持久化失败,同样必须释放当前 App 实例的恢复 claim,使后续显式重新加载对话时能以原稳定 `clientTurnId` 重试;claim 只表示当前实例内正在恢复,不能成为磁盘终态的替代品。
- Direct 的运行态素材验收不再把 `assets/art-spec.png` 当作背景、角色、道具或反馈;规范图只作为派生 reference。标准核心图集无论首次创建还是显式重生成,都必须原子取得恰好四张 canonical 独立切片后才算本次生成成功;每张切片必须有真实 alpha、可见像素、唯一规范像素内容及唯一 Canvas `resourceId / assetObjectId`。旧项目只在私有回执与公开清单、当前源图和顶层登记完全一致时投影四条 `slicePaths`;部分、opaque、重复或缺回执状态只返回 warning,不得猜测或伪造衍生素材。
- Direct 的运行态素材验收不再把 `assets/art-spec.png` 当作背景、角色、道具或反馈;规范图只作为派生 reference。标准核心图集无论首次创建还是显式重生成,都按实际产物原子提交,零切片也保留有效总图和告警;每张切片必须有真实 alpha、可见像素、唯一规范像素内容及唯一 Canvas `resourceId / assetObjectId`。旧项目只在私有回执与公开清单、当前源图和顶层登记完全一致时投影实际 `slicePaths`;部分、opaque、重复或缺回执状态只返回 warning,不得猜测或伪造衍生素材。
- 机器门只证明 PNG、真实 alpha、非空可见像素、切片像素唯一、稳定平台身份、顶层登记及源码/双视口实际渲染。背景是否混入实体、地面是否无缝、素材语义是否匹配、最终绘制尺寸是否满足玩法仍由 Codex 检查工具图片和 desktop/mobile 试玩截图;prompt 约束本身不算通过证据。
## DirectProject 工具权限现行覆盖(2026-08-24)