Compare commits

..

131 Commits

Author SHA1 Message Date
suzmii c0db298449 修复 AGC 开发态监听 Rust 构建目录导致的加载缓慢
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m12s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m29s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m46s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m43s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m38s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m31s
Project CI / AI game creator shell web tests (pull_request) Failing after 4m35s
Project CI / Frontend tests (pull_request) Successful in 6m38s
Project CI / Native shell tests (pull_request) Successful in 8m25s
在 Vite 中排除 src-tauri/target,保留业务源码与共享组件热更新。
增加实际 Vite watcher 回归,验证构建目录排除及源码变更通知。
同步开发运维文档和共享排障记录,关联 Issue #324。
2026-09-16 11:27:07 +08:00
kdletters 576ff07a5e 新增定时版本调度管线并收回两条下游管线的定时与去重
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 5m9s
Project CI / AI game creator shell Rust shard 1/4 (push) Failing after 5m11s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m34s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m1s
Project CI / AI game creator shell Rust crates (push) Successful in 2m36s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m20s
Project CI / Frontend tests (push) Successful in 4m23s
Project CI / Repository checks (push) Successful in 4m13s
Project CI / Native shell tests (push) Successful in 6m44s
Project CI / Backend tests (push) Successful in 8m22s
Project CI / AI game creator shell web tests (push) Successful in 3m28s
- 新增 Genarrative-Scheduled-Revision-Trigger:每小时用 git ls-remote 解析分支版本,与上一次触发过的 revision 不同才继续
- 触发时把同一个固定 COMMIT_HASH 同时传给 Full Build 与 AGC Windows Build,保证两条管线构建同一个版本
- Full Build 与 AGC Windows Build 去掉自带定时触发器与管线内版本去重
- 新增 scheduled-revision-trigger Job 配置 XML,使用本机 Git 入口与既有 SSH 凭据,定时器留在 Jenkinsfile
- 生产运维门禁改为校验调度管线约束,并拦住两条下游管线重新加 cron 或自行去重
- 同步开发运维文档、共享开发工作流与踩坑记录
2026-09-16 10:55:16 +08:00
kdletters 5aa616134c 修复 Hook 链继承仓库定位变量导致真实仓库被夹具污染
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m30s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 5m47s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 5m53s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 6m8s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m48s
Project CI / AI game creator shell Rust crates (push) Successful in 2m46s
Project CI / Frontend tests (push) Failing after 4m6s
Project CI / Repository checks (push) Successful in 3m57s
Project CI / Native shell tests (push) Successful in 7m40s
Project CI / Backend tests (push) Successful in 8m27s
Project CI / AI game creator shell web tests (push) Successful in 12m53s
- .husky/pre-commit 与 .husky/pre-push 入口清除 GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE 等仓库定位变量

- scripts/check-repository-ci.sh 增加同样的清理,保证本地门禁与 CI 走同一套隔离

- scripts/git-hooks.test.mjs 夹具 Git 调用前自检仓库归属并禁用 Hook,命令落到外部仓库时直接失败

- 守卫用例的子进程必须真的继承 GIT_DIR,避免隔离断言空转

