@@ -1,5 +1,166 @@
# 决策记录
## 2026-09-21 合并 origin/master: Supervisor 永久退役,策划 V1V2 退役落到当前两条产品路径
- 背景:`refactor/split-direct-project` ( DirectProject 独立聊天容器)与 `origin/master` ( #355 退役策划 Agent V1/V2)在 2026-09-18 之后各走一条线:本分支删掉 Supervisor 前端链路、把立项策划收敛到 `view/project-development/planning/` , master 删掉整套策划 V1/V2(前端会话 / 审批卡 / 适配器 / 类型与 Rust `planning_*_v2` 命令、`planning_gdd_model.rs` 、`planning_policy_v2.rs` 、`planning_session_v2.rs` )只保留 Design Agent。两边都在删 Supervisor,冲突集中在 `App.tsx` 、聊天视图(`PlanningChatView` 、`DirectProjectTurn` 、`ToolCallGroup` )、Direct composer / 引用输入区、`styles.css` 、Rust direct user item 与 appSurface 用例。
- 决策:按「Supervisor 永久退役」的当前口径合并,保留 DirectProject 独立容器 + Design Agent 两条产品路径,master 的策划 V1/V2 删除整体生效(GDD 审批卡、策划输入卡、`planningLane` 、`planningSessionV2` 、`planningSessionContract` 与对应 Rust 模块、`planning_*_v2` 命令与前端契约全部删除,不保留兼容别名或双跑路径)。两边的 patch 目的若有独立价值,就落到当前结构上而不是恢复 Supervisor:① 思考折叠入口收成一个共享表现 `chat/components/AgentReasoning/AgentReasoning.tsx` (折叠态单行纯文本预览 + 箭头、展开态安全 Markdown),DirectProject 回合与策划回合共用;② 输入盒的模型 / 推理档控件(`ConversationModelSelect` 、`ComposerReasoningEffortSelect` )按 ADR「行为中立的设置表现可复用」接到策划输入盒,写回仍走客户端配置通道。Rust 侧保留本分支的 canonical→wire 投影、无审计回合与 direct user item 严格校验,master 的 `prepare_new_web_project_at` 前置复核并入当前回合入口。
- 原因:Supervisor 结构(含它的 composer 控件、消息标签与运行态)是已退役对象,冲突里出现它只是两侧删除的落点不同;但「策划入口也要能选模型 / 推理档」和「折叠思考显示单行预览」是产品行为,属于 master 那边的独立目的,丢掉就是功能回退。把它们挂到当前两条路径上,既满足退役口径也不让 master 的行为丢失。
- 代价与取舍:master 用例里钉住 `.project-supervisor-composer-controls` 、`项目总控消息` 的断言改为当前类名 `.project-chat-composer-controls` 与「立项策划消息」;本分支 CSS 的 `project-supervisor-* → project-chat-*` 改名对 master 新增规则同样生效。`workspaceProjectKind` 在两条读它的路径(壳层 Cocos 插件门禁、Supervisor 提交路由)都退役后删除,插件可用性改由插件宿主自判。`.env` 的本地私有改动不进入本次合并。
- 验证方式:`apps/ai-game-creator-shell` `npm run typecheck` 与仓库 `npm run typecheck` 、`npx vitest run apps/ai-game-creator-shell/tests` ( 168 个文件,除 5 个 jsdom `localStorage` 环境失败文件与 1 条本分支既有的 `resourceTagStatsRefresh` 失败外全绿,`appSurface.test.ts` 198 passed / 13 skipped / 0 failed)、`npm run check:encoding` 、`npm run check:doc-index` 、`git diff --check` 、改动文件 eslint 0 error。Rust 侧定向 `cargo test --bin genarrative-ai-game-creator-shell -- direct_codex_user_item | skill_pack:: | sessions::developer_project_file_and_memory_writes_advance_project_revision` 全过;整套 Rust 分片在本容器有 55 条环境性失败(`/sbin -> usr/bin` 让 `command.exec` 沙箱的 merged-usr 预检失败,报 `command.exec sandbox unavailable` ),与本次合并无关:这些用例所在模块与 master 逐字相同。
## 2026-09-21 合并后两处回归:预览激活回接「运行」入口,聊天 `@` 清单按需重读
- 背景:合并 `origin/master` 后 CI 报两处回归。① `scripts/check-native-shells.mjs` 报 `AI game creator client preview activation drifted: missing await invoke<LocalPreviewStatus>('activate_local_game_preview'` : Supervisor 前端链路退役时(`0224ef7b9` 删 `pendingCommand` 死链那一步)把 `activate_local_game_preview` 的前端调用方一起带走,而这条守卫与 Rust `preview.rs` 的实现都还在。② `resourceTagStatsRefresh` 用例失败:聊天 `@` 选择器的标签统计与候选读 `useDirectProjectManifest` 自己那份清单,资源画布的标签写入既不发 `game-creator-manifest-invalidated` ( Rust 只在 agent 驱动的写入上发),壳也不再往聊天推清单快照,于是用户改完标签打开 `@` 仍看到旧标签。
- 决策:① 预览激活按 ADR 由「运行」入口承接:`executeRunLocal` 先调 `activate_local_game_preview` ( Rust 侧只核对内存 registry 与项目归属),返回 running 且拿到 loopback 地址就只切客户端运行视图并经 `DirectProjectChatHandle.announce` 说一句 `已切换到客户端运行视图:<url>` ,不再重复 `start_local_game_preview` ; stopped / 不属于本项目 / 权限位要求确认都当「没有可复用预览」,回落到重新启动。同时把 `activate_local_game_preview` 从 `scripts/check-config.mjs` 的 native-only 白名单删除(该门禁要求 App invoke 与白名单互斥)。② 聊天 `@` 选择器打开时按需重读清单:`DirectProjectComposer` 的两处选择器入口(工具条按钮与附件菜单项)都先触发 `onReferencePickerOpen` 再 `openPicker` ,由 `DirectProjectChatView` 用 `useDirectProjectManifest` 的 `refresh` 重读;不新增壳→聊天的清单推送通道,也不改 ADR 里「聊天自己订阅清单」的归属。
- 代价与取舍:编辑器内直接敲 `@` 的候选菜单读同一份清单,本轮没有纳入按需重读(它只列素材名、不显示标签统计)。更根治的做法是让资源写入在 Rust 侧广播 `game-creator-manifest-invalidated` ,本轮不做,先按现成的刷新入口收口。
- 影响范围:`apps/ai-game-creator-shell/src/App.tsx` 、`src/view/project-development/chat/DirectProjectChatView.tsx` 、`.../chat/components/DirectProjectComposer/DirectProjectComposer.tsx` 、`scripts/check-config.mjs` ;新增 `tests/previewActivation.test.tsx` (3 条:活体预览只切视图不重启、stopped 回落重启、权限确认回落重启;第一条在去掉接线后确实失败)。
- 验证方式:`npx vitest run apps/ai-game-creator-shell/tests` (除 5 个 jsdom `localStorage` 环境失败文件外全绿:`previewActivation` 3/3、`resourceTagStatsRefresh` 2/2、`runVersionSwitchEventSubscription` 2/2、`resourceReferenceInput` 34/34、`appSurface.test.ts` 198 passed / 13 skipped)、`node scripts/check-native-shells.mjs --groups=contract` 、`npm run ai-game-creator-shell:typecheck` 、`npm run check:encoding` 、`git diff --check` 、改动文件 prettier / eslint 全过。
## 2026-09-21 渠道进安装身份:不同渠道的 AGC 包体在同一台设备并存
- 背景:渠道此前只决定更新端点(`plugins.updater.endpoints` )与渲染层平台 origin, `productName` / `identifier` 与渠道无关,于是所有渠道共用 `%LOCALAPPDATA%\陶泥儿` 安装目录、同一个卸载项(`HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\陶泥儿` ,另有 `HKCU\Software\genarrative\陶泥儿` )以及同一份 `%APPDATA%\world.genarrative.ai-game-creator` 数据目录。本机 0.1.48 安装实测:主程序二进制里只有 1 处 `agc/dev-win/latest.json` 、0 处 release 端点,说明渠道在产物里只体现为端点。后果是后装的渠道静默顶掉先装的渠道,并接管更新端点、平台服务器与本地登录态/项目数据。
- 决策:渠道同时决定**安装身份**。默认渠道 `dev` 保持基线 `productName = 陶泥儿` 、`identifier = world.genarrative.ai-game-creator` (既有安装目录、卸载项与升级链不断);其它渠道派生 `陶泥儿 <渠道显示名>` ( `release` → `陶泥儿 Release` )与 `world.genarrative.ai-game-creator.<渠道>` 。身份与更新端点必须在同一个构建期 `--config` 里注入,禁止分别回读默认值。窗口标题、macOS 产物名(`.app` / updater 归档 / DMG 卷名)、首装包选择与 Windows 提权 ACL 的 managed 识别范围同批跟随该身份。
- 边界:不做本地数据迁移或共享——切渠道等于换一个客户端;`release` 与自定义渠道首次以新身份安装,不接管、不迁移既有 `dev` 安装与本地项目,由用户自行决定是否卸载其一。
- 影响范围:新增 `apps/ai-game-creator-shell/scripts/channel-identity.mjs` ; `build-release.mjs` 、`build-macos-ci.mjs` 、`check-config.mjs` 、`agent-swarm-test-chat.mjs` 、`src-tauri/src/{main.rs,windows.rs,config.rs}` 与对应测试;主规范 `docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md` 。
- 验证方式:`node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs release-oss.test.mjs prepare-macos-codex.test.mjs cargo-features.test.mjs` (64/64,新增渠道身份与渠道 DMG 首装选择用例)、`node apps/ai-game-creator-shell/scripts/check-config.mjs` (基线等于默认渠道身份、非默认渠道身份隔离)、`cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell -- config::private_path_elevation_policy_tests` ( 12/12,含基线/`<基线>.release` /`<基线>.beta-2` 与相似前缀反向断言)、`AGC_UPDATE_CHANNEL=release npm --prefix apps/ai-game-creator-shell run build -- --no-bundle --debug` (产物字符串实测 `陶泥儿 Release` × 1、`agc/release-win/latest.json` × 1、`world.genarrative.ai-game-creator.release` × 1、`agc/dev-win/latest.json` × 0)。真机双渠道安装、并存与各自更新尚未执行,按未验证项记录。
## 2026-09-21 项目快照按部署渠道分区,后台按渠道查看并按素材查询口径展示用户
- 背景:AGC 项目快照此前统一写在 `agc/project-snapshots/v1/{user}/{project}/` ,而开发与正式两套部署共用同一个 bucket(都默认 `agc-dev` )。结果是渠道混在一层前缀里:正式后台会列出开发渠道上传的项目,列表上也看不出项目属于哪个渠道;同时“项目工程”列表只有裸用户 ID,且用“加载更多”逐段追加,翻页与定位都困难。
- 决策(存储):对象键升级为 `agc/project-snapshots/v2/{channel}/{user}/{project}/` 。渠道是**部署渠道**,由服务端 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_CHANNEL` 决定(缺省沿用 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL` ,当前缺省 `dev` ),客户端不上报渠道、也不需要发新版;渠道名必须是小写字母开头的 `[a-z0-9-]{1,32}` ,非法值在上传与后台查询处失败关闭(`503` /`400` ),不悄悄回落。正式部署必须在 api-server 环境里显式写 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_CHANNEL=release` 。
- 决策(后台):新增 `GET /admin/api/project-snapshots/channels` 返回本部署渠道与远端已存在渠道的并集;列表与下载接口都接受 `channel` (缺省本部署渠道),游标里带上渠道并在解码时校验一致,跨渠道复用游标一律 400。页面顶部提供“渠道”选择框,切换渠道回到第 1 页。
- 决策(列表形态):“用户 ID”列改为与“素材查询”同口径的“用户”列——昵称 + 陶泥号 + 用户详情入口,昵称/陶泥号由 api-server 用 `resolve_work_author_by_user_id` 解析(账号不可读时回退占位作者),原始 `userId` 不再直接铺在列里;“加载更多”改为游标分页(每页 20/50/100 + 上一页/下一页 + 当前页),前端按页记录游标链,换令牌或换每页条数从第 1 页重来;翻页失败保留当前页。
- 原因:渠道属于“这套部署服务哪个客户端渠道”,客户端正式包按构建渠道连接不同平台服务(release → 正式站,dev → 开发站),所以服务端是唯一可靠的渠道来源;把渠道放进对象键第一层,既不需要迁移历史对象就能让两套部署互不可见,也让后台的渠道维度天然对齐存储布局。
- 影响范围:`server-rs/crates/platform-oss/src/{lib.rs,project_snapshots.rs,template_library.rs,examples/agc_project_snapshot_live_smoke.rs}` 、`server-rs/crates/api-server/src/{config.rs,project_snapshots.rs,admin_project_snapshots.rs,modules/admin.rs}` 、`server-rs/crates/shared-contracts/src/admin.rs` 、`apps/admin-web/src/{api/adminApiClient.ts,api/adminApiTypes.ts,pages/AdminProjectSnapshotsPage.tsx,pages/AdminProjectSnapshotsPage.test.tsx,styles/admin.css}` 、`deploy/env/api-server.env.example` 与本仓运维/技术文档。
- 未迁移的历史对象:`agc/project-snapshots/v1/` 下现存对象(只读核对过的两份历史清单)保留在 OSS,但不再写入、不再进入后台列表;需要取回时按旧前缀在 OSS 侧直接读取,确实要在后台看到时再单独开一个只读兼容视图。
- 验证方式:`cargo test -p platform-oss snapshot` 7 passed(渠道校验、键布局、v2 根渠道枚举、v1 历史键仍必须私有);`cargo test -p api-server project_snapshot` 18 passed/1 ignored(渠道失败关闭、游标跨渠道拒绝、用户昵称/陶泥号解析、归档与配额回归);`cargo test -p api-server protected_route_matrix` 、`route_contract` 通过(新路由纳入后台鉴权矩阵);`npm run admin-web:typecheck` 与 `npx vitest run apps/admin-web/src` 202 passed。
## 2026-09-21 Godot 模板入库与按模板建项的 Godot 分流
- 背景:模板库此前只有网页(html)、Cocos 与 Unity 占位,`runtime` 白名单早就接受 `godot` ,但既没有 Godot 模板,也没有按模板建项的 Godot 分流——Godot 模板即使上传,建项也会落到 Web 分支,写出 `game/index.html` 占位入口并让 `godotProjectRoot` 为空。
- 决策(模板内容):新增四个仓库内手写的 Godot 4.7 模板 `godot-empty-2d` 、`godot-empty-3d` 、`godot-hello-world` 、`godot-platformer-2d` , `entry` 统一为 `project.godot` 。模板只用内置 `ui_*` 输入动作、GL Compatibility 渲染,并用 `Polygon2D` / `BoxMesh` 搭可视骨架,不引入外部贴图或音频二进制;不打包 `.godot/` 缓存与导出产物。
- 决策(建项分流):`create_project_from_installed_template_at` 在复制模板后按工程文件分流——Cocos 更新自身身份后走 Cocos 导入,Godot 先改写 `project.godot` 里 `[application]` 段的 `config/name` (只改这一行,其余字节逐字保留)再走既有 Godot 导入,写入 `godotProjectRoot: "."` ,其余继续走 `init_local_game_project_at` 。分流靠工程文件识别,不新增只读 `entry` 或 `runtime` 字段的契约。
- 原因:Godot 工程身份是 `project.godot` 所在目录,与 Cocos 的 `package.json` 身份同一类问题;复用既有导入流程能同时拿到相对根记录、`.agent` 初始化与「不生成 Web 占位入口」这三条既有保证,并且与 Cocos 分支保持对称。
- 影响范围:`apps/ai-game-creator-shell/template-library/v1/godot-*` (新增模板源)、`apps/ai-game-creator-shell/src-tauri/src/template_library.rs` 、`.../src/project/manifest.rs` ( `apply_godot_project_display_name` )、`.../src/project/manifest/import_tests.rs` 、`docs/【模板规范】AGC模板包组织指南-2026-09-21.md` 、`docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md` 。
- 验证方式:本机 Godot `4.7.2.stable` 对四个模板逐一做「主场景实例化 + 3 帧 + GDScript `--check-only` 」,平台跳跃模板另做真实物理试玩(落地 y=627.99、1 秒右移 320px、跳上平台 y=515.93、三枚金币全收集);Rust 定向回归 `import_tests::rewrites_only_the_godot_display_name_line` 、`import_tests::keeps_a_godot_project_without_a_display_name_line_untouched` 、`template_library::tests::godot_template_creates_native_project_with_relative_root_and_display_name` 与既有 Cocos/Web 建项回归;发布走 `--only godot-*` 定向合并。
## 2026-09-21 Godot 工作区发现放宽与内置插件行去掉手动启动
- 背景:2026-08-10 “发现与歧义决策”把「一层多命中」和「`project.godot` 必须是普通文件」两条定为失败关闭。这两种布局都会让整个工作区被判成「没有 Godot 工程」,而 `PluginHost::list` 只按发现结果过滤,结果是 Godot 内置插件在设置→扩展 里直接消失,用户看不到任何可诊断入口。
- 决策(发现):根目录命中仍然优先;根未命中时一层直接子目录多命中改为按目录名排序取第一个,`godotProjectRoot` 记录该相对目录名,结果确定且可复现。`project.godot` 允许是符号链接 / Windows reparse point / 硬链接,判据改为「链接目标解析后是文件」,目录与悬空链接仍不算命中。工作区根本身是链接、候选子目录是链接、二层及更深不递归这三条边界不变。
- 决策(界面):设置→扩展 的内置插件行去掉手动「启动 / 停止」按钮。启动本来就由项目切换时的 `startAvailableAgcEditorPlugins` 自动完成,手动按钮只是第二个可绕过入口;停止改走该行的启用/禁用开关,`set_agc_plugin_enabled(false)` 会停止插件并断开编辑器连接。导入扩展行的启动按钮保留,因为导入扩展没有自动启动路径。
- 原因:Godot 插件列表是按项目过滤的,发现失败等于功能静默消失;把不确定性收敛成一个确定的排序选择,比让用户面对空列表更好。链接放宽只作用于只读的工程标记文件,受管描述文件、运行缓存与项目目录的链接拒绝规则不动。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs` 、`.../src/commands.rs` (错误文案)、`.../src/project/manifest/import_tests.rs` 、`.../src/tests/project.rs` 、`apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx` 、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md` §3.8、`docs/technical/【技术方案】AGC Godot编辑器插件接入-2026-09-20.md` 、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 。
- 验证方式:`cargo test --locked -p genarrative-ai-game-creator-shell --bin genarrative-ai-game-creator-shell -- project::manifest::import_tests::` 与 `-- tests::project::` ;前端 `agc:typecheck` 与 `runtime-settings` / `pluginHost` 套件。
## 2026-09-21 模板正文目录门禁:CLI 打包与后台上传同一份段名单
- 背景:模板包组织指南把 `.agent/` 、`.git/` 、`node_modules/` 、根目录 `dist/` 等列为「不要放进 ZIP」,但两条发布路径此前只校验路径安全与 `entry` 是否存在,放进去的东西会跟着建到用户项目里(模板自带 `.agent/` 会让新项目继承一个陌生身份)。这条约定只靠作者自觉。
- 决策:升级成机器门禁,CLI( `readProjectFiles` )与后台上传(`validate_import_archive` )用同一份段名单与同一句文案——任意层级拒绝 `.agent` / `.git` / `.svn` / `node_modules` ,根目录拒绝 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode` 。同名目录段只在根目录受限:正文内 `game/dist/**` 是模板自身内容;`.gitignore` 不等于 `.git` , Cocos 模板的 `.creator` / `.gitignore` 不受影响。
- 原因:两类目录性质不同。`.agent/` 、`.git/` 、`.svn/` 、`node_modules/` 放哪一层都是错的,进包即污染用户项目;而 `dist` / `library` / `temp` 这类只说明「作者把编辑器缓存或构建产物当成了模板内容」,出现在根目录才是信号,全面禁止会误伤工程内的正常同名目录。
- 影响范围:`scripts/agc-template-library-publish.mjs` 及其测试、`server-rs/crates/api-server/src/admin_templates.rs` 、`docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md` 、`docs/【模板规范】AGC模板包组织指南-2026-09-21.md` 、`docs/project-memory/plans/【里程碑】后台模板上传-2026-09-21.md` 。
- 验证方式:`node --test scripts/agc-template-library-publish.test.mjs` (28 项,新增 1 项:13 条拒绝用例 + `game/dist/**` 与 `game/.gitignore` 放行断言);`cargo test --locked -p api-server admin_templates` ( 8 项,新增 `template_import_archive_rejects_identity_and_build_directories` );仓库现有 9 个模板源无一条命中,本地重打包的 ZIP 摘要与线上清单 `zipSha256` 逐条一致;`cargo fmt --check` 、`check:encoding` 、`check:doc-index` 、`git diff --check` 通过。
## 2026-09-21 后台模板上传:成品 ZIP + 每模板封面,批量全有或全无
- 背景:模板发布此前只有本地 CLI(源目录 + 确定性打包 + `--only` ),后台上传需要一条不依赖本地仓库的通道,并支持一次提交多个模板。
- 决策(输入形态):后台上传**成品 ZIP**(库内字节原样发布,不重新打包),每个模板必须同时给一张 PNG/JPEG/WebP 封面(与 CLI 源布局 `v1/<id>/cover.*` 一致),`id/title/templateVersion/runtime/entry` 由页面逐行确认;简介、标签与引擎上传后用编辑接口补齐。服务端按字节嗅探封面格式,不信任 multipart 声明的 content-type。
- 决策(批量语义):一批 = 一把发布锁 + 一次清单提交,**全有或全无**;任一模板的 manifest/归档/封面不合法都在写入前整批拒绝并逐项给出原因。单批上限 20 个模板、单包 64 MiB、单封面 5 MiB、请求体 200 MiB。
- 决策(校验与安全):归档只校验不落盘——合法 zip、无符号链接、无绝对路径 / `..` / 盘符条目、必须包含声明的 `entry` 、条目数 ≤ 4096 且解压后 ≤ 512 MiB。
- 决策(版本与保留):新 ID 默认上架;已存在 ID 就地更新并保留 `enabled` 分组、其它条目与未知扩展字段;同一 ID 同一 `templateVersion` 的 ZIP 字节不同时拒绝并要求递增版本,字节一致时按内容复用(`reusedObjects` )。
- 决策(存储契约):`platform-oss` 的 `put_immutable` 为 `application/zip` 单独放宽到 64 MiB(此前只有 json/图片的 5 MiB),图片与元数据上限不变。
- 影响范围:`server-rs/crates/{shared-contracts/api-server/module-assets/platform-oss}` 、`apps/admin-web/src/{api,pages,styles}` 、模板库技术方案与 `docs/project-memory/plans/【里程碑】后台模板上传-2026-09-21.md` 。
- 验证方式:`cargo test` ( module-assets 11、platform-oss 12、api-server 定向 4)、`npm run admin-web:typecheck` 、后台页面与模型 40 项 vitest、`check:encoding` 、`check:doc-index` 、`git diff --check` ;真实 dev bucket 写入验证需用户显式确认。
## 2026-09-21 模板库线上产物对齐仓库源:递增版本重发 + 说明文档纳入发布
- 背景:`agc-dev` 上的 `templates/` 产物停在 2026-09-17 发布的那一版,仓库源在那之后改过(`5e4ff54a9` 删掉模板内嵌 `package-lock.json` 、`game.js` / `main.js` 等内容调整),9 个模板里 7 个的 ZIP 与线上不一致;dry-run 被「同一 `templateVersion` 的 ZIP 不得变」门禁拒绝,发布器因此无法把仓库状态发上去。
- 决策:按门禁要求为内容已变的 7 个模板递增 `templateVersion` 到 `0.1.1` ( `blank-2d-canvas` 、`blank-web` 内容未变,保持 `0.1.0` ),并完成一次真实发布;对象使用内容寻址键 `v1/<id>/sha256/<摘要>/…` , `index.json` 与说明文档在发布锁内最后提交,历史对象不删除。
- 决策(说明文档):`templates/README.md` 不再是手工副本——它由发布脚本从仓库源 `apps/ai-game-creator-shell/template-library/README.md` (≤64 KiB)覆盖写入并回读校验,写在清单之前;改契约只改仓库源。
- 决策(CLI 行为):发布器遇到门禁拒绝时用 `process.exitCode = 1` 正常退出,不再 `process.exit(1)` 让 Node 在 fetch 句柄未关闭时抛 libuv 断言(此前现场只剩 `Assertion failed` ,看不到拒绝原因)。
- 影响范围:`apps/ai-game-creator-shell/template-library/v1/*/meta.json` 、`apps/ai-game-creator-shell/template-library/README.md` 、`scripts/agc-template-library-publish.mjs` 与其测试、本文件与模板库技术方案。
- 验证方式:`node --test scripts/agc-template-library-publish.test.mjs` (27 项,含新增「说明文档随发布覆盖写且写在清单之前」「源目录没有 README 时不写线上文档」);真实发布 28 个对象回读通过;匿名读取线上清单(9 个模板、7 个 `0.1.1` 、全部内容寻址键)与 `templates/README.md` (与仓库源逐字节一致);AGC 壳 3 项线上用例(读清单、下载安装、下载并原生建项 Cocos)重跑通过。
## 2026-09-21 macOS 发布改为只出 arm64 单架构(Intel 暂不支持)
- 背景:Mac 发布管线按 `universal-apple-darwin` 构建,但随包 Node 便携运行时只有**单架构官方发行版**(`stage-node-runtime.mjs` 从 `process.execPath` 取材),于是 macOS Job #7 ~#13 连续失败在「Node 运行时不支持发布目标:universal-apple-darwin」。期间出现过一版「按宿主架构放行」的过渡实现,它能骗过通用包自检(`check-macos-bundle.mjs` 按 `process.arch` 校验),但 Intel 上那份 arm64 侧车不可执行,并且已发布的 dev-mac 0.1.86 就带着这个缺陷。
- 决策:macOS 固定只构建 `aarch64-apple-darwin` ,渠道清单只登记 `darwin-aarch64` (不再登记 `darwin-x86_64` ,避免把 arm64 产物发给 Intel 客户端);`targetRuntime('universal-apple-darwin')` 保持失败关闭,入口 `build-macos-ci.mjs` 只跑 arm64 隔离 smoke,首装包命名 `<产品名>_<版本>_aarch64.dmg` 。
- 原因:要让 Intel 真正可用,必须让发布包按架构各带一份**同版本**运行时(另下载另一架构官方发行版)+ 通用包自检按架构分别校验,这是一条独立且更大的改动;在 DDL 前用「只带宿主架构」糊过去等于把坏包发给 Intel 用户,比暂不支持更糟。单架构同时把构建时间与产物体积减半。
- 验证:`node --test apps/ai-game-creator-shell/scripts/*.test.mjs` 92/92(含 `targetRuntime('universal-apple-darwin')` 必须抛错、入口固定 arm64 目标与 `_aarch64.dmg` 后缀的守卫用例);`npm run check:production-ops` 、`check:encoding` 、`check:doc-index` 、prettier、eslint、`git diff --check` 通过;真实端到端由 Jenkins Mac Job 验证(清单只含 `darwin-aarch64` 、DMG 与更新包唯一匹配)。
- 影响范围:`apps/ai-game-creator-shell/scripts/{build-macos-ci.mjs,stage-node-runtime.mjs,prepare-macos-codex.test.mjs}` 、`jenkins/Jenkinsfile.ai-game-creator-shell-macos-build` 、本仓三份技术/运维文档与共享记忆。未动 Windows 渠道、未动 Rust 侧运行时解析(单架构仍是扁平 `game-runtime/node/` )。
- 恢复 Intel 的路径:先在 staging 支持按架构各带一份同版本运行时并让通用包自检按架构校验,再切回 universal 目标、把 `darwin-x86_64` 键登记回去并补 Intel 真机验收。
- 已知未覆盖:Intel Mac 用户的更新体验(清单缺 `darwin-x86_64` 键,客户端会「无可用更新」,未实测其 UI 文案);Mac 包里仍并列携带两套 Codex 原生依赖(只运行 arm64 切片,可按需瘦身)。
## 2026-09-21 图集切片上限:客户端结果门从 64 对齐到平台契约的 256
- 背景:现场(项目 `gameagent-6e53c9e8` , 2026-09-21 07:54)「AI 生成图标素材」失败:`platform-generation-result-unknown: 异步生成完成结果无法绑定到 operationId: External Editor 旧同步结果的图集切片超过 64 个` 。任务账本(`.agent/runtime/asset-generation-tasks/tasks.json` )显示它跑了 99 秒、`assetId` 为空、没有落任何素材;对应的持久化请求(`canvas-generation-requests/manual-canvas-asset-generate/slot-560175669f….json` )是 `sliceMode: connected-components` + `sliceCount: null` (自动切分)。也就是**平台已经生成并切完图了,是客户端在绑定结果这一步把整条结果判失败**,付费产物被丢弃。
- 根因:同一条链路里存在两个不同的切片上限。平台切分是 256(`server-rs/crates/api-server/src/editor_project_icon.rs` 的 `EDITOR_ICON_SPRITESHEET_MAX_SLICES` ),Agent 工具 schema 的 `sliceCount` 是 1..256( `agent_native_tools.rs` / `direct_tool_bridge.rs` ),持久化产物批次也是 1..256,公开契约(`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md` )写的更是「最多 256 个输出」;唯独客户端**两处结果绑定门**还是 `> 64` 就拒(`agent/generation/external_generation_state.rs` 与 `agent/generation/canvas_generation.rs` ,由 `602723ea0` 于 2026-08-03 引入)。自动切分落在这个窗口里(65~256 片)时,客户端比平台更严,于是把合法产出整条丢掉。
- 决策:两处门统一到 `PLATFORM_ART_SPRITESHEET_MAX_SLICES = 256` ,并抽成同一条判据 `platform_art_spritesheet_slice_count_exceeds_limit` 与同一句拒绝文案 `platform_art_spritesheet_slice_limit_error` (数字由常量插值,不再手写)。注释里点名三处同值权威(平台切分常量、工具 schema `sliceCount` 、公开契约),客户端不得比平台更严。
- 原因:客户端这两处门的作用是「防止把不可信/超预算的结果写进本地」,不是产品上限;真正的产品上限属于平台切分契约。两处各写一个字面量就会再次漂移,所以值只留一份、判据只留一条。
- 验证:新增 `canvas_generation_tests::spritesheet_slice_limit_matches_platform_and_tool_contract` (上限值、边界判据与文案)与 `external_generation_state_tests::legacy_result_accepts_slice_counts_up_to_platform_limit_and_rejects_beyond` (64/65/256 片必须能持久化且切片一条不少、257 片必须按同一句文案拒绝),两条都用**变异验证**确认过:把常量改回 64,回归用例立刻变红。定向执行 `cargo test -- spritesheet` ( 22 passed)、`cargo test -- external_generation_state_tests::` ( 10 passed)与两条新用例;`cargo fmt --check` 干净。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/generation/{canvas_generation.rs,external_generation_state.rs}` (+用例)。未动平台切分、OpenAPI、数据库或前端。
- 已知未覆盖:真实客户端复验(重新生成一次图标素材)与远程 CI 未跑;256 片时的累计下载/像素预算未实测——平台自己的总像素上限是 2048×2048,客户端预算是 4096²,按切片是整图互不重叠子矩形推算不会先撞预算,且真撞了也只是给出明确错误而不是损坏数据。
## 2026-09-21 本批自查(PR #441 ):三处修正
- 背景:推 PR 后按「局部到整体」自查这一批(三需求 + 验收修正),查出三条:①拖动到对话的落点在 `pointermove` 上每帧都 `setState` 一个新对象;②替换面板相对 **stage ** 写死 `top: 8.5rem` (与刚修的任务开关同一类隐患:工具条换行会压上去),且它和「生成任务」面板抢画布右上角同一个位置;③替换面板不显示「在替换哪张源素材」,而非模态化之后那点线索(画布上的源素材光环)会被一次空白点击清掉。
- 决策①:落点状态改为**逐值比较**(`sameResourceCardReferenceDrop` :条数 + 对话栏矩形四值),只有真的变了才落 state——指针在对话栏上移动不再每帧重渲染整个工作台。
- 决策②:面板从 stage 顶层移进**画布容器** `.game-resource-book-manager` (它本身就是 `position: relative` ,与左下角工具栏、右下角 Dock 同一套锚定口径),`top: 3.2rem` 排在任务开关下方;并在打开替换会话时**收起「生成任务」面板**——画布右上角同一时刻只留一块浮层(任务照旧在账本里推进,收起只影响这个视图)。
- 决策③:面板上方补一行「替换源素材:<显示名>」(投影显示名优先、manifest 资产名回落),源身份不再只靠画布光环。
- 验证:`resourceVersionReplacement.test.tsx` 20 passed(新增「面板锚在画布容器里、写明源素材、并与任务面板互斥」)、`resourceCardReferenceDropModel.test.ts` 6 passed(新增逐值比较)、`resourceCanvasChatReferenceDrop.test.tsx` 4 passed; `tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
- 影响范围:`apps/ai-game-creator-shell/src/view/project-development/{index.tsx,resourceCardReferenceDropModel.ts}` 、`.../features/resource-canvas/resourceCanvasChrome.css` 、用例 3 个文件、验收用例 C9 注脚。未动 Rust / SpacetimeDB / 共享组件行为(只新增了宿主的源素材行)。
- 已知未覆盖:仍无真机目视;替换会话与任务面板的互斥是新行为,客户端需要复看一次(若产品希望两者能同时看,把这一句收紧即可)。
## 2026-09-21 验收现场修正:任务开关压在工具条上、开关与面板并排
- 背景:客户端验收截图两条:①画布右上角那枚「生成任务 · N」开关与工具条(打开项目目录 / 资源面板 / 整理画布 / 管理未完成编辑 / 依赖 · 类型)**重叠**;②「生成任务」面板展开时,开关与面板**并排**摆着,产品口径是「这俩不应该并排,出来详情以后入口就应该隐藏」。
- 决策(重叠):锚点不再用 `position: absolute` + 写死的 `top: 3.5rem` (工具条是 stage 第一行,高度随按钮换行变化,写死的偏移迟早压上去),改成 **stage 网格里与工作面同一个单元格的另一个条目 ** : `grid-row` 按 `placement` 分档(资源画布 3 / UI 编辑器 2 / 运行表现层 `2 / -1` ) + `grid-column: 1` + `justify-self: end` + `align-self: start` + `margin: 0.85rem` (运行那档上边距 3.5rem 让开右上角版本入口)。为此把同格的三处容器也显式钉住第 1 列(`.game-workbench-stage > .game-resource-manager` 、`.…[data-resource-view-state='resources.ui-editor'] > .game-workbench-editor-shell` 、`.game-run-surface` )——锚点是显式定位条目,画布若走自动列放置会被挤进隐式第二列、画布直接压窄一半。
- 决策(并排):开关只在**完全收起**时渲染(`!open && phase === 'idle'` ,收起动画期间也不画,否则那 160ms 又会同框);展开态画布右上角只有面板,收起走面板头部那枚 × 或点画布外部。原来「点画布外部自动收起」的判据里对开合按钮的排除保留(收起态仍靠它开合)。
- 原因:验收口径优先于「照抄美术画布」——美术画布把开关常驻在面板旁边,但产品要的是「入口与详情不同时出现」。重叠那条的根因是**猜了一个绝对偏移量**:工具条高度不是常量,锚点必须由布局自己推导。
- 验证:`resourceCanvasAssetGenerationTasksPanel.test.tsx` 12 passed(新增「展开时开关让位:同一时刻只有面板那枚 ×」;计数用例改为收起态查开关、展开态查面板头部)、`resourceCanvasAssetGenerationTasksSidebarStyle.test.ts` 11 passed(锚点改成网格条目坐标:`grid-row` / `grid-column` / `justify-self` / `align-self` / `margin` 与运行档上边距;窄屏改判 `justify-self: stretch` )、`resourceCanvasGenerationTasksSidebarDismiss.test.tsx` 5 passed(收起改走面板 ×、收起后开关回来能再打开、锚点仍在 stage 里)、`resourceCanvasAssetGenerationBackgroundClose.test.tsx` 5 passed; `appSurface.test.ts` 538 passed / 17 skipped / 0 failed。
- 影响范围:`apps/ai-game-creator-shell/src/features/resource-canvas/{ResourceCanvasAssetGenerationTasksPanelView.tsx,resourceCanvasAssetGenerationTasksSidebar.css}` 、`apps/ai-game-creator-shell/src/styles.css` 、三个同场景用例文件、PRD §3.10 与更新时间。
- 已知未覆盖:仍无真机目视(网格落点在 Tauri 里的实际观感、工具条换行时锚点是否仍贴画布顶边需要现场复核)。
## 2026-09-21 AGC 替换面板改为非模态浮层,支持在画布上点选目标
- 背景:`docs/project-memory/todos/【待办】画布验收后续修复-2026-09-18.md` 的「后续需求」要求「在替换面板中支持画布点选目标」。2026-09-13 那版做的是「候选弹窗 footer 一个『点选替换』按钮 → 关掉弹窗 → 进画布点选态 → 点中即提交」:面板与画布互斥(弹窗外壳是全屏遮罩,留着它画布上的卡点不到),用户要么在面板里筛、要么离开面板。
- 决策:候选面板改成**非模态浮层**(共享组件新增 opt-in `nonModal` :不铺遮罩、不做焦点陷阱、面板自身限高 + 内部滚动、Esc 在 `document` 阶段截断后取消;网页端美术画布不传,弹窗行为逐字不变)。AGC 把它锚在画布**右上角、任务开关下方**(`.game-resource-replacement-panel` → `top: 8.5rem; right: 0.85rem` ,壳样式在共享样式表里,宿主只负责锚定)。**面板开着时画布照常可点**:点中合法候选即落成面板里的当前选择(面板里随之 `aria-selected` ),写入仍然只由面板「确认」发起;点中非法目标在面板里说明原因、零写入。旧的「点选替换」入口、画布提示条 `.game-resource-canvas-pick-hint` 与 `resourceReplacementPickMode` 随之退役(同一个功能不留两条 UI 路径)。
- 决策(同步口径):`selectedAssetIds` 的**数组引用不能当同步信号**(调用方每次渲染都会重建它,放进依赖会清掉用户在面板里的选择——组件里原本就为此写过一段注释),所以新增显式序号 `initialSelectionRevision` :只有宿主真的换了目标才重同步选择,搜索词与分类筛选保持原样。`confirmResourceVersionReplacement` 另外拿 `resourceReplacementPick` 兜底:点完画布当帧就按确认时不该报「请选择一个替换素材」。
- 原因:面板与画布是同一屏的两半——目标本来就在画布上,「先把面板关掉再点」是弹窗外壳带来的妥协,不是产品意图。换成非模态之后,合法性判据仍是同一条 `resolveResourceReplacementPick` 、写入仍是同一个 `confirm` 函数(载荷逐字一致),所以这是**外壳**的改动,不是第二套替换实现。
- 已知取舍(相对旧口径的行为变化,均已随测试钉住):面板开着时空白处点击不再被吞——画布恢复正常的清焦点 / 框选 / 平移语义(源身份在打开面板时就已冻结,替换不依赖画布选中);「点中即提交」不再存在,提交一律由「确认」触发;关闭面板后卡片单击语义原样(一次性抑制照旧收尾)。
- 验证:`resourceVersionReplacement.test.tsx` 19 passed(4 条旧点选用例改写为:非模态判据 + 点画布候选落进面板且零写入、面板确认才写入且载荷逐字一致+血缘、四类非法目标面板内报因零写入、Esc 只收面板不清选中;新增「画布点选不清掉面板搜索/分类」)、`projectAssetPickerDialogShellStyle.test.tsx` 7 passed(新增 nonModal 形态与浮层壳样式声明,含关掉即卸载)、`src/components/image-editor/ImageCanvasEditorView.test.tsx` 64 passed、`ImageCanvasEditorGenerationIntegration.test.tsx` 44 passed(网页端弹窗行为未变);连同 `appSurface.test.ts` 、`projectResourceLiveIntegration.test.tsx` 共 6 个文件 694 passed / 17 skipped / 0 failed; `tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
- 影响范围:`src/components/image-editor/ImageCanvasProjectAssetPickerDialog.tsx` 、`packages/shared/src/components/styles.css` 、`apps/ai-game-creator-shell/src/view/project-development/index.tsx` 、`.../features/resource-canvas/resourceCanvasChrome.css` 、`apps/ai-game-creator-shell/tests/{resourceVersionReplacement.test.tsx,projectAssetPickerDialogShellStyle.test.tsx}` 、PRD §5.3 / §7.8 第 8 条、`docs/technical/【测试用例】AGC资源工作台V3端到端验收-2026-09-11.md` 的 S15a。未动 Rust、SpacetimeDB、external v1。
- 已知未覆盖:真机观感(面板在画布右上角的落点、与任务开关同时打开时的间距)未在 Tauri 目视确认;面板里不显示「本次替换的是哪张源素材」,源身份只靠画布上那张卡的选中光环表达——点空白清掉选中后就只剩面板自己的候选列表。
## 2026-09-21 AGC 资源卡拖到对话实现批量 @ 引用
- 背景:`docs/project-memory/todos/【待办】画布验收后续修复-2026-09-18.md` 的「后续需求」要求「聊天拖拽批量引用」。改前只有两条入口:聊天输入框里输入 `@` 或点 `@` 按钮开素材选择面板,以及资源卡选中工具条上那枚「引用」按钮——都要先把素材找出来再点,多选批量引用没有一次成型的路径。
- 决策:资源卡**按住拖到右侧 Agent 对话栏、松手即批量引用**。落点判据是「指针是否在对话栏矩形内」:pointermove 在对话栏上时语义从排版切成引用(卡片不再跟着指针走,改铺一层虚线落点浮层 + 「松手即可 @ 引用 N 项素材」),拖回画布内松手仍然是原来的排版语义(照旧写手动坐标)。批量范围与拖动位移**同一集合**(`drag.moves` ):多选后拖任意一张 = 整批引用,拖未选中的卡 = 只引用它自己。
- 决策(引用构造与派发):引用构造收敛成纯函数 `resourceCardReferenceDropReferences` ,口径与工具条「引用」按钮**逐字一致**——只认已登记 manifest 的素材(`manifestAssetId` )、`source: 'resource-card'` 、kind / 分类 / 标签取自资源投影,于是同一素材从两处进来是同一枚引用(去重键同样一致)。派发走新增的批量事件 `RESOURCE_REFERENCE_INSERT_MANY_EVENT` ( `dispatchResourceReferenceInsertMany` ):N 条引用一次事务插进草稿、只聚焦一次,不逐条重建草稿。一条也构造不出来时(选中的素材都未登记)不静默:提示条说明原因。
- 原因:拖动本来就在指针捕获下走,指针跑到画布外仍回到卡片 handler,因此「落点」只能自己量;不落盘是因为对话栏那一段没有画布坐标可言(写下去会得到跑到画布外的坐标),而且用户在对话栏上松手的意图本来就不是排版。`0 x 0` 的对话栏矩形必须判成「没有落点」:零面积矩形会让任何点都命中,画布内正常拖动会被整段跳过。
- 验证:`resourceCardReferenceDropModel.test.ts` 5 passed(矩形/命中/构造/提示文案)、`resourceCanvasChatReferenceDrop.test.tsx` 4 passed(单卡拖到对话 → 1 条引用且零坐标写入、多选整批 → 2 条、拖回画布 → 照旧写手动坐标且零引用、pointercancel 收干净)、`resourceReferenceInput.test.tsx` 30 passed(新增批量事件一次派发且空批次不派发、一次 insertReferences 按序插入整批 chip);连同 `appSurface.test.ts` 在内 7 个用例文件 660 passed / 17 skipped / 0 failed; `tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
- 影响范围:`apps/ai-game-creator-shell/src/view/project-development/{index.tsx,resourceCardReferenceDropModel.ts}` 、`.../features/project-workspace/resourceReferences.ts` 、`apps/ai-game-creator-shell/src/App.tsx` 、`apps/ai-game-creator-shell/src/styles.css` 、`apps/ai-game-creator-shell/tests/{resourceCardReferenceDropModel.test.ts,resourceCanvasChatReferenceDrop.test.tsx,resourceReferenceInput.test.tsx}` 、`docs/【功能说明】AGC聊天素材引用-2026-09-08.md` 。未动 Rust、SpacetimeDB、`packages/` 、共享弹窗组件。
- 已知未覆盖:真实客户端里的手感(拖到对话栏的触发距离、提示条位置、多选整批的视觉反馈)未在 Tauri 目视确认;「复制对话保留有效引用」属于同一条后续需求里的另一半,本轮未做。
## 2026-09-21 AGC「生成任务」侧栏移到画布右上角(照抄美术画布,保留 AGC 样式)
- 背景:`docs/project-memory/todos/【待办】画布验收后续修复-2026-09-18.md` 的「后续需求」要求「生成任务列表移到画布右上角,进行中/已完成分组、失败可见、限高滚动及自动开合」。改前状态:侧栏本体是 `position: fixed; top: 4rem; bottom: 6rem; left: 0.75rem` 的左侧贴边面板,开合口只有工具条上那一枚「生成任务 · N」按钮(资源 / 运行两个页签各渲染一次),折叠态不留任何常驻入口;分组、失败可见、限高滚动、提交后自动展开这四条当时已经具备。
- 决策:侧栏改挂**画布右上角的锚点**(`.game-resource-generation-tasks-anchor` ),形态照抄网页端美术画布的任务侧栏(`ImageCanvasTaskSidebarView.tsx` + `src/index.css:6072+` 的 `.image-canvas-editor__task-sidebar*` ):**开关常驻右上角、面板在开关左侧展开**,收起态只剩那一枚开关;工具条上那两处重复入口删掉——同一个功能两个入口本身就是两处随时会漂移的状态。锚点是覆盖式的,仍然不 reflow 画布视口。颜色、圆角、字重、阴影**全部继续走 `--platform-*` token**,不照搬网页端的固定色值(该 CSS 文件头既有的口径)。
- 原因:右上角是画布上唯一「不被右侧『智能创作』对话面板占、也不与左下角栏目工具栏 / 右下角缩放 Dock 打架」的稳定空位;折叠态仍留一枚开关以后,用户不必先想起工具条在哪一行,也不再需要「关掉以后打不开」的兜底(原设计正是靠工具条入口常驻来解决这个问题)。
- 决策(分档坐标):锚点由 view 的 `placement` ( `canvas | run | editor` )落成 `data-generation-tasks-placement` ,坐标在样式里分档:资源栏目画布与 UI 编辑器用画布顶边那一档(`top: 3.5rem; right: 0.85rem` ),**运行表现层下移到 `top: 7rem` **——它右上角 `top: 20px; right: 20px` 被 C7 版本入口 `game-run-version-picker` 占着,不去抢那一块。
- 实现要点:开关改由 view 自己渲染(`data-resource-generation-task-toggle` 与计数属性原样保留,可访问名 `生成任务` 唯一),在途计数只在 view 内算一次、开关与面板头部同源(宿主原先那份 `resourceAssetGenerationInFlightCount` 因此删除);面板改由 `max-height: min(30rem, calc(100vh - 12rem))` 封顶 + 内部滚动,自己不再定位(坐标只由锚点一处决定);进场 / 退场动画位移方向跟着锚点翻到正 X。宿主的「点外部收起」判据(排除侧栏本体与 `data-resource-generation-task-toggle` )与「提交受理后自动展开」均未改。
- 验证:`resourceCanvasAssetGenerationTasksPanel.test.tsx` 11 passed(新增「折叠只剩右上角开关」「开关计数与面板头部同源」「锚点按工作面分档」)、`resourceCanvasAssetGenerationTasksSidebarStyle.test.ts` 11 passed(锚点坐标 / 运行档下移 / 面板不再自定位 / 开关走 token 且无硬编码色 / 窄屏占满宽度)、`resourceCanvasGenerationTasksSidebarDismiss.test.tsx` 5 passed(入口位置改为右上角锚点、工具条不再有生成任务按钮)、`resourceCanvasAssetGenerationBackgroundClose.test.tsx` 5 passed; `appSurface.test.ts` 538 passed / 17 skipped / 0 failed(与既有基线逐条一致);`tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit` 通过。
- 影响范围:`apps/ai-game-creator-shell/src/features/resource-canvas/{ResourceCanvasAssetGenerationTasksPanelView.tsx,resourceCanvasAssetGenerationTasksSidebar.css}` 、`apps/ai-game-creator-shell/src/view/project-development/index.tsx` 、`apps/ai-game-creator-shell/tests/{resourceCanvasAssetGenerationTasksPanel.test.tsx,resourceCanvasAssetGenerationTasksSidebarStyle.test.ts,resourceCanvasGenerationTasksSidebarDismiss.test.tsx}` 、PRD §3.10。未动 Rust、SpacetimeDB、`packages/` 、共享弹窗组件。
- 已知未覆盖:真实客户端观感(右上角坐标相对画布顶边的落点、与运行表现层版本入口的间距)未在 Tauri 里目视确认;窄屏(≤480px)只有声明级断言。
## 2026-09-21 画布绑定前置查询收口为项目摘要,失败文案补因链
- 背景:dev 上「AI 生成图片」连续失败,卡片显示 `解析读取外部画布项目响应失败:error decoding response body` ,每条恰好 `1 分 00 秒` ;同批的远端资源编辑终态只提示「已明确失败」,用户看不到原因也看不到下一步。(同批「图标素材切片超过 64 个」已由本文件「图集切片上限:客户端结果门从 64 对齐到平台契约的 256」条目决策,这里不再重复。)
- 决策(项目列表视图):`GET /api/editor/projects` 与 `GET /api/external/v1/editor/projects` 共用同一套 `view` 取值与摘要投影(缺省 `full` 保持兼容,未知取值失败关闭);`summary` 只回传 `projectId / title / updatedAt / cover` ,既不做内联媒体修复,也不带画布与全量资源。投影实现收敛到 `editor_project.rs` 一份,外部 API 与 MCP 复用同一份;AGC 画布绑定前置查询固定使用 `?view=summary` ,它只需要 `projectId` 。
- 决策(错误文案与终态出口):外部请求失败文案补 kind 语义与底层因链(`reqwest::Error` 的 `Display` 只有 kind,超时 / 正文截断 / 非法 JSON 显示成同一句话),且不拼接 URL;远端资源编辑终态文案带出稳定失败码并指向唯一出口「移出恢复队列」,上游原文继续不写入账本。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs` 、`server-rs/crates/api-server/src/external_editor_api.rs` 、`apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs` 、`project/resource_editor.rs` 与对应夹具。
- 验证方式:`cargo test --locked -p api-server -- summary` (含站内 `view=summary` 跳过媒体修复、未知 view 返回 400 的新用例)、AGC 壳 `agent::generation::` ( 99 项)与 `project::resource_editor::` ( 59 项)、`cargo fmt --check` 、`npm run check:encoding` 、`git diff --check` 。
## Unity 与 Godot 常用操作指导
两种编辑器的操作指导复用客户端审核 Skill pack: DirectProject 通过原生 Skill 或既有审核资源读取入口按需取得,Agent Runtime 的对应执行工具说明嵌入同源参考。指南不改变插件可用性、执行授权或 Runner 回执;只读说明不能证明编辑器已连接。常用示例与执行失败/部分修改、保存、撤销边界在同一参考中维护,避免提示词和文档各存一份代码。
@@ -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 lane; lane 1 顺序运行 shard 1/4、2/4, lane 2 顺序运行 shard 3/4、4/4。每条 lane 只预热一次 AGC 壳 manifest, lane 之间仍保持 job 级并发;不在同一 job 内并行多个测试进程。
- 影响范围:`.gitea/workflows/project-ci.yml` 、`scripts/project-ci-workflow.test.ts` 、`scripts/check-native-shells.mjs` 与 Rust 分片说明文档。job 名称改为 `AI game creator shell Rust lane 1/2` 、`lane 2/2` ;分组脚本与 4 片测试名单保持不变。
- 验证方式:运行 `npx vitest run scripts/project-ci-workflow.test.ts` 、`node --test apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.test.mjs` 、`npm run check:encoding` 和 `git diff --check` ;真实 Gitea run 需要确认两条 lane 均覆盖两片且 smoke、crates、Backend、Native、Frontend、Repository job 仍全部上报。
## 2026-09-14 AGC 资源画布改为「手动整理」:新素材不再自动重排,整张重排只由「整理画布」发起
- 背景:生成一张新素材会让整张资源画布重排。两个 layout hook 都把 `rederiveAutomaticPositions` 打开(type 侧无条件 `true` , dependency 侧长期等于 `resourceGraphReady` ),而该开关的语义是「每次资源协调签名变化就丢掉全部 `manuallyPlaced=false` 坐标、按当前资源与拓扑整体重算」;新增一张素材必然改签名,于是既有自动卡全部跟着挪位,用户刚记住的位置就没了。画布上也没有任何显式整理入口(`复位资源视图` 只复位视口)。
@@ -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` 通过。未复核真实客户端观感。