Merge branch 'master' into codex/agc-project-snapshot-upload
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled

This commit is contained in:
2026-09-17 18:15:34 +08:00
157 changed files with 7500 additions and 868 deletions
@@ -1,68 +1,129 @@
# 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` 根组件,覆盖首页、工作台和调试窗口;网络错误、格式错误或版本不高于当前版本均静默忽略,不阻塞客户端启动。
- 更新请求使用单例 Promise,React 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;两个渠道的版本号互不影响。
- 迁移(旧协议 → 渠道清单):
- 迁移起点:已发布客户端(含当前线上版本)内置自研清单地址 `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`;未显式指定时按目标平台取默认渠道。
- 上传:安装包与 `.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` / 下载事件 / 清单常量均无残留) |
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 Tools(MSVC 与 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`,因此回滚或测试旧版本前应确认不会覆盖线上更新入口。
| 条款 | 验收方式 | 证据 |
| ---------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| 清单与对象布局符合渠道约定 | `ossutil ls oss://agc-dev/agc/dev-win/<version>/`;`ossutil cat .../dev-win/latest.json` | 待执行:URL 指向已存在安装包,签名与 `.sig` 内容一致 |
| 旧协议迁移桥 | `ossutil cat oss://agc-dev/agc/latest.json` | 待执行:`sha256` / `size` 与同一安装包匹配 |
| 真实更新闭环(含升级后重启) | 0.1.47 客户端升级到新版本,再启动不再提示;`npm run agc` 仍无更新入口 | 待执行 |
| 签名校验失败拒绝安装 | 渠道清单签名与实际安装包不匹配时的表现 | 待执行(需要真实渠道清单) |
## 未决问题与决策
已决策:
- 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` 渠道只有构建与清单能力,不发布。
@@ -0,0 +1,136 @@
# 【技术方案】AGC 模板库与模板建项
## 交付范围
AGC 客户端接入公共 OSS 上的**游戏模板库**(真·游戏模板,正文是 zip),并让用户能浏览、搜索、筛选、下载模板,直接由模板创建项目。模板更新不再依赖客户端发版。
- OSS 侧:bucket `agc-dev`(endpoint `oss-rg-china-mainland.aliyuncs.com`)下的 `templates/` 前缀,公共读。
- 客户端侧:Rust `template_library` 模块(读清单、下载、安装、建项目)+ 模板库全屏页 + 首页模板推荐 + 左侧导航入口。
- 不在本次范围:模板制作工具、模板审核、模板计费、增量更新、已建项目的模板回填。
## OSS 契约
```text
templates/
index.json # 模板库清单,客户端唯一读取入口
v1/<templateId>/
template.json # 单模板元数据(含文件级摘要)
template.zip # 模板正文,zip 根 == AGC 项目根(如 game/index.html)
cover.(png|jpg|webp|svg) # 封面图(卡片展示,客户端 <img> 直接取)
```
`index.json`(schema `agc-template-library.v1`):
| 字段 | 说明 |
| --- | --- |
| `schemaVersion` | 固定 `agc-template-library.v1`;破坏性变更换 schema,不原地改语义 |
| `library` / `libraryVersion` / `updatedAt` | 库标识、库格式版本、本次更新时间 |
| `templates[].id` | 稳定标识,`[a-z0-9][a-z0-9._-]{0,63}`,同时是目录名 |
| `templates[].title/summary/tags[]` | 展示与搜索/筛选用文案;`tags` 参与标签筛选与关键词命中 |
| `templates[].runtime` | `html` / `unity` / `godot` / `cocos` |
| `templates[].engine` / `engineVersion` | 引擎标识与版本(如 `phaser` 4.2.1、`three.js` 0.180.0) |
| `templates[].templateVersion` / `updatedAt` | 模板内容版本;客户端按它判断是否需要重新下载 |
| `templates[].entry` | 解压后的项目入口相对路径,如 `game/index.html` |
| `templates[].zipKey` / `zipSizeBytes` / `zipSha256` | 模板包对象键、字节数、SHA-256(下载后强校验) |
| `templates[].coverKey` / `coverWidth` / `coverHeight` / `coverSha256` | 封面对象键与尺寸/摘要 |
| `templates[].metadataKey` | 单模板元数据对象键(`template.json`) |
约束:
- 所有对象键必须落在 `templates/` 前缀内;客户端只用「受信任 OSS 主机 + 对象键」自行拼 URL,**不直接信任清单里的地址**。
- 任何一项校验失败(schema、标识符、sha256、尺寸、键前缀)都让整次清单读取失败,前端拿到的是全有或全无的清单。
- 模板源在仓库 `apps/ai-game-creator-shell/template-library/`:`v1/<id>/{meta.json, project/**, cover.(png|jpg|webp|svg)}`,`template.zip` **不落仓库**,由脚本按 `project/` 现场打包(条目排序、固定时间戳,同内容重复打包摘要一致)。
- 上传与校验由 [`scripts/agc-template-library-publish.mjs`](../../scripts/agc-template-library-publish.mjs) 完成:`--source apps/ai-game-creator-shell/template-library [--dry-run] [--prune]`,脚本生成 `template.json` 与 `index.json`、上传后回读 zip 摘要;`--prune` 清理该模板前缀下本次没有产出的旧对象(例如换封面扩展名后的残留)。
- 当前模板:`blank-web`(空白网页)、`blank-2d-canvas`(空白二维画布)、`blank-3d-scene`(空白三维场景)、`phaser-2d-starter`(Phaser 2D 起步工程)、`threejs-3d-starter`(Three.js 3D 起步工程)。
- 客户端可用 `AGC_TEMPLATE_LIBRARY_BASE_URL` 覆盖库地址;只接受 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com`(拒绝其他主机、路径、http)。
## 客户端实现
### Rust:`apps/ai-game-creator-shell/src-tauri/src/template_library.rs`
| 命令 | 行为 |
| --- | --- |
| `fetch_game_template_library` | 读 `templates/index.json`(≤4 MiB),校验后缓存到 `<app_data>/templates/index.json`;网络失败时回退本机缓存并在 `source` 标 `cache` |
| `download_game_template` | 取清单里对应条目,流式下载 zip(≤512 MiB),校验字节数与 SHA-256,解压到 `<app_data>/templates/installed/<id>/<version>/`,最后写 `installed.json` 作为安装完成的唯一标记 |
| `create_automatic_local_game_project_from_template` | 需要时先安装模板,然后在 `<app_data>/projects/` 下按既有自动工作区规则建目录:先复制模板文件,再走 `init_local_game_project_at` 补 `.agent` 清单与标准目录 |
安全与健壮性:
- 解压只接受普通文件与目录:拒绝绝对路径、`..`、盘符、反斜杠、符号链接,并有文件数(4096)与单文件大小(256 MiB)上限。
- 安装目录名由标识符白名单拼出,不拼接远端字符串;重装时只清理该模板自己的安装目录。
- 模板文件与安装记录统一走 `write_game_creator_private_file` / `ensure_game_creator_private_directory_tree`,保持项目目录的私有 DACL 口径。
- 建项目失败时删除刚创建的项目目录,不留半成品。
### 前端
- `src/features/template-library/templateLibraryModel.ts`:清单类型、搜索(空白分隔多关键词「与」)、标签/运行时/已下载筛选、标签选项聚合、体积格式化等纯函数。
- `src/features/template-library/useTemplateLibrary.ts`:一次拉清单,暴露筛选状态、下载与「用模板建项目」;下载成功后只就地更新该条目的已下载状态。
- `src/view/template-library/index.tsx`:模板库全屏页(返回、刷新、搜索、运行时/标签筛选、仅看已下载、卡片显示封面与已下载徽标、下载/使用模板)。
- 卡片动作按安装状态收口:已下载且版本一致时**不再显示下载入口**,只留「使用模板」;版本落后才显示「更新」;缺包显示「下载」。
- 过程提示(下载完成、开始建项目)走浮层 toast(复用 `packages/shared` 的 `PlatformRuntimeStatusToast`,`document.body` 浮层 + 2.6 秒自动消失),不再占用页面内位置;页面内只保留可操作的错误与空态。
- 首页「灵感推荐」替换为「模板库」推荐位(`src/view/home/TemplateRecommendations.tsx`):只展示封面、标题、运行时与已下载徽标,点击进入模板库页面;首页不再直接触发建项目。
- 左侧导航新增模板库入口(`LauncherView = 'template-library'`)。
- `src-tauri/tauri.conf.json` 的 `csp` / `devCsp` 在 `img-src` 放行 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com`,用于封面图;`connect-src` 原本已放行同一域名。
- 旧的本机灵感图目录 `src/view/home/assets/inspiration/` 与 `InspirationGallery.tsx` 一并删除,不再保留退役实现。
## 验收与验证
## 本地压测假数据注入(feature 控制)
模板库的数据源在 Rust 侧(清单校验、安装状态、下载与建项目都在这里),TS 只消费快照做渲染,所以假数据注入也放在 Rust 侧,走与真实完全一致的链路。
- 开关:Cargo feature `template-library-fixtures`(**默认关闭**)。关闭时 `apply_template_library_fixtures` 是恒等透传,正式产物里不存在注入分支,并有单测保证这一点。
- 条数:环境变量 `AGC_TEMPLATE_LIBRARY_SYNTHETIC_COUNT`(默认 1000;`0` 表示不注入;上限 20000)。
- 假数据特征:真实条目保留在最前,其余按真实条目循环复制;`id`/标题唯一,封面地址追加 `?synthetic=N`(强制逐张请求,模拟“每个模板各自封面”);标签追加 `批次-00..19`;安装态按 1/3 混合。
- 运行方式:
```bash
# 本机 dev 客户端(保留 Windows 默认 feature)
AGC_DEV_CARGO_FEATURES=cocos-editor-execute,template-library-fixtures npm run dev
# 直接跑二进制
cargo run --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --features template-library-fixtures
# 覆盖条数
AGC_TEMPLATE_LIBRARY_SYNTHETIC_COUNT=300 AGC_DEV_CARGO_FEATURES=template-library-fixtures npm run dev
```
两种编译模式都要过模板库单测:默认构建跑「恒等透传」用例,`--features template-library-fixtures` 跑「补齐到配置条数」用例。
### 1000 条实测结论
### 卡片列表虚拟滚动(react-window)
- 列表改用 workspace 里已有的 `react-window@1.8.11` 的 `FixedSizeGrid`(`react-arborist` 已在用同一版本,不引入新包;类型来自 devDependency `@types/react-window`)。
- 布局契约收在纯函数 `templateLibraryGrid.ts`(单测覆盖):列数 = `floor((容器宽 + gap) / (最小卡宽 + gap))`、列宽 = 容器宽 / 列数、行高 = `卡片宽 × 9/16 + 文字区 150 + gap`;`buildTemplateRows` 按行切分并在行尾补 `null` 占位。
- 卡片抽成 `TemplateCard`(`memo`),网格只渲染可视行 + 2 行 overscan;筛选条件(关键词/标签/运行时/仅看已下载)变化时把滚动位置复位到顶部,避免"从筛选切回全量后停在空白处"。
- **页面高度契约**:页面根节点的高度按**父级 `.launcher-main` 的实测高度**内联设置,既不用百分比也不用 `100vh`。原因:外壳样式 `.launcher-main > .platform-theme { height: 100% }` 特异性高于 Tailwind 工具类,而这条百分比在 `.launcher-shell { min-height: 100vh }` 链路上是不定高,页面会退化成内容高度(虚拟网格视口高度 0、卡片区整片空白);`100vh` 又比真实舞台高一个标题栏高度(窗口 100vh=800 / 舞台 750),底部会被裁掉。
- 筛选区(运行时/标签)改成可独立滚动的区块(`max-h-[24vh]`),标签数量随库量增长时不再把卡片区挤出窗口。
- 回归:`templateLibraryGrid.test.ts` 覆盖列数/行高/行数/切行;页面测试用固定视口断言「1000 条只渲染 ≤ 40 张卡片,滚动高度仍按 250 行计算」。
- 页面能正常渲染 1000 张卡片(头部显示「共 1000 个模板 · 已下载 335 个」),并且滚动容器生效(窗口高度压到 430px 时右侧出现滚动条,页面内容被裁切而不是溢出到窗口外)。
- 需要后续收口的两点(本次未改):① 标签筛选条随库量膨胀——1000 条时聚合出 35 个标签、占三行;② 一次性渲染 1000 个卡片节点并触发 1000 次封面请求。建议标签只展示 Top N + 「更多」,卡片列表加分页或虚拟滚动。
- 前端回归:1000 条渲染 + 已安装过滤(334)/标签过滤(50)/关键词过滤数量自洽,见 `apps/ai-game-creator-shell/tests/templateLibraryView.test.tsx`。
```bash
# 模板库单测(清单校验、键安全、解压路径逃逸、安装与建项目)
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
# 前端模型与页面单测(AGC 测试统一在 apps/ai-game-creator-shell/tests/)
npx vitest run apps/ai-game-creator-shell/tests/templateLibraryModel.test.ts apps/ai-game-creator-shell/tests/templateLibraryView.test.tsx
# 类型检查 / 编码 / 空白
cd apps/ai-game-creator-shell && npm run typecheck
npm run check:encoding
git diff --check
# OSS 侧匿名可读
curl -s https://agc-dev.oss-rg-china-mainland.aliyuncs.com/templates/index.json
```
手工验收:打开模板库 → 搜索与筛选 → 下载(出现「已下载」徽标)→ 「使用模板」→ 进入项目工作台且项目里已有模板文件。
运行态核验(客户端真的拉过清单时):本机缓存 `<app_data>/templates/index.json` 与线上 `templates/index.json` 逐字节一致;`<app_data>/templates/installed/<id>/<version>/installed.json` 出现即表示该模板已下载完成。
## 失败与回退
- 清单读不到且没有本机缓存:模板库页显示错误与重试,首页推荐位显示「模板库暂时没有可用的模板」。
- 版本落后:`installedVersion != templateVersion` 视为需要重新下载,点「使用模板」会先重下再建项目。
- 需要回退整条链路时,删除 `templates/` 前缀即可让客户端回到"空模板库";客户端代码路径不受影响。
@@ -4,6 +4,12 @@
`patch_file` 的所有 edits 均匹配同一份原文件,参数顺序不影响结果。完成唯一匹配与不重叠校验后,按原文起点升序拼接未修改片段与替换文本,最后一次性写入;任一校验失败时不写文件。回归用例覆盖乱序 edits、中文内容与替换长度增减,并核对完整落盘内容。此行为仅属于策划 Agent 文件工具。
## 策划 Agent 运行 panic 边界
`finish_design_command` 的运行段包在 `catch_unwind` 边界内:任何运行期 panic 被转成一次普通失败,由既有错误分支从最后一个持久检查点恢复,向会话写入固定公开文案"策划运行发生内部错误,本轮已中断,可直接重试;若反复出现请反馈。",并以 `running=false、可重试、lastError 有值` 的视图正常返回。panic 负载只写入私有 design_debug,不进入用户可见消息;continue、recover_uncertain 与 decide 三个入口共享同一边界。边界不改变工具错误、Provider 瞬态重试和批次不确定恢复的既有语义。
运行段通过 task-local 携带项目根;首次运行时安装的 panic hook 在 panic 瞬间把代码位置(file:line:column)和负载追加写入私有 design_debug,随后交给原 hook 维持既有 stderr 输出。hook 只在策划运行段内生效(其它任务无 task-local 上下文时直接透传),多线程 runtime 下 future 跨 worker 迁移也能正确归因。
## 资源画布交互与工作台状态同步
- 工作台向窗口标题栏发布正在运行的项目时,输入未变化不得形成重复发布与清理的渲染循环;打开项目动作始终使用当前工作台处理逻辑,退出工作台后清除其标题栏状态。