Compare commits

...

45 Commits

Author SHA1 Message Date
kdletters 3fc3399032 接入 DotCraft Unity 编辑器插件
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m41s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m8s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m16s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m38s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m11s
Project CI / Repository checks (pull_request) Successful in 4m22s
Project CI / Frontend tests (pull_request) Successful in 5m52s
Project CI / Native shell tests (pull_request) Successful in 9m52s
Project CI / Backend tests (pull_request) Successful in 11m3s
Project CI / AI game creator shell web tests (pull_request) Successful in 6m41s
新增内置 Unity 插件与自包含 Attach helper,固定上游版本并保留许可证
统一 Runner 执行归属、项目身份校验、回执确认和不确定结果阻断
接入 Unity 工程导入、Agent 工具目录及 Windows 构建分发
补齐插件测试、CI 执行入口和 Windows .NET 10 工具链检查
记录真实 Unity 连接、执行、重载恢复及 Runner 边界验证
2026-09-18 19:44:36 +08:00
suzmii 9a21690fe9 Merge pull request '修复 AGC 画布生成、卡片展示与布局交互' (#412) from feat/agc-canvas-acceptance into master
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 5m52s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 5m56s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 4m29s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 5m8s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m7s
Project CI / AI game creator shell Rust crates (push) Successful in 2m27s
Project CI / Frontend tests (push) Successful in 8m33s
Project CI / Repository checks (push) Successful in 10m19s
Project CI / AI game creator shell web tests (push) Successful in 6m51s
Project CI / Backend tests (push) Successful in 18m39s
Project CI / Native shell tests (push) Successful in 20m53s
2026-09-18 15:50:44 +08:00
suzmii deff6319e2 合并主线项目创建目录选择更新
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m53s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m31s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m12s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m5s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m54s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m12s
Project CI / Backend tests (pull_request) Successful in 9m37s
Project CI / Native shell tests (pull_request) Successful in 12m21s
Project CI / Repository checks (pull_request) Successful in 11m10s
Project CI / AI game creator shell web tests (pull_request) Successful in 12m51s
Project CI / Frontend tests (pull_request) Successful in 23m14s
合入 master ee33af7b7 的项目目录选择与模板建项说明。
保留本批画布和对话修复,自动合并无冲突,类型检查通过。
2026-09-18 15:48:10 +08:00
suzmii 2257a78d6b 收口思考等待反馈与紧凑过程样式及失败状态
思考展开时隐藏重复预览并使用灯泡图标,首个响应前显示临时思考提示。
统一分钟内小数和分钟以上整数秒,保持内外过程的紧凑间距与正文层级。
失败工具与分组显示错误色,非零退出码不再误判为成功。
补齐状态、格式、样式和交互回归,本批其余需求留待后续PR。
2026-09-18 15:46:30 +08:00
suzmii a0f6b7c67d 统一思考与执行过程折叠样式及中文耗时
思考展开复用安全Markdown渲染,折叠显示浅色单行纯文本预览与右侧箭头。
共享过程摘要组件,执行过程按真实操作数显示统计并保留各组独立计时。
工具、整轮、生成任务和策划耗时统一为中文时分秒格式。
补齐格式与Markdown回归、更新已有界面断言并验证窄屏键盘操作。
2026-09-18 14:34:10 +08:00
kdletters ee33af7b75 补充模板建项命令的项目创建目录说明
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 6m4s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 6m4s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 6m13s
Project CI / AI game creator shell Rust smoke (push) Successful in 3m3s
Project CI / AI game creator shell Rust crates (push) Successful in 2m8s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 6m0s
Project CI / Frontend tests (push) Successful in 10m59s
Project CI / Repository checks (push) Successful in 10m54s
Project CI / Native shell tests (push) Successful in 13m12s
Project CI / Backend tests (push) Successful in 14m33s
Project CI / AI game creator shell web tests (push) Successful in 5m37s
- 模板库技术方案的命令表补记 create_automatic_local_game_project_from_template 支持 projectsRoot 覆盖,未指定时仍是应用数据目录
2026-09-18 14:30:02 +08:00
kdletters be370cc615 AGC 支持用户自选项目创建目录
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m12s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m17s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m26s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m37s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m57s
Project CI / AI game creator shell Rust crates (push) Successful in 2m10s
Project CI / Frontend tests (push) Successful in 6m4s
Project CI / Native shell tests (push) Successful in 8m58s
Project CI / Backend tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
- 新增设置「工作区」分类,承载「项目创建目录」的选择与恢复默认入口
- 自动建项命令新增可选 projectsRoot,未指定时沿用 AppData projects 默认目录
- 新增项目创建目录校验与解析:只接受目录选择器返回且通过私有路径门禁的既有目录
- 目录选择器新增可选 title 参数,用于「选择项目创建目录」文案
- 首页自动建项与模板建项在建项时携带用户选定的项目创建目录
- 客户端本地保存项目创建目录偏好,建项时重新校验,不作为授权凭据
- 补充设置页工作区、首页建项与目录偏好单测
- 修复项目开发视图缺失的 Shapes 图标导入,恢复 agc:typecheck 通过
- 同步实施计划、决策日志与排障记忆文档
2026-09-18 14:10:05 +08:00
suzmii 21b059bd41 修正工具组独立计时并移除重复整轮耗时
每个工具组只计算自身调用跨度,已完成组立即停止增长与进行中标记。
整轮总耗时仅在回合状态或完成小结显示一次,单工具仍按百毫秒动态更新。
补齐前组已完成后组运行、边界缺失与倒序的回归及浏览器证据。
2026-09-18 13:45:27 +08:00
suzmii 2d90b73b94 合并主线Mac运行资源与发布门禁更新
合入 origin/master b137f1759,保留画布验收与计时返修。
同步主线0.1.67版本及Mac随包资源、插件能力和发布目标检查。
合并双方排障说明,不提交个人依赖锁文件改动。
2026-09-18 13:27:28 +08:00
suzmii cd8f480719 修复生成草稿隔离与回合计时恢复及布局排队
按占位身份隔离生成面板并保存草稿,阻止改变失败请求输入后伪装重试。
保留用户真实发送时间,按既有用户条目身份恢复空运行态回合的终态边界。
修复跨栏目整理队列覆盖及多选拖动预览起点漂移。
补齐六项 review 发现的回归、原生绑定、交叉审查及浏览器验收记录。
2026-09-18 13:25:18 +08:00
suzmii 2ffccb844c 修复对话动态计时与初始消息重复并补齐游戏居中指引
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 5m51s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m45s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m31s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m3s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m4s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m38s
Project CI / Repository checks (pull_request) Successful in 5m39s
Project CI / Frontend tests (pull_request) Successful in 7m45s
Project CI / Backend tests (pull_request) Successful in 10m3s
Project CI / Native shell tests (pull_request) Successful in 8m54s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m31s
区分整轮总耗时与工具实际阶段时间,以百毫秒刷新一位小数并冻结终态。
修复正式与乐观用户消息到达时初始占位重复,保留真实同文多次发送。
在开发 Agent 系统提示与 Skill 明确禁止 Phaser 和 CSS 双重居中。
补齐原生事件、前端回归与验收文档,保留未完成项和远程 CI 风险。
2026-09-18 12:17:28 +08:00
suzmii 1bd153d7f2 合并主线对话状态与抠图背景色更新
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 6m9s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 5m28s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m33s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m23s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m39s
Project CI / Repository checks (pull_request) Successful in 4m50s
Project CI / Backend tests (pull_request) Successful in 8m38s
Project CI / Frontend tests (pull_request) Successful in 8m2s
Project CI / AI game creator shell web tests (pull_request) Successful in 8m11s
Project CI / Native shell tests (pull_request) Successful in 15m27s
合入 master a2e6f205d,保留远端功能分支已有合并历史。
融合生成选项的参考素材、目标栏目与抠图背景色字段。
保留主线统一线程状态与重试策略,完成前端及原生定向验证。
2026-09-18 10:31:55 +08:00
lhk229 f5a333b839 Merge branch 'master' into feat/agc-canvas-acceptance
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-18 01:04:04 +08:00
suzmii 20b1fd63de 合并最新主线抠图链路与开发监听修复
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m44s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m55s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m3s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m9s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m30s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m41s
Project CI / Native shell tests (pull_request) Successful in 15m5s
Project CI / Repository checks (pull_request) Successful in 16m54s
Project CI / Backend tests (pull_request) Successful in 19m49s
Project CI / Frontend tests (pull_request) Successful in 23m33s
Project CI / AI game creator shell web tests (pull_request) Successful in 11m6s
合入master的抠图账本链路、Vite监听边界及窗口测试更新

保留画布占位参考生成、资源展示与批量标签追加功能

合并态通过137项定向测试、AGC类型检查及Rust全目标编译
2026-09-17 22:18:23 +08:00
suzmii 105591bac5 支持画布与资源面板多选素材批量追加标签
复用标签编辑器并冻结整组选中素材,保留原标签与素材类型

原生批量校验后一次保存,覆盖权限、版本冲突与无变化边界

修复保存输入锁和切项目迟到回包,补齐混合选择入口原因

新增前后端回归并记录本地验证及待真实客户端验收事项
2026-09-17 21:51:59 +08:00
suzmii da44d66dc8 明确多选素材批量追加标签的行为合同
限定追加语义、冻结对象、原子保存与类型保留边界

记录已评审里程碑及前后端并行实现计划
2026-09-17 21:12:42 +08:00
suzmii 0a85c4f87e 合并主线资源菜单和引擎预览功能
合入PR410的常用操作收纳与卡片信息类型入口

保留本分支资源名称、文档图标、占位生成与批量移动能力

整合引擎结构摘要与当前图集测试契约
2026-09-17 21:06:15 +08:00
suzmii efce7b102f 对齐主线图集契约与窗口同步测试要求
补齐图集切分声明与工具目录字段边界断言

更新原生HTTP权限守卫及稳定空快照发布次数期望

修正末尾空行并记录CI排查证据,保留待远程验证状态
2026-09-17 21:01:26 +08:00
suzmii db5edc948c 合并最新主分支并修复画布静态检查问题
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m19s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Failing after 7m30s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m28s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Failing after 7m41s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m10s
Project CI / Native shell tests (pull_request) Failing after 4m28s
Project CI / Frontend tests (pull_request) Failing after 5m12s
Project CI / Backend tests (pull_request) Successful in 7m17s
Project CI / Repository checks (pull_request) Failing after 5m43s
Project CI / AI game creator shell web tests (pull_request) Failing after 4m34s
合入master的更新摘要、策划运行边界及图集契约调整

保留画布生成引用占位、资源卡展示与批量布局功能

修正导入顺序、未使用项和稳定回调依赖
2026-09-17 20:15:38 +08:00
suzmii 45c3780bf5 记录画布验收修复的验证证据与待验边界
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-17 19:46:49 +08:00
suzmii 6fcf42e4ac 修 appSurface 生成任务用例的异步排空:等阶段合并、等轮询退出
- 阶段文案与徽标改用 waitFor:本地队列的「排队中。」先于后端记录合并,同步断言在整套连跑时抢跑
- 用例收尾连等两个 poll 周期(2s)并断言账本读计数不再增长,确认这条轮询链在本用例内退出
- 留在线上的轮询醒来会打进下一个用例的 spy(宿主按调用时读 __TAURI__),故排空放在用例内,未动全局 harness
- 未改生产:后台任务跨面板保活保持原样
2026-09-17 19:44:48 +08:00
suzmii e0f9f811b7 生成浮层居中定位与画布安全带高度
补回 transform translateX(-50%):锚点给的是占位卡中心,少了它浮层整体右偏半个面板宽
高度只按画布安全带分配:装得下时挂在卡下面,装不下时与卡顶边对齐盖住占位,不再压成一百多像素
reveal 的面板高度按当前缩放从屏幕 px 折回世界坐标,占位与浮层并集参与可见性
浮层宽度并入可见性并集,靠边卡片不再把浮层切掉一半
新增真实矩形模拟用例(画布高 568)核对浮层高度与落点不越出安全带
2026-09-17 19:35:41 +08:00
suzmii 03b5c1c9f4 补画布生成入口宿主生命周期验收:占位提交、同 operation 重试与成功落点
- 新增测试文件 resourceCanvasGenerationHostLifecycle.test.tsx,渲染真实 ProjectDevelopmentView
- 覆盖音频入口:点工具出占位、提交后占位进入 submitted(后台仍在跑时的中间态)
- 覆盖失败后原请求重试:复用同一 operationId 与幂等键,不会变成新的付费生成
- 覆盖成功后落点:结果卡按占位位置落盘(手动接管),占位随之被撤掉
- 原生命令全部走假实现,不触发任何真实 Provider 调用
2026-09-17 19:20:31 +08:00
suzmii 58992330aa 生成浮层面板 chrome 与画布安全高度修复
浮层复用模态的面板类:边框圆角底色内边距与 header 排布不再缺失
高度上界按真实画布可用底边算(画布高减退底栏安全区与顶边),空间紧时收紧并内部滚动
提交动作行固定在浮层底部,避免长表单把提交按钮顶到底栏下面
新增浮层高度模型测试、结构 CSS 钉子与浮层 chrome 断言
新增宿主级整合用例:图片按正式归类落点并用占位最新位置、音频失败保留与幂等重试
2026-09-17 19:08:34 +08:00
suzmii 2c687b01a4 补齐生成结果分类与占位交互验收合同
明确原生目标分类、音频幂等及画布浮层可见性
2026-09-17 19:08:34 +08:00
suzmii c0e377f479 测试助手同时支持纯文本与引用输入区
appSurface 图集/图标规范入口的提示词仍是 textarea,helper 不能只走 Lexical 编辑器 API
两种字段分别用原生 change 与编辑器 update,读回同样两种口径
补 targetCategory 随任务保存与恢复的断言
2026-09-17 19:01:35 +08:00
suzmii 6001b87215 画布生成按入口栏目落盘目标分类
GUI 生成命令新增可选 targetCategory:generate_local_project_asset 与 start_local_project_asset_generation 经 options 传入完成登记
PlatformArtAssetGenerationOptions 增加 target_category,Agent 与 Direct 路径保持 None,仍按 kind 派生默认分类
新建条目与同 kind 重登记都按显式目标分类写入 manifest 既有 category 字段,不新增字段、不改数据结构
目标分类只接受 GameCreationAppAssetCategory 的六个合法值,version 与 all 等栏目伪值在提交前失败关闭
target_category 刻意不进 standalone 指纹与 durable 请求快照,分类只影响本地 manifest 落点,避免在途账本换槽重复 POST,同任务幂等恢复由调用方继续用同一栏目提交
补用例:kind=image 带 targetCategory=character 落到 character 而不是 unclassified、非法栏目值不产生生成请求、登记入口的显式分类与不传时的原 kind 派生行为
2026-09-17 18:58:53 +08:00
suzmii b08ab6ee66 生成占位可见性、落点与音频提交身份修复
打开生成浮层时把占位与浮层整块带进安全区(避开顶栏底栏),只做最小视口平移不改缩放
落点意图改为按草稿 ID 的 Map,避免并发完成互相覆盖;坐标在落点时刻取占位最新位置
落点 section 改用资源正式归类后的 category,修复图片落待归类、规范落文档时静默跳过
音频与背景音乐提交绑定占位与 operationId:在途时拒绝再次提交,失败保留输入并可同 operation 重试
宿主记录收起浮层时的草稿与音频提交身份,重开占位接着编辑而不是回到空表单
生成任务新增入口栏目 targetCategory 走原生可选入参,前端不据此伪分类
补可见性平移的定向测试
2026-09-17 18:50:29 +08:00
suzmii 7f80012d7f 清理占位重开状态里不再读取的动作 ID
生成浮层的草稿、错误与占位归属统一按 draftId 判等,动作 ID 已无读取方,删掉避免留下死状态
2026-09-17 18:50:29 +08:00
suzmii fbe95591d5 校准布局撤销作用域的行为说明
明确切模式后新操作会替换旧撤销栈但不会跨写布局
2026-09-17 18:31:16 +08:00
suzmii d222aad2ec 参考素材改为提交前预检并补参考链路用例
参考解析前增加纯本地预检:清单归属、受控路径与文件、媒体类型、可解码位图全部通过后才允许产生任何上传副作用,避免第一张参考已上传、第二张坏图才失败
明确拒绝 SVG 等矢量参考,媒体类型或文件扩展名任一命中即拒,本次不做矢量转换,坏图同样在本地失败关闭
asset.upload 门禁提前到预检阶段,显式拒绝该命令的项目连第一个上传凭证都不会签发
预检与实际上传共用同一份本地判据 helper,不新建平行流程,上传侧仍按同一判据重新读取与解码
补参考合同与恢复的账本用例:icon-spec 带用户参考、game-background 规范图加用户参考、图集超限、空白引用写入失败
补提交侧用例:拒绝路径、未登记、非图片、缺失文件、超限、SVG 与坏图,并断言零上传凭证与零生成 POST,反向验证同一张合格参考单独提交确实会签发凭证
补 asset.upload 拒绝用例,以及清单校验与 Direct 身份校验接受用户参考、拒绝用户参考顶替规范引用的覆盖
2026-09-17 18:28:19 +08:00
suzmii 13b28ebbc7 生成占位与卡下独立浮层,修复引用选择被焦点陷阱阻断
工具点击先在当前栏目创建临时占位卡,生成浮层挂在占位下沿;图片、音效、背景音乐入口一致
占位按项目与草稿 ID 归属宿主临时状态:可拖动、提交绑定任务 ID、成功结果落到占位最新位置
删除占位只隐藏展示,关闭浮层不冒充取消后台任务;失败保留占位与输入引用供重试
生成面板改为非模态浮层:ThemedModal 焦点陷阱曾阻断 portal 到 body 的引用选择器,点选不生效
引用选择器定位加顶边钳制,搜索收窄与键盘选择可用;不再越出视口
参考候选与陈旧引用校验排除 SVG(原生解码不支持),其余 image 类型不额外收窄
任务创建处对参考资产 ID 去重;补浮层真实组件整合、占位模型与拖动、参考模型回归测试
2026-09-17 18:22:39 +08:00
suzmii 30648e6b93 画布修复:重算意图各占队列槽、撤销不跨排序模式、整理判据与落盘同源
- 显式整理与「关系图首次就绪」各占一个队列槽,系统重算不再改写用户已按下的那笔整理
- 资源签名同步覆盖排队中的每一笔资源意图,先落盘的那笔不会把后一笔留成过期签名
- 撤销栈按「项目 + 排序模式」隔离:切模式后不会把另一份 sidecar 的坐标写过去
- 整理的「有没有变化」判据与落盘同源,目标栏目还有在途/排队手动落点时也算一次真实变化
- 删除本次改动退役且已无调用方的 resourceBookAllBandLocalPoint 与布局 dragX/dragY
- 补回归:排队中整理不被覆盖、切模式撤销不跨写、在途拖动后整理照常生效、分类变更后拖动按新栏目落盘
2026-09-17 18:18:32 +08:00
suzmii 9dd1052374 明确参考图片格式与上传前整组校验边界
排除不可解码SVG参考并约束上传权限与账号归属判据
2026-09-17 18:18:32 +08:00
suzmii aec9568c39 接通画布生成参考素材的原生链路
新增 referenceAssetIds 原生入参:start_local_project_asset_generation 与 generate_local_project_asset 经 prepare_local_project_asset_generation 统一收口当前项目 manifest 图片素材 id
PlatformArtAssetGenerationOptions 增加 reference_asset_ids,GUI、agent 桥、media 观察与 Direct 运行时构造点显式传空
参考解析固定规范图前置在前、用户参考按给出顺序随后,按远端资源 ID 去重,普通生成总量 5 张、有规范前置 4 张用户参考,图集拒绝额外参考而非静默丢弃
参考素材复用 manifest 到本地文件、图片解码、内容 hash、当前账号 binding 上传的既有通道,历史账号遗留远端 ID 不再复用
retained 阶段与清单校验改为与提交同一套请求合同:icon-spec、ui-prototype、game-background 允许正确引用,art-spritesheet 仍只接受唯一规范引用
修复 Part::file_name 的生命周期错误与 4 处测试构造点少传参考参数
2026-09-17 17:57:53 +08:00
suzmii 0a1f0e0b26 画布布局收口:整理只动当前栏目、多选拖动一笔提交与一次撤销
- 整理画布重排目标栏目内全部素材(含手动坐标与被筛选隐藏的卡),「所有资源」页覆盖全部栏目
- 栏外坐标逐值保留:不再把其他栏目的自动坐标丢给重算,避免被顺带规整成规范槽位
- 手动标记纳入历史快照与相等判定,撤销整理恢复原坐标与原手动标记
- 新增批量坐标写入入口:多选拖动与撤销都只排一笔 CAS、只压一条历史
- 多选拖动按同一位移整批跟随,取消 / 捕获丢失 / 失焦回滚,切项目丢弃进行中的位移
- 补测试:栏外自动坐标保留、all 页整理范围、批量撤销与取消边界
2026-09-17 17:47:11 +08:00
suzmii 5c31ae91ca 返修资源卡完整名悬停入口并收短文档卡注释
- 完整资源名改挂在覆盖整卡、真正命中指针的选中按钮 title 上,名称条自身不再挂悬停不到的 title

- 无障碍名保持原有 aria-label 不变,长名仍是 CSS 单行省略

- 断言改为校验选中按钮 title,并钉住名称条不挂 title

- 文档卡分支注释只写当前行为,去掉旧实现说明
2026-09-17 17:37:48 +08:00
suzmii 3ba6168c6a 同步资源卡名称与画布布局交互产品合同
统一卡面名称和文档图标展示要求

明确当前栏目整理与多选移动的撤销边界
2026-09-17 17:15:38 +08:00
suzmii 2628b83d4b 画布资源卡统一显示正式资源名并简化文档卡卡面
- 资源卡新增卡面名称条,所有类型卡统一显示正式资源 label,长名单行省略、完整名进 title

- 文档卡卡面改为居中图标,不再把正文前几行铺在卡面上,正文只在独立详情浮层读取

- 删除卡面正文摘要的取文本 helper 与行数/长度常量,移除对应 CSS 规则不留墓碑

- 名称条按媒体卡让出右下播放钮位置,替换血缘角标上移避免遮住名称

- 新增资源卡名称与文档卡卡面用例,并同步重命名链路与卡面正文的既有断言
2026-09-17 17:08:42 +08:00
suzmii a7b2b0e23b 画布图片生成支持真实参考图引用
新增参考图模型:候选为当前项目已登记图片,用户参考上限普通生成 5 张、带规范前置 4 张、图集为 0
生成浮层复用资源引用输入组件,提示词里的 @ 引用直接产出当前 manifest 资产 ID 作为参考
生成任务与队列新增 referenceAssetIds,派发时以 referenceAssetIds 交给原生侧解析账号绑定
图集入口不呈现参考选择器,超限在提交前给出原因并挡住提交,不做静默截断
补充参考模型、面板接线与队列派发的定向测试
2026-09-17 16:59:49 +08:00
suzmii ef982fdb78 拆分原生引用与前端生成并行实现边界
使用独立工作树处理原生引用,固定引用资产ID接缝
2026-09-17 16:59:48 +08:00
suzmii 91b65f94ca 确保工作树测试读取本地共享组件源码
补齐共享包测试别名以避免复用依赖指向其他工作树

记录工作树依赖复用的解析检查要求
2026-09-17 16:18:17 +08:00
suzmii 70513cf049 按规范评审固定参考上限并放行实现
普通生成最多五张参考,图集拒绝额外参考

复用现有引用快照并核验恢复校验,明确里程碑验收边界
2026-09-17 16:09:14 +08:00
suzmii 87aed0b765 补齐画布任务身份与引用持久化边界
明确参考图片入参、账号绑定与恢复校验

固定栏目整理撤销、多选范围与素材名称来源
2026-09-17 16:06:57 +08:00
suzmii 733a7015af 明确画布验收修复范围与行为合同
统一生成占位与参考、资源卡展示和布局交互的验收要求

补充单里程碑并行实现计划和独立评审门禁
2026-09-17 16:02:37 +08:00
216 changed files with 26737 additions and 1453 deletions
+22 -4
View File
@@ -221,8 +221,7 @@ jobs:
- name: Run AI game creator shell agent-run smoke
run: npm run check:native-shells:agc-rust-smoke
# AGC 壳依赖的共享 / 平台 crate 测试用的是 server-rs workspace 与两个无锁独立 crate
# 的 manifest,属另一套依赖图,因此单独一个 job 预热、单独跑。
# AGC 壳依赖的共享 / 平台和编辑器插件 crate 各自预热独立 manifest,再运行对应测试。
ai-game-creator-shell-rust-crates:
name: AI game creator shell Rust crates
runs-on: genarrative-ci
@@ -263,13 +262,15 @@ jobs:
# `cargo test --manifest-path` 单独跑这两个 crate。不在这里预热的话,这两条测试
# 会在测试阶段自己 `Updating crates.io index`crates.io 一抖动整条 job 就红
# (见 #327 / PR #316 run 1950)。
# 两个 crate 都没有提交 Cargo.lock,所以这里只能做不带锁标志的 fetch:
# Cocos 插件也使用独立且未提交的锁文件,一并预热。
# 这些 crate 都没有提交 Cargo.lock,所以这里只能做不带锁标志的 fetch:
# 加锁标志会因为缺少锁文件直接失败。生成的 Cargo.lock 落在两个 crate 目录内,
# 已被各自的 .gitignore 忽略,只留在容器里;随后的测试阶段因此能用锁定版本
# 解析,不再触碰 registry index。
for manifest_path in \
server-rs/crates/agent-runtime-core/Cargo.toml \
server-rs/crates/agent-runtime-orchestration/Cargo.toml; do
server-rs/crates/agent-runtime-orchestration/Cargo.toml \
plugins/agc-cocos-editor/native/cocos-editor-bridge/Cargo.toml; do
for attempt in $(seq 1 5); do
if cargo fetch \
--target x86_64-unknown-linux-gnu \
@@ -284,6 +285,23 @@ jobs:
done
done
- name: Prepare Unity plugin Rust dependencies
shell: bash
run: |
set -euo pipefail
for attempt in $(seq 1 5); do
if cargo fetch --locked \
--target x86_64-unknown-linux-gnu \
--manifest-path plugins/agc-unity-editor/native/unity-editor-bridge/Cargo.toml; then
break
fi
if [[ "${attempt}" -eq 5 ]]; then
echo 'Unity plugin Cargo dependency fetch failed after 5 attempts.' >&2
exit 1
fi
sleep $((attempt * 2))
done
- name: Run AI game creator shell shared crate gates
run: npm run check:native-shells:agc-rust-crates
+4
View File
@@ -48,6 +48,10 @@ temp*build*/
/apps/ai-game-creator-shell/src-tauri/resources/codex/mac-native/manifest.json
/apps/ai-game-creator-shell/src-tauri/resources/codex/mac-native/NOTICE.md
/plugins/agc-cocos-editor/native/payload/
/plugins/agc-unity-editor/dotnet/**/bin/
/plugins/agc-unity-editor/dotnet/**/obj/
/plugins/agc-unity-editor/dotnet/publish/
/plugins/agc-unity-editor/dotnet/native-build/
/apps/ai-game-creator-shell/logs/
/apps/ai-game-creator-shell/.llm-drafts/
/apps/ai-game-creator-shell/game-creator.config.local.json
@@ -105,6 +105,7 @@ export const agcReleasePathPatterns = [
'packages/',
'server-rs/crates/',
'plugins/agc-cocos-editor/',
'plugins/agc-unity-editor/',
'apps/desktop-shell/src-tauri/icons/',
'package.json',
'package-lock.json',
@@ -344,7 +344,11 @@ test('Windows remains the default and explicit Windows overrides macOS environme
context,
{
spawn: (_binary, command) => {
assert.ok(command.includes('--features=cocos-editor-execute'));
assert.ok(
command.includes(
'--features=cocos-editor-execute,unity-editor-execute',
),
);
assert.ok(command.includes('user-config.json'));
const configIndex = command.lastIndexOf('--config');
const config = JSON.parse(
@@ -19,6 +19,6 @@ export function withDefaultCargoFeatures(argv, features) {
export function defaultEditorFeatures(target) {
return target === 'win32' || target.includes('windows')
? ['cocos-editor-execute']
? ['cocos-editor-execute', 'unity-editor-execute']
: [];
}
@@ -9,7 +9,7 @@ test('Windows release includes the same editor feature as development', () => {
buildTauriBuildArguments([], 'x86_64-pc-windows-msvc', 'win32'),
[
'build',
'--features=cocos-editor-execute',
'--features=cocos-editor-execute,unity-editor-execute',
'--target',
'x86_64-pc-windows-msvc',
],
@@ -143,7 +143,6 @@ const allowedUncalledTauriCommands = [
'reload_agc_plugin',
'call_agc_plugin',
'read_agc_plugin_panel',
'set_agc_plugin_project_path',
'set_agc_plugin_enabled',
];
const sourceExtensions = new Set([
@@ -1,7 +1,10 @@
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { withDefaultCargoFeatures } from './cargo-features.mjs';
import {
defaultEditorFeatures,
withDefaultCargoFeatures,
} from './cargo-features.mjs';
import {
readAgcDevEndpoint,
resolveAgcDevEndpoint,
@@ -47,9 +50,8 @@ function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
];
}
// `agc_cocos_execute` 与 Cocos 编辑器适配器只在 `cocos-editor-execute` feature
// 注册。开发构建默认在 Windows 打开它,否则 Agent 的工具清单里根本没有该工具,
// 只能退化成改写脚本。可用 AGC_DEV_CARGO_FEATURES(逗号分隔)覆盖,传空串即关闭。
// 开发和发行构建使用同一平台编辑器 feature 集合。
// 可用 AGC_DEV_CARGO_FEATURES(逗号分隔)覆盖,传空串即关闭。
function readDevCargoFeatures(env = process.env) {
const override = env.AGC_DEV_CARGO_FEATURES;
if (override !== undefined) {
@@ -58,7 +60,7 @@ function readDevCargoFeatures(env = process.env) {
.map((value) => value.trim())
.filter(Boolean);
}
return process.platform === 'win32' ? ['cocos-editor-execute'] : [];
return defaultEditorFeatures(process.platform);
}
function withDevCargoFeatures(argv, features = readDevCargoFeatures()) {
+11
View File
@@ -1793,6 +1793,7 @@ dependencies = [
"ttf-parser",
"typed_floats",
"unicode-normalization",
"unity-editor-bridge",
"url",
"uuid",
"windows-sys 0.61.2",
@@ -6407,6 +6408,16 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unity-editor-bridge"
version = "0.1.0"
dependencies = [
"editor-adapter-api",
"serde",
"serde_json",
"windows-sys 0.61.2",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
@@ -12,6 +12,7 @@ template-library-fixtures = []
cocos-editor = ["cocos-editor-bridge/process-discovery"]
cocos-editor-execute = ["cocos-editor", "cocos-editor-bridge/windows-bootstrap"]
cocos-editor-injection = ["cocos-editor-execute", "cocos-editor-bridge/windows-injection"]
unity-editor-execute = []
[build-dependencies]
serde = { version = "1", features = ["derive"] }
@@ -27,6 +28,7 @@ nalgebra = { version = "0.35.0", features = ["serde-serialize"] }
agent-runtime-core = { path = "../../../server-rs/crates/agent-runtime-core" }
cocos-editor-bridge = { path = "../../../plugins/agc-cocos-editor/native/cocos-editor-bridge", default-features = false }
editor-adapter-api = { path = "../../../server-rs/crates/editor-adapter-api" }
unity-editor-bridge = { path = "../../../plugins/agc-unity-editor/native/unity-editor-bridge" }
base64 = "0.22"
axum = "0.8"
chromiumoxide = "0.9.1"
+109 -1
View File
@@ -196,6 +196,7 @@ fn main() {
);
let manifest_path = manifest_dir.join("prompts/runtime/manifest.json");
stage_bundled_codex_cli(&manifest_dir);
prepare_unity_editor_helper(&manifest_dir);
stage_plugin_workspace(&manifest_dir);
stage_cocos_editor_payload(&manifest_dir);
let compiled = runtime_prompt_bundle::compile_manifest(&manifest_path)
@@ -266,6 +267,107 @@ fn stage_cocos_editor_payload(manifest_dir: &std::path::Path) {
#[cfg(not(windows))]
fn stage_cocos_editor_payload(_manifest_dir: &std::path::Path) {}
/// Unity helper 是插件的随包运行文件。内容指纹避免每次 Cargo 检查都重新发布 .NET。
fn prepare_unity_editor_helper(manifest_dir: &std::path::Path) {
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_UNITY_EDITOR_EXECUTE");
let target = env::var("TARGET").expect("Cargo TARGET");
if env::var_os("CARGO_FEATURE_UNITY_EDITOR_EXECUTE").is_none()
|| target != "x86_64-pc-windows-msvc"
{
return;
}
let root = manifest_dir.join("../../../plugins/agc-unity-editor/dotnet");
let mut sources = Vec::new();
collect_unity_helper_sources(&root, &mut sources);
sources.sort();
let mut fingerprint = Sha256::new();
for source in &sources {
println!("cargo:rerun-if-changed={}", source.display());
fingerprint.update(
source
.strip_prefix(&root)
.expect("helper source")
.to_string_lossy()
.as_bytes(),
);
fingerprint.update([0]);
fingerprint.update(fs::read(source).expect("读取 Unity helper 源文件失败"));
}
let fingerprint = format!("{:x}", fingerprint.finalize());
let publish = root.join("publish/win-x64");
let executable = publish.join("Agc.Unity.Attach.exe");
let stamp = publish.join(".agc-source.sha256");
println!("cargo:rerun-if-changed={}", executable.display());
if unity_helper_publish_complete(&publish)
&& fs::read_to_string(&stamp).ok().as_deref() == Some(&fingerprint)
{
return;
}
assert!(
cfg!(windows),
"构建 Unity 插件 helper 需要 Windows .NET 10 与 x64 C++ 工具链"
);
let status = std::process::Command::new("powershell.exe")
.args([
"-NoProfile",
"-NonInteractive",
"-ExecutionPolicy",
"Bypass",
"-File",
])
.arg(root.join("build.ps1"))
.current_dir(&root)
.status()
.expect("无法启动 Unity helper 构建脚本");
assert!(
status.success() && unity_helper_publish_complete(&publish),
"Unity helper 构建失败或缺少运行文件/许可"
);
fs::write(stamp, fingerprint).expect("写入 Unity helper 构建指纹失败");
}
fn unity_helper_publish_complete(publish: &std::path::Path) -> bool {
[
"Agc.Unity.Attach.exe",
"NOTICE",
"THIRD-PARTY-NOTICES.txt",
"licenses/DotCraft-Apache-2.0.txt",
"licenses/Roslyn-MIT.txt",
"licenses/upstream.json",
"licenses/dotnet-LICENSE.TXT",
"licenses/dotnet-THIRD-PARTY-NOTICES.TXT",
"licenses/microsoft.codeanalysis.common-ThirdPartyNotices.rtf",
"licenses/microsoft.codeanalysis.csharp-ThirdPartyNotices.rtf",
]
.iter()
.all(|name| {
fs::symlink_metadata(publish.join(name)).is_ok_and(|metadata| {
metadata.is_file() && !metadata.file_type().is_symlink() && metadata.len() > 0
})
})
}
fn collect_unity_helper_sources(root: &std::path::Path, sources: &mut Vec<PathBuf>) {
for entry in fs::read_dir(root)
.expect("Unity helper 源码目录缺失")
.flatten()
{
let kind = entry.file_type().expect("读取 Unity helper 源文件类型失败");
assert!(!kind.is_symlink(), "Unity helper 源码不允许符号链接");
let name = entry.file_name();
if kind.is_dir() {
if !matches!(
name.to_str(),
Some("bin" | "obj" | "publish" | "native-build")
) {
collect_unity_helper_sources(&entry.path(), sources);
}
} else if kind.is_file() {
sources.push(entry.path());
}
}
}
/// 把 `plugins/` 工作区里的插件包随包映射到应用资源目录。
///
/// 只复制插件运行需要的清单、入口、面板和 native payload,不复制 native 源码、
@@ -313,9 +415,15 @@ fn stage_plugin_workspace(manifest_dir: &std::path::Path) {
for relative in [
std::path::PathBuf::from("src"),
std::path::PathBuf::from("panels"),
std::path::PathBuf::from("skills"),
std::path::PathBuf::from("native/payload"),
std::path::PathBuf::from("dotnet/publish/win-x64"),
] {
if relative == std::path::Path::new("native/payload") && !target.contains("windows") {
if (relative == std::path::Path::new("native/payload") && !target.contains("windows"))
|| (relative == std::path::Path::new("dotnet/publish/win-x64")
&& (target != "x86_64-pc-windows-msvc"
|| env::var_os("CARGO_FEATURE_UNITY_EDITOR_EXECUTE").is_none()))
{
continue;
}
copy_plugin_tree(&plugin_root.join(&relative), &destination.join(&relative));
@@ -14,6 +14,9 @@ Implement the user's actual game request in the current project as an npm-manage
3. Build with the project's npm script before previewing. The playable entry is the package directory's `dist/index.html`; never report an unbuilt bare-module page as playable. Import assets or configure public assets so all runtime media is included in dist; preview and exports cannot read outside it.
4. Build a complete playable loop: visible objective, responsive input, meaningful state changes, success or failure feedback, and a reliable restart path where the game needs one.
5. Fit the active game scene to desktop and mobile viewports without accidental page scrollbars. Reserve deliberate safe space for HUD elements instead of covering interactive content.
- **画布居中只能由一处负责。** 使用 `Phaser.Scale.FIT``autoCenter: Phaser.Scale.CENTER_BOTH` 时,canvas 的直接父容器应使用尺寸明确的普通块布局,不再对同一 canvas 叠加 Grid/Flex 居中、`place-items: center`、自动外边距或居中 transform。Phaser 自动计算的 margin 与 CSS 居中叠加会使竖屏画面向右偏移。
- 若决定由 CSS 居中,则显式使用 `autoCenter: Phaser.Scale.NO_CENTER`,由 CSS 独立完成定位;外围页面可以继续使用 Grid/Flex,限制只针对同一 canvas 的重复定位。
- 出现偏移先检查游戏自身的 CSS 与 Phaser scale 配置,不添加 AGC 预览容器固定偏移补偿。修改布局后重新构建 dist,在桌面、移动及窗口 resize 后检查 canvas 相对游戏父容器居中(误差不超过 1 CSS px)、画面完整且无意外滚动条;不能仅凭 build 成功宣称布局通过。
6. Invoke `taonier-art-assets` for every new game brief that needs visual assets. First reuse suitable registered Taonier art; when the brief's required visual elements are missing or unsuitable, call the reviewed `agc_tools` generation/edit workflow in the same task. After the tool returns, wire its relative paths into the game and verify the rendered result. A game with unused generated assets or placeholder emoji/CSS where requested art should appear is not complete. Load media defensively only for genuinely optional effects, and never relabel a local placeholder as platform art.
7. Let Phaser own the render loop and input dispatch. Avoid duplicate scenes, stale event listeners, and state that survives restart unintentionally.
8. After a meaningful game change, use the browser playtest Skill and fix issues shown by real evidence before reporting completion.
@@ -7,5 +7,7 @@
- Score, steps, health, timer, or other core state updates consistently.
- Restart restores all state and does not duplicate timers, animation loops, or event listeners.
- Desktop and mobile layouts keep the core scene visible without accidental document scrolling.
- 画布的缩放与居中由 Phaser 或 CSS 中的一方独立负责。`FIT + CENTER_BOTH` 不与同一 canvas 父容器的 Grid/Flex 居中、自动外边距或居中 transform 叠加;使用 CSS 居中时关闭 Phaser 自动居中(`NO_CENTER`)。
- 在构建后的实际页面检查桌面、移动和 resize:比较 canvas 与游戏父容器的中心,预期居中时水平/垂直误差不超过 1 CSS px,并检查画面没有溢出或意外滚动条。偏移先修游戏 CSS/scale 配置,不用修改 AGC 预览位置掩盖。
- HUD and overlays reserve space and do not cover essential interactive content.
- Requested Taonier art is visibly integrated into the core experience when available.
@@ -1,6 +1,6 @@
{
"schemaVersion": "agc-skill-pack.v1",
"version": "2026-08-26.24",
"version": "2026-08-26.25",
"skills": [
{
"name": "agc-game-production-workflow",
@@ -80,7 +80,7 @@
"agents/openai.yaml",
"references/game-quality-checklist.md"
],
"sha256": "05b5cfbf7a40fd303717491f5cea84ff339a73359c9678b283fd54d2b5c45efd"
"sha256": "e122d8f3a6d986b594b95c971754d68197bf7896912fa8267d44a7aa129a57ba"
},
{
"name": "agc-browser-playtest",
@@ -797,6 +797,16 @@ fn direct_thread_visible_item(
direct_thread_event_item(root, item)
}
/// AGC 预写的 canonical 用户条目 id`direct-codex:{clientTurnId}:user`。
///
/// 与 `direct_project_history::is_direct_project_codex_user_item` 的判据同一份口径(前缀 +
/// `:user` 后缀)。回合生命周期事件的 `userItemId` 只能来自这里或已落盘条目自身的 id;
/// clientTurnId 缺失时不猜身份,返回 `None` 让前端按"未知归属"处理。
fn direct_codex_user_item_id_for_client_turn_id(client_turn_id: &str) -> Option<String> {
let client_turn_id = client_turn_id.trim();
(!client_turn_id.is_empty()).then(|| format!("direct-codex:{client_turn_id}:user"))
}
fn direct_codex_command_is_game_verification(command: &str) -> bool {
let command = command.to_ascii_lowercase();
command.contains("game.static_smoke")
@@ -2927,7 +2937,7 @@ impl CodexAppServerConnection {
None => direct_project_local_message_item(
"user",
current_prompt,
Some(&format!("direct-codex:{client_turn_id}:user")),
direct_codex_user_item_id_for_client_turn_id(client_turn_id).as_deref(),
)
.map_err(platform_llm::LlmError::InvalidRequest)?,
};
@@ -3046,13 +3056,30 @@ impl CodexAppServerConnection {
};
turn_start_guard.armed = false;
let direct_thread_id = direct_thread_id_for_project(history_root);
// 回合边界的阶段时间:Turn 上游只有**秒**级 `startedAt` / `completedAt`,秒级截断
// 撑不起前端 0.1 秒粒度的展示,也可能让完成时刻落进该轮用户消息的同一秒、落在真实
// 发送时间之前,被判成无效边界后整轮新回合被吞掉。因此这里只在宿主处理对应阶段时取
// 毫秒钟(与条目侧"没有原生阶段时间就用宿主钟"同一口径),不再读上游秒字段。
let direct_turn_started_at_ms = direct_tool_call_now_ms();
// 本轮开口用户条目的 canonical id:只从已落盘的那条条目上读身份(`id`,工具条目才用
// `call_id`),不在事件侧重造一份。拿不到就留空,让前端按"归属不可证明"处理。
let direct_turn_user_item_id = direct_persisted_user_item
.as_ref()
.and_then(direct_thread_item_identity);
if self.inner.workspace_mode == CodexAppServerWorkspaceMode::DirectProject {
append_direct_thread_event(&direct_thread_id, DirectThreadEvent::turn_started());
append_direct_thread_event(
&direct_thread_id,
DirectThreadEvent::turn_started(direct_turn_started_at_ms)
.with_user_item_id(direct_turn_user_item_id.as_deref()),
);
if let Some(user_item) = direct_persisted_user_item.as_ref() {
if let Some(entry_item) = direct_thread_event_item(history_root, user_item) {
// 这里的条目时间可能是启动应答后的观测时间;前端按同一用户条目身份
// 保留更早的真实发送时间,不用此事件时间覆盖它。
let user_item_at = entry_item.at();
append_direct_thread_event(
&direct_thread_id,
DirectThreadEvent::item_completed(entry_item),
DirectThreadEvent::item_completed(entry_item, user_item_at),
);
}
}
@@ -3191,9 +3218,14 @@ impl CodexAppServerConnection {
.map_err(platform_llm::LlmError::InvalidRequest)?;
direct_project_history.complete_item(&item);
if let Some(entry_item) = entry_item {
// `rawResponseItem/completed` 不带阶段时间,宿主处理到这条
// 通知的钟就是该阶段唯一可证明的时间。
append_direct_thread_event(
&direct_thread_id,
DirectThreadEvent::item_completed(entry_item),
DirectThreadEvent::item_completed(
entry_item,
direct_tool_call_now_ms(),
),
);
}
}
@@ -3322,9 +3354,19 @@ impl CodexAppServerConnection {
if let Some(entry_item) =
direct_thread_visible_item(history_root, item)
{
// `item/started` 的通知层带 `startedAtMs`:这是工具真正
// 开始的阶段时间,优先于条目展示时间与宿主钟。
append_direct_thread_event(
&direct_thread_id,
DirectThreadEvent::item_started(entry_item),
DirectThreadEvent::item_started(
entry_item,
direct_thread_item_event_at_ms(
&params,
item,
false,
direct_tool_call_now_ms(),
),
),
);
}
}
@@ -3367,9 +3409,19 @@ impl CodexAppServerConnection {
&& matches!(status, "completed" | "interrupted" | "failed")
{
terminal_recorded = true;
// 终态时间:`durationMs` 与宿主记下的毫秒起点都可靠时才派生,
// 否则取宿主处理这条终态的钟;上游秒级 `completedAt` 一律不用。
append_direct_thread_event(
&direct_thread_id,
DirectThreadEvent::turn_completed(status.to_string()),
DirectThreadEvent::turn_completed(
status.to_string(),
direct_thread_turn_completed_at_ms(
turn,
Some(direct_turn_started_at_ms),
direct_tool_call_now_ms(),
),
)
.with_user_item_id(direct_turn_user_item_id.as_deref()),
);
}
match status {
@@ -3426,7 +3478,11 @@ impl CodexAppServerConnection {
"failed"
}
.to_string(),
),
// 这条兜底终态没有对应的 app-server 终态载荷,只能取宿主处理它的钟,
// 不能拿最后一次正文或工具更新时间当回合终点。
direct_tool_call_now_ms(),
)
.with_user_item_id(direct_turn_user_item_id.as_deref()),
);
}
let text = match collect_result {
@@ -3624,6 +3680,15 @@ enum DirectCodexTurnCancelTarget {
/// 这时显式释放这条守卫并把可读原因返回给界面。释放条件见
/// [`release_stale_direct_taonier_active_invocation`] 的注释;"正在跑的是另一轮"仍然
/// 保持原拒绝语义,什么都不释放。
///
/// 兜底终态带 `userItemId`:身份取 `release_stale_direct_taonier_active_invocation` 返回的
/// clientTurnId(客户端回合身份的唯一来源),与正常路径的开口条目 id 同一份 canonical 口径。
/// 拿不到 clientTurnId 就留空——这一轮不会再有原生终态,猜一个身份会让前端把边界盖到别人身上。
fn direct_stale_cancel_turn_completed_event(client_turn_id: &str) -> DirectThreadEvent {
DirectThreadEvent::turn_completed("aborted".to_string(), direct_tool_call_now_ms())
.with_user_item_id(direct_codex_user_item_id_for_client_turn_id(client_turn_id).as_deref())
}
pub(crate) fn cancel_direct_codex_turn_at(
root: &Path,
client_turn_id: Option<&str>,
@@ -3680,7 +3745,7 @@ pub(crate) fn cancel_direct_codex_turn_at(
// 兜底补一条,否则前端的"最新回合是否在跑"会永远停在运行中。
append_direct_thread_event(
&direct_thread_id_for_project(root),
DirectThreadEvent::turn_completed("aborted".to_string()),
direct_stale_cancel_turn_completed_event(&released),
);
Ok(DirectTurnCancelView {
outcome: DIRECT_TURN_CANCEL_OUTCOME_RELEASED.to_string(),
@@ -5006,6 +5071,109 @@ mod tests {
));
}
/// 阶段时间取自**通知层**字段,形状照抄 codex-cli 0.147 / 0.155 的 v2 协议 schema
/// `item/started` 带 `startedAtMs`、`item/completed` 带 `completedAtMs`(毫秒),
/// `turn/completed` 带 `turn.startedAt` / `turn.completedAt`(秒)与 `turn.durationMs`(毫秒)。
/// 分类函数把 params 原样交给事件级 `at` 的投影函数,所以字段位置必须在这里钉住;
/// 回合边界的秒字段按"不用"锁在这里,避免以后有人再把秒级截断当 0.1 秒精度。
#[test]
fn direct_lifecycle_stage_times_come_from_notification_params() {
let started = serde_json::json!({
"threadId": "thread-1",
"turnId": "turn-1",
"startedAtMs": 1_700_000_000_123u64,
"item": {"id": "call-1", "type": "commandExecution", "command": "ls"},
});
let completed = serde_json::json!({
"threadId": "thread-1",
"turnId": "turn-1",
"completedAtMs": 1_700_000_001_500u64,
"item": {"id": "call-1", "type": "commandExecution", "command": "ls"},
});
for (method, params, expected_at_ms) in [
("item/started", &started, 1_700_000_000_123u64),
("item/completed", &completed, 1_700_000_001_500u64),
] {
let Some(CodexTurnEvent::Item {
completed,
params: event_params,
}) = direct_codex_notification_event(method, params, None, None, "turn-1")
else {
panic!("{method} 必须分类成条目生命周期事件");
};
let item = event_params.get("item").expect("item payload");
assert_eq!(
direct_thread_item_event_at_ms(&event_params, item, completed, 9_999),
expected_at_ms,
"{method} 必须用通知层的阶段时间,而不是宿主钟"
);
}
let terminal = serde_json::json!({
"threadId": "thread-1",
"turn": {
"id": "turn-1",
"items": [],
"status": "completed",
"startedAt": 1_700_000_000i64,
"completedAt": 1_700_000_042i64,
},
});
let Some(CodexTurnEvent::Terminal(params)) =
direct_codex_notification_event("turn/completed", &terminal, None, None, "turn-1")
else {
panic!("turn/completed 必须分类成终态事件");
};
let turn = params.get("turn").unwrap_or(&params);
assert_eq!(
direct_thread_turn_completed_at_ms(turn, Some(1_700_000_000_500), 9_999),
9_999,
"上游只有秒级 completedAt:不采用,取宿主处理终态的毫秒钟"
);
let with_duration = serde_json::json!({
"id": "turn-1",
"items": [],
"status": "completed",
"startedAt": 1_700_000_000i64,
"completedAt": 1_700_000_042i64,
"durationMs": 42_500u64,
});
assert_eq!(
direct_thread_turn_completed_at_ms(&with_duration, Some(1_700_000_000_500), 9_999),
1_700_000_043_000,
"durationMs + 宿主高精度起点才派生结束"
);
}
/// 取消兜底终态也要带开口用户条目身份,且身份只有一个来源:release 返回的 clientTurnId
/// 走与正常路径同一份 canonical 口径;拿不到(空 / 空白)就留空,不猜。
#[test]
fn stale_cancel_terminal_event_keeps_opener_user_item_id_from_client_turn_id() {
let event = direct_stale_cancel_turn_completed_event("turn-0001");
assert_eq!(event.user_item_id(), Some("direct-codex:turn-0001:user"));
assert!(event.at().is_some(), "兜底终态仍要带宿主观测时间");
assert!(matches!(
event,
DirectThreadEvent::TurnCompleted { ref status, .. } if status == "aborted"
));
for missing in ["", " "] {
let event = direct_stale_cancel_turn_completed_event(missing);
assert_eq!(
event.user_item_id(),
None,
"拿不到 clientTurnId 时不得编造开口条目身份"
);
}
// canonical 口径与落盘侧同一份:`direct-codex:{clientTurnId}:user`。
assert_eq!(
direct_codex_user_item_id_for_client_turn_id(" turn-0001 ").as_deref(),
Some("direct-codex:turn-0001:user")
);
assert_eq!(direct_codex_user_item_id_for_client_turn_id(""), None);
}
fn test_llm() -> GameCreatorLlmConfig {
GameCreatorLlmConfig {
custom_enabled: false,
@@ -6740,12 +6908,33 @@ done
let consumed = crate::agent::consume_direct_thread(&bootstrap.subscription_id)
.expect("consume events");
// 回合起止必须与开口用户条目同源:前端在「只有锚点 + 历史、运行态为空」的回合里靠这个
// 身份把边界认领给同一条用户条目,缺了它就只能隐藏未知用时。
let lifecycle_user_item_ids = consumed
.events
.iter()
.filter(|event| {
matches!(
event,
DirectThreadEvent::TurnStarted { .. } | DirectThreadEvent::TurnCompleted { .. }
)
})
.map(DirectThreadEvent::user_item_id)
.collect::<Vec<_>>();
assert_eq!(
lifecycle_user_item_ids,
vec![
Some("direct-codex:turn-0001:user"),
Some("direct-codex:turn-0001:user"),
],
"turn.started / turn.completed 都要带本轮开口用户条目的 canonical itemId"
);
let mut user_items = Vec::new();
let mut assistant_items = Vec::new();
for event in &consumed.events {
let item = match event {
DirectThreadEvent::ItemStarted { item }
| DirectThreadEvent::ItemCompleted { item } => item,
DirectThreadEvent::ItemStarted { item, .. }
| DirectThreadEvent::ItemCompleted { item, .. } => item,
_ => continue,
};
match item {
File diff suppressed because one or more lines are too long
@@ -427,12 +427,16 @@ mod tests {
}
}
/// 事件级阶段时间只在重放稳定性用例里逐个指定;其余用例用一个固定值即可,
/// 它们断言的是队列 / 游标语义,不是时间本身。
const FIXED_AT_MS: u64 = 1_000;
fn item_started(item_id: &str) -> DirectThreadEvent {
DirectThreadEvent::item_started(message(item_id))
DirectThreadEvent::item_started(message(item_id), FIXED_AT_MS)
}
fn item_completed(item_id: &str) -> DirectThreadEvent {
DirectThreadEvent::item_completed(message(item_id))
DirectThreadEvent::item_completed(message(item_id), FIXED_AT_MS)
}
fn item_delta(item_id: &str) -> DirectThreadEvent {
@@ -450,7 +454,7 @@ mod tests {
#[test]
fn subscribers_have_independent_cursors_on_one_global_queue() {
let mut manager = DirectThreadManager::with_limits(100, 100_000);
manager.append("thread-1", DirectThreadEvent::turn_started());
manager.append("thread-1", DirectThreadEvent::turn_started(FIXED_AT_MS));
let first = manager.subscribe("thread-1");
let second = manager.subscribe("thread-1");
manager.append("thread-1", item_started("item-1"));
@@ -474,7 +478,7 @@ mod tests {
#[test]
fn bootstrap_contains_lifecycle_anchor_and_unfinished_events_only() {
let mut manager = DirectThreadManager::with_limits(100, 100_000);
manager.append("thread-1", DirectThreadEvent::turn_started());
manager.append("thread-1", DirectThreadEvent::turn_started(FIXED_AT_MS));
manager.append("thread-1", item_started("item-1"));
manager.append("thread-1", item_delta("item-1"));
manager.append("thread-1", item_completed("item-1"));
@@ -484,7 +488,7 @@ mod tests {
assert!(matches!(
bootstrap.events.as_slice(),
[
DirectThreadEvent::TurnStarted {},
DirectThreadEvent::TurnStarted { .. },
DirectThreadEvent::ItemStarted { item, .. },
] if item.item_id() == "item-2"
));
@@ -589,15 +593,118 @@ mod tests {
let mut manager = DirectThreadManager::with_limits(100, 100_000);
manager.append(
"thread-1",
DirectThreadEvent::turn_completed("completed".to_string()),
DirectThreadEvent::turn_completed("completed".to_string(), FIXED_AT_MS),
);
let bootstrap = manager.subscribe("thread-1");
assert!(matches!(
bootstrap.events.as_slice(),
[DirectThreadEvent::TurnCompleted { status }] if status == "completed"
[DirectThreadEvent::TurnCompleted { status, at, .. }]
if status == "completed" && *at == Some(FIXED_AT_MS)
));
}
/// 阶段时间必须随事件一起进队列:bootstrap 与重复订阅都拿到**原值**,
/// 重放不得重新取钟(否则每次重连都会把已固定的起止时间改掉)。
#[test]
fn replayed_events_keep_their_original_stage_time() {
let mut manager = DirectThreadManager::with_limits(100, 100_000);
manager.append("thread-1", DirectThreadEvent::turn_started(1_000));
manager.append(
"thread-1",
DirectThreadEvent::item_started(message("item-1"), 2_000),
);
let first = manager.subscribe("thread-1");
assert_eq!(
first
.events
.iter()
.map(DirectThreadEvent::at)
.collect::<Vec<_>>(),
vec![Some(1_000), Some(2_000)]
);
// 第二个订阅看到的是同一份事件,时间不因"又取了一次当前时间"而漂移。
let second = manager.subscribe("thread-1");
assert_eq!(second.events, first.events);
manager.append(
"thread-1",
DirectThreadEvent::item_completed(message("item-1"), 3_000),
);
let completion = manager
.consume(&first.subscription_id)
.expect("consume completion")
.events;
assert_eq!(
completion
.iter()
.map(DirectThreadEvent::at)
.collect::<Vec<_>>(),
vec![Some(3_000)]
);
// 重复消费不产生新事件,也不改写已下发过的时间。
assert!(manager
.consume(&first.subscription_id)
.expect("empty consume")
.events
.is_empty());
assert_eq!(
completion
.iter()
.map(DirectThreadEvent::at)
.collect::<Vec<_>>(),
vec![Some(3_000)]
);
}
/// 生命周期锚点重放时必须带上开口用户条目身份:前端在「只有锚点 + 历史切片、运行态一直空」
/// 的回合里也要能把边界认领给同一条用户条目,而不是按时间戳猜。
#[test]
fn bootstrap_replays_opener_user_item_id() {
let mut manager = DirectThreadManager::with_limits(100, 100_000);
manager.append(
"thread-1",
DirectThreadEvent::turn_started(1_000)
.with_user_item_id(Some("direct-codex:turn-1:user")),
);
let bootstrap = manager.subscribe("thread-1");
assert_eq!(
bootstrap
.events
.iter()
.map(DirectThreadEvent::user_item_id)
.collect::<Vec<_>>(),
vec![Some("direct-codex:turn-1:user")]
);
assert_eq!(bootstrap.events[0].at(), Some(1_000));
// 锚点是独立保存的副本:队列里那条事件被回收之后,新订阅仍拿到同一个身份。
manager
.consume(&bootstrap.subscription_id)
.expect("consume anchor");
manager.append(
"thread-1",
DirectThreadEvent::item_completed(message("item-1"), 2_000),
);
manager.append(
"thread-1",
DirectThreadEvent::turn_completed("completed".to_string(), 3_000)
.with_user_item_id(Some("direct-codex:turn-1:user")),
);
let second = manager.subscribe("thread-1");
assert_eq!(
second
.events
.iter()
.map(DirectThreadEvent::user_item_id)
.collect::<Vec<_>>(),
vec![Some("direct-codex:turn-1:user")],
"起止同源:终态锚点也带同一个开口用户条目身份"
);
assert_eq!(second.events[0].at(), Some(3_000));
}
#[test]
fn queue_cleanup_only_removes_a_cleanable_prefix() {
let mut manager = DirectThreadManager::with_limits(100, 100_000);
File diff suppressed because it is too large Load Diff
@@ -2391,6 +2391,8 @@ async fn bridge_generate_image(state: &DirectToolBridgeState, arguments: &Value)
slice_mode,
grid_x,
grid_y,
reference_asset_ids: Vec::new(),
target_category: None,
screen_color,
};
let _generation_guard = state.image_generation_gate.lock().await;
@@ -2590,6 +2592,51 @@ async fn bridge_web_search_at(root: &Path, arguments: &Value, search_url: &str)
}
}
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
async fn bridge_unity_execute(state: &DirectToolBridgeState, arguments: &Value) -> Value {
let prepared = (|| {
if !crate::builtin_plugins::unity_editor_agent_tool_available_for_project(&state.root) {
return Err("当前项目不是 Unity 项目或 Unity 插件不可用".to_string());
}
enforce_project_permission_policy(&state.root, "unity.editor.execute")?;
bridge_reject_unknown_fields(arguments, &["code"])?;
let code = arguments
.get("code")
.and_then(Value::as_str)
.ok_or_else(|| "code 必须是 C# 代码".to_string())?;
if code.trim().is_empty() || code.len() > 131072 || code.contains('\0') {
return Err("code 不能为空、包含 NUL 或超过 128 KiB".to_string());
}
Ok(code.to_string())
})();
let code = match prepared {
Ok(code) => code,
Err(error) => {
return bridge_tool_result(
redact_agent_runtime_error(&state.root, &error, 480),
Vec::new(),
true,
)
}
};
let root = state.root.clone();
let result = tokio::task::spawn_blocking(move || {
if !crate::builtin_plugins::unity_editor_agent_tool_available_for_project(&root) {
return Err("当前 Unity 插件不可用".to_string());
}
crate::editor_adapters::execute_unity_editor_code(&root, &code)
})
.await;
match result {
Ok(Ok(response)) => {
let failed = response["ok"] != true || response["status"] != "completed";
bridge_tool_result(redact_agent_runtime_error(&state.root, &response.to_string(), 32_000), Vec::new(), failed)
}
Ok(Err(error)) => bridge_tool_result(redact_agent_runtime_error(&state.root, &error, 480), Vec::new(), true),
Err(_) => bridge_tool_result(json!({"ok":false,"status":"needs-reconciliation","retryAllowed":false,"error":"Unity 执行任务异常,请人工核对结果"}).to_string(), Vec::new(), true),
}
}
#[cfg(all(windows, feature = "cocos-editor-execute"))]
async fn bridge_cocos_execute(state: &DirectToolBridgeState, arguments: &Value) -> Value {
bridge_cocos_call(state, arguments, None).await
@@ -2601,6 +2648,9 @@ async fn bridge_cocos_call(
arguments: &Value,
operation: Option<&str>,
) -> Value {
if !crate::builtin_plugins::is_enabled(crate::builtin_plugins::AGC_COCOS_EDITOR_PLUGIN_ID) {
return bridge_tool_result("Cocos 编辑器插件已禁用".to_string(), Vec::new(), true);
}
if !crate::builtin_plugins::cocos_editor_agent_tool_available_for_project(&state.root) {
return bridge_tool_result(
"当前项目不是 Cocos Creator 项目或 Cocos 插件不可用,agc_cocos_execute 不可用"
@@ -2787,6 +2837,8 @@ async fn handle_direct_tool_bridge(
"agc_list_project_files" => bridge_list_project_files(&state.root, &request.arguments),
#[cfg(all(windows, feature = "cocos-editor-execute"))]
"agc_cocos_execute" => bridge_cocos_execute(&state, &request.arguments).await,
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
"agc_unity_execute" => bridge_unity_execute(&state, &request.arguments).await,
#[cfg(all(windows, feature = "cocos-editor-execute"))]
operation if cocos_editor_bridge::is_cocos_operation(operation) => {
bridge_cocos_call(&state, &request.arguments, Some(operation)).await
@@ -75,7 +75,14 @@ pub(crate) fn run_direct_tools_mcp_if_requested(args: &[String]) -> Option<i32>
async fn direct_tools_mcp_specs() -> Value {
let mut cocos_editor_available = false;
if cfg!(all(windows, feature = "cocos-editor-execute")) {
let mut unity_editor_available = false;
if cfg!(all(windows, feature = "cocos-editor-execute"))
|| cfg!(all(
windows,
target_arch = "x86_64",
feature = "unity-editor-execute"
))
{
// 每次 tools/list 询问绑定的宿主;失败时不广告可选插件工具。
if let Ok(result) = tokio::time::timeout(
std::time::Duration::from_secs(5),
@@ -96,10 +103,22 @@ async fn direct_tools_mcp_specs() -> Value {
.iter()
.any(|tool| tool == crate::builtin_plugins::AGC_COCOS_EDITOR_TOOL_NAME)
});
unity_editor_available = availability
.as_ref()
.and_then(|v| v["tools"].as_array())
.is_some_and(|tools| {
tools
.iter()
.any(|tool| tool == crate::builtin_plugins::AGC_UNITY_EDITOR_TOOL_NAME)
});
}
}
}
direct_tools_mcp_specs_for(controlled_web_search_enabled(), cocos_editor_available)
direct_tools_mcp_specs_for_plugins(
controlled_web_search_enabled(),
cocos_editor_available,
unity_editor_available,
)
}
/// 工具 kind(wire 值)对应的提示词上限。
@@ -128,7 +147,16 @@ fn resource_tool_prompt_schema_max_chars() -> usize {
.unwrap_or(0)
}
fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_available: bool) -> Value {
#[cfg(test)]
fn direct_tools_mcp_specs_for(controlled_web_search: bool, cocos_editor_available: bool) -> Value {
direct_tools_mcp_specs_for_plugins(controlled_web_search, cocos_editor_available, false)
}
fn direct_tools_mcp_specs_for_plugins(
controlled_web_search: bool,
_cocos_editor_available: bool,
_unity_editor_available: bool,
) -> Value {
let tools = vec![
json!({
"name": "client.session.info",
@@ -568,6 +596,14 @@ fn direct_tools_mcp_specs_for(controlled_web_search: bool, _cocos_editor_availab
.cloned(),
);
}
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
if _unity_editor_available {
tools.push(json!({
"name": "agc_unity_execute",
"description": "在当前项目已打开的 Windows x64 Unity Mono Editor 执行 C#,可使用 return 返回值。仅提交 code;宿主绑定项目及进程。needs-reconciliation 或超时后禁止自动重发。",
"inputSchema": {"type":"object", "properties":{"code":{"type":"string", "minLength":1, "maxLength":131072}}, "required":["code"], "additionalProperties":false}
}));
}
if controlled_web_search {
tools.push(json!({
"name": "agc_web_search",
@@ -655,6 +691,24 @@ async fn call_agc_cocos_execute(arguments: &Value) -> Value {
call_client_tool_bridge("agc_cocos_execute", arguments).await
}
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
async fn call_agc_unity_execute(arguments: &Value) -> Value {
let validated = validate_tool_object_fields(arguments, &["code"]).and_then(|()| {
let code = arguments
.get("code")
.and_then(Value::as_str)
.ok_or_else(|| "code 必须是 C# 代码".to_string())?;
if code.trim().is_empty() || code.len() > 131072 || code.contains('\0') {
return Err("code 不能为空、包含 NUL 或超过 128 KiB".to_string());
}
Ok(())
});
if let Err(error) = validated {
return mcp_tool_result(error, Vec::new(), true);
}
call_client_tool_bridge("agc_unity_execute", arguments).await
}
fn mcp_success(id: Value, result: Value) -> Value {
json!({ "jsonrpc": "2.0", "id": id, "result": result })
}
@@ -1757,6 +1811,8 @@ async fn handle_direct_tools_mcp_request(root: &Path, request: Value) -> Option<
"agc_write_file" => call_agc_write_file(&arguments).await,
#[cfg(all(windows, feature = "cocos-editor-execute"))]
"agc_cocos_execute" => call_agc_cocos_execute(&arguments).await,
#[cfg(all(windows, target_arch = "x86_64", feature = "unity-editor-execute"))]
"agc_unity_execute" => call_agc_unity_execute(&arguments).await,
#[cfg(all(windows, feature = "cocos-editor-execute"))]
operation if cocos_editor_bridge::is_cocos_operation(operation) => {
call_client_tool_bridge(operation, &arguments).await
@@ -67,10 +67,11 @@ pub(crate) use canvas_generation::{
generate_platform_art_asset_with_options_at,
generate_platform_art_asset_with_required_slices_at, maybe_generate_platform_art_asset_step,
needs_platform_art_asset_generation, normalize_platform_art_asset_generation_kind,
normalize_platform_art_reference_asset_ids, normalize_platform_art_target_category,
platform_art_asset_art_spec, platform_art_asset_output_extension_matches,
prepare_platform_art_asset_output_path, project_canvas_asset_media_types,
role_has_canvas_assets, suggested_canvas_tool_call, PlatformArtAssetGenerationOptions,
PLATFORM_ART_ASSET_GENERATION_KINDS,
platform_art_runtime_references_match_request_contract, prepare_platform_art_asset_output_path,
project_canvas_asset_media_types, role_has_canvas_assets, suggested_canvas_tool_call,
PlatformArtAssetGenerationOptions, PLATFORM_ART_ASSET_GENERATION_KINDS,
};
#[allow(unused_imports)]
pub(crate) use draft_validation::{
File diff suppressed because it is too large Load Diff
@@ -1620,7 +1620,7 @@ pub(crate) fn agent_runtime_tool_action_input_summary(
.unwrap_or(160)
),
"command.run_limited" => format!("commandId={}", text(&["commandId", "command_id", "id"])),
"cocos.editor.execute" => format!(
"cocos.editor.execute" | "unity.editor.execute" => format!(
"codeChars={} · codeSha256={:x}",
chars(&["code"]),
Sha256::digest(
@@ -383,6 +383,16 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_
true,
|| observe_agent_runtime_cocos_editor_execute(root, action, pending_action),
),
"unity.editor.execute" => observe_agent_runtime_project_snapshot_with_lock(
root,
agent_id,
run_id,
action,
&action_fingerprint,
pending_action,
true,
|| observe_agent_runtime_unity_editor_execute(root, action, pending_action),
),
"preview.validate" => {
observe_agent_runtime_preview_validate(
root,
@@ -97,6 +97,7 @@ pub(in crate::agent) fn game_creator_agent_runtime_tool_command_id(
"command.run_limited" => Some("command.run_limited"),
#[cfg(feature = "cocos-editor-execute")]
"cocos.editor.execute" => Some("cocos.editor.execute"),
"unity.editor.execute" => Some("unity.editor.execute"),
"preview.start" => Some("preview.start"),
"preview.validate" => Some("preview.validate"),
"image.inspect" => Some("image.inspect"),

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