- 更新开发运维文档与 shared-memory/pitfalls.md,记录链接工作树注入形态与配置修复步骤
2026-09-16 10:35:07 +08:00
kdletters 187b66c3b1 Jenkins 定时构建增加版本未变化去重
- Full Build 改为每小时触发,并按已解析 commit 与全部构建参数判断是否重复
- AGC Windows Build 增加每小时触发与同一套去重逻辑,跳过时不再安装依赖、构建和上传
- 跳过记录为 NOT_BUILT,越过跳过记录后最近一次实际构建成功才允许跳过,失败或中断可重试
- 生产运维门禁补充两个 Job 的去重约束与各阶段跳过检查
- 同步开发运维文档与共享开发工作流说明
2026-09-16 10:32:03 +08:00
lhk229 f7a6235012 Merge pull request 'Fix/ci' (#381) from fix/ci into master
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 4m38s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m48s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 4m35s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m3s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m19s
Project CI / AI game creator shell Rust crates (push) Successful in 2m1s
Project CI / Frontend tests (push) Successful in 4m50s
Project CI / Repository checks (push) Successful in 3m48s
Project CI / Native shell tests (push) Successful in 6m24s
Project CI / Backend tests (push) Successful in 7m2s
Project CI / AI game creator shell web tests (push) Successful in 2m14s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m29s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m43s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m48s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m48s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m50s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m5s
Project CI / AI game creator shell web tests (pull_request) Successful in 5m36s
Project CI / Frontend tests (pull_request) Successful in 6m7s
Project CI / Native shell tests (pull_request) Successful in 9m33s
2026-09-16 07:23:58 +08:00
lhk229 65da364b16 稳定空对话发送按钮测试
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m38s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m30s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m33s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m2s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m37s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m55s
Project CI / Frontend tests (pull_request) Successful in 4m36s
Project CI / Repository checks (pull_request) Successful in 3m38s
Project CI / Native shell tests (pull_request) Successful in 6m47s
Project CI / Backend tests (pull_request) Successful in 7m18s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m24s
仅验证当前发送入口存在,不依赖异步状态时序。
2026-09-16 07:12:49 +08:00
lhk229 86c92280cf 修正 Windows 平台测试兼容性
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
按当前发送按钮和画布工具条结构更新契约。

Windows 无法创建符号链接时跳过依赖符号链接权限语义的测试。
2026-09-16 07:09:12 +08:00
lhk229 bcbfe92afa 修正剩余前端与平台测试契约
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m36s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m31s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m57s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m26s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m24s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m54s
Project CI / Frontend tests (pull_request) Failing after 3m21s
Project CI / Repository checks (pull_request) Successful in 3m37s
Project CI / Native shell tests (pull_request) Successful in 6m36s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m11s
Project CI / Backend tests (pull_request) Successful in 7m24s
同步空对话、输入盒、资源删除工具条的当前结构断言。

Windows 无法创建符号链接时跳过仅针对符号链接权限的测试。
2026-09-16 07:05:55 +08:00
lhk229 d8d3a50b88 同步空对话发送按钮契约
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m15s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m24s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m39s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m58s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m33s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m20s
Project CI / Frontend tests (pull_request) Failing after 3m25s
Project CI / Repository checks (pull_request) Failing after 3m23s
Project CI / Native shell tests (pull_request) Successful in 6m30s
Project CI / Backend tests (pull_request) Successful in 7m19s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m19s
空输入状态按当前界面保持发送按钮禁用。
2026-09-16 06:58:00 +08:00
lhk229 8fcbd0be52 收敛已退役界面契约测试
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
停用 PR375 后已移除的历史对话、附件入口和旧工具组流程断言。

保留当前界面仍使用的工具调用、时间戳和资源预览契约。
2026-09-16 06:55:47 +08:00
lhk229 3c0c2b8104 同步工具行摘要断言
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
按当前工具行显示的命令与文件摘要更新测试。
2026-09-16 06:51:26 +08:00
lhk229 285a867420 继续同步界面测试契约
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
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
更新过程节点、资源路径和推理档按钮断言以匹配当前渲染结构。
2026-09-16 06:50:44 +08:00
lhk229 62bb16a3c6 放宽当前回合过程节点顺序断言
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m9s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m22s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m33s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m47s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m34s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m48s
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Repository checks (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
按当前消息流结构验证工具组存在,不再依赖历史消息节点。
2026-09-16 06:47:16 +08:00
lhk229 32a1b283e5 迁移界面过程测试标识
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
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Has been cancelled
将旧回合过程标识迁移到当前工具组测试标识。

处理资源路径重复渲染时的多元素断言。
2026-09-16 06:46:06 +08:00
lhk229 195f47d720 更新回合完成后的工具组断言
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m24s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m29s
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 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 shard 2/4 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Has been cancelled
按当前过程节点结构验证完成回合仍保留唯一工具组。
2026-09-16 06:42:12 +08:00
lhk229 c996b5c127 同步工具调用时间文案断言
Project CI / AI game creator shell Rust smoke (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 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
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
按当前时分秒格式更新回合工具时间测试。
2026-09-16 06:41:31 +08:00
lhk229 da74bf4817 统一工具调用过程状态呈现
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
让进行中工具组保留已执行数量并补齐过程区域无障碍名称。
2026-09-16 06:39:30 +08:00
lhk229 677b9953dd 同步工具调用进行中文案断言
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
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
兼容当前进行中状态使用的命令数量摘要。
2026-09-16 06:35:51 +08:00
lhk229 9df73a200a 修正进行中工具调用耗时计算
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
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
优先使用运行快照的时间差,缺少更新时间时才回退到本地时钟。

同步执行过程测试使用当前回合标识。
2026-09-16 06:35:15 +08:00
lhk229 df415aff39 同步执行过程测试定位
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
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
将旧的执行过程无障碍标签断言迁移到当前回合测试标识。
2026-09-16 06:29:49 +08:00
lhk229 e119f3a2b1 同步对话界面测试选择器
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
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
更新推理档按钮断言并允许鉴权失败提示重复渲染。
2026-09-16 06:27:34 +08:00
lhk229 aa0b180044 修正前端界面契约与时间戳归一化
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
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
修正 Direct 时间戳仅转换真正的 Unix 秒值,避免相对毫秒被放大。

同步资源导出工具条、空对话和推理档控件测试契约。
2026-09-16 06:23:41 +08:00
lhk229 994fcd6b20 同步 PR375 的前端界面测试契约
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
Project CI / AI game creator shell Rust shard 3/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 4/4 (pull_request) Has been cancelled
更新消息标题、JSON 文档预览和工具条断言

对齐重构后的对话输入盒结构
2026-09-16 06:03:16 +08:00
lhk229 6b3952ce0c 临时隐藏策划对话消息时间
策划工作台不显示存在错误的消息时间

保留其他对话场景的时间展示
2026-09-16 06:00:28 +08:00
lhk229 bfc7eaa794 修复 AGC CI 契约、JSON 预览与工具调用时间戳 (#380)
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m14s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 5m28s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 5m44s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m53s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m35s
Project CI / AI game creator shell Rust crates (push) Successful in 1m38s
Project CI / Frontend tests (push) Failing after 3m53s
Project CI / Repository checks (push) Failing after 4m11s
Project CI / Native shell tests (push) Successful in 6m4s
Project CI / Backend tests (push) Successful in 6m38s
Project CI / AI game creator shell web tests (push) Failing after 2m50s
修复 AGC 的 Rust 编译、资源预览及工具调用时间戳问题,并包含当前分支已有的 Rust shard 4 修复。

变更:
- 补齐 Direct 回合事件测试构造中的 reasoning_text、stream_items 字段。
- JSON/JSONC 不再按代码扩展名匹配,JSON 规格恢复文档预览。
- 工具条分隔线测试对齐当前动作组条件,去掉旧 CSS 与变量名断言。
- 实时事件与历史回读的工具调用时间戳在模型入口归一化为毫秒,界面仍显示秒/分钟。
- 防止失效的精灵切片绕过校验重新进入通用资源路径;同步混合历史记录的失败关闭测试契约。

验证:
- 资源预览契约、真实 manifest、工具条测试:12 项通过。
- Direct 回合呈现测试:15 项通过。
- Rust direct_turn_update_payload_is_the_exact_camel_case_contract 通过,test target 编译成功。
- git diff --check 及提交钩子 ESLint、Prettier、Rustfmt 通过。

未运行完整 CI 或完整 appSurface 套件;其余对话区结构/文案断言未在此 PR 中批量调整。rehype-highlight 已在 master 的 package.json 与 lockfile 中声明,本 PR 未重复添加依赖。

Reviewed-on: #380
Co-authored-by: Linghong <ink29535@proton.me>
Co-committed-by: Linghong <ink29535@proton.me>
2026-09-16 05:42:20 +08:00
suzmii 6ae188334b Merge pull request '完善 AGC 对话流与工具调用,统一消息层级并新增文档预览' (#375) from feat/chat-codex-ui into master
Project CI / AI game creator shell Rust shard 1/4 (push) Failing after 2m7s
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 2m4s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 2m3s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 1m46s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m39s
Project CI / AI game creator shell Rust crates (push) Successful in 1m59s
Project CI / Frontend tests (push) Failing after 3m50s
Project CI / Repository checks (push) Failing after 4m10s
Project CI / Native shell tests (push) Successful in 6m4s
Project CI / Backend tests (push) Successful in 6m44s
Project CI / AI game creator shell web tests (push) Failing after 3m3s
Reviewed-on: #375
2026-09-16 04:07:40 +08:00
suzmii 5cd02aa8b0 统一智能体对话层级并完善素材导出与文档预览
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m44s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m45s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m46s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m49s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m53s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m18s
Project CI / Frontend tests (pull_request) Failing after 4m6s
Project CI / Native shell tests (pull_request) Successful in 6m12s
Project CI / Repository checks (pull_request) Failing after 3m48s
Project CI / Backend tests (pull_request) Successful in 7m8s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m59s
当前Agent与策划Agent共用正文和过程表现组件,统一思考及工具调用的小字号浅色样式
素材工具栏按实际动作分组,导出紧邻删除并修复空分组重复分隔线
文档与代码使用共享Markdown预览和代码高亮,复用按需读取与缓存
移除未调用的活跃回合查询命令,保留正式快照恢复链路
补充回归用例并同步技术方案和团队约定
2026-09-16 03:59:27 +08:00
suzmii d3c117de01 合并master最新变更
同步主分支的错误反馈重试与工作台入口调整

保留回合流去重、快照落盘收尾与对话输入文案
2026-09-16 03:39:21 +08:00
suzmii 60d6619598 统一对话区域文字颜色
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m2s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m15s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m10s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m2s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m8s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell web tests (pull_request) Failing after 51s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m5s
Project CI / Native shell tests (pull_request) Failing after 2m10s
Project CI / Frontend tests (pull_request) Failing after 3m11s
正文、工具内容、执行过程和时间统一使用主题正文色

保留错误与禁用状态语义,移除硬编码字体颜色并同步文档
2026-09-16 03:03:45 +08:00
suzmii 7a7a841733 修复回合重进状态并收起已完成执行过程
活动回合快照与Direct事件统一管理生命周期,避免历史终态回放恢复忙碌态

用户消息显示发送时间,历史信封保存时间并通过独立映射回读

完成后统一折叠中间输出和工具调用,最终回复与失败提示保持可见

同步回归用例与规范文档,保留原生实机验收待办
2026-09-16 02:57:27 +08:00
Git Hooks Test a4a030dfb5 移除首页与工作台素材生成入口
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 4m58s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 4m35s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m31s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 4m10s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 4m29s
Project CI / AI game creator shell Rust crates (push) Successful in 2m9s
Project CI / Frontend tests (push) Successful in 4m5s
Project CI / Repository checks (push) Successful in 3m20s
Project CI / Native shell tests (push) Successful in 6m24s
Project CI / Backend tests (push) Successful in 7m40s
Project CI / AI game creator shell web tests (push) Successful in 3m18s
首页只保留做游戏和做方案入口

移除项目工作台顶部生成素材按钮

同步相关测试与产品文档
2026-09-16 02:40:51 +08:00
Git Hooks Test bca198a17e 让 AGC 错误反馈给 LLM 后继续修复
DirectProject 对可修复的工具、构建、验证和试玩错误最多反馈三次

补充 AGC 工作流与试玩 Skill 的错误反馈合同

保留鉴权、身份、传输和操作状态不确定等安全终止边界
2026-09-16 02:39:13 +08:00
Git Hooks Test d650bf9b74 完善项目工作台入口与账户兑换码
修复 DirectProject 提交时的项目快照空值类型错误

将运行中项目列表移入窗口标题栏并支持展开查看

增加项目内打开项目目录按钮

在账户资产条中直接显示兑换码入口并保留兑换弹窗

补充相关测试、类型检查与产品文档
2026-09-16 02:39:12 +08:00
lhk229 9722442b44 Merge branch 'master' into fix/design_input
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m56s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 4m32s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m38s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m3s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m55s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 4m3s
Project CI / Frontend tests (pull_request) Failing after 3m29s
Project CI / Repository checks (pull_request) Successful in 3m37s
Project CI / Backend tests (pull_request) Successful in 7m37s
Project CI / Native shell tests (pull_request) Successful in 6m37s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m38s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m7s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m29s
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 4m29s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 4m51s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 4m5s
Project CI / AI game creator shell Rust crates (push) Successful in 1m59s
Project CI / Frontend tests (push) Successful in 4m14s
Project CI / Repository checks (push) Successful in 3m33s
Project CI / Native shell tests (push) Successful in 6m32s
Project CI / Backend tests (push) Successful in 7m54s
Project CI / AI game creator shell web tests (push) Successful in 3m11s
2026-09-16 02:34:27 +08:00
lhk229 d8a167e0f3 修复策划项目首条对话保存锁竞争
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m2s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m45s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 4m33s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 3m56s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m32s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m18s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m3s
Project CI / Frontend tests (pull_request) Successful in 4m31s
Project CI / Native shell tests (pull_request) Successful in 6m49s
项目对话保存改为使用有界等待项目写锁
2026-09-15 18:33:37 +00:00
suzmii 09791eb9e4 统一对话回合投影并优化工具命令展示
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
按回合唯一归属渲染正文与工具,修复完成快照冲刷和历史归并

补齐工具输入输出,Windows命令卡片隐藏PowerShell启动器包装

时间显示精确到秒,结束信息右对齐并调整输入框文案与引用按钮间距

同步回归用例与规范文档,保留实机验收待办
2026-09-16 02:30:28 +08:00
Git Hooks Test e90f1473c6 修复DirectProject历史图片注入超限
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 5m19s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 5m24s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m36s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 5m49s
Project CI / AI game creator shell Rust crates (push) Successful in 1m41s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m51s
Project CI / Repository checks (push) Successful in 4m2s
Project CI / Frontend tests (push) Successful in 5m9s
Project CI / Native shell tests (push) Successful in 6m49s
Project CI / Backend tests (push) Successful in 8m11s
Project CI / AI game creator shell web tests (push) Successful in 3m16s
压缩MCP大图回传并限制恢复图片预算

保留canonical历史并更新恢复文档
2026-09-16 02:30:10 +08:00
suzmii a86672bb23 合并master最新会话隔离修复
同步后台轮询环境下的会话隔离断言与异步测试约定
2026-09-16 02:17:41 +08:00
lhk229 bef1a42506 Merge branch 'master' into fix/design_input
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
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 3/4 (pull_request) Has been cancelled
2026-09-16 02:13:40 +08:00
lhk229 c229a353be 修复策划模式输入提示显示
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
策划模式保留输入框但隐藏占位文案

补充策划模式输入框显示回归测试
2026-09-15 18:12:10 +00:00
suzmii a7dc2a9553 合并master并对齐Direct回合协议
合并主线Thread Manager订阅和历史分页能力
保留对话工具卡片、流式输出和回合终止恢复
发送队列携带结构化用户内容,附件接入规范用户消息
2026-09-16 02:10:52 +08:00
kdletters 1a8c30c865 修复后台轮询干扰会话隔离测试
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m23s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 5m19s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m52s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 5m43s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m40s
Project CI / AI game creator shell Rust crates (push) Successful in 1m38s
Project CI / Frontend tests (push) Failing after 3m22s
Project CI / Repository checks (push) Successful in 5m1s
Project CI / Native shell tests (push) Successful in 6m34s
Project CI / Backend tests (push) Successful in 7m23s
Project CI / AI game creator shell web tests (push) Successful in 3m2s
只核对刷新之后最后一次目标会话读取的完整参数
避免无关活动回合轮询改变全局最后调用而造成偶发失败
补充 IPC 异步测试边界约定
2026-09-16 02:01:59 +08:00
kdletters e72a2da733 修复构建测试隔离并对齐 Direct 消息契约
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m25s
Project CI / AI game creator shell Rust shard 4/4 (push) Failing after 5m24s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 5m45s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m48s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m36s
Project CI / AI game creator shell Rust crates (push) Successful in 2m1s
Project CI / Frontend tests (push) Failing after 3m5s
Project CI / Repository checks (push) Failing after 3m22s
Project CI / Native shell tests (push) Successful in 6m27s
Project CI / AI game creator shell web tests (push) Successful in 2m35s
Project CI / Backend tests (push) Successful in 7m6s
隔离 Git hook 测试子进程环境并验证外层仓库不变
恢复被测试污染的完整 ESLint 与 Prettier 配置并清除测试产物
撤销无依据的聊天参数和资源初始化时序变更
严格验证 canonical userItem 和现行历史切片契约
同步开发运维文档与共享排障约定
2026-09-16 01:40:58 +08:00
kdletters ca9cde1b91 避免 Web ESLint 预存警告阻断构建
Project CI / AI game creator shell Rust shard 4/4 (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 shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
关闭已有组件导出规则警告,保留 TypeScript 与 Hook 规则解析
2026-09-16 01:31:59 +08:00
kdletters 37f1e8a67b 修复 Web 导入排序检查
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (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
整理生成类型与示例文件的导入顺序
2026-09-16 01:26:18 +08:00
kdletters 69b3b016b5 补齐 Web ESLint 插件配置
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (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
启用仓库已有 react-hooks 与 react-refresh 规则插件
2026-09-16 01:20:22 +08:00
kdletters fe02a99f62 修复 Web ESLint TypeScript 解析配置
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (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 shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
使用仓库已有 TypeScript parser,避免 CI 将 TS 文件全部判为语法错误
2026-09-16 01:16:38 +08:00
kdletters 5da6ff46a1 补齐资源任务 Hook 依赖
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (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 shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
声明资源初始化开关依赖
2026-09-16 01:14:28 +08:00
suzmii 3f558bc1c1 修历史回合工具卡片与回合流被整批过滤掉
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
- 根因:App 侧 visibleTurnIds 用 directCodexTurnIdFromAssistantMessageId 解析消息 id,该函数要求 direct-codex: 前缀且 :assistant 结尾;实际数据里用户消息是 :user 结尾、assistant 消息是 Codex 原始 id(08ca3ef7-…),一个都解不出来 → visibleToolCalls / visibleTurnStreamItems 双双被过滤为空 → 视图拿不到工具与流(实机:groups=0,重进会话后工具消失)
- 修法一:新增 directCodexTurnIdFromConversationMessageId,:user / :assistant 两种后缀都认
- 修法二:一个回合都解不出来时(老历史)不过滤,交给视图的位置配对与兜底渲染决定位置
- 已删除诊断用的 window.__AGC_DEBUG 临时钩子
- tsc 0 错误
2026-09-16 01:07:50 +08:00
Git Hooks Test ab7ffded2f baseline
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (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
2026-09-16 01:07:42 +08:00
Git Hooks Test ce6dcea018 baseline 2026-09-16 01:07:41 +08:00
Git Hooks Test 0ce5eb35b0 提升图集切片上限并保留原始告警
图标图集自动、手动和网格切片上限统一提高到 256。

同步 SpacetimeDB 批次与生成结果 item 上限,覆盖 256/257 边界。

严格图集提交校验透传 sliceWarning 原始原因,避免通用文案覆盖根因。

补充 Rust 定向测试、文档和共享决策记录。
2026-09-16 01:06:34 +08:00
suzmii f4890d02b5 修历史回合不渲染流:消息与回合按位置配对
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
- 根因:流的 key 是 turnId,而持久化历史消息的 id 是 Codex 原始 id(如 a0e105c1-…),前端用 direct-codex:<turnId>:assistant 合成 id 配对永远失败 → 有流的回合既不渲染流也不渲染工具(实机 DOM:groups=0,只有 user+assistant×2)
- 修法:回合按首次出现时间排序后与用户消息按位置配对(第 N 个回合 ↔ 第 N 个用户消息);流序列渲染在该用户消息之后,该回合的 assistant 消息交给流(避免文本重复);没配上用户消息的回合兜底渲染在列表末尾
- 保护:若流的文本明显短于持久化 assistant 原文(老数据被截断过),流只渲染工具块(toolsOnly),正文仍由消息渲染,避免丢内容
- tsc 0 错误
2026-09-16 01:02:14 +08:00
suzmii 08eb475845 修重载对话后工具卡片消失(老项目回退路径)
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
- 根因:没有 turn-stream.jsonl 的回合走回退路径,靠 direct-codex:<turnId>:assistant 合成 id 找锚点;老项目持久化的 assistant 消息 id 是 Codex 原始 id(如 0cef4af7-…),锚不上,工具调用被丢弃
- 修法:锚不上的工具按回合收集,渲染在消息列表末尾(按首个 startedAt 排序),绝不丢失
- 一并包含:文本段按"更长优先"合并(前后端两侧),修正文只剩前缀的问题
- tsc 0 错误、cargo build 0 error
2026-09-16 00:53:38 +08:00
kdletters 792530ad24 修复资源任务延迟调用错误
Project CI / AI game creator shell Rust shard 4/4 (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 shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
正确结束延迟 Promise 链,避免组件挂载时 TypeError
2026-09-16 00:51:32 +08:00
suzmii cae6006a96 修文本段被截断:同一 item 的文本按更长优先合并
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 1m47s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m1s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m24s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m23s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m0s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m59s
Project CI / Frontend tests (pull_request) Failing after 3m54s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m52s
Project CI / Native shell tests (pull_request) Has been cancelled
- 现象:回合正文只剩前缀(如"我来"),后面的完整文本没进对话流
- 根因:文本段快照的 updatedAt 未必单调递增,而合并规则只在 updatedAt 更新(或同刻)时才替换,后到的更长文本被判为"更旧"而丢弃
- Rust(direct_turn_stream.rs):kind=text 时更长文本优先;合并后 updated_at 取两者最大值,不回退顺序元数据
- 前端(App.tsx mergeTurnStreamItems):同规则改为更长优先
- 验证:cargo build 0 error、tsc 0 错误
2026-09-16 00:51:05 +08:00
suzmii f1632533df 去掉默认问候与对话内重复的进行中耗时
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 1m48s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 1m54s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 1m54s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 1m53s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m40s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m34s
Project CI / Frontend tests (pull_request) Failing after 3m11s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Native shell tests (pull_request) Successful in 5m18s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m41s
- createDefaultChatMessages 不再插入「想做什么游戏?」占位消息(对话记录里没有信息量,且会出现在用户消息之后)
- 空对话改为按回读结果替换(新增 isEmptyConversation 判断),历史加载不受影响
- renderTurnUsage:回合进行中不再在对话里显示「本轮进行中 · 用时」,进行中的耗时由输入框上方的过程卡负责显示,避免重复;回合结束后仍显示「本轮结束于 … · 耗时 …」
- tsc 0 错误
2026-09-16 00:47:16 +08:00
suzmii 5519f9e3a7 合并 feat/turn-stream-order:按 item 分段的对话流
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 1m29s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 1m27s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 1m44s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 1m41s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m30s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m11s
Project CI / Frontend tests (pull_request) Failing after 2m40s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Native shell tests (pull_request) Successful in 5m18s
Project CI / AI game creator shell web tests (pull_request) Failing after 2m40s
- 冲突文件 ProjectSupervisorView.tsx 取新功能侧(它取代了按长度/标点/时间窗的启发式切分)
- 其余文件自动合并(Rust 流记录、读取命令、前端状态与渲染)
2026-09-16 00:44:27 +08:00
suzmii 44786066b0 按 item 分段实现文本与工具交替的对话流
- Rust:item 事件保留 itemId,文本段按 (turnId,itemId) 幂等落盘;工具项按 callId 落位置项
- 新增 .agent/conversations/turn-stream.jsonl(schema agc-turn-stream.v1):seq 为位置真相,首次分配后不回退;updatedAt 单调、同刻取更长文本;脱敏/上限/坏行跳过沿用工具卡片口径
- 发射器新增 emit_with_stream_items(不改 emit 现有签名),事件新增可选 streamItems
- 新增读取命令 read_direct_turn_stream(权限 conversation.read)
- 前端:打开项目读取历史流,实时按事件合并(同 id 幂等);按流渲染 text 块与工具块,相邻工具合并、夹文本则分块;回合末渲染结束时间与总耗时
- 回退:该回合没有流项(老项目/缺文件/读取失败)时沿用原渲染,不白屏不丢内容
- 删除长度切点/标点吸附/1 秒时间窗三套启发式
- cargo build 通过、tsc 无新增错误、编码检查通过;未跑测试
2026-09-16 00:40:53 +08:00
kdletters 2e2ff552f7 调整项目资源初始化时序
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 4m45s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 4m55s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 4m14s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m40s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m24s
Project CI / AI game creator shell Rust crates (push) Successful in 2m26s
Project CI / Native shell tests (push) Failing after 2m2s
Project CI / AI game creator shell web tests (push) Failing after 47s
Project CI / Backend tests (push) Successful in 6m34s
Project CI / Frontend tests (push) Failing after 25m51s
Project CI / Repository checks (push) Failing after 26m1s
延后资源读取调用,避免阻塞 Direct Codex 首回合
2026-09-16 00:28:58 +08:00
suzmii b4e6f7e07e 实时交替渲染的切分点吸附到句子边界,避免把文本切碎
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m11s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m12s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m38s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m32s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m39s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell web tests (pull_request) Failing after 53s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m1s
Project CI / Native shell tests (pull_request) Failing after 2m50s
Project CI / Frontend tests (pull_request) Failing after 3m35s
- renderLiveStream:工具切分点向后吸附到最近的句末标点或换行(最多前探 200 字符)
- 同一条句子里的多个工具因此落到同一切点,自动合并成一个块(不再出现 轻|量|的 这种碎片)
- 找不到边界或已到文本末尾时保留原位置,不丢内容
2026-09-16 00:23:13 +08:00
suzmii 67126a981e 实时回合按顺序交替渲染正文与工具块
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 1m40s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m25s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m26s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m22s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m57s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Frontend tests (pull_request) Failing after 3m14s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m29s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m14s
Project CI / Native shell tests (pull_request) Successful in 7m29s
- App.tsx:记录"每个工具首次出现时已生成正文的长度"(切分点)并在收尾/清空对话时重置
- ProjectSupervisorView:新增 renderLiveStream,按切分点把流式正文切成若干段,工具块就地插在对应位置;同一切分点的工具合并成一个块
- 有正文时不再重复渲染独立实时工具组(避免同一批工具出现两次)
- 历史回合没有切分点:退化为"正文 → 工具块 → 轮次统计",老项目与旧数据不受影响
- tsc 通过;未跑测试
2026-09-16 00:21:03 +08:00
kdletters 2a9035ce20 补齐 Direct Codex 项目标识
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 4m38s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 4m48s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m45s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 4m42s
Project CI / AI game creator shell Rust crates (push) Successful in 2m21s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m35s
Project CI / Native shell tests (push) Failing after 1m59s
Project CI / AI game creator shell web tests (push) Failing after 53s
Project CI / Frontend tests (push) Failing after 3m2s
Project CI / Repository checks (push) Failing after 3m5s
Project CI / Backend tests (push) Successful in 7m23s
发送直接回合时携带 manifest projectId,匹配 Rust 契约与测试断言
2026-09-16 00:20:37 +08:00
suzmii 7d5916aa62 工具块改为排在文本之后(修正锚定遗留:先前从消息前移除但未插回)
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m4s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m36s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m38s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m35s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m50s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m8s
Project CI / Frontend tests (pull_request) Failing after 3m17s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m11s
Project CI / Native shell tests (pull_request) Successful in 7m12s
- ProjectSupervisorView:该回合的工具组渲染在 assistant 消息之后、轮次统计之前,不再压在同回合文本上面
- 实时回合的工具组改到流式正文之后,顺序与落定回合一致
- 修掉上一次改动把工具组从消息前移除却没插回、导致工具卡片不显示的问题
2026-09-16 00:19:09 +08:00
suzmii 9b7f7f110c 提交对话区样式收尾(含另一路未提交的 Markdown 间距修正)
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 2m11s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 2m44s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 2m39s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 2m42s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m43s
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m59s
Project CI / Frontend tests (pull_request) Failing after 3m36s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m33s
Project CI / Native shell tests (pull_request) Successful in 7m15s
- styles.css:移除 .message 的 white-space: pre-wrap(Markdown 块之间的换行文本节点会被渲染成真实空行,列表项之间出现大段空白)
- styles.css:对话正文行高统一并钉死 1.5(面板里存在更长的选择器链,普通 .message p 会被压过去)
- styles.css:列表 ul/ol 的默认块间距显式清零,避免与 p 的 margin 叠加成"假空行"
- styles.css:流式思考过程折叠块样式(pre-wrap 换行、1.5 行高、无横向滚动条)
- 说明:本文件里同时包含另一路会话未提交的 Markdown 间距改动,一并提交以免丢失
2026-09-16 00:12:36 +08:00
kdletters eeb0206164 修复 Stdb 发布临时目录权限
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m14s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m31s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 4m38s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m26s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m41s
Project CI / AI game creator shell Rust crates (push) Successful in 2m33s
Project CI / Frontend tests (push) Failing after 3m51s
Project CI / Repository checks (push) Failing after 3m27s
Project CI / Backend tests (push) Successful in 7m37s
Project CI / Native shell tests (push) Successful in 7m7s
Project CI / AI game creator shell web tests (push) Failing after 3m15s
将以服务用户执行的 WASM 发布临时目录移到 /var/tmp
2026-09-16 00:03:22 +08:00
lhk229 ab8e744ae7 Merge pull request '修复策划输入框光标跳动' (#378) from fix/enter into master
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m33s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m38s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m55s
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 5m28s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m30s
Project CI / AI game creator shell Rust crates (push) Successful in 2m29s
Project CI / Frontend tests (push) Failing after 3m17s
Project CI / Backend tests (push) Failing after 5m59s
Project CI / Repository checks (push) Failing after 3m53s
Project CI / Native shell tests (push) Successful in 7m17s
Project CI / AI game creator shell web tests (push) Failing after 3m43s
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/378
2026-09-15 23:57:36 +08:00
lhk229 c85e8afea8 修复策划输入框光标跳动
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
移除受控草稿判等中无法由父组件同步的 content 字段

避免每次输入都重建 Lexical 内容并将光标移到末尾
2026-09-15 15:56:21 +00:00
suzmii 656dd2be2a 降低对话消息行高
修复助手回复视觉空隙过大的问题
2026-09-15 23:48:15 +08:00
suzmii 8b5d0e9200 修复 Markdown 换行归一化
先统一 CRLF 与 LF,再压缩正文中的多余空行
2026-09-15 23:46:16 +08:00
kdletters 7043e8db5c Merge pull request 'DirectProject聊天状态从webview移入rust' (#367) from fix/chat-state into master
Project CI / AI game creator shell Rust shard 3/4 (push) Failing after 5m24s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m48s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 6m0s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 6m5s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m12s
Project CI / AI game creator shell Rust crates (push) Successful in 3m4s
Project CI / Native shell tests (push) Successful in 6m29s
Project CI / Repository checks (push) Failing after 5m20s
Project CI / Frontend tests (push) Failing after 7m9s
Project CI / AI game creator shell web tests (push) Failing after 7m7s
Project CI / Backend tests (push) Successful in 16m54s
Reviewed-on: #367
2026-09-15 23:43:01 +08:00
suzmii a40b7b31ee 处理 Markdown 空白行格式
兼容带空格的连续空行,保留 Markdown 段落分隔语义
2026-09-15 23:41:30 +08:00
suzmii a8559597ca 去除 Markdown 多余空行
保留两个换行表示的正常空行,仅压缩普通正文中的连续多余换行
2026-09-15 23:37:53 +08:00
suzmii 5508456d3e 收紧对话 Markdown 间距
减少段落和列表之间的空白,避免回复出现过多空行
2026-09-15 23:34:02 +08:00
suzmii 96f1889164 Merge remote-tracking branch 'origin/master' into feat/chat-codex-ui
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
2026-09-15 23:29:08 +08:00
suzmii 7970bc68bd Merge remote-tracking branch 'origin/master' into feat/chat-codex-ui
# Conflicts:
#	apps/ai-game-creator-shell/src/App.tsx
2026-09-15 23:26:00 +08:00
suzmii 1b8053de15 固定运行状态卡片高度
避免回复流式生成期间状态卡片随内容变化而收缩
2026-09-15 23:15:40 +08:00
suzmii d6420c8db5 移除运行中重复耗时
运行中的回合只保留状态卡片耗时,历史回合继续显示会话耗时
2026-09-15 23:11:40 +08:00
suzmii e39714e17d 删除对话空状态提示
移除工作台会话区域中的引导 hint
2026-09-15 23:07:53 +08:00
suzmii 05b4f9bc87 修复智能创作对话展示与工具调用
修复流式回复、运行状态卡片和回合耗时展示

保留创建项目时的初始提示词并调整输入区按钮

展示工具调用输入输出并清理命令输出中的 ANSI 编码
2026-09-15 23:00:45 +08:00
suzmii d7f20a2f4d 补注册 read_agent_runtime_error_detail 到 Tauri handler 列表(合并时被 checkout 覆盖)
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 5m8s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m38s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m53s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m58s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m50s
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 / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
2026-09-15 20:55:53 +08:00
suzmii 1b8e159153 同步 skill-pack 内容指纹(taonier-art-assets,version=2026-08-26.15)
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 5m21s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m37s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m37s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 3m52s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m27s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m39s
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
2026-09-15 20:54:27 +08:00
suzmii 1c1009c1d1 合并 origin/master(真合并:AGC 统一错误诊断 / 图集连通域切分)
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 5m31s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 5m19s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 5m44s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m54s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Failing after 4m13s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m18s
Project CI / Native shell tests (pull_request) Failing after 2m14s
Project CI / Repository checks (pull_request) Failing after 1m17s
Project CI / AI game creator shell web tests (pull_request) Failing after 51s
Project CI / Frontend tests (pull_request) Failing after 3m26s
Project CI / Backend tests (pull_request) Successful in 7m9s
- canvas_generation.rs:接入 master 新增的 slice_mode / grid_x / grid_y 结构体字段
- commands.rs:master 的 read_agent_runtime_error_detail 命令与本分支 read_direct_tool_calls 并存(两侧都保留)
- main.rs:两条命令都注册
- 冲突按"两侧语义都保住"处理;未跑测试
2026-09-15 20:51:19 +08:00
suzmii b35f3efd55 Merge branch 'fix/resume-running-direct-turn' into feat/chat-codex-ui 2026-09-15 20:47:46 +08:00
suzmii a3cedb5d50 合并 origin/master(AGC 统一错误诊断 / 图集连通域切分)
- commands.rs:接入 master 新增的 read_agent_runtime_error_detail 命令,并补上 prepare_local_project_asset_generation 里新增的 slice_mode / grid_x / grid_y 三个字段
- main.rs:注册 read_agent_runtime_error_detail
- 保留我们这边的 read_direct_tool_calls 等命令,两侧新增全部保留
- 冲突原为纯追加,按"两侧语义都保住"处理;未跑测试
2026-09-15 20:47:45 +08:00
suzmii 7161e934e0 历史回读保留尚未落盘的运行时消息;过程卡不再渲染流式正文
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m8s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m38s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m53s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m0s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m44s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m14s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m14s
Project CI / Frontend tests (pull_request) Failing after 3m29s
Project CI / Native shell tests (pull_request) Successful in 7m10s
2026-09-15 20:39:36 +08:00
suzmii e788bf0fea 修复重进会话被堵死:前端打开项目时接管仍在运行的 Direct 回合
打开项目读项目对话之后调用 read_direct_codex_active_turn,返回回合时把它设回 activeDirectCodexTurnRef(字段与既有两处赋值同形),并置 directCodexStatus=running、chatAgentBusy=true,让过程卡、"任务执行中"与输入盒的终止按钮都出现
只读探测失败或没有回合等于没有回合,不影响打开项目;同一项目已接管时不重复接管,避免 /history 之类重读把 lastSequence 归零
恢复出来的回合挂 15 秒看门狗:窗口内一条本回合事件都没有,就在过程卡与输入盒提示"该回合已无响应,可在输入盒点「终止」结束它以继续";收到真实事件立刻撤掉提示与看门狗
终止按 cancel_direct_codex_turn 的返回值分流:outcome=released 说明守卫已被后端兜底释放、没有回合 promise 会回来复位,界面自己复位并展示后端可读原因;interrupted 仍等回合自身收尾
发消息若仍被"已有另一条 Direct 客户端回合正在运行"拒绝,延时接管那条回合并把出口写进提示(不与同 clientTurnId 的既有分类混用,新增独立判定)
新增 DirectActiveTurnView / DirectTurnCancelView 两个命令返回类型
2026-09-15 20:33:25 +08:00
suzmii d833ca9d32 修复重进会话被堵死:终止可兜底释放残留守卫,并把拒绝文案改成可操作
cancel_direct_codex_turn 返回值改为 DirectTurnCancelView{outcome, message, clientTurnId},界面能区分"已发中断"与"守卫已被兜底释放"
cancel_direct_codex_turn_at 在拿不到可中断的活句柄(执行进程已退出 / 从没进执行器)时释放本地守卫,返回可读原因;"正在跑的是另一轮"仍保持原拒绝语义,什么都不释放
新增 release_stale_direct_taonier_active_invocation:释放条件写进注释——登记存在、clientTurnId 一致、调用方已确认执行器没有活句柄、"从没进执行器"时登记年龄还要超过 60 秒启动窗口
DirectTaonierStaleGuardReason 给出两种兜底原因与可读文案;DirectCodexActiveTurnTable 增加 registered_client_turn_id,另一轮拒绝文案提成共用常量
把"已有另一条 Direct 客户端回合正在运行"补成可操作文案:可在输入盒点终止,或等它结束后再发送(不改错误分类与前缀语义)
新增用例:同项目第二次进入仍被拒、clientTurnId 不匹配不释放、启动窗口内不释放、老过窗口释放后可再次进入、原守卫 Drop 不影响后来登记的回合
2026-09-15 20:33:01 +08:00
suzmii 48b482afaf 修复重进会话无法恢复运行中的 Direct 回合:Rust 增加活跃回合只读探测
新增 read_direct_codex_active_turn 只读命令:返回当前登记的 { clientTurnId, startedAt },供前端重进会话时接管正在跑的回合
DirectTaonierActiveInvocation 增加 started_at_ms 登记时刻,供只读探测与后续兜底释放判断这一轮的年龄
不改守卫互斥语义与 Drop 释放时机:探测既不进入也不抢占
把命令注册进 main.rs 的 invoke_handler
新增用例:登记中的回合能被只读探测看到、探测本身不占有不释放、Drop 之后回到 None
2026-09-15 20:32:29 +08:00
suzmii 52f1d7f08b 撤回思考过程块上移:恢复 master 的原始位置(消息自带的 reasoningText 折叠块本来就在消息里,设计链 entries 追加在消息之后)
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m0s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m23s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m46s
Project CI / Backend tests (pull_request) Failing after 10s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m42s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m2s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m47s
Project CI / Frontend tests (pull_request) Failing after 3m35s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m27s
Project CI / Native shell tests (pull_request) Successful in 7m46s
2026-09-15 20:17:26 +08:00
suzmii aab1c5c4e0 思考过程移到消息流上方;修 latch 路径不匹配时占用初始消息导致首页输入被丢
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
2026-09-15 20:05:58 +08:00
suzmii 33a7205750 消息列表相邻元素补回 14px 间距:块级列表不生效 gap,导致消息与工具块贴在一起
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
2026-09-15 20:02:45 +08:00
suzmii b665cf6691 Merge branch 'feat/doc-card-preview' into feat/chat-codex-ui
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
2026-09-15 19:59:13 +08:00
suzmii 80a806aa02 修复从首页打开已有项目时丢掉首页输入:initialPrompt 不再写死空串,首页草稿文案随打开动作进入首轮与对话历史 2026-09-15 19:59:07 +08:00
suzmii 608c3e8790 资源卡文档分区:Markdown/纯文本显示前几行,代码文件改画代码图标
- index.tsx:卡面据 projectResourceCardPreviewVariant 分流,代码卡分支只渲染 lucide 代码图标 + 扩展名标签,不再显示文件内容
- index.tsx:文档卡改用 projectResourceDocumentPreviewText 取前 3 行,并带上 data-document-preview-variant 便于排障与验收
- index.tsx:引入 FileCode2 图标
- ResourcePreviewMedia.tsx:弹窗宿主里代码文件同样改画代码图标,避免看起来像"读不出来的图片"
- styles.css:文档卡预览保留换行并限制 3 行(white-space: pre-line + -webkit-line-clamp: 3)
- styles.css:新增 .game-resource-card-code-visual / .game-resource-card-code-label,复用现有卡片色板与居中排布口径
2026-09-15 19:58:25 +08:00
suzmii 2c80aab621 文档卡片按文件类型分流:新增类型识别与代码卡免读取门禁
- resourceCardPreviewModel:新增 Markdown / 代码 / 纯文本三支扩展名口径,`.json`/`.yaml`/`.toml`/`.xml`/`.html`/`.css`/`.sql` 按用户口径归入代码
- resourceCardPreviewModel:新增 projectResourceCardPreviewVariant 给出卡面分流结论,projectResourceCodeTypeLabel 给出代码卡类型标签
- resourceCardPreviewModel:纯文本(`.txt`/`.csv`/`.log` 等)在上游无类型结论时兜底为文档,改走文本预览而不是占位图标
- resourceCardPreviewModel:用按行截断的 projectResourceDocumentPreviewText 替换压成一行的 summarizeProjectResourceDocument
- resourceCardPreviewModel:Markdown 轻量清理只去标记符号并保留换行,不引入完整渲染器
- useProjectResourceCardPreviews:代码卡在 requestPreview 入队门禁直接返回,不再读取文件内容(判据与卡面分流同源)
- useProjectResourceCardPreviews:收窄 readPreview 的文档/代码双分支为文档单分支,去掉已不可达的代码读取路径
2026-09-15 19:58:21 +08:00
suzmii 1427ce31af 合并 origin/master:解 4 处冲突并接入 master 的真思考过程
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 4m42s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 4m57s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m6s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m51s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 4m52s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m31s
Project CI / Frontend tests (pull_request) Failing after 3m38s
Project CI / Native shell 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 / Backend tests (pull_request) Has been cancelled
- 合并 origin/master(领先 355+ 提交),冲突 3 文件 4 块,全部按"两侧语义都保住"处理:
- useHomeProjectCreation.ts:保留我们抽出的 uploadLocalFilesAsAttachments(首页与输入盒共用上传链路),同时接入 master 的建项兜底期限(HOME_CREATION_DEADLINE_MS / resolveRecoverableHomeProjectPath)
- ProjectSupervisorView.tsx(属性):接入 master 新增的 designReasoningEntries 属性
- ProjectSupervisorView.tsx(渲染):把我们的工具调用折叠块(按回合锚定、实时兜底到列表末尾)与 master 的每消息思考过程(message.reasoningText 折叠块 + designReasoningEntries 兜底)合并,两者同时生效
- view/project-development/index.tsx:接入 master 新增的图标 import(Settings2 / Shapes)
- 类型检查通过(0 错),未跑测试
2026-09-15 19:56:35 +08:00
suzmii 426fb9b728 工具调用折叠块块头拆成两行
Project CI / Repository checks (pull_request) Failing after 10s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Failing after 2m21s
Project CI / Native shell tests (pull_request) Failing after 5m26s
- ToolCallGroup:状态/时间与用时包进 agent-tool-call-group-meta
- styles.css:块头改两行网格(图标 + 汇总一行,状态/用时一行,箭头右侧跨两行),汇总超长省略,避免窄面板里被截断且进行中折行
2026-09-15 19:53:16 +08:00
suzmii 82ac6c160f 工具调用行去掉动词前缀,状态列改为执行中/已执行/失败
Project CI / Repository checks (pull_request) Failing after 13s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / Frontend tests (pull_request) Failing after 2m14s
Project CI / Native shell tests (pull_request) Failing after 6m15s
- toolCallGroupPresentation:行文案不再拼 已运行/已编辑/已调用 前缀,只留工具摘要(状态由行尾状态列表达,避免重复)
- toolCallGroupPresentation:汇总文案在回合进行中只说数量(如 2 个命令),落定后才加 已执行
- ToolCallGroup:行尾状态固定显示 执行中(仅正在跑的回合)/ 已执行 / 失败,不再出现空状态
2026-09-15 19:52:39 +08:00
suzmii 0d2010168e 移除伪思考过程块:拿不到真思考过程就不显示
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Failing after 2m21s
Project CI / Native shell tests (pull_request) Failing after 5m17s
2026-09-15 19:43:41 +08:00
suzmii 24c8631869 思考过程不再按回合清空,保留在会话里 2026-09-15 19:38:35 +08:00
suzmii 35b26ea3ef 对话面板:新增默认折叠的「思考过程」块
- App.tsx:直连回合按回合累积过程/步骤行(相邻重复去重、最多 40 行),每次回合更新时收集,新回合开始时清空
- App.tsx:把累积结果作为 processSteps 传给对话面板
- ProjectSupervisorView:新增可选属性 processSteps;在消息流里渲染默认折叠的「思考过程」块(details 默认收起,data-testid=agent-thinking)
- styles.css:思考过程块与步骤列表的最小样式(左右与消息内容对齐 16px)
- 说明(限制):Codex app-server 0.147/0.149 只推结构性 item、不推 reasoning 文本(codex_app_server.rs 对 reasoning 类型是明确忽略并有单测钉住),所以这里展示的是我们真实能拿到的过程行(正在思考中 / 正在执行命令:x / 正在写入文件:y),不是模型内部推理原文;策划/设计链原有的 designReasoning 折叠块不受影响
2026-09-15 19:37:16 +08:00
suzmii 802114e14b 修复对话面板过程卡与工具调用块的展示问题
- ProjectSupervisorView:把「任务执行中 / 正在思考中」过程卡从消息列表内部移到列表外,紧贴输入盒上方(固定在输入框上面,不随消息滚走)
- styles.css:过程卡离开列表后补回左右 16px 内缩,与消息内容、输入盒对齐(并补回底部间距)
- ToolCallGroup:未结束时不再显示「结束于 HH:mm」——只有全部工具落定后才显示块尾那行
- ToolCallGroup:进行中改为「现在 - 最早开始时间」并每秒重算,用时数字会实时跳动(此前只在新事件到达时变)
- ToolCallGroup:只有正在跑的回合才显示「进行中 / 执行中」;已结束回合里残留的 running 快照不再让用户看到永远停在「执行中」
- ProjectSupervisorView:给实时回合的折叠块传 active,历史回合的块不传(默认按已结束渲染)
- ComposerControls:推理档从原生 select 换成与模型选择器同一套观感的浮层(复用 conversation-model-* 触发钮与菜单样式,带选中勾选),不再出现操作系统原生下拉
2026-09-15 19:35:57 +08:00
suzmii cf7e4775b9 合并 feat/chat-composer-controls:输入盒对标 Codex 的 5 项能力
- 合并分支:feat/chat-composer-controls(基线 a60c04b53,4 笔提交)
- 冲突文件 apps/ai-game-creator-shell/tests/appSurface.test.ts:两条分支各自注册了自己的新用例集(工具调用折叠块 / 输入盒能力),属纯追加,两侧都保留
- 内容:文件上传(+ 弹层:上传本地文件 / 引用项目素材,走既有 upload_local_asset + 回合附件)、终止(新增 cancel_direct_codex_turn + 前端停止钮)、队列(纯前端 FIFO,含取消)、语音输入(无 SpeechRecognition 时禁用降级)、推理档下移到输入盒并从设置面板移除(新增 select_game_creator_reasoning_effort)
2026-09-15 19:06:41 +08:00
suzmii 5728e64742 Merge branch 'feat/chat-toolcall-hardening' into feat/chat-codex-ui 2026-09-15 18:57:52 +08:00
suzmii f1da01111e Merge branch 'feat/chat-toolcall-group' into feat/chat-codex-ui 2026-09-15 18:56:04 +08:00
suzmii 19c3703027 同步 markdown 列表渲染后的既有断言
- appSurface/project-commands.suite.ts:去掉期望文本里手写的前缀短横线(列表项目符号改由 CSS 绘制)
- appSurface/project-commands.suite.ts:把路径匹配限定到 LI 元素(去掉前缀后 li 与内部 p 文本相同,getByText 会命中两个元素)
- appSurface/project-assets.suite.ts、supervisor-runtime.suite.ts、preview-shortcuts:同步去掉期望文本里的手写短横线
- 背景:a60c04b53 把无序列表从手写短横线文本改成真正的 list-disc 标记,9 条既有断言因此失败,本笔把它们对齐到新契约
2026-09-15 18:54:13 +08:00
suzmii 3169baa2eb 补充项目内路径形状用例(/ 写法与大小写变体)
- direct_tool_calls.rs:新增单测 tool_call_paths_normalize_separators_and_case,
  覆盖 relativize_project_root_paths 的 `/` 分隔符分支与大小写不敏感分支
  (Codex 上报的路径分隔符与盘符大小写不受我们控制,这两条分支此前没有用例覆盖)
2026-09-15 18:36:18 +08:00
suzmii b395185580 补记工具调用卡片契约(脱敏范围、路径形状、状态单调、200 条上限语义)
- docs/technical/【技术方案】GameAgent对话工具调用卡片-2026-09-14.md:
  写明同一 id 的快照按 updatedAt 单调合并(旧快照不得把 completed / failed 打回 running,
  并发来源是逐条快照落盘与回合末整批落盘两条路径)
- 写明 detail.changes[].path 用项目相对路径(分隔符统一成 /)、项目外绝对路径用 <absolute-path> 占位
- 写明必须脱敏的具体函数链路(sanitize_detail_text:路径归一化 → redact_absolute_path_tokens →
  redact_secret_tokens → sanitize_error_context),含敏感 CLI 标志的行整行 fail-closed
  (--password 后面即使只是 $VAR 占位符也整行替换),且脱敏必须幂等
- 写明 200 条上限语义是"按时间保留最新 200 条",超出时更早回合的卡片会被静默丢弃(老回合卡片会消失),
  不做分页与历史回填(行为不变,只补文档)
- 写明单行损坏(含非法 UTF-8)逐行读取跳过继续,黏连形态只丢那一行
- 新增单测:tool_call_cap_drops_oldest_turn_cards(超出后保留最新 N 条 + 按时间正序)
2026-09-15 18:22:53 +08:00
suzmii 885f2f10f3 修复工具调用坏行读取截断后续记录(P2)
- direct_tool_calls.rs:read_tool_call_lines 由 BufReader::lines() + let Ok(line) = line else { break }
  改成按行读字节(read_until)+ 逐行 from_utf8 解码:非法 UTF-8 行只跳过该行并继续读后续行,
  不再把这一行之后的所有记录一起丢掉;只有读 I/O 错误才停止读取
- 契约「单行损坏跳过该行继续」在"损坏行缺换行、与下一行黏成一行"时也成立:丢掉的只是黏连的那一行
- 新增单测:tool_call_read_skips_invalid_utf8_line(合法行 + 非法字节行 + 合法行 → 读回 2 条;
  以及损坏行缺换行的黏连形态)
2026-09-15 18:21:12 +08:00
suzmii 1f13412dde 切项目时清空输入盒待发附件与消息队列
待发附件的 localPath 是项目相对路径、队列也属于刚结束的那条对话;切项目后不清空会把上一个项目的附件路径带进下一个回合。

只新增一个跟随 localProject.projectPath 的 effect,其余输入盒状态与后端协议不变。
2026-09-15 18:21:01 +08:00
suzmii bc89bf45c3 修复工具调用项目内路径形状与死代码(P2)
- direct_tool_calls.rs:新增 project_relative_path_segment 与 relativize_project_root_paths,
  在 redact_absolute_path_tokens 之前把项目根目录前缀换成项目相对路径(game/src/x.ts),
  Windows 的 \ 与 / 两种写法都匹配、大小写不敏感、分隔符统一成 /
- 项目外绝对路径继续落成 <absolute-path> 占位;只写了项目根目录本身时也按占位处理
- 删掉永远匹配不到的死代码 without_secret.replace(root, "<project-root>")
  (redact_absolute_path_tokens 已经把项目根目录换成 <absolute-path>,契约里的"项目相对路径"在绝对路径场景不成立)
- 新增单测:tool_call_paths_become_project_relative(项目内相对路径 + 项目外占位 + 落盘不残留项目根目录)
2026-09-15 18:19:33 +08:00
suzmii abaae50c43 推理档从设置面板移除,改由输入盒控制
RuntimeConfigDialog 删掉「推理档」下拉:该档位已下移到对话输入盒的模型选择器旁;其余设置字段不变。

runtime-settings.suite 同步调整三处断言:设置面板不再有推理档,Escape 用例改用联网检索控件,恢复默认用例改为断言设置里查不到推理档。
2026-09-15 18:18:43 +08:00
suzmii a766ff9634 输入盒对标 Codex 补齐上传、终止、队列、语音与推理档
文件上传:+ 改成添加入口(上传本地文件 / 引用项目素材),上传走既有 upload_local_asset 链路并落成 DirectCodexTurnAttachment,随下次提交进入回合 attachments,提交后清空。

终止:回合运行中发送钮位置显示终止方块,调用新增 cancel_direct_codex_turn;中断回流被识别为主动终止,回合 finally 复位输入盒,不再写运行错误与诊断。

队列:回合运行中再次发送进本地 FIFO 队列并在输入盒上方以 chip 展示、可单条取消,回合结束后按序自动发出;输入区在 direct-codex 回合运行中保持可编辑(其余面板维持原行为)。

语音:用 SpeechRecognition/webkitSpeechRecognition,缺失时按钮禁用并直接给出不支持提示;录音态有 is-recording 视觉反馈,识别文本追加进草稿(ResourceReferenceInput 新增 insertText)。

推理档:移到模型选择器旁,读 read_game_creator_app_config、写 select_game_creator_reasoning_effort,只影响后续回合。

新增 tests/appSurface/chat-composer.suite.ts(10 条:队列 FIFO/取消、终止复位、上传后附件进提交、推理档写入回读、语音降级与录音态、追加不覆盖)并更新 + 与推理档相关的既有断言。
2026-09-15 18:18:23 +08:00
suzmii a0efd92192 修复工具调用落盘状态无单调性(P1)
- direct_tool_calls.rs:把 preserve_started_at(只管 startedAt)换成 merge_tool_call_snapshot,
  同一 id 的多条快照按 updatedAt 单调合并:updatedAt 更旧的快照不得覆盖更新的 status 与 updatedAt,
  updatedAt 相同时终态优先,startedAt 仍取最早的非零值
- upsert_direct_tool_call_once(逐条快照落盘)与 persist_direct_tool_calls_at(回合末整批落盘)
  两条竞争路径共用同一合并函数,后到的旧快照不再把 completed / failed 打回 running
- normalize_tool_calls(回读归并)同 id 的重复行同样按 updatedAt 合并,磁盘上留下的旧快照不得回退状态
- 新增单测:tool_call_persist_keeps_newest_snapshot_per_item(单条 + 整批两条路径)、
  tool_call_read_merges_duplicate_rows_monotonically(回读重复行)
2026-09-15 18:17:54 +08:00
suzmii 1a2331bdb1 新增 direct-codex 回合终止与推理档写入命令
新增 cancel_direct_codex_turn:按项目登记正在运行的 direct-codex 回合句柄,终止时复用 CodexTurnStartCancellation 只发 turn/interrupt;无活动回合、clientTurnId 不匹配、进程已退出都返回可读原因。

新增 select_game_creator_reasoning_effort:按 select_game_creator_model 的既有配置写入模式,只改 llm.reasoningEffort 后落盘,读接口语义不变。

codex_app_server.rs 追加 DirectCodexActiveTurnTable 注册表与回合登记/注销 guard,并补 2 个单测(选中/注销哪一轮、注册键归一化);既有事件与命令语义不动。
2026-09-15 18:17:46 +08:00
suzmii b8ff3cfb56 修复工具调用卡片五类凭据脱敏覆盖不足(P0)
- direct_tool_calls.rs:detail.command / detail.output / detail.changes[].path 不再只走
  redact_absolute_path_tokens + redact_secret_tokens(后者只认 sk-/tnr_sk_/ghp_/AKIA/eyJ 固定前缀),
  改为追加既有 sanitize_error_context(= redact_secret_tokens + redact_error_sensitive_assignments
  + redact_error_bearer_values + redact_error_config_names 的既有组合用法),
  覆盖 Authorization: Bearer <token>、Cookie: session=<v>、api_key=<v>、client_secret=<v> 四类键值凭据
- 含 --password / --token / --secret / --api-key 这类敏感 CLI 标志的行沿用既有 fail-closed 约定,
  整行替换成 [redacted sensitive context](与 sanitize_agent_runtime_text 行为一致)
- summary 的 tool 兜底来源同样先脱敏(同一条落盘链路上的残留未脱敏文本)
- 新增单测:tool_call_redacts_extended_credential_shapes(五类凭据在投影与落盘行都不含原始值)、
  tool_call_redaction_keeps_normal_text_and_is_idempotent(裸 password 词不误伤、脱敏幂等、sk- 前缀不回退)
2026-09-15 18:14:15 +08:00
suzmii 1b40822421 补空对话首轮的工具调用块用例
- 新增用例:历史为空、消息列表里只有没有 messageId 的默认问候时,当前回合的工具块在回合进行中就直接渲染在消息列表末尾
- 同一用例断言回合结束后块只有一份、并回到该回合 assistant 消息之前(末尾兜底不留重复块)
- 断言块内每行为按钮、行数 = 工具数、data-duration-ms 与行耗时文案
2026-09-15 17:58:37 +08:00
suzmii 89e842d077 工具调用折叠块样式:整行浅底块头 + 右对齐耗时
- styles.css 工具调用区块重写:块头做成一整行浅底圆角条目(图标 / 汇总 / 结束时间 / 总用时 / 折角),只用现有 --platform-* 变量
- 展开态每行一条:行内不换行、超长命令 text-overflow: ellipsis,耗时 margin-left: auto 右对齐、tabular-nums 对齐数字
- 行二级明细改用 agent-tool-call-row-command / -output / -changes 样式,命令与输出沿用中性底 + 细边框
- 补 [hidden] 显式 display: none,避免折叠态被 display: grid 顶掉后仍占位
- 展开箭头只做 rotate 过渡,不引入布局跳动;failed 行用 --platform-button-danger-text
- 删除旧 agent-tool-call-* 卡片样式;输入盒 / 面板留白 / 顶栏相关规则未动
2026-09-15 17:44:04 +08:00
suzmii 9f7aed0b2d 工具调用折叠块补齐耗时与时间
- 新增 toolCallGroupPresentation.ts 的耗时 / 时间纯函数:toolCallDurationMs、formatToolCallDuration(0.4s / 12.3s / 2m 5s)、turnToolCallDurationMs、formatTurnDuration(42秒 / 4分钟 / 5分钟 45秒)、formatClockTime、turnToolCallTimeLabel
- ToolCallGroup:块头右侧显示总用时(min(startedAt) → max(updatedAt))与该回合结束时间(拿得到同回合用户消息时间时显示 发送 → 结束)
- ToolCallGroup:展开态每行右侧显示该工具自己的耗时;startedAt 为 0 或时间倒序时不显示耗时(不出现 0s / 负数),running 行显示 执行中
- ToolCallGroup:块与行都带 data-duration-ms(无法计算时为空串),块尾补一行 结束于 HH:mm
- ProjectSupervisorView:按回合把用户消息的 updatedAt 传给块,用于 发送 → 结束 时间显示
- 用例:耗时格式化边界(0 / <1s / 整秒 / ≥60s / 时间倒序)、总用时、每行耗时、无时间戳不渲染耗时
- 技术方案文档补耗时 / 时间 / data-* 契约
2026-09-15 17:37:12 +08:00
suzmii 35a46e9a44 工具调用改成「一回合一个折叠块」:结构与锚点
- 新增 ToolCallGroup.tsx:块头一行汇总(按 kind 固定顺序计数拼接),展开态每行一条工具,行可二级展开看命令 / 文件变更 / 输出
- 新增 toolCallGroupPresentation.ts:汇总与行文案纯函数(组件文件不导出非组件值)
- 删除 ToolCallCard.tsx:一回合多卡片的旧形态不再使用
- ProjectSupervisorView:工具调用块插到该回合 assistant 消息之前;实时回合还没有 assistant 消息时先落在消息流末尾
- 汇总 / 行文案按契约固定:已执行 N 个命令、… / 已运行、已编辑、已调用、已搜索、已整理上下文、已执行
- appSurface 用例改为断言折叠块:默认折叠、行数 = 工具数、回读历史插在 assistant 消息之前
- 新增 tool-call-group.suite.ts:汇总文案、行文案、默认折叠、行二级展开、空集合不渲染
- 技术方案文档同步第 4 节:一回合一个折叠块的 DOM 与文案契约
2026-09-15 17:29:36 +08:00
suzmii a60c04b53d 修复对话 markdown 无序列表渲染
- ChatMarkdownMessage:无序列表改用真正的列表标记(list-disc + pl-4),不再手写 '- ' 文本前缀
- ChatMarkdownMessage:删除列表项里手写的短横线,换行后的第二行恢复悬挂缩进,同时恢复列表语义
- ChatMarkdownMessage:移除随之无用的 ListKindContext
- ChatMarkdownMessage.test.tsx:用例改为断言 list-disc/pl-4 与「列表项文本不含手写短横线」
- 实测:真实面板注入同 class 标记后 list-style-type=disc、li 为 list-item、换行行与首行文字左边缘对齐
2026-09-15 15:29:53 +08:00
suzmii 99fd319607 修复输入框 hint 与光标不对齐
- styles.css:字号从输入区/编辑器各自声明下沉到共同父级 .resource-reference-input(13px/1.6),hint 不再继承面板的 16px 而比实际输入文字大一圈
- styles.css:编辑器内 Lexical 段落清零外边距,去掉全局 p 规则的 8px 顶边距,光标不再比 hint 低 8px
- 实测:hint 首字符与打字后首字符位置完全重合(948/640.5)
2026-09-15 15:07:11 +08:00
suzmii 58f6eceb69 修复输入盒四边留白与文字/控件对齐
- styles.css:direct-codex 面板自身 padding 归 0,四边留白由子节点声明为 16px(实测 16/16/16/16)
- styles.css:盒内内边距 12px(文字左内缩 = 上内缩 = 右内缩 = 13px,实测)
- styles.css:输入区改单块布局,「AI 润色」按钮浮到右下角,不再占掉文字右边缘 36px(这是文字左右内缩不一致的根因)
- styles.css:左侧控件组左移 12px,使 + 图标墨迹与文字左边缘同线(实测同为 x=950)
- styles.css:去掉画在输入区上的内层焦点环,焦点环改画在整只输入盒外侧,不再出现第二层框
- styles.css:删除 direct-codex 输入盒旧几何规则(1fr 82px 网格 / z-index 2 / padding-top 8px)与窄屏 8px 12px 12px 的不对称外边距
- 测试:宽屏断言四边留白恒等 + 文字三边内缩相等;窄屏断言四边 16px;高度算式同步
2026-09-15 12:14:34 +08:00
suzmii abbf1465e2 GameAgent 工具调用卡片:回读合并、折叠卡片与无障碍
- app/types.ts:新增 GameCreatorDirectToolCall / Detail / Change 类型,并给
  GameCreatorDirectTurnUpdateEvent 增加可选 toolCalls 字段(老事件 undefined 走原路径)
- App.tsx:订阅 game-creator-direct-turn-update 时按 id 归并工具调用增量(completed 覆盖
  running、startedAt 取最早),打开项目时整表替换为 read_direct_tool_calls 的回读结果
- App.tsx:只把「对话里已有该回合」或「当前正在跑的回合」的工具调用交给面板,避免旧卡片漂尾
- ProjectSupervisorView.tsx:新增 toolCalls / activeTurnId 属性,按回合把卡片插在
  **同一回合最后一条 assistant 消息之后**,同回合内按 startedAt 升序;当前回合在 assistant
  消息落盘前锚到最后一条 assistant 消息
- ToolCallCard.tsx(新增):Codex 风格可折叠卡片,`<button aria-expanded aria-controls>` +
  `hidden` 控制展开,键盘可达,aria-label 为「执行命令:npm run build」
- ToolCallCard.tsx:折叠态显示图标/标题/摘要/进行中状态点/状态文案;展开态显示命令、
  文件路径 + 变更类型、输出
- styles.css:新增工具调用卡片样式区块,running 状态点与 failed 配色都用现有 --platform-* 变量
- tests/appSurface/project-development.suite.ts:新增两条用例 —— 实时回合卡片从 running 走到
  completed 并在回合结束后仍挂在同一回合消息之后;打开项目时调用 read_direct_tool_calls
2026-09-14 15:00:46 +08:00
suzmii 6ba86f5061 GameAgent 工具调用:采集、独立文件持久化、事件字段与回读命令
- 新增 agent/direct_tool_calls.rs:把 Codex item 投影成结构化 DirectToolCall(command / file_change /
  mcp_tool / web_search / context_compaction / other),标题按 kind 固定(`编辑 N 个文件` 按去重路径数)
- 新增 agent/direct_tool_calls.rs:独立文件 `.agent/conversations/tool-calls.jsonl` 的幂等 upsert
  (同 id 只落一行、completed 覆盖 started、startedAt 取最早非零值)与 200 条上限裁剪
- 新增 agent/direct_tool_calls.rs:command/output 截断 4000 字符、summary 截断 120 字符,
  先抹绝对路径再抹密钥(复用 redact_absolute_path_tokens / redact_secret_tokens)
- 新增 agent/direct_tool_calls.rs:回读按时间正序、单行损坏跳过、文件缺失返回空数组
- 新增 agent/direct_tool_calls.rs 单测 8 条:幂等 upsert、非 0 退出码判 failed、截断、脱敏、
  坏行跳过、上限裁剪与排序、file_change 标题与摘要、非工具 item 不产卡片
- agent/codex_app_server.rs:DirectCodexTurnObservation 增加 ToolCall 变体;item/started 与
  item/completed 各采一次,turnId 沿用 AGC 客户端回合 id
- agent/direct_runtime.rs:观察者采集到工具调用时增量下发,回合结束整批落盘(一次锁、一次重写)
- agent/direct_runtime.rs:单项落盘走阻塞线程池,落盘失败不影响回合结果
- agent/runtime_driver/entrypoints.rs:DirectGameCreatorTurnUpdateEmitter::emit 增加可选
  toolCalls 参数
- main.rs:GameCreatorDirectTurnUpdateEvent 增加可选 toolCalls 字段,skip_serializing_if 保证
  老事件序列化结果不变
- commands.rs / main.rs:新增 read_direct_tool_calls(projectPath) 命令并注册到 invoke_handler
2026-09-14 15:00:32 +08:00
suzmii 7adabea656 新增工具调用卡片技术方案(契约冻结)
- docs/technical:【技术方案】GameAgent对话工具调用卡片-2026-09-14.md:工具调用条目形状、独立历史文件 tool-calls.jsonl、事件新增 toolCalls 字段、read_direct_tool_calls 回读命令、卡片 DOM 与无障碍要求、验收判据与不做项
- docs/README.md:把该方案登记进「AI 游戏创作与 Agent Runtime」索引
- 方案明确不改 project.jsonl 既有格式与 Codex 上下文注入路径,避免污染模型上下文
2026-09-14 14:19:57 +08:00
suzmii bc796c1db4 删除对话输入盒内的项目信息标签行
- ProjectSupervisorView:移除输入盒内「项目名 · 本地」信息标签行,删除随之无用的 projectDisplayName 辅助函数
- styles.css:输入盒网格从三行改回两行(文本区 / 控制排),删除信息标签行及其胶囊样式规则
- chatDialogFrameLayout.test.ts:把「表单子树含信息标签行」的断言反转成不再包含,并删除相关 CSS 常量与取值
- project-development.suite.ts:输入盒断言从「含项目名/本地标签」改为「不再渲染该标签行」,网格行数断言同步为两行
2026-09-14 14:17:20 +08:00
suzmii 8410426d12 补充对话面板纵向几何的回归断言
- chatDialogFrameLayout.test.ts:新增用例锁定面板必须跨满对话列全部 grid 行、height 100%、且不得叠加 flex 简写
- chatDialogFrameLayout.test.ts:新增断言锁定 direct-codex 面板把消息列表 min-height 从 96px 解除到 0
- 说明这次断言对应的一次真实渲染事故:新会话只有一条欢迎消息时,消息列表塌成 54px、输入盒被拉伸到 212px
2026-09-14 12:49:37 +08:00
suzmii cd816078a3 修复右侧对话面板纵向几何:消息列表塌陷与输入盒被顶出面板
- styles.css:为 .game-workbench-chat .project-supervisor-surface 补 grid-row: 1 / -1,并就近说明原因
- styles.css:说明该几何必须靠 height: 100% 撑满轨道,不能再叠加 flex 简写
- styles.css:文件末尾新增实测修复区块,解除 direct-codex 消息列表的 340px 最小高度下限
- styles.css:新增实测修复区块,把 direct-codex 的 surface / conversation 固定为纵向链
2026-09-14 12:48:22 +08:00
suzmii 92db1e7065 右侧对话面板改为 Codex 桌面客户端界面语言
- ProjectSupervisorView:direct-codex 面板改成三段式(顶部极简条 / 唯一滚动区消息列表 / 文档流输入盒)
- ProjectSupervisorView:顶部只留状态点 + 状态文案(aria-live=polite)+ 设置齿轮,移除头像、标题、副标题、审批按钮与泥点钱包槽
- ProjectSupervisorView:新增 direct-codex 空态(线稿图标 + 主标题「你想让陶泥儿做什么游戏?」+ 短指令副标题),整体在消息区垂直水平居中
- ProjectSupervisorView:输入盒改成三行结构(`项目名` · `本地` 信息标签 / ResourceReferenceInput 文本区 / 控制排)
- ProjectSupervisorView:控制排左为 `+` 附件钮与 `@` 引用钮(都复用 composerRef.openPicker),右为模型下拉与圆形主色发送钮
- ProjectSupervisorView:新增 walletEntry 可选属性,透传到设置浮层的「泥点」行
- ProjectSupervisorSettingsDialog(新增):设置齿轮打开的独立浮层(role=dialog + aria-modal + Esc 关闭 + 点遮罩关闭),承载运行配置入口、操作权限入口与泥点
- approvalMode(新增):把「严格 / 风险 / 无」审批模式选项与文案抽成可复用模块
- ApprovalModeDialog(新增):审批模式对话框改成可复用组件,index.tsx 改为复用它
- project-development/index.tsx:`!uiEditorRoute` 的 game-workbench-chat 头部精简掉标题/审批按钮/钱包槽,并用 cloneElement 把 walletEntry 透传给 supervisor 元素
- project-development/index.tsx:`planningStartMode` 分支保持原样
- ConversationModelSelect:触发按钮只显示已选模型 displayName,未加载完时显示「模型」占位(菜单内加载提示保留)
- styles.css:消息列表去掉绝对定位与 196px/256px 底边留白,改成唯一滚动区
- styles.css:输入盒去掉绝对定位,改成文档流三行网格,成为唯一有边框的容器(1px 边框、14px 圆角)
- styles.css:助手消息去掉整块底色,用户消息保留右对齐浅底气泡 + 主色描边
- styles.css:新增顶栏、空态、设置浮层与暖色执行过程卡的样式区块,颜色全部取自现有 platform 变量
- styles.css:窄屏(760px/1000px 断点)下输入盒留在文档流并保留四周边距
- chatDialogFrameLayout.test.ts:把「输入区浮在消息列表之上」的旧契约改写成「输入盒在消息列表下方、文档流、列表无浮层留白」的新契约
- project-development.suite.ts:同步更新表单布局、提交按钮尺寸与钱包槽的断言,并新增空态 + 设置浮层用例
2026-09-14 12:32:54 +08:00
137 changed files with 14737 additions and 1722 deletions
+3
View File
@@ -1 +1,4 @@
# Git 在链接工作树里执行 Hook 时会注入 GIT_DIR 等仓库定位变量,优先级高于 cwd;
# 子进程(npm、lint-staged、测试夹具)会继承它们并写到真实仓库,故在入口统一清除。
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR GIT_PREFIX GIT_CONFIG_PARAMETERS GIT_CEILING_DIRECTORIES
npm run format:staged
+3
View File
@@ -1 +1,4 @@
# Git 在链接工作树里执行 Hook 时会注入 GIT_DIR 等仓库定位变量,优先级高于 cwd;
# 钩子链(npm → check:repository-ci → 测试夹具)会继承它们并写到真实仓库,故在入口统一清除。
unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR GIT_PREFIX GIT_CONFIG_PARAMETERS GIT_CEILING_DIRECTORIES
npm run check:pre-push-master -- "$@"
+1
View File
@@ -57,6 +57,7 @@
"react-colorful": "^5.8.0",
"react-dom": "^19.0.0",
"react-markdown": "^10.1.0",
"rehype-highlight": "^7.0.2",
"remark-gfm": "^4.0.1",
"vite": "^6.2.0",
"zustand": "^5.0.14"
@@ -0,0 +1,113 @@
import assert from 'node:assert/strict';
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { test } from 'node:test';
import { setTimeout as delay } from 'node:timers/promises';
import { fileURLToPath } from 'node:url';
import { createServer, loadConfigFromFile, normalizePath } from 'vite';
test(
'AGC 排除 Rust 构建目录且保留源码与共享组件监听',
{ timeout: 30_000 },
async () => {
const loaded = await loadConfigFromFile(
{ command: 'serve', mode: 'development' },
fileURLToPath(new URL('../vite.config.ts', import.meta.url)),
);
assert.ok(loaded);
assert.notEqual(loaded.config.server?.watch, null);
assert.notEqual(loaded.config.server?.hmr, false);
assert.ok(
[loaded.config.server?.watch?.ignored]
.flat()
.includes('**/src-tauri/target/**'),
);
const fixture = await mkdtemp(join(tmpdir(), 'agc-vite-watch-'));
const root = join(fixture, 'apps', 'ai-game-creator-shell');
const source = join(root, 'src', 'main.js');
const css = join(root, 'src', 'styles.css');
const shared = join(fixture, 'packages', 'shared', 'src', 'component.js');
const target = join(root, 'src-tauri', 'target');
const artifact = join(target, 'debug', 'incremental', 'cache.bin');
let server;
try {
for (const file of [source, css, shared, artifact]) {
await mkdir(dirname(file), { recursive: true });
await writeFile(
file,
file === css ? 'body { color: red; }' : 'export default 1;',
);
}
// 使用真实 Vite watcher 和实际配置,仅将扫描根替换为小型夹具;
// 不加载业务插件、后端或原生窗口,也不扫描开发机上的大型 target。
server = await createServer({
configFile: false,
envFile: false,
root,
logLevel: 'silent',
server: {
watch: loaded.config.server?.watch,
middlewareMode: true,
hmr: false,
fs: { allow: [fixture] },
},
optimizeDeps: { noDiscovery: true, include: [] },
});
const waitForWatchedFile = async (file) => {
const normalized = normalizePath(file);
for (let attempt = 0; attempt < 100; attempt += 1) {
if (
Object.entries(server.watcher.getWatched()).some(
([directory, names]) =>
names.some(
(name) => normalizePath(join(directory, name)) === normalized,
),
)
)
return;
await delay(50);
}
assert.fail(`源码必须仍被监听:${normalized}`);
};
await waitForWatchedFile(source);
// 真实模块转换应将 root 外的共享源码加入监听。
await server.transformRequest(`/@fs/${normalizePath(shared)}`);
for (const file of [source, css, shared]) {
const normalized = normalizePath(file);
await waitForWatchedFile(file);
const changed = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
server.watcher.off('change', onChange);
reject(new Error(`未收到源码变更:${normalized}`));
}, 5_000);
function onChange(path) {
if (normalizePath(path) !== normalized) return;
clearTimeout(timer);
server.watcher.off('change', onChange);
resolve();
}
server.watcher.on('change', onChange);
});
await writeFile(
file,
file === css ? 'body { color: blue; }' : 'export default 2;',
);
await changed;
}
const targetPath = normalizePath(target);
const targetDirectories = Object.keys(server.watcher.getWatched())
.map(normalizePath)
.filter(
(path) => path === targetPath || path.startsWith(`${targetPath}/`),
);
assert.deepEqual(targetDirectories, [], 'Rust target 不应创建目录监听器');
} finally {
await server?.close();
await rm(fixture, { recursive: true, force: true });
}
},
);
@@ -13,6 +13,7 @@ Use `agc_browser_playtest` from the `agc_tools` MCP server. Do not replace it wi
2. Inspect both desktop and mobile results, including page readiness, visible text, screenshots, console errors, exceptions, failed requests, Canvas probes, blocked actions, and interaction evidence.
3. Compare screenshots with the user's request. Check that the active game fills its intended area, HUD elements do not cover gameplay, controls are visible, and requested platform art appears in the core experience.
4. If evidence exposes a defect, edit the actual game files and call the tool again when that is useful. The client enforces its own execution and resource bounds; do not invent a fixed repair loop in the response.
Feed the structured diagnostics, console errors, failed requests, and exception text back to the same LLM repair turn before reporting the playtest as failed. Treat the evidence as debugging input and rerun the affected stage after a real code or project change.
5. Treat browser infrastructure failure, an unloaded page, an unhandled exception, or missing evidence as a failed validation. Do not claim success from a partial result.
6. Use game-specific reasoning for quality. Do not require a fixed board, fixed text, fixed number of slices, or a legacy harness scenario; the tool result is evidence for Codex to interpret.
@@ -27,6 +27,8 @@ For a small edit to an existing game where the brief and suitable assets are unc
When a stage tool, command, or verification fails, retry at most three times before treating that stage as failed. Keep the retries serial and scoped to the same stage and the same input: a retry must not open a parallel path, skip ahead to a later stage, or substitute a placeholder for the missing output.
Every repairable failure must be fed back to the current LLM as the next debugging context before the stage is considered failed. Preserve the redacted tool or command error, the stage, the attempted input, and the evidence already collected; ask the LLM to inspect the current project, make the smallest real repair, and rerun the failed stage. A client-side `isError` tool result or a failed verification is feedback for the LLM, not by itself a terminal user-facing result. Do not silently swallow the error, replace it with a placeholder, or stop after the first failed attempt. Authentication, permission, billing, project identity, corrupted history, transport loss, cancellation, and uncertain paid-operation state remain terminal safety boundaries.
Only after the third attempt also fails, stop and tell the user the failure reason — which stage failed, which tool or command reported the error, what the error says, and what is still missing. A stage whose three attempts never succeeded is not complete, and its missing output cannot be reported as delivered.
Read the referenced specialist Skills for their detailed contracts: `agc-project-structure`, `taonier-art-assets`, `agc-web-game-development`, `agc-client-projection`, and `agc-browser-playtest`.
@@ -1,6 +1,6 @@
{
"schemaVersion": "agc-skill-pack.v1",
"version": "2026-08-26.15",
"version": "2026-08-26.16",
"skills": [
{
"name": "agc-game-production-workflow",
@@ -22,7 +22,7 @@
"agents/openai.yaml",
"references/workflow-contract.md"
],
"sha256": "d9d8e7e0a6bc512e0b463e0e4bd77edee1cc57f4a6965c9553e0920e38985d5c"
"sha256": "f25e5bd27e8fc82c61b08dc66366b5b253ee8d16d7fa72dbf2c94d2462f4e7fc"
},
{
"name": "agc-project-structure",
@@ -98,7 +98,7 @@
"agents/openai.yaml",
"references/browser-evidence-contract.md"
],
"sha256": "4437cd8a927a1c79a5faf4bcd40e9946676c08a3b460ab171298cabf899f49ad"
"sha256": "92ecce42d6589e034d32b75bcd155c1fee34a8c7b843eea5780c0577300ed521"
},
{
"name": "agc-client-projection",
@@ -22,7 +22,9 @@ mod direct_project_turn_history;
mod direct_runtime;
mod direct_thread_manager;
mod direct_tool_bridge;
mod direct_tool_calls;
mod direct_tools_mcp;
mod direct_turn_stream;
mod generation;
mod interaction;
mod prompt;
@@ -36,8 +38,9 @@ mod runtime_tools;
mod skill_pack;
use codex_app_server::*;
pub(crate) use codex_app_server::{
cancel_direct_codex_turn_at,
direct_codex_canonical_project_identity_for_commands as direct_codex_canonical_project_identity,
direct_game_creator_codex_chat_at, direct_game_creator_home_codex_chat,
direct_game_creator_codex_chat_at, direct_game_creator_home_codex_chat, DirectTurnCancelView,
};
use codex_cli::*;
pub(crate) use codex_cli::{
@@ -53,7 +56,9 @@ pub(crate) use direct_project_turn_history::*;
pub(crate) use direct_runtime::*;
pub(crate) use direct_thread_manager::*;
pub(crate) use direct_tool_bridge::*;
pub(crate) use direct_tool_calls::*;
pub(crate) use direct_tools_mcp::*;
pub(crate) use direct_turn_stream::*;
pub(crate) use generation::*;
pub(crate) use interaction::*;
pub(crate) use prompt::*;
@@ -7,17 +7,89 @@ use super::direct_project_history_injection_oversize_error;
use serde_json::Value;
use std::path::Path;
const DIRECT_PROJECT_HISTORY_IMAGE_TOTAL_MAX_BYTES: usize = 8 * 1024 * 1024;
const DIRECT_PROJECT_HISTORY_IMAGE_OMITTED_TEXT: &str =
"[历史图片预览已省略:本次恢复图片预算已用尽]";
fn omit_image_block(object: &mut serde_json::Map<String, Value>, text_type: &str) {
object.clear();
object.insert("type".to_string(), Value::String(text_type.to_string()));
object.insert(
"text".to_string(),
Value::String(DIRECT_PROJECT_HISTORY_IMAGE_OMITTED_TEXT.to_string()),
);
}
fn compact_history_images(value: &mut Value, remaining_bytes: &mut usize) {
match value {
Value::Array(values) => values
.iter_mut()
.for_each(|value| compact_history_images(value, remaining_bytes)),
Value::Object(object) => {
let is_image_block = object.get("type").and_then(Value::as_str) == Some("image");
if is_image_block {
if let Some(data) = object.get("data").and_then(Value::as_str) {
if let Some((preview, mime_type)) = crate::agent::compact_mcp_image_data(data) {
if preview.len() > *remaining_bytes {
omit_image_block(object, "text");
} else {
*remaining_bytes -= preview.len();
object.insert("data".to_string(), Value::String(preview));
object.insert(
"mimeType".to_string(),
Value::String(mime_type.to_string()),
);
}
}
}
}
if object.get("type").and_then(Value::as_str) == Some("input_image") {
if let Some(url) = object
.get("image_url")
.and_then(Value::as_str)
.map(str::to_string)
{
if let Some((header, data)) = url.split_once(",") {
if header.ends_with(";base64") {
if let Some((preview, mime_type)) =
crate::agent::compact_mcp_image_data(data)
{
if preview.len() > *remaining_bytes {
omit_image_block(object, "input_text");
} else {
*remaining_bytes -= preview.len();
object.insert(
"image_url".to_string(),
Value::String(format!("data:{mime_type};base64,{preview}")),
);
}
}
}
}
}
}
object
.values_mut()
.for_each(|value| compact_history_images(value, remaining_bytes));
}
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
}
}
pub(super) fn build_direct_project_history_injection_params(
history_root: &Path,
thread_id: &str,
) -> Result<Value, platform_llm::LlmError> {
let canonical_items = read_direct_project_history_items_at(history_root)
.map_err(platform_llm::LlmError::InvalidRequest)?;
let mut remaining_image_bytes = DIRECT_PROJECT_HISTORY_IMAGE_TOTAL_MAX_BYTES;
let items = canonical_items
.iter()
.map(|item| {
direct_codex_user_item_to_response_item(history_root, item)
.map_err(platform_llm::LlmError::InvalidRequest)
let mut projected = direct_codex_user_item_to_response_item(history_root, item)
.map_err(platform_llm::LlmError::InvalidRequest)?;
compact_history_images(&mut projected, &mut remaining_image_bytes);
Ok(projected)
})
.collect::<Result<Vec<_>, _>>()?;
let params = serde_json::json!({"threadId": thread_id, "items": items});
@@ -30,3 +102,27 @@ pub(super) fn build_direct_project_history_injection_params(
}
Ok(params)
}
#[cfg(test)]
mod tests {
use super::{compact_history_images, DIRECT_PROJECT_HISTORY_IMAGE_OMITTED_TEXT};
use serde_json::json;
#[test]
fn history_image_budget_omits_only_wire_preview_when_exhausted() {
let mut item = json!({
"type": "function_call_output",
"output": {"content": [{
"type": "image",
"data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
"mimeType": "image/png"
}]}
});
let mut remaining = 1;
compact_history_images(&mut item, &mut remaining);
let block = &item["output"]["content"][0];
assert_eq!(block["type"], "text");
assert_eq!(block["text"], DIRECT_PROJECT_HISTORY_IMAGE_OMITTED_TEXT);
assert_eq!(remaining, 1);
}
}
@@ -222,6 +222,12 @@ impl CodexTurnStartCancellation {
self.maybe_interrupt();
}
/// app-server 连接是否还活着:句柄只剩 Weak 时说明进程已被回收,此时"终止"必须
/// 明确报错,而不是静默成功让界面以为回合已经停了。
fn app_server_alive(&self) -> bool {
self.inner.strong_count() > 0
}
fn cancel(&self) {
self.cancelled.store(true, Ordering::Release);
self.maybe_interrupt();
@@ -576,8 +582,21 @@ enum CodexTurnEvent {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum DirectCodexTurnObservation {
AccumulatedText(String),
/// 一个 assistant 文本段的当前累计全文。
///
/// `item_id` 是一次 assistant 消息的稳定身份:同一个 id 的后续 delta 属于**同一段**
/// id 变了就是新的一段。回合流的"文本段 + 工具"顺序用它来分段,而不是按 delta 分。
AgentMessageSegment {
item_id: String,
accumulated_text: String,
completed: bool,
},
IntermediateText(String),
/// 模型的思考过程(reasoning item 的明文摘要):流式阶段整段替换下发。
Reasoning(String),
Activity(&'static str),
/// 一条结构化工具调用(`item/started` 与 `item/completed` 各采一次,按 id 幂等)。
ToolCall(crate::DirectToolCall),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -862,6 +881,37 @@ fn direct_codex_mcp_tool_intermediate_text(item: &serde_json::Value) -> String {
/// (with the concrete command/tool/path) while tools run; it does not push
/// plan/reasoning text deltas. Showing what the agent is actually doing is
/// the only reliable way to make the execution phase feel alive.
/// 从 reasoning item 里抽明文思考文本:优先 `summary[].text`,其次 `content[].text`。
///
/// Codex 的 reasoning item 形如
/// `{ "type": "reasoning", "summary": [...], "content": [{ "text": "..." }], "encrypted_content": ... }`
/// 没有 `role` 字段;明文(至少 content/summary 之一)存在时我们才展示,拿不到就返回 None。
fn direct_codex_item_reasoning_text(item: &serde_json::Value) -> Option<String> {
if item.get("type").and_then(serde_json::Value::as_str) != Some("reasoning") {
return None;
}
let collect = |key: &str| -> Option<String> {
let parts = item
.get(key)?
.as_array()?
.iter()
.filter_map(|entry| {
entry
.get("text")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|text| !text.is_empty())
})
.collect::<Vec<_>>();
if parts.is_empty() {
None
} else {
Some(parts.join("\n\n"))
}
};
collect("summary").or_else(|| collect("content"))
}
fn direct_codex_item_intermediate_text(item: &serde_json::Value) -> Option<String> {
const MAX_ITEM_TEXT_CHARS: usize = 240;
let item_type = item
@@ -2734,6 +2784,12 @@ impl CodexAppServerConnection {
let _turn_guard = self.inner.turn_gate.lock().await;
let mut request = request;
let history_root = direct_history_root.unwrap_or(&self.inner.workspace_path);
// 工具调用卡片的 turnId 用 AGC 客户端回合 id(与实时事件、落盘条目同一口径),
// 不用 Codex app-server 自己的 turnId——前端要按它把卡片挂回对应的那一轮。
let direct_tool_call_turn_id: Option<String> = direct_client_turn_id
.map(str::trim)
.filter(|turn_id| !turn_id.is_empty())
.map(str::to_string);
if self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject {
let current_prompt = direct_codex_current_user_prompt(&request).trim();
if current_prompt.is_empty() {
@@ -2821,6 +2877,17 @@ impl CodexAppServerConnection {
}
let turn_start_cancellation =
Arc::new(CodexTurnStartCancellation::new(&self.inner, &thread_id));
// Direct 回合登记为"可终止":终止命令只作用在这一轮上,回合结束时自动注销。
let _active_turn_guard = direct_tool_call_turn_id
.as_deref()
.filter(|_| self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject)
.map(|turn_id| {
register_active_direct_codex_turn(
direct_codex_active_turn_key(history_root),
turn_id,
Arc::clone(&turn_start_cancellation),
)
});
let mut turn_start_guard = CodexTurnStartGuard {
cancellation: Arc::clone(&turn_start_cancellation),
armed: true,
@@ -2938,6 +3005,18 @@ impl CodexAppServerConnection {
observer(DirectCodexTurnObservation::AccumulatedText(
streamed_text.clone(),
));
// 同一个 assistant item 的当前累计全文:回合流按 item 分段,
// 段内只追加、段间才换行,不能拿"整轮累计"当一段。
let segment_text = direct_project_history
.accumulated_text_for(&item_id)
.unwrap_or_else(|| delta.clone());
if !segment_text.trim().is_empty() {
observer(DirectCodexTurnObservation::AgentMessageSegment {
item_id: item_id.clone(),
accumulated_text: segment_text,
completed: false,
});
}
}
if let Some(callback) = on_agent_message_delta.as_deref_mut() {
callback(&platform_llm::LlmStreamDelta {
@@ -3027,6 +3106,10 @@ impl CodexAppServerConnection {
// 让执行期间聊天窗口显示“正在做什么”,而不是只
// 有活动状态来回跳动。completed 事件不再重复。
if !completed {
if let Some(reasoning) = direct_codex_item_reasoning_text(item)
{
observer(DirectCodexTurnObservation::Reasoning(reasoning));
}
if let Some(text) = direct_codex_item_intermediate_text(item) {
observer(DirectCodexTurnObservation::IntermediateText(
text,
@@ -3042,6 +3125,24 @@ impl CodexAppServerConnection {
completed,
&params,
);
// 工具调用卡片:item/started 与 item/completed 各采一次,
// 由下游按 id 幂等 upsert 成同一条。采集失败(拿不到 id /
// 非工具类 item)就静默跳过,不影响这一轮的其它投影。
if let Some(turn_id) = direct_tool_call_turn_id.as_deref() {
if let Some(tool_call) = direct_tool_call_from_item(
history_root,
item,
turn_id,
completed,
direct_tool_call_now_ms(),
) {
if let Some(observer) = direct_observer.as_deref_mut() {
observer(DirectCodexTurnObservation::ToolCall(
tool_call,
));
}
}
}
if completed {
if let Some(audit) = audit.as_mut() {
audit.observe_item(&params);
@@ -3049,6 +3150,27 @@ impl CodexAppServerConnection {
}
}
if item_type == "agentMessage" {
// 某些 app-server 实现会在工具开始后停止发送 agentMessage delta
// 但会在 item/completed 携带完整文本。把这份最终快照补进回合流,
// 让流中的文本段不会停在工具前的短前缀。
if completed {
if let (Some(item_id), Some(text)) = (
item.get("id").and_then(serde_json::Value::as_str),
item.get("text")
.and_then(serde_json::Value::as_str)
.filter(|value| !value.trim().is_empty()),
) {
if let Some(observer) = direct_observer.as_deref_mut() {
observer(
DirectCodexTurnObservation::AgentMessageSegment {
item_id: item_id.to_string(),
accumulated_text: text.to_string(),
completed: true,
},
);
}
}
}
if let Some(text) = item
.get("text")
.and_then(serde_json::Value::as_str)
@@ -3086,17 +3208,32 @@ impl CodexAppServerConnection {
}
Some(CodexTurnEvent::Terminal(params)) => {
let turn = params.get("turn").unwrap_or(&params);
if final_text.is_none() {
final_text = turn
.get("items")
.and_then(serde_json::Value::as_array)
.and_then(|items| {
items.iter().rev().find_map(|item| {
(item.get("type")?.as_str()? == "agentMessage")
.then(|| item.get("text")?.as_str().map(str::to_string))
.flatten()
})
});
if let Some(items) = turn.get("items").and_then(serde_json::Value::as_array)
{
for item in items {
if item.get("type").and_then(serde_json::Value::as_str)
!= Some("agentMessage")
{
continue;
}
if let Some(text) = item
.get("text")
.and_then(serde_json::Value::as_str)
.filter(|text| !text.trim().is_empty())
{
final_text = Some(text.to_string());
if let (Some(item_id), Some(observer)) = (
item.get("id").and_then(serde_json::Value::as_str),
direct_observer.as_deref_mut(),
) {
observer(DirectCodexTurnObservation::AgentMessageSegment {
item_id: item_id.to_string(),
accumulated_text: text.to_string(),
completed: true,
});
}
}
}
}
let status = turn
.get("status")
@@ -3195,6 +3332,223 @@ impl Drop for CodexTurnStartGuard {
}
}
/// Direct 回合中断表:与具体取消句柄解耦的最小实现,"选哪一轮 / 注销哪一轮"可单测。
struct DirectCodexActiveTurnTable<T> {
entries: HashMap<std::path::PathBuf, (String, T)>,
}
impl<T> DirectCodexActiveTurnTable<T> {
fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
fn register(&mut self, key: std::path::PathBuf, client_turn_id: &str, value: T) {
self.entries
.insert(key, (client_turn_id.to_string(), value));
}
/// 只有当前登记项仍是本回合的句柄时才注销,避免旧回合的收尾清掉后来注册的回合。
fn unregister(&mut self, key: &Path, is_same: impl Fn(&T) -> bool) {
if self
.entries
.get(key)
.is_some_and(|(_, value)| is_same(value))
{
self.entries.remove(key);
}
}
/// 选中要终止的回合:没有活动回合、或前端给的 clientTurnId 与活动回合不一致时都返回
/// 可读原因,绝不误伤另一个回合。
fn select(&self, key: &Path, client_turn_id: Option<&str>) -> Result<&(String, T), String> {
let active = self
.entries
.get(key)
.ok_or_else(|| "当前项目没有正在运行的陶泥儿回合,无法终止".to_string())?;
if let Some(expected) = client_turn_id
.map(str::trim)
.filter(|value| !value.is_empty())
{
if active.0 != expected {
return Err(DIRECT_CODEX_ANOTHER_TURN_RUNNING_MESSAGE.to_string());
}
}
Ok(active)
}
/// 当前登记在这一轮上的 clientTurnId;没有任何登记时返回 `None`。
fn registered_client_turn_id(&self, key: &Path) -> Option<&str> {
self.entries
.get(key)
.map(|(client_turn_id, _)| client_turn_id.as_str())
}
}
/// "正在跑的是另一轮"的统一文案:`select` 与"终止"兜底路径共用,保证两处拒绝语义一致。
const DIRECT_CODEX_ANOTHER_TURN_RUNNING_MESSAGE: &str = "正在运行的是另一个陶泥儿回合,已拒绝终止";
/// 正在运行的 Direct 回合中断句柄,按项目根(canonical,去掉 Windows `\\?\` 前缀)索引。
///
/// `CodexTurnStartCancellation` 本身已经能在 turn/start 响应到达**前后**发出
/// `turn/interrupt`;这里只是把它留一个 Tauri 命令取得到的引用,回合结束后由
/// [`DirectCodexActiveTurnGuard`] 移除。只做新增:不改既有事件、命令语义。
static GAME_CREATOR_DIRECT_CODEX_ACTIVE_TURNS: OnceLock<
std::sync::Mutex<DirectCodexActiveTurnTable<Arc<CodexTurnStartCancellation>>>,
> = OnceLock::new();
fn direct_codex_active_turns(
) -> &'static std::sync::Mutex<DirectCodexActiveTurnTable<Arc<CodexTurnStartCancellation>>> {
GAME_CREATOR_DIRECT_CODEX_ACTIVE_TURNS
.get_or_init(|| std::sync::Mutex::new(DirectCodexActiveTurnTable::new()))
}
/// 注册键:与 Direct 回合用的 `codex_root` 同一形态(canonical 且去掉 `\\?\` 前缀),
/// 这样前端传进来的项目路径与注册时的路径一定落到同一个键上。
fn direct_codex_active_turn_key(root: &Path) -> std::path::PathBuf {
let canonical = std::fs::canonicalize(root).unwrap_or_else(|_| root.to_path_buf());
match canonical
.to_str()
.and_then(|value| value.strip_prefix("\\\\?\\"))
{
Some(stripped) => std::path::PathBuf::from(stripped),
None => canonical,
}
}
struct DirectCodexActiveTurnGuard {
key: std::path::PathBuf,
cancellation: Arc<CodexTurnStartCancellation>,
}
impl Drop for DirectCodexActiveTurnGuard {
fn drop(&mut self) {
let Some(active_turns) = GAME_CREATOR_DIRECT_CODEX_ACTIVE_TURNS.get() else {
return;
};
let Ok(mut entries) = active_turns.lock() else {
return;
};
let cancellation = Arc::clone(&self.cancellation);
entries.unregister(&self.key, |current| Arc::ptr_eq(current, &cancellation));
}
}
/// 把一个 Direct 回合登记为"可终止",返回的 guard 在回合结束时注销它。
fn register_active_direct_codex_turn(
key: std::path::PathBuf,
client_turn_id: &str,
cancellation: Arc<CodexTurnStartCancellation>,
) -> DirectCodexActiveTurnGuard {
if let Ok(mut entries) = direct_codex_active_turns().lock() {
entries.register(key.clone(), client_turn_id, Arc::clone(&cancellation));
}
DirectCodexActiveTurnGuard { key, cancellation }
}
/// 已向正在跑的回合发出中断:界面等这一轮自己的收尾复位。
pub(crate) const DIRECT_TURN_CANCEL_OUTCOME_INTERRUPTED: &str = "interrupted";
/// 这一轮已经没有人替它收尾,本地守卫已被兜底释放:界面必须自己复位。
pub(crate) const DIRECT_TURN_CANCEL_OUTCOME_RELEASED: &str = "released";
/// `cancel_direct_codex_turn` 的返回值:界面据此决定是自己复位,还是等回合自己收尾。
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DirectTurnCancelView {
/// [`DIRECT_TURN_CANCEL_OUTCOME_INTERRUPTED`] 或
/// [`DIRECT_TURN_CANCEL_OUTCOME_RELEASED`]。
pub(crate) outcome: String,
/// 给用户看的可读结果。
pub(crate) message: String,
/// 被终止 / 被释放的 clientTurnId。
pub(crate) client_turn_id: String,
}
/// "终止"这一步要作用在哪:发中断,还是走残留守卫兜底释放。
enum DirectCodexTurnCancelTarget {
/// app-server 侧还有活句柄:正常发 `turn/interrupt`。
Interrupt(Arc<CodexTurnStartCancellation>),
/// app-server 侧已经拿不到可中断的活句柄;带上是哪种情况。
Stale(DirectTaonierStaleGuardReason),
}
/// 终止当前项目正在运行的 Direct 回合。
///
/// 正常路径:只向正在跑的 Codex app-server 回合发 `turn/interrupt`app-server 随后回
/// `turn/completed status=interrupted`,正在 await 的那个回合命令会带着可读原因返回),
/// 不动任何既有事件或命令语义。
///
/// 兜底路径:app-server 侧已经拿不到可中断的活句柄时,说明这一轮不会再有人替它收尾。
/// 只发中断会让本地守卫(`DirectTaonierActiveInvocationGuard`)永远留在进程内,用户此后
/// 每条消息都会被"已有另一条回合正在运行"拒绝——这正是"重进会话被堵死"的死锁形态。
/// 这时显式释放这条守卫并把可读原因返回给界面。释放条件见
/// [`release_stale_direct_taonier_active_invocation`] 的注释;"正在跑的是另一轮"仍然
/// 保持原拒绝语义,什么都不释放。
pub(crate) fn cancel_direct_codex_turn_at(
root: &Path,
client_turn_id: Option<&str>,
) -> Result<DirectTurnCancelView, String> {
let key = direct_codex_active_turn_key(root);
let expected = client_turn_id
.map(str::trim)
.filter(|value| !value.is_empty());
{
let entries = direct_codex_active_turns()
.lock()
.map_err(|_| "Direct 回合中断表已损坏,无法终止".to_string())?;
if let (Some(registered), Some(expected)) =
(entries.registered_client_turn_id(&key), expected)
{
if registered != expected {
return Err(DIRECT_CODEX_ANOTHER_TURN_RUNNING_MESSAGE.to_string());
}
}
}
let target = {
let entries = direct_codex_active_turns()
.lock()
.map_err(|_| "Direct 回合中断表已损坏,无法终止".to_string())?;
match entries.select(&key, client_turn_id) {
Ok((_, cancellation)) if cancellation.app_server_alive() => {
DirectCodexTurnCancelTarget::Interrupt(Arc::clone(cancellation))
}
Ok(_) => {
DirectCodexTurnCancelTarget::Stale(DirectTaonierStaleGuardReason::ExecutorExited)
}
Err(_) => DirectCodexTurnCancelTarget::Stale(
DirectTaonierStaleGuardReason::NeverReachedExecutor,
),
}
};
match target {
DirectCodexTurnCancelTarget::Interrupt(cancellation) => {
cancellation.cancel();
Ok(DirectTurnCancelView {
outcome: DIRECT_TURN_CANCEL_OUTCOME_INTERRUPTED.to_string(),
message: "已向正在运行的回合发出终止".to_string(),
client_turn_id: client_turn_id
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or_default()
.to_string(),
})
}
DirectCodexTurnCancelTarget::Stale(reason) => {
let released =
release_stale_direct_taonier_active_invocation(root, client_turn_id, reason)?;
Ok(DirectTurnCancelView {
outcome: DIRECT_TURN_CANCEL_OUTCOME_RELEASED.to_string(),
message: format!(
"{},已释放这一轮的占用,可以直接重新发送消息",
reason.message()
),
client_turn_id: released,
})
}
}
}
struct CodexThreadLease {
connection: CodexAppServerConnection,
key: CodexNodeThreadKey,
@@ -4100,6 +4454,52 @@ pub(crate) fn build_direct_codex_history_prompt(
mod tests {
use super::*;
/// 终止只作用在"当前项目正在跑的那一轮"上:没有活动回合 / clientTurnId 不匹配都要
/// 返回可读原因,不能误伤别人;注销也只注销本回合自己的句柄。
#[test]
fn direct_codex_active_turn_table_selects_only_the_running_turn() {
let mut table: DirectCodexActiveTurnTable<u8> = DirectCodexActiveTurnTable::new();
let key = std::path::PathBuf::from("C:/projects/direct-turn-demo");
assert_eq!(
table.select(&key, None).expect_err("no active turn"),
"当前项目没有正在运行的陶泥儿回合,无法终止"
);
table.register(key.clone(), "turn-a", 1);
assert_eq!(table.select(&key, None).expect("active turn").0, "turn-a");
assert_eq!(table.select(&key, Some("turn-a")).expect("same turn").1, 1);
assert_eq!(
table
.select(&key, Some("turn-b"))
.expect_err("another running turn"),
"正在运行的是另一个陶泥儿回合,已拒绝终止"
);
// 句柄已被后来的回合替换:旧回合收尾不得注销新回合。
table.register(key.clone(), "turn-b", 2);
table.unregister(&key, |value| *value == 1);
assert_eq!(table.select(&key, None).expect("newer turn").0, "turn-b");
table.unregister(&key, |value| *value == 2);
assert!(table.select(&key, None).is_err());
}
/// 注册键:前端传的项目路径与回合注册时的路径必须归一化成同一个键(Windows 上
/// `canonicalize` 会带 `\\?\` 前缀,去掉后两边才相等)。
#[test]
fn direct_codex_active_turn_key_normalizes_windows_prefix() {
let root = tempfile::tempdir().expect("temp dir");
let canonical = std::fs::canonicalize(root.path()).expect("canonical root");
let expected = canonical
.to_str()
.and_then(|value| value.strip_prefix("\\\\?\\"))
.map(std::path::PathBuf::from)
.unwrap_or(canonical);
let key = direct_codex_active_turn_key(root.path());
assert_eq!(key, expected);
// 归一化后的键不再带 Windows 扩展长度前缀:前端传进来的普通路径才能命中同一个键。
assert!(!key.to_string_lossy().starts_with("\\\\?\\"));
}
#[test]
fn direct_thread_item_projection_drops_full_app_server_payload() {
let item = serde_json::json!({
@@ -5,6 +5,7 @@ use crate::project::{
};
use crate::{LocalConversationMessageRecord, LocalConversationResult};
use serde_json::Value;
use std::collections::BTreeMap;
use std::fs::{self, File};
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
@@ -91,6 +92,10 @@ fn record(item: &Value) -> Result<String, String> {
serde_json::to_string(&serde_json::json!({
"type": DIRECT_PROJECT_HISTORY_RECORD_TYPE,
"payload": item,
"recordedAt": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|duration| duration.as_millis() as u64)
.unwrap_or(0),
}))
.map_err(|error| format!("序列化 DirectProject 历史失败:{error}"))
}
@@ -470,6 +475,13 @@ fn direct_project_message_item(role: &str, content: &str, message_id: Option<&st
}
pub(crate) fn read_direct_project_history_items_at(root: &Path) -> Result<Vec<Value>, String> {
Ok(read_direct_project_history_entries_at(root)?
.into_iter()
.map(|(item, _)| item)
.collect())
}
fn read_direct_project_history_entries_at(root: &Path) -> Result<Vec<(Value, u64)>, String> {
let path = history_path(root);
if !prepare_game_creator_private_path_for_read(&path, false, "DirectProject 历史")? {
return Ok(Vec::new());
@@ -507,7 +519,13 @@ pub(crate) fn read_direct_project_history_items_at(root: &Path) -> Result<Vec<Va
if is_direct_project_internal_context_item(&item) {
continue;
}
items.push(item);
items.push((
item,
parsed
.get("recordedAt")
.and_then(Value::as_u64)
.unwrap_or(0),
));
}
Ok(items)
}
@@ -516,18 +534,30 @@ pub(crate) fn read_direct_project_history_items_slice_at(
root: &Path,
before_item_id: Option<&str>,
limit: usize,
) -> Result<(Vec<Value>, bool), String> {
let items = read_direct_project_history_items_at(root)?;
) -> Result<(Vec<Value>, bool, BTreeMap<String, u64>), String> {
let items = read_direct_project_history_entries_at(root)?;
let end = match before_item_id {
Some(item_id) => items
.iter()
.position(|item| item.get("id").and_then(Value::as_str) == Some(item_id))
.position(|(item, _)| item.get("id").and_then(Value::as_str) == Some(item_id))
.ok_or_else(|| format!("DirectProject 历史中不存在 item{item_id}"))?,
None => items.len(),
};
let bounded_limit = limit.clamp(1, 200);
let start = end.saturating_sub(bounded_limit);
Ok((items[start..end].to_vec(), start > 0))
let slice = &items[start..end];
let timestamps = slice
.iter()
.filter_map(|(item, at)| {
let id = item.get("id").and_then(Value::as_str)?;
(*at > 0).then(|| (id.to_string(), *at))
})
.collect();
Ok((
slice.iter().map(|(item, _)| item.clone()).collect(),
start > 0,
timestamps,
))
}
pub(crate) fn read_direct_project_last_item_id_at(root: &Path) -> Result<Option<String>, String> {
@@ -546,10 +576,10 @@ pub(crate) fn read_direct_project_chat_history_at(
root: &Path,
) -> Result<LocalConversationResult, String> {
let path = history_path(root);
let items = read_direct_project_history_items_at(root)?;
let items = read_direct_project_history_entries_at(root)?;
let messages = items
.into_iter()
.filter_map(|item| {
.filter_map(|(item, recorded_at)| {
let role = item.get("role").and_then(Value::as_str)?;
if !matches!(role, "user" | "assistant") {
return None;
@@ -571,7 +601,7 @@ pub(crate) fn read_direct_project_chat_history_at(
content,
agent_id: None,
message_id: item.get("id").and_then(Value::as_str).map(str::to_string),
updated_at: 0,
updated_at: recorded_at,
})
})
.collect();
@@ -610,6 +640,40 @@ mod tests {
const RESPONSE_ITEM_ROW: &str = r#"{"type":"response_item","payload":{"type":"message","role":"user","id":"codex-item-2","content":[{"type":"input_text","text":"再加一个按钮"}]}}"#;
const RESPONSE_ASSISTANT_ROW: &str = r#"{"type":"response_item","payload":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"已完成"}]}}"#;
#[test]
fn history_timestamps_survive_reload_and_idempotent_append_without_changing_raw_items() {
let root = init_history_project("history-time");
let item = json!({
"type": "message", "role": "user", "id": "sent-message",
"content": [{"type": "input_text", "text": "修改游戏"}],
});
append_direct_project_user_message_at(root.path(), &item).unwrap();
let (items, _, timestamps) =
super::read_direct_project_history_items_slice_at(root.path(), None, 20).unwrap();
assert_eq!(items, vec![item.clone()]);
assert!(timestamps["sent-message"] > 0);
append_direct_project_user_message_at(root.path(), &item).unwrap();
let (_, _, reloaded) =
super::read_direct_project_history_items_slice_at(root.path(), None, 20).unwrap();
assert_eq!(timestamps, reloaded);
}
#[test]
fn old_history_without_envelope_time_stays_unknown() {
let root = init_history_project("history-unknown-time");
write_history_lines(root.path(), &[RESPONSE_ITEM_ROW]);
let (_, _, timestamps) =
super::read_direct_project_history_items_slice_at(root.path(), None, 20).unwrap();
assert!(timestamps.is_empty());
assert_eq!(
read_direct_project_chat_history_at(root.path())
.unwrap()
.messages[0]
.updated_at,
0
);
}
/// 判据:争用类失败会被"有界退避重试"真的吃掉,最终把条目落一行。
///
/// 注入标记是"让接下来 N 次单次尝试返回争用失败";退避表只补一次重试,所以注入 1 次
@@ -22,6 +22,14 @@ impl DirectProjectHistoryAccumulator {
}
}
/// 某个 assistant item 目前累计到的全文。
///
/// 回合流按 item 分段:同一个 item 的后续 delta 是同一段的增长,item 变了才是新的一段。
/// 没有这条 item(非 DirectProject 工作区、或已经 complete)时返回 `None`。
pub(crate) fn accumulated_text_for(&self, item_id: &str) -> Option<String> {
self.text_by_item_id.get(item_id).cloned()
}
fn take_partial_items(&mut self) -> impl Iterator<Item = Value> + '_ {
std::mem::take(&mut self.text_by_item_id)
.into_iter()
File diff suppressed because it is too large Load Diff
@@ -31,7 +31,7 @@ pub(crate) fn normalize_direct_client_turn_id(
pub(crate) async fn chat_with_game_creator_direct_codex(
project_path: String,
prompt: String,
user_item: DirectCodexUserItem,
mut user_item: DirectCodexUserItem,
creation_type: Option<String>,
client_turn_id: Option<String>,
attachments: Option<Vec<DirectCodexTurnAttachment>>,
@@ -50,6 +50,17 @@ pub(crate) async fn chat_with_game_creator_direct_codex(
attachments.as_deref().unwrap_or_default(),
);
let attachments = attachments.unwrap_or_default();
if !attachments.is_empty() {
let attachment_context =
render_direct_codex_user_prompt("", &attachments).map_err(|error| {
audit.finish(false);
error
})?;
let DirectCodexUserItem::Message(message) = &mut user_item;
message.content.push(DirectCodexUserContentPart::InputText {
text: attachment_context,
});
}
validate_direct_codex_user_item(root, &user_item).map_err(|error| {
audit.finish(false);
error
@@ -81,6 +92,6 @@ pub(crate) async fn chat_with_game_creator_direct_codex(
}
};
audit.finish(true);
turn_emitter.emit("completed", Some("none"), Some(reply.clone()));
turn_emitter.emit("completed", Some("none"), Some(reply.clone()), None);
Ok(reply)
}
@@ -58,6 +58,7 @@ pub(crate) struct DirectThreadConsumeResult {
pub(crate) struct DirectThreadHistorySlice {
pub(crate) items: Vec<Value>,
pub(crate) has_more: bool,
pub(crate) item_timestamps: std::collections::BTreeMap<String, u64>,
}
#[derive(Clone, Debug)]
@@ -19,6 +19,8 @@ const DIRECT_TOOL_BRIDGE_MAX_WRITE_CONTENT_BYTES: usize = 1_500_000;
const DIRECT_TOOL_BRIDGE_MAX_ART_BRIEF_CHARS: usize = 4_000;
const DIRECT_TOOL_BRIDGE_MAX_IMAGE_PROMPT_CHARS: usize = 32_000;
const DIRECT_TOOL_BRIDGE_MAX_IMAGE_BYTES: u64 = 6 * 1024 * 1024;
const DIRECT_TOOL_BRIDGE_IMAGE_PREVIEW_MAX_BYTES: usize = 256 * 1024;
const DIRECT_TOOL_BRIDGE_IMAGE_PREVIEW_MAX_DIMENSION: u32 = 1024;
const DIRECT_TOOL_BRIDGE_MAX_SEARCH_QUERY_CHARS: usize = 400;
const DIRECT_TOOL_BRIDGE_MAX_SEARCH_RESULTS: usize = 5;
const DIRECT_TOOL_BRIDGE_SEARCH_URL: &str = "https://www.bing.com/search?format=rss";
@@ -694,14 +696,44 @@ fn direct_tool_bridge_state_with_search(
})
}
/// 将 MCP 图片 block 限制为可安全回显和持久化的预览。
///
/// 工具结果会被 Codex 原样写入 DirectProject 历史;这里保留小图的原始
/// PNG,大图则缩放并转成 JPEG。项目文件中的原图不受影响,历史恢复仍有
/// 可见证据,但不会把多张几 MiB 的截图永久复制进上下文。
pub(crate) fn compact_mcp_image_data(data: &str) -> Option<(String, &'static str)> {
let bytes = BASE64_STANDARD.decode(data).ok()?;
if bytes.is_empty() {
return None;
}
if bytes.len() <= DIRECT_TOOL_BRIDGE_IMAGE_PREVIEW_MAX_BYTES {
return Some((data.to_string(), "image/png"));
}
let image = image::load_from_memory(&bytes).ok()?;
let mut preview = image.thumbnail(
DIRECT_TOOL_BRIDGE_IMAGE_PREVIEW_MAX_DIMENSION,
DIRECT_TOOL_BRIDGE_IMAGE_PREVIEW_MAX_DIMENSION,
);
for (dimension, quality) in [(1024, 78), (768, 70), (512, 60), (384, 50)] {
if preview.width() > dimension || preview.height() > dimension {
preview = image.thumbnail(dimension, dimension);
}
let mut encoded = Vec::new();
let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut encoded, quality);
preview.write_with_encoder(encoder).ok()?;
if encoded.len() <= DIRECT_TOOL_BRIDGE_IMAGE_PREVIEW_MAX_BYTES {
return Some((BASE64_STANDARD.encode(encoded), "image/jpeg"));
}
}
None
}
fn bridge_tool_result(text: String, images: Vec<String>, is_error: bool) -> Value {
let mut content = vec![json!({ "type": "text", "text": text })];
content.extend(images.into_iter().map(|data| {
json!({
"type": "image",
"data": data,
"mimeType": "image/png"
})
content.extend(images.into_iter().filter_map(|data| {
let (data, mime_type) = compact_mcp_image_data(&data).unwrap_or((data, "image/png"));
Some(json!({ "type": "image", "data": data, "mimeType": mime_type }))
}));
json!({ "content": content, "isError": is_error })
}
@@ -2672,7 +2704,7 @@ pub(crate) async fn start_direct_tool_bridge(
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Write};
use std::io::{Cursor, Read, Write};
#[tokio::test]
async fn controlled_search_client_omits_agc_marker() {
@@ -2766,6 +2798,35 @@ mod tests {
assert!(bridge_search_max_results(&json!({ "maxResults": 6 })).is_err());
}
#[test]
fn large_mcp_images_are_reduced_to_bounded_jpeg_previews() {
let image = image::RgbaImage::from_fn(1600, 1200, |x, y| {
image::Rgba([
(x % 251) as u8,
(y % 251) as u8,
((x.wrapping_mul(31) + y.wrapping_mul(17)) % 251) as u8,
u8::MAX,
])
});
let mut png = Cursor::new(Vec::new());
image::DynamicImage::ImageRgba8(image)
.write_to(&mut png, image::ImageFormat::Png)
.expect("encode image fixture");
assert!(png.get_ref().len() > DIRECT_TOOL_BRIDGE_IMAGE_PREVIEW_MAX_BYTES);
let (preview, mime_type) =
compact_mcp_image_data(&BASE64_STANDARD.encode(png.into_inner()))
.expect("large valid image should produce preview");
assert_eq!(mime_type, "image/jpeg");
assert!(
BASE64_STANDARD
.decode(preview)
.expect("preview base64")
.len()
<= DIRECT_TOOL_BRIDGE_IMAGE_PREVIEW_MAX_BYTES
);
}
#[test]
fn search_parser_accepts_only_bounded_public_https_results() {
let body = r#"<rss><channel><item><title>Tauri &amp; Rust</title><link>https://tauri.app/</link><description>&lt;b&gt;Cross-platform apps&lt;/b&gt;</description></item><item><title>Private</title><link>http://127.0.0.1:8082/private</link><description>private</description></item><item><title>Credentials</title><link>https://user:pass@example.test/path</link><description>private</description></item><item><title>Loopback host</title><link>https://localhost/private</link><description>private</description></item><item><title>Local host</title><link>https://service.internal/private</link><description>private</description></item></channel></rss>"#;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,439 @@
//! GameAgent 对话「回合流」的采集、持久化与回读。
//!
//! 顺序真相放在一处:`<projectRoot>/.agent/conversations/turn-stream.jsonl` 按**出现顺序**
//! 记录一个回合里的文本段与工具调用。工具条目只记位置标记(`callId`),工具本身的正文
//! 仍然来自 `tool-calls.jsonl`(同一 id 幂等合并只有一处实现)。
//!
//! 位置稳定:每条条目的 `seq` 在**首次出现**时由观察方分配并落盘,后续更新(同一 id 的
//! 文本追加 / 工具状态变化)只改内容不改 `seq`。因此并发落盘的先后顺序不会让"新工具插到
//! 旧文本前面"——渲染顺序只由 `seq` 决定。
//!
//! `project.jsonl` 保留原始消息;本流补充文本与工具交替的 item 顺序,不能重复展示两份正文。
use crate::agent::sanitize_detail_text;
use crate::config::{prepare_game_creator_private_path_for_read, write_game_creator_private_file};
use crate::project::{enforce_project_permission_policy, project_append_lock_for};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
/// 行信封类型,与既有历史文件同构(`{"type": …, "payload": {…}}`)。
pub(crate) const DIRECT_TURN_STREAM_RECORD_TYPE: &str = "turn_stream_item";
/// 条目 schema 版本。
pub(crate) const DIRECT_TURN_STREAM_SCHEMA_VERSION: &str = "agc-turn-stream.v1";
/// 回读上限:只保留最后这么多条(按 `seq` 取最新)。
pub(crate) const DIRECT_TURN_STREAM_LIMIT: usize = 400;
/// 单条文本段的字符上限(与工具明细同口径的截断,避免单段失控)。
const DIRECT_TURN_STREAM_TEXT_MAX_CHARS: usize = 8000;
/// 没有流式分段时,最终回复那一段的固定 item id。
const DIRECT_TURN_STREAM_FINAL_ITEM_ID: &str = "final";
/// 回合失败说明那一段的固定 item id:失败说明也是这一回合的内容,排在流末尾。
pub(crate) const DIRECT_TURN_STREAM_FAILURE_ITEM_ID: &str = "failure";
/// 文本段。
pub(crate) const DIRECT_TURN_STREAM_KIND_TEXT: &str = "text";
/// 工具调用的位置标记。
pub(crate) const DIRECT_TURN_STREAM_KIND_TOOL: &str = "tool";
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DirectTurnStreamItem {
pub(crate) schema_version: String,
/// 幂等身份:文本段 `text:<turnId>:<itemId>`、工具 `tool:<turnId>:<callId>`。
pub(crate) id: String,
pub(crate) turn_id: String,
/// `text` | `tool`
pub(crate) kind: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) text: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) call_id: Option<String>,
/// 首次出现的写入序号:**顺序真相**,同刻按它排序。
pub(crate) seq: u64,
/// 条目首次出现的本机毫秒时刻。
pub(crate) at: u64,
pub(crate) updated_at: u64,
}
#[cfg(test)]
mod snapshot_tests {
use super::*;
fn text(
turn: &str,
id: &str,
seq: u64,
at: u64,
updated: u64,
text: &str,
) -> DirectTurnStreamItem {
direct_turn_stream_text_item(Path::new("."), turn, id, text, seq, at, updated)
}
#[test]
fn late_older_snapshot_cannot_undo_completed_text_or_position() {
let complete = text("turn", "item", 1, 1000, 1002, "正文");
let late = text("turn", "item", 9, 1001, 1001, "更长但已经过期的草稿");
let merged = normalize_stream_items(vec![complete, late]);
assert_eq!(merged.len(), 1);
assert_eq!(merged[0].text.as_deref(), Some("正文"));
assert_eq!(merged[0].seq, 1);
assert_eq!(merged[0].at, 1000);
}
#[test]
fn retention_does_not_treat_new_turn_seq_one_as_oldest() {
let mut snapshots = (1..=DIRECT_TURN_STREAM_LIMIT)
.map(|seq| text("old", &seq.to_string(), seq as u64, 1000, 1000, ""))
.collect::<Vec<_>>();
snapshots.push(text("new", "one", 1, 2000, 2000, ""));
let merged = normalize_stream_items(snapshots);
assert_eq!(merged.len(), DIRECT_TURN_STREAM_LIMIT);
assert_eq!(merged.last().unwrap().turn_id, "new");
}
}
impl DirectTurnStreamItem {
fn order_key(&self) -> (u64, u64, &str) {
(self.seq, self.at, self.id.as_str())
}
}
fn turn_stream_path(root: &Path) -> PathBuf {
root.join(".agent/conversations/turn-stream.jsonl")
}
/// 文本段条目的幂等 id:同一个 Codex assistant item 只占一行。
pub(crate) fn direct_turn_stream_text_item_id(turn_id: &str, item_id: &str) -> String {
format!("text:{}:{}", turn_id.trim(), item_id.trim())
}
/// 工具条目(位置标记)的幂等 id:同一个 callId 只占一行。
pub(crate) fn direct_turn_stream_tool_item_id(turn_id: &str, call_id: &str) -> String {
format!("tool:{}:{}", turn_id.trim(), call_id.trim())
}
/// 构造一条文本段条目:脱敏 + 截断与 `tool-calls.jsonl` 同口径。
pub(crate) fn direct_turn_stream_text_item(
root: &Path,
turn_id: &str,
item_id: &str,
text: &str,
seq: u64,
at: u64,
updated_at: u64,
) -> DirectTurnStreamItem {
DirectTurnStreamItem {
schema_version: DIRECT_TURN_STREAM_SCHEMA_VERSION.to_string(),
id: direct_turn_stream_text_item_id(turn_id, item_id),
turn_id: turn_id.trim().to_string(),
kind: DIRECT_TURN_STREAM_KIND_TEXT.to_string(),
text: Some(sanitize_stream_text(root, text)),
call_id: None,
seq,
at,
updated_at,
}
}
/// 构造一条工具条目:只记位置,正文仍来自 `DirectToolCall`。
pub(crate) fn direct_turn_stream_tool_item(
turn_id: &str,
call: &crate::DirectToolCall,
seq: u64,
at: u64,
) -> DirectTurnStreamItem {
DirectTurnStreamItem {
schema_version: DIRECT_TURN_STREAM_SCHEMA_VERSION.to_string(),
id: direct_turn_stream_tool_item_id(turn_id, &call.id),
turn_id: turn_id.trim().to_string(),
kind: DIRECT_TURN_STREAM_KIND_TOOL.to_string(),
text: None,
call_id: Some(call.id.trim().to_string()),
seq,
at,
updated_at: call.updated_at,
}
}
/// 文本脱敏 + 截断:与 `tool-calls.jsonl` 同一套 `sanitize_detail_text`。
pub(crate) fn sanitize_stream_text(root: &Path, text: &str) -> String {
let sanitized = sanitize_detail_text(root, text);
if sanitized.chars().count() <= DIRECT_TURN_STREAM_TEXT_MAX_CHARS {
return sanitized;
}
let mut truncated = sanitized
.chars()
.take(DIRECT_TURN_STREAM_TEXT_MAX_CHARS)
.collect::<String>();
truncated.push('…');
truncated
}
fn record_line(item: &DirectTurnStreamItem) -> Result<String, String> {
serde_json::to_string(&serde_json::json!({
"type": DIRECT_TURN_STREAM_RECORD_TYPE,
"payload": item,
}))
.map_err(|error| format!("序列化回合流条目失败:{error}"))
}
/// 解析一行信封;坏行 / 非本文件条目都返回 `None`(尽力而为的展示数据,不整体失败)。
fn stream_item_from_line(line: &str) -> Option<DirectTurnStreamItem> {
let trimmed = line.trim();
if trimmed.is_empty() {
return None;
}
let parsed: Value = serde_json::from_str(trimmed).ok()?;
if parsed.get("type").and_then(Value::as_str) != Some(DIRECT_TURN_STREAM_RECORD_TYPE) {
return None;
}
let payload = parsed.get("payload")?;
let mut item: DirectTurnStreamItem = serde_json::from_value(payload.clone()).ok()?;
if item.id.trim().is_empty() || item.turn_id.trim().is_empty() {
return None;
}
if !matches!(
item.kind.as_str(),
DIRECT_TURN_STREAM_KIND_TEXT | DIRECT_TURN_STREAM_KIND_TOOL
) {
return None;
}
if item.schema_version.trim().is_empty() {
item.schema_version = DIRECT_TURN_STREAM_SCHEMA_VERSION.to_string();
}
Some(item)
}
fn read_stream_lines(path: &Path) -> Vec<DirectTurnStreamItem> {
let Ok(file) = File::open(path) else {
return Vec::new();
};
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
let mut items = Vec::new();
loop {
buffer.clear();
match reader.read_until(b'\n', &mut buffer) {
Ok(0) => break,
// 单行解码失败(非法 UTF-8)只跳过这一行,继续读后面的行。
Ok(_) => match std::str::from_utf8(&buffer) {
Ok(line) => {
if let Some(item) = stream_item_from_line(line) {
items.push(item);
}
}
Err(_) => continue,
},
// 读 I/O 错误:无法再定位下一行边界,停止读取(已读到的照常返回)。
Err(_) => break,
}
}
items
}
/// 同一 id 的重复行合并:`seq` 取最早(位置钉死,后到的不得回退),`at` 取最早非零,
/// `updated_at` 取最大;文本只在更新(或同刻更长)的快照上替换。
fn merge_stream_snapshot(
existing: &DirectTurnStreamItem,
incoming: &DirectTurnStreamItem,
) -> DirectTurnStreamItem {
let text_len = |item: &DirectTurnStreamItem| {
item.text
.as_deref()
.map(str::chars)
.map(Iterator::count)
.unwrap_or_default()
};
// writer 保证更新时间单调;完成快照可以纠正正文,旧快照不能靠更长抢回所有权。
let take_incoming = incoming.updated_at > existing.updated_at
|| (incoming.updated_at == existing.updated_at && text_len(incoming) > text_len(existing));
let mut merged = existing.clone();
if take_incoming {
merged.text = incoming.text.clone();
}
merged.updated_at = merged.updated_at.max(incoming.updated_at);
if merged.call_id.is_none() {
merged.call_id = incoming.call_id.clone();
}
merged.seq = merged.seq.min(incoming.seq);
merged.at = [merged.at, incoming.at]
.into_iter()
.filter(|at| *at > 0)
.min()
.unwrap_or_default();
merged
}
/// 按身份归并;跨回合按起点,回合内按 seq,不能用局部 seq 判断全局新旧。
fn normalize_stream_items(items: Vec<DirectTurnStreamItem>) -> Vec<DirectTurnStreamItem> {
let mut by_id: BTreeMap<String, DirectTurnStreamItem> = BTreeMap::new();
for item in items {
let merged = match by_id.remove(&item.id) {
Some(previous) => merge_stream_snapshot(&previous, &item),
None => item,
};
by_id.insert(merged.id.clone(), merged);
}
let mut normalized = by_id.into_values().collect::<Vec<_>>();
let mut turn_starts = BTreeMap::<String, u64>::new();
for item in &normalized {
turn_starts
.entry(item.turn_id.clone())
.and_modify(|at| *at = (*at).min(item.at))
.or_insert(item.at);
}
normalized.sort_by(|left, right| {
(turn_starts[&left.turn_id], &left.turn_id, left.order_key()).cmp(&(
turn_starts[&right.turn_id],
&right.turn_id,
right.order_key(),
))
});
if normalized.len() > DIRECT_TURN_STREAM_LIMIT {
normalized.drain(..normalized.len() - DIRECT_TURN_STREAM_LIMIT);
}
normalized
}
/// 锁内读改写:整文件重写(追加与就地更新混用,没有纯追加的 JSONL 语义)。
/// 文件规模由 400 条上限与 8000 字符截断兜住。
fn with_locked_stream_items<T>(
root: &Path,
mutate: impl FnOnce(&mut Vec<DirectTurnStreamItem>) -> T,
) -> Result<T, String> {
let path = turn_stream_path(root);
let _project_lock = crate::acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"conversation.write",
)?;
let lock = project_append_lock_for(&path)?;
let _append_guard = lock.lock("回合流写入")?;
let mut items = read_stream_lines(&path);
let outcome = mutate(&mut items);
let normalized = normalize_stream_items(items);
let mut body = String::new();
for item in &normalized {
body.push_str(&record_line(item)?);
body.push('\n');
}
write_game_creator_private_file(&path, body.as_bytes(), "回合流历史")?;
Ok(outcome)
}
/// 幂等 upsert 一条回合流条目。
///
/// 位置(`seq` / `at`)只在第一次出现时确定:同一 id 的后续快照不得回退位置,
/// 也不得把已经写下的文本改短(并发落盘下"后到的旧快照"不会覆盖新快照)。
pub(crate) fn upsert_direct_turn_stream_item_at(
root: &Path,
item: &DirectTurnStreamItem,
) -> Result<(), String> {
enforce_project_permission_policy(root, "conversation.write")?;
with_locked_stream_items(root, |items| {
// normalize_stream_items 在锁内归并全部版本;不得提前删除比较基准。
items.push(item.clone());
})
}
/// 回读:文件缺失返回空数组;单行损坏跳过;按 `seq` 正序,最多最后 400 条。
pub(crate) fn read_direct_turn_stream_at(root: &Path) -> Result<Vec<DirectTurnStreamItem>, String> {
let path = turn_stream_path(root);
if !prepare_game_creator_private_path_for_read(&path, false, "回合流历史")? {
return Ok(Vec::new());
}
Ok(normalize_stream_items(read_stream_lines(&path)))
}
/// 追加一段固定身份的文本段(失败说明等):位置排在当前流末尾。
///
/// 幂等:同一 `(turnId, itemId)` 已经存在时只更新文本与 `updatedAt`(回合重放 / 重复收尾
/// 不会多出一段)。返回写下的那一条,调用方用它下发同一份快照。
pub(crate) fn append_direct_turn_stream_text_at(
root: &Path,
turn_id: &str,
item_id: &str,
text: &str,
) -> Result<Option<DirectTurnStreamItem>, String> {
let turn_id = turn_id.trim();
let text = text.trim();
if turn_id.is_empty() || text.is_empty() {
return Ok(None);
}
let sanitized = sanitize_stream_text(root, text);
let item_id = item_id.trim();
enforce_project_permission_policy(root, "conversation.write")?;
let now = crate::agent::direct_tool_call_now_ms();
with_locked_stream_items(root, |items| {
let existing_id = direct_turn_stream_text_item_id(turn_id, item_id);
if let Some(existing) = items.iter_mut().find(|item| item.id == existing_id) {
// 位置不动:只替换文本与 updatedAt。
existing.text = Some(sanitized.clone());
existing.updated_at = now.max(existing.updated_at);
return Some(existing.clone());
}
// 首次出现:位置钉在末尾(当前最大 seq + 1)。
let next_seq = items.iter().map(|item| item.seq).max().unwrap_or(0) + 1;
let item = DirectTurnStreamItem {
schema_version: DIRECT_TURN_STREAM_SCHEMA_VERSION.to_string(),
id: existing_id,
turn_id: turn_id.to_string(),
kind: DIRECT_TURN_STREAM_KIND_TEXT.to_string(),
text: Some(sanitized),
call_id: None,
seq: next_seq,
at: now,
updated_at: now,
};
items.push(item.clone());
Some(item)
})
}
/// 没有任何 item 文本时补最终回复;已有 item 由完成事件负责,不能猜测覆盖某一段。
pub(crate) fn finalize_direct_turn_stream_reply_at(
root: &Path,
turn_id: &str,
visible_reply: &str,
) -> Result<Option<DirectTurnStreamItem>, String> {
let turn_id = turn_id.trim();
if turn_id.is_empty() || visible_reply.trim().is_empty() {
return Ok(None);
}
// 入口再做一次可见性投影:调用方给的是原始回复时,思考块不能落进对话流。
let visible_reply = crate::agent::project_direct_codex_visible_text(visible_reply)
.unwrap_or_else(|| visible_reply.trim().to_string());
let visible_reply = visible_reply.as_str();
enforce_project_permission_policy(root, "conversation.write")?;
let now = crate::agent::direct_tool_call_now_ms();
with_locked_stream_items(root, |items| {
if items
.iter()
.any(|item| item.turn_id == turn_id && item.kind == DIRECT_TURN_STREAM_KIND_TEXT)
{
None
} else {
let next_seq = items
.iter()
.filter(|item| item.turn_id == turn_id)
.map(|item| item.seq)
.max()
.unwrap_or(0)
+ 1;
let item = direct_turn_stream_text_item(
root,
turn_id,
DIRECT_TURN_STREAM_FINAL_ITEM_ID,
visible_reply,
next_seq,
now,
now,
);
items.push(item.clone());
Some(item)
}
})
}
@@ -6519,6 +6519,7 @@ impl PlatformArtSliceContractRollback {
fn validate_strict_platform_art_spritesheet_contract(
slices: &[PreparedPlatformArtAssetSlice],
slice_warning: Option<&str>,
canvas_context: &ExternalCanvasGenerationContext,
canvas_project_id: Option<&str>,
resource_id: Option<&str>,
@@ -6532,7 +6533,11 @@ fn validate_strict_platform_art_spritesheet_contract(
has_visible_pixels: bool,
) -> Result<(), String> {
if slices.is_empty() {
return Err("spritesheet 图集至少需要一个独立切片".to_string());
return Err(slice_warning
.map(str::trim)
.filter(|warning| !warning.is_empty())
.map(|warning| format!("spritesheet 图集至少需要一个独立切片;原始切片告警:{warning}"))
.unwrap_or_else(|| "spritesheet 图集至少需要一个独立切片".to_string()));
}
let resource_id = resource_id
.map(str::trim)
@@ -7337,6 +7342,7 @@ fn commit_prepared_platform_art_asset_with_before_replace_hook(
if require_complete_core_slices {
validate_strict_platform_art_spritesheet_contract(
&slices,
slice_warning.as_deref(),
&canvas_context,
canvas_project_id.as_deref(),
resource_id.as_deref(),
@@ -9798,6 +9804,7 @@ mod canvas_generation_tests {
.collect::<Vec<_>>();
validate_strict_platform_art_spritesheet_contract(
&slices,
None,
&canvas_context,
Some("canvas-project"),
Some("spritesheet-resource"),
@@ -9813,6 +9820,35 @@ mod canvas_generation_tests {
.expect("valid slice identities and pixel evidence do not require a fixed layout");
}
#[test]
fn strict_spritesheet_contract_preserves_slice_warning_when_empty() {
let canvas_context = ExternalCanvasGenerationContext {
project_id: "canvas-project".to_string(),
asset_folder_id: "asset-folder".to_string(),
canvas_name: "empty-slice-warning".to_string(),
};
let error = validate_strict_platform_art_spritesheet_contract(
&[],
Some("识别出的素材数量超过输出上限:86,最多允许 256 个"),
&canvas_context,
None,
None,
None,
None,
"route",
"kind",
None,
&[],
false,
false,
)
.expect_err("empty slices must expose the original platform warning");
assert!(error.contains("至少需要一个独立切片"));
assert!(error.contains("原始切片告警"));
assert!(error.contains("识别出的素材数量超过输出上限:86,最多允许 256 个"));
}
#[test]
fn strict_spritesheet_contract_rejects_an_opaque_slice() {
let canvas_context = ExternalCanvasGenerationContext {
@@ -9850,6 +9886,7 @@ mod canvas_generation_tests {
let error = validate_strict_platform_art_spritesheet_contract(
&slices,
None,
&canvas_context,
Some("canvas-project"),
Some("spritesheet-resource"),
@@ -12449,7 +12486,10 @@ mod canvas_generation_tests {
.expect("init strict slice project");
let path = root.join("assets/art-spritesheet.png");
fs::write(&path, b"old-image").expect("write old spritesheet");
let prepared = prepared_replacement(root, b"new-image");
let mut prepared = prepared_replacement(root, b"new-image");
prepared.slice_warning = Some(
"图标 spritesheet 识别出的素材数量超过输出上限:86,最多允许 256 个。".to_string(),
);
let error = commit_prepared_platform_art_asset_strict_slices_at(
root,
@@ -12460,6 +12500,8 @@ mod canvas_generation_tests {
.expect_err("strict spritesheet commit must require at least one slice");
assert!(error.contains("至少需要一个独立切片"));
assert!(error.contains("原始切片告警"));
assert!(error.contains("识别出的素材数量超过输出上限:86,最多允许 256 个"));
assert_eq!(fs::read(path).expect("read preserved sheet"), b"old-image");
assert!(!root
.join("assets/art-spritesheet-slices/manifest.json")
@@ -49,6 +49,61 @@ impl DirectGameCreatorTurnUpdateEmitter {
status: &'static str,
activity: Option<&'static str>,
accumulated_text: Option<String>,
tool_calls: Option<Vec<crate::DirectToolCall>>,
) {
self.emit_with_reasoning(status, activity, accumulated_text, tool_calls, None);
}
/// 带思考过程的回合更新:`reasoning_text` 为"当前累计的思考全文"(前端整段替换)。
pub(crate) fn emit_with_reasoning(
&self,
status: &'static str,
activity: Option<&'static str>,
accumulated_text: Option<String>,
tool_calls: Option<Vec<crate::DirectToolCall>>,
reasoning_text: Option<String>,
) {
self.emit_full(
status,
activity,
accumulated_text,
tool_calls,
reasoning_text,
Vec::new(),
);
}
/// 带回合流的回合更新:`stream_items` 是"顺序真相"里本次变化的那几条。
///
/// 前端按这些条目的 `seq` 顺序渲染,所以它们必须来自与落盘同一份数据,
/// 不能在前端各算一套顺序。
pub(crate) fn emit_with_stream_items(
&self,
status: &'static str,
activity: Option<&'static str>,
accumulated_text: Option<String>,
tool_calls: Option<Vec<crate::DirectToolCall>>,
stream_items: Vec<crate::DirectTurnStreamItem>,
) {
self.emit_full(
status,
activity,
accumulated_text,
tool_calls,
None,
stream_items,
);
}
#[allow(clippy::too_many_arguments)]
fn emit_full(
&self,
status: &'static str,
activity: Option<&'static str>,
accumulated_text: Option<String>,
tool_calls: Option<Vec<crate::DirectToolCall>>,
reasoning_text: Option<String>,
stream_items: Vec<crate::DirectTurnStreamItem>,
) {
let status_is_allowed = matches!(
status,
@@ -100,6 +155,9 @@ impl DirectGameCreatorTurnUpdateEmitter {
status: status.to_string(),
activity: activity.map(str::to_string),
accumulated_text,
tool_calls,
reasoning_text,
stream_items: (!stream_items.is_empty()).then_some(stream_items),
updated_at,
},
);
@@ -1996,6 +1996,29 @@ pub(crate) fn write_game_creator_app_config(
persist_game_creator_app_config(config, overlays, false)
}
#[tauri::command]
pub(crate) fn cancel_direct_codex_turn(
project_path: String,
client_turn_id: Option<String>,
) -> Result<DirectTurnCancelView, String> {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "agent.kill")?;
cancel_direct_codex_turn_at(root, client_turn_id.as_deref())
}
#[tauri::command]
pub(crate) fn select_game_creator_reasoning_effort(
effort: String,
) -> Result<GameCreatorAppConfigView, String> {
let _guard = GAME_CREATOR_CONFIG_WRITE_LOCK
.lock()
.map_err(|_| "配置写入锁不可用")?;
let effort = game_creator_llm_reasoning_effort_name(&effort, "llm.reasoningEffort")?;
let (mut config, overlays) = load_game_creator_app_config_for_write()?;
config.llm.reasoning_effort = effort;
persist_game_creator_app_config(config, overlays, false)
}
#[tauri::command]
pub(crate) fn select_game_creator_model(
model_id: String,
@@ -5170,7 +5193,12 @@ pub(crate) fn create_game_creator_agent_session(
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
enforce_project_permission_policy(root, "conversation.write")?;
let _lock = acquire_project_write_lock(root, "conversation.write")?;
// 首轮策划消息可能紧跟项目初始化写入到达;对话保存应等待这段短暂的
// 项目锁竞争,避免把可恢复的初始化竞态直接显示成保存失败。
let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait(
root,
"conversation.write",
)?;
create_game_creator_agent_session_at(root, agent_id.trim(), title.trim())
}
@@ -5278,6 +5306,31 @@ pub(crate) async fn read_agent_runtime_error_detail(
.await
.map_err(|error| format!("读取统一错误诊断后台任务失败:{error}"))?
}
#[tauri::command]
pub(crate) async fn read_direct_tool_calls(
project_path: String,
) -> Result<Vec<DirectToolCall>, String> {
tauri::async_runtime::spawn_blocking(move || {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
read_direct_tool_calls_at(root)
})
.await
.map_err(|error| format!("读取工具调用历史后台任务失败:{error}"))?
}
#[tauri::command]
pub(crate) async fn read_direct_turn_stream(
project_path: String,
) -> Result<Vec<DirectTurnStreamItem>, String> {
tauri::async_runtime::spawn_blocking(move || {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
read_direct_turn_stream_at(root)
})
.await
.map_err(|error| format!("读取回合流历史后台任务失败:{error}"))?
}
#[tauri::command]
pub(crate) fn list_game_creator_direct_active_turns(
@@ -5325,12 +5378,16 @@ pub(crate) async fn read_direct_project_history_slice(
tauri::async_runtime::spawn_blocking(move || {
let root = Path::new(project_path.trim());
enforce_project_permission_policy(root, "conversation.read")?;
let (items, has_more) = read_direct_project_history_items_slice_at(
let (items, has_more, item_timestamps) = read_direct_project_history_items_slice_at(
root,
before_item_id.as_deref(),
limit.unwrap_or(20),
)?;
Ok(DirectThreadHistorySlice { items, has_more })
Ok(DirectThreadHistorySlice {
items,
has_more,
item_timestamps,
})
})
.await
.map_err(|error| format!("读取 DirectProject 历史切片后台任务失败:{error}"))?
@@ -1010,6 +1010,17 @@ struct GameCreatorDirectTurnUpdateEvent {
status: String,
activity: Option<String>,
accumulated_text: Option<String>,
/// 本回合内发生变化的结构化工具调用集合(只有变化时才带,老事件没有这个字段)。
/// `skip_serializing_if`:字段缺席时前端拿到 `undefined`,行为与改造前一致。
#[serde(skip_serializing_if = "Option::is_none")]
tool_calls: Option<Vec<crate::DirectToolCall>>,
/// 本回合当前累计的思考过程(流式整段替换);拿不到时字段缺席。
#[serde(skip_serializing_if = "Option::is_none")]
reasoning_text: Option<String>,
/// 本回合**顺序真相**里本次发生变化的那几条(文本段 / 工具位置标记)。
/// `skip_serializing_if`:字段缺席时前端拿到 `undefined`,行为与改造前一致。
#[serde(skip_serializing_if = "Option::is_none")]
stream_items: Option<Vec<crate::DirectTurnStreamItem>>,
updated_at: u64,
}
@@ -2666,6 +2677,8 @@ fn main() {
chat_with_game_creator_role_agent,
chat_with_game_creator_role_agent_stream,
chat_with_game_creator_direct_codex,
cancel_direct_codex_turn,
select_game_creator_reasoning_effort,
start_planning_session_v2,
continue_planning_session_v2,
decide_planning_artifact_v2,
@@ -2769,6 +2782,8 @@ fn main() {
archive_game_creator_agent_session,
read_local_conversation,
read_direct_project_conversation,
read_direct_tool_calls,
read_direct_turn_stream,
read_agent_runtime_error_detail,
list_game_creator_direct_active_turns,
subscribe_direct_project_thread,
@@ -272,7 +272,7 @@ fn project_history_skips_direct_project_rows_but_keeps_other_broken_rows_failing
}
#[test]
fn mixed_project_history_rows_stay_readable_from_both_sides() {
fn mixed_project_history_rows_read_by_generic_chain_but_fail_closed_for_direct_project() {
let root = unique_conversation_test_root();
init_local_game_project_at(&root, "project-1", "写侧统一测试").expect("init project");
write_project_history(&root, &[DIRECT_PROJECT_ROW]);
@@ -309,19 +309,12 @@ fn mixed_project_history_rows_stay_readable_from_both_sides() {
vec!["模式切换后由通用写入器补写的回复", "带 id 的旧格式回复"]
);
// 反向:DirectProject 链把同一份文件里的两种行都读出来,混合文件不构成毒化
let direct_items = crate::agent::read_direct_project_history_items_at(&root)
.expect("DirectProject must keep reading the mixed history");
assert_eq!(
direct_items
.iter()
.map(|item| item["content"][0]["text"].as_str().unwrap_or_default())
.collect::<Vec<_>>(),
vec![
"再加一个按钮",
"模式切换后由通用写入器补写的回复",
"带 id 的旧格式回复",
]
// 反向:DirectProject 链只接受 response_item 信封,混合文件里的 legacy 行让它失败关闭
let error = crate::agent::read_direct_project_history_items_at(&root)
.expect_err("DirectProject must fail closed on legacy rows");
assert!(
error.starts_with("DirectProject 历史记录类型无效"),
"{error}"
);
std::fs::remove_dir_all(root).ok();
File diff suppressed because it is too large Load Diff
+111
View File
@@ -1096,6 +1096,57 @@ export type GameCreatorDirectTurnActivity =
| 'response-finalization'
| 'none';
export type GameCreatorDirectToolCallKind =
| 'command'
| 'file_change'
| 'mcp_tool'
| 'web_search'
| 'context_compaction'
| 'other';
export type GameCreatorDirectToolCallStatus =
| 'running'
| 'completed'
| 'failed';
export interface GameCreatorDirectToolCallChange {
path: string;
kind: 'add' | 'update' | 'delete' | string;
}
export interface GameCreatorDirectToolCallDetail {
command?: string;
output?: string;
changes?: GameCreatorDirectToolCallChange[];
}
/**
* 一条工具调用(Codex item 的结构化投影)。
*
* 契约见 `docs/technical/【技术方案】GameAgent对话工具调用卡片-2026-09-14.md`
* 字段形状与 Rust 侧 `DirectToolCall`、独立历史文件
* `.agent/conversations/tool-calls.jsonl` 的 payload 一致(这里少 `turnId` 的变体用于
* 事件增量,见下面 `GameCreatorDirectTurnToolCall`)。
*/
export interface GameCreatorDirectToolCall {
schemaVersion: string;
id: string;
turnId: string;
kind: GameCreatorDirectToolCallKind;
title: string;
summary: string;
status: GameCreatorDirectToolCallStatus;
detail: GameCreatorDirectToolCallDetail;
startedAt: number;
updatedAt: number;
}
/** 事件里下发的增量条目:与持久化同形,去掉 `turnId`(回合 id 在事件顶层)。 */
export type GameCreatorDirectTurnToolCall = Omit<
GameCreatorDirectToolCall,
'turnId'
>;
export interface GameCreatorDirectTurnUpdateEvent {
projectPath: string;
turnId: string;
@@ -1103,9 +1154,69 @@ export interface GameCreatorDirectTurnUpdateEvent {
status: GameCreatorDirectTurnUpdateStatus;
activity?: GameCreatorDirectTurnActivity | null;
accumulatedText?: string | null;
/**
* 本回合内**发生变化**的结构化工具调用(只有变化时才带,不是每个 heartbeat 都带全量)。
* 可选:老版本事件没有这个字段,前端拿到 `undefined` 时必须与改造前行为一致。
*/
toolCalls?: GameCreatorDirectTurnToolCall[] | null;
/**
* 本回合当前累计的思考过程(流式,整段替换);拿不到时字段缺席。
*/
reasoningText?: string | null;
/**
* 「文本段 + 工具」的**顺序真相**里本次发生变化的那几条。
*
* 顺序由 `seq`(条目首次出现时钉死)决定,与落盘 `turn-stream.jsonl` 完全同一份数据,
* 前端不再自己猜切点。可选:老版本事件没有这个字段。
*/
streamItems?: TurnStreamItem[] | null;
updatedAt: number;
}
/** 回合流里的一个 `text` 段;`text` 是该段当前累计全文(会随 delta 增长)。 */
export interface TurnStreamTextItem extends TurnStreamItemBase {
kind: 'text';
text: string;
}
/** 回合流里的一个 `tool` 位置标记;工具正文在 `tool-calls.jsonl`(按 `callId` 关联)。 */
export interface TurnStreamToolItem extends TurnStreamItemBase {
kind: 'tool';
callId: string;
}
interface TurnStreamItemBase {
schemaVersion: string;
/** 幂等身份:文本段 `text:<turnId>:<itemId>`、工具 `tool:<turnId>:<callId>`。 */
id: string;
turnId: string;
/** 首次出现的写入序号:**顺序真相**,按它升序渲染。 */
seq: number;
/** 条目首次出现的时刻(Unix 毫秒),同 `seq` 时用它排序。 */
at: number;
updatedAt: number;
}
/**
* 回合流条目(`read_direct_turn_stream` 的返回元素)。
*
* 与 Rust `DirectTurnStreamItem` 同形:`text` 段 ↔ `tool` 位置标记。
*/
export type TurnStreamItem = TurnStreamTextItem | TurnStreamToolItem;
/** `cancel_direct_codex_turn` 的返回值。 */
export interface DirectTurnCancelView {
/**
* `interrupted` = 已向正在跑的回合发出中断,界面等这一轮自己的收尾复位;
* `released` = app-server 侧已无句柄,本轮守卫被兜底释放,界面必须自己复位。
*/
outcome: string;
/** 给用户看的可读结果。 */
message: string;
/** 被终止 / 被释放的 clientTurnId。 */
clientTurnId: string;
}
export interface AgentRunControlResult {
runId: string;
status: string;

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