合并 master 并接入 DirectProject 新聊天架构

- 合并 origin/master(304 个提交:DirectProject 聊天容器重构、Project Supervisor 退役、策划附件导入、CI 隔离编译缓存等)。
- 接受 master 对 ProjectSupervisorView / SupervisorChatOnlyView 的退役与预览快捷测试收敛;发布入口改由 DirectProject 聊天头承载。
- DirectProjectChatHeader 新增「发布到游戏广场」入口(无回调不渲染、回合忙态禁用),DirectProjectChatView 透传 onRequestGamePublish。
- App.tsx 继续由工作台壳持有试玩包导出与 GameDistributionPublishPanel,沿用 project.export_package 权限确认队列;check-config 把该命令从 native-only 清单移回 App invoke。
- 后台游戏审核 API / 类型 / 路由测试与 master 新增的 AGC 模板管理按双方保留合并,并修掉拼接造成的接口与用例闭合缺陷。
- 修正 master 自带的 viteProxyConfig 断言:/api/creation-entry 属退役路由,测试改为断言不进入代理。
- 记录合并踩坑:语法结构内部的冲突不能简单按「双方保留」拼接,必须按某一侧骨架重建并跑 tsc 与单文件测试。
- 验证:全量 vitest 393 文件 / 4374 用例通过,root / AGC / admin-web 三端 typecheck,cargo check 与游戏分发 Rust 测试,encoding、doc-index、rustfmt、SpacetimeDB schema guard。
This commit is contained in:
2026-09-22 16:45:29 +08:00
651 changed files with 78860 additions and 60952 deletions
@@ -1,5 +1,166 @@
# 决策记录
## 2026-09-21 合并 origin/masterSupervisor 永久退役,策划 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: 异步生成完成结果无法绑定到 operationIdExternal 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 packDirectProject 通过原生 Skill 或既有审核资源读取入口按需取得,Agent Runtime 的对应执行工具说明嵌入同源参考。指南不改变插件可用性、执行授权或 Runner 回执;只读说明不能证明编辑器已连接。常用示例与执行失败/部分修改、保存、撤销边界在同一参考中维护,避免提示词和文档各存一份代码。
@@ -15,6 +176,26 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
> 当前口径(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`),跨语言一致性只能靠正则解析源码的测试来钉。
@@ -35,7 +216,6 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
- 决策(无写副作用):只在生成本轮 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 仍受同一窗口问题影响。
@@ -49,6 +229,46 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
- 决策:`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 不再被档位收进区间)
- 背景:AGC 客户端此前把 `agentLlm.<agent>.maxRetries` 按运行档位重新收进固定区间——`autonomous-game-build` 档位(自主构建的游戏开发 Agent 及其继承档位的专业子 Agent)被抬到 12~16`standard` 档位(含立项策划入口的策划 Agent 与普通 Agent 对话)被压到最多 3;瞬态分类里的上游 400 还在同一预算上再收窄到 2 次。现场把 `maxRetries` 设成 5 时,游戏开发与策划两条链路都不按设置执行。
@@ -98,6 +318,7 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
- 背景: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
@@ -119,6 +340,7 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
- 标准美术包:客户端显式声明 `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` 不是已登记资源时只报「不属于当前项目已登记资源」,模型会原地重试而不会先登记。
@@ -216,7 +438,7 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
- 决策(槽身份 = 精确动作身份):`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。
@@ -237,6 +459,7 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
- 决策:图片类生成接线改为 `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` 跑完。
@@ -249,6 +472,14 @@ Godot 编辑器操控复用既有 AGC 插件宿主、EditorAdapter、Runner 和
- 影响范围:`.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 lanelane 1 顺序运行 shard 1/4、2/4lane 2 顺序运行 shard 3/4、4/4。每条 lane 只预热一次 AGC 壳 manifestlane 之间仍保持 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` 坐标、按当前资源与拓扑整体重算」;新增一张素材必然改签名,于是既有自动卡全部跟着挪位,用户刚记住的位置就没了。画布上也没有任何显式整理入口(`复位资源视图` 只复位视口)。
@@ -8120,7 +8351,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 编辑器启动和内嵌运行预览不在本切片范围。
@@ -8897,7 +9128,7 @@ 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。
@@ -8936,7 +9167,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。
@@ -8961,6 +9191,7 @@ 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 项目并支持进入。
@@ -8982,6 +9213,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 核,是同一时间窗里更大的单一消耗方。
@@ -9051,3 +9298,53 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 发号顺序固定「先写总号 → 再构建 → 再发渠道清单」,失败不回滚只烧号;统一构建发一次号供 `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` 通过。未复核真实客户端观感。
@@ -4,7 +4,7 @@
## 标准流程
前端测试稳定性验证使用根目录 `npm test`(与 Frontend tests job 相同),保留 Vitest 的 8 worker 上限。涉及异步资源展示时,组件测试必须 mock 所有会触发的网络请求,每次调用创建独立 `Response`,并等待最终 DOM 状态而非仅等待 fetch 被调用。换签 Hook 的测试通过 `vitest.config.ts` 的 include 纳入全量运行;新增测试文件后需确认实际执行名单,命令参数指定文件不会绕过 include 白名单。排查顺序依赖可使用 `npm test -- --sequence.shuffle --sequence.seed=9467`,但不能以重试成功替代失败原因分析。
前端测试稳定性验证使用根目录 `npm test` 执行全量集合,保留 Vitest 的 8 worker 上限。CI 的 `Frontend tests` 使用 `npm run test:ci:frontend`,继承根配置并排除 `apps/ai-game-creator-shell/tests/**`;该目录由 `AI game creator shell web tests` 执行,两个 job 的 Vitest 文件集合互斥且并集等于本地全量。原生壳定向检查与 Repository checks 的 AppSurface 检查仍保留。涉及异步资源展示时,组件测试必须 mock 所有会触发的网络请求,每次调用创建独立 `Response`,并等待最终 DOM 状态而非仅等待 fetch 被调用。换签 Hook 的测试通过 `vitest.config.ts` 的 include 纳入全量运行;新增测试文件后需确认实际执行名单,命令参数指定文件不会绕过 include 白名单。排查顺序依赖可使用 `npm test -- --sequence.shuffle --sequence.seed=9467`,但不能以重试成功替代失败原因分析。
用例隔离必须包括浏览器状态与 mock 实现:修改 `window.history` 后恢复基线路由;`spyOn(window, 'getSelection')` 等 spy 在用例结束后 restore`clearAllMocks` 仅清调用记录,不能恢复被上一个用例替换的返回值。顺序打乱暴露的失败应修复泄漏来源,保留原有业务断言。
@@ -61,6 +61,8 @@ AGC 预览快捷操作的界面测试按独立命令或有状态短流程注册
Rust 分片失败日志保留有界的失败详情,包括 panic 位置、断言和最终通过/失败数量;分片选中数量标为 selected,避免误读为失败数量。修改分片日志时运行 `node --test apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs`,用最小 Rust fixture 验证失败详情和成功摘要。
Linux process-session 的 owner SIGKILL 测试在启动 owner 后立即建立清理 guard,正常结束和 panic 展开都必须终止并回收 owner,再按独立临时项目目录清理残留进程。先完成「杀掉 owner 后子进程自行退出」的原有断言,guard 只在退出测试作用域时兜底,不得提前清理子树使生命周期回归假绿;清理本身不得 panic 或无限等待。
AGC 运行时配置默认值调整时,同步核对 Rust 默认值、分发配置模板、设置弹窗默认草稿和 `runtime-settings.suite.ts` 的恢复默认断言;显式传入旧值的配置读取用例仍验证原值保留,不批量替换测试数据。
AGC 测试构造单 HTML 项目时,必须在初始化之前写入 HTML,避免自动建立 npm 工程;npm 预览和导出测试应提供 dist 产物。已有图片生成 pending/operation 属于持久化恢复合同,修改工具默认参数后仍须验证旧动作恢复不重复提交、不因默认值变化被误判为新意图。
@@ -90,8 +92,10 @@ 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 镜像缓存,再重跑门禁
AGC Rust 两条 lane、crates、smoke、Backend 和桌面壳测试使用镜像内可信 sccache 对象快照;Native shell release step 显式清空双 wrapper,前端/repository checks 不启用。维护者通过 `scripts/build-gitea-rust-cache.sh` 从远端 master 在限额、无宿主挂载的临时容器中按实际 cwd/profile/目标预热全部测试组,仅编译、不执行测试/应用;后端 workspace spacetime-module 保持独立,AGC 的三个 cwd 入口之间清理预热 target,防止 fresh 判断漏产缓存键。最终镜像只追加 sccache、对象和来源元数据,不包含源码或 target。容量上限 4 GiB,不替代宿主旧镜像/归档清理。PR 只写当前容器层、不回传,不开放 Docker API/发布权限;继续禁用 incremental。`ci-rust-cache.sh` 在快照缺失、工具链不符或 wrapper 探测失败时直接编译,并隔离远程缓存配置和 daemon。分片日志记录编译耗时,收尾输出命中统计;两个 lane 的测试和前置检查不同,耗时差不是严格 A/B。线上存在活跃 CI 时不得重启 runner 或切换标签;全组启用前须刷新完整快照并逐组验证,详见开发运维文档
`.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 镜像缓存,再重跑门禁。
@@ -28,15 +28,15 @@ AI 游戏创作 / DirectProject / UI workflow
3. `docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md`
4. `docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`
5. `docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`(历史 V1 方案,仅供追溯)
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/【技术方案】立项策划AgentFast 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 编辑器、宿主壳和当前测试专题文档
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/【技术方案】立项策划AgentFast 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 编辑器、宿主壳和当前测试专题文档
图片画布 / 媒体生成:
+196 -4
View File
@@ -1,5 +1,103 @@
# 踩坑与排障记录
## 客户端图标素材描述不能先包装再截断
- 现象:资源画布填写了具体图标需求,平台实际收到的 `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: 异步生成完成结果无法绑定到 operationIdExternal 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 约 1520 分钟,而每次 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 核验存活再回调。
@@ -28,6 +126,11 @@
- **现象**`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 分实例,卸载保留未提交/失败草稿,成功提交不再复活草稿;旧项目占位不存在时丢弃其保存回调。失败重试保留原请求输入和引用身份,引用失效不能静默过滤;修改已绑定输入须明确另起请求,不伪装成原请求重试。
@@ -64,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`」。
@@ -110,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 编辑器再返回后,资源画布滚轮平移/缩放不再响应;返回前同一手势正常。
@@ -117,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` 已 20012:40:19 才判 ready)。
@@ -125,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,响应体卡住会使后续刷新复用同一挂起请求、选择器持续忙碌。
@@ -135,7 +242,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 个 job4 个分片 + 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 内多进程并行多个片(实测比串行还慢)。
@@ -149,6 +256,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 资源画布不要恢复「无条件重派生」,否则新增一张素材就整张重排
- **现象**:用户生成一张新素材后,画布上既有卡片全部移位,刚摆好的位置失效。
@@ -162,7 +270,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` 分支保护设置。
@@ -4051,6 +4159,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` 限制。
@@ -5333,6 +5449,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 releaseTauri 依次打印 `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 与必需文件清单。回归测试自身也必须跨平台:仓库根目录用 `fileURLToPath(new URL(...))` / `defaultAppRoot()` 解析,不能用 `URL.pathname` 得到 `/C:/...` 后再 `path.resolve`;模拟 Windows 与 POSIX 缓存路径时要分别使用 `path.win32``path.posix`,不要用当前节点的原生分隔符断言另一平台。
## AGC 登录态续期必须同步本地运行时
- 模型目录 HTTP 请求与 DirectProject 的 Rust/app-server 使用同一账号,但凭据分别保存在 WebView 与 Rust / Runner;续期应复用 `requestPlatformSessionRefresh` 完成用户核验及本地会话安装,不能只写 localStorage。
@@ -5729,6 +5853,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`;账号并没有切换,重新登录后短时间内可复现。
@@ -5748,8 +5889,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`)。
@@ -5768,6 +5909,13 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **验证**:修复后同一台机器、同一路径下 35 秒内新增 `Maximum update depth` **0 条**renderer 工作集 **254 MB**(修复前 4.24.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-22 合并 master 时“双方保留”不是通用解法
- **现象**:把分支与 master 的同一批冲突统一按“ours + theirs 依次保留”处理后,`apps/admin-web/src/api/adminApiTypes.ts``api/adminApiClient.ts``app/adminRoutes.test.ts` 都出现语法/结构错误(接口少闭合、函数体被截断、用例少 `});`)。prettier 能通过、vitest 里被转译的纯类型文件也不报错,只有 `tsc`admin-web typecheck)与随后单文件跑测试才暴露。
- **原因**:冲突块可能切断一个语法结构(接口、函数、`test(...)` 调用),而双方各自的块只是在“同一位置添加内容”,拼起来会丢闭合;类型文件在 vitest 里被 esbuild 直接剥离类型,不会校验。
- **处理**:冲突若落在语法结构内部,按“以某一侧为完整骨架、把另一侧的新增内容插到正确位置”重建,而不是简单拼接;重建后必须跑 `tsc`(含 `npm run admin-web:typecheck`)并对改动文件单独跑一次 vitest,别只看全量测试是否绿。
- **验证**:合并后 admin-web 23 个测试文件 212 用例全绿、两端 typecheck 通过。
## 2026-09-20 复用注册端口段时可能连到别的 worktree 的 SpacetimeDB
- **现象**:在 `/data/dsk/Genarrative``npm run dev:api-server` 后,日志显示端口段 `10000-10099 (dsk)`、spacetime `http://127.0.0.1:10002`,但 api-server 反复报 `ws://127.0.0.1:10002/v1/database/xushi-p4wfr/subscribe` 返回 `HTTP error: 404 Not Found`,且始终不响应 `/healthz`
@@ -5803,3 +5951,47 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **原因**:同目录的 `ThemedModal``createPortal` + `focus-trap-react` 渲染模态。焦点陷阱存在时,模态之外的 `click` 不会触达 React 的处理器(实测:临时把 `FocusTrap` 换成普通 `div`、其余不动,同一个被模态遮住的按钮点击立刻恢复生效),所以自动化里“点模态背后的按钮”不会报错,只是静默无效。
- **处理(现行口径)**:① 产品行为保留“导出成功后自动打开面板”(一键发布入口),但受影响的用例必须先用 `findByRole('dialog', { name: '发布到游戏广场' })` 断言面板出现、点「关闭发布面板」再继续后续会话操作;② 给这类“新增自动弹窗”改流程时,先跑一遍相关 `appSurface` 用例,避免只跑新增用例;③ 排查同类“点了没反应”时,先看当前是否有焦点陷阱模态打开,而不是先怀疑事件绑定或状态。
- **关联**`apps/ai-game-creator-shell/src/App.tsx``setPublishPanelOpen(true)`)、`apps/ai-game-creator-shell/src/components/modal/ThemedModal.tsx``apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx``apps/ai-game-creator-shell/tests/appSurface/project-preview/preview-shortcuts/assert-project-tools-and-preview.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 一起带走)。
## 2026-09-22 Rust 对象快照必须对齐 CI 编译目录和 Cargo 环境
- 现象:sccache 快照已包含数百 MiB 对象,但全新 target 的“热缓存”仍然全部 miss,甚至比直接 rustc 更慢。
- 原因:Rust cache key 包含编译 cwdsccache `0.18.0` 还会 hash `CARGO_*` 环境(jobserver、jobs 等少数例外除外)。不同 checkout 根目录、随机的 `CARGO_BUILD_RUSTC_WRAPPER` 路径、预热遗漏 workflow 的 HTTP/retry/color 环境都可能让整套缓存 miss。小型真实 Rust 实验显示仅配置 `SCCACHE_BASEDIRS` 不能消除 cwd 差异。快照存在不等于缓存有效。
- 处理:预热使用已核实的 Gitea 路径 `/workspace/GenarrativeAI/Genarrative`,并与分片运行器一样从 AGC `src-tauri` 启动 Cargo;保存 `workspace.txt`,路径不符时回退直接编译。wrapper 放在容器内固定路径,daemon 状态与 Unix socket 仍使用随机私有目录;预热环境与 workflow 的 Cargo 环境由定向契约测试核对。不要为命中率随意增加 `RUSTFLAGS`、改写源码路径或恢复共享可写 target。
- 验证:相同源码、资源上限和独立干净 target 下分别记录无缓存、冷缓存、热缓存的编译耗时和 hit/miss;只有真实热命中有净收益才切换候选镜像。PR 的写入始终留在 job 容器层,公共快照仍由可信维护流程生成。
- 统计:job 私有 daemon 设置 `SCCACHE_IDLE_TIMEOUT=0`,由 `report` 显式停止;最终测试 bin 的不可缓存编译或测试可能超过一分钟,短 idle timeout 会让 daemon 提前退出,结尾查询启动新 daemon 后误报零次请求。容器销毁仍会回收该 job 的全部进程。
- 磁盘:快照构建拒绝含 `/opt/genarrative-ci/rust-cache` 的基础镜像,始终从无对象缓存的镜像重建;容器内删除旧对象不能释放 Docker 底层。对象缓存容量上限不涵盖宿主旧镜像及导出归档,切换验证后按运维文档人工保留当前版和一个回滚版,同时保护运行中 CI 使用的镜像。
- 扩展:预热所有 Rust 测试组时保留各自 cwd、profile、features 和锁策略;同一临时 target 的 Cargo fresh 不代表不同 cwd 都已生成缓存键,AGC 提示词契约、分片和 smoke 切换入口前清理预热 target。不要把 workspace 与 spacetime-module 合并成一次编译;Native shell release step 清空双 wrapper,避免将测试缓存扩展成发布缓存。当前 sccache 0.18.0 的 READ_ONLY 在 miss 后仍打包产物并产生 cache write error,不适合用来承诺“未命中无开销”。
@@ -51,12 +51,14 @@ 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 工程。
@@ -16,6 +16,11 @@
## 开发中
- 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 与执行校验。
@@ -24,13 +29,15 @@
- 策划 Agent 复用现有模型/推理档控件,宿主不另加模型检查或自动换模型。用户发起执行时采样全局选择,同轮工具循环和自动重试固定使用回合快照;自动恢复复用该快照,旧记录保留已知模型并补齐一次推理档。只持久化模型和档位,不保存连接凭据;GameAgent 保持原逻辑。详见策划 Agent 生产迁移与工作区浏览方案 §4.1。
- 策划附件通过独立文件导入命令保存到 `design_artifacts/references/`,同名另存;首页和聊天框只展示导入结果,不登记游戏资产、不修改清单或 revision、不注入回合附件及路径。Agent 通过现有工作区工具自行发现,只有文件时不合成消息。详见策划 Agent 生产迁移与工作区浏览方案 §4.2。
- AGC 思考与执行入口共用共享单行摘要骨架;Markdown 在展开正文走既有安全渲染,折叠预览使用纯文本。耗时统一复用中文时分秒格式(不足一分钟一位小数,达到分钟后整数秒),格式化与各层计时边界分离。过程行在运行中和完成后的折叠层内保持同一紧凑间距;失败状态按明确终态与非零退出码呈现红色。
- Direct 对话计时区分条目展示时间与生命周期事件时间:整轮用用户发送到明确终态的跨度,工具用各自开始/完成边界;运行时用 100ms 叶子时钟刷新一位小数,终态冻结,旧历史缺边界不推测。不得用整秒时间的大小比较取代 Thread Manager 的事件顺序判定新回合。
- AGC 批量追加素材标签由原生在一次项目写锁与 revision CAS 下合并各项原标签,先校验全批再写 manifest;前端不能循环单素材分类命令,不回传展示层推导的分类或旧标签全集,以免部分写入或覆盖未编辑字段。
- AGC 平台服务固定为 `https://dev.genarrative.world`,会话凭据按 origin 隔离。发布渠道为 `dev/release/自定义名称`Windows/Mac 是系统,OSS 的 `<channel>-win/mac` 仅是延续既有地址的分区。官网通过服务端 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL`(默认 dev)选择渠道,公开同源 `/api/client-downloads` 汇总其各系统首装包与真实版本;未发布隐藏,单系统失败不影响其它下载,不跨渠道补齐。发布先上传 EXE/DMG 再写对应分区清单,不维护会互相覆盖的共享 OSS 索引。主站 Vite 代理复用实际 `runtimeServerTarget`。完整约定见 AGC 客户端更新检查与下载专题。
- 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 继续接入现有画布关闭与滚轮归属判据。
@@ -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 | | |
```