Compare commits

..

320 Commits

Author SHA1 Message Date
lhk229 85f2831f16 补齐客户端埋点环境变量示例
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m21s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m1s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m30s
Project CI / Backend tests (pull_request) Successful in 5m28s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m23s
Project CI / Native shell tests (pull_request) Successful in 6m43s
Project CI / Frontend tests (pull_request) Successful in 2m26s
Project CI / Repository checks (pull_request) Successful in 2m52s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m3s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m17s
Project CI / AI game creator shell Rust crates (push) Successful in 1m2s
Project CI / Backend tests (push) Successful in 4m49s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m6s
Project CI / Repository checks (push) Has started running
Project CI / Native shell tests (push) Successful in 6m0s
Project CI / AI game creator shell web tests (push) Has started running
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m48s
Project CI / Frontend tests (push) Successful in 2m8s
补充本地、生产及容器的埋点 origin 配置示例和 dev/release 说明。
同步技术方案中的运行时配置入口与地址匹配要求。
2026-09-22 16:14:05 +00:00
lhk229 1e186369c9 客户端埋点设置 (#446)
Project CI / AI game creator shell Rust crates (push) Successful in 1m24s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m56s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/446
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-23 00:09:12 +08:00
lhk229 5c2f85c089 修复 CI 缓存镜像组装的基础镜像引用
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 Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Successful in 1m18s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m50s
Project CI / Backend tests (push) Successful in 3m40s
Project CI / Frontend tests (push) Successful in 2m0s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m13s
Project CI / Native shell tests (push) Successful in 5m47s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m34s
Project CI / AI game creator shell web tests (push) Successful in 1m32s
Project CI / Repository checks (push) Successful in 1m55s
使用临时本地标签和宿主默认 builder 组装缓存镜像
组装成功或失败后清理临时标签,保持基础镜像清理归属
补充基础镜像复用、重建及失败清理测试
同步部署文档和项目共享记忆
2026-09-22 15:03:05 +00:00
lhk229 4380d453d7 修复 CI 缓存对象误判与产物下载完整性检查
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
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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 lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
按 ZIP 成员内容和元数据识别等价缓存对象,保留真实冲突拒绝规则。
核对产物大小与 ZIP 完整性,为截断和临时传输失败增加有界重试。
补充回归测试并同步部署文档与共享记忆。
通过 80 项缓存测试、17 项工作流测试及真实产物和 HTTP 截断验证。
2026-09-22 14:40:55 +00:00
kdletters f502829fde 发布灰度默认关闭:修掉客户端“看得到点不动”与后台看不到 key (#482)
Project CI / AI game creator shell Rust crates (push) Successful in 1m28s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m2s
Project CI / Backend tests (push) Successful in 5m19s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m59s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m19s
Project CI / Native shell tests (push) Successful in 6m48s
Project CI / Frontend tests (push) Successful in 2m54s
Project CI / Repository checks (push) Successful in 2m58s
Project CI / AI game creator shell web tests (push) Successful in 2m34s
## 解决什么

你反馈的两个现象都是真问题,这个 PR 一次修掉:

1. **没开灰度也能看到发布按钮**:发布开关此前是「无 gate 行 = 默认开放」,所以没配灰度时后端把 `gameDistributionPublishEnabled` 判成 true,客户端就渲染了发布入口。
2. **按钮点了没反应**:发布动作的提示原先只写 `workspaceStatus`,而普通项目走 `DirectProjectChatView` 时并不渲染工作台状态行,于是「先打开一个项目再发布」「构建失败…」这类信息全都看不见。
3. **后台看不到 key**:预设项此前在未合并的另一个 PR 里,而且后台只列「已创建」的 gate 行。

## 改成什么

- `is_game_distribution_publish_enabled_for_user` 要求 gate 行存在且 `enabled=true`:**灰度默认关闭**,未登录/无行/`enabled=false` 一律不开放;只有白名单 / 灰度比例 / 用户标签命中才开放。作者写入与新版本激活都按这条判据。
- 新增与作者无关的总开关 `is_game_distribution_publish_open`:管理员审核激活新版本只看「灰度是否开启」,不会因为审核员不在作者白名单里而被挡住(这是合入后立刻发现的回归,已修 + 有用例)。
- AGC 新增 `announcePublishMessage`:发布相关提示同时写工作台状态与 DirectProject 对话,点「发布到游戏广场」不会再像没反应。
- 后台「灰度发布配置」新增「可配置开关」列表:`game-distribution:publish` 等预设即使还没创建行也会显示,点「配置」按默认关闭填表,运营开启后填白名单/比例即可放量。

## 验证

- `npm run check:game-distribution-media-e2e`(真实 Phaser 构建产物)现在覆盖:灰度关闭 → 作者拿不到入口 + 写入口 503 → 开启灰度 → 真实封面/截图直传 → 创建游戏/版本/上传/送审 → 管理员审核通过 → 网关 index.html 与 bundle 200 → nosniff;全流程通过。
- 后端:`cargo test -p api-server game_distribution` 19 passed(含新增 `game_distribution_publish_open_ignores_author_allowlist_for_activation`)、`frontend_runtime_config` 6 passed。
- 前端:admin 灰度页 10 用例(含「未创建的预设开关可见并可一键配置」)、AGC 聊天头 3 用例、网页发布页 15 用例;三端 typecheck、`check:encoding`、`check:doc-index`、`check:rustfmt`、全量 `npm test`(400 文件 / 4441 用例)与 `check:repository-ci -- origin/master HEAD` 全绿。

## 上线注意

灰度默认关闭是**刻意**的:合入后到运营在后台把 `game-distribution:publish` 配置并开启之前,作者看不到发布入口、写入口 503;已公开游戏、目录、详情、发行网关、安全下架不受影响。

Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/482
Co-authored-by: kdletters <kdletters@qq.com>
Co-committed-by: kdletters <kdletters@qq.com>
2026-09-22 21:38:03 +08:00
kdletters a56790eaa6 合并 Phaser 一键发布自动构建与打包
Project CI / AI game creator shell Rust crates (push) Successful in 1m36s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m9s
Project CI / Backend tests (push) Successful in 5m41s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m33s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m53s
Project CI / Native shell tests (push) Successful in 7m28s
Project CI / Frontend tests (push) Successful in 3m38s
Project CI / Repository checks (push) Successful in 3m36s
Project CI / AI game creator shell web tests (push) Successful in 2m55s
2026-09-22 21:03:57 +08:00
kdletters 6e1fbac5d2 合并旧创作模板历史表删除
Project CI / AI game creator shell Rust crates (push) Successful in 1m27s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m0s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m54s
Project CI / Backend tests (push) Successful in 5m29s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m28s
Project CI / Native shell tests (push) Successful in 6m54s
Project CI / Frontend tests (push) Successful in 3m11s
Project CI / Repository checks (push) Successful in 3m20s
Project CI / AI game creator shell web tests (push) Successful in 3m6s
2026-09-22 20:46:37 +08:00
kdletters b93d15e46e 合并 master:DirectProject 旧消息重解析优化
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m35s
Project CI / Backend tests (pull_request) Failing after 17s
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
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
- 合并 origin/master(a46302846:优化 DirectProject 旧消息重解析导致的会话区卡顿)。
- 自动合并无冲突;Phaser 发布前构建与发行网关根路径改动保持接线。
- 同步目的:满足仓库「PR head 必须包含最新 base」的门禁,随后由 Project CI 复验。
2026-09-22 20:42:32 +08:00
kdletters 57a72f652c Phaser 项目一键发布:发布前自动构建与打包
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
- AGC:export_local_project_package 改为发布前构建——已有可玩入口直接打包;否则解析 game/(Phaser 4 + Vite 脚手架)或项目根的 npm build 脚本,缺 game/node_modules 时先跑 project.bootstrap(npm install),再走 project.verify 的受控 npm 运行器执行 npm run build,最后校验入口并打包;安装/构建失败返回带日志尾部的可操作错误。
- 新增 resolve_publish_build_plan 纯函数与用例:game 子工程优先并要求先装依赖、根 npm 工程回退、没有可构建工程时失败关闭。
- 后端:发行网关根路径(含尾斜杠)等价于 index.html,路由级用例覆盖 Cookie 拒绝门与根路径;生产仍由每游戏 origin 把根路径映射到该游戏入口。
- 脚本:check:game-distribution-media-e2e 支持 E2E_PACKAGE_ZIP 直接发布真实构建产物,断言从包内派生入口/资源,本地审核入口改为发行网关路径。
- 文档:玩法链路与实施计划记录「作者不构建、不打 ZIP」的 Phaser 发布口径与验证证据。
- 验证:真实 Phaser 4.2.1 + Vite 7 构建产物(相对引用)经发布链路后网关 index.html 200 / assets 1,388,719 B 200,播放页在 allow-scripts 沙箱 iframe 内渲染 PHASER-PUBLISH-OK 且点击交互生效。
2026-09-22 20:38:00 +08:00
kdletters 55801deb38 Merge remote-tracking branch 'origin/master' into codex/clear-retired-tables-phase2
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
Project CI / AI game creator shell Rust lane 2/2 (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 / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
2026-09-22 20:22:09 +08:00
k88936 a46302846a Merge pull request '优化 DirectProject 旧消息重解析导致的会话区卡顿' (#479) from fix/typing-stuck into master
Project CI / AI game creator shell Rust crates (push) Successful in 1m13s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m27s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m2s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m45s
Project CI / Backend tests (push) Successful in 7m7s
Project CI / Native shell tests (push) Successful in 7m36s
Project CI / Frontend tests (push) Successful in 3m30s
Project CI / Repository checks (push) Successful in 3m24s
Project CI / AI game creator shell web tests (push) Successful in 2m45s
Reviewed-on: #479
2026-09-22 20:20:07 +08:00
kdletters 1eaa51b72b 记录阶段二生产发布结果
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m41s
Project CI / Backend tests (pull_request) Failing after 17s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (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
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
记录 release 直接切换 2.8.3 锁定二进制
记录阶段二 wasm 发布后的 84 张现役表与旧 schema 清零
记录运行时服务恢复和公网 200
2026-09-22 20:09:35 +08:00
k88936 185649323e 优化 DirectProject 旧消息重解析导致的会话区卡顿
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m6s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m42s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m32s
Project CI / Backend tests (pull_request) Successful in 7m19s
Project CI / Frontend tests (pull_request) Successful in 3m47s
Project CI / Native shell tests (pull_request) Successful in 8m14s
Project CI / Repository checks (pull_request) Successful in 3m40s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m14s
为 ChatMarkdownMessage 加 memo,旧消息文本不变时不再重新解析 Markdown 与语法高亮
流式期间跳过语法高亮,定稿后再补,避免每个 chunk 重跑一遍 highlight.js
为 AgentReasoning 加 memo,内容未变的思考块整块跳过重渲染
把 AgentReasoning 折叠态预览的 Markdown AST 解析收进 useMemo
2026-09-22 20:04:52 +08:00
kdletters 05a5ea4d85 补充历史表清空与备份现场证据
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
记录 release 阶段一 apply、全零复核和观察窗口结果
记录 files-minimal 备份与 restore dry-run 验证
记录大库非 minimal files 备份的 catalog 序列化边界
2026-09-22 19:58:06 +08:00
k88936 944ae9a6eb Merge pull request '调试支持:开发模式下增加 Codex CLI 执行器锚定逻辑,确保裸命令名按 PATH 解析为绝对路径' (#475) from fix/linux-dev into master
Project CI / AI game creator shell Rust crates (push) Successful in 1m14s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m38s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m30s
Project CI / Backend tests (push) Successful in 6m47s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m16s
Project CI / Native shell tests (push) Successful in 7m14s
Project CI / Frontend tests (push) Successful in 3m21s
Project CI / AI game creator shell web tests (push) Successful in 3m0s
Project CI / Repository checks (push) Successful in 3m42s
Reviewed-on: #475
2026-09-22 19:53:06 +08:00
lhk229 7bc411119c 修复 CI 镜像脚本的 Buildx 驱动检查兼容性
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
读取 buildx inspect 的 Driver 字段,移除不受支持的 --format 参数。
收紧 Docker 测试替身参数检查,并覆盖错误驱动拒绝行为。
更新部署文档与共享记忆,记录真实 Buildx 兼容性验证要求。
2026-09-22 11:45:22 +00:00
kdletters 3103a6e632 Merge remote-tracking branch 'origin/master' into codex/clear-retired-tables-phase2
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m56s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m55s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m20s
Project CI / Backend tests (pull_request) Successful in 7m15s
Project CI / Native shell tests (pull_request) Successful in 8m18s
Project CI / Frontend tests (pull_request) Successful in 3m28s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m18s
Project CI / Repository checks (pull_request) Successful in 3m38s
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
2026-09-22 19:43:10 +08:00
kdletters b417144baf Merge remote-tracking branch 'origin/master' into codex/clear-retired-tables-phase2 2026-09-22 19:42:44 +08:00
kdletters fd0c1007ad 清除旧创作模板历史表定义与绑定
删除 63 张旧玩法表的 module 定义与 legacy schema
移除清空 procedure、迁移白名单和旧行兼容逻辑
重新生成 spacetime-client bindings
新增一次性 schema guard 删除白名单与回归测试
清理后台表名映射、旧回填工具和权威文档
2026-09-22 19:42:29 +08:00
lhk229 079466b29b 合并最新 master 到 CI 下载缓存优化分支
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
合入 origin/master 的浏览器进程清理与引用输入框重构。
保留 CI 下载缓存、最小构建上下文和维护日志优化。
通过 17 项工作流测试、2 项 npm 缓存导出测试及编码检查。
2026-09-22 11:33:40 +00:00
lhk229 1e6b0e5684 优化 Gitea CI 镜像下载缓存与维护日志
使用专用 BuildKit builder 持久复用 Cargo 与 npm 下载缓存,并支持从可信镜像导入
按当前依赖物化镜像下载快照,配置独立缓存回收策略
补齐 AGC vendor 本地依赖清单并缩小构建上下文
增加缓存维护阶段、耗时和失败日志路径
补充下载缓存与构建上下文测试,同步部署说明和共享记忆
2026-09-22 11:23:46 +00:00
k88936 a0d72bbef2 调试支持:开发模式下增加 Codex CLI 执行器锚定逻辑,确保裸命令名按 PATH 解析为绝对路径
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
2026-09-22 19:19:01 +08:00
k88936 da0df57ebc Merge pull request '重构/抽取带引用功能的输入框' (#464) from refactor/extract-dep-from-ref-inputer into master
Project CI / AI game creator shell Rust crates (push) Successful in 1m28s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m0s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m4s
Project CI / Backend tests (push) Successful in 7m8s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m47s
Project CI / Native shell tests (push) Successful in 8m26s
Project CI / Frontend tests (push) Successful in 3m50s
Project CI / AI game creator shell web tests (push) Successful in 3m28s
Project CI / Repository checks (push) Successful in 3m45s
Reviewed-on: #464
2026-09-22 19:05:31 +08:00
lhk229 e8db0d1c87 Codex/agc browser orphan cleanup (#461)
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Reviewed-on: #461
2026-09-22 19:05:20 +08:00
k88936 9ae9ed362a Merge remote-tracking branch 'refs/remotes/origin/master' into refactor/extract-dep-from-ref-inputer
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m25s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m56s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m51s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m30s
Project CI / Backend tests (pull_request) Successful in 7m17s
Project CI / Native shell tests (pull_request) Successful in 7m34s
Project CI / Frontend tests (pull_request) Successful in 4m4s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m9s
Project CI / Repository checks (pull_request) Successful in 3m36s
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
2026-09-22 18:51:27 +08:00
kdletters 060a850f74 修复 AGC 更新摘要泄漏提交说明
Project CI / AI game creator shell Rust smoke (push) Successful in 1m28s
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
自动摘要改为读取提交消息首行,仅展示标题并忽略后续说明

更新摘要不再附带短 SHA

补充多行提交消息与摘要格式回归测试

同步客户端更新检查与开发运维文档
2026-09-22 18:44:55 +08:00
lhk229 b1be6947b0 契约检查补齐现有 OSS 上传白名单,保留精确校验。
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (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
Project CI / AI game creator shell Rust lane 2/2 (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 / AI game creator shell Rust crates (push) Successful in 1m21s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m53s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m29s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m32s
Project CI / Frontend tests (push) Successful in 3m43s
Project CI / Backend tests (push) Successful in 8m59s
Project CI / Native shell tests (push) Successful in 9m45s
Project CI / Repository checks (push) Successful in 4m23s
Project CI / AI game creator shell web tests (push) Successful in 3m19s
修正过期注释并更新排障记录。
Native shell 契约检查、5 条素材上传测试、编码及格式检查全部通过。
2026-09-22 10:23:29 +00:00
lhk229 b7ca94334f 修正 CI 缓存部署的网络地址与验收流程
Project CI / AI game creator shell Rust lane 2/2 (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
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Successful in 1m24s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m57s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
区分任务容器、Runner 网关和宿主源码拉取地址
记录内层 Docker 独立地址池与遗留网络定向清理方式
补充真实任务网络中的源码拉取和 API 连通性验收
同步团队开发流程中的部署约定
2026-09-22 10:05:23 +00:00
kdletters 54a5401cce 将正式 Full Build 纳入 release 每日调度
Project CI / AI game creator shell Rust crates (push) Successful in 1m22s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m54s
Project CI / Native shell tests (push) Failing after 4m4s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m23s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 7m53s
Project CI / Backend tests (push) Successful in 7m54s
Project CI / Frontend tests (push) Successful in 5m34s
Project CI / Repository checks (push) Successful in 5m31s
Project CI / AI game creator shell web tests (push) Successful in 4m16s
按服务端与客户端 scope 分别触发 release Full Build 和 AGC 双平台
等待并汇总三类下游结果,按成功 lane 推进 revision
同步 release 参数、生产门禁与运维文档
2026-09-22 17:59:57 +08:00
kdletters 4d45afbd6e 修复 release 渠道 macOS 测试的默认环境隔离
隔离默认渠道测试中的 AGC_UPDATE_CHANNEL 环境变量
补充 Jenkins 渠道参数进入 Node 测试进程的排障记录
2026-09-22 17:59:57 +08:00
k88936 1ecb92c53d Merge branch 'master' into refactor/extract-dep-from-ref-inputer
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m17s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m51s
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
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
2026-09-22 17:57:51 +08:00
suzmii 4990dae829 Merge pull request '运行页入口收口:过程提示退出对话区,顶栏统一承载运行入口' (#457) from feat/agc-run-preview-tip into master
Project CI / AI game creator shell Rust crates (push) Successful in 1m25s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m55s
Project CI / Native shell tests (push) Failing after 3m12s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m35s
Project CI / Backend tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Reviewed-on: #457
2026-09-22 17:47:59 +08:00
k88936 c42e087a30 Merge remote-tracking branch 'origin/master' into refactor/extract-dep-from-ref-inputer
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 21s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 20s
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
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
2026-09-22 17:41:24 +08:00
suzmii 9b0fcd48d2 Merge branch 'master' into feat/agc-run-preview-tip
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m24s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m53s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 20s
Project CI / Backend tests (pull_request) Failing after 21s
Project CI / Native shell tests (pull_request) Failing after 21s
Project CI / Frontend tests (pull_request) Failing after 21s
Project CI / Repository checks (pull_request) Failing after 21s
Project CI / AI game creator shell web tests (pull_request) Failing after 21s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
2026-09-22 17:31:32 +08:00
kdletters 56fd5a98f7 修复 release 客户端下载清单的编码空格校验
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 21s
Project CI / Backend tests (push) Failing after 21s
Project CI / Native shell tests (push) Failing after 21s
Project CI / Frontend tests (push) Failing after 21s
Project CI / Repository checks (push) Failing after 20s
Project CI / AI game creator shell web tests (push) Failing after 14s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 1m56s
Project CI / AI game creator shell Rust crates (push) Failing after 1m54s
Project CI / AI game creator shell Rust smoke (push) Failing after 1m56s
放行客户端下载清单 URL 中百分号编码的空格
补充 release 安装包文件名回归测试
更新客户端下载规范与排障文档
2026-09-22 17:29:19 +08:00
suzmii c2d756a772 Merge branch 'master' into feat/agc-run-preview-tip
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m28s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m59s
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
Project CI / AI game creator shell Rust lane 2/2 (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 / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
2026-09-22 17:28:39 +08:00
kdletters 67e95eee4e Merge pull request '游戏分发与在线游玩:封面/截图冻结、网页可见可玩与 AGC 发布接入' (#466) from codex/game-distribution into master
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/466
2026-09-22 17:26:59 +08:00
kdletters c0c5255b31 Merge branch 'master' into codex/game-distribution
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
2026-09-22 17:26:50 +08:00
k88936 d5fb34209a 重构:候选懒加载改为菜单回调,match 收成纯函数
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (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 lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
- ReferenceProvider 新增可选 onMenuQueryChange(query):输入区在 useEffect 里回调(菜单关闭时收到 null),作为懒加载的唯一入口
- ProviderMentionMenu 不再让 provider 在渲染期产生副作用:match 只负责过滤已就绪的数据,契约里写明它必须是纯函数
- useSkillReferenceProvider 把目录读取从 match 搬进 onMenuQueryChange,第一次敲出 $ 才读;失败仍放开重试(下一次菜单查询变化时重试)并保留 console.warn
- referenceSourceProviders 用例改为经菜单回调驱动,并显式断言 match 不触发任何 Tauri invoke、菜单关闭(null)也不触发
- 同步 ADR、决策记录与功能说明里 provider 契约的那段描述
2026-09-22 17:24:24 +08:00
lhk229 ff7c5f3589 补齐 CI 缓存维护器请求与任务契约校验
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
要求 API 请求显式指定 HTTP 方法并更新全部调用点
增加 workflow 全部任务名称与缓存导出映射的契约测试
从 Rust 任务映射推导集合和数量,消除重复硬编码
同步开发流程中的维护约定
2026-09-22 09:22:54 +00:00
lhk229 705bb1e6b3 CI缓存自动维护与清理 (#462)
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/462
2026-09-22 17:19:11 +08:00
kdletters e5460280ce 发布入口接入灰度开关
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
- 后端:GET /api/runtime/frontend-config 新增 gameDistributionPublishEnabled,复用 `game-distribution:publish` 判据(未配置或 enabled=false 时对已登录作者默认开放,显式收紧后只放行白名单/灰度命中用户,匿名恒为 false),前端入口与写入口共用同一事实源。
- 后端用例:新增 frontend_runtime_config_game_distribution_publish_is_scoped_to_authenticated_gate,覆盖默认开放、enabled=true 无白名单、白名单命中、deny 名单、enabled=false 回退与 rolloutPercent=100。
- 网页:平台壳按灰度隐藏「发布游戏 / 发布新版本」入口;/games/publish 直接访问时渲染「发布功能正在灰度中」并提供重新检查;读取失败按放行处理,由后端写入口把关并返回可读文案。
- AGC:新增 readGamePublishAvailability(同一运行时配置字段),只有命中才把发布回调交给 DirectProject 聊天头;字段缺失或读取失败按不开放处理。
- 文档:玩法链路、后端数据契约与实施计划记录灰度口径、入口行为与验证命令。
2026-09-22 17:12:04 +08:00
k88936 aa973ea34d 同类问题:另外两个宿主的引用 provider 也按内容签名记忆
- PlanningChatView 的 chatProjectAssets 由工作台壳每次渲染重新 .filter() 派生、DirectProjectComposer 的 assets 由 manifest 订阅每次渲染重新派生,两处的 [assets] 依赖同样从不命中缓存
- 两处改用 assetsSignature(...) 做依赖,provider 身份不再随宿主渲染抖动(流式回复时每帧都换),输入区不再每帧重注册 Lexical 更新监听
2026-09-22 17:12:02 +08:00
suzmii f7250f5dd1 合并 master(bba4f0eb2):DirectProject 聊天三态与快照文档并入运行页入口收口
Project CI / AI game creator shell Rust lane 2/2 (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
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
- 只冲突两份共享记忆的尾部追加(pitfalls / decision-log):保留双方新增条目,直接合并非取舍
- App.tsx / styles.css 自动合并通过;本分支的运行页入口收口与 onRunNotice 通道未受影响
2026-09-22 17:08:03 +08:00
k88936 fbfc7e5e62 修复:候选菜单的 key 改用引用身份,同名素材不再撞 key
- 导出 resourceReferences 的 chatReferenceKey(只认稳定身份、不含显示名的身份键),并加注释说明它为什么不能省
- ReferenceMentionOption 由 super(chatReferenceMentionToken(...)) 改为 super(chatReferenceKey(...)):characters/hero.png 与 enemies/hero.png 都展开成 @hero,拿显示 token 当 key 会让 React 撞 key、复用错 DOM,键盘高亮与选中落到另一条候选上
- referenceSourceProviders 用例补一批同名素材:断言两条候选的显示 token 相同、身份键不同
2026-09-22 17:06:08 +08:00
k88936 3fdde6f51d 修复:并发导入附件时仍然守住 8 个上限
- useDirectProjectChatController 增加 attachmentsPendingCountRef:已放行但还没落成正文芯片的条数先在预算里占住名额,放行的部分在 finally 还回去
- 原来的预算只减草稿里已有的附件芯片数,两次并发导入会各自按同一份快照放行,最终插进正文的芯片可以超过 MAX_CHAT_COMPOSER_ATTACHMENTS(回归自「待发附件列表」被删掉的那次重构)
- 新增 tests/chatComposerAttachmentCap.test.tsx:两次并发各带 5 个文件,断言放行 5 + 3,并补 jsdom 缺失的 Blob.arrayBuffer
2026-09-22 16:59:36 +08:00
kdletters 1bd5fe9ff0 合并 master:AGC 项目快照整目录上传
Project CI / AI game creator shell Rust lane 2/2 (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
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
- 合并 origin/master(bba4f0eb2:项目快照整目录上传 .agent,保留项目身份与 Agent 历史)。
- 自动合并无冲突;共享记忆与实施计划文档经复核保留双方条目,游戏分发功能面未受影响。
- 同步目的:Gitea PR 门禁要求 PR head 包含目标分支最新提交,合并后重新推送触发 Project CI。
2026-09-22 16:59:05 +08:00
k88936 39ac337569 诊断:Skill 目录读取失败不再静默
- skillReferenceProvider 的 loadSkillCatalog 失败分支补 console.warn('[skill-reference] …'),带上是哪一次重试、错在哪,排障时不再只能看到「敲 $ 没反应」
- referenceSourceProviders 用例断言这条日志(并用 vi.restoreAllMocks 收尾),把「失败可重试」与「失败留痕」两件事一起钉住
2026-09-22 16:56:31 +08:00
k88936 28bacfc125 画布生成面板的引用 provider 按素材内容签名记忆
- ResourceCanvasAssetGenerationPanelView 的 referenceProviders 改依赖 assetsSignature(referenceAssets):referenceAssets 由 .filter() 派生、每次渲染都是新数组,原来的 [referenceAssets] 依赖永远命中不了缓存
- 注释写明「provider 身份一变,输入区就重新注册 Lexical 更新监听」,避免以后又改回数组依赖
2026-09-22 16:55:39 +08:00
kdletters d047b16b91 合并 master:DirectProject 回合三态与 AGC release 每日调度
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
- 合并 origin/master 11 个提交(DirectProject 回合三态与投影 memo、宿主崩掉后的回合收口、权限拒绝后的忙态、策划对话布局修复、AGC release 每日调度)。
- 本次自动合并无冲突;游戏分发发布入口(App 的 requestGamePublish、DirectProjectChatView 透传、聊天头「发布到游戏广场」按钮)在新聊天状态机下保持接线。
- 验证:root / AGC / admin-web 三端 typecheck、全量 vitest、游戏分发 Rust 测试、encoding、doc-index、rustfmt 与 SpacetimeDB schema guard。
2026-09-22 16:54:38 +08:00
kdletters f1b282f3cd 合并 master 并接入 DirectProject 新聊天架构
- 合并 origin/master(304 个提交:DirectProject 聊天容器重构、Project Supervisor 退役、策划附件导入、CI 隔离编译缓存等)。
- 接受 master 对 ProjectSupervisorView / SupervisorChatOnlyView 的退役与预览快捷测试收敛;发布入口改由 DirectProject 聊天头承载。
- DirectProjectChatHeader 新增「发布到游戏广场」入口(无回调不渲染、回合忙态禁用),DirectProjectChatView 透传 onRequestGamePublish。
- App.tsx 继续由工作台壳持有试玩包导出与 GameDistributionPublishPanel,沿用 project.export_package 权限确认队列;check-config 把该命令从 native-only 清单移回 App invoke。
- 后台游戏审核 API / 类型 / 路由测试与 master 新增的 AGC 模板管理按双方保留合并,并修掉拼接造成的接口与用例闭合缺陷。
- 修正 master 自带的 viteProxyConfig 断言:/api/creation-entry 属退役路由,测试改为断言不进入代理。
- 记录合并踩坑:语法结构内部的冲突不能简单按「双方保留」拼接,必须按某一侧骨架重建并跑 tsc 与单文件测试。
- 验证:全量 vitest 393 文件 / 4374 用例通过,root / AGC / admin-web 三端 typecheck,cargo check 与游戏分发 Rust 测试,encoding、doc-index、rustfmt、SpacetimeDB schema guard。
2026-09-22 16:45:29 +08:00
kdletters bba4f0eb24 项目快照整目录上传 .agent,保留项目身份与 Agent 历史
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m16s
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
Project CI / AI game creator shell Rust lane 1/2 (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 / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Successful in 1m17s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m47s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m3s
Project CI / Backend tests (push) Successful in 5m59s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m6s
Project CI / Native shell tests (push) Successful in 7m23s
Project CI / Frontend tests (push) Successful in 3m48s
Project CI / Repository checks (push) Successful in 3m37s
Project CI / AI game creator shell web tests (push) Successful in 3m40s
- 新增 should_skip_project_snapshot_sync_path:快照同步放行 .agent 组件,并放行其中 .db / .db-wal / .db-shm 的 Agent 状态数据库
- 组件与后缀排除表提取为常量;should_skip_project_snapshot_path 行为不变,项目索引、checkpoint、Agent 上下文与 git 检查继续排除整个 .agent
- project_snapshot 扫描入口切到新口径,.agent 的 manifest、agent.db、会话、日志、checkpoint、workbench 与 project.lock 全部参与同步
- 新增整目录扫描用例与策略用例,覆盖 .agent 内凭据、.env、版本库与构建目录仍被排除
- 主规范更新上传内容边界与后台归档范围:工程 ZIP 可用于还原项目身份与 AGC 对话历史
- 里程碑、实施计划与共享记忆决策记录同步 .agent 全量口径、真实上传与后台 ZIP 的运行时验证证据,以及 v1/v2 键布局边界
2026-09-22 16:39:12 +08:00
kdletters fdc14404b2 新增 AGC release 每日调度
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
新增 Genarrative-Scheduled-Release-Trigger,每天 04:00 发布 Windows/macOS release 渠道

release 调度复用统一发号源,并为 macOS 启用过期构建让位

现有小时 dev 调度保持同时触发 Windows 与 macOS

更新生产运维门禁、发号权限、技术方案与共享记忆
2026-09-22 16:32:11 +08:00
lhk229 3775e05970 修复策划对话布局与流式显示 (#460)
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
恢复固定消息区域和工作区状态条样式,修正窄屏单列布局
限制长阶段提示、澄清卡片和文件导入结果高度,保证输入区可达
补齐实时正文和思考的滚动跟随,保留用户上滚意图
移除读取策划历史时补造的发送时间
补充布局与显示回归测试,同步技术方案和共享排障约定

Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/460
2026-09-22 16:25:51 +08:00
k88936 742bad4fee Merge pull request 'Fix/unfinished turn' (#463) from fix/unfinished-turn into master
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Reviewed-on: #463
2026-09-22 16:23:25 +08:00
suzmii 0434647978 修复快速编辑草稿回填用例的竞态:面板先出现、Lexical 的值后落
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
- 同一条用例在 CI run 2702 与本地一次全量里各命中过一次「expected '' to include '把角色头发改成红色'」:面板(dialog)一出现就同步读 textContent,编辑器值还没落
- 回填类断言统一改走 expectPanelPromptContains(内部 waitFor),不再赌时序
- 「提交后重开是空面板」那条否定断言改用 composerValue 读(它先让编辑器落值),避免空串让断言空过
- 连跑该文件 9 次 + 全量 3 次(173 文件 / 1826 通过 / 18 跳过)均全绿
2026-09-22 16:13:10 +08:00
k88936 a6a938d5cc 文档:新增引用输入区重构与宿主注入的实施计划
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m34s
Project CI / Backend tests (pull_request) Failing after 17s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m25s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m23s
Project CI / Repository checks (pull_request) Failing after 16s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m26s
Project CI / Frontend tests (pull_request) Successful in 5m37s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m6s
Project CI / Native shell tests (pull_request) Successful in 10m39s
- 记录一句话交付、五条验收判据、六个提交切分与验证方式
- 追加执行状态:六项已落地内容、顺带清理、验证结果(全绿门禁与定向用例)与未完成项(CSS 类名重命名、真机手感、粘贴解析)
2026-09-22 16:07:06 +08:00
k88936 e6b6a0aeca 文档:记录引用候选由宿主注入的决策、术语与功能说明
- 新增 ADR《引用候选由宿主注入》:注入式 provider、面板外移、附件并入正文的理由、备选与影响
- decision-log 增加 2026-09-22 条目:注入 / 分离 / 附件 / 显示口径四项决策、未纳入项、验证方式,并记录顺带清理 activeVersionId
- 功能说明《AGC聊天素材引用》同步:Provider 注入、ResourceReferencePicker 自持数据、$ Skill 只出现在注入该 provider 的宿主、附件进入正文与上限口径、显示名注入
- CONTEXT.md 补引用来源 / provider / 选择器 / 附件芯片等术语与关系
- docs/README.md 登记新 ADR
2026-09-22 16:06:40 +08:00
k88936 1fbce7ffc5 清理工作台壳到聊天壳已经传不过去的 activeVersionId
- ProjectChatComponentProps 删除 activeVersionId:它只在策划输入盒的 @ 面板里生效,而策划输入盒的 @ 触发钮早就不渲染,参数从壳传进聊天后无人消费
- WorkspaceLauncher 不再把这个参数透传给 ProjectChat;工作台壳自己那份 activeVersionId 保留,仍供 ProjectDevelopmentView 的游戏运行版本使用
- App 的 AppProps 与解构同步删除该参数
- styles.css 里那条「ResourceReferenceInput 收到 showTriggerButton={!directCodex}」的注释改成当前口径:@ 触发钮在控制排的 .project-chat-reference-trigger,输入区不注入 ResourceReferencePickerAction
2026-09-22 16:05:08 +08:00
k88936 7e009a4c94 引用输入区改为宿主注入引用来源,素材选择器与输入区分离
- 新增 features/project-workspace/reference-source(types + 资源/Skill/附件/运行画面区域四个 provider 工厂),输入区按 providers 顺序取第一个非空回答,不再判断引用种类
- ResourceReferenceInput 删除 assets / versions / activeVersionId / skills / showTriggerButton / openPicker,改为 providers / inputActions / submitSuppressed / containerRef 四个通用接缝
- 新增 ResourceReferencePicker 与 ResourceReferencePickerAction:面板自己拿数据(素材、版本、缩略图预览 invoke)并自管筛选态,由宿主渲染,确认后只经 insertReferences 交回输入区
- 四个宿主改为按需注入 provider 并各自渲染选择器:DirectProject 注入资源+Skill+附件+运行区域,策划输入盒 / 画布生成面板 / 资源卡快速编辑只注入资源+两个静默 provider,非 DirectProject 宿主不再出现 $ Skill 候选
- 附件并入 ChatReference,编辑器收敛为单一 ResourceReferenceNode(删除 AttachmentReferenceNode),附件 chip 的 DOM 契约(data-attachment-reference / data-attachment-status / title / 移除按钮 aria-label)逐字保留
- 附件改为导入成功即以芯片进入正文、失败与异常一律不插入;控制器删除待发附件状态与 removeAttachment,ComposerPendingAttachments 与其提交时拼接一并删除,单次上限按草稿中的附件芯片数计算,导入进行中禁止发送
- directCodexContentToPromptText 的引用 token 前后各留一个空白(不重复),显示名反查改由调用方注入 resourceLabelResolver(assets),并补 TODO:后续改为按组件渲染引用
- 队列 chip 文案、快速编辑出站提示词、润色回填的引用恢复统一走 joinMentionText 的同一份 token 口径
- 新增 tests/referenceSourceProviders.test.ts 规则矩阵单测(触发符、候选过滤与截断、toReference、refresh、mentionToken、Skill 目录懒加载与失败重试),既有输入区/聊天用例迁移为注入 provider
- styles.css 删除随面板外移与待发附件列表一起失效的规则
2026-09-22 16:01:54 +08:00
k88936 00f36b4863 Merge remote-tracking branch 'origin/master' into fix/unfinished-turn
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m18s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m33s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m5s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m54s
Project CI / Backend tests (pull_request) Successful in 5m42s
Project CI / Native shell tests (pull_request) Successful in 7m25s
Project CI / Frontend tests (pull_request) Successful in 3m43s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m10s
Project CI / Repository checks (pull_request) Successful in 3m34s
2026-09-22 15:52:19 +08:00
k88936 a35956f3ec 宿主崩掉不再留下永远开着的回合:本地命令失败按身份兜底收口
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
- directThreadChat 新增 commandClosedTurnUserItemId 与 stopDirectThreadTurn:本地命令返回即这一轮在宿主那边收场,只放掉「是否在跑」、不写终态时间
- 同身份迟到的 turn.started 不再把这一轮拉回运行态;迟到的 turn.completed 例外放行,仍要拿它补上真正的结束时间
- 身份不同的回合不动:「正在跑的是另一轮」这种拒绝不能顺手抹掉别人的回合
- useDirectThreadChatSubscription 暴露 stopCommandTurn;controller 在真失败分支按本轮身份调用,主动终止与 another-turn-running 不走这条
- 新增用例:reducer 两条(不复活 / 不碰别人的回合)、appSurface 一条(失败后不再显示「陶泥儿正在处理」、输入盒回到发送态);变异去掉兜底收口后该用例变红
- decision-log 记录本次决策,以及仍未做的宿主侧根因(Rust 侧 drop 守卫补终态事件)
2026-09-22 15:34:48 +08:00
kdletters 500835407a 修复 AGC Windows 构建的 NSIS 回归测试路径兼容
Project CI / AI game creator shell Rust crates (push) Successful in 1m31s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m0s
Project CI / Backend tests (push) Successful in 5m9s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m3s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m11s
Project CI / Native shell tests (push) Successful in 7m3s
Project CI / Frontend tests (push) Successful in 3m50s
Project CI / Repository checks (push) Successful in 4m5s
Project CI / AI game creator shell web tests (push) Successful in 4m7s
NSIS 缓存目录解析改为按目标平台使用 win32/posix path,避免 Windows 将 /tmp 解析到当前盘

回归测试改用 defaultAppRoot,并补齐 Windows/POSIX 路径断言,消除 C:\\C:\\ 拼坏与分隔符误判

同步 shared-memory,记录跨平台路径测试约束
2026-09-22 15:33:33 +08:00
suzmii 3b2a6c8853 Merge remote-tracking branch 'origin/master' into feat/agc-run-preview-tip 2026-09-22 15:26:54 +08:00
k88936 cab31613f2 权限被拒不再抹掉下一轮的忙态:出队统一交给 finally
- startTurn 的权限被拒分支不再自己 endTurnCommand + dispatchNextQueuedTurn,改为标记 queueAdvance 交给 finally 收口
- finally 固定顺序:刷新清单 → endTurnCommand() → 出队,保证清忙态早于出队,不再清掉下一轮刚设上的忙态与 pendingUserItemId
- 模块注释把这条顺序写成契约,并说明权限被拒这种「本轮从未发出但要继续出队」的走法
- 新增 appSurface 用例:权限被拒 + 后面排着消息时,被出队那一轮在飞期间输入区仍是终止态;改前该用例红
2026-09-22 15:20:38 +08:00
k88936 67d2f08421 窗口期渲染用例先等 invoke 发出,别让断言在命令未发出时就过
- chat-composer 的 does not report a finished turn 用例捕获 invoke 并先等它被调用,再断言窗口期不出现终态文案
- 写权限门是异步的:不等待时断言会在 invoke 之前通过、收尾的 pending[0]?.resolve 变成空操作,用例盖不住它声称覆盖的窗口
2026-09-22 14:59:13 +08:00
k88936 c8a8b0b0b8 DirectProject 聊天投影补 memo,让下游 memo 不再空转
- DirectProjectChatView 的 directTurns 改用 useMemo 缓存,判据为 directEntries / localMessages / directTurnRunning / pendingUserItemId
- 注释写明不缓存时每次渲染都是新数组引用,useDirectProjectTurnStatus 里以 turns 为判据的 useMemo 永远命中不了,读者会误以为 turnStatus 引用稳定
2026-09-22 14:56:50 +08:00
k88936 417137baf8 把 DirectProject 聊天状态与数据流的说明写进代码注释
- useDirectProjectChatController 模块注释补三层地图、三份原始输入、一次发送的时序(含空窗步骤)与状态变量归属
- DirectChatTurnState 注明三态各自能断言什么、判据的四个输入及来路、state 真值表、两个易读错的点与「finished 拿不到终态时间」的已知边界
- DirectThreadChatState.turnRunning 注明它只是原生真相,不等于界面上的「这一轮在跑吗」
- DirectProjectTurnUsage 注释写清两类「finished 却拿不到终态时间」的回合、覆盖范围与修法口径,说明不再甩给任何文档
- decision-log 的未修边界条目改为指向上述代码位置,避免同一套说明在代码与文档里各留一份
2026-09-22 14:20:27 +08:00
k88936 cfcd3cd604 DirectProject 空窗期不再谎报本轮结束:三态接入渲染
- DirectProjectTurnUsage 渲染判据改为 state !== 'finished',awaiting-start 不再显示「本轮结束于 … 耗时 0.0秒」
- DirectProjectTurn 的过程折叠判据同样改为 state !== 'finished';流式正文与工具卡片仍按 state === 'running'
- 组件注释写明「否定式读 !== 'finished'、肯定式读 === 'running'」这条分工,并标出仍由 A 负责的另一半兜底与覆盖范围
- 新增 tests/directProjectTurn.test.tsx:awaiting-start / running 不显示终态文案且不折叠、finished 有终态时显示结束时间与耗时
- appSurface chat-composer 新增窗口期用例:invoke 挂起且无任何原生事件时不出现「本轮结束于」
- decision-log 追加本条决策、保守卡片口径的理由、未修边界与变异验证结论
2026-09-22 14:07:07 +08:00
k88936 860dcb1bf5 DirectProject 聊天状态显式化:回合三态与「在跑吗」唯一派生入口
- DirectChatTurn 的 active 布尔改为三态 state(running / awaiting-start / finished),三态判定收在投影里、用身份而不是时间戳
- buildDirectChatTurns 新增 pendingUserItemId 入参;controller 的 beginTurnCommand / endTurnCommand 与 turnBusy 同生共死地维护它
- 新增 useDirectProjectTurnStatus 作为「在跑吗」的唯一派生入口:nativeRunning / commandInFlight / displayBusy / latestTurnState
- DirectProjectChatView 不再手拼 busy:header 与 composer 读 displayBusy,「正在处理」卡片读 nativeRunning
- DirectProjectConversation 的 running 入参改名 nativeRunning,语义写在类型注释里
- DirectProjectTurn 改读 turn.state;渲染条件保持不变,awaiting-start 仍按已结束渲染(空窗缺陷另开改动)
- directTurnPresentation 的起点优先级与三态判定由嵌套三元改为逐级 if 覆盖
- 新增 directProjectTurnStatus 用例 4 条;directTurnPresentation 补三态用例 4 条并改按 state 断言
- ADR 补「活动回合唯一判据约束的是原生回合」澄清与代码指针
- decision-log 记录本次重构决策、未修的窗口期缺陷与验证证据
2026-09-22 14:06:20 +08:00
lhk229 292e42cf5e 为全部 Rust 测试组启用隔离编译缓存
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
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (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 lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Successful in 1m22s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m51s
Project CI / Backend tests (push) Successful in 5m35s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 7m8s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m11s
Project CI / Native shell tests (push) Successful in 7m6s
Project CI / Frontend tests (push) Successful in 3m30s
Project CI / Repository checks (push) Successful in 3m28s
Project CI / AI game creator shell web tests (push) Successful in 3m7s
扩展缓存消费到 AGC 双 lane、crates、smoke、后端及桌面壳测试
补齐各组实际编译目标和工作目录的可信快照预热
保留 SpacetimeDB 独立编译边界并排除发布构建缓存
根据完整快照体积将对象缓存上限调整为 4 GiB
更新工作流契约测试、运维文档及项目共享记忆
2026-09-22 05:52:16 +00:00
suzmii 6dde321bd1 修 review 发现:运行反馈全面退出对话区,并补齐顶栏与守卫细节
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m5s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m13s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m56s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 7m55s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m21s
Project CI / Native shell tests (pull_request) Successful in 8m53s
- executeRunLocal 的三条失败路径(缺 Tauri / 缺项目 / 启动预览报错)也改走 onRunNotice 失败色提示,对话区不再出现过程行;随之删除已无调用方的 announceProjectChatMessage 与不再有意义的 announceToChat 参数
- RunNoticeToast 的失败提示延长到 6 秒(成功仍 2.6 秒),避免错误一闪而过
- 版本入口在 UI 编辑器壳里不渲染;版本名改由一层 span 承载省略号,长版本名不再被硬裁
- 视图拿不到 onNotice 时把「在浏览器打开失败」写进 console,不再静默吞掉
- check-native-shells 增加「运行页视图里 openUrl( 只有一个调用点」的判据,堵住自动开浏览器的口子
- 新增外壳级接线用例 runNoticeShellWiring;previewActivation 补「启动失败走失败色提示且不写对话区」;runNoticeToast 补失败时长;样式守卫补省略号
- decision-log 收敛到最终口径(成功与失败都出对话区、失败留 6 秒、编辑器不挂版本入口)
2026-09-22 13:51:30 +08:00
suzmii 82de6d46a9 合并 master(48eb3d500):CI 缓存与运行时测试重组并入运行页入口收口
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m9s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m4s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m26s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 7m59s
Project CI / Backend tests (pull_request) Successful in 6m44s
Project CI / Native shell tests (pull_request) Successful in 7m55s
Project CI / Frontend tests (pull_request) Successful in 3m32s
Project CI / Repository checks (pull_request) Successful in 3m6s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m33s
- 只冲突 docs/project-memory/shared-memory/pitfalls.md:保留双方新增的排障记录(本分支的 Codex 原生包版本 + master 的 Rust 对象快照)
- 其余 master 改动(CI 缓存脚本、process_session 测试拆分等)自动合并
2026-09-22 12:56:22 +08:00
suzmii 0a8debef56 合并 master:运行页入口收口并入 DirectProject 重构
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
- 保留 master 的 DirectProject / ProjectChat 重构与测试重组:原 appSurface 的 project-commands / project-preview / supervisor-runtime 三个 suite 已被 master 删除,接受删除并把相关断言重写到 tests/previewActivation.test.tsx 等新用例
- 本分支改动重放到 master 结构:运行与预览成功的反馈改走 onRunNotice toast(带 tone),对话区不再写过程提示
- 运行页顶栏统一:预览地址改成「在浏览器打开」按钮,版本入口搬进同一动作区复用同一套按钮皮,状态行与预览地址小字退役
- 生成任务入口、面板与锚点不在运行页渲染,placement 里的 run 档一并删除
- 手工合并 App.tsx / WorkspaceLauncher.tsx / model.ts / check-config.mjs / decision-log / pitfalls,并把两条决策记录与一条排障记录补进 master 版本文档
2026-09-22 12:50:19 +08:00
lhk229 48eb3d5007 Opt/ci (#456)
Project CI / AI game creator shell Rust crates (push) Successful in 2m22s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m13s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m28s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 8m58s
Project CI / Backend tests (push) Successful in 7m16s
Project CI / Native shell tests (push) Successful in 8m10s
Project CI / Frontend tests (push) Successful in 3m49s
Project CI / Repository checks (push) Successful in 4m8s
Project CI / AI game creator shell web tests (push) Successful in 4m1s
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/456
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-22 12:42:27 +08:00
suzmii 5a87c5f965 运行页顶栏收口:预览地址改按钮,版本入口并入顶栏同一套样式
- 删除运行区域上方的状态行与预览地址小字,运行画面回到两行栅格
- 预览地址改成顶栏动作区里的「在浏览器打开」按钮(opener 插件 openUrl),只在有活预览时渲染
- 版本入口搬进同一个顶栏动作区,外观复用该容器的基础按钮样式,去掉自带的边框、底色与 hover
- 打开失败走 onNotice 的失败色 toast:onRunNotice 通道改成带 tone 的对象,四处调用点同步
- 补顶栏入口正向守卫,删掉状态行的旧样式用例并新增顶栏与浏览器打开的用例
2026-09-22 11:51:51 +08:00
k88936 1b951619c9 Merge pull request '统一错误事件同步留痕到 AppData 应用日志' (#453) from feat/log-the-dianosis into master
Project CI / AI game creator shell Rust crates (push) Successful in 2m57s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m46s
Project CI / Backend tests (push) Successful in 5m16s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m57s
Project CI / Native shell tests (push) Successful in 6m36s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 10m24s
Project CI / Repository checks (push) Successful in 2m37s
Project CI / Frontend tests (push) Successful in 3m54s
Project CI / AI game creator shell web tests (push) Successful in 2m15s
Reviewed-on: #453
2026-09-22 10:54:42 +08:00
suzmii 8e6ee50ad8 运行页收口:状态行同在画面外,生成任务不进运行页
- 运行区域上方新增状态行,预览地址小字与版本入口同排:两者都在游戏画面之外,版本入口不再绝对定位压在画面上
- 小字单行省略并用 title 给出完整地址,状态行高度恒定 30px,没有版本入口时也不塌
- 运行页不再渲染生成任务入口、面板与锚点;[data-generation-tasks-placement='run'] 那一档坐标与 placement 里的 run 一并删除
- 按新口径改写两侧用例,新增运行区域状态行的声明级守卫用例
- 记录决策到 shared-memory/decision-log.md
2026-09-22 10:29:53 +08:00
k88936 a3586bc995 Merge remote-tracking branch 'refs/remotes/origin/master' into feat/log-the-dianosis
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m56s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m49s
Project CI / Backend tests (pull_request) Successful in 5m7s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m13s
Project CI / Native shell tests (pull_request) Successful in 6m34s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m57s
Project CI / Repository checks (pull_request) Successful in 2m25s
Project CI / Frontend tests (pull_request) Successful in 3m39s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m46s
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
#	docs/project-memory/shared-memory/pitfalls.md
2026-09-22 10:26:13 +08:00
k88936 74e6ac90a7 把摘要移出应用日志身份行,避免整行脱敏吃掉事件定位
- 身份行只留程序生成或调用方常量字段(eventId / source / stage / code / retryable / clientTurnId / elapsedMs / detailRef),去掉了 summary
- summary 移到详情行(hint / summary / detail / metadata),自由文本全部只出现在详情行
- 原因:sanitize_diagnostic_message 命中凭据标记时替换整行,且裸词标记(如 credential rotation failed)脱敏消不掉,summary 留在身份行会连 eventId、detailRef 一起被替换
- 用例补一条裸标记词场景:详情行被整体替换时身份行仍能按 eventId 与 code 定位
- 同步【技术方案】AGC错误报告与诊断上传、【技术方案】AI游戏创作智能体App实施计划、决策记录与踩坑记录的两行字段口径
- 验证:cargo test -- runtime_error direct_tool_bridge(41 passed)、cargo fmt --check、git diff --check
2026-09-22 10:22:28 +08:00
suzmii e6083050a8 运行/预览提示退出对话区,改 toast 与运行区域小字
- 删除播放、/preview、/open-preview 与生成后自动启动预览写进对话区的成功提示,对话区只保留对话内容
- 新增 onRunNotice 通道,工作台壳用 RunNoticeToast 弹 2.6 秒浮层,同一句连续触发会重新计时
- 运行表现层在预览框上方常驻小字「已载入客户端运行视图:URL」,没有活预览时不占位
- 更新 appSurface 四处用例,新增 onRunNotice、小字与 RunNoticeToast 用例
- 同步 check-config 与 check-native-shells 的字符串判据,并记录决策到 shared-memory
2026-09-22 10:00:38 +08:00
k88936 6973a2cb45 Merge pull request '重构/拆分directproject聊天组件' (#420) from refactor/split-direct-project into master
Project CI / AI game creator shell Rust crates (push) Successful in 2m56s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m55s
Project CI / Backend tests (push) Successful in 5m5s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 10m20s
Project CI / Frontend tests (push) Failing after 2m20s
Project CI / Native shell tests (push) Successful in 6m36s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 10m34s
Project CI / AI game creator shell web tests (push) Successful in 1m36s
Project CI / Repository checks (push) Successful in 1m48s
Reviewed-on: #420
2026-09-22 02:53:26 +08:00
k88936 19e1883f71 合并 origin/master:策划附件导入接入策划聊天入口
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m55s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m54s
Project CI / Backend tests (pull_request) Successful in 5m4s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 10m18s
Project CI / Native shell tests (pull_request) Successful in 6m34s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m32s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m29s
Project CI / Repository checks (pull_request) Successful in 1m42s
- 保留 DirectProject 独立聊天容器与 Supervisor 退役结果,不为冲突恢复运行时兼容分支
- 策划工作区文件导入接入 PlanningChatView 输入盒,导入期间暂缓发送、阶段批准、澄清与重试
- 工作台壳按项目记录导入进行态,导入未完成时不切换游戏运行态并提示等待
- 首页策划附件导入结果落到工作台 fileImportNotice 提示条,可单独关闭
- 设计态输入盒样式选择器由 project-supervisor-* 收敛为 project-chat-*
- 策划导入回归用例适配退役后的 harness 与 chatProps 命名
2026-09-22 02:24:47 +08:00
lhk229 4e41bd3e56 修复预览发布测试状态文件恢复竞态
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m54s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m49s
Project CI / Backend tests (pull_request) Successful in 5m7s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m49s
Project CI / Native shell tests (pull_request) Successful in 6m47s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m45s
Project CI / Frontend tests (pull_request) Successful in 5m10s
Project CI / Repository checks (pull_request) Successful in 3m57s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m26s
Project CI / AI game creator shell Rust crates (push) Successful in 1m16s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m11s
Project CI / Backend tests (push) Successful in 4m55s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m50s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m2s
Project CI / Native shell tests (push) Successful in 7m2s
Project CI / Repository checks (push) Successful in 3m51s
Project CI / AI game creator shell web tests (push) Successful in 3m23s
Project CI / Frontend tests (push) Successful in 5m48s
deploy_and_uninstall 测试在断言状态文件恢复前,改为轮询等待持久化状态到达 Stopped

新增 wait_for_persisted_status 辅助函数,经 load_deployments 真实加载路径校验状态文件
2026-09-22 00:53:47 +08:00
k88936 5315b233c8 预览激活回接「运行」入口,聊天 @ 清单打开选择器时按需重读
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m58s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m49s
Project CI / Backend tests (pull_request) Successful in 5m7s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 10m7s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m15s
Project CI / Native shell tests (pull_request) Successful in 6m37s
Project CI / Frontend tests (pull_request) Successful in 3m8s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m45s
Project CI / Repository checks (pull_request) Successful in 1m56s
- App.tsx 新增 activateRunningPreview,运行入口先问 Rust 要活体预览(activate_local_game_preview),命中就只切客户端运行视图
- App.tsx 的 executeRunLocal 命中活体预览时经 DirectProject 聊天的 announce 播报「已切换到客户端运行视图」,不再重复调用 start_local_game_preview;stopped / 非本项目 / 权限位要求确认一律回落到重新启动
- check-config.mjs 从 native-only 白名单移除 activate_local_game_preview 并同步注释(该门禁要求 App invoke 与白名单互斥)
- DirectProjectComposer 新增 onReferencePickerOpen,工具条按钮与附件菜单两处选择器入口打开前先刷新清单
- DirectProjectChatView 用 useDirectProjectManifest 的 refresh 重读聊天自己那份 @ 清单,不新增壳到聊天的清单推送通道
- 新增 tests/previewActivation.test.tsx,钉住切视图不重启 / stopped 回落重启 / 权限确认回落重启三条路径
- decision-log 记录本轮两处回归的背景、决策、取舍与验证方式
2026-09-21 23:09:43 +08:00
k88936 a11cdf13d7 画布生成面板重开草稿与重试身份收敛到 canonical content
生成面板 state、重开草稿、重试身份与提交载荷统一为一份 content[]
重试身份改用 directCodexContentKey 内容指纹比对并删除 referenceIdsMatch
directCodexContentToLegacyContentDto 只在任务落账的出站边界派生
账本回到草稿的反向翻译收敛为 legacyContentDtoToContent 单一实现
token 扫描从 ResourceReferenceInput 提到 resourceReferences 并两处共用
无法解析的资源引用合成 unknown 占位而不再静默丢弃
出站提示词规范化收敛为 resourceCanvasAssetGenerationPromptText 供两处共用
directCodexContentToPromptText 去掉 trim,空白只在整条 content 上判定
同步决策记录与 canonical content 里程碑落地结果
2026-09-21 22:56:32 +08:00
lhk229 085181c795 修复策划附件导入工作区并解除资源清单耦合
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m55s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m21s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m58s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 10m40s
Project CI / Backend tests (pull_request) Successful in 5m37s
Project CI / Native shell tests (pull_request) Successful in 7m30s
Project CI / Repository checks (pull_request) Successful in 3m46s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m22s
Project CI / Frontend tests (pull_request) Successful in 4m54s
Project CI / AI game creator shell Rust crates (push) Successful in 2m57s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m51s
Project CI / Backend tests (push) Failing after 3m47s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 9m22s
Project CI / Native shell tests (push) Successful in 6m12s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 10m13s
Project CI / Frontend tests (push) Successful in 3m51s
Project CI / Repository checks (push) Successful in 2m44s
Project CI / AI game creator shell web tests (push) Successful in 2m25s
新增策划工作区文件导入命令,支持同名另存并保留文件字节
首页和聊天框附件直接导入策划工作区,脱离游戏资源清单与回合附件
导入期间暂缓发送、阶段操作和游戏制作切换,保留逐文件失败提示
补充导入、刷新与切换回归测试,同步技术方案和共享约定
2026-09-21 22:49:21 +08:00
k88936 cc980267fe 用例改为按生产口径传入已脱敏 detail
- 原用例直接传带 token=secret 的原文,与函数「detail 已是 sidecar 脱敏文本」的前置条件不符,测的不是真实数据流
- 改为先按 8 KiB 口径 redact 出 safe_detail 再传入,metadata 仍按生产原样传未脱敏 JSON,保留脱敏覆盖
- 验证:cargo test runtime_error(7 passed)
2026-09-21 22:45:06 +08:00
k88936 4bdf9e2cd4 写明应用日志投影为什么对已脱敏 detail 再过一遍
- 原注释写「只再按应用日志预算截一次」,实现却是完整脱敏流水线,合同与实现不一致
- 采纳方式是把合同写实:detail/metadata/summary 都在这里脱敏;已脱敏的 detail 也走同一遍,理由是截断会切开脱敏标记,且这是 pub(crate) 边界不假设调用方先脱敏
- 未采纳「去掉再脱敏改为只截断」:该建议会把安全性绑定在调用方自觉上,收益只是一次可忽略的 canonicalize
- 验证:rustfmt(注释改动,无行为变化)
2026-09-21 22:42:56 +08:00
k88936 5847505020 用例按逐行口径核验应用日志落盘边界
- 生产是把两行分别交给 append_application_log_line 脱敏截断,原用例先 join(" ") 再 sanitize,测不到逐行截断与「只该脱敏其中一行」的情况
- 改为逐行 sanitize,并分别断言身份行(eventId/code/detailRef/summary)与详情行(detail 脱敏、metadata 无凭据)
- 验证:cargo test runtime_error(7 passed)
2026-09-21 22:20:25 +08:00
k88936 c49ee8d157 详情行的 metadata 补上单行压平
- metadata 之前直接以 {metadata} 插值进日志行,只靠 Value::to_string() 的转义保证单行,和函数「每个字段都先压平」的约定不一致
- 改为与其他字段一样走 single_line_log_field,日志行完整性不再依赖序列化器的转义行为
- 验证:cargo test runtime_error(7 passed)
2026-09-21 22:16:05 +08:00
k88936 852f5de886 修复应用日志身份行的摘要只压行未脱敏
- 身份行的 summary 由调用方给,direct_tool_bridge 传的是工具错误原文,而 app_log! 会把整行写 stderr,那里没有 sanitize_diagnostic_message 兜底
- 新增 AGENT_RUNTIME_ERROR_APP_LOG_SUMMARY_CHARS(320 字符,与 sidecar 摘要预算同口径),summary 落日志前先脱敏再截断
- 函数合同写明 detail/metadata/summary 三类外来文本都在这里脱敏,不再只是压平换行
- 同步【技术方案】AGC错误报告与诊断上传与共享记忆 decision-log 的预算口径
- 验证:cargo test runtime_error(7 passed)
2026-09-21 22:12:41 +08:00
k88936 8968762274 合并 origin/master:DirectProject 输入校验取回空白片段回归用例
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m57s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m47s
Project CI / Native shell tests (pull_request) Failing after 50s
Project CI / Frontend tests (pull_request) Failing after 1m50s
Project CI / Backend tests (pull_request) Successful in 4m45s
Project CI / Repository checks (pull_request) Successful in 2m1s
Project CI / AI game creator shell web tests (pull_request) Failing after 1m38s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 9m21s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m31s
- validation.rs 保留本分支的 content_has_meaningful_input 与 Skill/附件独立上限,空输入报错取回 master 的用户可见文案“聊天内容不能为空”
- wire.rs 取回 master 新增的 text_projection / user_input_rejects / valid_references / nonempty_text 四条回归用例,测试导入合并两侧
- resourceReferenceInput.test.tsx 取 master 的注释口径:草稿 text 投影会 trim,提交用的 content 保留 chip 后的分隔空格
- pitfalls.md 与《AGC聊天素材引用》改为「任意非文本 part 均算有效内容」,与四类引用的实际校验对齐
- 本地私有改动的 .env 不进入本次合并
2026-09-21 21:42:41 +08:00
k88936 8b2d0cb9df 统一错误事件同步留痕到 AppData 应用日志
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m57s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m50s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 6m44s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 7m28s
Project CI / Frontend tests (pull_request) Successful in 4m9s
Project CI / Native shell tests (pull_request) Successful in 8m24s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m37s
- 新增 agent_runtime_error_app_log_lines,把统一错误事件的同一份已脱敏诊断投影成应用日志两行:agent.runtime.error 身份行与 agent.runtime.error.detail 详情行
- 身份行含 eventId/source/stage/code/retryable/clientTurnId/elapsedMs/detailRef/summary,详情行含 hint/detail/metadata;detail 与 metadata 各按 1200/200 字符预算先脱敏再截断,字段仍只来自 sidecar 那份 diagnosis
- 拆两行的原因:整行命中凭据标记会被 sanitize_diagnostic_message 整体替换成脱敏占位,详情行被吃掉时身份行仍能定位 eventId 与 detailRef
- app_log! 先于 sidecar 写入,sidecar 写失败也留下可提交的诊断;各字段先压平换行,保证应用日志逐行读取且 stderr 输出不拆行
- 新增用例覆盖身份字段、凭据脱敏、单行口径与实际 sanitize_diagnostic_message 落盘边界(含超长诊断)
- 同步文件【技术方案】AGC错误报告与诊断上传与【技术方案】AI游戏创作智能体App实施计划,共享记忆 decision-log 与 pitfalls 明确项目内 .agent/runtime/errors sidecar 与进程内错误报告事件池是两套东西,本次只写日志行
- 验证:cargo test runtime_error(7 passed)、cargo fmt --check、npm run check:encoding、git diff --check
2026-09-21 21:34:45 +08:00
k88936 7745264990 同步canonical content里程碑落地结果与润色口径
里程碑与实施计划状态改为 implemented

记录润色按 token 原位恢复引用与附件、被改写 part 末尾补位

记录遗留 TODO:润色删掉 token 时无法反推原位置
2026-09-21 21:23:17 +08:00
k88936 295d4af384 润色回写按token原位恢复引用与附件
统一引用与附件的润色扫描口径,token 存活时原位换回真 part

token 被改写时剩余 part 补在末尾,附件不再丢出 canonical content

删除无调用方的 EMPTY_CHAT_COMPOSER_DRAFT

补充润色原位恢复与末尾补位两条回归用例
2026-09-21 21:22:04 +08:00
k88936 6981648796 合并 origin/master:Supervisor 永久退役,项目对话收敛为 DirectProject 与 Design Agent
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m56s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m48s
Project CI / Native shell tests (pull_request) Failing after 45s
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Frontend tests (pull_request) Failing after 1m52s
Project CI / AI game creator shell web tests (pull_request) Failing after 1m42s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m57s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 8m1s
- 解决 refactor/split-direct-project 与 origin/master 在 App.tsx、立项策划聊天视图、Direct composer/引用输入区、styles.css、Rust direct user item 与 appSurface 用例上的冲突,按「Supervisor 永久退役」口径保留 DirectProject 独立聊天容器与 Design Agent 两条产品路径
- 采纳 master 的策划 Agent V1/V2 退役:删除 GDD 审批卡、策划输入卡、planningLane、planningSessionV2、planningSessionContract、规划展示适配与 Rust planning_*_v2 命令、模块、契约及对应用例,不保留兼容别名或双跑路径
- 把 master「折叠思考显示单行预览」的目的落到当前结构:新增共享表现 chat/components/AgentReasoning/AgentReasoning.tsx(折叠态单行纯文本预览 + 箭头、展开态安全 Markdown),DirectProject 回合与策划回合共用,删掉两处写死的 pre 折叠实现
- 把 master「策划入口可选模型 / 推理档」的目的接到当前策划输入盒:复用 ConversationModelSelect 与 ComposerReasoningEffortSelect,配置写回仍走客户端配置通道
- App.tsx 删除只服务退役 Supervisor / 策划 V2 的 state、ref、effect、回调与死参数,并删除两条读路径都退役后的 workspaceProjectKind;openWorkspace 的工程类型入参保留为未使用契约
- Rust 侧保留本分支 canonical→wire 投影、无审计 Direct 回合与 direct user item 严格校验,并入 master 的 prepare_new_web_project_at 前置复核
- 更新 ADR 与 shared-memory 决策记录:策划当前只有 Design Agent、两条路径的共享表现清单,以及本次合并的口径、代价与验证证据
- 验证:AGC 与仓库 typecheck、check:encoding、check:doc-index、git diff --check、改动文件 eslint 0 error;AGC vitest 168 个文件中除 5 个 jsdom localStorage 环境失败文件与本分支既有 resourceTagStatsRefresh 失败外全绿,appSurface 198 passed / 13 skipped;Rust 定向用例 direct_codex_user_item、skill_pack、sessions 全过(整套分片在本容器受 /sbin -> usr/bin 触发沙箱预检失败,与本合并无关)
2026-09-21 21:09:34 +08:00
lhk229 2d61a4c057 Merge pull request 'Codex/fix direct image generation ci' (#452) from codex/fix-direct-image-generation-ci into master
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/452
2026-09-21 19:46:05 +08:00
lhk229 e2bdb1105b 合并最新 master 到图片生成 CI 修复分支
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m7s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m52s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m36s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m10s
Project CI / Repository checks (pull_request) Successful in 5m5s
Project CI / Frontend tests (pull_request) Successful in 5m40s
Project CI / Backend tests (pull_request) Successful in 8m16s
Project CI / Native shell tests (pull_request) Successful in 9m49s
Project CI / AI game creator shell web tests (pull_request) Successful in 4m45s
合入最新 master 的客户端图标素材描述修复
保留图片生成通知测试的宿主执行会话初始化与失败断言
2026-09-21 11:20:53 +00:00
lhk229 71f5f36764 修复图片生成通知测试缺失宿主执行会话
复用真实宿主执行会话与交付合同夹具,补齐 Direct 工具桥测试前置状态
保留图片资源提交后的刷新通知断言,并核对空提示词拒绝原因
同步更新图片生成集成测试初始化说明
2026-09-21 11:15:39 +00:00
lhk229 f71361c292 修复客户端图标素材生成丢失用户描述 (#450)
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
删除图标生成的固定提示词包装、默认美术说明和两次截断
按服务端单条描述语义校验非空及200个Unicode字符,超限拒绝并保留原文
保留内部换行,直接以单条iconDescriptions提交用户描述
更新前端、原生入口和实际HTTP请求回归测试
同步工具说明、生成契约及项目排障文档

Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/450
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-21 19:12:35 +08:00
suzmii cc8c03a40d 运行游戏不再往聊天写"运行通过"提示
- 删掉 AGC 播放/运行本地游戏成功后的聊天提示:它每次播放都会堆在对话底部遮挡运行画面,成功与否由客户端运行视图本身体现,失败仍照原样回报
- appSurface 三处用例改为断言 start_local_game_preview 真被调用且聊天里不再出现该提示
- 补排障记录:本地 node_modules 内置 Codex 原生包过旧会让 AGC build script panic,需在仓库根目录 npm ci
2026-09-21 19:08:43 +08:00
lhk229 6e9e7edcf8 修复回显测试身份数据与 Linux 条件编译
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m2s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m46s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 6m12s
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web 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 / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
按实际用户输入生成 SHA-256 请求摘要,保留回显去重与历史记录断言
保留 Windows 安装身份辅助函数的平台限制,补齐两处 AppData 调用分支的条件编译
同步 Direct 回合测试与安装路径策略文档
2026-09-21 10:59:28 +00:00
lhk229 480fe995ed Merge remote-tracking branch 'origin/master' into codex/fix-agc-command-runtime-ci 2026-09-21 10:43:06 +00:00
lhk229 afd5b8d91f 修复 Linux 命令执行将孤儿僵尸误判为未回收进程
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m59s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m46s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m39s
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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
命令主进程退出后区分存活成员与僵尸成员,保留进程组身份校验和失败关闭语义
处理 procfs 扫描期间进程退出及非 UTF-8 进程名,空进程组直接返回
新增隔离 subreaper 回归测试并补全命令 observation 断言诊断
同步 Runtime 技术方案与共享排障记录
2026-09-21 10:30:41 +00:00
kdletters c60d116af0 合并 codex/agc-godot-templates-20260921 到 master:新增 Godot 模板与按模板建项分流
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
- 新增 Godot 模板源 godot-empty-2d / godot-empty-3d / godot-hello-world / godot-platformer-2d

- 按模板建项新增 Godot 分流:改写 project.godot 的 config/name 后走既有 Godot 导入并写入相对根

- Godot 工作区发现放宽:一层多命中按目录名排序取首个,project.godot 允许链接,内置插件行去掉手动启动

- 解决 decision-log.md 冲突:保留渠道安装身份与项目快照按渠道分区两条记录,并合入 Godot 两条记录
2026-09-21 18:17:33 +08:00
kdletters 3eb99f5124 合并 codex/agc-channel-install-identity 到 master:AGC 渠道安装身份隔离
- AGC 渠道安装身份隔离:不同渠道包体可在同一台设备并存
2026-09-21 18:17:12 +08:00
kdletters 34b1ad5d5c 新增 Godot 模板并补按模板建项的 Godot 分流
- 新增 4 个仓库内手写 Godot 4.7 模板源:godot-empty-2d、godot-empty-3d、godot-hello-world、godot-platformer-2d,entry 统一为 project.godot,各自带 meta.json 与 960×540 封面
- template_library.rs:复制模板后按 project.godot 识别 Godot 模板,改写工程显示名后走既有 Godot 导入并写入 godotProjectRoot,不再落到 Web 占位入口
- manifest.rs:新增 apply_godot_project_display_name,只改 [application] 段的 config/name 一行,其余字节逐字保留
- manifest.rs:放宽 Godot 工作区发现,一层子目录多命中按目录名排序取第一个,project.godot 允许是符号链接 / reparse point / 硬链接
- commands.rs:同步 Godot 导入错误文案,去掉「普通文件」限定
- RuntimeConfigDialog.tsx:内置插件行去掉手动启动 / 停止按钮,启动由前端自动启动承担、停止走该行启用开关
- import_tests.rs 与 tests/project.rs:补显示名改写、多命中确定性、链接标记与错误文案的定向回归
- 同步更新 PRD、模板包组织指南、模板库与模板建项技术方案、AGC Godot 编辑器插件接入方案、实施计划与共享决策记录
2026-09-21 17:25:34 +08:00
kdletters c52e0814d6 AGC渠道安装身份隔离:不同渠道包体可在同一台设备并存
- 新增 channel-identity.mjs 单点定义渠道安装身份,默认渠道保持基线 productName 与 identifier
- build-release.mjs 渠道 --config 同批注入 productName、identifier 与 updater 端点
- build-macos-ci.mjs 按发布渠道解析产品名,派生 .app、updater 归档与 DMG 名称
- main.rs 与 windows.rs 窗口标题取构建期产品名,同机并存的渠道客户端可区分
- config.rs 提权 ACL 的 managed 范围按目录名识别基线目录与渠道派生目录
- check-config.mjs 断言基线等于默认渠道身份且非默认渠道身份必须隔离
- build-release.test.mjs 与 prepare-macos-codex.test.mjs 补齐渠道身份与渠道首装包用例
- 更新 AGC 更新主规范、开发运维文档、共享记忆与渠道安装身份隔离的里程碑与实施计划
2026-09-21 17:06:03 +08:00
kdletters 4951b71d71 项目快照按渠道分区,后台项目工程支持渠道筛选与游标分页
Project CI / AI game creator shell Rust crates (push) Successful in 2m46s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m37s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 6m5s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 6m40s
Project CI / Repository checks (push) Successful in 5m37s
Project CI / Frontend tests (push) Successful in 6m21s
Project CI / Backend tests (push) Successful in 9m56s
Project CI / Native shell tests (push) Successful in 10m57s
Project CI / AI game creator shell web tests (push) Successful in 5m29s
项目快照对象键升级为 agc/project-snapshots/v2/{channel}/{user}/{project}/,渠道取部署配置 GENARRATIVE_AGC_PROJECT_SNAPSHOT_CHANNEL(缺省沿用客户端下载渠道),非法渠道在写入处失败关闭
后台新增渠道列表接口,项目工程列表与下载接受 channel,游标绑定渠道并拒绝跨渠道复用
后台项目工程用户列改为素材查询口径:昵称 + 陶泥号 + 用户详情入口,由 api-server 解析作者信息
后台项目工程改为游标分页:每页 20/50/100、上一页/下一页与当前页提示,翻页失败保留当前页
部署环境示例补充快照渠道配置,并同步运维、技术方案、里程碑与决策记录文档
2026-09-21 16:57:16 +08:00
lhk229 8520b50c81 修复游戏智能体输入含空白片段时被误判为空 (#449)
Project CI / AI game creator shell Rust smoke (push) Successful in 2m20s
Project CI / AI game creator shell Rust crates (push) Successful in 1m17s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 6m51s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 5m21s
Project CI / Backend tests (push) Successful in 7m33s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
改为按整条消息判断有效内容,保留空白文本片段及两类引用的原有校验
补充多行文本、纯引用、全空白输入及非法引用的定向回归测试
同步素材引用文档、共享排障记录和过时测试注释

Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/449
2026-09-21 16:36:59 +08:00
kdletters c5afc02e94 模板正文目录门禁:CLI 打包与后台上传统一拒绝身份/版本库/依赖/构建目录
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
- scripts/agc-template-library-publish.mjs:readProjectFiles 增加正文门禁,任意层级拒绝 .agent/.git/.svn/node_modules,根目录拒绝 dist/build/library/temp/local/.idea/.vscode
- scripts/agc-template-library-publish.test.mjs:新增夹具源目录与门禁用例,覆盖 13 条拒绝路径及 game/dist/**、game/.gitignore 放行
- server-rs/crates/api-server/src/admin_templates.rs:上传归档校验与 CLI 用同一份段名单,新增 template_import_archive_rejects_identity_and_build_directories
- docs/【模板规范】AGC模板包组织指南-2026-09-21.md:已知边界改为「已有机器门禁」,并说明同名目录段只在根目录受限
- docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md:CLI 与后台上传的校验口径补门禁,并追加 2026-09-21 证据(九个模板本地重打包摘要与线上清单逐条一致)
- docs/project-memory/plans/【里程碑】后台模板上传-2026-09-21.md:验收标准 2 补充门禁证据
- docs/project-memory/shared-memory/decision-log.md:新增 2026-09-21 正文目录门禁决策记录
2026-09-21 16:25:35 +08:00
kdletters b5e1c0f1d6 新增模板包组织规范并接入文档索引
Project CI / AI game creator shell Rust crates (push) Successful in 2m48s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m37s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 6m5s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 8m59s
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
- 新增 docs/【模板规范】AGC模板包组织指南-2026-09-21.md:ZIP 根目录契约、html 与 Cocos 工程的建议结构与保留项、禁止放入的内容(.agent/.git/node_modules/dist/密钥/嵌套锁文件)、路径与体积上限、封面要求、元数据字段约束、版本不可变规则与发布前自检清单
- 说明两条发布路径的输入差异(CLI 源目录 vs 后台成品 ZIP 上传)与各自上限,并标注「工具不会自动拦截 .agent/.git」这一已知边界
- 主规范与模板库 README(会发布到 OSS templates/README.md)各加一条指向该指南的入口
- docs/README.md 登记新文档,check:doc-index 通过(194 份)
2026-09-21 16:16:21 +08:00
kdletters 15e7d6065e 记录后台模板上传里程碑的实现与证据
Project CI / AI game creator shell Rust crates (push) Successful in 2m58s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m51s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
- 里程碑 Status 更新为 implemented-awaiting-runtime-acceptance
- 补实现与证据表:路由/权限、写入前整批拒绝、批量原子性、版本一致性、CAS 与锁、保留语义、内容寻址与回读、界面用例与静态检查
- 明确仍未执行:真实 dev bucket 的批量写入 smoke(需用户确认,建议两个 smoke-import 模板验证后下架)
2026-09-21 16:10:55 +08:00
kdletters 2e23a54211 后台模板上传(页面):模板管理页支持批量选择 ZIP 与封面并整批提交
Project CI / AI game creator shell Rust crates (push) Successful in 3m2s
Project CI / AI game creator shell Rust smoke (push) Successful in 4m5s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
- 新增上传模型:文件名派生模板 ID、按同名匹配封面、逐行字段校验与 multipart 组装(标签去重与上限)
- 模板管理页新增「上传模板」入口与弹窗:多选 ZIP、按 ID 同名多选封面、逐行编辑 ID/名称/版本/运行时/entry、逐行错误、沿用写入确认与防重复提交
- 失败只在弹窗内交代并区分 409 刷新引导与 503 锁占用提示,401 仍交由会话处理;成功后用服务端快照刷新列表并展示逐条导入结果
- admin API client 支持 multipart 请求(不预设 JSON Content-Type),新增 importAdminAgcTemplates 与对应类型
- 主规范新增「后台模板上传」章节并收口原「不上传 ZIP」表述;决策记录补一条
- 验证:admin-web typecheck、页面/模型/客户端 40 项 vitest、check:doc-index、check:encoding、cargo fmt --check、git diff --check
2026-09-21 16:06:23 +08:00
kdletters 4863c8c066 后台模板上传(后端):新增批量导入接口与存储/领域支持
- shared-contracts 新增导入 manifest、逐条与结果 DTO(camelCase,拒绝未知字段)
- module-assets 新增 prepare_template_import:新 ID 默认上架,已存在条目就地更新并保留上架状态与未知扩展字段;同一 ID 同版本字节不同时按 CLI 同一句文案拒绝,字节一致时按内容复用既有对象
- platform-oss 为 application/zip 放宽单对象上限到 64 MiB(图片与元数据仍 5 MiB),测试替身同步该口径
- api-server 新增 POST /admin/api/agc-templates/import:multipart(manifest + zip_N/cover_N),逐项校验归档安全性、entry 存在性与封面(按字节嗅探格式,仅 PNG/JPEG/WebP)后才取发布锁
- 锁内流程:CAS 校验 revision → 内容寻址写入 zip/封面/元数据并回读 → 一次提交清单 → 释放锁;断连由独立任务持有,不确定结果保留锁并返回 503
- 路由挂 require_admin_auth 与 200 MiB body 上限,页签权限矩阵与路由契约测试同步
- 新增用例:清单与文件字段校验、归档越界与符号链接拒绝、内容寻址键与封面嗅探、zip 内容类型存储上限
- 新增「后台模板上传」里程碑与实施计划文档
- 验证:cargo test(module-assets 11 项、platform-oss 12 项、api-server 定向 4 项)、cargo fmt --check、check:doc-index、check:encoding、git diff --check
2026-09-21 16:06:23 +08:00
kdletters 33036b65fc 修复 AGC Windows 构建的 Tauri NSIS 工具链重复下载与截断失败
Project CI / AI game creator shell Rust crates (push) Successful in 3m3s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m50s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 6m16s
Project CI / Backend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
tauri-bundler 打包时现场从 GitHub 下载 nsis-3.11.zip 与 nsis_tauri_utils.dll 且不重试,
Checkout 的 git clean -fdx 又会删掉工作区内的 target/.tauri,于是每个构建都要重下,
响应一旦被截断只会抛 `io: unexpected end of file`,让发布在 Rust 编译数分钟后失败。

- Jenkins Checkout 的 git clean 增加 -e apps/ai-game-creator-shell/src-tauri/target/.tauri,只保留工具缓存
- 新增 nsis-toolset.mjs:按固定 SHA1 预置 target/.tauri/NSIS,带 4 次重试并把原始归档缓存到工作区外
- 新增 ensure-nsis-toolset.mjs 与 nsis:prepare 脚本,作为 Jenkins 预检入口
- buildRelease 在 Windows 目标打包前接入工具链预置,预置失败即失败关闭
- Jenkins 新增 Tauri NSIS toolchain 阶段,在编译前预置、跑回归测试并校验 makensis 可执行
- 新增 nsis-toolset.test.mjs 并补 3 项 build-release 预置接入测试
- 同步开发运维文档与 shared-memory 排障记录
2026-09-21 15:59:30 +08:00
kdletters f4ebc45612 模板库线上对齐仓库源:递增版本重发并把说明文档纳入发布
Project CI / AI game creator shell Rust smoke (push) Successful in 1m56s
Project CI / AI game creator shell Rust crates (push) Successful in 1m23s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 4m48s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m38s
Project CI / Frontend tests (push) Successful in 10m0s
Project CI / Native shell tests (push) Successful in 11m37s
Project CI / Repository checks (push) Successful in 8m49s
Project CI / Backend tests (push) Successful in 12m24s
Project CI / AI game creator shell web tests (push) Successful in 4m50s
- 7 个内容已变的模板 templateVersion 升到 0.1.1,blank-2d-canvas 与 blank-web 保持 0.1.0
- 发布脚本把仓库 README.md 覆盖写到 templates/README.md(上限 64 KiB,写在清单之前并回读校验),线上说明文档不再是手工副本
- 修复守卫拒绝时直接 process.exit(1) 触发 libuv 断言崩溃、看不到拒绝原因的问题
- 新增发布用例:说明文档随发布覆盖写且写在清单之前、源目录没有 README 时不写线上文档
- 模板库新增仓库侧 README.md 源文件(发布后与线上逐字节一致)
- 技术方案补说明文档契约与 2026-09-21 重发布记录;决策记录与踩坑记录各补一条
- 验证:node --test 27 项、真实发布 28 个对象回读通过、线上清单与 README 核对一致、AGC 壳 3 项线上用例重跑通过、check:doc-index、check:encoding、eslint、git diff --check
2026-09-21 14:51:39 +08:00
kdletters 19ed1776f1 收口 AGC 模板库客户端接入里程碑:补缓存回退与建项失败清理证据
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
- 客户端清单来源抽成可测判定:远端合法正文优先并标 network,远端失败回退本机缓存并标 cache,无缓存时暴露远端错误
- 远端已答话但正文非法(不是合法 UTF-8 或不符合 schema)与缓存自身损坏时一律失败关闭,不用缓存掩盖远端问题
- 补 5 项清单来源判定用例
- 大小 / 摘要不一致与越界归档用例补断言:拒绝后不留安装目录、不产生已下载判据
- 新增建项失败清理用例:复制阶段失败时删除刚创建的项目目录,不留半成品
- 主规范并入清单来源与安装判据口径,并写入 2026-09-21 复核证据
- 里程碑置为 accepted 并补 7 条验收项证据表,按工作流删除对应实施计划
- 验证:cargo test template_library 24 项与 --ignored 线上 3 项、前端 4 个模板用例文件 38 项、appSurface 首页模板 2 项、AGC typecheck、check:encoding、check:doc-index、cargo fmt --check、git diff --check
2026-09-21 14:40:15 +08:00
k88936 4698527025 完成DirectProject composer canonical content收口
输入区草稿只保留 Lexical content 数组

Direct 与 Planning 提交边界直接读取 canonical content

润色保留引用解析并保护附件节点

通过单向 DTO 适配 legacy text 引用与附件消费
2026-09-21 14:37:49 +08:00
k88936 ec408bfafd 删除 DirectProject 聊天 IPC 的死参数 prompt
Rust 侧删掉 chat_with_game_creator_direct_codex 的 prompt 入参:回合输入只由 canonical userItem 派生,该参数从未被读取

DirectProjectTurnInput 去掉 prompt 字段,提交与排队派发不再计算 @显示名 投影文本

界面断言改读 userItem.content,并删掉退役的附件 sidecar 用例与只断言「不存在」的终止用例

同步实施计划与附件路径映射两份技术方案里的 Direct 消息契约

共享记忆补记该死参数的口径与成因
2026-09-21 14:27:23 +08:00
kdletters 4d313a44eb 收口后台模板管理里程碑:回填验收证据并删除临时实施计划
Project CI / AI game creator shell Rust crates (push) Successful in 2m58s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m58s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m32s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 8m26s
Project CI / Backend tests (push) Successful in 8m33s
Project CI / Repository checks (push) Successful in 4m50s
Project CI / Native shell tests (push) Successful in 9m20s
Project CI / Frontend tests (push) Successful in 5m57s
Project CI / AI game creator shell web tests (push) Successful in 3m19s
- 里程碑 Status 从 approved 更新为 implemented-awaiting-runtime-acceptance,并注明按验收口径未对真实 OSS 写入
- 新增验收证据:合同/权限/领域/存储/CLI 五层用例结果,本地 API 健康检查与未授权读取写入失败关闭,授权读取 9 个模板快照,受控浏览器桌面与 390×844 移动端 smoke
- 新增复审记录:写路径顺序与 409 语义、后台 DTO 结构上无法改动 ZIP/版本、封面公共域名与单 bucket 合同一致、迟到响应判据的覆盖方式
- 按实施计划第 4 步删除临时实施计划文件,里程碑文档保留为唯一验收依据
- 验证:check:doc-index(192 份)、check:encoding、git diff --check
2026-09-21 14:21:25 +08:00
k88936 ab52805d3c 收口DirectProject输入区canonical content唯一事实源
删除输入区 text/references 双轨草稿状态

Direct 与 Planning 改为从 Lexical content 读取

润色保留 chip 并保护附件 canonical content

增加 legacy content DTO 单向翻译与附件回归测试

补充 DirectProject composer canonical content 实施计划
2026-09-21 14:11:18 +08:00
kdletters 01ed9fadc6 修复手工 release 发布未透传 AGC 渠道
Project CI / AI game creator shell Rust crates (push) Successful in 2m45s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m36s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m5s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 7m54s
Project CI / Backend tests (push) Successful in 11m10s
Project CI / Repository checks (push) Successful in 6m17s
Project CI / Frontend tests (push) Successful in 8m0s
Project CI / Native shell tests (push) Successful in 11m32s
Project CI / AI game creator shell web tests (push) Has been cancelled
补充手工发布目标到 AGC 更新渠道的踩坑记录

补充 release 客户端包分区与补发流程说明
2026-09-21 14:04:02 +08:00
kdletters 02a7abeb3f 新增后台 AGC 模板管理与模板库内容寻址落库
Project CI / AI game creator shell Rust crates (push) Successful in 2m53s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m42s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
- 后台新增「模板管理」页:按权限查看并编辑模板名称/简介/标签/封面,支持上架与下架;路由、侧栏、tab 权限、API 客户端与 DTO 同步接入
- api-server 新增 /admin/api/agc-templates 读取与更新路由,复用现有后台鉴权与 tab 权限校验,权限不足按既有失败关闭口径拒绝
- module-assets 收敛模板库纯规则(active/inactive 投影、索引 schema 与重复项校验、ZIP 大小与摘要核对),platform-oss 承担对象存储读写、不可变对象复用与发布互斥锁
- shared-contracts 补后台模板 DTO;spacetime-module 账号存储适配保持既有 schema 不变
- AGC 壳内置模板索引与建项读取改为内容寻址对象,补模板安装、越界归档、索引不匹配等用例
- 模板库发布脚本支持定向发布与上架/下架合并、内容地址复用与锁语义,CLI 守卫用例同步;jenkins 两条客户端打包管线统一触发邮件通知 Job 并传递首装包链接
- 内置 5 个 Cocos 官方模板载荷与索引 fixture(内容地址对象,共 90 个文件)
- 补「后台模板管理」里程碑与实施计划,更新模板库技术方案、开发运维文档与共享记忆
- 验证:admin-web 39 项用例与 typecheck、发布脚本 25 项用例、api-server 与 AGC 壳编译、模板库定向 Rust 用例(AGC 壳 18 项、module-assets/platform-oss 16 项、后台权限 1 项)、check:encoding / check:doc-index / check:production-ops / rustfmt 全部通过
2026-09-21 13:56:20 +08:00
suzmii 6845cdcbc5 修复 Mac 包校验脚本对合法随包 npm 的误判
Project CI / AI game creator shell Rust smoke (push) Successful in 2m14s
Project CI / AI game creator shell Rust crates (push) Successful in 1m26s
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
- check-macos-bundle 的「包里不得出现 node_modules」门禁只放行了 game-runtime/node/node_modules/npm,但 recursive readdir 会把目录项 game-runtime/node/node_modules 也列出来,于是合法包必然断言失败
- 这条门禁是 #439 引入的,之后每次构建都死在更早的阶段(staging/测试),直到 2026-09-21 arm64 单架构跑通才第一次走到它:12:58、13:24、13:43 三轮都挂在这里
- 修复:放行 npm 目录自身、其子树与它的上级 node_modules 容器目录;已用真实 arm64 产物跑通完整 smoke(资源、架构、摘要、权限、Codex 正式查找、app-server 握手、缺组件拒绝全部通过)
2026-09-21 13:50:32 +08:00
suzmii 4044ffd89b 修复 Mac 发布 Job 手工触发时 Checkout 因未定义参数失败
- 手工「立即构建」不会注入可选参数(COMMIT_HASH / SKIP_IF_SUPERSEDED 未定义而非空值),set -u 下 `[ -n "$COMMIT_HASH" ]` 直接以 unbound variable 退出,Job 2026-09-21 12:54 那一轮就是这样在 Checkout 失败的
- 两段 Checkout 脚本开头统一兜底:SOURCE_BRANCH 默认 master、COMMIT_HASH 默认空、SKIP_IF_SUPERSEDED 默认 false,调度器显式传参时行为不变(仍按 pinned commit 构建并判让位)
2026-09-21 13:50:32 +08:00
suzmii 8d0c021126 修正 Mac 发布文档口径:自动触发与描述原文
- 运维文档原写「不设置 trigger;不接入现有每小时版本调度」,与现状矛盾:调度 Job 会先发号再把 SOURCE_BRANCH/COMMIT_HASH/AGC_UPDATE_CHANNEL/AGC_RELEASE_VERSION/SKIP_IF_SUPERSEDED 透传给 Mac Job,正常发布不需要手工点构建
- 把 Jenkins Job 描述与节点描述的当前原文(arm64 单架构 + 由调度器自动触发 + dry-run 默认关)写进文档,并要求每次改口径同步 Jenkins 侧描述,避免描述停留在 universal/手动触发的旧口径
2026-09-21 13:50:32 +08:00
kdletters 1cb91f9444 修复画布绑定查询与生成失败归因
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
- 画布绑定前置的项目列表查询改为 view=summary,站内与外部路由统一支持摘要视图(缺省 full 兼容,未知取值 400)
- 项目摘要投影收敛到 editor_project,外部 API 与 MCP 复用同一份实现,摘要不做内联媒体修复也不回传画布与全量资源
- 外部编辑器请求失败文案补 kind 语义与底层因链,区分请求超时、正文截断与非法 JSON,不拼接 URL
- 远端资源编辑终态文案带出稳定失败码并指向移出恢复队列这一唯一出口,上游原文仍不写入账本
- 补齐站内摘要视图与画布测试夹具的请求断言用例
- 更新共享决策记录与踩坑记录(图集切片上限条目归上游 #442,本提交不重复)
2026-09-21 13:33:23 +08:00
kdletters 2e31a485d8 修复 Stdb publish 事务确认超时被误判为失败
Project CI / AI game creator shell Rust crates (push) Successful in 3m0s
Project CI / AI game creator shell Rust smoke (push) Successful in 4m0s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m41s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 8m44s
Project CI / Backend tests (push) Successful in 8m52s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
- production-stdb-publish 新增 run_spacetime_publish:仅在出现 timeout waiting for transaction confirmation 时重试一次同版本 publish(SpacetimeDB 对已生效的同版本为幂等),重试成功即视为模块已在位,不再把生产留在维护态
- 其它失败原因保持原有失败关闭语义;生产运维门禁断言补上该重试口径
2026-09-21 13:27:37 +08:00
suzmii 4ad131d74d Merge pull request 'AGC 画布:锚点让位标题栏、生成浮层贴边、拖动误触点击、读秒 100ms 与图集参考口径' (#448) from fix/agc-canvas-bugfix into master
Project CI / AI game creator shell Rust smoke (push) Successful in 3m58s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m26s
Project CI / AI game creator shell Rust crates (push) Successful in 1m40s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 8m43s
Project CI / Frontend tests (push) Failing after 5m41s
Project CI / Backend tests (push) Successful in 7m29s
Project CI / Repository checks (push) Successful in 4m50s
Project CI / Native shell tests (push) Successful in 9m32s
Project CI / AI game creator shell web tests (push) Has been cancelled
Reviewed-on: #448
2026-09-21 13:04:11 +08:00
kdletters 886bdc06ed 去掉渠道环境变量用例里重复的 stageRuntime
Project CI / AI game creator shell Rust crates (push) Successful in 2m44s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m37s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 6m56s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 7m58s
Project CI / Backend tests (push) Successful in 6m33s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
- 两次独立修复(#444 与本仓库 55cb04765)在同一处各加了一个 stageRuntime,合并后成为重复对象键,Web-Build 的 ESLint(no-dupe-keys) 失败
- 保留一条带说明的 stageRuntime stub,用例语义不变
2026-09-21 12:49:34 +08:00
suzmii 562e1ba586 「生成任务」侧栏读秒改为 100ms 粒度:与对话侧共用同一份时钟
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m57s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 4m2s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 7m15s
Project CI / Repository checks (pull_request) Failing after 20s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 8m16s
Project CI / Frontend tests (pull_request) Successful in 7m30s
Project CI / AI game creator shell web tests (pull_request) Successful in 4m1s
Project CI / Native shell tests (pull_request) Successful in 11m1s
- ResourceCanvasAssetGenerationTasksPanelView:删掉面板自带的 1000ms interval 与 nowMillis 状态;两条任务行的「已耗时」下沉为叶子组件(AssetGenerationElapsedLabel / ResourceEditElapsedLabel),各自订阅共享 hook useLiveNow(LIVE_TIMER_TICK_MS = 100)。
- 为什么不是面板级时钟:面板上通常同时挂着多条任务,100ms 的 tick 落在面板上等于每 0.1 秒重建整个侧栏;useLiveNow 的注释明确要求调用方是叶子节点。终态行不订阅,已耗时定格在账本给的结束时间上。
- 行为:不足一分钟的耗时本来就显示到 0.1 秒(formatElapsedDuration),现在每 100ms 真的更新一次,不再是「带小数却 1 秒跳一格」。
- 用例:新增「读秒按 100ms 刷新(0.0 → 0.1 → 0.5 秒)」「终态行不再走表,定格在结束时间」「秒表只订在运行中的那一行上(setInterval 只被调用一次)」;把 LIVE_TIMER_TICK_MS 临时改回 1000 时第一条用例即红,粒度确实被钉住。
2026-09-21 12:48:21 +08:00
suzmii e18067e028 Merge pull request 'macOS 发布改为只出 arm64 单架构(修复 Mac 管线 #7 起连续失败)' (#447) from feat/agc-macos-arm64-only into master
Project CI / AI game creator shell Rust smoke (push) Successful in 2m11s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Reviewed-on: #447
2026-09-21 12:38:03 +08:00
suzmii 613d62fdb2 同步里程碑计划:macOS 清单只登记 darwin-aarch64
Project CI / AI game creator shell Rust crates (pull_request) Failing after 53s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m3s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m52s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 7m40s
Project CI / Repository checks (pull_request) Failing after 2m0s
Project CI / Backend tests (pull_request) Successful in 7m42s
Project CI / Native shell tests (pull_request) Successful in 8m47s
Project CI / Frontend tests (pull_request) Successful in 6m54s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m1s
- docs/project-memory/plans 的渠道化里程碑仍写着 dev-mac universal 同时写两个平台键,与 2026-09-21 的单架构决策冲突,按当前口径更新并指向决策记录
2026-09-21 12:35:36 +08:00
suzmii b4ce6877c8 修掉「拖动占位卡仍触发点击」的真因:点击抑制的计时起点必须在松手那一刻
- useResourceCanvasGenerationPlaceholders:抑制状态在越过拖动阈值时只登记、不起计时器,改到 pointerup 才起 0ms 计时(deferDragClickSuppressionCleanup);pointercancel 与切项目走 clearDragClickSuppression 立刻收干净。
- 原实现(越过阈值时就起 0ms 计时器)在真实鼠标下不成立:一次拖动里 pointermove 横跨多轮宏任务,计时器早在松手前就把抑制清掉,收尾那次 click 照样生效——jsdom 同步发 move/up 看不见这一档,所以上一版用例是假绿。
- 用例补上真实时序:拖动过程中显式让时钟走一轮宏任务再松手;宿主用例还补上浏览器在 pointerup 之后补发的 lostpointercapture,并断言它不能把这次拖动的抑制一起清掉。
- 敏感性已双向验证:把计时器搬回「越过阈值那一刻」、以及让 lostpointercapture 清抑制,两条用例都会红;改用例前的同步版本对两种错法都会绿(所以上一版没抓住)。
- 注释写明计时起点为什么是松手那一刻,并与资源卡链路的 deferSkippedResourceCardClickCleanup 对齐口径。
2026-09-21 12:34:47 +08:00
suzmii e53992fdc2 macOS 发布改为只出 arm64 单架构
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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 / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
- build-macos-ci 固定 aarch64-apple-darwin:单架构构建、只跑 arm64 隔离 smoke、首装包命名 <产品名>_<版本>_aarch64.dmg,构建清单记录 smokes 与实际清单平台键
- stage-node-runtime 的 universal-apple-darwin 恢复失败关闭并说明原因:随包 Node 只有单架构官方发行版,按宿主架构放行会让 Intel 上这份侧车不可执行,且通用包自检按 process.arch 校验抓不到
- 守卫用例改为断言入口固定 arm64 目标与 _aarch64.dmg 后缀、universal 目标必须抛错
- Jenkinsfile 与三份技术/运维文档同步单架构口径:清单只登记 darwin-aarch64,Intel 非目标,恢复 universal 的前置条件写在文档里
- 共享记忆补 2026-09-21 决策记录,pitfalls 记下「只带宿主架构的通用包」陷阱,避免再次用过渡实现糊过去
2026-09-21 12:30:56 +08:00
suzmii a5ee06a4c7 Merge pull request '修复macOS主机上AGC发布测试未stub Node运行时staging' (#444) from fix/agc-test-node-runtime-stub into master
Project CI / AI game creator shell Rust smoke (push) Successful in 4m16s
Project CI / AI game creator shell Rust crates (push) Successful in 1m6s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m51s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 8m45s
Project CI / Repository checks (push) Failing after 1m55s
Project CI / Backend tests (push) Successful in 6m33s
Project CI / Native shell tests (push) Successful in 7m47s
Project CI / Frontend tests (push) Successful in 5m49s
Project CI / AI game creator shell web tests (push) Has been cancelled
Reviewed-on: #444
2026-09-21 12:23:52 +08:00
suzmii 7b57cba14d 补齐「图集(生成图标素材)为什么没有 @」的当面说明与依据
- 生成浮层:不给 @ 选择器的入口新增一段说明行(`data-resource-canvas-generation-reference-free`),讲清「该入口走平台图集通道,参考只有一张权威规范图(图标规范),不支持另挑参考图」——验收现场那条「生成图标素材没法 @」读起来像漏了按钮,原因其实在通道上。
- 样式:说明行允许折行、沿用次要文字色;与既有的「参考图 n/5」计数行区分开。
- 用例:图集入口断言说明行在场;逐条入口断言「说明行」与「@ 选择器」严格互补;原「不呈现参考选择」的判据从按词匹配改成按计数元素/计数值匹配(说明行本身会提到「参考图」三个字)。
- 文档:入口矩阵补「图集为什么没有 @」一条,写明图集走 `icon-spritesheets` 通道、提交体只有 referenceId + iconDescriptions(无 referenceImageSrcs)、结果绑定硬校验参考恰好一张,以及「平台 OpenAPI 该请求 schema 有 referenceImageSrcs 字段,放开属于产品 + 平台口径变更」。
2026-09-21 12:23:32 +08:00
k88936 94471ff191 补充共享记忆:受控输入区回写的 effect 时机与删订阅留死 prop 两条踩坑
- docs/project-memory/shared-memory/pitfalls.md 新增「受控 Lexical 输入区的回写用被动 effect 会用滞后渲染的 props 清空用户草稿」:现象、宿主回显晚于编辑器提交的原因、useLayoutEffect 的处理与 chat-composer 用例验证
- 新增「删链路只删订阅、没删它喂的 state」:design-agent-update 订阅被删后 designReasoning 恒为空、残留的订阅就绪 promise 是误删线索,以及恢复后的验证入口
2026-09-21 12:22:32 +08:00
k88936 f3097e1096 删除按已退役工作台壳聊天入口编写的预览与 Run 历史用例
- 删除 apps/ai-game-creator-shell/tests/appSurface/project-preview.suite.ts 及其下 preview-shortcuts.suite / run-history.suite 与全部辅助模块(fixtures、invoke-mock、message-bubble、四个 assert-* 流程)
- 删除 tests/appSurface.test.ts 里对应的 import 与注册
- 这些用例断言的是已退役的 Supervisor 工作台壳:项目摘要面板、开发环境、LLM 状态、Agent 状态、编排 Trace、`/runs` `/read` 聊天命令,以及 `chat_with_game_creator_agent`;这些对象在当前源码里都已不存在
2026-09-21 12:22:08 +08:00
k88936 f1294d4d6b 修正首页建项两用例对 DirectProject 首轮链路的过期假设
- apps/ai-game-creator-shell/tests/appSurface/home.suite.ts 两个首页建项用例补上 `get_local_game_manifest` 桩:首轮需求由 DirectProject 聊天容器认领,它要先读到项目清单(项目身份)才发送,不桩这条命令时清单读失败、首轮永不发出
- 「imports home attachments」用例改为 waitFor 断言首轮 invoke:发送发生在清单到位之后,不能在「工作台已出现」的同一帧断言
- 同用例的 `prompt` 期望由 `按这个角色做游戏` 改为 `按这个角色做游戏@角色参考.png`:附件以 `agc_attachment_reference` 进入 canonical content 后按 `@名称` 口径投影,与输入区、队列 chip 同一份派生
2026-09-21 12:22:02 +08:00
k88936 ba3bd59f24 恢复策划 Agent 实时事件订阅(876529e66 误删)
- apps/ai-game-creator-shell/src/App.tsx 重新订阅 `design-agent-update`:本轮正文与工具提示回到 transient reply,`reasoningText` 回到 `planningV2Reasoning`,`view` 回到 applyDesignAgentViewAfterTransient;Rust 侧一直照旧发射这些事件,前端却不再监听
- 补回 designAgentEventSubscriptionReadyRef 的建/解函数与 designAgentEventSubscriptionResolveRef,让 `executeDesignAgentTurn` 等待监听器挂好再发起回合,开局事件不丢
- 新增 designAgentReasoningTurnRef:回合结束(designAgentTurnRef 清空)后仍认本轮 reasoning,最后一条思考不会被视图落盘前的空隙吃掉;切项目时随 resetChatState 清空
2026-09-21 12:21:51 +08:00
kdletters 55cb047654 支持 macOS 通用包构建时的 Node 运行时暂存
Project CI / Frontend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
- stage-node-runtime 的 targetRuntime 支持 universal-apple-darwin:在 macOS 宿主上按宿主架构暂存官方 Node(通用包自检按 process.arch 校验),非 darwin 宿主仍失败关闭
- 同步 stage-node-runtime 测试期望:darwin 宿主可用、linux 宿主仍拒绝,Universal Node 合并(lipo)留作未完成项
- 共享记忆记录该限制:当前通用包侧车仍是宿主架构,Intel Mac 需等待真正的通用 Node
2026-09-21 12:15:28 +08:00
k88936 317c8557f4 修复输入区受控回写用滞后渲染的 props 清空用户草稿
- apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx 的受控同步由 useLayoutEffect 承担:宿主对草稿的回显晚于编辑器提交,被动 effect 可能在上一次渲染的 props 上延迟执行,读到旧 value 加新编辑器状态后重建整份草稿,把刚输入的内容清掉并形成来回清空
- 补注释说明该 effect 必须与本次提交同帧执行,以及直接改成 content 单事实源前的行为约束
2026-09-21 11:59:17 +08:00
suzmii beebcca4de 修复 AGC 画布四项验收问题:锚点让位标题栏、浮层不再撞画布边缘、拖动不再触发点击、图标规范参考口径对齐
- 生成任务锚点新增「资源总览」档:资源总览页仍贴画布顶边内缩 0.85rem,资源栏目画布页让开顶部钉住的整宽栏目标题栏(42px + 0.5rem),两档切换走 margin-top 过渡(减动效下关掉)。
- 生成浮层的顶边与可用高度改为按占位卡的真实屏幕位置算(卡下沿到安全底边),不再假设卡片贴在安全带上沿——占位落到下半屏时浮层底边不再越过画布下沿被切掉。
- 生成浮层新增水平收口:按「浮层左右各留 12px」把锚点收进画布可视范围,卡片靠边时浮层不再有一半落到画布之外;宽度口径与面板 CSS 同源(min(560px, 100% - 24px))。
- 新增画布卡片手势的共享判据 resourceCanvasGestureExceededDragThreshold(阈值仍取 RESOURCE_CANVAS_DRAG_THRESHOLD):资源卡与生成占位卡共用同一份「拖动还是点击」判断。
- 生成占位卡修掉拖动收尾误触点击:越过阈值才算拖动、登记一次性点击抑制(consumeDragClick)由宿主消费,「拖动中」样式也从越过阈值那一刻起才亮。
- 生成占位卡的删除按钮起手手势不再被当成「点卡片」:按起手点判定,松手落回卡片时不触发开合生成浮层。
- 图标规范面板的参考选择注释按现行判据改写(只有图集 icon-spritesheet 不接受用户参考),入口矩阵文档同步补「参考图(@ 引用)」口径并修掉「本地 IPC 没有参考图入参」的旧说法。
- 补齐回归用例:手势模型判据、占位卡拖动/点击与阈值、宿主级「拖完不弹浮层且普通点击仍开合」、浮层顶边与水平收口、锚点分档与跨文件标题栏高度守卫、逐条生成入口的 @ 与判据一致、图标规范面板的 @ 与提交带引用。
- 共享记忆 pitfalls.md 记录两条排障口径:卡类手势必须只有一份阈值与点击抑制判据;画布浮层几何只能按真实屏幕位置算、分页锚点必须让开钉死的标题栏。
2026-09-21 11:58:13 +08:00
kdletters 9bf35e8745 修复 macOS 构建上的渠道环境变量用例宿主依赖
Project CI / AI game creator shell Rust crates (push) Successful in 2m58s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m50s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m0s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 8m5s
Project CI / Backend tests (push) Successful in 7m2s
Project CI / Native shell tests (push) Successful in 8m12s
Project CI / Repository checks (push) Successful in 4m8s
Project CI / Frontend tests (push) Successful in 5m15s
Project CI / AI game creator shell web tests (push) Failing after 6m13s
- build-release.test.mjs 的「packaged renderer receives the same channel as the updater manifest」用 windows target 却未注入 stageRuntime,真实 staging 要求宿主 Node 平台/架构与目标一致,导致 macOS(arm64)/Linux 上必然失败
- 该用例只断言传给 spawn 的 VITE_AGC_PLATFORM_CHANNEL,改为注入空 stageRuntime,与同文件其它用例一致
2026-09-21 11:53:32 +08:00
kdletters 90207bc1d5 minimal 备份增加 freeze 副本路径
Project CI / AI game creator shell Rust smoke (push) Successful in 2m35s
Project CI / AI game creator shell Rust crates (push) Successful in 1m14s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m4s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 7m28s
Project CI / Backend tests (push) Successful in 7m25s
Project CI / Frontend tests (push) Successful in 7m18s
Project CI / Repository checks (push) Successful in 5m51s
Project CI / Native shell tests (push) Successful in 9m16s
Project CI / AI game creator shell web tests (push) Successful in 3m41s
- database-backup-to-oss.mjs 新增 --freeze-dir:minimal 模式先把选中子集 rsync 成冻结副本(保持原始相对路径)再从副本上传,避免热备时最新 commitlog 持续追加触发 stat 漂移失败
- files-minimal systemd 模板与发布前 minimal 备份默认传入 freeze 目录
- 生产运维门禁同步 freeze 口径断言
2026-09-21 11:37:14 +08:00
k88936 7b1f7bdf17 给 parseAgentRunTrace 与 formatAgentRunStatus 补上纯函数用例
- 新增 tests/agentRunTrace.test.ts,用旧 trace 夹具断言归一化结果(路径/工具数组/上限/任务图/通行计划)
- 覆盖 toolCalls 总数回填、taskGraph 缺失时回退种子任务图
- 覆盖畸形 trace 的拒绝路径(steps / schemaVersion / artifacts / taskGraph / dependencyWaves)
- 覆盖 formatAgentRunStatus 带与不带 lifecycleStatus 的两种文案
2026-09-21 11:36:02 +08:00
k88936 abb2988875 删除无调用方的 pick_local_file 与 delete_local_* 三条 Tauri 命令
- commands.rs 删除 pick_local_file、delete_local_project_file、delete_local_game_memory 三条命令实现
- main.rs 取消上述三条命令的注册
- Rust 用例只保留写命令推进 revision 的断言(删除两条已退役命令的断言并同步改名)
- check-config.mjs 移除这三条命令的白名单条目与旧说明
- check-config.mjs 把旧命令聊天剩下的 15 条无前端调用方命令登记为 native-only,并删掉随该死链失效的 guardrail 片段
2026-09-21 11:34:15 +08:00
kdletters 2f248ea0c3 新增 minimal 备份口径并切换 release 定时备份
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
- database-backup-to-oss.mjs 新增 --minimal/--retain-snapshots:按上游 retention 语义只备份最近 N 份 snapshot、覆盖最老保留 snapshot 的 commitlog 边界段及其后全部段,外加身份/配置/控制库/模块字节,且不停止任何服务
- 备份检查脚本补充 minimal 计划用例(保留次新+最新 snapshot、丢弃更早 snapshot 与 clog 段、状态路径齐全、保留数校验)
- 发布前备份默认走 minimal 热备(GENARRATIVE_STDB_PUBLISH_BACKUP_MINIMAL=0 可回到冷备),不再需要 44.7GiB 冷备空间
- Server-Provision 新增 DATABASE_BACKUP_PROFILE=files-minimal 与 systemd drop-in,release 推荐使用并继续禁止 files-history
- 运维文档与共享记忆记录上游 retention 依据、实测 40G→3.6G 与不停服热备口径
2026-09-21 11:33:39 +08:00
k88936 912f9afcd2 删除按已退役 Supervisor 聊天入口编写的 appSurface 用例
- 删除 project-commands / agent-runtime 两个 100% 依赖已退役聊天入口的用例文件及其注册
- 删除 project-assets 里 21 条「from chat」用例(整个套件同为该入口),并移除注册
- 删除 project-conversation 里 7 条、project-development 里 2 条依赖 home 聊天框与斜杠命令的用例
- appSurface 失败数由 114 降到 10,剩余 10 条为 DirectProject/预览/首页链路自身的既有失败
2026-09-21 11:19:12 +08:00
k88936 0224ef7b9d 删除 Project Supervisor 聊天遗留的 pendingCommand 死链
- 删除 App.tsx 里 pendingCommand 状态、确认/取消处理与 21 个 execute* 分发分支
- 删除随该链路一起不可达的 21 个 execute* 实现与 requireChatProjectForUserAction
- 删除只服务该链路的 PendingCommand 类型、pendingCommandPresentation.ts 与 resolvePendingCommandProjectPath
- PlanningChatView 去掉 pendingCommand 面板相关 props、渲染分支与导入
- rememberCommand.test.ts 只保留现役 helper(path/remember/needsInitializedChatProject/agent 卡片)的用例
2026-09-21 11:15:02 +08:00
kdletters 117d482f93 发布前备份空间预检前置并支持自动降级
Project CI / AI game creator shell Rust smoke (push) Successful in 2m13s
Project CI / AI game creator shell Rust crates (push) Successful in 1m7s
Project CI / AI game creator shell Rust lane 1/2 (push) Failing after 7m9s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 6m57s
Project CI / Native shell tests (push) Successful in 7m58s
Project CI / Repository checks (push) Successful in 5m13s
Project CI / Frontend tests (push) Successful in 7m19s
Project CI / Backend tests (push) Successful in 10m10s
Project CI / AI game creator shell web tests (push) Successful in 4m47s
- database-backup-to-oss.mjs 新增 --check-space-only:archive 按 data×1.1、files 按 max(data×0.05, 2GiB) 计算门槛,空间不足以退出码 3 返回
- production-stdb-publish.sh 在进入维护与停服务之前先做空间预检,archive 不足时自动降级 files(files 不支持 defer,async 收敛为 sync),可用环境变量关闭
- 尚未开始 publish 的失败自动恢复本次停掉的 API/controller/worker 并退出维护,只有真正开始发布之后的失败才保持维护态
- 备份检查脚本与生产运维门禁补上新口径与执行顺序断言,运维文档与共享记忆记录事故与规则
2026-09-21 10:57:29 +08:00
kdletters 5ac7f65c92 合并 master 并保留游戏分发功能
- 合并 origin/master(含 429f991bd 定向回滚、Godot 原生绑定迁移与策划 Agent 模型控件)。
- 回滚提交删除的游戏分发代码按功能面补回:SpacetimeDB 模块注册与迁移表、api-server 路由与权限映射、后台审核页、AGC 发布命令与 payload 类型、SelectionStage 的 games 阶段、ProjectSupervisorView 的 overlay 挂载点、主站路由与标题、Cargo 依赖与锁、vite/vitest 代理与 tailwind source。
- 保留 master 的 Godot C++ 迁移(移除旧 vendored GDExtension、新的 composer 控件布局)与其它 master 侧改动。
- 修正 master 自带的红灯断言:/api/creation-entry 已被 api-server 列入退役路由,dev 中间件按退役路径 404,测试改为断言不进入代理。
- 验证:cargo check(api-server / spacetime-module / spacetime-client / AGC 壳)、cargo test(api-server 游戏分发 17、module-game-distribution 11、AGC 本地导出 7)、全量 vitest 4557 passed、两端 typecheck、eslint、rustfmt、编码、文档索引与 SpacetimeDB schema guard。
2026-09-21 00:20:45 +08:00
kdletters 2f0e2a48a3 把游戏分发客户端纳入 ESLint 边界
- `.eslintrc.cjs`:`src/services/gameDistributionClient.ts` 及其测试加入活跃服务白名单,不再被遗留忽略列表吞掉。
- 客户端:按仓库规则修掉排序与未使用类型导入(顺带移除两个只在 re-export 中出现的多余导入)。
- 修复 `scripts/vite-retired-css-plugin.test.ts` 的边界门禁:活跃模块不得停留在 ESLint 忽略名单里。
2026-09-20 22:56:43 +08:00
kdletters 00b96f244d 补齐 AGC 素材直传的错误文案与能力作用域验证
- AGC:直传对象存储抛错(能力作用域拒绝、网络不可达、请求被取消)时统一转成中文可操作文案,不再把英文插件错误原样暴露给作者。
- 测试:新增直传抛错的单测,确认失败时不再登记素材。
- 文档:记录用 tauri-plugin-http 同一套 urlpattern 逻辑验证 `https://*.aliyuncs.com/*` 命中 dev/生产 OSS 桶且拒绝无关主机,并说明桌面端真实执行仍需在装有 AGC 的机器上补跑。
2026-09-20 22:49:56 +08:00
kdletters 0dde6ee501 打磨封面与截图的文件选择交互
- 网页发布页:封面与截图改用透明 input 覆盖自定义胶囊按钮,点击命中原生控件;上传后不再出现「未选择任何文件」与「已选择」互相矛盾的状态。
- AGC 发布面板:同一处理,按钮文案随状态在「选择封面图片 / 更换封面图片」「添加截图 / 截图已满 6 张」之间切换。
- 文档:实施计划补充真实浏览器发布链路证据(网页上传封面 → my-games 待审 → 版本 frozenMetadata 指向本次上传素材)与交互打磨记录。
2026-09-20 22:45:50 +08:00
kdletters 4a91e5865a 创建游戏同步校验封面与截图素材归属
- 服务端:抽出 `resolve_owned_game_media`,创建游戏与创建版本复用同一素材校验;不存在的素材返回 400,借用他人素材返回 403,避免游戏行先落一个无效素材 ID。
- 脚本:新增 `npm run check:game-distribution-media-e2e`(`scripts/check-game-distribution-media-e2e.mjs`),在本地 dev 栈上跑真实素材直传、冻结、审核生效、匿名换签读与发行网关的可重复检查。
- 文档:后端数据契约补充创建游戏的素材复核口径,实施计划记录真实本地栈 27 项媒体验证与真实浏览器封面/截图展示证据。
2026-09-20 22:34:33 +08:00
kdletters 4a2b270714 补齐游戏分发封面与截图的冻结、上传与展示
- 服务端:游戏表追加可空 cover_object_key / screenshots_json,版本表追加可空 metadata_json,创建版本时校验必需封面、最多 6 张截图与图片素材归属,并从素材记录派生对象键生成冻结资料。
- 服务端:审核通过时把冻结资料整体生效到游戏行;只有已公开且存在有效活动版本的游戏,其封面/截图素材才在 /api/assets/read-url 获得匿名读授权。
- 服务端:作者与管理员回读版本时追加 frozenMetadata,带回封面与截图素材 ID 供续发复用,公开投影仍只暴露对象键。
- 网页:/games/publish 新增「封面与截图」区,复用平台图片直传与 confirm 通道,缺封面或截图超量在发请求前拦截,更新版本时按冻结资料回填且不要求重新上传。
- 网页:游戏广场卡片与详情页 hero 换签展示真实封面,详情页新增可点击的截图缩略图条,无素材或换签失败时静默回退原有渐变占位。
- AGC:发布面板支持封面必选与截图最多 6 张,新增素材直传服务并在创建游戏前拦截缺封面,同一文件重复提交复用素材 ID。
- AGC:http 能力作用域新增阿里云 OSS 直传地址,客户端同时校验直传目标必须是平台素材存储。
- 契约与文档:同步 shared-contracts、packages/shared 契约,更新 SpacetimeDB 迁移注释、后端架构、玩法链路与实施计划文档,并补齐网页端与 AGC 定向测试。
2026-09-20 22:19:50 +08:00
kdletters 789aef4ce5 补齐游戏分发结构化可观测事件
- api-server 游戏分发模块新增 package_confirmed/package_rejected/version_submitted/version_cancelled 事件,带 request_id、game_id、version_id、耗时与体积/文件数(不记录凭据、URL 与文件内容)
- 新增 review_backlog_listed(审核积压)、review_decided(审核结论与公开修订号)、game_unpublished/game_suspended(撤销传播)与 publish_switch_blocked/unavailable(开关拦截)事件
- 发行网关拒绝路径补 debug 级 release_rejected(cookie、扩展名、未公开、非公开版本)便于排查发行 4xx
- 运维文档给出可直接 grep 的事件名与关键字段,实施计划记录真实栈复跑证据
2026-09-20 21:30:32 +08:00
kdletters 7ff6e46774 修复分支上的格式与导入排序门禁问题
- 用 rustfmt 修正 AGC Tauri 导出包测试的断言排版
- 按 simple-import-sort 修正 AGC App.tsx 导入顺序与 packages/shared 导出顺序
2026-09-20 21:17:27 +08:00
kdletters 2254831db5 补充游戏内真实触屏输入的运行时证据
- 用 CDP Input.dispatchTouchEvent 向游玩页 iframe 派发真实 touchStart/touchEnd,沙箱内探测包收到 touchstart/touchend/pointerdown(pointerType=touch)/click 并命中目标元素
- 记录触摸模拟必须在 iframe 文档创建前启用的踩坑口径
2026-09-20 21:11:37 +08:00
kdletters 64425e8d1a Merge commit 'a2acfa9a6' into codex/game-distribution 2026-09-20 20:59:58 +08:00
kdletters 328ac31844 恢复游戏分发完整实现(特性分支)
- 主站:游戏广场、详情、在线游玩、网页发布与作者中心,以及共享契约与客户端服务
- 后端:module-game-distribution 领域层、SpacetimeDB 表/迁移/绑定、spacetime-client facade、api-server 路由与发行网关
- 后台:游戏审核页(待审列表、通过/拒绝、安全下架)
- AGC:发布面板、本地导出包读取命令与发布服务,含默认跳过的真实链路测试
- 运维:发行来源 nginx 模板与门禁、game-distribution:publish 灰度发布开关、OSS PutObject 受控重试
- 文档:主规范、里程碑与实施计划、决策日志与踩坑记录
2026-09-20 20:49:42 +08:00
k88936 1c830e6fe6 修正钱包入口回归用例的断言目标
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 17s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 17s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 18s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 16s
Project CI / Native shell tests (pull_request) Failing after 18s
Project CI / Backend tests (pull_request) Failing after 18s
Project CI / AI game creator shell Rust smoke (pull_request) Failing after 19s
Project CI / Frontend tests (pull_request) Failing after 7s
Project CI / AI game creator shell web tests (pull_request) Failing after 12s
Project CI / Repository checks (pull_request) Failing after 12s
- apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts 的「keeps the wallet entry available when the workbench opens the UI editor」改为断言 game-workbench-layout-account 的两处落点(策划工作台与主工作台)以及主工作台的 !uiEditorRoute 门控
- 用例原先断言 game-workbench-chat-wallet 出现一次,但该槽位在当前源码里已不存在(钱包移到布局层),断言恒为 null 导致用例一直失败;注释同步改成当前落点
2026-09-19 23:37:25 +08:00
k88936 860f3f09d4 UI 编辑器打开时不再重复渲染工作台布局层的钱包入口
- apps/ai-game-creator-shell/src/view/project-development/index.tsx 主工作台布局里的 walletEntry 槽改为 !uiEditorRoute && walletEntry
- UI 编辑器路由下 UiEditorPage 自己会用同一份 walletEntry 渲染头部钱包,布局层再渲染一次会让同一个入口挂载两份、浮层与编辑器工具条钱包重叠
2026-09-19 23:36:57 +08:00
k88936 dc1cac1c15 给 Skill 引用补上每条消息的数量上限
- apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/validation.rs 新增 MAX_DIRECT_CODEX_SKILL_REFERENCES=32 与 skill_count 计数,超出即拒绝
- 附件与素材引用已有上限,Skill 引用是唯一没有上限的非文本 part:每条都会触发一次 root/<name>/SKILL.md 文件探测并往 turn/start.input 里加一项,畸形超长 userItem 可以放大文件探测与模型输入
- 补上 inline_skill_reference_count_is_bounded_independently;cargo test direct_codex_user_item::validation:7 passed
2026-09-19 23:35:41 +08:00
k88936 c5a7386465 统一附件摘要的清洗口径
- apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs 抽出 attachment_reference_summary,turn 输入与 history/prompt 投影共用同一份摘要
- 原先 direct_codex_user_item_to_codex_turn_input 自己拼摘要,跳过了 sanitize_attachment_name/media_type/local_path,模型侧可能收到 C:\tmp\notes.md 这类原始路径名,而历史投影里是清洗过的
- cargo test user_item:24 passed;cargo check 通过
2026-09-19 23:32:55 +08:00
k88936 926fe9ae0f 复用 validate 返回的清单,去掉 wire 输入投影里重复的一次清单读取
- apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/wire.rs 的 direct_codex_user_item_to_wire_input 直接用 validate_direct_codex_user_item 返回的 manifest
- 原先 validate 之后再 read_manifest_for_project 一次,每次投影都会多读一次项目清单(并重复 seed task 变更),与 direct_codex_user_item_to_codex_turn_input 的口径也不一致
- cargo test user_item:24 passed;cargo check 通过(本机 stale target 需先 touch shared-contracts/src/lib.rs)
2026-09-19 23:30:22 +08:00
k88936 e7967cf67b 删除 runtime-settings 用例里空的发布态设置注册函数
- 删除 apps/ai-game-creator-shell/tests/appSurface/runtime-settings.suite.ts 里空的 registerPublishedRuntimeSettingsTests
- apps/ai-game-creator-shell/tests/appSurface.test.ts 去掉对应 import 与调用
- 它注册 0 条用例,留着会让人以为发布态运行时设置仍有覆盖
2026-09-19 23:20:25 +08:00
k88936 5aeaf38112 删除 project-development 用例里空的用户面边界注册函数
- 删除 apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts 里空的 registerUserSurfaceBoundaryTests
- apps/ai-game-creator-shell/tests/appSurface.test.ts 去掉对应 import 与调用
- 它注册 0 条用例,留着只会让人以为用户面边界仍有覆盖
2026-09-19 23:19:58 +08:00
k88936 6f5ca47385 删除空的 run-trace 用例文件与其注册
- 删除 apps/ai-game-creator-shell/tests/appSurface/project-preview/run-trace.suite.ts(只剩一个空的 registerProjectRunTraceTests)
- apps/ai-game-creator-shell/tests/appSurface/project-preview.suite.ts 去掉对应 import 与调用
- 说明:该文件被清空后 parseAgentRunTrace 与 formatAgentRunStatus 已没有用例覆盖,重新补测属于后续单独任务,本提交只清理空壳
2026-09-19 23:19:34 +08:00
k88936 3e3c3df41f 删除空的 run-status 用例文件与其注册
- 删除 apps/ai-game-creator-shell/tests/appSurface/project-preview/run-status.suite.ts(只剩一个空的 registerProjectRunStatusTests)
- apps/ai-game-creator-shell/tests/appSurface/project-preview.suite.ts 去掉对应 import 与调用
- 空注册函数挂在测试入口上会让人以为运行状态还有用例覆盖,实际一条都没有
2026-09-19 23:19:29 +08:00
k88936 051c21d97f 删掉立项策划用例里传给 App 的无效 agentDockVisible prop
- apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts 三处 React.createElement(App, {...}) 不再传 agentDockVisible
- 同 design-agent.suite:该 prop 属于 ProjectDevelopmentView,AppProps 未声明,传入不影响任何渲染,只会误导读者以为 Agent dock 被隐藏
2026-09-19 23:18:49 +08:00
k88936 21382926dc 删掉设计 Agent 用例里传给 App 的无效 agentDockVisible prop
- apps/ai-game-creator-shell/tests/appSurface/design-agent.suite.ts 四处 React.createElement(App, {...}) 不再传 agentDockVisible
- agentDockVisible 是 ProjectDevelopmentView(工作台)的 prop,AppProps 里没有它,传了也不会生效;它替换掉的旧 orchestrationMode/projectSupervisorOnly 曾经是真实的 App prop,留着会让人误以为这里会隐藏 Agent dock
2026-09-19 23:18:39 +08:00
k88936 62f1ec83bf 让工作台壳写入的运行态提示不被通用错误兜底覆盖
- apps/ai-game-creator-shell/src/features/agent-runtime/model.ts 的 projectRuntimeVisibleError 公共文案白名单补上「待回答问题已变更,请刷新策划状态」「需要在 Tauri App 内运行。」「请先初始化本地项目」
- 这三条是 App.tsx setProjectChatError 直接写入的给用户看的动作指引,此前不在白名单,经策划运行态窄条渲染时会被替换成通用的「策划 Agent 执行失败,请稍后重试」
2026-09-19 23:17:30 +08:00
k88936 263588af6a 把运行中回合的秒级计时下移到耗时文案组件
- apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectConversation/DirectProjectConversation.tsx 新增 DirectTurnElapsed,自己持有 1 秒 tick 与已耗时渲染
- apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx 删除 turnUsageNow state 与其 interval,不再把每秒 tick 传给会话区
- 原先每秒 tick 触发的是整个聊天视图重渲染(含 Markdown 回合列表、工具调用组与队列芯片),现在只重渲染耗时那一小块
2026-09-19 23:16:42 +08:00
k88936 50291a5e77 订阅过期恢复后补跑被搁置的 consume
- apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectThreadChatSubscription.ts 在 consume 的 finally 之后补一次 consumeAgain 重排
- SUBSCRIPTION_EXPIRED 恢复期间到达的通知会因 consuming 仍为 true 只置位 consumeAgain,而 do/while 已因异常退出,原先这批事件要等下一次通知才会被消费
2026-09-19 23:15:48 +08:00
k88936 8aa737e032 「显示更早」读取失败时补上项目路径守卫
- apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts 的历史分页 catch 在写 statusNotice 前先比对 projectPathRef.current
- 与成功路径和首个屏幕加载一致;原先切换项目后旧项目在飞读取的失败会显示成新项目的错误
2026-09-19 23:15:15 +08:00
k88936 9243bb42a1 去掉 Direct 回合成功后多余的第二次清单刷新
- apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts 的 runTurn 不再在成功路径刷新清单,统一由 startTurn 的 finally 刷新
- 原先一轮成功会先后调用两次 get_local_game_manifest,finally 那次已覆盖成功与报错路径
2026-09-19 23:14:51 +08:00
k88936 67252d36b2 修掉写权限门拒绝后 Direct 发送队列永久卡住的问题
- apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts 在 ensureConversationWriteAllowed 返回 false 且未重跑时,主动放下忙态并继续 dispatchNextQueuedTurn
- 该轮已由 dispatchNextQueuedTurn 出队,原先 finally 只在 invoked 为真时继续出队,导致后续排队消息再也不发送且提示/队列芯片一直显示
2026-09-19 23:14:18 +08:00
k88936 97fc1918c4 给 Direct 回合的前置步骤补上错误出口
- apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts 在 startTurn 的异步 IIFE 里加 catch,按项目路径守卫后把错误交给 onRuntimeError
- 原先只有 try/finally:写权限门 rejection 会成为未处理拒绝,回合静默失败且已乐观追加的用户消息没有解释
2026-09-19 23:13:53 +08:00
k88936 034c369b88 排队发送后不再立刻抹掉「已加入发送队列」提示
- apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts 拆出 clearDraftInput(只清输入与附件),排队成功分支改用它
- 原先同一事件里先 enqueueTurn 写提示、紧接着 clearPendingInput 把 composerNotice 置空,React 批处理下用户永远看不到队列确认文案
2026-09-19 23:13:13 +08:00
k88936 884aabe6d7 Direct 聊天权限门改为必填,去掉默认放行
- apps/ai-game-creator-shell/src/view/project-development/chat/DirectProjectChatView.tsx 删除 ALLOW_CONVERSATION_ACCESS 及两个权限门 prop 的默认值,改为必填
- 原先缺省实现恒返回 true,调用方漏传等于静默跳过工作台壳的读写权限判定;唯一调用方 App.tsx 本来就会传入两个门
2026-09-19 23:12:28 +08:00
k88936 f58249a75e 给 clientTurnId 的回退生成补上随机熵
- apps/ai-game-creator-shell/src/view/project-development/chat/conversation/directCodexConversation.ts 在无 crypto.randomUUID 的回退分支追加 Math.random 派生的随机串
- 原先只有时间戳 + 页内序号,多个 WebView 在同一毫秒创建首轮会得到同一个 clientTurnId,而 Rust 侧用它做项目级唯一校验与历史关联键
- 结果仍是 ASCII 字母数字与连字符组成、长度在 6..160 之间,满足 native 校验
2026-09-19 23:12:02 +08:00
k88936 38fb9aa7ac 删除 Direct 输入区发送按钮里不可达的忙态分支
- apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectComposer/DirectProjectComposer.tsx 发送按钮只在非 busy 分支渲染,去掉 '思考中' 文案、Loader2 转圈与 disabled 里的 busy 条件,并移除不再使用的 Loader2 import
- busy 时仍由既有 ComposerStopButton 承担停止入口,渲染结果不变
2026-09-19 23:11:14 +08:00
k88936 b5e10f3851 合并 Direct 输入区来自同一模块的两条 type import
- apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectComposer/DirectProjectComposer.tsx 把 gameCreationApp 的 GameCreationAppAssetManifestEntry 与 GameIterationVersion 合并成一条 import type
- 不涉及任何运行时行为
2026-09-19 23:10:51 +08:00
k88936 c4e2793db1 把回合执行过程渲染的两层三目抽成 renderTurnProcess
- apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectConversation/DirectProjectTurn.tsx 新增 renderTurnProcess,负责空过程、运行中平铺与结束后折叠三种分支
- 组件主体不再嵌套三目,渲染结果与 data-testid="turn-process" 等 DOM 结构保持不变
2026-09-19 23:10:35 +08:00
k88936 79cb510891 把排队消息标题的嵌套三元改成提前返回
- apps/ai-game-creator-shell/src/view/project-development/chat/components/DirectProjectComposer/chatComposerQueue.ts 的 queuedChatTurnLabel 先处理空文本再判断截断
- 返回值不变
2026-09-19 23:10:01 +08:00
k88936 3160131889 给澄清问答卡提交路径补上 rejection 兜底
- apps/ai-game-creator-shell/src/view/project-development/planning/PlanningUserInputCard.tsx 在 Promise.resolve(onSubmit(...)).finally(...) 之间加 .catch(() => undefined)
- onSubmit 的契约允许返回 rejected promise,原先只挂 finally 会把 rejection 变成未处理拒绝;与 GddApprovalCard 的处理口径一致
2026-09-19 23:09:38 +08:00
k88936 c46687f0ef 把澄清问答卡提交按钮文案的嵌套三元改成顺序分支
- apps/ai-game-creator-shell/src/view/project-development/planning/PlanningUserInputCard.tsx 把 answerPrepared / controlBusy||submitting 的嵌套三元改成先取默认值再覆盖的 submitLabel
- 文案取值不变
2026-09-19 23:09:20 +08:00
k88936 137533484b 抽出策划视图共用的 Design 模式判据与回调兜底
- apps/ai-game-creator-shell/src/view/project-development/planning/PlanningChatView.tsx 新增 designMode、designActionsBusy 与三个 Design 回调兜底常量
- DesignAgentPhaseStatus(顶部)与 DesignAgentPendingActions(底部)原先各自重复 designView||onDesignApprove、controlBusy||planGddDecisionBusy 和 ?? (()=>undefined),现在共用同一份取值
2026-09-19 23:09:00 +08:00
k88936 00063ba882 抽出策划输入区与提交按钮共用的禁用判据
- apps/ai-game-creator-shell/src/view/project-development/planning/PlanningChatView.tsx 新增 composerDisabled,替换 ResourceReferenceInput 与提交按钮上逐字重复的四条件表达式
- 判据取值不变,后续改条件时不会只改到其中一处
2026-09-19 23:08:02 +08:00
k88936 cb3d452a44 把策划输入框提交按钮文案的嵌套三元改成顺序分支
- apps/ai-game-creator-shell/src/view/project-development/planning/PlanningChatView.tsx 的 submitLabel 改为先给默认值再按 needsUserInput / controlBusy 覆盖
- 取值结果不变,只是去掉三层嵌套三元表达式
2026-09-19 23:07:42 +08:00
k88936 257e407df7 策划会话 GDD 展示的 agentId 改用 PLANNING_SESSION_AGENT_ID 常量
- apps/ai-game-creator-shell/src/view/project-development/planning/planningSessionV2.ts 的 gddDisplayFromArtifact 不再写死 'planning-agent-v2' 字面量
- 与同结构里的 rootAgentId 共用 planningSessionContract.ts 的常量,避免 id 变更时要改两处
2026-09-19 23:07:25 +08:00
k88936 0d3ab25499 删除输入区里只会在挂载时跑一次的草稿清空 effect
- apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx 删除依赖只有 resetPromptPolish(稳定 useCallback)的 effect
- 该 effect 读 liveDraftRef.current 却把它排除在依赖外,实际只在挂载时执行一次;清空草稿时重置润色状态的逻辑已由 OnChangePlugin 内联承担
2026-09-19 23:07:02 +08:00
k88936 2a8063a83a 让技能清单查询失败后可以重试
- apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx 在 list_agc_skill_catalog / list_client_extensions 的 catch 里放开 skillCatalogRequestedRef
- 原先标记在调用前就置位且失败时不回退,一次瞬时失败会让该输入区此后不再拉取技能候选
2026-09-19 23:06:05 +08:00
k88936 37c7477068 修正草稿文本把附件引用误回读成 $名称的问题
- apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx 的 readDraftFromNodes 改为按 part.type 显式分支:附件引用回读为 @名称,技能引用回读为 $名称,未知类型返回空串
- 该 text 会被受控同步比较(对照 directCodexContentToPromptText 展开的 value),附件口径不一致会让受控输入区每次都判定草稿变化而重建编辑器
- 顺带去掉该处嵌套三元,与 resourceReferences.ts 的渲染口径对齐
2026-09-19 23:05:34 +08:00
k88936 e259106267 拆出引用草稿键与 token 的取值 helper 消除嵌套三元
- apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx 新增 referenceDraftKey/referenceDraftToken,替换 readDraftFromNodes 里构造 labels 时的嵌套三元表达式
- 行为与原先一致,只是把「resource 取 id、skill 取 name、其余取 label」的规则收敛到一处
2026-09-19 23:04:47 +08:00
k88936 7a89a119d9 校正 check-config 免调用白名单中三条命令的说明
- apps/ai-game-creator-shell/scripts/check-config.mjs 为 delete_local_game_memory、delete_local_project_file、pick_local_file 补写准确的剩余调用方说明(前两条只剩 Rust 测试直接调用命令函数,pick_local_file 前端与 Rust 均无调用方)
- 修正原先「命令由 native Runtime、CLI swarm 与 Rust 测试使用」这一对三条命令不成立的笼统说明
2026-09-19 23:03:51 +08:00
k88936 57b28ce5cd 删除 ts-rs 生成器漂移产生的 DirectCodexUserMessageEnvelope 死文件
- 删除 apps/ai-game-creator-shell/src/view/project-development/chat/generated/DirectCodexUserMessageEnvelope.ts
- 该文件是 ts-rs 导出按本机依赖版本漂移时多出来的产物,前端无任何引用,按 project-memory/pitfalls.md 的口径不提交
2026-09-19 23:03:27 +08:00
k88936 876529e668 删除 Project Supervisor 前端链路并将项目对话收敛到 DirectProject 与立项策划
- 删除 ProjectSupervisorView、SupervisorChatOnlyView、ProjectWorkspaceChatPane、AgentConversationOverlay、DeveloperProjectPanels、DeveloperRuntimePanels 与 features/agent-runtime/panels.tsx
- 删除 Supervisor 独立调试窗口:windows.rs 的 supervisor_chat_window_url / open_project_supervisor_chat_window、main.rs 的 invoke 注册、?supervisor-chat 与 ?agent-chat 前端入口、developer.json capability 和对应 Rust 用例
- 删除工作台壳的开发者 Agent 面板:DeveloperAgentPanel、useDeveloperAgentPanel、useDeveloperAgentState、developerAgentControls
- App.tsx 删除只服务退役面板的 state/ref/effect/handler(agentConversation*、文件/记忆/资产/画板/预览面板处理、commandLog、llmConfigStatus、editorBaseUrl、回放历史与 trace 面板状态、selectedAgent 等)以及由此产生的空分支,并把 requestRuntimeConfigOpen 接回本地 RuntimeConfigDialog
- 立项策划独立成模块:Design Agent 与 Planning V2 的容器和表现移到 view/project-development/planning/(PlanningChatView、PlanningUserInputCard、GddApprovalCard、DesignAgentSurface、PlanningLaneRuntimeStrip、planningLane、planningSessionV2、planningSessionContract)
- 改名到中性概念:ProjectSupervisorComponentProps→ProjectChatComponentProps、WorkspaceLauncherShellProps.ProjectSupervisor→ProjectChat、ProjectDevelopmentView.supervisor→chat、orchestrationMode→agentDockVisible、initialSupervisorMessageClaims→initialTurnClaims、ProjectManifestSnapshotSource 'supervisor'→'chat'、CSS project-supervisor-*/project-planning-*→project-chat-*
- 删除 PROJECT_SUPERVISOR_AGENT_ID 与 PROJECT_SUPERVISOR_PLAN_SOURCE 常量
- 工作台壳自己订阅 game-creator-manifest-invalidated,用 revision→清单→revision 配对读(source: asset-event)并入 currentProjectContext,且不重新检查项目目录;测试夹具改为按监听器集合广播该事件
- 删除只钉住退役界面的 appSurface 用例(旧调试窗口、开发者面板、Agent 对话浮层、运行历史面板)
- 同步文档:ADR、DirectProject 聊天模块抽离实施计划与里程碑、Provider 推理里程碑、策划会话 RuntimeV2、AI 游戏创作实施计划,并在 decision-log 新增 2026-09-19 条
- 删除随退役面板失去调用方的模块与导出:projectSummaryCommands.ts(1054 行旧 Supervisor 斜杠命令处理器)、AGENT_RUN_HISTORY_* 常量、agent-runtime/model.ts 与 project-summary/agentPresentation.ts 里的死导出,以及 agentRunTrace / memoryCommands / projectCommandPolicy 中无调用方的校验与解析函数
- 更新 scripts/check-config.mjs 与 scripts/check-native-shells.mjs 的窗口与命令守卫
2026-09-19 21:59:44 +08:00
k88936 99be1d76fe 抽离 DirectProject 独立聊天容器并删除工作台壳 Direct 状态
- 新增 view/project-development/chat 模块:DirectProjectChatView 自己持有项目清单订阅、首屏锚点、历史分页、发送队列、附件、终止、草稿与本地消息
- DirectProjectChatView 入参收窄为项目路径、入口首轮需求(canonical content[])和两条权限门,Direct 状态不再穿过 App.tsx
- 线程订阅与聊天 reducer 提成 useDirectThreadChatSubscription:subscribe/consume/notify 单飞循环、订阅欠账、SUBSCRIPTION_EXPIRED 重订与 DirectThreadChatState
- chat/components 按「一个组件一个目录、逻辑与表现同目录」重组(ChatHeader、Composer、Conversation、SettingsDialog、ToolCallGroup)
- DirectProjectChatView 拆成聊天头、会话区、回合、输入盒四个组件
- App.tsx 删除 Direct 专属 state/ref/effect/handler 与 directCodex 分支,首轮需求按 canonical content 交给聊天
- 工作台级动作(运行本地预览等)经 DirectProjectChatHandle.announce 交给聊天自己的本地消息流
- Direct 首轮认领记录提成 app/initialSupervisorMessageClaims,供各入口共用
- Rust 侧 Direct 契约 ts-rs 导出目录改到 chat/generated
- 补齐 direct_codex_user_item/wire.rs 既有的 rustfmt 空格漂移
- 同步 ADR、实施计划、decision-log、pitfalls 与 Direct 技术文档
2026-09-19 15:08:31 +08:00
k88936 38ad2ce256 抽离项目开发 DirectProject 聊天模块
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 5m42s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m31s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m9s
Project CI / Backend tests (pull_request) Failing after 30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m16s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m54s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m32s
Project CI / Frontend tests (pull_request) Failing after 16m6s
Project CI / Native shell tests (pull_request) Successful in 24m45s
Project CI / AI game creator shell web tests (pull_request) Failing after 24m28s
将 DirectProject 表现与 controller 移到 view/project-development/chat

删除运行时 Direct feature flag 与 ProjectSupervisorView 的 Direct 行为分支

迁移 ts-rs 生成契约并删除无意义 generated/index.ts

将钱包入口提升到工作台布局并保留现有 Direct 测试覆盖
2026-09-18 22:06:15 +08:00
k88936 65f8e44a88 拆分 DirectProject 聊天架构文档
记录 DirectProject 独立聊天容器与工作台钱包布局决策

更新实施里程碑、计划和项目上下文,明确 DirectProject 固定属于项目开发工作台

记录代码与生成契约迁移的验收边界
2026-09-18 21:04:23 +08:00
k88936 ade1b7803e 修复 DirectProject 校验清单合并
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m3s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m25s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m30s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m41s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m9s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m40s
Project CI / Frontend tests (pull_request) Failing after 7m45s
Project CI / AI game creator shell web tests (pull_request) Failing after 8m31s
Project CI / Native shell tests (pull_request) Successful in 13m1s
让校验函数返回已读取的项目清单

补齐附件在 Codex 输入转换中的分支;图片 part 已退役,只保留附件 part
2026-09-18 14:27:23 +08:00
k88936 71000b6df1 通过 Tauri command 提供 Skill 候选
从已校验的 AGC Skill manifest 派生 list_agc_skill_catalog 命令

移除前端内置 Skill 名称硬编码并增加读取命令测试
2026-09-18 14:27:23 +08:00
k88936 c047543825 复用 DirectProject 引用摘要构造
提取素材与运行区域摘要 helper

让历史投影和 turn 输入转换共用同一份摘要逻辑
2026-09-18 14:27:21 +08:00
k88936 34b3af1d2a 复用已校验的项目清单
DirectProject wire 转换只读取一次项目 manifest

避免每个素材引用重复 I/O 与解析并缩小校验到解析之间的竞态窗口
2026-09-18 14:26:50 +08:00
k88936 f7cba30b6b 拒绝保留路径段的 Skill 名称
在 DirectProject Skill 校验中拒绝 . 与 ..

阻止 Skill 路径解析通过父目录段逃逸配置根目录
2026-09-18 14:26:50 +08:00
k88936 273f12633c 按字符数限制 Skill 名称
使用 Unicode 字符数量而非 UTF-8 字节长度执行 120 字符上限

让多字节 Skill 名称与资源 ID 的长度校验口径一致
2026-09-18 14:26:50 +08:00
k88936 5f5aa17152 展开引用列表指纹分支
将资源、Skill 与运行区域 key 生成改为显式分支

移除 chatReferenceListKey 的嵌套三元表达式
2026-09-18 14:26:50 +08:00
k88936 3a57d9fbdf 移除未使用的 JSON 类型导入
删除 codex_app_server 中未被直接引用的 serde_json::Value 导入
2026-09-18 14:26:50 +08:00
k88936 382b925ab9 复用引用节点插入逻辑
统一资源与 Skill 候选的 Lexical 节点插入流程

避免两个选择处理器在删除查询和关闭菜单行为上漂移
2026-09-18 14:26:50 +08:00
k88936 c1482012c6 整理引用芯片标题分支
提取资源、Skill 与运行区域标题计算 helper

移除 ResourceReferenceChip 的嵌套三元表达式
2026-09-18 14:26:50 +08:00
k88936 c3a17a6efc 更新Skill提及里程碑状态
标记输入提示实现进入进行中阶段
2026-09-18 14:26:50 +08:00
k88936 1bfdc3a760 接通Skill提及Codex输入
将 canonical Skill part 按原顺序转换为 type skill 输入项

沿用客户端隔离 Skill root 解析受控路径
2026-09-18 14:26:50 +08:00
k88936 1e27cd229d 接入客户端已启用Skill候选
从扩展索引读取启用状态并合并内置 Skill 清单

输入提示只展示可用 Skill 并保持取消安全
2026-09-18 14:26:49 +08:00
k88936 774710452f 校验Skill提及用户输入
为 canonical Skill part 增加名称边界校验

将 Skill 提及保留为 Codex 可识别的 $name 文本
2026-09-18 14:26:49 +08:00
k88936 aaecb82622 增加输入框Skill提及候选
扩展聊天引用模型支持 Skill 节点与 canonical content

复用 Lexical typeahead 增加 $Skill 候选和芯片展示

 Conflicts:
	apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx

 Conflicts:
	apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx

 Conflicts:
	apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx
	apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts
2026-09-18 14:26:49 +08:00
k88936 64ad24ac77 补充DirectProject Skill提及输入规范
更新聊天输入主规范并增加 Skill 提及边界

新增 Skill 提及里程碑与小切片实施计划
2026-09-18 14:26:49 +08:00
k88936 75ec3361dc 限制附件元数据格式与长度
校验 inline 附件名称和媒体类型的控制字符、格式与长度。

共享附件清洗上限常量并补充异常元数据拒绝测试。
2026-09-18 14:12:47 +08:00
k88936 bc1dc868a1 校验已导入附件的项目路径
imported 附件必须携带有效非空的项目相对路径。

failed 附件仍允许无路径,并补充状态路径组合测试。
2026-09-18 14:07:07 +08:00
k88936 50204ff7aa 限制 DirectProject inline 附件数量
为 canonical 附件增加独立的八个条目上限。

补充超限附件拒绝测试,避免绕过素材引用数量门禁。
2026-09-18 14:03:30 +08:00
k88936 70fa160819 清洗附件摘要元数据
wire 投影复用附件名称与媒体类型的安全清洗。

附件项目路径输出归一化结果,避免原始控制字符进入模型提示。
2026-09-18 14:00:00 +08:00
k88936 2e78cbf152 Merge remote-tracking branch 'refs/remotes/origin/master' into fix/ref-persistent
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m18s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m22s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m30s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m41s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m56s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m25s
Project CI / Repository checks (pull_request) Failing after 4m0s
Project CI / Frontend tests (pull_request) Failing after 4m50s
Project CI / Native shell tests (pull_request) Successful in 8m29s
Project CI / Backend tests (pull_request) Successful in 11m23s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m16s
2026-09-18 13:29:58 +08:00
k88936 840baf208f 移除草稿转换的文本引用回退
将 ChatComposerDraft.content 设为必填的 canonical 唯一来源。

删除 text 与 references 重建 DirectCodexUserItem 的兼容分支及其无效空值兜底。
2026-09-18 13:19:01 +08:00
k88936 d8af0fa882 保留聊天草稿中的附件引用节点
为 agc_attachment_reference 增加 Lexical 附件节点与可移除 chip。

恢复初始 canonical content 时不再静默丢弃附件,并让编辑器读回完整附件 part。
2026-09-18 13:17:31 +08:00
k88936 faed0ec251 移除 DirectProject 入口的重复回合审计
DirectProject 继续以 project.jsonl 作为完整回合记录。

入口不再创建 DirectCodexTurnAudit,保留底层可选审计接口供独立测试和其它路径使用。
2026-09-18 12:56:43 +08:00
k88936 6e63a118e0 延后资源草稿直到素材索引可用
避免初始编辑器挂载时 assetsById 为空导致资源引用永久丢失。

保留 Lexical 节点自行解析资产名称的后续 TODO。
2026-09-18 12:53:44 +08:00
k88936 4ae5ab14fa 移除队列标签不可达兜底分支
队列标签统一复用 canonical content 的文本派生。

删除不会被现有引用类型触发的附件和素材兜底文案。
2026-09-18 11:36:10 +08:00
k88936 b73a329b2c 修复附件场景下历史命令识别
使用不含附件的文本输入判断 /history 命令。

避免待发送附件把 canonical prompt 拼成长文本后绕过历史记录加载。
2026-09-18 11:35:18 +08:00
k88936 06004bfeea 清理资源引用标签的重复分支
移除相同结果的死三元分支。

保持资源与运行区域引用均使用统一的 @标签 文本。
2026-09-18 11:34:35 +08:00
k88936 ecea7b4ad3 解决 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 定向测试。

同步合并应用壳层、后端绑定和项目文档变更。
2026-09-18 10:47:55 +08:00
k88936 b1e82d64cb 合并 DirectProject 聊天真相源与 canonical content 行为
- 统一历史切片与 Thread Manager 事件投影,保留 DirectProject 单一聊天事实源

- 保留 canonical content 严格校验、资源显示名派生、附件引用与队列语义

- 修复首页首轮缺失 canonical user item 的真实回归并完成定向验证
2026-09-18 02:49:09 +08:00
k88936 b7622f4a24 Merge remote-tracking branch 'origin/master' into fix/chat-status-lost
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/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
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
# Conflicts:
#	docs/project-memory/shared-memory/pitfalls.md
2026-09-17 16:57:47 +08:00
k88936 cb840c6e8c Merge branch 'master' into fix/chat-status-lost
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
# Conflicts:
#	docs/project-memory/shared-memory/decision-log.md
#	docs/project-memory/shared-memory/pitfalls.md
2026-09-17 16:53:18 +08:00
k88936 0a9cdf6395 补 DirectProject 历史分页连拉的实现证据
里程碑前端连拉那条转成已验收,证据指向新单测与跨页同回合集成用例
证据要求补上本轮实跑结果与变异验证现象,避免再出现打勾但只覆盖一半
决策记录把验证口径写成具体条数与变异验证结论
2026-09-17 16:51:30 +08:00
k88936 e382d23892 补跨页同回合的历史分页回归用例
用 51 条历史复现用户报的现象:20 条工具条目整屏落进同一个折叠的执行过程
断言一次点击连拉两页拿到新用户气泡,并按 process-31 / process-11 两次锚点推进
断言连拉插入旧内容后视口仍停在原处,不被拉回列表底部
2026-09-17 16:49:08 +08:00
k88936 37ae7508b8 App 聊天历史分页改走连拉循环
首屏与「显示更早」共用 readDirectHistoryPages,不再各自单发一页
首屏基线取空并在守卫后一次性并入聊天 reducer,失败仍按原有语义抛出
「显示更早」按当前视图条目判断新回合,取到多页一次性并入并推进锚点
连拉中途失败保留已取到的页,报错统一走同一个出口
2026-09-17 16:39:24 +08:00
k88936 1ae6c6cd06 新增 DirectProject 历史分页连拉模块与单测
directHistoryPaging 按「合并后聊天投影回合数增加」判断可见反馈,出现新回合或 hasMore=false 即停
连拉上限 DIRECT_HISTORY_MAX_PAGES_PER_ACTION 放进 app/constants,每个操作最多 5 次请求
锚点未推进(items 为空 / firstItemId 为 null 或原地打转)时立即停手并收口 hasMore
某页读取失败不丢已取到的页,返回 error 交给调用方报错
directThreadChat 抽出 projectDirectHistoryItems,让分页判据与视图共用同一份投影
2026-09-17 16:27:09 +08:00
k88936 459f8a60e5 文档先行:定死 DirectProject 历史分页连拉的可见性口径
把「可显示」判据明确为合并后聊天投影出现新回合,并写清每操作 5 页上限与锚点不前进即停
里程碑把原第 56 条按后端锚点回扫与前端连拉拆成两条独立验收项,前端那条先留待补证据
实施计划第 6 步同步口径,并把新分页单测加入验证命令
决策记录新增 2026-09-17 条目,记下落点、上限粒度、失败与竞态边界
2026-09-17 16:18:51 +08:00
k88936 63c495dab0 DirectProject 不再把 Codex 回显的用户消息下发给聊天
- 运行态条目投影新增回显过滤 direct_thread_visible_item,item/started 与 rawResponseItem/completed 两条路径共用
- is_direct_project_codex_user_item 提升为 pub(crate),落盘侧与运行态侧共用同一判据
- 新增 E2E direct_project_turn_does_not_forward_codex_user_echo_as_chat_items:修复前事件流会多出两条同文本用户条目(已做变异验证)
- 同步 ADR 与 shared-memory decision-log:回显过滤必须同时覆盖落盘与运行态两侧
2026-09-17 14:41:58 +08:00
k88936 b37d91882c Revert "修复DirectProject对话历史分页卡页"
This reverts commit 1b010fb1f8.
2026-09-17 14:38:39 +08:00
k88936 dd339638cc Revert "修复历史重读后旧消息追加到末尾的乱序"
This reverts commit 036395c6c9.
2026-09-17 14:38:22 +08:00
k88936 0caf998227 重新生成 ts-rs 前端绑定并让前端按 null 语义构造运行时区段
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m35s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m31s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m15s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m47s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m15s
Project CI / Repository checks (pull_request) Failing after 19s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m15s
Project CI / Frontend tests (pull_request) Failing after 5m44s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m49s
Project CI / Native shell tests (pull_request) Successful in 10m42s
- 跑 export_bindings 后 prettier 格式化:DirectCodexUser* 5 个文件内容更新,新增 DirectCodexUserMessageEnvelope.ts 与 ui-editor 的 BindingChange/BindingDTO
- DirectCodexUserRuntimeRegionPart 的可选字段由 `?:` 变成 `T | null`(Rust Option 会显式序列化成 null,不是省略键)
- chatReferenceToContentPart 改用 `?? null`,消息条目的 satisfies 目标改为 DirectCodexUserItem(type:'message' 归到信封类型)
- ResourceReferenceInput 不再复制一份区段字面量,改为复用 chatReferenceToContentPart
- 验证:tsc --noEmit 通过;resourceReferences 等 5 个用例集 81 passed;appSurface 452 passed / 17 skipped(连跑两次)
2026-09-17 14:03:05 +08:00
k88936 6eed6bd13f 收敛 content→可读文本为单一口径并强制传 manifest(review 第 2、3 条)
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m10s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m43s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m28s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m13s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m0s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m58s
Project CI / Frontend tests (pull_request) Failing after 4m24s
Project CI / AI game creator shell web tests (pull_request) Failing after 4m2s
Project CI / Native shell tests (pull_request) Successful in 8m31s
- directCodexContentToPromptText 的 assets 改为必填,去掉 = [] 默认值:漏传素材清单从运行期文案退化变回编译期错误
- queuedChatTurnLabel 的 assets 与 ComposerTurnQueue 的 assets 属性同样必填,队列 chip 与消息正文共用同一个派生(chip 只多做压单行与限长)
- 断言改为显式传 [] 或 manifest:resourceReferences、chat-composer 用例按新签名收敛
- 更新 pitfalls 条目与 decision-log(canonical content 有效性只判整条 content、投影逐字透传、可读文本单一口径必填 assets)
2026-09-17 14:02:21 +08:00
k88936 b543a060b7 放宽 DirectProject content 校验为「整条判定」并按逐字投影(review 第 1 条,① 口径)
- Rust validate_direct_codex_user_item 改为只判整条 content:有一段非空白文本或任意非文本 part 即有效,单个纯空白 input_text(段落分隔 / 软换行 / chip 后的分隔空格)合法,不再逐个 part 拒绝空文本
- 新增 content_has_meaningful_input,并与前端 hasMeaningfulDirectCodexContent 同口径;wire.rs 的「不能转换为空 prompt」只作兜底
- ResourceReferenceInput 的投影层原样透传编辑器节点:不做空白过滤、也不与相邻 part 合并,删掉 appendInputText 与中间版本的「向前合并」收集器
- 按逐字投影更新资源输入、Godot 回合用例的 canonical content 断言
- 同步里程碑实现结论与证据、pitfalls 条目;Rust validation/wire 新增「单个纯空白 part 通过、整条全空白拒绝」用例
2026-09-17 14:01:41 +08:00
k88936 9993b2feb5 通知分类收敛成生产读取器与单测共用的唯一实现
- direct_codex_notification_event 收编 resolution / request / item / rawItem / Terminal 全部分支,并接收 turnId
- 读取器删掉自己那份内联分类,只保留「必须有 turnId 才处理」与活动/正文节流
- agentMessage 缺 itemId 的告警与 direct-missing-item:{turnId} 兜底身份原样保留,避免行为变化
- 新增 direct_notification_classification_covers_the_reader_branches 钉住各分支;E2E 假 app-server 用例继续覆盖读取器
2026-09-17 13:47:32 +08:00
k88936 d348cf9064 线程 id 只按路径归一,不再受 manifest 可读性影响
- direct_thread_id_for_project 改用 resolve_direct_codex_project_authority(存在/是目录/canonicalize)
- 不再读 .agent/manifest.json:那次读取是连接池权威身份的要求,线程 id 只是路径 key
- 否则 manifest 在订阅时读失败、跑回合时读成功,会让符号链接等非 canonical 调用方路径算出两个线程 id,订阅绑到空线程
- 新增 direct_project_thread_id_ignores_manifest_readability,旧实现下会退回调用方原始字符串(已做变异验证)
2026-09-17 13:42:35 +08:00
k88936 e640219243 思考正文改走生产路径的 ReasoningDelta 通道
- 新增 direct_codex_reasoning_delta_event,两条 reasoning 增量通知只在这一处分类
- 生产 stdout 读取器改用该函数,并跳过「preparing 活动」的降级与活动节流(活动节流会按类别吃掉逐段正文)
- 旧分类函数 direct_codex_notification_event 同步改用它,避免两份实现再次分叉
- 单测 direct_reasoning_deltas_stream_text_while_plan_and_command_output_stay_activity 取代原先断言 Activity 的旧用例
- 新增 E2E codex_app_server_streams_reasoning_deltas_without_activity_fallback:把读取器的路由关掉即转红(已做变异验证)
2026-09-17 13:22:36 +08:00
k88936 da2ad83c3b 分页锚点改用文件里的原始 item id
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 6m22s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m34s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m49s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m49s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m7s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m19s
Project CI / Frontend tests (pull_request) Failing after 6m15s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m47s
Project CI / Native shell tests (pull_request) Successful in 12m22s
- 新增 direct_project_history_anchor_id:优先取 project.jsonl 原始 id,缺 id 时退回归一身份兜底
- read_direct_project_history_items_slice_at 的锚点匹配与 first_item_id 都改用原始 id
- 工具条目的调用与输出共用 call_id,按归一身份当锚点会先跳过 output 再把上一屏的 function_call 带回来
- 原分页用例更名 history_window_paginates_upwards_by_item_id_anchor
- 新增 pagination_anchor_uses_raw_item_id_for_tool_call_pairs,旧实现下会返回 call-1 而非 fc-1(已做变异验证)
2026-09-17 00:24:01 +08:00
k88936 ad19c9475e 回扫的 terminated 改成按真实换行结尾判定
- DirectProjectHistoryReverseLines 增记 saw_delimiter,第一段按"文件是否以换行结尾"判定,之后的行按已切掉的分隔符判定
- 尾部残行(上一行完整、这一行没有换行)解析失败时改为跳过继续回扫,不再报错、也不再就地结束丢掉更早的完整历史
- 换行结尾的坏行(含文件第一段)与顺序读取保持一致地失败关闭
- 补两条回扫用例:尾部残行不丢更早条目、坏掉的第一段必须失败关闭(都按旧实现变异验证过)
2026-09-17 00:18:49 +08:00
k88936 975a2e577c 终止按钮改按综合繁忙信号判定
- handleCancelDirectCodexTurn 的守卫改用 supervisorChatBusy,与「终止」按钮的可见条件对齐
- 刚提交、turn.started 还没到达的窗口里点击终止不再回"当前没有正在运行的回合"
- chat-composer 用例补一条:生命周期事件缺席时也能发出 cancel_direct_codex_turn
2026-09-17 00:10:20 +08:00
k88936 1b40f030e7 删掉聊天 reducer 里没人读的订阅身份与首屏锚点
- DirectThreadChatState 去掉 subscriptionId 与 lastCompletedItemId 两个只写不读的字段
- resolveDirectThreadBootstrap 只把 bootstrap 事件 reduce 进状态,不再顺带写入这两个字段
- directThreadChat 单测改成断言聊天状态里不再出现这两个字段(bootstrap 载荷本身不变)
2026-09-17 00:05:57 +08:00
k88936 098693636a 分页切片开头的前导条目并进后面那个回合
- buildDirectChatTurns 先攒住第一个用户条目之前的过程条目,交给后面那个回合
- 不再渲染没有用户气泡的孤儿回合;整份历史都没有用户条目时仍保留一个回合
- directTurnPresentation 单测补一条分页切片从半截回合开头的情形
2026-09-17 00:02:11 +08:00
k88936 e17af3008e 首屏历史切片改到 staleness 守卫之后再并入聊天 reducer
- loadProjectConversation 不再在 read_direct_project_history_slice 的 then 回调里直接写 directThreadChat
- 切片先暂存,等 loadVersion / localProjectPathRef 守卫通过后再与 hasMore、锚点一起落到状态
- 现有 launcher 按项目路径 key 重挂载 App,跨项目污染暂不可复现;本次只是让这条写入与其它状态写入的时序一致
2026-09-16 23:59:29 +08:00
k88936 64bd006981 测试骨架的历史切片按 limit 与锚点分页
- harness.ts 的 read_direct_project_history_slice 按生产口径从队尾取一屏、锚点条目不进窗口
- 按本屏最老一条算 firstItemId,收满一屏后再有条目才算 hasMore
- project-development.suite.ts 补一条用例:26 条历史只显示尾部 20 条,点「显示更早的对话」按锚点取回前 6 条后按钮消失
2026-09-16 23:52:02 +08:00
k88936 6a4cb2bc18 测试骨架的 subscribe bootstrap 按生产口径过滤
- harness.ts 记录未完成条目集合、事件序号与最新生命周期锚点
- subscribe 只回放未完成条目与最新生命周期锚点,已完成条目与增量正文不再补发
- 去掉已下发事件的序号记录,避免序号表无限增长
2026-09-16 23:48:46 +08:00
k88936 1b3a3b003f 测试骨架不再重复下发 turn.started
- harness.ts 记录运行态"最新回合是否在跑",emitDirectThreadEvents 只在生命周期事件上更新
- completeDirectThreadTurn 仅在回合没在跑时补 turn.started,避免与用例先发的事件重叠
2026-09-16 23:45:19 +08:00
k88936 ff8e517c8e 清理 DirectProject 回合结束后丢弃回复正文的死语句
- App.tsx 不再绑定 chat_with_game_creator_direct_codex 的返回值,删掉 void reply
- 保留说明:回复正文按条目身份从线程事件 / 历史切片进聊天,本地不再补第二条
2026-09-16 23:42:58 +08:00
k88936 d874a13a92 补记 Node 26 下 vitest jsdom 用例的 localStorage 坑
- pitfalls 记录:Node 26 的实验性 globalThis.localStorage 会顶掉 jsdom 的 Storage,导致只声明 jsdom 的用例读到 undefined
- 给出跑法:NODE_OPTIONS=--localstorage-file=… 后 clientApi 与 chatPromptPolish 共 26 项转绿
- 里程碑证据更新为全量结果:125 files / 1676 passed / 17 skipped,tsc exit 0
2026-09-16 22:41:54 +08:00
k88936 5eba53a5cc 补齐 DirectProject 聊天真相源收敛的文档结论
- 主规范:运行态事件改为下发与历史切片同形的脱敏原始条目,不再只给类型与身份
- 主规范与 ADR 写清回执竞态:回执前到达的通知只能记欠账,回执到达后必须补一次 consume
- ADR 与实施计划说明 list_game_creator_direct_active_turns 有意保留,服务首页运行中项目列表
- 里程碑验收标准按自动化证据勾选,运行时验收与已知环境缺口单独标注
- 实施计划补 export_bindings 后需 prettier 生成目录、测试文件更名与竞态风险项
2026-09-16 22:16:24 +08:00
k88936 33ddb5de27 DirectProject 聊天框改用线程订阅作为唯一运行态来源
- App.tsx 删除 Direct 回合事件订阅、turn-stream/tool-calls 读取、活动回合快照接管与瞬时应答文本
- 新增 game-creator-direct-thread-notify 唤醒的 subscribe/consume 单飞循环,过期时重订一次
- 通知先于订阅回执到达时记一笔欠账,回执到达后补一次 consume,避免回合尾部事件卡在队列里
- 历史切片并入同一个 reducer,聊天条目只由 selectDirectChatEntries 投影
- ProjectSupervisorView 改为渲染用户气泡 / 执行过程折叠区 / 最终回复,运行态只保留一个 turnRunning
- directTurnPresentation 重写为条目分区:连续工具合块、本地运行期说明只作最终提示
- 测试改按订阅事件与历史条目断言,补回执竞态、持久化工具卡片、空对话首轮三条用例
2026-09-16 22:16:15 +08:00
k88936 bf8f96c115 DirectProject 回合事件补齐用户条目与终态兜底
- 新增 direct_thread_id_for_project:订阅、回合事件写入与兜底释放共用一个线程身份
- 订阅命令改用同一个身份函数,删掉命令层自己那套路径归一
- 用户消息落盘后立刻作为 item.completed 下发,前端用同一个 itemId 合并乐观气泡与历史条目
- 采集终态事件是否已经下发,缺失时按成功 / 失败补一条 turn.completed,避免前端永远停在运行中
- 取消回合的 Stale 分支补一条 turn.completed(aborted)
2026-09-16 22:15:57 +08:00
k88936 e469831543 修复快速编辑提示词把 @ 引用写成内部 id(review 第 3、4 条)
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m39s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m18s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m8s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m55s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m11s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m45s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m10s
Project CI / Native shell tests (pull_request) Successful in 8m37s
Project CI / Frontend tests (pull_request) Successful in 8m46s
- applyResourceQuickEditPrompt 与 applyResourceQuickEditDraft 改用 directCodexContentToPromptText(content, manifest.assets),并在 useCallback 依赖里加上 manifest.assets
- 两处必须同口径:比较用的草稿文本与落进 quickEditPanel.prompt 的文本都按显示名展开,出站 derive_local_project_resource 的 prompt 与聊天 @ 逐字一致,也不会每次输入都重跑 replaceText
- 补充两处注释说明素材清单口径,shared-memory/pitfalls 记录「派生文本漏传 manifest.assets」的现象、原因、处理与验证
2026-09-16 21:26:53 +08:00
k88936 869abad879 修复排队消息 chip 的 @ 引用露出内部 resourceId(review 第 2 条)
- queuedChatTurnLabel 增加 assets 入参并透传给 directCodexContentToPromptText,@ 引用按 manifest 显示名展开,与聊天输入区文案口径一致
- ComposerTurnQueue 增加 assets 属性,ProjectSupervisorView 把 chatProjectAssets 传下去
- chat-composer 用例新增「队列 chip 的 @ 引用按 manifest 显示名展开」,钉住显示名文案与不传清单时的稳定 id 兜底
2026-09-16 21:25:38 +08:00
k88936 280a2dc5d1 修复 DirectProject 输入草稿丢掉引用后的换行(review 第 1 条)
- ResourceReferenceInput 的草稿投影改为「向前合并」:上一 part 是引用 chip 时先把待写文本攒在 pendingText 上,等下一个文本 part 到来再合并,段落分隔符与软换行不再被 trim() 丢掉
- 攒到最后仍只有空白的文本不入 content,避免产出 Rust validate_direct_codex_user_item 会拒绝的空 input_text
- 新增 resourceReferenceInput 用例「引用后面的段落分隔不会被吞掉」,钉住 chip 后一段的 content part、派生文本与「不产生纯空白 part」
- 同步更新 canonical content 严格边界里程碑的实现结论与证据,并在 shared-memory/pitfalls 记录该缺陷的现象、原因、处理与验证
2026-09-16 21:24:41 +08:00
k88936 3c6e084a41 DirectProject 历史切片改为从文件尾回扫并删掉聊天框只读命令
- 新增 DirectProjectHistoryReverseLines:按块从文件尾逐行回读 project.jsonl
- 幂等回扫与"读一屏"共用这一套尾部回读,不再各自从文件头读到尾
- read_direct_project_history_items_slice_at 语义不变仍返回元组,新增第四项为本屏最老的 itemId 作分页锚点
- 分页锚点只按归一身份 itemId 匹配,不再接受第二个 id
- read_direct_project_last_item_id_at 改为尾部回扫读一行,不再读整份历史
- 删除只服务聊天框回读的 read_direct_tool_calls / read_direct_turn_stream 命令与注册
- 保留 list_game_creator_direct_active_turns:它服务首页跨页面"运行中的项目",不是聊天框读路径
- 补历史切片尾部回扫与分页锚点两条用例
2026-09-16 20:17:15 +08:00
k88936 ed3369a494 文档先行:把 DirectProject 聊天真相源收敛的文档改到当前实现状态
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 5m13s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m30s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m39s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m43s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m8s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m10s
Project CI / Frontend tests (pull_request) Failing after 3m24s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m7s
Project CI / Native shell tests (pull_request) Successful in 6m8s
- 主规范把事件模型写成 ts-rs 导出的 tagged enum,说明 seq 不下发、事件不带回合身份,并补上 ts 绑定生成与 f64 时间戳约定
- ADR 补上 tagged enum 绑定、turn id 删除、正文只增不减与回合回收、前端卡片去掉 turnId、未知类型前端丢弃
- 里程碑与实施计划同步:文件改名到 direct_thread_wire.rs,新增删回合身份与生成绑定两步,验证命令加上 export_bindings
- 工具卡片技术方案在文首加修订段:数据来源层作废,表现层契约仍然有效,DirectRuntime 的 tool-calls.jsonl 写入保留
- 对话回合唯一投影里程碑改为 superseded,列出仍然有效与已作废的分界
2026-09-16 19:57:21 +08:00
k88936 fdc48fe725 前端聊天投影按 ts-rs 生成类型收口,删掉 callId 与回合身份
- directThreadItemProjection.ts 直接消费生成的 DirectThreadItem,工具卡片 kind / 标题 / 摘要 / 状态与可见性判定都在这里
- directThreadChat.ts 聊天条目只留一个 itemId:去掉 callId 概念,去掉 deltaText 缓冲,增量直接追加到运行态条目正文
- 合并规则收成先到者赢、后到只补空:正文只增不减、工具状态允许从 running 升级到终态
- 回合结束把运行态条目并入历史并清空 live,条目既不消失也不重复
- directThreadEvents.ts 改为转发 ts-rs 绑定,只保留历史条目转消息的过渡函数
- ToolCallGroup 与 toolCallGroupPresentation 改用去掉回合身份的 DirectChatToolCard
- generated/index.ts 补上 DirectThread* 绑定导出
- 回退本仓库其它模块仍在改的 DirectCodexUser* 绑定重新生成(避免误改他人进行中的契约)
- 两个 vitest 用例文件按新 API 重写,11 条用例通过
2026-09-16 19:49:26 +08:00
k88936 2748468d12 DirectProject 聊天事件改用 ts-rs 导出的 tagged enum 并删掉 turn id
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 5m11s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m51s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m57s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m50s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m2s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m14s
Project CI / Native shell tests (pull_request) Failing after 2m13s
Project CI / AI game creator shell web tests (pull_request) Failing after 46s
Project CI / Frontend tests (pull_request) Failing after 3m32s
- 新增 direct_thread_wire.rs:DirectThreadItem / DirectThreadEvent / 订阅与历史切片全部改成 ts-rs 导出的 tagged enum,取代原大而全的可空结构体
- 删除 direct_thread_raw_item.rs,模块注册与直通引用改到 direct_thread_wire
- 条目身份只看一个 itemId:工具条目的第二个 id 在 Rust 边界归一,不再对外暴露
- 删除 DirectProject 聊天事件里的 turn id:生命周期用无载荷的 turn.started / turn.completed{status} 表示
- append 直接接收 DirectThreadEvent 并返回同一事件,队列内部自算 seq
- 请求事件改为携带 DirectThreadRequestKind,去掉字符串中转
- 思考增量走 ReasoningDelta 通道,与正文增量共用 item.delta
- at 用 #[ts(as = "f64")] 对齐 Tauri JSON 通道的 number
- 用 cargo test export_bindings 重新生成 project-workspace/generated 绑定
2026-09-16 19:43:57 +08:00
k88936 09ad0073fe 修正 DirectProject 订阅语义:bootstrap 事件就是当前要处理的事件
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
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Has been cancelled
subscribe 已在同一边界把游标钉在队尾,因此前端直接 reduce bootstrap 事件,不再补一次 consume
主规范、ADR 与实施计划同步删除"竞态窗口"说法,notify 仍是唯一唤醒来源
2026-09-16 19:06:05 +08:00
k88936 42b702d362 按 grill 结论修正 DirectProject 聊天真相源收敛计划
里程碑与实施计划改定边界:Thread Manager 只搬运脱敏原始条目,不生成卡片形状与可见性
条目身份归一为 call_id ?? id,Thread Manager 与前端不再暴露第二个 id 概念,分页锚点仍取文件里的原始 item id
合并规则收敛为"先到定形、后到补空白",删除逐字段优先级表
主规范补充条目形状三条职责边界,并说明思考正文流式下发不放宽可见范围
ADR 同步:DirectRuntime 自己的进度事件与投影文件保留,前端删除的是 DirectProject 的读路径
失败与中止说明只在运行期显示;历史切片改为从文件尾反向扫描;未知 item 类型透传并加 TODO
2026-09-16 19:05:04 +08:00
k88936 edfd1f9143 修正 Godot 直接回合用例对 canonical content 的断言
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
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 1/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
- appSurface Godot 用例不再断言被裁剪过的纯文本,改为断言编辑器实际提交的 canonical content(Shift+Enter 与组合态 Enter 插入的换行原样保留)
2026-09-16 18:51:08 +08:00
k88936 423f2d7184 修复独立总控聊天窗口未发送草稿的恢复
- App.tsx 增加 supervisorChatDraftHydratedRef 守卫的挂载水合:独立总控聊天窗口挂载走 loadProjectConversation,不经过 openWorkspace,此前 sessionStorage 里的草稿只写不读,重载后为空
- 仅在 supervisorChatOnly、存在持久化草稿且输入框已挂载时回填一次,避免覆盖用户后续输入
2026-09-16 18:50:55 +08:00
k88936 656c89e4b2 把 DirectProject 聊天条目的投影从前端收口,Rust 只搬运脱敏原始条目
新增 agent/direct_thread_raw_item.rs:把 Codex 原始 response item 与 app-server item 收敛成同一形状的脱敏原始条目,只做挑字段、脱敏、截断,不再生成卡片的 kind / 标题 / 折叠摘要
删除 agent/direct_chat_entry.rs:工具卡片形状不再由 Rust 生产
历史切片改为返回脱敏原始条目列表,不再在 Rust 侧合并 function_call 与 function_call_output
新增前端 directThreadItemProjection.ts:工具卡片 kind / 标题 / 摘要 / 状态与可见性判定全部在前端完成
directThreadChat reducer 改用同一投影函数处理运行态事件,新增 mergeDirectHistoryItems 走同一份投影与合并规则
DirectThreadHistorySlice 去掉 entries 与 itemTimestamps,改为 items + firstItemId
2026-09-16 18:45:13 +08:00
k88936 aa9076c95c 删除 DirectProject 图片专用 content part
- 移除 DirectCodexUserContentPart 的 agc_image_reference 分支与对应 ts-rs 绑定
- Rust 校验与 Codex wire 投影只保留 agc_attachment_reference
- 更新附件顺序投影测试的用例名、输入与断言
- 同步聊天素材功能说明、用户 Response item 里程碑与实施计划
- 在决策日志记录上传文件统一走附件 part、不保留图片兼容分支
2026-09-16 18:29:24 +08:00
k88936 0ec1179bf1 移除前端 consume 轮询兜底
- 唤醒只由 notify 驱动,bootstrap 后立刻 consume 一次补齐竞态窗口
- 定时 tick 无法自举:判断活动回合本身依赖事件,唤醒丢失时定时器不会启动
- 主规范、ADR 与实施计划同步删除该条款
2026-09-16 18:17:38 +08:00
k88936 2938a49cac 固化 DirectProject 聊天真相源契约
- 主规范新增首屏锚点、生命周期锚点必进 bootstrap、事件自足与不提供单点快照四条契约
- 新增 ADR 记录单一事实源取舍、删除清单与待验证的 id 空间风险
- 新增里程碑与实施计划,CONTEXT.md 补项目对话历史/运行态事件/聊天投影词条
- 目标文档合同与文档索引同步
2026-09-16 18:05:30 +08:00
k88936 ae0f9376c9 新增 DirectProject 聊天 reducer 深模块
- directThreadChat 把运行态事件与历史切片归并成同一份聊天条目,活动回合只由 turn.started/turn.completed 判定
- item.delta 载荷补 kind,reducer 不再靠 itemId 猜条目类型
- bootstrap 原子替换运行态并保留历史窗口,历史与运行态按 callId ?? itemId 去重合并
- 新增 5 条 reducer 单测
2026-09-16 18:05:10 +08:00
k88936 29d0cbb4df 运行态事件与历史切片共用聊天条目投影
- 新增 direct_chat_entry 深模块,把原始 response item 与 app-server item 投影成同一聊天条目
- item.started/item.completed 载荷改为完整条目,前端不再需要按 itemId 取快照
- 原始 function_call 与 function_call_output 按 callId 并成一张卡片,文本与明细统一脱敏截断
- 历史切片新增 entries 与 first_item_id,分页锚点不再依赖是否出现可显示条目
2026-09-16 18:01:50 +08:00
k88936 41366dd71d 修复DirectProject canonical内容边界(review 第 8 条)
- 移除 chatComposerDraftToDirectCodexUserItem 对纯空白 input_text 的预过滤,原样传递 draft.content
- ChatComposerDraft 只保留 canonical content,删除 text / references 旧字段
- 新增 directCodexContentToPromptText、hasMeaningfulDirectCodexContent、directCodexUserItemFromContent 供 caller 与展示派生
- 删除 executeChatAgentReply 的 userItem 兜底分支,首页首轮、队列出队、普通提交与策略重试显式构造 canonical user item
- QueuedChatTurn 只保存 clientTurnId 与 userItem,队列 chip 文案由 content 派生
- 旧 Planner / legacy Supervisor caller 显式构造纯文本 item 并留下迁移 TODO
- 资源输入区对外只暴露 canonical content,内部文本草稿改为文本 → content 重建
- 迁移草稿、润色、队列与 appSurface 测试 fixture 到 content-only
- 更新 canonical content 里程碑与实施计划文档结论
2026-09-16 17:46:41 +08:00
k88936 721e45f01b 统一 DirectProject 运行态条目的合并身份
- Thread Manager 事件新增 callId,条目身份统一为 callId 优先、其次 itemId
- 工具条目按 callId 归并 started/completed,不再留下永远收不到完成事件的幽灵活跃条目
- 历史锚点仍取 response item id,保证分页锚点能在 project.jsonl 中命中
- 补充跨 id 空间归并的单元测试
2026-09-16 17:45:28 +08:00
k88936 f4b496caca 补充DirectProject canonical内容严格边界计划
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
Project CI / AI game creator shell Rust shard 2/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 shard 1/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Has been cancelled
- 明确 content-only 输入与最终空内容校验

- 规划删除 userItem 兜底并修正 Direct caller
2026-09-16 15:28:25 +08:00
k88936 fc0fd846f9 避免快速编辑润色回填重复更新
- 回填前比较编辑器当前文本

- 防止输入区 onChange 与宿主回调形成循环
2026-09-16 13:22:57 +08:00
k88936 fa99d9ad62 避免总控草稿初始化误删
- 编辑器尚未恢复时跳过空草稿持久化

- 保留已有会话草稿等待输入区完成恢复
2026-09-16 13:20:47 +08:00
k88936 64a52005f8 修复清空草稿后润色状态残留
- 在编辑器发出空草稿时重置润色结果

- 清除本轮发送提醒确认标记
2026-09-16 13:20:01 +08:00
k88936 645d806fed 修复空初始草稿吞掉首次编辑
- 仅在初始内容实际非空时跳过初始化回调

- 保证快速编辑首次输入同步到宿主状态
2026-09-16 13:19:13 +08:00
k88936 0b9d16e8f1 修复润色按钮无法感知输入
- 用 React 状态同步编辑器当前文本

- 让按钮禁用态随用户输入即时更新
2026-09-16 13:18:31 +08:00
k88936 d5c4e0e780 修复快速编辑润色结果未回填
- 为快速编辑输入区绑定编辑器句柄

- 润色回调同时更新面板状态与编辑器文本
2026-09-16 13:17:50 +08:00
k88936 30e27abc60 修复建议命令草稿残留引用
- 准备命令草稿前先清空编辑器

- 避免提交用户上一轮选择的素材引用
2026-09-16 13:16:07 +08:00
k88936 b607501916 修复总控草稿恢复后被清空
- 仅在非纯聊天模式清空聊天编辑器

- 保留纯聊天模式恢复的会话草稿
2026-09-16 13:15:08 +08:00
k88936 46e3cd9744 内联 DirectProject 附件与图片内容
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m30s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m35s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m54s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 6m1s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m52s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m41s
Project CI / Frontend tests (pull_request) Failing after 3m54s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m41s
Project CI / Native shell tests (pull_request) Successful in 6m9s
新增附件与图片 canonical content part 及 ts-rs 绑定

移除 DirectProject Tauri 附件 sidecar 追加路径

修正前端引用字段与生成类型适配

补充附件图片顺序投影测试并同步里程碑文档
2026-09-16 13:03:11 +08:00
k88936 5fb3db662b 合并主线并保留 DirectProject 输入行为
合并主线回合队列、工具流展示与活动回合管理

保留 Lexical 单一输入状态及附件图片 sidecar 投影

收敛 DirectProject user item 校验、历史恢复与图片预算

同步输入链路里程碑和历史异常恢复文档
2026-09-16 11:27:09 +08:00
k88936 87b798322e 收敛输入编辑器单一状态并补齐附件投影
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m6s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 1m49s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 1m56s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m2s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m19s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m46s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Failing after 3m54s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m13s
Project CI / Native shell tests (pull_request) Successful in 7m14s
聊天与快速编辑输入统一由 Lexical 编辑器持有唯一当前状态

App 通过输入句柄读取草稿,移除跨视图 EditorState 和字符串镜像

DirectProject 将附件与图片 sidecar 投影纳入 prompt,支持附件-only 输入

迁移润色测试并更新 DirectProject 里程碑验收记录
2026-09-15 22:13:25 +08:00
k88936 ec286b3480 抽取通用富文本输入组件
新增可复用的 RichTextInput Lexical 输入外壳。

让首页输入与资源引用输入共享编辑器、回车提交和禁用状态。

保留首页附件与图片粘贴节点能力,并移除 DirectProject 草稿 legacy fallback。

统一聊天草稿 canonical content 状态并更新资源引用测试。
2026-09-15 19:42:37 +08:00
k88936 35733f0e33 过滤 canonical 草稿中的空白输入 part
在统一 user item 转换入口移除纯空白 input_text,覆盖恢复草稿和所有提交路径。
2026-09-15 17:54:00 +08:00
k88936 3c7b02b9f8 过滤 DirectProject 编辑器的空白内容 part
跳过纯空白文本节点并合并相邻文本,避免引用后的分隔空格和换行触发 Rust 空输入校验。
2026-09-15 17:53:33 +08:00
k88936 f2030a616f 复用 DirectProject 用户输入校验读取的 manifest
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 1m44s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m19s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m19s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m17s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m17s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m26s
Project CI / Frontend tests (pull_request) Failing after 3m5s
Project CI / AI game creator shell web tests (pull_request) Failing after 2m51s
Project CI / Native shell tests (pull_request) Successful in 6m21s
让校验返回已读取的 manifest,wire 投影复用同一份数据,避免一次输入重复读取项目清单。
2026-09-15 17:33:49 +08:00
k88936 b3b5d77990 按运行模式创建 DirectProject 回合输入
仅在 DirectCodex 产品运行时生成 direct clientTurnId 和 user item,避免旧 Supervisor 与策划链路复用错误身份。
2026-09-15 17:32:46 +08:00
k88936 1da106af7d 避免重复校验 DirectProject 用户输入
复用 canonical 到 prompt 投影函数中的校验,删除不可达空 prompt 分支。
2026-09-15 17:29:08 +08:00
k88936 cd2edd6966 移除未使用的 DirectProject 输入信封类型
删除未被调用且未生成到前端绑定的 DirectCodexUserMessageEnvelope,收窄内部 API。
2026-09-15 17:27:42 +08:00
k88936 22e830ff93 避免 DirectProject 回合输入的无必要克隆
在互斥的观察器分支中移动 optional user item,避免深层 JSON 克隆。
2026-09-15 17:26:34 +08:00
k88936 c266ae7b50 清理策划模式提交后的聊天草稿
规划 V2 提交时同步清空资源引用和 canonical content,避免切回 DirectProject 后复用旧输入。
2026-09-15 17:25:15 +08:00
k88936 a543b75cf7 拆开聊天草稿内容分支判断
将资源草稿转换的三种回退路径改为显式分支,保持现有优先级并提升可读性。
2026-09-15 17:24:05 +08:00
k88936 f213987f9a 复用资源引用的统一内容映射
让 Lexical 草稿读取复用 chatReferenceToContentPart,避免引用字段映射分叉。
2026-09-15 17:22:55 +08:00
k88936 2c623bb577 移除 DirectProject 回合输入的重复赋值
删除已在对象初始化时设置的 userItem 重复赋值。
2026-09-15 17:19:17 +08:00
k88936 3353906e6f 修正 Response item content 类型投影
区分 turn/start 的 text 与历史 message 的 input_text

将 AGC 私有引用转换为标准 input_text

新增回归测试避免未知 variant text
2026-09-15 16:57:04 +08:00
k88936 17716347e2 收紧历史测试与注释口径
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m17s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m7s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m7s
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m19s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m17s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m26s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m43s
Project CI / Frontend tests (pull_request) Failing after 4m6s
Project CI / Native shell tests (pull_request) Successful in 7m29s
移除 legacy 投影描述

将历史测试统一为 Response item 合同
2026-09-15 16:16:03 +08:00
k88936 9ad66a67a3 接入前端 canonical 用户 Response item
按 Lexical 顺序构造 message.content

DirectProject payload 改为发送 userItem

策略重试保留同一 canonical item
2026-09-15 16:12:56 +08:00
k88936 ba3aa3ccdb 更新用户 Response item 里程碑进度
标记 canonical、wire 与失败关闭验收项

同步实施计划为进行中状态
2026-09-15 16:09:28 +08:00
k88936 2b38eaafba 补充前端用户 Response item 契约测试
覆盖 Lexical 文本与引用交错顺序

验证资源引用只发送稳定 resourceId
2026-09-15 16:08:34 +08:00
k88936 7d5b9071e7 删除旧 DirectProject 引用协议
移除 DirectCodexTurnReference 平行 DTO 与渲染路径

将引用数量上限收回 canonical user item 校验模块
2026-09-15 16:01:45 +08:00
k88936 9e83f1d88c 补齐 Response item 严格合同测试
拒绝资源引用额外身份字段与未知 content part

补充历史 item wire 投影边界测试

同步 DirectProject 历史文档为无 fallback 合同
2026-09-15 15:55:15 +08:00
k88936 7e35d7c344 严格使用 DirectProject Response item 历史
移除 game-creator-conversation.v1 读取 fallback

历史注入前逐项投影 canonical user item 为 Codex message

无效 item 在持久化前失败关闭
2026-09-15 15:42:27 +08:00
k88936 5ec40c8b83 抽离 DirectProject 身份解析模块
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m16s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m20s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m4s
Project CI / Backend tests (pull_request) Failing after 18s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 1m54s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m22s
Project CI / Repository checks (pull_request) Failing after 9s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m20s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m36s
Project CI / Frontend tests (pull_request) Successful in 3m45s
Project CI / Native shell tests (pull_request) Successful in 5m27s
将项目 canonical identity 与 digest 放入独立文件

保持 Codex app-server 线程池调用接口不变
2026-09-15 15:22:39 +08:00
k88936 948a80fc49 重构 DirectProject Agent 深模块
将 direct_runtime 与 codex_app_server 改为目录模块

拆分 user item 的模型、校验与 wire 投影文件

抽离 DirectProject 历史注入载荷构造 seam
2026-09-15 15:19:17 +08:00
k88936 dfd6fadedf 新增 DirectProject 用户 Response item 契约
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m35s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m34s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m36s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m31s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m46s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m32s
Project CI / Frontend tests (pull_request) Successful in 4m0s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m34s
Project CI / Native shell tests (pull_request) Successful in 6m35s
新增 Rust canonical user item 与 Codex wire 转换模块

增加 ts-rs 绑定目录及生成文件检查边界

更新聊天素材引用规范与里程碑实施计划
2026-09-15 14:45:58 +08:00
1121 changed files with 90225 additions and 88805 deletions
+6
View File
@@ -239,6 +239,12 @@ GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR="false"
# Windows/macOS 是系统维度,不填写 dev-win/dev-mac。 # Windows/macOS 是系统维度,不填写 dev-win/dev-mac。
GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL="dev" GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL="dev"
# 客户端埋点接收绑定的公开 origin,由 API Server 运行时读取;修改后重启服务。
# 必须与客户端登录地址一致,不带 /api、路径或尾部斜杠;未配置/非法时上传接口返回 503。
# 本地端口按实际启动结果填写(端口漂移后需同步),localhost 与 127.0.0.1 不可混用。
# dev 使用 https://dev.genarrative.worldrelease 使用 https://www.genarrative.world。
GENARRATIVE_AGC_ANALYTICS_ORIGIN="http://127.0.0.1:8082"
# Optional: official VikingDB credentials for regenerating build-tag similarities # Optional: official VikingDB credentials for regenerating build-tag similarities
# with the Python embedding script. The script auto-loads `.env.local` and uses # with the Python embedding script. The script auto-loads `.env.local` and uses
# the fixed `bge-large-zh` embedding model. # the fixed `bge-large-zh` embedding model.
+2
View File
@@ -213,6 +213,8 @@ module.exports = {
'!src/services/clipboard.test.ts', '!src/services/clipboard.test.ts',
'!src/services/frontendRuntimeConfigService.ts', '!src/services/frontendRuntimeConfigService.ts',
'!src/services/frontendRuntimeConfigService.test.ts', '!src/services/frontendRuntimeConfigService.test.ts',
'!src/services/gameDistributionClient.ts',
'!src/services/gameDistributionClient.test.ts',
'!src/services/sseStream.ts', '!src/services/sseStream.ts',
'!src/services/sseStream.test.ts', '!src/services/sseStream.test.ts',
'src/AdventurePanel.tsx', 'src/AdventurePanel.tsx',
+92 -1
View File
@@ -82,12 +82,29 @@ jobs:
sleep $((attempt * 2)) sleep $((attempt * 2))
done done
- name: Prepare isolated Rust compilation cache
shell: bash
run: |
set -euo pipefail
node --test scripts/ci-rust-cache.test.mjs
bash scripts/ci-rust-cache.sh prepare
- name: Run AI game creator shell Rust shard 1/4 - name: Run AI game creator shell Rust shard 1/4
run: npm run check:native-shells:agc-rust-shard-1 run: npm run check:native-shells:agc-rust-shard-1
- name: Run AI game creator shell Rust shard 2/4 - name: Run AI game creator shell Rust shard 2/4
run: npm run check:native-shells:agc-rust-shard-2 run: npm run check:native-shells:agc-rust-shard-2
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
- name: Publish master Rust cache artifact
continue-on-error: true
if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/master' }}
env:
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: python3 scripts/export-gitea-rust-cache.py
ai-game-creator-shell-rust-lane-2: ai-game-creator-shell-rust-lane-2:
name: AI game creator shell Rust lane 2/2 name: AI game creator shell Rust lane 2/2
runs-on: genarrative-ci runs-on: genarrative-ci
@@ -118,12 +135,25 @@ jobs:
sleep $((attempt * 2)) sleep $((attempt * 2))
done done
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run AI game creator shell Rust shard 3/4 - name: Run AI game creator shell Rust shard 3/4
run: npm run check:native-shells:agc-rust-shard-3 run: npm run check:native-shells:agc-rust-shard-3
- name: Run AI game creator shell Rust shard 4/4 - name: Run AI game creator shell Rust shard 4/4
run: npm run check:native-shells:agc-rust-shard-4 run: npm run check:native-shells:agc-rust-shard-4
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
- name: Publish master Rust cache artifact
continue-on-error: true
if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/master' }}
env:
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: python3 scripts/export-gitea-rust-cache.py
# agent-run smoke 会 spawn `cargo run`(走壳自己的 manifest),同样不装 npm 依赖, # agent-run smoke 会 spawn `cargo run`(走壳自己的 manifest),同样不装 npm 依赖,
# 单独一个 job,免得把已经压到 4 分钟级的片 job 拖长。 # 单独一个 job,免得把已经压到 4 分钟级的片 job 拖长。
ai-game-creator-shell-rust-smoke: ai-game-creator-shell-rust-smoke:
@@ -156,9 +186,22 @@ jobs:
sleep $((attempt * 2)) sleep $((attempt * 2))
done done
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run AI game creator shell agent-run smoke - name: Run AI game creator shell agent-run smoke
run: npm run check:native-shells:agc-rust-smoke run: npm run check:native-shells:agc-rust-smoke
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
- name: Publish master Rust cache artifact
continue-on-error: true
if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/master' }}
env:
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: python3 scripts/export-gitea-rust-cache.py
# AGC 壳依赖的共享 / 平台和编辑器插件 crate 各自预热独立 manifest,再运行对应测试。 # AGC 壳依赖的共享 / 平台和编辑器插件 crate 各自预热独立 manifest,再运行对应测试。
ai-game-creator-shell-rust-crates: ai-game-creator-shell-rust-crates:
name: AI game creator shell Rust crates name: AI game creator shell Rust crates
@@ -257,9 +300,22 @@ jobs:
sleep $((attempt * 2)) sleep $((attempt * 2))
done done
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run AI game creator shell shared crate gates - name: Run AI game creator shell shared crate gates
run: npm run check:native-shells:agc-rust-crates run: npm run check:native-shells:agc-rust-crates
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
- name: Publish master Rust cache artifact
continue-on-error: true
if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/master' }}
env:
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: python3 scripts/export-gitea-rust-cache.py
backend-tests: backend-tests:
name: Backend tests name: Backend tests
runs-on: genarrative-ci runs-on: genarrative-ci
@@ -330,6 +386,9 @@ jobs:
- name: Check server-rs boundaries - name: Check server-rs boundaries
run: npm run check:server-rs-ddd run: npm run check:server-rs-ddd
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run server-rs workspace tests - name: Run server-rs workspace tests
run: cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml run: cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml
@@ -342,6 +401,16 @@ jobs:
- name: Check SpacetimeDB module - name: Check SpacetimeDB module
run: cargo check --locked -p spacetime-module --manifest-path server-rs/Cargo.toml run: cargo check --locked -p spacetime-module --manifest-path server-rs/Cargo.toml
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
- name: Publish master Rust cache artifact
continue-on-error: true
if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/master' }}
env:
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: python3 scripts/export-gitea-rust-cache.py
# 客户端的壳级与契约门禁:静态契约断言、H5 / 微信 / 移动 / 桌面壳运行时门禁, # 客户端的壳级与契约门禁:静态契约断言、H5 / 微信 / 移动 / 桌面壳运行时门禁,
# 以及依赖发布产物的构建 smoke。 # 以及依赖发布产物的构建 smoke。
native-shell-tests: native-shell-tests:
@@ -384,15 +453,32 @@ jobs:
- name: Run native shell contract gates - name: Run native shell contract gates
run: npm run check:native-shells:contract run: npm run check:native-shells:contract
- name: Prepare isolated Rust compilation cache
run: bash scripts/ci-rust-cache.sh prepare
- name: Run native shell gates - name: Run native shell gates
run: npm run check:native-shells:shells run: npm run check:native-shells:shells
- name: Run native shell release build smoke - name: Run native shell release build smoke
# 发布构建有独立 profile/features 和资源 staging,不消费测试对象快照。
env:
RUSTC_WRAPPER: ''
CARGO_BUILD_RUSTC_WRAPPER: ''
run: npm run check:native-shells:release run: npm run check:native-shells:release
- name: Ensure native lockfiles are unchanged - name: Ensure native lockfiles are unchanged
run: git diff --exit-code -- apps/desktop-shell/src-tauri/Cargo.lock apps/ai-game-creator-shell/src-tauri/Cargo.lock run: git diff --exit-code -- apps/desktop-shell/src-tauri/Cargo.lock apps/ai-game-creator-shell/src-tauri/Cargo.lock
- name: Report isolated Rust compilation cache
if: always()
run: bash scripts/ci-rust-cache.sh report
- name: Publish master Rust cache artifact
continue-on-error: true
if: ${{ !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/master' }}
env:
GENARRATIVE_GITEA_TOKEN: ${{ github.token }}
run: python3 scripts/export-gitea-rust-cache.py
frontend-tests: frontend-tests:
name: Frontend tests name: Frontend tests
runs-on: genarrative-ci runs-on: genarrative-ci
@@ -410,7 +496,7 @@ jobs:
run: bash scripts/ci-npm-ci-with-retry.sh run: bash scripts/ci-npm-ci-with-retry.sh
- name: Run frontend and script tests - name: Run frontend and script tests
run: npm run test run: npm run test:ci:frontend
- name: Run BgFilter worker smoke harness tests - name: Run BgFilter worker smoke harness tests
run: npm run bgfilter-worker:smoke-test run: npm run bgfilter-worker:smoke-test
@@ -474,6 +560,11 @@ jobs:
- name: Install npm dependencies - name: Install npm dependencies
run: bash scripts/ci-npm-ci-with-retry.sh run: bash scripts/ci-npm-ci-with-retry.sh
- name: Validate CI cache maintenance behavior
run: |
python3 -m unittest discover -s scripts -p 'test_gitea_cache_*.py'
node --test scripts/export-ci-npm-download-cache.test.mjs
- name: Run repository checks - name: Run repository checks
run: npm run check:repository-ci run: npm run check:repository-ci
+40
View File
@@ -174,6 +174,14 @@ _Avoid_: mock 先行堆积、前后端各自发散、先做排行榜 UI
## 项目开发对话(DirectProject ## 项目开发对话(DirectProject
**DirectProject 专属聊天模块**:
AGC 普通项目聊天的独立容器,拥有 DirectProject 的聊天状态、运行态订阅、历史读取、发送队列、附件和中止交互,并把聊天投影交给专属表现层渲染;它不承接 Supervisor、Design Agent 或 Planning V2 的运行态。
_Avoid_: 把 DirectProject 作为项目总控聊天的一个布尔分支、把四种 Agent 会话抽象成同一事实源
**项目工作台布局**:
承载本地项目的资源工作区、项目级工具和独立聊天产品路径的外层界面;布局拥有跨面板的账户/钱包入口,聊天模块只负责项目对话,不嵌套账户展示。
_Avoid_: 把钱包入口塞进聊天设置、让聊天组件拥有工作台级账户状态
**项目对话历史**: **项目对话历史**:
AGC 本地项目内 Codex 原始对话条目的持久集合,是聊天展示、工具卡片和线程恢复注入的唯一持久事实源。 AGC 本地项目内 Codex 原始对话条目的持久集合,是聊天展示、工具卡片和线程恢复注入的唯一持久事实源。
_Avoid_: 会话缓存、展示态历史、按 UI 需要另存的对话副本 _Avoid_: 会话缓存、展示态历史、按 UI 需要另存的对话副本
@@ -186,6 +194,34 @@ _Avoid_: 进度通知、快照轮询、第二套历史
把项目对话历史条目与运行态事件转换成消息气泡和工具卡片的读取期转换;不持久化,也不构成事实源。 把项目对话历史条目与运行态事件转换成消息气泡和工具卡片的读取期转换;不持久化,也不构成事实源。
_Avoid_: 投影缓存文件、已脱敏卡片库、第二套 reducer _Avoid_: 投影缓存文件、已脱敏卡片库、第二套 reducer
**引用候选**:
输入区可以命中的对象集合(`@` 素材、`$` Skill),由宿主按种类注入;输入区不判断候选属于哪一类。
_Avoid_: 输入区自己读项目清单或应用目录、把候选取值写死在组件里
**引用 provider**:
一种引用种类向输入区提供的全部能力:触发符、候选、身份解析与正文文本形态;每种引用各一份,宿主按需选择性注入。
_Avoid_: 一个总装对象决定所有种类、输入区按种类分叉、provider 之间互相知道对方
**静默 provider**:
不提供候选、只负责已有引用身份与文本形态的 provider;附件与运行画面区域属于这一类,只能由外部插入或草稿回填进入正文。
_Avoid_: 给附件或运行画面区域造候选、为它们保留输入区内的专门分支
**引用文本语法**:
引用在正文文本里的形态(`@显示名` / `$名称` / `@附件名`)及其反解析;出站与解析必须同一口径,token 前后各留一个空白。
_Avoid_: 出站与解析各写一套、在空白边界之外再补兼容别名、让解析依赖具体种类的字段
**引用输入区**:
只负责编辑与渲染引用的共享输入组件;候选、身份解析与文本语法都来自注入的 provider,它不持有项目清单、不访问后端。
_Avoid_: 输入区自己拉 Skill 目录、把选择器面板塞在输入区内部
**引用选择器**:
宿主渲染的独立面板,自己拿数据与筛选状态,确认后把选中的引用交给输入区的插入缝。
_Avoid_: 输入区自带面板、每个宿主各画一个、绕开插入缝另开第二条通道
**附件芯片**:
聊天附件在正文里的唯一表示;附件导入成功即以芯片进入正文,正文之外不存在第二份附件状态。
_Avoid_: 待发送附件列表与正文芯片并存、提交时再拼一遍附件
## Relationships ## Relationships
- 一个 **汪汪声浪大作战** 单局包含多个 **有效声浪触发** - 一个 **汪汪声浪大作战** 单局包含多个 **有效声浪触发**
@@ -201,6 +237,10 @@ _Avoid_: 投影缓存文件、已脱敏卡片库、第二套 reducer
- **个人历史成绩** 由最近记录列表和个人最佳摘要组成,只允许本人查看;排行榜只公开入榜胜利成绩。 - **个人历史成绩** 由最近记录列表和个人最佳摘要组成,只允许本人查看;排行榜只公开入榜胜利成绩。
- **正式作品入口闭环** 必须覆盖创作入口、作品详情 CTA、广场/作品卡片、我的作品/个人作品架、稳定作品 ID runtime 路由和 `work_play_start` 埋点。 - **正式作品入口闭环** 必须覆盖创作入口、作品详情 CTA、广场/作品卡片、我的作品/个人作品架、稳定作品 ID runtime 路由和 `work_play_start` 埋点。
- **Phase 2 实施顺序** 固定为:契约与领域规则 → SpacetimeDB 表/reducer 与 api-server BFF → 最小前端纵切 → 投影与列表体验 → 收口验证。 - **Phase 2 实施顺序** 固定为:契约与领域规则 → SpacetimeDB 表/reducer 与 api-server BFF → 最小前端纵切 → 投影与列表体验 → 收口验证。
- **引用输入区** 由宿主注入的若干 **引用 provider** 组成;**引用候选** 与 **引用文本语法** 都来自 provider,输入区不判断引用种类。
- **引用选择器** 不属于 **引用输入区**:它自己拿数据,确认后只通过输入区的插入缝交付引用。
- 只有带触发符的 **引用 provider** 会产生候选;**静默 provider** 没有触发符,只能由外部插入或草稿回填进入正文。
- **附件芯片** 是本轮附件的唯一事实源;附件导入失败时不产生芯片。
## Example dialogue ## Example dialogue
@@ -3,12 +3,19 @@ import { afterEach, expect, test, vi } from 'vitest';
import { import {
createAdminAccount, createAdminAccount,
executeAdminRechargeRefund, executeAdminRechargeRefund,
getAdminAgcTemplates,
getAdminFeatureGateConfig, getAdminFeatureGateConfig,
getAdminUserDetail, getAdminUserDetail,
importAdminAgcTemplates,
listAdminAgcTrackingEvents,
listAdminGameDistributionReviews,
listAdminRechargeOrders, listAdminRechargeOrders,
reconcileAdminUserConsumption, reconcileAdminUserConsumption,
resolveAdminRechargeRefundManualReview, resolveAdminRechargeRefundManualReview,
reviewAdminGameDistributionVersion,
suspendAdminGameDistributionGame,
updateAdminAccount, updateAdminAccount,
updateAdminAgcTemplate,
uploadAdminEditorShowcaseCampaignImage, uploadAdminEditorShowcaseCampaignImage,
upsertAdminFeatureGateConfig, upsertAdminFeatureGateConfig,
upsertProfileWalletConfig, upsertProfileWalletConfig,
@@ -18,6 +25,117 @@ afterEach(() => {
vi.unstubAllGlobals(); vi.unstubAllGlobals();
}); });
test('客户端埋点查询传递筛选和游标并复用后台认证', async () => {
const payload = { entries: [], nextCursor: null };
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ ok: true, data: payload }), {
status: 200,
}),
);
vi.stubGlobal('fetch', fetchMock);
expect(
await listAdminAgcTrackingEvents('token', {
userId: 'user+1',
projectId: 'project-1',
cursor: 'page/2',
limit: 50,
}),
).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
'/admin/api/agc/tracking-events?userId=user%2B1&projectId=project-1&cursor=page%2F2&limit=50',
expect.objectContaining({
headers: expect.objectContaining({ Authorization: 'Bearer token' }),
}),
);
});
test('模板管理读取和更新复用认证封装,提交 revision 和封面但不提交 ZIP 或版本', async () => {
const library = { revision: 'revision-new', writable: true, templates: [] };
const fetchMock = vi.fn().mockImplementation(
async () =>
new Response(JSON.stringify({ ok: true, data: library }), {
status: 200,
}),
);
vi.stubGlobal('fetch', fetchMock);
const controller = new AbortController();
expect(await getAdminAgcTemplates('admin-token', controller.signal)).toEqual(
library,
);
const update = {
expectedRevision: 'revision-old',
title: '空白模板',
summary: '简介',
tags: ['2D'],
enabled: true,
cover: { contentType: 'image/png', dataBase64: 'aW1hZ2U=' },
};
expect(
await updateAdminAgcTemplate('admin-token', 'template/1', update),
).toEqual(library);
expect(fetchMock.mock.calls[0]).toEqual([
'/admin/api/agc-templates',
expect.objectContaining({
method: 'GET',
signal: controller.signal,
headers: expect.objectContaining({ Authorization: 'Bearer admin-token' }),
}),
]);
expect(fetchMock.mock.calls[1]).toEqual([
'/admin/api/agc-templates/template%2F1',
expect.objectContaining({
method: 'PUT',
headers: expect.objectContaining({
Authorization: 'Bearer admin-token',
'Content-Type': 'application/json',
}),
body: JSON.stringify(update),
}),
]);
});
test('模板批量导入走 multipart,不预设 JSON Content-Type', async () => {
const imported = {
revision: 'rev-2',
writable: true,
templates: [],
imported: [
{
id: 'alpha',
templateVersion: '0.1.0',
zipSizeBytes: 4,
zipSha256: 'a'.repeat(64),
reusedObjects: false,
},
],
};
const fetchMock = vi.fn().mockImplementation(
async () =>
new Response(JSON.stringify({ ok: true, data: imported }), {
status: 200,
}),
);
vi.stubGlobal('fetch', fetchMock);
const form = new FormData();
form.append(
'manifest',
JSON.stringify({ expectedRevision: 'rev-1', templates: [] }),
);
form.append(
'zip_0',
new File([new Uint8Array([1])], 'alpha.zip', { type: 'application/zip' }),
);
expect(await importAdminAgcTemplates('admin-token', form)).toEqual(imported);
const [url, init] = fetchMock.mock.calls[0]!;
expect(url).toBe('/admin/api/agc-templates/import');
expect(init.method).toBe('POST');
expect(init.body).toBe(form);
expect(init.headers).not.toHaveProperty('Content-Type');
expect(init.headers.Authorization).toBe('Bearer admin-token');
});
test('后台账号创建和更新同时携带 Tab 与独立操作权限', async () => { test('后台账号创建和更新同时携带 Tab 与独立操作权限', async () => {
const fetchMock = vi.fn().mockImplementation(() => const fetchMock = vi.fn().mockImplementation(() =>
Promise.resolve( Promise.resolve(
@@ -364,3 +482,134 @@ test('退款人工复核使用独立 resolve 管理员路由', async () => {
}), }),
); );
}); });
test('游戏审核列表与审核动作使用约定的 URL、方法和幂等键', async () => {
const fetchMock = vi.fn().mockImplementation(() =>
Promise.resolve(
new Response(JSON.stringify({ entries: [], nextCursor: null }), {
status: 200,
}),
),
);
vi.stubGlobal('fetch', fetchMock);
await listAdminGameDistributionReviews('admin-token');
await reviewAdminGameDistributionVersion(
'admin-token',
'gamever/1',
'game-review-key-1',
{
decision: 'approve',
expectedPublicationRevision: 3,
entryUrl: 'https://games.example.test/releases/game_1/index.html',
},
);
expect(fetchMock.mock.calls[0]?.[0]).toBe(
'/admin/api/game-distribution/reviews?limit=48',
);
expect(fetchMock.mock.calls[1]?.[0]).toBe(
'/admin/api/game-distribution/versions/gamever%2F1/review',
);
expect(fetchMock.mock.calls[1]?.[1]).toEqual(
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
Authorization: 'Bearer admin-token',
'Idempotency-Key': 'game-review-key-1',
}),
body: JSON.stringify({
decision: 'approve',
expectedPublicationRevision: 3,
entryUrl: 'https://games.example.test/releases/game_1/index.html',
}),
}),
);
});
test('安全下架请求携带公开修订号、原因与幂等键', async () => {
const fetchMock = vi.fn().mockImplementation(() =>
Promise.resolve(
new Response(JSON.stringify({ game: {}, replayed: false }), {
status: 200,
}),
),
);
vi.stubGlobal('fetch', fetchMock);
await suspendAdminGameDistributionGame(
'admin-token',
'game/1',
'game-suspend-key-1',
{ expectedPublicationRevision: 7, reason: '版权投诉' },
);
expect(fetchMock.mock.calls[0]?.[0]).toBe(
'/admin/api/game-distribution/games/game%2F1/suspend',
);
expect(fetchMock.mock.calls[0]?.[1]).toEqual(
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
Authorization: 'Bearer admin-token',
'Idempotency-Key': 'game-suspend-key-1',
}),
body: JSON.stringify({
expectedPublicationRevision: 7,
reason: '版权投诉',
}),
}),
);
expect(() =>
suspendAdminGameDistributionGame('admin-token', ' ', 'key', {
expectedPublicationRevision: 1,
}),
).toThrow('缺少游戏 ID');
expect(() =>
suspendAdminGameDistributionGame('admin-token', 'game-1', ' ', {
expectedPublicationRevision: 1,
}),
).toThrow('下架幂等键必须是 1 到 128 个字符');
expect(fetchMock).toHaveBeenCalledTimes(1);
});
test('游戏审核拒绝请求携带理由,空幂等键在本地失败关闭', async () => {
const fetchMock = vi.fn().mockImplementation(() =>
Promise.resolve(
new Response(JSON.stringify({ version: {}, replayed: false }), {
status: 200,
}),
),
);
vi.stubGlobal('fetch', fetchMock);
await reviewAdminGameDistributionVersion(
'admin-token',
'version-1',
'game-review-key-2',
{
decision: 'reject',
expectedPublicationRevision: 0,
reviewReason: '运行时报错',
},
);
expect(fetchMock.mock.calls[0]?.[1]).toEqual(
expect.objectContaining({
body: JSON.stringify({
decision: 'reject',
expectedPublicationRevision: 0,
reviewReason: '运行时报错',
}),
}),
);
expect(() =>
reviewAdminGameDistributionVersion('admin-token', 'version-1', ' ', {
decision: 'reject',
expectedPublicationRevision: 0,
reviewReason: 'x',
}),
).toThrow('审核幂等键必须是 1 到 128 个字符');
expect(fetchMock).toHaveBeenCalledTimes(1);
});
+153 -1
View File
@@ -1,5 +1,8 @@
import type { import type {
AdminAccountListResponse, AdminAccountListResponse,
AdminAgcTemplateLibraryResponse,
AdminAgcTrackingEventListResponse,
AdminAgcTrackingEventQuery,
AdminConfirmEditorShowcaseCampaignImageUploadRequest, AdminConfirmEditorShowcaseCampaignImageUploadRequest,
AdminCreateAccountRequest, AdminCreateAccountRequest,
AdminCreateAccountResponse, AdminCreateAccountResponse,
@@ -29,9 +32,14 @@ import type {
AdminExternalApiKeyListQuery, AdminExternalApiKeyListQuery,
AdminExternalApiKeyListResponse, AdminExternalApiKeyListResponse,
AdminFeatureGateConfigResponse, AdminFeatureGateConfigResponse,
AdminGameDistributionReviewListResponse,
AdminGameDistributionReviewRequest,
AdminGameDistributionReviewResponse,
AdminImportAgcTemplatesResponse,
AdminLoginResponse, AdminLoginResponse,
AdminMeResponse, AdminMeResponse,
AdminOverviewResponse, AdminOverviewResponse,
AdminProjectSnapshotChannelsResponse,
AdminProjectSnapshotListQuery, AdminProjectSnapshotListQuery,
AdminProjectSnapshotListResponse, AdminProjectSnapshotListResponse,
AdminRechargeOrderListQuery, AdminRechargeOrderListQuery,
@@ -47,6 +55,7 @@ import type {
AdminTrackingEventListResponse, AdminTrackingEventListResponse,
AdminUpdateAccountRequest, AdminUpdateAccountRequest,
AdminUpdateAccountResponse, AdminUpdateAccountResponse,
AdminUpdateAgcTemplateRequest,
AdminUploadedEditorShowcaseCampaignImage, AdminUploadedEditorShowcaseCampaignImage,
AdminUpsertEditorShowcaseCampaignRequest, AdminUpsertEditorShowcaseCampaignRequest,
AdminUpsertFeatureGateConfigRequest, AdminUpsertFeatureGateConfigRequest,
@@ -85,6 +94,8 @@ interface AdminRequestOptions {
method?: string; method?: string;
token?: string; token?: string;
body?: unknown; body?: unknown;
/** multipart 表单:交给浏览器自己带 boundary,不能预设 Content-Type。 */
formData?: FormData;
headers?: Record<string, string>; headers?: Record<string, string>;
signal?: AbortSignal; signal?: AbortSignal;
} }
@@ -172,6 +183,8 @@ export async function request<T>(
if (typeof options.body !== 'undefined') { if (typeof options.body !== 'undefined') {
headers['Content-Type'] = 'application/json'; headers['Content-Type'] = 'application/json';
init.body = JSON.stringify(options.body); init.body = JSON.stringify(options.body);
} else if (options.formData) {
init.body = options.formData;
} }
const response = await fetch(buildRequestUrl(path), init); const response = await fetch(buildRequestUrl(path), init);
@@ -207,6 +220,7 @@ export function listAdminProjectSnapshots(
) { ) {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (query.cursor) params.set('cursor', query.cursor); if (query.cursor) params.set('cursor', query.cursor);
if (query.channel) params.set('channel', query.channel);
params.set('limit', String(query.limit ?? 20)); params.set('limit', String(query.limit ?? 20));
return request<AdminProjectSnapshotListResponse>( return request<AdminProjectSnapshotListResponse>(
`/admin/api/project-snapshots?${params.toString()}`, `/admin/api/project-snapshots?${params.toString()}`,
@@ -214,13 +228,27 @@ export function listAdminProjectSnapshots(
); );
} }
export function getAdminProjectSnapshotChannels(
token: string,
signal?: AbortSignal,
) {
return request<AdminProjectSnapshotChannelsResponse>(
'/admin/api/project-snapshots/channels',
{ token, signal },
);
}
export async function downloadAdminProjectSnapshot( export async function downloadAdminProjectSnapshot(
token: string, token: string,
channel: string,
userId: string, userId: string,
projectId: string, projectId: string,
signal?: AbortSignal, signal?: AbortSignal,
) { ) {
const path = `/admin/api/project-snapshots/${encodeURIComponent(userId)}/${encodeURIComponent(projectId)}/download`; const params = new URLSearchParams();
if (channel) params.set('channel', channel);
const query = params.toString();
const path = `/admin/api/project-snapshots/${encodeURIComponent(userId)}/${encodeURIComponent(projectId)}/download${query ? `?${query}` : ''}`;
const response = await fetch(buildRequestUrl(path), { const response = await fetch(buildRequestUrl(path), {
headers: { headers: {
Authorization: `Bearer ${token.trim()}`, Authorization: `Bearer ${token.trim()}`,
@@ -383,6 +411,31 @@ export function listAdminTrackingEventKeys(token: string) {
); );
} }
export function listAdminAgcTrackingEvents(
token: string,
query: AdminAgcTrackingEventQuery = {},
) {
return request<AdminAgcTrackingEventListResponse>(
`/admin/api/agc/tracking-events${buildQueryString((params) => {
for (const key of [
'userId',
'projectId',
'creativeTaskId',
'agentRunId',
'eventName',
'clientVersion',
'startTime',
'endTime',
'cursor',
] as const) {
appendQueryParam(params, key, query[key]);
}
appendNumericQueryParam(params, 'limit', query.limit);
})}`,
{ token },
);
}
export function listAdminErrorReports( export function listAdminErrorReports(
token: string, token: string,
query: { query: {
@@ -1176,3 +1229,102 @@ export function saveAgcModelCatalog(
{ token, method: 'PUT', body }, { token, method: 'PUT', body },
); );
} }
export function listAdminGameDistributionReviews(token: string, limit = 48) {
const normalizedLimit = Number.isFinite(limit)
? Math.min(Math.max(Math.trunc(limit), 1), 48)
: 48;
return request<AdminGameDistributionReviewListResponse>(
`/admin/api/game-distribution/reviews?limit=${normalizedLimit}`,
{ token },
);
}
/**
* 审核游戏发行版本。幂等键由调用方生成并在同一次提交内复用,避免重复点击产生
* 两条审核结论。
*/
/**
* 安全下架整个游戏。管理员下架同样要求 CAS 修订号与幂等键,避免并发审核互相覆盖。
*/
export function suspendAdminGameDistributionGame(
token: string,
gameId: string,
idempotencyKey: string,
payload: import('./adminApiTypes').AdminGameDistributionSuspendRequest,
) {
const normalizedGameId = gameId.trim();
const normalizedKey = idempotencyKey.trim();
if (!normalizedGameId) {
throw new Error('缺少游戏 ID');
}
if (!normalizedKey || normalizedKey.length > 128) {
throw new Error('下架幂等键必须是 1 到 128 个字符');
}
return request<
import('./adminApiTypes').AdminGameDistributionSuspendResponse
>(
`/admin/api/game-distribution/games/${encodeURIComponent(normalizedGameId)}/suspend`,
{
method: 'POST',
token,
headers: { 'Idempotency-Key': normalizedKey },
body: payload,
},
);
}
export function reviewAdminGameDistributionVersion(
token: string,
versionId: string,
idempotencyKey: string,
payload: AdminGameDistributionReviewRequest,
) {
const normalizedVersionId = versionId.trim();
const normalizedKey = idempotencyKey.trim();
if (!normalizedVersionId) {
throw new Error('缺少发行版本 ID');
}
if (!normalizedKey || normalizedKey.length > 128) {
throw new Error('审核幂等键必须是 1 到 128 个字符');
}
return request<AdminGameDistributionReviewResponse>(
`/admin/api/game-distribution/versions/${encodeURIComponent(normalizedVersionId)}/review`,
{
method: 'POST',
token,
headers: { 'Idempotency-Key': normalizedKey },
body: payload,
},
);
}
export function getAdminAgcTemplates(token: string, signal?: AbortSignal) {
return request<AdminAgcTemplateLibraryResponse>('/admin/api/agc-templates', {
token,
signal,
});
}
export function updateAdminAgcTemplate(
token: string,
id: string,
body: AdminUpdateAgcTemplateRequest,
) {
return request<AdminAgcTemplateLibraryResponse>(
`/admin/api/agc-templates/${encodeURIComponent(id)}`,
{ token, method: 'PUT', body },
);
}
/** 批量导入模板包:manifest 与 zip_N / cover_N 一起走 multipart,一批一次锁一次提交。 */
export function importAdminAgcTemplates(token: string, formData: FormData) {
return request<AdminImportAgcTemplatesResponse>(
'/admin/api/agc-templates/import',
{
token,
method: 'POST',
formData,
},
);
}
+156
View File
@@ -105,11 +105,15 @@ export interface AdminProjectSnapshotEntry {
fileCount: number; fileCount: number;
totalBytes: number; totalBytes: number;
status: 'ready' | 'partial' | 'unverified'; status: 'ready' | 'partial' | 'unverified';
channel: string;
authorDisplayName?: string | null;
authorPublicUserCode?: string | null;
} }
export interface AdminProjectSnapshotListQuery { export interface AdminProjectSnapshotListQuery {
cursor?: string | null; cursor?: string | null;
limit?: number; limit?: number;
channel?: string | null;
} }
export interface AdminProjectSnapshotListResponse { export interface AdminProjectSnapshotListResponse {
@@ -117,6 +121,11 @@ export interface AdminProjectSnapshotListResponse {
nextCursor: string | null; nextCursor: string | null;
} }
export interface AdminProjectSnapshotChannelsResponse {
defaultChannel: string;
channels: string[];
}
export interface AdminErrorReportEntry { export interface AdminErrorReportEntry {
batchId: string; batchId: string;
eventCount: number; eventCount: number;
@@ -811,6 +820,44 @@ export interface AdminTrackingEventListResponse {
entries: AdminTrackingEventEntryPayload[]; entries: AdminTrackingEventEntryPayload[];
} }
export interface AdminAgcTrackingEventQuery {
userId?: string;
projectId?: string;
creativeTaskId?: string;
agentRunId?: string;
eventName?: string;
clientVersion?: string;
startTime?: string;
endTime?: string;
cursor?: string;
limit?: number;
}
export interface AdminAgcTrackingEventEntry {
eventId: string;
schemaVersion: number;
eventName: string;
eventTime: string;
userId: string;
editorSessionId: string;
projectId: string | null;
creativeTaskId: string | null;
agentRunId: string | null;
agentTurnId: string | null;
status: string | null;
errorCode: string | null;
source: string;
clientVersion: string;
properties: Record<string, unknown>;
batchId: string;
receivedAt: string;
}
export interface AdminAgcTrackingEventListResponse {
entries: AdminAgcTrackingEventEntry[];
nextCursor: string | null;
}
export interface AdminTrackingEventKeyPayload { export interface AdminTrackingEventKeyPayload {
eventKey: string; eventKey: string;
eventTitle: string; eventTitle: string;
@@ -1033,3 +1080,112 @@ export interface AdminAgcModelCatalog {
defaultModelId: string; defaultModelId: string;
models: AdminAgcModel[]; models: AdminAgcModel[];
} }
export interface AdminGameDistributionReviewEntry {
versionId: string;
gameId: string;
versionNumber: number;
packageSha256: string;
packageBytes: number;
status: string;
publicationRevision: number;
reviewReason: string | null;
createdAt: string;
updatedAt: string;
}
export interface AdminGameDistributionReviewListResponse {
entries: AdminGameDistributionReviewEntry[];
nextCursor: string | null;
}
export interface AdminGameDistributionReviewRequest {
decision: 'approve' | 'reject';
expectedPublicationRevision: number;
reviewReason?: string;
entryUrl?: string;
}
export interface AdminGameDistributionReviewResponse {
version: AdminGameDistributionReviewEntry;
replayed: boolean;
}
export interface AdminGameDistributionSuspendRequest {
expectedPublicationRevision: number;
reason?: string;
}
export interface AdminGameDistributionSuspendResponse {
game: {
id: string;
title: string;
status: string;
publicationRevision: number;
};
replayed: boolean;
}
export interface AdminAgcTemplatePayload {
id: string;
title: string;
summary: string;
tags: string[];
runtime: string;
engine: string;
engineVersion: string;
templateVersion: string;
enabled: boolean;
coverUrl: string;
zipSizeBytes: number;
}
export interface AdminAgcTemplateLibraryResponse {
revision: string;
writable: boolean;
templates: AdminAgcTemplatePayload[];
}
export interface AdminUpdateAgcTemplateRequest {
expectedRevision: string;
title: string;
summary: string;
tags: string[];
enabled: boolean;
cover?: {
contentType: string;
dataBase64: string;
};
}
export interface AdminImportAgcTemplateItemPayload {
id: string;
title: string;
summary: string;
tags: string[];
runtime: string;
engine: string;
engineVersion: string;
templateVersion: string;
entry: string;
zipField: string;
coverField: string;
}
export interface AdminImportAgcTemplatesManifest {
expectedRevision: string;
templates: AdminImportAgcTemplateItemPayload[];
}
export interface AdminImportAgcTemplateResult {
id: string;
templateVersion: string;
zipSizeBytes: number;
zipSha256: string;
reusedObjects: boolean;
}
export interface AdminImportAgcTemplatesResponse
extends AdminAgcTemplateLibraryResponse {
imported: AdminImportAgcTemplateResult[];
}
@@ -2,6 +2,7 @@ import { afterEach, expect, test, vi } from 'vitest';
import { import {
downloadAdminProjectSnapshot, downloadAdminProjectSnapshot,
getAdminProjectSnapshotChannels,
listAdminProjectSnapshots, listAdminProjectSnapshots,
} from './adminApiClient'; } from './adminApiClient';
@@ -21,12 +22,12 @@ test('项目列表携带分页与后台授权,解析标准响应', async () =>
expect( expect(
await listAdminProjectSnapshots( await listAdminProjectSnapshots(
'admin-token', 'admin-token',
{ cursor: 'user/a+项目', limit: 20 }, { cursor: 'user/a+项目', limit: 20, channel: 'release' },
controller.signal, controller.signal,
), ),
).toEqual(payload); ).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith( expect(fetchMock).toHaveBeenCalledWith(
'/admin/api/project-snapshots?cursor=user%2Fa%2B%E9%A1%B9%E7%9B%AE&limit=20', '/admin/api/project-snapshots?cursor=user%2Fa%2B%E9%A1%B9%E7%9B%AE&channel=release&limit=20',
expect.objectContaining({ expect.objectContaining({
headers: expect.objectContaining({ Authorization: 'Bearer admin-token' }), headers: expect.objectContaining({ Authorization: 'Bearer admin-token' }),
signal: controller.signal, signal: controller.signal,
@@ -34,6 +35,23 @@ test('项目列表携带分页与后台授权,解析标准响应', async () =>
); );
}); });
test('渠道列表按后台授权读取,解析本部署渠道', async () => {
const payload = { defaultChannel: 'release', channels: ['dev', 'release'] };
const fetchMock = vi
.fn()
.mockResolvedValue(
new Response(JSON.stringify({ ok: true, data: payload })),
);
vi.stubGlobal('fetch', fetchMock);
expect(await getAdminProjectSnapshotChannels('admin-token')).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
'/admin/api/project-snapshots/channels',
expect.objectContaining({
headers: expect.objectContaining({ Authorization: 'Bearer admin-token' }),
}),
);
});
test('ZIP 下载以授权请求读取并优先保留中文附件名', async () => { test('ZIP 下载以授权请求读取并优先保留中文附件名', async () => {
const fetchMock = vi.fn().mockResolvedValue( const fetchMock = vi.fn().mockResolvedValue(
new Response('PK\u0003\u0004', { new Response('PK\u0003\u0004', {
@@ -48,6 +66,7 @@ test('ZIP 下载以授权请求读取并优先保留中文附件名', async () =
const controller = new AbortController(); const controller = new AbortController();
const archive = await downloadAdminProjectSnapshot( const archive = await downloadAdminProjectSnapshot(
'admin-token', 'admin-token',
'release',
'user/a', 'user/a',
'project/b', 'project/b',
controller.signal, controller.signal,
@@ -55,7 +74,7 @@ test('ZIP 下载以授权请求读取并优先保留中文附件名', async () =
expect(archive.filename).toBe('三消-r2.zip'); expect(archive.filename).toBe('三消-r2.zip');
expect(archive.blob.type).toBe('application/zip'); expect(archive.blob.type).toBe('application/zip');
expect(fetchMock).toHaveBeenCalledWith( expect(fetchMock).toHaveBeenCalledWith(
'/admin/api/project-snapshots/user%2Fa/project%2Fb/download', '/admin/api/project-snapshots/user%2Fa/project%2Fb/download?channel=release',
expect.objectContaining({ expect.objectContaining({
headers: expect.objectContaining({ headers: expect.objectContaining({
Authorization: 'Bearer admin-token', Authorization: 'Bearer admin-token',
@@ -81,7 +100,8 @@ test.each([
vi.fn().mockResolvedValue(new Response('PK', { headers })), vi.fn().mockResolvedValue(new Response('PK', { headers })),
); );
expect( expect(
(await downloadAdminProjectSnapshot('token', 'user', 'project')).filename, (await downloadAdminProjectSnapshot('token', 'release', 'user', 'project'))
.filename,
).toBe(expected.replace('工程', 'project')); ).toBe(expected.replace('工程', 'project'));
}); });
@@ -104,7 +124,7 @@ test.each([401, 403, 409, 500])(
), ),
); );
await expect( await expect(
downloadAdminProjectSnapshot('token', 'user', 'project'), downloadAdminProjectSnapshot('token', 'release', 'user', 'project'),
).rejects.toMatchObject({ ).rejects.toMatchObject({
status, status,
code: 'SNAPSHOT_FAILURE', code: 'SNAPSHOT_FAILURE',
@@ -123,6 +143,6 @@ test('200 JSON 或 HTML 不能被保存为成功 ZIP', async () => {
), ),
); );
await expect( await expect(
downloadAdminProjectSnapshot('token', 'user', 'project'), downloadAdminProjectSnapshot('token', 'release', 'user', 'project'),
).rejects.toMatchObject({ code: 'INVALID_PROJECT_ARCHIVE_RESPONSE' }); ).rejects.toMatchObject({ code: 'INVALID_PROJECT_ARCHIVE_RESPONSE' });
}); });
+21
View File
@@ -19,6 +19,8 @@ import {
} from '../auth/adminAuthStore'; } from '../auth/adminAuthStore';
import { AdminAccountsPage } from '../pages/AdminAccountsPage'; import { AdminAccountsPage } from '../pages/AdminAccountsPage';
import { AdminAgcModelsPage } from '../pages/AdminAgcModelsPage'; import { AdminAgcModelsPage } from '../pages/AdminAgcModelsPage';
import { AdminAgcTemplatesPage } from '../pages/AdminAgcTemplatesPage';
import { AdminAgcTrackingPage } from '../pages/AdminAgcTrackingPage';
import { AdminDashboardPage } from '../pages/AdminDashboardPage'; import { AdminDashboardPage } from '../pages/AdminDashboardPage';
import { AdminDatabaseTablesPage } from '../pages/AdminDatabaseTablesPage'; import { AdminDatabaseTablesPage } from '../pages/AdminDatabaseTablesPage';
import { AdminDebugHttpPage } from '../pages/AdminDebugHttpPage'; import { AdminDebugHttpPage } from '../pages/AdminDebugHttpPage';
@@ -26,6 +28,7 @@ import { AdminEditorAssetQueryPage } from '../pages/AdminEditorAssetQueryPage';
import { AdminEditorGenerationPricingPage } from '../pages/AdminEditorGenerationPricingPage'; import { AdminEditorGenerationPricingPage } from '../pages/AdminEditorGenerationPricingPage';
import { AdminEditorShowcaseReviewPage } from '../pages/AdminEditorShowcaseReviewPage'; import { AdminEditorShowcaseReviewPage } from '../pages/AdminEditorShowcaseReviewPage';
import { AdminErrorReportsPage } from '../pages/AdminErrorReportsPage'; import { AdminErrorReportsPage } from '../pages/AdminErrorReportsPage';
import { AdminGameDistributionReviewPage } from '../pages/AdminGameDistributionReviewPage';
import { AdminGrayReleaseConfigPage } from '../pages/AdminGrayReleaseConfigPage'; import { AdminGrayReleaseConfigPage } from '../pages/AdminGrayReleaseConfigPage';
import { AdminInviteCodePage } from '../pages/AdminInviteCodePage'; import { AdminInviteCodePage } from '../pages/AdminInviteCodePage';
import { AdminLoginPage } from '../pages/AdminLoginPage'; import { AdminLoginPage } from '../pages/AdminLoginPage';
@@ -231,6 +234,12 @@ export function AdminApp() {
onUnauthorized={handleUnauthorized} onUnauthorized={handleUnauthorized}
/> />
) : null} ) : null}
{activeRouteId === 'agc-tracking' ? (
<AdminAgcTrackingPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'error-reports' ? ( {activeRouteId === 'error-reports' ? (
<AdminErrorReportsPage <AdminErrorReportsPage
token={token} token={token}
@@ -294,12 +303,24 @@ export function AdminApp() {
{activeRouteId === 'agc-models' ? ( {activeRouteId === 'agc-models' ? (
<AdminAgcModelsPage token={token} onUnauthorized={handleUnauthorized} /> <AdminAgcModelsPage token={token} onUnauthorized={handleUnauthorized} />
) : null} ) : null}
{activeRouteId === 'agc-templates' ? (
<AdminAgcTemplatesPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'editor-showcase' ? ( {activeRouteId === 'editor-showcase' ? (
<AdminEditorShowcaseReviewPage <AdminEditorShowcaseReviewPage
token={token} token={token}
onUnauthorized={handleUnauthorized} onUnauthorized={handleUnauthorized}
/> />
) : null} ) : null}
{activeRouteId === 'game-distribution' ? (
<AdminGameDistributionReviewPage
token={token}
onUnauthorized={handleUnauthorized}
/>
) : null}
{activeRouteId === 'editor-assets' ? ( {activeRouteId === 'editor-assets' ? (
<AdminEditorAssetQueryPage <AdminEditorAssetQueryPage
token={token} token={token}
+4
View File
@@ -5,6 +5,7 @@ import {
Coins, Coins,
Database, Database,
FolderArchive, FolderArchive,
Gamepad2,
GitBranch, GitBranch,
Images, Images,
LayoutDashboard, LayoutDashboard,
@@ -39,6 +40,7 @@ const routeIcons = {
tables: Database, tables: Database,
debug: Bug, debug: Bug,
tracking: Table2, tracking: Table2,
'agc-tracking': Table2,
'error-reports': Bug, 'error-reports': Bug,
'gray-release': GitBranch, 'gray-release': GitBranch,
redeem: TicketPercent, redeem: TicketPercent,
@@ -49,10 +51,12 @@ const routeIcons = {
'recharge-orders': ReceiptText, 'recharge-orders': ReceiptText,
'editor-generation-pricing': Coins, 'editor-generation-pricing': Coins,
'editor-showcase': Star, 'editor-showcase': Star,
'game-distribution': Gamepad2,
'editor-assets': Images, 'editor-assets': Images,
'project-snapshots': FolderArchive, 'project-snapshots': FolderArchive,
accounts: Users, accounts: Users,
'agc-models': ListChecks, 'agc-models': ListChecks,
'agc-templates': Images,
} satisfies Record<AdminRouteId, typeof LayoutDashboard>; } satisfies Record<AdminRouteId, typeof LayoutDashboard>;
export function AdminShell({ export function AdminShell({
@@ -8,6 +8,22 @@ import {
routeHash, routeHash,
} from './adminRoutes'; } from './adminRoutes';
test('客户端埋点路由遵守成员页签权限', () => {
expect(resolveAdminRoute('#agc-tracking')).toBe('agc-tracking');
expect(
getAccessibleAdminRoutes({
accountRole: 'member',
tabPermissions: ['agc-tracking'],
}).map((route) => route.id),
).toEqual(['agc-tracking']);
expect(
getAccessibleAdminRoutes({
accountRole: 'member',
tabPermissions: ['tracking'],
}).some((route) => route.id === 'agc-tracking'),
).toBe(false);
});
test('后台默认进入 Dashboard', () => { test('后台默认进入 Dashboard', () => {
expect(adminRoutes[0]).toEqual({ expect(adminRoutes[0]).toEqual({
id: 'dashboard', id: 'dashboard',
@@ -147,3 +163,51 @@ test('项目工程入口对 owner 与已授权 member 开放且可分配权限',
}), }),
).not.toContainEqual(route); ).not.toContainEqual(route);
}); });
test('后台游戏审核路由可通过导航和 hash 访问', () => {
expect(adminRoutes).toContainEqual({
id: 'game-distribution',
label: '游戏审核',
hash: '#game-distribution',
});
expect(resolveAdminRoute('#game-distribution')).toBe('game-distribution');
expect(routeHash('game-distribution')).toBe('#game-distribution');
});
test('member 可单独获得游戏审核 Tab 权限', () => {
const routes = getAccessibleAdminRoutes({
accountRole: 'member',
tabPermissions: ['game-distribution'],
});
expect(routes.map((route) => route.id)).toEqual(['game-distribution']);
expect(resolveAccessibleAdminRoute('#game-distribution', routes)).toBe(
'game-distribution',
);
});
test('模板管理只对 owner 或具有 agc-templates 权限的 member 可见', () => {
expect(adminRoutes).toContainEqual({
id: 'agc-templates',
label: '模板管理',
hash: '#agc-templates',
});
expect(resolveAdminRoute('#agc-templates')).toBe('agc-templates');
expect(routeHash('agc-templates')).toBe('#agc-templates');
expect(
getAccessibleAdminRoutes({ accountRole: 'owner', tabPermissions: [] }).some(
(route) => route.id === 'agc-templates',
),
).toBe(true);
expect(
getAccessibleAdminRoutes({
accountRole: 'member',
tabPermissions: ['agc-templates'],
}).map((route) => route.id),
).toEqual(['agc-templates']);
expect(
getAccessibleAdminRoutes({
accountRole: 'member',
tabPermissions: ['editor-assets'],
}).some((route) => route.id === 'agc-templates'),
).toBe(false);
});
+6
View File
@@ -5,6 +5,7 @@ export type AdminRouteId =
| 'tables' | 'tables'
| 'debug' | 'debug'
| 'tracking' | 'tracking'
| 'agc-tracking'
| 'error-reports' | 'error-reports'
| 'gray-release' | 'gray-release'
| 'redeem' | 'redeem'
@@ -15,9 +16,11 @@ export type AdminRouteId =
| 'recharge-orders' | 'recharge-orders'
| 'editor-generation-pricing' | 'editor-generation-pricing'
| 'editor-showcase' | 'editor-showcase'
| 'game-distribution'
| 'editor-assets' | 'editor-assets'
| 'project-snapshots' | 'project-snapshots'
| 'agc-models' | 'agc-models'
| 'agc-templates'
| 'accounts'; | 'accounts';
export type AdminTabPermission = Exclude< export type AdminTabPermission = Exclude<
@@ -39,6 +42,7 @@ export const adminRoutes: AdminRouteDefinition[] = [
{ id: 'tables', label: '表查询', hash: '#tables' }, { id: 'tables', label: '表查询', hash: '#tables' },
{ id: 'debug', label: 'API 调试', hash: '#debug' }, { id: 'debug', label: 'API 调试', hash: '#debug' },
{ id: 'tracking', label: '埋点数据', hash: '#tracking' }, { id: 'tracking', label: '埋点数据', hash: '#tracking' },
{ id: 'agc-tracking', label: '客户端埋点', hash: '#agc-tracking' },
{ id: 'error-reports', label: '错误报告', hash: '#error-reports' }, { id: 'error-reports', label: '错误报告', hash: '#error-reports' },
{ id: 'gray-release', label: '灰度发布', hash: '#gray-release' }, { id: 'gray-release', label: '灰度发布', hash: '#gray-release' },
{ id: 'redeem', label: '兑换码', hash: '#redeem' }, { id: 'redeem', label: '兑换码', hash: '#redeem' },
@@ -53,7 +57,9 @@ export const adminRoutes: AdminRouteDefinition[] = [
hash: '#editor-generation-pricing', hash: '#editor-generation-pricing',
}, },
{ id: 'agc-models', label: 'AGC 模型', hash: '#agc-models', ownerOnly: true }, { id: 'agc-models', label: 'AGC 模型', hash: '#agc-models', ownerOnly: true },
{ id: 'agc-templates', label: '模板管理', hash: '#agc-templates' },
{ id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase' }, { id: 'editor-showcase', label: '精选审核', hash: '#editor-showcase' },
{ id: 'game-distribution', label: '游戏审核', hash: '#game-distribution' },
{ id: 'editor-assets', label: '素材查询', hash: '#editor-assets' }, { id: 'editor-assets', label: '素材查询', hash: '#editor-assets' },
{ id: 'project-snapshots', label: '项目工程', hash: '#project-snapshots' }, { id: 'project-snapshots', label: '项目工程', hash: '#project-snapshots' },
{ id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true }, { id: 'accounts', label: '账号管理', hash: '#accounts', ownerOnly: true },
+1
View File
@@ -1,3 +1,4 @@
import '@genarrative/shared/styles.css';
import './styles/admin.css'; import './styles/admin.css';
import { StrictMode } from 'react'; import { StrictMode } from 'react';
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,129 @@
// @vitest-environment jsdom
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import {
AdminApiError,
listAdminAgcTrackingEvents,
} from '../api/adminApiClient';
import type { AdminAgcTrackingEventEntry } from '../api/adminApiTypes';
import { AdminAgcTrackingPage } from './AdminAgcTrackingPage';
vi.mock('../api/adminApiClient', async (original) => ({
...(await original<typeof import('../api/adminApiClient')>()),
listAdminAgcTrackingEvents: vi.fn(),
}));
vi.mock('../components/AdminUserReferenceButton', () => ({
AdminUserReferenceButton: () => null,
}));
const entry: AdminAgcTrackingEventEntry = {
eventId: 'event-1',
schemaVersion: 1,
eventName: 'project_save',
eventTime: '2026-09-21T12:00:00.123Z',
userId: 'user-1',
editorSessionId: 'session-1',
projectId: 'project-1',
creativeTaskId: 'project-1',
agentRunId: null,
agentTurnId: null,
status: 'success',
errorCode: null,
source: 'gui',
clientVersion: '1.0',
properties: { reason: 'manual' },
batchId: 'batch-1',
receivedAt: '2026-09-21T12:15:00.000Z',
};
beforeEach(() => {
vi.mocked(listAdminAgcTrackingEvents).mockReset();
});
afterEach(cleanup);
test('翻页沿用游标,筛选和刷新从第一页重新查询,详情保留 null 和事件属性', async () => {
vi.mocked(listAdminAgcTrackingEvents)
.mockResolvedValueOnce({ entries: [entry], nextCursor: 'cursor-page-2' })
.mockResolvedValueOnce({
entries: [{ ...entry, eventId: 'event-2' }],
nextCursor: null,
})
.mockResolvedValue({ entries: [entry], nextCursor: 'cursor-new' });
render(<AdminAgcTrackingPage token="admin-token" onUnauthorized={vi.fn()} />);
await screen.findByText('保存项目', { selector: 'td' });
expect(listAdminAgcTrackingEvents).toHaveBeenLastCalledWith('admin-token', {
cursor: undefined,
limit: 50,
});
fireEvent.click(screen.getByText('查看详情'));
const dialog = screen.getByRole('dialog');
expect(within(dialog).getAllByText('—').length).toBe(3);
expect(within(dialog).getByText(/"reason": "manual"/)).toBeTruthy();
expect(within(dialog).getByText(entry.eventTime)).toBeTruthy();
fireEvent.click(within(dialog).getByLabelText('关闭详情'));
fireEvent.click(screen.getByText('下一页'));
await waitFor(() =>
expect(listAdminAgcTrackingEvents).toHaveBeenLastCalledWith('admin-token', {
cursor: 'cursor-page-2',
limit: 50,
}),
);
await waitFor(() =>
expect(screen.getByText('查询').closest('button')?.disabled).toBe(false),
);
fireEvent.click(screen.getByText('上一页'));
await screen.findByText('第 1 页');
expect(listAdminAgcTrackingEvents).toHaveBeenCalledTimes(2);
fireEvent.change(screen.getByLabelText('项目 ID'), {
target: { value: 'project-2' },
});
fireEvent.click(screen.getByText('查询'));
await waitFor(() =>
expect(listAdminAgcTrackingEvents).toHaveBeenLastCalledWith(
'admin-token',
expect.objectContaining({
projectId: 'project-2',
cursor: undefined,
limit: 50,
}),
),
);
expect(screen.getByText('第 1 页')).toBeTruthy();
await waitFor(() =>
expect(screen.getByText('刷新').closest('button')?.disabled).toBe(false),
);
fireEvent.click(screen.getByText('刷新'));
await waitFor(() =>
expect(listAdminAgcTrackingEvents).toHaveBeenCalledTimes(4),
);
});
test('空结果、权限失败与登录失效沿用后台反馈', async () => {
const unauthorized = vi.fn();
vi.mocked(listAdminAgcTrackingEvents)
.mockResolvedValueOnce({ entries: [], nextCursor: null })
.mockRejectedValueOnce(
new AdminApiError({ status: 403, message: '无权访问客户端埋点' }),
)
.mockRejectedValueOnce(
new AdminApiError({ status: 401, message: '登录失效' }),
);
render(<AdminAgcTrackingPage token="token" onUnauthorized={unauthorized} />);
await screen.findByText('暂无客户端埋点数据');
fireEvent.click(screen.getByText('刷新'));
expect((await screen.findByRole('alert')).textContent).toContain(
'无权访问客户端埋点',
);
fireEvent.click(screen.getByText('刷新'));
await waitFor(() =>
expect(unauthorized).toHaveBeenCalledWith('登录状态已失效'),
);
});
@@ -0,0 +1,379 @@
import { Modal } from '@genarrative/shared/components';
import { FormEvent, useEffect, useRef, useState } from 'react';
import { listAdminAgcTrackingEvents } from '../api/adminApiClient';
import type {
AdminAgcTrackingEventEntry,
AdminAgcTrackingEventListResponse,
AdminAgcTrackingEventQuery,
} from '../api/adminApiTypes';
import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton';
import { handlePageError } from './pageUtils';
const eventLabels: Record<string, string> = {
editor_session_start: '编辑器会话开始',
editor_session_end: '编辑器会话结束',
editor_focus_start: '编辑器获得焦点',
editor_focus_end: '编辑器失去焦点',
project_create_success: '项目创建成功',
project_open: '打开项目',
creative_task_submit: '首次提交创作目标',
agent_run_completed: 'Agent 运行完成',
agent_run_failed: 'Agent 运行失败',
project_revision_created: '项目产生修改',
preview_ready: '预览就绪',
project_save: '保存项目',
};
const fieldLabels: Record<keyof AdminAgcTrackingEventEntry, string> = {
eventId: '事件 ID',
schemaVersion: '事件版本',
eventName: '事件类型',
eventTime: '发生时间',
userId: '用户 ID',
editorSessionId: '编辑器会话 ID',
projectId: '项目 ID',
creativeTaskId: '创作目标 ID',
agentRunId: 'Agent run ID',
agentTurnId: 'Agent turn ID',
status: '结果',
errorCode: '错误码',
source: '来源',
clientVersion: '客户端版本',
properties: '事件属性',
batchId: '批次 ID',
receivedAt: '入库时间',
};
function formatTime(value: string) {
const date = new Date(value);
return Number.isNaN(date.getTime()) ? value : date.toLocaleString('zh-CN');
}
export function AdminAgcTrackingPage({
token,
onUnauthorized,
}: {
token: string;
onUnauthorized: (message?: string) => void;
}) {
const [filters, setFilters] = useState({
userId: '',
projectId: '',
eventName: '',
startTime: '',
endTime: '',
});
const [query, setQuery] = useState<AdminAgcTrackingEventQuery>({});
const [cursors, setCursors] = useState<Array<string | undefined>>([
undefined,
]);
const [page, setPage] = useState(0);
const [refresh, setRefresh] = useState(0);
const [entries, setEntries] = useState<AdminAgcTrackingEventEntry[]>([]);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [detail, setDetail] = useState<AdminAgcTrackingEventEntry | null>(null);
const cursor = cursors[page];
// 首页没有入站游标,返回首页时保留原快照,刷新才获取新数据。
const firstPage = useRef<{
token: string;
query: AdminAgcTrackingEventQuery;
response: AdminAgcTrackingEventListResponse;
} | null>(null);
useEffect(() => {
const saved = firstPage.current;
if (!cursor && saved?.token === token && saved.query === query) {
setEntries(saved.response.entries);
setNextCursor(saved.response.nextCursor);
setError('');
setLoading(false);
return;
}
let active = true;
setLoading(true);
setError('');
setEntries([]);
setNextCursor(null);
void listAdminAgcTrackingEvents(token, { ...query, cursor, limit: 50 })
.then((response) => {
if (!active) return;
if (!cursor) firstPage.current = { token, query, response };
setEntries(response.entries);
setNextCursor(response.nextCursor);
})
.catch((failure: unknown) => {
if (active) handlePageError(failure, onUnauthorized, setError);
})
.finally(() => {
if (active) setLoading(false);
});
return () => {
active = false;
};
}, [token, query, cursor, refresh, onUnauthorized]);
function resetPages() {
firstPage.current = null;
setCursors([undefined]);
setPage(0);
setDetail(null);
}
function search(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (
filters.startTime &&
filters.endTime &&
new Date(filters.startTime) >= new Date(filters.endTime)
) {
setError('结束时间必须晚于开始时间');
return;
}
resetPages();
setQuery({
userId: filters.userId.trim(),
projectId: filters.projectId.trim(),
eventName: filters.eventName,
startTime: filters.startTime
? new Date(filters.startTime).toISOString()
: undefined,
endTime: filters.endTime
? new Date(filters.endTime).toISOString()
: undefined,
});
}
function related(
key: 'creativeTaskId' | 'agentRunId' | 'clientVersion',
value: string,
) {
resetPages();
setFilters({
userId: '',
projectId: '',
eventName: '',
startTime: '',
endTime: '',
});
setQuery({ [key]: value });
}
return (
<section className="admin-page admin-page-wide">
<div className="admin-page-heading">
<div>
<h2></h2>
<p></p>
</div>
<button
type="button"
className="admin-secondary-button"
disabled={loading}
onClick={() => {
resetPages();
setRefresh((value) => value + 1);
}}
>
</button>
</div>
<form className="admin-panel admin-form" onSubmit={search}>
<div className="admin-filter-grid">
{(['userId', 'projectId'] as const).map((key) => (
<label key={key} className="admin-field">
<span>{fieldLabels[key]}</span>
<input
value={filters[key]}
onChange={(event) =>
setFilters({ ...filters, [key]: event.target.value })
}
/>
</label>
))}
<label className="admin-field">
<span></span>
<select
value={filters.eventName}
onChange={(event) =>
setFilters({ ...filters, eventName: event.target.value })
}
>
<option value=""></option>
{Object.entries(eventLabels).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</label>
{(['startTime', 'endTime'] as const).map((key) => (
<label key={key} className="admin-field">
<span>
{key === 'startTime'
? '发生时间起点(含)'
: '发生时间终点(不含)'}
</span>
<input
type="datetime-local"
value={filters[key]}
onChange={(event) =>
setFilters({ ...filters, [key]: event.target.value })
}
/>
</label>
))}
</div>
<div className="admin-action-row">
<button
type="submit"
className="admin-primary-button"
disabled={loading}
>
</button>
</div>
{(['creativeTaskId', 'agentRunId', 'clientVersion'] as const)
.filter((key) => query[key])
.map((key) => (
<p key={key}>
{fieldLabels[key]}{query[key]}
</p>
))}
</form>
{error ? (
<p role="alert" className="admin-error-message">
{error}
</p>
) : null}
<div className="admin-panel">
<div className="admin-table-wrap">
<table className="admin-table">
<thead>
<tr>
{[
'入库时间',
'发生时间',
'用户',
'事件名称',
'项目',
'来源',
'结果',
'客户端版本',
'详情',
].map((label) => (
<th key={label}>{label}</th>
))}
</tr>
</thead>
<tbody>
{entries.map((entry) => (
<tr key={entry.eventId}>
<td>{formatTime(entry.receivedAt)}</td>
<td>{formatTime(entry.eventTime)}</td>
<td>
{entry.userId}
<AdminUserReferenceButton
token={token}
userId={entry.userId}
onUnauthorized={onUnauthorized}
/>
</td>
<td>{eventLabels[entry.eventName] ?? entry.eventName}</td>
<td>{entry.projectId ?? '—'}</td>
<td>{entry.source}</td>
<td>{entry.status ?? '—'}</td>
<td>{entry.clientVersion}</td>
<td>
<button
type="button"
className="admin-ghost-button"
onClick={() => setDetail(entry)}
>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{loading ? (
<p role="status"></p>
) : !error && entries.length === 0 ? (
<p></p>
) : null}
<div className="admin-action-row">
<button
type="button"
className="admin-secondary-button"
disabled={loading || page === 0}
onClick={() => setPage((value) => value - 1)}
>
</button>
<span> {page + 1} </span>
<button
type="button"
className="admin-secondary-button"
disabled={loading || !nextCursor}
onClick={() => {
if (!nextCursor) return;
setCursors([...cursors.slice(0, page + 1), nextCursor]);
setPage(page + 1);
}}
>
</button>
</div>
</div>
{detail ? (
<Modal
open
title="客户端埋点详情"
closeLabel="关闭详情"
onClose={() => setDetail(null)}
className="genarrative-ui"
>
<dl>
{(
Object.keys(fieldLabels) as Array<
keyof AdminAgcTrackingEventEntry
>
)
.filter((key) => key !== 'properties')
.map((key) => (
<div key={key}>
<dt>{fieldLabels[key]}</dt>
<dd style={{ overflowWrap: 'anywhere' }}>
{detail[key] == null ? '—' : String(detail[key])}
</dd>
</div>
))}
</dl>
<div className="admin-action-row">
{(['creativeTaskId', 'agentRunId', 'clientVersion'] as const).map(
(key) =>
detail[key] ? (
<button
type="button"
key={key}
className="admin-secondary-button"
onClick={() => related(key, detail[key]!)}
>
{fieldLabels[key]}
</button>
) : null,
)}
</div>
<h3></h3>
<pre style={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>
{JSON.stringify(detail.properties, null, 2)}
</pre>
</Modal>
) : null}
</section>
);
}
@@ -2641,48 +2641,6 @@ const databaseTableLabelMap: Record<string, string> = {
profile_recharge_order: '充值订单', profile_recharge_order: '充值订单',
profile_feedback_submission: '反馈提交', profile_feedback_submission: '反馈提交',
profile_save_archive: '存档记录', profile_save_archive: '存档记录',
story_session: '剧情会话',
story_event: '剧情事件',
npc_state: 'NPC 状态',
inventory_slot: '背包槽位',
battle_state: '战斗状态',
treasure_record: '宝藏记录',
quest_record: '任务记录',
quest_log: '任务日志',
player_progression: '玩家进度',
chapter_progression: '章节进度',
custom_world_profile: '自定义世界档案',
custom_world_session: '自定义世界会话',
custom_world_agent_session: '自定义世界 Agent 会话',
custom_world_agent_message: '自定义世界 Agent 消息',
custom_world_agent_operation: '自定义世界 Agent 操作',
custom_world_draft_card: '自定义世界草稿卡片',
custom_world_gallery_entry: '自定义世界画廊条目',
puzzle_agent_session: '拼图 Agent 会话',
puzzle_agent_message: '拼图 Agent 消息',
puzzle_work_profile: '拼图作品档案',
puzzle_event: '拼图事件',
puzzle_runtime_run: '拼图运行记录',
puzzle_leaderboard_entry: '拼图排行榜条目',
match3d_agent_session: '抓大鹅 Agent 会话',
match3d_agent_message: '抓大鹅 Agent 消息',
match3d_work_profile: '抓大鹅作品档案',
match3d_runtime_run: '抓大鹅运行记录',
square_hole_agent_session: '方洞挑战 Agent 会话',
square_hole_agent_message: '方洞挑战 Agent 消息',
square_hole_work_profile: '方洞挑战作品档案',
square_hole_runtime_run: '方洞挑战运行记录',
visual_novel_agent_session: '视觉小说 Agent 会话',
visual_novel_agent_message: '视觉小说 Agent 消息',
visual_novel_work_profile: '视觉小说作品档案',
visual_novel_runtime_run: '视觉小说运行记录',
visual_novel_runtime_history_entry: '视觉小说历史条目',
visual_novel_runtime_event: '视觉小说运行事件',
big_fish_creation_session: '大鱼吃小鱼创建会话',
big_fish_agent_message: '大鱼吃小鱼 Agent 消息',
big_fish_asset_slot: '大鱼吃小鱼资产槽位',
big_fish_event: '大鱼吃小鱼事件',
big_fish_runtime_run: '大鱼吃小鱼运行记录',
asset_object: '资产对象', asset_object: '资产对象',
asset_entity_binding: '资产实体绑定', asset_entity_binding: '资产实体绑定',
asset_event: '资产事件', asset_event: '资产事件',
@@ -2724,48 +2682,6 @@ const databaseTableDescriptionMap: Record<string, string> = {
profile_recharge_order: '充值订单表', profile_recharge_order: '充值订单表',
profile_feedback_submission: '反馈提交记录表', profile_feedback_submission: '反馈提交记录表',
profile_save_archive: '用户存档记录表', profile_save_archive: '用户存档记录表',
story_session: '剧情会话表',
story_event: '剧情事件表',
npc_state: 'NPC 状态表',
inventory_slot: '背包槽位表',
battle_state: '战斗状态表',
treasure_record: '宝藏记录表',
quest_record: '任务记录表',
quest_log: '任务日志表',
player_progression: '玩家进度表',
chapter_progression: '章节进度表',
custom_world_profile: '自定义世界档案表',
custom_world_session: '自定义世界会话表',
custom_world_agent_session: '自定义世界 Agent 会话表',
custom_world_agent_message: '自定义世界 Agent 消息表',
custom_world_agent_operation: '自定义世界 Agent 操作表',
custom_world_draft_card: '自定义世界草稿卡片表',
custom_world_gallery_entry: '自定义世界画廊条目表',
puzzle_agent_session: '拼图 Agent 会话表',
puzzle_agent_message: '拼图 Agent 消息表',
puzzle_work_profile: '拼图作品档案表',
puzzle_event: '拼图事件表',
puzzle_runtime_run: '拼图运行记录表',
puzzle_leaderboard_entry: '拼图排行榜条目表',
match3d_agent_session: '抓大鹅 Agent 会话表',
match3d_agent_message: '抓大鹅 Agent 消息表',
match3d_work_profile: '抓大鹅作品档案表',
match3d_runtime_run: '抓大鹅运行记录表',
square_hole_agent_session: '方洞挑战 Agent 会话表',
square_hole_agent_message: '方洞挑战 Agent 消息表',
square_hole_work_profile: '方洞挑战作品档案表',
square_hole_runtime_run: '方洞挑战运行记录表',
visual_novel_agent_session: '视觉小说 Agent 会话表',
visual_novel_agent_message: '视觉小说 Agent 消息表',
visual_novel_work_profile: '视觉小说作品档案表',
visual_novel_runtime_run: '视觉小说运行记录表',
visual_novel_runtime_history_entry: '视觉小说历史条目表',
visual_novel_runtime_event: '视觉小说运行事件表',
big_fish_creation_session: '大鱼吃小鱼创建会话表',
big_fish_agent_message: '大鱼吃小鱼 Agent 消息表',
big_fish_asset_slot: '大鱼吃小鱼资产槽位表',
big_fish_event: '大鱼吃小鱼事件表',
big_fish_runtime_run: '大鱼吃小鱼运行记录表',
asset_object: '资产对象表', asset_object: '资产对象表',
asset_entity_binding: '资产实体绑定表', asset_entity_binding: '资产实体绑定表',
asset_event: '资产事件表', asset_event: '资产事件表',
@@ -0,0 +1,186 @@
/* @vitest-environment jsdom */
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, expect, test, vi } from 'vitest';
import {
listAdminGameDistributionReviews,
reviewAdminGameDistributionVersion,
suspendAdminGameDistributionGame,
} from '../api/adminApiClient';
import type { AdminGameDistributionReviewEntry } from '../api/adminApiTypes';
import {
AdminGameDistributionReviewPage,
resolveGameReleaseEntryUrlError,
} from './AdminGameDistributionReviewPage';
vi.mock('../api/adminApiClient', () => ({
isAdminApiError: vi.fn(
(error: unknown) =>
typeof error === 'object' &&
error !== null &&
'status' in error &&
typeof error.status === 'number',
),
formatAdminApiError: vi.fn((error: unknown) =>
error instanceof Error ? error.message : '请求失败',
),
listAdminGameDistributionReviews: vi.fn(),
reviewAdminGameDistributionVersion: vi.fn(),
suspendAdminGameDistributionGame: vi.fn(),
}));
const entry: AdminGameDistributionReviewEntry = {
versionId: 'version-1',
gameId: 'game_1',
versionNumber: 2,
packageSha256: 'a'.repeat(64),
packageBytes: 2048,
status: 'pending_review',
publicationRevision: 4,
reviewReason: null,
createdAt: '2026-09-20T08:00:00Z',
updatedAt: '2026-09-20T08:00:00Z',
};
beforeEach(() => {
vi.mocked(listAdminGameDistributionReviews).mockReset();
vi.mocked(reviewAdminGameDistributionVersion).mockReset();
vi.mocked(suspendAdminGameDistributionGame).mockReset();
vi.mocked(listAdminGameDistributionReviews).mockResolvedValue({
entries: [entry],
nextCursor: null,
});
});
test('发行入口必须是带完整来源的 HTTPS 地址', () => {
expect(resolveGameReleaseEntryUrlError('')).toBe('请填写发行入口');
expect(
resolveGameReleaseEntryUrlError('http://games.test/a/index.html'),
).toBe('发行入口必须以 https:// 开头');
expect(
resolveGameReleaseEntryUrlError('https://games.test/a/index.html?token=1'),
).toBe('发行入口不能包含 query 或 fragment');
expect(
resolveGameReleaseEntryUrlError('https://u:p@games.test/a/index.html'),
).toBe('发行入口不能包含凭据');
expect(
resolveGameReleaseEntryUrlError('https://games.test/a/index.html'),
).toBe('');
});
test('通过审核时提交当前 publicationRevision 与发行入口并刷新列表', async () => {
vi.mocked(reviewAdminGameDistributionVersion).mockResolvedValue({
version: { ...entry, status: 'published' },
replayed: false,
});
render(
<AdminGameDistributionReviewPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
await screen.findByText('game_1');
fireEvent.change(screen.getByLabelText('发行入口'), {
target: { value: 'https://games.test/releases/game_1/index.html' },
});
fireEvent.click(screen.getByRole('button', { name: '通过' }));
await waitFor(() =>
expect(reviewAdminGameDistributionVersion).toHaveBeenCalledTimes(1),
);
const [token, versionId, idempotencyKey, payload] =
vi.mocked(reviewAdminGameDistributionVersion).mock.calls[0] ?? [];
expect(token).toBe('admin-token');
expect(versionId).toBe('version-1');
expect(String(idempotencyKey)).toContain('version-1');
expect(payload).toEqual({
decision: 'approve',
expectedPublicationRevision: 4,
entryUrl: 'https://games.test/releases/game_1/index.html',
});
await waitFor(() =>
expect(vi.mocked(listAdminGameDistributionReviews)).toHaveBeenCalledTimes(
2,
),
);
});
test('缺少拒绝理由时不调用审核接口', async () => {
render(
<AdminGameDistributionReviewPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
await screen.findByText('game_1');
fireEvent.click(screen.getByRole('button', { name: '拒绝' }));
expect(await screen.findByText('拒绝审核必须填写理由')).toBeTruthy();
expect(reviewAdminGameDistributionVersion).not.toHaveBeenCalled();
});
test('安全下架需要二次确认,并携带公开修订号与原因', async () => {
vi.mocked(suspendAdminGameDistributionGame).mockResolvedValue({
game: {
id: 'game_1',
title: '测试游戏',
status: 'suspended',
publicationRevision: 5,
},
replayed: false,
});
render(
<AdminGameDistributionReviewPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
await screen.findByText('game_1');
fireEvent.change(screen.getByLabelText('下架原因'), {
target: { value: '盗用素材' },
});
fireEvent.click(screen.getByRole('button', { name: '安全下架' }));
// 第一次点击只弹出确认面板,不直接调用后端。
expect(suspendAdminGameDistributionGame).not.toHaveBeenCalled();
expect(await screen.findByRole('dialog')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '确认' }));
await waitFor(() =>
expect(suspendAdminGameDistributionGame).toHaveBeenCalledTimes(1),
);
const [token, gameId, idempotencyKey, payload] =
vi.mocked(suspendAdminGameDistributionGame).mock.calls[0] ?? [];
expect(token).toBe('admin-token');
expect(gameId).toBe('game_1');
expect(String(idempotencyKey)).toContain('game_1');
expect(payload).toEqual({
expectedPublicationRevision: 4,
reason: '盗用素材',
});
expect(await screen.findByText(//u)).toBeTruthy();
});
test('取消确认时不下架', async () => {
render(
<AdminGameDistributionReviewPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
await screen.findByText('game_1');
fireEvent.click(screen.getByRole('button', { name: '安全下架' }));
await screen.findByRole('dialog');
fireEvent.click(screen.getByRole('button', { name: '取消' }));
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
expect(suspendAdminGameDistributionGame).not.toHaveBeenCalled();
});
@@ -0,0 +1,365 @@
import { RefreshCcw } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import {
listAdminGameDistributionReviews,
reviewAdminGameDistributionVersion,
suspendAdminGameDistributionGame,
} from '../api/adminApiClient';
import type { AdminGameDistributionReviewEntry } from '../api/adminApiTypes';
import { useAdminWriteConfirm } from '../components/useAdminWriteConfirm';
import { handlePageError } from './pageUtils';
interface AdminGameDistributionReviewPageProps {
token: string;
onUnauthorized: (message?: string) => void;
}
function formatBytes(value: number) {
if (value >= 1024 * 1024) {
return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
}
if (value >= 1024) {
return `${(value / 1024).toFixed(1)} KiB`;
}
return `${value} B`;
}
function formatTime(value: string) {
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) return value;
return parsed.toLocaleString('zh-CN', { hour12: false });
}
function createSuspendIdempotencyKey(gameId: string) {
const random =
typeof crypto !== 'undefined' && 'randomUUID' in crypto
? crypto.randomUUID()
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
return `game-suspend-${gameId}-${random}`.slice(0, 128);
}
function createReviewIdempotencyKey(versionId: string) {
const random =
typeof crypto !== 'undefined' && 'randomUUID' in crypto
? crypto.randomUUID()
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
return `game-review-${versionId}-${random}`.slice(0, 128);
}
export function resolveGameReleaseEntryUrlError(value: string) {
const normalized = value.trim();
if (!normalized) return '请填写发行入口';
if (!normalized.startsWith('https://')) {
return '发行入口必须以 https:// 开头';
}
if (normalized.includes('?') || normalized.includes('#')) {
return '发行入口不能包含 query 或 fragment';
}
try {
const parsed = new URL(normalized);
if (parsed.username || parsed.password) {
return '发行入口不能包含凭据';
}
} catch {
return '发行入口不是合法 URL';
}
return '';
}
export function AdminGameDistributionReviewPage({
token,
onUnauthorized,
}: AdminGameDistributionReviewPageProps) {
const [entries, setEntries] = useState<AdminGameDistributionReviewEntry[]>(
[],
);
const [isLoading, setIsLoading] = useState(false);
const [busyVersionId, setBusyVersionId] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [statusMessage, setStatusMessage] = useState('');
const [entryUrlByVersion, setEntryUrlByVersion] = useState<
Record<string, string>
>({});
const [reasonByVersion, setReasonByVersion] = useState<
Record<string, string>
>({});
const [suspendReasonByGame, setSuspendReasonByGame] = useState<
Record<string, string>
>({});
const [busyGameId, setBusyGameId] = useState('');
const writeConfirm = useAdminWriteConfirm();
const loadReviews = useCallback(async () => {
setIsLoading(true);
setErrorMessage('');
try {
const response = await listAdminGameDistributionReviews(token);
setEntries(response.entries);
} catch (error) {
handlePageError(error, onUnauthorized, setErrorMessage);
} finally {
setIsLoading(false);
}
}, [token, onUnauthorized]);
useEffect(() => {
void loadReviews();
}, [loadReviews]);
async function submitReview(
entry: AdminGameDistributionReviewEntry,
decision: 'approve' | 'reject',
) {
const entryUrl = (entryUrlByVersion[entry.versionId] ?? '').trim();
const reason = (reasonByVersion[entry.versionId] ?? '').trim();
if (decision === 'approve') {
const invalid = resolveGameReleaseEntryUrlError(entryUrl);
if (invalid) {
setErrorMessage(invalid);
return;
}
} else if (!reason) {
setErrorMessage('拒绝审核必须填写理由');
return;
}
setBusyVersionId(entry.versionId);
setErrorMessage('');
setStatusMessage('');
try {
await reviewAdminGameDistributionVersion(
token,
entry.versionId,
createReviewIdempotencyKey(entry.versionId),
decision === 'approve'
? {
decision,
expectedPublicationRevision: entry.publicationRevision,
entryUrl,
}
: {
decision,
expectedPublicationRevision: entry.publicationRevision,
reviewReason: reason,
},
);
setStatusMessage(
decision === 'approve'
? `版本 v${entry.versionNumber} 已通过审核`
: `版本 v${entry.versionNumber} 已拒绝`,
);
await loadReviews();
} catch (error) {
handlePageError(error, onUnauthorized, setErrorMessage);
} finally {
setBusyVersionId('');
}
}
/**
* 管理员安全下架:先二次确认,再带当前公开修订号调用后端;并发审核导致修订号变化时
* 由服务端返回冲突,前端只提示刷新,不静默重试。
*/
async function suspendGame(entry: AdminGameDistributionReviewEntry) {
const reason = (suspendReasonByGame[entry.gameId] ?? '').trim();
const confirmed = await writeConfirm.confirmWrite({
action: '安全下架游戏',
target: `${entry.gameId}(版本 v${entry.versionNumber}`,
});
if (!confirmed) return;
setBusyGameId(entry.gameId);
setErrorMessage('');
setStatusMessage('');
try {
await suspendAdminGameDistributionGame(
token,
entry.gameId,
createSuspendIdempotencyKey(entry.gameId),
{
expectedPublicationRevision: entry.publicationRevision,
...(reason ? { reason } : {}),
},
);
setStatusMessage(`游戏 ${entry.gameId} 已安全下架,发行入口已关闭`);
setSuspendReasonByGame((current) => ({ ...current, [entry.gameId]: '' }));
await loadReviews();
} catch (error) {
handlePageError(error, onUnauthorized, setErrorMessage);
} finally {
setBusyGameId('');
}
}
return (
<section className="admin-page admin-page-wide">
<div className="admin-page-heading">
<h1></h1>
<button
type="button"
className="admin-secondary-button"
onClick={() => void loadReviews()}
disabled={isLoading}
>
<RefreshCcw aria-hidden="true" />
</button>
</div>
{errorMessage ? (
<div className="admin-alert admin-alert-warning" role="alert">
{errorMessage}
</div>
) : null}
{statusMessage ? (
<div className="admin-alert admin-alert-success" role="status">
{statusMessage}
</div>
) : null}
<div className="admin-panel">
<div className="admin-panel-heading">
<h2></h2>
<span className="admin-muted-text"> {entries.length} </span>
</div>
{isLoading ? (
<p className="admin-muted-text"></p>
) : null}
{!isLoading && entries.length === 0 ? (
<p className="admin-muted-text"></p>
) : null}
{!isLoading && entries.length > 0 ? (
<div className="admin-table-wrap">
<table className="admin-table admin-table-wide">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{entries.map((entry) => {
const busy = busyVersionId === entry.versionId;
return (
<tr key={entry.versionId}>
<td>
<code>{entry.gameId}</code>
</td>
<td>
v{entry.versionNumber}
<div className="admin-muted-text">{entry.status}</div>
{entry.reviewReason ? (
<div className="admin-muted-text">
{entry.reviewReason}
</div>
) : null}
</td>
<td>
{formatBytes(entry.packageBytes)}
<div className="admin-muted-text">
<code>{entry.packageSha256.slice(0, 12)}</code>
</div>
</td>
<td>{formatTime(entry.createdAt)}</td>
<td>
<div className="admin-action-row">
<div className="admin-field">
<label
htmlFor={`game-release-url-${entry.versionId}`}
>
</label>
<input
id={`game-release-url-${entry.versionId}`}
value={entryUrlByVersion[entry.versionId] ?? ''}
placeholder="https://"
onChange={(event) =>
setEntryUrlByVersion((current) => ({
...current,
[entry.versionId]: event.target.value,
}))
}
disabled={busy}
/>
</div>
<button
type="button"
className="admin-primary-button"
disabled={busy}
onClick={() => void submitReview(entry, 'approve')}
>
</button>
<div className="admin-field">
<label
htmlFor={`game-reject-reason-${entry.versionId}`}
>
</label>
<input
id={`game-reject-reason-${entry.versionId}`}
value={reasonByVersion[entry.versionId] ?? ''}
onChange={(event) =>
setReasonByVersion((current) => ({
...current,
[entry.versionId]: event.target.value,
}))
}
disabled={busy}
/>
</div>
<button
type="button"
className="admin-ghost-button"
disabled={busy}
onClick={() => void submitReview(entry, 'reject')}
>
</button>
<div className="admin-field">
<label
htmlFor={`game-suspend-reason-${entry.versionId}`}
>
</label>
<input
id={`game-suspend-reason-${entry.versionId}`}
value={suspendReasonByGame[entry.gameId] ?? ''}
onChange={(event) =>
setSuspendReasonByGame((current) => ({
...current,
[entry.gameId]: event.target.value,
}))
}
disabled={busy}
/>
</div>
<button
type="button"
className="admin-danger-button"
disabled={busy || busyGameId === entry.gameId}
onClick={() => void suspendGame(entry)}
>
{busyGameId === entry.gameId
? '正在下架…'
: '安全下架'}
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
) : null}
</div>
{writeConfirm.confirmDialog}
</section>
);
}
@@ -175,6 +175,107 @@ test('灰度发布页可选择模板库并默认启用零比例灰度', async ()
); );
}); });
test('灰度发布页可选择游戏发布开关,默认保持「未开启即开放」语义', async () => {
const user = userEvent.setup();
render(
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
);
await screen.findByRole('button', { name: 'editor.new-toolbar' });
await user.selectOptions(screen.getByLabelText('Gate Key 前缀'), [
'game-distribution',
]);
expect((screen.getByLabelText('Gate Key') as HTMLInputElement).value).toBe(
'game-distribution:publish',
);
expect(
(screen.getByLabelText('Gate Key 目标') as HTMLSelectElement).value,
).toBe('publish');
// 该开关的语义是「未配置/关闭 = 默认开放」,所以选中后不能默认打开收紧。
expect((screen.getByLabelText('启用') as HTMLInputElement).checked).toBe(
false,
);
expect((screen.getByLabelText('灰度比例') as HTMLInputElement).value).toBe(
'0',
);
expect(
(screen.getByLabelText('描述') as HTMLTextAreaElement).value,
).toContain('游戏发布入口灰度');
});
test('灰度发布页保存游戏发布开关时写入白名单与比例', async () => {
const user = userEvent.setup();
vi.mocked(upsertAdminFeatureGateConfig).mockResolvedValueOnce({
gates: [
...configResponse.gates,
{
gateKey: 'game-distribution:publish',
enabled: true,
rolloutPercent: 20,
allowUserIds: ['user-internal'],
allowUserTags: [],
denyUserIds: [],
description: '游戏发布入口灰度',
updatedAt: '2026-09-22T10:00:00Z',
},
],
});
render(
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
);
await screen.findByRole('button', { name: 'editor.new-toolbar' });
await user.selectOptions(screen.getByLabelText('Gate Key 前缀'), [
'game-distribution',
]);
fireEvent.click(screen.getByLabelText('启用'));
fireEvent.change(screen.getByLabelText('灰度比例'), {
target: { value: '20' },
});
fireEvent.change(screen.getByLabelText('允许用户 ID'), {
target: { value: 'user-internal' },
});
fireEvent.change(screen.getByLabelText('描述'), {
target: { value: '游戏发布入口灰度' },
});
await user.click(screen.getByRole('button', { name: '保存配置' }));
await user.click(screen.getByRole('button', { name: '确认' }));
await waitFor(() =>
expect(upsertAdminFeatureGateConfig).toHaveBeenCalledWith('admin-token', {
gateKey: 'game-distribution:publish',
enabled: true,
rolloutPercent: 20,
allowUserIds: ['user-internal'],
allowUserTags: [],
denyUserIds: [],
description: '游戏发布入口灰度',
}),
);
});
test('未创建的预设开关在后台可见并可一键配置', async () => {
const user = userEvent.setup();
render(
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
);
const row = await screen.findByText('game-distribution:publish');
expect(row).not.toBeNull();
// 该开关默认未创建:列表里给出「配置」入口,点击后按默认关闭填充表单。
const configureButton = row.closest('tr')?.querySelector('button');
expect(configureButton).not.toBeNull();
await user.click(configureButton!);
expect((screen.getByLabelText('Gate Key') as HTMLInputElement).value).toBe(
'game-distribution:publish',
);
expect((screen.getByLabelText('启用') as HTMLInputElement).checked).toBe(
false,
);
});
test('灰度发布页保存时转换数组和百分比', async () => { test('灰度发布页保存时转换数组和百分比', async () => {
const user = userEvent.setup(); const user = userEvent.setup();
vi.mocked(upsertAdminFeatureGateConfig).mockResolvedValueOnce({ vi.mocked(upsertAdminFeatureGateConfig).mockResolvedValueOnce({
@@ -28,6 +28,7 @@ interface GateTargetOption {
const GATE_PREFIX_LABELS: Record<string, string> = { const GATE_PREFIX_LABELS: Record<string, string> = {
'image-editor': '画布', 'image-editor': '画布',
agc: '客户端', agc: '客户端',
'game-distribution': '游戏分发',
}; };
const FIXED_GATE_TARGETS: GateTargetOption[] = [ const FIXED_GATE_TARGETS: GateTargetOption[] = [
@@ -45,6 +46,14 @@ const FIXED_GATE_TARGETS: GateTargetOption[] = [
label: 'Agent 侧边栏', label: 'Agent 侧边栏',
description: '画布 Agent 入口灰度', description: '画布 Agent 入口灰度',
}, },
{
prefix: 'game-distribution',
suffix: 'publish',
key: 'game-distribution:publish',
label: '游戏发布',
description:
'游戏发布入口灰度:未配置或关闭时对已登录作者默认开放,开启后只放行白名单 / 灰度命中',
},
]; ];
export function AdminGrayReleaseConfigPage({ export function AdminGrayReleaseConfigPage({
@@ -197,6 +206,11 @@ export function AdminGrayReleaseConfigPage({
setErrorMessage(''); setErrorMessage('');
} }
// 预设里尚未创建行的开关也要可见:运营需要先看到 key 才能配置灰度。
const unconfiguredGateTargets = FIXED_GATE_TARGETS.filter(
(option) => !gates.some((gate) => gate.gateKey === option.key),
);
function buildPayload(): AdminUpsertFeatureGateConfigRequest { function buildPayload(): AdminUpsertFeatureGateConfigRequest {
return { return {
gateKey: gateKey.trim(), gateKey: gateKey.trim(),
@@ -443,6 +457,53 @@ export function AdminGrayReleaseConfigPage({
</div> </div>
)} )}
</section> </section>
<section className="admin-panel">
<div className="admin-panel-heading">
<h3></h3>
<span>{unconfiguredGateTargets.length}</span>
</div>
{unconfiguredGateTargets.length ? (
<div className="admin-table-wrap">
<table className="admin-table admin-table-compact">
<thead>
<tr>
<th>Gate</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{unconfiguredGateTargets.map((option) => (
<tr key={option.key}>
<td>
{option.key}
<small>
{GATE_PREFIX_LABELS[option.prefix] ?? option.prefix} ·{' '}
{option.label}
</small>
</td>
<td>{option.description}</td>
<td>
<button
className="admin-text-button"
type="button"
onClick={() => applyGateTarget(option)}
>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<div className="admin-empty-state">
{isLoading ? '加载中' : '预设开关都已创建'}
</div>
)}
</section>
</div> </div>
{confirmDialog} {confirmDialog}
@@ -13,6 +13,7 @@ import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import { import {
AdminApiError, AdminApiError,
downloadAdminProjectSnapshot, downloadAdminProjectSnapshot,
getAdminProjectSnapshotChannels,
listAdminProjectSnapshots, listAdminProjectSnapshots,
} from '../api/adminApiClient'; } from '../api/adminApiClient';
import type { AdminProjectSnapshotEntry } from '../api/adminApiTypes'; import type { AdminProjectSnapshotEntry } from '../api/adminApiTypes';
@@ -23,6 +24,7 @@ vi.mock('../api/adminApiClient', async () => ({
'../api/adminApiClient', '../api/adminApiClient',
)), )),
downloadAdminProjectSnapshot: vi.fn(), downloadAdminProjectSnapshot: vi.fn(),
getAdminProjectSnapshotChannels: vi.fn(),
listAdminProjectSnapshots: vi.fn(), listAdminProjectSnapshots: vi.fn(),
})); }));
@@ -35,12 +37,18 @@ const entry: AdminProjectSnapshotEntry = {
fileCount: 12, fileCount: 12,
totalBytes: 2048, totalBytes: 2048,
status: 'ready', status: 'ready',
channel: 'dev',
authorDisplayName: '陶泥作者',
authorPublicUserCode: 'SY-00000007',
}; };
beforeEach(() => { beforeEach(() => {
vi.mocked(listAdminProjectSnapshots) vi.mocked(listAdminProjectSnapshots)
.mockReset() .mockReset()
.mockResolvedValue({ items: [entry], nextCursor: null }); .mockResolvedValue({ items: [entry], nextCursor: null });
vi.mocked(getAdminProjectSnapshotChannels)
.mockReset()
.mockResolvedValue({ defaultChannel: 'dev', channels: ['dev'] });
vi.mocked(downloadAdminProjectSnapshot).mockReset(); vi.mocked(downloadAdminProjectSnapshot).mockReset();
}); });
afterEach(() => { afterEach(() => {
@@ -88,36 +96,166 @@ test('按项目展示完整性并限制未完成工程下载', async () => {
).toBe(false); ).toBe(false);
}); });
test('加载更多合并项目,刷新失败保留列表和错误,重试从首页开始', async () => { test('按游标翻页回到上一页时复用已取得的游标', async () => {
vi.mocked(listAdminProjectSnapshots)
.mockResolvedValueOnce({ items: [entry], nextCursor: 'page-2' })
.mockResolvedValueOnce({
items: [{ ...entry, projectId: 'project-2', projectName: '第二工程' }],
nextCursor: 'page-3',
})
.mockResolvedValueOnce({ items: [entry], nextCursor: 'page-2' });
render(<AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />);
await screen.findByText('三消工程');
const pagination = await screen.findByRole('navigation', {
name: '项目工程分页',
});
expect(pagination.textContent).toContain('第 1 页');
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
1,
'token',
{ cursor: null, limit: 20, channel: 'dev' },
expect.any(AbortSignal),
);
expect(
screen.getByRole('button', { name: '上一页' }).hasAttribute('disabled'),
).toBe(true);
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
await screen.findByText('第二工程');
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
2,
'token',
{ cursor: 'page-2', limit: 20, channel: 'dev' },
expect.any(AbortSignal),
);
expect(screen.queryByText('三消工程')).toBeNull();
expect(pagination.textContent).toContain('第 2 页');
expect(
screen.getByRole('button', { name: '下一页' }).hasAttribute('disabled'),
).toBe(false);
fireEvent.click(screen.getByRole('button', { name: '上一页' }));
await screen.findByText('三消工程');
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
3,
'token',
{ cursor: null, limit: 20, channel: 'dev' },
expect.any(AbortSignal),
);
expect(pagination.textContent).toContain('第 1 页');
});
test('切换每页条数从第一页按新条数重新加载', async () => {
vi.mocked(listAdminProjectSnapshots)
.mockResolvedValueOnce({ items: [entry], nextCursor: 'page-2' })
.mockResolvedValueOnce({
items: [{ ...entry, projectId: 'project-2', projectName: '第二工程' }],
nextCursor: 'page-3',
})
.mockResolvedValueOnce({ items: [entry], nextCursor: null });
render(<AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />);
await screen.findByText('三消工程');
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
await screen.findByText('第二工程');
const pagination = screen.getByRole('navigation', { name: '项目工程分页' });
expect(pagination.textContent).toContain('第 2 页');
fireEvent.change(screen.getByLabelText('每页条数'), {
target: { value: '50' },
});
await screen.findByText('三消工程');
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
3,
'token',
{ cursor: null, limit: 50, channel: 'dev' },
expect.any(AbortSignal),
);
// 重新加载时页脚会重建,必须重新取节点再断言。
expect(
screen.getByRole('navigation', { name: '项目工程分页' }).textContent,
).toContain('第 1 页');
});
test('刷新重载当前页,翻页失败保留当前页并提示错误', async () => {
vi.mocked(listAdminProjectSnapshots) vi.mocked(listAdminProjectSnapshots)
.mockResolvedValueOnce({ items: [entry], nextCursor: 'page-2' }) .mockResolvedValueOnce({ items: [entry], nextCursor: 'page-2' })
.mockResolvedValueOnce({ .mockResolvedValueOnce({
items: [{ ...entry, projectId: 'project-2', projectName: '第二工程' }], items: [{ ...entry, projectId: 'project-2', projectName: '第二工程' }],
nextCursor: null, nextCursor: null,
}) })
.mockRejectedValueOnce(new Error('远端清单读取失败')) .mockRejectedValueOnce(new Error('翻页读取失败'));
.mockResolvedValueOnce({ items: [], nextCursor: null });
render(<AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />); render(<AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />);
fireEvent.click(await screen.findByRole('button', { name: '加载更多' })); await screen.findByText('三消工程');
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
await screen.findByText('第二工程'); await screen.findByText('第二工程');
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
2,
'token',
{ cursor: 'page-2', limit: 20 },
expect.any(AbortSignal),
);
expect(screen.getByText('三消工程')).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: '刷新' })); fireEvent.click(screen.getByRole('button', { name: '刷新' }));
await screen.findByRole('alert'); await screen.findByRole('alert');
expect(screen.getByText('第二工程')).toBeTruthy(); expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
expect(screen.queryByText('暂无已上传项目')).toBeNull(); 3,
fireEvent.click(screen.getByRole('button', { name: '刷新' }));
await screen.findByText('暂无已上传项目');
expect(listAdminProjectSnapshots).toHaveBeenLastCalledWith(
'token', 'token',
{ cursor: null, limit: 20 }, { cursor: 'page-2', limit: 20, channel: 'dev' },
expect.any(AbortSignal), expect.any(AbortSignal),
); );
const pagination = screen.getByRole('navigation', { name: '项目工程分页' });
expect(pagination.textContent).toContain('第 2 页');
expect(screen.getByText('第二工程')).toBeTruthy();
expect(screen.queryByText('暂无已上传项目')).toBeNull();
vi.mocked(listAdminProjectSnapshots).mockResolvedValueOnce({
items: [],
nextCursor: null,
});
fireEvent.click(screen.getByRole('button', { name: '刷新' }));
await screen.findByText('暂无已上传项目');
expect(screen.queryByRole('alert')).toBeNull();
});
test('用户列与素材查询同口径展示昵称、陶泥号和用户详情入口', async () => {
render(<AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />);
const row = (await screen.findByText('三消工程')).closest('tr')!;
expect(within(row).getByText('陶泥作者')).toBeTruthy();
expect(within(row).getByText('SY-00000007')).toBeTruthy();
expect(
within(row).getByRole('button', { name: '查看用户信息' }),
).toBeTruthy();
expect(within(row).queryByText('user-1')).toBeNull();
});
test('默认查询本部署渠道,切换渠道后从第一页按该渠道重新查询', async () => {
vi.mocked(getAdminProjectSnapshotChannels).mockResolvedValue({
defaultChannel: 'release',
channels: ['dev', 'release'],
});
vi.mocked(listAdminProjectSnapshots)
.mockResolvedValueOnce({ items: [entry], nextCursor: 'page-2' })
.mockResolvedValueOnce({ items: [entry], nextCursor: null })
.mockResolvedValueOnce({
items: [{ ...entry, channel: 'dev', projectName: 'dev 工程' }],
nextCursor: null,
});
render(<AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />);
await screen.findByText('三消工程');
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
1,
'token',
{ cursor: null, limit: 20, channel: 'release' },
expect.any(AbortSignal),
);
const channelSelect = screen.getByLabelText('项目工程渠道');
expect((channelSelect as HTMLSelectElement).value).toBe('release');
fireEvent.click(screen.getByRole('button', { name: '下一页' }));
await screen.findByText('第 2 页,本页 1 个项目');
fireEvent.change(channelSelect, { target: { value: 'dev' } });
await screen.findByText('dev 工程');
expect(listAdminProjectSnapshots).toHaveBeenNthCalledWith(
3,
'token',
{ cursor: null, limit: 20, channel: 'dev' },
expect.any(AbortSignal),
);
expect(screen.getByText('第 1 页,本页 1 个项目')).toBeTruthy();
}); });
test('下载使用返回的中文文件名,随后释放对象 URL', async () => { test('下载使用返回的中文文件名,随后释放对象 URL', async () => {
@@ -154,6 +292,7 @@ test('下载使用返回的中文文件名,随后释放对象 URL', async () =
expect(createObjectURL).toHaveBeenCalledWith(blob); expect(createObjectURL).toHaveBeenCalledWith(blob);
expect(downloadAdminProjectSnapshot).toHaveBeenCalledWith( expect(downloadAdminProjectSnapshot).toHaveBeenCalledWith(
'token', 'token',
'dev',
'user-1', 'user-1',
'project-1', 'project-1',
expect.any(AbortSignal), expect.any(AbortSignal),
@@ -164,7 +303,7 @@ test('下载使用返回的中文文件名,随后释放对象 URL', async () =
test('取消下载中止请求且不显示错误,卸载中止列表请求', async () => { test('取消下载中止请求且不显示错误,卸载中止列表请求', async () => {
vi.mocked(downloadAdminProjectSnapshot).mockImplementation( vi.mocked(downloadAdminProjectSnapshot).mockImplementation(
(_token, _user, _project, signal) => (_token, _channel, _user, _project, signal) =>
new Promise((_resolve, reject) => { new Promise((_resolve, reject) => {
signal?.addEventListener('abort', () => signal?.addEventListener('abort', () =>
reject(new DOMException('Aborted', 'AbortError')), reject(new DOMException('Aborted', 'AbortError')),
@@ -177,7 +316,7 @@ test('取消下载中止请求且不显示错误,卸载中止列表请求', as
fireEvent.click(await screen.findByRole('button', { name: '下载完整工程' })); fireEvent.click(await screen.findByRole('button', { name: '下载完整工程' }));
fireEvent.click(await screen.findByRole('button', { name: '取消下载' })); fireEvent.click(await screen.findByRole('button', { name: '取消下载' }));
expect( expect(
vi.mocked(downloadAdminProjectSnapshot).mock.calls[0]?.[3]?.aborted, vi.mocked(downloadAdminProjectSnapshot).mock.calls[0]?.[4]?.aborted,
).toBe(true); ).toBe(true);
await waitFor(() => expect(screen.queryByRole('alert')).toBeNull()); await waitFor(() => expect(screen.queryByRole('alert')).toBeNull());
vi.mocked(listAdminProjectSnapshots).mockReturnValue(new Promise(() => {})); vi.mocked(listAdminProjectSnapshots).mockReturnValue(new Promise(() => {}));
@@ -248,6 +387,10 @@ test('更换登录令牌丢弃旧列表和晚返回请求', async () => {
onUnauthorized={onUnauthorized} onUnauthorized={onUnauthorized}
/>, />,
); );
// 渠道确定之后才会发出列表请求,这里等到旧令牌的请求真的在途再换令牌。
await waitFor(() =>
expect(listAdminProjectSnapshots).toHaveBeenCalledTimes(1),
);
const oldSignal = vi.mocked(listAdminProjectSnapshots).mock.calls[0]?.[2]; const oldSignal = vi.mocked(listAdminProjectSnapshots).mock.calls[0]?.[2];
view.rerender( view.rerender(
<AdminProjectSnapshotsPage <AdminProjectSnapshotsPage
@@ -278,7 +421,7 @@ test('卸载后完成的下载不会创建浏览器文件', async () => {
<AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />, <AdminProjectSnapshotsPage token="token" onUnauthorized={vi.fn()} />,
); );
fireEvent.click(await screen.findByRole('button', { name: '下载完整工程' })); fireEvent.click(await screen.findByRole('button', { name: '下载完整工程' }));
const signal = vi.mocked(downloadAdminProjectSnapshot).mock.calls[0]?.[3]; const signal = vi.mocked(downloadAdminProjectSnapshot).mock.calls[0]?.[4];
view.unmount(); view.unmount();
expect(signal?.aborted).toBe(true); expect(signal?.aborted).toBe(true);
await act(async () => { await act(async () => {
@@ -1,11 +1,19 @@
import { Download, RefreshCcw, X } from 'lucide-react'; import {
ChevronLeft,
ChevronRight,
Download,
RefreshCcw,
X,
} from 'lucide-react';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { import {
downloadAdminProjectSnapshot, downloadAdminProjectSnapshot,
getAdminProjectSnapshotChannels,
listAdminProjectSnapshots, listAdminProjectSnapshots,
} from '../api/adminApiClient'; } from '../api/adminApiClient';
import type { AdminProjectSnapshotEntry } from '../api/adminApiTypes'; import type { AdminProjectSnapshotEntry } from '../api/adminApiTypes';
import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton';
import { handlePageError } from './pageUtils'; import { handlePageError } from './pageUtils';
interface AdminProjectSnapshotsPageProps { interface AdminProjectSnapshotsPageProps {
@@ -31,21 +39,32 @@ const snapshotStatuses = {
}, },
}; };
const DEFAULT_PAGE_SIZE = 20;
const PAGE_SIZE_OPTIONS = [20, 50, 100];
export function AdminProjectSnapshotsPage({ export function AdminProjectSnapshotsPage({
token, token,
onUnauthorized, onUnauthorized,
}: AdminProjectSnapshotsPageProps) { }: AdminProjectSnapshotsPageProps) {
const [items, setItems] = useState<AdminProjectSnapshotEntry[]>([]); const [items, setItems] = useState<AdminProjectSnapshotEntry[]>([]);
const [nextCursor, setNextCursor] = useState<string | null>(null); const [nextCursor, setNextCursor] = useState<string | null>(null);
const [pageIndex, setPageIndex] = useState(1);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
// null 表示渠道还没确定:先读完可选渠道再发列表请求,避免用错渠道白跑一次。
const [channel, setChannel] = useState<string | null>(null);
const [channelOptions, setChannelOptions] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [hasLoaded, setHasLoaded] = useState(false); const [hasLoaded, setHasLoaded] = useState(false);
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const [downloadingKey, setDownloadingKey] = useState<string | null>(null); const [downloadingKey, setDownloadingKey] = useState<string | null>(null);
const listController = useRef<AbortController | null>(null); const listController = useRef<AbortController | null>(null);
const downloadController = useRef<AbortController | null>(null); const downloadController = useRef<AbortController | null>(null);
// 远端按游标分页且不给总数:第 N 页的起始游标只能由前 N-1 页依次返回,
// 因此按页记录已取得的游标,翻页只在这些游标之间移动。
const pageCursors = useRef<(string | null)[]>([null]);
const loadPage = useCallback( const loadPage = useCallback(
async (cursor: string | null = null) => { async (cursor: string | null, limit: number, page: number) => {
listController.current?.abort(); listController.current?.abort();
const controller = new AbortController(); const controller = new AbortController();
listController.current = controller; listController.current = controller;
@@ -54,23 +73,16 @@ export function AdminProjectSnapshotsPage({
try { try {
const response = await listAdminProjectSnapshots( const response = await listAdminProjectSnapshots(
token, token,
{ cursor, limit: 20 }, { cursor, limit, channel },
controller.signal, controller.signal,
); );
if (controller.signal.aborted) return; if (controller.signal.aborted) return;
setItems((current) => { setItems(response.items);
if (!cursor) return response.items;
const entries = new Map(
current.map((entry) => [snapshotKey(entry), entry]),
);
response.items.forEach((entry) =>
entries.set(snapshotKey(entry), entry),
);
return [...entries.values()];
});
setNextCursor(response.nextCursor); setNextCursor(response.nextCursor);
setPageIndex(page);
setHasLoaded(true); setHasLoaded(true);
} catch (error: unknown) { } catch (error: unknown) {
// 翻页或刷新失败时保留当前页,不把已看到的列表换成空表。
if (!controller.signal.aborted) if (!controller.signal.aborted)
handlePageError(error, onUnauthorized, setErrorMessage); handlePageError(error, onUnauthorized, setErrorMessage);
} finally { } finally {
@@ -80,22 +92,70 @@ export function AdminProjectSnapshotsPage({
} }
} }
}, },
[token, onUnauthorized], [token, onUnauthorized, channel],
); );
useEffect(() => { useEffect(() => {
// 换令牌或首次进入时取一次可选渠道;已选渠道保持不变,只在还没选时落到本部署渠道。
const controller = new AbortController();
void (async () => {
try {
const response = await getAdminProjectSnapshotChannels(
token,
controller.signal,
);
if (controller.signal.aborted) return;
setChannelOptions(response.channels);
setChannel((current) => current ?? response.defaultChannel);
} catch (error: unknown) {
if (controller.signal.aborted) return;
// 渠道列表失败不阻塞查询:不带渠道按本部署渠道查询,并提示失败原因。
handlePageError(error, onUnauthorized, setErrorMessage);
setChannel((current) => current ?? '');
}
})();
return () => controller.abort();
}, [token, onUnauthorized]);
useEffect(() => {
if (channel === null) return undefined;
pageCursors.current = [null];
setItems([]); setItems([]);
setNextCursor(null); setNextCursor(null);
setPageIndex(1);
setHasLoaded(false); setHasLoaded(false);
setDownloadingKey(null); setDownloadingKey(null);
void loadPage(); void loadPage(null, pageSize, 1);
return () => { return () => {
listController.current?.abort(); listController.current?.abort();
listController.current = null; listController.current = null;
downloadController.current?.abort(); downloadController.current?.abort();
downloadController.current = null; downloadController.current = null;
}; };
}, [loadPage]); }, [loadPage, pageSize, channel]);
function goToNextPage() {
if (!nextCursor) return;
pageCursors.current[pageIndex] = nextCursor;
void loadPage(nextCursor, pageSize, pageIndex + 1);
}
function goToPreviousPage() {
if (pageIndex <= 1) return;
void loadPage(
pageCursors.current[pageIndex - 2] ?? null,
pageSize,
pageIndex - 1,
);
}
function refreshCurrentPage() {
void loadPage(
pageCursors.current[pageIndex - 1] ?? null,
pageSize,
pageIndex,
);
}
async function downloadProject(entry: AdminProjectSnapshotEntry) { async function downloadProject(entry: AdminProjectSnapshotEntry) {
if (downloadController.current || entry.status === 'partial') return; if (downloadController.current || entry.status === 'partial') return;
@@ -106,6 +166,7 @@ export function AdminProjectSnapshotsPage({
try { try {
const archive = await downloadAdminProjectSnapshot( const archive = await downloadAdminProjectSnapshot(
token, token,
channel ?? '',
entry.userId, entry.userId,
entry.projectId, entry.projectId,
controller.signal, controller.signal,
@@ -140,19 +201,42 @@ export function AdminProjectSnapshotsPage({
setDownloadingKey(null); setDownloadingKey(null);
} }
// 渠道列表读取失败时至少保留当前渠道,避免选择框空掉后看不出在查哪个渠道。
const visibleChannelOptions = channelOptions.length
? channelOptions
: channel
? [channel]
: [];
return ( return (
<section className="admin-page admin-page-wide"> <section className="admin-page admin-page-wide">
<div className="admin-page-heading"> <div className="admin-page-heading">
<h2></h2> <h2></h2>
<button <div className="admin-action-row">
className="admin-secondary-button" <label className="admin-field admin-field-compact">
disabled={isLoading} <span></span>
type="button" <select
onClick={() => void loadPage()} aria-label="项目工程渠道"
> value={channel ?? ''}
<RefreshCcw size={17} aria-hidden="true" /> onChange={(event) => setChannel(event.target.value)}
<span>{isLoading ? '加载中' : '刷新'}</span> >
</button> {visibleChannelOptions.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</label>
<button
className="admin-secondary-button"
disabled={isLoading}
type="button"
onClick={refreshCurrentPage}
>
<RefreshCcw size={17} aria-hidden="true" />
<span>{isLoading ? '加载中' : '刷新'}</span>
</button>
</div>
</div> </div>
{errorMessage ? ( {errorMessage ? (
<div className="admin-alert" role="alert"> <div className="admin-alert" role="alert">
@@ -169,7 +253,7 @@ export function AdminProjectSnapshotsPage({
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th> ID</th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
@@ -187,7 +271,22 @@ export function AdminProjectSnapshotsPage({
<strong>{entry.projectName || entry.projectId}</strong> <strong>{entry.projectName || entry.projectId}</strong>
<small>{entry.projectId}</small> <small>{entry.projectId}</small>
</td> </td>
<td data-label="用户 ID">{entry.userId}</td> <td data-label="用户">
<div className="admin-inline-identity">
<div>
{projectOwnerDisplayName(entry)}
<small>
{entry.authorPublicUserCode?.trim() || '-'}
</small>
</div>
<AdminUserReferenceButton
token={token}
userId={entry.userId}
publicUserCode={entry.authorPublicUserCode}
onUnauthorized={onUnauthorized}
/>
</div>
</td>
<td data-label="同步时间"> <td data-label="同步时间">
<span> <span>
{new Date(entry.syncedAtMs).toLocaleString('zh-CN', { {new Date(entry.syncedAtMs).toLocaleString('zh-CN', {
@@ -239,23 +338,66 @@ export function AdminProjectSnapshotsPage({
{hasLoaded && items.length === 0 && !errorMessage ? ( {hasLoaded && items.length === 0 && !errorMessage ? (
<p className="admin-muted-text"></p> <p className="admin-muted-text"></p>
) : null} ) : null}
{nextCursor ? ( {hasLoaded ? (
<div className="admin-action-row"> <nav
<button className="admin-action-row admin-project-snapshot-pagination"
className="admin-secondary-button" aria-label="项目工程分页"
disabled={isLoading} >
type="button" <span className="admin-project-snapshot-pagination-info">
onClick={() => void loadPage(nextCursor)} {pageIndex}
> {items.length ? `,本页 ${items.length} 个项目` : ''}
{isLoading ? '加载中' : '加载更多'} </span>
</button> <div className="admin-action-row">
</div> <label className="admin-field admin-field-compact">
<span></span>
<select
aria-label="每页条数"
value={pageSize}
onChange={(event) => setPageSize(Number(event.target.value))}
>
{PAGE_SIZE_OPTIONS.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</label>
<button
aria-label="上一页"
className="admin-secondary-button"
disabled={isLoading || pageIndex <= 1}
title="上一页"
type="button"
onClick={goToPreviousPage}
>
<ChevronLeft size={17} aria-hidden="true" />
<span></span>
</button>
<button
aria-label="下一页"
className="admin-secondary-button"
disabled={isLoading || !nextCursor}
title="下一页"
type="button"
onClick={goToNextPage}
>
<span></span>
<ChevronRight size={17} aria-hidden="true" />
</button>
</div>
</nav>
) : null} ) : null}
</section> </section>
</section> </section>
); );
} }
function projectOwnerDisplayName(entry: AdminProjectSnapshotEntry) {
return (
entry.authorDisplayName?.trim() || entry.authorPublicUserCode?.trim() || '-'
);
}
function snapshotKey(entry: AdminProjectSnapshotEntry) { function snapshotKey(entry: AdminProjectSnapshotEntry) {
return `${entry.userId}/${entry.projectId}`; return `${entry.userId}/${entry.projectId}`;
} }
@@ -0,0 +1,155 @@
// @vitest-environment jsdom
import { describe, expect, it } from 'vitest';
import {
attachCoverFiles,
buildTemplateImportFormData,
deriveTemplateUploadRow,
parseTemplateTags,
sanitizeTemplateId,
TEMPLATE_IMPORT_MAX_BATCH,
type TemplateUploadRow,
validateTemplateUploadRows,
} from './adminAgcTemplateUploadModel';
function zipFile(name: string) {
return new File([new Uint8Array([0x50, 0x4b, 0x03, 0x04])], name, {
type: 'application/zip',
});
}
function coverFile(name: string) {
return new File([new Uint8Array([0x89, 0x50, 0x4e, 0x47])], name, {
type: 'image/png',
});
}
function row(zipName: string, patch: Partial<TemplateUploadRow> = {}) {
return { ...deriveTemplateUploadRow(zipFile(zipName)), ...patch };
}
describe('sanitizeTemplateId', () => {
it('lowercases, keeps whitelisted characters and drops traversal', () => {
expect(sanitizeTemplateId('Cocos Empty 2D.zip')).toBe('cocos-empty-2d');
expect(sanitizeTemplateId('../../evil.zip')).toBe('evil');
expect(sanitizeTemplateId('a..b.zip')).toBe('a.b');
expect(sanitizeTemplateId('__hidden__')).toBe('hidden__');
expect(sanitizeTemplateId(`${'x'.repeat(90)}.zip`)).toHaveLength(64);
});
});
describe('deriveTemplateUploadRow', () => {
it('starts from safe defaults so a batch only needs covers attached', () => {
const derived = row('my-template.zip');
expect(derived.id).toBe('my-template');
expect(derived.title).toBe('my-template');
expect(derived.runtime).toBe('html');
expect(derived.templateVersion).toBe('0.1.0');
expect(derived.entry).toBe('index.html');
expect(derived.coverFile).toBeNull();
});
});
describe('attachCoverFiles', () => {
it('matches covers by template id and ignores non-image files', () => {
const rows = [row('alpha.zip'), row('beta.zip')];
const covers = [
coverFile('alpha.png'),
coverFile('beta.webp'),
coverFile('notes.txt'),
];
const next = attachCoverFiles(rows, covers);
expect(next[0]?.coverFile?.name).toBe('alpha.png');
expect(next[1]?.coverFile?.name).toBe('beta.webp');
});
it('keeps an already matched cover when the new selection has no partner', () => {
const rows = [row('alpha.zip', { coverFile: coverFile('alpha.png') })];
const next = attachCoverFiles(rows, [coverFile('other.png')]);
expect(next[0]?.coverFile?.name).toBe('alpha.png');
});
});
describe('validateTemplateUploadRows', () => {
it('accepts a complete batch', () => {
const rows = [
row('alpha.zip', { coverFile: coverFile('alpha.png') }),
row('beta.zip', { coverFile: coverFile('beta.png'), runtime: 'cocos' }),
];
expect(validateTemplateUploadRows(rows)).toEqual({});
});
it('reports missing cover, duplicate id and invalid fields per row', () => {
const rows = [
row('alpha.zip'),
row('alpha.zip', { coverFile: coverFile('alpha.png'), key: 'second' }),
row('gamma.zip', {
coverFile: coverFile('gamma.png'),
runtime: 'docker',
templateVersion: 'Bad Version',
entry: '../escape.js',
title: ' ',
}),
];
const errors = validateTemplateUploadRows(rows);
expect(errors['alpha.zip']).toContain('缺少封面');
expect(errors.second).toContain('ID 在本批次内重复');
expect(errors['gamma.zip']).toContain('名称必须是 1-80 个字符');
});
it('rejects a batch larger than the server limit', () => {
const rows = Array.from(
{ length: TEMPLATE_IMPORT_MAX_BATCH + 1 },
(_, index) =>
row(`template-${index}.zip`, {
coverFile: coverFile(`template-${index}.png`),
}),
);
const errors = validateTemplateUploadRows(rows);
expect(Object.keys(errors)).toHaveLength(TEMPLATE_IMPORT_MAX_BATCH + 1);
expect(errors['template-0.zip']).toContain('单批最多上传');
});
});
describe('buildTemplateImportFormData', () => {
it('writes manifest field names and attaches zip / cover per row', () => {
const rows = [
row('alpha.zip', {
coverFile: coverFile('alpha.png'),
tags: '起步, 起步 2d',
title: ' Alpha ',
}),
row('beta.zip', { coverFile: coverFile('beta.png') }),
];
const form = buildTemplateImportFormData('a'.repeat(64), rows);
const manifest = JSON.parse(String(form.get('manifest')));
expect(manifest.expectedRevision).toBe('a'.repeat(64));
expect(manifest.templates).toHaveLength(2);
expect(manifest.templates[0]).toMatchObject({
id: 'alpha',
title: 'Alpha',
tags: ['起步', '2d'],
zipField: 'zip_0',
coverField: 'cover_0',
});
expect(manifest.templates[1]).toMatchObject({
id: 'beta',
zipField: 'zip_1',
coverField: 'cover_1',
});
expect((form.get('zip_0') as File).name).toBe('alpha.zip');
expect((form.get('cover_1') as File).name).toBe('beta.png');
});
it('parses tags with dedupe and caps them at the server limit', () => {
expect(parseTemplateTags(' a, ab c ')).toEqual(['a', 'b', 'c']);
const many = Array.from({ length: 20 }, (_, index) => `t${index}`).join(
',',
);
expect(parseTemplateTags(many)).toHaveLength(16);
});
});

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