Merge remote-tracking branch 'origin/master' into feat/ui-editor-v3
# Conflicts: # apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationQueue.ts # apps/ai-game-creator-shell/src/features/resource-canvas/useResourceCanvasGenerationPlaceholders.ts # apps/ai-game-creator-shell/tests/resourceCanvasGenerationFloatingPanel.test.tsx # docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
@@ -1,7 +1,273 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-09-21 合并 origin/master:Supervisor 永久退役,策划 V1V2 退役落到当前两条产品路径
|
||||
|
||||
- 背景:`refactor/split-direct-project`(DirectProject 独立聊天容器)与 `origin/master`(#355 退役策划 Agent V1/V2)在 2026-09-18 之后各走一条线:本分支删掉 Supervisor 前端链路、把立项策划收敛到 `view/project-development/planning/`,master 删掉整套策划 V1/V2(前端会话 / 审批卡 / 适配器 / 类型与 Rust `planning_*_v2` 命令、`planning_gdd_model.rs`、`planning_policy_v2.rs`、`planning_session_v2.rs`)只保留 Design Agent。两边都在删 Supervisor,冲突集中在 `App.tsx`、聊天视图(`PlanningChatView`、`DirectProjectTurn`、`ToolCallGroup`)、Direct composer / 引用输入区、`styles.css`、Rust direct user item 与 appSurface 用例。
|
||||
- 决策:按「Supervisor 永久退役」的当前口径合并,保留 DirectProject 独立容器 + Design Agent 两条产品路径,master 的策划 V1/V2 删除整体生效(GDD 审批卡、策划输入卡、`planningLane`、`planningSessionV2`、`planningSessionContract` 与对应 Rust 模块、`planning_*_v2` 命令与前端契约全部删除,不保留兼容别名或双跑路径)。两边的 patch 目的若有独立价值,就落到当前结构上而不是恢复 Supervisor:① 思考折叠入口收成一个共享表现 `chat/components/AgentReasoning/AgentReasoning.tsx`(折叠态单行纯文本预览 + 箭头、展开态安全 Markdown),DirectProject 回合与策划回合共用;② 输入盒的模型 / 推理档控件(`ConversationModelSelect`、`ComposerReasoningEffortSelect`)按 ADR「行为中立的设置表现可复用」接到策划输入盒,写回仍走客户端配置通道。Rust 侧保留本分支的 canonical→wire 投影、无审计回合与 direct user item 严格校验,master 的 `prepare_new_web_project_at` 前置复核并入当前回合入口。
|
||||
- 原因:Supervisor 结构(含它的 composer 控件、消息标签与运行态)是已退役对象,冲突里出现它只是两侧删除的落点不同;但「策划入口也要能选模型 / 推理档」和「折叠思考显示单行预览」是产品行为,属于 master 那边的独立目的,丢掉就是功能回退。把它们挂到当前两条路径上,既满足退役口径也不让 master 的行为丢失。
|
||||
- 代价与取舍:master 用例里钉住 `.project-supervisor-composer-controls`、`项目总控消息` 的断言改为当前类名 `.project-chat-composer-controls` 与「立项策划消息」;本分支 CSS 的 `project-supervisor-* → project-chat-*` 改名对 master 新增规则同样生效。`workspaceProjectKind` 在两条读它的路径(壳层 Cocos 插件门禁、Supervisor 提交路由)都退役后删除,插件可用性改由插件宿主自判。`.env` 的本地私有改动不进入本次合并。
|
||||
- 验证方式:`apps/ai-game-creator-shell` `npm run typecheck` 与仓库 `npm run typecheck`、`npx vitest run apps/ai-game-creator-shell/tests`(168 个文件,除 5 个 jsdom `localStorage` 环境失败文件与 1 条本分支既有的 `resourceTagStatsRefresh` 失败外全绿,`appSurface.test.ts` 198 passed / 13 skipped / 0 failed)、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`、改动文件 eslint 0 error。Rust 侧定向 `cargo test --bin genarrative-ai-game-creator-shell -- direct_codex_user_item | skill_pack:: | sessions::developer_project_file_and_memory_writes_advance_project_revision` 全过;整套 Rust 分片在本容器有 55 条环境性失败(`/sbin -> usr/bin` 让 `command.exec` 沙箱的 merged-usr 预检失败,报 `command.exec sandbox unavailable`),与本次合并无关:这些用例所在模块与 master 逐字相同。
|
||||
|
||||
## 2026-09-21 合并后两处回归:预览激活回接「运行」入口,聊天 `@` 清单按需重读
|
||||
|
||||
- 背景:合并 `origin/master` 后 CI 报两处回归。① `scripts/check-native-shells.mjs` 报 `AI game creator client preview activation drifted: missing await invoke<LocalPreviewStatus>('activate_local_game_preview'`:Supervisor 前端链路退役时(`0224ef7b9` 删 `pendingCommand` 死链那一步)把 `activate_local_game_preview` 的前端调用方一起带走,而这条守卫与 Rust `preview.rs` 的实现都还在。② `resourceTagStatsRefresh` 用例失败:聊天 `@` 选择器的标签统计与候选读 `useDirectProjectManifest` 自己那份清单,资源画布的标签写入既不发 `game-creator-manifest-invalidated`(Rust 只在 agent 驱动的写入上发),壳也不再往聊天推清单快照,于是用户改完标签打开 `@` 仍看到旧标签。
|
||||
- 决策:① 预览激活按 ADR 由「运行」入口承接:`executeRunLocal` 先调 `activate_local_game_preview`(Rust 侧只核对内存 registry 与项目归属),返回 running 且拿到 loopback 地址就只切客户端运行视图并经 `DirectProjectChatHandle.announce` 说一句 `已切换到客户端运行视图:<url>`,不再重复 `start_local_game_preview`;stopped / 不属于本项目 / 权限位要求确认都当「没有可复用预览」,回落到重新启动。同时把 `activate_local_game_preview` 从 `scripts/check-config.mjs` 的 native-only 白名单删除(该门禁要求 App invoke 与白名单互斥)。② 聊天 `@` 选择器打开时按需重读清单:`DirectProjectComposer` 的两处选择器入口(工具条按钮与附件菜单项)都先触发 `onReferencePickerOpen` 再 `openPicker`,由 `DirectProjectChatView` 用 `useDirectProjectManifest` 的 `refresh` 重读;不新增壳→聊天的清单推送通道,也不改 ADR 里「聊天自己订阅清单」的归属。
|
||||
- 代价与取舍:编辑器内直接敲 `@` 的候选菜单读同一份清单,本轮没有纳入按需重读(它只列素材名、不显示标签统计)。更根治的做法是让资源写入在 Rust 侧广播 `game-creator-manifest-invalidated`,本轮不做,先按现成的刷新入口收口。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/App.tsx`、`src/view/project-development/chat/DirectProjectChatView.tsx`、`.../chat/components/DirectProjectComposer/DirectProjectComposer.tsx`、`scripts/check-config.mjs`;新增 `tests/previewActivation.test.tsx`(3 条:活体预览只切视图不重启、stopped 回落重启、权限确认回落重启;第一条在去掉接线后确实失败)。
|
||||
- 验证方式:`npx vitest run apps/ai-game-creator-shell/tests`(除 5 个 jsdom `localStorage` 环境失败文件外全绿:`previewActivation` 3/3、`resourceTagStatsRefresh` 2/2、`runVersionSwitchEventSubscription` 2/2、`resourceReferenceInput` 34/34、`appSurface.test.ts` 198 passed / 13 skipped)、`node scripts/check-native-shells.mjs --groups=contract`、`npm run ai-game-creator-shell:typecheck`、`npm run check:encoding`、`git diff --check`、改动文件 prettier / eslint 全过。
|
||||
|
||||
## 2026-09-21 渠道进安装身份:不同渠道的 AGC 包体在同一台设备并存
|
||||
|
||||
- 背景:渠道此前只决定更新端点(`plugins.updater.endpoints`)与渲染层平台 origin,`productName` / `identifier` 与渠道无关,于是所有渠道共用 `%LOCALAPPDATA%\陶泥儿` 安装目录、同一个卸载项(`HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\陶泥儿`,另有 `HKCU\Software\genarrative\陶泥儿`)以及同一份 `%APPDATA%\world.genarrative.ai-game-creator` 数据目录。本机 0.1.48 安装实测:主程序二进制里只有 1 处 `agc/dev-win/latest.json`、0 处 release 端点,说明渠道在产物里只体现为端点。后果是后装的渠道静默顶掉先装的渠道,并接管更新端点、平台服务器与本地登录态/项目数据。
|
||||
- 决策:渠道同时决定**安装身份**。默认渠道 `dev` 保持基线 `productName = 陶泥儿`、`identifier = world.genarrative.ai-game-creator`(既有安装目录、卸载项与升级链不断);其它渠道派生 `陶泥儿 <渠道显示名>`(`release` → `陶泥儿 Release`)与 `world.genarrative.ai-game-creator.<渠道>`。身份与更新端点必须在同一个构建期 `--config` 里注入,禁止分别回读默认值。窗口标题、macOS 产物名(`.app` / updater 归档 / DMG 卷名)、首装包选择与 Windows 提权 ACL 的 managed 识别范围同批跟随该身份。
|
||||
- 边界:不做本地数据迁移或共享——切渠道等于换一个客户端;`release` 与自定义渠道首次以新身份安装,不接管、不迁移既有 `dev` 安装与本地项目,由用户自行决定是否卸载其一。
|
||||
- 影响范围:新增 `apps/ai-game-creator-shell/scripts/channel-identity.mjs`;`build-release.mjs`、`build-macos-ci.mjs`、`check-config.mjs`、`agent-swarm-test-chat.mjs`、`src-tauri/src/{main.rs,windows.rs,config.rs}` 与对应测试;主规范 `docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md`。
|
||||
- 验证方式:`node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs release-oss.test.mjs prepare-macos-codex.test.mjs cargo-features.test.mjs`(64/64,新增渠道身份与渠道 DMG 首装选择用例)、`node apps/ai-game-creator-shell/scripts/check-config.mjs`(基线等于默认渠道身份、非默认渠道身份隔离)、`cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell -- config::private_path_elevation_policy_tests`(12/12,含基线/`<基线>.release`/`<基线>.beta-2` 与相似前缀反向断言)、`AGC_UPDATE_CHANNEL=release npm --prefix apps/ai-game-creator-shell run build -- --no-bundle --debug`(产物字符串实测 `陶泥儿 Release` × 1、`agc/release-win/latest.json` × 1、`world.genarrative.ai-game-creator.release` × 1、`agc/dev-win/latest.json` × 0)。真机双渠道安装、并存与各自更新尚未执行,按未验证项记录。
|
||||
|
||||
## 2026-09-21 项目快照按部署渠道分区,后台按渠道查看并按素材查询口径展示用户
|
||||
|
||||
- 背景:AGC 项目快照此前统一写在 `agc/project-snapshots/v1/{user}/{project}/`,而开发与正式两套部署共用同一个 bucket(都默认 `agc-dev`)。结果是渠道混在一层前缀里:正式后台会列出开发渠道上传的项目,列表上也看不出项目属于哪个渠道;同时“项目工程”列表只有裸用户 ID,且用“加载更多”逐段追加,翻页与定位都困难。
|
||||
- 决策(存储):对象键升级为 `agc/project-snapshots/v2/{channel}/{user}/{project}/`。渠道是**部署渠道**,由服务端 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_CHANNEL` 决定(缺省沿用 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL`,当前缺省 `dev`),客户端不上报渠道、也不需要发新版;渠道名必须是小写字母开头的 `[a-z0-9-]{1,32}`,非法值在上传与后台查询处失败关闭(`503`/`400`),不悄悄回落。正式部署必须在 api-server 环境里显式写 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_CHANNEL=release`。
|
||||
- 决策(后台):新增 `GET /admin/api/project-snapshots/channels` 返回本部署渠道与远端已存在渠道的并集;列表与下载接口都接受 `channel`(缺省本部署渠道),游标里带上渠道并在解码时校验一致,跨渠道复用游标一律 400。页面顶部提供“渠道”选择框,切换渠道回到第 1 页。
|
||||
- 决策(列表形态):“用户 ID”列改为与“素材查询”同口径的“用户”列——昵称 + 陶泥号 + 用户详情入口,昵称/陶泥号由 api-server 用 `resolve_work_author_by_user_id` 解析(账号不可读时回退占位作者),原始 `userId` 不再直接铺在列里;“加载更多”改为游标分页(每页 20/50/100 + 上一页/下一页 + 当前页),前端按页记录游标链,换令牌或换每页条数从第 1 页重来;翻页失败保留当前页。
|
||||
- 原因:渠道属于“这套部署服务哪个客户端渠道”,客户端正式包按构建渠道连接不同平台服务(release → 正式站,dev → 开发站),所以服务端是唯一可靠的渠道来源;把渠道放进对象键第一层,既不需要迁移历史对象就能让两套部署互不可见,也让后台的渠道维度天然对齐存储布局。
|
||||
- 影响范围:`server-rs/crates/platform-oss/src/{lib.rs,project_snapshots.rs,template_library.rs,examples/agc_project_snapshot_live_smoke.rs}`、`server-rs/crates/api-server/src/{config.rs,project_snapshots.rs,admin_project_snapshots.rs,modules/admin.rs}`、`server-rs/crates/shared-contracts/src/admin.rs`、`apps/admin-web/src/{api/adminApiClient.ts,api/adminApiTypes.ts,pages/AdminProjectSnapshotsPage.tsx,pages/AdminProjectSnapshotsPage.test.tsx,styles/admin.css}`、`deploy/env/api-server.env.example` 与本仓运维/技术文档。
|
||||
- 未迁移的历史对象:`agc/project-snapshots/v1/` 下现存对象(只读核对过的两份历史清单)保留在 OSS,但不再写入、不再进入后台列表;需要取回时按旧前缀在 OSS 侧直接读取,确实要在后台看到时再单独开一个只读兼容视图。
|
||||
- 验证方式:`cargo test -p platform-oss snapshot` 7 passed(渠道校验、键布局、v2 根渠道枚举、v1 历史键仍必须私有);`cargo test -p api-server project_snapshot` 18 passed/1 ignored(渠道失败关闭、游标跨渠道拒绝、用户昵称/陶泥号解析、归档与配额回归);`cargo test -p api-server protected_route_matrix`、`route_contract` 通过(新路由纳入后台鉴权矩阵);`npm run admin-web:typecheck` 与 `npx vitest run apps/admin-web/src` 202 passed。
|
||||
|
||||
## 2026-09-21 Godot 模板入库与按模板建项的 Godot 分流
|
||||
|
||||
- 背景:模板库此前只有网页(html)、Cocos 与 Unity 占位,`runtime` 白名单早就接受 `godot`,但既没有 Godot 模板,也没有按模板建项的 Godot 分流——Godot 模板即使上传,建项也会落到 Web 分支,写出 `game/index.html` 占位入口并让 `godotProjectRoot` 为空。
|
||||
- 决策(模板内容):新增四个仓库内手写的 Godot 4.7 模板 `godot-empty-2d`、`godot-empty-3d`、`godot-hello-world`、`godot-platformer-2d`,`entry` 统一为 `project.godot`。模板只用内置 `ui_*` 输入动作、GL Compatibility 渲染,并用 `Polygon2D` / `BoxMesh` 搭可视骨架,不引入外部贴图或音频二进制;不打包 `.godot/` 缓存与导出产物。
|
||||
- 决策(建项分流):`create_project_from_installed_template_at` 在复制模板后按工程文件分流——Cocos 更新自身身份后走 Cocos 导入,Godot 先改写 `project.godot` 里 `[application]` 段的 `config/name`(只改这一行,其余字节逐字保留)再走既有 Godot 导入,写入 `godotProjectRoot: "."`,其余继续走 `init_local_game_project_at`。分流靠工程文件识别,不新增只读 `entry` 或 `runtime` 字段的契约。
|
||||
- 原因:Godot 工程身份是 `project.godot` 所在目录,与 Cocos 的 `package.json` 身份同一类问题;复用既有导入流程能同时拿到相对根记录、`.agent` 初始化与「不生成 Web 占位入口」这三条既有保证,并且与 Cocos 分支保持对称。
|
||||
- 影响范围:`apps/ai-game-creator-shell/template-library/v1/godot-*`(新增模板源)、`apps/ai-game-creator-shell/src-tauri/src/template_library.rs`、`.../src/project/manifest.rs`(`apply_godot_project_display_name`)、`.../src/project/manifest/import_tests.rs`、`docs/【模板规范】AGC模板包组织指南-2026-09-21.md`、`docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md`。
|
||||
- 验证方式:本机 Godot `4.7.2.stable` 对四个模板逐一做「主场景实例化 + 3 帧 + GDScript `--check-only`」,平台跳跃模板另做真实物理试玩(落地 y=627.99、1 秒右移 320px、跳上平台 y=515.93、三枚金币全收集);Rust 定向回归 `import_tests::rewrites_only_the_godot_display_name_line`、`import_tests::keeps_a_godot_project_without_a_display_name_line_untouched`、`template_library::tests::godot_template_creates_native_project_with_relative_root_and_display_name` 与既有 Cocos/Web 建项回归;发布走 `--only godot-*` 定向合并。
|
||||
|
||||
## 2026-09-21 Godot 工作区发现放宽与内置插件行去掉手动启动
|
||||
|
||||
- 背景:2026-08-10 “发现与歧义决策”把「一层多命中」和「`project.godot` 必须是普通文件」两条定为失败关闭。这两种布局都会让整个工作区被判成「没有 Godot 工程」,而 `PluginHost::list` 只按发现结果过滤,结果是 Godot 内置插件在设置→扩展 里直接消失,用户看不到任何可诊断入口。
|
||||
- 决策(发现):根目录命中仍然优先;根未命中时一层直接子目录多命中改为按目录名排序取第一个,`godotProjectRoot` 记录该相对目录名,结果确定且可复现。`project.godot` 允许是符号链接 / Windows reparse point / 硬链接,判据改为「链接目标解析后是文件」,目录与悬空链接仍不算命中。工作区根本身是链接、候选子目录是链接、二层及更深不递归这三条边界不变。
|
||||
- 决策(界面):设置→扩展 的内置插件行去掉手动「启动 / 停止」按钮。启动本来就由项目切换时的 `startAvailableAgcEditorPlugins` 自动完成,手动按钮只是第二个可绕过入口;停止改走该行的启用/禁用开关,`set_agc_plugin_enabled(false)` 会停止插件并断开编辑器连接。导入扩展行的启动按钮保留,因为导入扩展没有自动启动路径。
|
||||
- 原因:Godot 插件列表是按项目过滤的,发现失败等于功能静默消失;把不确定性收敛成一个确定的排序选择,比让用户面对空列表更好。链接放宽只作用于只读的工程标记文件,受管描述文件、运行缓存与项目目录的链接拒绝规则不动。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs`、`.../src/commands.rs`(错误文案)、`.../src/project/manifest/import_tests.rs`、`.../src/tests/project.rs`、`apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx`、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md` §3.8、`docs/technical/【技术方案】AGC Godot编辑器插件接入-2026-09-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
- 验证方式:`cargo test --locked -p genarrative-ai-game-creator-shell --bin genarrative-ai-game-creator-shell -- project::manifest::import_tests::` 与 `-- tests::project::`;前端 `agc:typecheck` 与 `runtime-settings` / `pluginHost` 套件。
|
||||
|
||||
## 2026-09-21 模板正文目录门禁:CLI 打包与后台上传同一份段名单
|
||||
|
||||
- 背景:模板包组织指南把 `.agent/`、`.git/`、`node_modules/`、根目录 `dist/` 等列为「不要放进 ZIP」,但两条发布路径此前只校验路径安全与 `entry` 是否存在,放进去的东西会跟着建到用户项目里(模板自带 `.agent/` 会让新项目继承一个陌生身份)。这条约定只靠作者自觉。
|
||||
- 决策:升级成机器门禁,CLI(`readProjectFiles`)与后台上传(`validate_import_archive`)用同一份段名单与同一句文案——任意层级拒绝 `.agent` / `.git` / `.svn` / `node_modules`,根目录拒绝 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode`。同名目录段只在根目录受限:正文内 `game/dist/**` 是模板自身内容;`.gitignore` 不等于 `.git`,Cocos 模板的 `.creator` / `.gitignore` 不受影响。
|
||||
- 原因:两类目录性质不同。`.agent/`、`.git/`、`.svn/`、`node_modules/` 放哪一层都是错的,进包即污染用户项目;而 `dist` / `library` / `temp` 这类只说明「作者把编辑器缓存或构建产物当成了模板内容」,出现在根目录才是信号,全面禁止会误伤工程内的正常同名目录。
|
||||
- 影响范围:`scripts/agc-template-library-publish.mjs` 及其测试、`server-rs/crates/api-server/src/admin_templates.rs`、`docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md`、`docs/【模板规范】AGC模板包组织指南-2026-09-21.md`、`docs/project-memory/plans/【里程碑】后台模板上传-2026-09-21.md`。
|
||||
- 验证方式:`node --test scripts/agc-template-library-publish.test.mjs`(28 项,新增 1 项:13 条拒绝用例 + `game/dist/**` 与 `game/.gitignore` 放行断言);`cargo test --locked -p api-server admin_templates`(8 项,新增 `template_import_archive_rejects_identity_and_build_directories`);仓库现有 9 个模板源无一条命中,本地重打包的 ZIP 摘要与线上清单 `zipSha256` 逐条一致;`cargo fmt --check`、`check:encoding`、`check:doc-index`、`git diff --check` 通过。
|
||||
|
||||
## 2026-09-21 后台模板上传:成品 ZIP + 每模板封面,批量全有或全无
|
||||
|
||||
- 背景:模板发布此前只有本地 CLI(源目录 + 确定性打包 + `--only`),后台上传需要一条不依赖本地仓库的通道,并支持一次提交多个模板。
|
||||
- 决策(输入形态):后台上传**成品 ZIP**(库内字节原样发布,不重新打包),每个模板必须同时给一张 PNG/JPEG/WebP 封面(与 CLI 源布局 `v1/<id>/cover.*` 一致),`id/title/templateVersion/runtime/entry` 由页面逐行确认;简介、标签与引擎上传后用编辑接口补齐。服务端按字节嗅探封面格式,不信任 multipart 声明的 content-type。
|
||||
- 决策(批量语义):一批 = 一把发布锁 + 一次清单提交,**全有或全无**;任一模板的 manifest/归档/封面不合法都在写入前整批拒绝并逐项给出原因。单批上限 20 个模板、单包 64 MiB、单封面 5 MiB、请求体 200 MiB。
|
||||
- 决策(校验与安全):归档只校验不落盘——合法 zip、无符号链接、无绝对路径 / `..` / 盘符条目、必须包含声明的 `entry`、条目数 ≤ 4096 且解压后 ≤ 512 MiB。
|
||||
- 决策(版本与保留):新 ID 默认上架;已存在 ID 就地更新并保留 `enabled` 分组、其它条目与未知扩展字段;同一 ID 同一 `templateVersion` 的 ZIP 字节不同时拒绝并要求递增版本,字节一致时按内容复用(`reusedObjects`)。
|
||||
- 决策(存储契约):`platform-oss` 的 `put_immutable` 为 `application/zip` 单独放宽到 64 MiB(此前只有 json/图片的 5 MiB),图片与元数据上限不变。
|
||||
- 影响范围:`server-rs/crates/{shared-contracts/api-server/module-assets/platform-oss}`、`apps/admin-web/src/{api,pages,styles}`、模板库技术方案与 `docs/project-memory/plans/【里程碑】后台模板上传-2026-09-21.md`。
|
||||
- 验证方式:`cargo test`(module-assets 11、platform-oss 12、api-server 定向 4)、`npm run admin-web:typecheck`、后台页面与模型 40 项 vitest、`check:encoding`、`check:doc-index`、`git diff --check`;真实 dev bucket 写入验证需用户显式确认。
|
||||
|
||||
## 2026-09-21 模板库线上产物对齐仓库源:递增版本重发 + 说明文档纳入发布
|
||||
|
||||
- 背景:`agc-dev` 上的 `templates/` 产物停在 2026-09-17 发布的那一版,仓库源在那之后改过(`5e4ff54a9` 删掉模板内嵌 `package-lock.json`、`game.js` / `main.js` 等内容调整),9 个模板里 7 个的 ZIP 与线上不一致;dry-run 被「同一 `templateVersion` 的 ZIP 不得变」门禁拒绝,发布器因此无法把仓库状态发上去。
|
||||
- 决策:按门禁要求为内容已变的 7 个模板递增 `templateVersion` 到 `0.1.1`(`blank-2d-canvas`、`blank-web` 内容未变,保持 `0.1.0`),并完成一次真实发布;对象使用内容寻址键 `v1/<id>/sha256/<摘要>/…`,`index.json` 与说明文档在发布锁内最后提交,历史对象不删除。
|
||||
- 决策(说明文档):`templates/README.md` 不再是手工副本——它由发布脚本从仓库源 `apps/ai-game-creator-shell/template-library/README.md`(≤64 KiB)覆盖写入并回读校验,写在清单之前;改契约只改仓库源。
|
||||
- 决策(CLI 行为):发布器遇到门禁拒绝时用 `process.exitCode = 1` 正常退出,不再 `process.exit(1)` 让 Node 在 fetch 句柄未关闭时抛 libuv 断言(此前现场只剩 `Assertion failed`,看不到拒绝原因)。
|
||||
- 影响范围:`apps/ai-game-creator-shell/template-library/v1/*/meta.json`、`apps/ai-game-creator-shell/template-library/README.md`、`scripts/agc-template-library-publish.mjs` 与其测试、本文件与模板库技术方案。
|
||||
- 验证方式:`node --test scripts/agc-template-library-publish.test.mjs`(27 项,含新增「说明文档随发布覆盖写且写在清单之前」「源目录没有 README 时不写线上文档」);真实发布 28 个对象回读通过;匿名读取线上清单(9 个模板、7 个 `0.1.1`、全部内容寻址键)与 `templates/README.md`(与仓库源逐字节一致);AGC 壳 3 项线上用例(读清单、下载安装、下载并原生建项 Cocos)重跑通过。
|
||||
|
||||
## 2026-09-21 macOS 发布改为只出 arm64 单架构(Intel 暂不支持)
|
||||
|
||||
- 背景:Mac 发布管线按 `universal-apple-darwin` 构建,但随包 Node 便携运行时只有**单架构官方发行版**(`stage-node-runtime.mjs` 从 `process.execPath` 取材),于是 macOS Job #7~#13 连续失败在「Node 运行时不支持发布目标:universal-apple-darwin」。期间出现过一版「按宿主架构放行」的过渡实现,它能骗过通用包自检(`check-macos-bundle.mjs` 按 `process.arch` 校验),但 Intel 上那份 arm64 侧车不可执行,并且已发布的 dev-mac 0.1.86 就带着这个缺陷。
|
||||
- 决策:macOS 固定只构建 `aarch64-apple-darwin`,渠道清单只登记 `darwin-aarch64`(不再登记 `darwin-x86_64`,避免把 arm64 产物发给 Intel 客户端);`targetRuntime('universal-apple-darwin')` 保持失败关闭,入口 `build-macos-ci.mjs` 只跑 arm64 隔离 smoke,首装包命名 `<产品名>_<版本>_aarch64.dmg`。
|
||||
- 原因:要让 Intel 真正可用,必须让发布包按架构各带一份**同版本**运行时(另下载另一架构官方发行版)+ 通用包自检按架构分别校验,这是一条独立且更大的改动;在 DDL 前用「只带宿主架构」糊过去等于把坏包发给 Intel 用户,比暂不支持更糟。单架构同时把构建时间与产物体积减半。
|
||||
- 验证:`node --test apps/ai-game-creator-shell/scripts/*.test.mjs` 92/92(含 `targetRuntime('universal-apple-darwin')` 必须抛错、入口固定 arm64 目标与 `_aarch64.dmg` 后缀的守卫用例);`npm run check:production-ops`、`check:encoding`、`check:doc-index`、prettier、eslint、`git diff --check` 通过;真实端到端由 Jenkins Mac Job 验证(清单只含 `darwin-aarch64`、DMG 与更新包唯一匹配)。
|
||||
- 影响范围:`apps/ai-game-creator-shell/scripts/{build-macos-ci.mjs,stage-node-runtime.mjs,prepare-macos-codex.test.mjs}`、`jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`、本仓三份技术/运维文档与共享记忆。未动 Windows 渠道、未动 Rust 侧运行时解析(单架构仍是扁平 `game-runtime/node/`)。
|
||||
- 恢复 Intel 的路径:先在 staging 支持按架构各带一份同版本运行时并让通用包自检按架构校验,再切回 universal 目标、把 `darwin-x86_64` 键登记回去并补 Intel 真机验收。
|
||||
- 已知未覆盖:Intel Mac 用户的更新体验(清单缺 `darwin-x86_64` 键,客户端会「无可用更新」,未实测其 UI 文案);Mac 包里仍并列携带两套 Codex 原生依赖(只运行 arm64 切片,可按需瘦身)。
|
||||
|
||||
## 2026-09-21 图集切片上限:客户端结果门从 64 对齐到平台契约的 256
|
||||
|
||||
- 背景:现场(项目 `gameagent-6e53c9e8`,2026-09-21 07:54)「AI 生成图标素材」失败:`platform-generation-result-unknown: 异步生成完成结果无法绑定到 operationId:External Editor 旧同步结果的图集切片超过 64 个`。任务账本(`.agent/runtime/asset-generation-tasks/tasks.json`)显示它跑了 99 秒、`assetId` 为空、没有落任何素材;对应的持久化请求(`canvas-generation-requests/manual-canvas-asset-generate/slot-560175669f….json`)是 `sliceMode: connected-components` + `sliceCount: null`(自动切分)。也就是**平台已经生成并切完图了,是客户端在绑定结果这一步把整条结果判失败**,付费产物被丢弃。
|
||||
- 根因:同一条链路里存在两个不同的切片上限。平台切分是 256(`server-rs/crates/api-server/src/editor_project_icon.rs` 的 `EDITOR_ICON_SPRITESHEET_MAX_SLICES`),Agent 工具 schema 的 `sliceCount` 是 1..256(`agent_native_tools.rs` / `direct_tool_bridge.rs`),持久化产物批次也是 1..256,公开契约(`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`)写的更是「最多 256 个输出」;唯独客户端**两处结果绑定门**还是 `> 64` 就拒(`agent/generation/external_generation_state.rs` 与 `agent/generation/canvas_generation.rs`,由 `602723ea0` 于 2026-08-03 引入)。自动切分落在这个窗口里(65~256 片)时,客户端比平台更严,于是把合法产出整条丢掉。
|
||||
- 决策:两处门统一到 `PLATFORM_ART_SPRITESHEET_MAX_SLICES = 256`,并抽成同一条判据 `platform_art_spritesheet_slice_count_exceeds_limit` 与同一句拒绝文案 `platform_art_spritesheet_slice_limit_error`(数字由常量插值,不再手写)。注释里点名三处同值权威(平台切分常量、工具 schema `sliceCount`、公开契约),客户端不得比平台更严。
|
||||
- 原因:客户端这两处门的作用是「防止把不可信/超预算的结果写进本地」,不是产品上限;真正的产品上限属于平台切分契约。两处各写一个字面量就会再次漂移,所以值只留一份、判据只留一条。
|
||||
- 验证:新增 `canvas_generation_tests::spritesheet_slice_limit_matches_platform_and_tool_contract`(上限值、边界判据与文案)与 `external_generation_state_tests::legacy_result_accepts_slice_counts_up_to_platform_limit_and_rejects_beyond`(64/65/256 片必须能持久化且切片一条不少、257 片必须按同一句文案拒绝),两条都用**变异验证**确认过:把常量改回 64,回归用例立刻变红。定向执行 `cargo test -- spritesheet`(22 passed)、`cargo test -- external_generation_state_tests::`(10 passed)与两条新用例;`cargo fmt --check` 干净。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/generation/{canvas_generation.rs,external_generation_state.rs}`(+用例)。未动平台切分、OpenAPI、数据库或前端。
|
||||
- 已知未覆盖:真实客户端复验(重新生成一次图标素材)与远程 CI 未跑;256 片时的累计下载/像素预算未实测——平台自己的总像素上限是 2048×2048,客户端预算是 4096²,按切片是整图互不重叠子矩形推算不会先撞预算,且真撞了也只是给出明确错误而不是损坏数据。
|
||||
|
||||
## 2026-09-21 本批自查(PR #441):三处修正
|
||||
|
||||
- 背景:推 PR 后按「局部到整体」自查这一批(三需求 + 验收修正),查出三条:①拖动到对话的落点在 `pointermove` 上每帧都 `setState` 一个新对象;②替换面板相对 **stage** 写死 `top: 8.5rem`(与刚修的任务开关同一类隐患:工具条换行会压上去),且它和「生成任务」面板抢画布右上角同一个位置;③替换面板不显示「在替换哪张源素材」,而非模态化之后那点线索(画布上的源素材光环)会被一次空白点击清掉。
|
||||
- 决策①:落点状态改为**逐值比较**(`sameResourceCardReferenceDrop`:条数 + 对话栏矩形四值),只有真的变了才落 state——指针在对话栏上移动不再每帧重渲染整个工作台。
|
||||
- 决策②:面板从 stage 顶层移进**画布容器** `.game-resource-book-manager`(它本身就是 `position: relative`,与左下角工具栏、右下角 Dock 同一套锚定口径),`top: 3.2rem` 排在任务开关下方;并在打开替换会话时**收起「生成任务」面板**——画布右上角同一时刻只留一块浮层(任务照旧在账本里推进,收起只影响这个视图)。
|
||||
- 决策③:面板上方补一行「替换源素材:<显示名>」(投影显示名优先、manifest 资产名回落),源身份不再只靠画布光环。
|
||||
- 验证:`resourceVersionReplacement.test.tsx` 20 passed(新增「面板锚在画布容器里、写明源素材、并与任务面板互斥」)、`resourceCardReferenceDropModel.test.ts` 6 passed(新增逐值比较)、`resourceCanvasChatReferenceDrop.test.tsx` 4 passed;`tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/view/project-development/{index.tsx,resourceCardReferenceDropModel.ts}`、`.../features/resource-canvas/resourceCanvasChrome.css`、用例 3 个文件、验收用例 C9 注脚。未动 Rust / SpacetimeDB / 共享组件行为(只新增了宿主的源素材行)。
|
||||
- 已知未覆盖:仍无真机目视;替换会话与任务面板的互斥是新行为,客户端需要复看一次(若产品希望两者能同时看,把这一句收紧即可)。
|
||||
|
||||
## 2026-09-21 验收现场修正:任务开关压在工具条上、开关与面板并排
|
||||
|
||||
- 背景:客户端验收截图两条:①画布右上角那枚「生成任务 · N」开关与工具条(打开项目目录 / 资源面板 / 整理画布 / 管理未完成编辑 / 依赖 · 类型)**重叠**;②「生成任务」面板展开时,开关与面板**并排**摆着,产品口径是「这俩不应该并排,出来详情以后入口就应该隐藏」。
|
||||
- 决策(重叠):锚点不再用 `position: absolute` + 写死的 `top: 3.5rem`(工具条是 stage 第一行,高度随按钮换行变化,写死的偏移迟早压上去),改成 **stage 网格里与工作面同一个单元格的另一个条目**:`grid-row` 按 `placement` 分档(资源画布 3 / UI 编辑器 2 / 运行表现层 `2 / -1`)+ `grid-column: 1` + `justify-self: end` + `align-self: start` + `margin: 0.85rem`(运行那档上边距 3.5rem 让开右上角版本入口)。为此把同格的三处容器也显式钉住第 1 列(`.game-workbench-stage > .game-resource-manager`、`.…[data-resource-view-state='resources.ui-editor'] > .game-workbench-editor-shell`、`.game-run-surface`)——锚点是显式定位条目,画布若走自动列放置会被挤进隐式第二列、画布直接压窄一半。
|
||||
- 决策(并排):开关只在**完全收起**时渲染(`!open && phase === 'idle'`,收起动画期间也不画,否则那 160ms 又会同框);展开态画布右上角只有面板,收起走面板头部那枚 × 或点画布外部。原来「点画布外部自动收起」的判据里对开合按钮的排除保留(收起态仍靠它开合)。
|
||||
- 原因:验收口径优先于「照抄美术画布」——美术画布把开关常驻在面板旁边,但产品要的是「入口与详情不同时出现」。重叠那条的根因是**猜了一个绝对偏移量**:工具条高度不是常量,锚点必须由布局自己推导。
|
||||
- 验证:`resourceCanvasAssetGenerationTasksPanel.test.tsx` 12 passed(新增「展开时开关让位:同一时刻只有面板那枚 ×」;计数用例改为收起态查开关、展开态查面板头部)、`resourceCanvasAssetGenerationTasksSidebarStyle.test.ts` 11 passed(锚点改成网格条目坐标:`grid-row` / `grid-column` / `justify-self` / `align-self` / `margin` 与运行档上边距;窄屏改判 `justify-self: stretch`)、`resourceCanvasGenerationTasksSidebarDismiss.test.tsx` 5 passed(收起改走面板 ×、收起后开关回来能再打开、锚点仍在 stage 里)、`resourceCanvasAssetGenerationBackgroundClose.test.tsx` 5 passed;`appSurface.test.ts` 538 passed / 17 skipped / 0 failed。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/features/resource-canvas/{ResourceCanvasAssetGenerationTasksPanelView.tsx,resourceCanvasAssetGenerationTasksSidebar.css}`、`apps/ai-game-creator-shell/src/styles.css`、三个同场景用例文件、PRD §3.10 与更新时间。
|
||||
- 已知未覆盖:仍无真机目视(网格落点在 Tauri 里的实际观感、工具条换行时锚点是否仍贴画布顶边需要现场复核)。
|
||||
|
||||
## 2026-09-21 AGC 替换面板改为非模态浮层,支持在画布上点选目标
|
||||
|
||||
- 背景:`docs/project-memory/todos/【待办】画布验收后续修复-2026-09-18.md` 的「后续需求」要求「在替换面板中支持画布点选目标」。2026-09-13 那版做的是「候选弹窗 footer 一个『点选替换』按钮 → 关掉弹窗 → 进画布点选态 → 点中即提交」:面板与画布互斥(弹窗外壳是全屏遮罩,留着它画布上的卡点不到),用户要么在面板里筛、要么离开面板。
|
||||
- 决策:候选面板改成**非模态浮层**(共享组件新增 opt-in `nonModal`:不铺遮罩、不做焦点陷阱、面板自身限高 + 内部滚动、Esc 在 `document` 阶段截断后取消;网页端美术画布不传,弹窗行为逐字不变)。AGC 把它锚在画布**右上角、任务开关下方**(`.game-resource-replacement-panel` → `top: 8.5rem; right: 0.85rem`,壳样式在共享样式表里,宿主只负责锚定)。**面板开着时画布照常可点**:点中合法候选即落成面板里的当前选择(面板里随之 `aria-selected`),写入仍然只由面板「确认」发起;点中非法目标在面板里说明原因、零写入。旧的「点选替换」入口、画布提示条 `.game-resource-canvas-pick-hint` 与 `resourceReplacementPickMode` 随之退役(同一个功能不留两条 UI 路径)。
|
||||
- 决策(同步口径):`selectedAssetIds` 的**数组引用不能当同步信号**(调用方每次渲染都会重建它,放进依赖会清掉用户在面板里的选择——组件里原本就为此写过一段注释),所以新增显式序号 `initialSelectionRevision`:只有宿主真的换了目标才重同步选择,搜索词与分类筛选保持原样。`confirmResourceVersionReplacement` 另外拿 `resourceReplacementPick` 兜底:点完画布当帧就按确认时不该报「请选择一个替换素材」。
|
||||
- 原因:面板与画布是同一屏的两半——目标本来就在画布上,「先把面板关掉再点」是弹窗外壳带来的妥协,不是产品意图。换成非模态之后,合法性判据仍是同一条 `resolveResourceReplacementPick`、写入仍是同一个 `confirm` 函数(载荷逐字一致),所以这是**外壳**的改动,不是第二套替换实现。
|
||||
- 已知取舍(相对旧口径的行为变化,均已随测试钉住):面板开着时空白处点击不再被吞——画布恢复正常的清焦点 / 框选 / 平移语义(源身份在打开面板时就已冻结,替换不依赖画布选中);「点中即提交」不再存在,提交一律由「确认」触发;关闭面板后卡片单击语义原样(一次性抑制照旧收尾)。
|
||||
- 验证:`resourceVersionReplacement.test.tsx` 19 passed(4 条旧点选用例改写为:非模态判据 + 点画布候选落进面板且零写入、面板确认才写入且载荷逐字一致+血缘、四类非法目标面板内报因零写入、Esc 只收面板不清选中;新增「画布点选不清掉面板搜索/分类」)、`projectAssetPickerDialogShellStyle.test.tsx` 7 passed(新增 nonModal 形态与浮层壳样式声明,含关掉即卸载)、`src/components/image-editor/ImageCanvasEditorView.test.tsx` 64 passed、`ImageCanvasEditorGenerationIntegration.test.tsx` 44 passed(网页端弹窗行为未变);连同 `appSurface.test.ts`、`projectResourceLiveIntegration.test.tsx` 共 6 个文件 694 passed / 17 skipped / 0 failed;`tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
|
||||
- 影响范围:`src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx`、`packages/shared/src/components/styles.css`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`.../features/resource-canvas/resourceCanvasChrome.css`、`apps/ai-game-creator-shell/tests/{resourceVersionReplacement.test.tsx,projectAssetPickerDialogShellStyle.test.tsx}`、PRD §5.3 / §7.8 第 8 条、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md` 的 S15a。未动 Rust、SpacetimeDB、external v1。
|
||||
- 已知未覆盖:真机观感(面板在画布右上角的落点、与任务开关同时打开时的间距)未在 Tauri 目视确认;面板里不显示「本次替换的是哪张源素材」,源身份只靠画布上那张卡的选中光环表达——点空白清掉选中后就只剩面板自己的候选列表。
|
||||
|
||||
## 2026-09-21 AGC 资源卡拖到对话实现批量 @ 引用
|
||||
|
||||
- 背景:`docs/project-memory/todos/【待办】画布验收后续修复-2026-09-18.md` 的「后续需求」要求「聊天拖拽批量引用」。改前只有两条入口:聊天输入框里输入 `@` 或点 `@` 按钮开素材选择面板,以及资源卡选中工具条上那枚「引用」按钮——都要先把素材找出来再点,多选批量引用没有一次成型的路径。
|
||||
- 决策:资源卡**按住拖到右侧 Agent 对话栏、松手即批量引用**。落点判据是「指针是否在对话栏矩形内」:pointermove 在对话栏上时语义从排版切成引用(卡片不再跟着指针走,改铺一层虚线落点浮层 + 「松手即可 @ 引用 N 项素材」),拖回画布内松手仍然是原来的排版语义(照旧写手动坐标)。批量范围与拖动位移**同一集合**(`drag.moves`):多选后拖任意一张 = 整批引用,拖未选中的卡 = 只引用它自己。
|
||||
- 决策(引用构造与派发):引用构造收敛成纯函数 `resourceCardReferenceDropReferences`,口径与工具条「引用」按钮**逐字一致**——只认已登记 manifest 的素材(`manifestAssetId`)、`source: 'resource-card'`、kind / 分类 / 标签取自资源投影,于是同一素材从两处进来是同一枚引用(去重键同样一致)。派发走新增的批量事件 `RESOURCE_REFERENCE_INSERT_MANY_EVENT`(`dispatchResourceReferenceInsertMany`):N 条引用一次事务插进草稿、只聚焦一次,不逐条重建草稿。一条也构造不出来时(选中的素材都未登记)不静默:提示条说明原因。
|
||||
- 原因:拖动本来就在指针捕获下走,指针跑到画布外仍回到卡片 handler,因此「落点」只能自己量;不落盘是因为对话栏那一段没有画布坐标可言(写下去会得到跑到画布外的坐标),而且用户在对话栏上松手的意图本来就不是排版。`0 x 0` 的对话栏矩形必须判成「没有落点」:零面积矩形会让任何点都命中,画布内正常拖动会被整段跳过。
|
||||
- 验证:`resourceCardReferenceDropModel.test.ts` 5 passed(矩形/命中/构造/提示文案)、`resourceCanvasChatReferenceDrop.test.tsx` 4 passed(单卡拖到对话 → 1 条引用且零坐标写入、多选整批 → 2 条、拖回画布 → 照旧写手动坐标且零引用、pointercancel 收干净)、`resourceReferenceInput.test.tsx` 30 passed(新增批量事件一次派发且空批次不派发、一次 insertReferences 按序插入整批 chip);连同 `appSurface.test.ts` 在内 7 个用例文件 660 passed / 17 skipped / 0 failed;`tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/view/project-development/{index.tsx,resourceCardReferenceDropModel.ts}`、`.../features/project-workspace/resourceReferences.ts`、`apps/ai-game-creator-shell/src/App.tsx`、`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/tests/{resourceCardReferenceDropModel.test.ts,resourceCanvasChatReferenceDrop.test.tsx,resourceReferenceInput.test.tsx}`、`docs/【功能说明】AGC聊天素材引用-2026-09-08.md`。未动 Rust、SpacetimeDB、`packages/`、共享弹窗组件。
|
||||
- 已知未覆盖:真实客户端里的手感(拖到对话栏的触发距离、提示条位置、多选整批的视觉反馈)未在 Tauri 目视确认;「复制对话保留有效引用」属于同一条后续需求里的另一半,本轮未做。
|
||||
|
||||
## 2026-09-21 AGC「生成任务」侧栏移到画布右上角(照抄美术画布,保留 AGC 样式)
|
||||
|
||||
- 背景:`docs/project-memory/todos/【待办】画布验收后续修复-2026-09-18.md` 的「后续需求」要求「生成任务列表移到画布右上角,进行中/已完成分组、失败可见、限高滚动及自动开合」。改前状态:侧栏本体是 `position: fixed; top: 4rem; bottom: 6rem; left: 0.75rem` 的左侧贴边面板,开合口只有工具条上那一枚「生成任务 · N」按钮(资源 / 运行两个页签各渲染一次),折叠态不留任何常驻入口;分组、失败可见、限高滚动、提交后自动展开这四条当时已经具备。
|
||||
- 决策:侧栏改挂**画布右上角的锚点**(`.game-resource-generation-tasks-anchor`),形态照抄网页端美术画布的任务侧栏(`ImageCanvasTaskSidebarView.tsx` + `src/index.css:6072+` 的 `.image-canvas-editor__task-sidebar*`):**开关常驻右上角、面板在开关左侧展开**,收起态只剩那一枚开关;工具条上那两处重复入口删掉——同一个功能两个入口本身就是两处随时会漂移的状态。锚点是覆盖式的,仍然不 reflow 画布视口。颜色、圆角、字重、阴影**全部继续走 `--platform-*` token**,不照搬网页端的固定色值(该 CSS 文件头既有的口径)。
|
||||
- 原因:右上角是画布上唯一「不被右侧『智能创作』对话面板占、也不与左下角栏目工具栏 / 右下角缩放 Dock 打架」的稳定空位;折叠态仍留一枚开关以后,用户不必先想起工具条在哪一行,也不再需要「关掉以后打不开」的兜底(原设计正是靠工具条入口常驻来解决这个问题)。
|
||||
- 决策(分档坐标):锚点由 view 的 `placement`(`canvas | run | editor`)落成 `data-generation-tasks-placement`,坐标在样式里分档:资源栏目画布与 UI 编辑器用画布顶边那一档(`top: 3.5rem; right: 0.85rem`),**运行表现层下移到 `top: 7rem`**——它右上角 `top: 20px; right: 20px` 被 C7 版本入口 `game-run-version-picker` 占着,不去抢那一块。
|
||||
- 实现要点:开关改由 view 自己渲染(`data-resource-generation-task-toggle` 与计数属性原样保留,可访问名 `生成任务` 唯一),在途计数只在 view 内算一次、开关与面板头部同源(宿主原先那份 `resourceAssetGenerationInFlightCount` 因此删除);面板改由 `max-height: min(30rem, calc(100vh - 12rem))` 封顶 + 内部滚动,自己不再定位(坐标只由锚点一处决定);进场 / 退场动画位移方向跟着锚点翻到正 X。宿主的「点外部收起」判据(排除侧栏本体与 `data-resource-generation-task-toggle`)与「提交受理后自动展开」均未改。
|
||||
- 验证:`resourceCanvasAssetGenerationTasksPanel.test.tsx` 11 passed(新增「折叠只剩右上角开关」「开关计数与面板头部同源」「锚点按工作面分档」)、`resourceCanvasAssetGenerationTasksSidebarStyle.test.ts` 11 passed(锚点坐标 / 运行档下移 / 面板不再自定位 / 开关走 token 且无硬编码色 / 窄屏占满宽度)、`resourceCanvasGenerationTasksSidebarDismiss.test.tsx` 5 passed(入口位置改为右上角锚点、工具条不再有生成任务按钮)、`resourceCanvasAssetGenerationBackgroundClose.test.tsx` 5 passed;`appSurface.test.ts` 538 passed / 17 skipped / 0 failed(与既有基线逐条一致);`tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/features/resource-canvas/{ResourceCanvasAssetGenerationTasksPanelView.tsx,resourceCanvasAssetGenerationTasksSidebar.css}`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/{resourceCanvasAssetGenerationTasksPanel.test.tsx,resourceCanvasAssetGenerationTasksSidebarStyle.test.ts,resourceCanvasGenerationTasksSidebarDismiss.test.tsx}`、PRD §3.10。未动 Rust、SpacetimeDB、`packages/`、共享弹窗组件。
|
||||
- 已知未覆盖:真实客户端观感(右上角坐标相对画布顶边的落点、与运行表现层版本入口的间距)未在 Tauri 里目视确认;窄屏(≤480px)只有声明级断言。
|
||||
|
||||
## 2026-09-21 画布绑定前置查询收口为项目摘要,失败文案补因链
|
||||
|
||||
- 背景:dev 上「AI 生成图片」连续失败,卡片显示 `解析读取外部画布项目响应失败:error decoding response body`,每条恰好 `1 分 00 秒`;同批的远端资源编辑终态只提示「已明确失败」,用户看不到原因也看不到下一步。(同批「图标素材切片超过 64 个」已由本文件「图集切片上限:客户端结果门从 64 对齐到平台契约的 256」条目决策,这里不再重复。)
|
||||
- 决策(项目列表视图):`GET /api/editor/projects` 与 `GET /api/external/v1/editor/projects` 共用同一套 `view` 取值与摘要投影(缺省 `full` 保持兼容,未知取值失败关闭);`summary` 只回传 `projectId / title / updatedAt / cover`,既不做内联媒体修复,也不带画布与全量资源。投影实现收敛到 `editor_project.rs` 一份,外部 API 与 MCP 复用同一份;AGC 画布绑定前置查询固定使用 `?view=summary`,它只需要 `projectId`。
|
||||
- 决策(错误文案与终态出口):外部请求失败文案补 kind 语义与底层因链(`reqwest::Error` 的 `Display` 只有 kind,超时 / 正文截断 / 非法 JSON 显示成同一句话),且不拼接 URL;远端资源编辑终态文案带出稳定失败码并指向唯一出口「移出恢复队列」,上游原文继续不写入账本。
|
||||
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/external_editor_api.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs`、`project/resource_editor.rs` 与对应夹具。
|
||||
- 验证方式:`cargo test --locked -p api-server -- summary`(含站内 `view=summary` 跳过媒体修复、未知 view 返回 400 的新用例)、AGC 壳 `agent::generation::`(99 项)与 `project::resource_editor::`(59 项)、`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## Unity 与 Godot 常用操作指导
|
||||
|
||||
两种编辑器的操作指导复用客户端审核 Skill pack:DirectProject 通过原生 Skill 或既有审核资源读取入口按需取得,Agent Runtime 的对应执行工具说明嵌入同源参考。指南不改变插件可用性、执行授权或 Runner 回执;只读说明不能证明编辑器已连接。常用示例与执行失败/部分修改、保存、撤销边界在同一参考中维护,避免提示词和文档各存一份代码。
|
||||
|
||||
## 2026-09-20 Godot 编辑器执行接入
|
||||
|
||||
原生引导采用固定版本的官方 `godot-cpp` 和 MSVC x64 构建,绑定及 C++ runtime 静态链接。依赖归档和缓存源码须核验,安装目录仍只分发原生载荷及许可。EDITOR 阶段动态加载/卸载时显式清理 C++ 实例绑定与单例包装,保留纯 GDScript 的异步执行和原有协议;执行权限、项目身份与缓存归属继续由现有宿主处理。
|
||||
|
||||
可用性边界按引擎区分:Cocos/Unity 保持不按工程类型过滤,Godot 仍绑定当前 Godot 项目,切项目撤销旧插件上下文;前端统一根据宿主投影启动插件。Runtime 工具目录只对 Godot 追加项目条件,编辑器说明沿用外置提示词及审核 Skill 参考。
|
||||
|
||||
Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和不确定执行回执合同,编辑器实现留在 `plugins/agc-godot-editor`。用户选择 DLL 原件随 AGC 安装资源分发,并确认按编辑器实例在 AGC 私有缓存准备临时加载副本,以满足 Godot Windows 加载器的同目录 `~DLL` 写入要求;项目内不复制 DLL,只用受管 `.gdextension` 引导。Godot 自动 UID 伴生文件必须记录归属并在确认卸载后按内容匹配清理。工作区根不迁移到 Godot 子目录,原始项目配置与场景只通过明确编辑操作修改。完整合同及验证范围见 [Godot 编辑器插件接入](<../../technical/【技术方案】AGC Godot编辑器插件接入-2026-09-20.md>)。
|
||||
|
||||
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
|
||||
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
|
||||
> 当前口径(2026-09-18):历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据。策划 Agent V1/V2 的 Runtime、专用命令、审批卡、展示适配和旧测试已删除;当前策划入口统一使用 Design Agent。如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
|
||||
|
||||
## 2026-09-20 DirectProject 工具并行与交付收敛
|
||||
|
||||
- 所有工具具备有界并行调度能力,MCP 每入口在途上限 8;独立图片在客户端进程内最多 2 个。只保留同资源冲突、编辑器实例、canonical 美术包和短提交事务的必要串行边界。同一付费动作必须在容量排队前取得原 durable 槽锁,不重写幂等算法。
|
||||
- Web 工程由客户端提供经完整性校验的 Node/npm 和浏览器健康预检,保留隔离 HOME;缺失或损坏不能静默退回项目或系统中的另一份 Node。
|
||||
- Direct 回合的合同、证据和预算以宿主私有账本为准,项目侧记录只作展示。GUI/CLI 共用入口;首次副作用前冻结非空验收合同,可信新 Web 工程由宿主补充构建和双端验证底线。普通无副作用聊天不强制构建。
|
||||
- 视觉、固定玩法和托管命令分层;`validation.maxRuns` 按执行/返修批次管理,正常开发命令共享批次;累计执行时间与整轮墙钟分别受 `maxExecutionSeconds` / `maxTurnSeconds` 约束,显式配置与 Provider 重试独立。源码、构建输出、环境输入与证据文件摘要分别复核,项目可编辑记录不能抬高预算或伪造成功。
|
||||
- 原生工具使用已验证的捆绑版本逐次审批能力,第三方 MCP 显式逐调用询问;所有 Direct 入口接受宿主同一状态,未知远端结果不得以本地进程退出代替。独立客户端 HTTP MCP 使用明确的 ExternalClient 来源,保留其既有边界,不借用另一 Direct 回合的预算。
|
||||
- 交付必须先封口、排空和取得执行器退出证明,再核对当前文件并提交完成;Windows 用自有 Job 约束进程树,托管命令在恢复主线程前绑定。完整退出证明不足时保持未完成,不把模型最终回复当作验收。非阻塞扩项进入新的用户回合。
|
||||
- 模型配置、实际请求标识和流分段耗时写入现有审计账本;统计采用并发区间并集,有界后台写入,详细条目截断后仍聚合。上游内部排队和推理耗时不可见时保持未知。
|
||||
- Direct 工具集中 SDK 原生 `apply_patch` / `update_plan` 是全局串行单例,按回合为每个 Direct 连接导出一份只把 `apply_patch_tool_type` 置空的完整模型目录即可移除该注册;其余 metadata、匹配与 fallback 不变,不得伪造 `readOnlyHint` 或改造 SDK。等价能力由宿主 MCP 的 `agc_apply_patch`(官方 parser、当前回合 Write 许可、受控进程树、短项目事务)与 `agc_update_plan`(宿主计划状态,不作为验收证据)提供;缺少合法回包通道的原生问答工具一并关闭。
|
||||
- 捆绑 Codex 固定版本只在 `build_support/codex_bundle.rs` 声明一次(当前 0.155.1),构建期侧车清单、宿主补丁执行器身份、逐次审批协议允许列表和模型目录捕获共同引用;升级原生依赖时同步重取同一 tag 的 vendor 解析源码与 UPSTREAM 证据,并复跑真实目录、补丁往返与并发夹具。0.155 起原生执行入口改为统一 exec(`exec_command` + `write_stdin`,旧 `shell_command` 不再注册),宿主许可与预算照常覆盖。
|
||||
- 付费许可按原回合原租约绑定并传递到实际提交点:容量与同动作锁等待可取消,每次新增 POST 前与封口共用短锁复核,封口/终止/耗尽后零新增提交;已越过提交边界的请求不丢弃,保留 operation ID 与不确定状态走 GET 对账。本地写入同理,等待项目锁后必须复核原许可,未结算或失败的写入围栏未恢复前不得封口。
|
||||
- 权威合同:[AI 游戏创作智能体 App 实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)。
|
||||
|
||||
## 2026-09-20 最近项目检查保持项目级隔离
|
||||
|
||||
- 背景:最近项目刷新会重新检查所有路径。若其中一个目录损坏、超时或不可读,清空整张状态表会让已确认正常的项目暂时全部显示“检查中”,用户只能移除坏项目后看到列表恢复。
|
||||
- 决策:最近项目状态按路径独立投影;刷新时保留仍在列表中的最后一次结果,只有新增或尚未检查的项目进入“检查中”。检查代次或列表成员变化后,迟到结果不得写回,单个项目的失败不能改变其它项目的可打开状态。
|
||||
- 验证:`recentProjectsHook.test.tsx` 覆盖“新增慢/坏项目刷新时保留正常项目”;`recentProjectsModel.test.ts`、`unityProjectOpen.test.tsx` 与前端类型检查一并执行。
|
||||
|
||||
## 2026-09-17 GameCreationApp 资源 kind 只保留一份词汇表:严格解析 + `app_log!` 留痕
|
||||
|
||||
- 背景:kind 曾经有三份实现——Rust 手写 `GAME_CREATION_APP_CANONICAL_ASSET_KINDS` + `canonical_game_creation_app_asset_kind()`(带 legacy 别名表与 `font → document` 特例)、TS 手写 `GAME_CREATION_APP_CANONICAL_ASSET_KINDS` + `GAME_CREATION_APP_LEGACY_ASSET_KINDS` + `canonicalGameCreationAppAssetKind()`、以及 ts-rs 生成的 TS union。两份手写表互相引用又各自收口,判据直接分叉(同一个 `"UI"` 一边归一成 `ui-design`、一边收口成 `unknown`),跨语言一致性只能靠正则解析源码的测试来钉。
|
||||
- 决策(唯一真源):kind 的变体、线上值、`as_str()`、`ALL`、严格解析与 ts-rs 绑定全部由 `server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs` 的声明表派生。两份手写 canonical 列表、legacy 别名表、`canonical_*()` 函数一律删除;生成的 TS union 落在 `packages/shared/src/contracts/generated/GameCreationAppAssetKind.ts`,`packages/shared/src/contracts/gameCreationApp.ts` 只 re-export 它,运行期列表 `GAME_CREATION_APP_ASSET_KINDS` 用穷举 `Record<GameCreationAppAssetKind, true>` 守住。
|
||||
- 决策(单一解析入口):`GameCreationAppAssetKind::parse_with_context()` 是唯一公开解析入口,严格匹配降为私有 `match_canonical()`;易混的 `from_str_lossy()` 与公开 `from_str_or_unknown()` 都已删除,认不出 canonical 值只有这一处收口并留痕(读侧回捞 legacy kind 只查 `ALL` 判真值,不另开解析函数)。口径是等值匹配——不 trim、不 lowercase、不查别名、不迁移;认不出的值收口成 `Unknown`(分类落 `unclassified`)。这是有意接受的行为(历史误写的 kind 不会被"救回"正确栏目),不再提供任何兼容入口;`font` 是正式成员,不再走别名。
|
||||
- 决策(登记边界的 kind 口径):外部登记统一走 `assets::registration_asset_kind()`——空白入参落中性 `image`,非空认不出的值严格收口成 `unknown` 并留痕;三条登记边界(Tauri `register_local_asset`、画板导入、平台导入)不再各写一份 trim/兜底。`Unknown` 是无法解析的边界结果,具体写入方必须自行决定拒绝或保留待后续归类,不能静默猜成其他 kind。栅格归一化的 `source_subtype` 只接受图片族成员,文档/字体/音频等一律按 `image` 登记;Agent 回执派生物按 `Text => Document` 落 `document`。
|
||||
- 决策(判据不许散落):切片残留登记只看 `assets/art-spritesheet-slices/` 路径(不再附带 `kind == Icon`);sprite 身份比较忽略随 kind 派生的 `metadata.asset_type`;TS 侧「UI 编辑器文档资产」判据只留 `isGameCreationAppUiDesignDocAsset()` 一份,资源画布入口 / UI 编辑器桥接 / 资源引用缩略图统一调用。
|
||||
- 决策(已知代价,不补救):既有项目里无法解析的 kind 读入即 `unknown`,依赖 kind 等值比较的运行门禁会按"缺少该资源"处理。这是严格解析的必然结果,本次明确不为存量数据做迁移;将来若要迁就必须单独立项,不能改写解析边界。
|
||||
- 决策(留痕必须真的落地):原实现用 `tracing::warn!`,而 AGC 壳没有 tracing subscriber,等于没有日志。现在 `shared-contracts` 只暴露可注册回调 `set_non_canonical_asset_kind_reporter()`,AGC 壳在 `main()` 里接到 `app_log!`,日志同时含原始输入串与调用上下文;`kind-observability` feature 与 `tracing` 依赖一并删除。TS 侧对应 `parseGameCreationAppAssetKind()` 的 `console.warn`。
|
||||
- 决策(平台/画板词汇表):平台生成输入先严格解析为 `GameCreationAppAssetKind`,登记时直接写入已解析的 enum,不再保留 `platform_art_asset_manifest_kind()` 或任何平台别名/fallback 映射;图片快速编辑来源同样只允许 canonical 静态图片成员并要求 `mediaType=image`,原 `EDITOR_IMAGE_EDIT_STATIC_IMAGE_ASSET_KINDS` 兼容白名单已删除。
|
||||
- 验证:`cargo test -p shared-contracts`(含词汇表唯一性、严格性与留痕用例)、`cargo test --locked -p shared-contracts --features ts-bindings export_bindings` 后 `git diff` 为空、AGC bin 定向用例(`derived_asset_manifest_kind_is_never_unknown_for_text_derivatives`、`non_canonical_manifest_asset_kinds_report_raw_values_only`)、`npx vitest run packages/shared/src/contracts/gameCreationApp.test.ts apps/ai-game-creator-shell/tests/uiDesignResourceBridge.test.ts apps/ai-game-creator-shell/tests/appSurface.test.ts`。
|
||||
- 关联文档:[AI 游戏创作智能体 App 实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md) 的 2026-09-17 节。
|
||||
|
||||
## 2026-09-16 DirectProject 引用渲染收敛到 canonical user item 深模块
|
||||
|
||||
- 背景:`agent/direct_codex_references.rs`(平行 `DirectCodexTurnReference` DTO 与渲染路径)已退役,引用身份、上限与投影收敛到 `agent/direct_codex_user_item/`;本轮 prompt 里引用只投影为 `[素材引用 resourceId=…;项目路径=…]` 摘要,`MAX_DIRECT_CODEX_REFERENCES` 归 `direct_codex_user_item/validation.rs`。
|
||||
- 决策:2026-09-14「Direct Codex 引用 UI 设计文档生成代码上下文」的 prompt 注入改落在 `agent/direct_codex_user_item/wire.rs`,入口是 `direct_codex_user_item_to_prompt`:引用命中 `ui-design-doc` + `application/json` 时顺序调用 `generate_ui_design_code_at`,成功追加 `请先阅读生成的带有文档的代码片段: {relative_path}`,失败追加原始 `生成代码遇到错误{error}`,其它引用继续处理,manifest 身份摘要不变。
|
||||
- 决策(无写副作用):只在生成本轮 prompt 时展开该上下文;历史 item 回读走 `direct_codex_user_item_to_response_item` 的纯投影,不得触发 UI 代码导出或任何项目写入。
|
||||
- 验证:`agent::direct_codex_user_item` 定向 16 条通过,覆盖成功注入、生成失败保留引用摘要、非 UI 文档不触发与历史回读不产生 `ui/generated-*.js`。
|
||||
|
||||
## 2026-09-19 AGC Direct 删除每回合四项媒体资源请求上限
|
||||
|
||||
- 背景:2026-08-24 引入的单回合四项上限以「整个 agent run(一条用户消息到回合结束)」为窗口,计数只增不减、请求完成不释放额度;autonomous 游戏构建要求 agent 不停下跑完整局,额度耗尽后的报错实际是终态,与技能的三次重试纪律冲突,现场表现为长时间无效重试。2026-09-18 先将不计费的抠图豁免,但付费 create/derive 仍受同一窗口问题影响。
|
||||
- 决策:整体删除该上限机制。移除 `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` 互斥保证,成本控制由服务端计费与泥点余额兜底,客户端不再按回合计数设限。
|
||||
- 验证方式:`cargo check`(ai-game-creator-shell src-tauri)通过,203 项警告与基线一致;无测试断言该上限,未新增测试。
|
||||
|
||||
## 2026-09-18 AGC Direct 抠图不占每回合四项付费媒体额度
|
||||
|
||||
- 状态:2026-09-19 起该上限机制整体删除(见上条),本条目仅作追溯。
|
||||
- 背景:2026-08-24 起 `agc_create_or_derive_resource` 与 `agc_remove_background` 共用每回合四项媒体资源请求上限。抠图服务端持久化 `generation_cost_mud_points: 0`(不计费),bgfilter 实测单张约 1 秒,上限导致一回合抠超过四张时后续请求被直接拒绝、agent 反复无效重试。
|
||||
- 决策:`agc_remove_background` 不再经过 `resource_request_ids` 计数,直接按回合身份与请求指纹确定性派生 operation/idempotency id(与 map 复用结果一致),同指纹重试与 pending 对账语义不变。付费的 create/derive(含 `agc_edit_image` 委托)维持四项上限与原有报错文案,且抠图请求不再挤占其额度。
|
||||
- 验证方式:`cargo check`(ai-game-creator-shell src-tauri)与 `git diff --check` 通过;未新增测试。
|
||||
|
||||
## 2026-09-18 AGC 未提交快速编辑草稿按资源路径归属,正式恢复账本不动
|
||||
|
||||
- 背景:画布验收项 AGC-006/023 要求「点外部 / Esc 收起面板、或换素材卡」不再无条件丢弃用户刚写的提示词与 `@` 引用,于是宿主内存里多了一份未提交草稿表(`resourceCanvasQuickEditModel.ts` 的 `ResourceQuickEditDraftStore`),与原生 `list_pending_local_project_resource_edits` / `resume_local_project_resource_edit` 那条正式可恢复账本**并存**。
|
||||
- 决策:草稿键是投影的稳定身份 `ProjectResource.path`,不是投影 id。资源投影本来就按 path 去重(`resourceProjectionModel` 的 `uniqueByPath`),而 id 会变——任务产物经 `normalize_local_project_raster_resource` 登记成正式素材后,同一张卡从 `task:<任务>:<路径>` 变成 `asset:<id>`。用 id 当键时,任何**不是打开中这一笔快速编辑自身触发**的重投影(换素材卡收起面板、Agent 或外部编辑器把同一路径登记成资产)都会让草稿落到再也点不到的键上:恢复入口按 id 过滤后静默丢弃、重开面板按新 id 查不到。按路径归属后不需要任何「跟着投影搬家」的换键逻辑,那条逻辑本身就是搬丢的来源。
|
||||
- 决策:本地草稿是**会话内存态**——不落盘、不进账本、不参与对账;共享入口「管理未完成编辑」同时列出来源不同的两种条目,草稿条目显式标注「本会话未提交,关闭客户端不保留」,账本读取失败时仍报出本会话草稿条数,避免用户把两者当成同一种持久事实。
|
||||
- 决策:`ResourcePromptPolishSlot` 与共享聊天输入区(`ResourceReferenceInput`)共用 `usePromptPolish`;「与原文相同」以**规范化之后要写回宿主的文本**为准(回包被长度上限截回原文同样算没变化),此时不落原文快照、不回填宿主,只给提示,避免出现点了等于没点的「恢复原文」假入口。
|
||||
- 边界:发送前提醒面板里「AI 润色并发送」遇到原样回包仍按用户意图直接提交,本轮按产品取舍保留(记录在 `docs/project-memory/todos/【待办】画布验收后续修复-2026-09-18.md`)。
|
||||
- 落地:`apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasQuickEditModel.ts`(工厂空表 / 按路径写入读取丢弃 / `listResourceQuickEditDraftEntries`)、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`(草稿接线与恢复入口)、`apps/ai-game-creator-shell/src/features/resource-canvas/ResourcePromptPolishSlot.tsx`、`apps/ai-game-creator-shell/src/features/project-workspace/{usePromptPolish.ts,ResourceReferenceInput.tsx}`;回归见 `tests/resourceCanvasQuickEditModel.test.ts`、`tests/resourceCanvasQuickEditDraft.test.tsx`(含「面板收起后旁路重投影」用例,改回 id 作键即红灯)、`tests/usePromptPolish.test.tsx`、`tests/resourcePromptPolishSlot.test.tsx`、`tests/chatPromptPolish.test.tsx`。
|
||||
|
||||
## 2026-09-18 派生资源名称在前端镜像 Rust 门禁,宁可按原话拦下也不截断
|
||||
|
||||
- 背景:快速编辑(`-编辑版`)与角色动画(`-角色动画`)的派生资源名由前端按源资源 label 拼出,直接进 `derive_local_project_resource` 的 `input.assetName`;Rust `normalize_resource_edit_name`(`resource_editor.rs:802-808`)要求 `trim` 后非空、码点数 1..=120 且不含控制字符,任一不满足即整条请求失败。源 label 取任务标题(Agent 回执)或长生成文件名时,用户必须提交一次才看到「派生资源名称必须在 1..=120 字符内且不能包含控制字符」。这是画布验收遗留项 AGC-025 的成因之一。
|
||||
- 决策:前端只做镜像,不另造中文语义——上限 120、`Cc` 控制字符判定(C0 / DEL / C1,`Cf` 继续放行)、`trim` 后判空、按 Unicode 码点计长(Rust `chars().count()`,不是 JS UTF-16 `.length`)四条与 Rust 逐条对齐,提示文案与 Rust 返回串逐字相同;两份判定由跨语言测试直接读 Rust 源码对表。
|
||||
- 决策:名称不合法时**阻止并提示**,不截断。截到 120 会让同前缀的两个长源名塌成同一个名字,把一次可见失败换成一次静默重名;默认名函数因此保持返回不合法原名,由检查结果里的 `error` 让调用方先停下。
|
||||
- 落地:`apps/ai-game-creator-shell/src/view/project-development/resourceEditModel.ts` 的 `RESOURCE_EDIT_NAME_MAX_LENGTH` / `RESOURCE_EDIT_NAME_INVALID_NOTICE` / `resolveResourceEditNameCheck` / `resolveDerivedResourceNameCheck(resource, 'edit' | 'character-animation')`,纯函数与跨语言对表见 `tests/resourceEditModel.test.ts`;两种后缀共用同一张后缀表,避免「-编辑版」与「-角色动画」再次分叉。
|
||||
- 接线:`index.tsx` 的 `submitResourceQuickEdit` 与 `submitResourceCharacterAnimation` 都在**调 `resolveResourceDeriveSource` 之前**做检查(正规化写盘发生在那一步之后),不合法时只把 `status: 'failed'` 与 Rust 原话写进各自的浮层面板并 return,不发派生请求;`assetName` 用检查结果里的 `name`。回归见 `tests/projectResourceLiveIntegration.test.tsx` 的两条「源名超 120 码点」用例与既有的合法名校验 `source-art-编辑版` / `hero-角色动画`。
|
||||
- 边界:资源画布的图片类生成走另一条门禁(`commands.rs` 的 `LOCAL_PROJECT_ASSET_MAX_ASSET_NAME_CHARS`,文案「素材名称超出安全边界」),不在本条口径内;提示词上限仍是 `resourceEditPromptMaxLength` 那一份,本决策不动它。
|
||||
|
||||
## 2026-09-19 删除 Project Supervisor 前端链路:普通项目只有 DirectProject,立项策划只有策划聊天
|
||||
|
||||
- 背景:`ProjectSupervisorView` 同时是工作台普通项目的聊天宿主、Supervisor 独立调试窗口(`?supervisor-chat` / `?agent-chat`)和纯聊天容器(`SupervisorChatOnlyView`)的入口;同一条链路还带着 `App.tsx` 里的 Supervisor 会话/运行态轮询、工作台壳的 Supervisor 运行态与专业 Agent 面板、开发态文件/记忆/资产/预览面板和 `AgentConversationOverlay`。普通项目的 DirectProject 因此长期作为「Supervisor 宿主里的一个分支」存在,Direct 自己的订阅、历史、发送、队列、附件和中止生命周期没有独立归属。
|
||||
- 决策(前端整体退役):删除 `ProjectSupervisorView`、`SupervisorChatOnlyView`、`ProjectWorkspaceChatPane`、`AgentConversationOverlay`、`DeveloperProjectPanels`、`DeveloperRuntimePanels`、`features/agent-runtime/panels.tsx`、`DeveloperAgentPanel`、`useDeveloperAgentPanel`、`useDeveloperAgentState`、`developerAgentControls`、`src-tauri/capabilities/developer.json` 与 `?supervisor-chat` / `?agent-chat` 调试入口;`App.tsx` 里只服务这些面板的 state/ref/effect/handler(`agentConversation*`、`handleFile*`、`handleMemory*`、`handleCanvas*`、`handlePreview*`、`commandLog`、`llmConfigStatus`、`editorBaseUrl`、run trace/history 面板状态等)一并删除。不保留兼容别名、feature flag、双跑路径或墓碑注释。
|
||||
- 决策(策划链路独立成模块):Design Agent 与 Planning V2 的容器与表现移入 `view/project-development/planning/`(`PlanningChatView`、`PlanningUserInputCard`、`GddApprovalCard`、`DesignAgentSurface`、`PlanningLaneRuntimeStrip`、`planningLane`、`planningSessionV2`、`planningSessionContract`),策划入口不再借用 Supervisor 的组件身份。
|
||||
- 决策(改名到中性概念):`ProjectSupervisorComponentProps → ProjectChatComponentProps`、`WorkspaceLauncherShellProps.ProjectSupervisor → ProjectChat`、`ProjectDevelopmentView.supervisor → chat`、`orchestrationMode → agentDockVisible`、`initialSupervisorMessageClaims → initialTurnClaims`(`claimInitialTurnForPage`)、`ProjectManifestSnapshotSource 'supervisor' → 'chat'`、CSS `project-supervisor-*` / `project-planning-*` → `project-chat-*`;`PROJECT_SUPERVISOR_AGENT_ID` / `PROJECT_SUPERVISOR_PLAN_SOURCE` 常量删除。
|
||||
- 决策(清单失效事件归壳):`game-creator-manifest-invalidated` 现在由工作台壳(`WorkspaceLauncher`)与 DirectProject 聊天各自订阅——壳用一次配对读(`revision → 清单 → revision`,`source: 'asset-event'`)把运行态落盘的新资源并入 `currentProjectContext`,**不**重新检查项目目录;聊天侧仍由 `useDirectProjectManifest` 刷新自己的 `@` 引用清单。
|
||||
- 决策(残留清理):随面板一起失去调用方的模块与导出一并删除,不留死代码——`features/project-workspace/projectSummaryCommands.ts`(旧 Supervisor 斜杠命令处理器,1054 行)、`app/constants.ts` 的 `AGENT_RUN_HISTORY_*`、`features/agent-runtime/model.ts` 里只被退役面板使用的 22 个导出与 `AGENT_RUNTIME_DEDICATED_CARD_TOOLS`、`features/project-summary/agentPresentation.ts` 里 17 个 Agent 面板 / LLM 配置 / 读取草稿摘要函数(含 `summarizeAgentStatusCardsForChat`、`summarizeAgentRunHistoryReadDrafts` 等),以及 `agentRunTrace` / `memoryCommands` / `projectCommandPolicy` 中失去调用方的校验与解析函数。
|
||||
- 边界:后端 Rust Runtime 的 `project-supervisor` 运行身份、`start_game_creator_supervisor_runtime_task` 命令、`agent-runtime:supervisor-*` 端到端脚本与 `project-supervisor` 提示词/工具策略不在本次删除范围内:它们是 Runtime 的 Agent 身份与既有权衡,不是前端链路。
|
||||
- 验证:全量 `npx vitest run apps/ai-game-creator-shell/tests` 126 失败 / 1517 通过 / 15 跳过(1658 条),`HEAD` 基线同口径 166 失败 / 1637 通过 / 17 跳过(1820 条):逐条比对失败集合**没有新增项**;基线里 40 条与 Supervisor 前端面绑定的失败用例随实现与用例一并删除(含旧调试窗口、开发者面板与 Agent 对话浮层的 51 条用例);剩余 115 条 appSurface 失败与 10 条 `clientApi.test.ts` 失败(jsdom `localStorage` 环境)在基线上同样失败;残留清理后按同一口径复跑,失败集合与失败条数均无变化。`npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit --noUnusedLocals`、prettier、eslint、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check` 通过。
|
||||
- 关联文档:[ADR DirectProject 独立聊天容器与工作台钱包布局](../../adr/【ADR】DirectProject独立聊天容器与工作台钱包布局-2026-09-18.md)、[实施计划](../plans/【实施计划】DirectProject聊天模块抽离-2026-09-18.md)。
|
||||
|
||||
## 2026-09-19 DirectProject 聊天容器按「组件带逻辑」分层,线程订阅与 reducer 提成独立 hook
|
||||
|
||||
- 背景:DirectProject 聊天虽然已经搬进 `view/project-development/chat/`,但 `DirectProjectChatView.tsx` 单文件 500+ 行,表现层只按 `components/`、`composer/` 平铺文件,线程订阅(`subscribe → consume → notify`)与聊天 reducer 状态混在 `useDirectProjectChatController` 里,组件和它自己的逻辑分散在两个目录。
|
||||
- 决策(组件带逻辑):`chat/components/` 下一个组件一个目录,组件自己的逻辑与表现同目录:`ToolCallGroup/`(`ToolCallGroup.tsx` + `toolCallGroupPresentation.ts`)、`DirectProjectComposer/`(`DirectProjectComposer.tsx` + `ComposerControls.tsx` + `chatComposerQueue.ts` + `chatComposerVoice.ts`)、`DirectProjectConversation/`(`DirectProjectConversation.tsx` + `DirectProjectTurn.tsx`)、`DirectProjectChatHeader/`、`DirectProjectSettingsDialog/`。视图只做组合,不再内联渲染分区、回合和输入盒。
|
||||
- 决策(订阅与 reducer 独立 hook):新增 `chat/controller/useDirectThreadChatSubscription.ts`,持有 `subscribe → consume → notify` 单飞循环与 `DirectThreadChatState`(含通知先于订阅回执的欠账处理、`SUBSCRIPTION_EXPIRED` 重订、订阅回执锚点闸门、项目切换清理),并暴露 `entries`、`turnRunning`、`mergeHistoryItems`、`markTurnStopped`。控制器只读投影结果,历史分页仍并入同一个 reducer,不复制第二份事实源。
|
||||
- 决策(壳只传窄上下文):`DirectProjectChatView` 的公开入参只有项目路径、入口首轮需求(canonical `content[]`)和两条权限门;工作台级动作(`game.run_local` 预览结果等)不再写 `App.tsx` 的 `messages`,改由壳通过 `DirectProjectChatHandle.announce` 交给聊天自己的本地消息流,壳不持有 Direct 聊天消息。
|
||||
- 边界:不改 Tauri/Rust 命令、DTO、Thread Manager、Codex app-server、持久化事实源、路由选择、发送/队列/附件/中止/历史锚点语义;不保留旧路径 alias、双跑或兼容 fallback。
|
||||
- 验证:五个 Direct 纯模块测试 40 条通过;`appSurface.test.ts -t "direct"` 15 通过 / 4 失败,4 条与 `HEAD` 同口径基线同为失败(harness 未桩模型配置),基线同口径 14 通过 / 5 失败;全量 `appSurface.test.ts` 由基线 155 失败降到 153 失败;app `tsc --noEmit`、eslint、prettier 通过。
|
||||
- 关联文档:[ADR DirectProject 独立聊天容器与工作台钱包布局](../../adr/【ADR】DirectProject独立聊天容器与工作台钱包布局-2026-09-18.md)、[实施计划](../plans/【实施计划】DirectProject聊天模块抽离-2026-09-18.md)。
|
||||
|
||||
## 2026-09-18 Provider 瞬态重试次数严格按设置执行(游戏开发 Agent 与策划 Agent 不再被档位收进区间)
|
||||
|
||||
@@ -16,6 +282,7 @@
|
||||
- 背景:AGC 已通过本地项目 ID 建立并持久化本地项目到主站远端画布项目的绑定,但 `agc_remove_background` 提交请求仍把本地 `manifest.project_id` 放入 `projectId`;`assetFolderId` 已使用远端素材目录 ID。主站因此按项目不存在或不属于当前账号返回 404,主站抠图和 BgFilter 本身均正常。
|
||||
- 决策:抠图请求及工具回执统一使用 `prepare_external_canvas_generation_context` 返回的远端 `context.project_id`;本地 manifest 项目 ID 只用于绑定键和本地状态,不得作为主站业务请求的 `projectId`。
|
||||
- 验证:客户端定向 Rust 测试、格式、编码和 diff 检查通过;未修改主站路由或 BgFilter。
|
||||
|
||||
## 2026-09-17 图集切分模式改为显式声明
|
||||
|
||||
## 2026-09-17 DirectProject 首屏历史锚点只认订阅回执的 lastCompletedItemId
|
||||
@@ -37,6 +304,7 @@
|
||||
- 标准美术包:客户端显式声明 `sliceMode=connected-components` + `sliceCount=4`,本地按用途位置写四张 canonical 切片前再次校验数量正好为四,数量不符时失败关闭,禁止截断或补位。
|
||||
- 测试环境:在提权 shell 的 Windows 主机上,`%TEMP%` 下新建目录的默认所有者是 `BUILTIN\Administrators` 而不是当前 TokenUser,AGC 的所有者校验会拒绝测试自己创建的项目根;测试构建对该情形(仅限 `%TEMP%` 内、且失败原因为所有者不匹配)先按“本调用创建的对象”初始化所有者后重试,临时目录之外的越权所有者继续失败关闭。
|
||||
- 权威合同:[画板图标素材生成入口设计](../../【编辑器】画板图标素材生成入口设计-2026-06-15.md)。
|
||||
|
||||
## 2026-09-17 `agc_tools` 媒体资源提示词上限收敛为单一口径,并按 kind 暴露给模型
|
||||
|
||||
- 背景:有人反馈「客户端没法由 agent 调用图片快速编辑功能以及背景音乐生成功能」。核查后工具本身都在(`agc_edit_image` / `agc_create_or_derive_resource`),图片快速编辑在 2026-09-14 的真实项目日志里也有成功记录;但存在三类真实缺陷:① `agc_create_or_derive_resource` 的 `prompt` 在 schema 里只声明 4000,真实上限却是按 kind 分的(背景音乐 140、音效 1900、视频/角色动画 4000、图片 32000),MCP 层还额外写死了一条 140 判断,模型从 schema 与 skill 都看不出 140/1900,写一句正常长度的背景音乐描述就当场被拒;② 客户端 UI 用同一口径但会截断并提示,agent 侧却只有硬拒,形成「UI 能做、agent 调不动」的观感;③ `sourceLocalAssetId` 不是已登记资源时只报「不属于当前项目已登记资源」,模型会原地重试而不会先登记。
|
||||
@@ -134,7 +402,7 @@
|
||||
- 决策(槽身份 = 精确动作身份):`run_id = slot-<sha256(动作身份材料)>`,材料为 `prompt / output_path / aspect_ratio / image_size / asset_kind / asset_label / replace_existing / require_slices`(`canvas_generation.rs`)。不同 prompt 或素材名 → 不同槽 → 不同进程锁键与不同 `.lock` 文件 → 可同时在途。**不用随机 uuid**:随机身份会让「同一精确动作重放」落到新路径,必须再造一层 action→ledger 索引才能保幂等;用动作指纹让「槽身份 ≡ 精确动作身份」,路径查找即幂等查找。
|
||||
- 决策(幂等不变):同一精确动作 → 同一路径 → 命中已有 prepared/accepted 账本并复用原 `idempotencyKey` / `operationId`,不二次 POST;相同动作并发仍被拒的既有语义保持。
|
||||
- 决策(旧槽账本最小懒迁移):旧槽账本形状可读、不 panic、不 fail-closed;在 durable guard 之后、任何远端 POST 之前,**仅当**旧槽账本的 `agentId / runId / actionFingerprint` 与本次精确动作一致时,把它迁移到新路径(保留 `idempotencyKey` / `operationId` / 状态)并删除旧文件;属于其他动作的旧账本一律不动。旧「固定槽」(`run_id == agent_id`)账本的 fail-closed 拒绝保持原样。
|
||||
- 已知边界:`slice_count` **不进**身份(保留升级前粒度,也是旧账本迁移可行的前提)→ 仅切片数不同的两条图集请求仍共槽、第二条失败关闭;当前所有 standalone 槽的生产调用方都把 `slice_count` 传成 `None`(唯一能传 sliceCount 的是 agent 工具通道,它不走 standalone 槽),该边界当前不可达,但**缺负向用例**。
|
||||
- 当前身份边界:精确动作指纹包含 `slice_count`,Agent 图片工具同样使用 standalone 动作槽;不同精确动作可并行,同一动作在容量排队前持有原跨进程槽锁。不得再依据早期“Agent 不走 standalone 槽”的说明拆除幂等或重复提交。
|
||||
- 前端口径:本批**仍保留单条在途的前端排队**(提交节流),真并行派发需要并发收口设计(配对读 + manifest CAS + 聚焦意图互不覆盖),留待下一批。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs`、`.../external_generation_state.rs`。**未改** `/api/external/v1` 契约 / OpenAPI / DTO,未改 `recovery_scan.rs`(身份白名单与孤儿清理语义不变),账本 schema 仍是 v3、字段集不变,只改 `runId` 取值来源。
|
||||
- 验证方式:新增 `standalone_generation_binds_each_exact_request_to_its_own_stable_slot`、`distinct_standalone_actions_hold_independent_durable_output_slots`、`concurrent_distinct_standalone_generations_both_succeed_with_one_post_each`(端到端:两条 `outputPath=None` 的不同动作要求两条 POST 同时到达,各自 poll → read-url → 下载 → 落盘)、`legacy_output_slot_ledger_is_adopted_by_the_same_exact_action_only`。变异验证(已实测):把 `run_id` 退回旧公式 → 4/4 红(含「durable 输出槽身份必须等于该精确动作的身份」与并发用例的「任何远端 POST 前拒绝并发请求」);把懒迁移短路 → 旧账本用例红。定向 `agent::generation::` + `recovery_scan` 95 passed、`direct_runtime media` 195 passed。
|
||||
@@ -155,6 +423,7 @@
|
||||
- 决策:图片类生成接线改为 `start_local_project_asset_generation` + `list_local_project_asset_generations` 后,同步命令 `generate_local_project_asset` **已无生产调用方**,只剩 `src-tauri/src/tests/project.rs` 的三条集成用例与 `commands.rs` 的自身单测在调它;因此登记进 `scripts/check-config.mjs` 的 native-only 白名单(该门禁有「App invoke 与白名单互斥」断言,谁重新给它接调用方就必须同时删掉这条白名单项)。
|
||||
- 待办:它是**注册中的可调用 IPC**,一旦被将来代码调用就是一条绕过任务账本、单次阻塞最长 35 分钟的并行生成路径。下一批次应删除它,或改为转调 `start_local_project_asset_generation`(连带迁移那三条集成用例)。
|
||||
- 关联文档:[栏目画布底部工具栏入口矩阵](../../technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md)。
|
||||
|
||||
## 2026-09-14 AGC 壳 Rust 套件按「一片一 job」拆分,客户端 Rust 关键路径压到 7 分钟以内
|
||||
|
||||
- 背景:`AI game creator shell Rust tests` 是客户端 CI 的关键路径(run 2097 实测 15 分 27 秒)。拆开来看:前置 5 分 30 秒(checkout 10s + `npm ci` 2m45s + Cargo fetch 2m35s)、编译 1m39s、**AGC 壳 bin target 的 2466 条单测串行 507s**、`agent-run` smoke 51s。这 2466 条全在 `apps/ai-game-creator-shell/src-tauri` 的 bin target 里,一条 `cargo test … -- --test-threads=1` 跑完。
|
||||
@@ -167,6 +436,14 @@
|
||||
- 影响范围:`.gitea/workflows/project-ci.yml`(十一个 job)、`scripts/check-native-shells.mjs`(分组由五个到十个:新增 `agc-rust-crates`、`agc-rust-shard-1..4`、`agc-rust-smoke`,移除 `agc-rust` 与随后的 `agc-rust-shell`)、根 `package.json`、`scripts/project-ci-workflow.test.ts`(新增纯 cargo job 免 `npm ci`、分片运行器覆盖校验、crate 级 job 预热顺序断言)、开发运维文档与共享记忆。本仓库不把 Project CI 的 context 配成 `master` 分支保护的合并必需检查(2026-09-14 复核),合并前由人工确认结果,因此 job 拆分/改名不需要同步分支保护设置。
|
||||
- 验证方式:`npx vitest run scripts/project-ci-workflow.test.ts`;分片运行器本地以 `agent-runtime-core`(7 条 → 2/2/2/1)与 `platform-llm`(146 条 → 49/49/48)验证分片、`--exact` 与片 TMPDIR 隔离,负例 `--shard-index=5` 立即失败;`node scripts/check-native-shells.mjs --groups=contract` 回归。预期每个分片 job 收敛到 5 分钟以内(前置约 1 分 30 秒 + 编译约 1 分 39 秒 + 约 617 条用例)。
|
||||
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)、[踩坑记录](pitfalls.md)。
|
||||
|
||||
## 2026-09-20 AGC Rust 分片收敛为两条 lane,匹配 runner 有效并发
|
||||
|
||||
- 背景:四个独立 shard job 让每个 job 重复 checkout、Cargo 依赖预热和测试二进制编译;Gitea run 2105 的 11 个 job 时长合计约 39 分钟,而整轮 wall-clock 为 20 分 23 秒,反推有效并发约 1.9 个 job。继续按「一片一 job」拆分已经把新增 job 开销和排队时间重新放回关键路径。
|
||||
- 决策:保留 4 片名单、`--test-threads=1`、独立 `TMPDIR` 和每片的全集/互斥校验,但把 workflow 收敛为两条 Rust lane;lane 1 顺序运行 shard 1/4、2/4,lane 2 顺序运行 shard 3/4、4/4。每条 lane 只预热一次 AGC 壳 manifest,lane 之间仍保持 job 级并发;不在同一 job 内并行多个测试进程。
|
||||
- 影响范围:`.gitea/workflows/project-ci.yml`、`scripts/project-ci-workflow.test.ts`、`scripts/check-native-shells.mjs` 与 Rust 分片说明文档。job 名称改为 `AI game creator shell Rust lane 1/2`、`lane 2/2`;分组脚本与 4 片测试名单保持不变。
|
||||
- 验证方式:运行 `npx vitest run scripts/project-ci-workflow.test.ts`、`node --test apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs`、`npm run check:encoding` 和 `git diff --check`;真实 Gitea run 需要确认两条 lane 均覆盖两片且 smoke、crates、Backend、Native、Frontend、Repository job 仍全部上报。
|
||||
|
||||
## 2026-09-14 AGC 资源画布改为「手动整理」:新素材不再自动重排,整张重排只由「整理画布」发起
|
||||
|
||||
- 背景:生成一张新素材会让整张资源画布重排。两个 layout hook 都把 `rederiveAutomaticPositions` 打开(type 侧无条件 `true`,dependency 侧长期等于 `resourceGraphReady`),而该开关的语义是「每次资源协调签名变化就丢掉全部 `manuallyPlaced=false` 坐标、按当前资源与拓扑整体重算」;新增一张素材必然改签名,于是既有自动卡全部跟着挪位,用户刚记住的位置就没了。画布上也没有任何显式整理入口(`复位资源视图` 只复位视口)。
|
||||
@@ -190,7 +467,7 @@
|
||||
|
||||
- 决策:待实施的生产迁移以自由协作策划原型为行为基线,仅复用 Provider、恢复、文件操作、审计和 UI 通信;不继承旧 Planning V2 的强制工具、问询轮数、GDD 内容校验和版本审批。保留五阶段与顾问态、当前阶段资源注入和产物存在性检查,系统阶段空必需清单不增加解析或登记功能。
|
||||
- 交互边界:正式审批由 ✅/❌ 决定;❌ 只取消待审批、不唤醒 Agent,等待审批时禁止发送消息但允许浏览工作区。用户可直接查看工作区,编辑可暂不做,不引入用户与 Agent 协同编辑锁或冲突合并。
|
||||
- 影响范围:策划入口、会话与工具实现、资源打包、文件浏览;实施中。无旧 Planning V2 会话的策划项目走新设计 Agent,已有 V2 会话仍走原链路。
|
||||
- 影响范围:策划入口、会话与工具实现、资源打包、文件浏览;迁移已完成。当前入口统一使用新 Design Agent,旧 Planning V2 会话不再继续运行。
|
||||
- 关联文档:[策划 Agent 生产迁移与工作区浏览](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)。
|
||||
|
||||
## 记录格式
|
||||
@@ -8038,7 +8315,7 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
## 2026-08-10 AGC 打开现有 Godot 项目
|
||||
|
||||
- 项目双根决策(2026-08-14 更新):项目组只保留通用“打开项目 / 新建项目”,不再提供独立 Godot 入口。用户选择目录始终是工作区根,也是 `.agent`、Session、Runner、沙箱、通用文件工具和外围资料的唯一授权根;实际 Godot 根由根目录或一层直接子目录中的普通文件 `project.godot` 唯一确定,并以工作区相对 `godotProjectRoot` 记录,根目录使用 `.`。不得把 Runtime 根切换成 Godot 子目录,也不得复制工程或建立第二套工作区。
|
||||
- 发现与歧义决策:根目录命中优先;根未命中时只检查一层直接子目录,唯一命中才通过,多个命中在任何 `.agent` 写入前失败关闭。候选目录与工程文件拒绝符号链接和 Windows reparse point,二层及更深不递归。未来只有 Godot 专属命令显式使用经过校验的相对 Godot cwd。
|
||||
- 发现与歧义决策:根目录命中优先;根未命中时只检查一层直接子目录,唯一命中才通过,多个命中在任何 `.agent` 写入前失败关闭。候选目录与工程文件拒绝符号链接和 Windows reparse point,二层及更深不递归。未来只有 Godot 专属命令显式使用经过校验的相对 Godot cwd。(2026-09-21 部分取代:多命中改为按目录名排序取第一个,`project.godot` 允许链接并按目标判定;候选子目录与更深层的边界不变,见本文件「2026-09-21 Godot 工作区发现放宽与内置插件行去掉手动启动」。)
|
||||
- 元数据决策:首次导入只在工作区根创建并保留 `.agent/manifest.json`、`.agent/agent.db`、`.agent/logs/` 与 `.agent/runtime/`;不得创建默认 Web 原型的 `game/`、`assets/`、`memory/`、`exports/`。已有有效 `.agent` 项目继续复用身份;缺失或错误的可推导 `godotProjectRoot` 只在 Godot 打开边界按唯一文件布局校准,歧义时不改写。
|
||||
- Windows 锁文件决策:提升权限进程新建 `.agent/.manifest.json.lock` 时,Windows 可能把 owner 设为 `Administrators`。仅在固定锁路径已取得不共享独占句柄并确认是普通、非 reparse、单链接文件后,才初始化为当前 `TokenUser`;随后再次复核句柄并执行原有 owner/DACL 校验,不放宽既有异常对象的安全规则。
|
||||
- 运行决策:Godot 项目提交给 Project Supervisor 时使用 `standard` Run Profile,避免触发 Web 专用 `game/index.html`、HTTP preview 与自主 Web 完成门。Godot 编辑器启动和内嵌运行预览不在本切片范围。
|
||||
@@ -8815,18 +9092,19 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 决策(矩阵):`ui-interaction` = 生成图片 / 生成规范(图标规范、自定义规范)/ 生成图标素材 / 生成 UI 设计图 / 上传;`character` = 生成图片 / 生成规范(角色规范、自定义规范)/ 生成角色形象 / 上传;`scene` = 生成图片 / 生成规范(自定义规范)/ 上传;`audio` = 生成背景音乐 / 生成音效 / 上传。文档、待归类、项目版本、「所有资源」展开态与资源总览**不渲染**工具栏(`resolveResourceCanvasBottomTools` 返回空数组,渲染判据 `resourceBookState.view === 'child'` 且 category 命中四个栏目)。工具项与顺序是纯数据表,事实源落在 `resourceCanvasBottomToolbarModel.ts`。
|
||||
- 决策(落点与几何):工具栏是 `.game-resource-book-manager` 的**直接子节点**(与画本场景、右下 Dock、通知条并列,不进带 `scale()` 的场景层),锚在**左下角**(`left/bottom: 14px; z-index: 40`)——右下角是既有缩放 / 撤销 Dock,左下角是画布上唯一两者都不占的稳定空位;外壳消费共享 `packages/image-canvas-react` 的 `CanvasToolbar / CanvasToolbarGroup / CanvasChromeButton`(AGC 侧第一次真正消费这组共享 chrome)。一处必要覆盖:共享 `.genarrative-image-canvas__toolbar` 是横向滚动容器,会裁掉它自己弹出的二级菜单,消费端改成 `overflow: visible` + `flex-wrap: wrap`。
|
||||
- 决策(接线与收敛):图片类入口 `generate_local_project_asset`(kind 映射:图片 `image` / 角色形象 `character` / 图标规范 `icon-spec` / 角色规范与自定义规范 `spec` / 图标素材 `art-spritesheet` / UI 设计图 `ui-prototype`);音频入口复用既有 `derive_local_project_resource` 无源生成链路(**不另写一份音频生成**);上传复用 `upload_local_asset`。两者成功后的刷新形状统一为「**配对读 `(revision, manifest)`** → `onManifestChange` → `pendingResourceFocusRef` 定位新卡」,不重算依赖图、不另写布局。**音频入口收敛**:既有「生成素材」浮层入口改为只保留视频(`ResourceCanvasGenerationPanelView` 新增 `kinds` prop,单类型入口不再渲染类型选择器,面板标题与提交文案跟类型走),视频能力不删只是不进工具栏。
|
||||
- 决策(参数口径,避免假控件):比例 / 尺寸选项与默认值复用网页端纯模型 `ImageCanvasGenerationModel.ts`,但按本地 IPC 白名单收窄(本地通道明确拒绝 `4:3`,照搬就是一个点了必失败的选项);提示词上限复用 `resourceEditPromptMaxLength`(32000,与 Rust `LOCAL_PROJECT_ASSET_MAX_PROMPT_CHARS` 同口径);**面板不渲染模型选择器**(本地 IPC 没有 `model` 入参)。规范入口是固定档:只读展示规格,不给点了不生效的比例控件。
|
||||
- 决策(参数口径,避免假控件):比例 / 尺寸选项与默认值复用网页端纯模型 `ImageCanvasGenerationModel.ts`,但按本地 IPC 白名单收窄(本地通道明确拒绝 `4:3`,照搬就是一个点了必失败的选项);图标素材描述去除首尾空白后最多 `200` 个 Unicode 字符,作为唯一 `iconDescriptions` 元素原样提交,不包装、截断或拆条;其余图片提示词上限为 `32000`,前端与 Rust 原生校验保持一致;**面板不渲染模型选择器**(本地 IPC 没有 `model` 入参)。规范入口是固定档:只读展示规格,不给点了不生效的比例控件。
|
||||
- 决策(前置规范图,本轮的关键设计):`ui-prototype` 与 `art-spritesheet` 在 Rust 侧要求项目里已有登记并绑定当前账号的 `assets/art-spec.png`(`local_path` 精确匹配 + `kind == icon-spec` + 图片媒体类型 + 画布来源)。前端判据 `projectHasIconSpecReference` 与它逐字对齐;缺前置时入口**保持可点击**并给可执行原因(`aria-disabled` 而非原生 `disabled`,文案含 `assets/art-spec.png` 与「生成规范 → 图标规范」),且零生成请求。**同时**把「图标规范」入口在缺前置时按 `assets/art-spec.png` 落盘(`outputPath`),否则那两条入口会被前置条件永久锁死、工具栏自身无法满足自己的前置;已有权威规范图时不再传 `outputPath`(Rust `replace_existing` 固定 `false`,指向已存在文件会被硬拒),改为生成一张新的普通图标规范资产。这条是**前端策略**,不扩接口。
|
||||
- 已知能力缺口(用户口径是「缺参数就停下汇报,不自行扩接口」,记账备查):本地 IPC 没有 ① `model` ② `specType` ③ `replaceExisting` 入参。后果:面板不给模型选择;**角色规范与自定义规范共用 `spec` 通道**(靠 `assetName` / 提示词区分),因此「角色规范」不是真正的角色设定板通道;权威规范图存在时无法重写它。另:网页端 composer 子视图(含 `ImageCanvasSpecGenerationPanelView` 直连 `/api/editor/llm/icon-specs/*`)**没有复用**,因为 AGC 无该 BFF 通道且本地 IPC 不接受模型 / 参考图入参,照搬会渲染改不了请求的控件——本轮只把**纯模型**接进来。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/features/resource-canvas/{resourceCanvasBottomToolbarModel.ts,ResourceCanvasBottomToolbarView.tsx,ResourceCanvasAssetGenerationPanelView.tsx,ResourceCanvasGenerationPanelView.tsx,resourceCanvasChrome.css}`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、测试 `apps/ai-game-creator-shell/tests/{resourceCanvasBottomToolbar.test.tsx(新增),resourceCanvasGenerationEntry.test.tsx,projectResourceLiveIntegration.test.tsx,appSurface/project-development.suite.ts}`、PRD §3.10 / §7.9 / §8、`docs/technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md`(新增)、验收用例 S11 / S11a。**未动**:Rust、external v1 / OpenAPI、`packages/`(只消费共享 chrome)、SpacetimeDB。
|
||||
- 验证方式:`npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts`(423 passed,含新增 6 条:栏目分流与总览不渲染、UI 栏 5 个入口载荷、前置缺失可点击说明且零请求、角色栏 2 个入口、音频入口走既有链路、上传 + 配对读清单,另 1 条工具栏与 Dock 的 CSS 几何契约);`resourceCanvasBottomToolbar.test.tsx` 15 passed(新增);`resourceCanvasGenerationEntry.test.tsx` 11 passed(新增单类型用例 1 条);`projectResourceLiveIntegration.test.tsx` 25 passed(「生成素材」面板改名断言同步更新);`npm run agc:typecheck` 全绿(**其中的 `check-config.mjs` 报错已因本轮落地调用方而消失**)、`npm run check:encoding`、`git diff --check` 干净。未 commit。
|
||||
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`(§3.10 / §7.9 / §8)、`docs/technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md`、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md`(S11 / S11a / §7.3)。
|
||||
|
||||
## 2026-09-13 Cocos 插件按当前项目类型暴露
|
||||
## 2026-09-20 Cocos 与 Unity 插件独立于工程类型
|
||||
|
||||
- 决策:`agc-cocos-editor` 只有在当前受控项目通过 Cocos Creator 根目录识别(`package.json.creator.version` + 普通 `assets/`)时才暴露插件、面板和 Cocos 工具;无项目或其它项目类型均隐藏并失败关闭。
|
||||
- 决策:项目切换离开 Cocos 时立即停止已运行的插件实例;启动、面板读取、插件 RPC、Runtime execute 和 DirectProject MCP 工具目录/执行入口全部再次校验项目类型。Cocos 编辑器操作优先经内置插件入口,禁止回退到项目 `extensions/`、`package.json` 插件或第三方 MCP。
|
||||
- 验证:新增 builtin/plugin host 项目级门禁测试,Direct MCP fixture 补最小 Cocos 工程结构;Rust 定向测试、显式 `cocos-editor-execute` feature 编译、编码检查和 `git diff --check` 已执行。
|
||||
- 决策:`agc-cocos-editor` 与 `agc-unity-editor` 的插件列表、启动、面板、插件 RPC、Runtime 与 DirectProject 工具暴露不按当前工程类型过滤;无项目、普通 AGC、Godot、Cocos、Unity 上下文遵循同一套 enable、原生适配器、平台与 feature 规则。前端根据宿主列表中各插件状态分别自动启动,不按项目类型二选一,也不自动展开面板。
|
||||
- 决策:跨工程类型切换保留插件实例及管理能力,继续更新受控项目上下文、失效旧连接并隔离旧请求回执。实际编辑器操作仍要求当前受控项目匹配真实引擎工程与编辑器目标;显式跨项目路径、缺失项目、无目标进程、身份或握手不匹配均在派发前失败,权限、并发、期限与执行不确定阻断保持有效。
|
||||
- 边界:插件可见和工具可调用不能证明任意非引擎目录可成为编辑器执行目标;不改变项目类型、导入或持久数据。Cocos 编辑器操作继续使用内置插件,禁止回退到项目 `extensions/`、`package.json` 插件或第三方 MCP。
|
||||
- 验收口径:分别取得宿主/内置开关、工具目录、前端启动投影与真实目标拒绝证据;真实编辑器、安装包和 CI 与定向测试分层报告。
|
||||
|
||||
## 2026-09-14 DirectProject Codex 取消路径白名单并启用完整 sandbox
|
||||
|
||||
@@ -8853,7 +9131,6 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 影响范围:新增 `apps/ai-game-creator-shell/src-tauri/src/asset_generation_tasks.rs`(+ `main.rs` 注册)、`src/features/resource-canvas/{resourceCanvasAssetGenerationTaskModel.ts,resourceCanvasAssetGenerationQueue.ts,ResourceCanvasAssetGenerationTasksPanelView.tsx}`;改动 `ResourceCanvasAssetGenerationPanelView.tsx` / `ResourceCanvasGenerationPanelView.tsx` / `src/view/project-development/index.tsx`;测试改动 `tests/{resourceCanvasAssetGenerationBackgroundClose.test.tsx,resourceCanvasAssetGenerationQueue.test.ts,resourceCanvasAssetGenerationTasksPanel.test.tsx}`(新增)与 `tests/appSurface/project-development.suite.ts`(把「每个入口一次 `generate_local_project_asset`」改成 `start_local_project_asset_generation` + `list_...` 轮询桩,载荷断言逐字不变)。**未动**:external v1 / OpenAPI、`packages/`、SpacetimeDB、音频入口的 pending-edit 账本语义、生成参数与 IPC 载荷字段名。
|
||||
- 关联文档:`docs/technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md`(§4 / §4a / §8)、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md`(S11a / §7.3)。
|
||||
|
||||
|
||||
## 2026-09-15 非 Suno 的 VectorEngine 能力切换到 Tiantoken
|
||||
|
||||
- 决策:新增本地私密环境变量 `TIANTOKEN_BASE_URL` / `TIANTOKEN_API_KEY`(图片 timeout 可独立配置),承载原 VectorEngine 的文本和图片;`VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` 仅保留给 Suno 背景音乐与 Suno 音效。编辑器 SFX V2 继续走 ElevenLabs。
|
||||
@@ -8878,11 +9155,18 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 决策:平台会话拆成身份(`userId + api origin + identity generation`)与凭据(当前 access token)。identity generation 只在登录、切号、登出或新的 GUI authority epoch 推进;同账号续期只更新凭据并推进只用于拒绝迟到写入的 revision。冻结会话校验、MCP 会话身份与 Runner attach 统一按身份判定,换号 / 退出仍然失败关闭。刷新失败只在服务端明确 401/403 且一次收敛重试后仍失败时清会话;`/api/auth/refresh` 的轮换失败不再下发清空 refresh cookie 的响应。
|
||||
- 关联规范:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 的“2026-09-16 平台会话身份与凭据分离”;开发期计划见 `docs/project-memory/plans/【里程碑】平台会话身份与凭据分离-2026-09-16.md` 与对应实施计划。
|
||||
- 验证:AGC `platform_session::tests`、`runner::tests`、`assets::tests` 定向通过;AGC `appSurface` 前端套件 468 项通过;网站 `src/services/apiClient.test.ts` 33 项通过;`cargo test -p api-server refresh_session` 通过。项目夹具类 Rust 用例受本机临时目录属主为 `BUILTIN\Administrators` 的环境限制,未计入本次证据。
|
||||
|
||||
## 2026-09-15 Direct 回合跨页面继续运行与活动项目面板
|
||||
|
||||
- 决策:采用后台继续运行语义。Direct 回合由进程内项目身份锁持有,页面离开不取消;重进项目通过活动回合只读快照与 Thread Manager bootstrap/consume 恢复忙碌态和进度。左上角面板复用同一快照列出正在运行的 Direct 项目并支持进入。
|
||||
- 边界:快照不写项目文件、不进入公共 API、不跨应用重启恢复;读取失败保留上一份结果并单独提示,不改写成权限或审批失败。身份锁排他性、付费身份和项目写锁不变。
|
||||
|
||||
## 2026-09-14 Direct Codex 引用 UI 设计文档生成代码上下文
|
||||
|
||||
- 决策:UI Editor JSON 文档资产唯一使用 `kind:"ui-design-doc"` 且 `mediaType:"application/json"`;`ui-prototype` 保持图片语义,旧 `UI` / `ui` / `ui-design` 不作为该文档分支输入,不做 fallback 或迁移。
|
||||
- 决策:Direct Codex 结构化资源引用命中该 kind 时,顺序调用 UI Editor persistence 的 `generate_ui_design_code_at`,生成 `ui/generated-*.js`,并把 `请先阅读生成的带有文档的代码片段: {relative_path}` 追加到当前 prompt。生成失败不阻断本轮引用,追加原始 `生成代码遇到错误{error}`,其它引用继续处理。
|
||||
- 原因:复用 `html_renderer/mod.rs` 统一产物,确保 LLM 读取的代码包含 UI 节点元数据和文档注释;严格 kind + mediaType 判定避免图片资产误走代码生成。
|
||||
|
||||
## 2026-09-17 AGC 模板库落在 oss://agc-dev/templates/
|
||||
|
||||
- 背景:AGC 需要「真·游戏模板」库,让用户能浏览、筛选、下载模板并直接由模板创建项目,且模板内容更新不依赖客户端发版。
|
||||
@@ -8893,6 +9177,22 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 关联规范:`docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md`;开发期计划见 `docs/project-memory/plans/【里程碑】AGC模板库客户端接入-2026-09-17.md` 与对应实施计划。
|
||||
- 验证:Rust 模板库 8 项定向单测、前端模型 9 项单测、AGC `tsc` 类型检查通过;`templates/index.json` 匿名可读且每个 `zipKey` 回读 SHA-256 与清单一致;发布脚本 `scripts/agc-template-library-publish.mjs` 支持 `--dry-run` 与上传后回读校验。
|
||||
|
||||
## 2026-09-16 DirectProject 上传文件统一使用附件 content part
|
||||
|
||||
- 决策:DirectProject 本地上传不区分图片与其它文件。用户使用同一个文件入口,前端不按 `mediaType` 做图片判断,所有上传文件统一写成 `agc_attachment_reference`;项目资源 `@` 引用仍使用 `agc_resource_reference`。
|
||||
- 原因:原 `agc_image_reference` 与附件 payload 完全相同,Rust wire 投影也把两者合并成同一段文本,不能代表真实的多模态图片输入。保留该 discriminator 只会制造错误语义。
|
||||
- 边界:本次不新增 `input_image`,不保留图片类型兼容分支,不迁移旧历史;图片多模态能力未来单独设计独立 payload 与 wire 投影。
|
||||
|
||||
## 2026-09-17 DirectProject canonical content 的有效性只判整条 content
|
||||
|
||||
- 决策:canonical user item 的有效输入判据只落在**整条 content** 上——只要有一段非空白文本、或任何一个非文本 part 就算有效输入;单个纯空白 `input_text`(段落分隔、软换行、chip 后的分隔空格)是合法 part。Rust `validate_direct_codex_user_item` 的 `content_has_meaningful_input` 与前端 `hasMeaningfulDirectCodexContent` 同口径,`wire.rs` 的「不能转换为空 prompt」只作兜底。
|
||||
- 决策:编辑器投影层(`ResourceReferenceInput` 的 `collectDraftParts`)原样透传编辑器节点:不做空白过滤,也不与相邻 part 合并。前端不替用户改写他输入的内容,canonical content 与编辑器内容逐字对应。
|
||||
- 决策:content → 可读文本只有 `directCodexContentToPromptText(content, assets)` 一个口径,`assets`(当前项目 manifest)必填:消息正文、队列 chip 文案、润色判据、草稿持久化与出站 prompt 全部由它派生,`agc_resource_reference` 按 `@显示名` 展开,只有素材已不在清单里时才回落 `resourceId`。
|
||||
- 原因:`3c7b02b9f` 为了让 content 通过「空 `input_text`」校验而在投影层丢空白 part,把引用后的段落分隔一起丢了(`@素材` 与下一段粘成一个词);`41366dd71` 又把消息 / 队列 / 快速编辑的文本派生切到这条投影上,缺陷扩散到界面与出站 prompt。
|
||||
- 边界:不新增 content part 类型,不迁移历史(历史 content 原样回放),不为旧口径保留兼容分支;前端仍不发整条全空白的一轮,app-server 输入里出现纯空白 text item 由本决定接受。
|
||||
- 验证:Rust `validation.rs` / `wire.rs` 用例「单个纯空白 part 通过校验、整条全空白拒绝」;AGC 侧 `resourceReferenceInput.test.tsx`、`resourceReferences.test.ts`、`appSurface/project-development.suite.ts`(Godot 回合)、`projectResourceLiveIntegration.test.tsx` 改为按逐字投影断言,`ai-game-creator-shell:typecheck` 与定向 vitest 通过。
|
||||
- 关联规范:`docs/project-memory/plans/【里程碑】DirectProject canonical content严格边界-2026-09-16.md`。
|
||||
|
||||
## 2026-09-16 CI 宿主 CPU 上限:Jenkins 16 核 / Gitea Actions runner 12 核
|
||||
|
||||
- 背景:`genarrative-station`(32 逻辑核)上 Jenkins Built-In Node 与 Gitea Actions runner 共用同一宿主。Jenkins `jenkins.service` 原先没有任何 CPU 限制(`cpu.max=max`),构建期 Web / Api / Stdb 三分支并行(Vitest 8 线程 + 两次默认 32 job 的 cargo)把整机顶到 80%~95%;`gitea-runner` 容器 `--cpus=24`(75%)在 push 触发的 CI 波峰里实测峰值 24.8~25.3 核,是同一时间窗里更大的单一消耗方。
|
||||
@@ -8946,6 +9246,73 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 关联规范:`docs/project-memory/plans/【实施计划】AGC项目创建目录可选-2026-09-17.md`、`docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md`。
|
||||
- 验证:`cargo test --bin genarrative-ai-game-creator-shell creation_root`(2 项)、模板库定向单测 14 项、偏好模型 3 项、`appSurface` 472 项(含设置页「工作区」选择/恢复目录与「设置里选目录后建项带 `projectsRoot`」两条场景)、`tsc`、`check:encoding` 与 `check-doc-index` 通过。
|
||||
|
||||
## 2026-09-20 Rust 工具链升到 1.98.1:仓库侧四处同步 + runner 镜像必须重建
|
||||
|
||||
- 背景:macOS 27(CLT for Xcode 27.0)冷构建时 proc-macro dylib 被链接成畸形 Mach-O(`mis-aligned LINKEDIT string pool`),上游 rust-lang/rust#157750 已修复,而仓库锁的 stable `1.96.0` 仍复现(Issue #431)。
|
||||
- 决策:根 `rust-toolchain.toml` 的 channel 由 `1.96.0` 改为 `1.98.1`;`deploy/container/gitea-ci-job.Dockerfile` 的 Rust stage 由 `rust:1.96-bookworm@sha256:19817ead…` 换成 `rust:1.98-bookworm@sha256:93ce27a88655056a51dbdd8f5f2d7ddc071c7b0070fb288a37b5a285fc83971e`(按 registry 配置实测该 digest 的 `RUST_VERSION=1.98.1`,与 channel 逐字一致)。
|
||||
- 决策(版本标签):镜像 tag / label 一并从 `20260807.1`、`2026.08.07.1` 升为 `20260920.1`、`2026.09.20.1`(同一次核对发现 Dockerfile 里有两处 `org.opencontainers.image.version`:一处 `2026.07.23.1`、一处 `2026.08.07.1`,后写的覆盖先写的,本次一并统一为 `2026.09.20.1`)(`scripts/gitea-ci-job-image.sh`、`deploy/container/gitea-ci-job.Dockerfile`),`deploy/container/README.md` 与开发运维文档同步 digest、Rust 版本和归档示例名。镜像内容变了却沿用旧 tag,排障和回滚都会认错版本。
|
||||
- 不变口径:镜像继续 `RUSTUP_AUTO_INSTALL=0`,job 现场不下载工具链;`scripts/check-gitea-ci-job-image.sh` 仍按 `rust-toolchain.toml` 的 channel 逐字校验镜像内工具链名,所以基础镜像的 `RUST_VERSION` 必须与 channel 完全相同。`std::os::windows::fs::MetadataExt::number_of_links`(rust-lang#63010)在 `1.98.1` 上实测仍未稳定,`#[cfg(windows)]` 侧继续使用自行声明 `ByHandleFileInformation` 的实现。
|
||||
- 新增护栏:`scripts/project-ci-workflow.test.ts` 增加一条一致性用例——Dockerfile 的 `ARG RUST_IMAGE` 版本段必须等于 `rust-toolchain.toml` 的 channel 主次版本、其 digest 必须同时出现在两份运维文档里、镜像 tag 日期戳必须与 Dockerfile 的 `org.opencontainers.image.version` 一致,避免本次这种「改了 Dockerfile 忘了文档」的漂移。
|
||||
- 待办(不在本次仓库改动内):在 station 上执行 `build / verify / export / load-runner`,备份 runner config 后把 `genarrative-ci` 映射切到新 Image ID 并 `docker restart --timeout 660 gitea-runner`;内层只有 1.96 时 PR 的 job 必然失败。macOS 与 Windows AGC 构建机(`jenkins/Jenkinsfile.ai-game-creator-shell-build`,preflight 只校验 rustc/cargo 是否存在)需确认已装 `1.98.1`,macOS 冷构建是本次问题的原始验证目标。`deploy/container/api-server.Dockerfile` 的 `FROM rust:1.93-bookworm` 是另一处未加 digest 的 Rust 版本 pin,本次未动。
|
||||
- 验证:分支 `chore/rust-toolchain-1-98` / PR #432;`npx vitest run scripts/project-ci-workflow.test.ts`、`npm run check:encoding`、`git diff --check` 通过。Windows:`1.98.1` 下 `npm run agc:build -- --debug` 的前端构建、Rust 编译与 NSIS 安装包生成成功(见 PR 描述记录);macOS 与镜像重建后的 CI 结果仍待验证。
|
||||
|
||||
## 2026-09-20 AGC 客户端版本号收敛为单一发号源
|
||||
|
||||
- 客户端版本号唯一事实源改为 OSS `agc/global-version.json`;渠道清单只写本次拿到的号,仓库里 5 个版本文件只作构建输入参考。
|
||||
- 发号顺序固定「先写总号 → 再构建 → 再发渠道清单」,失败不回滚只烧号;统一构建发一次号供 `dev-win` / `dev-mac` 共用,单渠道热修只作用于该渠道。
|
||||
- 发号收口到 Jenkins Job `Genarrative-Agc-Global-Version-Issue`(`disableConcurrentBuilds()`;集群无 `lockable-resources`,以写后回读不一致即失败关闭兜底并发)。
|
||||
- 原渠道高水位逻辑降级为断言:请求号低于本渠道清单版本即失败关闭;`AGC_RELEASE_DRY_RUN` 只预览不烧号。
|
||||
|
||||
## 2026-09-20 AGC 音频生成并入图片类那份后台任务账本
|
||||
|
||||
- 背景:音频栏目的「生成背景音乐 / 生成音效」原先走同步派生通道(`derive_local_project_resource`),提交后前端一直等到生成结束(最长 35 分钟),所以它不出现在画布「生成任务」侧栏里,也没有本地排队;图片类早已改成「提交即返回 + 项目内任务账本」。
|
||||
- 决策:音频改走**同一条命令** `start_local_project_asset_generation`(新增可选入参 `idempotencyKey`,音频 kind 必带);原生在 `kind` 上分叉一次(`is_audio_asset_generation_kind` 只放行 `sound-effect` / `background-music`),音频分支落**同一份**项目内账本 `.agent/runtime/asset-generation-tasks/tasks.json`,生成由 `run_local_project_audio_generation_task` 在后台跑并写 running → completed / failed。图片类载荷与分支逐字未改。
|
||||
- 决策:音频的**任务 id 就是这次生成的 operation id**,请求身份 = 面板铸造的 `operationId` + 幂等键。重试(含失败后点占位重开)必须复用同一对,否则会同一次生成变成第二次付费请求。账本不存幂等键,所以重开项目恢复出来的音频任务只用于展示与定位,不承接重试。
|
||||
- 决策(UI 口径):音频面板与图片类一致——点「生成」**同步关闭**,面板里不存在「排队中。」「正在生成。」「提交中…」与「后台运行并关闭」这类阶段文案与在途按钮,阶段文案的唯一来源是后端账本、唯一去处是「生成任务」侧栏。只有「点击瞬间就失败」(校验 / 权限 / 提交 IPC 立即报错,即后端从未受理)才由宿主把面板连原草稿与原请求身份带回来;受理之后才失败只在侧栏收口为失败。
|
||||
- 决策(时机):项目 revision 的 CAS 由「提交前读」改为「派发时刻读」(`run_local_project_audio_generation_at`);冲突按失败收口,不静默重试,避免把生成写到用户没预期的基线上。
|
||||
- 不变口径:平台侧 `/api/editor/audios/*/generations` 路由、请求体、计费与 `/api/external/v1`、OpenAPI、共享 DTO、SpacetimeDB schema 一律未动;音频仍复用既有资源编辑派生实现,不复制生成逻辑。
|
||||
- 关联规范:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`(§3.10 / §7.9)、`docs/technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md`、`docs/project-memory/plans/【里程碑】AGC音频生成进入后台任务账本-2026-09-20.md`。
|
||||
- 验证:9 个定向 vitest 文件 73 项、`appSurface.test.ts` 552 项(17 跳过)、`cargo test asset_generation_task` 14 项、`npm run agc:typecheck`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check` 全部通过;未跑真实付费生成。
|
||||
|
||||
## 2026-09-20 派生/修改类任务并入「生成任务」侧栏(AGC-039 / AGC-040)
|
||||
|
||||
- 背景:客户端验收现场,快速编辑提交后画布右上角「生成任务」全程是「还没有生成任务」,同一条草稿又还挂在「管理未完成编辑」里,用户据此判断「点了没生成」;随后同一次修改被重复提交,同一张源图派生了两份一模一样的「-编辑版」。
|
||||
- 决策(取数):派生/修改类任务(快速编辑、生成动画、视频 / 音效 / 背景音乐、抠图)进图片类生成任务**同一个**「生成任务」侧栏,数据源 = 原生资源编辑账本 `list_pending_local_project_resource_edits`(重开项目、别处提交、阶段文案以后端为首)+ 本会话**本地提交记录**(按下提交当帧即可见),两边按 `operationId` 合并去重。**不改**原生资源编辑账本 schema,也**不**把派生任务写进图片类生成账本 `.agent/runtime/asset-generation-tasks/tasks.json`——两套账本各管各的事实,只在视图层合并。
|
||||
- 决策(草稿口径):提交进行中的那一笔不再列进「管理未完成编辑」;失败回落自动把它还回未完成列表,提示词与 `@` 引用不丢。
|
||||
- 决策(重复提交):同一张素材存在在途提交时,重新打开面板再提交会被拦下(可见原因),不重铸 operation 身份。**未做**「同提示词成功后再提交」的去重——那是用户显式重复的付费动作,本批只消除误触来源。
|
||||
- 理由:用户对「生成」的心智是「交出去就得能看见它在跑」,而不是必须区分两条账本;而防重复必须在**身份重铸之前**拦,等到原生账本判重时已经派生过一次。
|
||||
- 验证:新增模型用例 9 条、快速编辑两组宿主用例(提交当帧进侧栏并收口 / 在途重复提交被拦),两处新判据做过变异验证;定向 7 个文件 97 条全绿。
|
||||
- 口径更正(2026-09-21):当时记的「`appSurface` 16 条既有失败」是**从 `apps/ai-game-creator-shell` 目录跑**造成的 cwd 假红(`resolve(process.cwd(), 'apps/…')` 路径翻倍 → ENOENT),不是用例本身红。规范跑法是仓库根 `npm test`;现已用 `tests/repoPath.ts`(按 `import.meta.url` 反推仓库根)修掉 19 个文件的同类写法,从仓库根跑 `appSurface` 为 0 失败。远程 CI 与真实客户端验收仍未跑。
|
||||
|
||||
## 2026-09-21 卡片浮层改为「提交即关」:快速编辑 / 生成动画只负责交任务
|
||||
|
||||
- 背景:客户端验收反馈——「生成动画」这类卡片浮层在失焦时不会收起,而且这块的关闭判据一直是东一处西一处拼的(点画布空白、换选中卡、Esc、点画布以外各有一条);动画面板在生成中还会被「生成中不关」判据锁在画布上。产品口径澄清:生成都归「生成任务」侧栏,**卡片浮层只用于提交任务,提交完生命周期就结束**。
|
||||
- 决策(提交即关):快速编辑 / 生成动画点提交当帧即关面板,不等 IPC、不留等待态;阶段与结果只由「生成任务」侧栏承载(本地提交记录 + 原生待办按 `operationId` 合并,见 2026-09-20 那条)。只有**点击瞬间就失败**留在面板里;**受理之后才失败**把侧栏收口为失败 + 提示条并回落草稿,不重开面板。
|
||||
- 决策(身份跟着资源走):按「入口 + 资源路径」记最后一次派生请求(`resourceEditRequestKey`)。重开面板再提交时提示词没变就沿用同一 `operationId` / 幂等键,重试仍命中同一 operation 账本;派生成成功即清掉,下一次编辑是新的一笔。原「按上次打开的 sourceLayerId 复用」在提交即关之后不再成立(面板重开时层 id 可能已被重投影换掉)。
|
||||
- 清理:`canDismissResourceCanvasQuickEdit`(生成中的浮层不参与清焦点)与宿主里两个只为它服务的面板状态 ref 一并删除;`clearResourceCanvasFocus` 恢复成「清选中 + 关两块浮层」的直线逻辑。
|
||||
- 影响面:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`.../features/resource-canvas/resourceCanvasFocusModel.ts`、`tests/{projectResourceLiveIntegration,resourceCanvasQuickEditDraft,resourceCanvasFloatingDismiss}.test.tsx`、PRD §3.10。
|
||||
- 验证:定向 `projectResourceLiveIntegration`(32 条,三条断言面板留在失败态的用例按新口径改写为「重开面板再重试,身份不变」)、`resourceCanvasQuickEditDraft`(10 条)、`resourceCanvasFloatingDismiss`(18 条)全绿;`npm --prefix apps/ai-game-creator-shell run typecheck` 通过。
|
||||
- 合并前复核(2026-09-21):合并 master 后按**仓库根**跑全量 `npx vitest run`,**373 个测试文件全过、4512 通过 / 34 跳过 / 0 失败**;PR #419 显示 `No Conflicts`。真实客户端观感与远程 CI 未复验(后者按用户要求不追,runner/镜像问题见 Issue #431)。
|
||||
|
||||
## 2026-09-21 统一错误事件额外投影到 AppData 应用日志
|
||||
|
||||
- 背景:`direct-codex-failure:v2 ... 详情:.agent/runtime/errors/error-<id>-9.json`(例如 `stage=code-generation code=turn-idle-timeout`)里的诊断正文只落在项目目录,而“报告问题”只上传 AppData `diagnostics/application.log`;用户提交上来的失败消息因此只有一个指向项目文件的引用,团队复现不到 stderr 摘要、退出状态这些真因。
|
||||
- 决策:统一错误写入边界 `agent/runtime_error.rs` 在落 `.agent/runtime/errors/<eventId>.json` 之前先把同一份已脱敏诊断投影成应用日志两行——`agent.runtime.error`(身份行:eventId / source / stage / code / retryable / clientTurnId / elapsedMs / detailRef)与 `agent.runtime.error.detail`(详情行:hint / summary / detail / metadata)。字段仍只从 sidecar 那份 diagnosis 来,不新增第二份来源;落盘前 summary 按 320 字符、detail / metadata 按(1200 / 200 字符)预算脱敏截断(`direct_tool_bridge` 会把它当自由文本传工具错误原文,而 `app_log!` 同时写 stderr,那里没有 `sanitize_diagnostic_message` 兜底),自由文本先压平换行。
|
||||
- 决策(两行而不是一行):整行一旦命中 `sanitize_diagnostic_message` 的凭据标记(token / bearer / authorization / credential / api key)会被整体替换成 `<sensitive diagnostic details redacted>`;拆开后详情行即使被吃掉,身份行仍能定位 eventId 与 detailRef。后续复核发现:裸词标记(例如 `credential rotation failed`)脱敏消不掉,summary 留在身份行时仍会连 eventId 一起被替换,因此 summary / hint 等自由文本一律只放详情行,身份行只留程序生成与调用方常量字段。
|
||||
- 边界:进程内错误报告事件池(`error_report`)与项目内 `.agent/runtime/errors/<eventId>.json` sidecar 是两套东西——本次只把 sidecar 的同一份诊断作为**应用日志行**落盘,不进事件池、不改报告上传协议、不改 `read_agent_runtime_error_detail` 详情入口。
|
||||
- 影响面:只改 `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_error.rs` 这一处写入边界(覆盖 direct-codex / agc-tools / agent-runtime 三类来源);sidecar schema、对话投影、前端详情入口与错误报告协议均未改动。
|
||||
- 验证:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml runtime_error`(7 passed,含新增 `error_event_app_log_lines_keep_identity_and_redact_detail`)与 `-- direct_runtime direct_tool_bridge`(131 passed)全绿;`cargo fmt --check`、`npm run check:encoding`、`git diff --check` 通过。
|
||||
|
||||
## 2026-09-21 画布生成面板:重开草稿、重试身份与提交载荷全部收敛到一份 canonical content
|
||||
|
||||
- 背景:DirectProject composer 收口到 `content[]` 之后,栏目画布的图片类生成浮层仍留着第二套形状——面板里 `prompt` + `references` 两份 state,重开草稿 `ResourceCanvasAssetGenerationPanelDraft` 与重试身份 `boundRequestRef` 也是 `prompt + 参考身份`,`directCodexContentToLegacyContentDto` 因此在面板渲染、关闭草稿、重试判据、宿主入队四个边界各算一遍;同时草稿用「正文 + 引用列表」两条线重建编辑器输入,`@显示名` 与引用 chip 会重复成 `@素材-a@素材-a`。
|
||||
- 决策(一份事实源):图片类生成面板的 state、重开草稿、重试身份判据与 `ResourceCanvasAssetGenerationSubmitInput` 统一只有 `content[]`(外加素材名 / 比例 / 尺寸三个非文本参数)。重试身份改用 `directCodexContentKey` 做粒度无关的内容指纹比对(合并相邻 `input_text`、丢空串),不再单独冻结 `referenceIds` + `prompt`;`resourceCanvasAssetGenerationReferenceIdsMatch` 随之删除。
|
||||
- 决策(DTO 只在出站边界):`directCodexContentToLegacyContentDto` 的 `text` / `references` 只在**任务落账**(`createResourceCanvasAssetGenerationTask`)那一处派生,面板与宿主 state 不再持有该 DTO 的第二份形状;账本回到草稿的反向翻译集中在宿主重开路径的 `legacyContentDtoToContent`。
|
||||
- 决策(反解析只有一条):legacy「text + references」→ content 走与润色回写同一套 token 扫描(`@显示名` / `$名称` / `@附件名`,`buildContentFromTextTokens`),命中的位置换回真 part、文本里找不到的补末尾。`directCodexContentToLegacyContentDto` 遇到 manifest 里已不存在的资源引用时合成 `kind: unknown` 的占位引用而**不再丢弃**,让「已不在当前项目」的判据照样能触发。
|
||||
- 决策(空白口径):`directCodexContentToPromptText` 逐字投影、不再 `trim`(前端只在整条 content 上判空);出站提示词的收边规范化收敛成一个共享函数 `resourceCanvasAssetGenerationPromptText`,面板校验与任务落账共用,图集走 Unicode White_Space、其余走 JS 口径。
|
||||
- 影响面:`apps/ai-game-creator-shell/src/features/{project-workspace/resourceReferences.ts,project-workspace/ResourceReferenceInput.tsx,resource-canvas/ResourceCanvasAssetGenerationPanelView.tsx,resource-canvas/resourceCanvasAssetGenerationTaskModel.ts,resource-canvas/resourceCanvasAssetGenerationReferenceModel.ts}`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx` 与对应 6 个定向测试文件。
|
||||
- 验证:定向 `resourceCanvasAssetGenerationReferences` / `resourceCanvasAssetGenerationBackgroundClose` / `resourceCanvasBottomToolbar` / `resourceCanvasGenerationFloatingPanel(Chrome)` / `resourceReferenceInput` / `resourceReferences` / `resourceCanvasAssetGenerationTasksPanel` 全绿;全量 `npm run test -- apps/ai-game-creator-shell/tests` 只剩 `clientHttp` / `clientApi` / `clientAuthStorage` / `projectCreationDirectory` / `recentProjectsHook` 五个 jsdom `localStorage` 环境用例红(与本次改动无调用关系);TS typecheck、`check:encoding`、`git diff --check` 通过。未复核真实客户端观感。
|
||||
|
||||
## 2026-09-18 UI 编辑器深模块 seam 收敛
|
||||
|
||||
- 决策:UI 编辑器的语义状态写入通过 React-free `stateTransition` seam;页面 hook 继续负责 React/history/lock adapter。保存前增加 `stateInvariants` projection,Rust 持久化规则仍是最终权威。
|
||||
|
||||
@@ -20,46 +20,54 @@
|
||||
|
||||
完整规则和模板见 [`docs/【协作规范】规范驱动开发工作流-2026-09-12.md`](../../【协作规范】规范驱动开发工作流-2026-09-12.md)。小型局部修改仍直接使用下方轻量流程;执行中若触及公开行为,立即升级到 SDD。
|
||||
|
||||
任务开始时先写清一句话交付结果、验收判据和不做项,再按“必须项 / 风险项 / 可选项”排序。先完成修改、定向验证和边界检查组成的最小闭环;设置时间盒和检查点,新增发现只有在影响交付判据时才扩大范围,否则记录为后续事项。不要让工具探测、历史整理或验证便利自行改变任务目标。
|
||||
任务开始时先写清一句话交付结果、验收判据和修改范围,再按“必须项 / 风险项 / 可选项”排序。先完成修改、定向验证和边界检查组成的最小闭环;设置时间盒和检查点,新增发现只有在影响交付判据时才扩大范围,其余记录为后续事项。
|
||||
|
||||
## 开始前
|
||||
|
||||
- worktree 复用 `node_modules` 时,测试与构建的 workspace alias 必须指向当前工作树源码,不能经依赖软链接读取另一工作树的共享包。遇到仅 worktree 出现的 JSX 编译错误时先核对解析路径,不用给组件补全局变量来掩盖错误来源。
|
||||
- worktree 复用 `node_modules` 时,测试与构建的 workspace alias 必须指向当前工作树源码。遇到仅 worktree 出现的 JSX 编译错误时先核对解析路径,修复错误的依赖解析。
|
||||
|
||||
- 运行 `git status --short`,保留用户已有的未提交修改;不要在共享工作树中使用破坏性 Git 命令。
|
||||
- 复杂任务先读 `AGENTS.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`、`docs/README.md` 和对应专题。
|
||||
- 需要完整 SDD 的任务先确认主规范位置和验收证据,再创建 `docs/project-memory/plans/` 下的里程碑规范与实现计划;计划完成、取消或合并后删除。
|
||||
- 后端事实以 `server-rs/crates/api-server/src/app.rs`、`server-rs/crates/api-server/src/modules.rs`、Cargo manifest、SpacetimeDB schema 和源码为准;现役 API 不从未挂载模块推导。
|
||||
- 后端事实以 `server-rs/crates/api-server/src/app.rs`、`server-rs/crates/api-server/src/modules.rs`、Cargo manifest、SpacetimeDB schema 和源码为准;现役 API 按实际挂载路由核对。
|
||||
- External v1 以 `docs/openapi/genarrative-external-v1.openapi.json` 与 `modules/external_api.rs` 为准。
|
||||
- 本地端口的默认值只用于启动配置;实际运行端口以 `.app/dev-stack.json` 和启动日志为准。
|
||||
- 任务涉及 SpacetimeDB schema 时,先读 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 与现有 schema 检查脚本,不依赖仓库中已不存在的旧表目录或基线文件。
|
||||
- 任务涉及 SpacetimeDB schema 时,先读 `docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md` 与现有 schema 检查脚本。
|
||||
|
||||
## 修改边界
|
||||
|
||||
- 后端路线固定为 `server-rs + Axum + SpacetimeDB`,访问 SpacetimeDB 统一经 `spacetime-client` facade。
|
||||
- `module-*` 放领域规则,`spacetime-module` 放表和事务,`api-server` 放 HTTP/SSE/BFF,`platform-*` 放外部副作用,`shared-contracts` 放 DTO 与公开契约。
|
||||
- 前端不承接正式业务真相;页面状态必须来自后端投影、API 或持久化合同。
|
||||
- 旧模板、旧公开作品、旧运行态、旧 Node/Express/PostgreSQL/Go/maincloud 路线和人工 `spacetime --root-dir` 命令不作为新实现目标。
|
||||
- 已退役对象没有现役 caller、公开契约、持久化迁移或活跃实例时,不添加兼容代码、兼容测试、墓碑注释或墓碑文档。
|
||||
- 修改中文文件优先局部补丁,保持 UTF-8;不把中文文案替换成英文。
|
||||
- 前端负责表现与交互;页面正式状态来自后端投影、API 或持久化合同。
|
||||
- 本地 SpacetimeDB 数据隔离使用项目脚本或 `--data-dir`,发布目标显式传 `--server` / `--server-url`。
|
||||
- 已退役对象没有现役 caller、公开契约、持久化数据、活跃实例或迁移要求时,清理实现、专属测试和说明,权威文档保持当前状态;公开契约、持久化数据和正式迁移按实际需求保留最小兼容及对应测试。
|
||||
- 修改中文文件优先局部补丁,保持中文与 UTF-8。
|
||||
|
||||
## 文档维护
|
||||
|
||||
- 当前稳定合同进入 `docs/`;长期决策、通用流程、排障经验进入 `shared-memory/`。
|
||||
- 文档现行、历史、待复核、开放事项和活动计划的分类以 [`docs/【协作规范】文档生命周期与现状索引-2026-09-12.md`](../../【协作规范】文档生命周期与现状索引-2026-09-12.md) 为准;`historical` 和 `review` 文件开头保留状态头,不能直接作为实现依据。
|
||||
- `plans/` 只保存正在执行且有明确下一门禁的计划;`todos/` 只保存真实开放且有关闭条件的事项。完成或作废后删除或融合。
|
||||
- 不把分支名、一次性测试轮次、提交流水账和个人路径写成长期规则。
|
||||
- 长期规则记录可复用的当前合同与验证方法,执行历史由 Git 保存。
|
||||
- H5 HostBridge 真实调用链的临时替身词扫描必须覆盖生产调用链;宿主壳真实能力以现行 HostBridge 协议与代码为准。
|
||||
|
||||
## 验证路由
|
||||
|
||||
提示词外置变更运行 `runtime_prompt_bundle_build` 与 `prompt_source_boundaries` 两个 Rust 集成测试,验证编译期文本、目录登记和源码边界;现有 `agc-rust-shard-1` 本地/CI 入口先执行这组检查,再运行分片单测。
|
||||
|
||||
提示词测试验证实际请求中的片段来源、动态参数和工具结构;措辞不作为逐字契约。已有行为测试覆盖的限制不再另设整段文案检查。Direct 回合测试复用生产的消息转换和文件投影函数,不维护仅供测试调用的回合编排副本。
|
||||
|
||||
AGC 预览快捷操作的界面测试按独立命令或有状态短流程注册,每例重新建立 fixture、原生调用 mock 和页面,并等待项目打开后再记录调用计数。预览启停、导出确认与取消等连续行为保留在同一用例;互不依赖的只读命令不串成一条长对话,也不共享 DOM 或提高超时来容纳整组流程。
|
||||
|
||||
Rust 分片失败日志保留有界的失败详情,包括 panic 位置、断言和最终通过/失败数量;分片选中数量标为 selected,避免误读为失败数量。修改分片日志时运行 `node --test apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs`,用最小 Rust fixture 验证失败详情和成功摘要。
|
||||
|
||||
AGC 运行时配置默认值调整时,同步核对 Rust 默认值、分发配置模板、设置弹窗默认草稿和 `runtime-settings.suite.ts` 的恢复默认断言;显式传入旧值的配置读取用例仍验证原值保留,不批量替换测试数据。
|
||||
|
||||
AGC 测试构造单 HTML 项目时,必须在初始化之前写入 HTML,避免自动建立 npm 工程;npm 预览和导出测试应提供 dist 产物。已有图片生成 pending/operation 属于持久化恢复合同,修改工具默认参数后仍须验证旧动作恢复不重复提交、不因默认值变化被误判为新意图。
|
||||
|
||||
SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.md`;该项目适配层按需调用已安装的官方 `spacetimedb` 插件 skill,插件提供通用 SDK/CLI/MCP 知识,项目 skill 负责 Genarrative 架构边界和验证门禁。
|
||||
|
||||
按改动范围选择定向门禁,不以无关全量扫描代替契约验证:
|
||||
按改动范围选择定向门禁:
|
||||
|
||||
| 范围 | 至少运行 |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -82,8 +90,8 @@ SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.m
|
||||
|
||||
## Jenkins 定时版本调度
|
||||
|
||||
定时与版本比较只保留在 `Genarrative-Scheduled-Revision-Trigger` 一处:每小时用 `git ls-remote` 解析 `SOURCE_BRANCH` 远端 HEAD,与上一次触发过的 revision 比较,变化时才把同一个 `COMMIT_HASH` 同时传给 `Genarrative-Full-Build-And-Deploy` 与 `Genarrative-Agc-Windows-Build`,保证两条管线构建同一个版本。`Genarrative-Full-Build-And-Deploy` 与 `Genarrative-Agc-Windows-Build` 不得自带 `triggers` / `cron`,也不得在管线内再做一套版本去重;`npm run check:production-ops` 会拦住这两类回退。调度状态与生效步骤见 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
定时与版本比较只保留在 `Genarrative-Scheduled-Revision-Trigger` 一处:每小时用 `git ls-remote` 解析 `SOURCE_BRANCH` 远端 HEAD,与上一次触发过的 revision 比较,变化时才把同一个 `COMMIT_HASH` 传给 `Genarrative-Full-Build-And-Deploy`,并先经 `Genarrative-Agc-Global-Version-Issue` 发号、再把同一个总版本号透传给 `Genarrative-Agc-Windows-Build` 与 `Genarrative-Agc-MacOS-Build`,保证两个客户端的平台分区发布同一个版本。这三个下游 Job 都不得自带 `triggers` / `cron`,也不得在管线内再做一套版本去重;`npm run check:production-ops` 会拦住这两类回退。macOS 节点是日常办公机,调度触发它时置 `SKIP_IF_SUPERSEDED=true`:节点离线期间排队的旧构建在恢复后会自行让位,不发布过期版本。调度状态与生效步骤见 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## Gitea CI 依赖闭合
|
||||
|
||||
`.gitea/workflows/project-ci.yml` 的客户端门禁拆成八个 job,每个 job 只预热自己会构建的那几份依赖:`AI game creator shell Rust shard 1/4` 到 `4/4` 各只预取 AGC 壳 manifest 并各跑一片(AGC 壳那份 `Cargo.lock` 的 path 依赖已含 `platform-llm`、`platform-agent`、`agent-runtime-core` 与 `shared-contracts`),`AI game creator shell Rust smoke` 同样只预取 AGC 壳 manifest(`agent-run` smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`),`AI game creator shell Rust crates` 预取 `server-rs/Cargo.toml` 与两个独立 crate,`Native shell tests` 预取桌面壳与 AGC 壳 manifest,`AI game creator shell web tests` 不触碰 Cargo,不预热。AGC 壳的 4 个分片 job、smoke job 与 crates job 只用 cargo 与 node 内建模块,因此不执行 `npm ci`。两个被 `server-rs/Cargo.toml` 排除、且没有提交 `Cargo.lock` 的独立 crate(`agent-runtime-core`、`agent-runtime-orchestration`)只能在 `AI game creator shell Rust crates` 里用不带锁标志的 fetch。AGC 壳的 bin target 单测(约 2466 条)由 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs` 编译一次后按 `--list` 名单分 4 片:CI 的每个分片 job 用 `--shard-index=<i>` 只跑自己那片,片内保持 `--test-threads=1` 并各自使用独立 `TMPDIR`,片与片之间靠 job 级并发摊开;本地不传 `--shard-index` 时仍是同一条命令把 4 片放进程里并行。不要改回「一个 job 内多进程并行这几片」——同一容器里它们会争抢共享 `HOME`、target 目录与固定临时路径,实测比整套串行还慢。每个分片 job 都会自校验「片并集等于全集且互斥」,因此改分片规则不会静默漏跑。Backend host workspace tests 使用 `cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`,避免 `spacetime-module` 的 `spacetime-types` feature 统一污染普通领域 crate 的 host 测试;随后单独执行 `cargo test --locked -p spacetime-module --no-fail-fast`,由 `spacetime-module/src/active.rs` 在 host 测试构建期间提供仅测试期的 SpacetimeDB ABI 链接支持,使该 crate 的纯单元测试也纳入 Backend 门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,host 链接支持不得被当作运行时替身。Backend 另外执行 `cargo check --locked -p spacetime-module` 验证模块源码。AGC 壳检查还会运行 `platform-llm` 与 `shared-contracts` 的 server-rs workspace 测试,这些命令以及 AGC 壳测试必须带 `--locked`,避免在测试阶段重新解析 registry index;锁文件发生变化时应先更新受信任 CI 镜像缓存,再重跑门禁。
|
||||
`.gitea/workflows/project-ci.yml` 的客户端门禁拆成 lane 与功能 job,每个 job 只预热自己会构建的那几份依赖:`AI game creator shell Rust lane 1/2`、`lane 2/2` 各自预取一次 AGC 壳 manifest,并顺序运行两片 Rust bin 单测;`AI game creator shell Rust smoke` 同样只预取 AGC 壳 manifest(`agent-run` smoke 会用 `src-tauri/Cargo.toml` spawn `cargo run`),`AI game creator shell Rust crates` 预取 `server-rs/Cargo.toml` 与独立 crate,`Native shell tests` 预取桌面壳与 AGC 壳 manifest,`AI game creator shell web tests` 不触碰 Cargo,不预热。两条 Rust lane、smoke job 与 crates job 只用 cargo 与 node 内建模块,因此不执行 `npm ci`。两个被 `server-rs/Cargo.toml` 排除、且没有提交 `Cargo.lock` 的独立 crate(`agent-runtime-core`、`agent-runtime-orchestration`)只能在 `AI game creator shell Rust crates` 里用不带锁标志的 fetch。AGC 壳的 bin target 单测(约 2466 条)由 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs` 编译后按 `--list` 名单分 4 片:每次分片调用用 `--shard-index=<i>` 只跑自己那片,片内保持 `--test-threads=1` 并使用独立 `TMPDIR`;两条 lane 之间并发,lane 内顺序运行两片,避免重复依赖预热和同一容器内多进程争抢。不要改回「一个 job 内多进程并行这几片」——同一容器里它们会争抢共享 `HOME`、target 目录与固定临时路径,实测比整套串行还慢。每个分片调用都会自校验「片并集等于全集且互斥」,因此改分片规则不会静默漏跑。Backend host workspace tests 使用 `cargo test --locked --workspace --exclude spacetime-module --no-fail-fast`,避免 `spacetime-module` 的 `spacetime-types` feature 统一污染普通领域 crate 的 host 测试;随后单独执行 `cargo test --locked -p spacetime-module --no-fail-fast`,由 `spacetime-module/src/active.rs` 在 host 测试构建期间提供仅测试期的 SpacetimeDB ABI 链接支持,使该 crate 的纯单元测试也纳入 Backend 门禁。`spacetime-module` 的 reducer / procedure 运行时行为仍必须通过真实 SpacetimeDB runtime/integration harness 验证,host 链接支持不得被当作运行时替身。Backend 另外执行 `cargo check --locked -p spacetime-module` 验证模块源码。AGC 壳检查还会运行 `platform-llm` 与 `shared-contracts` 的 server-rs workspace 测试,这些命令以及 AGC 壳测试必须带 `--locked`,避免在测试阶段重新解析 registry index;锁文件发生变化时应先更新受信任 CI 镜像缓存,再重跑门禁。
|
||||
|
||||
@@ -24,19 +24,19 @@
|
||||
AI 游戏创作 / DirectProject / UI workflow:
|
||||
|
||||
1. `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`
|
||||
2. `docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`
|
||||
2. `docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`(历史方案,仅供追溯)
|
||||
3. `docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md`
|
||||
4. `docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`
|
||||
5. `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`(仅存量旧链路)
|
||||
2. `docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md`
|
||||
3. `docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md`
|
||||
4. `docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`
|
||||
5. `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`
|
||||
6. `docs/technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`
|
||||
7. `docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`
|
||||
8. `docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`
|
||||
9. `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`
|
||||
10. UI 编辑器、宿主壳和当前测试专题文档
|
||||
5. `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`(历史 V1 方案,仅供追溯)
|
||||
6. `docs/technical/【技术方案】DirectProject Codex原始历史与异常恢复-2026-09-04.md`
|
||||
7. `docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md`
|
||||
8. `docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`
|
||||
9. `docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`
|
||||
10. `docs/technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`
|
||||
11. `docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`
|
||||
12. `docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md`
|
||||
13. `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`
|
||||
14. UI 编辑器、宿主壳和当前测试专题文档
|
||||
|
||||
图片画布 / 媒体生成:
|
||||
|
||||
|
||||
@@ -1,5 +1,136 @@
|
||||
# 踩坑与排障记录
|
||||
|
||||
## 客户端图标素材描述不能先包装再截断
|
||||
|
||||
- 现象:资源画布填写了具体图标需求,平台实际收到的 `iconDescriptions` 却只包含泛化的小游戏美术指令,生成结果不遵循输入。
|
||||
- 原因:客户端先给用户描述套“Web 小游戏首版原型、核心美术素材”和默认 brief 模板,再把包装后的文本压入单项 `200` 字符;固定前缀已占满额度,用户描述在发给 API Server 之前就被丢弃。服务端校验只能看到被截短的文本,无法恢复原需求。
|
||||
- 处理:客户端画布图标生成只 trim 描述并以单项 `iconDescriptions` 原样提交,保留内部换行;面板与原生入口按 `200` 个 Unicode 码点校验并拒绝空白或超限输入,不静默截断、不机械拆条。服务端共用的提示词流程负责规范图、背景与排布要求;其它图片生成的 `32000` 字符上限保持不变。
|
||||
- 验证:检查实际请求体与 trim 后的原文一致,并覆盖 `200/201` 码点、补充平面字符、换行和空白输入;不能只断言“请求长度未超限”。完整合同见 [画板图标素材生成入口设计](../../【编辑器】画板图标素材生成入口设计-2026-06-15.md)。
|
||||
|
||||
## 2026-09-21 不同渠道的包体在同一台设备安装会互相顶掉
|
||||
|
||||
- **现象**:在一台已经装了某个渠道 AGC 客户端的设备上安装另一个渠道的安装包,装完后旧客户端直接消失(安装目录被覆盖、卸载项被接管),更新端点、平台服务器与本地登录态一起换成新渠道的;两个渠道的客户端无法共存。
|
||||
- **原因**:渠道此前只烘焙了 `plugins.updater.endpoints` 与 `VITE_AGC_PLATFORM_CHANNEL`(`apps/ai-game-creator-shell/scripts/build-release.mjs` 的 `createChannelConfig`),`productName` / `identifier` 用的是渠道无关的基线值。Tauri 的 Windows 安装目录与卸载项由 `productName` 决定,WebView2 数据目录与客户端数据目录由 `identifier` 决定,于是所有渠道落到 `%LOCALAPPDATA%\陶泥儿`、`HKCU\...\Uninstall\陶泥儿` 与 `%APPDATA%\world.genarrative.ai-game-creator`。
|
||||
- **处理(现行口径)**:渠道进入安装身份,默认渠道保持基线身份不变,其它渠道派生 `<产品名> <渠道显示名>` 与 `<基线>.<渠道>`;身份与端点在同一次构建期 `--config` 注入。见决策记录 2026-09-21 条目。
|
||||
- **核对方式**:装完任渠道的包后看 `HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\<产品名>` 的 `InstallLocation`、`%APPDATA%\<identifier>` 与主程序窗口标题是否按渠道分开;同名安装目录或同名数据目录说明身份没有生效。
|
||||
- **易错点**:只改安装包文件名或快捷方式名而不改 `identifier`,两个渠道仍会抢同一份 Agent Runner / 项目锁与登录态;反过来把渠道后缀加在默认渠道上,既有安装的升级链会断(客户端认不出旧安装)。相似前缀目录(如 `world.genarrative.ai-game-creator-backup`)不得进入提权 ACL 的 managed 范围。
|
||||
- **关联**:`apps/ai-game-creator-shell/scripts/channel-identity.mjs`、`build-release.mjs`、`build-macos-ci.mjs`、`src-tauri/src/config.rs`。
|
||||
|
||||
## 发布器守卫拒绝时不要把 process.exit 用在 fetch 句柄未关闭处
|
||||
|
||||
- 现象:`agc-template-library-publish.mjs --dry-run` 撞上「同一 `templateVersion` 的 ZIP 不得变」门禁时,终端只剩一句 `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c`,看不到任何拒绝原因,看起来像脚本崩溃而不是被拒绝。
|
||||
- 原因:`main().catch(...)` 里直接 `process.exit(1)`;此时 dry-run 刚用 fetch 读过公共清单,句柄仍在关闭流程中,Node/libuv 在 Windows 上先抛断言,把真实错误信息挤掉。
|
||||
- 处理:`catch` 里只设 `process.exitCode = 1`,让事件循环自然退出;同批把说明文档纳入受管发布(见决策记录 2026-09-21 条目)。
|
||||
- 验证:把模板源复制到临时目录、把某个模板的 `templateVersion` 改回与线上同版本并改动一个字节,`--dry-run` 应打印 `同版本 ZIP 内容或尺寸变化,请递增 templateVersion` 且退出码为 1,不再出现 `Assertion failed`。
|
||||
- 关联:`scripts/agc-template-library-publish.mjs`。
|
||||
|
||||
## macOS 只出 arm64 单架构,universal 必须失败关闭
|
||||
|
||||
`stage-node-runtime.mjs` 只把**构建宿主的 Node**打成便携运行时(官方发行版是单架构,没有 universal 发行版),而 2026-09-21 之前 `build-macos-ci.mjs` 构建的是 `universal-apple-darwin`:macOS Job #7~#13 因此在 `stageNodeRuntime` 直接抛「Node 运行时不支持发布目标:universal-apple-darwin」。期间出现过一版「按宿主架构放行」的过渡实现(`targetRuntime` 对 universal 返回宿主架构),它能骗过通用包自检(`check-macos-bundle.mjs` 按 `process.arch` 校验),但**Intel Mac 上这份 arm64 侧车不可执行**,等于把坏包发出去。当前决策:macOS 固定只构建 `aarch64-apple-darwin`,清单只登记 `darwin-aarch64`,`targetRuntime('universal-apple-darwin')` 保持失败关闭。恢复 Intel 的正确路径是先在 staging 支持按架构各带一份**同版本**运行时(另下载另一架构官方发行版)并让通用包自检按架构分别校验,再切回 universal 目标、把 `darwin-x86_64` 键登记回去;不得用「只带宿主架构」充数,也不得把 arm64 产物登记成 x86_64 键。
|
||||
|
||||
## release 冷备空间不足会把生产留在维护态
|
||||
|
||||
`Genarrative-Stdb-Module-Publish` 先进入维护模式、停掉 API/controller/worker,再执行发布前冷备份;archive 口径要求 `data × 1.1`(40.6GiB 数据 → 44.7GiB,加上生产根盘只剩 13.5GiB),于是 2026-09-21 的 release 发布在停服后失败并保持维护态,站点 503 直到人工恢复。规则:空间预检必须先于 `maintenance-on` 与停服;archive 不足且未显式禁用降级时改用 files(`max(data × 0.05, 2GiB)`,不落地本地归档,但 files 不支持 `--defer-upload`,会从 async 收敛为 sync);只有真正开始 `spacetime publish` 之后的失败才允许保持维护态。另:定时备份的失效锁在当前仓库版本会自动清理,但生产机 `/var/lib/genarrative/backup-tools/database-backup-to-oss.mjs` 若是旧版会拒绝抢锁并要求人工删锁,需随 provision 更新。
|
||||
|
||||
按上游 [#5555](https://github.com/clockworklabs/SpacetimeDB/pull/5555) 的 retention 语义,只有最近 `retain-snapshots`(默认 2)份 snapshot 与覆盖它之后的 commitlog 段是重启所需,其余历史可丢;因此备份改为 `files + full + --minimal --retain-snapshots 2`(release 实测 40G → 3.6G,热备不停服),不再做增量差异计算,也不需要 44.7G 冷备空间。
|
||||
|
||||
## copyArtifacts 报「Unable to find project for artifact copy」的用户触发构建差异
|
||||
|
||||
Copy Artifact 插件在**非 SYSTEM 认证**下按「认证用户」判权:只有当被复制 Job 的 `CopyArtifactPermissionProperty`(仓库里由 Declarative 的 `copyArtifactPermission(...)` 维护)显式列出当前消费者,或者该 Job 对认证用户开放 Item.Read 时才放行;`ACL.SYSTEM2` 的定时构建会短路通过。因此会出现「定时调度一路成功、手动发布必挂」的现象(2026-09-21 手动发布 #6/#7 与同期的用户触发探测全部命中,定时调度 #104+ 正常)。`Genarrative-Agc-Global-Version-Issue` 生产权限模式的授权名单必须同时包含 `Genarrative-Scheduled-Revision-Trigger` 与 `Genarrative-Manual-Build-And-Deploy`;改完 `copyArtifactPermission` 后要先跑一次发号 Job 把 Job property 写回 Jenkins,只改仓库文件不生效。
|
||||
|
||||
## 手工发布目标不会自动映射成 AGC 更新渠道
|
||||
|
||||
`Genarrative-Manual-Build-And-Deploy` 的 `DEPLOY_TARGET=release` 只控制 Stdb / API / Web 全量发布,不会自动成为 AGC 的 `AGC_UPDATE_CHANNEL`。2026-09-21 的手工发布 #10 就因此让 Windows #107 与 macOS #16 使用默认 `dev`,把 `0.1.95` 上传到 `agc/dev-win`、`agc/dev-mac`,而 `agc/release-win/latest.json`、`agc/release-mac/latest.json` 保持 404。现行口径:手工入口按 `release -> release`、`development -> dev` 同时给 Windows 与 macOS AGC Build 传 `AGC_UPDATE_CHANNEL`;补发已烧号的同一版本时用相同 `AGC_RELEASE_VERSION` 直接重跑两条 AGC Job,不重新发号。OSS 发布对象是 `agc/<channel>-win|mac/`,不存在 `agc/release/` 这一层。
|
||||
|
||||
## 同一条链路两处上限不一致:平台合法产出被客户端整条丢弃
|
||||
|
||||
- 现象:客户端报「生成素材失败:platform-generation-result-unknown: 异步生成完成结果无法绑定到 operationId:External Editor 旧同步结果的图集切片超过 64 个」,而平台侧这次生成**其实已经成功并切完图**(任务账本耗时正常、`assetId` 为空、没有任何素材落盘,付费产物被丢)。
|
||||
- 成因:图集切片上限在链路里存在两份字面量——平台切分、Agent 工具 schema `sliceCount` 与持久化产物批次都是 256,客户端结果绑定门写着 64(`agent/generation/{canvas_generation.rs,external_generation_state.rs}`)。自动切分(`connected-components` + `sliceCount=null`)切出 65~256 片是合法产出,客户端比平台更严就会把结果整条判失败。
|
||||
- 处理:客户端门统一到 `PLATFORM_ART_SPRITESHEET_MAX_SLICES = 256`,判据与文案各只留一份(数字由常量插值),并在注释里点名三处同值权威(平台切分常量、工具 schema、公开契约)。
|
||||
- 复用判据:凡是「平台产出 → 客户端校验后落盘」的链路,客户端门只能表达**安全 / 预算**约束,不得比平台的产品上限更严;两边上限要引同一个常量或同一份文档,改一边时必须同时改另一边,并补一条「上限之内必须能落盘」的回归用例。
|
||||
|
||||
## 2026-09-21 画布卡片「拖一下就触发点击」:阈值与点击抑制必须是卡类手势的一份判据
|
||||
|
||||
- **现象**:拖动未生成的资源占位卡(背景音乐 / 音效等所有类型)松手后,卡片自己的点击语义被多执行一次——生成浮层被顺手弹开或收起。
|
||||
- **成因**:浏览器在 `pointerdown` 与 `pointerup` 落在同一节点上时**一定会补一次 `click`**,与中间移动了多少无关。生成占位卡那条手势把「收到过 pointermove」当成拖动信号(按下时浏览器就可能补一次零位移的 move),而且没有任何点击抑制;资源卡那条链路早就用 `RESOURCE_CANVAS_DRAG_THRESHOLD`(5px)+ `skipNextResourceCardClickRef` 处理过这件事,两条链路各写一套,于是只有占位卡漏。
|
||||
- **处理(现行口径)**:卡类手势只有一份判据 `resourceCanvasGestureExceededDragThreshold`(`features/resource-canvas/resourceCanvasCardGestureModel.ts`,阈值仍取 `RESOURCE_CANVAS_DRAG_THRESHOLD`);拖动收尾那次 `click` 由手势层登记一次性抑制、宿主在「点卡片」的入口消费(占位卡是 `consumeDragClick`,资源卡是 `skipNextResourceCardClickRef`),抑制活过一个宏任务就清干净。「拖动中」样式也从越过阈值那一刻起才亮。
|
||||
- **相邻一档**:从删除按钮起手、松手落回卡片的手势,`click` 会被派给共同祖先(卡片)——判据要看**起手点**(`ResourceCanvasGenerationPlaceholderCardView` 的 `gestureOriginRef`),不能只看移动距离。
|
||||
- **易错点**:把「拖动样式」放在 `pointerdown` 上置位,等于承认「按下即拖动」,紧接着的点击又会被自己的抑制吃掉;阈值与抑制必须同时按同一判据走。
|
||||
|
||||
## 2026-09-21 画布浮层几何只能按卡片的真实屏幕位置算,分页锚点必须让开钉死的标题栏
|
||||
|
||||
- **现象一(浮层与画布边缘碰撞)**:生成浮层底边越过画布下沿被 `overflow: hidden` 切掉,卡片靠左右边时浮层还会有一半落到画布之外。
|
||||
- **成因**:可用高度只按「安全带高度 − 卡高」算,等于假设占位卡永远贴在安全带上沿;占位卡是追加在栏目内容最下方的,落到下半屏是常态。水平方向则完全没有收口——浮层 560px 宽、按卡片中心居中展开,卡片靠边必然越界。
|
||||
- **处理(现行口径)**:`resolveResourceCanvasGenerationPanelPlacement` 吃卡片的**屏幕顶边**,顶边与可用高度都由真实位置算(卡下面塞不下就改为盖住占位,且顶边收进安全带);水平用 `clampResourceCanvasGenerationPanelAnchorX` 把锚点收进「浮层左右各留 12px」的区间。浮层宽度口径(`min(560px, calc(100% - 24px))`)在 CSS 与模型里各一份,有声明级守卫用例钉住同源。
|
||||
- **现象二(生成任务开关压在标题栏上)**:资源栏目画布页顶部钉着整宽的栏目标题栏(`.game-resource-book-scene-titlebar`,42px,右端是「返回资源总览」),右上角开关按总览页的坐标摆就会直接压在它上面。
|
||||
- **处理(现行口径)**:「生成任务」锚点按页分档(`canvas-overview` / `canvas` / `run` / `editor`):画布页让开那条标题栏的高度再加一段间隙,总览页仍贴画布顶边内缩;两档之间的高度差走 `margin-top` 过渡(`prefers-reduced-motion` 下关掉)。让开多少与标题栏多高是**跨文件关系**,守卫用例读全局样式表里那条 `min-height` 断言「让开得比它高」。
|
||||
- **易错点**:给锚点写死 `top`,或只按某一页的 chrome 算一次坐标,工具条换行、页面切换或标题栏高度调整后都会重新压上去。
|
||||
|
||||
## Jenkins Windows 节点的 PATH 白名单决定 Godot 原生扩展能否构建
|
||||
|
||||
`Genarrative-Agc-Windows-Build` 在阶段里用 `AGC_WINDOWS_PATH` 整体替换 PATH、不继承节点机器的 PATH,所以 Godot C++ 引导需要的 CMake 与 Python 必须显式写进这份白名单,装在机器 PATH 上并不生效。2026-09-21 的 #97–#99 连续失败都停在 `Get-Command cmake.exe`(#93–#96 是更早的手写 C ABI 在 MSVC C 模式下的对齐问题):节点只有 Visual Studio Build Tools(`C:\BuildTools`)自带的 CMake 3.31,缺 Python 3。修复后白名单包含 `C:\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin`、`C:\Python312`、`C:\Python312\Scripts`,preflight 校验 CMake ≥3.25、Python 3 和 Visual Studio 17 2022 生成器;把 `cmake.exe` 单独复制到别的目录会丢掉 `share/cmake-*/Modules`,不能替代加入安装目录。新节点的 Python 用 `python-3.12.10-amd64.exe /quiet InstallAllUsers=1 TargetDir=C:\Python312 PrependPath=1 Include_launcher=1 InstallLauncherAllUsers=1` 静默安装即可,CMake 不必另装。
|
||||
|
||||
## AGC 画布绑定前置查询不能取全量项目列表
|
||||
|
||||
- 现象:dev 上「AI 生成图片」连续失败,卡片显示 `解析读取外部画布项目响应失败:error decoding response body`,每条恰好 `1 分 00 秒`(三条同因,各自独立计时)。
|
||||
- 原因:绑定前置的 `GET /api/(external/v1/)editor/projects` 缺省 `view=full`,会把账号下每个项目的画布与全量资源一起返回(19 个大项目的 fixture 就已超过 4 MiB);客户端这条请求只有 60 秒预算,卡在读正文时被 reqwest 总超时打断。而 `reqwest::Error` 的 `Display` 只打印 kind,超时、正文被截断和非法 JSON 显示成同一句话,现场看不出根因。
|
||||
- 处理:只确认项目身份的消费者固定取 `view=summary`(站内与外部路由都支持,缺省 `full` 不变,未知取值失败关闭);摘要视图不做内联媒体修复、不带画布与全量资源;外部请求失败文案补 kind 语义与 source 因链,且不拼接 URL。
|
||||
- 验证:站内路由用例断言 `view=summary` 不回传 `canvas / layers / resources` 且不触发媒体修复、`view=unknown` 返回 400;AGC 壳用例断言失败文案不再等于 `error decoding response body`、补出因链且不含绝对地址。
|
||||
- 关联:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/external_editor_api.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs`。
|
||||
|
||||
## 远端资源编辑终态必须指出唯一出口
|
||||
|
||||
- 现象:「生成背景音乐」再次提交 0.1 秒就失败,卡片只有 `remote-terminal-failed: 远端资源编辑已明确失败,不允许再次请求`,既没有原因也没有下一步。
|
||||
- 原因:上一次同 `operationId` 的请求被平台确定性拒绝(HTTP 400 或任务 `failed`)后,账本落到 `remote-failed`,之后所有重试都在 `ensure_resource_edit_phase_resumable` 失败关闭;唯一出口是「待恢复资源编辑」里的移出恢复队列,但终态文案没有指向它。
|
||||
- 处理:终态文案带出稳定失败码,并明确「先在待恢复资源编辑中把它移出恢复队列」;上游失败原文仍不写入账本(只存分类码),首次失败的原始拒绝说明继续由当次错误文案承担。
|
||||
- 验证:`remote_failed_status_is_terminal_and_can_only_be_archived`、`submission_bad_request_is_terminal_while_gateway_failure_requires_reconciliation` 等资源编辑用例继续通过,账本序列化不含上游失败原文。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`。
|
||||
|
||||
## Tauri `--no-sign` 会连带跳过 updater 签名
|
||||
|
||||
AGC macOS 发布入口一度传入 `--no-sign`(目的是绕过没有 Apple 证书的代码签名),结果 Tauri 打印 `Warn Updater signing is skipped due to --no-sign flag.`,产物只有 `*.app.tar.gz` 而没有 `.sig`,发布入口按设计在「缺少更新包签名」处失败关闭(2026-09-20 首次 Jenkins 实跑命中)。正确做法是不传 `--no-sign`,改为剥离 `APPLE_*` 凭据让 Tauri 跳过 Apple 签名——minisign 更新包签名与 Apple 代码签名这两个开关在 Tauri 里并不独立。Apple 签名状态要按 `codesign -dv` 实测记录,不能硬编码。
|
||||
|
||||
## 复用 workspace 的构建必须显式清理本次要写的产物
|
||||
|
||||
Jenkins workspace 跨构建保留:上一轮失败留下的同名 `陶泥儿_<version>_universal.dmg` 会让 `hdiutil create` 以「文件已经存在」失败,而上一轮遗留的 `*.app.tar.gz.sig` 更危险——本轮即使没签出签名,验签门禁也会读到旧签名而误判通过。构建入口必须在构建前删除本次将写出的确切路径(更新包、签名、同版本 DMG 及其校验文件、`latest.json`、`release-notes.txt`),`hdiutil create` 同时用 `-ov`,让「归档里的产物来自本次构建」成为结构性事实而非假设。
|
||||
|
||||
## AGC macOS 单次构建耗时集中在主 crate 重复编译
|
||||
|
||||
AGC 主 crate(`genarrative_ai_game_creator_shell`)单架构 codegen 约 15–20 分钟,而每次 Tauri 构建都会重新生成前端 `dist`,`build.rs` 对 `dist` 目录的 `rerun-if-changed` 因此每次都判定变化,导致两个架构各重编一次主 crate。实测:`CARGO_BUILD_JOBS=4` 时首次 Jenkins 构建 78 分钟,提到 6 后为 41–43 分钟且成功;依赖 crate 走 sccache 与 target 缓存,首轮 0 命中属预期。剩余优化空间在「不必要地重建 dist」这一层,需单独设计(例如按内容摘要决定是否重跑前端构建),不要在发布入口里用假缓存换取速度。
|
||||
|
||||
## Godot C++ 扩展构建与对象生命周期
|
||||
|
||||
- 原生引导通过官方 `godot-cpp` 管理 Variant、String 和 Ref,不自行维护 ABI 存储。Godot 类型必须在扩展终止回调内释放,不能依赖 DLL 静态对象析构;桥节点可能已经退出,应按实例 ID 核验存活再回调。
|
||||
- 正式 Windows 构建使用 CMake 的 Visual Studio x64 generator,并实际验证 MSVC 编译;不能用 GCC 成功替代 MSVC 验收。固定官方归档按 SHA256 校验,缓存源码被修改时拒绝构建并保留证据。
|
||||
|
||||
## Rust 同步回调的测试记录按线程隔离
|
||||
|
||||
- `shared-contracts` 的资源 kind reporter 是进程级回调。仅给注册和断言加锁,无法阻止其他并行 manifest 测试触发该回调,导致日志数量和内容断言偶发混入其他测试记录。
|
||||
- kind 解析与回调在调用线程同步执行,测试收集器使用线程局部存储,各用例开始时清空本线程记录;生产 reporter 保持不变。保留完整记录断言,并用两个线程分别解析和核对记录,验证隔离;不要通过全局串行测试或放宽断言掩盖干扰。
|
||||
|
||||
## AGC 自动同步必须绑定真实项目生命周期
|
||||
|
||||
- 正式客户端在单窗口中用 React 状态打开/切换工程,窗口 URL 不代表当前工程。原生后台同步应读取由当前窗口显式登记的活动工程;首次打开、离开、切换、关窗及退出等待分别验证,不能只用携带 `projectPath` 的独立测试窗口证明正式入口可用。
|
||||
- 增量文件没有变化不等于远端清单没有变化。项目名和完整性元数据也参与提交判据,避免临时跳过恢复后永久停留在 partial,或新出现超限文件后仍显示 ready。历史清单缺少完整性字段属于未知,不能默认成完整。
|
||||
- ZIP 导出按一次冻结清单恢复相对路径并逐文件核验;直接下载内容寻址的 OSS 目录不能得到可用工程。源码/素材归档不包含依赖缓存、凭据和 AGC 对话运行状态。
|
||||
|
||||
## 2026-09-19 资源 kind 词汇收敛后,前端判据与 fixture 必须一起按 canonical 成员重写
|
||||
|
||||
- **现象**:工具栏入口的 `assetKind` 换成共享 `GameCreationAppAssetKind`(图集从平台词 `art-spritesheet` 改成 `icon-spritesheet`)后,「图集不接受用户参考」的判据仍写在旧的 `['art-spritesheet']` 字符串清单里,判据恒假:生成面板重新给图集渲染参考图选择器,原生提交再按合同显式拒绝多余参考。
|
||||
- **同类第二处**:把 `hasRegisteredArtImageAssets` 从 `kind === 'art-spritesheet'` 直接换成「kind ∈ 视觉族」会把候选 UI 原型图(`ui-design`)算成美术图片产出,提前顶掉「美术资源计划已完成,尚未生成或登记图片」;判据必须显式排除 `GAME_CREATION_APP_UI_DESIGN_ASSET_KIND`。
|
||||
- **处理(现行口径)**:前端的资源 kind 判据只比较 canonical 成员或共享契约导出的谓词,不再维护第二份字符串清单;词汇收敛时先 grep 旧词在 `src/` 与 `tests/` 两侧的落点,fixture 同步按 canonical 成员重写。
|
||||
- **易错点**:fixture 与生产代码写着同一个旧词时,单测会陪着一起变绿,用例证明不了任何事;另外 `tests/appSurface.test.ts` 不是全部同族用例,资源画布 / 画布生成参考等用例散在 `tests/*.test.ts(x)`,词汇收敛必须连这些一起跑。
|
||||
|
||||
## 2026-09-17 ts-rs 生成物换目录后,忘记同步忽略规则会让「生成物抖动」假装成代码改动
|
||||
|
||||
- **现象**:`GameCreationAppAssetKind` 的 ts-rs `export_to` 从 `apps/ai-game-creator-shell/src/contracts/generated/` 换到 `packages/shared/src/contracts/generated/` 后,任何 `cargo build` / `cargo test` 都会重写生成文件;若新目录没进 `.prettierignore` 与 `.eslintrc.cjs` 的 `ignorePatterns`,lint-staged / prettier 会把生成物重新格式化,于是每次提交都出现「生成物被改」,`cargo test export_bindings` 也不再幂等(跑完 `git diff` 不为空)。
|
||||
- **处理(现行口径)**:生成目录一律成对登记 `.prettierignore` + eslint `ignorePatterns`;改 `export_to` 时同步改这两处,并用 `cargo test --locked -p shared-contracts --features ts-bindings export_bindings --manifest-path server-rs/Cargo.toml` 后 `git diff` 为空来验证幂等。
|
||||
- **易错点**:旧的 `apps/ai-game-creator-shell/src/contracts/generated/` 目录下的同名文件不会自动删除,换目录后必须显式删除旧文件,否则会出现「两个同名 union,改动只落在一个目录」的假绿。
|
||||
|
||||
## universal 主程序必须配套双架构原生依赖
|
||||
|
||||
AGC macOS 主程序可合并为 universal,但 Codex 原生包的 `codex-package.json`、code-mode host 和 zsh 仍有架构身份。两套包应各自保留上游布局与摘要,放入 `coding-agent/mac-native/darwin-arm64/`、`darwin-x64/`,由正在运行的主程序切片选择;不能只把主程序用 lipo 合并后复用最后一次构建的单架构资源。Tauri universal 两次 Cargo 构建共用 staging,每次都必须 stage 完整的两套资源。发布清单两个平台键同 URL/签名,只在 universal 产物上成立;Rosetta 隔离 smoke 不代替 Intel 真机验收。
|
||||
|
||||
## 生成草稿与异步展示边界必须按身份隔离
|
||||
|
||||
非模态生成浮层切换占位时按 draftId 分实例,卸载保留未提交/失败草稿,成功提交不再复活草稿;旧项目占位不存在时丢弃其保存回调。失败重试保留原请求输入和引用身份,引用失效不能静默过滤;修改已绑定输入须明确另起请求,不伪装成原请求重试。
|
||||
@@ -36,6 +167,7 @@ Vite 默认监听应用根下的 Rust `src-tauri/target`,构建产物较多时
|
||||
- **易错点**:① 把弹层改成 `left: 0` 或往右挪也能让它可见,但那是改变展开方向,弹层会跑到触发钮右边(用户明确否决);② 只放开最外层聊天列不够——surface 与 conversation 各自都会裁,三层必须同时放开;③ 只按宽度比大小会误判:280px 面板里控制排本身也超出(发送钮右侧溢出 22px,被窗口右缘吃掉),那不是本条的原因,别顺手去改控制排布局。
|
||||
- **验证**:`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts` 的 `keeps the landscape workbench edge-to-edge with internal chat scrolling` 钉住三条 override 声明在场(删掉任一条即红)。真机几何用 playwright-cli 打开一份只含真实 `styles.css` 与真实 composer DOM 的最小复现页实测(视口 1000×700、面板 280px):弹层 rect 修复前后都是 `[-42, 108]`(位置未动),`elementFromPoint` 的命中区间从修复前的 `[2, 108]` 变成整块;档位文字在截图中完整可见。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/styles.css`(`面板纵向布局(2026-07 Codex 风格改造)` 区块之后)、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`。
|
||||
|
||||
## 2026-09-17 资源卡「内容跟着边框动」与「模型缩略图被拉伸」是两条不同的几何陷阱
|
||||
|
||||
- **内容跟着状态边框位移**:卡片底态是 `border: 0`,悬停 / 选中才加 1px 边框;卡片是 `box-sizing: border-box`,而卡面(`.game-resource-card-visual`)与角标都是 `position: absolute; inset: 0`(包含块 = **padding box**)⇒ 状态一切换,内容盒四边各被吃掉 1px,卡面与角标整体位移并缩小 2px。修法:底态写成 `border: 1px solid transparent;`,状态只点亮 `border-color`;契约用例改成断言「资源卡规则里不得出现非 1px 的 `border` / `border-width`」。
|
||||
@@ -82,6 +214,7 @@ Direct 工具桥会 canonicalize 项目根,事件中的路径可能带 `\\?\`
|
||||
- **处理(现行口径)**:① 渲染 `ProjectDevelopmentView` 的桩统一登记 `list_local_project_asset_generations`(返回**数组**,空账本 `[]`;Rust 侧返回 `Vec<AssetGenerationTaskRecord>`,不是 `{ tasks: [] }`);② `unexpectedCommands` 这类门禁**不要放宽**,只登记合法命令;③ 生产代码对 IPC 返回值做形状防御(`Array.isArray` 归一化),IPC 拒绝走既有提示路径,不产生 unhandled rejection(`resourceCanvasAssetGenerationTaskModel.ts` / `resourceCanvasAssetGenerationQueue.ts` / `index.tsx` 的恢复 effect)。
|
||||
- **易错点**:① 桩返回**非数组**时用例可能"看着绿"但同时报 unhandled rejection(实测:把 `[]` 误写成 `{ tasks: [] }` 就是 8 passed + 7 unhandled error),所以判"绿"必须同时看 unhandled 计数;② 新增挂载期 IPC 后要一次性 grep 所有 `ProjectDevelopmentView` 的桩,而不是等 CI 逐个炸;③ 提示条类断言(如「无读时提示」)会把「桩抛错」翻译成「多了一条提示」,排查时先看 unhandled,再看断言。
|
||||
- **关联**:`apps/ai-game-creator-shell/tests/resourceCanvasManualLayout.test.tsx`、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`、`apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationTaskModel.ts`。
|
||||
|
||||
## 2026-09-14 UI 编辑器返回后资源画布滚轮平移失效
|
||||
|
||||
- **现象**:资源管理打开 UI 编辑器再返回后,资源画布滚轮平移/缩放不再响应;返回前同一手势正常。
|
||||
@@ -89,6 +222,7 @@ Direct 工具桥会 canonicalize 项目根,事件中的路径可能带 `\\?\`
|
||||
- **处理**:将 `uiEditorRoute` 纳入 wheel effect 依赖,使进入/退出 UI 编辑器时先清理旧节点监听,再给返回后的新 manager 绑定同一处理器。
|
||||
- **验证**:`npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts -t "restores resource canvas panning"`;回归用例覆盖打开栏目、wheel 平移、进入 UI 编辑器、返回并再次 wheel 平移。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`。
|
||||
|
||||
## 2026-09-14 AGC 就绪等待被 WMI 拖成分钟级:端口归属探测从 Get-NetTCPConnection 换成 netstat
|
||||
|
||||
- **现象**:`npm run agc` 从 `[ai-game-creator-shell] starting backend stack` 到 `backend ready` 要等约 80 秒,中途反复出现 `等待配套后端就绪时归属校验未通过(api-server-owner-mismatch: 未知进程)`;而这段时间后端其实已经好了(实测 api-server 12:39:01 已在 8084 监听、`/healthz` 已 200,12:40:19 才判 ready)。
|
||||
@@ -97,6 +231,7 @@ Direct 工具桥会 canonicalize 项目根,事件中的路径可能带 `\\?\`
|
||||
- **验证**:`apps/ai-game-creator-shell/tests/start-dev-stack.test.ts` 新增两条——「探测脚本使用 netstat 且不再出现 Get-NetTCPConnection」「命令行按 PID 缓存后随请求下发、TTL 过期即失效」;定向 vitest 55 passed。本机实测:不含 SpacetimeDB 端口的探测 368 ms(原约 22 秒)、含 SpacetimeDB 端口 3.8 秒、命中缓存 368 ms;`npm run agc:serve` 的 `starting backend stack` → `backend ready` 由约 80 秒降到 16.7 秒(其中归属校验只占 4.4 秒,其余是 SpacetimeDB + api-server 的真实启动时间)。
|
||||
- **残留**:这台机器上首次 WMI 调用本身仍是秒级(曾见 18 秒),所以「新 SpacetimeDB PID 的第一次探测」仍可能多花几秒;命令行在进程存活期内不变,TTL 只用来限制 PID 复用造成的误判窗口。
|
||||
- **关联**:`apps/ai-game-creator-shell/scripts/start-dev-stack.mjs`(`readWindowsPortOwnerIdentities`)、`apps/ai-game-creator-shell/tests/start-dev-stack.test.ts`、`apps/ai-game-creator-shell/scripts/dev-windows-process.mjs`(退出清理仍走整份 `Win32_Process` 快照,自带 1 秒缓存,不在本次范围)。
|
||||
|
||||
## 2026-09-15 AGC JSON API 的响应体也必须有等待上限
|
||||
|
||||
- `fetchClientHttp` 的超时只覆盖请求到响应头返回;随后直接等待 `response.text()` 仍可能无限挂起。模型目录共用一个在途 Promise,响应体卡住会使后续刷新复用同一挂起请求、选择器持续忙碌。
|
||||
@@ -113,7 +248,7 @@ Direct 工具桥会 canonicalize 项目根,事件中的路径可能带 `\\?\`
|
||||
|
||||
- **现象**:`AI game creator shell Rust tests` 一直是客户端 CI 的关键路径。run 2097 实测 15 分 27 秒,其中 `apps/ai-game-creator-shell/src-tauri` 的 bin target 单测(2466 条)一条 `cargo test -- --test-threads=1` 串行占 507 秒。
|
||||
- **为什么原本是整个 suite 串行**:2026-07-21 `a273377b1` 的判据是「共享 Agent Runtime 后台锁与异步终态在 libtest 并行调度下互相干扰」,即**同进程内**的全局后台锁、异步终态与进程级 static 被交叉触发;另有少数用例自身 spawn 当前测试二进制(`std::env::current_exe()`)跑 fixture,会碰容器里共享的 target 与固定临时路径。
|
||||
- **处理(现行口径)**:新增 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs`:`cargo test --no-run` 编译一次后用 `--list` 名单把用例按 `index % shards` 切成 4 片,CI 的每个分片 job 用 `--shard-index=<i>` 只跑自己那片(`--exact <名单> --test-threads=1`,片内串行),片与片之间靠 **job 级并发**摊开。配套把 `ai-game-creator-shell:check:rust` 拆成 `:rust:crates` 与 `:rust:shell`,AGC 相关门禁在 CI 里共 6 个 job(4 个分片 + smoke + crates)。
|
||||
- **处理(现行口径)**:新增 `apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs`:`cargo test --no-run` 编译后用 `--list` 名单把用例按 `index % shards` 切成 4 片,CI 的每次分片调用用 `--shard-index=<i>` 只跑自己那片(`--exact <名单> --test-threads=1`,片内串行);两条 Rust lane 各顺序运行两片,lane 之间靠 **job 级并发**摊开,避免每片重复依赖预热。配套把 `ai-game-creator-shell:check:rust` 拆成 `:rust:crates` 与 `:rust:shell`,AGC 相关门禁在 CI 里由两条 lane、smoke 和 crates job 承载。
|
||||
- **反面实验(run 2102,勿重做)**:起先把 4 片放进**同一个 job** 内的 4 个进程并行,结果门禁步骤跑满 18 分钟仍未结束,比整套串行的 507 秒还慢——同一容器内这几片共享 `HOME`、target 目录与固定临时路径,会互相拖慢。因此 `--shard-index` 是 CI 的唯一入口;不带 `--shard-index` 的「单命令内多片并行」只留给本地全量自测。
|
||||
- **易错点**:① 分片规则必须自校验「片并集等于 `--list` 全集且互斥」,否则改分片方式会静默漏跑门禁;② 每片要拿独立 `TMPDIR`,`tempfile::tempdir()` 默认落在它下面(测试里的硬编码 `/tmp/...` 多是「必须拒绝」的负向断言,不是真实读写);③ 不要给分片 job 装 `npm ci`——AGC 壳 Rust 门禁与 `agent-run` smoke 只用 cargo 与 node 内建模块,那些 `npm ci` 正是达标 7 分钟的主要障碍;④ 片 job 只需预热 AGC 壳自己的 manifest(其 `Cargo.lock` 的 path 依赖已覆盖 `platform-llm` / `platform-agent` / `agent-runtime-core` / `shared-contracts`),`server-rs` 那份预热属于 crate 级 job;⑤ 分片后 `--test-threads=1` 不再出现在 workflow 里,但它是分片运行器的片内参数,别再往 workflow 里补整套串行命令。
|
||||
- **不要做的事**:不要退回「整套 `--test-threads=1`」(507 秒长尾回来了),不要放开成整套并行(同进程内后台锁与异步终态会再互相干扰),也不要在单个 job 内多进程并行多个片(实测比串行还慢)。
|
||||
@@ -127,6 +262,7 @@ Direct 工具桥会 canonicalize 项目根,事件中的路径可能带 `\\?\`
|
||||
- **处理**:`runNativeShellGate` 收窄为 `(group, gate)`,label 回到各门禁执行体里以字面量打印;分组能力与 `--groups=` 语义不变。脚本内已注明"不要把 label 抽成变量",外部壳的 `check-config` 是它的消费者。
|
||||
- **验证**:`node apps/mobile-shell/scripts/check-config.mjs` exit 0;`node apps/desktop-shell/scripts/check-config.mjs` 已越过第 740–777 行的根脚本断言段(本机随后卡在本机不存在的 Tauri 生成产物目录,与本次改动无关);`--groups=contract`、vitest `scripts/project-ci-workflow.test.ts`、eslint 均通过。修复后 run 2097 六个 job 全绿。
|
||||
- **关联**:`scripts/check-native-shells.mjs`(`runNativeShellGate`)、`apps/desktop-shell/scripts/check-config.mjs`、`apps/mobile-shell/scripts/check-config.mjs`。
|
||||
|
||||
## 2026-09-14 AGC 资源画布不要恢复「无条件重派生」,否则新增一张素材就整张重排
|
||||
|
||||
- **现象**:用户生成一张新素材后,画布上既有卡片全部移位,刚摆好的位置失效。
|
||||
@@ -140,7 +276,7 @@ Direct 工具桥会 canonicalize 项目根,事件中的路径可能带 `\\?\`
|
||||
|
||||
- **现象**:把 `Native shell tests` 拆成客户端三个 job 后,如果只跑 `npm run check:native-shells:release`,静态契约和壳运行时门禁都不会执行;如果只跑 `--groups=contract`,`desktop-release-binary-artifact` 又会因为缺少 `build/native/desktop/` 产物而失败。
|
||||
- **原因**:分组是执行范围,不是"额外检查"。`desktop-release-binary-artifact` 断言依赖同 job 内的 `desktop-shell-stage-release-binary` 步骤,所以它归 `release` 组,不能放进 `contract`;反过来,任何"只跑一组"的命令都不能被当成完整门禁。
|
||||
- **处理**:分组与 job 的对应关系固定为 `contract`+`shells`+`release` → `Native shell tests`,`agc-web` → `AI game creator shell web tests`,`agc-rust-shard-1..4` → `AI game creator shell Rust shard 1/4 .. 4/4`,`agc-rust-smoke` → `AI game creator shell Rust smoke`,`agc-rust-crates` → `AI game creator shell Rust crates`;`scripts/project-ci-workflow.test.ts` 校验"每个分组恰好被一个 job 调用一次"和"CI 不再调用全量 `npm run check:native-shells`",新增分组必须同步门禁脚本、根脚本与 workflow 三处。
|
||||
- **处理**:分组与 job 的对应关系固定为 `contract`+`shells`+`release` → `Native shell tests`,`agc-web` → `AI game creator shell web tests`,`agc-rust-shard-1..2` → `AI game creator shell Rust lane 1/2`,`agc-rust-shard-3..4` → `AI game creator shell Rust lane 2/2`,`agc-rust-smoke` → `AI game creator shell Rust smoke`,`agc-rust-crates` → `AI game creator shell Rust crates`;`scripts/project-ci-workflow.test.ts` 校验"每个分组恰好被一个 lane/job 调用一次"和"CI 不再调用全量 `npm run check:native-shells`",新增分组必须同步门禁脚本、根脚本与 workflow 三处。
|
||||
- **易错点**:① 拆 job / 改 job 名后要确认分支保护里没有残留已不再上报的旧 job 名(本仓库现在不配 required context,只需人工确认 CI 结果,见置顶条目的「分支保护口径」);② 每个 job 只预热自己会构建的 Cargo 依赖,`agent-run:smoke` 因为会 spawn `cargo` 必须与 AGC 壳的依赖预热同 job;③ 本地全量 `npm run check:native-shells` 仍会串行跑完所有分组,用它作为本地完整门禁,不要用单组脚本冒充。
|
||||
- **关联**:`.gitea/workflows/project-ci.yml`、`scripts/check-native-shells.mjs`、`scripts/project-ci-workflow.test.ts`、`.gitea` 分支保护设置。
|
||||
|
||||
@@ -343,7 +479,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
## 2026-08-15 `#[cfg(windows)]` 里的代码不参与 Linux CI 编译,CI 绿不代表能构建
|
||||
|
||||
- 现象:把 master(`9f5c84ee7`)合进 `feat/five_min_design` 后,`cargo check --all-targets` 在 Windows 上直接 `error[E0658]: use of unstable library feature 'windows_by_handle'`,位置是 `apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs` 的 `metadata.number_of_links()`。该文件与 `origin/master` **逐字节相同**,即 master 自身在 Windows 上就构建不过。
|
||||
- 原因:`std::os::windows::fs::MetadataExt::number_of_links` 至今未稳定(rust-lang#63010),而 `rust-toolchain.toml` 锁的是 stable `1.96.0`。引入它的提交是 `578f8019f`(优化 AGC 项目入口并识别 Godot 工作区),其中 unix 分支用 `MetadataExt::nlink()`(已稳定)、windows 分支用了未稳定的对应物。**Linux CI 上 `#[cfg(windows)]` 整块不参与编译,所以 CI 全绿。**
|
||||
- 原因:`std::os::windows::fs::MetadataExt::number_of_links` 至今未稳定(rust-lang#63010);当时 `rust-toolchain.toml` 锁的 stable 是 `1.96.0`,2026-09-20 升到 `1.98.1` 后在同一台 Windows 机器上用该 stable 实测仍报 `error[E0658]: use of unstable library feature 'windows_by_handle'`(见 decision-log 同日条),因此本条的处置口径不变。引入它的提交是 `578f8019f`(优化 AGC 项目入口并识别 Godot 工作区),其中 unix 分支用 `MetadataExt::nlink()`(已稳定)、windows 分支用了未稳定的对应物。**Linux CI 上 `#[cfg(windows)]` 整块不参与编译,所以 CI 全绿。**
|
||||
- 更普遍的形状:只要一段代码只在某个 `#[cfg(target_os)]` 下编译,它就完全绕过了其它平台的 CI——不只是 unstable feature,还包括类型错误、借用错误、缺失 import。跨平台分支是「双写」,两侧都得有人真的编译过。
|
||||
- 处理:本仓库对「文件是不是无硬链接普通文件」统一自行声明 `ByHandleFileInformation` 并调用 `GetFileInformationByHandle`,见 `runner/endpoint.rs`、`tool_plan_handoff/storage_windows.rs`、`project/agent_db.rs`、`git_inspect.rs`、`image_inspect.rs`、`agent/generation/canvas_generation.rs`。`manifest.rs` 当前已采用同一实现,并保留 fail-closed 语义:无法取得句柄信息或确认存在硬链接时均拒绝,同时拒绝 directory / reparse point。
|
||||
- 验证:改后 `cargo check --offline --all-targets` 通过、`cargo fmt --check` 通过、`project::manifest` 与 godot 相关定向测试 65 passed / 0 failed。判断「是不是本次合并引入」的通用手法:`git diff origin/master -- <file>` 为空即说明该文件就是 master 原样,问题不在合并。
|
||||
@@ -4029,6 +4165,14 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- 验证:活会话下 finalization 和 `runner.shutdown_if_idle` 必须失败关闭;分别验证 graceful handler 尾部输出、宽限超时后的 force、忽略 SIGHUP 的 npm 孙进程和 Windows Job 路径,只有 child 已终态、同组残留已处理且 PTY 尾部排空才出现唯一 terminal record。另用允许程序证明代理和固定 cwd 不是文件系统 / 网络沙箱,不得把该现象误写成测试失败或安全能力。
|
||||
- 关联:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`apps/ai-game-creator-shell/src-tauri/src/runner.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent.rs`。
|
||||
|
||||
## 一次性命令不能把孤儿僵尸误判为仍在执行的进程组
|
||||
|
||||
- 现象:Linux CI 的命令已退出,但 `command.exec` 返回 `needs-reconciliation`,后续修复计划或输出读取请求一直等不到;普通 WSL 下相同命令可通过。
|
||||
- 原因:容器 PID 1 未回收孤儿 `bwrap` 僵尸,`kill(-pgid, 0)` 仍返回成功;主进程已被 wait 回收,后续 leader 启动身份核对必然失败,掩盖了真实退出结果与原本应触发的日志审计错误。
|
||||
- 处理:确认 target 终态且回收主进程后扫描 `/proc/<pid>/stat`,空组或仅含 `Z / X` 成员无需发送信号;有存活成员仍保留 leader 身份门禁,读取失败保守进入 reconciliation,不放宽未知进程组的信号权限。
|
||||
- 验证:隔离 subreaper 夹具覆盖 leader 已回收时的存活后代拒绝、孤儿僵尸接受和空组接受;原有诊断命令、审计失败、命令修复与长输出/历史读取测试在不回收孤儿的 PID namespace 下验证。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/command_exec.rs`、`apps/ai-game-creator-shell/src-tauri/src/tests/command_runtime.rs`。
|
||||
|
||||
## 命令环境变量、代理和进程组不能冒充 OS 沙箱
|
||||
|
||||
- 现象:命令看似使用隔离 HOME / TMP、离线包管理器和不可达代理,仍能直接读取宿主用户文件、用原始 socket 联网,或由 `project.verify` 的平行 npm spawn 绕开 `command.exec` 限制。
|
||||
@@ -4613,7 +4757,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
|
||||
- 现象:`Repository checks`、`Frontend tests`、`Backend tests` 和 `Native shell tests` 都从全新 job 容器开始,apt、setup-node、rustup 和原生系统库在不同 job 里重复安装;后端与原生壳的安装时间可达数分钟,并把软件源和代理瞬时失败放大为四份。
|
||||
- 原因:Gitea Actions job 彼此隔离,上一个 job 在容器内安装的包不会自动进入下一个 job;把同一套不随 PR 变化的工具链写在 workflow step 中,必然每次重做。
|
||||
- 处理:用 `deploy/container/gitea-ci-job.Dockerfile` 预装 Node 22、固定 npm、Rust 1.96、`rustfmt`、Chrome、`bwrap`、`rg`、`ffmpeg`、`clang/lld` 和 Tauri / 后端系统依赖,并按锁预热唯一根 npm workspace、server-rs、桌面壳与 AI 游戏创作壳 Cargo 四份下载缓存。四个 job 统一 `runs-on: genarrative-ci`,先用镜像内脚本直接从 Gitea checkout,再以 runtime 模式运行 `scripts/check-gitea-ci-job-image.sh`,同时检查四份缓存锁、工具链、完整 bwrap 与 Chrome headless。`RUSTUP_AUTO_INSTALL=0`;`rust-toolchain.toml` 变更时先重建镜像,不把下载 fallback 放回 job。
|
||||
- 处理:用 `deploy/container/gitea-ci-job.Dockerfile` 预装 Node 22、固定 npm、Rust 1.98.1、`rustfmt`、Chrome、`bwrap`、`rg`、`ffmpeg`、`clang/lld` 和 Tauri / 后端系统依赖,并按锁预热唯一根 npm workspace、server-rs、桌面壳与 AI 游戏创作壳 Cargo 四份下载缓存。四个 job 统一 `runs-on: genarrative-ci`,先用镜像内脚本直接从 Gitea checkout,再以 runtime 模式运行 `scripts/check-gitea-ci-job-image.sh`,同时检查四份缓存锁、工具链、完整 bwrap 与 Chrome headless。`RUSTUP_AUTO_INSTALL=0`;`rust-toolchain.toml` 变更时先重建镜像,不把下载 fallback 放回 job。
|
||||
- 依赖边界:每个 job 仍必须各自执行 `npm ci`,让当前 lockfile 和 PR 依赖在干净环境中验证;区别是命中镜像 cache 时只做本地解包,锁新增依赖时才走受控网络。不要把 `node_modules` 或 Cargo `target` 烘进镜像,也不要向不受信任 PR 挂载跨 job 可写 cache。
|
||||
- 锁漂移边界:runtime 校验输出任一 `*_cache_lock=partial` 说明镜像内 lock 与当前 checkout 不同,不代表新增依赖已经缓存;必须同时输出 Actions warning,提示可信分支落地后刷新镜像。必须在新镜像中对 server-rs、桌面壳和 AI 游戏创作壳当前 lock 执行真实 `cargo fetch --locked --offline`;`cargo metadata --no-deps` 不会证明依赖 archive 可用,不能作为替代。
|
||||
- 构建网络边界:`CARGO_NET_RETRY` 只覆盖部分 crate 下载,registry `config.json` / index TLS 握手仍可能直接终止整次 fetch。Dockerfile 对每个 `cargo fetch --locked` 再做最多 5 次整命令级有界重试,最终仍执行断网 fetch,不能降低为无锁重试或省略离线闭合验证。
|
||||
@@ -5311,6 +5455,14 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- 处理:Windows 专用 Tauri 配置设置 `bundle.useLocalToolsDir: true`,把工具缓存到 `src-tauri/target/.tauri/NSIS`;Jenkins 预检验证实际用户、项目工具目录可写,并在构建失败时打印实际缓存路径和绝对路径执行结果。
|
||||
- 验证:不要把 PATH 中 `makensis` 可发现当作 Tauri bundler 工具可执行的充分证据;需要在 Windows Agent 上检查 `target/.tauri/NSIS/makensis.exe`、ACL、EDR/Defender 和直接 `-VERSION` 结果。
|
||||
|
||||
## Tauri NSIS 工具链必须在打包前预置并重试(2026-09-21)
|
||||
|
||||
- 现象:AGC Windows 发布构建已完成 Rust release,Tauri 依次打印 `Downloading .../nsis-3.11.zip`、`Info extracting NSIS`、`Downloading .../nsis_tauri_utils.dll` 之后,直接以 ``failed to bundle project `io: unexpected end of file` `` 失败(退出码 1),安装包不会产出。
|
||||
- 原因:tauri-bundler 的 `download_and_verify` 现场从 GitHub 取 NSIS 工具链,只有一次机会、没有重试;响应体被截断即报 `io: unexpected end of file`,看起来像打包错误其实是网络问题。Checkout 阶段的 `git clean -fdx` 每次都会清掉 `target/.tauri`,所以每个构建都要重新下载,在受限网络下必然反复失败。
|
||||
- 处理:新增 `apps/ai-game-creator-shell/scripts/nsis-toolset.mjs` 与 `ensure-nsis-toolset.mjs`,在 `buildRelease`(Windows 目标且需要打包时)与 Jenkins `Tauri NSIS toolchain` 阶段按固定 SHA1 预置 `target/.tauri/NSIS`:原始归档带 4 次重试,缓存在工作区外的 `%ProgramData%\genarrative\tauri-nsis-cache`(可用 `AGC_TAURI_NSIS_CACHE_DIR` 覆盖),镜像开关沿用 bundler 的 `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE` / `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR`。该阶段同时执行 `makensis.exe -VERSION`,把 2026-09-02 记录的缓存目录不可执行问题也提前到编译之前暴露。Checkout 阶段改为 `git clean -fdx -e apps/ai-game-creator-shell/src-tauri/target/.tauri`:Tauri 的工具缓存位于工作区内,裸 `git clean -fdx` 会连它一起删,排除后同一节点的稳态构建不再需要联网,只有冷缓存(新节点、工作区重建)才下载。
|
||||
- 验证:`node --test apps/ai-game-creator-shell/scripts/nsis-toolset.test.mjs`(已就绪零下载复用、缓存离线还原、失败重试、哈希不符与归档越界失败关闭)与 `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs`;真实节点上该阶段必须早于 Rust 编译失败关闭。
|
||||
- 注意:不要改回 `bundle.useLocalToolsDir: false` 去用 `%LOCALAPPDATA%`,也不要依赖 PATH 里预装的 `makensis`;升级 `@tauri-apps/cli` 时同步核对归档 URL、SHA1 与必需文件清单。
|
||||
|
||||
## AGC 登录态续期必须同步本地运行时
|
||||
|
||||
- 模型目录 HTTP 请求与 DirectProject 的 Rust/app-server 使用同一账号,但凭据分别保存在 WebView 与 Rust / Runner;续期应复用 `requestPlatformSessionRefresh` 完成用户核验及本地会话安装,不能只写 localStorage。
|
||||
@@ -5468,11 +5620,12 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
## 资源 kind 别名只救新登记,不救存量 category(2026-09-10)
|
||||
|
||||
- 现象:真机 `What do u wanna do kitten` 的 66 项资源里 58 项落「待归类」,其中 57 项是 `kind:"ui"` 的 UI 资产,本该在「UI 交互」。
|
||||
- 成因链:`ui` 不在 `GAME_CREATION_APP_CANONICAL_ASSET_KINDS` 里、也没有别名,于是走 `canonicalGameCreationAppAssetKind` 的 `image` 兜底,再经 `image → unclassified` 被误分到「待归类」。它并不是历史遗留值——`assets.rs:1656` 的 `infer_canvas_export_asset_kind`(画板导出导入)**现在仍在写出** `ui` / `animation` / `asset`。
|
||||
- 成因链:`ui` 不在 canonical 词汇表里、也没有别名,于是落「待归类」。它并不是历史遗留值——`assets.rs` 的 `infer_canvas_export_asset_kind`(画板导出导入)当时仍在写出 `ui` / `animation` / `asset`。
|
||||
- **现行口径(2026-09-17 起,改这里之前必读)**:kind 只有一份词汇表(Rust `GameCreationAppAssetKind` 声明表 → ts-rs 生成 TS union),解析是**严格等值匹配**:不 trim、不 lowercase、不查别名、不迁移;非 canonical 原值收口成 `unknown`(分类 `unclassified`)并把**原始串 + 调用上下文**交给壳层注册的 `app_log!` 回调。**别名表已整体删除,不要再加回来**——这条现象现在的表现是「日志里能查到谁还在写旧值」,先修写入方,不要在读侧做兼容。
|
||||
- **关键陷阱(2026-09-11 已修)**:补别名只影响「今后新登记」的资产。`register_local_asset_entry`(`assets.rs`)命中同 `localPath` 的既有资产时,旧实现只覆盖 `kind` / `media_type` / `source`、**不重算 `category`**;而读取侧优先信任落盘 `category`、只在它缺失或非法时才按 `kind` 派生。所以这 57 条的落盘 `category: "unclassified"` 会一直有效,重导入也自愈不了。**修法与必须保留的不变量**:更新分支只在 `kind` 真的变化时才重派生 `category`,否则「同路径重登记且 kind 变了」会留下「新 kind + 旧分类」的错位,而陈旧的非 `unclassified` 值会被无条件信任、自愈也不触发;反过来同 kind 重登记**禁止**动 `category`,落盘分类是权威值,被 `register_local_asset_keeps_explicit_category_when_kind_is_unchanged` 钉住。
|
||||
- 为什么读取侧要信任落盘值:存在用户手动改分类的正式链路 `update_manifest_asset_classification_at`(`project/manifest.rs:1110`),读时无条件重派生会吃掉用户的手动设置。
|
||||
- 根治选项(需产品拍板):① 读时把 `unclassified` 当作「未设置」再按 kind 派生(简单但失去"我就是要 unclassified"的表达力);② 一次性回填这 57 条(保留人工设置语义,需迁移脚本);③ 只改写入侧让新素材 canonical 化(治不了存量)。
|
||||
- 另一处必须成对维护:别名表有**两份实现**——TS 侧 `packages/shared/src/contracts/gameCreationApp.ts` 的 `GAME_CREATION_APP_LEGACY_ASSET_KINDS`(读投影用)与 Rust 侧 `server-rs/crates/shared-contracts/src/game_creation_app.rs` 的 `canonical_game_creation_app_asset_kind`(写入侧按 kind 派生 category 用)。只改一边就会让落盘 category 与读侧栏目互相矛盾。交叉守卫见 `apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts`(直接解析 Rust 源码比对)。
|
||||
- 成对维护点(**2026-09-17 起不再存在**):当时别名表有**两份实现**——TS 侧 `GAME_CREATION_APP_LEGACY_ASSET_KINDS` 与 Rust 侧 `canonical_game_creation_app_asset_kind`,靠 `apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts` 正则解析 Rust 源码交叉钉住。现在两份手写表与那个守卫测试都已删除:唯一词汇表由 Rust 枚举声明表派生、经 ts-rs 生成 union,跨语言一致性回到**编译期**(TS 侧 `Record<GameCreationAppAssetKind, …>` 穷举,少一个成员就编译不过)。不要再引入"正则解析 Rust 源码"的跨语言守卫。
|
||||
- 真机计数守卫见 `apps/ai-game-creator-shell/tests/resourceCardPreviewRealManifest.test.ts`。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/assets.rs`、`apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs`、`packages/shared/src/contracts/gameCreationApp.ts`、`server-rs/crates/shared-contracts/src/game_creation_app.rs`。
|
||||
|
||||
@@ -5481,20 +5634,20 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- 规则(已拍板落地):`gameCreationAppAssetCategory` 在「落盘 `category === 'unclassified'` **且** 该资产 `kind` 能派生出明确的非 `unclassified` 分类」时采用派生值,其余情况信任落盘值。
|
||||
- 为什么需要这条:`register_local_asset_entry`(`assets.rs`)命中同 `localPath` 的既有资产时**只在 `kind` 变化时**重算 `category`(2026-09-11 起);所以历史上被写成 `unclassified` 的资产(典型是 `kind:"ui"` / `kind:"UI"` 因不在 canonical 目录而落到 `image → unclassified`)在补齐别名后**不会自愈**。选读时重派生而不是写迁移脚本:不需要迁移、且永久自愈(任何历史上被系统错判成 unclassified 的都会自动归位)。真机验证:`What do u wanna do kitten` 的待归类从 58 降到 1(只剩 code 类 `game-entry`),UI 交互从 2 升到 59。
|
||||
- **两个口径不能混用(2026-09-11 收口)**:`gameCreationAppAssetCategory` / `game_creation_app_asset_effective_category` 是**读显示**口径;写回 manifest 必须用 `gameCreationAppAssetPersistedCategory`(只做缺失 / 非法兜底),它等于 Rust 反序列化后的落盘原值。把自愈值回写会把「只改标签」变成静默改分类——真机上同一条 `kind:"ui"` 资产因此同时存在 `unclassified` 与 `ui-interaction` 两种落盘值。
|
||||
- **跨端必须同构**:Agent 侧资源投影(`direct_tool_bridge.rs`)走 Rust 的 `game_creation_app_asset_effective_category`,不许直接透传落盘 `category`;两侧不一致时同一条资产会出现「UI 显示 UI 交互、Agent 读到待归类」(真机 55 条)。守卫是 `assetKindCanonicalMapping.test.ts` 解析 Rust 源码里的 `EFFECTIVE_CATEGORY_CONTRACT` 决策矩阵。
|
||||
- **跨端必须同构**:Agent 侧资源投影(`direct_tool_bridge.rs`)走 Rust 的 `game_creation_app_asset_effective_category`,不许直接透传落盘 `category`;两侧不一致时同一条资产会出现「UI 显示 UI 交互、Agent 读到待归类」(真机 55 条)。**现行守卫(2026-09-17 起)**:`shared-contracts` 的 `EFFECTIVE_CATEGORY_CONTRACT` 决策矩阵用例 + `packages/shared` 的 `gameCreationApp.test.ts` 同口径用例;解析 Rust 源码的 `assetKindCanonicalMapping.test.ts` 已删除。
|
||||
- 为什么可以覆盖落盘值:落盘 `category` 的权威性来自「用户可在分类与标签面板手动设置」(`update_manifest_asset_classification_at`,`project/manifest.rs`)。收窄条件把覆盖窗口压到最小——只有当落盘值是 `unclassified`(即"没有明确分类")时才覆盖。
|
||||
- **唯一盲区**:用户**手动**把一个 kind 已能明确分类的资产设成「待归类」时,该手动值会被覆盖。这是有意接受的取舍:「手动设为待归类」意图边缘,且 kind 已经表达了分类;而漏掉这条规则,所有历史误判都无法自愈。若将来产品需要"显式待归类",应改成在 manifest 里区分"未设置"与"显式 unclassified"(例如 `category` 缺省 vs 显式写入),而不是取消本条规则。
|
||||
- 不受影响:`image` / `video` / `code` / `publication-material` 的 canonical 分类本身就是 `unclassified`,派生结果等于落盘值,规则不触发(已用断言钉住)。
|
||||
- 写入侧已同步 canonical 化:`infer_canvas_export_asset_kind`(`assets.rs`)现在直接产出 canonical kind(`ui → ui-design`、`animation → character-animation`、`asset → image`),不再依赖别名表兜底;`canvas_export_asset_kind_is_always_canonical` 用例逐分支钉死,别名表从此只承担存量兼容。
|
||||
- 写入侧已同步 canonical 化:`infer_canvas_export_asset_kind`(`assets.rs`)现在直接产出 canonical kind(`ui → ui-design`、`animation → character-animation`、`asset → image`),不再依赖别名表兜底;`canvas_export_asset_kind_is_always_canonical` 用例逐分支钉死。**2026-09-17 起别名表整体删除**,存量兼容也不做:读侧严格解析,认不出的原值只留痕。
|
||||
- 关联:`packages/shared/src/contracts/gameCreationApp.ts` 的 `gameCreationAppAssetCategory`、`apps/ai-game-creator-shell/src-tauri/src/assets.rs`、`apps/ai-game-creator-shell/tests/resourceCardPreviewRealManifest.test.ts`。
|
||||
|
||||
## 大写 `UI` / `font` 不在 alias 表 → 8 条真机 UI 资产永远落「待归类」,且自愈救不回(2026-09-11)
|
||||
|
||||
- 现象:真机 `What do u wanna do kitten` 有 8 条资产的 `kind` 是**大写** `"UI"`、`mediaType` 是 `application/json`、`localPath` 是 `ui/UI 设计 N.json`,永远停在「待归类」。
|
||||
- 成因链:写入侧写大写——`ui_editor/resource_bridge.rs` 的 `register_local_asset_at(root, &relative_path, "UI", "application/json", …)`,`workflow.rs` / `persistence.rs` 同;而 alias 表只有小写 `"ui" => "ui-design"`,于是 `"UI"` 落到 `canonical_game_creation_app_asset_kind` 的 `image` 兜底 → `image → unclassified`。
|
||||
- **为什么读时自愈救不回来**:自愈规则的前提是「派生值不是 unclassified」,而 `"UI"` 的派生值**就是** `unclassified`,规则永不触发。所以只能在 alias 表收口——别名表必须**大小写不敏感**(`UI` / `ui` / `ui-prototype` 都要落 `ui-design`),并把 `font`(`ttf / otf / woff / woff2` 上传登记的 kind,见 `commands.rs` 的 `register_local_asset_entry(root, &relative_path, "font", …)`)一并补进别名表落 `document`。
|
||||
- 守卫三层:Rust `asset_category_mapping_covers_every_canonical_kind` 逐条断言(旧断言曾把 `"UI" → unclassified` 钉死,正是这条 bug 的护栏反向加固);`ui_editor/resource_bridge.rs` 的 `bridge_is_idempotent_and_installs_source_image` 走真实生产函数 → 真实写入 → 断言落盘 `category`;TS `assetKindCanonicalMapping.test.ts` 的「写侧 kind 字面量 → 分类」直接解析写侧源码的第 3 个实参,写点换个新字面量就会红。
|
||||
- 关联:`server-rs/crates/shared-contracts/src/game_creation_app.rs`、`packages/shared/src/contracts/gameCreationApp.ts`、`apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs`、`apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts`。
|
||||
- 成因链:写入侧写大写——`ui_editor/resource_bridge.rs` 的 `register_local_asset_at(root, &relative_path, "UI", "application/json", …)`,`workflow.rs` / `persistence.rs` 同;当时的 alias 表只有小写 `"ui" => "ui-design"`,`"UI"` 因此落 `image` 兜底 → `unclassified`。**注意 `font` 早已是正式 canonical 成员(不再靠别名落 `document`)**。
|
||||
- **为什么读时自愈救不回来**:自愈规则的前提是「派生值不是 unclassified」,而 `"UI"` 的派生值**就是** `unclassified`,规则永不触发。**现行口径(2026-09-17 起)**:不给 `"UI"` 找归一口径,而是修写入侧写 canonical 字面量;读侧严格解析,认不出的原值收口 `unknown` + `app_log!` 留痕(原始串 + 上下文)。
|
||||
- 守卫三层:Rust `asset_category_mapping_covers_every_kind` 穷举断言(旧的 `"UI" → unclassified` 断言正是这条 bug 的反向加固,已删);`ui_editor/resource_bridge.rs` 的 `bridge_is_idempotent_and_installs_source_image` 走真实生产函数 → 真实写入 → 断言落盘 `category`;TS `parseGameCreationAppAssetKind` / `console.warn` 用例钉住"非 canonical 值收口 + 留痕原值"。
|
||||
- 关联:`server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs`、`packages/shared/src/contracts/gameCreationApp.ts`、`apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs`、`apps/ai-game-creator-shell/tests/assetKind.test.ts`。
|
||||
|
||||
## AGC 资源搜索栏改成「临时叫出」的浮层,工具条带与它的下移逻辑一并撤掉(2026-09-11)
|
||||
|
||||
@@ -5706,6 +5859,23 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- Vitest 的 `toHaveBeenCalledWith` 匹配任意一次调用,失败输出会列出其它命令;应先定位相同命令的真实参数差异,不能由其它调用的序号推断时序故障。
|
||||
- 存在后台轮询的 IPC mock 不应要求目标命令占据全局最后一次调用。验证刷新时先记录调用边界,再筛选该边界之后的目标命令,严格核对其最后一次参数,避免后台查询影响断言,也避免旧调用掩盖刷新未执行。
|
||||
|
||||
## 2026-09-16 Lexical 投影丢掉引用后的换行:`@素材` 和下一段粘成一个词
|
||||
|
||||
- **现象**:聊天输入区里先 `@` 一个素材、回车换段再写文字,提交出去的 canonical content 里没有任何分隔,直接读成 `@hero把这一版改成夜景`;同一个字符串还会进 agent 输入、队列 chip 文案与润色判据。
|
||||
- **原因**:`3c7b02b9f`(2026-09-15)为了让 content 通过 Rust 的「空 `input_text`」校验,在投影层加了 `appendInputText`(`if (text.trim())` 才落 part,并与相邻文本合并)。root 子节点之间补的段落分隔符与 `LineBreakNode` 传进来的都是 `'\n'`,`trim()` 为空 ⇒ 整段丢掉;chip 后那一段文字随后另起一个 part,派生文本用 `''` 直接拼接,于是粘成 `@hero把这一版改成夜景`。`41366dd71` 又把消息正文 / 队列 chip / 快速编辑的文本派生切到这条投影上,缺陷扩散到界面与出站 prompt。
|
||||
- **处理(最终口径)**:不保留任何前端过滤,而是去掉规则和它的成因——Rust `validate_direct_codex_user_item` 改成只判整条 content(`content_has_meaningful_input`:有一段非空白文本或任意非文本 part 即有效),单个纯空白 `input_text` 合法;`ResourceReferenceInput` 的投影原样透传编辑器节点,既不丢空白也不与相邻 part 合并。中间版本(把待写文本「向前合并」到下一个 part)已随之删除:它仍会丢掉尾随换行与「两个 chip 之间只隔一个换行」的分隔,也仍要让前端替用户改写内容。
|
||||
- **验证**:Rust `validation.rs` / `wire.rs` 新增「单个纯空白 part 通过校验、整条全空白拒绝」用例;`apps/ai-game-creator-shell/tests/resourceReferenceInput.test.tsx`「引用后面的段落分隔原样落进 content」断言 `[ref, { type: 'input_text', text: '\n' }, { type: 'input_text', text: '…' }]` 与派生文本逐字一致;`tests/appSurface/project-development.suite.ts` 的 Godot 用例断言 Shift+Enter 的两个换行各自成 part。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx`(`collectDraftParts`)、`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs`、`docs/project-memory/plans/【里程碑】DirectProject canonical content严格边界-2026-09-16.md`。
|
||||
|
||||
## 2026-09-16 派生文本漏传 `manifest.assets`:`@引用` 从显示名退化成内部 id
|
||||
|
||||
- **现象**:快速编辑面板里 `@` 插了素材再点「修改」,出站 `derive_local_project_resource` 的 `prompt` 是 `把夜色改成星空@source-rules`,而界面 chip 与聊天输入区显示的是 `@rules`;仓库自带的 `projectResourceLiveIntegration` 用例因此长期是红的(`expected '把夜色改成星空@rules' to be '把夜色改成星空@source-rules'`)。同一根因还让排队消息 chip 显示 `@asset:…`。
|
||||
- **原因**:`directCodexContentToPromptText(content, assets)` 的 `assets` 有默认值 `[]`,漏传不报错、只是把 `agc_resource_reference` 退化成 `${resourceId}`。`ResourceReferenceInput` 自己读草稿的四处都带了 `manifest.assets`,而它的三个消费方漏传:`chatComposerQueue.queuedChatTurnLabel`(宿主 `ComposerTurnQueue` 也没接素材清单)、`project-development/index.tsx` 的 `applyResourceQuickEditPrompt` 与 `applyResourceQuickEditDraft`;后两个的 `useCallback` 依赖里同样没有 `manifest.assets`,改完还会读到旧清单。
|
||||
- **处理**:三个消费方全部补上素材清单并进依赖数组——`queuedChatTurnLabel(turn, assets)` + `ComposerTurnQueue` 新增 `assets` 属性(由 `ProjectSupervisorView` 传 `chatProjectAssets`)、快速编辑的两处改用 `manifest.assets`。改「比较用的草稿文本」与「落进面板的文本」必须同一个口径,否则 `replaceText` 会每次输入都重跑一遍。
|
||||
- **加固**:`directCodexContentToPromptText(content, assets)` 与 `queuedChatTurnLabel(turn, assets)` 的 `assets` 改为**必填**(删掉 `= []` 默认值),测试里刻意不传 manifest 的地方显式写 `[]`。理由:默认值把「漏传素材清单」从编译期错误降级成运行期文案退化,正是本条缺陷的入口;队列 chip 与消息正文从此共用同一个派生(`queuedChatTurnLabel` 只多做「压成单行 + 限长」)。
|
||||
- **验证**:`apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx` 的「快速编辑提示词里能 @ 出资源选择器」由红转绿(该文件 29 passed);`tests/appSurface/chat-composer.suite.ts` 新增「队列 chip 的 @ 引用按 manifest 显示名展开」;`appSurface.test.ts` 467 passed、定向 51 passed、`npm run ai-game-creator-shell:typecheck`、`npm run check:encoding` 通过。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/features/project-workspace/chatComposerQueue.ts`、`ComposerControls.tsx`、`ProjectSupervisorView.tsx`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx`。
|
||||
|
||||
## 2026-09-16 并发生图遇到“登录态冲突”:身份代次与凭据轮换混用
|
||||
|
||||
- **现象**:DirectProject 长回合里并发派发的生图 / 素材生成请求中途报 `authentication-required: 陶泥儿登录态已变化,旧账号请求已停止,请使用当前账号重试`,或平台工具返回 `HTTP 401 invalid-token`;账号并没有切换,重新登录后短时间内可复现。
|
||||
@@ -5725,8 +5895,8 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
## 2026-09-16 AGC 壳跑过 `cargo test` 后前端 typecheck 必红:ts-rs 导出把 `generated/*.ts` 重写成另一种形状
|
||||
|
||||
- **现象**:在 `apps/ai-game-creator-shell/src-tauri` 跑过 `cargo test` 之后,`npm run ai-game-creator-shell:typecheck` 报 10 条类型错(`src/features/project-workspace/resourceReferences.ts:106-112` 的 `string | undefined` 不能赋给 `string | null`;同文件 134 行的对象字面量带 `type: 'message'`,而 `DirectCodexUserMessageItem` 里没有该字段),`npm run ai-game-creator-shell:build` 也死在 `beforeBuildCommand` 的同一条 typecheck 上。
|
||||
- **原因**:crate 里的 ts-rs 导出会按**本机依赖版本**重写 `src/features/project-workspace/generated/DirectCodexUser*.ts`:注释头变成 "This file was generated…"、字符串改双引号、`DirectCodexUserMessageItem` 丢掉 `type: 'message'` 判别字段、并多出一个 `DirectCodexUserMessageEnvelope.ts`。仓库里提交的那份是前端真正依赖的形状(前端按带 `type` 的判别联合写),重写后两边就对不上——错在生成器版本漂移,不在前端。
|
||||
- **处理(现行口径)**:不要把重写结果当改动提交。跑过 `cargo test` 或构建后只恢复 `apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUser*.ts` 的仓库版本,再删掉多出来的 `DirectCodexUserMessageEnvelope.ts`;不要恢复整个 `generated/` 目录,以免误删 DirectThread 的现役绑定。绑定与前端形状冲突时以**已提交的 DirectCodexUser 绑定 + 前端**为基准排查。
|
||||
- **原因**:crate 里的 ts-rs 导出会按**本机依赖版本**重写 `src/view/project-development/chat/generated/DirectCodexUser*.ts`:注释头变成 "This file was generated…"、字符串改双引号、`DirectCodexUserMessageItem` 丢掉 `type: 'message'` 判别字段、并多出一个 `DirectCodexUserMessageEnvelope.ts`。仓库里提交的那份是前端真正依赖的形状(前端按带 `type` 的判别联合写),重写后两边就对不上——错在生成器版本漂移,不在前端。
|
||||
- **处理(现行口径)**:不要把重写结果当改动提交。跑过 `cargo test` 或构建后只恢复 `apps/ai-game-creator-shell/src/view/project-development/chat/generated/DirectCodexUser*.ts` 的仓库版本,再删掉多出来的 `DirectCodexUserMessageEnvelope.ts`;不要恢复整个 `generated/` 目录,以免误删 DirectThread 的现役绑定。绑定与前端形状冲突时以**已提交的 DirectCodexUser 绑定 + 前端**为基准排查。
|
||||
- **验证**:恢复仓库版本后 `npm run ai-game-creator-shell:typecheck` exit 0(`[skill-pack] OK`);保留重写结果时同一条命令 exit 2。release 构建本身还会在 `src/features/ui-editor/types/` 落下 `BindingChange.ts` / `BindingDTO.ts` 两个无人引用的生成产物;它们不属于前端契约,发现后直接删除,不提交。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/`(ts-rs 导出源)、`apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts`、`apps/ai-game-creator-shell/scripts/build-release.mjs`(`beforeBuildCommand`)。
|
||||
|
||||
@@ -5744,3 +5914,37 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- **处理(现行口径)**:① 依赖里只放数据,回调走 ref(`openActiveProjectRef`)——effect 不再因回调换身份而重跑;② `useDirectActiveTurns` 轮询只在快照内容变化时才 `setActiveTurns`(并给空态做引用稳定),避免每 5 秒换一次数组身份去带动下游 effect;③ `WindowChrome` 的 context value 用 `useMemo` 收口。判断类问题的通行判据:**凡是把"每次渲染新生成的函数/对象"写进 effect 依赖的,一律视为 bug**。
|
||||
- **验证**:修复后同一台机器、同一路径下 35 秒内新增 `Maximum update depth` **0 条**,renderer 工作集 **254 MB**(修复前 4.2–4.4 GB);`apps/ai-game-creator-shell/tests/directActiveTurns.test.tsx` 断言轮询返回值不变时快照引用不变。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/features/app-shell/WorkspaceLauncher.tsx`、`apps/ai-game-creator-shell/src/features/agent-runtime/directActiveTurns.ts`、`apps/ai-game-creator-shell/src/components/WindowChrome.tsx`、`apps/ai-game-creator-shell/src/features/app-shell/useHomeProjectCreation.ts`。
|
||||
|
||||
## 2026-09-21 受控 Lexical 输入区的回写用被动 effect:滞后渲染的 props 会把用户草稿清空
|
||||
|
||||
- **现象**:DirectProject 输入盒里粘贴(或连续输入)长文本,提交时 `chat_with_game_creator_direct_codex` 根本没发出去,界面停在空输入盒;`chat-composer` 用例里表现为「队列/终止/语音追加」五条一起红,但手工操作只在快速输入后偶发。
|
||||
- **原因**:`ResourceReferenceInput` 的受控回写写在 `useEffect` 里,判据是「`value`/`references` 与编辑器当前草稿不一致就重建 root」。宿主对草稿的回显(`chatInput`/`chatReferences`)永远晚于编辑器的 Lexical 提交:一次渲染提交后它的被动 effect 可能排在用户下一次输入之后才执行,于是它读到的是**旧 `value` 加新编辑器状态**,判定不一致→`applyDraftToRoot` 整份重建→编辑器 `onChange` 把空草稿写回宿主→宿主再回显空值,来回清空。`references` 每次 `handleComposerDraft` 都换数组身份,进一步保证这段 effect 每次都跑。
|
||||
- **处理**:受控回写改成 `useLayoutEffect`,与本次提交同帧执行,读到的 props 与编辑器状态属于同一次提交;被动 effect 的滞后回调不再可能出现。旧 `value`+`references` 双轨语义本身仍是待收口的债务(见 canonical content 里程碑)。
|
||||
- **验证**:`apps/ai-game-creator-shell/tests/appSurface/chat-composer.suite.ts` 的队列、终止×2、恢复回合、语音追加五条用例转绿;`tests/resourceReferenceInput.test.tsx` 全绿。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx`、`docs/project-memory/plans/【里程碑】DirectProject composer canonical content闭环-2026-09-21.md`。
|
||||
|
||||
## 2026-09-21 删链路只删订阅、没删它喂的 state:留下恒为默认值的死 prop
|
||||
|
||||
- **现象**:`876529e66` 之后策划 Agent 一轮里再也看不到流式正文和「思考过程」,`planningV2Reasoning` 恒为 `''`、`designReasoning` 这个 prop 永远传空;对应用例只在 `appSurface` 里红一条,很容易被当成「测试过期」删掉。
|
||||
- **原因**:该提交目的是删除 Project Supervisor,但把 App 里唯一订阅 `design-agent-update` 的 effect 一并删了;Rust 侧 `design_*` 三个命令仍在 `app.emit("design-agent-update", …)`(含 `reasoning_text`/`text`/`view`),前端却无人监听。判断线索是残留的 `designAgentEventSubscriptionReady()`——它在发起回合前被 await,但没有任何代码再创建这个 promise,说明订阅被误删而不是有意退役。
|
||||
- **处理**:恢复订阅(正文/工具提示 → transient reply,`reasoningText` → `planningV2Reasoning`,`view` → `applyDesignAgentViewAfterTransient`),补回订阅就绪 promise 的建/解函数;新增 `designAgentReasoningTurnRef` 让回合结束后仍认本轮 reasoning。
|
||||
- **验证**:`tests/appSurface/design-agent.suite.ts`「keeps the current turn reasoning after completion and supports collapse/expand」与「renders historical reasoning as independent collapsed sections」同时通过。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/App.tsx`、`apps/ai-game-creator-shell/src-tauri/src/agent/design_runtime.rs`。
|
||||
|
||||
## 2026-09-21 DirectProject 聊天 IPC 里的「可读 prompt 投影」是死参数,接回模型输入就污染标签
|
||||
|
||||
- **现象**:`chat_with_game_creator_direct_codex` 的 Tauri 入参曾并列带一个 `prompt: String`(前端把 canonical content 渲染成 `@显示名` 文本),Rust 侧从不读取——`cargo check` 直接报 `src/agent/direct_runtime/user_input.rs` 的 `unused variable: prompt`;前端每次提交仍要算一遍,界面测试也钉着这份投影文本。
|
||||
- **成因**:DirectProject 回合真正的输入只来自 canonical `userItem`。`direct_codex_user_item_to_codex_turn_input`(`agent/direct_codex_user_item/wire.rs`)在 DirectProject 分支重建 `turn/start.input`,`LlmRunRequest` 里那份 `user_prompt` 会被覆盖;客户端投影走的是另一套口径(素材被删或改名时退化成裸 resourceId),一旦有人把它接回 Codex,就把 `@显示名` 标签污染了模型输入。
|
||||
- **现行口径**:IPC 只传 `projectPath`、`clientTurnId`、`userItem` 与可选 `creationType`;Rust 只从 `userItem` 派生回合输入(空判定与三维契约探测用的派生 prompt 仍在 Rust 内部生成)。前端那份 `@显示名` 投影只服务本地的 `/history` 识别与队列 chip 文案,不出 IPC;界面断言只能读 `userItem.content`。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime/user_input.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs`、`apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts`、`apps/ai-game-creator-shell/tests/appSurface/chat-composer.suite.ts`。
|
||||
|
||||
## 2026-09-21 DirectProject 结构化消息不能逐个拒绝空白文本片段
|
||||
|
||||
- **现象**:多行正文、末尾空段落或合法引用前后的分隔空格会让有内容的消息报错;编辑器为了保持结构产生的空白文本片段被误判为“聊天内容为空”。
|
||||
- **原因**:校验器对每个 `input_text` 单独执行 `trim().is_empty()` 并立即拒绝,混淆了结构化片段合法性和整条消息是否有实际内容。
|
||||
- **处理(现行口径)**:`input_text` 允许空字符串、空格和换行,校验过程保持全部片段的原文、分段与顺序,不做合并或删除;遍历完整条消息后,只在既没有非空白文字、也没有任意非文本 part(素材引用 / 运行画面引用 / Skill 引用 / 附件引用)时返回“聊天内容不能为空”。各类引用仍逐个执行原有校验,消息带正文也不能绕过非法引用。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs`、`docs/【功能说明】AGC聊天素材引用-2026-09-08.md`。
|
||||
|
||||
## 2026-09-21 应用日志整行凭据脱敏会吃掉整条结构化诊断
|
||||
|
||||
`append_application_log_line` 在落盘前对整行做 `sanitize_diagnostic_message`:行内只要出现 `token=`、`bearer `、`authorization`、`credential`、`api key` / `apikey` / `api_key` 这类标记,**整行**就被换成 `<sensitive diagnostic details redacted>`,只留下时间戳与 `RUST module:` 前缀;同时每行还会被截到 2048 字符。于是把“身份字段 + 诊断正文”拼成一行 `app_log!` 时,正文里一个凭据词就可能让整条记录连 `eventId`、`code` 一起消失(2026-09-21 加统一错误事件的日志投影时按两行落:身份行只放程序生成与调用方常量字段,summary / hint / detail 等自由文本一律只放详情行,且自由文本先自行压平换行——裸词标记脱敏消不掉,自由文本放错行会把 eventId、code 一起带走)。
|
||||
|
||||
@@ -51,18 +51,21 @@ SpacetimeDB crate、SDK、CLI / standalone 与生成 bindings 按 `2.8.3` 对齐
|
||||
|
||||
## AGC DirectProject 与 UI workflow
|
||||
|
||||
- AGC 模板库包含 Creator 3.8.8 的四个官方 Cocos 模板;Cocos 建项复用原生导入,重建项目 UUID 并保留场景与资源。发布使用内容地址保留历史对象,并在确认 Bucket 从未开启版本控制后获取排他锁;`--only` 在锁内合并最新清单,清单写入结果不明时留锁,同版本 ZIP 变化拒绝发布。详细合同见 [AGC 模板库与模板建项](../../technical/【技术方案】AGC模板库与模板建项-2026-09-17.md)。
|
||||
|
||||
- AGC 的本地 `llm.customEnabled` 默认关闭,只能手动修改配置文件;开启后设置支持自定义 Responses 端点、读取 `/models`、勾选和预览 `visibleModels`。对话下拉只显示勾选项,LLM 请求经客户端凭据代理直连自定义上游;不会回退官方中转,平台资源服务仍使用账号权限。详见 AGC 后台模型别名与对话选择规范。
|
||||
|
||||
- DirectProject 对话先在完整历史中按回合/原始 item 身份关联,再分页渲染;每个回合只有一个呈现入口。有流按 item `seq` 交替文本和工具,无流采用历史正文;禁止位置猜配或同时展示累计回复与 item 正文。流写入单调归并,收尾等待落盘任务,不按磁盘“最后一段”猜最终回复位置。详见 AGC 实施计划的“DirectProject 回合展示唯一归属”。
|
||||
- 回合生命周期只由活动 client 回合快照和 Direct 事件恢复;Provider 的历史终态通知不能创建活动 client 回合。消息发送时间保存在历史信封,原始 item 不混入宿主字段;完成后的中间文本和工具默认收进“执行过程”,最终回复及失败提示保持可见。
|
||||
|
||||
- AGC 安装产品名统一为“陶泥儿”,由 Tauri `productName` 控制安装项、快捷方式与 EXE 产品描述;Windows 内置 Codex 安装到顶层 `coding-agent/win-x64/`,打包资源映射与运行时查找路径必须一致。内部可执行文件名与应用 identifier 保持稳定。
|
||||
- AGC 安装产品名基线为“陶泥儿”,由 Tauri `productName` 控制安装项、快捷方式与 EXE 产品描述;默认渠道 `dev` 保持基线产品名与 identifier `world.genarrative.ai-game-creator` 不变,其它渠道派生 `陶泥儿 <渠道显示名>` 与 `<基线>.<渠道>`,让不同渠道的包体在同一台设备上并存而不互相顶掉(详见《AGC客户端更新检查与下载》的渠道与安装身份合同)。Windows 内置 Codex 安装到顶层 `coding-agent/win-x64/`,打包资源映射与运行时查找路径必须一致。内部可执行文件名保持稳定。
|
||||
|
||||
- 新 Web 游戏为 `game/` 下的 npm + Vite + Phaser 4.2.1 工程,使用包导入且允许其它依赖;npm 预览与导出只读取 dist,运行素材需纳入构建。单 HTML → Phaser 迁移固定走 DirectProject:文件落盘后先用受控 `project.bootstrap` 在 `game` 执行无参数 `npm install`,再用支持相对 cwd 的 `project.verify` 构建并确认 `game/dist/index.html`,已有单 HTML/Godot 不通过 JSON Generator 伪装成 npm 工程。
|
||||
|
||||
- 通用 Agent Rust 分层为 `agent-runtime-core`(catalog、执行生命周期、ToolHost/spawn/all-join/Provider 契约)、`agent-runtime-orchestration`(动态无环任务图、ready、依赖波次、返工下游闭包和受限自主扩图提案)与 `platform-agent` 游戏适配器;循环返工通过新 pass / epoch 表达,不在单张依赖图中建立回边。LLM 可经宿主结构化 function call 提出新增节点/边,编排层只生成经校验的新候选图,epoch 与持久化仍由宿主掌控。
|
||||
- DirectProject 始终连接客户端内置的 `agc_tools` STDIO MCP,并在启动时额外读取客户端扩展仓库中已启用的第三方 MCP 独立项。第三方 STDIO/HTTP 配置只写入本次隔离 `CODEX_HOME`,单项非 required,启停、重命名和内容指纹进入 app-server pool identity;完整 Plugin Runtime、hooks/apps 和单文件脚本手动指定入口仍关闭。Skill 正文与 references 由 Codex 原生按需读取;`agc_tools` 负责标准美术准备、已登记资源有界查询、视频 / 角色动画 / 音效 / BGM 的 create-or-derive、已登记图片去背景、desktop/mobile 浏览器试玩和受控 `agc_web_search`;付费资源调用仍由客户端绑定回合、幂等账本、请求上限和投影权威。
|
||||
- 2026-09-09 起,AGC 已新增遵循 OpenAI Agent Plugins 组合模型的通用 Plugin Host/SDK:Plugin、Skill 和 MCP 进入统一扩展 catalog;插件生命周期、行分隔 JSON-RPC、UI 面板、Capability Registry、权限和审计由 `plugin_host` 统一承接,Skill/MCP 仍分别交给各自现有 loader/transport;目标编辑器只通过通用 `EditorAdapter` 扩展点接入。详见 `docs/technical/【技术方案】AGC通用插件宿主与编辑器适配-2026-09-09.md`。
|
||||
- Unity 编辑器能力以 `plugins/agc-unity-editor` 内置插件提供,固定复用 Apache-2.0 的 DotCraft Attach 核心;Windows x64 / Unity Mono 接入不安装项目包。GUI、Runtime 与 DirectProject 通过现有 Runner 统一执行归属,跨进程回执与持久不确定阻断统一处理。首次打开 Unity 工程只初始化 AGC `.agent` 元数据,保留原引擎工程;详见 `docs/technical/【技术方案】AGC Unity编辑器插件接入-2026-09-18.md`。
|
||||
- DirectProject 的 Codex 原生文件、搜索、命令、图片查看和 Skill 仅在用户项目 cwd 与 `workspaceWrite(writableRoots=[project])` 内可用;原生命令允许联网以支持 npm 安装,npm 缓存位于项目内 `.npm-cache/`。多 Agent、Apps、插件、hooks、图片生成、Goals、Workspace Dependencies、Tool Suggestion 和原生浏览器/电脑控制保持关闭。app-server 使用隔离 `CODEX_HOME`,provider 凭据只由 AGC 客户端代理持有,不能进入模型上下文或 shell 环境。
|
||||
- `ui-prototype`(设计图片)与 UI 编辑器 `UI` JSON 是不同资源。白名单 `ui.workflow.run` 按页面执行 `prepare → recognize → status → finalize`,由 provider-backed 识别、合并和组件绑定持久化 State/revision,并把 `reference-ready → structure-ready → merge-ready → binding-ready → application-ready → completed` 投影到 manifest。Provider 缺失、请求失败、工具缺失、结果不匹配或仍有待审节点时保留真实阶段并返回 blocker,不得用 deterministic seed 伪造完成。
|
||||
- UI workflow 的资源桥接与 Runtime 边界以 `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md` 和 AGC 实施计划的 2026-08-24 覆盖段为准;只生成图片、登记空 JSON 或进入普通图片画布都不构成 workflow 完成。
|
||||
|
||||
@@ -16,23 +16,44 @@
|
||||
|
||||
## 开发中
|
||||
|
||||
- AGC 思考与执行入口共用共享单行摘要骨架;Markdown 只在展开正文走既有安全渲染,折叠预览只取纯文本,不在 summary 嵌套链接或按钮。耗时统一复用中文时分秒格式(不足一分钟一位小数,达到分钟后整数秒),格式化与各层计时边界分离。过程行在运行中和完成后的折叠层内保持同一紧凑间距;失败状态按明确终态与非零退出码呈现红色,不由自然语言输出猜测。
|
||||
- DirectProject 工具可并行调度,依赖由调用方等待,同资源事务与付费动作幂等不能放松。Web 创作先用客户端环境预检,分层验证共用持久的 `validation.maxRuns`,不改写 Provider 的 `llm.maxRetries`;成功证据按输入指纹复用,达标后交付。模型请求计时只保存安全元数据与可观测边界,未知不补零,写盘不能阻塞响应流。详见 AGC 主专题的“DirectProject 交付效率与可观测性”。
|
||||
|
||||
- DirectProject 源码修改走 `agc_apply_patch`、进度走 `agc_update_plan`:SDK 原生的 `apply_patch` / `update_plan` 注册会被按回合移除(全局串行单例),不要恢复它们或用伪造工具注解换取并发。补丁只在当前项目内、受当前回合 Write 许可和受控进程约束,失败可能已部分写入,未知结果不自动重放;计划完成不构成验收证据。
|
||||
|
||||
- 捆绑 Codex 版本只在 `build_support/codex_bundle.rs` 固定一次,不要在测试或脚本里另写字面量;升级 SDK 后必须重跑模型目录真实用例、宿主补丁往返、并发夹具与发行载荷 smoke。原生命令工具名随 SDK 版本变化(0.155 起为 `exec_command` / `write_stdin`),脚本与夹具应按真实目录取用,不要按旧名字硬编码。
|
||||
- AGC 主模型追溯保存在项目 `.agent/model-usage.jsonl`,请求目录标识与响应确认的型号分别记录;旧项目当前配置补录必须标注来源,不冒充历史事实。仅保存有界模型与回合身份字段,不保存配置、凭据或对话正文,不增加 UI 展示。详见 AGC 实施计划“项目主模型使用记录”。
|
||||
|
||||
- Agent 提示词正文与工具说明放在所属组件的 `prompts/`;AGC 通过现有 Prompt Bundle 编译加载,服务端独立 crate 编译包含自己的提示词文件。代码负责变量填充、结构化 schema 与执行校验。
|
||||
|
||||
- 策划 Agent 的顾问态由用户指示驱动,不自主推进项目、主动安排下一步或提交阶段审批;完成单次请求不结束顾问态。五个策划阶段的审批用于检阅已完成产物,关键选择先问询;过程文档按需记录且不重复正式正文。顶层设计按需保留易混淆方向及排除理由,提示词精简应保留这些行为与设计边界。详见策划 Agent 生产迁移与工作区浏览方案。
|
||||
|
||||
- 策划 Agent 复用现有模型/推理档控件,宿主不另加模型检查或自动换模型。用户发起执行时采样全局选择,同轮工具循环和自动重试固定使用回合快照;自动恢复复用该快照,旧记录保留已知模型并补齐一次推理档。只持久化模型和档位,不保存连接凭据;GameAgent 保持原逻辑。详见策划 Agent 生产迁移与工作区浏览方案 §4.1。
|
||||
|
||||
- 策划附件通过独立文件导入命令保存到 `design_artifacts/references/`,同名另存;首页和聊天框只展示导入结果,不登记游戏资产、不修改清单或 revision、不注入回合附件及路径。Agent 通过现有工作区工具自行发现,只有文件时不合成消息。详见策划 Agent 生产迁移与工作区浏览方案 §4.2。
|
||||
|
||||
- AGC 思考与执行入口共用共享单行摘要骨架;Markdown 在展开正文走既有安全渲染,折叠预览使用纯文本。耗时统一复用中文时分秒格式(不足一分钟一位小数,达到分钟后整数秒),格式化与各层计时边界分离。过程行在运行中和完成后的折叠层内保持同一紧凑间距;失败状态按明确终态与非零退出码呈现红色。
|
||||
|
||||
- Direct 对话计时区分条目展示时间与生命周期事件时间:整轮用用户发送到明确终态的跨度,工具用各自开始/完成边界;运行时用 100ms 叶子时钟刷新一位小数,终态冻结,旧历史缺边界不推测。不得用整秒时间的大小比较取代 Thread Manager 的事件顺序判定新回合。
|
||||
|
||||
- AGC 批量追加素材标签由原生在一次项目写锁与 revision CAS 下合并各项原标签,先校验全批再写 manifest;前端不能循环单素材分类命令,不回传展示层推导的分类或旧标签全集,以免部分写入或覆盖未编辑字段。
|
||||
|
||||
- AGC 正式包的平台服务跟随构建渠道:`release` 连接 `https://www.genarrative.world`,`dev` 连接 `https://dev.genarrative.world`;本地 debug 态保留 release/dev/custom 服务器选择,会话凭据始终按 origin 隔离。发布渠道为 `dev/release/自定义名称`,Windows/Mac 是系统,OSS 的 `<channel>-win/mac` 仅是延续既有地址的分区。官网通过服务端 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL`(默认 dev)选择渠道,公开同源 `/api/client-downloads` 汇总其各系统首装包与真实版本;未发布隐藏,单系统失败不影响其它下载,不跨渠道补齐。发布先上传 EXE/DMG 再写对应分区清单,不维护会互相覆盖的共享 OSS 索引。主站 Vite 代理复用实际 `runtimeServerTarget`。渠道同时决定安装身份:默认渠道 `dev` 必须保持基线 `productName` / `identifier` 不变,其它渠道派生独立身份,使不同渠道的包体可在同一台设备并存且本地数据不共享;身份与更新端点必须在同一次构建期注入里确定,禁止分别回读默认值。完整约定见 AGC 客户端更新检查与下载专题。
|
||||
- AGC 模板库灰度复用 `agc:template-library`:未配置关闭,已配置时遵循现有灰度启停、用户 ID/标签和比例规则;服务端返回权威结论,客户端入口和原生清单/下载/建项均执行门禁,主体切换丢弃旧异步结果。公开 OSS 不是保密边界,已创建项目不受影响。
|
||||
|
||||
- 画布卡片类型与信息角标共用 `CanvasCardCornerActions`;菜单收纳共用 `OverflowActions`,宿主决定展示数量和资源命令。AGC 选中菜单前 5 项直显,Web 默认不折叠;浮层 portal 继续接入现有画布关闭与滚轮归属判据。
|
||||
- 修改范围保持聚焦;优先扩展现有系统、页面、组件、DTO 和脚本,不新建平行入口或业务真相。
|
||||
- UI 开发优先复用现有公共组件;跨页面或跨端重复的视觉/交互模式应沉淀到 `packages/shared`,由现有页面迁移使用,禁止在业务页复制同类 UI。共享组件只承载通用表现与交互,不下沉领域规则、后端副作用或正式业务状态。
|
||||
- AGC 当前 Agent 与策划 Agent 的消息层级共用 `packages/shared` 的 `AgentMessageContent`:正文使用 `body`,思考、中间输出与工具调用使用 `process`;宿主不按 Agent 类型重新定义过程字号和颜色,错误状态保留语义色。
|
||||
- 修改范围保持聚焦;优先扩展现有系统、页面、组件、DTO 和脚本。
|
||||
- Agent 可见内容直接描述当前任务、输入和成功条件,细节按调用需要提供。
|
||||
- UI 开发优先复用现有公共组件;跨页面或跨端重复的视觉/交互模式应沉淀到 `packages/shared`,由现有页面迁移使用。共享组件承载通用表现与交互,领域规则、后端副作用和正式业务状态由后端负责。
|
||||
- AGC 当前 Agent 与策划 Agent 的消息层级共用 `packages/shared` 的 `AgentMessageContent`:正文使用 `body`,思考、中间输出与工具调用使用 `process`;过程字号和颜色由共享组件统一定义,错误状态保留语义色。
|
||||
- 后端遵循 `module-*`、`spacetime-module`、`spacetime-client`、`api-server`、`platform-*`、`shared-contracts` 的现役边界。
|
||||
- 前端只负责表现、交互和临时 UI 状态;正式状态来自后端投影、API 或持久化契约。
|
||||
- 对已明确退役且无现役调用方、公开契约、持久化迁移或活跃实例的对象,不写兼容实现、维持旧行为的测试、墓碑注释或墓碑文档。
|
||||
- 对已明确退役且无现役调用方、公开契约、持久化数据、活跃实例或迁移要求的对象,直接清理实现、专属测试和说明,将权威文档更新为当前状态;历史由 Git 保存。公开契约、持久化数据和正式迁移按实际需求保留最小兼容及对应测试。
|
||||
- 修改 `/api/external/v1` 时,同批更新 `docs/openapi/genarrative-external-v1.openapi.json` 与契约测试。
|
||||
- 修改 SpacetimeDB schema 时遵守字段追加/default 约束,同步 migration、表目录、生成绑定,并运行 schema 检查;删除、改名、重排或改类型前先确认迁移计划。
|
||||
- 日志不递归输出完整配置、应用状态或 provider client;新增字段默认不进入安全摘要。
|
||||
- 中文文案、注释和文档保持 UTF-8,优先局部补丁,不擅自翻译成英文。
|
||||
- HTTP 横切能力集中在 Axum/Tower 中间件:正常与降级路由复用追踪层;指标与 trace 使用 `MatchedPath` 模板及固定兜底,不把请求 ID、实际资源 ID 或 query 放入指标标签。在途请求通过 RAII guard 覆盖 Future 取消与 panic unwind;请求执行和响应体存活分别计量,不能把 handler 耗时当作 SSE 全生命周期。
|
||||
- 业务依赖在组合根显式装配,Axum `FromRef` 只抽取可浅拷贝的窄能力。项目元数据与 External API 鉴权不持有完整 `AppState`,测试经相同接口注入替代依赖。集中鉴权仍保留方法级 fallback、公开入口、MCP 和 body limit 顺序;Provider span 跳过完整参数,不隐藏计费、重试、幂等或事务规则。
|
||||
- 中文文案、注释和文档保持中文与 UTF-8,优先局部补丁。
|
||||
|
||||
## 文档生命周期
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
## 验收标准与证据
|
||||
|
||||
| 条款 | 验收方式 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| | | 待补 |
|
||||
| ---- | -------- | ---- |
|
||||
| | | 待补 |
|
||||
|
||||
## 未决问题与决策
|
||||
```
|
||||
@@ -49,11 +49,11 @@
|
||||
```md
|
||||
# 【里程碑】<只描述行为切片>
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Version | 1.0 |
|
||||
| Status | proposed |
|
||||
| Date | YYYY-MM-DD |
|
||||
| 字段 | 值 |
|
||||
| ----------- | ------------------- |
|
||||
| Version | 1.0 |
|
||||
| Status | proposed |
|
||||
| Date | YYYY-MM-DD |
|
||||
| Parent Spec | `docs/<主规范路径>` |
|
||||
|
||||
## 目标
|
||||
@@ -84,11 +84,11 @@
|
||||
```md
|
||||
# 【实施计划】<对应里程碑>
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| 字段 | 值 |
|
||||
| --------- | ---------------------------------------------- |
|
||||
| Milestone | `docs/project-memory/plans/【里程碑】<文件名>` |
|
||||
| Status | ready |
|
||||
| Owner | <人或 Agent> |
|
||||
| Status | ready |
|
||||
| Owner | <人或 Agent> |
|
||||
|
||||
## 修改边界
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
## 验收证据矩阵模板
|
||||
|
||||
```md
|
||||
| 规范条款 | 验证命令/操作 | 结果 | 证据位置 | 未验证原因 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| | | PASS/FAIL | | |
|
||||
| 规范条款 | 验证命令/操作 | 结果 | 证据位置 | 未验证原因 |
|
||||
| -------- | ------------- | --------- | -------- | ---------- |
|
||||
| | | PASS/FAIL | | |
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user