解决 DirectProject 合并冲突
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m2s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m54s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m20s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m31s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m3s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m49s
Project CI / Frontend tests (pull_request) Failing after 6m56s
Project CI / Repository checks (pull_request) Failing after 6m23s
Project CI / Native shell tests (pull_request) Successful in 10m17s
Project CI / Backend tests (pull_request) Successful in 11m8s
Project CI / AI game creator shell web tests (pull_request) Failing after 6m0s

合并双方 DirectProject 历史分页、锚点闸门与订阅竞态处理。

保留 canonical content 输入、聊天 reducer、附件发送和历史分页行为。

合并 Codex 用户回显过滤、增量脱敏及 Direct 回合完成状态处理。

补齐历史截断、损坏行 fail-closed、首屏 anchor 与 appSurface 定向测试。

同步合并应用壳层、后端绑定和项目文档变更。
This commit is contained in:
2026-09-18 10:47:55 +08:00
258 changed files with 18457 additions and 1876 deletions
+1
View File
@@ -40,6 +40,7 @@
- [AGC 通用插件宿主与编辑器适配](./technical/【技术方案】AGC通用插件宿主与编辑器适配-2026-09-09.md):通用插件宿主、SDK、权限审计、UI 挂载和 Cocos 编辑器适配边界。
- [AGC Cocos Creator 编辑器桥接模块](<./technical/【技术方案】AGC Cocos Creator 编辑器桥接模块-2026-09-09.md>):独立 crate、feature 开关、目标校验与 Windows 注入边界。
- [AGC 客户端更新检查与下载](./technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md):启动版本检测、OSS 清单格式和下载约定。
- [AGC 模板库与模板建项](./technical/【技术方案】AGC模板库与模板建项-2026-09-17.md)`templates/` 前缀的模板库契约、下载安装与「用模板建项目」链路。
- [DirectProject 本轮附件路径映射](./technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md):Direct 首轮只映射附件原名与项目相对路径,不灌正文、不区别 GDD。
- [Direct 回合行为审计账本](./technical/【技术方案】Direct回合行为审计账本-2026-08-31.md)Direct GUI 回合把 native 读 / MCP / 写文件落成项目内有界时间线,用于判断有没有打开本轮附件。
- [项目开发工作台 PRD](./prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md):当前工作台页面和验收边界。
@@ -30,7 +30,7 @@ AGC 项目开发聊天框当前同时从三处取数据:Direct 回合事件(
- 思考过程与工具活动同样从运行态事件与历史条目推断,界面展示保持不变。
- 运行态事件必须自足:`item.started` / `item.completed` 携带与历史切片同形的完整**脱敏原始条目**,前端按归一后的 `itemId` 合并快照得到运行中与完成态;不提供按 `itemId` 单点取快照的接口。
- 思考正文以 `item.delta{kind:"reasoning"}` 流式下发(`item/reasoning/summaryTextDelta``item/reasoning/textDelta`)。这不放宽可见范围:同一段文本本来就已落进 `project.jsonl` 并在 `item.completed` 展示;plan 文本与命令输出仍只降级为活动状态。
- 首屏历史由 `subscribe` 返回的 `lastCompletedItemId` 锚定,再取最近切片;删除返回整份对话的历史命令。
- 首屏历史由 `subscribe` 返回的 `lastCompletedItemId` 锚定,再取最近切片;删除返回整份对话的历史命令。锚点是切片**新端(较新一侧)的边界且含该条**(命令参数 `throughItemId`):比锚点更新的条目只从运行态事件来,历史切片与实时流因此不重叠;向后翻页仍用切片返回的 `firstItemId` 作为 `beforeItemId`(不含锚点)。订阅回执到达之前不读首屏,也不退化成"取文件尾"(那会把回执之后才完成的条目也拉进历史)。
- 生命周期锚点独立于 replay 队列保存(队列会回收 `cleanable` 事件,新订阅的游标又在队尾,回收后无法反推"最新回合是 started 还是 completed"),`subscribe` 必须返回最新的一条 `turn.started` / `turn.completed`,否则新订阅无法判定回合是否仍在运行。
- 前端工具卡片形状是 `Omit<GameCreatorDirectToolCall, 'turnId'>`:聊天卡片不再有回合身份,`tool-calls.jsonl` 的持久化形状仍保留 `turnId`DirectRuntime 的账本没动)。
- 未识别 item 类型由 Rust 原样透传(只带类型与身份,Rust 侧留 TODO),当前由前端投影丢弃:哪些类型可见属于前端决策,不回 Rust 加白名单。
@@ -3363,7 +3363,7 @@
},
"EditorIconSpritesheetGenerationRequest": {
"type": "object",
"required": ["referenceId", "iconDescriptions"],
"required": ["referenceId", "iconDescriptions", "sliceMode"],
"properties": {
"referenceId": {
"type": "string",
@@ -3395,26 +3395,25 @@
"connected-components",
"grid"
],
"default": "connected-components",
"description": "图集切分模式。connected-components 按透明像素 alpha 连通域识别独立素材;grid 按用户提供的 gridX/gridY 划分网格槽。省略时使用 connected-components。"
"description": "必填,没有默认值:必须在引用解析、定价、入队和任何 provider / OSS 副作用之前显式声明切分模式。需求明确要求等分网格、固定槽位或指定行列数时传 grid,并用 gridX/gridY 传入来自需求本身的行列数;自由排布、数量不定或只要求一张图集时传 connected-components,需要约束素材张数时用 sliceCount。connected-components 不接受 gridX/gridYgrid 必须同时提供 gridX/gridY(各 1..32)。省略、null 或空字符串返回 400field=sliceMode),模式与网格参数互相矛盾返回 400field=gridX/gridY),两者都不会产生计费、入队或 provider 调用。响应中的 sliceMode 回显本次实际采用的模式。"
},
"gridX": {
"type": "integer",
"minimum": 1,
"maximum": 32,
"description": "grid 模式的横向网格数量。"
"description": "grid 模式的横向网格数量,只能与 sliceMode=grid 同时出现;与 connected-components 同时提交返回 400。"
},
"gridY": {
"type": "integer",
"minimum": 1,
"maximum": 32,
"description": "grid 模式的纵向网格数量。"
"description": "grid 模式的纵向网格数量,只能与 sliceMode=grid 同时出现;与 connected-components 同时提交返回 400。"
},
"sliceCount": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"description": "connected-components 模式下可选的目标切片数量;省略时按图像内容自动识别。grid 模式的切片数量由 gridX×gridY 决定。"
"maximum": 256,
"description": "connected-components 模式下可选的目标切片数量1..256;省略时按图像内容自动识别上限。识别结果与该目标数量不一致、为 0 或超过 256 时返回 422 并给出实际识别数量,不会静默截断。grid 模式的切片数量由 gridX×gridY 决定,不接受该字段。"
},
"screenColor": {
"type": ["string", "null"],
@@ -3649,7 +3648,7 @@
"connected-components",
"grid"
],
"description": "实际采用的图集切分模式。"
"description": "本次实际采用的图集切分模式,与请求显式声明的 sliceMode 一致;图集生成入口不回退到任何默认模式。"
},
"gridX": {
"type": "integer",
@@ -3664,7 +3663,7 @@
"sliceCount": {
"type": "integer",
"minimum": 0,
"maximum": 100,
"maximum": 256,
"description": "实际生成的切片数量。"
},
"sliceWarning": {
@@ -0,0 +1,37 @@
# 【实施计划】AGC 客户端更新切换到官方更新插件
| 字段 | 值 |
| --------- | ----------------------------------------------------------------------------------- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC客户端更新切换到官方更新插件-2026-09-17.md` |
| Status | ready |
| Owner | Codex |
## 修改边界
- 允许修改:AGC 客户端原生侧(依赖、插件注册、更新相关命令与其测试)、AGC 前端更新服务与更新提示、「关于」页检查入口、capability 与 Tauri 配置、AGC 客户端测试、主规范与开发运维文档。
- 明确不修改:发布脚本与 Jenkins(渠道化属于下一个里程碑)、OSS 对象布局、SpacetimeDB、`/api/external/v1`、网站与其它 App。
## 实现顺序
1. 生成发布签名密钥对:私钥落在仓库外 `%USERPROFILE%\.tauri\`,公钥写入客户端配置(公钥发布后不可更换)。
2. 原生侧:加入官方更新插件依赖并注册;删除自研更新下载命令、下载进度事件、安装器启动逻辑与其专属测试;新增供 macOS 安装后重启的应用命令。
3. 配置与权限:打开更新产物生成,写入公钥、渠道端点(默认 Windows 渠道)与 Windows 静默安装模式;capability 增加更新权限,并移除只为自研清单放行的 OSS 白名单与 CSP 连接项。
4. 前端:更新服务改为调用官方插件(检查、下载、进度、安装、重启收敛),删除自研清单解析、版本比较与下载实现;更新提示改用插件进度回调;保留开发态特性开关语义。
5. 测试:改写更新服务定向用例(开关关闭不发请求、更新元数据映射、失败静默、进度与重启、无待装更新时失败关闭)。
6. 文档:更新技术方案与开发运维说明,删除自研链路描述。
## 验证命令
1. `npm --prefix apps/ai-game-creator-shell run typecheck`(含 `check-config.mjs` 与 skill-pack 校验)
2. `npx vitest run apps/ai-game-creator-shell/tests/appUpdate.test.ts apps/ai-game-creator-shell/tests/featureFlags.test.ts apps/ai-game-creator-shell/tests/dev-feature-flags.test.ts`
3. `cargo check --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`
4. `npx eslint` / `npx prettier --check`(改动文件)
5. `npm run check:encoding``npm run check:doc-index``git diff --check`
6. 运行时:`npm run agc` 启动不产生更新清单请求;检索确认自研命令、事件与白名单条目无残留。
## 风险与回滚点
- 公钥不可更换:密钥已生成但尚未发布任何签名版本,若需要带密码的私钥仍可在首次发布前重新生成。
- Windows 安装模式由插件配置决定(本里程碑固定 `quiet`,与旧 PowerShell `/S` 一致);若改为 `passive` 会多出安装进度条 UI。
- 插件在 Windows 上安装成功后自行退出进程,前端不再有机会更新界面;提示面板的完成态只在 macOS / Linux 可见。
- 回滚点:改动集中在客户端与配置,回滚后即可退回自研链路;旧 OSS `agc/latest.json` 在发布管线渠道化前不删除。
@@ -0,0 +1,37 @@
# 【实施计划】AGC 更新发布管线渠道化
| 字段 | 值 |
| --------- | ------------------------------------------------------------------------- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC更新发布管线渠道化-2026-09-17.md` |
| Status | ready |
| Owner | Codex |
## 修改边界
- 允许修改:AGC 发布脚本(`apps/ai-game-creator-shell/scripts/build-release.mjs``release-upload.mjs` 及其测试)、AGC 发布流水线 `jenkins/Jenkinsfile.ai-game-creator-shell-build`、开发运维与技术方案文档。
- 明确不修改:客户端插件接入与前端更新服务(上一里程碑已完成)、SpacetimeDB、`/api/external/v1`、网站与其它 App、其它 Jenkins Job。
- 不执行 OSS 上传:本里程碑只交付脚本、流水线定义与本地可验证产物;真实发布需要单独授权与凭据。
## 实现顺序
1. 发布脚本:解析并校验渠道(渠道与目标平台绑定,未显式指定时按平台取默认渠道),把渠道写进远端清单地址与构建期端点配置。
2. 清单生成:按渠道产出官方更新插件清单(版本、发布说明、发布时间、平台键与签名),universal macOS 产物同时挂两个平台键;缺少签名或签名为空时失败关闭。
3. 迁移桥:Windows 渠道额外产出旧协议 sha256 清单,指向同一渠道的最新安装包,供已发布客户端升级到新协议。
4. 上传:按渠道写版本目录(安装包与签名)与渠道 latest 指针,旧协议指针单独覆盖写。
5. 流水线:新增渠道参数与签名凭据注入,归档安装包、签名、渠道清单与 commit。
6. 测试与文档:更新发布脚本单测(渠道校验、清单结构、签名缺失失败关闭、旧协议清单),同步开发运维与技术方案。
## 验证命令
1. `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs apps/ai-game-creator-shell/scripts/cargo-features.test.mjs`
2. 本地清单 smoke:伪造 bundle 目录 + 真实签名私钥,断言渠道清单与旧协议清单结构、缺少签名时失败关闭
3. `npm --prefix apps/ai-game-creator-shell run typecheck`
4. `npm run ai-game-creator-shell:build -- --no-bundle`(渠道端点注入后的构建 smoke;不改版本、不读远端清单、不生成清单)
5. `npm run check:encoding``npm run check:doc-index``git diff --check`、prettier 与 eslint(改动文件)
## 风险与回滚点
- 版本递增按渠道独立:`dev-win``dev-mac` 的清单地址不同,互不影响;旧协议指针只由 `dev-win` 写入。
- 签名缺失即失败关闭:构建机未注入签名私钥时发布中止,不产生半成品清单。
- 渠道端点写进产物:渠道名一旦发布不可改名(改名等于已发布客户端再也找不到更新)。
- 回滚点:发布脚本与流水线都在本里程碑内,回滚后客户端仍可用原先的自研清单协议;迁移桥可独立停用。
@@ -0,0 +1,59 @@
# AGC 模板库客户端接入实施计划
Version: 1.0
Status: active
Date: 2026-09-17
Milestone Spec: `docs/project-memory/plans/【里程碑】AGC模板库客户端接入-2026-09-17.md`
## 步骤
1. **OSS 库布局与契约**
-`agc-dev` 落地 `templates/` 前缀:`index.json``v1/<id>/{template.json,template.zip,cover.*}`
- 清单补齐 `tags``coverKey/coverWidth/coverHeight/coverSha256`,正文改为 zip(zip 根 == 项目根)。
- 模板源落在 `apps/ai-game-creator-shell/template-library/v1/<id>/{meta.json,project/**,cover.*}`zip 由 `scripts/agc-template-library-publish.mjs` 现场打包(不落仓库)。
- 交付:发布脚本(校验 + 打包 + 上传 + 回读校验,支持 `--dry-run` / `--prune`)、`templates/README.md`,以及 5 个模板(3 个空白 + 2 个起步工程)。
- 验收:匿名 `GET templates/index.json` 可读,每个 `zipKey` 回读 SHA-256 与清单一致。
2. **Rust 模板库模块**
- 新增 `src-tauri/src/template_library.rs`:清单解析与校验、受信任 base、缓存/安装目录、zip 安全解压、安装记录、由模板建项目。
- 注册命令 `fetch_game_template_library``download_game_template``create_automatic_local_game_project_from_template`
- 交付:模块内 8 项单测(schema/重复模板、键前缀、base 校验、解压逃逸、摘要与大小、安装记录、建项目与失败清理)。
- 验收:`cargo test --bin genarrative-ai-game-creator-shell template_library` 全绿。
3. **前端状态链路**
- `src/features/template-library/templateLibraryModel.ts`(类型与搜索/筛选纯函数)与 `useTemplateLibrary.ts`(拉取、下载、建项目、就地更新已下载状态)。
- `useHomeProjectCreation` 增加 `enterCreatedTemplateProject`,复用既有进项目通道。
- 交付:9 项模型单测。
- 验收:`npx vitest run src/features/template-library` 全绿。
4. **界面接入**
- 新增 `src/view/template-library/index.tsx` 全屏页;`LauncherView` 增加 `template-library`;左侧导航加模板库入口。
- 首页「灵感推荐」替换为 `TemplateRecommendations`;删除 `InspirationGallery.tsx``assets/inspiration/`
- `tauri.conf.json``img-src` 放行受信任 OSS 主机以加载封面。
- 验收:模板库页可搜索、筛选、下载、显示已下载并成功建项目;首页推荐位可跳转。
5. **文档与共享记忆**
- 主规范 `docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md`,并在 `docs/README.md` 建索引。
- 本里程碑与实施计划;`decision-log.md` 记录库路径、清单 schema、缓存目录与 CSP 约定。
- 验收:`node scripts/check-doc-index.mjs` 通过。
## 验证命令
```bash
cargo check --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell template_library
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell template_library -- --ignored
cd apps/ai-game-creator-shell && npx tsc -p tsconfig.json --noEmit
npx vitest run apps/ai-game-creator-shell/tests/templateLibraryModel.test.ts apps/ai-game-creator-shell/tests/templateLibraryView.test.tsx
node scripts/agc-template-library-publish.mjs --source <dir> --dry-run
npm run check:encoding
node scripts/check-doc-index.mjs
git diff --check
```
## 风险与回退
- **封面走 WebView 直连**:仅放行受信任 OSS 主机;若日后改用后端签名,清单的 `coverKey` 不变。
- **模板包体积**:下载上限 512 MiB、解压文件数 4096、单文件 256 MiB;超限直接拒绝,不落盘。
- **清单漂移**:客户端只信「受信任主机 + 对象键」,清单中的地址字段不参与请求。
- **回退**:清空 `templates/` 前缀即回到空模板库;客户端保留错误与空态展示,不阻断其它功能。
@@ -0,0 +1,16 @@
# AGC 资源菜单收纳实施计划
对应:[里程碑](./【里程碑】AGC资源菜单收纳-2026-09-17.md)Issue #409,产品已确认方案 A。
## PR #410 CI 修复
以远端合并提交 959beebf 为基线:修复菜单文件 import 排序、Web 角标结构断言、活动回合空快照与晚到请求竞态;窗口发布次数断言对齐稳定快照合同。原生 HTTP scope 检查对齐官方 updater 当前权限,不恢复退役 OSS 白名单;Rust 图集测试补齐显式切片模式与 strict schema 字段,不放宽正式校验。按故障项定向测试后运行前端全套及原生契约检查;Rust 使用独立 target,实际未执行的检查必须单独列出。推送需再次确认。
本地修复验证:`npm test` 342 个文件通过(4137 项通过、37 项跳过),窗口与空快照最后一次定向复验 9 项通过;`lint:eslint`、根目录/AGC 类型检查、原生 contract 检查、Rust fmt、编码、文档索引与 diff 检查通过。Rust 工具目录 schema 用例及后台平台美术生成用例均在 Windows 独立 target 下通过;生成用例同时检查真实 mock 请求中的 grid、2×2 参数与响应匹配。未执行全量 Rust 分片、Linux CI、生产服务或真实客户端手感验收。
1. 在 shared 扩展通用操作收纳及卡片角标控件;共用工具栏只给 AGC 开启 5 项限制,Web 卡片迁移共用角标而不改现有回调。
2. AGC 卡片承接类型和信息,保留当前面板与命令链;信息使用资源身份防止换选竞态。
3. 补工具栏/工作台定向回归,检查禁用、移入、Escape、换选和卡片事件边界。
4. 并行执行定向 Vitest、AGC 类型检查、编码与文档索引检查,再自审整体调用链。
风险:portal 浮层点击外部判定、缩放角标与拖拽冲突、原测试依赖完整工具栏。回滚仅撤销本分支 UI 与文档修改;无数据迁移。首个检查点为组件用例通过,第二个为工作台集成与类型检查。真实客户端未测则明确保留待验收状态。
@@ -0,0 +1,43 @@
# AGC 项目定时快照上传实施计划
Version: 1.0
Status: active
Date: 2026-09-17
Parent Milestone: `【里程碑】AGC项目定时快照上传-2026-09-17.md`
## 修改边界
1. `server-rs/crates/shared-contracts/src/`:新增 `agc_project_snapshots` DTO(单文件上传请求/响应、同步清单信封),只放共享字段,不放 OSS 细节。
2. `apps/ai-game-creator-shell/src-tauri/src/project_snapshot/`:新增客户端模块,包含扫描与排除规则、索引读写、差异对比、上传编排、状态与日志;不修改 `project/` 下既有 manifest 与写锁语义。
3. `apps/ai-game-creator-shell/src-tauri/src/main.rs`:注册新模块、命令与生命周期钩子;`windows.rs` 的窗口关闭与应用退出路径接入触发调用,不改变现有窗口创建/关闭顺序。
4. `server-rs/crates/platform-oss/src/lib.rs`:新增项目快照私有前缀常量与(必要时)独立 bucket 配置入口;不改动既有前缀枚举语义与资源写路径。
5. `server-rs/crates/api-server/src/project_snapshots.rs`:新增路由、鉴权、校验与 OSS 写入;不改动 `error_reports``assets` 既有路由。
6. `.env.example`:补充 `GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_*` 说明与默认值。
7. `docs/`:主规范已更新;完成后把持久结论合并回主规范并删除本计划。
## 实现顺序
1. 先写 `shared-contracts` DTO 与客户端差异引擎(扫描、排除、索引、diff)及单测,此时无网络依赖,可独立验证。
2. 接上传编排:按差异集合逐文件提交,成功后再提交清单,最后推进索引;用本地 TCP stub server 覆盖成功、幂等跳过、鉴权失败与部分失败路径。
3. 接触发接线:周期定时器、工作区窗口关闭与应用退出;确认关闭路径的有界超时和串行化。
4. 最后接服务端路由与 OSS 写入,补参数校验与幂等跳过测试;服务端完成前客户端按"未配置即失败关闭、不写入索引"处理。
每一步都保留既有失败关闭行为;新模块默认不改变其它同步路径(Runner、项目写锁、Resource Editor)。
## 验证命令
- `cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check`
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml project_snapshot -- --nocapture`
- `cargo test -p api-server --bin api-server project_snapshots -- --nocapture`
- `cargo fmt -p api-server -p shared-contracts -p platform-oss -- --check`
- `npm run --prefix apps/ai-game-creator-shell typecheck`(若触及前端)
- `npm run check:encoding`
- `git diff --check`
- 运行时按需:`npm run agc` 打开项目观察索引写入与同步日志,关闭窗口确认关闭触发。
## 风险与回滚
- 上传体积与带宽:首轮全量可能很大,先设单文件与单次同步总量上限并把超限项记入跳过清单;不静默截断。
- 数据出境边界:只上传项目目录内普通文件,排除 `.agent/runtime``.agent/logs``.git`、构建产物与临时文件;凭据类文件不在白名单内。
- 服务端未配置 bucket 时客户端必须失败关闭,不能把本地索引推进成"已同步",否则后续同步会漏传。
- 回滚:客户端可停用触发接线(保留模块与测试)即可回到无上传行为;服务端路由与配置项可单独移除,不影响既有 OSS 前缀与错误报告链路。
@@ -19,7 +19,7 @@
3. 删掉回合身份:`turn.started` 无载荷、`turn.completed{status}`,条目 / 增量 / 请求 / 生命周期锚点都不带 turn id;队列 `append` 直接收 `DirectThreadEvent``seq` 内部自算。
4. 思考正文流式:`item/reasoning/summaryTextDelta``item/reasoning/textDelta` 产出 `item.delta{kind:"reasoning"}`;plan 文本与命令输出保持活动状态。
5. 前端收敛为单一 reducer`subscribe` 返回的 bootstrap 事件就是已暂存的运行态,游标已经在队尾,前端直接 reduce 这批事件即可(不需要为了拿这批事件再补一次 `consume`);此后只由 notify 唤醒 `consume`。唯一例外是回执竞态:Rust 注册完 subscriber 就开始通知,而前端要等回执才知道 `subscriptionId`,这段时间到达的通知只能记欠账,回执到达后立刻补一次 `consume`(否则整轮最后一个事件之后可能再无通知,事件会卡死在队列里)。合并规则只保留"先到定形、后到补空白"(正文只增不减、工具状态允许从 running 升级到终态),`item.delta` 直接追加到运行态条目正文,删掉 `deltaText` 缓冲,`turn.completed` 把运行态条目并入历史再清空。
6. 首屏与分页:以 `lastCompletedItemId` 为锚点取最近切片,历史读取改为从文件尾反向扫描;锚点按原始 item id 推进;一次翻页操作在前端连拉,直到合并后聊天投影出现新回合(新的用户气泡)或 `hasMore=false`,每个操作上限 5 页;锚点未推进(`items` 为空 / `firstItemId` 为 null / 与请求锚点相同)时立即停止。可见性口径只在 `features/project-workspace/directHistoryPaging.ts` 实现一份,首屏与「显示更早」共用。
6. 首屏与分页:以 `lastCompletedItemId` 为锚点取最近切片,历史读取改为从文件尾反向扫描;切片的新端边界由这个锚点给出(含该条,命令参数 `throughItemId`),首屏读取等订阅回执里的锚点,回执到达前不发请求、也不退化成「取文件尾」;之后锚点按原始 item id 推进(`beforeItemId`,不含锚点)。一次翻页操作在前端连拉,直到合并后聊天投影出现新回合(新的用户气泡)或 `hasMore=false`,每个操作上限 5 页;锚点未推进(`items` 为空 / `firstItemId` 为 null / 与请求锚点相同)时立即停止。可见性口径只在 `features/project-workspace/directHistoryPaging.ts` 实现一份,首屏与「显示更早」共用。
7. App.tsx 接线:订阅 + 立即 reduce bootstrap + notify 唤醒 consume,聊天视图改由 reducer 状态投影(含工具卡片),删除 Direct 回合事件订阅与 `directTurnStream` / `directToolCalls` 状态。
8. 删除只服务旧读路径的命令与前端调用(`read_direct_project_history``read_direct_turn_stream``read_direct_tool_calls`),DirectRuntime 自己的写入保留。`list_game_creator_direct_active_turns` 是唯一的例外并有意保留:它服务首页跨页面的「运行中的项目」列表(`WorkspaceLauncher` / `directActiveTurns.ts`),不是聊天框读路径。
9. 测试与文档收口:补 reducer 单测、解锁跳过的工具卡片用例、更新主规范并把冲突的实施计划与工具卡片文档改写为当前状态。
@@ -0,0 +1,39 @@
# 【实施计划】图集切片模式显式决策
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】图集切片模式显式决策-2026-09-17.md` |
| Status | ready |
| Owner | Codex |
## 修改边界
- 允许修改:`server-rs/crates/api-server`(图标图集生成入口、错误体、画板 Agent 工具装配、OpenAPI 契约测试)、平台画板前端(`src/services/image-editor``src/components/image-editor`)、AGC 客户端(`apps/ai-game-creator-shell/src-tauri` 的 MCP 工具说明、桥接校验、原生工具 schema、图集生成选项与调用方、AGC Skill)、`.codex/skills/genarrative-external-editor-api``docs/openapi/genarrative-external-v1.openapi.json`、主规范与共享记忆。
- 明确不修改 `platform-editor-agent`:画板 Agent 的工具参数不变,其链路在装配层固定显式声明 `connected-components`,画板因此不具备网格生成入口。
- 明确不修改:拆分 / 去背 / 像素规整算法、切片上限、手动拆分入口行为、SpacetimeDB schema、旧版本客户端兼容分支。
## 实现顺序
1. 平台入口:`sliceMode` 由可选改必填并校验模式自洽性,失败发生在引用解析、定价、入队之前。
2. 公开契约:OpenAPI 请求体去掉默认值、补必填与失败语义,并补契约测试。
3. 平台自有调用方显式声明模式:画板 Agent 工具装配(固定连通域)、画板前端提交计划(固定连通域)。
4. AGC 客户端:MCP 工具说明与桥接校验、原生工具 schema 与观察器、图集生成选项与全部调用方、AGC Skill 与外部 MCP 说明。
5. 错误可执行性:切片模式按原始字符串接收后逐项校验,统一返回 `field`、允许取值与决策分支;`sliceCount` 契约上限与切片上限对齐。
6. 反馈闭环:生成结果回显生效声明与切片路径,严格图集在本地提交前校验回显与请求一致。
7. 标准美术包显式声明 `connected-components` + `sliceCount=4`,用途映射前校验切片数量正好为四。
8. 测试环境:为提权 Windows 主机上的 `%TEMP%` 所有者偏差补测试构建专用的所有者初始化重试(仅限临时目录内、且失败原因为所有者不匹配)。
9. 文档与共享记忆同步,最后运行定向验证与编码 / diff 检查。
## 验证命令
1. `cargo test -p api-server editor_icon_spritesheet`(名称按实际测试筛选)
2. `cargo test -p platform-editor-agent`
3. `npm run test -- src/services/image-editor/editorProjectClient.test.ts`(按仓库既有前端测试入口)
4. `cargo test -p ai-game-creator-shell` 定向筛选 `slice_mode` / `generate_image`
5. `npm run check:encoding``npm run check:doc-index``git diff --check`
## 风险与回滚点
- 风险 1:已发布的 AGC 客户端与第三方外部 API 调用方在未更新前会因缺失 `sliceMode` 收到 `400`。回滚点为「恢复服务端兜底读取连通域」,但该兜底与本次里程碑目标冲突,需产品确认后再引入过渡期。
- 风险 2:AGC 原生工具 schema 从“可选”改为“显式声明”,自主运行时可能出现一轮可修复的工具参数失败。回滚点为「保留 schema 字段但收回 description 中的强制措辞」。
- 风险 3:画板前端显式声明模式后,画板自身不再具备网格生成能力;需要网格时改用外部 API 或后续单独开放画板入口。
@@ -0,0 +1,46 @@
# 【里程碑】AGC macOS 渠道更新落地
| 字段 | 值 |
| ----------- | ------------------------------------------------------------------ |
| Version | 1.0 |
| Status | deferred |
| Date | 2026-09-17 |
| Parent Spec | `docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md` |
## 目标
`dev-mac` 渠道可产出并发布 macOS 更新包,客户端在 macOS 上完成检查、安装与重启接管新版本。
## 范围
- macOS 更新产物:按 universal 目标构建(Intel 与 Apple Silicon 共用一个包),更新包与其签名按渠道约定生成并上传,清单把同一对象挂到两个 macOS 平台键。
- macOS 安装后的重启收敛:安装完成后由客户端重启进程运行新版本,不依赖安装程序代为重启。
- macOS 代码签名与公证依赖的确认与记录:未签名或未公证的产物视为不可发布。
- macOS 构建执行环境(本机 mac 或新增 macOS 节点)与渠道发布的衔接方式。
## 不在范围内
- Windows 渠道行为调整。
- 微软商店或 App Store 分发。
- 更新包体积优化与增量更新。
## 依赖与前置条件
- 客户端插件化与发布管线渠道化两个里程碑已验收。
- macOS 签名证书与公证凭据可用;若不满足,本里程碑只能交付构建与清单能力,并明确标注未验证项。
- macOS 通用包所需的双架构工具链(两个 darwin 目标)在构建机上可用。
本里程碑暂缓执行:macOS 构建机与签名 / 公证凭据尚未就绪,改由后续独立变更承接;暂缓期间 dev-mac 渠道不发布。
## 验收标准
- [ ] `dev-mac` 渠道清单包含两个 macOS 平台条目且指向同一个 universal 安装包与签名,对象在 OSS 上一致可下载。
- [ ] macOS 客户端能完成一次真实更新:检查、下载、安装、重启后运行新版本,且升级后产物仍是 universal 包。
- [ ] 覆盖写渠道 latest 指针后,旧版本 macOS 客户端可升级到新版本;Windows 与 macOS 渠道互不干扰。
- [ ] 未签名或未公证产物在发布阶段失败关闭,或在不满足条件时明确记录为未验证项而非静默通过。
## 证据要求
- 自动化:macOS 更新产物选择与清单生成用例、仓库门禁。
- 运行时:macOS 上一次真实更新闭环(含重启后版本核对),OSS 对象与清单核对。
- 边界:签名校验失败、公证缺失、渠道缺少 macOS 平台条目、跨架构不匹配时的表现。
@@ -0,0 +1,45 @@
# 【里程碑】AGC 客户端更新切换到官方更新插件
| 字段 | 值 |
| ----------- | ------------------------------------------------------------------ |
| Version | 1.0 |
| Status | approved |
| Date | 2026-09-17 |
| Parent Spec | `docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md` |
## 目标
客户端自动更新的检查、下载、签名校验与安装改由 Tauri 官方更新插件承担,前端只保留触发与展示,并按渠道读取清单;开发态继续不检查更新。
## 范围
- 官方更新插件在客户端两侧接入:原生侧注册与配置,前端调用官方 API 替代自研检查与下载。
- 渠道作为构建期常量进入客户端:每个渠道的产物只读该渠道清单,运行期不切换渠道。
- 保留并复核现有开发态特性开关语义:开发态不检查更新、不显示更新入口。
- 更新能力只授予客户端主窗口。
## 不在范围内
- 发布管线与 OSS 对象布局的渠道化改造。
- macOS 产物落地、签名与公证。
- 旧客户端迁移桥(是否保留旧清单指针)。
## 依赖与前置条件
- 发布签名公钥可用;公钥写入客户端配置,来源见主规范未决问题。
- 渠道清单地址与对象布局按主规范约定确定,渠道集合固定为 `dev-win``dev-mac`
- 官方插件版本与当前 Tauri 主版本兼容。
## 验收标准
- [ ] 正式包走官方更新插件的检查与安装路径;更新包校验失败时必须拒绝安装并清理临时文件。
- [ ] 客户端只请求本渠道清单,且不因清单缺失、格式错误或网络失败阻塞启动。
- [ ] 开发态启动不产生任何更新清单请求,也不显示更新入口。
- [ ] 更新能力只授予客户端主窗口,其它窗口调用被拒绝。
- [ ] 自研清单解析、下载命令、下载进度事件与相应的 CSP / HTTP 白名单放行整条删除,无残留兼容分支。
## 证据要求
- 自动化:前端定向用例(渠道映射、开发态开关、失败关闭)、原生侧定向用例、类型检查与仓库门禁。
- 运行时:`agc` 开发启动无清单请求;使用测试渠道清单完成一次真实检查与安装闭环(含升级后重启)。
- 边界:签名不匹配、下载中断、清单 404、渠道缺少当前平台条目、非主窗口调用。
@@ -0,0 +1,47 @@
# 【里程碑】AGC 更新发布管线渠道化
| 字段 | 值 |
| ----------- | ------------------------------------------------------------------ |
| Version | 1.0 |
| Status | approved |
| Date | 2026-09-17 |
| Parent Spec | `docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md` |
## 目标
构建与发布管线按渠道产出官方更新插件要求的清单与签名产物并上传到渠道路径,发布入口可通过渠道参数在渠道之间切换。
## 范围
- 构建期按渠道生成清单:版本按渠道独立递增,清单包含该渠道平台的下载地址与签名;`dev-mac` 的 universal 包按同一地址与签名同时写入 `darwin-aarch64``darwin-x86_64`
- 构建期生成更新产物签名,并在缺少签名私钥或私钥不可用时失败关闭。
- 渠道参数与目标平台绑定校验:Windows 目标只能发布 `dev-win`macOS 目标只能发布 `dev-mac`;未显式指定时按目标平台取默认渠道。
- 上传按渠道落位:安装包与签名进版本目录,清单覆盖写渠道路径的 latest 指针。
- Jenkins 流水线增加渠道参数与签名凭据注入,凭据不落盘、不进日志、不进归档。
## 不在范围内
- 客户端侧的更新链路改造。
- macOS 构建环境建设与 mac 产物签名、公证。
- 旧客户端迁移桥;若决定保留,作为本里程碑的可选增量单独评审。
## 依赖与前置条件
- 客户端切换到官方更新插件的里程碑已验收:清单格式、公钥与客户端期望一致。
- 签名密钥对已生成并进入构建凭据,公钥已写入客户端配置。
- OSS 上传凭据与既有发布入口可复用。
## 验收标准
- [ ] 指定渠道发布时该渠道清单版本按渠道独立递增,另一个渠道清单不受影响。
- [ ] 渠道与目标平台不匹配、缺少签名私钥或私钥密码错误时发布失败关闭,不产生半成品清单。
- [ ] 发布后 OSS 上安装包、签名与渠道清单三者一致:清单内地址指向已存在的对象,签名与安装包匹配。
- [ ] universal macOS 产物的两个平台键指向同一对象同一签名,不存在只挂单一架构键或指向不存在对象的情况。
- [ ] Jenkins 归档与日志中不出现签名私钥内容,凭据只注入构建进程。
- [ ] 未显式指定渠道时按目标平台取默认渠道,且 `--no-bundle` smoke 路径仍不读远端版本、不改版本、不生成清单。
## 证据要求
- 自动化:发布脚本单测(渠道解析与校验、版本递增、清单结构、签名缺失失败关闭)、仓库门禁。
- 运行时:一次真实渠道发布加 OSS 对象核对(清单、安装包、签名),并用该清单触发一次客户端更新闭环。
- 边界:渠道与平台不匹配、签名密钥缺失、远端清单 404、远端清单格式非法、重复发布时的 latest 覆盖。
@@ -0,0 +1,40 @@
# AGC 模板库客户端接入
Version: 1.0
Status: active
Date: 2026-09-17
Parent Spec: `docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md`
## 目标
AGC 客户端能读取公共 OSS 上的游戏模板库,并把「浏览 → 筛选 → 下载 → 用模板建项目」做成一条可用链路,模板更新不再依赖客户端发版。
## 范围
- OSS `templates/` 前缀的库布局、清单 schema、封面与 zip 元数据契约。
- Rust 侧读取清单、下载安装、由模板创建项目的命令与安全边界。
- 模板库全屏页(搜索、标签/运行时筛选、仅看已下载)、首页模板推荐位、左侧导航入口。
- 仓库内发布脚本与文档、模板库定向单测与类型检查。
## 不做
- 不做模板制作工具、模板审核、模板计费与推荐算法。
- 不做模板增量更新(按 `templateVersion` 全量重下)。
- 不把模板回填进已创建项目,也不改写用户项目内容。
- 不放宽现有项目私有 DACL、下载摘要校验和受信任 OSS 主机边界。
## 验收标准
1. `fetch_game_template_library` 能读到清单并合并本机已安装状态;远端不可用时回退本机缓存并标明 `source=cache`
2. `download_game_template` 对字节数与 SHA-256 不一致、越界对象键、非 `templates/` 前缀的包一律拒绝,且不落半成品目录。
3. zip 解压拒绝绝对路径、`..`、盘符、符号链接;安装完成后才写 `installed.json` 作为已下载判据。
4. `create_automatic_local_game_project_from_template` 建出的项目同时具备模板文件、`.agent` 清单与标准目录;失败时不留项目目录。
5. 模板库页可按关键词、标签、运行时与「仅看已下载」筛选;卡片显示封面与已下载徽标;已下载且版本一致时不再显示下载入口(落后显示「更新」);过程提示以浮层 toast 呈现,不占页面内位置;「使用模板」在版本落后时先重下再建项。
6. 首页推荐位展示模板库内容并可进入模板库页;左侧导航有模板库入口且为独立全屏页。
7. 定向 Rust 单测 9 项(含线上清单 fixture)、前端模型 9 项 + 页面/推荐位 8 项、`tsc` 类型检查、`npm run check:encoding``git diff --check` 全部通过;可选真连检查能读线上清单、下载安装线上模板并据此建项目。
## 依赖
- 现有自动工作区建项链路(`create_automatic_local_game_project_at` / `init_local_game_project_at`)。
- 现有 AGC 更新通道使用的受信任 OSS 主机与 CSP 白名单口径。
- 仓库 OSS 凭据(本机 `.env.secrets.local``ALIYUN_OSS_*`)与 `scripts/agc-template-library-publish.mjs`
@@ -0,0 +1,31 @@
# AGC 资源菜单收纳
- Version: 1
- Status: implemented,本地自动化通过,待真实客户端验收
- Date: 2026-09-17
- Parent Spec: ../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md
## 范围与评审
仅调整前端入口和临时浮层状态,不修改资源命令、权限、持久化、后端或 Web 默认菜单行为。主菜单保留前 5 项,其余悬停/点击向上展开;类型与信息下沉卡片。产品已选定方案 A,边界与既有资源操作合同无冲突,按单里程碑实施。对应 Issue #409,已获得创建 Issue 与本地实施授权;推送、PR 与飞书写入仍需单独确认。
## 验收
- 动作顺序、禁用状态和回调保持一致,少于等于 5 项不出现更多。
- 更多支持鼠标移入浮层、点击、键盘、外部关闭与视口约束。
- 卡片类型、信息入口不触发拖拽;未选中卡直接看信息,换选不残留旧信息。
- 定向组件与工作台测试、类型检查、编码/文档索引/diff 检查通过;真实客户端视觉和触摸板手感单独验收。
## 产品结论与验收待办
正式实现采用方案 A;方案 B 不进入工作台,A/B 演示只留在忽略目录供本地参考。
完整工作台回归的 2 项失败已定位为新增信息按钮导致“版本 1”模糊匹配重复,改为精确查询资源选中按钮,完整重跑通过。
## 验收证据
- `appSurface.test.ts`450 项通过、20 项跳过。
- 收纳/卡片、Web 工具栏/卡片、资源类型实时链路、替换/重命名、浮层判据、动作可用性:178 项通过;追加真实工作台「更多滚轮不平移画布、Escape 仅收菜单、换选收起」与「跨卡片信息身份」2 项通过。
- 根目录类型检查与 AGC 类型检查(含 skill-pack / config 检查)通过;编码、文档索引与 diff 检查通过。
- 浏览器中真实组件预览已检查上方展开、执行回调后关闭、卡片信息入口;预览仅用演示数据,不替代真实 AGC 客户端。
- 剩余:真实客户端、原生保存对话框、触摸板操作人工验收。未推送,未创建 PR,未更新飞书。
@@ -0,0 +1,49 @@
# AGC 项目定时快照上传
Version: 1.0
Status: active
Date: 2026-09-17
Parent Spec: `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 的“2026-09-17 AGC 项目定时快照上传(agc-dev)”
## 目标
AGC 在项目打开期间按周期把用户项目增量上传到 OSS `agc-dev`,并在项目关闭时立即补一次同步;只上传内容发生变化的文件,重复内容不重复上传,远端缺少对应对象时才新建。
## 范围
- 客户端 `src-tauri/src/project_snapshot/``scan.rs` / `diff.rs` / `index.rs` / `transport.rs`):项目扫描、排除规则、增量索引与差异对比、上传编排、状态查询。
- 触发接线:工作区窗口存活周期定时器、工作区窗口关闭(`CloseRequested`);应用退出只做有界等待,不重复发起同步。
- 服务端 `POST /api/agc/project-snapshots/files``POST /api/agc/project-snapshots/manifest`:登录态鉴权、参数校验、私有前缀 OSS 写入、HEAD 幂等跳过。
- 目标 bucket 配置:`GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_*`,默认 `agc-dev`
- 契约:`shared-contracts::agc_project_snapshots` 新增请求/响应 DTO 与项目 ID、相对路径、摘要校验函数。
- 客户端增量索引:`<AppData>/project-snapshots/<projectId>/index.json`,按用户身份判等,换号后按冷启动全量重算。
- 排除口径:复用 `should_skip_project_snapshot_path`(整个 `.agent``.git`、构建与依赖目录、凭据目录、敏感后缀、符号链接与重解析点)。
## 不做
- 不做云端下载/恢复、跨设备合并、版本回滚。
- 不保留多版本历史:清单写入成功后回收不再被引用的旧对象,同一路径只保留当前内容。
- 不下发 bucket 生命周期策略;不做跨节点的用户级总量配额与计费口径。
- 不新增 SpacetimeDB 表或 procedure,不修改 `/api/external/v1` 与 External OpenAPI。
- 不在客户端暴露上传状态、时间线或入口按钮;状态只落本机诊断日志,排障走 native-only 命令。
## 验收标准
1. 首次同步上传项目内全部符合条件的普通文件;再次同步在无改动时上传 0 个文件。
2. 只修改一个文件时,差异集合恰好包含一个修改项;删除一个文件时上传集合为空且清单中不再包含该文件。
3. `(字节数, 修改时间)` 未变的文件复用已存摘要,不重复读取内容计算摘要。
4. 排除规则命中项(`.agent/runtime``.agent/logs``.git``node_modules`、构建产物、临时文件、符号链接)与超限文件进入跳过清单,不进入上传集合。
5. 任一次同步失败(非鉴权类)不推进本地索引,下一次触发重算并重试;鉴权/权限类失败不自动重试。
6. 同一项目的并发触发串行执行,不产生两路重复上传。
7. 工作区窗口关闭与应用退出都会触发一次同步,且关闭路径不因同步失败而阻塞退出超过超时上限。
8. 服务端拒绝越界 `projectId`、相对路径与摘要;相同摘要重复提交走跳过分支且不写入新对象。
9. 新增日志与错误文案不含 Access Token、AccessKey、绝对路径与项目内容。
10. 清单写入成功后,上一版清单里不再被引用的对象被回收;上一版清单不可读时整轮不删除任何对象。
11. 单项目超过 2 GiB 时客户端明确失败、服务端按 413 拒绝;超过服务端小时配额或 5 秒最小间隔时返回 429 且带 `Retry-After`
12. 同步期间被改写的文件既不上传也不推进索引,沿用上一轮记录,且不会被误判成删除。
## 依赖
- 现有 `platform_session`(用户身份与 Access Token)、项目 manifest(稳定 `project_id`)。
- 现有 `platform-oss`PUT/HEAD、私有访问)、`api-server` 登录态中间件与 `shared-contracts`
- 现有 AppData 私有文件写入与目录解析工具。
@@ -19,7 +19,7 @@ AGC 项目开发对话的显示与恢复只依赖两项输入:**项目对话
- 线上模型与提示词同源:条目与事件是 ts-rs 导出的 tagged enum,前端消费生成绑定(改 Rust 模型后跑 `cargo test export_bindings`);毫秒时间戳用 `#[ts(as = "f64")]` 对齐 Tauri JSON 通道的 `number`
- 条目身份归一:进队列前收敛成一个 `itemId`(工具条目在 `project.jsonl` 里带调用 id 与 response item id 两个值);历史切片另给 `firstItemId` 作为分页锚点,锚点始终是文件里的原始 item id。
- 思考正文流式:`item/reasoning/summaryTextDelta``item/reasoning/textDelta``item.delta{kind:"reasoning"}` 下发正文;plan 文本与命令输出仍只降级为活动状态。
- 历史读取以 `subscribe` 返回的 `lastCompletedItemId` 为首屏锚点,切片从**文件尾反向扫描**;后端切片只按原始条目切片,可见性判断留在前端聊天投影;一次翻页操作在前端连拉取页,直到合并后聊天投影出现新回合或 `hasMore=false`,每个操作上限 5 页。
- 历史读取以 `subscribe` 返回的 `lastCompletedItemId` 为首屏锚点,切片从**文件尾反向扫描**;首屏切片的新端边界就是这个锚点(含该条,命令参数 `throughItemId`),比锚点更新的条目只从运行态事件来;向后翻页用切片返回的 `firstItemId``beforeItemId`,不含锚点)。后端切片只按原始条目切片,可见性判断留在前端聊天投影;一次翻页操作在前端连拉取页,直到合并后聊天投影出现新回合或 `hasMore=false`,每个操作上限 5 页。
- 前端收敛为单一事件 reducer 与单一聊天投影;活动回合只由 `turn.started``turn.completed` 判定(事件不带 turn id,前端只有一个 `turnRunning` 布尔);`item.delta` 直接追加到运行态条目正文,不保留增量缓冲;`turn.completed` 把该回合运行态条目并入历史再清空。
- 删除前端对 Direct 回合事件、`turn-stream.jsonl``tool-calls.jsonl`、活动回合快照的读取,以及只服务这些读取的命令与状态。
@@ -42,6 +42,7 @@ AGC 项目开发对话的显示与恢复只依赖两项输入:**项目对话
- 未知 item 类型由 Rust 原样透传(只有类型与身份,Rust 侧留 TODO),当前由前端投影丢弃。
- 前端聊天卡片的工具形状是 `Omit<GameCreatorDirectToolCall, 'turnId'>``tool-calls.jsonl` 的持久化形状与 DirectRuntime 的写入保持不变。
- 「可显示」的判据取**前端回合反馈**:一次翻页操作连拉到「合并后聊天投影的回合数增加」为止。工具卡片与思考文本虽然能通过 `projectDirectThreadItem`,但可能整页落进已渲染回合的折叠「执行过程」,不构成用户可见反馈;口径只在 `directHistoryPaging.ts` 里实现一份,首屏与「显示更早」共用。
- 首屏切片的**新端边界**只认 `subscribe` 回执里的 `lastCompletedItemId`(含该条):回执到达之前不读首屏,也不退化成「取文件尾」;锚点缺失(订阅不可用 / 失败 / 历史为空)时才按文件尾取尾屏,`/history` 手动重读保持按当前文件尾取尾屏的恢复语义。
## 依赖与前置条件
@@ -55,6 +56,7 @@ AGC 项目开发对话的显示与恢复只依赖两项输入:**项目对话
- [ ] 回合进行中终止并重启进程后重进项目:已落盘的部分文本与工具卡片按原顺序出现,且界面不显示忙碌态。【待真实 app-server 运行时验收】
- [ ] 进程存活期间的页面重进(含切走再切回)能恢复运行中回合,并允许终止。【待真实 app-server 运行时验收】
- [x] 历史切片从文件尾反向回扫,锚点始终是文件里的原始 item id;切片内全部是不可显示条目时仍能继续向前,不出现锚点停滞。【自动化:`direct_project_history` 尾部回扫与分页锚点用例】
- [x] 首屏切片的新端边界是 `subscribe` 回执里的 `lastCompletedItemId`(含该条):比它更新的条目只从运行态事件来;锚点缺失时按文件尾取尾屏,订阅回执到达前不发首屏请求。【自动化:`cargo test agent::direct_project_history` 22 条(含 `through_item_id` 锚点用例)+ `directHistoryAnchorGate.test.ts` 10 条 + `project-development.suite.ts``anchors the first history page at the subscribe receipt instead of the file tail`(变异验证见下)】
- [x] 前端一次翻页操作自动连拉,直到合并后聊天投影出现新回合或 `hasMore=false`,每个操作上限 5 页;锚点不前进时立即停止,不空转。【自动化:`directHistoryPaging.test.ts` 8 条 + `project-development.suite.ts` 跨页同回合用例(变异验证见下)】
- [x] 聊天视图不再读取 `turn-stream.jsonl` / `tool-calls.jsonl` / Direct 回合事件 / 活动回合快照;`read_direct_turn_stream``read_direct_tool_calls` 命令已删除。`list_game_creator_direct_active_turns` **有意保留**:它服务首页跨页面的「运行中的项目」列表(`WorkspaceLauncher` / `directActiveTurns.ts`),不属于聊天框读路径。
- [x] 同一工具调用在实时与回读各只出现一张卡片(两个 id 空间按归一后的 `itemId` 对齐)。【自动化:reducer「先到定形、后到补空」合并单测 + 工具卡片渲染用例】
@@ -67,6 +69,7 @@ AGC 项目开发对话的显示与恢复只依赖两项输入:**项目对话
- 自动化(已跑):`cargo test direct_thread`25 条)、`cargo test direct_project_history`20 条)、`cargo test export_bindings`(生成绑定与工作区无差异)、`NODE_OPTIONS=--localstorage-file=… npx vitest run apps/ai-game-creator-shell/tests`125 files / 1676 passed / 17 skipped)、`npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit`exit 0)、ESLint 与 `prettier`
- 自动化(分页连拉,已跑):`npx vitest run apps/ai-game-creator-shell/tests/directHistoryPaging.test.ts`8 passed)、`npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts`470 tests / 453 passed / 17 skipped,含新增的 `keeps pulling earlier pages while the page only deepens the rendered turn`)、`npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit`(exit 0)。变异验证:把停止判据退化成「这一页有可渲染条目就停」后,跨页用例以 `Unable to find an element with the text: 更早的用户提问` 变红,即用户报的「点了没变化」。
- 自动化(首屏锚点,已跑):`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml agent::direct_project_history`22 passed)、`npx vitest run apps/ai-game-creator-shell/tests/directHistoryAnchorGate.test.ts`10 passed)、`npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts`475 tests / 457 passed / 17 skipped,含新增的 `anchors the first history page at the subscribe receipt instead of the file tail`)、`npx tsc -p apps/ai-game-creator-shell/tsconfig.json --noEmit`exit 0)、ESLint / prettier / `npm run check:encoding` / `npm run check:doc-index` / `git diff --check`。同一次全量里唯一失败是既有的 `edits the published runtime config without leaking API keys into chat``appSurface/runtime-settings.suite.ts`),与本次改动无关:把本次前端改动 stash 掉后它同样变红。变异验证:① 锚点闸门忽略「已消费」后,`directHistoryAnchorGate.test.ts` 的「同项目已消费返回 null」变红;② 首屏不等闸门后,`anchors the first history page at the subscribe receipt instead of the file tail``throughItemId` 缺失变红。
- 运行时(待补):真实 app-server 会话下的新回合、杀进程重开、页面重进、分页与终止。
- 边界:订阅过期、回执竞态、不可显示切片、无 `turn.completed` 的残回合、工具输出超长截断与脱敏。
- 环境注意:`rehype-highlight` 已装齐后 `ChatMarkdownMessage` / `AgentMessageContent` 转绿;Node 26 下 vitest 的 jsdom 用例需要 `--localstorage-file` 才能拿到 `window.localStorage``clientApi.test.ts` / `chatPromptPolish.test.tsx`),已记入 `docs/project-memory/shared-memory/pitfalls.md`
@@ -0,0 +1,49 @@
# 【里程碑】图集切片模式显式决策
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | proposed |
| Date | 2026-09-17 |
| Parent Spec | `docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md` |
## 目标
图标图集生成的切分模式不再具备任何隐式默认:平台入口、AGC 客户端自有流程、画板前端和所有 Agent / 工具说明都必须在请求中显式声明 `sliceMode`,并在同一份决策要求下选择 `connected-components``grid`
## 范围
- `sliceMode` 在图标图集生成入口成为必填;缺失、`null`、空字符串在副作用之前失败关闭。
- `grid``connected-components` 的参数自洽性:`grid` 必须带行列数,连通域不得携带网格尺寸。
- 决策要求写入主规范、公开契约、MCP / Agent 工具说明、Skill 与客户端自有路径,口径一致。
- 依赖平台默认值的自有调用方全部改为显式声明,且不新增兜底分支。
- 失败信息可执行:所有拒绝路径都带字段名与决策要求,`sliceCount` 的目标数量与上限语义在契约中写清。
- 端到端可证明:生成结果回显生效的切分声明与切片路径,严格图集在本地提交前校验回显与请求一致。
- 标准美术包显式声明四张 canonical 切片的切分声明,并在用途映射前校验切片数量正好为四。
## 不在范围内
- 不改动图集生成、去背、像素规整、拆分算法本身和切片上限。
- 不新增切分模式,不恢复已退役的固定网格契约。
- 不改动手动 `拆分图集` 入口的既有行为。
- 不为旧版本客户端保留过渡性兜底。
## 依赖与前置条件
- 无外部依赖;`sliceMode``gridX``gridY` 契约字段已在现行版本存在。
## 验收标准
- [ ] 省略 / `null` / 空字符串 `sliceMode` 的图集生成请求在定价、入队、扣费和 provider 调用之前返回 `400`,错误体含 `field=sliceMode`
- [ ] `grid``gridX``gridY`、越界、乘积超限时 `400``connected-components` 携带 `gridX`/`gridY``400`
- [ ] 公开契约、MCP / Agent 工具说明、Skill 与画板前端类型都要求显式声明,且不再声明任何默认值。
- [ ] AGC 客户端与画板前端的所有图集生成路径都显式传入模式,不再依赖平台兜底。
- [ ] 响应回显的 `sliceMode` 与请求声明一致;`grid` 时同时回显行列数。
- [ ] 拒绝信息包含字段名、允许取值与决策分支;`sliceCount` 契约上限与切片上限一致。
- [ ] 标准美术包声明 `sliceCount=4`,数量不符时在写入用途清单前失败关闭。
## 证据要求
- 自动化:平台定向测试(缺失、空串、连通域带网格尺寸、grid 缺维度、正常两种模式)、OpenAPI 契约测试、前端与 AGC 客户端定向测试。
- 运行时:本地 `api-server` smoke 提交一次缺字段请求,确认返回 `400` 且无扣费 / 入队记录。
- 边界:确认失败发生在引用解析、定价、入队与 OSS 副作用之前;确认响应字段与请求一致。
@@ -0,0 +1,57 @@
# 【里程碑】资源画布支持引擎资源预览
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | implemented; 真机 Cocos 工程验收待补 |
| Date | 2026-09-17 |
| Parent Spec | `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` |
## 目标
引擎(Cocos Creator 等)工程里已经存在的资源,可以被登记进 manifest,并作为资源画布的卡片**只读预览**:模型出缩略图、序列化资源出结构摘要、客户端解不了的容器出类型卡。
## 范围
- 登记与发现:模型、动画、场景/预制体/瓦片地图/地形、材质/特效、图集与字体配置、图像容器、引擎二进制容器。
- 生成目录过滤:Cocos 工程的 `library/``temp/``profiles/``local/` 不进发现结果(仅当当前目录确实是引擎工程时生效)。
- 画布准入与卡面:新增「引擎资源」三个卡面分支(模型 / 结构摘要 / 类型卡),并按扩展名给出引擎语义的类型角标。
- 模型放大预览:选中模型卡后用工具条的「3D 预览」打开浮层,给出与三维建模软件一致的视角操作(左键旋转 / 右键或中键平移 / 滚轮缩放 / 复位视角)。
- 只读预览通道:模型字节读取、引擎序列化文本读取(非 UTF-8 时降级)、图像容器原生转码。
## 不在范围内
- 引擎资源的编辑、派生、生成与回写(画布上仍是只读预览;「快速编辑」等现有工具链不承接这些类型)。
- 引擎私有格式的解码:压缩纹理(`.texture` / `.cubemap` / `.rt`)、Spine 二进制(`.skel`)、DragonBones 二进制(`.dbbin`)、PSD、EXR、裸 PCM 只出类型卡。
- 新增 manifest 契约字段(`cocosUuid` 等)、新增 canonical kind、新增画布分类轴:本轮复用既有 kind,避免旧客户端读不出 manifest(`deny_unknown_fields`)。
- 多文件 glTF`baseURI` 指向外部 `.bin` / 贴图)的渲染:卡面只渲染自包含的 `.glb` / 单文件 `.gltf` / `.fbx`,其余降级成类型卡(本轮选择放宽字节上限,不做资源路径解析)。
- 项目快照 / 版本指纹 / 检查点对生成目录的口径:本轮只过滤**发现结果**,不改快照语义。
## 依赖与前置条件
- 现有四道闸门:发现(`bridge_project_file_class`)、登记(`agent_local_project_file_type`)、画布准入(`projectedResourceKind`)、卡面读取(`resourceCardPreviewModel` + 原生预览命令)。
- 既有预览管线(可见性门禁、3 槽并发、LRU 预算、取消与失败语义)不改口径。
- 前端新增 `three` 运行时依赖(缩略图渲染器)与 `@types/three` 类型依赖。
## 验收标准
- [x] 表内引擎资源都能通过发现层拿到非空 `mediaType`,并按 `model` / `binary` / `image` / `document` / `audio` 等类别被筛出。
- [x] 表内引擎资源都能登记进 manifest,`kind` 只落在既有 canonical 词表内。
- [x] 表内引擎资源都能进入资源画布,并落到预期的卡面分支与类型角标。
- [x] 模型卡在渲染不可用时降级成类型卡,不冒泡成预览失败。
- [x] 引擎序列化资源的非 UTF-8 变体降级成类型卡,不报错。
- [x] 图像容器(`.tga` / `.tif` / `.tiff` / `.hdr`)由原生侧转码成 PNG 后按图片卡显示。
- [x] 引擎工程的生成目录不出现在发现结果里,同名目录在非引擎工程里照常列出。
- [x] 模型预览上限放宽到与通用媒体预览一致(32 MiB),超出后仍是类型卡而不是半渲染。
- [x] 模型卡可以放大到独立浮层里交互查看:拖动与滚轮都真实改变画面,「复位视角」回到打开时的取景。
- [x] 真机 Cocos 工程(含模型与动画资源)在客户端内滚动浏览的视觉验收。
## 证据要求
- 自动化:`cargo check``cargo fmt --check``cargo test --bin genarrative-ai-game-creator-shell cocos`(9 通过,含发现分类 / 登记 / 提示词 / 插件门禁);`cargo test … resource_inspect::tests`(7 通过:结构化预览与二进制降级、TGA→PNG 转码、模型媒体类型签名 + 既有文本 / SVG / 尺寸用例);`cargo test … agent_asset_import_tests`9 通过);`cargo test … local_project_file_listing_skips_engine_generated_directories_only_for_engine_projects``npx vitest run apps/ai-game-creator-shell/tests/resourceCocosPreviewContract.test.tsx`7 通过);`npx vitest run apps/ai-game-creator-shell/tests/resource apps/ai-game-creator-shell/tests/project`52 文件 / 543 用例);`npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts`450 通过 / 20 跳过);`npm run typecheck`app);`npm run check:encoding``git diff --check``npm run check:doc-index`
- 运行时(2026-09-17 完成):在真实客户端(`npm run agc` 起的 Tauri 客户端 + WebView2 CDP 驱动)里打开一个**本地临时 Cocos 夹具工程**(含模型 / 动画 / 序列化资源 / TGA / 引擎容器,不入库),经 `import_local_cocos_project` 与受控登记导入 10 个引擎资源后逐栏核对:模型卡出真实三维缩略图(`data-model-preview-status=ready`,卡面是渲染出来的 PNG data URL);`.anim` / `.scene` / `.prefab` / `.plist` / `.effect` 出结构摘要或文本预览;`.tga` 出真实图片(原生转码生效);`.texture` / `.bin` / `.pcm` 出「纹理 · TEXTURE」「二进制 · BIN」「音频片段 · PCM」类型卡。验收截图(本地留存,不入库):场景与环境栏(模型 + 摘要)、文档栏(类型卡)、待归类栏(TGA 转码)、音频栏、模型放大预览。
- 运行时(目录过滤):同一现场调 `list_local_project_files`,根级 `library/` / `temp/` / `profiles/` **0 条**,而 `assets/library/inside.bin` 正常列出。
- 运行时(模型交互):同一现场选中 `cube.glb` → 工具条出现「3D 预览」→ 浮层内 `data-model-viewer-status=ready`、canvas 已挂载;真实鼠标拖拽与滚轮各产生一次不同的渲染结果(三张截图 MD5 互不相同),点「复位视角」后的截图与打开时**逐字节一致**(MD5 `A6531331206456C666909F80384B53AD`)。证据:`agc-model-viewer-initial.png` / `agc-viewer-rotated.png` / `agc-viewer-zoomed-out.png` / `agc-viewer-reset.png`
- 运行时(卡面几何):同一现场量 `cube.glb` 卡的卡片盒 / 卡面 / 类型角标 / 缩略图四个矩形的 `x/y/w/h`,在**指针移开、悬停、选中**三种状态下**完全一致**(此前悬停会把卡面四边各吃 1px);缩略图渲染尺寸为 `356x252`(布局盒 178×126 × 2 超采样),绘制比 1.4127 与卡面盒 267×189 的比例一致,缩放后不发虚、不裁切。证据:`agc-fix-hover.png` / `agc-fix-selected.png`
- 边界:`.meta` 仍然只可发现、不可登记;引擎工程的 `library/` / `temp/` / `profiles/` / `local/` 不进发现结果,同名目录在非引擎工程里照常列出;`.pcm` / `.texture` 等容器不发起读取。
- 未验证:真机客户端内的视觉验收(本机没有可用的 Cocos 工程实例)。Rust 侧其余仍用 `tempfile::tempdir()` 的既有用例在本机仍被 `Windows 安全对象不属于当前用户` 阻断(本次把预览与导入这两个模块的用例改用工程自带的 `crate::tests::canonical_test_tempdir`,它们已能真实跑通)。
@@ -3,6 +3,40 @@
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.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 时,游戏开发与策划两条链路都不按设置执行。
- 决策:删除这三处区间限制,`maxRetries` 严格等于允许的物理重试次数(显式 `0` 仍表示不重试)。运行档位只保留「上游 400 是否算瞬态错误」的判定(autonomous 档位才重试 400),不再改写次数:`AGENT_RUNTIME_PROVIDER_TRANSIENT_RETRY_LIMIT``AGENT_RUNTIME_AUTONOMOUS_PROVIDER_TRANSIENT_RETRY_FLOOR``AGENT_RUNTIME_AUTONOMOUS_PROVIDER_TRANSIENT_RETRY_LIMIT``AGENT_RUNTIME_AUTONOMOUS_PROVIDER_UPSTREAM_400_RETRY_LIMIT` 四个常量与 `game_creator_agent_runtime_provider_transient_max_retries_at` 一并删除,换成返回 `{max_retries, retry_upstream_400}``game_creator_agent_runtime_provider_transient_retry_policy_at`
- 边界:只改重试次数的来源。瞬态错误分类(`timeout / connectivity / transport / 408 / 429 / 5xx / empty-response / deserialize / stream-unavailable`,以及 autonomous 档位下的 400)、`retryBackoffMs` 指数退避(仍封顶 30s)、durable retry sidecar / lifecycle / `-transient-N` slot 身份、cancel / steer / goal 门禁、耗尽后的 reconciliation 口径与「泥点不足不重试」都不变;前端设置面板与 `agentLlm.<agent>.maxRetries` 契约不变。
- 验证方式:`provider_transient_retry_` 7 项中重写后的档位用例与 upstream-400 用例通过(断言 `maxRetries` 直取设置值、400 与其它瞬态共用同一预算),`provider_retry_` 其余 26/28 通过;该组 2 项(`provider_transient_retry_transport_failure_closes_then_stable_retry_succeeds``provider_transient_retry_backoff_is_exponential_and_capped_at_thirty_seconds`)与 `provider_retry_waiting_final_reply_*` 2 项在本机改动前后同为失败(`stash` 基线复跑确认,现象是等待自动重试唤醒超时)。本机串行全量套件另有既有环境失败(`tempfile::tempdir()` 归属校验、缺少 npm 构建产物、Windows 启动失败 MessageBox 阻塞 `startup_log_slot_fail_without_path...`);抽查其中 5 项在 `stash` 基线上同样失败,与本次改动无关。仓库 `cargo fmt --check``npm run check:encoding``git diff --check` 通过。
- 关联文档:[AI游戏创作智能体App实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)、[踩坑记录](pitfalls.md)。
## 2026-09-17 AGC 抠图提交使用远端画布项目身份
- 背景: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
- 背景:ADR「首屏历史由 `subscribe` 返回的 `lastCompletedItemId` 锚定,再取最近切片」只落了一半。`DirectThreadManager` 是搬运层,内存里没有「已完成条目」的锚点,`subscribe` 一律返回 `last_completed_item_id: None``commands.rs``subscribe_direct_project_thread` 在为空时用 `read_direct_project_last_item_id_at` 从磁盘回填,所以线上回执里的值是真的(订阅那一刻文件里最后一条可显示条目的原始 item id)。前端侧:首屏一直在 `loadProjectConversation` 里用 `beforeItemId: null` 直接取文件尾一屏,`lastCompletedItemId``1b40f030e` 起不再被任何代码读取。
- 决策(锚点语义):首屏切片的新端(较新一侧)边界就是这个锚点,**含锚点条目本身**;切片命令新增 `throughItemId` 参数表达「取到这条为止」。比锚点更新的条目只从运行态事件来,历史切片与实时流因此不重叠(原来的文件尾读取会把订阅回执之后才完成的条目也拉进历史,与运行态事件同 id 重叠,只靠前端合并兜住)。
- 决策(读取时机):订阅回执到达之前不读首屏,也不退化成「取文件尾」;锚点缺失(订阅不可用 / 失败 / 历史为空)时才按文件尾取尾屏。`/history` 手动重读保持「按当前文件尾取尾屏」的恢复语义,不锚定。
- 决策(翻页不变):向后翻页仍用切片返回的 `firstItemId``beforeItemId`(不含锚点),`hasMore` 与连拉口径不变。
- 影响范围:`agent/direct_project_history.rs`(切片锚点 + `through_item_id` 参数)、`commands.rs``read_direct_project_history_slice` 命令参数)、AGC 前端首屏读取接线与测试骨架。**未改**:DirectRuntime 的 `turn-stream.jsonl` / `tool-calls.jsonl` 写入与进度事件、`list_game_creator_direct_active_turns`、SpacetimeDB 与 HTTP 契约。
- 验证方式(已跑):Rust 侧 `cargo test agent::direct_project_history`(22 passed,含「窗口取到锚点那条、排除比锚点更新的条目、`beforeItemId``throughItemId` 互斥报错」三类用例);前端 `npx vitest run .../directHistoryAnchorGate.test.ts`10 passed)与 appSurface 的 `anchors the first history page at the subscribe receipt instead of the file tail`(全量 475 tests / 457 passed / 17 skipped;唯一失败 `edits the published runtime config without leaking API keys into chat` 与本次改动无关,stash 掉本次前端改动后同样变红);`tsc` / ESLint / prettier / `check:encoding` / `check:doc-index` / `git diff --check` 全绿。变异验证:闸门忽略「已消费」、首屏不等闸门两处改动各自让对应用例变红。
- 关联文档:[ADR](../../adr/【ADR】DirectProject对话历史单一事实源-2026-09-16.md)、[里程碑](../plans/【里程碑】DirectProject聊天真相源收敛-2026-09-16.md)、[实施计划](../plans/【实施计划】DirectProject聊天真相源收敛-2026-09-16.md)。
- 决策:`sliceMode` 在图标图集生成入口成为必填字段且不保留任何默认值。省略、`null` 或空字符串必须在引用解析、定价、入队和 provider / OSS 副作用之前返回 `400``field=sliceMode`);`grid` 必须同时提供 `gridX`/`gridY``connected-components` 不得携带网格尺寸,二者矛盾同样在副作用前失败关闭。
- 决策要求:只有用户或需求明确要求等分网格、固定槽位或指定行列数时才使用 `grid`,且行列数必须来自该需求;自由排布、数量不定或只要求一张图集时显式传 `connected-components`,需要约束素材张数时用 `sliceCount`,不得用网格参数表达张数,也不得用固定 `2×2` 表达“四类素材”。
- 影响面:平台两个图集生成入口(`/api/editor/...``/api/external/v1/editor/...`)、OpenAPI、画板 Agent 工具、画板前端提交计划、AGC 客户端 MCP 工具说明与桥接校验、AGC 原生工具 schema 与观察器、AGC Skill 与外部编辑器 Skill。
- 迁移影响:省略 `sliceMode` 的旧调用方(含已发布但未更新的 AGC 客户端和第三方外部 API 调用方)会在图集生成上收到 `400`;本次同时把仓库内自有调用方改为显式声明,不为旧客户端保留兜底分支。
- 错误可执行性:缺失、空白、未知取值都以 `400` + `field=sliceMode` 返回允许取值和决策分支,`grid` 缺维度提示 `sliceCount` 才是张数约束;`sliceCount` 的公开契约上限与切片上限统一为 `256`(识别数量与目标不一致返回 `422` 并回报实际数量)。
- 反馈闭环:图集生成结果回显生效的 `sliceMode`/`gridX`/`gridY``slicePaths`;严格图集提交前必须证明平台回显的模式(`grid` 时含行列数)与请求显式声明一致,缺失或不一致一律失败关闭。
- 标准美术包:客户端显式声明 `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` 不是已登记资源时只报「不属于当前项目已登记资源」,模型会原地重试而不会先登记。
@@ -13,6 +47,21 @@
- 验证方式:新增 `tool_prompt_limits_agree_with_the_client_authority`(四个 kind 的 schema 上限、MCP 校验与客户端权威口径同数字,超限文案带真实上限)、`bridge_resource_prompt_limits_follow_the_client_authority`(工具桥侧同类门禁,含图片编辑的 32000 边界)、`edit_image_tool_reaches_the_platform_image_edit_route``background_music_tool_reaches_the_platform_audio_route`(MCP 工具层 → 真实工具桥 → 假平台,断言 `/api/editor/images/edits``/api/editor/audios/background-music/generations` 的路径、Bearer、Idempotency-Key、正文与派生资源落盘,图片编辑正文不得回填 assetKind)、`background_music_prompt_over_the_limit_is_rejected_before_any_bridge_call`(超限在桥请求之前失败)、`unregistered_source_reports_the_registration_follow_up_tools``agent::direct_tools_mcp` 22 passed、`agent::skill_pack` 4 passed、`agent::direct_tool_bridge` 17 passed(7 条本机既有失败见下)、`npm run agc:skill-pack:check``skill-pack:test` 通过。本机 `tempfile::tempdir()` 归属校验失败导致的既有用例(`project::resource_editor` 45 条、`agent::direct_tool_bridge` 7 条)在本轮改动前后**同为失败**(stash 基线复跑确认),与本次无关。
- 关联文档:[AI游戏创作智能体App实施计划](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md)、[踩坑记录](pitfalls.md)。
## 2026-09-17 资源画布支持引擎资源只读预览
- 背景:Cocos Creator 工程里已有的引擎资源(模型、动画、预制体、材质、图集、压缩纹理…)此前在发现层就止步:`.glb` / `.prefab` / `.anim` / `.texture` 等扩展名既不可登记,也不进资源画布,工程导入后画布上只看得到位图、音频与脚本。
- 决策(范围):本轮只做**只读预览**。引擎资源可以被发现、登记进 manifest、进入资源画布并按类型出预览;不承接编辑、派生、生成与回写,也不解码引擎私有容器(`.texture` / `.cubemap` / `.rt` / `.skel` / `.dbbin` / `.psd` / `.exr` / `.pcm` 只出类型卡)。
- 决策(契约):**不新增 manifest 契约字段、不新增 canonical kind、不新增画布分类轴**。引擎资源复用既有 kind(模型/场景/预制体/地形 → `scene`,动画 → `character-animation`,材质/特效 → `code`,图集与容器 → `document`,图像容器 → `image`,裸 PCM → `audio`),避免 `deny_unknown_fields` 让旧客户端读不出整份 manifest;引擎语义由**类型角标**(模型 / 动画 / 材质 / 图集 / 纹理…)表达,不复用「图片 / 文档」。
- 决策(卡面与读取):新增三个卡面分支 —— `model``.glb` / `.gltf` / `.fbx`,由单例 WebGL 渲染器出缩略图,整页只保留一个 WebGL 上下文)、`structured`(Cocos 序列化资源的结构摘要,非 UTF-8 变体降级成类型卡而不是报错)、`binary`(不发起任何读取,不占预览读取槽)。图像容器(`.tga` / `.tif` / `.tiff` / `.hdr`)先在原生侧转码成 PNG,再走既有图片预览链路。
- 边界:`.meta` 等引擎导入侧车文件仍然只可发现、不可登记;发现层新增 `model` / `binary` 两个**发现类别**(不是 manifest kind)。多文件 glTF(外部 `.bin` / 贴图)与超限模型降级成类型卡;预览管线既有语义(可见性门禁、3 槽并发、LRU 预算、取消与重试口径)不变。
- 决策(发现过滤):引擎工程的 `library/` / `temp/` / `profiles/` / `local/` 不再进发现结果,判定收窄为「工程根直接子目录 + 当前目录确实是 Cocos Creator 工程(`package.json.creator.version` + `assets/`)」。**不放进全局跳过表**:这些名字在别的工程里可能是真实源码目录。过滤落在唯一一份目录遍历(`list_local_project_files_at`)上,因此 Agent 发现、前端资源树与提示词里的未登记清单同步生效;项目快照 / 版本指纹 / 检查点的语义本轮不动。
- 决策(模型上限与缓存键):模型预览字节上限从 16 MiB 放宽到 32 MiB,与通用媒体预览取同一上限(base64 载荷约 43 MiB);超过上限仍是类型卡,不做半渲染。缩略图缓存键改用**稳定身份**(资源身份 + 路径 + 字节数)而不是 blob URL:预览缓存淘汰后重读同一模型不会重新解析 + 重新渲染。要再往上放宽,必须先把预览载荷换成 Tauri 原始字节通道。
- 决策(模型放大预览):模型卡可以在工具条打开「3D 预览」独立浮层,浮层内是**交互式视角**(OrbitControls:左键旋转 / 右键或中键平移 / 滚轮缩放 / 复位视角),与三维建模软件同一套操作习惯。加载 / 取景 / 释放三条口径抽到共用模块 `resourceModelScene`,缩略图与浮层不许各写一套;画布上的卡片仍然是静态缩略图并继续共用**唯一**一个 WebGL 上下文,只有打开浮层时才新建交互式上下文,关闭即 dispose。浮层仍是只读预览:不写 manifest、不参与编辑与派生。
- 验证:`cargo check``cargo fmt --check` 通过;`cargo test … cocos` 9 条通过(发现分类 / 登记 / 提示词投影 / 插件门禁);`cargo test … resource_inspect::tests` 7 条通过(含结构化预览与二进制降级、TGA→PNG 转码、模型签名判定);`cargo test … agent_asset_import_tests` 9 条通过;生成目录过滤用例通过(引擎工程过滤、非引擎工程不过滤);`tests/resourceCocosPreviewContract.test.tsx` 7 条通过(含模型卡渲染不可用时的降级、稳定缓存键);`npx vitest run apps/ai-game-creator-shell/tests/resource apps/ai-game-creator-shell/tests/project` 52 文件 / 543 用例通过;`appSurface.test.ts` 450 通过 / 20 跳过;app `tsc --noEmit``npm run check:encoding``git diff --check``npm run check:doc-index` 通过。
- 真机验收(2026-09-17 补):在真实客户端内打开一个含模型 / 动画 / 序列化资源 / TGA / 引擎容器的 Cocos 夹具工程,模型卡出三维缩略图、序列化资源出结构摘要、TGA 出转码后的真实图片、引擎容器出类型卡;同现场 `list_local_project_files` 对根级 `library/` / `temp/` / `profiles/` 返回 0 条、`assets/library/` 正常列出。证据见里程碑文档「证据要求」。
- 未验证:本机其余仍用 `tempfile::tempdir()` 的既有 Rust 用例继续被 `Windows 安全对象不属于当前用户` 阻断(与本决策无关;根因与手工夹具相同,已记入 `pitfalls.md`)。
- 关联文档:`docs/project-memory/plans/【里程碑】资源画布支持引擎资源预览-2026-09-17.md`
## 2026-09-16 抠图模式与背景色契约
- External v1 抠图和 AGC `agc_remove_background` 支持 `complex`(语义分割识别前景)与 `flat`(纯色背景抠图);明确纯色背景优先 flat,模式缺省仍为 complex,主站前端保持现有行为。
@@ -8197,8 +8246,8 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
## 2026-08-24 AGC Direct 抠图语义工具
- 决策:将 External v1 `/api/external/v1/editor/images/background-removals` 通过 `agc_remove_background` 加入受控 `agc_tools`。工具只接受当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端负责正式 resourceId、画布/素材目录、稳定 operation/idempotency 身份、权限和错误脱敏,不向 Codex 暴露内部 BgFilter worker、凭据或任意 API。
- 约束:异步结果只投影有界队列状态,不允许模型自行构造源 URL 或在不确定提交后更换请求身份;External v1 负责 API Key、幂等接收与统一 operation 查询,客户端不得绕过该契约。
- 决策:将抠图能力通过 `agc_remove_background` 加入受控 `agc_tools`。工具只接受当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;普通登录态使用账号鉴权的 `/api/editor/images/background-removals`ExternalDeveloper 模式使用 External v1 `/api/external/v1/editor/images/background-removals`客户端负责正式 resourceId、画布/素材目录、稳定 operation/idempotency 身份、权限和错误脱敏,不向 Codex 暴露内部 BgFilter worker、凭据或任意 API。
- 约束:异步结果与恢复语义以本文「2026-09-17 AGC 抠图接入本地资源编辑恢复闭环」决策为准。不允许模型自行构造源 URL 或在不确定提交后更换请求身份;两种路由都接收客户端稳定幂等身份,External v1 继续负责 API Key、幂等接收与统一 operation 查询,客户端不得绕过该契约。
## 2026-08-24 资源详情动作、空态滚动与最终图多步恢复
@@ -8834,6 +8883,16 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 决策:采用后台继续运行语义。Direct 回合由进程内项目身份锁持有,页面离开不取消;重进项目通过活动回合只读快照与 Thread Manager bootstrap/consume 恢复忙碌态和进度。左上角面板复用同一快照列出正在运行的 Direct 项目并支持进入。
- 边界:快照不写项目文件、不进入公共 API、不跨应用重启恢复;读取失败保留上一份结果并单独提示,不改写成权限或审批失败。身份锁排他性、付费身份和项目写锁不变。
## 2026-09-17 AGC 模板库落在 oss://agc-dev/templates/
- 背景:AGC 需要「真·游戏模板」库,让用户能浏览、筛选、下载模板并直接由模板创建项目,且模板内容更新不依赖客户端发版。
- 决策:模板库固定在 bucket `agc-dev`endpoint `oss-rg-china-mainland.aliyuncs.com`)的 `templates/` 前缀,公共读;**不再嵌套 `agc/` 这一层**`agc/` 继续只放客户端安装包与 `latest.json` 更新通道)。目录为 `templates/index.json` + `templates/v1/<templateId>/{template.json,template.zip,cover.png}`zip 根等于 AGC 项目根。
- 决策:清单 schema 为 `agc-template-library.v1`,每条模板带 `tags``coverKey/coverWidth/coverHeight/coverSha256``zipKey/zipSizeBytes/zipSha256``templateVersion`;客户端只信「受信任 OSS 主机 + 对象键」自行拼 URL,清单里的地址字段不参与请求。
- 决策:客户端缓存与安装根为 `<app_data>/templates/``index.json` 缓存 + `installed/<id>/<version>/`,安装完成才写 `installed.json`);建项目在 `<app_data>/projects/` 下走既有自动工作区规则,先铺模板文件再补 `.agent` 清单。
- 决策:模板库首页推荐位替换原「灵感推荐」本机图片目录(已删除 `InspirationGallery.tsx``assets/inspiration/`);左侧导航新增模板库入口,打开独立全屏页。`tauri.conf.json``img-src` 放行受信任 OSS 主机用于封面图。
- 关联规范:`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`
@@ -8857,3 +8916,38 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 边界:Deploy 阶段在远端 dev / release agent 执行,不受该上限约束。调整只动这两处:`systemctl set-property / revert jenkins.service``docker update --cpus=<n> gitea-runner` 加同步 compose(备份 `/opt/gitea-stack/compose.yml.bak-<时间戳>`)。
- 验证:限速后 `Genarrative-Full-Build-And-Deploy` #289 / #290 SUCCESS;采样期 Jenkins 峰值 10.2~10.5 核、限流不足 2s(可忽略),runner 峰值 12.07 核且持续出现 throttling,整机回落到 2.6%~19.8%。
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)。
## 2026-09-17 AGC 抠图接入本地资源编辑恢复闭环
- 背景:`agc_remove_background` 原先只提交 `/api/editor/images/background-removals` 并返回 `queued`,没有轮询远端任务、下载完成媒体或写入本地 manifestBgFilter 已成功处理但 Agent 因此永远只能看到受理回执。
- 决策:抠图作为 `LocalProjectResourceEditKind::BackgroundRemoval` 接入现有资源编辑账本,模式和背景色写入 operation 身份;提交后复用同一套轮询、结果下载、staging、manifest 提交和恢复逻辑。已有账本优先恢复,禁止在未知结果时换 operation/idempotency 重发。
- 边界:主站异步队列、BgFilter 和 SpacetimeDB schema 不变;Agent 只获得本地完成资源和安全身份投影,不接触内部 worker 或凭据。
- 恢复:已受理任务中断后按原 operation 续查;提交结果不确定时保留账本并人工对账。升级前无账本的 queued 回执不自动迁移或重发,已有远端成果通过正式素材导入恢复。
- 展示:恢复面板按账本显示抠图模式,平面背景模式同时显示已记录的自动背景色或颜色值,缺失字段不推断默认值,帮助区分同名待处理任务。
## 2026-09-17 Jenkins 公网入口 jenkins.genarrative.world 复用 router 反向隧道口径
- 背景:Jenkins controller 实际与 Gitea 同机运行在 `genarrative-station``jenkins.service``--httpPort=8080 --prefix=/jenkins``JENKINS_HOME=/var/lib/jenkins`),此前只有内网入口 `http://192.168.35.82:8080/jenkins/``router.genarrative.world` 已有「dev Nginx → dev loopback → station 反向隧道」的成熟口径。
- 决策:沿用 router 口径,不新增网关组件。`genarrative-station``gitea-reverse-tunnel.service` 增加 `-R 127.0.0.1:18085:127.0.0.1:8080`dev loopback `18085` → station Jenkins `127.0.0.1:8080`);dev 新增 `/etc/nginx/conf.d/jenkins.genarrative.world.conf``80` 只做 ACME webroot 与 `301``443` 用 Certbot 证书反代 `http://127.0.0.1:18085` 并保留 `Upgrade` / `X-Forwarded-*`;证书按 router 口径用 `certbot certonly --webroot -w /var/www/html -d jenkins.genarrative.world --renew-hook 'systemctl reload nginx'` 申请。
- 路径口径:Jenkins 固定 `--prefix=/jenkins`,域名根路径 `302``https://jenkins.genarrative.world/jenkins/login``/jenkins` 补斜杠,其余未带前缀路径 `302``/jenkins$request_uri`;证书不复制到 Pingora 私有目录,公网 `80/443` 仍由 dev Nginx 监听。
- 边界:本次只暴露 HTTP/HTTPS UI`slaveAgentPort` 保持 `-1`agent 继续由 Jenkins 用 SSH launcher 连 dev / release),不改 Jenkins `jenkinsUrl` 与鉴权策略;Jenkins 登录页因此进入公网可达面,访问控制继续依赖 Jenkins 自身账号体系。
- 验证:dev `nginx -t``systemctl reload nginx` 通过;`curl -sI https://jenkins.genarrative.world/` 返回 `302 /jenkins/login``/jenkins/login` 返回 `200`、登录页静态资源 `200``http://` 入口 `301`Let's Encrypt 证书 `CN=jenkins.genarrative.world` 到期 `2026-12-16`;公网探测 `82.157.175.59` 仍只开放 `80/443/22`
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)。
## 2026-09-17 预览部署控制面公网入口 build.genarrative.world
- 背景:多人内网预览控制面(`preview-deployer-server` + `shared/Genarrative-Preview-Deployer` Job)此前只在内网 `http://192.168.35.82/build/` 提供,2026-08-15 决策明确「不配置公网域名」;本次要求给它加公网入口。
- 决策:沿用 router / Jenkins 同一口径新增 `build.genarrative.world` 作为控制面公网入口,并把 `preview.genarrative.world` 作为 `*.preview.genarrative.world` 规划的父域名先落一张落地页(实例本身仍只在内网)。station `gitea-reverse-tunnel.service` 增加 `-R 127.0.0.1:18086:127.0.0.1:8410`dev 新增 `/etc/nginx/conf.d/build.genarrative.world.conf``80` ACME+`301``443``/build/``/api/preview-deployer/` 反代到 `127.0.0.1:18086``/``/build/`,公网侧 `proxy_cookie_flags ~ secure`)与 `/etc/nginx/conf.d/preview.genarrative.world.conf`(单域名证书 + 落地页)。
- 白名单:控制面按 `Host` 精确匹配、对非 GET 的 `/api/*` 精确匹配 `Origin`,因此同步改为 `GENARRATIVE_PREVIEW_DEPLOYER_ALLOWED_HOSTS=192.168.35.82,build.genarrative.world``GENARRATIVE_PREVIEW_DEPLOYER_ALLOWED_ORIGINS=http://192.168.35.82,https://build.genarrative.world``GENARRATIVE_PREVIEW_DEPLOYER_SECURE_COOKIE` 保持 `false`(内网 HTTP 入口继续可用),公网 cookie 的 `Secure` 由 dev nginx 强制。重启 `genarrative-preview-deployer.service` 会清空内存会话,内网用户需重新输入口令。
- 边界:本次只暴露控制面(触发/查看构建、卸载),预览实例不暴露;`preview.genarrative.world` 没有通配记录,实例地址仍是内网 `http://192.168.35.82:84xx`,控制面页面展示的 `webUrl` 也仍是内网地址。要变成公网实例地址,需要 `*.preview.genarrative.world` 通配证书(只能 DNS-01)、station 侧按 Host 分发和页面 URL 口径改造。
- 验证:`nginx -t` 与 reload 通过;`https://build.genarrative.world/` `302 → /build/``/build/` `200`、SPA 资源 `200``/api/preview-deployer/session` 返回 `{"authenticated":false}`;错误或缺失 `Origin` 的 POST `403`、错误口令 `401`、字段名不符 `422`;两个新域名证书到期 `2026-12-16`;内网 `Host: 192.168.35.82``200`、未知 Host `403`jenkins / dev / git 入口回归正常。
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)、[Jenkins容器预览部署控制面技术方案](../../technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md)。
## 2026-09-17 预览控制面增加公网预览地址口径(代码已实现,待随控制面发布)
- 背景:控制面页面此前只展示内网地址 `http://192.168.35.82:<webPort>`;公网通配域名 `*.preview.genarrative.world` 已解析到 dev,需要页面能显示对应的公网入口。
- 决策:公网地址由控制面自己派生,不接受 Jenkins 产物或状态文件提供的任意地址。`preview-deployer-server` 新增可选配置 `GENARRATIVE_PREVIEW_DEPLOYER_WEB_DOMAIN`(如 `preview.genarrative.world`,只接受小写字母、数字、短横线和点号,不带协议与端口),并在公开 DTO 新增 `webPublicUrl`:仅当记录已有内网 `webUrl` 时取 `https://<instanceId>.<WEB_DOMAIN>`(实例 ID 仍由分支派生,形如 `preview-<16位hex>`),卸载时与 `webUrl` / `webPort` 一起清空;状态文件里的旧值在加载时被重新派生覆盖。
- 前端:`apps/preview-deployer-web` 在存在公网地址时把「打开公网预览」作为主入口,内网地址降级为次级链接;未配置时行为与之前一致。
- 上线依赖(本次未完成):`*.preview.genarrative.world` 通配证书(Let's Encrypt 通配只能走 DNS-01,域名在 DNSPodcertbot 无官方插件,需要 DNSPod API Token 配合 acme.sh)、station 侧按 Host 分发到 `84xx` 端口、dev 通配 vhost 与隧道;控制面本体需在 station 用 `scripts/deploy/preview-deployer-install.sh` 重建发布。
- 验证:`cargo test -p preview-deployer-server`13 项)、`apps/preview-deployer-web` vitest(13 项,含新增公网地址用例)、`npx tsc --noEmit``npm run preview-deployer:web:build``PREVIEW_DEPLOYER_WEB_BASE=/build/`)、`npm run check:preview-deployer``npm run check:encoding``git diff --check` 全部通过。
- 关联文档:[开发运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)、[Jenkins容器预览部署控制面技术方案](../../technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md)。
@@ -51,6 +51,8 @@
## 验证路由
AGC 运行时配置默认值调整时,同步核对 Rust 默认值、分发配置模板、设置弹窗默认草稿和 `runtime-settings.suite.ts` 的恢复默认断言;显式传入旧值的配置读取用例仍验证原值保留,不批量替换测试数据。
AGC 测试构造单 HTML 项目时,必须在初始化之前写入 HTML,避免自动建立 npm 工程;npm 预览和导出测试应提供 dist 产物。已有图片生成 pending/operation 属于持久化恢复合同,修改工具默认参数后仍须验证旧动作恢复不重复提交、不因默认值变化被误判为新意图。
SpacetimeDB 任务统一先读取 `.codex/skills/genarrative-spacetimedb/SKILL.md`;该项目适配层按需调用已安装的官方 `spacetimedb` 插件 skill,插件提供通用 SDK/CLI/MCP 知识,项目 skill 负责 Genarrative 架构边界和验证门禁。
+30 -2
View File
@@ -1,5 +1,13 @@
# 踩坑与排障记录
## AGC 空快照测试必须等待请求完成
`waitFor(() => expect(activeTurns).toEqual([]))` 在 Hook 初始状态就能成功,不能证明首次异步读取已经完成。引用稳定性回归应显式控制 Promise 完成,并同时检查首次空响应与禁用后的引用;快照签名初值必须与初始空数组一致。窗口同步测试应验证未变化状态不重复发布,不能依赖一次多余的空态更新。
## AGC Windows 开发态首次页面加载缓慢
Vite 默认监听应用根下的 Rust `src-tauri/target`,构建产物较多时会创建大量 Windows 文件监听器。AGC 配置通过 `server.watch.ignored: ['**/src-tauri/target/**']` 排除此目录,不关闭业务源码、CSS、共享组件监听或 HMR。排查时区分后端就绪、Vite 扫描和原生窗口首绘;监听目录回归不能代替实机首绘测量,验证入口见本地开发运维文档。
## 2026-09-17 AGC 输入盒的「推理档」弹层被祖先裁切:要放开裁切而不是挪弹层
- **现象**:窄窗口下(视口 ≤1000px 时右侧对话面板只有 280px 宽)点开输入盒右下角的「推理档」,弹层是个**空盒子**:档位文字(默认 / 低 / 中 / 高 / 最高)整片看不见,只剩一个方框。
@@ -8,6 +16,16 @@
- **易错点**:① 把弹层改成 `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`」。
- **模型缩略图看起来被拉伸 / 被裁**:三个原因叠在一起 —— ① 缩略图渲染器的 `PerspectiveCamera` 是单例复用件,`aspect` 默认 `1` 且从没更新,方形投影被塞进宽扁缓冲;② 卡面里 `width/height: 100%` 的图片挂在 `place-items: center` 的网格里,网格项高度会退化成"按内容定高"(百分比高度解析成 `auto`),图片按自然比例长过卡片、被 `overflow: hidden` 裁掉,看起来就像被拉伸;③ 栏目画布用 `transform: scale(var(--resource-section-zoom))` 放大(真机 1.5 倍),而 `ResizeObserver` / `offsetWidth` 只看**布局盒**,缩放变化既不触发 observer,按布局尺寸 1:1 渲染的图也会被放大到发虚。修法:渲染前设 `camera.aspect`;图片改 `position: absolute; inset: 0` + `object-fit: contain`;渲染尺寸取 `offsetWidth/offsetHeight × 2` 超采样(上限 1024),并把实际渲染尺寸暴露到 `data-model-render-size` 方便排障。
## 2026-09-17 从提权会话创建的目录会被 AGC 的 Windows owner 校验直接拒绝
- **现象**:在 Codex 会话里手工创建的工程目录(例如 `C:\Users\<user>\Documents\Codex\...\cocos-preview-fixture`),用客户端打开时报 `Windows 安全对象不属于当前用户:<path>`Rust 侧同样用 `tempfile::tempdir()` 建夹具的用例也成片失败在同一句上。
- **原因**:这个 shell 以管理员身份运行,`New-Item` / `tempfile` 新建目录的 owner 是 `BUILTIN\Administrators`,而 AGC 的校验要求 owner 等于当前用户 SID(`KDLETTERS\<user>`)。`Get-Acl <path> | Select Owner``whoami` 一比就能定性;同一台机器上由客户端自己创建的目录 owner 正确,所以「客户端自己建的项目能用、手建的不能用」。
- **处理**:手工夹具先 `icacls <path> /setowner "<DOMAIN>\<user>" /T`Rust 用例改用工程自带的 `crate::tests::canonical_test_tempdir(prefix)`(它会 canonicalize 并重置目录 owner),不要直接用 `tempfile::tempdir()`。判「用例失败与本改动无关」时,先确认失败信息是不是这一条。
## JSON 卡片显示与 UI 编辑能力必须同源
@@ -17,6 +35,8 @@ JSON 的文本读取分支不等于卡面应该展示原始 State 摘要。卡
工作台向窗口标题栏发布运行项目时,若 effect 依赖普通函数派生的回调,发布 Context 会重新渲染工作台,进而再次发布并清理,形成更新深度循环。转发入口须稳定,并在提交阶段更新实际处理器引用;发布数据变化与卸载清理分开。回归测试必须组合真实窗口 Provider 和工作台消费者,只有独立画布测试无法覆盖这条反馈链;回归时用有界发布次数阻止测试失控。画布快速操作时暴露的更新深度错误,也须检查外层状态同步,不能直接归因于滚轮频率。
活动回合快照的初始签名须与初始空数组一致,首次异步返回空数组不能额外换引用。停用、重新启用或切换读取器时应使旧请求失效,避免晚到结果覆盖新快照;测试需控制 Promise 完成时机,不能用“初始数组已为空”当作请求已结束。无原生读取器时窗口只发布一次空状态。
## 2026-09-17 工具 schema 声明的上限与真实校验不一致,会表现成「agent 调不动这个功能」
- **现象**:用户反馈「客户端没法由 agent 调用图片快速编辑功能以及背景音乐生成功能」。查工具目录时两个工具都在(`agc_edit_image``agc_create_or_derive_resource`),图片快速编辑在真实项目日志里还有成功记录;但 agent 侧写一句正常长度的背景音乐描述就失败,而客户端 UI 用同一个提示词却只是被截断加提示。
@@ -5688,8 +5708,8 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **现象**:在 `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` 或构建后`git checkout -- apps/ai-game-creator-shell/src/features/project-workspace/generated`,再删掉多出来的 `DirectCodexUserMessageEnvelope.ts`,然后才做 typecheck / 打包;绑定与前端形状冲突时以**已提交的绑定 + 前端**为基准排查。
- **验证**:恢复提交版本后 `npm run ai-game-creator-shell:typecheck` exit 0`[skill-pack] OK`);保留重写结果时同一条命令 exit 2。release 构建本身还会在 `src/features/ui-editor/types/` 落下 `BindingChange.ts` / `BindingDTO.ts` 两个无人引用的未跟踪文件,属同类生成产物。
- **处理(现行口径)**:不要把重写结果当改动提交。跑过 `cargo test` 或构建后只恢复 `apps/ai-game-creator-shell/src/features/project-workspace/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`)。
## 2026-09-16 Node 26 下 vitest 的 jsdom 用例拿不到 window.localStorage
@@ -5698,3 +5718,11 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **原因(已核对,不是推断)**Node 26 在 `globalThis` 上定义了实验性 `localStorage` 访问器,不传 `--localstorage-file` 时它返回 `undefined`。vitest 0.34 的 jsdom 环境把 jsdom window 的描述符复制到全局时,不会覆盖 `globalThis` 上已存在的键,于是 jsdom 真正的 `Storage` 被 Node 的 `undefined` 顶掉。`appSurface` 之所以不受影响,是因为 `apps/ai-game-creator-shell/tests/appSurface/harness.ts` 自己用 `Object.defineProperty(window, 'localStorage', …)` 挂了内存实现。
- **处理**:跑这类用例时给 Node 加 `--localstorage-file`,例如 `NODE_OPTIONS="--localstorage-file=/tmp/vitest-node-localstorage" npx vitest run <files>``apps/ai-game-creator-shell/tests/clientApi.test.ts``chatPromptPolish.test.tsx` 加这个开关后 26 项全绿。不要改业务代码或往测试里塞假 storage 来绕过。
- **关联**`vitest.config.ts``environment: 'node'` + 用例内 docblock 切 jsdom)、`apps/ai-game-creator-shell/tests/appSurface/harness.ts` 的内存 localStorage 垫片。
## 2026-09-17 AGC 壳首页无限 setState:effect 依赖了每次渲染都换身份的普通函数
- **现象**:dev 客户端停在首页、不点任何东西也会持续刷 `WEBVIEW error webview: Maximum update depth exceeded …`(5 秒涨 ~8.5 KB 日志),对应 WebView2 renderer 工作集涨到 **4.2 GB**、CPU 持续累计(约 0.7–1.5 核);表现上很像"模板库卡片太多/滚动卡",实际与页面内容无关。
- **原因**`WorkspaceLauncher` 里发布"活动项目面板"数据的 effect,依赖数组里带了 `openActiveProject`;它由 `useCallback([openProject, setProjectPath])` 生成,而 `openProject` 来自 `useHomeProjectCreation` 的**普通函数声明**(每次渲染都是新身份)→ `openActiveProject` 每渲染都变 → effect 每渲染重跑 → cleanup/主体调 `setActiveProjectRuns``WindowChrome` 的 state → 标题栏重渲染 → 又一轮。`WindowChrome` 的 context value 当时还是内联对象,进一步放大了连带重渲染。日志里没有组件栈,是靠在 `console.error` 包装里抓 `new Error().stack`(该日志与 setState 同栈)才定位到 `WorkspaceLauncher.tsx``commitHookEffectListUnmount → dispatchSetState`
- **处理(现行口径)**:① 依赖里只放数据,回调走 ref`openActiveProjectRef`)——effect 不再因回调换身份而重跑;② `useDirectActiveTurns` 轮询只在快照内容变化时才 `setActiveTurns`(并给空态做引用稳定),避免每 5 秒换一次数组身份去带动下游 effect;③ `WindowChrome` 的 context value 用 `useMemo` 收口。判断类问题的通行判据:**凡是把"每次渲染新生成的函数/对象"写进 effect 依赖的,一律视为 bug**。
- **验证**:修复后同一台机器、同一路径下 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`
@@ -16,6 +16,7 @@
## 开发中
- 画布卡片类型与信息角标共用 `CanvasCardCornerActions`;菜单收纳共用 `OverflowActions`,宿主决定展示数量和资源命令。AGC 选中菜单前 5 项直显,Web 默认不折叠;浮层 portal 继续接入现有画布关闭与滚轮归属判据。
- 修改范围保持聚焦;优先扩展现有系统、页面、组件、DTO 和脚本,不新建平行入口或业务真相。
- UI 开发优先复用现有公共组件;跨页面或跨端重复的视觉/交互模式应沉淀到 `packages/shared`,由现有页面迁移使用,禁止在业务页复制同类 UI。共享组件只承载通用表现与交互,不下沉领域规则、后端副作用或正式业务状态。
- AGC 当前 Agent 与策划 Agent 的消息层级共用 `packages/shared``AgentMessageContent`:正文使用 `body`,思考、中间输出与工具调用使用 `process`;宿主不按 Agent 类型重新定义过程字号和颜色,错误状态保留语义色。
@@ -1,68 +1,137 @@
# AGC 客户端更新检查与下载
## 交付范围
更新时间:`2026-09-17`
AGC 每次启动时由根窗口检查一次公开 OSS 更新清单。清单默认位于
`https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/latest.json`,构建时可用
`VITE_AGC_UPDATE_MANIFEST_URL` 覆盖为同一受信任 OSS 域名下的 HTTPS 地址。客户端版本取
`apps/ai-game-creator-shell/package.json`,通过 `version` 与清单版本比较;只有远端版本更高时显示更新提示。
本文件是 AGC 客户端自动更新的主规范:更新能力由 Tauri 官方插件 `tauri-plugin-updater` 承担,并按下文渠道分发。
清单格式:
## 目标
- 客户端自动更新改用 Tauri 官方 `tauri-plugin-updater`:清单请求、版本比较、更新包下载、签名校验、安装与退出全部在原生侧完成;前端只负责触发、展示和渠道选择。
- 更新按渠道分发。当前渠道集合为 `dev-win`Windows x64)与 `dev-mac`(macOS);构建管线按渠道产出并上传清单,客户端只读取自己渠道的清单。
- 更新链路的信任来源从「清单里的 sha256 + 受信域名」升级为「发布签名 + 受信域名」:清单里的 `signature` 由构建期私钥生成,客户端用内置公钥校验,校验不过就拒绝安装。
## 非目标
- 不做灰度放量、分批更新、强制更新和自动回滚;渠道只决定「取哪份清单」。
- 不做后台静默自动安装:是否下载安装始终由用户在更新提示里确认(仅「是否显示提示」受渠道与开发态开关影响)。
- 不支持应用商店分发(Microsoft Store / App Store)、移动端更新和企业内网自建更新服务。
- 不为自研 sha256 清单协议保留长期实现;迁移桥(见「契约与迁移」)只用于把已发布客户端带到新协议,随后整条删除。
## 入口与边界
- 用户入口:
- 客户端启动时在根窗口检查一次渠道清单,发现新版本时显示更新提示,用户可下载并安装。
- 运行时设置「关于」页提供手动检查更新(强制刷新)。
- 涉及模块:AGC 客户端(Rust `src-tauri`、前端 `src/`)、AGC 构建与发布脚本(`apps/ai-game-creator-shell/scripts/`)、Jenkins 发布流水线、OSS 对象布局。
- 正式状态来源:
- 客户端当前版本以 Tauri app version 为唯一权威来源(`tauri.conf.json`,由发布脚本与 `package.json``Cargo.toml``Cargo.lock` 同步递增)。
- 远端最新版本取自当前渠道的 `latest.json`
- 信任边界:清单地址在构建期确定并烘焙进产物;客户端不接受用户输入、后端响应或项目文件提供的更新地址,也不回退到其它渠道或旧协议地址。
## 必须成立的行为
### 正常路径
- 正式包启动时检查一次渠道清单;仅当清单版本高于当前版本时显示更新提示,提示包含目标版本与发布说明。
- 用户确认后下载更新包:下载期间显示进度与已下载字节数;下载完成后按平台安装。
- Windows 使用静默安装模式(NSIS `quiet`),安装启动成功后客户端退出并由安装程序重启新版本;macOS 由客户端在安装完成后重启进程接管新版本。
- 渠道在构建期确定并烘焙进产物:`dev-win` 产物只读 `dev-win` 清单,`dev-mac` 产物只读 `dev-mac` 清单,同一份二进制不会在运行期跨渠道切换。
- 开发态(`npm run agc` / `agc:serve` 由 Vite dev server 提供前端)不检查更新、不显示更新入口,也不下载任何更新包。
### 失败、重试与幂等
- 清单请求失败均静默忽略,不阻塞客户端启动:网络错误、TLS 错误、404(渠道尚未发布版本)、格式非法、渠道没有当前平台条目、远端版本不高于当前版本。
- 同一客户端生命周期内只自动检查一次;手动检查可强制刷新。
- 签名校验失败、下载中断或写入失败必须失败关闭:删除临时文件、不启动安装程序,并给出可读错误文案;不接受「校验失败但继续安装」。
- 重复点击下载或安装不产生并发安装;安装开始后客户端不再接受新的更新操作。
### 权限、归属与数据边界
- 更新能力通过 Tauri capability 显式授予客户端主窗口,其它窗口(调试窗口等)不得授予。
- 客户端只允许访问渠道清单声明的地址,只允许安装清单声明且签名校验通过的对象。
- 清单与安装包在 OSS 上保持公开可读;签名私钥与 OSS 凭据只存在于构建环境(Jenkins 凭据、本机发布配置),不写入仓库、日志、构建产物或客户端包。
- 客户端不记录更新地址以外的敏感信息;失败文案不回显凭据、绝对路径或响应正文。
## 契约与迁移
- 清单格式(Tauri updater v2):
```json
{
"version": "0.1.13",
"downloadUrl": "https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/0.1.13/Genarrative-AI-Game-Creator.exe",
"sha256": "<64位十六进制摘要>",
"size": 123456789,
"releaseNotes": "修复与改进"
"version": "0.1.48",
"notes": "发布说明,可为空",
"pub_date": "2026-09-17T00:00:00Z",
"platforms": {
"windows-x86_64": {
"signature": "<.sig 文件内容>",
"url": "https://<oss>/agc/dev-win/0.1.48/<安装包文件名>"
}
}
}
```
`downloadUrl` 必须是 HTTPS;如提供 `sha256` / `size`,Tauri 下载时会校验摘要和字节数。点击“下载更新”后,客户端将安装包流式写入系统临时目录并显示进度,校验成功后通过 Windows UAC 提权启动 NSIS 静默安装并退出旧客户端。
- 渠道与平台映射:
## 启动与失败策略
| 渠道 | 构建目标 | 清单平台键 | 更新包 | 清单地址 |
| --------- | ------------------------ | ---------------------------------------------- | ------------------------ | ------------------------------------ |
| `dev-win` | `x86_64-pc-windows-msvc` | `windows-x86_64` | NSIS `.exe` + `.exe.sig` | `<OSS base>/agc/dev-win/latest.json` |
| `dev-mac` | `universal-apple-darwin` | `darwin-aarch64` + `darwin-x86_64`(同一对象) | `*.app.tar.gz` + `.sig` | `<OSS base>/agc/dev-mac/latest.json` |
- 检查挂在 `WindowChrome` 根组件,覆盖首页、工作台和调试窗口;网络错误、格式错误或版本不高于当前版本均静默忽略,不阻塞客户端启动
- 更新请求使用单例 PromiseReact StrictMode 或同一窗口重复挂载不会重复请求
- Tauri HTTP capability 与 CSP 仅放行默认 OSS 域名;若更换域名,需同步更新 `capabilities/main.json``tauri.conf.json` 和发布环境配置
- 对象布局:清单固定写成 `agc/<channel>/latest.json`;安装包与签名写成 `agc/<channel>/<version>/<file>``<file>.sig`
- macOS 使用 universal 包:`dev-mac` 按 universal 目标构建(Intel 与 Apple Silicon 共用一个包),清单把同一个 `.app.tar.gz` 与同一个签名分别写入 `darwin-aarch64``darwin-x86_64`,升级后仍是 universal 包。这是 Tauri 官方发布工具对 universal 产物的既有写法
- 上一条的两个键不能合成单一 `darwin-universal` 键:更新插件按运行时实际架构解析清单键(Apple Silicon 命中 `darwin-aarch64`Intel 命中 `darwin-x86_64`),不存在自动命中 `darwin-universal` 的情形。将来真要单独发该键,必须在客户端同时设置自定义 target,否则清单里这一项永远不会被读取
- 构建期要求:打开 `bundle.createUpdaterArtifacts` 以生成 `.sig`;构建环境提供签名私钥与密码(私钥内容不得入库);公钥写入客户端配置。公钥在首个带更新能力的版本发布后不可更换,更换等于放弃自动更新(只能手动重装)。
- 版本递增按渠道独立进行:发布脚本读取该渠道远端 `latest.json``version`,与本地版本取较高者递增 patch;两个渠道的版本号互不影响。
- 版本高水位:发布脚本取「渠道清单版本」与「旧协议迁移指针版本」(迁移窗口内)中的较大值再递增。只看渠道清单会在渠道启用初期把版本链改小 —— 2026-09-17 首次渠道发布即把旧指针的 0.1.57 退回 0.1.48,随后以显式 0.1.60 纠偏;迁移窗口结束(旧指针 404)后自动只剩渠道清单,`dev-mac` 不参与旧指针比较。
- 迁移(旧协议 → 渠道清单):
- 迁移起点:已发布客户端(含当前线上版本)内置自研清单地址 `agc/latest.json`(sha256 格式),下载与安装由自研 Rust 命令完成。
- 迁移策略见「未决问题与决策」。迁移完成后,自研清单解析、下载命令、下载进度事件以及为此放行的 CSP / HTTP 白名单条目按「四不写」整条删除,不留兼容分支与墓碑说明。
## 发布约定
## 构建与发布
当前发布目标固定为 Windows x64 NSIS。执行 `npm run ai-game-creator-shell:build` 会先读取
`VITE_AGC_UPDATE_MANIFEST_URL`(默认 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/latest.json`)的
`latest.json`,取本地与 OSS 的较高版本并递增一个 patch,然后同步更新 package、Tauri 和 Cargo
版本后再向 Tauri 传入 `--target x86_64-pc-windows-msvc` 构建。OSS 清单首次不存在时按本地版本递增;
OSS 请求失败、清单格式错误或版本无效会终止发布,避免覆盖线上版本。构建完成后自动扫描 `.exe`
安装包,并在 `apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/latest.json`
生成包含版本、下载地址、大小和 SHA-256 的清单。可通过 `AGC_BUILD_TARGET` 显式覆盖目标(发布仍应使用
Windows x64),通过 `AGC_UPDATE_ARTIFACT` 指定要发布的安装包,通过 `AGC_UPDATE_OSS_BASE_URL` 指定
OSS 前缀,通过 `AGC_RELEASE_VERSION` 指定三段版本号(仅在明确需要复现指定版本时使用),通过
`AGC_UPDATE_RELEASE_NOTES` 写入发布说明,支持多行文本且保留内部换行;`--no-bundle` smoke 构建不会读取 OSS、修改版本或生成清单。
- 发布入口:`npm run ai-game-creator-shell:release:upload`(构建 + 按渠道上传);仅构建不发布的 smoke 使用 `--no-bundle` 分支,不读远端版本、不改版本、不生成清单。
- 渠道由构建参数显式指定,并按目标平台校验:Windows 目标只允许 `dev-win`macOS 目标只允许 `dev-mac`;未显式指定时按目标平台取默认渠道。
- 定时调度只在本轮到达的提交包含 AGC 相关路径(客户端、共享包、`server-rs/crates`、AGC 插件、桌面壳图标、根依赖清单)时才触发渠道发布;纯文档或流水线自身的提交只跑 Full Build,不推高客户端版本号。判定失败或勾选强制触发时按"需要发布"处理。
- 更新摘要自动生成:发布脚本用渠道清单里的 `commit` 字段(上一次发布的提交)到本次提交之间、且只覆盖客户端相关路径的提交列表生成 `notes`(每条 `- 提交标题(短 SHA`,最多 12 条、主题 80 字、整体 900 字,超出折叠或截断),同时写入旧协议清单的 `releaseNotes` 和归档文件 `release-notes.txt``AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准;无法判定起点(缺少上次 `commit` 或本地没有该提交)时不写摘要。清单缺少 `commit` 时回退用上一次成功构建的 `COMMIT_HASH`CI 通过 `AGC_UPDATE_PREVIOUS_COMMIT` 传入)作为锚点,因此首次启用摘要或更换渠道后也能立即产出摘要。锚点仍不可得(清单读取失败或没有 CI 锚点)时降级为「最近客户端改动」列表并注明可能与上一版重复 —— 摘要属于附注,任何情况下都不允许因为它让发布失败。
- 清单里的 `commit` 是非标准字段:更新插件忽略未知字段,发布脚本用它定位下一次摘要的起点。
- 上传:安装包与 `.sig` 上传到 `agc/<channel>/<version>/`,清单以 `--force` 覆盖上传到 `agc/<channel>/latest.json`,保证 latest 指针与清单内 URL 指向已存在的对象。
- Jenkins 流水线需要新增渠道参数与签名凭据;签名私钥与密码只以受保护凭据注入当前进程,不写入 workspace、日志或归档产物。
- 归档证据:安装包、`.sig`、渠道清单与源码 commit。
每次发布安装包上传完成后,再使用 ossutil 的 `--force` 覆盖上传同一目录生成的 `latest.json`,确保固定的 latest 指针和 `downloadUrl` 指向已存在的 OSS 对象;未显式强制覆盖时,ossutil 在目标已存在时会交互询问并按默认值跳过,不能作为 Jenkins 非交互发布方式。清单和安装包均使用公开可读对象,不在清单中保存凭据、签名或本地路径。构建脚本本身不负责上传 OSS,发布流水线通过 `release:upload` 完成上传。
## 验收标准与证据
如需一键构建并上传,可执行 `npm run ai-game-creator-shell:release:upload`。该命令要求本机已安装并配置 `ossutil`
先按上述规则比较 OSS 版本、递增 patch、构建 Windows x64 NSIS,再上传安装包和 `latest.json`。默认上传到
`agc-dev` / `oss-rg-china-mainland.aliyuncs.com`,也可用 `AGC_OSS_BUCKET``AGC_OSS_ENDPOINT``OSSUTIL_BIN`
覆盖;本机执行时凭据由 ossutil 本机配置读取,不能写入仓库或命令行参数。
已获得的证据:
## Jenkins Windows 构建节点
| 条款 | 验收方式 | 证据 |
| ---------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| 渠道与端点映射、渠道校验 | `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs` | 通过(默认渠道、错配失败关闭、未知渠道失败关闭) |
| universal 包挂两个平台键 | 同上 + 本地发布烟测(伪造 bundle) | 通过(两键同 URL 同签名,不生成迁移清单) |
| 缺签名时失败关闭 | 同上 | 通过 |
| 开发态不检查更新 | `vitest run apps/ai-game-creator-shell/tests/appUpdate.test.ts` | 通过(开关关闭时不请求清单) |
| 旧自研链路整条删除 | 代码检索无残留命令、事件与白名单条目 | 通过(`download_agc_update` / 下载事件 / 清单常量均无残留) |
| 清单与对象布局符合渠道约定 | `Genarrative-Agc-Windows-Build` #682026-09-17SUCCESS | 通过:`agc/dev-win/latest.json` = 0.1.48 + `windows-x86_64``agc/dev-win/0.1.48/陶泥儿_0.1.48_x64-setup.exe` 与同名 `.sig` 公网可读 |
| 清单签名与签名对象一致 | 取回 `.sig` 对象与渠道清单 `signature` 比对 | 通过(逐字相同,420 字节) |
| 安装包与清单登记一致 | 下载安装包实算 SHA-256 与尺寸后与迁移桥清单比对 | 通过(size `104678031`、sha256 `1f67…4fd0` 一致) |
| 旧协议迁移桥 | 公网读取 `agc/latest.json` | 通过(0.1.48`downloadUrl` 指向同一对象,含 `sha256` / `size`) |
| 真实更新闭环(含升级后重启) | 0.1.47 客户端按提示下载安装并重启 | 通过(2026-09-17 用户实测:提示 → 下载 → 安装 → 关于页显示新版本,再次检查为已是最新) |
| 更新摘要端到端展示 | 公网读取渠道清单 `notes` 与客户端更新提示 | 通过(2026-09-17 用户实测:0.1.62 清单带 8 条自动摘要,客户端提示正常显示多行内容) |
AGC 发布流水线使用 `jenkins/Jenkinsfile.ai-game-creator-shell-build`,当前节点标签为
`windows && win2022`。节点应为 Windows Server 2022 x64 虚拟机,预装 Node.js 22、npm
10.9.7、Rust 1.96.0、Visual Studio Build ToolsMSVC 与 Windows SDK)、Git 和 ossutil
Jenkins Agent 服务必须能在同一用户环境中找到这些命令。Tauri Windows bundler 使用
`tauri.windows.conf.json` 中的 `bundle.useLocalToolsDir: true`,把固定版本的 NSIS 工具缓存到
`src-tauri/target/.tauri/NSIS`,不依赖 Jenkins 服务账户的 `%LOCALAPPDATA%\tauri` 或 PATH 中的系统 NSIS。
Jenkins Checkout 的 `git clean -fdx` 会清理该构建目录,因此每次全新工作区可能重新下载 NSIS;这只影响构建耗时,不改变工具来源或执行权限要求。
流水线参数 `AGC_UPDATE_RELEASE_NOTES` 使用 Jenkins `text` 类型,可直接输入多行发布说明;执行根 workspace 的 `npm ci`,然后调用
`npm run ai-game-creator-shell:release:upload`,并归档 Windows 安装包、`latest.json` 与源码 commit。
流水线会将未导出的空参数按空字符串处理:`COMMIT_HASH` 留空时沿用 Jenkins SCM 当前提交,`OSSUTIL_BIN` 留空时使用节点 PATH 中的 `ossutil`,不会因 PowerShell 对空环境变量调用 `.Trim()` 而提前失败。
待执行证据(首次渠道发布后回填):
Jenkins Job 在“Build and upload”阶段通过受保护凭据 ID `AliyunAccessKeyId`
`AliyunaccessKeySecret` 注入 AccessKey,仅在当前进程运行时传给 ossutil,不写入仓库、workspace 或构建日志;
本机运行仍使用 ossutil 配置。凭据必须具备 `PutObject` 权限;OSS 对客户端保持公共读即可,公共读本身不授予
Jenkins 上传权限。由于版本号取决于 OSS 当前清单,Job 已关闭并发构建;若 Jenkins
上存在多个 AGC 发布 Job,还应使用同一个 Lockable Resource 串行化发布。Job 参数
`AGC_RELEASE_VERSION` 留空时自动递增,填写后会使用指定版本并更新对应的 `latest.json`,因此回滚或测试旧版本前应确认不会覆盖线上更新入口。
| 条款 | 验收方式 | 证据 |
| -------------------- | --------------------------------------------------- | ------ |
| 签名校验失败拒绝安装 | 篡改渠道清单 `signature` 后观察客户端拒绝安装的表现 | 待执行 |
| 签名校验失败拒绝安装 | 篡改渠道清单 `signature` 后观察客户端拒绝安装的表现 | 待执行 |
## 未决问题与决策
已决策:
- macOS 采用 universal 包,同一产物同时挂 `darwin-aarch64``darwin-x86_64` 两个清单键(见「契约与迁移」)。
- 旧客户端迁移桥:保留一个版本周期。渠道清单上线后,发布管线同时把旧的 `agc/latest.json`sha256 格式)指向 `dev-win` 最新安装包,让已发布客户端自动升级到新协议;下个周期整条删除。
- 签名密钥:由本仓库维护者生成并保管,私钥保存在仓库外(`%USERPROFILE%\.tauri\genarrative-agc-updater.key`),只有公钥进入客户端配置;Jenkins 用受保护凭据 `AgcUpdaterSigningKey``AgcUpdaterSigningKeyPassword` 注入为 Tauri 打包器读取的 `TAURI_SIGNING_PRIVATE_KEY``TAURI_SIGNING_PRIVATE_KEY_PASSWORD`,本机可用 `TAURI_SIGNING_PRIVATE_KEY_PATH` 指向同一私钥。当前密钥不带密码;首次发布前仍可重新生成,首次发布后不可更换。
- macOS 发布方式:`dev-mac` 产物在本机 mac 上执行发布入口上传,Jenkins 暂不新增 macOS 节点;macOS 代码签名与公证凭据未确认前,相关闭环记为未验证项,不静默通过。
待办:
- macOS `dev-mac` 渠道落地(macOS 构建机、签名与公证、安装后重启验证、是否接入 Jenkins macOS 节点)暂缓,由后续独立变更单独完成;在此之前 `dev-mac` 渠道只有构建与清单能力,不发布。
@@ -61,10 +61,25 @@ OpenAPI 与客户端工具的对外说明只描述模式用途、参数约束和
}
```
客户端保留旧参数调用;新字段不填时不改变旧调用语义。客户端不读取图片、不自动选色、不把 `auto` 改写为具体颜色,使用原有 Bearer 认证、幂等键和队列返回模型
客户端保留旧参数调用;新字段不填时不改变模式语义。客户端不自动选色、不把 `auto` 改写为具体颜色;源资源校验、认证、异步任务查询、结果下载和本地登记由客户端负责
工具 schema、桥接参数校验和随包 `agc-client-projection` Skill/契约说明必须保持一致。模式与颜色属于请求意图,必须参与客户端幂等指纹;同一图片与名称的不同模式不能复用同一次请求。缺省 complex 且没有颜色时保留既有指纹。主站在默认值归一化之前计算 External 请求指纹,缺失的新字段不序列化,避免旧请求重放发生冲突。
客户端提交前建立的本地项目绑定会返回主站远端 `projectId``assetFolderId`,抠图请求必须使用这两个远端身份;本地 manifest `projectId` 仅用于绑定和本地状态,不能直接提交给主站。
### 本地结果与恢复合同
`agc_remove_background` 复用本地资源编辑账本,类型为 `background-removal`。源图片保持不变,抠图结果作为新资源写入项目。模式和背景色随账本持久化并参与请求指纹,其它编辑类型的历史指纹保持不变。
1. 客户端建立源图片的正式资源绑定,在提交前保存 operation、幂等键和请求意图。普通登录态提交 `/api/editor/images/background-removals`,从 `data.queueState.operationId` 读取受理身份;开发者模式提交 External v1 对应路由,从 `data.operationId` 读取身份。
2. 受理后持续查询账号路由 `/api/runtime/external-generation/jobs/{operationId}` 或 External v1 对应状态路由。`queued``running` 只描述远端返回状态;固定进度值、本地文件缺失或 pending 清单为空均不能证明 BgFilter 排队。
3. 远端 completed 后按稳定资源身份换取有效下载 URL,校验结果为带 alpha 通道的有效 PNG,随后复用 staging、manifest 和 revision 提交。只在本地登记完成后向 Agent 返回 `completed``operationId``resource.localAssetId`、相对路径和安全告警,不暴露临时 URL 或凭据。
4. 轮询中断、超时或下载失败保留已受理 operation;`agc_list_registered_assets.pendingOperations` 与客户端恢复面板可见。相同源资源、结果名称、模式和颜色的后续调用优先恢复同一任务,不再次提交。不同账号不能恢复原账号任务;切回原账号后按既有恢复规则续接。
5. 远端 failed 明确失败;提交响应不确定且无法确认 operation 时进入人工对账状态,不自动换键重发。失败/未知均不得伪造透明图或自动切换本地抠图方式。
6. 升级前仅返回 queued、没有本地账本的任务不自动迁移;已有远端结果须通过正式资源查询和导入恢复,不据旧回执重新发起付费请求。
本修复只扩展 AGC 客户端现有工作流,不修改主站队列、BgFilter 或 SpacetimeDB schema。验收覆盖账号与开发者两种响应封装、queued/running/completed、已受理中断恢复不重复 POST、远端失败不登记结果,以及既有资源编辑回归。
## 实施任务
### 任务一:冻结 BgFilter 契约
@@ -93,6 +108,13 @@ OpenAPI 与客户端工具的对外说明只描述模式用途、参数约束和
## 验收证据
2026-09-17 客户端闭环验证:
- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell project::resource_editor:: -- --test-threads=1` 通过。新增 HTTP fixture 覆盖开发者 queued/running/completed、账号 HTTP 200 queueState 与 `data.job`、换签下载、PNG alpha 校验、原图保留和新资源提交。
- 已受理任务的首次轮询失败后,pending 保留模式与颜色;恢复只查询原 operation,整个流程只 POST 一次,成功后清除 pending。远端 failed 不新增结果资源。既有账号隔离、提交原子性和崩溃恢复用例通过。
- `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell agent::direct_tool_bridge::tests -- --test-threads=1`、AGC 类型检查、技能包校验、Rust 格式、编码、文档索引与 diff 检查通过。
- 本轮未运行真实登录客户端 → 本地主站 → BgFilter 的端到端 smoke;当前本地后端已停止,自动化证据使用模拟 HTTP 服务。此前 BgFilter 成功日志只证明上游处理完成,不证明主站结果持久化或客户端导入成功。
2026-09-16 实测:
- 主站 `cargo test -p api-server background_removal`:36 项通过,覆盖非法请求入队前拒绝、缺省 complex、队列参数保留、旧请求指纹、父侧内部 RPC 和 provider multipart。

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