Compare commits

..

85 Commits

Author SHA1 Message Date
k88936 5383af4cc5 Merge pull request 'refactor/整理现有的string-typed资源类型' (#388) from feat/agc-organize-assetkind into master
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m7s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m13s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m19s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 8m1s
Project CI / AI game creator shell Rust crates (push) Successful in 1m17s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m53s
Project CI / Repository checks (push) Successful in 4m49s
Project CI / Frontend tests (push) Successful in 5m47s
Project CI / Backend tests (push) Successful in 7m37s
Project CI / Native shell tests (push) Successful in 8m4s
Project CI / AI game creator shell web tests (push) Successful in 3m3s
Reviewed-on: #388
2026-09-19 03:51:32 +08:00
k88936 47f7cb2bf1 修复 AGC 前端资源 kind 判据与用例的 canonical 残留
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m1s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m6s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m14s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m52s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m31s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m50s
Project CI / Repository checks (pull_request) Successful in 4m44s
Project CI / Frontend tests (pull_request) Successful in 6m20s
Project CI / Backend tests (pull_request) Successful in 8m27s
Project CI / Native shell tests (pull_request) Successful in 8m15s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m10s
- resource-canvas/resourceCanvasAssetGenerationReferenceModel.ts:「图集不接受用户参考」判据改用 canonical icon-spritesheet,删除残留平台词的字符串清单。
- view/project-development/index.tsx:美术摘要的图片产出判据显式排除 ui-design,候选 UI 原型图不再顶掉「仅完成计划」。
- tests/appSurface/project-development.suite.ts:generateCall 支持按素材名定位,规范 / 图集 / UI 设计图入口断言改用 canonical kind。
- tests/appSurface/project-assets.suite.ts:/asset-register 与开发面板用例改用 canonical kind,面板默认值按 unknown 断言。
- tests/resourceCanvasAssetGenerationReferences.test.tsx:夹具与提交断言改用 icon-spritesheet / ui-design。
- docs/technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md:载荷表与规范通道说明改用 canonical kind,并写明平台请求词汇只留在 source.generationKind。
- docs/project-memory/shared-memory/pitfalls.md:记录前端 kind 判据与 fixture 必须一起按 canonical 成员重写。
2026-09-19 03:21:37 +08:00
k88936 603650fbf4 修复 AGC 资源 kind 收敛后的 Rust 默认值与工具栏用例
- agent/runtime_tools/media.rs:asset_kind 只在入参未声明(空白)时落通道默认值,显式认不出的值保持 Unknown 交给下游失败关闭。
- tests/project.rs:规范图 / 图集用例的入参改传 canonical kind(icon-spec / icon-spritesheet),用例名与断言同步。
2026-09-19 03:21:25 +08:00
k88936 5d20f5bfeb 修复 AGC 资源 kind 枚举化后的编译与字符串回退
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 7m9s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 7m20s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 7m24s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 8m10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m39s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m54s
Project CI / Repository checks (pull_request) Failing after 4m43s
Project CI / Frontend tests (pull_request) Failing after 4m51s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
- canvas_generation:参考素材合同四个 helper 改收 GameCreationAppAssetKind,删除 ui-prototype/game-background/art-spritesheet 字符串比较

- direct_runtime:删除 game-background / art-spritesheet 的参考合同字符串映射,直接传枚举

- resource_editor:音效/BGM 编辑类型判据改为比较 ResourceEditSourceKind::Manifest(BackgroundMusic),不再拼字符串 haystack

- assets/commands:画板导入 kind 解析上移到 Tauri 命令边界,import_canvas_asset_at 只接收枚举

- 测试与批量标签脚手架:kind 参数由字符串改为 GameCreationAppAssetKind,恢复编译期穷举保护

- 技术方案:登记边界同口径改为描述命令边界解析
2026-09-19 02:38:30 +08:00
k88936 af38de9fd1 Merge remote-tracking branch 'origin/master' into feat/agc-organize-assetkind
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 3m23s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 3m1s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m41s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 2m20s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m38s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m25s
Project CI / AI game creator shell web tests (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
2026-09-19 01:54:10 +08:00
k88936 18252e24b8 合并主线并保留资源kind枚举收口
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (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
合并 origin/master 的 AGC 画布参考与项目能力更新

解决 canvas 生成恢复与任务模型冲突并继续使用共享 GameCreationAppAssetKind

保留 run_id、generationKind 与外部协议字符串的独立边界
2026-09-19 01:50:09 +08:00
lhk229 1ce2c10107 删除客户端每回合四项媒体资源请求上限 (#427)
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m10s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m28s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m32s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m48s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m1s
Project CI / AI game creator shell Rust crates (push) Successful in 1m58s
Project CI / Repository checks (push) Successful in 5m46s
Project CI / Frontend tests (push) Successful in 7m8s
Project CI / Backend tests (push) Successful in 9m19s
Project CI / Native shell tests (push) Successful in 9m42s
Project CI / AI game creator shell web tests (push) Successful in 5m5s
- direct_tool_bridge.rs 删除 DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN 常量、回合授权状态的 resource_request_ids 计数 map 与 resource_request_ids() 方法
- agc_create_or_derive_resource(含 agc_edit_image 委托)与 agc_remove_background 统一按回合身份与请求指纹确定性派生 operation/idempotency id,同指纹重试复用与 pending 对账语义不变
- 付费提交串行仍由 resource_generation_gate 互斥保证,成本控制交由服务端计费与泥点余额兜底
- 决策记录新增 2026-09-19 删除上限条目,2026-09-18 抠图豁免条目标记为仅作追溯
- 实施计划文档同步改为「不同请求串行提交,不设每回合计数上限」

Reviewed-on: #427
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-19 01:44:48 +08:00
k88936 48caa23997 收紧AGC资源kind枚举边界
canvas生成与运行时统一使用共享GameCreationAppAssetKind

删除平台kind兼容映射与退役kind拒绝测试

更新资源kind审计里程碑与绑定文档
2026-09-19 01:28:27 +08:00
lhk229 ea141d9f17 修复持久目标模式切换测试的异步竞态
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (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
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m11s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 5m4s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m50s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m59s
Project CI / AI game creator shell Rust crates (push) Successful in 2m28s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m37s
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
- developer-agent-window.suite.ts 在切换目标模式前等待按钮脱离 disabled,避免读取历史链路在途时点击落空导致 aria-pressed 断言偶发失败
2026-09-19 01:21:05 +08:00
lhk229 769740a46d Merge pull request '取消抠图工具的每回合四项媒体请求限制' (#426) from codex/remove-bg-turn-limit into master
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 6m56s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m15s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m24s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m21s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m25s
Project CI / AI game creator shell Rust crates (push) Successful in 2m55s
Project CI / Repository checks (push) Successful in 15m5s
Project CI / Backend tests (push) Successful in 18m40s
Project CI / Native shell tests (push) Successful in 20m39s
Project CI / Frontend tests (push) Failing after 19m35s
Project CI / AI game creator shell web tests (push) Successful in 13m6s
Reviewed-on: #426
2026-09-19 00:29:51 +08:00
lhk229 b472b9eec9 取消抠图工具的每回合四项媒体请求限制
Project CI / AI game creator shell Rust smoke (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
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
agc_remove_background 不再经过 resource_request_ids 付费计数,直接按回合身份与请求指纹确定性派生 id

抠图请求不再挤占付费 create/derive 的四项额度,幂等复用与 pending 对账语义不变

create_or_derive(含 agc_edit_image 委托)路径逐字节不变

决策记录补充 2026-09-18 抠图豁免条目

实施计划文档同步注明抠图不占四项额度
2026-09-19 00:14:01 +08:00
k88936 0d5940433d 删除资源 kind 兼容白名单
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m4s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (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
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
服务端图片快速编辑改用共享枚举静态图片判定

移除退役 kind 的兼容放行与冗余回归测试

清理客户端恒假 art-spritesheet 判断并统一远端 kind 解析

同步 AGC kind 里程碑、实施计划与决策记录
2026-09-18 17:09:36 +08:00
k88936 e5fd454bb3 收紧 AGC 资源 kind 类型边界并统一族判据
- 将 manifest 与本地导入回执的资源 kind 改为 ts-rs 生成绑定

- 集中音频与视觉资源族判据并复用 Rust 枚举方法

- 放宽 Unknown 未解析语义并清理上传类型墓碑注释

- 标记共享生成绑定并同步项目决策与实施计划
2026-09-18 15:21:19 +08:00
k88936 03d7bca9a5 Merge remote-tracking branch 'origin/master' into feat/agc-organize-assetkind
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m43s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m28s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m18s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m5s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m47s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m42s
Project CI / Repository checks (pull_request) Successful in 6m2s
Project CI / Frontend tests (pull_request) Successful in 8m48s
Project CI / Native shell tests (pull_request) Successful in 9m37s
Project CI / Backend tests (pull_request) Successful in 10m34s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m29s
2026-09-18 10:43:00 +08:00
k88936 2abad3b537 修复失败测试
测试现在直接从 AGENT_RUNTIME_CANVAS_ASSET_KINDS 派生期望值,避免再次漂移。
2026-09-18 10:42:40 +08:00
k88936 fc5cea43ac Merge remote-tracking branch 'origin/master' into feat/agc-organize-assetkind
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 5m34s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m33s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 5m47s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m56s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m20s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m27s
Project CI / Repository checks (pull_request) Successful in 5m58s
Project CI / Frontend tests (pull_request) Successful in 6m49s
Project CI / Native shell tests (pull_request) Successful in 10m59s
Project CI / Backend tests (pull_request) Successful in 12m9s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m5s
2026-09-18 01:55:16 +08:00
k88936 c2ab9674a0 合并资源编辑与当前主线改动
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (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
保留资源 kind 强类型与栅格 subtype 严格校验

合并抠图模式、幂等账本、恢复轮询和 PNG 透明通道校验

修正共享资源枚举在 Cocos 登记与抠图桥接中的类型传递

保留双方模板库、项目快照、发布和 Provider 重试改动
2026-09-18 01:48:13 +08:00
k88936 72cd0747a4 拒绝栅格资源 subtype 静默降级
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (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
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
非图片族和未知 sourceSubtype 改为明确报错
补充栅格正规化回归测试并同步规范
2026-09-17 23:06:30 +08:00
k88936 3d4c7929c3 统一画布生成 kind 为共享枚举
工具白名单与 schema 改用 GameCreationAppAssetKind
canonical 输入在生成适配边界映射旧账本值
2026-09-17 22:52:38 +08:00
k88936 8dce016552 修正 manifest kind 留痕去重范围
按 manifest 来源与资产身份去重
避免不同项目相同 ID 互相抑制日志
2026-09-17 19:18:07 +08:00
k88936 64879cfcdd 合并资源规范与画布 JSON 功能
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m2s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m4s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m20s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m21s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m52s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m5s
Project CI / Frontend tests (pull_request) Failing after 5m26s
Project CI / AI game creator shell web tests (pull_request) Failing after 4m11s
Project CI / Native shell tests (pull_request) Successful in 10m40s
合并 canonical 资源 kind 校验与按类型提示词上限。\n保留 UI JSON 预览识别、严格 UI 文档持久化和画布编辑入口。\n修复合并后 Rust 资源 kind 测试夹具并补齐定向回归用例。
2026-09-17 18:28:19 +08:00
k88936 6853ec5388 资源 kind 严格口径的文档补齐:单一解析入口与登记边界
- docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md:2026-09-17 节改为唯一解析入口 `parse_with_context()`(内部私有 `match_canonical()`,`from_str_lossy` / 公开 `from_str_or_unknown` 均已删除),并新增登记边界同口径、登记内容 kind 必须与内容相符、切片残留判据只看路径、sprite 身份不含展示串、TS 判据只留一份与「严格口径已知代价、不迁移」六条。
- docs/project-memory/shared-memory/decision-log.md:同一节补上单一解析入口、登记边界 kind 口径、判据不许散落、已知代价不补救四条决策,并把验证命令更新为实际跑过的定向用例。
2026-09-17 16:30:25 +08:00
k88936 796ad7bb51 资源 kind 只保留一个公开解析入口
- server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs:删掉公开的 `from_str_or_unknown()`(`from_str_lossy` 的残留命名),严格匹配降级为私有 `match_canonical()`;`parse_with_context()` 成为唯一公开解析入口,`FromStr`、serde 与所有边界一律走它,认不出 canonical 值只有这一处收口并留痕。
- server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs:词汇表用例改走唯一入口,并断言每个非 canonical 变体都留痕。
- apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs:读侧回捞 legacy kind 时不再调用第二个解析函数,直接用唯一词汇表 `ALL` 判真值(`unknown` 本身是成员,自然跳过)。
2026-09-17 16:27:48 +08:00
k88936 20b34918d3 UI 编辑器桥接用例改回真实成功路径
- apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts:`ensure_ui_design_resource_for_prototype` 的 mock 从返回 `null` 改为返回真实形状的桥接结果(asset + manifest + committedProjectRevision + created),并新增 `data-resource-view-state === 'resources.ui-editor'` 断言,让用例真的覆盖它名字里的成功路径而不是被组件吞进错误分支。
2026-09-17 16:19:38 +08:00
k88936 04e5512777 UI 编辑器文档资产的 kind + mediaType 判据收敛到共享契约唯一实现
- packages/shared/src/contracts/gameCreationApp.ts:新增 `isGameCreationAppUiDesignDocAsset(asset)`,`kind` 严格解析后再比文档 kind、`mediaType` 比 `application/json`,这是该判据的唯一定义。
- packages/shared/src/contracts/gameCreationApp.test.ts:补用例钉住判据——只满足一半(kind 对 / mediaType 不符、mediaType 对 / kind 不是文档)与 legacy kind(`UI`、`ui`、带空格、大写)一律返回 false。
- apps/ai-game-creator-shell/src/view/project-development/index.tsx:桥接结果校验与「自动打开 ui-workflow 完成资源」两处改用共享判据,删掉本地 kind/mediaType 双条件。
- apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts:`findLinkedUiDesignResource` 的过滤条件改用共享判据。
- apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx:缩略图文档分支改用共享判据(原来只看 mediaType,kind 不同也能命中)。
2026-09-17 16:19:26 +08:00
k88936 0510cd8d9c AGC 壳把资源 kind 的登记与解析边界统一到严格口径
- assets.rs:新增 `registration_asset_kind()`,空白入参落中性 `image`、非空认不出严格收口成 `Unknown` 并留痕;`import_canvas_asset_at` 改走该函数。
- commands.rs:`imported_platform_asset_kind` 与 `register_local_asset` 一并委托给 `registration_asset_kind()`,删掉两处各写一遍的 trim/严格解析分支。
- agent/generation/canvas_generation.rs:切片残留登记判据改为只看 `assets/art-spritesheet-slices/` 路径——历史切片刻的 kind 是非 canonical 值,严格解析后收口成 `unknown`,再带 kind 判据会漏掉半成品登记并静默跳过回填;补平台词汇映射用例钉住别名与未映射值落点。
- project/resource_editor.rs:栅格归一化的源 subtype 收口到图片族成员(音频/视频/文档/字体等一律按图片登记),Agent 回执派生物写回 `Document` 而不再主动写 `Unknown`;补派生 kind 用例。
- ui_editor/workflow.rs:新增 `same_sprite_asset_identity()`,比较 sprite 身份时忽略随 kind 派生的 `metadata.asset_type`,避免对既有 State 重跑工作流误报资源冲突。
2026-09-17 16:19:00 +08:00
k88936 7a237431e0 资源 kind 单源与严格口径的文档同步
- docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md:新增 2026-09-17 权威节,写明唯一词汇表、严格解析、app_log! 留痕与生成目录搬家。
- docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md:删掉「alias 表大小写不敏感」的旧口径,改为严格等值解析 + 原始串留痕,并把跨端守卫改为编译期绑定。
- docs/project-memory/shared-memory/decision-log.md:新增 2026-09-17 条目记录四项决策与验证命令。
- docs/project-memory/shared-memory/pitfalls.md:新增 ts-rs 生成目录搬家后必须同步忽略规则的坑;旧别名表相关条目补上现行口径,避免后人照旧重加别名表。
- docs/project-memory/plans/ / todos/:把 kind-observability、壳内生成目录、已删除的交叉守卫用例等过期描述改成当前口径。
2026-09-17 15:46:58 +08:00
k88936 6ed3772ff5 资源 kind 用例与 fixture 跟随严格口径(非 canonical 值一律停在待归类)
- packages/shared/src/contracts/gameCreationApp.test.ts:新增严格解析用例(认不出的原值 console.warn 留痕、返回 unknown),分类映射与自愈用例改用 canonical ui-design / icon,并断言 kind:'ui' 收口成 unknown 后落 unclassified。
- tests/resourceTypePanel.test.tsx / tests/resourceClassificationPanel.test.tsx:自愈用例的 fixture kind 由 ui 改为 ui-design,让「显示口径 ≠ 落盘口径」这条判据继续真的被验证。
- tests/projectResourceLiveIntegration.test.tsx:fixture kind 改为 ui-design;用例按 canonical UI 设计图带「(待视觉验收)」的标签定位按钮,并把用例名与注释改回真实口径。
- tests/resourceCardPreviewRealManifest.test.ts:真机 61 条资产的取证结论改为严格口径——57 条 ui 与 game-entry 停在待归类,明确栏目只由落盘 category 决定;自愈断言改用 ui-design。
- tests/appSurface/home.suite.ts / project-development.suite.ts:fixture 里的 art-spritesheet / game-background / animation / ui-prototype / ui 改为 canonical icon-spritesheet / scene / character-animation / ui-design / icon。
- tests/projectResourceProjectionModel.test.ts / projectResourceLiveUpdateModel.test.ts:fixture kind 由 game-entry / asset 改为 code / image。
2026-09-17 15:46:43 +08:00
k88936 a86ead93e4 资源登记草稿的推断 kind 收敛到 canonical 词汇
- projectArtifactSummaries.ts:inferProjectFileAssetDraft 的 json 分支由 data 改为 document、判不出扩展名时由 asset 改为 unknown,与 Rust 侧 uploaded_asset_kind 同口径——/asset-register 的 kind 会原样进 register_local_asset 的严格解析,写非 canonical 值只会被判成认不出并留痕。
2026-09-17 15:46:34 +08:00
k88936 b0ce384b8d 前端资源 kind 改为只消费 Rust 生成的 union,删除壳内第二份实现
- packages/shared/src/contracts/gameCreationApp.ts:删除手写 GAME_CREATION_APP_CANONICAL_ASSET_KINDS、GameCreationAppCanonicalAssetKind、GAME_CREATION_APP_LEGACY_ASSET_KINDS 与 canonicalGameCreationAppAssetKind(),改为 re-export ts-rs 生成的 union。
- packages/shared/src/contracts/gameCreationApp.ts:新增穷举 GAME_CREATION_APP_ASSET_KIND_MEMBERS / GAME_CREATION_APP_ASSET_KINDS、isGameCreationAppAssetKind 与严格解析 parseGameCreationAppAssetKind(认不出的原值 console.warn 留痕并返回 unknown)。
- packages/shared/src/contracts/gameCreationApp.ts:分类映射表与 gameCreationAppAssetCategoryForKind 改按生成 union 穷举,unknown 落 unclassified。
- packages/shared/src/contracts/generated/GameCreationAppAssetKind.ts:ts-rs 生成物从壳内私有目录搬到 packages/shared(跨端契约目录)。
- apps/ai-game-creator-shell/src/contracts/assetKind.ts:删除这份壳内重复实现,消费方改从 packages/shared 导入。
- apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts / features/ui-editor/uiDesignResourceBridge.ts / view/project-development/index.tsx:导入点改到 packages/shared 的同一份 parser 与常量。
- apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts:删除「正则解析 Rust 源码比对两份手写表」的守卫用例,跨语言一致性改由生成 union 在编译期保证。
- .prettierignore / .eslintrc.cjs:忽略规则跟着生成目录一起搬到 packages/shared/src/contracts/generated。
2026-09-17 15:45:24 +08:00
k88936 b56566f40e AGC 壳把资源 kind 留痕接到 app_log! 并把各解析边界统一为严格口径
- main.rs:新增 register_non_canonical_asset_kind_reporter(),把 shared-contracts 的原始串与上下文落到 app_log!,按 (原始串, 上下文) 去重;main() 启动时注册。
- commands.rs:imported_platform_asset_kind 改为直接 parse_with_context,不再先经别名表收口;用例改为断言 UI / ui / mystery / game-art / ui-prototype 等非 canonical 值一律落 unknown。
- assets.rs:画板/上传等用例的 kind 断言改用 GameCreationAppAssetKind::ALL 穷举口径。
- project/manifest.rs:读 manifest 的留痕去重键由「只有原始 kind」改成「原始 kind + 资产身份」,避免第二个写旧值的资产被静默吞掉。
- project/manifest.rs:from_str_lossy 调用点改名 from_str_or_unknown。
- agent/generation/canvas_generation.rs:画板 assetKind 的兜底分支改为 parse_with_context,非 canonical 值留痕收口为 unknown。
- project/version_resource_replacement.rs:subtypeEqual 改为 canonical kind 直接比较,去掉中间归一。
2026-09-17 15:44:02 +08:00
k88936 6782d0ea23 GameCreationApp 资源 kind 词汇表收敛为 Rust 单源并接上可注册留痕回调
- asset_kind.rs:新增声明宏,由一张 kind 表派生出枚举、线上值 as_str、ALL、严格解析与 ts-rs 绑定,仓库里不再有第二份 kind 列表。
- asset_kind.rs:from_str_lossy 改名 from_str_or_unknown 并去掉 trim / lowercase 归一,parse_with_context 只做严格等值匹配,认不出的原值不查别名、不迁移。
- asset_kind.rs:留痕改为可注册回调 set_non_canonical_asset_kind_reporter,把原始串与调用上下文交给壳层日志,未注册时静默。
- game_creation_app.rs:删除手写 GAME_CREATION_APP_CANONICAL_ASSET_KINDS 与 canonical_game_creation_app_asset_kind 别名表(含 font → document 特例),分类映射表按枚举成员穷举。
- shared-contracts/Cargo.toml:删除 kind-observability feature 与 tracing 依赖,留痕不再靠 tracing。
- api-server/editor_project.rs:图片编辑白名单注释改成「API 兼容面」,不再自称 kind 别名表。
2026-09-17 15:43:51 +08:00
k88936 e6092dda6f Merge remote-tracking branch 'origin/master' into feat/agc-organize-assetkind
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m6s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m19s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m23s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m9s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m55s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m36s
Project CI / Repository checks (pull_request) Successful in 5m36s
Project CI / Frontend tests (pull_request) Failing after 7m10s
Project CI / Backend tests (pull_request) Successful in 13m10s
Project CI / Native shell tests (pull_request) Successful in 11m34s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m38s
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
2026-09-17 13:52:45 +08:00
k88936 5f13698129 asset.list 的 kind 过滤改为拒绝非 canonical 值
- direct_tool_bridge.rs:`kind` 收口后既不是 canonical、也不是字面 `unknown` 时直接返回工具错误并带上原值,不再静默过滤出空列表。
- direct_tool_bridge.rs:新增用例,钉住"项目访问前就报错"与"字面 `unknown` 仍是合法过滤值"。
- direct_tools_mcp.rs:schema 描述改为必须是 canonical kind、非 canonical 值会被拒绝。
2026-09-17 13:42:22 +08:00
k88936 29ba9a358e 字体 kind 提升为 canonical 并去掉 font 别名
- game_creation_app.rs:`GAME_CREATION_APP_CANONICAL_ASSET_KINDS` 加入 `font`(17 → 18 项),分类表补 `font → document`,删除 `"font" => "document"` 别名。
- gameCreationApp.ts:TS 侧同步加入 `font`、删除别名表条目、补 `font: 'document'` 分类,并同步别名表注释。
- 测试:两侧用例改为断言 `font` 是 canonical 成员(大小写仍不敏感)、栏目仍是 document。
- 理由:字体上传写入侧直接落 `font`,再把它当别名会让「落盘值集合」与「canonical 集合」分叉;严格解析下写入侧的值必须本身就是 canonical。
2026-09-17 13:42:05 +08:00
k88936 3ab56e6cfa 非 canonical 资源 kind 严格收口并留痕原始串
- manifest.rs:读 manifest 时若存在 `unknown` kind,从原始 JSON 回捞 `assets[].kind` 原值,连同资产 ID、路径写进应用日志;同一原值每进程只记一次,避免刷屏。
- recovery_tests.rs:新增用例钉住回捞范围(只报非 canonical 值,跳过 canonical 与字面 `unknown`;结构异常返回空表)。
- commands.rs:`imported_platform_asset_kind` 收口平台 `assetKind` 时,非 canonical 原值一并留痕,供后续定位平台侧 legacy 写入点。
- shared-contracts/Cargo.toml:`ts-bindings` 不再顺带打开 `kind-observability`,改由 AGC 壳显式声明,日志开关不再随绑定需求被动启停。
- 口径:kind 保持严格解析(无 trim/lowercase、无别名表、无迁移),认不出的值一律收口 `unknown` 并留痕,不做 fallback。
2026-09-17 13:41:41 +08:00
k88936 56c4faa8ee 清理项目视觉资产判断里已不可达的旧 kind 字面量
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m58s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m44s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m26s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m3s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m0s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m50s
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 6m3s
Project CI / AI game creator shell web tests (pull_request) Successful in 6m3s
Project CI / Native shell tests (pull_request) Successful in 11m11s
- projectAssetSummaries.ts:`isProjectVisualAsset` 去掉 `sprite` / `ui` / `background` 三个旧 kind 判断。
- 理由:manifest kind 由 Rust 枚举序列化,只会是 canonical 成员或 `unknown`,这三个旧字面量在 TS 侧永远无法命中;图像与视频资产仍由 mediaType 分支覆盖。
2026-09-16 23:31:01 +08:00
k88936 d23c808376 新建 UI 设计资源复用无冲突编号规则
- ui_editor/resource_bridge.rs:`next_ui_design_path` 提升为 `pub(crate)` 并补注释,作为两个写入侧唯一的「UI 设计 N」命名规则。
- commands.rs:`create_ui_design_resource` 不再按已登记文档数直接取名,改为复用该规则,resourceId 与文件名同一编号。
- tests/project.rs:新增已存在 `ui/UI 设计 1.json` 时仍能创建并落到编号 2 的回归用例。
2026-09-16 23:24:15 +08:00
k88936 41930b858e 修正陶泥儿运行态图片筛选残留的旧 kind 字面量
- direct_runtime/mod.rs:`direct_game_sources_referenced_taonier_assets` 的 manifest 兜底由 `art-spritesheet | art-spritesheet-slice | game-background` 改为 `IconSpritesheet | Icon | Scene`,恢复对项目自定义图集与背景的收录。
- direct_runtime/mod.rs:同处与 `direct_registered_taonier_runtime_image_paths` 的「切片不得绕过严格校验」排除条件改用 `GameCreationAppAssetKind::Icon`,并顺手把 `icon-spec` 比较改为枚举成员。
- 理由:kind 收紧为枚举后 `as_str()` 只会返回 canonical 名,旧字面量恒不命中,兜底筛选与切片排除同时失效。
2026-09-16 23:13:21 +08:00
k88936 9dccf5318e UI 原型 kind 判断改用共享常量
- uiDesignResourceBridge.ts:原型 kind 比较由裸字符串 `'ui-design'` 改为 `GAME_CREATION_APP_UI_DESIGN_ASSET_KIND`,与 manifest kind 契约保持同一处定义。
2026-09-16 23:10:59 +08:00
k88936 f216381619 修正 UI 编辑器入口对 canonical ui-design 原型的判断
- packages/shared:新增 `GAME_CREATION_APP_UI_DESIGN_ASSET_KIND`(`ui-design`)并让 canonical kind 列表与分类表引用它,避免散落裸字符串。
- project-development/index.tsx:`openResourceUiEditor` 与 `selectedResourceOpensUiEditor` 的原型判断由旧值 `ui-prototype` 改为该常量,恢复原型图的「UI 编辑器」入口。
- resourceProjectionModel.ts:`isPendingUiPrototype` 同步改用该常量,恢复待视觉验收标注。
- project-development.suite.ts:候选界面图 fixture 改用 canonical kind,并补一条原型图打开 UI 编辑器桥接的回归用例(去掉修正即转红)。
2026-09-16 23:10:31 +08:00
k88936 d454d3fa4b 修正 asset.list 工具示例 kind 到 canonical 词汇
- direct_tools_mcp.rs:`agc_list_registered_assets` 的 kind 过滤示例把旧值 `art-spritesheet-slice` 换成 canonical 的 `icon-spritesheet`。
- 理由:manifest kind 收紧为枚举后,旧值在过滤前已收口为 `unknown`,按示例传参只会得到空结果。
2026-09-16 23:03:44 +08:00
k88936 a5ee45aa4e 构建脚本依赖不再打开 shared-contracts 的 ts-bindings
- Cargo.toml:`[build-dependencies]` 的 shared-contracts 去掉 `features = ["ts-bindings"]`,只保留 `[dependencies]` 那份。
- build.rs 只用 `GameCreationAppAgentGroup` 与 `new_game_creation_app_seed_tasks`,不需要 ts-rs 导出,没必要让构建脚本图额外编译 ts-rs。
2026-09-16 23:01:51 +08:00
k88936 d333f783fe 修正资源 kind 解析边界对非字符串输入的日志
- assetKind.ts:非字符串输入不再走 `String(raw)` 字符串化,改为记录 `rawType` 并直接收口为 `unknown`。
- assetKind.test.ts:新增非字符串输入用例,钉住新的日志形状。
2026-09-16 23:00:42 +08:00
k88936 faab318127 删除资源引用缩略图里已不可达的 ui kind 分支
- ResourceReferenceInput 缩略图:删掉 `kind === 'ui'` 判断与只服务它的 `kind` 变量,UI 设计文档仍由 `mediaType === 'application/json'` 识别。
- 理由:manifest kind 由 Rust 枚举序列化,只会是 canonical 成员或 `unknown`,该分支永远无法命中。
2026-09-16 23:00:07 +08:00
k88936 a4296a4bdd 合并 origin/master 并收敛 DirectProject 引用渲染到 canonical user item
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m17s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m15s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m45s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 7m59s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m8s
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m48s
Project CI / AI game creator shell web tests (pull_request) Successful in 6m24s
Project CI / Frontend tests (pull_request) Successful in 15m29s
Project CI / Native shell tests (pull_request) Successful in 19m38s
- .eslintrc.cjs 冲突取双方 ignore 并集,保留 contracts/generated/** 与 project-workspace/generated/**。
- 实施计划文档冲突保留双方内容:2026-09-15 资源 kind 枚举化权威口径与 master 新增计划段并存。
- resourceReferences.ts 冲突合并双方 import:kind 解析入口与 canonical DirectCodexUserItem 类型同时保留。
- project-development.suite.ts 冲突合并双方 import:UI 设计文档 kind/mediaType 常量与 ProjectSupervisorView 同时保留。
- decision-log.md 冲突取双方条目,并补记 DirectProject 引用渲染收敛到 canonical user item 的决策。
- 接受 master 退役 agent/direct_codex_references.rs,把 UI 设计文档引用生成代码上下文的 prompt 注入迁入 agent/direct_codex_user_item/wire.rs。
- assets.rs 资源登记 kind 参数收紧为 GameCreationAppAssetKind,相应测试 fixture 改用正式枚举成员。
- 同步 project-memory 实施计划、里程碑与扫描清单中的模块路径。
2026-09-16 21:01:20 +08:00
k88936 7eee89ded3 同步 AGC 资源 kind 枚举化的实施计划、里程碑与扫描清单
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m22s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m16s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m4s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m39s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m40s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m1s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m3s
Project CI / Frontend tests (pull_request) Successful in 4m35s
Project CI / Native shell tests (pull_request) Successful in 6m44s
- 实施计划:记录已落地内容(kind 由 `String` 收紧为枚举的破坏性变更、写入与读取统一正式成员、资源编辑源身份词汇表、`cargo test export_bindings` 生成路径、TS 穷举派生)
- 实施计划:修正「明确不修改」与验证命令,去掉与当前代码不符的表述
- 里程碑:验收标准除旧 alias 读边界一项外全部勾选,并补记 ts-rs 生成与退役类型导出两项证据
- 扫描清单:把「当前未决项」逐条落成结论(`asset`、`art-spritesheet*`、`game-*`、`ui*`、`Unknown` trace、生成路径),只保留 CI verify 接入一项待办
2026-09-16 19:08:28 +08:00
k88936 f9bc2d917c AGC 资源 kind 集合改为从生成 union 穷举派生
- assetKind.ts:新增 `GAME_CREATION_APP_ASSET_KIND_MEMBERS: Record<GameCreationAppAssetKind, true>`,生成 union 增删成员时会直接编译报错
- assetKind.ts:`GAME_CREATION_APP_ASSET_KINDS` 由该表派生,不再手写第二份字面量列表
- assetKind.ts:新增 `isGameCreationAppAssetKind` 类型守卫,`parseGameCreationAppAssetKind` 复用它
- view/project-development/index.tsx:资源引用的 `sourceSubtype` 只传正式 kind 成员,任务产物等非 manifest 来源传 `null`
- tests/assetKindCanonicalMapping.test.ts:两侧 kind 目录比对前先展开 Rust 常量引用(`ui-design-doc` 是常量),修正此前恒失败的两条;删除测试体里游离的 `'font',` 表达式
2026-09-16 19:07:58 +08:00
k88936 b96cd4b7c2 ts-rs 绑定统一走 cargo test export_bindings,删除退役类型导出
- asset_kind.rs:`GameCreationAppAssetKind` 自带 `#[ts(export, export_to = ../apps/ai-game-creator-shell/src/contracts/generated/)]`,`cargo test export_bindings` 即可复现生成文件
- binding.rs:删除手写的 `exports_ui_editor_types` 测试,不再由测试就地重写生成文件或重建退役类型
- binding.rs:`BindingChange` / `BindingDTO` 去掉 `TS` derive 与 `ts(export_to)`,只作为内部结构使用,不再导出退役的前端绑定
- binding.rs:清理仅为该测试存在的 `ts_rs` / `GameCreationAppAssetKind` 导入
- .prettierignore、.eslintrc.cjs:忽略 `src/contracts/generated/`,保留 ts-rs 原始格式使生成幂等且不污染工作树
- GameCreationAppAssetKind.ts:按生成器原始格式更新(与上述路径一致,无内容漂移)
2026-09-16 19:07:33 +08:00
k88936 f185d21f55 资源编辑源身份改用独立词汇表,只传正式 kind 成员
- resource_editor.rs:新增 `ResourceEditSourceKind`,把源身份拆成 `Manifest(GameCreationAppAssetKind)` / `ProjectVersion` / `AgentResultText`
- resource_editor.rs:`ResourceEditSourceSnapshot.asset_kind` 由 `String` 收紧为该枚举,项目版本与 Agent 回执不再冒充 manifest kind
- resource_editor.rs:`infer_resource_edit_source_asset_kind` 直接返回枚举成员,`art-image` / `svg` / `text` 收口为 `Image` / `Image` / `Document`
- resource_editor.rs:栅格源归一化只接受正式枚举成员,`task-artifact` 特判删除,未知值按 `Image` 收口
- resource_editor.rs:账本、HTTP 载荷与提示词边界改用 `as_str()`,manifest 写回改用 `manifest_kind()`
- resource_editor.rs:`source_subtype` 字段补充「只接受 manifest 正式 kind」的文档说明,测试 fixture 同步改用枚举成员
2026-09-16 19:07:23 +08:00
k88936 a61b0cce81 收敛 AGC 资源 kind 写入与读取到正式枚举成员
- assets.rs:`register_local_asset_at` / `register_local_asset_entry` 改为接收 `GameCreationAppAssetKind`,画板导入与画板同步在边界解析 kind
- assets.rs:`infer_canvas_export_asset_kind` 直接返回枚举成员,去掉仅供兜底的 canonical 归一
- commands.rs:资源登记按 `asset.register` 上下文解析 kind,字体导入写 `Font`,本地文件类型表与平台导入归一改用枚举
- direct_runtime.rs:直连游戏工程产物统一写 `Code`,图集切片读取按 `Icon` 叠加切片路径判定,陶泥儿美术身份改收枚举
- canvas_generation.rs:新增 `platform_art_asset_manifest_kind` 收口平台美术请求词汇,切片登记 category 改用 `Icon.as_str()`
- manifest.rs:必填视觉资产校验改用 `IconSpec` / `UiDesign` / `IconSpritesheet`,修正此前与写入侧对不上的比对
- project_gates.rs、autonomous_policy.rs、recovery_scan.rs、direct_tool_bridge.rs、ui_editor workflow/persistence/resource_bridge:读取侧与 fixture 改用正式枚举成员
- tests:资源相关 fixture 与断言改用正式枚举成员
2026-09-16 19:07:12 +08:00
k88936 2a75a2d2c8 回退资源 kind 绑定断言的误改
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (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
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
ts-rs 导出内容是双引号,测试重新生成后再校验,恢复双引号断言。
2026-09-16 17:05:03 +08:00
k88936 512f669f57 修正资源引用入口的源码断言
正则改为匹配 parseGameCreationAppAssetKind(selectedResource.subtype) 的新调用形状。
2026-09-16 16:59:51 +08:00
k88936 21dbbb4c3c 清理资源桥接测试的旧 kind fixture
将有效分类投影用例的资源 kind 改为 UiDesign 枚举与 ui-design 断言。

移除遗留 ui 字符串 fixture,使 AGC 壳测试恢复编译。
2026-09-16 16:53:27 +08:00
k88936 55d0c99e2b 修正资源 kind 转义测试期望
转义后的 ui-design 应解析为 UiDesign 枚举。
2026-09-15 20:57:14 +08:00
k88936 3907a8bf74 补齐资源 kind 字符串字段转换
资源编辑请求中的 sourceSubtype 统一从 AssetKind 显式转换字符串。
2026-09-15 20:52:53 +08:00
k88936 46dfa2cffb 修复资源枚举迁移后的测试类型错误
将字体、角色和资源图 fixture 改为 AssetKind 枚举。

将资源 kind 回传到字符串字段时显式使用 to_string。
2026-09-15 20:45:55 +08:00
k88936 97e2b26900 简化资源引用 kind 来源
引用资源时直接解析已存在的 subtype,移除无效 category 回退。
2026-09-15 20:14:13 +08:00
k88936 39e33f7a5b 更新上传资源 kind 枚举测试
按 Font 与 Unknown 返回值修正上传类型断言。

使用枚举字符串视图校验 canonical kind 集合。
2026-09-15 20:01:20 +08:00
k88936 040f646480 统一 ts-rs workspace 依赖版本
在 server-rs workspace 集中声明 ts-rs 版本。

shared-contracts 通过 workspace 依赖引用可选 ts-rs。
2026-09-15 19:53:06 +08:00
k88936 8b007c637d 修正图集切片读取侧枚举判断
恢复校验和测试筛选统一识别 Icon 资源 kind。

部分登记探测限定在图集切片路径,避免误判普通图标。
2026-09-15 19:48:48 +08:00
k88936 f0a62c6306 迁移资源桥接测试 fixture 到枚举
将图集切片、角色动画和规格资源 fixture 改用对应 AssetKind 变体。
2026-09-15 19:45:51 +08:00
k88936 30edfee00e 修复 Codex 资源引用测试 fixture
将 manifest 资源 kind 字符串 fixture 迁移为 Character 枚举。
2026-09-15 19:41:41 +08:00
k88936 c2da8e184e 修正 UI 工作流源资源枚举校验
工作流源图校验与 fixture 统一使用 UiDesign。

更新失败提示中的 manifest kind 名称。
2026-09-15 19:36:29 +08:00
k88936 a19319d815 修正 UI 原型资源桥接枚举使用
生产校验与测试 fixture 改用 GameCreationAppAssetKind::UiDesign。

保留 ui-prototype 作为本地文件路径标识。
2026-09-15 19:35:04 +08:00
k88936 d07ea186fa 收紧资源 kind 测试专用导入
仅在测试构建中导入 GameCreationAppAssetKind,避免正式构建产生未使用警告。
2026-09-15 19:33:13 +08:00
k88936 80caec373a 修正资源 kind 绑定测试断言
按 ts-rs 生成的单引号联合成员校验绑定内容。
2026-09-15 19:32:12 +08:00
k88936 3caefd6716 修复资源 kind 转义字符串反序列化
为 AssetKindVisitor 增加 visit_string 转发。

补充 JSON 转义 kind 的反序列化回归测试。
2026-09-15 19:31:21 +08:00
k88936 4d0dd2d721 收紧 UI 资源桥接的 manifest kind
UI 原型与文档链接改用生成枚举边界
删除 ui-prototype 的主动 alias 使用并更新测试清单
2026-09-15 18:06:27 +08:00
k88936 2befaaef9d 接入 AGC shell TS 资源 kind 边界
新增消费 Rust 生成绑定的未知值解析模块
迁移资源引用与画布引用入口并补充日志测试
更新逐项迁移清单与实施计划
2026-09-15 18:00:34 +08:00
k88936 20b0bf4dd7 补充 AGC manifest 未知 kind 验收
验证旧值读取为 Unknown 且序列化为 unknown
明确不执行 alias 迁移
2026-09-15 17:50:33 +08:00
k88936 b4fd8d9b8b 收紧 AGC manifest 资源 kind 字段
将 manifest 条目 kind 改为 Rust 枚举并把未知值收口为 Unknown
修正字体上传与图集切片的正式资源 kind
保持 source.kind 与 generationKind 等其他字符串字段不变
2026-09-15 17:37:28 +08:00
k88936 fe85fa2a62 建立 AGC 资源 kind Rust 枚举与 TypeScript 绑定
新增 GameCreationAppAssetKind 及 Unknown 解析边界
通过 ts-rs 生成 shell 专属 TypeScript 联合类型
补充未知值上下文日志与 Rust/绑定生成测试
2026-09-15 16:20:52 +08:00
k88936 b14a533b17 新增AGC资源kind绑定实施计划
限定首个代码切片为 Rust enum 与 ts-rs 生成边界。

明确不修改 manifest 字段和其他资源字符串领域。

列出绑定、serde round-trip 与定向验证门禁。
2026-09-15 15:29:37 +08:00
k88936 42969b2218 细化AGC资源kind构造器审计
补充 Rust manifest 构造器逐处迁移清单。

补充 shell TypeScript 裸 assetKind 消费方清单。

区分图集、工作流和 manifest kind 的处理边界。
2026-09-15 15:24:46 +08:00
k88936 c4ee75a1af 补充AGC资源kind词汇审计清单
记录 manifest kind 的 Rust 写入边界与 TypeScript 消费方。

区分资源 kind 与 source/generation/workflow 等字符串领域。

标注 font、audio、sound-effect、background-music 及旧值的生产状态。

通过文档索引、编码与 diff 检查。
2026-09-15 15:19:58 +08:00
k88936 6f2137cbb0 补充AGC资源kind枚举化规范
新增资源 kind 枚举化主规范与边界。

新增词汇审计、Rust 绑定、写入清理三个里程碑。

新增 shell-only 资源 kind 扫描 todo 清单。

通过文档索引、编码与 diff 检查。
2026-09-15 15:12:24 +08:00
k88936 70a2246e8b 移除 UI 文档引用新增测试
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m30s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m59s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m24s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m50s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 4m28s
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m7s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m22s
Project CI / Frontend tests (pull_request) Failing after 3m51s
Project CI / Native shell tests (pull_request) Successful in 7m4s
删除 Direct Codex UI 文档 fixture 与专门测试用例

保留生产引用逻辑和现有业务测试
2026-09-14 21:41:53 +08:00
k88936 b7e3dac661 统一引用 UI 文档资产类型常量
前端入口、资源桥接与引用预览统一使用共享常量

Rust 持久化与测试复用共享契约常量

补齐 UI 编辑器入口 fixture 的新资产类型
2026-09-14 21:36:43 +08:00
k88936 2e4a1996c8 同步 UI 编辑器入口测试资产类型
将项目开发 UI 编辑器回程测试 fixture 更新为 ui-design-doc
2026-09-14 21:12:03 +08:00
k88936 fe4e952853 更新 UI 文档资源编辑器入口
将 ui-design-doc 资源纳入选中资源的 UI 编辑器按钮判定

保留 ui-prototype 图片资源的编辑器桥接入口
2026-09-14 21:04:08 +08:00
k88936 18654b6806 同步 UI 文档资产共享契约与前端识别
共享契约新增 ui-design-doc 分类及中英文映射

前端编辑器与资源桥接严格识别文档资产

更新技术方案、实施计划和长期决策记录
2026-09-14 20:45:04 +08:00
k88936 ee7d00c0b1 统一 UI 编辑器文档资产类型
新增 ui-design-doc 与 application/json 常量

创建、桥接、工作流和持久化统一使用严格资产类型
2026-09-14 20:44:49 +08:00
k88936 9cd1a94369 接入 UI 文档引用代码上下文生成
Direct Codex 引用 ui-design-doc 时生成带文档的 JS 文件

生成成功或失败信息追加到当前 prompt

补充成功、失败与 ui-prototype 隔离测试
2026-09-14 20:44:49 +08:00
k88936 470e85c0ff Add UI-specific prompt rendering for design assets in resource references 2026-09-14 20:44:49 +08:00
146 changed files with 5147 additions and 5324 deletions
+3
View File
@@ -23,3 +23,6 @@
*.meta text *.meta text
*.anim text *.anim text
*.controller text *.controller text
# Rust ts-rs 生成的共享契约:保留在仓库中供 TS 消费,但不作为手写源文件统计。
packages/shared/src/contracts/generated/** linguist-generated=true
+10 -1
View File
@@ -1,5 +1,14 @@
{ {
"singleQuote": true, "singleQuote": true,
"semi": true, "semi": true,
"trailingComma": "all" "trailingComma": "all",
"overrides": [
{
"files": "packages/shared/src/contracts/generated/**/*.ts",
"options": {
"printWidth": 1000,
"singleQuote": false
}
}
]
} }
@@ -676,11 +676,11 @@ async function runSelfTest() {
designFoundationAssetCall?.arguments?.input?.outputPath === designFoundationAssetCall?.arguments?.input?.outputPath ===
'assets/ui-prototype.png' && 'assets/ui-prototype.png' &&
designFoundationAssetCall.arguments.input.aspectRatio === '16:9' && designFoundationAssetCall.arguments.input.aspectRatio === '16:9' &&
designFoundationAssetCall.arguments.input.assetKind === 'ui-prototype' && designFoundationAssetCall.arguments.input.assetKind === 'ui-design' &&
artAssetPlanAssetCall?.arguments?.input?.outputPath === artAssetPlanAssetCall?.arguments?.input?.outputPath ===
'assets/art-spritesheet.png' && 'assets/art-spritesheet.png' &&
artAssetPlanAssetCall.arguments.input.aspectRatio === '1:1' && artAssetPlanAssetCall.arguments.input.aspectRatio === '1:1' &&
artAssetPlanAssetCall.arguments.input.assetKind === 'art-spritesheet', artAssetPlanAssetCall.arguments.input.assetKind === 'icon-spritesheet',
'self-test-visual-assets-invalid', 'self-test-visual-assets-invalid',
); );
@@ -70,7 +70,7 @@ const requiredFormalArtifactSpecs = [
{ path: 'game/balance.json', kind: 'json' }, { path: 'game/balance.json', kind: 'json' },
{ path: 'assets/manifest.art.json', kind: 'json' }, { path: 'assets/manifest.art.json', kind: 'json' },
{ path: 'assets/manifest.audio.json', kind: 'json' }, { path: 'assets/manifest.audio.json', kind: 'json' },
{ path: 'game/index.html', kind: 'game-entry' }, { path: 'game/index.html', kind: 'file' },
{ path: 'exports/README.md', kind: 'file' }, { path: 'exports/README.md', kind: 'file' },
]; ];
const editorImageArtifactSpecs = [ const editorImageArtifactSpecs = [
@@ -290,7 +290,7 @@ function deterministicArtManifest(
{ {
id: 'garden-guardians-spritesheet', id: 'garden-guardians-spritesheet',
path: 'assets/art-spritesheet.png', path: 'assets/art-spritesheet.png',
kind: 'art-spritesheet', kind: 'icon-spritesheet',
usage: ['defenders', 'enemies', 'battlefield-ui'], usage: ['defenders', 'enemies', 'battlefield-ui'],
source: 'canvas', source: 'canvas',
status: 'ready', status: 'ready',
@@ -710,7 +710,7 @@ function canvasAssetCall(agentId) {
outputPath: 'assets/ui-prototype.png', outputPath: 'assets/ui-prototype.png',
aspectRatio: '16:9', aspectRatio: '16:9',
imageSize: '2K', imageSize: '2K',
assetKind: 'ui-prototype', assetKind: 'ui-design',
assetLabel: '游戏横屏界面原型图', assetLabel: '游戏横屏界面原型图',
replaceExisting: false, replaceExisting: false,
}); });
@@ -721,7 +721,7 @@ function canvasAssetCall(agentId) {
outputPath: 'assets/art-spritesheet.png', outputPath: 'assets/art-spritesheet.png',
aspectRatio: '1:1', aspectRatio: '1:1',
imageSize: '1K', imageSize: '1K',
assetKind: 'art-spritesheet', assetKind: 'icon-spritesheet',
assetLabel: '游戏首版核心美术素材', assetLabel: '游戏首版核心美术素材',
replaceExisting: false, replaceExisting: false,
sliceMode: 'connected-components', sliceMode: 'connected-components',
@@ -830,12 +830,12 @@ function missingGeneratedVisualAssetObservation(context, agentId) {
}, },
'design-foundation': { 'design-foundation': {
path: 'assets/ui-prototype.png', path: 'assets/ui-prototype.png',
kind: 'ui-prototype', kind: 'ui-design',
summary: '策划界面原型图尚未按正式视觉流程生成并登记,不能完成任务', summary: '策划界面原型图尚未按正式视觉流程生成并登记,不能完成任务',
}, },
'art-asset-plan': { 'art-asset-plan': {
path: 'assets/art-spritesheet.png', path: 'assets/art-spritesheet.png',
kind: 'art-spritesheet', kind: 'icon-spritesheet',
summary: '首版美术素材图尚未按正式视觉流程生成并登记,不能完成任务', summary: '首版美术素材图尚未按正式视觉流程生成并登记,不能完成任务',
}, },
}[agentId]; }[agentId];
@@ -2667,7 +2667,7 @@ function createDeterministicCanvasFixture(apiKey) {
asset: { asset: {
assetId: `asset-${sliceImageId}`, assetId: `asset-${sliceImageId}`,
assetObjectId: sliceAssetObjectId, assetObjectId: sliceAssetObjectId,
assetKind: 'art-spritesheet-slice', assetKind: 'icon',
projectId, projectId,
taskId, taskId,
}, },
@@ -2707,7 +2707,7 @@ function createDeterministicCanvasFixture(apiKey) {
spritesheetAsset: { spritesheetAsset: {
assetId: `asset-${imageId}`, assetId: `asset-${imageId}`,
assetObjectId, assetObjectId,
assetKind: 'art-spritesheet', assetKind: 'icon-spritesheet',
projectId, projectId,
taskId, taskId,
}, },
@@ -2749,7 +2749,7 @@ function createDeterministicCanvasFixture(apiKey) {
const assetObjectId = `asset-object-${imageId}`; const assetObjectId = `asset-object-${imageId}`;
const resourceId = `resource-${imageId}`; const resourceId = `resource-${imageId}`;
const assetKind = const assetKind =
typeof body?.assetKind === 'string' ? body.assetKind : 'game-art'; typeof body?.assetKind === 'string' ? body.assetKind : 'image';
images.set(imageId, { images.set(imageId, {
...image, ...image,
objectKey, objectKey,
+1 -1
View File
@@ -3931,7 +3931,6 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]
@@ -5017,6 +5016,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"ts-rs",
] ]
[[package]] [[package]]
@@ -56,7 +56,7 @@ platform-agent = { path = "../../../server-rs/crates/platform-agent" }
portable-pty = "0.9" portable-pty = "0.9"
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "native-tls", "stream"] } reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "native-tls", "stream"] }
regex = "1" regex = "1"
shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false } shared-contracts = { path = "../../../server-rs/crates/shared-contracts", default-features = false, features = ["ts-bindings"] }
tauri = { version = "2.11.2", features = [] } tauri = { version = "2.11.2", features = [] }
tauri-plugin-dialog = "2.7.1" tauri-plugin-dialog = "2.7.1"
tauri-plugin-http = { version = "2.5.9", default-features = false, features = ["charset", "cookies", "http2", "rustls-tls"] } tauri-plugin-http = { version = "2.5.9", default-features = false, features = ["charset", "cookies", "http2", "rustls-tls"] }
@@ -63,7 +63,7 @@
"agents/openai.yaml", "agents/openai.yaml",
"references/platform-art-contract.md" "references/platform-art-contract.md"
], ],
"sha256": "47ac742d9b88e5d6cd9833484ab212152578e58ae27f7add312fd1d78183385c" "sha256": "6668bf1aa69601bcc65c97fdcd879c699c3139befcd70805d95614997f6e44e9"
}, },
{ {
"name": "agc-web-game-development", "name": "agc-web-game-development",
@@ -19,13 +19,13 @@ image, UI design image, or publication material; use `agc_edit_image` for an
edit of an existing registered image; use `taonier_prepare_game_art` only for edit of an existing registered image; use `taonier_prepare_game_art` only for
the complete game-art package and its canonical slices. the complete game-art package and its canonical slices.
With `agc_generate_image`, `kind="character"` and `kind="art-spritesheet"` With `agc_generate_image`, `kind="character"` and `kind="icon-spritesheet"`
generate the subject on a solid-colour background and automatically matte it generate the subject on a solid-colour background and automatically matte it
away afterwards, producing transparent-background results; write the prompt away afterwards, producing transparent-background results; write the prompt
for the subject only, never for a scene. `kind="image"` keeps the rendered for the subject only, never for a scene. `kind="image"` keeps the rendered
frame without extra processing. frame without extra processing.
When `agc_generate_image` is used with `kind="art-spritesheet"`, `sliceMode` is When `agc_generate_image` is used with `kind="icon-spritesheet"`, `sliceMode` is
required and has no default, so decide it explicitly: required and has no default, so decide it explicitly:
- Use `sliceMode="grid"` with `gridX` and `gridY` (1-32 each) only when the user - Use `sliceMode="grid"` with `gridX` and `gridY` (1-32 each) only when the user
@@ -1198,7 +1198,7 @@ mod tests {
"tool": "agc_generate_image", "tool": "agc_generate_image",
"arguments": { "arguments": {
"prompt": prompt, "prompt": prompt,
"kind": "art-spritesheet", "kind": "icon-spritesheet",
"sliceMode": "connected-components", "sliceMode": "connected-components",
"sliceCount": 8, "sliceCount": 8,
"screenColor": "#CFEFFF" "screenColor": "#CFEFFF"
@@ -1,6 +1,10 @@
use super::model::{DirectCodexUserContentPart, DirectCodexUserItem}; use super::model::{DirectCodexUserContentPart, DirectCodexUserItem, DirectCodexUserMessageItem};
use super::validation::validate_direct_codex_user_item; use super::validation::validate_direct_codex_user_item;
use crate::agent::{read_manifest_for_project, sanitize_attachment_local_path}; use crate::agent::{read_manifest_for_project, sanitize_attachment_local_path};
use crate::ui_editor::persistence::{
generate_ui_design_code_at, GenerateUiDesignCodeInput, UI_DESIGN_DOC_ASSET_KIND,
UI_DESIGN_DOC_MEDIA_TYPE,
};
use serde_json::Value; use serde_json::Value;
use std::path::Path; use std::path::Path;
@@ -111,29 +115,165 @@ pub(crate) fn direct_codex_user_item_to_prompt(
item: &DirectCodexUserItem, item: &DirectCodexUserItem,
) -> Result<String, String> { ) -> Result<String, String> {
let wire = direct_codex_user_item_to_wire_input(root, item)?; let wire = direct_codex_user_item_to_wire_input(root, item)?;
wire.as_array() let DirectCodexUserItem::Message(message) = item;
let mut prompt = wire
.as_array()
.ok_or_else(|| "DirectProject user item wire input 不是数组".to_string()) .ok_or_else(|| "DirectProject user item wire input 不是数组".to_string())
.map(|parts| { .and_then(|parts| {
parts parts
.iter() .iter()
.filter_map(|part| part.get("text").and_then(Value::as_str)) .map(|part| {
.collect::<String>() part.get("text")
}) .and_then(Value::as_str)
.and_then(|prompt| { .ok_or_else(|| "DirectProject user item wire part 缺少 text".to_string())
if prompt.trim().is_empty() { })
Err("DirectProject user item 不能转换为空 prompt".to_string()) .collect::<Result<String, String>>()
} else { })?;
Ok(prompt) if let Some(code_context) = render_ui_design_code_context(root, message)? {
prompt.push('\n');
prompt.push_str(&code_context);
}
if prompt.trim().is_empty() {
return Err("DirectProject user item 不能转换为空 prompt".to_string());
}
Ok(prompt)
}
/// 本轮 prompt 的 UI 设计文档引用上下文:复用 UI Editor 代码导出,把带文档注释的
/// JS 片段路径交给模型;生成失败只追加原始错误,不阻断本轮引用,其它引用继续处理。
///
/// 只在生成本轮 prompt 时展开:历史 item 回读走 `direct_codex_user_item_to_response_item`
/// 的纯投影,不得在这里产生项目写副作用。
fn render_ui_design_code_context(
root: &Path,
message: &DirectCodexUserMessageItem,
) -> Result<Option<String>, String> {
let referenced_ids = message
.content
.iter()
.filter_map(|part| match part {
DirectCodexUserContentPart::AgcResourceReference { resource_id } => {
Some(resource_id.trim())
} }
_ => None,
}) })
.collect::<Vec<_>>();
if referenced_ids.is_empty() {
return Ok(None);
}
let manifest = read_manifest_for_project(root)?;
let mut lines = Vec::new();
for resource_id in referenced_ids {
let is_ui_design_doc = manifest.assets.iter().any(|asset| {
asset.id == resource_id
&& asset.kind == UI_DESIGN_DOC_ASSET_KIND
&& asset.media_type == UI_DESIGN_DOC_MEDIA_TYPE
});
if !is_ui_design_doc {
continue;
}
lines.push(
match generate_ui_design_code_at(GenerateUiDesignCodeInput {
project_path: root.to_string_lossy().into_owned(),
expected_project_id: manifest.project_id.clone(),
asset_id: resource_id.to_string(),
}) {
Ok(result) => format!("请先阅读生成的带有文档的代码片段: {}", result.relative_path),
Err(error) => format!("生成代码遇到错误{error}"),
},
);
}
if lines.is_empty() {
return Ok(None);
}
Ok(Some(lines.join("\n")))
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::direct_codex_user_item_to_response_item; use super::{direct_codex_user_item_to_prompt, direct_codex_user_item_to_response_item};
use crate::ui_editor::persistence::UI_DESIGN_DOC_MEDIA_TYPE;
use serde_json::json; use serde_json::json;
use shared_contracts::game_creation_app::{
GameCreationAppAssetKind, GameCreationAppAssetSource, GameCreationAppAssetSourceKind,
};
use std::path::Path; use std::path::Path;
const PROMPT_CONTEXT_PROJECT_ID: &str = "direct-prompt-context-project";
fn prompt_context_project() -> tempfile::TempDir {
let directory = tempfile::tempdir().expect("temp project");
crate::init_local_game_project_at(
directory.path(),
PROMPT_CONTEXT_PROJECT_ID,
"Direct prompt 上下文测试",
)
.expect("init project");
directory
}
fn register_fixture_asset(
root: &Path,
relative_path: &str,
kind: GameCreationAppAssetKind,
media_type: &str,
) -> String {
let absolute_path = crate::resolve_local_project_path(root, relative_path)
.expect("resolve fixture asset path");
std::fs::create_dir_all(absolute_path.parent().expect("fixture asset parent"))
.expect("create fixture asset parent");
std::fs::write(&absolute_path, b"{}").expect("write fixture asset file");
crate::assets::register_local_asset_at(
root,
relative_path,
kind,
media_type,
"prompt-context-test",
GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Generated,
canvas_project_id: None,
resource_id: Some("prompt-context".to_string()),
asset_object_id: None,
task_id: None,
prompt: None,
model: None,
generation_route: None,
generation_kind: None,
reference_resource_ids: Vec::new(),
},
)
.expect("register fixture asset")
.id
}
fn ui_design_doc_fixture(initialize_state: bool) -> (tempfile::TempDir, String) {
let directory = prompt_context_project();
let asset_id = register_fixture_asset(
directory.path(),
"ui/design.json",
GameCreationAppAssetKind::UiDesignDoc,
UI_DESIGN_DOC_MEDIA_TYPE,
);
if initialize_state {
crate::ui_editor::persistence::initialize_ui_design_state_at(
directory.path(),
PROMPT_CONTEXT_PROJECT_ID,
&asset_id,
)
.expect("initialize UI design state");
}
(directory, asset_id)
}
fn user_item_with_resource_reference(asset_id: &str) -> serde_json::Value {
json!({
"type": "message",
"role": "user",
"id": "turn-1:user",
"content": [{"type": "agc_resource_reference", "resourceId": asset_id}],
})
}
#[test] #[test]
fn standard_response_item_passes_through_without_agc_private_parts() { fn standard_response_item_passes_through_without_agc_private_parts() {
let item = json!({ let item = json!({
@@ -174,4 +314,81 @@ mod tests {
.expect_err("history item without type must fail"); .expect_err("history item without type must fail");
assert!(error.contains("缺少 type"), "{error}"); assert!(error.contains("缺少 type"), "{error}");
} }
#[test]
fn ui_design_doc_reference_appends_generated_code_context() {
let (project, asset_id) = ui_design_doc_fixture(true);
let item: super::DirectCodexUserItem =
serde_json::from_value(user_item_with_resource_reference(&asset_id))
.expect("canonical user item");
let prompt =
direct_codex_user_item_to_prompt(project.path(), &item).expect("prompt projection");
assert!(
prompt.contains(&format!(
"[素材引用 resourceId={asset_id};项目路径=ui/design.json]"
)),
"{prompt}"
);
assert!(
prompt.contains("请先阅读生成的带有文档的代码片段: ui/generated-"),
"{prompt}"
);
}
#[test]
fn history_projection_never_writes_generated_ui_design_code() {
let (project, asset_id) = ui_design_doc_fixture(true);
let item = user_item_with_resource_reference(&asset_id);
direct_codex_user_item_to_response_item(project.path(), &item).expect("history projection");
let generated_root =
crate::resolve_local_project_path(project.path(), "ui").expect("resolve ui directory");
let generated_files = std::fs::read_dir(generated_root)
.expect("read ui directory")
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry
.file_name()
.to_string_lossy()
.starts_with("generated-")
})
.count();
assert_eq!(
generated_files, 0,
"历史回读只做纯投影,不得生成 UI 设计代码"
);
}
#[test]
fn ui_design_generation_failure_keeps_reference_and_reports_error() {
let (project, asset_id) = ui_design_doc_fixture(false);
let item: super::DirectCodexUserItem =
serde_json::from_value(user_item_with_resource_reference(&asset_id))
.expect("canonical user item");
let prompt =
direct_codex_user_item_to_prompt(project.path(), &item).expect("prompt projection");
assert!(prompt.contains("素材引用 resourceId="), "{prompt}");
assert!(prompt.contains("生成代码遇到错误"), "{prompt}");
}
#[test]
fn other_asset_kind_reference_does_not_generate_ui_design_code() {
let project = prompt_context_project();
let asset_id = register_fixture_asset(
project.path(),
"assets/hero.png",
GameCreationAppAssetKind::Character,
"image/png",
);
let item: super::DirectCodexUserItem =
serde_json::from_value(user_item_with_resource_reference(&asset_id))
.expect("canonical user item");
let prompt =
direct_codex_user_item_to_prompt(project.path(), &item).expect("prompt projection");
assert!(prompt.contains("素材引用 resourceId="), "{prompt}");
assert!(
!prompt.contains("请先阅读生成的带有文档的代码片段"),
"{prompt}"
);
assert!(!prompt.contains("生成代码遇到错误"), "{prompt}");
}
} }
File diff suppressed because it is too large Load Diff
@@ -27,7 +27,6 @@ const DIRECT_TOOL_BRIDGE_SEARCH_URL: &str = "https://www.bing.com/search?format=
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_NAME_CHARS: usize = 120; const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_NAME_CHARS: usize = 120;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_KIND_CHARS: usize = 80; const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_KIND_CHARS: usize = 80;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_PAGE_SIZE: usize = 100; const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_PAGE_SIZE: usize = 100;
const DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN: usize = 4;
const DIRECT_TOOL_BRIDGE_MAX_ACCOUNT_ASSET_ID_CHARS: usize = 512; const DIRECT_TOOL_BRIDGE_MAX_ACCOUNT_ASSET_ID_CHARS: usize = 512;
const DIRECT_TOOL_BRIDGE_MAX_LOCAL_ASSET_PATH_CHARS: usize = 512; const DIRECT_TOOL_BRIDGE_MAX_LOCAL_ASSET_PATH_CHARS: usize = 512;
@@ -68,7 +67,6 @@ struct DirectToolBridgeActiveTurnAuthorization {
turn_id: String, turn_id: String,
brief_sha256: Option<String>, brief_sha256: Option<String>,
completed_result: Option<Value>, completed_result: Option<Value>,
resource_request_ids: BTreeMap<String, (String, String)>,
} }
enum DirectToolBridgeRegenerationCall { enum DirectToolBridgeRegenerationCall {
@@ -207,7 +205,6 @@ impl DirectToolBridgeState {
turn_id: turn_id.clone(), turn_id: turn_id.clone(),
brief_sha256: None, brief_sha256: None,
completed_result: None, completed_result: None,
resource_request_ids: BTreeMap::new(),
}); });
Ok(DirectToolBridgeTurnGuard { Ok(DirectToolBridgeTurnGuard {
state: Arc::clone(self), state: Arc::clone(self),
@@ -637,30 +634,17 @@ impl DirectToolBridgeState {
Ok(()) Ok(())
} }
fn resource_request_ids(&self, request_fingerprint: &str) -> Result<(String, String), String> { /// 取当前回合身份,媒体资源请求按回合身份与请求指纹确定性派生 operation/idempotency id。
let mut authorization = self fn active_resource_turn_id(&self) -> Result<String, String> {
let authorization = self
.turn_authorization .turn_authorization
.lock() .lock()
.map_err(|_| "AGC 工具桥回合授权状态不可用".to_string())?; .map_err(|_| "AGC 工具桥回合授权状态不可用".to_string())?;
let active = authorization authorization
.active .active
.as_mut() .as_ref()
.ok_or_else(|| "当前没有客户端签发的资源生成回合身份".to_string())?; .map(|active| active.turn_id.clone())
if let Some(ids) = active.resource_request_ids.get(request_fingerprint) { .ok_or_else(|| "当前没有客户端签发的资源生成回合身份".to_string())
return Ok(ids.clone());
}
if active.resource_request_ids.len() >= DIRECT_TOOL_BRIDGE_MAX_RESOURCE_CALLS_PER_TURN {
return Err("单个用户回合最多只能创建四项媒体资源请求".to_string());
}
let operation_id =
direct_resource_request_uuid(&active.turn_id, "operation", request_fingerprint);
let idempotency_key =
direct_resource_request_uuid(&active.turn_id, "idempotency", request_fingerprint);
active.resource_request_ids.insert(
request_fingerprint.to_string(),
(operation_id.clone(), idempotency_key.clone()),
);
Ok((operation_id, idempotency_key))
} }
} }
@@ -1213,9 +1197,9 @@ fn bridge_registered_resource(
"mediaType": asset.media_type, "mediaType": asset.media_type,
// Agent 与 UI 必须看到同一个口径:UI 栏目走 TS 的 `gameCreationAppAssetCategory` // Agent 与 UI 必须看到同一个口径:UI 栏目走 TS 的 `gameCreationAppAssetCategory`
// (落盘值 + 按 kind 派生 + 读时自愈),这里走 Rust 的同构实现。 // (落盘值 + 按 kind 派生 + 读时自愈),这里走 Rust 的同构实现。
// 直接透传落盘 `asset.category` 会让 `kind:"ui"` 的资产在 UI 显示「UI 交互」、 // 直接透传落盘 `asset.category` 会让 UI 设计资产在 UI 显示「UI 交互」、
// 在 Agent 侧读到 `unclassified`(真机 55 条分歧)。 // 在 Agent 侧读到 `unclassified`(真机 55 条分歧)。
"category": game_creation_app_asset_effective_category(&asset.kind, asset.category), "category": game_creation_app_asset_effective_category(asset.kind, asset.category),
"tags": asset.tags, "tags": asset.tags,
"canvasProjectId": asset.source.canvas_project_id, "canvasProjectId": asset.source.canvas_project_id,
"resourceId": asset.source.resource_id, "resourceId": asset.source.resource_id,
@@ -1256,7 +1240,9 @@ fn bridge_list_registered_assets(root: &Path, arguments: &Value) -> Value {
arguments, arguments,
"kind", "kind",
DIRECT_TOOL_BRIDGE_MAX_RESOURCE_KIND_CHARS, DIRECT_TOOL_BRIDGE_MAX_RESOURCE_KIND_CHARS,
)?; )?
.map(|kind| bridge_asset_list_kind_filter(&kind))
.transpose()?;
let asset_id = bridge_optional_bounded_string( let asset_id = bridge_optional_bounded_string(
arguments, arguments,
"assetId", "assetId",
@@ -1276,7 +1262,7 @@ fn bridge_list_registered_assets(root: &Path, arguments: &Value) -> Value {
let mut assets = manifest let mut assets = manifest
.assets .assets
.iter() .iter()
.filter(|asset| kind.as_ref().is_none_or(|kind| asset.kind == *kind)) .filter(|asset| kind.is_none_or(|kind| asset.kind == kind))
.filter(|asset| { .filter(|asset| {
asset_id asset_id
.as_ref() .as_ref()
@@ -1342,6 +1328,24 @@ fn bridge_list_registered_assets(root: &Path, arguments: &Value) -> Value {
} }
} }
/// `asset.list` 的 `kind` 过滤:只接受 canonical 值,认不出的值直接报错。
///
/// 以前认不出的值会收口成 `unknown` 再参与等值过滤,结果是**静默返回空列表**:调用方(模型)
/// 会以为项目里没有这类资产,而不是"你传的 kind 不合法",于是继续按错误前提往下走。
/// `unknown` 本身仍是合法输入(确有 kind 未知的资产),只拒绝"既不是 canonical、也不是字面
/// `unknown`"的原值。
fn bridge_asset_list_kind_filter(raw: &str) -> Result<GameCreationAppAssetKind, String> {
let kind = GameCreationAppAssetKind::parse_with_context(raw, "asset.list.kind");
if kind == GameCreationAppAssetKind::Unknown
&& raw.trim() != GameCreationAppAssetKind::Unknown.as_str()
{
return Err(format!(
"kind 不是已知的 manifest 资源 kind{raw};请改用 canonical kind(如 image、scene、character、icon、icon-spritesheet、character-animation、audio、video、document"
));
}
Ok(kind)
}
fn bridge_project_file_class(path: &str) -> (&'static str, Option<&'static str>) { fn bridge_project_file_class(path: &str) -> (&'static str, Option<&'static str>) {
let extension = Path::new(path) let extension = Path::new(path)
.extension() .extension()
@@ -1890,8 +1894,11 @@ async fn bridge_create_or_derive_resource(
)) ))
.await? .await?
} else { } else {
let (operation_id, idempotency_key) = let turn_id = state.active_resource_turn_id()?;
state.resource_request_ids(&request_fingerprint)?; let operation_id =
direct_resource_request_uuid(&turn_id, "operation", &request_fingerprint);
let idempotency_key =
direct_resource_request_uuid(&turn_id, "idempotency", &request_fingerprint);
let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision; let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision;
let source_resource_id = source_asset let source_resource_id = source_asset
.as_ref() .as_ref()
@@ -1909,7 +1916,7 @@ async fn bridge_create_or_derive_resource(
source_asset_id: source_asset.as_ref().map(|asset| asset.id.clone()), source_asset_id: source_asset.as_ref().map(|asset| asset.id.clone()),
source_path: source_asset.as_ref().map(|asset| asset.local_path.clone()), source_path: source_asset.as_ref().map(|asset| asset.local_path.clone()),
source_media_type: source_asset.as_ref().map(|asset| asset.media_type.clone()), source_media_type: source_asset.as_ref().map(|asset| asset.media_type.clone()),
source_subtype: source_asset.as_ref().map(|asset| asset.kind.clone()), source_subtype: source_asset.as_ref().map(|asset| asset.kind.to_string()),
producer_task_id: source_asset producer_task_id: source_asset
.as_ref() .as_ref()
.and_then(|asset| asset.source.task_id.clone()), .and_then(|asset| asset.source.task_id.clone()),
@@ -2001,7 +2008,11 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
}) })
.await? .await?
} else { } else {
let (operation_id, idempotency_key) = state.resource_request_ids(&fingerprint)?; // id 按回合身份与请求指纹确定性派生,同指纹重试与 pending 对账语义不变。
let turn_id = state.active_resource_turn_id()?;
let operation_id = direct_resource_request_uuid(&turn_id, "operation", &fingerprint);
let idempotency_key =
direct_resource_request_uuid(&turn_id, "idempotency", &fingerprint);
let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision; let revision = read_game_creator_agent_runtime_project_revision(&state.root)?.revision;
let request = DeriveLocalProjectResourceInput { let request = DeriveLocalProjectResourceInput {
project_path: state.root.to_string_lossy().into_owned(), project_path: state.root.to_string_lossy().into_owned(),
@@ -2015,7 +2026,7 @@ async fn bridge_remove_background(state: &DirectToolBridgeState, arguments: &Val
source_asset_id: Some(source_asset_id.clone()), source_asset_id: Some(source_asset_id.clone()),
source_path: Some(source_asset.local_path.clone()), source_path: Some(source_asset.local_path.clone()),
source_media_type: Some(source_asset.media_type.clone()), source_media_type: Some(source_asset.media_type.clone()),
source_subtype: Some(source_asset.kind.clone()), source_subtype: Some(source_asset.kind.to_string()),
producer_task_id: source_asset.source.task_id.clone(), producer_task_id: source_asset.source.task_id.clone(),
source_version_id: None, source_version_id: None,
prompt: "去除背景".to_string(), prompt: "去除背景".to_string(),
@@ -2183,35 +2194,37 @@ async fn bridge_prepare_game_art(state: &DirectToolBridgeState, arguments: &Valu
result result
} }
fn bridge_image_generation_kind(arguments: &Value) -> Result<String, String> { fn bridge_image_generation_kind(arguments: &Value) -> Result<GameCreationAppAssetKind, String> {
let kind = arguments let kind = arguments
.get("kind") .get("kind")
.and_then(Value::as_str) .and_then(Value::as_str)
.map(str::trim) .map(str::trim)
.filter(|value| !value.is_empty()) .filter(|value| !value.is_empty())
.unwrap_or("image"); .unwrap_or("image");
normalize_platform_art_asset_generation_kind(kind) normalize_platform_art_asset_generation_kind(kind).ok_or_else(|| {
.map(str::to_string) format!(
.ok_or_else(|| { "工具参数 kind 只允许 {}",
format!( PLATFORM_ART_ASSET_GENERATION_KINDS
"工具参数 kind 只允许 {}", .iter()
PLATFORM_ART_ASSET_GENERATION_KINDS.join("") .map(|kind| kind.as_str())
) .collect::<Vec<_>>()
}) .join("")
)
})
} }
/// 切分模式没有默认值:图集必须显式声明,且声明必须与 kind 和网格参数自洽。 /// 切分模式没有默认值:图集必须显式声明,且声明必须与 kind 和网格参数自洽。
fn validate_generate_image_slice_declaration( fn validate_generate_image_slice_declaration(
kind: &str, kind: GameCreationAppAssetKind,
slice_mode: Option<&str>, slice_mode: Option<&str>,
grid_x: Option<u32>, grid_x: Option<u32>,
grid_y: Option<u32>, grid_y: Option<u32>,
slice_count: Option<usize>, slice_count: Option<usize>,
) -> Result<(), String> { ) -> Result<(), String> {
if kind == "art-spritesheet" { if kind == GameCreationAppAssetKind::IconSpritesheet {
if slice_mode.is_none() { if slice_mode.is_none() {
return Err( return Err(
"kind=art-spritesheet 必须显式声明 sliceMode,没有默认值:需求要求等分网格、固定槽位或指定行列数时传 sliceMode=grid 并提供 gridX/gridY;自由排布、数量不定或只要求一张图集时传 sliceMode=connected-components" "kind=icon-spritesheet 必须显式声明 sliceMode,没有默认值:需求要求等分网格、固定槽位或指定行列数时传 sliceMode=grid 并提供 gridX/gridY;自由排布、数量不定或只要求一张图集时传 sliceMode=connected-components"
.to_string(), .to_string(),
); );
} }
@@ -2224,19 +2237,19 @@ fn validate_generate_image_slice_declaration(
} }
if slice_mode.is_some() || grid_x.is_some() || grid_y.is_some() || slice_count.is_some() { if slice_mode.is_some() || grid_x.is_some() || grid_y.is_some() || slice_count.is_some() {
return Err(format!( return Err(format!(
"工具参数 sliceMode/gridX/gridY/sliceCount 仅对 kind=art-spritesheet 生效,当前 kind={kind}" "工具参数 sliceMode/gridX/gridY/sliceCount 仅对 kind=icon-spritesheet 生效,当前 kind={kind}"
)); ));
} }
Ok(()) Ok(())
} }
/// 抠图纯色背景只服务 character 与 art-spritesheet 链路;格式校验收口为 /// 抠图纯色背景只服务 character 与 icon-spritesheet 链路;格式校验收口为
/// `auto` 或 `#RRGGBB`(服务端另有支持色板,客户端不复制),`auto`/空串归一为 /// `auto` 或 `#RRGGBB`(服务端另有支持色板,客户端不复制),`auto`/空串归一为
/// None(服务端自动决策),hex 统一大写后透传。其它 kind 携带该字段直接拒绝, /// None(服务端自动决策),hex 统一大写后透传。其它 kind 携带该字段直接拒绝,
/// 避免服务端静默忽略造成“已生效”的误解。 /// 避免服务端静默忽略造成“已生效”的误解。
fn normalize_generate_image_screen_color( fn normalize_generate_image_screen_color(
arguments: &Value, arguments: &Value,
kind: &str, kind: GameCreationAppAssetKind,
) -> Result<Option<String>, String> { ) -> Result<Option<String>, String> {
let Some(value) = arguments.get("screenColor") else { let Some(value) = arguments.get("screenColor") else {
return Ok(None); return Ok(None);
@@ -2244,9 +2257,12 @@ fn normalize_generate_image_screen_color(
if value.is_null() { if value.is_null() {
return Ok(None); return Ok(None);
} }
if !matches!(kind, "character" | "art-spritesheet") { if !matches!(
kind,
GameCreationAppAssetKind::Character | GameCreationAppAssetKind::IconSpritesheet
) {
return Err(format!( return Err(format!(
"工具参数 screenColor 仅对 kind=character 和 kind=art-spritesheet 生效,当前 kind={kind}" "工具参数 screenColor 仅对 kind=character 和 kind=icon-spritesheet 生效,当前 kind={kind}"
)); ));
} }
let raw = value let raw = value
@@ -2373,18 +2389,18 @@ async fn bridge_generate_image(state: &DirectToolBridgeState, arguments: &Value)
}) })
.transpose()?; .transpose()?;
validate_generate_image_slice_declaration( validate_generate_image_slice_declaration(
kind.as_str(), kind,
slice_mode.as_deref(), slice_mode.as_deref(),
grid_x, grid_x,
grid_y, grid_y,
slice_count, slice_count,
)?; )?;
let screen_color = normalize_generate_image_screen_color(arguments, kind.as_str())?; let screen_color = normalize_generate_image_screen_color(arguments, kind)?;
let options = PlatformArtAssetGenerationOptions { let options = PlatformArtAssetGenerationOptions {
output_path, output_path,
aspect_ratio, aspect_ratio,
image_size, image_size,
asset_kind: kind.clone(), asset_kind: kind,
asset_label: asset_name.clone(), asset_label: asset_name.clone(),
replace_existing: false, replace_existing: false,
slice_count, slice_count,
@@ -2873,14 +2889,19 @@ pub(crate) async fn start_direct_tool_bridge(
mod tests { mod tests {
#[test] #[test]
fn generate_image_slice_declaration_is_explicit_and_self_consistent() { fn generate_image_slice_declaration_is_explicit_and_self_consistent() {
let missing = let missing = validate_generate_image_slice_declaration(
validate_generate_image_slice_declaration("art-spritesheet", None, None, None, None) GameCreationAppAssetKind::IconSpritesheet,
.expect_err("art-spritesheet without sliceMode must fail closed"); None,
None,
None,
None,
)
.expect_err("icon-spritesheet without sliceMode must fail closed");
assert!(missing.contains("没有默认值"), "{missing}"); assert!(missing.contains("没有默认值"), "{missing}");
assert!(missing.contains("connected-components"), "{missing}"); assert!(missing.contains("connected-components"), "{missing}");
assert!(validate_generate_image_slice_declaration( assert!(validate_generate_image_slice_declaration(
"art-spritesheet", GameCreationAppAssetKind::IconSpritesheet,
Some("connected-components"), Some("connected-components"),
None, None,
None, None,
@@ -2888,7 +2909,7 @@ mod tests {
) )
.is_ok()); .is_ok());
assert!(validate_generate_image_slice_declaration( assert!(validate_generate_image_slice_declaration(
"art-spritesheet", GameCreationAppAssetKind::IconSpritesheet,
Some("grid"), Some("grid"),
Some(3), Some(3),
Some(2), Some(2),
@@ -2896,7 +2917,7 @@ mod tests {
) )
.is_ok()); .is_ok());
let grid_with_count = validate_generate_image_slice_declaration( let grid_with_count = validate_generate_image_slice_declaration(
"art-spritesheet", GameCreationAppAssetKind::IconSpritesheet,
Some("grid"), Some("grid"),
Some(2), Some(2),
Some(2), Some(2),
@@ -2906,58 +2927,80 @@ mod tests {
assert!(grid_with_count.contains("gridX×gridY"), "{grid_with_count}"); assert!(grid_with_count.contains("gridX×gridY"), "{grid_with_count}");
let wrong_kind = validate_generate_image_slice_declaration( let wrong_kind = validate_generate_image_slice_declaration(
"image", GameCreationAppAssetKind::Image,
Some("connected-components"), Some("connected-components"),
None, None,
None, None,
None, None,
) )
.expect_err("slice declaration must stay scoped to art-spritesheet"); .expect_err("slice declaration must stay scoped to icon-spritesheet");
assert!( assert!(
wrong_kind.contains("仅对 kind=art-spritesheet 生效"), wrong_kind.contains("仅对 kind=icon-spritesheet 生效"),
"{wrong_kind}" "{wrong_kind}"
); );
let wrong_kind_count = let wrong_kind_count = validate_generate_image_slice_declaration(
validate_generate_image_slice_declaration("image", None, None, None, Some(8)) GameCreationAppAssetKind::Image,
.expect_err("sliceCount-only violation must be rejected"); None,
None,
None,
Some(8),
)
.expect_err("sliceCount-only violation must be rejected");
assert!( assert!(
wrong_kind_count.contains("sliceCount"), wrong_kind_count.contains("sliceCount"),
"sliceCount-only violation must name sliceCount: {wrong_kind_count}" "sliceCount-only violation must name sliceCount: {wrong_kind_count}"
); );
assert!(validate_generate_image_slice_declaration("image", None, None, None, None).is_ok()); assert!(validate_generate_image_slice_declaration(
GameCreationAppAssetKind::Image,
None,
None,
None,
None
)
.is_ok());
} }
#[test] #[test]
fn generate_image_screen_color_is_normalized_and_kind_gated() { fn generate_image_screen_color_is_normalized_and_kind_gated() {
// 省略与显式 null 等价,且不触发 kind 门禁。 // 省略与显式 null 等价,且不触发 kind 门禁。
assert_eq!( assert_eq!(
normalize_generate_image_screen_color(&json!({}), "image").expect("omitted"), normalize_generate_image_screen_color(&json!({}), GameCreationAppAssetKind::Image)
.expect("omitted"),
None None
); );
assert_eq!( assert_eq!(
normalize_generate_image_screen_color(&json!({"screenColor": null}), "image") normalize_generate_image_screen_color(
.expect("null"), &json!({"screenColor": null}),
GameCreationAppAssetKind::Image
)
.expect("null"),
None None
); );
// auto 家族归一为 None(服务端自动决策),大小写与空白不敏感。 // auto 家族归一为 None(服务端自动决策),大小写与空白不敏感。
for raw in ["auto", "AUTO", " auto ", ""] { for raw in ["auto", "AUTO", " auto ", ""] {
assert_eq!( assert_eq!(
normalize_generate_image_screen_color(&json!({"screenColor": raw}), "character") normalize_generate_image_screen_color(
.expect("auto variants"), &json!({"screenColor": raw}),
GameCreationAppAssetKind::Character
)
.expect("auto variants"),
None, None,
"{raw}" "{raw}"
); );
} }
// hex 统一大写透传;色板白名单由服务端权威校验,客户端只守格式。 // hex 统一大写透传;色板白名单由服务端权威校验,客户端只守格式。
assert_eq!( assert_eq!(
normalize_generate_image_screen_color(&json!({"screenColor": "#cfefff"}), "character") normalize_generate_image_screen_color(
.expect("lowercase hex"), &json!({"screenColor": "#cfefff"}),
GameCreationAppAssetKind::Character
)
.expect("lowercase hex"),
Some("#CFEFFF".to_string()) Some("#CFEFFF".to_string())
); );
assert_eq!( assert_eq!(
normalize_generate_image_screen_color( normalize_generate_image_screen_color(
&json!({"screenColor": " #A0BBA0 "}), &json!({"screenColor": " #A0BBA0 "}),
"art-spritesheet" GameCreationAppAssetKind::IconSpritesheet
) )
.expect("padded hex"), .expect("padded hex"),
Some("#A0BBA0".to_string()) Some("#A0BBA0".to_string())
@@ -2965,19 +3008,24 @@ mod tests {
// 非 auto/非 hex、非字符串一律拒绝。 // 非 auto/非 hex、非字符串一律拒绝。
for bad in [json!("green"), json!("#GGGGGG"), json!("#FFF"), json!(12)] { for bad in [json!("green"), json!("#GGGGGG"), json!("#FFF"), json!(12)] {
assert!( assert!(
normalize_generate_image_screen_color(&json!({"screenColor": bad}), "character") normalize_generate_image_screen_color(
.is_err(), &json!({"screenColor": bad}),
GameCreationAppAssetKind::Character
)
.is_err(),
"{bad}" "{bad}"
); );
} }
// 其它 kind 携带该字段直接拒绝,即使取值合法。 // 其它 kind 携带该字段直接拒绝,即使取值合法。
let gated = let gated = normalize_generate_image_screen_color(
normalize_generate_image_screen_color(&json!({"screenColor": "#CFEFFF"}), "image") &json!({"screenColor": "#CFEFFF"}),
.expect_err("screenColor must stay scoped to character/art-spritesheet"); GameCreationAppAssetKind::Image,
)
.expect_err("screenColor must stay scoped to character/icon-spritesheet");
assert!(gated.contains("kind=character"), "{gated}"); assert!(gated.contains("kind=character"), "{gated}");
assert!(normalize_generate_image_screen_color( assert!(normalize_generate_image_screen_color(
&json!({"screenColor": "auto"}), &json!({"screenColor": "auto"}),
"ui-prototype" GameCreationAppAssetKind::UiDesign
) )
.is_err()); .is_err());
} }
@@ -3746,12 +3794,14 @@ mod tests {
fn bridge_art_resource_exposes_only_the_safe_identity_projection() { fn bridge_art_resource_exposes_only_the_safe_identity_projection() {
let asset = GameCreationAppAssetManifestEntry { let asset = GameCreationAppAssetManifestEntry {
id: "local-art-1".to_string(), id: "local-art-1".to_string(),
kind: "art-spritesheet-slice".to_string(), kind: GameCreationAppAssetKind::Icon,
media_type: "image/png".to_string(), media_type: "image/png".to_string(),
local_path: "assets/art-spritesheet-slices/player.png".to_string(), local_path: "assets/art-spritesheet-slices/player.png".to_string(),
image_sequence_frames: None, image_sequence_frames: None,
image_sequence_duration_ms: None, image_sequence_duration_ms: None,
category: game_creation_app_asset_category_for_kind("art-spritesheet-slice"), category: game_creation_app_asset_category_for_kind(
GameCreationAppAssetKind::IconSpritesheet,
),
tags: Vec::new(), tags: Vec::new(),
source: GameCreationAppAssetSource { source: GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Canvas, kind: GameCreationAppAssetSourceKind::Canvas,
@@ -3803,7 +3853,7 @@ mod tests {
fn bridge_registered_resource_exposes_formal_sequence_without_private_generation_fields() { fn bridge_registered_resource_exposes_formal_sequence_without_private_generation_fields() {
let asset = GameCreationAppAssetManifestEntry { let asset = GameCreationAppAssetManifestEntry {
id: "animation-1".to_string(), id: "animation-1".to_string(),
kind: "character-animation".to_string(), kind: GameCreationAppAssetKind::CharacterAnimation,
media_type: "video/mp4".to_string(), media_type: "video/mp4".to_string(),
local_path: "assets/edits/animation.mp4".to_string(), local_path: "assets/edits/animation.mp4".to_string(),
image_sequence_frames: Some(vec![ image_sequence_frames: Some(vec![
@@ -3816,7 +3866,9 @@ mod tests {
}, },
]), ]),
image_sequence_duration_ms: Some(4_000), image_sequence_duration_ms: Some(4_000),
category: game_creation_app_asset_category_for_kind("character-animation"), category: game_creation_app_asset_category_for_kind(
GameCreationAppAssetKind::CharacterAnimation,
),
tags: Vec::new(), tags: Vec::new(),
source: GameCreationAppAssetSource { source: GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Canvas, kind: GameCreationAppAssetSourceKind::Canvas,
@@ -3854,7 +3906,7 @@ mod tests {
fn bridge_registered_resource_keeps_explicit_manifest_classification() { fn bridge_registered_resource_keeps_explicit_manifest_classification() {
let asset = GameCreationAppAssetManifestEntry { let asset = GameCreationAppAssetManifestEntry {
id: "spec-1".to_string(), id: "spec-1".to_string(),
kind: "spec".to_string(), kind: GameCreationAppAssetKind::Spec,
media_type: "application/json".to_string(), media_type: "application/json".to_string(),
local_path: "assets/specs/hero.json".to_string(), local_path: "assets/specs/hero.json".to_string(),
image_sequence_frames: None, image_sequence_frames: None,
@@ -3890,13 +3942,13 @@ mod tests {
/// ///
/// UI 栏目走 TS 的 `gameCreationAppAssetCategory`(落盘值 + 按 kind 派生 + 读时自愈), /// UI 栏目走 TS 的 `gameCreationAppAssetCategory`(落盘值 + 按 kind 派生 + 读时自愈),
/// Agent 走 Rust 的同构实现 `game_creation_app_asset_effective_category`。直接透传落盘 /// Agent 走 Rust 的同构实现 `game_creation_app_asset_effective_category`。直接透传落盘
/// `category` 会让 `kind:"ui"` 的资产在 UI 显示「UI 交互」、在 Agent 侧读到 /// `category` 会让 `ui-design` 的资产在 UI 显示「UI 交互」、在 Agent 侧读到
/// `unclassified`——真机 122 条资产里有 55 条这样分叉 /// `unclassified`,两侧分类口径必须一致
#[test] #[test]
fn bridge_registered_resource_projects_effective_category_not_raw_persisted_value() { fn bridge_registered_resource_projects_effective_category_not_raw_persisted_value() {
let asset = GameCreationAppAssetManifestEntry { let asset = GameCreationAppAssetManifestEntry {
id: "ui-1".to_string(), id: "ui-design-1".to_string(),
kind: "ui".to_string(), kind: GameCreationAppAssetKind::UiDesign,
media_type: "application/json".to_string(), media_type: "application/json".to_string(),
local_path: "assets/UI 设计 1.json".to_string(), local_path: "assets/UI 设计 1.json".to_string(),
image_sequence_frames: None, image_sequence_frames: None,
@@ -3919,7 +3971,7 @@ mod tests {
}; };
let projection = bridge_registered_resource(&asset, false); let projection = bridge_registered_resource(&asset, false);
assert_eq!(projection["kind"], "ui"); assert_eq!(projection["kind"], "ui-design");
assert_eq!(projection["category"], "ui-interaction"); assert_eq!(projection["category"], "ui-interaction");
} }
@@ -247,7 +247,7 @@ fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_availab
"type": "string", "type": "string",
"enum": PLATFORM_ART_ASSET_GENERATION_KINDS, "enum": PLATFORM_ART_ASSET_GENERATION_KINDS,
"default": "image", "default": "image",
"description": "image=普通新图(不做额外处理),character=角色图(纯色底生成后自动抠图,产出透明背景立绘,prompt 只描述角色主体),spec/icon-spec=统一视觉规范图(spec 是服务端同义词,客户端统一登记为 icon-spec),ui-prototype=完整 UI 设计图,art-spritesheet=透明游戏素材图集(纯色底生成后自动抠图并切片,项目须已有 icon-spec 规范图),publication-material=发布宣传图" "description": "image=普通新图(不做额外处理),character=角色图(纯色底生成后自动抠图,产出透明背景立绘,prompt 只描述角色主体),icon-spec=统一视觉规范图ui-design=完整 UI 设计图,icon-spritesheet=透明游戏素材图集(纯色底生成后自动抠图并切片,项目须已有 icon-spec 规范图),publication-material=发布宣传图"
}, },
"aspectRatio": { "aspectRatio": {
"type": "string", "type": "string",
@@ -273,7 +273,7 @@ fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_availab
"sliceMode": { "sliceMode": {
"type": "string", "type": "string",
"enum": ["connected-components", "grid"], "enum": ["connected-components", "grid"],
"description": "仅 kind=art-spritesheet 生效,且必填、没有默认值:需求明确要求等分网格、固定槽位或指定行列数时传 grid,并用 gridX/gridY 传入来自需求本身的行列数;自由排布、数量不定或只要求一张图集时传 connected-components,需要约束素材张数时用 sliceCount。省略、与 kind 不匹配或与 gridX/gridY 互相矛盾时客户端直接拒绝,不会替你选择" "description": "仅 kind=icon-spritesheet 生效,且必填、没有默认值:需求明确要求等分网格、固定槽位或指定行列数时传 grid,并用 gridX/gridY 传入来自需求本身的行列数;自由排布、数量不定或只要求一张图集时传 connected-components,需要约束素材张数时用 sliceCount。省略、与 kind 不匹配或与 gridX/gridY 互相矛盾时客户端直接拒绝,不会替你选择"
}, },
"gridX": { "gridX": {
"type": "integer", "type": "integer",
@@ -291,11 +291,11 @@ fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_availab
"type": "integer", "type": "integer",
"minimum": 1, "minimum": 1,
"maximum": 256, "maximum": 256,
"description": "只与 kind=art-spritesheet 且 sliceMode=connected-components 同时提供,用于约束目标素材张数;省略时按图像内容自动识别" "description": "只与 kind=icon-spritesheet 且 sliceMode=connected-components 同时提供,用于约束目标素材张数;省略时按图像内容自动识别"
}, },
"screenColor": { "screenColor": {
"type": "string", "type": "string",
"description": "抠图纯色背景,仅 kind=character(角色形象)和 kind=art-spritesheet(图标素材)生效,其它 kind 携带会被拒绝。生成时把主体置于该纯色背景上,回图后据此抠除背景。取值只能是 auto 或下列色板 hex 之一,传值只填 hex 本身、不要附带色名:#CFEFFF(浅雾蓝)、#B0C2E0(浅钢蓝)、#FFD6C2(暖浅桃色)、#E6D8FF(淡薰衣草紫)、#F4D8E8(浅粉灰)、#7FB3FF(中度天蓝)、#FFF2A8(浅柠黄)、#CFFFE1(淡薄荷绿)、#D8DEE8(浅中性灰)、#D8D2E8(淡灰紫)、#A8F7F0(高对比浅青)、#A0BBA0(灰竹绿);auto 时由服务端自动选色。手动指定时不能与角色或素材本体的颜色接近" "description": "抠图纯色背景,仅 kind=character(角色形象)和 kind=icon-spritesheet(图标素材)生效,其它 kind 携带会被拒绝。生成时把主体置于该纯色背景上,回图后据此抠除背景。取值只能是 auto 或下列色板 hex 之一,传值只填 hex 本身、不要附带色名:#CFEFFF(浅雾蓝)、#B0C2E0(浅钢蓝)、#FFD6C2(暖浅桃色)、#E6D8FF(淡薰衣草紫)、#F4D8E8(浅粉灰)、#7FB3FF(中度天蓝)、#FFF2A8(浅柠黄)、#CFFFE1(淡薄荷绿)、#D8DEE8(浅中性灰)、#D8D2E8(淡灰紫)、#A8F7F0(高对比浅青)、#A0BBA0(灰竹绿);auto 时由服务端自动选色。手动指定时不能与角色或素材本体的颜色接近"
} }
}, },
"required": ["prompt"], "required": ["prompt"],
@@ -341,7 +341,7 @@ fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_availab
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
"maxLength": 80, "maxLength": 80,
"description": "可选的 manifest 资源 kind 精确过滤,例如 video、character-animation、sound-effect、background-music 或 art-spritesheet-slice" "description": "可选的 manifest 资源 kind 精确过滤,必须是 canonical kind例如 video、character-animation、sound-effect、background-music、icon-spritesheet);传非 canonical 值会被拒绝并报错,不会静默返回空列表"
}, },
"assetId": { "assetId": {
"type": "string", "type": "string",
@@ -2523,7 +2523,7 @@ mod tests {
// 目录外的 kind 必须在本地拒绝:既不下发 bridge,也不产生任何计费副作用。 // 目录外的 kind 必须在本地拒绝:既不下发 bridge,也不产生任何计费副作用。
let unsupported_kind = call_agc_generate_image(&json!({ let unsupported_kind = call_agc_generate_image(&json!({
"prompt": "像素月光主角", "prompt": "像素月光主角",
"kind": "game-art" "kind": "future-kind"
})) }))
.await; .await;
assert_eq!(unsupported_kind["isError"], json!(true)); assert_eq!(unsupported_kind["isError"], json!(true));
@@ -2533,7 +2533,7 @@ mod tests {
let oversized_prompt = call_agc_generate_image(&json!({ let oversized_prompt = call_agc_generate_image(&json!({
"prompt": "x".repeat(DIRECT_TOOLS_MCP_MAX_IMAGE_PROMPT_CHARS + 1), "prompt": "x".repeat(DIRECT_TOOLS_MCP_MAX_IMAGE_PROMPT_CHARS + 1),
"kind": "art-spritesheet" "kind": "icon-spritesheet"
})) }))
.await; .await;
assert_eq!(oversized_prompt["isError"], json!(true)); assert_eq!(oversized_prompt["isError"], json!(true));
@@ -2897,12 +2897,12 @@ mod tests {
fn tool_chain_image_asset(id: &str, local_path: &str) -> GameCreationAppAssetManifestEntry { fn tool_chain_image_asset(id: &str, local_path: &str) -> GameCreationAppAssetManifestEntry {
GameCreationAppAssetManifestEntry { GameCreationAppAssetManifestEntry {
id: id.to_string(), id: id.to_string(),
kind: "image".to_string(), kind: GameCreationAppAssetKind::Image,
media_type: "image/png".to_string(), media_type: "image/png".to_string(),
local_path: local_path.to_string(), local_path: local_path.to_string(),
image_sequence_frames: None, image_sequence_frames: None,
image_sequence_duration_ms: None, image_sequence_duration_ms: None,
category: game_creation_app_asset_category_for_kind("image"), category: game_creation_app_asset_category_for_kind(GameCreationAppAssetKind::Image),
tags: Vec::new(), tags: Vec::new(),
source: GameCreationAppAssetSource { source: GameCreationAppAssetSource {
kind: GameCreationAppAssetSourceKind::Canvas, kind: GameCreationAppAssetSourceKind::Canvas,
File diff suppressed because it is too large Load Diff
@@ -31,9 +31,9 @@ fn is_direct_platform_art_generation_runtime_context(
context: &PlatformArtGenerationRuntimeContext, context: &PlatformArtGenerationRuntimeContext,
) -> bool { ) -> bool {
let asset_kind = match context.run_id.as_str() { let asset_kind = match context.run_id.as_str() {
"art-spec" => "icon-spec", "art-spec" => GameCreationAppAssetKind::IconSpec,
"game-background" => "game-background", "game-background" => GameCreationAppAssetKind::Scene,
"art-spritesheet" => "art-spritesheet", "art-spritesheet" => GameCreationAppAssetKind::IconSpritesheet,
_ => return false, _ => return false,
}; };
context.agent_id == "direct-codex-art" context.agent_id == "direct-codex-art"
@@ -42,7 +42,11 @@ fn is_direct_platform_art_generation_runtime_context(
&& context.source == "direct-codex" && context.source == "direct-codex"
&& context.action_id == format!("direct-taonier-{}", context.run_id) && context.action_id == format!("direct-taonier-{}", context.run_id)
&& context.action_fingerprint && context.action_fingerprint
== format!("direct-taonier-art-v1:{asset_kind}:{}", context.run_id) == format!(
"direct-taonier-art-v1:{}:{}",
asset_kind.as_str(),
context.run_id
)
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@@ -98,7 +98,7 @@ pub(crate) fn render_local_asset_prompt_context(root: &Path) -> Result<String, S
output.push_str("- "); output.push_str("- ");
output.push_str(&asset.id); output.push_str(&asset.id);
output.push_str(": "); output.push_str(": ");
output.push_str(&asset.kind); output.push_str(asset.kind.as_str());
output.push_str(" / "); output.push_str(" / ");
output.push_str(&asset.media_type); output.push_str(&asset.media_type);
output.push_str(" / "); output.push_str(" / ");
@@ -396,7 +396,7 @@ fn agent_runtime_action_receipt_safe_detail_with_owner(
let inspection_kind = detail let inspection_kind = detail
.get("inspectionKind") .get("inspectionKind")
.and_then(serde_json::Value::as_str) .and_then(serde_json::Value::as_str)
.filter(|value| *value == AGENT_RUNTIME_UI_PROTOTYPE_INSPECTION_KIND); .filter(|value| *value == AGENT_RUNTIME_UI_DESIGN_INSPECTION_KIND);
let validation_profile = detail let validation_profile = detail
.get("validationProfile") .get("validationProfile")
.and_then(serde_json::Value::as_str) .and_then(serde_json::Value::as_str)
@@ -408,7 +408,7 @@ fn agent_runtime_action_receipt_safe_detail_with_owner(
) )
}); });
let (passed, checks, issues) = if validation_profile.is_some() { let (passed, checks, issues) = if validation_profile.is_some() {
if inspection_kind != Some(AGENT_RUNTIME_UI_PROTOTYPE_INSPECTION_KIND) { if inspection_kind != Some(AGENT_RUNTIME_UI_DESIGN_INSPECTION_KIND) {
return None; return None;
} }
let passed = detail.get("passed")?.as_bool()?; let passed = detail.get("passed")?.as_bool()?;
@@ -2061,17 +2061,23 @@ mod tests {
.expect("resume first manifest task fixture"); .expect("resume first manifest task fixture");
for (local_path, kind) in [ for (local_path, kind) in [
("assets/art-spec.png", "icon-spec"), ("assets/art-spec.png", GameCreationAppAssetKind::IconSpec),
("assets/ui-prototype.png", "ui-prototype"), (
("assets/art-spritesheet.png", "art-spritesheet"), "assets/ui-prototype.png",
GameCreationAppAssetKind::UiDesign,
),
(
"assets/art-spritesheet.png",
GameCreationAppAssetKind::IconSpritesheet,
),
] { ] {
let (generation_route, generation_kind, reference_resource_ids) = match kind { let (generation_route, generation_kind, reference_resource_ids) = match kind {
"icon-spec" => ( GameCreationAppAssetKind::IconSpec => (
"/api/external/v1/editor/images/generations", "/api/external/v1/editor/images/generations",
"spec", "spec",
Vec::new(), Vec::new(),
), ),
"ui-prototype" => ( GameCreationAppAssetKind::UiDesign => (
"/api/external/v1/editor/images/generations", "/api/external/v1/editor/images/generations",
"ui-design", "ui-design",
vec!["resource-icon-spec".to_string()], vec!["resource-icon-spec".to_string()],
@@ -2082,7 +2088,7 @@ mod tests {
vec!["resource-icon-spec".to_string()], vec!["resource-icon-spec".to_string()],
), ),
}; };
let bytes = if kind == "art-spritesheet" { let bytes = if kind == GameCreationAppAssetKind::IconSpritesheet {
use image::ImageEncoder; use image::ImageEncoder;
let mut bytes = Vec::new(); let mut bytes = Vec::new();
image::codecs::png::PngEncoder::new(&mut bytes) image::codecs::png::PngEncoder::new(&mut bytes)
@@ -1136,7 +1136,7 @@ pub(in crate::agent) fn ui_prototype_visual_inspection_blocker_detail_at_locked(
&& record && record
.get("inspectionKind") .get("inspectionKind")
.and_then(serde_json::Value::as_str) .and_then(serde_json::Value::as_str)
== Some(AGENT_RUNTIME_UI_PROTOTYPE_INSPECTION_KIND) == Some(AGENT_RUNTIME_UI_DESIGN_INSPECTION_KIND)
&& record && record
.get("validationProfile") .get("validationProfile")
.and_then(serde_json::Value::as_str) .and_then(serde_json::Value::as_str)
@@ -1216,11 +1216,19 @@ pub(in crate::agent) fn visual_asset_completion_blocker_at_locked(
return None; return None;
} }
let (expected_path, expected_kind, label) = match agent_id { let (expected_path, expected_kind, label) = match agent_id {
"art-director" => (AGENT_RUNTIME_ART_SPEC_PATH, "icon-spec", "统一视觉规范图"), "art-director" => (
"design-foundation" => ("assets/ui-prototype.png", "ui-prototype", "策划界面原型图"), AGENT_RUNTIME_ART_SPEC_PATH,
GameCreationAppAssetKind::IconSpec,
"统一视觉规范图",
),
"design-foundation" => (
"assets/ui-prototype.png",
GameCreationAppAssetKind::UiDesign,
"策划界面原型图",
),
"art-asset-plan" => ( "art-asset-plan" => (
"assets/art-spritesheet.png", "assets/art-spritesheet.png",
"art-spritesheet", GameCreationAppAssetKind::IconSpritesheet,
"首版美术素材图", "首版美术素材图",
), ),
_ => return None, _ => return None,
@@ -336,7 +336,11 @@ fn build_game_creator_agent_background_tool_plan_request_at(
render_game_creator_agent_runtime_steers_for_prompt(root, agent_id, session_id, run_id)?; render_game_creator_agent_runtime_steers_for_prompt(root, agent_id, session_id, run_id)?;
let loop_index = loop_index.saturating_add(1); let loop_index = loop_index.saturating_add(1);
let context_preload_notice = game_creator_agent_context_preload_notice(agent_id); let context_preload_notice = game_creator_agent_context_preload_notice(agent_id);
let canvas_asset_kind_catalog = AGENT_RUNTIME_CANVAS_ASSET_KINDS.join("|"); let canvas_asset_kind_catalog = AGENT_RUNTIME_CANVAS_ASSET_KINDS
.iter()
.map(|kind| kind.as_str())
.collect::<Vec<_>>()
.join("|");
let limited_command_contract = if runtime_owner_artifact_validation_available { let limited_command_contract = if runtime_owner_artifact_validation_available {
"当前固定 owner 不得调用 command.run_limited 或 project.verify;完成固定正式产物后直接交付,由 Runtime 在收束门内执行结构验证。".to_string() "当前固定 owner 不得调用 command.run_limited 或 project.verify;完成固定正式产物后直接交付,由 Runtime 在收束门内执行结构验证。".to_string()
} else { } else {
@@ -1,7 +1,7 @@
use super::*; use super::*;
pub(crate) const AGENT_RUNTIME_CANVAS_ASSET_KINDS: &[&str] = pub(crate) const AGENT_RUNTIME_CANVAS_ASSET_KINDS: &[GameCreationAppAssetKind] =
&["game-art", "icon-spec", "ui-prototype", "art-spritesheet"]; GameCreationAppAssetKind::CANVAS_ASSET_KINDS;
#[cfg(test)] #[cfg(test)]
mod canvas_asset_kind_contract_tests { mod canvas_asset_kind_contract_tests {
@@ -11,7 +11,12 @@ mod canvas_asset_kind_contract_tests {
fn canvas_asset_kind_catalog_preserves_authoritative_contract() { fn canvas_asset_kind_catalog_preserves_authoritative_contract() {
assert_eq!( assert_eq!(
AGENT_RUNTIME_CANVAS_ASSET_KINDS, AGENT_RUNTIME_CANVAS_ASSET_KINDS,
&["game-art", "icon-spec", "ui-prototype", "art-spritesheet"] &[
GameCreationAppAssetKind::Image,
GameCreationAppAssetKind::IconSpec,
GameCreationAppAssetKind::UiDesign,
GameCreationAppAssetKind::IconSpritesheet,
]
); );
} }
} }
@@ -16,7 +16,7 @@ pub(in crate::agent) fn art_manifest_content() -> String {
"status": "generated", "status": "generated",
"assets": [{ "assets": [{
"path": ART_SPRITESHEET_PATH, "path": ART_SPRITESHEET_PATH,
"kind": "art-spritesheet", "kind": GameCreationAppAssetKind::IconSpritesheet.as_str(),
"usage": REQUIRED_SLICE_USAGES, "usage": REQUIRED_SLICE_USAGES,
}], }],
"sliceManifest": ART_SLICE_MANIFEST_PATH, "sliceManifest": ART_SLICE_MANIFEST_PATH,
@@ -8,12 +8,12 @@ pub(in crate::agent) fn autonomous_registered_derived_visuals_need_repair_at(roo
( (
"design-foundation", "design-foundation",
"assets/ui-prototype.png", "assets/ui-prototype.png",
"ui-prototype", GameCreationAppAssetKind::UiDesign,
), ),
( (
"art-asset-plan", "art-asset-plan",
"assets/art-spritesheet.png", "assets/art-spritesheet.png",
"art-spritesheet", GameCreationAppAssetKind::IconSpritesheet,
), ),
] ]
.into_iter() .into_iter()
@@ -644,9 +644,9 @@ fn standalone_platform_art_generation_ledger_identity_is_valid(
}; };
let direct_identity_is_valid = if agent_id == "direct-codex-art" { let direct_identity_is_valid = if agent_id == "direct-codex-art" {
let expected_asset_kind = match run_id { let expected_asset_kind = match run_id {
"art-spec" => Some("icon-spec"), "art-spec" => Some(GameCreationAppAssetKind::IconSpec),
"game-background" => Some("game-background"), "game-background" => Some(GameCreationAppAssetKind::Scene),
"art-spritesheet" => Some("art-spritesheet"), "art-spritesheet" => Some(GameCreationAppAssetKind::IconSpritesheet),
_ => None, _ => None,
}; };
expected_asset_kind.is_some_and(|asset_kind| { expected_asset_kind.is_some_and(|asset_kind| {
@@ -655,7 +655,9 @@ fn standalone_platform_art_generation_ledger_identity_is_valid(
&& string_field("source") == Some("direct-codex") && string_field("source") == Some("direct-codex")
&& string_field("actionId") == Some(format!("direct-taonier-{run_id}").as_str()) && string_field("actionId") == Some(format!("direct-taonier-{run_id}").as_str())
&& string_field("actionFingerprint") && string_field("actionFingerprint")
== Some(format!("direct-taonier-art-v1:{asset_kind}:{run_id}").as_str()) == Some(
format!("direct-taonier-art-v1:{}:{run_id}", asset_kind.as_str()).as_str(),
)
}) })
} else { } else {
false false
@@ -1561,10 +1563,14 @@ mod orphaned_external_generation_recovery_tests {
fs::create_dir_all(workflow_path.parent().expect("workflow parent")) fs::create_dir_all(workflow_path.parent().expect("workflow parent"))
.expect("workflow directory"); .expect("workflow directory");
let rollback_assets = [ let rollback_assets = [
("assets/art-spec.png", "icon-spec", b"old-spec".as_slice()), (
"assets/art-spec.png",
GameCreationAppAssetKind::IconSpec,
b"old-spec".as_slice(),
),
( (
"assets/direct-game-background.png", "assets/direct-game-background.png",
"game-background", GameCreationAppAssetKind::Scene,
b"old-background".as_slice(), b"old-background".as_slice(),
), ),
] ]
@@ -1702,7 +1708,7 @@ mod orphaned_external_generation_recovery_tests {
write_complete_stage(serde_json::json!({ write_complete_stage(serde_json::json!({
"prompt": "Direct art spec", "prompt": "Direct art spec",
"kind": "spec", "kind": "spec",
"assetKind": "game-background", "assetKind": "scene",
"projectId": "canvas-project", "projectId": "canvas-project",
"assetFolderId": "canvas-assets", "assetFolderId": "canvas-assets",
"generationInputs": { "generationInputs": {

Some files were not shown because too many files have changed in this diff Show More