把 AI 项目命名接进「生成任务」体系:账本 v2 任务类型、命名任务入队与推进、面板命名行

- 账本 v2:记录新增 taskType(asset-generation / project-naming,缺省 asset-generation,旧壳按素材任务读回),kind 改为可选;schema 升 agc-asset-generation-task.v2,读取同时接受 v1 与 v2。
- 新增 enqueue_local_project_naming_task / update_local_project_naming_task:命名任务入队时登记 live、终态写盘后摘除(写盘失败只回滚本轮插入),中断残留按命名口径收口为 failed(文案不含「目标素材」)。
- 首页命名链路改为推进这条任务:入队 → 命名中 → 已完成(已应用 AI 名称 / 建议名与现状一致 / 用户已手动改名而跳过)或失败(无可用名称 / 自动改名失败,均保留兜底名);账本只作展示旁路,推进失败只写诊断日志。
- 前端模型与面板支持命名行:按 taskType 把命名记录从素材任务分支剔除,命名行固定展示名「AI 项目命名」、只在终态显示结论、不渲染缩略图/提示词/定位动作;命令名写字面量以满足 check-config 静态枚举门禁。
- 工作台订阅账本变更事件做一次受控快照读取刷新命名行(不新增固定频率轮询);另修:命名行的 useLiveNow 收进独立组件,避免行种类变化导致 hooks 数量变化。
- 测试:homeProjectNamingAsync 12 例(新增入队→命名中→终态推进序列断言)、projectNamingGenerationTaskRow 3 例、appSurface 建项后命名行可见 + 事件刷新出终态结论 + 标题/列表同步;Rust asset_generation_task 22 例(含 naming_task_stays_in_flight_across_ledger_reads_until_terminal、legacy_v1_ledger_reads_back_*)。
- 文档:decision-log 改写为「生成任务体系里的后台任务」、pitfalls 新增 taskType/live 集合条目、实施计划专题补任务接入口径。
- 门禁:AGC 全量 1925 passed / 17 skipped;cargo 定向 22 + 6;npm run agc:typecheck(含 check:tests:types 与 check-config);cargo fmt --check;check:encoding;git diff --check。
This commit is contained in:
2026-10-04 16:59:59 +08:00
parent 459fe5b432
commit a554b0c7ca
13 changed files with 1325 additions and 70 deletions
@@ -3,10 +3,11 @@
## 2026-10-03 首页自动建项与 AI 项目命名解耦(Issue 599)
- 背景:首页「开启创作」原先串行执行「Web 预检 → `await suggest_automatic_project_name` → `create_automatic_local_game_project`」。项目名称不是创建工作区、导入附件或发起首轮创作的前置条件,命名请求(`AUTOMATIC_PROJECT_NAME_TIMEOUT_MS = 15s`,正常请求同样占时)却把用户按在「正在创建工作区」上。
- 决策:建项与命名拆成两条并行链路。建项固定传 `name: null`,由宿主既有兜底名(`GameAgent 项目 <8 位短 id>` / `策划项目 <8 位短 id>`)落盘并立即进入项目;命名请求在建项前打出、结果交给后台任务。后台拿到合法名称后调用新增的 `rename_local_game_project_if_unchanged(projectPath, expectedProjectId, expectedName, name)`:只有「项目 ID 相同」且「当前名称仍是本次创建的兜底名」才改名,返回 `renamed: true/false`(跳过时不写盘、不是错误)。用户已手动改名、项目 ID 不符、名称与现状相同一律跳过;空名 / 控制字符 / 超长由 `normalize_game_creation_project_name` 失败关闭。
- 边界:改名是簿记写入,与既有 `rename_local_game_project` 同口径**不推进项目 revision**(推 revision 会让运行时验证凭证无故漂移);返回的 `revision` 是当时盘上的值。后台改名结果写「当前项目上下文」与「最近项目行重检」必须等建项主体收尾(`entrySettled`):AI 比进项目更快时直接写上下文会被随后的 `enterProjectDevelopment` 用兜底名覆盖,最近项目行也要等进项目登记过才会被重检;写入前再过壳的生命周期守卫(`mounted` + 代次),关窗/卸载后只保留已落盘的改名、不写 UI 投影。「做方案」与手动选目录建项不发起自动命名。条件改名的 `expectedProjectId` / `expectedName` 为空时按仓库同类入口口径失败关闭(而不是静默 `renamed: false`)。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/{commands.rs,desktop.rs}`、`apps/ai-game-creator-shell/src/{app/types.ts,features/app-shell/{useHomeProjectCreation.ts,WorkspaceLauncher.tsx}}`、`apps/ai-game-creator-shell/tests/{homeProjectNamingAsync.test.tsx,appSurface/home.suite.ts}`。
- 验证:`npx vitest run apps/ai-game-creator-shell/tests/homeProjectNamingAsync.test.tsx`(12 passed,含「命名请求永不返回仍进工作区」「AI 结果先于进项目落定仍不被兜底名覆盖」「手动改名不被覆盖」「非法/空响应保留兜底名」「做方案不发起命名」「切到别的工作区不被劫持」「卸载/pagehide 后不写上下文」);AGC 全量 `npm run test -- apps/ai-game-creator-shell/tests`(1922 passed / 17 skipped,合并 `origin/master` tip `ccd271572` 后复测);`cargo test --features=cocos-editor-execute,unity-editor-execute,godot-editor-execute conditional_project_rename_tests`(6 passed,含空 `expectedProjectId` / `expectedName` 失败关闭);`npm run agc:typecheck`(含 `check:tests:types`)、`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
- 决策(建项与命名解耦):建项固定传 `name: null`,由宿主既有兜底名(`GameAgent 项目 <8 位短 id>` / `策划项目 <8 位短 id>`)落盘并立即进入项目;命名请求在建项前并行打出、结果交给后台任务。后台拿到合法名称后调用新增的 `rename_local_game_project_if_unchanged(projectPath, expectedProjectId, expectedName, name)`:只有「项目 ID 相同」且「当前名称仍是本次创建的兜底名」才改名,返回 `renamed: true/false`(跳过时不写盘、不是错误)。用户已手动改名、项目 ID 不符、名称与现状相同一律跳过;空 `expectedProjectId` / `expectedName`、空名 / 控制字符 / 超长一律失败关闭。
- 决策(纳入「生成任务」体系):自动命名做成**项目工作台那条既有「生成任务」列表里的一条后台任务**,而不只是页面里的一个 promise。账本记录加 `taskType`(`asset-generation` / `project-naming`,缺省 `asset-generation`)、`kind` 变可选,schema 升 `agc-asset-generation-task.v2`(读取同时接受 v1/v2,v1 记录按素材任务读回);新增 `enqueue_local_project_naming_task`(排队中)/ `update_local_project_naming_task`(命名中 → 已完成/失败),展示名固定「AI 项目命名」,与素材生成共用同一份账本、同一个变更事件与同一个侧栏。状态流转:排队中 → 命名中 → 已完成(已应用 AI 名称 / 建议名与现状一致 / 用户已手动改名而跳过)或失败(无可用名称 / 自动改名失败,均保留兜底名)。命名任务由命名链路推进、**不进 live 集合就会被中断收口误判**,所以 enqueue 写账本前登记 live、update 终态写盘后摘除(中断残留仍按命名口径收口为失败)。前端按 `taskType` 把命名记录从素材任务分支里剔除,命名行不渲染缩略图/提示词/派发与定位动作,结论只在终态显示。
- 边界:改名是簿记写入,与既有 `rename_local_game_project` 同口径**不推进项目 revision**(推 revision 会让运行时验证凭证无故漂移);返回的 `revision` 是当时盘上的值。后台改名结果写「当前项目上下文」与「最近项目行重检」必须等建项主体收尾(`entrySettled`):AI 比进项目更快时直接写上下文会被随后的 `enterProjectDevelopment` 用兜底名覆盖,最近项目行也要等进项目登记过才会被重检;写入前再过壳的生命周期守卫(`mounted` + 代次),关窗/卸载后只保留已落盘的改名,不写 UI 投影。账本是**展示旁路**:`enqueue` / `update` 失败只写诊断日志,绝不影响建项、命名与首轮创作;「做方案」与手动选目录建项不发起自动命名。本次未改共享契约(`packages/shared/**`)、server-rs 与任何 SpacetimeDB schema/HTTP 路由。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/{commands.rs,desktop.rs,asset_generation_tasks.rs,asset_generation_tasks/runtime.rs}`、`apps/ai-game-creator-shell/src/{app/types.ts,features/app-shell/{useHomeProjectCreation.ts,WorkspaceLauncher.tsx},features/resource-canvas/{resourceCanvasAssetGenerationTaskModel.ts,ResourceCanvasAssetGenerationTasksPanelView.tsx},view/project-development/index.tsx}`、`apps/ai-game-creator-shell/tests/{homeProjectNamingAsync.test.tsx,projectNamingGenerationTaskRow.test.tsx,appSurface/home.suite.ts}`。
- 验证:`npx vitest run apps/ai-game-creator-shell/tests/homeProjectNamingAsync.test.tsx`(12 passed,含「命名请求永不返回仍进工作区」「AI 结果先于进项目落定仍不被兜底名覆盖」「手动改名不被覆盖且任务按已完成+已跳过收口」「非法/空响应 → 任务 failed 且保留兜底名」「做方案不发起命名」「切到别的工作区不被劫持」「卸载/pagehide 后不写上下文」,并断言入队→命名中→终态的账本推进序列);`projectNamingGenerationTaskRow.test.tsx`(3 passed:固定展示名/不渲染缩略图提示词定位、终态才显示结论、失败徽章与原因 + 在途计数);AGC 全量 `npm run test -- apps/ai-game-creator-shell/tests`(1925 passed / 17 skipped);`cargo test --features=cocos-editor-execute,unity-editor-execute,godot-editor-execute asset_generation_task`(22 passed,含 `naming_task_stays_in_flight_across_ledger_reads_until_terminal` 与 `legacy_v1_ledger_reads_back_with_the_default_asset_generation_task_type`)与 `conditional_project_rename_tests`(6 passed);`npm run agc:typecheck`(含 `check:tests:types`)、`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
## 2026-10-03 AGC 栏目画布上传素材按入口栏目登记(Issue 359)
@@ -2,6 +2,15 @@
这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。
## 2026-10-04 把非素材任务塞进「生成任务」账本:taskType 维度、v1 兼容与 live 集合
- **现象**:首页「AI 项目命名」改成进「生成任务」列表后,第一次读账本(工作台打开项目就会读)就把那条还在跑的命名任务标成**失败**;随后前端的终态推进被拒(记录已终态)。
- **根因**:账本的中断收口判据是「非终态 + 不在本进程 live 集合」= 上次运行的残留。素材生成任务由 Rust 后台任务在 `start` 里先登记 live 再落账本;项目命名任务由前端驱动(`enqueue` / `update` 两条 IPC),没有登记 live,于是任何一次 `list_local_project_asset_generations` 都把它当成残留收口。
- **处理(现行口径)**:账本记录加 `taskType`(`asset-generation` / `project-naming`,缺省 `asset-generation`)、`kind` 变可选;schema 升 `agc-asset-generation-task.v2`,读取同时接受 v1 与 v2(v1 记录按素材任务读回)。`enqueue_local_project_naming_task` 写账本前登记 live(写盘失败只回滚本轮插入的那条),`update_local_project_naming_task` 在非终态时幂等登记、终态写盘成功后摘除。命名没有素材可交叉核对,中断收口一律 failed 且文案与素材口径区分(不含「目标素材」)。
- **边界**:命名任务的结论文案(已应用 / 用户已改名跳过 / 失败保留兜底名)由命名链路给出;账本只是展示旁路——`enqueue` / `update` 失败只写诊断日志,绝不影响建项、命名与首轮创作。前端按 `taskType` 把命名记录从素材任务列表里剔除(素材分支会按 `kind` 解析),命名行固定展示名「AI 项目命名」,只在终态显示结论。
- **判据/取证**:Rust `cargo test … asset_generation_task` 22 条(含 `naming_task_stays_in_flight_across_ledger_reads_until_terminal`、`legacy_v1_ledger_reads_back_with_the_default_asset_generation_task_type`、`interrupted_naming_task_is_settled_as_failed_with_naming_wording`);前端 `homeProjectNamingAsync.test.tsx`(入队→running→终态的推进序列)、`projectNamingGenerationTaskRow.test.tsx`(行渲染)、appSurface 的「syncs the workbench title and recent project list with the AI name after creation」(建项后行可见 + 事件驱动刷新出终态结论)。
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/asset_generation_tasks.rs`、`apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts`、`apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationTaskModel.ts`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`。
## 2026-10-03 后台异步回填被「进项目」覆盖:AI 项目名只在兜底名仍成立时改
- **现象**(Issue 599):首页建项把 AI 命名改成「与建项并行、结果后台回填」后,偶尔工作台标题停在兜底名(`GameAgent 项目 <短 id>`),而磁盘 manifest 已经是 AI 名字。